aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBadlop <badlop@process-one.net>2013-03-14 10:33:02 +0100
committerBadlop <badlop@process-one.net>2013-03-14 10:33:02 +0100
commit9deb294328bb3f9eb6bd2c0e7cd500732e9b5830 (patch)
tree7e1066c130250627ee0abab44a135f583a28d07f /src
parentlist_to_integer/2 only works in OTP R14 and newer (diff)
Accumulated patch to binarize and indent code
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.in4
-rw-r--r--src/acl.erl367
-rw-r--r--src/adhoc.erl177
-rw-r--r--src/adhoc.hrl37
-rw-r--r--src/cache_tab.erl10
-rw-r--r--src/configure.erl2
-rw-r--r--src/cyrsasl.erl200
-rw-r--r--src/cyrsasl_anonymous.erl20
-rw-r--r--src/cyrsasl_digest.erl298
-rw-r--r--src/cyrsasl_plain.erl68
-rw-r--r--src/cyrsasl_scram.erl297
-rw-r--r--src/ejabberd.hrl51
-rw-r--r--src/ejabberd_admin.erl18
-rw-r--r--src/ejabberd_app.erl61
-rw-r--r--src/ejabberd_auth.erl523
-rw-r--r--src/ejabberd_auth_anonymous.erl208
-rw-r--r--src/ejabberd_auth_external.erl290
-rw-r--r--src/ejabberd_auth_internal.erl547
-rw-r--r--src/ejabberd_auth_ldap.erl550
-rw-r--r--src/ejabberd_auth_odbc.erl323
-rw-r--r--src/ejabberd_auth_pam.erl110
-rw-r--r--src/ejabberd_c2s.erl3102
-rw-r--r--src/ejabberd_c2s_config.erl42
-rw-r--r--src/ejabberd_captcha.erl913
-rw-r--r--src/ejabberd_check.erl4
-rw-r--r--src/ejabberd_commands.erl22
-rw-r--r--src/ejabberd_commands.hrl31
-rw-r--r--src/ejabberd_config.erl235
-rw-r--r--src/ejabberd_config.hrl20
-rw-r--r--src/ejabberd_ctl.erl86
-rw-r--r--src/ejabberd_ctl.hrl9
-rw-r--r--src/ejabberd_frontend_socket.erl111
-rw-r--r--src/ejabberd_hooks.erl33
-rw-r--r--src/ejabberd_listener.erl80
-rw-r--r--src/ejabberd_local.erl287
-rw-r--r--src/ejabberd_node_groups.erl12
-rw-r--r--src/ejabberd_piefxis.erl984
-rw-r--r--src/ejabberd_rdbms.erl59
-rw-r--r--src/ejabberd_receiver.erl241
-rw-r--r--src/ejabberd_regexp.erl78
-rw-r--r--src/ejabberd_router.erl447
-rw-r--r--src/ejabberd_s2s.erl545
-rw-r--r--src/ejabberd_s2s_in.erl1166
-rw-r--r--src/ejabberd_s2s_out.erl1556
-rw-r--r--src/ejabberd_service.erl390
-rw-r--r--src/ejabberd_sm.erl694
-rw-r--r--src/ejabberd_socket.erl208
-rw-r--r--src/ejabberd_sup.erl8
-rw-r--r--src/ejabberd_system_monitor.erl347
-rw-r--r--src/ejabberd_tmp_sup.erl9
-rw-r--r--src/ejabberd_update.erl7
-rw-r--r--src/ejabberd_zlib/Makefile.in2
-rw-r--r--src/ejabberd_zlib/ejabberd_zlib.erl207
-rw-r--r--src/ejabberdctl.template1
-rw-r--r--src/ejd2odbc.erl618
-rw-r--r--src/eldap/Makefile.in6
-rw-r--r--src/eldap/eldap.erl1231
-rw-r--r--src/eldap/eldap.hrl49
-rw-r--r--src/eldap/eldap_filter.erl55
-rw-r--r--src/eldap/eldap_filter_yecc.yrl2
-rw-r--r--src/eldap/eldap_pool.erl72
-rw-r--r--src/eldap/eldap_utils.erl264
-rw-r--r--src/expat_erl.c16
-rw-r--r--src/extauth.erl162
-rw-r--r--src/gen_iq_handler.erl146
-rw-r--r--src/gen_mod.erl317
-rw-r--r--src/idna.erl228
-rw-r--r--src/jd2ejd.erl236
-rw-r--r--src/jlib.erl1156
-rw-r--r--src/jlib.hrl783
-rw-r--r--src/mod_adhoc.erl351
-rw-r--r--src/mod_announce.erl666
-rw-r--r--src/mod_blocking.erl671
-rw-r--r--src/mod_caps.erl764
-rw-r--r--src/mod_configure.erl3481
-rw-r--r--src/mod_configure2.erl269
-rw-r--r--src/mod_disco.erl683
-rw-r--r--src/mod_echo.erl103
-rw-r--r--src/mod_ip_blacklist.erl89
-rw-r--r--src/mod_irc/Makefile.in2
-rw-r--r--src/mod_irc/iconv.erl46
-rw-r--r--src/mod_irc/mod_irc.erl1860
-rw-r--r--src/mod_irc/mod_irc_connection.erl2274
-rw-r--r--src/mod_last.erl359
-rw-r--r--src/mod_muc/Makefile.in2
-rw-r--r--src/mod_muc/mod_muc.erl1160
-rw-r--r--src/mod_muc/mod_muc_log.erl1581
-rw-r--r--src/mod_muc/mod_muc_room.erl6587
-rw-r--r--src/mod_muc/mod_muc_room.hrl144
-rw-r--r--src/mod_offline.erl1118
-rw-r--r--src/mod_ping.erl193
-rw-r--r--src/mod_pres_counter.erl149
-rw-r--r--src/mod_privacy.erl1583
-rw-r--r--src/mod_privacy.hrl35
-rw-r--r--src/mod_private.erl363
-rw-r--r--src/mod_proxy65/Makefile.in2
-rw-r--r--src/mod_proxy65/mod_proxy65.erl49
-rw-r--r--src/mod_proxy65/mod_proxy65.hrl25
-rw-r--r--src/mod_proxy65/mod_proxy65_lib.erl64
-rw-r--r--src/mod_proxy65/mod_proxy65_service.erl319
-rw-r--r--src/mod_proxy65/mod_proxy65_sm.erl171
-rw-r--r--src/mod_proxy65/mod_proxy65_stream.erl287
-rw-r--r--src/mod_pubsub/Makefile.in2
-rw-r--r--src/mod_pubsub/gen_pubsub_node.erl283
-rw-r--r--src/mod_pubsub/gen_pubsub_nodetree.erl124
-rw-r--r--src/mod_pubsub/mod_pubsub.erl6199
-rw-r--r--src/mod_pubsub/mod_pubsub_odbc.erl5711
-rw-r--r--src/mod_pubsub/node_buddy.erl151
-rw-r--r--src/mod_pubsub/node_club.erl150
-rw-r--r--src/mod_pubsub/node_dag.erl131
-rw-r--r--src/mod_pubsub/node_dispatch.erl170
-rw-r--r--src/mod_pubsub/node_flat.erl145
-rw-r--r--src/mod_pubsub/node_flat_odbc.erl152
-rw-r--r--src/mod_pubsub/node_hometree.erl1269
-rw-r--r--src/mod_pubsub/node_hometree_odbc.erl1982
-rw-r--r--src/mod_pubsub/node_mb.erl158
-rw-r--r--src/mod_pubsub/node_pep.erl591
-rw-r--r--src/mod_pubsub/node_pep_odbc.erl460
-rw-r--r--src/mod_pubsub/node_private.erl154
-rw-r--r--src/mod_pubsub/node_public.erl150
-rw-r--r--src/mod_pubsub/nodetree_dag.erl287
-rw-r--r--src/mod_pubsub/nodetree_tree.erl263
-rw-r--r--src/mod_pubsub/nodetree_tree_odbc.erl533
-rw-r--r--src/mod_pubsub/nodetree_virtual.erl98
-rw-r--r--src/mod_pubsub/pubsub.hrl112
-rw-r--r--src/mod_pubsub/pubsub_db_odbc.erl181
-rw-r--r--src/mod_pubsub/pubsub_index.erl38
-rw-r--r--src/mod_pubsub/pubsub_odbc.patch1571
-rw-r--r--src/mod_pubsub/pubsub_subscription.erl509
-rw-r--r--src/mod_pubsub/pubsub_subscription_odbc.erl440
-rw-r--r--src/mod_register.erl1045
-rw-r--r--src/mod_roster.erl2171
-rw-r--r--src/mod_roster.hrl31
-rw-r--r--src/mod_service_log.erl68
-rw-r--r--src/mod_shared_roster.erl1836
-rw-r--r--src/mod_shared_roster_ldap.erl907
-rw-r--r--src/mod_sic.erl71
-rw-r--r--src/mod_stats.erl364
-rw-r--r--src/mod_time.erl106
-rw-r--r--src/mod_vcard.erl1628
-rw-r--r--src/mod_vcard_ldap.erl1317
-rw-r--r--src/mod_vcard_xupdate.erl187
-rw-r--r--src/mod_version.erl90
-rw-r--r--src/odbc/Makefile.in2
-rw-r--r--src/odbc/ejabberd_odbc.erl613
-rw-r--r--src/odbc/ejabberd_odbc_sup.erl90
-rw-r--r--src/odbc/mysql.sql9
-rw-r--r--src/odbc/odbc_queries.erl1221
-rw-r--r--src/odbc/pg.sql9
-rw-r--r--src/p1_fsm.erl50
-rw-r--r--src/p1_mnesia.erl29
-rw-r--r--src/pam/Makefile.in2
-rw-r--r--src/pam/epam.erl98
-rw-r--r--src/randoms.erl21
-rw-r--r--src/scram.erl73
-rw-r--r--src/sha.erl88
-rw-r--r--src/shaper.erl63
-rw-r--r--src/str.erl287
-rw-r--r--src/stringprep/Makefile.in2
-rw-r--r--src/stringprep/stringprep.erl68
-rw-r--r--src/stringprep/stringprep_drv.c50
-rw-r--r--src/stringprep/stringprep_sup.erl10
-rw-r--r--src/stun/Makefile.in2
-rw-r--r--src/stun/ejabberd_stun.erl224
-rw-r--r--src/stun/stun.hrl88
-rw-r--r--src/stun/stun_codec.erl282
-rw-r--r--src/tls/Makefile.in2
-rw-r--r--src/tls/tls.erl495
-rw-r--r--src/translate.erl219
-rw-r--r--src/treap.erl201
-rw-r--r--src/web/Makefile.in2
-rw-r--r--src/web/ejabberd_http.erl1291
-rw-r--r--src/web/ejabberd_http.hrl28
-rw-r--r--src/web/ejabberd_http_bind.erl1559
-rw-r--r--src/web/ejabberd_http_poll.erl386
-rw-r--r--src/web/ejabberd_web.erl85
-rw-r--r--src/web/ejabberd_web_admin.erl4660
-rw-r--r--src/web/ejabberd_web_admin.hrl103
-rw-r--r--src/web/http_bind.hrl33
-rw-r--r--src/web/mod_http_bind.erl97
-rw-r--r--src/web/mod_http_fileserver.erl141
-rw-r--r--src/web/mod_register_web.erl720
-rw-r--r--src/xml.c11
-rw-r--r--src/xml.erl444
-rw-r--r--src/xml_stream.erl210
185 files changed, 49898 insertions, 42893 deletions
diff --git a/src/Makefile.in b/src/Makefile.in
index 42af5b2f2..a70e25c10 100644
--- a/src/Makefile.in
+++ b/src/Makefile.in
@@ -35,7 +35,7 @@ ERLANG_CFLAGS += @ERLANG_SSLVER@
# make debug=true to compile Erlang module with debug informations.
ifdef debug
- EFLAGS+=+debug_info +export_all
+ EFLAGS+=+debug_info
endif
DEBUGTOOLS = p1_prof.erl
@@ -79,7 +79,7 @@ exec_prefix = @exec_prefix@
SUBDIRS = @mod_irc@ @mod_pubsub@ @mod_muc@ @mod_proxy65@ @eldap@ @pam@ @web@ stringprep stun @tls@ @odbc@ @ejabberd_zlib@
ERLSHLIBS += expat_erl.so
-ERLBEHAVS = cyrsasl.erl gen_mod.erl p1_fsm.erl
+ERLBEHAVS = cyrsasl.erl gen_mod.erl p1_fsm.erl ejabberd_auth.erl
SOURCES_ALL = $(wildcard *.erl)
SOURCES_MISC = $(ERLBEHAVS) $(DEBUGTOOLS)
SOURCES += $(filter-out $(SOURCES_MISC),$(SOURCES_ALL))
diff --git a/src/acl.erl b/src/acl.erl
index b5f64141b..77c55e79d 100644
--- a/src/acl.erl
+++ b/src/acl.erl
@@ -25,225 +25,264 @@
%%%----------------------------------------------------------------------
-module(acl).
+
-author('alexey@process-one.net').
--export([start/0,
- to_record/3,
- add/3,
- add_list/3,
- match_rule/3,
- % for debugging only
- match_acl/3]).
+-export([start/0, to_record/3, add/3, add_list/3,
+ match_rule/3, match_acl/3]).
-include("ejabberd.hrl").
+-include("jlib.hrl").
-record(acl, {aclname, aclspec}).
+-type regexp() :: binary().
+-type glob() :: binary().
+-type aclname() :: {atom(), binary() | global}.
+-type aclspec() :: all | none |
+ {user, binary()} |
+ {user, binary(), binary()} |
+ {server, binary()} |
+ {resource, binary()} |
+ {user_regexp, regexp()} |
+ {shared_group, binary()} |
+ {shared_group, binary(), binary()} |
+ {user_regexp, regexp(), binary()} |
+ {server_regexp, regexp()} |
+ {resource_regexp, regexp()} |
+ {node_regexp, regexp(), regexp()} |
+ {user_glob, glob()} |
+ {user_glob, glob(), binary()} |
+ {server_glob, glob()} |
+ {resource_glob, glob()} |
+ {node_glob, glob(), glob()}.
+
+-type acl() :: #acl{aclname :: aclname(),
+ aclspec :: aclspec()}.
+
+-export_type([acl/0]).
+
start() ->
mnesia:create_table(acl,
- [{disc_copies, [node()]},
- {type, bag},
+ [{disc_copies, [node()]}, {type, bag},
{attributes, record_info(fields, acl)}]),
mnesia:add_table_copy(acl, node(), ram_copies),
+ update_table(),
ok.
+-spec to_record(binary(), atom(), aclspec()) -> acl().
+
to_record(Host, ACLName, ACLSpec) ->
- #acl{aclname = {ACLName, Host}, aclspec = normalize_spec(ACLSpec)}.
+ #acl{aclname = {ACLName, Host},
+ aclspec = normalize_spec(ACLSpec)}.
+
+-spec add(binary(), aclname(), aclspec()) -> {atomic, ok} | {aborted, any()}.
add(Host, ACLName, ACLSpec) ->
- F = fun() ->
+ F = fun () ->
mnesia:write(#acl{aclname = {ACLName, Host},
aclspec = normalize_spec(ACLSpec)})
end,
mnesia:transaction(F).
+-spec add_list(binary(), [acl()], boolean()) -> false | ok.
+
add_list(Host, ACLs, Clear) ->
- F = fun() ->
- if
- Clear ->
- Ks = mnesia:select(
- acl, [{{acl, {'$1', Host}, '$2'}, [], ['$1']}]),
- lists:foreach(fun(K) ->
- mnesia:delete({acl, {K, Host}})
- end, Ks);
- true ->
- ok
+ F = fun () ->
+ if Clear ->
+ Ks = mnesia:select(acl,
+ [{{acl, {'$1', Host}, '$2'}, [],
+ ['$1']}]),
+ lists:foreach(fun (K) -> mnesia:delete({acl, {K, Host}})
+ end,
+ Ks);
+ true -> ok
end,
- lists:foreach(fun(ACL) ->
+ lists:foreach(fun (ACL) ->
case ACL of
- #acl{aclname = ACLName,
- aclspec = ACLSpec} ->
- mnesia:write(
- #acl{aclname = {ACLName, Host},
- aclspec = normalize_spec(ACLSpec)})
+ #acl{aclname = ACLName,
+ aclspec = ACLSpec} ->
+ mnesia:write(#acl{aclname =
+ {ACLName,
+ Host},
+ aclspec =
+ normalize_spec(ACLSpec)})
end
- end, ACLs)
+ end,
+ ACLs)
end,
case mnesia:transaction(F) of
- {atomic, _} ->
- ok;
- _ ->
- false
+ {atomic, _} -> ok;
+ _ -> false
end.
-normalize(A) ->
- jlib:nodeprep(A).
-normalize_spec({A, B}) ->
- {A, normalize(B)};
+normalize(A) -> jlib:nodeprep(iolist_to_binary(A)).
+
+normalize_spec({A, B}) -> {A, normalize(B)};
normalize_spec({A, B, C}) ->
{A, normalize(B), normalize(C)};
-normalize_spec(all) ->
- all;
-normalize_spec(none) ->
- none.
-
+normalize_spec(all) -> all;
+normalize_spec(none) -> none.
+-spec match_rule(global | binary(), atom(), jid() | ljid()) -> any().
match_rule(global, Rule, JID) ->
case Rule of
- all -> allow;
- none -> deny;
- _ ->
- case ejabberd_config:get_global_option({access, Rule, global}) of
- undefined ->
- deny;
- GACLs ->
- match_acls(GACLs, JID, global)
- end
+ all -> allow;
+ none -> deny;
+ _ ->
+ case ejabberd_config:get_global_option(
+ {access, Rule, global}, fun(V) -> V end)
+ of
+ undefined -> deny;
+ GACLs -> match_acls(GACLs, JID, global)
+ end
end;
-
match_rule(Host, Rule, JID) ->
case Rule of
- all -> allow;
- none -> deny;
- _ ->
- case ejabberd_config:get_global_option({access, Rule, global}) of
- undefined ->
- case ejabberd_config:get_global_option({access, Rule, Host}) of
- undefined ->
- deny;
- ACLs ->
- match_acls(ACLs, JID, Host)
- end;
- GACLs ->
- case ejabberd_config:get_global_option({access, Rule, Host}) of
- undefined ->
- match_acls(GACLs, JID, Host);
- ACLs ->
- case lists:reverse(GACLs) of
- [{allow, all} | Rest] ->
- match_acls(
- lists:reverse(Rest) ++ ACLs ++
- [{allow, all}],
- JID, Host);
- _ ->
- match_acls(GACLs ++ ACLs, JID, Host)
- end
- end
- end
+ all -> allow;
+ none -> deny;
+ _ ->
+ case ejabberd_config:get_global_option(
+ {access, Rule, global}, fun(V) -> V end)
+ of
+ undefined ->
+ case ejabberd_config:get_global_option(
+ {access, Rule, Host}, fun(V) -> V end)
+ of
+ undefined -> deny;
+ ACLs -> match_acls(ACLs, JID, Host)
+ end;
+ GACLs ->
+ case ejabberd_config:get_global_option(
+ {access, Rule, Host}, fun(V) -> V end)
+ of
+ undefined -> match_acls(GACLs, JID, Host);
+ ACLs ->
+ case lists:reverse(GACLs) of
+ [{allow, all} | Rest] ->
+ match_acls(lists:reverse(Rest) ++
+ ACLs ++ [{allow, all}],
+ JID, Host);
+ _ -> match_acls(GACLs ++ ACLs, JID, Host)
+ end
+ end
+ end
end.
-match_acls([], _, _Host) ->
- deny;
+match_acls([], _, _Host) -> deny;
match_acls([{Access, ACL} | ACLs], JID, Host) ->
case match_acl(ACL, JID, Host) of
- true ->
- Access;
- _ ->
- match_acls(ACLs, JID, Host)
+ true -> Access;
+ _ -> match_acls(ACLs, JID, Host)
end.
+-spec match_acl(atom(), jid() | ljid(), binary()) -> boolean().
+
match_acl(ACL, JID, Host) ->
case ACL of
- all -> true;
- none -> false;
- _ ->
- {User, Server, Resource} = jlib:jid_tolower(JID),
- lists:any(fun(#acl{aclspec = Spec}) ->
- case Spec of
- all ->
- true;
- {user, U} ->
- (U == User)
- andalso
- ((Host == Server) orelse
- ((Host == global) andalso
- lists:member(Server, ?MYHOSTS)));
- {user, U, S} ->
- (U == User) andalso (S == Server);
- {server, S} ->
- S == Server;
- {resource, R} ->
- R == Resource;
- {user_regexp, UR} ->
- ((Host == Server) orelse
- ((Host == global) andalso
- lists:member(Server, ?MYHOSTS)))
- andalso is_regexp_match(User, UR);
- {shared_group, G} ->
- Mod = loaded_shared_roster_module(Host),
- Mod:is_user_in_group({User, Server}, G, Host);
- {shared_group, G, H} ->
- Mod = loaded_shared_roster_module(H),
- Mod:is_user_in_group({User, Server}, G, H);
- {user_regexp, UR, S} ->
- (S == Server) andalso
- is_regexp_match(User, UR);
- {server_regexp, SR} ->
- is_regexp_match(Server, SR);
- {resource_regexp, RR} ->
- is_regexp_match(Resource, RR);
- {node_regexp, UR, SR} ->
- is_regexp_match(Server, SR) andalso
- is_regexp_match(User, UR);
- {user_glob, UR} ->
- ((Host == Server) orelse
- ((Host == global) andalso
- lists:member(Server, ?MYHOSTS)))
- andalso
- is_glob_match(User, UR);
- {user_glob, UR, S} ->
- (S == Server) andalso
- is_glob_match(User, UR);
- {server_glob, SR} ->
- is_glob_match(Server, SR);
- {resource_glob, RR} ->
- is_glob_match(Resource, RR);
- {node_glob, UR, SR} ->
- is_glob_match(Server, SR) andalso
- is_glob_match(User, UR);
- WrongSpec ->
- ?ERROR_MSG(
- "Wrong ACL expression: ~p~n"
- "Check your config file and reload it with the override_acls option enabled",
- [WrongSpec]),
- false
- end
- end,
- ets:lookup(acl, {ACL, global}) ++
+ all -> true;
+ none -> false;
+ _ ->
+ {User, Server, Resource} = jlib:jid_tolower(JID),
+ lists:any(fun (#acl{aclspec = Spec}) ->
+ case Spec of
+ all -> true;
+ {user, U} ->
+ U == User andalso
+ (Host == Server orelse
+ Host == global andalso
+ lists:member(Server, ?MYHOSTS));
+ {user, U, S} -> U == User andalso S == Server;
+ {server, S} -> S == Server;
+ {resource, R} -> R == Resource;
+ {user_regexp, UR} ->
+ (Host == Server orelse
+ Host == global andalso
+ lists:member(Server, ?MYHOSTS))
+ andalso is_regexp_match(User, UR);
+ {shared_group, G} ->
+ Mod = loaded_shared_roster_module(Host),
+ Mod:is_user_in_group({User, Server}, G, Host);
+ {shared_group, G, H} ->
+ Mod = loaded_shared_roster_module(H),
+ Mod:is_user_in_group({User, Server}, G, H);
+ {user_regexp, UR, S} ->
+ S == Server andalso is_regexp_match(User, UR);
+ {server_regexp, SR} ->
+ is_regexp_match(Server, SR);
+ {resource_regexp, RR} ->
+ is_regexp_match(Resource, RR);
+ {node_regexp, UR, SR} ->
+ is_regexp_match(Server, SR) andalso
+ is_regexp_match(User, UR);
+ {user_glob, UR} ->
+ (Host == Server orelse
+ Host == global andalso
+ lists:member(Server, ?MYHOSTS))
+ andalso is_glob_match(User, UR);
+ {user_glob, UR, S} ->
+ S == Server andalso is_glob_match(User, UR);
+ {server_glob, SR} -> is_glob_match(Server, SR);
+ {resource_glob, RR} ->
+ is_glob_match(Resource, RR);
+ {node_glob, UR, SR} ->
+ is_glob_match(Server, SR) andalso
+ is_glob_match(User, UR);
+ WrongSpec ->
+ ?ERROR_MSG("Wrong ACL expression: ~p~nCheck your "
+ "config file and reload it with the override_a"
+ "cls option enabled",
+ [WrongSpec]),
+ false
+ end
+ end,
+ ets:lookup(acl, {ACL, global}) ++
ets:lookup(acl, {ACL, Host}))
end.
is_regexp_match(String, RegExp) ->
case ejabberd_regexp:run(String, RegExp) of
- nomatch ->
- false;
- match ->
- true;
- {error, ErrDesc} ->
- ?ERROR_MSG(
- "Wrong regexp ~p in ACL: ~p",
- [RegExp, ErrDesc]),
- false
+ nomatch -> false;
+ match -> true;
+ {error, ErrDesc} ->
+ ?ERROR_MSG("Wrong regexp ~p in ACL: ~p",
+ [RegExp, ErrDesc]),
+ false
end.
is_glob_match(String, Glob) ->
- is_regexp_match(String, ejabberd_regexp:sh_to_awk(Glob)).
+ is_regexp_match(String,
+ ejabberd_regexp:sh_to_awk(Glob)).
loaded_shared_roster_module(Host) ->
case gen_mod:is_loaded(Host, mod_shared_roster_ldap) of
- true ->
- mod_shared_roster_ldap;
- false ->
- mod_shared_roster
+ true -> mod_shared_roster_ldap;
+ false -> mod_shared_roster
+ end.
+
+update_table() ->
+ Fields = record_info(fields, acl),
+ case mnesia:table_info(acl, attributes) of
+ Fields ->
+ ejabberd_config:convert_table_to_binary(
+ acl, Fields, bag,
+ fun(#acl{aclspec = Spec}) when is_tuple(Spec) ->
+ element(2, Spec);
+ (_) ->
+ '$next'
+ end,
+ fun(#acl{aclname = {ACLName, Host},
+ aclspec = Spec} = R) ->
+ NewHost = if Host == global ->
+ Host;
+ true ->
+ iolist_to_binary(Host)
+ end,
+ R#acl{aclname = {ACLName, NewHost},
+ aclspec = normalize_spec(Spec)}
+ end);
+ _ ->
+ ?INFO_MSG("Recreating acl table", []),
+ mnesia:transform_table(acl, ignore, Fields)
end.
diff --git a/src/adhoc.erl b/src/adhoc.erl
index 94353aead..6af65e5d1 100644
--- a/src/adhoc.erl
+++ b/src/adhoc.erl
@@ -25,11 +25,14 @@
%%%----------------------------------------------------------------------
-module(adhoc).
+
-author('henoch@dtek.chalmers.se').
--export([parse_request/1,
- produce_response/2,
- produce_response/1]).
+-export([
+ parse_request/1,
+ produce_response/2,
+ produce_response/1
+]).
-include("ejabberd.hrl").
-include("jlib.hrl").
@@ -37,93 +40,121 @@
%% Parse an ad-hoc request. Return either an adhoc_request record or
%% an {error, ErrorType} tuple.
+%%
+-spec(parse_request/1 ::
+(
+ IQ :: iq_request())
+ -> adhoc_response()
+ %%
+ | {error, _}
+).
+
parse_request(#iq{type = set, lang = Lang, sub_el = SubEl, xmlns = ?NS_COMMANDS}) ->
?DEBUG("entering parse_request...", []),
- Node = xml:get_tag_attr_s("node", SubEl),
- SessionID = xml:get_tag_attr_s("sessionid", SubEl),
- Action = xml:get_tag_attr_s("action", SubEl),
+ Node = xml:get_tag_attr_s(<<"node">>, SubEl),
+ SessionID = xml:get_tag_attr_s(<<"sessionid">>, SubEl),
+ Action = xml:get_tag_attr_s(<<"action">>, SubEl),
XData = find_xdata_el(SubEl),
- {xmlelement, _, _, AllEls} = SubEl,
+ #xmlel{children = AllEls} = SubEl,
Others = case XData of
- false ->
- AllEls;
- _ ->
- lists:delete(XData, AllEls)
+ false -> AllEls;
+ _ -> lists:delete(XData, AllEls)
end,
-
- #adhoc_request{lang = Lang,
- node = Node,
- sessionid = SessionID,
- action = Action,
- xdata = XData,
- others = Others};
-parse_request(_) ->
- {error, ?ERR_BAD_REQUEST}.
+ #adhoc_request{
+ lang = Lang,
+ node = Node,
+ sessionid = SessionID,
+ action = Action,
+ xdata = XData,
+ others = Others
+ };
+parse_request(_) -> {error, ?ERR_BAD_REQUEST}.
%% Borrowed from mod_vcard.erl
-find_xdata_el({xmlelement, _Name, _Attrs, SubEls}) ->
+find_xdata_el(#xmlel{children = SubEls}) ->
find_xdata_el1(SubEls).
-find_xdata_el1([]) ->
- false;
-find_xdata_el1([{xmlelement, Name, Attrs, SubEls} | Els]) ->
- case xml:get_attr_s("xmlns", Attrs) of
- ?NS_XDATA ->
- {xmlelement, Name, Attrs, SubEls};
- _ ->
- find_xdata_el1(Els)
+find_xdata_el1([]) -> false;
+find_xdata_el1([El | Els]) when is_record(El, xmlel) ->
+ case xml:get_tag_attr_s(<<"xmlns">>, El) of
+ ?NS_XDATA -> El;
+ _ -> find_xdata_el1(Els)
end;
-find_xdata_el1([_ | Els]) ->
- find_xdata_el1(Els).
+find_xdata_el1([_ | Els]) -> find_xdata_el1(Els).
%% Produce a <command/> node to use as response from an adhoc_response
%% record, filling in values for language, node and session id from
%% the request.
-produce_response(#adhoc_request{lang = Lang,
- node = Node,
- sessionid = SessionID},
- Response) ->
- produce_response(Response#adhoc_response{lang = Lang,
- node = Node,
- sessionid = SessionID}).
+%%
+-spec(produce_response/2 ::
+(
+ Adhoc_Request :: adhoc_request(),
+ Adhoc_Response :: adhoc_response())
+ -> Xmlel::xmlel()
+).
%% Produce a <command/> node to use as response from an adhoc_response
%% record.
-produce_response(#adhoc_response{lang = _Lang,
- node = Node,
- sessionid = ProvidedSessionID,
- status = Status,
- defaultaction = DefaultAction,
- actions = Actions,
- notes = Notes,
- elements = Elements}) ->
- SessionID = if is_list(ProvidedSessionID), ProvidedSessionID /= "" ->
- ProvidedSessionID;
- true ->
- jlib:now_to_utc_string(now())
- end,
+produce_response(#adhoc_request{lang = Lang, node = Node, sessionid = SessionID},
+ Adhoc_Response) ->
+ produce_response(Adhoc_Response#adhoc_response{
+ lang = Lang, node = Node, sessionid = SessionID
+ }).
+
+%%
+-spec(produce_response/1 ::
+(
+ Adhoc_Response::adhoc_response())
+ -> Xmlel::xmlel()
+).
+
+produce_response(
+ #adhoc_response{
+ %lang = _Lang,
+ node = Node,
+ sessionid = ProvidedSessionID,
+ status = Status,
+ defaultaction = DefaultAction,
+ actions = Actions,
+ notes = Notes,
+ elements = Elements
+ }) ->
+ SessionID = if is_binary(ProvidedSessionID),
+ ProvidedSessionID /= <<"">> -> ProvidedSessionID;
+ true -> jlib:now_to_utc_string(now())
+ end,
case Actions of
- [] ->
- ActionsEls = [];
- _ ->
- case DefaultAction of
- "" ->
- ActionsElAttrs = [];
- _ ->
- ActionsElAttrs = [{"execute", DefaultAction}]
- end,
- ActionsEls = [{xmlelement, "actions",
- ActionsElAttrs,
- [{xmlelement, Action, [], []} || Action <- Actions]}]
+ [] ->
+ ActionsEls = [];
+ _ ->
+ case DefaultAction of
+ <<"">> -> ActionsElAttrs = [];
+ _ -> ActionsElAttrs = [{<<"execute">>, DefaultAction}]
+ end,
+ ActionsEls = [
+ #xmlel{
+ name = <<"actions">>,
+ attrs = ActionsElAttrs,
+ children = [
+ #xmlel{name = Action, attrs = [], children = []}
+ || Action <- Actions]
+ }
+ ]
end,
NotesEls = lists:map(fun({Type, Text}) ->
- {xmlelement, "note",
- [{"type", Type}],
- [{xmlcdata, Text}]}
- end, Notes),
- {xmlelement, "command",
- [{"xmlns", ?NS_COMMANDS},
- {"sessionid", SessionID},
- {"node", Node},
- {"status", atom_to_list(Status)}],
- ActionsEls ++ NotesEls ++ Elements}.
+ #xmlel{
+ name = <<"note">>,
+ attrs = [{<<"type">>, Type}],
+ children = [{xmlcdata, Text}]
+ }
+ end, Notes),
+ #xmlel{
+ name = <<"command">>,
+ attrs = [
+ {<<"xmlns">>, ?NS_COMMANDS},
+ {<<"sessionid">>, SessionID},
+ {<<"node">>, Node},
+ {<<"status">>, iolist_to_binary(atom_to_list(Status))}
+ ],
+ children = ActionsEls ++ NotesEls ++ Elements
+ }.
diff --git a/src/adhoc.hrl b/src/adhoc.hrl
index b294f84fb..0910dc621 100644
--- a/src/adhoc.hrl
+++ b/src/adhoc.hrl
@@ -19,18 +19,27 @@
%%%
%%%----------------------------------------------------------------------
--record(adhoc_request, {lang,
- node,
- sessionid,
- action,
- xdata,
- others}).
+-record(adhoc_request,
+{
+ lang = <<"">> :: binary(),
+ node = <<"">> :: binary(),
+ sessionid = <<"">> :: binary(),
+ action = <<"">> :: binary(),
+ xdata = false :: false | xmlel(),
+ others = [] :: [xmlel()]
+}).
--record(adhoc_response, {lang,
- node,
- sessionid,
- status,
- defaultaction = "",
- actions = [],
- notes = [],
- elements = []}).
+-record(adhoc_response,
+{
+ lang = <<"">> :: binary(),
+ node = <<"">> :: binary(),
+ sessionid = <<"">> :: binary(),
+ status :: atom(),
+ defaultaction = <<"">> :: binary(),
+ actions = [] :: [binary()],
+ notes = [] :: [{binary(), binary()}],
+ elements = [] :: [xmlel()]
+}).
+
+-type adhoc_request() :: #adhoc_request{}.
+-type adhoc_response() :: #adhoc_response{}.
diff --git a/src/cache_tab.erl b/src/cache_tab.erl
index 74f47db6a..95343e4f5 100644
--- a/src/cache_tab.erl
+++ b/src/cache_tab.erl
@@ -380,11 +380,11 @@ do_setopts(#state{procs_num = N} = State, Opts) ->
shrink_size = ShrinkSize}.
get_proc_num() ->
- case erlang:system_info(logical_processors) of
- unknown ->
- 1;
- Num ->
- Num
+ case catch erlang:system_info(logical_processors) of
+ Num when is_integer(Num) ->
+ Num;
+ _ ->
+ 1
end.
get_proc_by_hash(Tab, Term) ->
diff --git a/src/configure.erl b/src/configure.erl
index 34246fd30..87b7bc208 100644
--- a/src/configure.erl
+++ b/src/configure.erl
@@ -62,7 +62,7 @@ start() ->
RootDirS = "ERLANG_DIR = " ++ code:root_dir() ++ "\n",
%% Load the ejabberd application description so that ?VERSION can read the vsn key
application:load(ejabberd),
- Version = "EJABBERD_VERSION = " ++ ?VERSION ++ "\n",
+ Version = "EJABBERD_VERSION = " ++ binary_to_list(?VERSION) ++ "\n",
ExpatDir = "EXPAT_DIR = c:\\sdk\\Expat-2.0.0\n",
OpenSSLDir = "OPENSSL_DIR = c:\\sdk\\OpenSSL\n",
DBType = "DBTYPE = generic\n", %% 'generic' or 'mssql'
diff --git a/src/cyrsasl.erl b/src/cyrsasl.erl
index 9d1377ffc..0672267b2 100644
--- a/src/cyrsasl.erl
+++ b/src/cyrsasl.erl
@@ -25,43 +25,76 @@
%%%----------------------------------------------------------------------
-module(cyrsasl).
+
-author('alexey@process-one.net').
--export([start/0,
- register_mechanism/3,
- listmech/1,
- server_new/7,
- server_start/3,
- server_step/2]).
+-export([start/0, register_mechanism/3, listmech/1,
+ server_new/7, server_start/3, server_step/2]).
-include("ejabberd.hrl").
--record(sasl_mechanism, {mechanism, module, password_type}).
--record(sasl_state, {service, myname, realm,
- get_password, check_password, check_password_digest,
- mech_mod, mech_state}).
+%%
+-export_type([
+ mechanism/0,
+ mechanisms/0,
+ sasl_mechanism/0
+]).
+
+-record(sasl_mechanism,
+ {mechanism = <<"">> :: mechanism() | '$1',
+ module :: atom(),
+ password_type = plain :: password_type() | '$2'}).
+
+-type(mechanism() :: binary()).
+-type(mechanisms() :: [mechanism(),...]).
+-type(password_type() :: plain | digest | scram).
+-type(props() :: [{username, binary()} |
+ {authzid, binary()} |
+ {auth_module, atom()}]).
+
+-type(sasl_mechanism() :: #sasl_mechanism{}).
--export([behaviour_info/1]).
+-record(sasl_state,
+{
+ service,
+ myname,
+ realm,
+ get_password,
+ check_password,
+ check_password_digest,
+ mech_mod,
+ mech_state
+}).
-behaviour_info(callbacks) ->
- [{mech_new, 4}, {mech_step, 2}];
-behaviour_info(_Other) ->
- undefined.
+-callback mech_new(binary(), fun(), fun(), fun()) -> any().
+-callback mech_step(any(), binary()) -> {ok, props()} |
+ {ok, props(), binary()} |
+ {continue, binary(), any()} |
+ {error, binary()} |
+ {error, binary(), binary()}.
start() ->
- ets:new(sasl_mechanism, [named_table,
- public,
- {keypos, #sasl_mechanism.mechanism}]),
+ ets:new(sasl_mechanism,
+ [named_table, public,
+ {keypos, #sasl_mechanism.mechanism}]),
cyrsasl_plain:start([]),
cyrsasl_digest:start([]),
cyrsasl_scram:start([]),
cyrsasl_anonymous:start([]),
ok.
+%%
+-spec(register_mechanism/3 ::
+(
+ Mechanim :: mechanism(),
+ Module :: module(),
+ PasswordType :: password_type())
+ -> any()
+).
+
register_mechanism(Mechanism, Module, PasswordType) ->
ets:insert(sasl_mechanism,
- #sasl_mechanism{mechanism = Mechanism,
- module = Module,
+ #sasl_mechanism{mechanism = Mechanism, module = Module,
password_type = PasswordType}).
%%% TODO: use callbacks
@@ -89,95 +122,96 @@ register_mechanism(Mechanism, Module, PasswordType) ->
%% end.
check_credentials(_State, Props) ->
- User = xml:get_attr_s(username, Props),
+ User = proplists:get_value(username, Props, <<>>),
case jlib:nodeprep(User) of
- error ->
- {error, "not-authorized"};
- "" ->
- {error, "not-authorized"};
- _LUser ->
- ok
+ error -> {error, <<"not-authorized">>};
+ <<"">> -> {error, <<"not-authorized">>};
+ _LUser -> ok
end.
+-spec(listmech/1 ::
+(
+ Host ::binary())
+ -> Mechanisms::mechanisms()
+).
+
listmech(Host) ->
Mechs = ets:select(sasl_mechanism,
[{#sasl_mechanism{mechanism = '$1',
- password_type = '$2',
- _ = '_'},
+ password_type = '$2', _ = '_'},
case catch ejabberd_auth:store_type(Host) of
- external ->
- [{'==', '$2', plain}];
- scram ->
- [{'/=', '$2', digest}];
- {'EXIT',{undef,[{Module,store_type,[]} | _]}} ->
- ?WARNING_MSG("~p doesn't implement the function store_type/0", [Module]),
- [];
- _Else ->
- []
+ external -> [{'==', '$2', plain}];
+ scram -> [{'/=', '$2', digest}];
+ {'EXIT', {undef, [{Module, store_type, []} | _]}} ->
+ ?WARNING_MSG("~p doesn't implement the function store_type/0",
+ [Module]),
+ [];
+ _Else -> []
end,
['$1']}]),
filter_anonymous(Host, Mechs).
server_new(Service, ServerFQDN, UserRealm, _SecFlags,
GetPassword, CheckPassword, CheckPasswordDigest) ->
- #sasl_state{service = Service,
- myname = ServerFQDN,
- realm = UserRealm,
- get_password = GetPassword,
+ #sasl_state{service = Service, myname = ServerFQDN,
+ realm = UserRealm, get_password = GetPassword,
check_password = CheckPassword,
- check_password_digest= CheckPasswordDigest}.
+ check_password_digest = CheckPasswordDigest}.
server_start(State, Mech, ClientIn) ->
- case lists:member(Mech, listmech(State#sasl_state.myname)) of
- true ->
- case ets:lookup(sasl_mechanism, Mech) of
- [#sasl_mechanism{module = Module}] ->
- {ok, MechState} = Module:mech_new(
- State#sasl_state.myname,
- State#sasl_state.get_password,
- State#sasl_state.check_password,
- State#sasl_state.check_password_digest),
- server_step(State#sasl_state{mech_mod = Module,
- mech_state = MechState},
- ClientIn);
- _ ->
- {error, "no-mechanism"}
- end;
- false ->
- {error, "no-mechanism"}
+ case lists:member(Mech,
+ listmech(State#sasl_state.myname))
+ of
+ true ->
+ case ets:lookup(sasl_mechanism, Mech) of
+ [#sasl_mechanism{module = Module}] ->
+ {ok, MechState} =
+ Module:mech_new(State#sasl_state.myname,
+ State#sasl_state.get_password,
+ State#sasl_state.check_password,
+ State#sasl_state.check_password_digest),
+ server_step(State#sasl_state{mech_mod = Module,
+ mech_state = MechState},
+ ClientIn);
+ _ -> {error, <<"no-mechanism">>}
+ end;
+ false -> {error, <<"no-mechanism">>}
end.
server_step(State, ClientIn) ->
Module = State#sasl_state.mech_mod,
MechState = State#sasl_state.mech_state,
case Module:mech_step(MechState, ClientIn) of
- {ok, Props} ->
- case check_credentials(State, Props) of
- ok ->
- {ok, Props};
- {error, Error} ->
- {error, Error}
- end;
- {ok, Props, ServerOut} ->
- case check_credentials(State, Props) of
- ok ->
- {ok, Props, ServerOut};
- {error, Error} ->
- {error, Error}
- end;
- {continue, ServerOut, NewMechState} ->
- {continue, ServerOut,
- State#sasl_state{mech_state = NewMechState}};
- {error, Error, Username} ->
- {error, Error, Username};
- {error, Error} ->
- {error, Error}
+ {ok, Props} ->
+ case check_credentials(State, Props) of
+ ok -> {ok, Props};
+ {error, Error} -> {error, Error}
+ end;
+ {ok, Props, ServerOut} ->
+ case check_credentials(State, Props) of
+ ok -> {ok, Props, ServerOut};
+ {error, Error} -> {error, Error}
+ end;
+ {continue, ServerOut, NewMechState} ->
+ {continue, ServerOut, State#sasl_state{mech_state = NewMechState}};
+ {error, Error, Username} ->
+ {error, Error, Username};
+ {error, Error} ->
+ {error, Error}
end.
%% Remove the anonymous mechanism from the list if not enabled for the given
%% host
+%%
+-spec(filter_anonymous/2 ::
+(
+ Host :: binary(),
+ Mechs :: mechanisms())
+ -> mechanisms()
+).
+
filter_anonymous(Host, Mechs) ->
case ejabberd_auth_anonymous:is_sasl_anonymous_enabled(Host) of
- true -> Mechs;
- false -> Mechs -- ["ANONYMOUS"]
+ true -> Mechs;
+ false -> Mechs -- [<<"ANONYMOUS">>]
end.
diff --git a/src/cyrsasl_anonymous.erl b/src/cyrsasl_anonymous.erl
index cb0b1e3ff..3090cfe9d 100644
--- a/src/cyrsasl_anonymous.erl
+++ b/src/cyrsasl_anonymous.erl
@@ -31,26 +31,20 @@
-behaviour(cyrsasl).
--record(state, {server}).
+-record(state, {server = <<"">> :: binary()}).
start(_Opts) ->
- cyrsasl:register_mechanism("ANONYMOUS", ?MODULE, plain),
+ cyrsasl:register_mechanism(<<"ANONYMOUS">>, ?MODULE, plain),
ok.
-stop() ->
- ok.
+stop() -> ok.
mech_new(Host, _GetPassword, _CheckPassword, _CheckPasswordDigest) ->
{ok, #state{server = Host}}.
-mech_step(State, _ClientIn) ->
- %% We generate a random username:
- User = lists:concat([randoms:get_string() | tuple_to_list(now())]),
- Server = State#state.server,
-
- %% Checks that the username is available
+mech_step(#state{server = Server}, _ClientIn) ->
+ User = iolist_to_binary([randoms:get_string() | tuple_to_list(now())]),
case ejabberd_auth:is_user_exists(User, Server) of
- true -> {error, "not-authorized"};
- false -> {ok, [{username, User},
- {auth_module, ejabberd_auth_anonymous}]}
+ true -> {error, <<"not-authorized">>};
+ false -> {ok, [{username, User}, {auth_module, ejabberd_auth_anonymous}]}
end.
diff --git a/src/cyrsasl_digest.erl b/src/cyrsasl_digest.erl
index 557e498cd..3bb88431b 100644
--- a/src/cyrsasl_digest.erl
+++ b/src/cyrsasl_digest.erl
@@ -25,134 +25,145 @@
%%%----------------------------------------------------------------------
-module(cyrsasl_digest).
+
-author('alexey@sevcom.net').
--export([start/1,
- stop/0,
- mech_new/4,
- mech_step/2]).
+-export([start/1, stop/0, mech_new/4, mech_step/2, parse/1]).
-include("ejabberd.hrl").
-behaviour(cyrsasl).
--record(state, {step, nonce, username, authzid, get_password, check_password, auth_module,
- host, hostfqdn}).
+-type get_password_fun() :: fun((binary()) -> {false, any()} |
+ {binary(), atom()}).
+
+-type check_password_fun() :: fun((binary(), binary(), binary(),
+ fun((binary()) -> binary())) ->
+ {boolean(), any()} |
+ false).
+
+-record(state, {step = 1 :: 1 | 3 | 5,
+ nonce = <<"">> :: binary(),
+ username = <<"">> :: binary(),
+ authzid = <<"">> :: binary(),
+ get_password = fun(_) -> {false, <<>>} end :: get_password_fun(),
+ check_password = fun(_, _, _, _) -> false end :: check_password_fun(),
+ auth_module :: atom(),
+ host = <<"">> :: binary(),
+ hostfqdn = <<"">> :: binary()}).
start(_Opts) ->
Fqdn = get_local_fqdn(),
- ?INFO_MSG("FQDN used to check DIGEST-MD5 SASL authentication: ~p", [Fqdn]),
- cyrsasl:register_mechanism("DIGEST-MD5", ?MODULE, digest).
+ ?INFO_MSG("FQDN used to check DIGEST-MD5 SASL authentication: ~s",
+ [Fqdn]),
+ cyrsasl:register_mechanism(<<"DIGEST-MD5">>, ?MODULE,
+ digest).
-stop() ->
- ok.
+stop() -> ok.
-mech_new(Host, GetPassword, _CheckPassword, CheckPasswordDigest) ->
- {ok, #state{step = 1,
- nonce = randoms:get_string(),
- host = Host,
- hostfqdn = get_local_fqdn(),
- get_password = GetPassword,
- check_password = CheckPasswordDigest}}.
+mech_new(Host, GetPassword, _CheckPassword,
+ CheckPasswordDigest) ->
+ {ok,
+ #state{step = 1, nonce = randoms:get_string(),
+ host = Host, hostfqdn = get_local_fqdn(),
+ get_password = GetPassword,
+ check_password = CheckPasswordDigest}}.
mech_step(#state{step = 1, nonce = Nonce} = State, _) ->
{continue,
- "nonce=\"" ++ Nonce ++
- "\",qop=\"auth\",charset=utf-8,algorithm=md5-sess",
+ <<"nonce=\"", Nonce/binary,
+ "\",qop=\"auth\",charset=utf-8,algorithm=md5-sess">>,
State#state{step = 3}};
-mech_step(#state{step = 3, nonce = Nonce} = State, ClientIn) ->
+mech_step(#state{step = 3, nonce = Nonce} = State,
+ ClientIn) ->
case parse(ClientIn) of
- bad ->
- {error, "bad-protocol"};
- KeyVals ->
- DigestURI = xml:get_attr_s("digest-uri", KeyVals),
- UserName = xml:get_attr_s("username", KeyVals),
- case is_digesturi_valid(DigestURI, State#state.host, State#state.hostfqdn) of
- false ->
- ?DEBUG("User login not authorized because digest-uri "
- "seems invalid: ~p (checking for Host ~p, FQDN ~p)", [DigestURI,
- State#state.host, State#state.hostfqdn]),
- {error, "not-authorized", UserName};
- true ->
- AuthzId = xml:get_attr_s("authzid", KeyVals),
- case (State#state.get_password)(UserName) of
- {false, _} ->
- {error, "not-authorized", UserName};
- {Passwd, AuthModule} ->
- case (State#state.check_password)(UserName, "",
- xml:get_attr_s("response", KeyVals),
- fun(PW) -> response(KeyVals, UserName, PW, Nonce, AuthzId,
- "AUTHENTICATE") end) of
- {true, _} ->
- RspAuth = response(KeyVals,
- UserName, Passwd,
- Nonce, AuthzId, ""),
- {continue,
- "rspauth=" ++ RspAuth,
- State#state{step = 5,
- auth_module = AuthModule,
- username = UserName,
- authzid = AuthzId}};
- false ->
- {error, "not-authorized", UserName};
- {false, _} ->
- {error, "not-authorized", UserName}
- end
- end
- end
+ bad -> {error, <<"bad-protocol">>};
+ KeyVals ->
+ DigestURI = proplists:get_value(<<"digest-uri">>, KeyVals, <<>>),
+ %DigestURI = xml:get_attr_s(<<"digest-uri">>, KeyVals),
+ UserName = proplists:get_value(<<"username">>, KeyVals, <<>>),
+ %UserName = xml:get_attr_s(<<"username">>, KeyVals),
+ case is_digesturi_valid(DigestURI, State#state.host,
+ State#state.hostfqdn)
+ of
+ false ->
+ ?DEBUG("User login not authorized because digest-uri "
+ "seems invalid: ~p (checking for Host "
+ "~p, FQDN ~p)",
+ [DigestURI, State#state.host, State#state.hostfqdn]),
+ {error, <<"not-authorized">>, UserName};
+ true ->
+ AuthzId = proplists:get_value(<<"authzid">>, KeyVals, <<>>),
+ %AuthzId = xml:get_attr_s(<<"authzid">>, KeyVals),
+ case (State#state.get_password)(UserName) of
+ {false, _} -> {error, <<"not-authorized">>, UserName};
+ {Passwd, AuthModule} ->
+ case (State#state.check_password)(UserName, <<"">>,
+ proplists:get_value(<<"response">>, KeyVals, <<>>),
+ %xml:get_attr_s(<<"response">>, KeyVals),
+ fun (PW) ->
+ response(KeyVals,
+ UserName,
+ PW,
+ Nonce,
+ AuthzId,
+ <<"AUTHENTICATE">>)
+ end)
+ of
+ {true, _} ->
+ RspAuth = response(KeyVals, UserName, Passwd, Nonce,
+ AuthzId, <<"">>),
+ {continue, <<"rspauth=", RspAuth/binary>>,
+ State#state{step = 5, auth_module = AuthModule,
+ username = UserName,
+ authzid = AuthzId}};
+ false -> {error, <<"not-authorized">>, UserName};
+ {false, _} -> {error, <<"not-authorized">>, UserName}
+ end
+ end
+ end
end;
-mech_step(#state{step = 5,
- auth_module = AuthModule,
- username = UserName,
- authzid = AuthzId}, "") ->
- {ok, [{username, UserName}, {authzid, AuthzId},
- {auth_module, AuthModule}]};
+mech_step(#state{step = 5, auth_module = AuthModule,
+ username = UserName, authzid = AuthzId},
+ <<"">>) ->
+ {ok,
+ [{username, UserName}, {authzid, AuthzId},
+ {auth_module, AuthModule}]};
mech_step(A, B) ->
- ?DEBUG("SASL DIGEST: A ~p B ~p", [A,B]),
- {error, "bad-protocol"}.
+ ?DEBUG("SASL DIGEST: A ~p B ~p", [A, B]),
+ {error, <<"bad-protocol">>}.
-parse(S) ->
- parse1(S, "", []).
+parse(S) -> parse1(binary_to_list(S), "", []).
parse1([$= | Cs], S, Ts) ->
parse2(Cs, lists:reverse(S), "", Ts);
-parse1([$, | Cs], [], Ts) ->
- parse1(Cs, [], Ts);
-parse1([$\s | Cs], [], Ts) ->
- parse1(Cs, [], Ts);
-parse1([C | Cs], S, Ts) ->
- parse1(Cs, [C | S], Ts);
-parse1([], [], T) ->
- lists:reverse(T);
-parse1([], _S, _T) ->
- bad.
-
-parse2([$\" | Cs], Key, Val, Ts) ->
+parse1([$, | Cs], [], Ts) -> parse1(Cs, [], Ts);
+parse1([$\s | Cs], [], Ts) -> parse1(Cs, [], Ts);
+parse1([C | Cs], S, Ts) -> parse1(Cs, [C | S], Ts);
+parse1([], [], T) -> lists:reverse(T);
+parse1([], _S, _T) -> bad.
+
+parse2([$" | Cs], Key, Val, Ts) ->
parse3(Cs, Key, Val, Ts);
parse2([C | Cs], Key, Val, Ts) ->
parse4(Cs, Key, [C | Val], Ts);
-parse2([], _, _, _) ->
- bad.
+parse2([], _, _, _) -> bad.
-parse3([$\" | Cs], Key, Val, Ts) ->
+parse3([$" | Cs], Key, Val, Ts) ->
parse4(Cs, Key, Val, Ts);
parse3([$\\, C | Cs], Key, Val, Ts) ->
parse3(Cs, Key, [C | Val], Ts);
parse3([C | Cs], Key, Val, Ts) ->
parse3(Cs, Key, [C | Val], Ts);
-parse3([], _, _, _) ->
- bad.
+parse3([], _, _, _) -> bad.
parse4([$, | Cs], Key, Val, Ts) ->
- parse1(Cs, "", [{Key, lists:reverse(Val)} | Ts]);
+ parse1(Cs, "", [{list_to_binary(Key), list_to_binary(lists:reverse(Val))} | Ts]);
parse4([$\s | Cs], Key, Val, Ts) ->
parse4(Cs, Key, Val, Ts);
parse4([C | Cs], Key, Val, Ts) ->
parse4(Cs, Key, [C | Val], Ts);
parse4([], Key, Val, Ts) ->
- parse1([], "", [{Key, lists:reverse(Val)} | Ts]).
-
-
%% @doc Check if the digest-uri is valid.
%% RFC-2831 allows to provide the IP address in Host,
%% however ejabberd doesn't allow that.
@@ -162,14 +173,17 @@ parse4([], Key, Val, Ts) ->
%% xmpp/server3.example.org/jabber.example.org, xmpp/server3.example.org and
%% xmpp/jabber.example.org
%% The last version is not actually allowed by the RFC, but implemented by popular clients
-is_digesturi_valid(DigestURICase, JabberDomain, JabberFQDN) ->
+ parse1([], "", [{list_to_binary(Key), list_to_binary(lists:reverse(Val))} | Ts]).
+
+is_digesturi_valid(DigestURICase, JabberDomain,
+ JabberFQDN) ->
DigestURI = stringprep:tolower(DigestURICase),
- case catch string:tokens(DigestURI, "/") of
- ["xmpp", Host] ->
- IsHostFqdn = is_host_fqdn(Host, JabberFQDN),
+ case catch str:tokens(DigestURI, <<"/">>) of
+ [<<"xmpp">>, Host] ->
+ IsHostFqdn = is_host_fqdn(binary_to_list(Host), binary_to_list(JabberFQDN)),
(Host == JabberDomain) or IsHostFqdn;
- ["xmpp", Host, ServName] ->
- IsHostFqdn = is_host_fqdn(Host, JabberFQDN),
+ [<<"xmpp">>, Host, ServName] ->
+ IsHostFqdn = is_host_fqdn(binary_to_list(Host), binary_to_list(JabberFQDN)),
(ServName == JabberDomain) and IsHostFqdn;
_ ->
false
@@ -185,62 +199,60 @@ is_host_fqdn(Host, [Fqdn | FqdnTail]) when Host /= Fqdn ->
is_host_fqdn(Host, FqdnTail).
get_local_fqdn() ->
- case (catch get_local_fqdn2()) of
- Str when is_list(Str) -> Str;
- _ -> "unknown-fqdn, please configure fqdn option in ejabberd.cfg!"
+ case catch get_local_fqdn2() of
+ Str when is_binary(Str) -> Str;
+ _ ->
+ <<"unknown-fqdn, please configure fqdn "
+ "option in ejabberd.cfg!">>
end.
+
get_local_fqdn2() ->
- case ejabberd_config:get_local_option(fqdn) of
- ConfiguredFqdn when is_list(ConfiguredFqdn) ->
- ConfiguredFqdn;
- _undefined ->
- {ok, Hostname} = inet:gethostname(),
- {ok, {hostent, Fqdn, _, _, _, _}} = inet:gethostbyname(Hostname),
- Fqdn
+ case ejabberd_config:get_local_option(
+ fqdn, fun iolist_to_binary/1) of
+ ConfiguredFqdn when is_binary(ConfiguredFqdn) ->
+ ConfiguredFqdn;
+ undefined ->
+ {ok, Hostname} = inet:gethostname(),
+ {ok, {hostent, Fqdn, _, _, _, _}} =
+ inet:gethostbyname(Hostname),
+ list_to_binary(Fqdn)
end.
-digit_to_xchar(D) when (D >= 0) and (D < 10) ->
- D + 48;
-digit_to_xchar(D) ->
- D + 87.
-
hex(S) ->
- hex(S, []).
-
-hex([], Res) ->
- lists:reverse(Res);
-hex([N | Ns], Res) ->
- hex(Ns, [digit_to_xchar(N rem 16),
- digit_to_xchar(N div 16) | Res]).
+ sha:to_hexlist(S).
+proplists_get_bin_value(Key, Pairs, Default) ->
+ case proplists:get_value(Key, Pairs, Default) of
+ L when is_list(L) ->
+ list_to_binary(L);
+ L2 ->
+ L2
+ end.
-response(KeyVals, User, Passwd, Nonce, AuthzId, A2Prefix) ->
- Realm = xml:get_attr_s("realm", KeyVals),
- CNonce = xml:get_attr_s("cnonce", KeyVals),
- DigestURI = xml:get_attr_s("digest-uri", KeyVals),
- NC = xml:get_attr_s("nc", KeyVals),
- QOP = xml:get_attr_s("qop", KeyVals),
+response(KeyVals, User, Passwd, Nonce, AuthzId,
+ A2Prefix) ->
+ Realm = proplists_get_bin_value(<<"realm">>, KeyVals, <<>>),
+ CNonce = proplists_get_bin_value(<<"cnonce">>, KeyVals, <<>>),
+ DigestURI = proplists_get_bin_value(<<"digest-uri">>, KeyVals, <<>>),
+ NC = proplists_get_bin_value(<<"nc">>, KeyVals, <<>>),
+ QOP = proplists_get_bin_value(<<"qop">>, KeyVals, <<>>),
+ MD5Hash = crypto:md5(<<User/binary, ":", Realm/binary, ":",
+ Passwd/binary>>),
A1 = case AuthzId of
- "" ->
- binary_to_list(
- crypto:md5(User ++ ":" ++ Realm ++ ":" ++ Passwd)) ++
- ":" ++ Nonce ++ ":" ++ CNonce;
- _ ->
- binary_to_list(
- crypto:md5(User ++ ":" ++ Realm ++ ":" ++ Passwd)) ++
- ":" ++ Nonce ++ ":" ++ CNonce ++ ":" ++ AuthzId
+ <<"">> ->
+ <<MD5Hash/binary, ":", Nonce/binary, ":", CNonce/binary>>;
+ _ ->
+ <<MD5Hash/binary, ":", Nonce/binary, ":", CNonce/binary, ":",
+ AuthzId/binary>>
end,
A2 = case QOP of
- "auth" ->
- A2Prefix ++ ":" ++ DigestURI;
- _ ->
- A2Prefix ++ ":" ++ DigestURI ++
- ":00000000000000000000000000000000"
+ <<"auth">> ->
+ <<A2Prefix/binary, ":", DigestURI/binary>>;
+ _ ->
+ <<A2Prefix/binary, ":", DigestURI/binary,
+ ":00000000000000000000000000000000">>
end,
- T = hex(binary_to_list(crypto:md5(A1))) ++ ":" ++ Nonce ++ ":" ++
- NC ++ ":" ++ CNonce ++ ":" ++ QOP ++ ":" ++
- hex(binary_to_list(crypto:md5(A2))),
- hex(binary_to_list(crypto:md5(T))).
-
-
-
+ T = <<(hex((crypto:md5(A1))))/binary, ":", Nonce/binary,
+ ":", NC/binary, ":", CNonce/binary, ":", QOP/binary,
+ ":", (hex((crypto:md5(A2))))/binary>>,
+ hex((crypto:md5(T))).
diff --git a/src/cyrsasl_plain.erl b/src/cyrsasl_plain.erl
index 7192cd161..c5c5f2e02 100644
--- a/src/cyrsasl_plain.erl
+++ b/src/cyrsasl_plain.erl
@@ -25,6 +25,7 @@
%%%----------------------------------------------------------------------
-module(cyrsasl_plain).
+
-author('alexey@process-one.net').
-export([start/1, stop/0, mech_new/4, mech_step/2, parse/1]).
@@ -34,67 +35,56 @@
-record(state, {check_password}).
start(_Opts) ->
- cyrsasl:register_mechanism("PLAIN", ?MODULE, plain),
+ cyrsasl:register_mechanism(<<"PLAIN">>, ?MODULE, plain),
ok.
-stop() ->
- ok.
+stop() -> ok.
mech_new(_Host, _GetPassword, CheckPassword, _CheckPasswordDigest) ->
{ok, #state{check_password = CheckPassword}}.
mech_step(State, ClientIn) ->
case prepare(ClientIn) of
- [AuthzId, User, Password] ->
- case (State#state.check_password)(User, Password) of
- {true, AuthModule} ->
- {ok, [{username, User}, {authzid, AuthzId},
- {auth_module, AuthModule}]};
- _ ->
- {error, "not-authorized", User}
- end;
- _ ->
- {error, "bad-protocol"}
+ [AuthzId, User, Password] ->
+ case (State#state.check_password)(User, Password) of
+ {true, AuthModule} ->
+ {ok,
+ [{username, User}, {authzid, AuthzId},
+ {auth_module, AuthModule}]};
+ _ -> {error, <<"not-authorized">>, User}
+ end;
+ _ -> {error, <<"bad-protocol">>}
end.
prepare(ClientIn) ->
case parse(ClientIn) of
- [[], UserMaybeDomain, Password] ->
- case parse_domain(UserMaybeDomain) of
- %% <NUL>login@domain<NUL>pwd
- [User, _Domain] ->
- [UserMaybeDomain, User, Password];
- %% <NUL>login<NUL>pwd
- [User] ->
- ["", User, Password]
- end;
- %% login@domain<NUL>login<NUL>pwd
- [AuthzId, User, Password] ->
- [AuthzId, User, Password];
- _ ->
- error
+ [<<"">>, UserMaybeDomain, Password] ->
+ case parse_domain(UserMaybeDomain) of
+ %% <NUL>login@domain<NUL>pwd
+ [User, _Domain] -> [UserMaybeDomain, User, Password];
+ %% <NUL>login<NUL>pwd
+ [User] -> [<<"">>, User, Password]
+ end;
+ %% login@domain<NUL>login<NUL>pwd
+ [AuthzId, User, Password] -> [AuthzId, User, Password];
+ _ -> error
end.
-
-parse(S) ->
- parse1(S, "", []).
+parse(S) -> parse1(binary_to_list(S), "", []).
parse1([0 | Cs], S, T) ->
- parse1(Cs, "", [lists:reverse(S) | T]);
-parse1([C | Cs], S, T) ->
- parse1(Cs, [C | S], T);
+ parse1(Cs, "", [list_to_binary(lists:reverse(S)) | T]);
+parse1([C | Cs], S, T) -> parse1(Cs, [C | S], T);
%parse1([], [], T) ->
% lists:reverse(T);
parse1([], S, T) ->
- lists:reverse([lists:reverse(S) | T]).
-
+ lists:reverse([list_to_binary(lists:reverse(S)) | T]).
-parse_domain(S) ->
- parse_domain1(S, "", []).
+parse_domain(S) -> parse_domain1(binary_to_list(S), "", []).
parse_domain1([$@ | Cs], S, T) ->
- parse_domain1(Cs, "", [lists:reverse(S) | T]);
+ parse_domain1(Cs, "", [list_to_binary(lists:reverse(S)) | T]);
parse_domain1([C | Cs], S, T) ->
parse_domain1(Cs, [C | S], T);
parse_domain1([], S, T) ->
- lists:reverse([lists:reverse(S) | T]).
+ lists:reverse([list_to_binary(lists:reverse(S)) | T]).
diff --git a/src/cyrsasl_scram.erl b/src/cyrsasl_scram.erl
index dc671b243..33d18cd1a 100644
--- a/src/cyrsasl_scram.erl
+++ b/src/cyrsasl_scram.erl
@@ -25,166 +25,185 @@
%%%----------------------------------------------------------------------
-module(cyrsasl_scram).
+
-author('stephen.roettger@googlemail.com').
--export([start/1,
- stop/0,
- mech_new/4,
- mech_step/2]).
+-export([start/1, stop/0, mech_new/4, mech_step/2]).
-include("ejabberd.hrl").
-behaviour(cyrsasl).
--record(state, {step, stored_key, server_key, username, get_password, check_password,
- auth_message, client_nonce, server_nonce}).
+-record(state,
+ {step = 2 :: 2 | 4,
+ stored_key = <<"">> :: binary(),
+ server_key = <<"">> :: binary(),
+ username = <<"">> :: binary(),
+ get_password :: fun(),
+ check_password :: fun(),
+ auth_message = <<"">> :: binary(),
+ client_nonce = <<"">> :: binary(),
+ server_nonce = <<"">> :: binary()}).
-define(SALT_LENGTH, 16).
+
-define(NONCE_LENGTH, 16).
start(_Opts) ->
- cyrsasl:register_mechanism("SCRAM-SHA-1", ?MODULE, scram).
+ cyrsasl:register_mechanism(<<"SCRAM-SHA-1">>, ?MODULE,
+ scram).
-stop() ->
- ok.
+stop() -> ok.
-mech_new(_Host, GetPassword, _CheckPassword, _CheckPasswordDigest) ->
+mech_new(_Host, GetPassword, _CheckPassword,
+ _CheckPasswordDigest) ->
{ok, #state{step = 2, get_password = GetPassword}}.
mech_step(#state{step = 2} = State, ClientIn) ->
- case string:tokens(ClientIn, ",") of
- [CBind, UserNameAttribute, ClientNonceAttribute] when (CBind == "y") or (CBind == "n") ->
- case parse_attribute(UserNameAttribute) of
- {error, Reason} ->
- {error, Reason};
- {_, EscapedUserName} ->
- case unescape_username(EscapedUserName) of
- error ->
- {error, "protocol-error-bad-username"};
- UserName ->
- case parse_attribute(ClientNonceAttribute) of
- {$r, ClientNonce} ->
- case (State#state.get_password)(UserName) of
- {false, _} ->
- {error, "not-authorized", UserName};
- {Ret, _AuthModule} ->
- {StoredKey, ServerKey, Salt, IterationCount} = if
- is_tuple(Ret) ->
- Ret;
- true ->
- TempSalt = crypto:rand_bytes(?SALT_LENGTH),
- SaltedPassword = scram:salted_password(Ret, TempSalt, ?SCRAM_DEFAULT_ITERATION_COUNT),
- {scram:stored_key(scram:client_key(SaltedPassword)),
- scram:server_key(SaltedPassword), TempSalt, ?SCRAM_DEFAULT_ITERATION_COUNT}
- end,
- ClientFirstMessageBare = string:substr(ClientIn, string:str(ClientIn, "n=")),
- ServerNonce = base64:encode_to_string(crypto:rand_bytes(?NONCE_LENGTH)),
- ServerFirstMessage = "r=" ++ ClientNonce ++ ServerNonce ++ "," ++
- "s=" ++ base64:encode_to_string(Salt) ++ "," ++
- "i=" ++ integer_to_list(IterationCount),
- {continue,
- ServerFirstMessage,
- State#state{step = 4, stored_key = StoredKey, server_key = ServerKey,
- auth_message = ClientFirstMessageBare ++ "," ++ ServerFirstMessage,
- client_nonce = ClientNonce, server_nonce = ServerNonce, username = UserName}}
- end;
- _Else ->
- {error, "not-supported"}
- end
- end
- end;
- _Else ->
- {error, "bad-protocol"}
- end;
+ case str:tokens(ClientIn, <<",">>) of
+ [CBind, UserNameAttribute, ClientNonceAttribute]
+ when (CBind == <<"y">>) or (CBind == <<"n">>) ->
+ case parse_attribute(UserNameAttribute) of
+ {error, Reason} -> {error, Reason};
+ {_, EscapedUserName} ->
+ case unescape_username(EscapedUserName) of
+ error -> {error, <<"protocol-error-bad-username">>};
+ UserName ->
+ case parse_attribute(ClientNonceAttribute) of
+ {$r, ClientNonce} ->
+ case (State#state.get_password)(UserName) of
+ {false, _} -> {error, <<"not-authorized">>, UserName};
+ {Ret, _AuthModule} ->
+ {StoredKey, ServerKey, Salt, IterationCount} =
+ if is_tuple(Ret) -> Ret;
+ true ->
+ TempSalt =
+ crypto:rand_bytes(?SALT_LENGTH),
+ SaltedPassword =
+ scram:salted_password(Ret,
+ TempSalt,
+ ?SCRAM_DEFAULT_ITERATION_COUNT),
+ {scram:stored_key(scram:client_key(SaltedPassword)),
+ scram:server_key(SaltedPassword),
+ TempSalt,
+ ?SCRAM_DEFAULT_ITERATION_COUNT}
+ end,
+ ClientFirstMessageBare =
+ str:substr(ClientIn,
+ str:str(ClientIn, <<"n=">>)),
+ ServerNonce =
+ jlib:encode_base64(crypto:rand_bytes(?NONCE_LENGTH)),
+ ServerFirstMessage =
+ iolist_to_binary(
+ ["r=",
+ ClientNonce,
+ ServerNonce,
+ ",", "s=",
+ jlib:encode_base64(Salt),
+ ",", "i=",
+ integer_to_list(IterationCount)]),
+ {continue, ServerFirstMessage,
+ State#state{step = 4, stored_key = StoredKey,
+ server_key = ServerKey,
+ auth_message =
+ <<ClientFirstMessageBare/binary,
+ ",", ServerFirstMessage/binary>>,
+ client_nonce = ClientNonce,
+ server_nonce = ServerNonce,
+ username = UserName}}
+ end;
+ _Else -> {error, <<"not-supported">>}
+ end
+ end
+ end;
+ _Else -> {error, <<"bad-protocol">>}
+ end;
mech_step(#state{step = 4} = State, ClientIn) ->
- case string:tokens(ClientIn, ",") of
- [GS2ChannelBindingAttribute, NonceAttribute, ClientProofAttribute] ->
- case parse_attribute(GS2ChannelBindingAttribute) of
- {$c, CVal} when (CVal == "biws") or (CVal == "eSws") ->
- %% biws is base64 for n,, => channelbinding not supported
- %% eSws is base64 for y,, => channelbinding supported by client only
- Nonce = State#state.client_nonce ++ State#state.server_nonce,
- case parse_attribute(NonceAttribute) of
- {$r, CompareNonce} when CompareNonce == Nonce ->
- case parse_attribute(ClientProofAttribute) of
- {$p, ClientProofB64} ->
- ClientProof = base64:decode(ClientProofB64),
- AuthMessage = State#state.auth_message ++ "," ++ string:substr(ClientIn, 1, string:str(ClientIn, ",p=")-1),
- ClientSignature = scram:client_signature(State#state.stored_key, AuthMessage),
- ClientKey = scram:client_key(ClientProof, ClientSignature),
- CompareStoredKey = scram:stored_key(ClientKey),
- if CompareStoredKey == State#state.stored_key ->
- ServerSignature = scram:server_signature(State#state.server_key, AuthMessage),
- {ok, [{username, State#state.username}], "v=" ++ base64:encode_to_string(ServerSignature)};
- true ->
- {error, "bad-auth"}
- end;
- _Else ->
- {error, "bad-protocol"}
- end;
- {$r, _} ->
- {error, "bad-nonce"};
- _Else ->
- {error, "bad-protocol"}
- end;
- _Else ->
- {error, "bad-protocol"}
+ case str:tokens(ClientIn, <<",">>) of
+ [GS2ChannelBindingAttribute, NonceAttribute,
+ ClientProofAttribute] ->
+ case parse_attribute(GS2ChannelBindingAttribute) of
+ {$c, CVal} when (CVal == <<"biws">>) or (CVal == <<"eSws">>) ->
+ %% biws is base64 for n,, => channelbinding not supported
+ %% eSws is base64 for y,, => channelbinding supported by client only
+ Nonce = <<(State#state.client_nonce)/binary,
+ (State#state.server_nonce)/binary>>,
+ case parse_attribute(NonceAttribute) of
+ {$r, CompareNonce} when CompareNonce == Nonce ->
+ case parse_attribute(ClientProofAttribute) of
+ {$p, ClientProofB64} ->
+ ClientProof = jlib:decode_base64(ClientProofB64),
+ AuthMessage =
+ iolist_to_binary(
+ [State#state.auth_message,
+ ",",
+ str:substr(ClientIn, 1,
+ str:str(ClientIn, <<",p=">>)
+ - 1)]),
+ ClientSignature =
+ scram:client_signature(State#state.stored_key,
+ AuthMessage),
+ ClientKey = scram:client_key(ClientProof,
+ ClientSignature),
+ CompareStoredKey = scram:stored_key(ClientKey),
+ if CompareStoredKey == State#state.stored_key ->
+ ServerSignature =
+ scram:server_signature(State#state.server_key,
+ AuthMessage),
+ {ok, [{username, State#state.username}],
+ <<"v=",
+ (jlib:encode_base64(ServerSignature))/binary>>};
+ true -> {error, <<"bad-auth">>}
+ end;
+ _Else -> {error, <<"bad-protocol">>}
+ end;
+ {$r, _} -> {error, <<"bad-nonce">>};
+ _Else -> {error, <<"bad-protocol">>}
end;
- _Else ->
- {error, "bad-protocol"}
- end.
+ _Else -> {error, <<"bad-protocol">>}
+ end;
+ _Else -> {error, <<"bad-protocol">>}
+ end.
parse_attribute(Attribute) ->
- AttributeLen = string:len(Attribute),
- if
- AttributeLen >= 3 ->
- SecondChar = lists:nth(2, Attribute),
- case is_alpha(lists:nth(1, Attribute)) of
- true ->
- if
- SecondChar == $= ->
- String = string:substr(Attribute, 3),
- {lists:nth(1, Attribute), String};
- true ->
- {error, "bad-format second char not equal sign"}
- end;
- _Else ->
- {error, "bad-format first char not a letter"}
- end;
- true ->
- {error, "bad-format attribute too short"}
- end.
+ AttributeLen = byte_size(Attribute),
+ if AttributeLen >= 3 ->
+ AttributeS = binary_to_list(Attribute),
+ SecondChar = lists:nth(2, AttributeS),
+ case is_alpha(lists:nth(1, AttributeS)) of
+ true ->
+ if SecondChar == $= ->
+ String = str:substr(Attribute, 3),
+ {lists:nth(1, AttributeS), String};
+ true -> {error, <<"bad-format second char not equal sign">>}
+ end;
+ _Else -> {error, <<"bad-format first char not a letter">>}
+ end;
+ true -> {error, <<"bad-format attribute too short">>}
+ end.
-unescape_username("") ->
- "";
+unescape_username(<<"">>) -> <<"">>;
unescape_username(EscapedUsername) ->
- Pos = string:str(EscapedUsername, "="),
- if
- Pos == 0 ->
- EscapedUsername;
- true ->
- Start = string:substr(EscapedUsername, 1, Pos-1),
- End = string:substr(EscapedUsername, Pos),
- EndLen = string:len(End),
- if
- EndLen < 3 ->
- error;
- true ->
- case string:substr(End, 1, 3) of
- "=2C" ->
- Start ++ "," ++ unescape_username(string:substr(End, 4));
- "=3D" ->
- Start ++ "=" ++ unescape_username(string:substr(End, 4));
- _Else ->
- error
- end
- end
- end.
-
-is_alpha(Char) when Char >= $a, Char =< $z ->
- true;
-is_alpha(Char) when Char >= $A, Char =< $Z ->
- true;
-is_alpha(_) ->
- false.
+ Pos = str:str(EscapedUsername, <<"=">>),
+ if Pos == 0 -> EscapedUsername;
+ true ->
+ Start = str:substr(EscapedUsername, 1, Pos - 1),
+ End = str:substr(EscapedUsername, Pos),
+ EndLen = byte_size(End),
+ if EndLen < 3 -> error;
+ true ->
+ case str:substr(End, 1, 3) of
+ <<"=2C">> ->
+ <<Start/binary, ",",
+ (unescape_username(str:substr(End, 4)))/binary>>;
+ <<"=3D">> ->
+ <<Start/binary, "=",
+ (unescape_username(str:substr(End, 4)))/binary>>;
+ _Else -> error
+ end
+ end
+ end.
+is_alpha(Char) when Char >= $a, Char =< $z -> true;
+is_alpha(Char) when Char >= $A, Char =< $Z -> true;
+is_alpha(_) -> false.
diff --git a/src/ejabberd.hrl b/src/ejabberd.hrl
index 6edd5b8de..d2832ee4d 100644
--- a/src/ejabberd.hrl
+++ b/src/ejabberd.hrl
@@ -21,44 +21,57 @@
%% This macro returns a string of the ejabberd version running, e.g. "2.3.4"
%% If the ejabberd application description isn't loaded, returns atom: undefined
--define(VERSION, element(2, application:get_key(ejabberd,vsn))).
+-define(VERSION, ejabberd_config:get_version()).
--define(MYHOSTS, ejabberd_config:get_global_option(hosts)).
--define(MYNAME, hd(ejabberd_config:get_global_option(hosts))).
--define(MYLANG, ejabberd_config:get_global_option(language)).
+-define(MYHOSTS, ejabberd_config:get_myhosts()).
--define(MSGS_DIR, "msgs").
--define(CONFIG_PATH, "ejabberd.cfg").
--define(LOG_PATH, "ejabberd.log").
+-define(MYNAME, hd(ejabberd_config:get_myhosts())).
--define(EJABBERD_URI, "http://www.process-one.net/en/ejabberd/").
+-define(MYLANG, ejabberd_config:get_mylang()).
+
+-define(MSGS_DIR, <<"msgs">>).
+
+-define(CONFIG_PATH, <<"ejabberd.cfg">>).
+
+-define(LOG_PATH, <<"ejabberd.log">>).
+
+-define(EJABBERD_URI, <<"http://www.process-one.net/en/ejabberd/">>).
-define(S2STIMEOUT, 600000).
%%-define(DBGFSM, true).
--record(scram, {storedkey, serverkey, salt, iterationcount}).
+-record(scram,
+ {storedkey = <<"">>,
+ serverkey = <<"">>,
+ salt = <<"">>,
+ iterationcount = 0 :: integer()}).
+
+-type scram() :: #scram{}.
+
-define(SCRAM_DEFAULT_ITERATION_COUNT, 4096).
%% ---------------------------------
%% Logging mechanism
%% Print in standard output
--define(PRINT(Format, Args),
- io:format(Format, Args)).
+-define(PRINT(Format, Args), io:format(Format, Args)).
-define(DEBUG(Format, Args),
- ejabberd_logger:debug_msg(?MODULE,?LINE,Format, Args)).
+ ejabberd_logger:debug_msg(?MODULE, ?LINE, Format,
+ Args)).
-define(INFO_MSG(Format, Args),
- ejabberd_logger:info_msg(?MODULE,?LINE,Format, Args)).
-
+ ejabberd_logger:info_msg(?MODULE, ?LINE, Format, Args)).
+
-define(WARNING_MSG(Format, Args),
- ejabberd_logger:warning_msg(?MODULE,?LINE,Format, Args)).
-
+ ejabberd_logger:warning_msg(?MODULE, ?LINE, Format,
+ Args)).
+
-define(ERROR_MSG(Format, Args),
- ejabberd_logger:error_msg(?MODULE,?LINE,Format, Args)).
+ ejabberd_logger:error_msg(?MODULE, ?LINE, Format,
+ Args)).
-define(CRITICAL_MSG(Format, Args),
- ejabberd_logger:critical_msg(?MODULE,?LINE,Format, Args)).
-
+ ejabberd_logger:critical_msg(?MODULE, ?LINE, Format,
+ Args)).
diff --git a/src/ejabberd_admin.erl b/src/ejabberd_admin.erl
index 031470c04..9fa95abf6 100644
--- a/src/ejabberd_admin.erl
+++ b/src/ejabberd_admin.erl
@@ -117,17 +117,17 @@ commands() ->
#ejabberd_commands{name = register, tags = [accounts],
desc = "Register a user",
module = ?MODULE, function = register,
- args = [{user, string}, {host, string}, {password, string}],
+ args = [{user, binary}, {host, binary}, {password, binary}],
result = {res, restuple}},
#ejabberd_commands{name = unregister, tags = [accounts],
desc = "Unregister a user",
module = ?MODULE, function = unregister,
- args = [{user, string}, {host, string}],
+ args = [{user, binary}, {host, binary}],
result = {res, restuple}},
#ejabberd_commands{name = registered_users, tags = [accounts],
desc = "List all registered users in HOST",
module = ?MODULE, function = registered_users,
- args = [{host, string}],
+ args = [{host, binary}],
result = {users, {list, {username, string}}}},
#ejabberd_commands{name = registered_vhosts, tags = [server],
desc = "List all registered vhosts in SERVER",
@@ -158,6 +158,11 @@ commands() ->
module = ejabberd_piefxis, function = export_host,
args = [{dir, string}, {host, string}], result = {res, rescode}},
+ #ejabberd_commands{name = export_odbc, tags = [mnesia, odbc],
+ desc = "Export all tables as SQL queries to a file",
+ module = ejd2odbc, function = export,
+ args = [{host, string}, {file, string}], result = {res, rescode}},
+
#ejabberd_commands{name = delete_expired_messages, tags = [purge],
desc = "Delete expired offline messages from database",
module = ?MODULE, function = delete_expired_messages,
@@ -296,11 +301,12 @@ stop_kindly(DelaySeconds, AnnouncementText) ->
ok.
send_service_message_all_mucs(Subject, AnnouncementText) ->
- Message = io_lib:format("~s~n~s", [Subject, AnnouncementText]),
+ Message = list_to_binary(
+ io_lib:format("~s~n~s", [Subject, AnnouncementText])),
lists:foreach(
fun(ServerHost) ->
MUCHost = gen_mod:get_module_opt_host(
- ServerHost, mod_muc, "conference.@HOST@"),
+ ServerHost, mod_muc, <<"conference.@HOST@">>),
mod_muc:broadcast_service_message(MUCHost, Message)
end,
?MYHOSTS).
@@ -320,6 +326,8 @@ update("all") ->
update(ModStr) ->
update_module(ModStr).
+update_module(ModuleNameBin) when is_binary(ModuleNameBin) ->
+ update_module(binary_to_list(ModuleNameBin));
update_module(ModuleNameString) ->
ModuleName = list_to_atom(ModuleNameString),
case ejabberd_update:update([ModuleName]) of
diff --git a/src/ejabberd_app.erl b/src/ejabberd_app.erl
index 562c4870f..393d7afb2 100644
--- a/src/ejabberd_app.erl
+++ b/src/ejabberd_app.erl
@@ -57,8 +57,6 @@ start(normal, _Args) ->
ejabberd_config:start(),
ejabberd_check:config(),
connect_nodes(),
- %% Loading ASN.1 driver explicitly to avoid races in LDAP
- catch asn1rt:load_driver(),
Sup = ejabberd_sup:start_link(),
ejabberd_rdbms:start(),
ejabberd_auth:start(),
@@ -135,41 +133,48 @@ db_init() ->
start_modules() ->
lists:foreach(
fun(Host) ->
- case ejabberd_config:get_local_option({modules, Host}) of
- undefined ->
- ok;
- Modules ->
- lists:foreach(
- fun({Module, Args}) ->
- gen_mod:start_module(Host, Module, Args)
- end, Modules)
- end
+ Modules = ejabberd_config:get_local_option(
+ {modules, Host},
+ fun(Mods) ->
+ lists:map(
+ fun({M, A}) when is_atom(M), is_list(A) ->
+ {M, A}
+ end, Mods)
+ end, []),
+ lists:foreach(
+ fun({Module, Args}) ->
+ gen_mod:start_module(Host, Module, Args)
+ end, Modules)
end, ?MYHOSTS).
%% Stop all the modules in all the hosts
stop_modules() ->
lists:foreach(
fun(Host) ->
- case ejabberd_config:get_local_option({modules, Host}) of
- undefined ->
- ok;
- Modules ->
- lists:foreach(
- fun({Module, _Args}) ->
- gen_mod:stop_module_keep_config(Host, Module)
- end, Modules)
- end
+ Modules = ejabberd_config:get_local_option(
+ {modules, Host},
+ fun(Mods) ->
+ lists:map(
+ fun({M, A}) when is_atom(M), is_list(A) ->
+ {M, A}
+ end, Mods)
+ end, []),
+ lists:foreach(
+ fun({Module, _Args}) ->
+ gen_mod:stop_module_keep_config(Host, Module)
+ end, Modules)
end, ?MYHOSTS).
connect_nodes() ->
- case ejabberd_config:get_local_option(cluster_nodes) of
- undefined ->
- ok;
- Nodes when is_list(Nodes) ->
- lists:foreach(fun(Node) ->
- net_kernel:connect_node(Node)
- end, Nodes)
- end.
+ Nodes = ejabberd_config:get_local_option(
+ cluster_nodes,
+ fun(Ns) ->
+ true = lists:all(fun is_atom/1, Ns),
+ Ns
+ end, []),
+ lists:foreach(fun(Node) ->
+ net_kernel:connect_node(Node)
+ end, Nodes).
%% @spec () -> string()
%% @doc Returns the full path to the ejabberd log file.
diff --git a/src/ejabberd_auth.erl b/src/ejabberd_auth.erl
index 7485f8234..298cdf1eb 100644
--- a/src/ejabberd_auth.erl
+++ b/src/ejabberd_auth.erl
@@ -27,32 +27,21 @@
%% TODO: Use the functions in ejabberd auth to add and remove users.
-module(ejabberd_auth).
+
-author('alexey@process-one.net').
%% External exports
--export([start/0,
- set_password/3,
- check_password/3,
- check_password/5,
- check_password_with_authmodule/3,
- check_password_with_authmodule/5,
- try_register/3,
- dirty_get_registered_users/0,
- get_vh_registered_users/1,
- get_vh_registered_users/2,
+-export([start/0, set_password/3, check_password/3,
+ check_password/5, check_password_with_authmodule/3,
+ check_password_with_authmodule/5, try_register/3,
+ dirty_get_registered_users/0, get_vh_registered_users/1,
+ get_vh_registered_users/2, export/1,
get_vh_registered_users_number/1,
- get_vh_registered_users_number/2,
- get_password/2,
- get_password_s/2,
- get_password_with_authmodule/2,
- is_user_exists/2,
- is_user_exists_in_other_modules/3,
- remove_user/2,
- remove_user/3,
- plain_password_required/1,
- store_type/1,
- entropy/1
- ]).
+ get_vh_registered_users_number/2, get_password/2,
+ get_password_s/2, get_password_with_authmodule/2,
+ is_user_exists/2, is_user_exists_in_other_modules/3,
+ remove_user/2, remove_user/3, plain_password_required/1,
+ store_type/1, entropy/1]).
-export([auth_modules/1]).
@@ -61,55 +50,80 @@
%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
-start() ->
- lists:foreach(
- fun(Host) ->
- lists:foreach(
- fun(M) ->
- M:start(Host)
- end, auth_modules(Host))
- end, ?MYHOSTS).
+-type opts() :: [{prefix, binary()} | {from, integer()} |
+ {to, integer()} | {limit, integer()} |
+ {offset, integer()}].
+
+-callback start(binary()) -> any().
+-callback plain_password_required() -> boolean().
+-callback store_type() -> plain | external | scram.
+-callback set_password(binary(), binary(), binary()) -> ok | {error, atom()}.
+-callback remove_user(binary(), binary()) -> any().
+-callback remove_user(binary(), binary(), binary()) -> any().
+-callback is_user_exists(binary(), binary()) -> boolean() | {error, atom()}.
+-callback check_password(binary(), binary(), binary()) -> boolean().
+-callback check_password(binary(), binary(), binary(), binary(),
+ fun((binary()) -> binary())) -> boolean().
+-callback try_register(binary(), binary(), binary()) -> {atomic, atom()} |
+ {error, atom()}.
+-callback dirty_get_registered_users() -> [{binary(), binary()}].
+-callback get_vh_registered_users(binary()) -> [{binary(), binary()}].
+-callback get_vh_registered_users(binary(), opts()) -> [{binary(), binary()}].
+-callback get_vh_registered_users_number(binary()) -> number().
+-callback get_vh_registered_users_number(binary(), opts()) -> number().
+-callback get_password(binary(), binary()) -> false | binary().
+-callback get_password_s(binary(), binary()) -> binary().
+start() ->
%% This is only executed by ejabberd_c2s for non-SASL auth client
+ lists:foreach(fun (Host) ->
+ lists:foreach(fun (M) -> M:start(Host) end,
+ auth_modules(Host))
+ end,
+ ?MYHOSTS).
+
plain_password_required(Server) ->
- lists:any(
- fun(M) ->
- M:plain_password_required()
- end, auth_modules(Server)).
+ lists:any(fun (M) -> M:plain_password_required() end,
+ auth_modules(Server)).
store_type(Server) ->
- lists:foldl(
- fun(_, external) ->
- external;
- (M, scram) ->
- case M:store_type() of
- external ->
- external;
- _Else ->
- scram
- end;
- (M, plain) ->
- M:store_type()
- end, plain, auth_modules(Server)).
-
%% @doc Check if the user and password can login in server.
%% @spec (User::string(), Server::string(), Password::string()) ->
%% true | false
+ lists:foldl(fun (_, external) -> external;
+ (M, scram) ->
+ case M:store_type() of
+ external -> external;
+ _Else -> scram
+ end;
+ (M, plain) -> M:store_type()
+ end,
+ plain, auth_modules(Server)).
+
+-spec check_password(binary(), binary(), binary()) -> boolean().
+
check_password(User, Server, Password) ->
- case check_password_with_authmodule(User, Server, Password) of
- {true, _AuthModule} -> true;
- false -> false
+ case check_password_with_authmodule(User, Server,
+ Password)
+ of
+ {true, _AuthModule} -> true;
+ false -> false
end.
%% @doc Check if the user and password can login in server.
%% @spec (User::string(), Server::string(), Password::string(),
%% Digest::string(), DigestGen::function()) ->
%% true | false
-check_password(User, Server, Password, Digest, DigestGen) ->
- case check_password_with_authmodule(User, Server, Password,
- Digest, DigestGen) of
- {true, _AuthModule} -> true;
- false -> false
+-spec check_password(binary(), binary(), binary(), binary(),
+ fun((binary()) -> binary())) -> boolean().
+
+check_password(User, Server, Password, Digest,
+ DigestGen) ->
+ case check_password_with_authmodule(User, Server,
+ Password, Digest, DigestGen)
+ of
+ {true, _AuthModule} -> true;
+ false -> false
end.
%% @doc Check if the user and password can login in server.
@@ -122,199 +136,224 @@ check_password(User, Server, Password, Digest, DigestGen) ->
%% AuthModule = ejabberd_auth_anonymous | ejabberd_auth_external
%% | ejabberd_auth_internal | ejabberd_auth_ldap
%% | ejabberd_auth_odbc | ejabberd_auth_pam
-check_password_with_authmodule(User, Server, Password) ->
- check_password_loop(auth_modules(Server), [User, Server, Password]).
+-spec check_password_with_authmodule(binary(), binary(), binary()) -> false |
+ {true, atom()}.
-check_password_with_authmodule(User, Server, Password, Digest, DigestGen) ->
- check_password_loop(auth_modules(Server), [User, Server, Password,
- Digest, DigestGen]).
+check_password_with_authmodule(User, Server,
+ Password) ->
+ check_password_loop(auth_modules(Server),
+ [User, Server, Password]).
-check_password_loop([], _Args) ->
- false;
+-spec check_password_with_authmodule(binary(), binary(), binary(), binary(),
+ fun((binary()) -> binary())) -> false |
+ {true, atom()}.
+
+check_password_with_authmodule(User, Server, Password,
+ Digest, DigestGen) ->
+ check_password_loop(auth_modules(Server),
+ [User, Server, Password, Digest, DigestGen]).
+
+check_password_loop([], _Args) -> false;
check_password_loop([AuthModule | AuthModules], Args) ->
case apply(AuthModule, check_password, Args) of
- true ->
- {true, AuthModule};
- false ->
- check_password_loop(AuthModules, Args)
+ true -> {true, AuthModule};
+ false -> check_password_loop(AuthModules, Args)
end.
+-spec set_password(binary(), binary(), binary()) -> ok |
+ {error, atom()}.
%% @spec (User::string(), Server::string(), Password::string()) ->
%% ok | {error, ErrorType}
%% where ErrorType = empty_password | not_allowed | invalid_jid
-set_password(_User, _Server, "") ->
- %% We do not allow empty password
+set_password(_User, _Server, <<"">>) ->
{error, empty_password};
set_password(User, Server, Password) ->
- lists:foldl(
- fun(M, {error, _}) ->
- M:set_password(User, Server, Password);
- (_M, Res) ->
- Res
- end, {error, not_allowed}, auth_modules(Server)).
-
%% @spec (User, Server, Password) -> {atomic, ok} | {atomic, exists} | {error, not_allowed}
-try_register(_User, _Server, "") ->
- %% We do not allow empty password
- {error, not_allowed};
+ lists:foldl(fun (M, {error, _}) ->
+ M:set_password(User, Server, Password);
+ (_M, Res) -> Res
+ end,
+ {error, not_allowed}, auth_modules(Server)).
+
+-spec try_register(binary(), binary(), binary()) -> {atomic, atom()} |
+ {error, atom()}.
+
+try_register(_User, _Server, <<"">>) ->
+ {error, not_allowed};
try_register(User, Server, Password) ->
- case is_user_exists(User,Server) of
- true ->
- {atomic, exists};
- false ->
- case lists:member(jlib:nameprep(Server), ?MYHOSTS) of
- true ->
- Res = lists:foldl(
- fun(_M, {atomic, ok} = Res) ->
- Res;
- (M, _) ->
- M:try_register(User, Server, Password)
- end, {error, not_allowed}, auth_modules(Server)),
- case Res of
- {atomic, ok} ->
- ejabberd_hooks:run(register_user, Server,
- [User, Server]),
- {atomic, ok};
- _ -> Res
- end;
- false ->
- {error, not_allowed}
- end
+ case is_user_exists(User, Server) of
+ true -> {atomic, exists};
+ false ->
+ case lists:member(jlib:nameprep(Server), ?MYHOSTS) of
+ true ->
+ Res = lists:foldl(fun (_M, {atomic, ok} = Res) -> Res;
+ (M, _) ->
+ M:try_register(User, Server, Password)
+ end,
+ {error, not_allowed}, auth_modules(Server)),
+ case Res of
+ {atomic, ok} ->
+ ejabberd_hooks:run(register_user, Server,
+ [User, Server]),
+ {atomic, ok};
+ _ -> Res
+ end;
+ false -> {error, not_allowed}
+ end
end.
%% Registered users list do not include anonymous users logged
+-spec dirty_get_registered_users() -> [{binary(), binary()}].
+
dirty_get_registered_users() ->
- lists:flatmap(
- fun(M) ->
- M:dirty_get_registered_users()
- end, auth_modules()).
+ lists:flatmap(fun (M) -> M:dirty_get_registered_users()
+ end,
+ auth_modules()).
+
+-spec get_vh_registered_users(binary()) -> [{binary(), binary()}].
%% Registered users list do not include anonymous users logged
get_vh_registered_users(Server) ->
- lists:flatmap(
- fun(M) ->
- M:get_vh_registered_users(Server)
- end, auth_modules(Server)).
+ lists:flatmap(fun (M) ->
+ M:get_vh_registered_users(Server)
+ end,
+ auth_modules(Server)).
+
+-spec get_vh_registered_users(binary(), opts()) -> [{binary(), binary()}].
get_vh_registered_users(Server, Opts) ->
- lists:flatmap(
- fun(M) ->
- case erlang:function_exported(
- M, get_vh_registered_users, 2) of
- true ->
- M:get_vh_registered_users(Server, Opts);
- false ->
- M:get_vh_registered_users(Server)
- end
- end, auth_modules(Server)).
+ lists:flatmap(fun (M) ->
+ case erlang:function_exported(M,
+ get_vh_registered_users,
+ 2)
+ of
+ true -> M:get_vh_registered_users(Server, Opts);
+ false -> M:get_vh_registered_users(Server)
+ end
+ end,
+ auth_modules(Server)).
get_vh_registered_users_number(Server) ->
- lists:sum(
- lists:map(
- fun(M) ->
- case erlang:function_exported(
- M, get_vh_registered_users_number, 1) of
- true ->
- M:get_vh_registered_users_number(Server);
- false ->
- length(M:get_vh_registered_users(Server))
- end
- end, auth_modules(Server))).
+ lists:sum(lists:map(fun (M) ->
+ case erlang:function_exported(M,
+ get_vh_registered_users_number,
+ 1)
+ of
+ true ->
+ M:get_vh_registered_users_number(Server);
+ false ->
+ length(M:get_vh_registered_users(Server))
+ end
+ end,
+ auth_modules(Server))).
+
+-spec get_vh_registered_users_number(binary(), opts()) -> number().
get_vh_registered_users_number(Server, Opts) ->
- lists:sum(
- lists:map(
- fun(M) ->
- case erlang:function_exported(
- M, get_vh_registered_users_number, 2) of
- true ->
- M:get_vh_registered_users_number(Server, Opts);
- false ->
- length(M:get_vh_registered_users(Server))
- end
- end, auth_modules(Server))).
-
%% @doc Get the password of the user.
%% @spec (User::string(), Server::string()) -> Password::string()
+ lists:sum(lists:map(fun (M) ->
+ case erlang:function_exported(M,
+ get_vh_registered_users_number,
+ 2)
+ of
+ true ->
+ M:get_vh_registered_users_number(Server,
+ Opts);
+ false ->
+ length(M:get_vh_registered_users(Server))
+ end
+ end,
+ auth_modules(Server))).
+
+-spec get_password(binary(), binary()) -> false | binary().
+
get_password(User, Server) ->
- lists:foldl(
- fun(M, false) ->
- M:get_password(User, Server);
- (_M, Password) ->
- Password
- end, false, auth_modules(Server)).
+ lists:foldl(fun (M, false) ->
+ M:get_password(User, Server);
+ (_M, Password) -> Password
+ end,
+ false, auth_modules(Server)).
+
+-spec get_password_s(binary(), binary()) -> binary().
get_password_s(User, Server) ->
case get_password(User, Server) of
- false ->
- "";
- Password when is_list(Password) ->
- Password;
- _ ->
- ""
+ false -> <<"">>;
+ Password -> Password
end.
%% @doc Get the password of the user and the auth module.
%% @spec (User::string(), Server::string()) ->
%% {Password::string(), AuthModule::atom()} | {false, none}
-get_password_with_authmodule(User, Server) ->
- lists:foldl(
- fun(M, {false, _}) ->
- {M:get_password(User, Server), M};
- (_M, {Password, AuthModule}) ->
- {Password, AuthModule}
- end, {false, none}, auth_modules(Server)).
+-spec get_password_with_authmodule(binary(), binary()) -> {false | binary(), atom()}.
+get_password_with_authmodule(User, Server) ->
%% Returns true if the user exists in the DB or if an anonymous user is logged
%% under the given name
-is_user_exists(User, Server) ->
- lists:any(
- fun(M) ->
- case M:is_user_exists(User, Server) of
- {error, Error} ->
- ?ERROR_MSG("The authentication module ~p returned an "
- "error~nwhen checking user ~p in server ~p~n"
- "Error message: ~p",
- [M, User, Server, Error]),
- false;
- Else ->
- Else
- end
- end, auth_modules(Server)).
+ lists:foldl(fun (M, {false, _}) ->
+ {M:get_password(User, Server), M};
+ (_M, {Password, AuthModule}) -> {Password, AuthModule}
+ end,
+ {false, none}, auth_modules(Server)).
+-spec is_user_exists(binary(), binary()) -> boolean().
+
+is_user_exists(User, Server) ->
%% Check if the user exists in all authentications module except the module
%% passed as parameter
%% @spec (Module::atom(), User, Server) -> true | false | maybe
+ lists:any(fun (M) ->
+ case M:is_user_exists(User, Server) of
+ {error, Error} ->
+ ?ERROR_MSG("The authentication module ~p returned "
+ "an error~nwhen checking user ~p in server "
+ "~p~nError message: ~p",
+ [M, User, Server, Error]),
+ false;
+ Else -> Else
+ end
+ end,
+ auth_modules(Server)).
+
+-spec is_user_exists_in_other_modules(atom(), binary(), binary()) -> boolean() | maybe.
+
is_user_exists_in_other_modules(Module, User, Server) ->
- is_user_exists_in_other_modules_loop(
- auth_modules(Server)--[Module],
- User, Server).
-is_user_exists_in_other_modules_loop([], _User, _Server) ->
+ is_user_exists_in_other_modules_loop(auth_modules(Server)
+ -- [Module],
+ User, Server).
+
+is_user_exists_in_other_modules_loop([], _User,
+ _Server) ->
false;
-is_user_exists_in_other_modules_loop([AuthModule|AuthModules], User, Server) ->
+is_user_exists_in_other_modules_loop([AuthModule
+ | AuthModules],
+ User, Server) ->
case AuthModule:is_user_exists(User, Server) of
- true ->
- true;
- false ->
- is_user_exists_in_other_modules_loop(AuthModules, User, Server);
- {error, Error} ->
- ?DEBUG("The authentication module ~p returned an error~nwhen "
- "checking user ~p in server ~p~nError message: ~p",
- [AuthModule, User, Server, Error]),
- maybe
+ true -> true;
+ false ->
+ is_user_exists_in_other_modules_loop(AuthModules, User,
+ Server);
+ {error, Error} ->
+ ?DEBUG("The authentication module ~p returned "
+ "an error~nwhen checking user ~p in server "
+ "~p~nError message: ~p",
+ [AuthModule, User, Server, Error]),
+ maybe
end.
+-spec remove_user(binary(), binary()) -> ok.
%% @spec (User, Server) -> ok
%% @doc Remove user.
%% Note: it may return ok even if there was some problem removing the user.
remove_user(User, Server) ->
- lists:foreach(
- fun(M) ->
- M:remove_user(User, Server)
- end, auth_modules(Server)),
- ejabberd_hooks:run(remove_user, jlib:nameprep(Server), [User, Server]),
+ lists:foreach(fun (M) -> M:remove_user(User, Server)
+ end,
+ auth_modules(Server)),
+ ejabberd_hooks:run(remove_user, jlib:nameprep(Server),
+ [User, Server]),
ok.
%% @spec (User, Server, Password) -> ok | not_exists | not_allowed | bad_request | error
@@ -322,41 +361,49 @@ remove_user(User, Server) ->
%% The removal is attempted in each auth method provided:
%% when one returns 'ok' the loop stops;
%% if no method returns 'ok' then it returns the error message indicated by the last method attempted.
+-spec remove_user(binary(), binary(), binary()) -> any().
+
remove_user(User, Server, Password) ->
- R = lists:foldl(
- fun(_M, ok = Res) ->
- Res;
- (M, _) ->
- M:remove_user(User, Server, Password)
- end, error, auth_modules(Server)),
+ R = lists:foldl(fun (_M, ok = Res) -> Res;
+ (M, _) -> M:remove_user(User, Server, Password)
+ end,
+ error, auth_modules(Server)),
case R of
- ok -> ejabberd_hooks:run(remove_user, jlib:nameprep(Server), [User, Server]);
- _ -> none
+ ok ->
+ ejabberd_hooks:run(remove_user, jlib:nameprep(Server),
+ [User, Server]);
+ _ -> none
end,
R.
%% @spec (IOList) -> non_negative_float()
%% @doc Calculate informational entropy.
-entropy(IOList) ->
- case binary_to_list(iolist_to_binary(IOList)) of
- "" ->
- 0.0;
- S ->
- Set = lists:foldl(
- fun(C, [Digit, Printable, LowLetter, HiLetter, Other]) ->
- if C >= $a, C =< $z ->
- [Digit, Printable, 26, HiLetter, Other];
- C >= $0, C =< $9 ->
- [9, Printable, LowLetter, HiLetter, Other];
- C >= $A, C =< $Z ->
- [Digit, Printable, LowLetter, 26, Other];
- C >= 16#21, C =< 16#7e ->
- [Digit, 33, LowLetter, HiLetter, Other];
- true ->
- [Digit, Printable, LowLetter, HiLetter, 128]
- end
- end, [0, 0, 0, 0, 0], S),
- length(S) * math:log(lists:sum(Set))/math:log(2)
+entropy(B) ->
+ case binary_to_list(B) of
+ "" -> 0.0;
+ S ->
+ Set = lists:foldl(fun (C,
+ [Digit, Printable, LowLetter, HiLetter,
+ Other]) ->
+ if C >= $a, C =< $z ->
+ [Digit, Printable, 26, HiLetter,
+ Other];
+ C >= $0, C =< $9 ->
+ [9, Printable, LowLetter, HiLetter,
+ Other];
+ C >= $A, C =< $Z ->
+ [Digit, Printable, LowLetter, 26,
+ Other];
+ C >= 33, C =< 126 ->
+ [Digit, 33, LowLetter, HiLetter,
+ Other];
+ true ->
+ [Digit, Printable, LowLetter,
+ HiLetter, 128]
+ end
+ end,
+ [0, 0, 0, 0, 0], S),
+ length(S) * math:log(lists:sum(Set)) / math:log(2)
end.
%%%----------------------------------------------------------------------
@@ -365,19 +412,27 @@ entropy(IOList) ->
%% Return the lists of all the auth modules actually used in the
%% configuration
auth_modules() ->
- lists:usort(
- lists:flatmap(
- fun(Server) ->
- auth_modules(Server)
- end, ?MYHOSTS)).
+ lists:usort(lists:flatmap(fun (Server) ->
+ auth_modules(Server)
+ end,
+ ?MYHOSTS)).
+
+-spec auth_modules(binary()) -> [atom()].
%% Return the list of authenticated modules for a given host
auth_modules(Server) ->
LServer = jlib:nameprep(Server),
- Method = ejabberd_config:get_local_option({auth_method, LServer}),
- Methods = if
- Method == undefined -> [];
- is_list(Method) -> Method;
- is_atom(Method) -> [Method]
- end,
- [list_to_atom("ejabberd_auth_" ++ atom_to_list(M)) || M <- Methods].
+ Methods = ejabberd_config:get_local_option(
+ {auth_method, LServer},
+ fun(V) when is_list(V) ->
+ true = lists:all(fun is_atom/1, V),
+ V;
+ (V) when is_atom(V) ->
+ [V]
+ end, []),
+ [jlib:binary_to_atom(<<"ejabberd_auth_",
+ (jlib:atom_to_binary(M))/binary>>)
+ || M <- Methods].
+
+export(Server) ->
+ ejabberd_auth_internal:export(Server).
diff --git a/src/ejabberd_auth_anonymous.erl b/src/ejabberd_auth_anonymous.erl
index ebdbf9680..c19effabe 100644
--- a/src/ejabberd_auth_anonymous.erl
+++ b/src/ejabberd_auth_anonymous.erl
@@ -39,27 +39,24 @@
%% Function used by ejabberd_auth:
--export([login/2,
- set_password/3,
- check_password/3,
- check_password/5,
- try_register/3,
- dirty_get_registered_users/0,
- get_vh_registered_users/1,
- get_password/2,
- get_password/3,
- is_user_exists/2,
- remove_user/2,
- remove_user/3,
- store_type/0,
+-export([login/2, set_password/3, check_password/3,
+ check_password/5, try_register/3,
+ dirty_get_registered_users/0, get_vh_registered_users/1,
+ get_vh_registered_users/2, get_vh_registered_users_number/1,
+ get_vh_registered_users_number/2, get_password_s/2,
+ get_password/2, get_password/3, is_user_exists/2,
+ remove_user/2, remove_user/3, store_type/0,
plain_password_required/0]).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
--record(anonymous, {us, sid}).
%% Create the anonymous table if at least one virtual host has anonymous features enabled
%% Register to login / logout events
+-record(anonymous, {us = {<<"">>, <<"">>} :: {binary(), binary()},
+ sid = {now(), self()} :: ejabberd_sm:sid()}).
+
start(Host) ->
%% TODO: Check cluster mode
mnesia:create_table(anonymous, [{ram_copies, [node()]},
@@ -80,13 +77,13 @@ allow_anonymous(Host) ->
%% anonymous protocol can be: sasl_anon|login_anon|both
is_sasl_anonymous_enabled(Host) ->
case allow_anonymous(Host) of
- false -> false;
- true ->
- case anonymous_protocol(Host) of
- sasl_anon -> true;
- both -> true;
- _Other -> false
- end
+ false -> false;
+ true ->
+ case anonymous_protocol(Host) of
+ sasl_anon -> true;
+ both -> true;
+ _Other -> false
+ end
end.
%% Return true if anonymous login is enabled on the server
@@ -94,30 +91,33 @@ is_sasl_anonymous_enabled(Host) ->
%% clients that do not support anonymous login)
is_login_anonymous_enabled(Host) ->
case allow_anonymous(Host) of
- false -> false;
- true ->
- case anonymous_protocol(Host) of
- login_anon -> true;
- both -> true;
- _Other -> false
- end
+ false -> false;
+ true ->
+ case anonymous_protocol(Host) of
+ login_anon -> true;
+ both -> true;
+ _Other -> false
+ end
end.
%% Return the anonymous protocol to use: sasl_anon|login_anon|both
%% defaults to login_anon
anonymous_protocol(Host) ->
- case ejabberd_config:get_local_option({anonymous_protocol, Host}) of
- sasl_anon -> sasl_anon;
- login_anon -> login_anon;
- both -> both;
- _Other -> sasl_anon
- end.
+ ejabberd_config:get_local_option(
+ {anonymous_protocol, Host},
+ fun(sasl_anon) -> sasl_anon;
+ (login_anon) -> login_anon;
+ (both) -> both
+ end,
+ sasl_anon).
%% Return true if multiple connections have been allowed in the config file
%% defaults to false
allow_multiple_connections(Host) ->
ejabberd_config:get_local_option(
- {allow_multiple_connections, Host}) =:= true.
+ {allow_multiple_connections, Host},
+ fun(V) when is_boolean(V) -> V end,
+ false).
%% Check if user exist in the anonymus database
anonymous_user_exist(User, Server) ->
@@ -134,36 +134,39 @@ anonymous_user_exist(User, Server) ->
%% Remove connection from Mnesia tables
remove_connection(SID, LUser, LServer) ->
US = {LUser, LServer},
- F = fun() ->
- mnesia:delete_object({anonymous, US, SID})
- end,
+ F = fun () -> mnesia:delete_object({anonymous, US, SID})
+ end,
mnesia:transaction(F).
%% Register connection
-register_connection(SID, #jid{luser = LUser, lserver = LServer}, Info) ->
- AuthModule = xml:get_attr_s(auth_module, Info),
- case AuthModule == ?MODULE of
- true ->
- ejabberd_hooks:run(register_user, LServer, [LUser, LServer]),
- US = {LUser, LServer},
- mnesia:sync_dirty(
- fun() -> mnesia:write(#anonymous{us = US, sid=SID})
- end);
- false ->
- ok
+register_connection(SID,
+ #jid{luser = LUser, lserver = LServer}, Info) ->
+ AuthModule = list_to_atom(binary_to_list(xml:get_attr_s(<<"auth_module">>, Info))),
+ case AuthModule == (?MODULE) of
+ true ->
+ ejabberd_hooks:run(register_user, LServer,
+ [LUser, LServer]),
+ US = {LUser, LServer},
+ mnesia:sync_dirty(fun () ->
+ mnesia:write(#anonymous{us = US,
+ sid = SID})
+ end);
+ false -> ok
end.
%% Remove an anonymous user from the anonymous users table
-unregister_connection(SID, #jid{luser = LUser, lserver = LServer}, _) ->
- purge_hook(anonymous_user_exist(LUser, LServer),
- LUser, LServer),
+unregister_connection(SID,
+ #jid{luser = LUser, lserver = LServer}, _) ->
+ purge_hook(anonymous_user_exist(LUser, LServer), LUser,
+ LServer),
remove_connection(SID, LUser, LServer).
%% Launch the hook to purge user data only for anonymous users
purge_hook(false, _LUser, _LServer) ->
ok;
purge_hook(true, LUser, LServer) ->
- ejabberd_hooks:run(anonymous_purge_hook, LServer, [LUser, LServer]).
+ ejabberd_hooks:run(anonymous_purge_hook, LServer,
+ [LUser, LServer]).
%% ---------------------------------
%% Specific anonymous auth functions
@@ -172,41 +175,42 @@ purge_hook(true, LUser, LServer) ->
%% When anonymous login is enabled, check the password for permenant users
%% before allowing access
check_password(User, Server, Password) ->
- check_password(User, Server, Password, undefined, undefined).
-check_password(User, Server, _Password, _Digest, _DigestGen) ->
- %% We refuse login for registered accounts (They cannot logged but
- %% they however are "reserved")
- case ejabberd_auth:is_user_exists_in_other_modules(?MODULE,
- User, Server) of
- %% If user exists in other module, reject anonnymous authentication
- true -> false;
- %% If we are not sure whether the user exists in other module, reject anon auth
- maybe -> false;
- false -> login(User, Server)
+ check_password(User, Server, Password, undefined,
+ undefined).
+
+check_password(User, Server, _Password, _Digest,
+ _DigestGen) ->
+ case
+ ejabberd_auth:is_user_exists_in_other_modules(?MODULE,
+ User, Server)
+ of
+ %% If user exists in other module, reject anonnymous authentication
+ true -> false;
+ %% If we are not sure whether the user exists in other module, reject anon auth
+ maybe -> false;
+ false -> login(User, Server)
end.
login(User, Server) ->
case is_login_anonymous_enabled(Server) of
- false -> false;
- true ->
- case anonymous_user_exist(User, Server) of
- %% Reject the login if an anonymous user with the same login
- %% is already logged and if multiple login has not been enable
- %% in the config file.
- true -> allow_multiple_connections(Server);
- %% Accept login and add user to the anonymous table
- false -> true
- end
+ false -> false;
+ true ->
+ case anonymous_user_exist(User, Server) of
+ %% Reject the login if an anonymous user with the same login
+ %% is already logged and if multiple login has not been enable
+ %% in the config file.
+ true -> allow_multiple_connections(Server);
+ %% Accept login and add user to the anonymous table
+ false -> true
+ end
end.
%% When anonymous login is enabled, check that the user is permanent before
%% changing its password
set_password(User, Server, _Password) ->
case anonymous_user_exist(User, Server) of
- true ->
- ok;
- false ->
- {error, not_allowed}
+ true -> ok;
+ false -> {error, not_allowed}
end.
%% When anonymous login is enabled, check if permanent users are allowed on
@@ -214,25 +218,42 @@ set_password(User, Server, _Password) ->
try_register(_User, _Server, _Password) ->
{error, not_allowed}.
-dirty_get_registered_users() ->
- [].
+dirty_get_registered_users() -> [].
get_vh_registered_users(Server) ->
- [{U, S} || {U, S, _R} <- ejabberd_sm:get_vh_session_list(Server)].
+ [{U, S}
+ || {U, S, _R}
+ <- ejabberd_sm:get_vh_session_list(Server)].
+get_vh_registered_users(Server, _) ->
+ get_vh_registered_users(Server).
+
+get_vh_registered_users_number(Server) ->
+ length(get_vh_registered_users(Server)).
+
+get_vh_registered_users_number(Server, _) ->
+ get_vh_registered_users_number(Server).
%% Return password of permanent user or false for anonymous users
get_password(User, Server) ->
- get_password(User, Server, "").
+ get_password(User, Server, <<"">>).
get_password(User, Server, DefaultValue) ->
- case anonymous_user_exist(User, Server) or login(User, Server) of
- %% We return the default value if the user is anonymous
- true ->
- DefaultValue;
- %% We return the permanent user password otherwise
- false ->
- false
+ case anonymous_user_exist(User, Server) or
+ login(User, Server)
+ of
+ %% We return the default value if the user is anonymous
+ true -> DefaultValue;
+ %% We return the permanent user password otherwise
+ false -> false
+ end.
+
+get_password_s(User, Server) ->
+ case get_password(User, Server) of
+ false ->
+ <<"">>;
+ Password ->
+ Password
end.
%% Returns true if the user exists in the DB or if an anonymous user is logged
@@ -240,14 +261,11 @@ get_password(User, Server, DefaultValue) ->
is_user_exists(User, Server) ->
anonymous_user_exist(User, Server).
-remove_user(_User, _Server) ->
- {error, not_allowed}.
+remove_user(_User, _Server) -> {error, not_allowed}.
-remove_user(_User, _Server, _Password) ->
- not_allowed.
+remove_user(_User, _Server, _Password) -> not_allowed.
-plain_password_required() ->
- false.
+plain_password_required() -> false.
store_type() ->
plain.
diff --git a/src/ejabberd_auth_external.erl b/src/ejabberd_auth_external.erl
index 08f2856ac..8ae6a1df5 100644
--- a/src/ejabberd_auth_external.erl
+++ b/src/ejabberd_auth_external.erl
@@ -25,27 +25,21 @@
%%%----------------------------------------------------------------------
-module(ejabberd_auth_external).
+
-author('alexey@process-one.net').
+-behaviour(ejabberd_auth).
+
%% External exports
--export([start/1,
- set_password/3,
- check_password/3,
- check_password/5,
- try_register/3,
- dirty_get_registered_users/0,
- get_vh_registered_users/1,
+-export([start/1, set_password/3, check_password/3,
+ check_password/5, try_register/3,
+ dirty_get_registered_users/0, get_vh_registered_users/1,
get_vh_registered_users/2,
get_vh_registered_users_number/1,
- get_vh_registered_users_number/2,
- get_password/2,
- get_password_s/2,
- is_user_exists/2,
- remove_user/2,
- remove_user/3,
- store_type/0,
- plain_password_required/0
- ]).
+ get_vh_registered_users_number/2, get_password/2,
+ get_password_s/2, is_user_exists/2, remove_user/2,
+ remove_user/3, store_type/0,
+ plain_password_required/0]).
-include("ejabberd.hrl").
@@ -53,55 +47,59 @@
%%% API
%%%----------------------------------------------------------------------
start(Host) ->
- extauth:start(
- Host, ejabberd_config:get_local_option({extauth_program, Host})),
+ Cmd = ejabberd_config:get_local_option(
+ {extauth_program, Host},
+ fun(V) ->
+ binary_to_list(iolist_to_binary(V))
+ end,
+ "extauth"),
+ extauth:start(Host, Cmd),
case check_cache_last_options(Host) of
- cache ->
- ok = ejabberd_auth_internal:start(Host);
- no_cache ->
- ok
+ cache -> ok = ejabberd_auth_internal:start(Host);
+ no_cache -> ok
end.
check_cache_last_options(Server) ->
- %% if extauth_cache is enabled, then a mod_last module must also be enabled
case get_cache_option(Server) of
- false -> no_cache;
- {true, _CacheTime} ->
- case get_mod_last_configured(Server) of
- no_mod_last ->
- ?ERROR_MSG("In host ~p extauth is used, extauth_cache is enabled but "
- "mod_last is not enabled.", [Server]),
- no_cache;
- _ -> cache
- end
+ false -> no_cache;
+ {true, _CacheTime} ->
+ case get_mod_last_configured(Server) of
+ no_mod_last ->
+ ?ERROR_MSG("In host ~p extauth is used, extauth_cache "
+ "is enabled but mod_last is not enabled.",
+ [Server]),
+ no_cache;
+ _ -> cache
+ end
end.
-plain_password_required() ->
- true.
+plain_password_required() -> true.
-store_type() ->
- external.
+store_type() -> external.
check_password(User, Server, Password) ->
case get_cache_option(Server) of
- false -> check_password_extauth(User, Server, Password);
- {true, CacheTime} -> check_password_cache(User, Server, Password, CacheTime)
+ false -> check_password_extauth(User, Server, Password);
+ {true, CacheTime} ->
+ check_password_cache(User, Server, Password, CacheTime)
end.
-check_password(User, Server, Password, _Digest, _DigestGen) ->
+check_password(User, Server, Password, _Digest,
+ _DigestGen) ->
check_password(User, Server, Password).
set_password(User, Server, Password) ->
case extauth:set_password(User, Server, Password) of
- true -> set_password_internal(User, Server, Password),
- ok;
- _ -> {error, unknown_problem}
+ true ->
+ set_password_internal(User, Server, Password), ok;
+ _ -> {error, unknown_problem}
end.
try_register(User, Server, Password) ->
case get_cache_option(Server) of
- false -> try_register_extauth(User, Server, Password);
- {true, _CacheTime} -> try_register_external_cache(User, Server, Password)
+ false -> try_register_extauth(User, Server, Password);
+ {true, _CacheTime} ->
+ try_register_external_cache(User, Server, Password)
end.
dirty_get_registered_users() ->
@@ -110,56 +108,60 @@ dirty_get_registered_users() ->
get_vh_registered_users(Server) ->
ejabberd_auth_internal:get_vh_registered_users(Server).
-get_vh_registered_users(Server, Data) ->
- ejabberd_auth_internal:get_vh_registered_users(Server, Data).
+get_vh_registered_users(Server, Data) ->
+ ejabberd_auth_internal:get_vh_registered_users(Server,
+ Data).
get_vh_registered_users_number(Server) ->
ejabberd_auth_internal:get_vh_registered_users_number(Server).
get_vh_registered_users_number(Server, Data) ->
- ejabberd_auth_internal:get_vh_registered_users_number(Server, Data).
+ ejabberd_auth_internal:get_vh_registered_users_number(Server,
+ Data).
%% The password can only be returned if cache is enabled, cached info exists and is fresh enough.
get_password(User, Server) ->
case get_cache_option(Server) of
- false -> false;
- {true, CacheTime} -> get_password_cache(User, Server, CacheTime)
+ false -> false;
+ {true, CacheTime} ->
+ get_password_cache(User, Server, CacheTime)
end.
get_password_s(User, Server) ->
case get_password(User, Server) of
- false -> [];
- Other -> Other
+ false -> <<"">>;
+ Other -> Other
end.
%% @spec (User, Server) -> true | false | {error, Error}
is_user_exists(User, Server) ->
try extauth:is_user_exists(User, Server) of
- Res -> Res
+ Res -> Res
catch
- _:Error -> {error, Error}
+ _:Error -> {error, Error}
end.
remove_user(User, Server) ->
case extauth:remove_user(User, Server) of
- false -> false;
- true ->
- case get_cache_option(Server) of
- false -> false;
- {true, _CacheTime} ->
- ejabberd_auth_internal:remove_user(User, Server)
- end
+ false -> false;
+ true ->
+ case get_cache_option(Server) of
+ false -> false;
+ {true, _CacheTime} ->
+ ejabberd_auth_internal:remove_user(User, Server)
+ end
end.
remove_user(User, Server, Password) ->
case extauth:remove_user(User, Server, Password) of
- false -> false;
- true ->
- case get_cache_option(Server) of
- false -> false;
- {true, _CacheTime} ->
- ejabberd_auth_internal:remove_user(User, Server, Password)
- end
+ false -> false;
+ true ->
+ case get_cache_option(Server) of
+ false -> false;
+ {true, _CacheTime} ->
+ ejabberd_auth_internal:remove_user(User, Server,
+ Password)
+ end
end.
%%%
@@ -168,45 +170,50 @@ remove_user(User, Server, Password) ->
%% @spec (Host::string()) -> false | {true, CacheTime::integer()}
get_cache_option(Host) ->
- case ejabberd_config:get_local_option({extauth_cache, Host}) of
- CacheTime when is_integer(CacheTime) -> {true, CacheTime};
- _ -> false
+ case ejabberd_config:get_local_option(
+ {extauth_cache, Host},
+ fun(I) when is_integer(I), I > 0 -> I end) of
+ undefined -> false;
+ CacheTime -> {true, CacheTime}
end.
%% @spec (User, Server, Password) -> true | false
check_password_extauth(User, Server, Password) ->
- extauth:check_password(User, Server, Password) andalso Password /= "".
+ extauth:check_password(User, Server, Password) andalso
+ Password /= <<"">>.
%% @spec (User, Server, Password) -> true | false
try_register_extauth(User, Server, Password) ->
extauth:try_register(User, Server, Password).
-check_password_cache(User, Server, Password, CacheTime) ->
+check_password_cache(User, Server, Password,
+ CacheTime) ->
case get_last_access(User, Server) of
- online ->
- check_password_internal(User, Server, Password);
- never ->
- check_password_external_cache(User, Server, Password);
- mod_last_required ->
- ?ERROR_MSG("extauth is used, extauth_cache is enabled but mod_last is not enabled in that host", []),
- check_password_external_cache(User, Server, Password);
- TimeStamp ->
- %% If last access exists, compare last access with cache refresh time
- case is_fresh_enough(TimeStamp, CacheTime) of
- %% If no need to refresh, check password against Mnesia
- true ->
- case check_password_internal(User, Server, Password) of
- %% If password valid in Mnesia, accept it
- true ->
- true;
- %% Else (password nonvalid in Mnesia), check in extauth and cache result
- false ->
- check_password_external_cache(User, Server, Password)
- end;
- %% Else (need to refresh), check in extauth and cache result
- false ->
- check_password_external_cache(User, Server, Password)
- end
+ online ->
+ check_password_internal(User, Server, Password);
+ never ->
+ check_password_external_cache(User, Server, Password);
+ mod_last_required ->
+ ?ERROR_MSG("extauth is used, extauth_cache is enabled "
+ "but mod_last is not enabled in that "
+ "host",
+ []),
+ check_password_external_cache(User, Server, Password);
+ TimeStamp ->
+ case is_fresh_enough(TimeStamp, CacheTime) of
+ %% If no need to refresh, check password against Mnesia
+ true ->
+ case check_password_internal(User, Server, Password) of
+ %% If password valid in Mnesia, accept it
+ true -> true;
+ %% Else (password nonvalid in Mnesia), check in extauth and cache result
+ false ->
+ check_password_external_cache(User, Server, Password)
+ end;
+ %% Else (need to refresh), check in extauth and cache result
+ false ->
+ check_password_external_cache(User, Server, Password)
+ end
end.
get_password_internal(User, Server) ->
@@ -215,60 +222,54 @@ get_password_internal(User, Server) ->
%% @spec (User, Server, CacheTime) -> false | Password::string()
get_password_cache(User, Server, CacheTime) ->
case get_last_access(User, Server) of
- online ->
- get_password_internal(User, Server);
- never ->
- false;
- mod_last_required ->
- ?ERROR_MSG("extauth is used, extauth_cache is enabled but mod_last is not enabled in that host", []),
- false;
- TimeStamp ->
- case is_fresh_enough(TimeStamp, CacheTime) of
- true ->
- get_password_internal(User, Server);
- false ->
- false
- end
+ online -> get_password_internal(User, Server);
+ never -> false;
+ mod_last_required ->
+ ?ERROR_MSG("extauth is used, extauth_cache is enabled "
+ "but mod_last is not enabled in that "
+ "host",
+ []),
+ false;
+ TimeStamp ->
+ case is_fresh_enough(TimeStamp, CacheTime) of
+ true -> get_password_internal(User, Server);
+ false -> false
+ end
end.
-
%% Check the password using extauth; if success then cache it
check_password_external_cache(User, Server, Password) ->
case check_password_extauth(User, Server, Password) of
- true ->
- set_password_internal(User, Server, Password), true;
- false ->
- false
+ true ->
+ set_password_internal(User, Server, Password), true;
+ false -> false
end.
%% Try to register using extauth; if success then cache it
try_register_external_cache(User, Server, Password) ->
case try_register_extauth(User, Server, Password) of
- {atomic, ok} = R ->
- set_password_internal(User, Server, Password),
- R;
- _ -> {error, not_allowed}
+ {atomic, ok} = R ->
+ set_password_internal(User, Server, Password), R;
+ _ -> {error, not_allowed}
end.
%% @spec (User, Server, Password) -> true | false
check_password_internal(User, Server, Password) ->
- ejabberd_auth_internal:check_password(User, Server, Password).
+ ejabberd_auth_internal:check_password(User, Server,
+ Password).
%% @spec (User, Server, Password) -> ok | {error, invalid_jid}
set_password_internal(User, Server, Password) ->
- ejabberd_auth_internal:set_password(User, Server, Password).
-
%% @spec (TimeLast, CacheTime) -> true | false
%% TimeLast = online | never | integer()
%% CacheTime = integer() | false
-is_fresh_enough(online, _CacheTime) ->
- true;
-is_fresh_enough(never, _CacheTime) ->
- false;
+ ejabberd_auth_internal:set_password(User, Server,
+ Password).
+
is_fresh_enough(TimeStampLast, CacheTime) ->
{MegaSecs, Secs, _MicroSecs} = now(),
Now = MegaSecs * 1000000 + Secs,
- (TimeStampLast + CacheTime > Now).
+ TimeStampLast + CacheTime > Now.
%% @spec (User, Server) -> online | never | mod_last_required | TimeStamp::integer()
%% Code copied from mod_configure.erl
@@ -276,38 +277,35 @@ is_fresh_enough(TimeStampLast, CacheTime) ->
%% TODO: Update time format to XEP-0202: Entity Time
get_last_access(User, Server) ->
case ejabberd_sm:get_user_resources(User, Server) of
- [] ->
- _US = {User, Server},
- case get_last_info(User, Server) of
- mod_last_required ->
- mod_last_required;
- not_found ->
- never;
- {ok, Timestamp, _Status} ->
- Timestamp
- end;
- _ ->
- online
+ [] ->
+ _US = {User, Server},
+ case get_last_info(User, Server) of
+ mod_last_required -> mod_last_required;
+ not_found -> never;
+ {ok, Timestamp, _Status} -> Timestamp
+ end;
+ _ -> online
end.
%% @spec (User, Server) -> {ok, Timestamp, Status} | not_found | mod_last_required
+
get_last_info(User, Server) ->
case get_mod_last_enabled(Server) of
- mod_last -> mod_last:get_last_info(User, Server);
- no_mod_last -> mod_last_required
+ mod_last -> mod_last:get_last_info(User, Server);
+ no_mod_last -> mod_last_required
end.
%% @spec (Server) -> mod_last | no_mod_last
get_mod_last_enabled(Server) ->
case gen_mod:is_loaded(Server, mod_last) of
- true -> mod_last;
- false -> no_mod_last
+ true -> mod_last;
+ false -> no_mod_last
end.
get_mod_last_configured(Server) ->
case is_configured(Server, mod_last) of
- true -> mod_last;
- false -> no_mod_last
+ true -> mod_last;
+ false -> no_mod_last
end.
is_configured(Host, Module) ->
- lists:keymember(Module, 1, ejabberd_config:get_local_option({modules, Host})).
+ gen_mod:is_loaded(Host, Module).
diff --git a/src/ejabberd_auth_internal.erl b/src/ejabberd_auth_internal.erl
index 4b5bcd327..b3587e211 100644
--- a/src/ejabberd_auth_internal.erl
+++ b/src/ejabberd_auth_internal.erl
@@ -25,32 +25,29 @@
%%%----------------------------------------------------------------------
-module(ejabberd_auth_internal).
+
-author('alexey@process-one.net').
+-behaviour(ejabberd_auth).
+
%% External exports
--export([start/1,
- set_password/3,
- check_password/3,
- check_password/5,
- try_register/3,
- dirty_get_registered_users/0,
- get_vh_registered_users/1,
+-export([start/1, set_password/3, check_password/3,
+ check_password/5, try_register/3,
+ dirty_get_registered_users/0, get_vh_registered_users/1,
get_vh_registered_users/2,
get_vh_registered_users_number/1,
- get_vh_registered_users_number/2,
- get_password/2,
- get_password_s/2,
- is_user_exists/2,
- remove_user/2,
- remove_user/3,
- store_type/0,
- plain_password_required/0
- ]).
+ get_vh_registered_users_number/2, get_password/2,
+ get_password_s/2, is_user_exists/2, remove_user/2,
+ remove_user/3, store_type/0, export/1,
+ plain_password_required/0]).
-include("ejabberd.hrl").
--record(passwd, {us, password}).
--record(reg_users_counter, {vhost, count}).
+-record(passwd, {us = {<<"">>, <<"">>} :: {binary(), binary()} | '$1',
+ password = <<"">> :: binary() | scram() | '_'}).
+
+-record(reg_users_counter, {vhost = <<"">> :: binary(),
+ count = 0 :: integer() | '$1'}).
-define(SALT_LENGTH, 16).
@@ -58,8 +55,9 @@
%%% API
%%%----------------------------------------------------------------------
start(Host) ->
- mnesia:create_table(passwd, [{disc_copies, [node()]},
- {attributes, record_info(fields, passwd)}]),
+ mnesia:create_table(passwd,
+ [{disc_copies, [node()]},
+ {attributes, record_info(fields, passwd)}]),
mnesia:create_table(reg_users_counter,
[{ram_copies, [node()]},
{attributes, record_info(fields, reg_users_counter)}]),
@@ -72,22 +70,22 @@ update_reg_users_counter_table(Server) ->
Set = get_vh_registered_users(Server),
Size = length(Set),
LServer = jlib:nameprep(Server),
- F = fun() ->
- mnesia:write(#reg_users_counter{vhost = LServer,
- count = Size})
+ F = fun () ->
+ mnesia:write(#reg_users_counter{vhost = LServer,
+ count = Size})
end,
mnesia:sync_dirty(F).
plain_password_required() ->
case is_scrammed() of
- false -> false;
- true -> true
+ false -> false;
+ true -> true
end.
store_type() ->
case is_scrammed() of
- false -> plain; %% allows: PLAIN DIGEST-MD5 SCRAM
- true -> scram %% allows: PLAIN SCRAM
+ false -> plain; %% allows: PLAIN DIGEST-MD5 SCRAM
+ true -> scram %% allows: PLAIN SCRAM
end.
check_password(User, Server, Password) ->
@@ -95,46 +93,40 @@ check_password(User, Server, Password) ->
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
case catch mnesia:dirty_read({passwd, US}) of
- [#passwd{password = Password}] when is_list(Password) ->
- Password /= "";
- [#passwd{password = Scram}] when is_record(Scram, scram) ->
- is_password_scram_valid(Password, Scram);
- _ ->
- false
+ [#passwd{password = Password}]
+ when is_binary(Password) ->
+ Password /= <<"">>;
+ [#passwd{password = Scram}]
+ when is_record(Scram, scram) ->
+ is_password_scram_valid(Password, Scram);
+ _ -> false
end.
-check_password(User, Server, Password, Digest, DigestGen) ->
+check_password(User, Server, Password, Digest,
+ DigestGen) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
case catch mnesia:dirty_read({passwd, US}) of
- [#passwd{password = Passwd}] when is_list(Passwd) ->
- DigRes = if
- Digest /= "" ->
- Digest == DigestGen(Passwd);
- true ->
- false
- end,
- if DigRes ->
- true;
- true ->
- (Passwd == Password) and (Password /= "")
- end;
- [#passwd{password = Scram}] when is_record(Scram, scram) ->
- Passwd = base64:decode(Scram#scram.storedkey),
- DigRes = if
- Digest /= "" ->
- Digest == DigestGen(Passwd);
- true ->
- false
- end,
- if DigRes ->
- true;
- true ->
- (Passwd == Password) and (Password /= "")
- end;
- _ ->
- false
+ [#passwd{password = Passwd}] when is_binary(Passwd) ->
+ DigRes = if Digest /= <<"">> ->
+ Digest == DigestGen(Passwd);
+ true -> false
+ end,
+ if DigRes -> true;
+ true -> (Passwd == Password) and (Password /= <<"">>)
+ end;
+ [#passwd{password = Scram}]
+ when is_record(Scram, scram) ->
+ Passwd = jlib:decode_base64(Scram#scram.storedkey),
+ DigRes = if Digest /= <<"">> ->
+ Digest == DigestGen(Passwd);
+ true -> false
+ end,
+ if DigRes -> true;
+ true -> (Passwd == Password) and (Password /= <<"">>)
+ end;
+ _ -> false
end.
%% @spec (User::string(), Server::string(), Password::string()) ->
@@ -143,49 +135,48 @@ set_password(User, Server, Password) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
- if
- (LUser == error) or (LServer == error) ->
- {error, invalid_jid};
- true ->
- F = fun() ->
- Password2 = case is_scrammed() and is_list(Password) of
- true -> password_to_scram(Password);
- false -> Password
- end,
- mnesia:write(#passwd{us = US,
- password = Password2})
- end,
- {atomic, ok} = mnesia:transaction(F),
- ok
+ if (LUser == error) or (LServer == error) ->
+ {error, invalid_jid};
+ true ->
+ F = fun () ->
+ Password2 = case is_scrammed() and is_binary(Password)
+ of
+ true -> password_to_scram(Password);
+ false -> Password
+ end,
+ mnesia:write(#passwd{us = US, password = Password2})
+ end,
+ {atomic, ok} = mnesia:transaction(F),
+ ok
end.
%% @spec (User, Server, Password) -> {atomic, ok} | {atomic, exists} | {error, invalid_jid} | {aborted, Reason}
-try_register(User, Server, Password) ->
+try_register(User, Server, PasswordList) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
+ Password = iolist_to_binary(PasswordList),
US = {LUser, LServer},
- if
- (LUser == error) or (LServer == error) ->
- {error, invalid_jid};
- true ->
- F = fun() ->
- case mnesia:read({passwd, US}) of
- [] ->
- Password2 = case is_scrammed() and is_list(Password) of
- true -> password_to_scram(Password);
- false -> Password
- end,
- mnesia:write(#passwd{us = US,
- password = Password2}),
- mnesia:dirty_update_counter(
- reg_users_counter,
- LServer, 1),
- ok;
- [_E] ->
- exists
- end
- end,
- mnesia:transaction(F)
+ if (LUser == error) or (LServer == error) ->
+ {error, invalid_jid};
+ true ->
+ F = fun () ->
+ case mnesia:read({passwd, US}) of
+ [] ->
+ Password2 = case is_scrammed() and
+ is_binary(Password)
+ of
+ true -> password_to_scram(Password);
+ false -> Password
+ end,
+ mnesia:write(#passwd{us = US,
+ password = Password2}),
+ mnesia:dirty_update_counter(reg_users_counter,
+ LServer, 1),
+ ok;
+ [_E] -> exists
+ end
+ end,
+ mnesia:transaction(F)
end.
%% Get all registered users in Mnesia
@@ -194,75 +185,81 @@ dirty_get_registered_users() ->
get_vh_registered_users(Server) ->
LServer = jlib:nameprep(Server),
- mnesia:dirty_select(
- passwd,
- [{#passwd{us = '$1', _ = '_'},
- [{'==', {element, 2, '$1'}, LServer}],
- ['$1']}]).
-
-get_vh_registered_users(Server, [{from, Start}, {to, End}])
- when is_integer(Start) and is_integer(End) ->
- get_vh_registered_users(Server, [{limit, End-Start+1}, {offset, Start}]);
-
-get_vh_registered_users(Server, [{limit, Limit}, {offset, Offset}])
- when is_integer(Limit) and is_integer(Offset) ->
+ mnesia:dirty_select(passwd,
+ [{#passwd{us = '$1', _ = '_'},
+ [{'==', {element, 2, '$1'}, LServer}], ['$1']}]).
+
+get_vh_registered_users(Server,
+ [{from, Start}, {to, End}])
+ when is_integer(Start) and is_integer(End) ->
+ get_vh_registered_users(Server,
+ [{limit, End - Start + 1}, {offset, Start}]);
+get_vh_registered_users(Server,
+ [{limit, Limit}, {offset, Offset}])
+ when is_integer(Limit) and is_integer(Offset) ->
case get_vh_registered_users(Server) of
- [] ->
- [];
- Users ->
- Set = lists:keysort(1, Users),
- L = length(Set),
- Start = if Offset < 1 -> 1;
- Offset > L -> L;
- true -> Offset
- end,
- lists:sublist(Set, Start, Limit)
+ [] -> [];
+ Users ->
+ Set = lists:keysort(1, Users),
+ L = length(Set),
+ Start = if Offset < 1 -> 1;
+ Offset > L -> L;
+ true -> Offset
+ end,
+ lists:sublist(Set, Start, Limit)
end;
-
-get_vh_registered_users(Server, [{prefix, Prefix}])
- when is_list(Prefix) ->
- Set = [{U,S} || {U, S} <- get_vh_registered_users(Server), lists:prefix(Prefix, U)],
+get_vh_registered_users(Server, [{prefix, Prefix}])
+ when is_binary(Prefix) ->
+ Set = [{U, S}
+ || {U, S} <- get_vh_registered_users(Server),
+ str:prefix(Prefix, U)],
lists:keysort(1, Set);
-
-get_vh_registered_users(Server, [{prefix, Prefix}, {from, Start}, {to, End}])
- when is_list(Prefix) and is_integer(Start) and is_integer(End) ->
- get_vh_registered_users(Server, [{prefix, Prefix}, {limit, End-Start+1}, {offset, Start}]);
-
-get_vh_registered_users(Server, [{prefix, Prefix}, {limit, Limit}, {offset, Offset}])
- when is_list(Prefix) and is_integer(Limit) and is_integer(Offset) ->
- case [{U,S} || {U, S} <- get_vh_registered_users(Server), lists:prefix(Prefix, U)] of
- [] ->
- [];
- Users ->
- Set = lists:keysort(1, Users),
- L = length(Set),
- Start = if Offset < 1 -> 1;
- Offset > L -> L;
- true -> Offset
- end,
- lists:sublist(Set, Start, Limit)
+get_vh_registered_users(Server,
+ [{prefix, Prefix}, {from, Start}, {to, End}])
+ when is_binary(Prefix) and is_integer(Start) and
+ is_integer(End) ->
+ get_vh_registered_users(Server,
+ [{prefix, Prefix}, {limit, End - Start + 1},
+ {offset, Start}]);
+get_vh_registered_users(Server,
+ [{prefix, Prefix}, {limit, Limit}, {offset, Offset}])
+ when is_binary(Prefix) and is_integer(Limit) and
+ is_integer(Offset) ->
+ case [{U, S}
+ || {U, S} <- get_vh_registered_users(Server),
+ str:prefix(Prefix, U)]
+ of
+ [] -> [];
+ Users ->
+ Set = lists:keysort(1, Users),
+ L = length(Set),
+ Start = if Offset < 1 -> 1;
+ Offset > L -> L;
+ true -> Offset
+ end,
+ lists:sublist(Set, Start, Limit)
end;
-
get_vh_registered_users(Server, _) ->
get_vh_registered_users(Server).
get_vh_registered_users_number(Server) ->
LServer = jlib:nameprep(Server),
- Query = mnesia:dirty_select(
- reg_users_counter,
- [{#reg_users_counter{vhost = LServer, count = '$1'},
- [],
- ['$1']}]),
+ Query = mnesia:dirty_select(reg_users_counter,
+ [{#reg_users_counter{vhost = LServer,
+ count = '$1'},
+ [], ['$1']}]),
case Query of
- [Count] ->
- Count;
- _ -> 0
+ [Count] -> Count;
+ _ -> 0
end.
-get_vh_registered_users_number(Server, [{prefix, Prefix}]) when is_list(Prefix) ->
- Set = [{U, S} || {U, S} <- get_vh_registered_users(Server), lists:prefix(Prefix, U)],
+get_vh_registered_users_number(Server,
+ [{prefix, Prefix}])
+ when is_binary(Prefix) ->
+ Set = [{U, S}
+ || {U, S} <- get_vh_registered_users(Server),
+ str:prefix(Prefix, U)],
length(Set);
-
get_vh_registered_users_number(Server, _) ->
get_vh_registered_users_number(Server).
@@ -271,15 +268,16 @@ get_password(User, Server) ->
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
case catch mnesia:dirty_read(passwd, US) of
- [#passwd{password = Password}] when is_list(Password) ->
- Password;
- [#passwd{password = Scram}] when is_record(Scram, scram) ->
- {base64:decode(Scram#scram.storedkey),
- base64:decode(Scram#scram.serverkey),
- base64:decode(Scram#scram.salt),
- Scram#scram.iterationcount};
- _ ->
- false
+ [#passwd{password = Password}]
+ when is_binary(Password) ->
+ Password;
+ [#passwd{password = Scram}]
+ when is_record(Scram, scram) ->
+ {jlib:decode_base64(Scram#scram.storedkey),
+ jlib:decode_base64(Scram#scram.serverkey),
+ jlib:decode_base64(Scram#scram.salt),
+ Scram#scram.iterationcount};
+ _ -> false
end.
get_password_s(User, Server) ->
@@ -287,12 +285,13 @@ get_password_s(User, Server) ->
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
case catch mnesia:dirty_read(passwd, US) of
- [#passwd{password = Password}] when is_list(Password) ->
- Password;
- [#passwd{password = Scram}] when is_record(Scram, scram) ->
- [];
- _ ->
- []
+ [#passwd{password = Password}]
+ when is_binary(Password) ->
+ Password;
+ [#passwd{password = Scram}]
+ when is_record(Scram, scram) ->
+ <<"">>;
+ _ -> <<"">>
end.
%% @spec (User, Server) -> true | false | {error, Error}
@@ -301,12 +300,9 @@ is_user_exists(User, Server) ->
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
case catch mnesia:dirty_read({passwd, US}) of
- [] ->
- false;
- [_] ->
- true;
- Other ->
- {error, Other}
+ [] -> false;
+ [_] -> true;
+ Other -> {error, Other}
end.
%% @spec (User, Server) -> ok
@@ -316,13 +312,13 @@ remove_user(User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
- F = fun() ->
+ F = fun () ->
mnesia:delete({passwd, US}),
- mnesia:dirty_update_counter(reg_users_counter,
- LServer, -1)
- end,
+ mnesia:dirty_update_counter(reg_users_counter, LServer,
+ -1)
+ end,
mnesia:transaction(F),
- ok.
+ ok.
%% @spec (User, Server, Password) -> ok | not_exists | not_allowed | bad_request
%% @doc Remove user if the provided password is correct.
@@ -330,79 +326,65 @@ remove_user(User, Server, Password) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
- F = fun() ->
+ F = fun () ->
case mnesia:read({passwd, US}) of
- [#passwd{password = Password}] when is_list(Password) ->
- mnesia:delete({passwd, US}),
- mnesia:dirty_update_counter(reg_users_counter,
- LServer, -1),
- ok;
- [#passwd{password = Scram}] when is_record(Scram, scram) ->
- case is_password_scram_valid(Password, Scram) of
- true ->
- mnesia:delete({passwd, US}),
- mnesia:dirty_update_counter(reg_users_counter,
- LServer, -1),
- ok;
- false ->
- not_allowed
- end;
- _ ->
- not_exists
+ [#passwd{password = Password}]
+ when is_binary(Password) ->
+ mnesia:delete({passwd, US}),
+ mnesia:dirty_update_counter(reg_users_counter, LServer,
+ -1),
+ ok;
+ [#passwd{password = Scram}]
+ when is_record(Scram, scram) ->
+ case is_password_scram_valid(Password, Scram) of
+ true ->
+ mnesia:delete({passwd, US}),
+ mnesia:dirty_update_counter(reg_users_counter,
+ LServer, -1),
+ ok;
+ false -> not_allowed
+ end;
+ _ -> not_exists
end
- end,
+ end,
case mnesia:transaction(F) of
- {atomic, ok} ->
- ok;
- {atomic, Res} ->
- Res;
- _ ->
- bad_request
+ {atomic, ok} -> ok;
+ {atomic, Res} -> Res;
+ _ -> bad_request
end.
update_table() ->
Fields = record_info(fields, passwd),
case mnesia:table_info(passwd, attributes) of
- Fields ->
- maybe_scram_passwords(),
- ok;
- [user, password] ->
- ?INFO_MSG("Converting passwd table from "
- "{user, password} format", []),
- Host = ?MYNAME,
- {atomic, ok} = mnesia:create_table(
- ejabberd_auth_internal_tmp_table,
- [{disc_only_copies, [node()]},
- {type, bag},
- {local_content, true},
- {record_name, passwd},
- {attributes, record_info(fields, passwd)}]),
- mnesia:transform_table(passwd, ignore, Fields),
- F1 = fun() ->
- mnesia:write_lock_table(ejabberd_auth_internal_tmp_table),
- mnesia:foldl(
- fun(#passwd{us = U} = R, _) ->
- mnesia:dirty_write(
- ejabberd_auth_internal_tmp_table,
- R#passwd{us = {U, Host}})
- end, ok, passwd)
- end,
- mnesia:transaction(F1),
- mnesia:clear_table(passwd),
- F2 = fun() ->
- mnesia:write_lock_table(passwd),
- mnesia:foldl(
- fun(R, _) ->
- mnesia:dirty_write(R)
- end, ok, ejabberd_auth_internal_tmp_table)
- end,
- mnesia:transaction(F2),
- mnesia:delete_table(ejabberd_auth_internal_tmp_table);
- _ ->
- ?INFO_MSG("Recreating passwd table", []),
- mnesia:transform_table(passwd, ignore, Fields)
+ Fields ->
+ convert_to_binary(Fields),
+ maybe_scram_passwords(),
+ ok;
+ _ ->
+ ?INFO_MSG("Recreating passwd table", []),
+ mnesia:transform_table(passwd, ignore, Fields)
end.
+convert_to_binary(Fields) ->
+ ejabberd_config:convert_table_to_binary(
+ passwd, Fields, set,
+ fun(#passwd{us = {U, _}}) -> U end,
+ fun(#passwd{us = {U, S}, password = Pass} = R) ->
+ NewUS = {iolist_to_binary(U), iolist_to_binary(S)},
+ NewPass = case Pass of
+ #scram{storedkey = StoredKey,
+ serverkey = ServerKey,
+ salt = Salt} ->
+ Pass#scram{
+ storedkey = iolist_to_binary(StoredKey),
+ serverkey = iolist_to_binary(ServerKey),
+ salt = iolist_to_binary(Salt)};
+ _ ->
+ iolist_to_binary(Pass)
+ end,
+ R#passwd{us = NewUS, password = NewPass}
+ end).
+
%%%
%%% SCRAM
%%%
@@ -411,38 +393,43 @@ update_table() ->
%% or if at least the first password is scrammed.
is_scrammed() ->
OptionScram = is_option_scram(),
- FirstElement = mnesia:dirty_read(passwd, mnesia:dirty_first(passwd)),
+ FirstElement = mnesia:dirty_read(passwd,
+ mnesia:dirty_first(passwd)),
case {OptionScram, FirstElement} of
- {true, _} ->
- true;
- {false, [#passwd{password = Scram}]} when is_record(Scram, scram) ->
- true;
- _ ->
- false
+ {true, _} -> true;
+ {false, [#passwd{password = Scram}]}
+ when is_record(Scram, scram) ->
+ true;
+ _ -> false
end.
is_option_scram() ->
- scram == ejabberd_config:get_local_option({auth_password_format, ?MYNAME}).
+ scram ==
+ ejabberd_config:get_local_option({auth_password_format, ?MYNAME},
+ fun(V) -> V end).
maybe_alert_password_scrammed_without_option() ->
case is_scrammed() andalso not is_option_scram() of
- true ->
- ?ERROR_MSG("Some passwords were stored in the database as SCRAM, "
- "but 'auth_password_format' is not configured 'scram'. "
- "The option will now be considered to be 'scram'.", []);
- false ->
- ok
+ true ->
+ ?ERROR_MSG("Some passwords were stored in the database "
+ "as SCRAM, but 'auth_password_format' "
+ "is not configured 'scram'. The option "
+ "will now be considered to be 'scram'.",
+ []);
+ false -> ok
end.
maybe_scram_passwords() ->
case is_scrammed() of
- true -> scram_passwords();
- false -> ok
+ true -> scram_passwords();
+ false -> ok
end.
scram_passwords() ->
- ?INFO_MSG("Converting the stored passwords into SCRAM bits", []),
- Fun = fun(#passwd{password = Password} = P) ->
+ ?INFO_MSG("Converting the stored passwords into "
+ "SCRAM bits",
+ []),
+ Fun = fun (#passwd{password = Password} = P) ->
Scram = password_to_scram(Password),
P#passwd{password = Scram}
end,
@@ -450,21 +437,39 @@ scram_passwords() ->
mnesia:transform_table(passwd, Fun, Fields).
password_to_scram(Password) ->
- password_to_scram(Password, ?SCRAM_DEFAULT_ITERATION_COUNT).
+ password_to_scram(Password,
+ ?SCRAM_DEFAULT_ITERATION_COUNT).
password_to_scram(Password, IterationCount) ->
Salt = crypto:rand_bytes(?SALT_LENGTH),
- SaltedPassword = scram:salted_password(Password, Salt, IterationCount),
- StoredKey = scram:stored_key(scram:client_key(SaltedPassword)),
+ SaltedPassword = scram:salted_password(Password, Salt,
+ IterationCount),
+ StoredKey =
+ scram:stored_key(scram:client_key(SaltedPassword)),
ServerKey = scram:server_key(SaltedPassword),
- #scram{storedkey = base64:encode(StoredKey),
- serverkey = base64:encode(ServerKey),
- salt = base64:encode(Salt),
+ #scram{storedkey = jlib:encode_base64(StoredKey),
+ serverkey = jlib:encode_base64(ServerKey),
+ salt = jlib:encode_base64(Salt),
iterationcount = IterationCount}.
is_password_scram_valid(Password, Scram) ->
IterationCount = Scram#scram.iterationcount,
- Salt = base64:decode(Scram#scram.salt),
- SaltedPassword = scram:salted_password(Password, Salt, IterationCount),
- StoredKey = scram:stored_key(scram:client_key(SaltedPassword)),
- (base64:decode(Scram#scram.storedkey) == StoredKey).
+ Salt = jlib:decode_base64(Scram#scram.salt),
+ SaltedPassword = scram:salted_password(Password, Salt,
+ IterationCount),
+ StoredKey =
+ scram:stored_key(scram:client_key(SaltedPassword)),
+ jlib:decode_base64(Scram#scram.storedkey) == StoredKey.
+
+export(_Server) ->
+ [{passwd,
+ fun(Host, #passwd{us = {LUser, LServer}, password = Password})
+ when LServer == Host ->
+ Username = ejabberd_odbc:escape(LUser),
+ Pass = ejabberd_odbc:escape(Password),
+ [[<<"delete from users where username='">>, Username, <<"';">>],
+ [<<"insert into users(username, password) "
+ "values ('">>, Username, <<"', '">>, Pass, <<"');">>]];
+ (_Host, _R) ->
+ []
+ end}].
diff --git a/src/ejabberd_auth_ldap.erl b/src/ejabberd_auth_ldap.erl
index 5e5ca2422..998f21215 100644
--- a/src/ejabberd_auth_ldap.erl
+++ b/src/ejabberd_auth_ldap.erl
@@ -25,73 +25,59 @@
%%%----------------------------------------------------------------------
-module(ejabberd_auth_ldap).
+
-author('alexey@process-one.net').
-behaviour(gen_server).
+-behaviour(ejabberd_auth).
%% gen_server callbacks
--export([init/1,
- handle_info/2,
- handle_call/3,
- handle_cast/2,
- terminate/2,
- code_change/3
- ]).
+-export([init/1, handle_info/2, handle_call/3,
+ handle_cast/2, terminate/2, code_change/3]).
%% External exports
--export([start/1,
- stop/1,
- start_link/1,
- set_password/3,
- check_password/3,
- check_password/5,
- try_register/3,
- dirty_get_registered_users/0,
- get_vh_registered_users/1,
- get_vh_registered_users_number/1,
- get_password/2,
- get_password_s/2,
- is_user_exists/2,
- remove_user/2,
- remove_user/3,
- store_type/0,
- plain_password_required/0
- ]).
+-export([start/1, stop/1, start_link/1, set_password/3,
+ check_password/3, check_password/5, try_register/3,
+ dirty_get_registered_users/0, get_vh_registered_users/1,
+ get_vh_registered_users/2,
+ get_vh_registered_users_number/1,
+ get_vh_registered_users_number/2, get_password/2,
+ get_password_s/2, is_user_exists/2, remove_user/2,
+ remove_user/3, store_type/0,
+ plain_password_required/0]).
-include("ejabberd.hrl").
--include("eldap/eldap.hrl").
-
--record(state, {host,
- eldap_id,
- bind_eldap_id,
- servers,
- backups,
- port,
- tls_options,
- dn,
- password,
- base,
- uids,
- ufilter,
- sfilter,
- lfilter, %% Local filter (performed by ejabberd, not LDAP)
- deref_aliases,
- dn_filter,
- dn_filter_attrs
- }).
-
%% Unused callbacks.
-handle_cast(_Request, State) ->
- {noreply, State}.
-code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
-handle_info(_Info, State) ->
- {noreply, State}.
%% -----
+-include("eldap/eldap.hrl").
--define(LDAP_SEARCH_TIMEOUT, 5). % Timeout for LDAP search queries in seconds
-
+-record(state,
+ {host = <<"">> :: binary(),
+ eldap_id = <<"">> :: binary(),
+ bind_eldap_id = <<"">> :: binary(),
+ servers = [] :: [binary()],
+ backups = [] :: [binary()],
+ port = ?LDAP_PORT :: inet:port_number(),
+ tls_options = [] :: list(),
+ dn = <<"">> :: binary(),
+ password = <<"">> :: binary(),
+ base = <<"">> :: binary(),
+ uids = [] :: [{binary()} | {binary(), binary()}],
+ ufilter = <<"">> :: binary(),
+ sfilter = <<"">> :: binary(),
+ lfilter :: {any(), any()},
+ deref_aliases = never :: never | searching | finding | always,
+ dn_filter :: binary(),
+ dn_filter_attrs = [] :: [binary()]}).
+
+handle_cast(_Request, State) -> {noreply, State}.
+
+code_change(_OldVsn, State, _Extra) -> {ok, State}.
+
+handle_info(_Info, State) -> {noreply, State}.
+
+-define(LDAP_SEARCH_TIMEOUT, 5).
%%%----------------------------------------------------------------------
%%% API
@@ -99,10 +85,8 @@ handle_info(_Info, State) ->
start(Host) ->
Proc = gen_mod:get_module_proc(Host, ?MODULE),
- ChildSpec = {
- Proc, {?MODULE, start_link, [Host]},
- transient, 1000, worker, [?MODULE]
- },
+ ChildSpec = {Proc, {?MODULE, start_link, [Host]},
+ transient, 1000, worker, [?MODULE]},
supervisor:start_child(ejabberd_sup, ChildSpec).
stop(Host) ->
@@ -115,56 +99,45 @@ start_link(Host) ->
Proc = gen_mod:get_module_proc(Host, ?MODULE),
gen_server:start_link({local, Proc}, ?MODULE, Host, []).
-terminate(_Reason, _State) ->
- ok.
+terminate(_Reason, _State) -> ok.
init(Host) ->
State = parse_options(Host),
eldap_pool:start_link(State#state.eldap_id,
- State#state.servers,
- State#state.backups,
- State#state.port,
- State#state.dn,
- State#state.password,
- State#state.tls_options),
+ State#state.servers, State#state.backups,
+ State#state.port, State#state.dn,
+ State#state.password, State#state.tls_options),
eldap_pool:start_link(State#state.bind_eldap_id,
- State#state.servers,
- State#state.backups,
- State#state.port,
- State#state.dn,
- State#state.password,
- State#state.tls_options),
+ State#state.servers, State#state.backups,
+ State#state.port, State#state.dn,
+ State#state.password, State#state.tls_options),
{ok, State}.
-plain_password_required() ->
- true.
+plain_password_required() -> true.
-store_type() ->
- external.
+store_type() -> external.
check_password(User, Server, Password) ->
- %% In LDAP spec: empty password means anonymous authentication.
- %% As ejabberd is providing other anonymous authentication mechanisms
- %% we simply prevent the use of LDAP anonymous authentication.
- if Password == "" ->
- false;
- true ->
- case catch check_password_ldap(User, Server, Password) of
- {'EXIT', _} -> false;
- Result -> Result
- end
+ if Password == <<"">> -> false;
+ true ->
+ case catch check_password_ldap(User, Server, Password)
+ of
+ {'EXIT', _} -> false;
+ Result -> Result
+ end
end.
-check_password(User, Server, Password, _Digest, _DigestGen) ->
+check_password(User, Server, Password, _Digest,
+ _DigestGen) ->
check_password(User, Server, Password).
set_password(User, Server, Password) ->
{ok, State} = eldap_utils:get_state(Server, ?MODULE),
case find_user_dn(User, State) of
- false ->
- {error, user_not_found};
- DN ->
- eldap_pool:modify_passwd(State#state.eldap_id, DN, Password)
+ false -> {error, user_not_found};
+ DN ->
+ eldap_pool:modify_passwd(State#state.eldap_id, DN,
+ Password)
end.
%% @spec (User, Server, Password) -> {error, not_allowed}
@@ -173,55 +146,56 @@ try_register(_User, _Server, _Password) ->
dirty_get_registered_users() ->
Servers = ejabberd_config:get_vh_by_auth_method(ldap),
- lists:flatmap(
- fun(Server) ->
- get_vh_registered_users(Server)
- end, Servers).
+ lists:flatmap(fun (Server) ->
+ get_vh_registered_users(Server)
+ end,
+ Servers).
get_vh_registered_users(Server) ->
case catch get_vh_registered_users_ldap(Server) of
- {'EXIT', _} -> [];
- Result -> Result
- end.
+ {'EXIT', _} -> [];
+ Result -> Result
+ end.
+
+get_vh_registered_users(Server, _) ->
+ get_vh_registered_users(Server).
get_vh_registered_users_number(Server) ->
length(get_vh_registered_users(Server)).
-get_password(_User, _Server) ->
- false.
+get_vh_registered_users_number(Server, _) ->
+ get_vh_registered_users_number(Server).
+
+get_password(_User, _Server) -> false.
-get_password_s(_User, _Server) ->
- "".
+get_password_s(_User, _Server) -> <<"">>.
%% @spec (User, Server) -> true | false | {error, Error}
is_user_exists(User, Server) ->
case catch is_user_exists_ldap(User, Server) of
- {'EXIT', Error} ->
- {error, Error};
- Result ->
- Result
+ {'EXIT', Error} -> {error, Error};
+ Result -> Result
end.
-remove_user(_User, _Server) ->
- {error, not_allowed}.
+remove_user(_User, _Server) -> {error, not_allowed}.
-remove_user(_User, _Server, _Password) ->
- not_allowed.
+remove_user(_User, _Server, _Password) -> not_allowed.
%%%----------------------------------------------------------------------
%%% Internal functions
%%%----------------------------------------------------------------------
check_password_ldap(User, Server, Password) ->
- {ok, State} = eldap_utils:get_state(Server, ?MODULE),
- case find_user_dn(User, State) of
- false ->
- false;
- DN ->
- case eldap_pool:bind(State#state.bind_eldap_id, DN, Password) of
- ok -> true;
- _ -> false
- end
- end.
+ {ok, State} = eldap_utils:get_state(Server, ?MODULE),
+ case find_user_dn(User, State) of
+ false -> false;
+ DN ->
+ case eldap_pool:bind(State#state.bind_eldap_id, DN,
+ Password)
+ of
+ ok -> true;
+ _ -> false
+ end
+ end.
get_vh_registered_users_ldap(Server) ->
{ok, State} = eldap_utils:get_state(Server, ?MODULE),
@@ -230,114 +204,123 @@ get_vh_registered_users_ldap(Server) ->
Server = State#state.host,
ResAttrs = result_attrs(State),
case eldap_filter:parse(State#state.sfilter) of
- {ok, EldapFilter} ->
- case eldap_pool:search(Eldap_ID,
- [{base, State#state.base},
- {filter, EldapFilter},
- {timeout, ?LDAP_SEARCH_TIMEOUT},
- {deref_aliases, State#state.deref_aliases},
- {attributes, ResAttrs}]) of
- #eldap_search_result{entries = Entries} ->
- lists:flatmap(
- fun(#eldap_entry{attributes = Attrs,
- object_name = DN}) ->
+ {ok, EldapFilter} ->
+ case eldap_pool:search(Eldap_ID,
+ [{base, State#state.base},
+ {filter, EldapFilter},
+ {timeout, ?LDAP_SEARCH_TIMEOUT},
+ {deref_aliases, State#state.deref_aliases},
+ {attributes, ResAttrs}])
+ of
+ #eldap_search_result{entries = Entries} ->
+ lists:flatmap(fun (#eldap_entry{attributes = Attrs,
+ object_name = DN}) ->
case is_valid_dn(DN, Attrs, State) of
- false -> [];
- _ ->
- case eldap_utils:find_ldap_attrs(UIDs, Attrs) of
- "" -> [];
- {User, UIDFormat} ->
- case eldap_utils:get_user_part(User, UIDFormat) of
- {ok, U} ->
- case jlib:nodeprep(U) of
- error -> [];
- LU -> [{LU, jlib:nameprep(Server)}]
- end;
- _ -> []
- end
- end
+ false -> [];
+ _ ->
+ case
+ eldap_utils:find_ldap_attrs(UIDs,
+ Attrs)
+ of
+ <<"">> -> [];
+ {User, UIDFormat} ->
+ case
+ eldap_utils:get_user_part(User,
+ UIDFormat)
+ of
+ {ok, U} ->
+ case jlib:nodeprep(U) of
+ error -> [];
+ LU ->
+ [{LU,
+ jlib:nameprep(Server)}]
+ end;
+ _ -> []
+ end
+ end
end
- end, Entries);
- _ ->
- []
- end;
- _ ->
- []
- end.
+ end,
+ Entries);
+ _ -> []
+ end;
+ _ -> []
+ end.
is_user_exists_ldap(User, Server) ->
{ok, State} = eldap_utils:get_state(Server, ?MODULE),
case find_user_dn(User, State) of
- false -> false;
- _DN -> true
- end.
+ false -> false;
+ _DN -> true
+ end.
handle_call(get_state, _From, State) ->
- {reply, {ok, State}, State};
-
+ {reply, {ok, State}, State};
handle_call(stop, _From, State) ->
{stop, normal, ok, State};
-
handle_call(_Request, _From, State) ->
{reply, bad_request, State}.
find_user_dn(User, State) ->
ResAttrs = result_attrs(State),
- case eldap_filter:parse(State#state.ufilter, [{"%u", User}]) of
- {ok, Filter} ->
- case eldap_pool:search(State#state.eldap_id,
- [{base, State#state.base},
- {filter, Filter},
- {deref_aliases, State#state.deref_aliases},
- {attributes, ResAttrs}]) of
- #eldap_search_result{entries = [#eldap_entry{attributes = Attrs,
- object_name = DN} | _]} ->
- dn_filter(DN, Attrs, State);
- _ ->
- false
- end;
- _ ->
- false
+ case eldap_filter:parse(State#state.ufilter,
+ [{<<"%u">>, User}])
+ of
+ {ok, Filter} ->
+ case eldap_pool:search(State#state.eldap_id,
+ [{base, State#state.base}, {filter, Filter},
+ {deref_aliases, State#state.deref_aliases},
+ {attributes, ResAttrs}])
+ of
+ #eldap_search_result{entries =
+ [#eldap_entry{attributes = Attrs,
+ object_name = DN}
+ | _]} ->
+ dn_filter(DN, Attrs, State);
+ _ -> false
+ end;
+ _ -> false
end.
%% apply the dn filter and the local filter:
dn_filter(DN, Attrs, State) ->
- %% Check if user is denied access by attribute value (local check)
case check_local_filter(Attrs, State) of
- false -> false;
- true -> is_valid_dn(DN, Attrs, State)
+ false -> false;
+ true -> is_valid_dn(DN, Attrs, State)
end.
%% Check that the DN is valid, based on the dn filter
-is_valid_dn(DN, _, #state{dn_filter = undefined}) ->
- DN;
-
+is_valid_dn(DN, _, #state{dn_filter = undefined}) -> DN;
is_valid_dn(DN, Attrs, State) ->
DNAttrs = State#state.dn_filter_attrs,
UIDs = State#state.uids,
- Values = [{"%s", eldap_utils:get_ldap_attr(Attr, Attrs), 1} || Attr <- DNAttrs],
- SubstValues = case eldap_utils:find_ldap_attrs(UIDs, Attrs) of
- "" -> Values;
- {S, UAF} ->
- case eldap_utils:get_user_part(S, UAF) of
- {ok, U} -> [{"%u", U} | Values];
- _ -> Values
- end
- end ++ [{"%d", State#state.host}, {"%D", DN}],
- case eldap_filter:parse(State#state.dn_filter, SubstValues) of
- {ok, EldapFilter} ->
- case eldap_pool:search(State#state.eldap_id,
- [{base, State#state.base},
- {filter, EldapFilter},
- {deref_aliases, State#state.deref_aliases},
- {attributes, ["dn"]}]) of
- #eldap_search_result{entries = [_|_]} ->
- DN;
- _ ->
- false
- end;
- _ ->
- false
+ Values = [{<<"%s">>,
+ eldap_utils:get_ldap_attr(Attr, Attrs), 1}
+ || Attr <- DNAttrs],
+ SubstValues = case eldap_utils:find_ldap_attrs(UIDs,
+ Attrs)
+ of
+ <<"">> -> Values;
+ {S, UAF} ->
+ case eldap_utils:get_user_part(S, UAF) of
+ {ok, U} -> [{<<"%u">>, U} | Values];
+ _ -> Values
+ end
+ end
+ ++ [{<<"%d">>, State#state.host}, {<<"%D">>, DN}],
+ case eldap_filter:parse(State#state.dn_filter,
+ SubstValues)
+ of
+ {ok, EldapFilter} ->
+ case eldap_pool:search(State#state.eldap_id,
+ [{base, State#state.base},
+ {filter, EldapFilter},
+ {deref_aliases, State#state.deref_aliases},
+ {attributes, [<<"dn">>]}])
+ of
+ #eldap_search_result{entries = [_ | _]} -> DN;
+ _ -> false
+ end;
+ _ -> false
end.
%% The local filter is used to check an attribute in ejabberd
@@ -346,109 +329,92 @@ is_valid_dn(DN, Attrs, State) ->
%% {equal, {"accountStatus",["active"]}}
%% {notequal, {"accountStatus",["disabled"]}}
%% {ldap_local_filter, {notequal, {"accountStatus",["disabled"]}}}
-check_local_filter(_Attrs, #state{lfilter = undefined}) ->
+check_local_filter(_Attrs,
+ #state{lfilter = undefined}) ->
true;
-check_local_filter(Attrs, #state{lfilter = LocalFilter}) ->
+check_local_filter(Attrs,
+ #state{lfilter = LocalFilter}) ->
{Operation, FilterMatch} = LocalFilter,
local_filter(Operation, Attrs, FilterMatch).
-
+
local_filter(equal, Attrs, FilterMatch) ->
{Attr, Value} = FilterMatch,
case lists:keysearch(Attr, 1, Attrs) of
- false -> false;
- {value,{Attr,Value}} -> true;
- _ -> false
+ false -> false;
+ {value, {Attr, Value}} -> true;
+ _ -> false
end;
local_filter(notequal, Attrs, FilterMatch) ->
not local_filter(equal, Attrs, FilterMatch).
-result_attrs(#state{uids = UIDs, dn_filter_attrs = DNFilterAttrs}) ->
- lists:foldl(
- fun({UID}, Acc) ->
- [UID | Acc];
- ({UID, _}, Acc) ->
- [UID | Acc]
- end, DNFilterAttrs, UIDs).
+result_attrs(#state{uids = UIDs,
+ dn_filter_attrs = DNFilterAttrs}) ->
+ lists:foldl(fun ({UID}, Acc) -> [UID | Acc];
+ ({UID, _}, Acc) -> [UID | Acc]
+ end,
+ DNFilterAttrs, UIDs).
%%%----------------------------------------------------------------------
%%% Auxiliary functions
%%%----------------------------------------------------------------------
parse_options(Host) ->
- Eldap_ID = atom_to_list(gen_mod:get_module_proc(Host, ?MODULE)),
- Bind_Eldap_ID = atom_to_list(gen_mod:get_module_proc(Host, bind_ejabberd_auth_ldap)),
- LDAPServers = ejabberd_config:get_local_option({ldap_servers, Host}),
- LDAPBackups = case ejabberd_config:get_local_option({ldap_backups, Host}) of
- undefined -> [];
- Backups -> Backups
- end,
- LDAPEncrypt = ejabberd_config:get_local_option({ldap_encrypt, Host}),
- LDAPTLSVerify = ejabberd_config:get_local_option({ldap_tls_verify, Host}),
- LDAPTLSCAFile = ejabberd_config:get_local_option({ldap_tls_cacertfile, Host}),
- LDAPTLSDepth = ejabberd_config:get_local_option({ldap_tls_depth, Host}),
- LDAPPort = case ejabberd_config:get_local_option({ldap_port, Host}) of
- undefined -> case LDAPEncrypt of
- tls -> ?LDAPS_PORT;
- starttls -> ?LDAP_PORT;
- _ -> ?LDAP_PORT
- end;
- P -> P
- end,
- RootDN = case ejabberd_config:get_local_option({ldap_rootdn, Host}) of
- undefined -> "";
- RDN -> RDN
- end,
- Password = case ejabberd_config:get_local_option({ldap_password, Host}) of
- undefined -> "";
- Pass -> Pass
- end,
- UIDs = case ejabberd_config:get_local_option({ldap_uids, Host}) of
- undefined -> [{"uid", "%u"}];
- UI -> eldap_utils:uids_domain_subst(Host, UI)
- end,
- SubFilter = lists:flatten(eldap_utils:generate_subfilter(UIDs)),
- UserFilter = case ejabberd_config:get_local_option({ldap_filter, Host}) of
- undefined -> SubFilter;
- "" -> SubFilter;
- F ->
- eldap_utils:check_filter(F),
- "(&" ++ SubFilter ++ F ++ ")"
- end,
- SearchFilter = eldap_filter:do_sub(UserFilter, [{"%u", "*"}]),
- LDAPBase = ejabberd_config:get_local_option({ldap_base, Host}),
+ Cfg = eldap_utils:get_config(Host, []),
+ Eldap_ID = jlib:atom_to_binary(gen_mod:get_module_proc(Host, ?MODULE)),
+ Bind_Eldap_ID = jlib:atom_to_binary(
+ gen_mod:get_module_proc(Host, bind_ejabberd_auth_ldap)),
+ UIDsTemp = eldap_utils:get_opt(
+ {ldap_uids, Host}, [],
+ fun(Us) ->
+ lists:map(
+ fun({U, P}) ->
+ {iolist_to_binary(U),
+ iolist_to_binary(P)};
+ ({U}) ->
+ {iolist_to_binary(U)}
+ end, Us)
+ end, [{<<"uid">>, <<"%u">>}]),
+ UIDs = eldap_utils:uids_domain_subst(Host, UIDsTemp),
+ SubFilter = eldap_utils:generate_subfilter(UIDs),
+ UserFilter = case eldap_utils:get_opt(
+ {ldap_filter, Host}, [],
+ fun check_filter/1, <<"">>) of
+ <<"">> ->
+ SubFilter;
+ F ->
+ <<"(&", SubFilter/binary, F/binary, ")">>
+ end,
+ SearchFilter = eldap_filter:do_sub(UserFilter,
+ [{<<"%u">>, <<"*">>}]),
{DNFilter, DNFilterAttrs} =
- case ejabberd_config:get_local_option({ldap_dn_filter, Host}) of
- undefined ->
- {undefined, []};
- {DNF, undefined} ->
- {DNF, []};
- {DNF, DNFA} ->
- {DNF, DNFA}
- end,
- eldap_utils:check_filter(DNFilter),
- LocalFilter = ejabberd_config:get_local_option({ldap_local_filter, Host}),
- DerefAliases = case ejabberd_config:get_local_option(
- {ldap_deref_aliases, Host}) of
- undefined -> never;
- Val -> Val
- end,
- #state{host = Host,
- eldap_id = Eldap_ID,
- bind_eldap_id = Bind_Eldap_ID,
- servers = LDAPServers,
- backups = LDAPBackups,
- port = LDAPPort,
- tls_options = [{encrypt, LDAPEncrypt},
- {tls_verify, LDAPTLSVerify},
- {tls_cacertfile, LDAPTLSCAFile},
- {tls_depth, LDAPTLSDepth}],
- dn = RootDN,
- password = Password,
- base = LDAPBase,
- uids = UIDs,
- ufilter = UserFilter,
- sfilter = SearchFilter,
- lfilter = LocalFilter,
- deref_aliases = DerefAliases,
- dn_filter = DNFilter,
- dn_filter_attrs = DNFilterAttrs
- }.
+ eldap_utils:get_opt({ldap_dn_filter, Host}, [],
+ fun({DNF, DNFA}) ->
+ NewDNFA = case DNFA of
+ undefined ->
+ [];
+ _ ->
+ [iolist_to_binary(A)
+ || A <- DNFA]
+ end,
+ NewDNF = check_filter(DNF),
+ {NewDNF, NewDNFA}
+ end, {undefined, []}),
+ LocalFilter = eldap_utils:get_opt(
+ {ldap_local_filter, Host}, [], fun(V) -> V end),
+ #state{host = Host, eldap_id = Eldap_ID,
+ bind_eldap_id = Bind_Eldap_ID,
+ servers = Cfg#eldap_config.servers,
+ backups = Cfg#eldap_config.backups,
+ port = Cfg#eldap_config.port,
+ tls_options = Cfg#eldap_config.tls_options,
+ dn = Cfg#eldap_config.dn,
+ password = Cfg#eldap_config.password,
+ base = Cfg#eldap_config.base,
+ deref_aliases = Cfg#eldap_config.deref_aliases,
+ uids = UIDs, ufilter = UserFilter,
+ sfilter = SearchFilter, lfilter = LocalFilter,
+ dn_filter = DNFilter, dn_filter_attrs = DNFilterAttrs}.
+
+check_filter(F) ->
+ NewF = iolist_to_binary(F),
+ {ok, _} = eldap_filter:parse(NewF),
+ NewF.
diff --git a/src/ejabberd_auth_odbc.erl b/src/ejabberd_auth_odbc.erl
index 3f648d666..7a2e90e02 100644
--- a/src/ejabberd_auth_odbc.erl
+++ b/src/ejabberd_auth_odbc.erl
@@ -25,223 +25,197 @@
%%%----------------------------------------------------------------------
-module(ejabberd_auth_odbc).
+
-author('alexey@process-one.net').
+-behaviour(ejabberd_auth).
+
%% External exports
--export([start/1,
- set_password/3,
- check_password/3,
- check_password/5,
- try_register/3,
- dirty_get_registered_users/0,
- get_vh_registered_users/1,
+-export([start/1, set_password/3, check_password/3,
+ check_password/5, try_register/3,
+ dirty_get_registered_users/0, get_vh_registered_users/1,
get_vh_registered_users/2,
get_vh_registered_users_number/1,
- get_vh_registered_users_number/2,
- get_password/2,
- get_password_s/2,
- is_user_exists/2,
- remove_user/2,
- remove_user/3,
- store_type/0,
- plain_password_required/0
- ]).
+ get_vh_registered_users_number/2, get_password/2,
+ get_password_s/2, is_user_exists/2, remove_user/2,
+ remove_user/3, store_type/0,
+ plain_password_required/0]).
-include("ejabberd.hrl").
%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
-start(_Host) ->
- ok.
+start(_Host) -> ok.
-plain_password_required() ->
- false.
+plain_password_required() -> false.
-store_type() ->
- plain.
+store_type() -> plain.
%% @spec (User, Server, Password) -> true | false | {error, Error}
check_password(User, Server, Password) ->
case jlib:nodeprep(User) of
- error ->
- false;
- LUser ->
- Username = ejabberd_odbc:escape(LUser),
- LServer = jlib:nameprep(Server),
- try odbc_queries:get_password(LServer, Username) of
- {selected, ["password"], [{Password}]} ->
- Password /= ""; %% Password is correct, and not empty
- {selected, ["password"], [{_Password2}]} ->
- false; %% Password is not correct
- {selected, ["password"], []} ->
- false; %% Account does not exist
- {error, _Error} ->
- false %% Typical error is that table doesn't exist
- catch
- _:_ ->
- false %% Typical error is database not accessible
- end
+ error -> false;
+ LUser ->
+ Username = ejabberd_odbc:escape(LUser),
+ LServer = jlib:nameprep(Server),
+ try odbc_queries:get_password(LServer, Username) of
+ {selected, [<<"password">>], [[Password]]} ->
+ Password /= <<"">>;
+ {selected, [<<"password">>], [[_Password2]]} ->
+ false; %% Password is not correct
+ {selected, [<<"password">>], []} ->
+ false; %% Account does not exist
+ {error, _Error} ->
+ false %% Typical error is that table doesn't exist
+ catch
+ _:_ ->
+ false %% Typical error is database not accessible
+ end
end.
%% @spec (User, Server, Password, Digest, DigestGen) -> true | false | {error, Error}
-check_password(User, Server, Password, Digest, DigestGen) ->
+check_password(User, Server, Password, Digest,
+ DigestGen) ->
case jlib:nodeprep(User) of
- error ->
- false;
- LUser ->
- Username = ejabberd_odbc:escape(LUser),
- LServer = jlib:nameprep(Server),
- try odbc_queries:get_password(LServer, Username) of
- %% Account exists, check if password is valid
- {selected, ["password"], [{Passwd}]} ->
- DigRes = if
- Digest /= "" ->
- Digest == DigestGen(Passwd);
- true ->
- false
- end,
- if DigRes ->
- true;
- true ->
- (Passwd == Password) and (Password /= "")
- end;
- {selected, ["password"], []} ->
- false; %% Account does not exist
- {error, _Error} ->
- false %% Typical error is that table doesn't exist
- catch
- _:_ ->
- false %% Typical error is database not accessible
- end
+ error -> false;
+ LUser ->
+ Username = ejabberd_odbc:escape(LUser),
+ LServer = jlib:nameprep(Server),
+ try odbc_queries:get_password(LServer, Username) of
+ %% Account exists, check if password is valid
+ {selected, [<<"password">>], [[Passwd]]} ->
+ DigRes = if Digest /= <<"">> ->
+ Digest == DigestGen(Passwd);
+ true -> false
+ end,
+ if DigRes -> true;
+ true -> (Passwd == Password) and (Password /= <<"">>)
+ end;
+ {selected, [<<"password">>], []} ->
+ false; %% Account does not exist
+ {error, _Error} ->
+ false %% Typical error is that table doesn't exist
+ catch
+ _:_ ->
+ false %% Typical error is database not accessible
+ end
end.
%% @spec (User::string(), Server::string(), Password::string()) ->
%% ok | {error, invalid_jid}
set_password(User, Server, Password) ->
case jlib:nodeprep(User) of
- error ->
- {error, invalid_jid};
- LUser ->
- Username = ejabberd_odbc:escape(LUser),
- Pass = ejabberd_odbc:escape(Password),
- LServer = jlib:nameprep(Server),
- case catch odbc_queries:set_password_t(LServer, Username, Pass) of
- {atomic, ok} -> ok;
- Other -> {error, Other}
- end
+ error -> {error, invalid_jid};
+ LUser ->
+ Username = ejabberd_odbc:escape(LUser),
+ Pass = ejabberd_odbc:escape(Password),
+ LServer = jlib:nameprep(Server),
+ case catch odbc_queries:set_password_t(LServer,
+ Username, Pass)
+ of
+ {atomic, ok} -> ok;
+ Other -> {error, Other}
+ end
end.
-
%% @spec (User, Server, Password) -> {atomic, ok} | {atomic, exists} | {error, invalid_jid}
try_register(User, Server, Password) ->
case jlib:nodeprep(User) of
- error ->
- {error, invalid_jid};
- LUser ->
- Username = ejabberd_odbc:escape(LUser),
- Pass = ejabberd_odbc:escape(Password),
- LServer = jlib:nameprep(Server),
- case catch odbc_queries:add_user(LServer, Username, Pass) of
- {updated, 1} ->
- {atomic, ok};
- _ ->
- {atomic, exists}
- end
+ error -> {error, invalid_jid};
+ LUser ->
+ Username = ejabberd_odbc:escape(LUser),
+ Pass = ejabberd_odbc:escape(Password),
+ LServer = jlib:nameprep(Server),
+ case catch odbc_queries:add_user(LServer, Username,
+ Pass)
+ of
+ {updated, 1} -> {atomic, ok};
+ _ -> {atomic, exists}
+ end
end.
dirty_get_registered_users() ->
Servers = ejabberd_config:get_vh_by_auth_method(odbc),
- lists:flatmap(
- fun(Server) ->
- get_vh_registered_users(Server)
- end, Servers).
+ lists:flatmap(fun (Server) ->
+ get_vh_registered_users(Server)
+ end,
+ Servers).
get_vh_registered_users(Server) ->
LServer = jlib:nameprep(Server),
case catch odbc_queries:list_users(LServer) of
- {selected, ["username"], Res} ->
- [{U, LServer} || {U} <- Res];
- _ ->
- []
+ {selected, [<<"username">>], Res} ->
+ [{U, LServer} || [U] <- Res];
+ _ -> []
end.
get_vh_registered_users(Server, Opts) ->
LServer = jlib:nameprep(Server),
case catch odbc_queries:list_users(LServer, Opts) of
- {selected, ["username"], Res} ->
- [{U, LServer} || {U} <- Res];
- _ ->
- []
+ {selected, [<<"username">>], Res} ->
+ [{U, LServer} || [U] <- Res];
+ _ -> []
end.
get_vh_registered_users_number(Server) ->
LServer = jlib:nameprep(Server),
case catch odbc_queries:users_number(LServer) of
- {selected, [_], [{Res}]} ->
- list_to_integer(Res);
- _ ->
- 0
+ {selected, [_], [[Res]]} ->
+ jlib:binary_to_integer(Res);
+ _ -> 0
end.
get_vh_registered_users_number(Server, Opts) ->
LServer = jlib:nameprep(Server),
case catch odbc_queries:users_number(LServer, Opts) of
- {selected, [_], [{Res}]} ->
- list_to_integer(Res);
- _Other ->
- 0
+ {selected, [_], [[Res]]} ->
+ jlib:binary_to_integer(Res);
+ _Other -> 0
end.
get_password(User, Server) ->
case jlib:nodeprep(User) of
- error ->
- false;
- LUser ->
- Username = ejabberd_odbc:escape(LUser),
- LServer = jlib:nameprep(Server),
- case catch odbc_queries:get_password(LServer, Username) of
- {selected, ["password"], [{Password}]} ->
- Password;
- _ ->
- false
- end
+ error -> false;
+ LUser ->
+ Username = ejabberd_odbc:escape(LUser),
+ LServer = jlib:nameprep(Server),
+ case catch odbc_queries:get_password(LServer, Username)
+ of
+ {selected, [<<"password">>], [[Password]]} -> Password;
+ _ -> false
+ end
end.
get_password_s(User, Server) ->
case jlib:nodeprep(User) of
- error ->
- "";
- LUser ->
- Username = ejabberd_odbc:escape(LUser),
- LServer = jlib:nameprep(Server),
- case catch odbc_queries:get_password(LServer, Username) of
- {selected, ["password"], [{Password}]} ->
- Password;
- _ ->
- ""
- end
+ error -> <<"">>;
+ LUser ->
+ Username = ejabberd_odbc:escape(LUser),
+ LServer = jlib:nameprep(Server),
+ case catch odbc_queries:get_password(LServer, Username)
+ of
+ {selected, [<<"password">>], [[Password]]} -> Password;
+ _ -> <<"">>
+ end
end.
%% @spec (User, Server) -> true | false | {error, Error}
is_user_exists(User, Server) ->
case jlib:nodeprep(User) of
- error ->
- false;
- LUser ->
- Username = ejabberd_odbc:escape(LUser),
- LServer = jlib:nameprep(Server),
- try odbc_queries:get_password(LServer, Username) of
- {selected, ["password"], [{_Password}]} ->
- true; %% Account exists
- {selected, ["password"], []} ->
- false; %% Account does not exist
- {error, Error} ->
- {error, Error} %% Typical error is that table doesn't exist
- catch
- _:B ->
- {error, B} %% Typical error is database not accessible
- end
+ error -> false;
+ LUser ->
+ Username = ejabberd_odbc:escape(LUser),
+ LServer = jlib:nameprep(Server),
+ try odbc_queries:get_password(LServer, Username) of
+ {selected, [<<"password">>], [[_Password]]} ->
+ true; %% Account exists
+ {selected, [<<"password">>], []} ->
+ false; %% Account does not exist
+ {error, Error} -> {error, Error}
+ catch
+ _:B -> {error, B}
+ end
end.
%% @spec (User, Server) -> ok | error
@@ -249,37 +223,34 @@ is_user_exists(User, Server) ->
%% Note: it may return ok even if there was some problem removing the user.
remove_user(User, Server) ->
case jlib:nodeprep(User) of
- error ->
- error;
- LUser ->
- Username = ejabberd_odbc:escape(LUser),
- LServer = jlib:nameprep(Server),
- catch odbc_queries:del_user(LServer, Username),
- ok
+ error -> error;
+ LUser ->
+ Username = ejabberd_odbc:escape(LUser),
+ LServer = jlib:nameprep(Server),
+ catch odbc_queries:del_user(LServer, Username),
+ ok
end.
%% @spec (User, Server, Password) -> ok | error | not_exists | not_allowed
%% @doc Remove user if the provided password is correct.
remove_user(User, Server, Password) ->
case jlib:nodeprep(User) of
- error ->
- error;
- LUser ->
- Username = ejabberd_odbc:escape(LUser),
- Pass = ejabberd_odbc:escape(Password),
- LServer = jlib:nameprep(Server),
- F = fun() ->
- Result = odbc_queries:del_user_return_password(
- LServer, Username, Pass),
- case Result of
- {selected, ["password"], [{Password}]} ->
- ok;
- {selected, ["password"], []} ->
- not_exists;
- _ ->
- not_allowed
- end
- end,
- {atomic, Result} = odbc_queries:sql_transaction(LServer, F),
- Result
+ error -> error;
+ LUser ->
+ Username = ejabberd_odbc:escape(LUser),
+ Pass = ejabberd_odbc:escape(Password),
+ LServer = jlib:nameprep(Server),
+ F = fun () ->
+ Result = odbc_queries:del_user_return_password(LServer,
+ Username,
+ Pass),
+ case Result of
+ {selected, [<<"password">>], [[Password]]} -> ok;
+ {selected, [<<"password">>], []} -> not_exists;
+ _ -> not_allowed
+ end
+ end,
+ {atomic, Result} = odbc_queries:sql_transaction(LServer,
+ F),
+ Result
end.
diff --git a/src/ejabberd_auth_pam.erl b/src/ejabberd_auth_pam.erl
index 29752ba8d..a1400fe8e 100644
--- a/src/ejabberd_auth_pam.erl
+++ b/src/ejabberd_auth_pam.erl
@@ -24,102 +24,102 @@
%%%
%%%-------------------------------------------------------------------
-module(ejabberd_auth_pam).
+
-author('xram@jabber.ru').
-%% External exports
--export([start/1,
- set_password/3,
- check_password/3,
- check_password/5,
- try_register/3,
- dirty_get_registered_users/0,
- get_vh_registered_users/1,
- get_password/2,
- get_password_s/2,
- is_user_exists/2,
- remove_user/2,
- remove_user/3,
- store_type/0,
- plain_password_required/0
- ]).
+-behaviour(ejabberd_auth).
+%% External exports
%%====================================================================
%% API
%%====================================================================
+-export([start/1, set_password/3, check_password/3,
+ check_password/5, try_register/3,
+ dirty_get_registered_users/0, get_vh_registered_users/1,
+ get_vh_registered_users/2, get_vh_registered_users_number/1,
+ get_vh_registered_users_number/2,
+ get_password/2, get_password_s/2, is_user_exists/2,
+ remove_user/2, remove_user/3, store_type/0,
+ plain_password_required/0]).
+
start(_Host) ->
case epam:start() of
- {ok, _} -> ok;
- {error,{already_started, _}} -> ok;
- Err -> Err
+ {ok, _} -> ok;
+ {error, {already_started, _}} -> ok;
+ Err -> Err
end.
set_password(_User, _Server, _Password) ->
{error, not_allowed}.
-check_password(User, Server, Password, _Digest, _DigestGen) ->
+check_password(User, Server, Password, _Digest,
+ _DigestGen) ->
check_password(User, Server, Password).
check_password(User, Host, Password) ->
Service = get_pam_service(Host),
UserInfo = case get_pam_userinfotype(Host) of
- username -> User;
- jid -> User++"@"++Host
- end,
- case catch epam:authenticate(Service, UserInfo, Password) of
- true -> true;
- _ -> false
+ username -> User;
+ jid -> <<User/binary, "@", Host/binary>>
+ end,
+ case catch epam:authenticate(Service, UserInfo,
+ Password)
+ of
+ true -> true;
+ _ -> false
end.
try_register(_User, _Server, _Password) ->
{error, not_allowed}.
-dirty_get_registered_users() ->
- [].
+dirty_get_registered_users() -> [].
+
+get_vh_registered_users(_Host) -> [].
+
+get_vh_registered_users(_Host, _) -> [].
-get_vh_registered_users(_Host) ->
- [].
+get_vh_registered_users_number(_Host) -> 0.
-get_password(_User, _Server) ->
- false.
+get_vh_registered_users_number(_Host, _) -> 0.
-get_password_s(_User, _Server) ->
- "".
+get_password(_User, _Server) -> false.
+
+get_password_s(_User, _Server) -> <<"">>.
%% @spec (User, Server) -> true | false | {error, Error}
%% TODO: Improve this function to return an error instead of 'false' when connection to PAM failed
is_user_exists(User, Host) ->
Service = get_pam_service(Host),
UserInfo = case get_pam_userinfotype(Host) of
- username -> User;
- jid -> User++"@"++Host
- end,
+ username -> User;
+ jid -> <<User/binary, "@", Host/binary>>
+ end,
case catch epam:acct_mgmt(Service, UserInfo) of
- true -> true;
- _ -> false
+ true -> true;
+ _ -> false
end.
-remove_user(_User, _Server) ->
- {error, not_allowed}.
+remove_user(_User, _Server) -> {error, not_allowed}.
-remove_user(_User, _Server, _Password) ->
- not_allowed.
+remove_user(_User, _Server, _Password) -> not_allowed.
-plain_password_required() ->
- true.
+plain_password_required() -> true.
-store_type() ->
- external.
+store_type() -> external.
%%====================================================================
%% Internal functions
%%====================================================================
get_pam_service(Host) ->
- case ejabberd_config:get_local_option({pam_service, Host}) of
- undefined -> "ejabberd";
- Service -> Service
- end.
+ ejabberd_config:get_local_option(
+ {pam_service, Host},
+ fun iolist_to_binary/1,
+ <<"ejabberd">>).
+
get_pam_userinfotype(Host) ->
- case ejabberd_config:get_local_option({pam_userinfotype, Host}) of
- undefined -> username;
- Type -> Type
- end.
+ ejabberd_config:get_local_option(
+ {pam_userinfotype, Host},
+ fun(username) -> username;
+ (jid) -> jid
+ end,
+ username).
diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl
index ed26400f0..f1cde0e0f 100644
--- a/src/ejabberd_c2s.erl
+++ b/src/ejabberd_c2s.erl
@@ -25,7 +25,9 @@
%%%----------------------------------------------------------------------
-module(ejabberd_c2s).
+
-author('alexey@process-one.net').
+
-update_info({update, 0}).
-define(GEN_FSM, p1_fsm).
@@ -61,11 +63,13 @@
code_change/4,
handle_info/3,
terminate/3,
- print_state/1
+ print_state/1
]).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
+
-include("mod_privacy.hrl").
-define(SETS, gb_sets).
@@ -88,7 +92,7 @@
tls_options = [],
authenticated = false,
jid,
- user = "", server = ?MYNAME, resource = "",
+ user = "", server = ?MYNAME, resource = <<"">>,
sid,
pres_t = ?SETS:new(),
pres_f = ?SETS:new(),
@@ -107,9 +111,13 @@
%-define(DBGFSM, true).
-ifdef(DBGFSM).
+
-define(FSMOPTS, [{debug, [trace]}]).
+
-else.
+
-define(FSMOPTS, []).
+
-endif.
%% Module start with or without supervisor:
@@ -124,22 +132,26 @@
%% This is the timeout to apply between event when starting a new
%% session:
-define(C2S_OPEN_TIMEOUT, 60000).
+
-define(C2S_HIBERNATE_TIMEOUT, 90000).
-define(STREAM_HEADER,
- "<?xml version='1.0'?>"
- "<stream:stream xmlns='jabber:client' "
- "xmlns:stream='http://etherx.jabber.org/streams' "
- "id='~s' from='~s'~s~s>"
- ).
+ <<"<?xml version='1.0'?><stream:stream "
+ "xmlns='jabber:client' xmlns:stream='http://et"
+ "herx.jabber.org/streams' id='~s' from='~s'~s~"
+ "s>">>).
--define(STREAM_TRAILER, "</stream:stream>").
+-define(STREAM_TRAILER, <<"</stream:stream>">>).
-define(INVALID_NS_ERR, ?SERR_INVALID_NAMESPACE).
+
-define(INVALID_XML_ERR, ?SERR_XML_NOT_WELL_FORMED).
+
-define(HOST_UNKNOWN_ERR, ?SERR_HOST_UNKNOWN).
+
-define(POLICY_VIOLATION_ERR(Lang, Text),
?SERRT_POLICY_VIOLATION(Lang, Text)).
+
-define(INVALID_FROM, ?SERR_INVALID_FROM).
@@ -153,24 +165,23 @@ start_link(SockData, Opts) ->
?GEN_FSM:start_link(ejabberd_c2s, [SockData, Opts],
fsm_limit_opts(Opts) ++ ?FSMOPTS).
-socket_type() ->
- xml_stream.
+socket_type() -> xml_stream.
%% Return Username, Resource and presence information
get_presence(FsmRef) ->
- ?GEN_FSM:sync_send_all_state_event(FsmRef, {get_presence}, 1000).
+ (?GEN_FSM):sync_send_all_state_event(FsmRef,
+ {get_presence}, 1000).
get_aux_field(Key, #state{aux_fields = Opts}) ->
case lists:keysearch(Key, 1, Opts) of
- {value, {_, Val}} ->
- {ok, Val};
- _ ->
- error
+ {value, {_, Val}} -> {ok, Val};
+ _ -> error
end.
-set_aux_field(Key, Val, #state{aux_fields = Opts} = State) ->
+set_aux_field(Key, Val,
+ #state{aux_fields = Opts} = State) ->
Opts1 = lists:keydelete(Key, 1, Opts),
- State#state{aux_fields = [{Key, Val}|Opts1]}.
+ State#state{aux_fields = [{Key, Val} | Opts1]}.
del_aux_field(Key, #state{aux_fields = Opts} = State) ->
Opts1 = lists:keydelete(Key, 1, Opts),
@@ -179,11 +190,13 @@ del_aux_field(Key, #state{aux_fields = Opts} = State) ->
get_subscription(From = #jid{}, StateData) ->
get_subscription(jlib:jid_tolower(From), StateData);
get_subscription(LFrom, StateData) ->
- LBFrom = setelement(3, LFrom, ""),
- F = ?SETS:is_element(LFrom, StateData#state.pres_f) orelse
- ?SETS:is_element(LBFrom, StateData#state.pres_f),
- T = ?SETS:is_element(LFrom, StateData#state.pres_t) orelse
- ?SETS:is_element(LBFrom, StateData#state.pres_t),
+ LBFrom = setelement(3, LFrom, <<"">>),
+ F = (?SETS):is_element(LFrom, StateData#state.pres_f)
+ orelse
+ (?SETS):is_element(LBFrom, StateData#state.pres_f),
+ T = (?SETS):is_element(LFrom, StateData#state.pres_t)
+ orelse
+ (?SETS):is_element(LBFrom, StateData#state.pres_t),
if F and T -> both;
F -> from;
T -> to;
@@ -193,8 +206,7 @@ get_subscription(LFrom, StateData) ->
broadcast(FsmRef, Type, From, Packet) ->
FsmRef ! {broadcast, Type, From, Packet}.
-stop(FsmRef) ->
- ?GEN_FSM:send_event(FsmRef, closed).
+stop(FsmRef) -> (?GEN_FSM):send_event(FsmRef, closed).
%%%----------------------------------------------------------------------
%%% Callback functions from gen_fsm
@@ -209,63 +221,58 @@ stop(FsmRef) ->
%%----------------------------------------------------------------------
init([{SockMod, Socket}, Opts]) ->
Access = case lists:keysearch(access, 1, Opts) of
- {value, {_, A}} -> A;
- _ -> all
+ {value, {_, A}} -> A;
+ _ -> all
end,
Shaper = case lists:keysearch(shaper, 1, Opts) of
- {value, {_, S}} -> S;
- _ -> none
+ {value, {_, S}} -> S;
+ _ -> none
end,
- XMLSocket =
- case lists:keysearch(xml_socket, 1, Opts) of
- {value, {_, XS}} -> XS;
- _ -> false
- end,
+ XMLSocket = case lists:keysearch(xml_socket, 1, Opts) of
+ {value, {_, XS}} -> XS;
+ _ -> false
+ end,
Zlib = lists:member(zlib, Opts),
StartTLS = lists:member(starttls, Opts),
- StartTLSRequired = lists:member(starttls_required, Opts),
+ StartTLSRequired = lists:member(starttls_required,
+ Opts),
TLSEnabled = lists:member(tls, Opts),
- TLS = StartTLS orelse StartTLSRequired orelse TLSEnabled,
- TLSOpts1 =
- lists:filter(fun({certfile, _}) -> true;
- (_) -> false
- end, Opts),
+ TLS = StartTLS orelse
+ StartTLSRequired orelse TLSEnabled,
+ TLSOpts1 = lists:filter(fun ({certfile, _}) -> true;
+ (_) -> false
+ end,
+ Opts),
TLSOpts = [verify_none | TLSOpts1],
IP = peerip(SockMod, Socket),
%% Check if IP is blacklisted:
case is_ip_blacklisted(IP) of
- true ->
- ?INFO_MSG("Connection attempt from blacklisted IP: ~s (~w)",
- [jlib:ip_to_list(IP), IP]),
- {stop, normal};
- false ->
- Socket1 =
- if
- TLSEnabled ->
- SockMod:starttls(Socket, TLSOpts);
- true ->
- Socket
- end,
- SocketMonitor = SockMod:monitor(Socket1),
- {ok, wait_for_stream, #state{socket = Socket1,
- sockmod = SockMod,
- socket_monitor = SocketMonitor,
- xml_socket = XMLSocket,
- zlib = Zlib,
- tls = TLS,
- tls_required = StartTLSRequired,
- tls_enabled = TLSEnabled,
- tls_options = TLSOpts,
- streamid = new_id(),
- access = Access,
- shaper = Shaper,
- ip = IP},
- ?C2S_OPEN_TIMEOUT}
+ true ->
+ ?INFO_MSG("Connection attempt from blacklisted "
+ "IP: ~s (~w)",
+ [jlib:ip_to_list(IP), IP]),
+ {stop, normal};
+ false ->
+ Socket1 = if TLSEnabled andalso
+ SockMod /= ejabberd_frontend_socket ->
+ SockMod:starttls(Socket, TLSOpts);
+ true -> Socket
+ end,
+ SocketMonitor = SockMod:monitor(Socket1),
+ StateData = #state{socket = Socket1, sockmod = SockMod,
+ socket_monitor = SocketMonitor,
+ xml_socket = XMLSocket, zlib = Zlib, tls = TLS,
+ tls_required = StartTLSRequired,
+ tls_enabled = TLSEnabled, tls_options = TLSOpts,
+ streamid = new_id(), access = Access,
+ shaper = Shaper, ip = IP},
+ {ok, wait_for_stream, StateData, ?C2S_OPEN_TIMEOUT}
end.
%% Return list of all available resources of contacts,
get_subscribed(FsmRef) ->
- ?GEN_FSM:sync_send_all_state_event(FsmRef, get_subscribed, 1000).
+ (?GEN_FSM):sync_send_all_state_event(FsmRef,
+ get_subscribed, 1000).
%%----------------------------------------------------------------------
%% Func: StateName/2
@@ -276,17 +283,15 @@ get_subscribed(FsmRef) ->
wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
DefaultLang = case ?MYLANG of
- undefined ->
- "en";
- DL ->
- DL
- end,
- case xml:get_attr_s("xmlns:stream", Attrs) of
+ undefined -> <<"en">>;
+ DL -> DL
+ end,
+ case xml:get_attr_s(<<"xmlns:stream">>, Attrs) of
?NS_STREAM ->
- Server = jlib:nameprep(xml:get_attr_s("to", Attrs)),
+ Server = jlib:nameprep(xml:get_attr_s(<<"to">>, Attrs)),
case lists:member(Server, ?MYHOSTS) of
true ->
- Lang = case xml:get_attr_s("xml:lang", Attrs) of
+ Lang = case xml:get_attr_s(<<"xml:lang">>, Attrs) of
Lang1 when length(Lang1) =< 35 ->
%% As stated in BCP47, 4.4.1:
%% Protocols or specifications that
@@ -297,17 +302,17 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
_ ->
%% Do not store long language tag to
%% avoid possible DoS/flood attacks
- ""
+ <<"">>
end,
- change_shaper(StateData, jlib:make_jid("", Server, "")),
- case xml:get_attr_s("version", Attrs) of
- "1.0" ->
- send_header(StateData, Server, "1.0", DefaultLang),
+ change_shaper(StateData, jlib:make_jid(<<"">>, Server, <<"">>)),
+ case xml:get_attr_s(<<"version">>, Attrs) of
+ <<"1.0">> ->
+ send_header(StateData, Server, <<"1.0">>, DefaultLang),
case StateData#state.authenticated of
false ->
SASLState =
cyrsasl:server_new(
- "jabber", Server, "", [],
+ <<"jabber">>, Server, <<"">>, [],
fun(U) ->
ejabberd_auth:get_password_with_authmodule(
U, Server)
@@ -320,11 +325,12 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
ejabberd_auth:check_password_with_authmodule(
U, Server, P, D, DG)
end),
- Mechs = lists:map(
- fun(S) ->
- {xmlelement, "mechanism", [],
- [{xmlcdata, S}]}
- end, cyrsasl:listmech(Server)),
+ Mechs = lists:map(fun (S) ->
+ #xmlel{name = <<"mechanism">>,
+ attrs = [],
+ children = [{xmlcdata, S}]}
+ end,
+ cyrsasl:listmech(Server)),
SockMod =
(StateData#state.sockmod):get_sockmod(
StateData#state.socket),
@@ -334,10 +340,11 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
((SockMod == gen_tcp) orelse
(SockMod == tls)) of
true ->
- [{xmlelement, "compression",
- [{"xmlns", ?NS_FEATURE_COMPRESS}],
- [{xmlelement, "method",
- [], [{xmlcdata, "zlib"}]}]}];
+ [#xmlel{name = <<"compression">>,
+ attrs = [{<<"xmlns">>, ?NS_FEATURE_COMPRESS}],
+ children = [#xmlel{name = <<"method">>,
+ attrs = [],
+ children = [{xmlcdata, <<"zlib">>}]}]}];
_ ->
[]
end,
@@ -351,27 +358,30 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
true ->
case TLSRequired of
true ->
- [{xmlelement, "starttls",
- [{"xmlns", ?NS_TLS}],
- [{xmlelement, "required",
- [], []}]}];
+ [#xmlel{name = <<"starttls">>,
+ attrs = [{<<"xmlns">>, ?NS_TLS}],
+ children = [#xmlel{name = <<"required">>,
+ attrs = [],
+ children = []}]}];
_ ->
- [{xmlelement, "starttls",
- [{"xmlns", ?NS_TLS}], []}]
+ [#xmlel{name = <<"starttls">>,
+ attrs = [{<<"xmlns">>, ?NS_TLS}],
+ children = []}]
end;
false ->
[]
end,
send_element(StateData,
- {xmlelement, "stream:features", [],
- TLSFeature ++ CompressFeature ++
- [{xmlelement, "mechanisms",
- [{"xmlns", ?NS_SASL}],
- Mechs}] ++
- ejabberd_hooks:run_fold(
- c2s_stream_features,
- Server,
- [], [Server])}),
+ #xmlel{name = <<"stream:features">>,
+ attrs = [],
+ children =
+ TLSFeature ++ CompressFeature ++
+ [#xmlel{name = <<"mechanisms">>,
+ attrs = [{<<"xmlns">>, ?NS_SASL}],
+ children = Mechs}]
+ ++
+ ejabberd_hooks:run_fold(c2s_stream_features,
+ Server, [], [Server])}),
fsm_next_state(wait_for_feature_request,
StateData#state{
server = Server,
@@ -379,556 +389,578 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
lang = Lang});
_ ->
case StateData#state.resource of
- "" ->
- RosterVersioningFeature =
- ejabberd_hooks:run_fold(
- roster_get_versioning_feature,
- Server, [], [Server]),
- StreamFeatures =
- [{xmlelement, "bind",
- [{"xmlns", ?NS_BIND}], []},
- {xmlelement, "session",
- [{"xmlns", ?NS_SESSION}], []}]
- ++ RosterVersioningFeature
- ++ ejabberd_hooks:run_fold(
- c2s_stream_features,
- Server,
- [], [Server]),
- send_element(
- StateData,
- {xmlelement, "stream:features", [],
- StreamFeatures}),
- fsm_next_state(wait_for_bind,
- StateData#state{
- server = Server,
- lang = Lang});
- _ ->
- send_element(
- StateData,
- {xmlelement, "stream:features", [], []}),
- fsm_next_state(wait_for_session,
- StateData#state{
- server = Server,
- lang = Lang})
+ <<"">> ->
+ RosterVersioningFeature =
+ ejabberd_hooks:run_fold(roster_get_versioning_feature,
+ Server, [],
+ [Server]),
+ StreamFeatures = [#xmlel{name = <<"bind">>,
+ attrs = [{<<"xmlns">>, ?NS_BIND}],
+ children = []},
+ #xmlel{name = <<"session">>,
+ attrs = [{<<"xmlns">>, ?NS_SESSION}],
+ children = []}]
+ ++
+ RosterVersioningFeature ++
+ ejabberd_hooks:run_fold(c2s_stream_features,
+ Server, [], [Server]),
+ send_element(StateData,
+ #xmlel{name = <<"stream:features">>,
+ attrs = [],
+ children = StreamFeatures}),
+ fsm_next_state(wait_for_bind,
+ StateData#state{server = Server, lang = Lang});
+ _ ->
+ send_element(StateData,
+ #xmlel{name = <<"stream:features">>,
+ attrs = [],
+ children = []}),
+ fsm_next_state(wait_for_session,
+ StateData#state{server = Server, lang = Lang})
end
end;
- _ ->
- send_header(StateData, Server, "", DefaultLang),
- if
- (not StateData#state.tls_enabled) and
- StateData#state.tls_required ->
- send_element(
- StateData,
- ?POLICY_VIOLATION_ERR(
- Lang,
- "Use of STARTTLS required")),
- send_trailer(StateData),
- {stop, normal, StateData};
- true ->
- fsm_next_state(wait_for_auth,
- StateData#state{
- server = Server,
- lang = Lang})
- end
- end;
_ ->
- send_header(StateData, ?MYNAME, "", DefaultLang),
- send_element(StateData, ?HOST_UNKNOWN_ERR),
- send_trailer(StateData),
- {stop, normal, StateData}
+ send_header(StateData, Server, <<"">>, DefaultLang),
+ if not StateData#state.tls_enabled and
+ StateData#state.tls_required ->
+ send_element(StateData,
+ ?POLICY_VIOLATION_ERR(Lang,
+ <<"Use of STARTTLS required">>)),
+ send_trailer(StateData),
+ {stop, normal, StateData};
+ true ->
+ fsm_next_state(wait_for_auth,
+ StateData#state{server = Server,
+ lang = Lang})
+ end
end;
_ ->
- send_header(StateData, ?MYNAME, "", DefaultLang),
- send_element(StateData, ?INVALID_NS_ERR),
+ send_header(StateData, ?MYNAME, <<"">>, DefaultLang),
+ send_element(StateData, ?HOST_UNKNOWN_ERR),
send_trailer(StateData),
{stop, normal, StateData}
+ end;
+ _ ->
+ send_header(StateData, ?MYNAME, <<"">>, DefaultLang),
+ send_element(StateData, ?INVALID_NS_ERR),
+ send_trailer(StateData),
+ {stop, normal, StateData}
end;
-
wait_for_stream(timeout, StateData) ->
{stop, normal, StateData};
-
wait_for_stream({xmlstreamelement, _}, StateData) ->
send_element(StateData, ?INVALID_XML_ERR),
send_trailer(StateData),
{stop, normal, StateData};
-
wait_for_stream({xmlstreamend, _}, StateData) ->
send_element(StateData, ?INVALID_XML_ERR),
send_trailer(StateData),
{stop, normal, StateData};
-
wait_for_stream({xmlstreamerror, _}, StateData) ->
- send_header(StateData, ?MYNAME, "1.0", ""),
+ send_header(StateData, ?MYNAME, <<"1.0">>, <<"">>),
send_element(StateData, ?INVALID_XML_ERR),
send_trailer(StateData),
{stop, normal, StateData};
-
wait_for_stream(closed, StateData) ->
{stop, normal, StateData}.
-
wait_for_auth({xmlstreamelement, El}, StateData) ->
case is_auth_packet(El) of
- {auth, _ID, get, {U, _, _, _}} ->
- {xmlelement, Name, Attrs, _Els} = jlib:make_result_iq_reply(El),
- case U of
- "" ->
- UCdata = [];
- _ ->
- UCdata = [{xmlcdata, U}]
- end,
- Res = case ejabberd_auth:plain_password_required(
- StateData#state.server) of
- false ->
- {xmlelement, Name, Attrs,
- [{xmlelement, "query", [{"xmlns", ?NS_AUTH}],
- [{xmlelement, "username", [], UCdata},
- {xmlelement, "password", [], []},
- {xmlelement, "digest", [], []},
- {xmlelement, "resource", [], []}
- ]}]};
- true ->
- {xmlelement, Name, Attrs,
- [{xmlelement, "query", [{"xmlns", ?NS_AUTH}],
- [{xmlelement, "username", [], UCdata},
- {xmlelement, "password", [], []},
- {xmlelement, "resource", [], []}
- ]}]}
- end,
- send_element(StateData, Res),
- fsm_next_state(wait_for_auth, StateData);
- {auth, _ID, set, {_U, _P, _D, ""}} ->
- Err = jlib:make_error_reply(
- El,
- ?ERR_AUTH_NO_RESOURCE_PROVIDED(StateData#state.lang)),
- send_element(StateData, Err),
- fsm_next_state(wait_for_auth, StateData);
- {auth, _ID, set, {U, P, D, R}} ->
- JID = jlib:make_jid(U, StateData#state.server, R),
- case (JID /= error) andalso
- (acl:match_rule(StateData#state.server,
- StateData#state.access, JID) == allow) of
- true ->
- DGen = fun(PW) ->
- sha:sha(StateData#state.streamid ++ PW) end,
- case ejabberd_auth:check_password_with_authmodule(
- U, StateData#state.server, P, D, DGen) of
- {true, AuthModule} ->
- ?INFO_MSG(
- "(~w) Accepted legacy authentication for ~s by ~p",
- [StateData#state.socket,
- jlib:jid_to_string(JID), AuthModule]),
- SID = {now(), self()},
- Conn = get_conn_type(StateData),
- Info = [{ip, StateData#state.ip}, {conn, Conn},
+ {auth, _ID, get, {U, _, _, _}} ->
+ #xmlel{name = Name, attrs = Attrs} =
+ jlib:make_result_iq_reply(El),
+ case U of
+ <<"">> -> UCdata = [];
+ _ -> UCdata = [{xmlcdata, U}]
+ end,
+ Res = case
+ ejabberd_auth:plain_password_required(StateData#state.server)
+ of
+ false ->
+ #xmlel{name = Name, attrs = Attrs,
+ children =
+ [#xmlel{name = <<"query">>,
+ attrs = [{<<"xmlns">>, ?NS_AUTH}],
+ children =
+ [#xmlel{name = <<"username">>,
+ attrs = [],
+ children = UCdata},
+ #xmlel{name = <<"password">>,
+ attrs = [], children = []},
+ #xmlel{name = <<"digest">>,
+ attrs = [], children = []},
+ #xmlel{name = <<"resource">>,
+ attrs = [],
+ children = []}]}]};
+ true ->
+ #xmlel{name = Name, attrs = Attrs,
+ children =
+ [#xmlel{name = <<"query">>,
+ attrs = [{<<"xmlns">>, ?NS_AUTH}],
+ children =
+ [#xmlel{name = <<"username">>,
+ attrs = [],
+ children = UCdata},
+ #xmlel{name = <<"password">>,
+ attrs = [], children = []},
+ #xmlel{name = <<"resource">>,
+ attrs = [],
+ children = []}]}]}
+ end,
+ send_element(StateData, Res),
+ fsm_next_state(wait_for_auth, StateData);
+ {auth, _ID, set, {_U, _P, _D, <<"">>}} ->
+ Err = jlib:make_error_reply(El,
+ ?ERR_AUTH_NO_RESOURCE_PROVIDED((StateData#state.lang))),
+ send_element(StateData, Err),
+ fsm_next_state(wait_for_auth, StateData);
+ {auth, _ID, set, {U, P, D, R}} ->
+ JID = jlib:make_jid(U, StateData#state.server, R),
+ case JID /= error andalso
+ acl:match_rule(StateData#state.server,
+ StateData#state.access, JID)
+ == allow
+ of
+ true ->
+ DGen = fun (PW) ->
+ sha:sha(<<(StateData#state.streamid)/binary, PW/binary>>)
+ end,
+ case ejabberd_auth:check_password_with_authmodule(U,
+ StateData#state.server,
+ P, D, DGen)
+ of
+ {true, AuthModule} ->
+ ?INFO_MSG("(~w) Accepted legacy authentication for ~s by ~p",
+ [StateData#state.socket,
+ jlib:jid_to_string(JID), AuthModule]),
+ SID = {now(), self()},
+ Conn = (StateData#state.sockmod):get_conn_type(
+ StateData#state.socket),
+ Info = [{ip, StateData#state.ip}, {conn, Conn},
{auth_module, AuthModule}],
- Res1 = jlib:make_result_iq_reply(El),
- Res = setelement(4, Res1, []),
- send_element(StateData, Res),
- ejabberd_sm:open_session(
- SID, U, StateData#state.server, R, Info),
- change_shaper(StateData, JID),
- {Fs, Ts} = ejabberd_hooks:run_fold(
- roster_get_subscription_lists,
- StateData#state.server,
- {[], []},
- [U, StateData#state.server]),
- LJID = jlib:jid_tolower(
- jlib:jid_remove_resource(JID)),
- Fs1 = [LJID | Fs],
- Ts1 = [LJID | Ts],
- PrivList =
- ejabberd_hooks:run_fold(
- privacy_get_user_list, StateData#state.server,
- #userlist{},
- [U, StateData#state.server]),
- NewStateData =
- StateData#state{
- user = U,
- resource = R,
- jid = JID,
- sid = SID,
- conn = Conn,
- auth_module = AuthModule,
- pres_f = ?SETS:from_list(Fs1),
- pres_t = ?SETS:from_list(Ts1),
- privacy_list = PrivList},
- fsm_next_state_pack(session_established,
- NewStateData);
- _ ->
- IP = peerip(StateData#state.sockmod, StateData#state.socket),
- ?INFO_MSG(
- "(~w) Failed legacy authentication for ~s from IP ~s (~w)",
- [StateData#state.socket,
- jlib:jid_to_string(JID), jlib:ip_to_list(IP), IP]),
- Err = jlib:make_error_reply(
- El, ?ERR_NOT_AUTHORIZED),
- send_element(StateData, Err),
- fsm_next_state(wait_for_auth, StateData)
- end;
- _ ->
- if
- JID == error ->
- ?INFO_MSG(
- "(~w) Forbidden legacy authentication for "
- "username '~s' with resource '~s'",
- [StateData#state.socket, U, R]),
- Err = jlib:make_error_reply(El, ?ERR_JID_MALFORMED),
- send_element(StateData, Err),
- fsm_next_state(wait_for_auth, StateData);
- true ->
- ?INFO_MSG(
- "(~w) Forbidden legacy authentication for ~s",
- [StateData#state.socket,
- jlib:jid_to_string(JID)]),
- Err = jlib:make_error_reply(El, ?ERR_NOT_ALLOWED),
- send_element(StateData, Err),
- fsm_next_state(wait_for_auth, StateData)
- end
- end;
- _ ->
- process_unauthenticated_stanza(StateData, El),
- fsm_next_state(wait_for_auth, StateData)
+ Res1 = jlib:make_result_iq_reply(El),
+ Res = Res1#xmlel{children = []},
+ send_element(StateData, Res),
+ ejabberd_sm:open_session(SID, U, StateData#state.server, R, Info),
+ change_shaper(StateData, JID),
+ {Fs, Ts} =
+ ejabberd_hooks:run_fold(roster_get_subscription_lists,
+ StateData#state.server,
+ {[], []},
+ [U,
+ StateData#state.server]),
+ LJID =
+ jlib:jid_tolower(jlib:jid_remove_resource(JID)),
+ Fs1 = [LJID | Fs],
+ Ts1 = [LJID | Ts],
+ PrivList = ejabberd_hooks:run_fold(privacy_get_user_list,
+ StateData#state.server,
+ #userlist{},
+ [U, StateData#state.server]),
+ NewStateData = StateData#state{user = U,
+ resource = R,
+ jid = JID, sid = SID,
+ conn = Conn,
+ auth_module = AuthModule,
+ pres_f = (?SETS):from_list(Fs1),
+ pres_t = (?SETS):from_list(Ts1),
+ privacy_list = PrivList},
+ fsm_next_state(session_established, NewStateData);
+ _ ->
+ IP = peerip(StateData#state.sockmod,
+ StateData#state.socket),
+ ?INFO_MSG("(~w) Failed legacy authentication for "
+ "~s from IP ~s",
+ [StateData#state.socket,
+ jlib:jid_to_string(JID), jlib:ip_to_list(IP)]),
+ Err = jlib:make_error_reply(El, ?ERR_NOT_AUTHORIZED),
+ send_element(StateData, Err),
+ fsm_next_state(wait_for_auth, StateData)
+ end;
+ _ ->
+ if JID == error ->
+ ?INFO_MSG("(~w) Forbidden legacy authentication "
+ "for username '~s' with resource '~s'",
+ [StateData#state.socket, U, R]),
+ Err = jlib:make_error_reply(El, ?ERR_JID_MALFORMED),
+ send_element(StateData, Err),
+ fsm_next_state(wait_for_auth, StateData);
+ true ->
+ ?INFO_MSG("(~w) Forbidden legacy authentication "
+ "for ~s",
+ [StateData#state.socket,
+ jlib:jid_to_string(JID)]),
+ Err = jlib:make_error_reply(El, ?ERR_NOT_ALLOWED),
+ send_element(StateData, Err),
+ fsm_next_state(wait_for_auth, StateData)
+ end
+ end;
+ _ ->
+ process_unauthenticated_stanza(StateData, El),
+ fsm_next_state(wait_for_auth, StateData)
end;
-
wait_for_auth(timeout, StateData) ->
{stop, normal, StateData};
-
wait_for_auth({xmlstreamend, _Name}, StateData) ->
- send_trailer(StateData),
- {stop, normal, StateData};
-
+ send_trailer(StateData), {stop, normal, StateData};
wait_for_auth({xmlstreamerror, _}, StateData) ->
send_element(StateData, ?INVALID_XML_ERR),
send_trailer(StateData),
{stop, normal, StateData};
-
wait_for_auth(closed, StateData) ->
{stop, normal, StateData}.
-
-wait_for_feature_request({xmlstreamelement, El}, StateData) ->
- {xmlelement, Name, Attrs, Els} = El,
+wait_for_feature_request({xmlstreamelement, El},
+ StateData) ->
+ #xmlel{name = Name, attrs = Attrs, children = Els} = El,
Zlib = StateData#state.zlib,
TLS = StateData#state.tls,
TLSEnabled = StateData#state.tls_enabled,
TLSRequired = StateData#state.tls_required,
- SockMod = (StateData#state.sockmod):get_sockmod(StateData#state.socket),
- case {xml:get_attr_s("xmlns", Attrs), Name} of
- {?NS_SASL, "auth"} when not ((SockMod == gen_tcp) and TLSRequired) ->
- Mech = xml:get_attr_s("mechanism", Attrs),
- ClientIn = jlib:decode_base64(xml:get_cdata(Els)),
- case cyrsasl:server_start(StateData#state.sasl_state,
- Mech,
- ClientIn) of
- {ok, Props} ->
- (StateData#state.sockmod):reset_stream(
- StateData#state.socket),
- send_element(StateData,
- {xmlelement, "success",
- [{"xmlns", ?NS_SASL}], []}),
- U = xml:get_attr_s(username, Props),
- AuthModule = xml:get_attr_s(auth_module, Props),
- ?INFO_MSG("(~w) Accepted authentication for ~s by ~p",
- [StateData#state.socket, U, AuthModule]),
- fsm_next_state(wait_for_stream,
- StateData#state{
- streamid = new_id(),
- authenticated = true,
- auth_module = AuthModule,
- user = U });
- {continue, ServerOut, NewSASLState} ->
- send_element(StateData,
- {xmlelement, "challenge",
- [{"xmlns", ?NS_SASL}],
- [{xmlcdata,
- jlib:encode_base64(ServerOut)}]}),
- fsm_next_state(wait_for_sasl_response,
- StateData#state{
- sasl_state = NewSASLState});
- {error, Error, Username} ->
- IP = peerip(StateData#state.sockmod, StateData#state.socket),
- ?INFO_MSG(
- "(~w) Failed authentication for ~s@~s from IP ~s (~w)",
+ SockMod =
+ (StateData#state.sockmod):get_sockmod(StateData#state.socket),
+ case {xml:get_attr_s(<<"xmlns">>, Attrs), Name} of
+ {?NS_SASL, <<"auth">>}
+ when not ((SockMod == gen_tcp) and TLSRequired) ->
+ Mech = xml:get_attr_s(<<"mechanism">>, Attrs),
+ ClientIn = jlib:decode_base64(xml:get_cdata(Els)),
+ case cyrsasl:server_start(StateData#state.sasl_state,
+ Mech, ClientIn)
+ of
+ {ok, Props} ->
+ (StateData#state.sockmod):reset_stream(StateData#state.socket),
+ %U = xml:get_attr_s(username, Props),
+ U = proplists:get_value(username, Props, <<>>),
+ %AuthModule = xml:get_attr_s(auth_module, Props),
+ AuthModule = proplists:get_value(auth_module, Props, undefined),
+ ?INFO_MSG("(~w) Accepted authentication for ~s "
+ "by ~p",
+ [StateData#state.socket, U, AuthModule]),
+ send_element(StateData,
+ #xmlel{name = <<"success">>,
+ attrs = [{<<"xmlns">>, ?NS_SASL}],
+ children = []}),
+ fsm_next_state(wait_for_stream,
+ StateData#state{streamid = new_id(),
+ authenticated = true,
+ auth_module = AuthModule,
+ user = U});
+ {continue, ServerOut, NewSASLState} ->
+ send_element(StateData,
+ #xmlel{name = <<"challenge">>,
+ attrs = [{<<"xmlns">>, ?NS_SASL}],
+ children =
+ [{xmlcdata,
+ jlib:encode_base64(ServerOut)}]}),
+ fsm_next_state(wait_for_sasl_response,
+ StateData#state{sasl_state = NewSASLState});
+ {error, Error, Username} ->
+ IP = peerip(StateData#state.sockmod, StateData#state.socket),
+ ?INFO_MSG("(~w) Failed authentication for ~s@~s from IP ~s",
[StateData#state.socket,
- Username, StateData#state.server, jlib:ip_to_list(IP), IP]),
- send_element(StateData,
- {xmlelement, "failure",
- [{"xmlns", ?NS_SASL}],
- [{xmlelement, Error, [], []}]}),
- {next_state, wait_for_feature_request, StateData,
- ?C2S_OPEN_TIMEOUT};
- {error, Error} ->
- send_element(StateData,
- {xmlelement, "failure",
- [{"xmlns", ?NS_SASL}],
- [{xmlelement, Error, [], []}]}),
- fsm_next_state(wait_for_feature_request, StateData)
- end;
- {?NS_TLS, "starttls"} when TLS == true,
- TLSEnabled == false,
- SockMod == gen_tcp ->
- TLSOpts = case ejabberd_config:get_local_option(
- {domain_certfile, StateData#state.server}) of
- undefined ->
- StateData#state.tls_options;
- CertFile ->
- [{certfile, CertFile} |
- lists:keydelete(
- certfile, 1, StateData#state.tls_options)]
- end,
- Socket = StateData#state.socket,
- TLSSocket = (StateData#state.sockmod):starttls(
- Socket, TLSOpts,
- xml:element_to_binary(
- {xmlelement, "proceed", [{"xmlns", ?NS_TLS}], []})),
- fsm_next_state(wait_for_stream,
- StateData#state{socket = TLSSocket,
- streamid = new_id(),
- tls_enabled = true
- });
- {?NS_COMPRESS, "compress"} when Zlib == true,
- ((SockMod == gen_tcp) or
- (SockMod == tls)) ->
- case xml:get_subtag(El, "method") of
- false ->
- send_element(StateData,
- {xmlelement, "failure",
- [{"xmlns", ?NS_COMPRESS}],
- [{xmlelement, "setup-failed", [], []}]}),
- fsm_next_state(wait_for_feature_request, StateData);
- Method ->
- case xml:get_tag_cdata(Method) of
- "zlib" ->
- Socket = StateData#state.socket,
- ZlibSocket = (StateData#state.sockmod):compress(
- Socket,
- xml:element_to_binary(
- {xmlelement, "compressed",
- [{"xmlns", ?NS_COMPRESS}], []})),
- fsm_next_state(wait_for_stream,
- StateData#state{socket = ZlibSocket,
- streamid = new_id()
- });
- _ ->
- send_element(StateData,
- {xmlelement, "failure",
- [{"xmlns", ?NS_COMPRESS}],
- [{xmlelement, "unsupported-method",
- [], []}]}),
- fsm_next_state(wait_for_feature_request,
- StateData)
- end
- end;
- _ ->
- if
- (SockMod == gen_tcp) and TLSRequired ->
- Lang = StateData#state.lang,
- send_element(StateData, ?POLICY_VIOLATION_ERR(
- Lang,
- "Use of STARTTLS required")),
- send_trailer(StateData),
- {stop, normal, StateData};
- true ->
- process_unauthenticated_stanza(StateData, El),
- fsm_next_state(wait_for_feature_request, StateData)
- end
+ Username, StateData#state.server, jlib:ip_to_list(IP)]),
+ send_element(StateData,
+ #xmlel{name = <<"failure">>,
+ attrs = [{<<"xmlns">>, ?NS_SASL}],
+ children =
+ [#xmlel{name = Error, attrs = [],
+ children = []}]}),
+ {next_state, wait_for_feature_request, StateData,
+ ?C2S_OPEN_TIMEOUT};
+ {error, Error} ->
+ send_element(StateData,
+ #xmlel{name = <<"failure">>,
+ attrs = [{<<"xmlns">>, ?NS_SASL}],
+ children =
+ [#xmlel{name = Error, attrs = [],
+ children = []}]}),
+ fsm_next_state(wait_for_feature_request, StateData)
+ end;
+ {?NS_TLS, <<"starttls">>}
+ when TLS == true, TLSEnabled == false,
+ SockMod == gen_tcp ->
+ TLSOpts = case
+ ejabberd_config:get_local_option(
+ {domain_certfile, StateData#state.server},
+ fun iolist_to_binary/1)
+ of
+ undefined -> StateData#state.tls_options;
+ CertFile ->
+ [{certfile, CertFile} | lists:keydelete(certfile, 1,
+ StateData#state.tls_options)]
+ end,
+ Socket = StateData#state.socket,
+ TLSSocket = (StateData#state.sockmod):starttls(Socket,
+ TLSOpts,
+ xml:element_to_binary(#xmlel{name
+ =
+ <<"proceed">>,
+ attrs
+ =
+ [{<<"xmlns">>,
+ ?NS_TLS}],
+ children
+ =
+ []})),
+ fsm_next_state(wait_for_stream,
+ StateData#state{socket = TLSSocket,
+ streamid = new_id(),
+ tls_enabled = true});
+ {?NS_COMPRESS, <<"compress">>}
+ when Zlib == true,
+ (SockMod == gen_tcp) or (SockMod == tls) ->
+ case xml:get_subtag(El, <<"method">>) of
+ false ->
+ send_element(StateData,
+ #xmlel{name = <<"failure">>,
+ attrs = [{<<"xmlns">>, ?NS_COMPRESS}],
+ children =
+ [#xmlel{name = <<"setup-failed">>,
+ attrs = [], children = []}]}),
+ fsm_next_state(wait_for_feature_request, StateData);
+ Method ->
+ case xml:get_tag_cdata(Method) of
+ <<"zlib">> ->
+ Socket = StateData#state.socket,
+ ZlibSocket = (StateData#state.sockmod):compress(Socket,
+ xml:element_to_binary(#xmlel{name
+ =
+ <<"compressed">>,
+ attrs
+ =
+ [{<<"xmlns">>,
+ ?NS_COMPRESS}],
+ children
+ =
+ []})),
+ fsm_next_state(wait_for_stream,
+ StateData#state{socket = ZlibSocket,
+ streamid = new_id()});
+ _ ->
+ send_element(StateData,
+ #xmlel{name = <<"failure">>,
+ attrs = [{<<"xmlns">>, ?NS_COMPRESS}],
+ children =
+ [#xmlel{name =
+ <<"unsupported-method">>,
+ attrs = [],
+ children = []}]}),
+ fsm_next_state(wait_for_feature_request, StateData)
+ end
+ end;
+ _ ->
+ if (SockMod == gen_tcp) and TLSRequired ->
+ Lang = StateData#state.lang,
+ send_element(StateData,
+ ?POLICY_VIOLATION_ERR(Lang,
+ <<"Use of STARTTLS required">>)),
+ send_trailer(StateData),
+ {stop, normal, StateData};
+ true ->
+ process_unauthenticated_stanza(StateData, El),
+ fsm_next_state(wait_for_feature_request, StateData)
+ end
end;
-
wait_for_feature_request(timeout, StateData) ->
{stop, normal, StateData};
-
-wait_for_feature_request({xmlstreamend, _Name}, StateData) ->
- send_trailer(StateData),
- {stop, normal, StateData};
-
-wait_for_feature_request({xmlstreamerror, _}, StateData) ->
+wait_for_feature_request({xmlstreamend, _Name},
+ StateData) ->
+ send_trailer(StateData), {stop, normal, StateData};
+wait_for_feature_request({xmlstreamerror, _},
+ StateData) ->
send_element(StateData, ?INVALID_XML_ERR),
send_trailer(StateData),
{stop, normal, StateData};
-
wait_for_feature_request(closed, StateData) ->
{stop, normal, StateData}.
-
-wait_for_sasl_response({xmlstreamelement, El}, StateData) ->
- {xmlelement, Name, Attrs, Els} = El,
- case {xml:get_attr_s("xmlns", Attrs), Name} of
- {?NS_SASL, "response"} ->
- ClientIn = jlib:decode_base64(xml:get_cdata(Els)),
- case cyrsasl:server_step(StateData#state.sasl_state,
- ClientIn) of
- {ok, Props} ->
- (StateData#state.sockmod):reset_stream(
- StateData#state.socket),
- send_element(StateData,
- {xmlelement, "success",
- [{"xmlns", ?NS_SASL}], []}),
- U = xml:get_attr_s(username, Props),
- AuthModule = xml:get_attr_s(auth_module, Props),
- ?INFO_MSG("(~w) Accepted authentication for ~s by ~p",
- [StateData#state.socket, U, AuthModule]),
- fsm_next_state(wait_for_stream,
- StateData#state{
- streamid = new_id(),
- authenticated = true,
- auth_module = AuthModule,
- user = U});
- {ok, Props, ServerOut} ->
- (StateData#state.sockmod):reset_stream(
- StateData#state.socket),
- send_element(StateData,
- {xmlelement, "success",
- [{"xmlns", ?NS_SASL}],
- [{xmlcdata,
- jlib:encode_base64(ServerOut)}]}),
- U = xml:get_attr_s(username, Props),
- AuthModule = xml:get_attr_s(auth_module, Props),
- ?INFO_MSG("(~w) Accepted authentication for ~s by ~p",
- [StateData#state.socket, U, AuthModule]),
- fsm_next_state(wait_for_stream,
- StateData#state{
- streamid = new_id(),
- authenticated = true,
- auth_module = AuthModule,
- user = U});
- {continue, ServerOut, NewSASLState} ->
- send_element(StateData,
- {xmlelement, "challenge",
- [{"xmlns", ?NS_SASL}],
- [{xmlcdata,
- jlib:encode_base64(ServerOut)}]}),
- fsm_next_state(wait_for_sasl_response,
- StateData#state{sasl_state = NewSASLState});
- {error, Error, Username} ->
- IP = peerip(StateData#state.sockmod, StateData#state.socket),
- ?INFO_MSG(
- "(~w) Failed authentication for ~s@~s from IP ~s (~w)",
+wait_for_sasl_response({xmlstreamelement, El},
+ StateData) ->
+ #xmlel{name = Name, attrs = Attrs, children = Els} = El,
+ case {xml:get_attr_s(<<"xmlns">>, Attrs), Name} of
+ {?NS_SASL, <<"response">>} ->
+ ClientIn = jlib:decode_base64(xml:get_cdata(Els)),
+ case cyrsasl:server_step(StateData#state.sasl_state,
+ ClientIn)
+ of
+ {ok, Props} ->
+ catch
+ (StateData#state.sockmod):reset_stream(StateData#state.socket),
+% U = xml:get_attr_s(username, Props),
+ U = proplists:get_value(username, Props, <<>>),
+% AuthModule = xml:get_attr_s(auth_module, Props),
+ AuthModule = proplists:get_value(auth_module, Props, <<>>),
+ ?INFO_MSG("(~w) Accepted authentication for ~s "
+ "by ~p",
+ [StateData#state.socket, U, AuthModule]),
+ send_element(StateData,
+ #xmlel{name = <<"success">>,
+ attrs = [{<<"xmlns">>, ?NS_SASL}],
+ children = []}),
+ fsm_next_state(wait_for_stream,
+ StateData#state{streamid = new_id(),
+ authenticated = true,
+ auth_module = AuthModule,
+ user = U});
+ {ok, Props, ServerOut} ->
+ (StateData#state.sockmod):reset_stream(StateData#state.socket),
+% U = xml:get_attr_s(username, Props),
+ U = proplists:get_value(username, Props, <<>>),
+% AuthModule = xml:get_attr_s(auth_module, Props),
+ AuthModule = proplists:get_value(auth_module, Props, undefined),
+ ?INFO_MSG("(~w) Accepted authentication for ~s "
+ "by ~p",
+ [StateData#state.socket, U, AuthModule]),
+ send_element(StateData,
+ #xmlel{name = <<"success">>,
+ attrs = [{<<"xmlns">>, ?NS_SASL}],
+ children =
+ [{xmlcdata,
+ jlib:encode_base64(ServerOut)}]}),
+ fsm_next_state(wait_for_stream,
+ StateData#state{streamid = new_id(),
+ authenticated = true,
+ auth_module = AuthModule,
+ user = U});
+ {continue, ServerOut, NewSASLState} ->
+ send_element(StateData,
+ #xmlel{name = <<"challenge">>,
+ attrs = [{<<"xmlns">>, ?NS_SASL}],
+ children =
+ [{xmlcdata,
+ jlib:encode_base64(ServerOut)}]}),
+ fsm_next_state(wait_for_sasl_response,
+ StateData#state{sasl_state = NewSASLState});
+ {error, Error, Username} ->
+ IP = peerip(StateData#state.sockmod, StateData#state.socket),
+ ?INFO_MSG("(~w) Failed authentication for ~s@~s from IP ~s",
[StateData#state.socket,
- Username, StateData#state.server, jlib:ip_to_list(IP), IP]),
- send_element(StateData,
- {xmlelement, "failure",
- [{"xmlns", ?NS_SASL}],
- [{xmlelement, Error, [], []}]}),
- fsm_next_state(wait_for_feature_request, StateData);
- {error, Error} ->
- send_element(StateData,
- {xmlelement, "failure",
- [{"xmlns", ?NS_SASL}],
- [{xmlelement, Error, [], []}]}),
- fsm_next_state(wait_for_feature_request, StateData)
- end;
- _ ->
- process_unauthenticated_stanza(StateData, El),
- fsm_next_state(wait_for_feature_request, StateData)
+ Username, StateData#state.server, jlib:ip_to_list(IP)]),
+ send_element(StateData,
+ #xmlel{name = <<"failure">>,
+ attrs = [{<<"xmlns">>, ?NS_SASL}],
+ children =
+ [#xmlel{name = Error, attrs = [],
+ children = []}]}),
+ fsm_next_state(wait_for_feature_request, StateData);
+ {error, Error} ->
+ send_element(StateData,
+ #xmlel{name = <<"failure">>,
+ attrs = [{<<"xmlns">>, ?NS_SASL}],
+ children =
+ [#xmlel{name = Error, attrs = [],
+ children = []}]}),
+ fsm_next_state(wait_for_feature_request, StateData)
+ end;
+ _ ->
+ process_unauthenticated_stanza(StateData, El),
+ fsm_next_state(wait_for_feature_request, StateData)
end;
-
wait_for_sasl_response(timeout, StateData) ->
{stop, normal, StateData};
-
-wait_for_sasl_response({xmlstreamend, _Name}, StateData) ->
- send_trailer(StateData),
- {stop, normal, StateData};
-
-wait_for_sasl_response({xmlstreamerror, _}, StateData) ->
+wait_for_sasl_response({xmlstreamend, _Name},
+ StateData) ->
+ send_trailer(StateData), {stop, normal, StateData};
+wait_for_sasl_response({xmlstreamerror, _},
+ StateData) ->
send_element(StateData, ?INVALID_XML_ERR),
send_trailer(StateData),
{stop, normal, StateData};
-
wait_for_sasl_response(closed, StateData) ->
{stop, normal, StateData}.
-
resource_conflict_action(U, S, R) ->
- OptionRaw = case ejabberd_sm:is_existing_resource(U, S, R) of
- true ->
- ejabberd_config:get_local_option({resource_conflict,S});
- false ->
- acceptnew
+ OptionRaw = case ejabberd_sm:is_existing_resource(U, S,
+ R)
+ of
+ true ->
+ ejabberd_config:get_local_option(
+ {resource_conflict, S},
+ fun(setresource) -> setresource;
+ (closeold) -> closeold;
+ (closenew) -> closenew;
+ (acceptnew) -> acceptnew
+ end);
+ false ->
+ acceptnew
end,
Option = case OptionRaw of
- setresource -> setresource;
- closeold -> acceptnew; %% ejabberd_sm will close old session
- closenew -> closenew;
- acceptnew -> acceptnew;
- _ -> acceptnew %% default ejabberd behavior
+ setresource -> setresource;
+ closeold ->
+ acceptnew; %% ejabberd_sm will close old session
+ closenew -> closenew;
+ acceptnew -> acceptnew;
+ _ -> acceptnew %% default ejabberd behavior
end,
case Option of
- acceptnew ->
- {accept_resource, R};
- closenew ->
- closenew;
- setresource ->
- Rnew = lists:concat([randoms:get_string() | tuple_to_list(now())]),
- {accept_resource, Rnew}
+ acceptnew -> {accept_resource, R};
+ closenew -> closenew;
+ setresource ->
+ Rnew = iolist_to_binary([randoms:get_string()
+ | tuple_to_list(now())]),
+ {accept_resource, Rnew}
end.
wait_for_bind({xmlstreamelement, El}, StateData) ->
case jlib:iq_query_info(El) of
- #iq{type = set, xmlns = ?NS_BIND, sub_el = SubEl} = IQ ->
- U = StateData#state.user,
- R1 = xml:get_path_s(SubEl, [{elem, "resource"}, cdata]),
- R = case jlib:resourceprep(R1) of
- error -> error;
- "" ->
- lists:concat(
- [randoms:get_string() | tuple_to_list(now())]);
- Resource -> Resource
- end,
- case R of
- error ->
- Err = jlib:make_error_reply(El, ?ERR_BAD_REQUEST),
- send_element(StateData, Err),
- fsm_next_state(wait_for_bind, StateData);
- _ ->
- %%Server = StateData#state.server,
- %%RosterVersioningFeature =
- %% ejabberd_hooks:run_fold(
- %% roster_get_versioning_feature, Server, [], [Server]),
- %%StreamFeatures = [{xmlelement, "session",
- %% [{"xmlns", ?NS_SESSION}], []} |
- %% RosterVersioningFeature],
- %%send_element(StateData, {xmlelement, "stream:features",
- %% [], StreamFeatures}),
- case resource_conflict_action(U, StateData#state.server, R) of
- closenew ->
- Err = jlib:make_error_reply(El, ?STANZA_ERROR("409", "modify", "conflict")),
- send_element(StateData, Err),
- fsm_next_state(wait_for_bind, StateData);
- {accept_resource, R2} ->
- JID = jlib:make_jid(U, StateData#state.server, R2),
- Res = IQ#iq{type = result,
- sub_el = [{xmlelement, "bind",
- [{"xmlns", ?NS_BIND}],
- [{xmlelement, "jid", [],
- [{xmlcdata,
- jlib:jid_to_string(JID)}]}]}]},
- send_element(StateData, jlib:iq_to_xml(Res)),
- fsm_next_state(wait_for_session,
- StateData#state{resource = R2, jid = JID})
- end
- end;
- _ ->
- fsm_next_state(wait_for_bind, StateData)
+ #iq{type = set, xmlns = ?NS_BIND, sub_el = SubEl} =
+ IQ ->
+ U = StateData#state.user,
+ R1 = xml:get_path_s(SubEl,
+ [{elem, <<"resource">>}, cdata]),
+ R = case jlib:resourceprep(R1) of
+ error -> error;
+ <<"">> ->
+ iolist_to_binary([randoms:get_string()
+ | tuple_to_list(now())]);
+ Resource -> Resource
+ end,
+ case R of
+ error ->
+ Err = jlib:make_error_reply(El, ?ERR_BAD_REQUEST),
+ send_element(StateData, Err),
+ fsm_next_state(wait_for_bind, StateData);
+ _ ->
+ case resource_conflict_action(U, StateData#state.server,
+ R)
+ of
+ closenew ->
+ Err = jlib:make_error_reply(El,
+ ?STANZA_ERROR(<<"409">>,
+ <<"modify">>,
+ <<"conflict">>)),
+ send_element(StateData, Err),
+ fsm_next_state(wait_for_bind, StateData);
+ {accept_resource, R2} ->
+ JID = jlib:make_jid(U, StateData#state.server, R2),
+ Res = IQ#iq{type = result,
+ sub_el =
+ [#xmlel{name = <<"bind">>,
+ attrs = [{<<"xmlns">>, ?NS_BIND}],
+ children =
+ [#xmlel{name = <<"jid">>,
+ attrs = [],
+ children =
+ [{xmlcdata,
+ jlib:jid_to_string(JID)}]}]}]},
+ send_element(StateData, jlib:iq_to_xml(Res)),
+ fsm_next_state(wait_for_session,
+ StateData#state{resource = R2, jid = JID})
+ end
+ end;
+ _ -> fsm_next_state(wait_for_bind, StateData)
end;
-
wait_for_bind(timeout, StateData) ->
{stop, normal, StateData};
-
wait_for_bind({xmlstreamend, _Name}, StateData) ->
- send_trailer(StateData),
- {stop, normal, StateData};
-
+ send_trailer(StateData), {stop, normal, StateData};
wait_for_bind({xmlstreamerror, _}, StateData) ->
send_element(StateData, ?INVALID_XML_ERR),
send_trailer(StateData),
{stop, normal, StateData};
-
wait_for_bind(closed, StateData) ->
{stop, normal, StateData}.
-
-
wait_for_session({xmlstreamelement, El}, StateData) ->
case jlib:iq_query_info(El) of
#iq{type = set, xmlns = ?NS_SESSION} ->
@@ -988,23 +1020,18 @@ wait_for_session({xmlstreamelement, El}, StateData) ->
wait_for_session(timeout, StateData) ->
{stop, normal, StateData};
-
wait_for_session({xmlstreamend, _Name}, StateData) ->
- send_trailer(StateData),
- {stop, normal, StateData};
-
+ send_trailer(StateData), {stop, normal, StateData};
wait_for_session({xmlstreamerror, _}, StateData) ->
send_element(StateData, ?INVALID_XML_ERR),
send_trailer(StateData),
{stop, normal, StateData};
-
wait_for_session(closed, StateData) ->
{stop, normal, StateData}.
-
-session_established({xmlstreamelement, El}, StateData) ->
+session_established({xmlstreamelement, El},
+ StateData) ->
FromJID = StateData#state.jid,
- % Check 'from' attribute in stanza RFC 3920 Section 9.1.2
case check_from(El, FromJID) of
'invalid-from' ->
send_element(StateData, ?INVALID_FROM),
@@ -1013,124 +1040,109 @@ session_established({xmlstreamelement, El}, StateData) ->
_NewEl ->
session_established2(El, StateData)
end;
-
%% We hibernate the process to reduce memory consumption after a
%% configurable activity timeout
session_established(timeout, StateData) ->
- %% TODO: Options must be stored in state:
Options = [],
proc_lib:hibernate(?GEN_FSM, enter_loop,
[?MODULE, Options, session_established, StateData]),
fsm_next_state(session_established, StateData);
-
session_established({xmlstreamend, _Name}, StateData) ->
+ send_trailer(StateData), {stop, normal, StateData};
+session_established({xmlstreamerror,
+ <<"XML stanza is too big">> = E},
+ StateData) ->
+ send_element(StateData,
+ ?POLICY_VIOLATION_ERR((StateData#state.lang), E)),
send_trailer(StateData),
{stop, normal, StateData};
-
-session_established({xmlstreamerror, "XML stanza is too big" = E}, StateData) ->
- send_element(StateData, ?POLICY_VIOLATION_ERR(StateData#state.lang, E)),
- send_trailer(StateData),
- {stop, normal, StateData};
-
session_established({xmlstreamerror, _}, StateData) ->
send_element(StateData, ?INVALID_XML_ERR),
send_trailer(StateData),
{stop, normal, StateData};
-
session_established(closed, StateData) ->
{stop, normal, StateData}.
%% Process packets sent by user (coming from user on c2s XMPP
%% connection)
session_established2(El, StateData) ->
- {xmlelement, Name, Attrs, _Els} = El,
+ #xmlel{name = Name, attrs = Attrs} = El,
User = StateData#state.user,
Server = StateData#state.server,
FromJID = StateData#state.jid,
- To = xml:get_attr_s("to", Attrs),
+ To = xml:get_attr_s(<<"to">>, Attrs),
ToJID = case To of
- "" ->
- jlib:make_jid(User, Server, "");
- _ ->
- jlib:string_to_jid(To)
+ <<"">> -> jlib:make_jid(User, Server, <<"">>);
+ _ -> jlib:string_to_jid(To)
end,
- NewEl1 = jlib:remove_attr("xmlns", El),
- NewEl = case xml:get_attr_s("xml:lang", Attrs) of
- "" ->
- case StateData#state.lang of
- "" -> NewEl1;
- Lang ->
- xml:replace_tag_attr("xml:lang", Lang, NewEl1)
- end;
- _ ->
- NewEl1
+ NewEl1 = jlib:remove_attr(<<"xmlns">>, El),
+ NewEl = case xml:get_attr_s(<<"xml:lang">>, Attrs) of
+ <<"">> ->
+ case StateData#state.lang of
+ <<"">> -> NewEl1;
+ Lang ->
+ xml:replace_tag_attr(<<"xml:lang">>, Lang, NewEl1)
+ end;
+ _ -> NewEl1
end,
- NewState =
- case ToJID of
- error ->
- case xml:get_attr_s("type", Attrs) of
- "error" -> StateData;
- "result" -> StateData;
- _ ->
- Err = jlib:make_error_reply(NewEl, ?ERR_JID_MALFORMED),
- send_element(StateData, Err),
- StateData
- end;
- _ ->
- case Name of
- "presence" ->
- PresenceEl = ejabberd_hooks:run_fold(
- c2s_update_presence,
- Server,
- NewEl,
- [User, Server]),
- ejabberd_hooks:run(
- user_send_packet,
- Server,
- [FromJID, ToJID, PresenceEl]),
- case ToJID of
- #jid{user = User,
- server = Server,
- resource = ""} ->
- ?DEBUG("presence_update(~p,~n\t~p,~n\t~p)",
- [FromJID, PresenceEl, StateData]),
- presence_update(FromJID, PresenceEl,
- StateData);
- _ ->
- presence_track(FromJID, ToJID, PresenceEl,
- StateData)
- end;
- "iq" ->
- case jlib:iq_query_info(NewEl) of
- #iq{xmlns = Xmlns} = IQ
- when Xmlns == ?NS_PRIVACY;
- Xmlns == ?NS_BLOCKING ->
- process_privacy_iq(
- FromJID, ToJID, IQ, StateData);
- _ ->
- ejabberd_hooks:run(
- user_send_packet,
- Server,
- [FromJID, ToJID, NewEl]),
- check_privacy_route(FromJID, StateData, FromJID, ToJID, NewEl),
- StateData
- end;
- "message" ->
- ejabberd_hooks:run(user_send_packet,
- Server,
- [FromJID, ToJID, NewEl]),
- check_privacy_route(FromJID, StateData, FromJID,
- ToJID, NewEl),
- StateData;
- _ ->
- StateData
- end
- end,
- ejabberd_hooks:run(c2s_loop_debug, [{xmlstreamelement, El}]),
+ NewState = case ToJID of
+ error ->
+ case xml:get_attr_s(<<"type">>, Attrs) of
+ <<"error">> -> StateData;
+ <<"result">> -> StateData;
+ _ ->
+ Err = jlib:make_error_reply(NewEl,
+ ?ERR_JID_MALFORMED),
+ send_element(StateData, Err),
+ StateData
+ end;
+ _ ->
+ case Name of
+ <<"presence">> ->
+ PresenceEl =
+ ejabberd_hooks:run_fold(c2s_update_presence,
+ Server, NewEl,
+ [User, Server]),
+ ejabberd_hooks:run(user_send_packet, Server,
+ [FromJID, ToJID, PresenceEl]),
+ case ToJID of
+ #jid{user = User, server = Server,
+ resource = <<"">>} ->
+ ?DEBUG("presence_update(~p,~n\t~p,~n\t~p)",
+ [FromJID, PresenceEl, StateData]),
+ presence_update(FromJID, PresenceEl,
+ StateData);
+ _ ->
+ presence_track(FromJID, ToJID, PresenceEl,
+ StateData)
+ end;
+ <<"iq">> ->
+ case jlib:iq_query_info(NewEl) of
+ #iq{xmlns = Xmlns} = IQ
+ when Xmlns == (?NS_PRIVACY);
+ Xmlns == (?NS_BLOCKING) ->
+ process_privacy_iq(FromJID, ToJID, IQ,
+ StateData);
+ _ ->
+ ejabberd_hooks:run(user_send_packet, Server,
+ [FromJID, ToJID, NewEl]),
+ check_privacy_route(FromJID, StateData,
+ FromJID, ToJID, NewEl),
+ StateData
+ end;
+ <<"message">> ->
+ ejabberd_hooks:run(user_send_packet, Server,
+ [FromJID, ToJID, NewEl]),
+ check_privacy_route(FromJID, StateData, FromJID,
+ ToJID, NewEl),
+ StateData;
+ _ -> StateData
+ end
+ end,
+ ejabberd_hooks:run(c2s_loop_debug,
+ [{xmlstreamelement, El}]),
fsm_next_state(session_established, NewState).
-
-
%%----------------------------------------------------------------------
%% Func: StateName/3
%% Returns: {next_state, NextStateName, NextStateData} |
@@ -1162,24 +1174,22 @@ handle_event(_Event, StateName, StateData) ->
%% {stop, Reason, NewStateData} |
%% {stop, Reason, Reply, NewStateData}
%%----------------------------------------------------------------------
-handle_sync_event({get_presence}, _From, StateName, StateData) ->
+handle_sync_event({get_presence}, _From, StateName,
+ StateData) ->
User = StateData#state.user,
PresLast = StateData#state.pres_last,
-
Show = get_showtag(PresLast),
Status = get_statustag(PresLast),
Resource = StateData#state.resource,
-
Reply = {User, Resource, Show, Status},
fsm_reply(Reply, StateName, StateData);
-
-handle_sync_event(get_subscribed, _From, StateName, StateData) ->
- Subscribed = ?SETS:to_list(StateData#state.pres_f),
+handle_sync_event(get_subscribed, _From, StateName,
+ StateData) ->
+ Subscribed = (?SETS):to_list(StateData#state.pres_f),
{reply, Subscribed, StateName, StateData};
-
-handle_sync_event(_Event, _From, StateName, StateData) ->
- Reply = ok,
- fsm_reply(Reply, StateName, StateData).
+handle_sync_event(_Event, _From, StateName,
+ StateData) ->
+ Reply = ok, fsm_reply(Reply, StateName, StateData).
code_change(_OldVsn, StateName, StateData, _Extra) ->
{ok, StateName, StateData}.
@@ -1197,209 +1207,301 @@ handle_info({send_text, Text}, StateName, StateData) ->
handle_info(replaced, _StateName, StateData) ->
Lang = StateData#state.lang,
send_element(StateData,
- ?SERRT_CONFLICT(Lang, "Replaced by new connection")),
+ ?SERRT_CONFLICT(Lang,
+ <<"Replaced by new connection">>)),
send_trailer(StateData),
- {stop, normal, StateData#state{authenticated = replaced}};
+ {stop, normal,
+ StateData#state{authenticated = replaced}};
+handle_info({route, _From, _To, {broadcast, Data}},
+ StateName, StateData) ->
+ ?DEBUG("broadcast~n~p~n", [Data]),
+ case Data of
+ {item, IJID, ISubscription} ->
+ fsm_next_state(StateName,
+ roster_change(IJID, ISubscription, StateData));
+ {exit, Reason} ->
+ Lang = StateData#state.lang,
+ send_element(StateData, ?SERRT_CONFLICT(Lang, Reason)),
+ catch send_trailer(StateData),
+ {stop, normal, StateData};
+ {privacy_list, PrivList, PrivListName} ->
+ case ejabberd_hooks:run_fold(privacy_updated_list,
+ StateData#state.server,
+ false,
+ [StateData#state.privacy_list,
+ PrivList]) of
+ false ->
+ fsm_next_state(StateName, StateData);
+ NewPL ->
+ PrivPushIQ = #iq{type = set,
+ xmlns = ?NS_PRIVACY,
+ id = <<"push",
+ (randoms:get_string())/binary>>,
+ sub_el =
+ [#xmlel{name = <<"query">>,
+ attrs = [{<<"xmlns">>,
+ ?NS_PRIVACY}],
+ children =
+ [#xmlel{name = <<"list">>,
+ attrs = [{<<"name">>,
+ PrivListName}],
+ children = []}]}]},
+ PrivPushEl = jlib:replace_from_to(
+ jlib:jid_remove_resource(StateData#state.jid),
+ StateData#state.jid,
+ jlib:iq_to_xml(PrivPushIQ)),
+ send_element(StateData, PrivPushEl),
+ fsm_next_state(StateName,
+ StateData#state{privacy_list = NewPL})
+ end;
+ {blocking, What} ->
+ route_blocking(What, StateData),
+ fsm_next_state(StateName, StateData);
+ _ ->
+ fsm_next_state(StateName, StateData)
+ end;
%% Process Packets that are to be send to the user
-handle_info({route, From, To, Packet}, StateName, StateData) ->
- {xmlelement, Name, Attrs, Els} = Packet,
- {Pass, NewAttrs, NewState} =
- case Name of
- "presence" ->
- State = ejabberd_hooks:run_fold(
- c2s_presence_in, StateData#state.server,
- StateData,
- [{From, To, Packet}]),
- case xml:get_attr_s("type", Attrs) of
- "probe" ->
- LFrom = jlib:jid_tolower(From),
- LBFrom = jlib:jid_remove_resource(LFrom),
- NewStateData =
- case ?SETS:is_element(
- LFrom, State#state.pres_a) orelse
- ?SETS:is_element(
- LBFrom, State#state.pres_a) of
- true ->
- State;
- false ->
- case ?SETS:is_element(
- LFrom, State#state.pres_f) of
- true ->
- A = ?SETS:add_element(
- LFrom,
- State#state.pres_a),
- State#state{pres_a = A};
- false ->
- case ?SETS:is_element(
- LBFrom, State#state.pres_f) of
- true ->
- A = ?SETS:add_element(
- LBFrom,
- State#state.pres_a),
- State#state{pres_a = A};
- false ->
- State
- end
- end
- end,
- process_presence_probe(From, To, NewStateData),
- {false, Attrs, NewStateData};
- "error" ->
- NewA = remove_element(jlib:jid_tolower(From),
- State#state.pres_a),
- {true, Attrs, State#state{pres_a = NewA}};
- "invisible" ->
- Attrs1 = lists:keydelete("type", 1, Attrs),
- {true, [{"type", "unavailable"} | Attrs1], State};
- "subscribe" ->
- SRes = is_privacy_allow(State, From, To, Packet, in),
- {SRes, Attrs, State};
- "subscribed" ->
- SRes = is_privacy_allow(State, From, To, Packet, in),
- {SRes, Attrs, State};
- "unsubscribe" ->
- SRes = is_privacy_allow(State, From, To, Packet, in),
- {SRes, Attrs, State};
- "unsubscribed" ->
- SRes = is_privacy_allow(State, From, To, Packet, in),
- {SRes, Attrs, State};
- _ ->
- case privacy_check_packet(State, From, To, Packet, in) of
- allow ->
- LFrom = jlib:jid_tolower(From),
- LBFrom = jlib:jid_remove_resource(LFrom),
- case ?SETS:is_element(
- LFrom, State#state.pres_a) orelse
- ?SETS:is_element(
- LBFrom, State#state.pres_a) of
- true ->
- {true, Attrs, State};
- false ->
- case ?SETS:is_element(
- LFrom, State#state.pres_f) of
- true ->
- A = ?SETS:add_element(
- LFrom,
- State#state.pres_a),
- {true, Attrs,
- State#state{pres_a = A}};
- false ->
- case ?SETS:is_element(
- LBFrom, State#state.pres_f) of
- true ->
- A = ?SETS:add_element(
- LBFrom,
- State#state.pres_a),
- {true, Attrs,
- State#state{pres_a = A}};
- false ->
- {true, Attrs, State}
- end
- end
- end;
- deny ->
- {false, Attrs, State}
- end
- end;
- "broadcast" ->
- ?DEBUG("broadcast~n~p~n", [Els]),
- case Els of
- [{item, IJID, ISubscription}] ->
- {false, Attrs,
- roster_change(IJID, ISubscription,
- StateData)};
- [{exit, Reason}] ->
- {exit, Attrs, Reason};
- [{privacy_list, PrivList, PrivListName}] ->
- case ejabberd_hooks:run_fold(
- privacy_updated_list, StateData#state.server,
- false,
- [StateData#state.privacy_list,
- PrivList]) of
- false ->
- {false, Attrs, StateData};
- NewPL ->
- PrivPushIQ =
- #iq{type = set, xmlns = ?NS_PRIVACY,
- id = "push" ++ randoms:get_string(),
- sub_el = [{xmlelement, "query",
- [{"xmlns", ?NS_PRIVACY}],
- [{xmlelement, "list",
- [{"name", PrivListName}],
- []}]}]},
- PrivPushEl =
- jlib:replace_from_to(
- jlib:jid_remove_resource(
- StateData#state.jid),
- StateData#state.jid,
- jlib:iq_to_xml(PrivPushIQ)),
- send_element(StateData, PrivPushEl),
- {false, Attrs, StateData#state{privacy_list = NewPL}}
- end;
- [{blocking, What}] ->
- route_blocking(What, StateData),
- {false, Attrs, StateData};
- _ ->
- {false, Attrs, StateData}
- end;
- "iq" ->
- IQ = jlib:iq_query_info(Packet),
- case IQ of
- #iq{xmlns = ?NS_LAST} ->
- LFrom = jlib:jid_tolower(From),
- LBFrom = jlib:jid_remove_resource(LFrom),
- HasFromSub = (?SETS:is_element(LFrom, StateData#state.pres_f) orelse ?SETS:is_element(LBFrom, StateData#state.pres_f))
- andalso is_privacy_allow(StateData, To, From, {xmlelement, "presence", [], []}, out),
- case HasFromSub of
- true ->
- case privacy_check_packet(StateData, From, To, Packet, in) of
- allow ->
- {true, Attrs, StateData};
- deny ->
- {false, Attrs, StateData}
- end;
- _ ->
- Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
- ejabberd_router:route(To, From, Err),
- {false, Attrs, StateData}
- end;
- IQ when (is_record(IQ, iq)) or (IQ == reply) ->
- case privacy_check_packet(StateData, From, To, Packet, in) of
- allow ->
- {true, Attrs, StateData};
- deny when is_record(IQ, iq) ->
- Err = jlib:make_error_reply(
- Packet, ?ERR_SERVICE_UNAVAILABLE),
- ejabberd_router:route(To, From, Err),
- {false, Attrs, StateData};
- deny when IQ == reply ->
- {false, Attrs, StateData}
- end;
- IQ when (IQ == invalid) or (IQ == not_iq) ->
- {false, Attrs, StateData}
- end;
- "message" ->
- case privacy_check_packet(StateData, From, To, Packet, in) of
- allow ->
- {true, Attrs, StateData};
- deny ->
- {false, Attrs, StateData}
- end;
- _ ->
- {true, Attrs, StateData}
- end,
- if
- Pass == exit ->
+handle_info({route, From, To,
+ #xmlel{name = Name, attrs = Attrs, children = Els} = Packet},
+ StateName, StateData) ->
+ {Pass, NewAttrs, NewState} = case Name of
+ <<"presence">> ->
+ State =
+ ejabberd_hooks:run_fold(c2s_presence_in,
+ StateData#state.server,
+ StateData,
+ [{From, To,
+ Packet}]),
+ case xml:get_attr_s(<<"type">>, Attrs) of
+ <<"probe">> ->
+ LFrom = jlib:jid_tolower(From),
+ LBFrom =
+ jlib:jid_remove_resource(LFrom),
+ NewStateData = case
+ (?SETS):is_element(LFrom,
+ State#state.pres_a)
+ orelse
+ (?SETS):is_element(LBFrom,
+ State#state.pres_a)
+ of
+ true -> State;
+ false ->
+ case
+ (?SETS):is_element(LFrom,
+ State#state.pres_f)
+ of
+ true ->
+ A =
+ (?SETS):add_element(LFrom,
+ State#state.pres_a),
+ State#state{pres_a
+ =
+ A};
+ false ->
+ case
+ (?SETS):is_element(LBFrom,
+ State#state.pres_f)
+ of
+ true ->
+ A =
+ (?SETS):add_element(LBFrom,
+ State#state.pres_a),
+ State#state{pres_a
+ =
+ A};
+ false ->
+ State
+ end
+ end
+ end,
+ process_presence_probe(From, To,
+ NewStateData),
+ {false, Attrs, NewStateData};
+ <<"error">> ->
+ NewA =
+ remove_element(jlib:jid_tolower(From),
+ State#state.pres_a),
+ {true, Attrs,
+ State#state{pres_a = NewA}};
+ <<"subscribe">> ->
+ SRes = is_privacy_allow(State,
+ From, To,
+ Packet,
+ in),
+ {SRes, Attrs, State};
+ <<"subscribed">> ->
+ SRes = is_privacy_allow(State,
+ From, To,
+ Packet,
+ in),
+ {SRes, Attrs, State};
+ <<"unsubscribe">> ->
+ SRes = is_privacy_allow(State,
+ From, To,
+ Packet,
+ in),
+ {SRes, Attrs, State};
+ <<"unsubscribed">> ->
+ SRes = is_privacy_allow(State,
+ From, To,
+ Packet,
+ in),
+ {SRes, Attrs, State};
+ _ ->
+ case privacy_check_packet(State,
+ From, To,
+ Packet,
+ in)
+ of
+ allow ->
+ LFrom =
+ jlib:jid_tolower(From),
+ LBFrom =
+ jlib:jid_remove_resource(LFrom),
+ case
+ (?SETS):is_element(LFrom,
+ State#state.pres_a)
+ orelse
+ (?SETS):is_element(LBFrom,
+ State#state.pres_a)
+ of
+ true ->
+ {true, Attrs, State};
+ false ->
+ case
+ (?SETS):is_element(LFrom,
+ State#state.pres_f)
+ of
+ true ->
+ A =
+ (?SETS):add_element(LFrom,
+ State#state.pres_a),
+ {true, Attrs,
+ State#state{pres_a
+ =
+ A}};
+ false ->
+ case
+ (?SETS):is_element(LBFrom,
+ State#state.pres_f)
+ of
+ true ->
+ A =
+ (?SETS):add_element(LBFrom,
+ State#state.pres_a),
+ {true,
+ Attrs,
+ State#state{pres_a
+ =
+ A}};
+ false ->
+ {true,
+ Attrs,
+ State}
+ end
+ end
+ end;
+ deny -> {false, Attrs, State}
+ end
+ end;
+ <<"iq">> ->
+ IQ = jlib:iq_query_info(Packet),
+ case IQ of
+ #iq{xmlns = ?NS_LAST} ->
+ LFrom = jlib:jid_tolower(From),
+ LBFrom =
+ jlib:jid_remove_resource(LFrom),
+ HasFromSub =
+ ((?SETS):is_element(LFrom,
+ StateData#state.pres_f)
+ orelse
+ (?SETS):is_element(LBFrom,
+ StateData#state.pres_f))
+ andalso
+ is_privacy_allow(StateData,
+ To, From,
+ #xmlel{name
+ =
+ <<"presence">>,
+ attrs
+ =
+ [],
+ children
+ =
+ []},
+ out),
+ case HasFromSub of
+ true ->
+ case
+ privacy_check_packet(StateData,
+ From,
+ To,
+ Packet,
+ in)
+ of
+ allow ->
+ {true, Attrs,
+ StateData};
+ deny ->
+ {false, Attrs,
+ StateData}
+ end;
+ _ ->
+ Err =
+ jlib:make_error_reply(Packet,
+ ?ERR_FORBIDDEN),
+ ejabberd_router:route(To,
+ From,
+ Err),
+ {false, Attrs, StateData}
+ end;
+ IQ
+ when is_record(IQ, iq) or
+ (IQ == reply) ->
+ case
+ privacy_check_packet(StateData,
+ From, To,
+ Packet, in)
+ of
+ allow ->
+ {true, Attrs, StateData};
+ deny when is_record(IQ, iq) ->
+ Err =
+ jlib:make_error_reply(Packet,
+ ?ERR_SERVICE_UNAVAILABLE),
+ ejabberd_router:route(To,
+ From,
+ Err),
+ {false, Attrs, StateData};
+ deny when IQ == reply ->
+ {false, Attrs, StateData}
+ end;
+ IQ
+ when (IQ == invalid) or
+ (IQ == not_iq) ->
+ {false, Attrs, StateData}
+ end;
+ <<"message">> ->
+ case privacy_check_packet(StateData,
+ From, To,
+ Packet, in)
+ of
+ allow -> {true, Attrs, StateData};
+ deny -> {false, Attrs, StateData}
+ end;
+ _ -> {true, Attrs, StateData}
+ end,
+ if Pass == exit ->
%% When Pass==exit, NewState contains a string instead of a #state{}
Lang = StateData#state.lang,
send_element(StateData, ?SERRT_CONFLICT(Lang, NewState)),
send_trailer(StateData),
{stop, normal, StateData};
Pass ->
- Attrs2 = jlib:replace_from_to_attrs(jlib:jid_to_string(From),
- jlib:jid_to_string(To),
- NewAttrs),
- FixedPacket = {xmlelement, Name, Attrs2, Els},
+ Attrs2 =
+ jlib:replace_from_to_attrs(jlib:jid_to_string(From),
+ jlib:jid_to_string(To), NewAttrs),
+ FixedPacket = #xmlel{name = Name, attrs = Attrs2, children = Els},
send_element(StateData, FixedPacket),
ejabberd_hooks:run(user_receive_packet,
StateData#state.server,
@@ -1410,40 +1512,38 @@ handle_info({route, From, To, Packet}, StateName, StateData) ->
ejabberd_hooks:run(c2s_loop_debug, [{route, From, To, Packet}]),
fsm_next_state(StateName, NewState)
end;
-handle_info({'DOWN', Monitor, _Type, _Object, _Info}, _StateName, StateData)
- when Monitor == StateData#state.socket_monitor ->
+handle_info({'DOWN', Monitor, _Type, _Object, _Info},
+ _StateName, StateData)
+ when Monitor == StateData#state.socket_monitor ->
{stop, normal, StateData};
handle_info(system_shutdown, StateName, StateData) ->
case StateName of
- wait_for_stream ->
- send_header(StateData, ?MYNAME, "1.0", "en"),
- send_element(StateData, ?SERR_SYSTEM_SHUTDOWN),
- send_trailer(StateData),
- ok;
- _ ->
- send_element(StateData, ?SERR_SYSTEM_SHUTDOWN),
- send_trailer(StateData),
- ok
+ wait_for_stream ->
+ send_header(StateData, ?MYNAME, <<"1.0">>, <<"en">>),
+ send_element(StateData, ?SERR_SYSTEM_SHUTDOWN),
+ send_trailer(StateData),
+ ok;
+ _ ->
+ send_element(StateData, ?SERR_SYSTEM_SHUTDOWN),
+ send_trailer(StateData),
+ ok
end,
{stop, normal, StateData};
handle_info({force_update_presence, LUser}, StateName,
- #state{user = LUser, server = LServer} = StateData) ->
- NewStateData =
- case StateData#state.pres_last of
- {xmlelement, "presence", _Attrs, _Els} ->
- PresenceEl = ejabberd_hooks:run_fold(
- c2s_update_presence,
- LServer,
- StateData#state.pres_last,
- [LUser, LServer]),
- StateData2 = StateData#state{pres_last = PresenceEl},
- presence_update(StateData2#state.jid,
- PresenceEl,
- StateData2),
- StateData2;
- _ ->
- StateData
- end,
+ #state{user = LUser, server = LServer} = StateData) ->
+ NewStateData = case StateData#state.pres_last of
+ #xmlel{name = <<"presence">>} ->
+ PresenceEl =
+ ejabberd_hooks:run_fold(c2s_update_presence,
+ LServer,
+ StateData#state.pres_last,
+ [LUser, LServer]),
+ StateData2 = StateData#state{pres_last = PresenceEl},
+ presence_update(StateData2#state.jid, PresenceEl,
+ StateData2),
+ StateData2;
+ _ -> StateData
+ end,
{next_state, StateName, NewStateData};
handle_info({broadcast, Type, From, Packet}, StateName, StateData) ->
Recipients = ejabberd_hooks:run_fold(
@@ -1480,61 +1580,59 @@ print_state(State = #state{pres_t = T, pres_f = F, pres_a = A, pres_i = I}) ->
%%----------------------------------------------------------------------
terminate(_Reason, StateName, StateData) ->
case StateName of
- session_established ->
- case StateData#state.authenticated of
- replaced ->
- ?INFO_MSG("(~w) Replaced session for ~s",
- [StateData#state.socket,
- jlib:jid_to_string(StateData#state.jid)]),
- From = StateData#state.jid,
- Packet = {xmlelement, "presence",
- [{"type", "unavailable"}],
- [{xmlelement, "status", [],
- [{xmlcdata, "Replaced by new connection"}]}]},
- ejabberd_sm:close_session_unset_presence(
- StateData#state.sid,
- StateData#state.user,
- StateData#state.server,
- StateData#state.resource,
- "Replaced by new connection"),
- presence_broadcast(
- StateData, From, StateData#state.pres_a, Packet),
- presence_broadcast(
- StateData, From, StateData#state.pres_i, Packet);
- _ ->
- ?INFO_MSG("(~w) Close session for ~s",
- [StateData#state.socket,
- jlib:jid_to_string(StateData#state.jid)]),
-
- EmptySet = ?SETS:new(),
- case StateData of
- #state{pres_last = undefined,
- pres_a = EmptySet,
- pres_i = EmptySet,
- pres_invis = false} ->
- ejabberd_sm:close_session(StateData#state.sid,
- StateData#state.user,
- StateData#state.server,
- StateData#state.resource);
- _ ->
- From = StateData#state.jid,
- Packet = {xmlelement, "presence",
- [{"type", "unavailable"}], []},
- ejabberd_sm:close_session_unset_presence(
- StateData#state.sid,
- StateData#state.user,
- StateData#state.server,
- StateData#state.resource,
- ""),
- presence_broadcast(
- StateData, From, StateData#state.pres_a, Packet),
- presence_broadcast(
- StateData, From, StateData#state.pres_i, Packet)
- end
- end,
- bounce_messages();
- _ ->
- ok
+ session_established ->
+ case StateData#state.authenticated of
+ replaced ->
+ ?INFO_MSG("(~w) Replaced session for ~s",
+ [StateData#state.socket,
+ jlib:jid_to_string(StateData#state.jid)]),
+ From = StateData#state.jid,
+ Packet = #xmlel{name = <<"presence">>,
+ attrs = [{<<"type">>, <<"unavailable">>}],
+ children =
+ [#xmlel{name = <<"status">>, attrs = [],
+ children =
+ [{xmlcdata,
+ <<"Replaced by new connection">>}]}]},
+ ejabberd_sm:close_session_unset_presence(StateData#state.sid,
+ StateData#state.user,
+ StateData#state.server,
+ StateData#state.resource,
+ <<"Replaced by new connection">>),
+ presence_broadcast(StateData, From,
+ StateData#state.pres_a, Packet),
+ presence_broadcast(StateData, From,
+ StateData#state.pres_i, Packet);
+ _ ->
+ ?INFO_MSG("(~w) Close session for ~s",
+ [StateData#state.socket,
+ jlib:jid_to_string(StateData#state.jid)]),
+ EmptySet = (?SETS):new(),
+ case StateData of
+ #state{pres_last = undefined, pres_a = EmptySet, pres_i = EmptySet, pres_invis = false} ->
+ ejabberd_sm:close_session(StateData#state.sid,
+ StateData#state.user,
+ StateData#state.server,
+ StateData#state.resource);
+ _ ->
+ From = StateData#state.jid,
+ Packet = #xmlel{name = <<"presence">>,
+ attrs = [{<<"type">>, <<"unavailable">>}],
+ children = []},
+ ejabberd_sm:close_session_unset_presence(StateData#state.sid,
+ StateData#state.user,
+ StateData#state.server,
+ StateData#state.resource,
+ <<"">>),
+ presence_broadcast(StateData, From,
+ StateData#state.pres_a, Packet),
+ presence_broadcast(StateData, From,
+ StateData#state.pres_i, Packet)
+ end
+ end,
+ bounce_messages();
+ _ ->
+ ok
end,
(StateData#state.sockmod):close(StateData#state.socket),
ok.
@@ -1546,10 +1644,11 @@ terminate(_Reason, StateName, StateData) ->
change_shaper(StateData, JID) ->
Shaper = acl:match_rule(StateData#state.server,
StateData#state.shaper, JID),
- (StateData#state.sockmod):change_shaper(StateData#state.socket, Shaper).
+ (StateData#state.sockmod):change_shaper(StateData#state.socket,
+ Shaper).
send_text(StateData, Text) when StateData#state.xml_socket ->
- ?DEBUG("Send Text on stream = ~p", [lists:flatten(Text)]),
+ ?DEBUG("Send Text on stream = ~p", [Text]),
(StateData#state.sockmod):send_xml(StateData#state.socket,
{xmlstreamraw, Text});
send_text(StateData, Text) ->
@@ -1563,82 +1662,67 @@ send_element(StateData, El) ->
send_text(StateData, xml:element_to_binary(El)).
send_header(StateData, Server, Version, Lang)
- when StateData#state.xml_socket ->
- VersionAttr =
- case Version of
- "" -> [];
- _ -> [{"version", Version}]
- end,
- LangAttr =
- case Lang of
- "" -> [];
- _ -> [{"xml:lang", Lang}]
- end,
- Header =
- {xmlstreamstart,
- "stream:stream",
- VersionAttr ++
- LangAttr ++
- [{"xmlns", "jabber:client"},
- {"xmlns:stream", "http://etherx.jabber.org/streams"},
- {"id", StateData#state.streamid},
- {"from", Server}]},
- (StateData#state.sockmod):send_xml(
- StateData#state.socket, Header);
+ when StateData#state.xml_socket ->
+ VersionAttr = case Version of
+ <<"">> -> [];
+ _ -> [{<<"version">>, Version}]
+ end,
+ LangAttr = case Lang of
+ <<"">> -> [];
+ _ -> [{<<"xml:lang">>, Lang}]
+ end,
+ Header = {xmlstreamstart, <<"stream:stream">>,
+ VersionAttr ++
+ LangAttr ++
+ [{<<"xmlns">>, <<"jabber:client">>},
+ {<<"xmlns:stream">>,
+ <<"http://etherx.jabber.org/streams">>},
+ {<<"id">>, StateData#state.streamid},
+ {<<"from">>, Server}]},
+ (StateData#state.sockmod):send_xml(StateData#state.socket,
+ Header);
send_header(StateData, Server, Version, Lang) ->
- VersionStr =
- case Version of
- "" -> "";
- _ -> [" version='", Version, "'"]
- end,
- LangStr =
- case Lang of
- "" -> "";
- _ -> [" xml:lang='", Lang, "'"]
- end,
+ VersionStr = case Version of
+ <<"">> -> <<"">>;
+ _ -> [<<" version='">>, Version, <<"'">>]
+ end,
+ LangStr = case Lang of
+ <<"">> -> <<"">>;
+ _ -> [<<" xml:lang='">>, Lang, <<"'">>]
+ end,
Header = io_lib:format(?STREAM_HEADER,
- [StateData#state.streamid,
- Server,
- VersionStr,
+ [StateData#state.streamid, Server, VersionStr,
LangStr]),
- send_text(StateData, Header).
+ send_text(StateData, iolist_to_binary(Header)).
-send_trailer(StateData) when StateData#state.xml_socket ->
- (StateData#state.sockmod):send_xml(
- StateData#state.socket,
- {xmlstreamend, "stream:stream"});
+send_trailer(StateData)
+ when StateData#state.xml_socket ->
+ (StateData#state.sockmod):send_xml(StateData#state.socket,
+ {xmlstreamend, <<"stream:stream">>});
send_trailer(StateData) ->
send_text(StateData, ?STREAM_TRAILER).
-
-new_id() ->
- randoms:get_string().
-
+new_id() -> randoms:get_string().
is_auth_packet(El) ->
case jlib:iq_query_info(El) of
- #iq{id = ID, type = Type, xmlns = ?NS_AUTH, sub_el = SubEl} ->
- {xmlelement, _, _, Els} = SubEl,
- {auth, ID, Type,
- get_auth_tags(Els, "", "", "", "")};
- _ ->
- false
+ #iq{id = ID, type = Type, xmlns = ?NS_AUTH,
+ sub_el = SubEl} ->
+ #xmlel{children = Els} = SubEl,
+ {auth, ID, Type,
+ get_auth_tags(Els, <<"">>, <<"">>, <<"">>, <<"">>)};
+ _ -> false
end.
-
-get_auth_tags([{xmlelement, Name, _Attrs, Els}| L], U, P, D, R) ->
+get_auth_tags([#xmlel{name = Name, children = Els} | L],
+ U, P, D, R) ->
CData = xml:get_cdata(Els),
case Name of
- "username" ->
- get_auth_tags(L, CData, P, D, R);
- "password" ->
- get_auth_tags(L, U, CData, D, R);
- "digest" ->
- get_auth_tags(L, U, P, CData, R);
- "resource" ->
- get_auth_tags(L, U, P, D, CData);
- _ ->
- get_auth_tags(L, U, P, D, R)
+ <<"username">> -> get_auth_tags(L, CData, P, D, R);
+ <<"password">> -> get_auth_tags(L, U, CData, D, R);
+ <<"digest">> -> get_auth_tags(L, U, P, CData, R);
+ <<"resource">> -> get_auth_tags(L, U, P, D, CData);
+ _ -> get_auth_tags(L, U, P, D, R)
end;
get_auth_tags([_ | L], U, P, D, R) ->
get_auth_tags(L, U, P, D, R);
@@ -1664,7 +1748,7 @@ get_conn_type(StateData) ->
process_presence_probe(From, To, StateData) ->
LFrom = jlib:jid_tolower(From),
- LBFrom = setelement(3, LFrom, ""),
+ LBFrom = setelement(3, LFrom, <<"">>),
case StateData#state.pres_last of
undefined ->
ok;
@@ -1688,7 +1772,7 @@ process_presence_probe(From, To, StateData) ->
Packet = xml:append_subtags(
StateData#state.pres_last,
%% To is the one sending the presence (the target of the probe)
- [jlib:timestamp_to_xml(Timestamp, utc, To, ""),
+ [jlib:timestamp_to_xml(Timestamp, utc, To, <<"">>),
%% TODO: Delete the next line once XEP-0091 is Obsolete
jlib:timestamp_to_xml(Timestamp)]),
case privacy_check_packet(StateData, To, From, Packet, out) of
@@ -1707,9 +1791,9 @@ process_presence_probe(From, To, StateData) ->
end;
Cond2 ->
ejabberd_router:route(To, From,
- {xmlelement, "presence",
- [],
- []});
+ #xmlel{name = <<"presence">>,
+ attrs = [],
+ children = []});
true ->
ok
end
@@ -1717,500 +1801,414 @@ process_presence_probe(From, To, StateData) ->
%% User updates his presence (non-directed presence packet)
presence_update(From, Packet, StateData) ->
- {xmlelement, _Name, Attrs, _Els} = Packet,
- case xml:get_attr_s("type", Attrs) of
- "unavailable" ->
- Status = case xml:get_subtag(Packet, "status") of
- false ->
- "";
- StatusTag ->
- xml:get_tag_cdata(StatusTag)
- end,
- Info = [{ip, StateData#state.ip}, {conn, StateData#state.conn},
- {auth_module, StateData#state.auth_module}],
- ejabberd_sm:unset_presence(StateData#state.sid,
- StateData#state.user,
- StateData#state.server,
- StateData#state.resource,
- Status,
- Info),
- presence_broadcast(StateData, From, StateData#state.pres_a, Packet),
- presence_broadcast(StateData, From, StateData#state.pres_i, Packet),
- StateData#state{pres_last = undefined,
- pres_timestamp = undefined,
- pres_a = ?SETS:new(),
- pres_i = ?SETS:new(),
- pres_invis = false};
- "invisible" ->
- NewPriority = get_priority_from_presence(Packet),
- update_priority(NewPriority, Packet, StateData),
- NewState =
- if
- not StateData#state.pres_invis ->
- presence_broadcast(StateData, From,
- StateData#state.pres_a,
- Packet),
- presence_broadcast(StateData, From,
- StateData#state.pres_i,
- Packet),
- S1 = StateData#state{pres_last = undefined,
- pres_timestamp = undefined,
- pres_a = ?SETS:new(),
- pres_i = ?SETS:new(),
- pres_invis = true},
- presence_broadcast_first(From, S1, Packet);
- true ->
- StateData
- end,
- NewState;
- "error" ->
- StateData;
- "probe" ->
- StateData;
- "subscribe" ->
- StateData;
- "subscribed" ->
- StateData;
- "unsubscribe" ->
- StateData;
- "unsubscribed" ->
- StateData;
- _ ->
- OldPriority = case StateData#state.pres_last of
- undefined ->
- 0;
- OldPresence ->
- get_priority_from_presence(OldPresence)
- end,
- NewPriority = get_priority_from_presence(Packet),
- Timestamp = calendar:now_to_universal_time(now()),
- update_priority(NewPriority, Packet, StateData),
- FromUnavail = (StateData#state.pres_last == undefined) or
- StateData#state.pres_invis,
- ?DEBUG("from unavail = ~p~n", [FromUnavail]),
- NewStateData = StateData#state{pres_last = Packet,
- pres_invis = false,
- pres_timestamp = Timestamp},
- NewState =
- if
- FromUnavail ->
- ejabberd_hooks:run(user_available_hook,
- NewStateData#state.server,
- [NewStateData#state.jid]),
- if NewPriority >= 0 ->
- resend_offline_messages(NewStateData),
- resend_subscription_requests(NewStateData);
- true ->
- ok
- end,
- presence_broadcast_first(From, NewStateData, Packet);
- true ->
- presence_broadcast_to_trusted(NewStateData,
- From,
- NewStateData#state.pres_f,
- NewStateData#state.pres_a,
- Packet),
- if OldPriority < 0, NewPriority >= 0 ->
- resend_offline_messages(NewStateData);
- true ->
- ok
+ #xmlel{attrs = Attrs} = Packet,
+ case xml:get_attr_s(<<"type">>, Attrs) of
+ <<"unavailable">> ->
+ Status = case xml:get_subtag(Packet, <<"status">>) of
+ false -> <<"">>;
+ StatusTag -> xml:get_tag_cdata(StatusTag)
+ end,
+ Info = [{ip, StateData#state.ip},
+ {conn, StateData#state.conn},
+ {auth_module, StateData#state.auth_module}],
+ ejabberd_sm:unset_presence(StateData#state.sid,
+ StateData#state.user,
+ StateData#state.server,
+ StateData#state.resource, Status, Info),
+ presence_broadcast(StateData, From,
+ StateData#state.pres_a, Packet),
+ StateData#state{pres_last = undefined,
+ pres_timestamp = undefined, pres_a = (?SETS):new()};
+ <<"error">> -> StateData;
+ <<"probe">> -> StateData;
+ <<"subscribe">> -> StateData;
+ <<"subscribed">> -> StateData;
+ <<"unsubscribe">> -> StateData;
+ <<"unsubscribed">> -> StateData;
+ _ ->
+ OldPriority = case StateData#state.pres_last of
+ undefined -> 0;
+ OldPresence -> get_priority_from_presence(OldPresence)
end,
- NewStateData
- end,
- NewState
+ NewPriority = get_priority_from_presence(Packet),
+ Timestamp = calendar:now_to_universal_time(now()),
+ update_priority(NewPriority, Packet, StateData),
+ FromUnavail = (StateData#state.pres_last == undefined),
+ ?DEBUG("from unavail = ~p~n", [FromUnavail]),
+ NewStateData = StateData#state{pres_last = Packet,
+ pres_timestamp = Timestamp},
+ NewState = if FromUnavail ->
+ ejabberd_hooks:run(user_available_hook,
+ NewStateData#state.server,
+ [NewStateData#state.jid]),
+ if NewPriority >= 0 ->
+ resend_offline_messages(NewStateData),
+ resend_subscription_requests(NewStateData);
+ true -> ok
+ end,
+ presence_broadcast_first(From, NewStateData,
+ Packet);
+ true ->
+ presence_broadcast_to_trusted(NewStateData, From,
+ NewStateData#state.pres_f,
+ NewStateData#state.pres_a,
+ Packet),
+ if OldPriority < 0, NewPriority >= 0 ->
+ resend_offline_messages(NewStateData);
+ true -> ok
+ end,
+ NewStateData
+ end,
+ NewState
end.
%% User sends a directed presence packet
presence_track(From, To, Packet, StateData) ->
- {xmlelement, _Name, Attrs, _Els} = Packet,
+ #xmlel{attrs = Attrs} = Packet,
LTo = jlib:jid_tolower(To),
User = StateData#state.user,
Server = StateData#state.server,
- case xml:get_attr_s("type", Attrs) of
- "unavailable" ->
- check_privacy_route(From, StateData, From, To, Packet),
- I = remove_element(LTo, StateData#state.pres_i),
- A = remove_element(LTo, StateData#state.pres_a),
- StateData#state{pres_i = I,
- pres_a = A};
- "invisible" ->
- check_privacy_route(From, StateData, From, To, Packet),
- I = ?SETS:add_element(LTo, StateData#state.pres_i),
- A = remove_element(LTo, StateData#state.pres_a),
- StateData#state{pres_i = I,
- pres_a = A};
- "subscribe" ->
- ejabberd_hooks:run(roster_out_subscription,
- Server,
- [User, Server, To, subscribe]),
- check_privacy_route(From, StateData, jlib:jid_remove_resource(From),
- To, Packet),
- StateData;
- "subscribed" ->
- ejabberd_hooks:run(roster_out_subscription,
- Server,
- [User, Server, To, subscribed]),
- check_privacy_route(From, StateData, jlib:jid_remove_resource(From),
- To, Packet),
- StateData;
- "unsubscribe" ->
- ejabberd_hooks:run(roster_out_subscription,
- Server,
- [User, Server, To, unsubscribe]),
- check_privacy_route(From, StateData, jlib:jid_remove_resource(From),
- To, Packet),
- StateData;
- "unsubscribed" ->
- ejabberd_hooks:run(roster_out_subscription,
- Server,
- [User, Server, To, unsubscribed]),
- check_privacy_route(From, StateData, jlib:jid_remove_resource(From),
- To, Packet),
- StateData;
- "error" ->
- check_privacy_route(From, StateData, From, To, Packet),
- StateData;
- "probe" ->
- check_privacy_route(From, StateData, From, To, Packet),
- StateData;
- _ ->
- check_privacy_route(From, StateData, From, To, Packet),
- I = remove_element(LTo, StateData#state.pres_i),
- A = ?SETS:add_element(LTo, StateData#state.pres_a),
- StateData#state{pres_i = I,
- pres_a = A}
+ case xml:get_attr_s(<<"type">>, Attrs) of
+ <<"unavailable">> ->
+ check_privacy_route(From, StateData, From, To, Packet),
+ A = remove_element(LTo, StateData#state.pres_a),
+ StateData#state{pres_a = A};
+ <<"subscribe">> ->
+ ejabberd_hooks:run(roster_out_subscription, Server,
+ [User, Server, To, subscribe]),
+ check_privacy_route(From, StateData,
+ jlib:jid_remove_resource(From), To, Packet),
+ StateData;
+ <<"subscribed">> ->
+ ejabberd_hooks:run(roster_out_subscription, Server,
+ [User, Server, To, subscribed]),
+ check_privacy_route(From, StateData,
+ jlib:jid_remove_resource(From), To, Packet),
+ StateData;
+ <<"unsubscribe">> ->
+ ejabberd_hooks:run(roster_out_subscription, Server,
+ [User, Server, To, unsubscribe]),
+ check_privacy_route(From, StateData,
+ jlib:jid_remove_resource(From), To, Packet),
+ StateData;
+ <<"unsubscribed">> ->
+ ejabberd_hooks:run(roster_out_subscription, Server,
+ [User, Server, To, unsubscribed]),
+ check_privacy_route(From, StateData,
+ jlib:jid_remove_resource(From), To, Packet),
+ StateData;
+ <<"error">> ->
+ check_privacy_route(From, StateData, From, To, Packet),
+ StateData;
+ <<"probe">> ->
+ check_privacy_route(From, StateData, From, To, Packet),
+ StateData;
+ _ ->
+ check_privacy_route(From, StateData, From, To, Packet),
+ A = (?SETS):add_element(LTo, StateData#state.pres_a),
+ StateData#state{pres_a = A}
end.
-check_privacy_route(From, StateData, FromRoute, To, Packet) ->
- case privacy_check_packet(StateData, From, To, Packet, out) of
- deny ->
- Lang = StateData#state.lang,
- ErrText = "Your active privacy list has denied the routing of this stanza.",
- Err = jlib:make_error_reply(Packet, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)),
- ejabberd_router:route(To, From, Err),
- ok;
- allow ->
- ejabberd_router:route(FromRoute, To, Packet)
+check_privacy_route(From, StateData, FromRoute, To,
+ Packet) ->
+ case privacy_check_packet(StateData, From, To, Packet,
+ out)
+ of
+ deny ->
+ Lang = StateData#state.lang,
+ ErrText = <<"Your active privacy list has denied "
+ "the routing of this stanza.">>,
+ Err = jlib:make_error_reply(Packet,
+ ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)),
+ ejabberd_router:route(To, From, Err),
+ ok;
+ allow -> ejabberd_router:route(FromRoute, To, Packet)
end.
-privacy_check_packet(StateData, From, To, Packet, Dir) ->
- ejabberd_hooks:run_fold(
- privacy_check_packet, StateData#state.server,
- allow,
- [StateData#state.user,
- StateData#state.server,
- StateData#state.privacy_list,
- {From, To, Packet},
- Dir]).
-
%% Check if privacy rules allow this delivery
+privacy_check_packet(StateData, From, To, Packet,
+ Dir) ->
+ ejabberd_hooks:run_fold(privacy_check_packet,
+ StateData#state.server, allow,
+ [StateData#state.user, StateData#state.server,
+ StateData#state.privacy_list, {From, To, Packet},
+ Dir]).
+
is_privacy_allow(StateData, From, To, Packet, Dir) ->
- allow == privacy_check_packet(StateData, From, To, Packet, Dir).
+ allow ==
+ privacy_check_packet(StateData, From, To, Packet, Dir).
+%% Send presence when disconnecting
presence_broadcast(StateData, From, JIDSet, Packet) ->
- lists:foreach(fun(JID) ->
- FJID = jlib:make_jid(JID),
- case privacy_check_packet(StateData, From, FJID, Packet, out) of
- deny ->
- ok;
- allow ->
- ejabberd_router:route(From, FJID, Packet)
- end
- end, ?SETS:to_list(JIDSet)).
-
-presence_broadcast_to_trusted(StateData, From, T, A, Packet) ->
- lists:foreach(
- fun(JID) ->
- case ?SETS:is_element(JID, T) of
- true ->
- FJID = jlib:make_jid(JID),
- case privacy_check_packet(StateData, From, FJID, Packet, out) of
- deny ->
- ok;
- allow ->
- ejabberd_router:route(From, FJID, Packet)
- end;
- _ ->
- ok
- end
- end, ?SETS:to_list(A)).
+ JIDs = ?SETS:to_list(JIDSet),
+ JIDs2 = format_and_check_privacy(From, StateData, Packet, JIDs, out),
+ Server = StateData#state.server,
+ send_multiple(From, Server, JIDs2, Packet).
+%% Send presence when updating presence
+presence_broadcast_to_trusted(StateData, From, Trusted, JIDSet, Packet) ->
+ JIDs = ?SETS:to_list(JIDSet),
+ JIDs_trusted = [JID || JID <- JIDs, ?SETS:is_element(JID, Trusted)],
+ JIDs2 = format_and_check_privacy(From, StateData, Packet, JIDs_trusted, out),
+ Server = StateData#state.server,
+ send_multiple(From, Server, JIDs2, Packet).
+%% Send presence when connecting
presence_broadcast_first(From, StateData, Packet) ->
- ?SETS:fold(fun(JID, X) ->
- ejabberd_router:route(
- From,
- jlib:make_jid(JID),
- {xmlelement, "presence",
- [{"type", "probe"}],
- []}),
- X
- end,
- [],
- StateData#state.pres_t),
- if
- StateData#state.pres_invis ->
- StateData;
- true ->
- As = ?SETS:fold(
- fun(JID, A) ->
- FJID = jlib:make_jid(JID),
- case privacy_check_packet(StateData, From, FJID, Packet, out) of
- deny ->
- ok;
- allow ->
- ejabberd_router:route(From, FJID, Packet)
- end,
- ?SETS:add_element(JID, A)
- end,
- StateData#state.pres_a,
- StateData#state.pres_f),
- StateData#state{pres_a = As}
- end.
+ JIDsProbe =
+ ?SETS:fold(
+ fun(JID, L) -> [JID | L] end,
+ [],
+ StateData#state.pres_t),
+ PacketProbe = #xmlel{name = <<"presence">>, attrs = [{<<"type">>,<<"probe">>}], children = []},
+ JIDs2Probe = format_and_check_privacy(From, StateData, Packet, JIDsProbe, out),
+ Server = StateData#state.server,
+ send_multiple(From, Server, JIDs2Probe, PacketProbe),
+ {As, JIDs} =
+ ?SETS:fold(
+ fun(JID, {A, JID_list}) ->
+ {?SETS:add_element(JID, A), JID_list++[JID]}
+ end,
+ {StateData#state.pres_a, []},
+ StateData#state.pres_f),
+ JIDs2 = format_and_check_privacy(From, StateData, Packet, JIDs, out),
+ Server = StateData#state.server,
+ send_multiple(From, Server, JIDs2, Packet),
+ StateData#state{pres_a = As}.
+
+format_and_check_privacy(From, StateData, Packet, JIDs, Dir) ->
+ FJIDs = [jlib:make_jid(JID) || JID <- JIDs],
+ lists:filter(
+ fun(FJID) ->
+ case ejabberd_hooks:run_fold(
+ privacy_check_packet, StateData#state.server,
+ allow,
+ [StateData#state.user,
+ StateData#state.server,
+ StateData#state.privacy_list,
+ {From, FJID, Packet},
+ Dir]) of
+ deny -> false;
+ allow -> true
+ end
+ end,
+ FJIDs).
+
+send_multiple(From, Server, JIDs, Packet) ->
+ ejabberd_router_multicast:route_multicast(From, Server, JIDs, Packet).
remove_element(E, Set) ->
- case ?SETS:is_element(E, Set) of
- true ->
- ?SETS:del_element(E, Set);
- _ ->
- Set
+ case (?SETS):is_element(E, Set) of
+ true -> (?SETS):del_element(E, Set);
+ _ -> Set
end.
-
roster_change(IJID, ISubscription, StateData) ->
LIJID = jlib:jid_tolower(IJID),
- IsFrom = (ISubscription == both) or (ISubscription == from),
- IsTo = (ISubscription == both) or (ISubscription == to),
- OldIsFrom = ?SETS:is_element(LIJID, StateData#state.pres_f),
- FSet = if
- IsFrom ->
- ?SETS:add_element(LIJID, StateData#state.pres_f);
- true ->
- remove_element(LIJID, StateData#state.pres_f)
+ IsFrom = (ISubscription == both) or
+ (ISubscription == from),
+ IsTo = (ISubscription == both) or (ISubscription == to),
+ OldIsFrom = (?SETS):is_element(LIJID,
+ StateData#state.pres_f),
+ FSet = if IsFrom ->
+ (?SETS):add_element(LIJID, StateData#state.pres_f);
+ true -> remove_element(LIJID, StateData#state.pres_f)
end,
- TSet = if
- IsTo ->
- ?SETS:add_element(LIJID, StateData#state.pres_t);
- true ->
- remove_element(LIJID, StateData#state.pres_t)
+ TSet = if IsTo ->
+ (?SETS):add_element(LIJID, StateData#state.pres_t);
+ true -> remove_element(LIJID, StateData#state.pres_t)
end,
case StateData#state.pres_last of
- undefined ->
- StateData#state{pres_f = FSet, pres_t = TSet};
- P ->
- ?DEBUG("roster changed for ~p~n", [StateData#state.user]),
- From = StateData#state.jid,
- To = jlib:make_jid(IJID),
- Cond1 = (not StateData#state.pres_invis) and IsFrom
- and (not OldIsFrom),
- Cond2 = (not IsFrom) and OldIsFrom
- and (?SETS:is_element(LIJID, StateData#state.pres_a) or
- ?SETS:is_element(LIJID, StateData#state.pres_i)),
- if
- Cond1 ->
- ?DEBUG("C1: ~p~n", [LIJID]),
- case privacy_check_packet(StateData, From, To, P, out) of
- deny ->
- ok;
- allow ->
- ejabberd_router:route(From, To, P)
- end,
- A = ?SETS:add_element(LIJID,
- StateData#state.pres_a),
- StateData#state{pres_a = A,
- pres_f = FSet,
- pres_t = TSet};
- Cond2 ->
- ?DEBUG("C2: ~p~n", [LIJID]),
- PU = {xmlelement, "presence",
- [{"type", "unavailable"}], []},
- case privacy_check_packet(StateData, From, To, PU, out) of
- deny ->
- ok;
- allow ->
- ejabberd_router:route(From, To, PU)
- end,
- I = remove_element(LIJID,
- StateData#state.pres_i),
- A = remove_element(LIJID,
- StateData#state.pres_a),
- StateData#state{pres_i = I,
- pres_a = A,
- pres_f = FSet,
- pres_t = TSet};
- true ->
- StateData#state{pres_f = FSet, pres_t = TSet}
- end
+ undefined ->
+ StateData#state{pres_f = FSet, pres_t = TSet};
+ P ->
+ ?DEBUG("roster changed for ~p~n",
+ [StateData#state.user]),
+ From = StateData#state.jid,
+ To = jlib:make_jid(IJID),
+ Cond1 = IsFrom andalso not OldIsFrom,
+ Cond2 = not IsFrom andalso OldIsFrom andalso
+ ((?SETS):is_element(LIJID, StateData#state.pres_a)),
+ if Cond1 ->
+ ?DEBUG("C1: ~p~n", [LIJID]),
+ case privacy_check_packet(StateData, From, To, P, out)
+ of
+ deny -> ok;
+ allow -> ejabberd_router:route(From, To, P)
+ end,
+ A = (?SETS):add_element(LIJID, StateData#state.pres_a),
+ StateData#state{pres_a = A, pres_f = FSet,
+ pres_t = TSet};
+ Cond2 ->
+ ?DEBUG("C2: ~p~n", [LIJID]),
+ PU = #xmlel{name = <<"presence">>,
+ attrs = [{<<"type">>, <<"unavailable">>}],
+ children = []},
+ case privacy_check_packet(StateData, From, To, PU, out)
+ of
+ deny -> ok;
+ allow -> ejabberd_router:route(From, To, PU)
+ end,
+ A = remove_element(LIJID, StateData#state.pres_a),
+ StateData#state{pres_a = A, pres_f = FSet,
+ pres_t = TSet};
+ true -> StateData#state{pres_f = FSet, pres_t = TSet}
+ end
end.
-
update_priority(Priority, Packet, StateData) ->
Info = [{ip, StateData#state.ip}, {conn, StateData#state.conn},
{auth_module, StateData#state.auth_module}],
ejabberd_sm:set_presence(StateData#state.sid,
- StateData#state.user,
- StateData#state.server,
- StateData#state.resource,
- Priority,
- Packet,
- Info).
+ StateData#state.user, StateData#state.server,
+ StateData#state.resource, Priority, Packet, Info).
get_priority_from_presence(PresencePacket) ->
- case xml:get_subtag(PresencePacket, "priority") of
- false ->
- 0;
- SubEl ->
- case catch list_to_integer(xml:get_tag_cdata(SubEl)) of
- P when is_integer(P) ->
- P;
- _ ->
- 0
- end
+ case xml:get_subtag(PresencePacket, <<"priority">>) of
+ false -> 0;
+ SubEl ->
+ case catch
+ jlib:binary_to_integer(xml:get_tag_cdata(SubEl))
+ of
+ P when is_integer(P) -> P;
+ _ -> 0
+ end
end.
process_privacy_iq(From, To,
- #iq{type = Type, sub_el = SubEl} = IQ,
- StateData) ->
- {Res, NewStateData} =
- case Type of
- get ->
- R = ejabberd_hooks:run_fold(
- privacy_iq_get, StateData#state.server,
- {error, ?ERR_FEATURE_NOT_IMPLEMENTED},
- [From, To, IQ, StateData#state.privacy_list]),
- {R, StateData};
- set ->
- case ejabberd_hooks:run_fold(
- privacy_iq_set, StateData#state.server,
- {error, ?ERR_FEATURE_NOT_IMPLEMENTED},
- [From, To, IQ]) of
- {result, R, NewPrivList} ->
- {{result, R},
- StateData#state{privacy_list = NewPrivList}};
- R -> {R, StateData}
- end
- end,
- IQRes =
- case Res of
- {result, Result} ->
- IQ#iq{type = result, sub_el = Result};
- {error, Error} ->
- IQ#iq{type = error, sub_el = [SubEl, Error]}
- end,
- ejabberd_router:route(
- To, From, jlib:iq_to_xml(IQRes)),
+ #iq{type = Type, sub_el = SubEl} = IQ, StateData) ->
+ {Res, NewStateData} = case Type of
+ get ->
+ R = ejabberd_hooks:run_fold(privacy_iq_get,
+ StateData#state.server,
+ {error,
+ ?ERR_FEATURE_NOT_IMPLEMENTED},
+ [From, To, IQ,
+ StateData#state.privacy_list]),
+ {R, StateData};
+ set ->
+ case ejabberd_hooks:run_fold(privacy_iq_set,
+ StateData#state.server,
+ {error,
+ ?ERR_FEATURE_NOT_IMPLEMENTED},
+ [From, To, IQ])
+ of
+ {result, R, NewPrivList} ->
+ {{result, R},
+ StateData#state{privacy_list =
+ NewPrivList}};
+ R -> {R, StateData}
+ end
+ end,
+ IQRes = case Res of
+ {result, Result} ->
+ IQ#iq{type = result, sub_el = Result};
+ {error, Error} ->
+ IQ#iq{type = error, sub_el = [SubEl, Error]}
+ end,
+ ejabberd_router:route(To, From, jlib:iq_to_xml(IQRes)),
NewStateData.
-
resend_offline_messages(StateData) ->
- case ejabberd_hooks:run_fold(
- resend_offline_messages_hook, StateData#state.server,
- [],
- [StateData#state.user, StateData#state.server]) of
- Rs when is_list(Rs) ->
- lists:foreach(
- fun({route,
- From, To, {xmlelement, _Name, _Attrs, _Els} = Packet}) ->
- Pass = case privacy_check_packet(StateData, From, To, Packet, in) of
- allow ->
- true;
- deny ->
- false
- end,
- if
- Pass ->
- %% Attrs2 = jlib:replace_from_to_attrs(
- %% jlib:jid_to_string(From),
- %% jlib:jid_to_string(To),
- %% Attrs),
- %% FixedPacket = {xmlelement, Name, Attrs2, Els},
- %% Use route instead of send_element to go through standard workflow
- ejabberd_router:route(From, To, Packet);
- %% send_element(StateData, FixedPacket),
- %% ejabberd_hooks:run(user_receive_packet,
- %% StateData#state.server,
- %% [StateData#state.jid,
- %% From, To, FixedPacket]);
- true ->
- ok
- end
- end, Rs)
+ case
+ ejabberd_hooks:run_fold(resend_offline_messages_hook,
+ StateData#state.server, [],
+ [StateData#state.user, StateData#state.server])
+ of
+ Rs -> %%when is_list(Rs) ->
+ lists:foreach(fun ({route, From, To,
+ #xmlel{} = Packet}) ->
+ Pass = case privacy_check_packet(StateData,
+ From, To,
+ Packet, in)
+ of
+ allow -> true;
+ deny -> false
+ end,
+ if Pass ->
+ ejabberd_router:route(From, To, Packet);
+ %% send_element(StateData, FixedPacket),
+ %% ejabberd_hooks:run(user_receive_packet,
+ %% StateData#state.server,
+ %% [StateData#state.jid,
+ %% From, To, FixedPacket]);
+ true -> ok
+ end
+ end,
+ Rs)
end.
resend_subscription_requests(#state{user = User,
- server = Server} = StateData) ->
- PendingSubscriptions = ejabberd_hooks:run_fold(
- resend_subscription_requests_hook,
- Server,
- [],
- [User, Server]),
- lists:foreach(fun(XMLPacket) ->
- send_element(StateData,
- XMLPacket)
+ server = Server} =
+ StateData) ->
+ PendingSubscriptions =
+ ejabberd_hooks:run_fold(resend_subscription_requests_hook,
+ Server, [], [User, Server]),
+ lists:foreach(fun (XMLPacket) ->
+ send_element(StateData, XMLPacket)
end,
PendingSubscriptions).
-get_showtag(undefined) ->
- "unavailable";
+get_showtag(undefined) -> <<"unavailable">>;
get_showtag(Presence) ->
- case xml:get_path_s(Presence, [{elem, "show"}, cdata]) of
- "" -> "available";
- ShowTag -> ShowTag
+ case xml:get_path_s(Presence,
+ [{elem, <<"show">>}, cdata])
+ of
+ <<"">> -> <<"available">>;
+ ShowTag -> ShowTag
end.
-get_statustag(undefined) ->
- "";
+get_statustag(undefined) -> <<"">>;
get_statustag(Presence) ->
- case xml:get_path_s(Presence, [{elem, "status"}, cdata]) of
- ShowTag -> ShowTag
+ case xml:get_path_s(Presence,
+ [{elem, <<"status">>}, cdata])
+ of
+ ShowTag -> ShowTag
end.
process_unauthenticated_stanza(StateData, El) ->
- NewEl = case xml:get_tag_attr_s("xml:lang", El) of
- "" ->
- case StateData#state.lang of
- "" -> El;
- Lang ->
- xml:replace_tag_attr("xml:lang", Lang, El)
- end;
- _ ->
- El
+ NewEl = case xml:get_tag_attr_s(<<"xml:lang">>, El) of
+ <<"">> ->
+ case StateData#state.lang of
+ <<"">> -> El;
+ Lang -> xml:replace_tag_attr(<<"xml:lang">>, Lang, El)
+ end;
+ _ -> El
end,
case jlib:iq_query_info(NewEl) of
- #iq{} = IQ ->
- Res = ejabberd_hooks:run_fold(c2s_unauthenticated_iq,
- StateData#state.server,
- empty,
- [StateData#state.server, IQ,
- StateData#state.ip]),
- case Res of
- empty ->
- % The only reasonable IQ's here are auth and register IQ's
- % They contain secrets, so don't include subelements to response
- ResIQ = IQ#iq{type = error,
- sub_el = [?ERR_SERVICE_UNAVAILABLE]},
- Res1 = jlib:replace_from_to(
- jlib:make_jid("", StateData#state.server, ""),
- jlib:make_jid("", "", ""),
- jlib:iq_to_xml(ResIQ)),
- send_element(StateData, jlib:remove_attr("to", Res1));
- _ ->
- send_element(StateData, Res)
- end;
- _ ->
- % Drop any stanza, which isn't IQ stanza
- ok
+ #iq{} = IQ ->
+ Res = ejabberd_hooks:run_fold(c2s_unauthenticated_iq,
+ StateData#state.server, empty,
+ [StateData#state.server, IQ,
+ StateData#state.ip]),
+ case Res of
+ empty ->
+ ResIQ = IQ#iq{type = error,
+ sub_el = [?ERR_SERVICE_UNAVAILABLE]},
+ Res1 = jlib:replace_from_to(jlib:make_jid(<<"">>,
+ StateData#state.server,
+ <<"">>),
+ jlib:make_jid(<<"">>, <<"">>,
+ <<"">>),
+ jlib:iq_to_xml(ResIQ)),
+ send_element(StateData,
+ jlib:remove_attr(<<"to">>, Res1));
+ _ -> send_element(StateData, Res)
+ end;
+ _ ->
+ % Drop any stanza, which isn't IQ stanza
+ ok
end.
peerip(SockMod, Socket) ->
IP = case SockMod of
- gen_tcp -> inet:peername(Socket);
- _ -> SockMod:peername(Socket)
+ gen_tcp -> inet:peername(Socket);
+ _ -> SockMod:peername(Socket)
end,
case IP of
- {ok, IPOK} -> IPOK;
- _ -> undefined
+ {ok, IPOK} -> IPOK;
+ _ -> undefined
end.
%% fsm_next_state_pack: Pack the StateData structure to improve
@@ -2227,70 +2225,64 @@ fsm_next_state_gc(StateName, PackedStateData) ->
%% fsm_next_state: Generate the next_state FSM tuple with different
%% timeout, depending on the future state
fsm_next_state(session_established, StateData) ->
- {next_state, session_established, StateData, ?C2S_HIBERNATE_TIMEOUT};
+ {next_state, session_established, StateData,
+ ?C2S_HIBERNATE_TIMEOUT};
fsm_next_state(StateName, StateData) ->
{next_state, StateName, StateData, ?C2S_OPEN_TIMEOUT}.
%% fsm_reply: Generate the reply FSM tuple with different timeout,
%% depending on the future state
fsm_reply(Reply, session_established, StateData) ->
- {reply, Reply, session_established, StateData, ?C2S_HIBERNATE_TIMEOUT};
+ {reply, Reply, session_established, StateData,
+ ?C2S_HIBERNATE_TIMEOUT};
fsm_reply(Reply, StateName, StateData) ->
{reply, Reply, StateName, StateData, ?C2S_OPEN_TIMEOUT}.
%% Used by c2s blacklist plugins
-is_ip_blacklisted(undefined) ->
- false;
-is_ip_blacklisted({IP,_Port}) ->
+is_ip_blacklisted(undefined) -> false;
+is_ip_blacklisted({IP, _Port}) ->
ejabberd_hooks:run_fold(check_bl_c2s, false, [IP]).
%% Check from attributes
%% returns invalid-from|NewElement
check_from(El, FromJID) ->
- case xml:get_tag_attr("from", El) of
- false ->
- El;
- {value, SJID} ->
- JID = jlib:string_to_jid(SJID),
- case JID of
- error ->
- 'invalid-from';
- #jid{} ->
- if
- (JID#jid.luser == FromJID#jid.luser) and
- (JID#jid.lserver == FromJID#jid.lserver) and
- (JID#jid.lresource == FromJID#jid.lresource) ->
- El;
- (JID#jid.luser == FromJID#jid.luser) and
- (JID#jid.lserver == FromJID#jid.lserver) and
- (JID#jid.lresource == "") ->
- El;
- true ->
- 'invalid-from'
- end
- end
+ case xml:get_tag_attr(<<"from">>, El) of
+ false -> El;
+ {value, SJID} ->
+ JID = jlib:string_to_jid(SJID),
+ case JID of
+ error -> 'invalid-from';
+ #jid{} ->
+ if (JID#jid.luser == FromJID#jid.luser) and
+ (JID#jid.lserver == FromJID#jid.lserver)
+ and (JID#jid.lresource == FromJID#jid.lresource) ->
+ El;
+ (JID#jid.luser == FromJID#jid.luser) and
+ (JID#jid.lserver == FromJID#jid.lserver)
+ and (JID#jid.lresource == <<"">>) ->
+ El;
+ true -> 'invalid-from'
+ end
+ end
end.
fsm_limit_opts(Opts) ->
case lists:keysearch(max_fsm_queue, 1, Opts) of
- {value, {_, N}} when is_integer(N) ->
- [{max_queue, N}];
- _ ->
- case ejabberd_config:get_local_option(max_fsm_queue) of
- N when is_integer(N) ->
- [{max_queue, N}];
- _ ->
- []
- end
+ {value, {_, N}} when is_integer(N) -> [{max_queue, N}];
+ _ ->
+ case ejabberd_config:get_local_option(
+ max_fsm_queue,
+ fun(I) when is_integer(I), I > 0 -> I end) of
+ undefined -> [];
+ N -> [{max_queue, N}]
+ end
end.
bounce_messages() ->
receive
- {route, From, To, El} ->
- ejabberd_router:route(From, To, El),
- bounce_messages()
- after 0 ->
- ok
+ {route, From, To, El} ->
+ ejabberd_router:route(From, To, El), bounce_messages()
+ after 0 -> ok
end.
%%%----------------------------------------------------------------------
@@ -2298,40 +2290,40 @@ bounce_messages() ->
%%%----------------------------------------------------------------------
route_blocking(What, StateData) ->
- SubEl =
- case What of
- {block, JIDs} ->
- {xmlelement, "block",
- [{"xmlns", ?NS_BLOCKING}],
- lists:map(
- fun(JID) ->
- {xmlelement, "item",
- [{"jid", jlib:jid_to_string(JID)}],
- []}
- end, JIDs)};
- {unblock, JIDs} ->
- {xmlelement, "unblock",
- [{"xmlns", ?NS_BLOCKING}],
- lists:map(
- fun(JID) ->
- {xmlelement, "item",
- [{"jid", jlib:jid_to_string(JID)}],
- []}
- end, JIDs)};
- unblock_all ->
- {xmlelement, "unblock",
- [{"xmlns", ?NS_BLOCKING}], []}
- end,
- PrivPushIQ =
- #iq{type = set, xmlns = ?NS_BLOCKING,
- id = "push",
- sub_el = [SubEl]},
+ SubEl = case What of
+ {block, JIDs} ->
+ #xmlel{name = <<"block">>,
+ attrs = [{<<"xmlns">>, ?NS_BLOCKING}],
+ children =
+ lists:map(fun (JID) ->
+ #xmlel{name = <<"item">>,
+ attrs =
+ [{<<"jid">>,
+ jlib:jid_to_string(JID)}],
+ children = []}
+ end,
+ JIDs)};
+ {unblock, JIDs} ->
+ #xmlel{name = <<"unblock">>,
+ attrs = [{<<"xmlns">>, ?NS_BLOCKING}],
+ children =
+ lists:map(fun (JID) ->
+ #xmlel{name = <<"item">>,
+ attrs =
+ [{<<"jid">>,
+ jlib:jid_to_string(JID)}],
+ children = []}
+ end,
+ JIDs)};
+ unblock_all ->
+ #xmlel{name = <<"unblock">>,
+ attrs = [{<<"xmlns">>, ?NS_BLOCKING}], children = []}
+ end,
+ PrivPushIQ = #iq{type = set, xmlns = ?NS_BLOCKING,
+ id = <<"push">>, sub_el = [SubEl]},
PrivPushEl =
- jlib:replace_from_to(
- jlib:jid_remove_resource(
- StateData#state.jid),
- StateData#state.jid,
- jlib:iq_to_xml(PrivPushIQ)),
+ jlib:replace_from_to(jlib:jid_remove_resource(StateData#state.jid),
+ StateData#state.jid, jlib:iq_to_xml(PrivPushIQ)),
send_element(StateData, PrivPushEl),
%% No need to replace active privacy list here,
%% blocking pushes are always accompanied by
@@ -2344,45 +2336,35 @@ route_blocking(What, StateData) ->
%% Try to reduce the heap footprint of the four presence sets
%% by ensuring that we re-use strings and Jids wherever possible.
-pack(S = #state{pres_a=A,
- pres_i=I,
- pres_f=F,
- pres_t=T}) ->
- {NewA, Pack1} = pack_jid_set(A, gb_trees:empty()),
- {NewI, Pack2} = pack_jid_set(I, Pack1),
+pack(S = #state{pres_a = A, pres_f = F,
+ pres_t = T}) ->
+ {NewA, Pack2} = pack_jid_set(A, gb_trees:empty()),
{NewF, Pack3} = pack_jid_set(F, Pack2),
{NewT, _Pack4} = pack_jid_set(T, Pack3),
- %% Throw away Pack4 so that if we delete references to
- %% Strings or Jids in any of the sets there will be
- %% no live references for the GC to find.
- S#state{pres_a=NewA,
- pres_i=NewI,
- pres_f=NewF,
- pres_t=NewT}.
+ S#state{pres_a = NewA, pres_f = NewF,
+ pres_t = NewT}.
pack_jid_set(Set, Pack) ->
- Jids = ?SETS:to_list(Set),
+ Jids = (?SETS):to_list(Set),
{PackedJids, NewPack} = pack_jids(Jids, Pack, []),
- {?SETS:from_list(PackedJids), NewPack}.
+ {(?SETS):from_list(PackedJids), NewPack}.
pack_jids([], Pack, Acc) -> {Acc, Pack};
-pack_jids([{U,S,R}=Jid | Jids], Pack, Acc) ->
+pack_jids([{U, S, R} = Jid | Jids], Pack, Acc) ->
case gb_trees:lookup(Jid, Pack) of
- {value, PackedJid} ->
- pack_jids(Jids, Pack, [PackedJid | Acc]);
- none ->
- {NewU, Pack1} = pack_string(U, Pack),
- {NewS, Pack2} = pack_string(S, Pack1),
- {NewR, Pack3} = pack_string(R, Pack2),
- NewJid = {NewU, NewS, NewR},
- NewPack = gb_trees:insert(NewJid, NewJid, Pack3),
- pack_jids(Jids, NewPack, [NewJid | Acc])
+ {value, PackedJid} ->
+ pack_jids(Jids, Pack, [PackedJid | Acc]);
+ none ->
+ {NewU, Pack1} = pack_string(U, Pack),
+ {NewS, Pack2} = pack_string(S, Pack1),
+ {NewR, Pack3} = pack_string(R, Pack2),
+ NewJid = {NewU, NewS, NewR},
+ NewPack = gb_trees:insert(NewJid, NewJid, Pack3),
+ pack_jids(Jids, NewPack, [NewJid | Acc])
end.
pack_string(String, Pack) ->
case gb_trees:lookup(String, Pack) of
- {value, PackedString} ->
- {PackedString, Pack};
- none ->
- {String, gb_trees:insert(String, String, Pack)}
+ {value, PackedString} -> {PackedString, Pack};
+ none -> {String, gb_trees:insert(String, String, Pack)}
end.
diff --git a/src/ejabberd_c2s_config.erl b/src/ejabberd_c2s_config.erl
index b416edd2b..4dbc48f38 100644
--- a/src/ejabberd_c2s_config.erl
+++ b/src/ejabberd_c2s_config.erl
@@ -26,6 +26,7 @@
%%%----------------------------------------------------------------------
-module(ejabberd_c2s_config).
+
-author('mremond@process-one.net').
-export([get_c2s_limits/0]).
@@ -33,28 +34,33 @@
%% Get first c2s configuration limitations to apply it to other c2s
%% connectors.
get_c2s_limits() ->
- case ejabberd_config:get_local_option(listen) of
- undefined ->
- [];
- C2SFirstListen ->
- case lists:keysearch(ejabberd_c2s, 2, C2SFirstListen) of
- false ->
- [];
- {value, {_Port, ejabberd_c2s, Opts}} ->
- select_opts_values(Opts)
- end
+ case ejabberd_config:get_local_option(listen, fun(V) -> V end) of
+ undefined -> [];
+ C2SFirstListen ->
+ case lists:keysearch(ejabberd_c2s, 2, C2SFirstListen) of
+ false -> [];
+ {value, {_Port, ejabberd_c2s, Opts}} ->
+ select_opts_values(Opts)
+ end
end.
%% Only get access, shaper and max_stanza_size values
+
select_opts_values(Opts) ->
select_opts_values(Opts, []).
+
select_opts_values([], SelectedValues) ->
SelectedValues;
-select_opts_values([{access,Value}|Opts], SelectedValues) ->
- select_opts_values(Opts, [{access, Value}|SelectedValues]);
-select_opts_values([{shaper,Value}|Opts], SelectedValues) ->
- select_opts_values(Opts, [{shaper, Value}|SelectedValues]);
-select_opts_values([{max_stanza_size,Value}|Opts], SelectedValues) ->
- select_opts_values(Opts, [{max_stanza_size, Value}|SelectedValues]);
-select_opts_values([_Opt|Opts], SelectedValues) ->
+select_opts_values([{access, Value} | Opts],
+ SelectedValues) ->
+ select_opts_values(Opts,
+ [{access, Value} | SelectedValues]);
+select_opts_values([{shaper, Value} | Opts],
+ SelectedValues) ->
+ select_opts_values(Opts,
+ [{shaper, Value} | SelectedValues]);
+select_opts_values([{max_stanza_size, Value} | Opts],
+ SelectedValues) ->
+ select_opts_values(Opts,
+ [{max_stanza_size, Value} | SelectedValues]);
+select_opts_values([_Opt | Opts], SelectedValues) ->
select_opts_values(Opts, SelectedValues).
-
diff --git a/src/ejabberd_captcha.erl b/src/ejabberd_captcha.erl
index 319bf8e81..6cf23a493 100644
--- a/src/ejabberd_captcha.erl
+++ b/src/ejabberd_captcha.erl
@@ -32,36 +32,44 @@
-export([start_link/0]).
%% gen_server callbacks
--export([init/1, handle_call/3, handle_cast/2, handle_info/2,
- terminate/2, code_change/3]).
+-export([init/1, handle_call/3, handle_cast/2,
+ handle_info/2, terminate/2, code_change/3]).
--export([create_captcha/6, build_captcha_html/2, check_captcha/2,
- process_reply/1, process/2, is_feature_available/0,
- create_captcha_x/5, create_captcha_x/6]).
+-export([create_captcha/6, build_captcha_html/2,
+ check_captcha/2, process_reply/1, process/2,
+ is_feature_available/0, create_captcha_x/5,
+ create_captcha_x/6]).
-include("jlib.hrl").
+
-include("ejabberd.hrl").
+
-include("web/ejabberd_http.hrl").
-define(VFIELD(Type, Var, Value),
- {xmlelement, "field", [{"type", Type}, {"var", Var}],
- [{xmlelement, "value", [], [Value]}]}).
+ #xmlel{name = <<"field">>,
+ attrs = [{<<"type">>, Type}, {<<"var">>, Var}],
+ children =
+ [#xmlel{name = <<"value">>, attrs = [],
+ children = [Value]}]}).
+
+-define(CAPTCHA_TEXT(Lang),
+ translate:translate(Lang,
+ <<"Enter the text you see">>)).
--define(CAPTCHA_TEXT(Lang), translate:translate(Lang, "Enter the text you see")).
--define(CAPTCHA_LIFETIME, 120000). % two minutes
--define(LIMIT_PERIOD, 60*1000*1000). % one minute
+-define(CAPTCHA_LIFETIME, 120000).
--record(state, {limits = treap:empty()}).
--record(captcha, {id, pid, key, tref, args}).
+-define(LIMIT_PERIOD, 60*1000*1000).
--define(T(S),
- case catch mnesia:transaction(fun() -> S end) of
- {atomic, Res} ->
- Res;
- {_, Reason} ->
- ?ERROR_MSG("mnesia transaction failed: ~p", [Reason]),
- {error, Reason}
- end).
+-type error() :: efbig | enodata | limit | malformed_image | timeout.
+
+-record(state, {limits = treap:empty() :: treap:treap()}).
+
+-record(captcha, {id :: binary(),
+ pid :: pid(),
+ key :: binary(),
+ tref :: reference(),
+ args :: any()}).
%%====================================================================
%% API
@@ -71,98 +79,197 @@
%% Description: Starts the server
%%--------------------------------------------------------------------
start_link() ->
- gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
+ gen_server:start_link({local, ?MODULE}, ?MODULE, [],
+ []).
+
+-spec create_captcha(binary(), jid(), jid(),
+ binary(), any(), any()) -> {error, error()} |
+ {ok, binary(), [xmlel()]}.
-create_captcha(SID, From, To, Lang, Limiter, Args)
- when is_list(Lang), is_list(SID),
- is_record(From, jid), is_record(To, jid) ->
+create_captcha(SID, From, To, Lang, Limiter, Args) ->
case create_image(Limiter) of
- {ok, Type, Key, Image} ->
- Id = randoms:get_string(),
- B64Image = jlib:encode_base64(binary_to_list(Image)),
- JID = jlib:jid_to_string(From),
- CID = "sha1+" ++ sha:sha(Image) ++ "@bob.xmpp.org",
- Data = {xmlelement, "data",
- [{"xmlns", ?NS_BOB}, {"cid", CID},
- {"max-age", "0"}, {"type", Type}],
- [{xmlcdata, B64Image}]},
- Captcha =
- {xmlelement, "captcha", [{"xmlns", ?NS_CAPTCHA}],
- [{xmlelement, "x", [{"xmlns", ?NS_XDATA}, {"type", "form"}],
- [?VFIELD("hidden", "FORM_TYPE", {xmlcdata, ?NS_CAPTCHA}),
- ?VFIELD("hidden", "from", {xmlcdata, jlib:jid_to_string(To)}),
- ?VFIELD("hidden", "challenge", {xmlcdata, Id}),
- ?VFIELD("hidden", "sid", {xmlcdata, SID}),
- {xmlelement, "field", [{"var", "ocr"}, {"label", ?CAPTCHA_TEXT(Lang)}],
- [{xmlelement, "required", [], []},
- {xmlelement, "media", [{"xmlns", ?NS_MEDIA}],
- [{xmlelement, "uri", [{"type", Type}],
- [{xmlcdata, "cid:" ++ CID}]}]}]}]}]},
- BodyString1 = translate:translate(Lang, "Your messages to ~s are being blocked. To unblock them, visit ~s"),
- BodyString = io_lib:format(BodyString1, [JID, get_url(Id)]),
- Body = {xmlelement, "body", [],
- [{xmlcdata, BodyString}]},
- OOB = {xmlelement, "x", [{"xmlns", ?NS_OOB}],
- [{xmlelement, "url", [], [{xmlcdata, get_url(Id)}]}]},
- Tref = erlang:send_after(?CAPTCHA_LIFETIME, ?MODULE, {remove_id, Id}),
- case ?T(mnesia:write(#captcha{id=Id, pid=self(), key=Key,
- tref=Tref, args=Args})) of
- ok ->
- {ok, Id, [Body, OOB, Captcha, Data]};
- Err ->
- {error, Err}
- end;
- Err ->
- Err
+ {ok, Type, Key, Image} ->
+ Id = <<(randoms:get_string())/binary>>,
+ B64Image = jlib:encode_base64((Image)),
+ JID = jlib:jid_to_string(From),
+ CID = <<"sha1+", (sha:sha(Image))/binary,
+ "@bob.xmpp.org">>,
+ Data = #xmlel{name = <<"data">>,
+ attrs =
+ [{<<"xmlns">>, ?NS_BOB}, {<<"cid">>, CID},
+ {<<"max-age">>, <<"0">>}, {<<"type">>, Type}],
+ children = [{xmlcdata, B64Image}]},
+ Captcha = #xmlel{name = <<"captcha">>,
+ attrs = [{<<"xmlns">>, ?NS_CAPTCHA}],
+ children =
+ [#xmlel{name = <<"x">>,
+ attrs =
+ [{<<"xmlns">>, ?NS_XDATA},
+ {<<"type">>, <<"form">>}],
+ children =
+ [?VFIELD(<<"hidden">>,
+ <<"FORM_TYPE">>,
+ {xmlcdata, ?NS_CAPTCHA}),
+ ?VFIELD(<<"hidden">>, <<"from">>,
+ {xmlcdata,
+ jlib:jid_to_string(To)}),
+ ?VFIELD(<<"hidden">>,
+ <<"challenge">>,
+ {xmlcdata, Id}),
+ ?VFIELD(<<"hidden">>, <<"sid">>,
+ {xmlcdata, SID}),
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"var">>, <<"ocr">>},
+ {<<"label">>,
+ ?CAPTCHA_TEXT(Lang)}],
+ children =
+ [#xmlel{name =
+ <<"required">>,
+ attrs = [],
+ children = []},
+ #xmlel{name =
+ <<"media">>,
+ attrs =
+ [{<<"xmlns">>,
+ ?NS_MEDIA}],
+ children =
+ [#xmlel{name
+ =
+ <<"uri">>,
+ attrs
+ =
+ [{<<"type">>,
+ Type}],
+ children
+ =
+ [{xmlcdata,
+ <<"cid:",
+ CID/binary>>}]}]}]}]}]},
+ BodyString1 = translate:translate(Lang,
+ <<"Your messages to ~s are being blocked. "
+ "To unblock them, visit ~s">>),
+ BodyString = iolist_to_binary(io_lib:format(BodyString1,
+ [JID, get_url(Id)])),
+ Body = #xmlel{name = <<"body">>, attrs = [],
+ children = [{xmlcdata, BodyString}]},
+ OOB = #xmlel{name = <<"x">>,
+ attrs = [{<<"xmlns">>, ?NS_OOB}],
+ children =
+ [#xmlel{name = <<"url">>, attrs = [],
+ children = [{xmlcdata, get_url(Id)}]}]},
+ Tref = erlang:send_after(?CAPTCHA_LIFETIME, ?MODULE,
+ {remove_id, Id}),
+ ets:insert(captcha,
+ #captcha{id = Id, pid = self(), key = Key, tref = Tref,
+ args = Args}),
+ {ok, Id, [Body, OOB, Captcha, Data]};
+ Err -> Err
end.
+-spec create_captcha_x(binary(), jid(), binary(),
+ any(), [xmlel()]) -> {ok, [xmlel()]} |
+ {error, error()}.
+
create_captcha_x(SID, To, Lang, Limiter, HeadEls) ->
create_captcha_x(SID, To, Lang, Limiter, HeadEls, []).
-create_captcha_x(SID, To, Lang, Limiter, HeadEls, TailEls) ->
+-spec create_captcha_x(binary(), jid(), binary(),
+ any(), [xmlel()], [xmlel()]) -> {ok, [xmlel()]} |
+ {error, error()}.
+
+create_captcha_x(SID, To, Lang, Limiter, HeadEls,
+ TailEls) ->
case create_image(Limiter) of
- {ok, Type, Key, Image} ->
- Id = randoms:get_string(),
- B64Image = jlib:encode_base64(binary_to_list(Image)),
- CID = "sha1+" ++ sha:sha(Image) ++ "@bob.xmpp.org",
- Data = {xmlelement, "data",
- [{"xmlns", ?NS_BOB}, {"cid", CID},
- {"max-age", "0"}, {"type", Type}],
- [{xmlcdata, B64Image}]},
- HelpTxt = translate:translate(
- Lang,
- "If you don't see the CAPTCHA image here, "
- "visit the web page."),
- Imageurl = get_url(Id ++ "/image"),
- Captcha =
- {xmlelement, "x", [{"xmlns", ?NS_XDATA}, {"type", "form"}],
- [?VFIELD("hidden", "FORM_TYPE", {xmlcdata, ?NS_CAPTCHA}) | HeadEls] ++
- [{xmlelement, "field", [{"type", "fixed"}],
- [{xmlelement, "value", [], [{xmlcdata, HelpTxt}]}]},
- {xmlelement, "field", [{"type", "hidden"}, {"var", "captchahidden"}],
- [{xmlelement, "value", [], [{xmlcdata, "workaround-for-psi"}]}]},
- {xmlelement, "field",
- [{"type", "text-single"},
- {"label", translate:translate(Lang, "CAPTCHA web page")},
- {"var", "url"}],
- [{xmlelement, "value", [], [{xmlcdata, Imageurl}]}]},
- ?VFIELD("hidden", "from", {xmlcdata, jlib:jid_to_string(To)}),
- ?VFIELD("hidden", "challenge", {xmlcdata, Id}),
- ?VFIELD("hidden", "sid", {xmlcdata, SID}),
- {xmlelement, "field", [{"var", "ocr"}, {"label", ?CAPTCHA_TEXT(Lang)}],
- [{xmlelement, "required", [], []},
- {xmlelement, "media", [{"xmlns", ?NS_MEDIA}],
- [{xmlelement, "uri", [{"type", Type}],
- [{xmlcdata, "cid:" ++ CID}]}]}]}] ++ TailEls},
- Tref = erlang:send_after(?CAPTCHA_LIFETIME, ?MODULE, {remove_id, Id}),
- case ?T(mnesia:write(#captcha{id=Id, key=Key, tref=Tref})) of
- ok ->
- {ok, [Captcha, Data]};
- Err ->
- {error, Err}
- end;
- Err ->
- Err
+ {ok, Type, Key, Image} ->
+ Id = <<(randoms:get_string())/binary>>,
+ B64Image = jlib:encode_base64((Image)),
+ CID = <<"sha1+", (sha:sha(Image))/binary,
+ "@bob.xmpp.org">>,
+ Data = #xmlel{name = <<"data">>,
+ attrs =
+ [{<<"xmlns">>, ?NS_BOB}, {<<"cid">>, CID},
+ {<<"max-age">>, <<"0">>}, {<<"type">>, Type}],
+ children = [{xmlcdata, B64Image}]},
+ HelpTxt = translate:translate(Lang,
+ <<"If you don't see the CAPTCHA image here, "
+ "visit the web page.">>),
+ Imageurl = get_url(<<Id/binary, "/image">>),
+ Captcha = #xmlel{name = <<"x">>,
+ attrs =
+ [{<<"xmlns">>, ?NS_XDATA},
+ {<<"type">>, <<"form">>}],
+ children =
+ [?VFIELD(<<"hidden">>, <<"FORM_TYPE">>,
+ {xmlcdata, ?NS_CAPTCHA})
+ | HeadEls]
+ ++
+ [#xmlel{name = <<"field">>,
+ attrs = [{<<"type">>, <<"fixed">>}],
+ children =
+ [#xmlel{name = <<"value">>,
+ attrs = [],
+ children =
+ [{xmlcdata,
+ HelpTxt}]}]},
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"type">>, <<"hidden">>},
+ {<<"var">>, <<"captchahidden">>}],
+ children =
+ [#xmlel{name = <<"value">>,
+ attrs = [],
+ children =
+ [{xmlcdata,
+ <<"workaround-for-psi">>}]}]},
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"type">>, <<"text-single">>},
+ {<<"label">>,
+ translate:translate(Lang,
+ <<"CAPTCHA web page">>)},
+ {<<"var">>, <<"url">>}],
+ children =
+ [#xmlel{name = <<"value">>,
+ attrs = [],
+ children =
+ [{xmlcdata,
+ Imageurl}]}]},
+ ?VFIELD(<<"hidden">>, <<"from">>,
+ {xmlcdata, jlib:jid_to_string(To)}),
+ ?VFIELD(<<"hidden">>, <<"challenge">>,
+ {xmlcdata, Id}),
+ ?VFIELD(<<"hidden">>, <<"sid">>,
+ {xmlcdata, SID}),
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"var">>, <<"ocr">>},
+ {<<"label">>,
+ ?CAPTCHA_TEXT(Lang)}],
+ children =
+ [#xmlel{name = <<"required">>,
+ attrs = [], children = []},
+ #xmlel{name = <<"media">>,
+ attrs =
+ [{<<"xmlns">>,
+ ?NS_MEDIA}],
+ children =
+ [#xmlel{name =
+ <<"uri">>,
+ attrs =
+ [{<<"type">>,
+ Type}],
+ children =
+ [{xmlcdata,
+ <<"cid:",
+ CID/binary>>}]}]}]}]
+ ++ TailEls},
+ Tref = erlang:send_after(?CAPTCHA_LIFETIME, ?MODULE,
+ {remove_id, Id}),
+ ets:insert(captcha,
+ #captcha{id = Id, key = Key, tref = Tref}),
+ {ok, [Captcha, Data]};
+ Err -> Err
end.
%% @spec (Id::string(), Lang::string()) -> {FormEl, {ImgEl, TextEl, IdEl, KeyEl}} | captcha_not_found
@@ -171,206 +278,177 @@ create_captcha_x(SID, To, Lang, Limiter, HeadEls, TailEls) ->
%% TextEl = xmlelement()
%% IdEl = xmlelement()
%% KeyEl = xmlelement()
+-spec build_captcha_html(binary(), binary()) -> captcha_not_found |
+ {xmlel(),
+ {xmlel(), xmlel(),
+ xmlel(), xmlel()}}.
+
build_captcha_html(Id, Lang) ->
- case mnesia:dirty_read(captcha, Id) of
- [#captcha{}] ->
- ImgEl = {xmlelement, "img", [{"src", get_url(Id ++ "/image")}], []},
- TextEl = {xmlcdata, ?CAPTCHA_TEXT(Lang)},
- IdEl = {xmlelement, "input", [{"type", "hidden"},
- {"name", "id"},
- {"value", Id}], []},
- KeyEl = {xmlelement, "input", [{"type", "text"},
- {"name", "key"},
- {"size", "10"}], []},
- FormEl = {xmlelement, "form", [{"action", get_url(Id)},
- {"name", "captcha"},
- {"method", "POST"}],
- [ImgEl,
- {xmlelement, "br", [], []},
- TextEl,
- {xmlelement, "br", [], []},
- IdEl,
- KeyEl,
- {xmlelement, "br", [], []},
- {xmlelement, "input", [{"type", "submit"},
- {"name", "enter"},
- {"value", "OK"}], []}
- ]},
- {FormEl, {ImgEl, TextEl, IdEl, KeyEl}};
- _ ->
- captcha_not_found
+ case lookup_captcha(Id) of
+ {ok, _} ->
+ ImgEl = #xmlel{name = <<"img">>,
+ attrs =
+ [{<<"src">>, get_url(<<Id/binary, "/image">>)}],
+ children = []},
+ TextEl = {xmlcdata, ?CAPTCHA_TEXT(Lang)},
+ IdEl = #xmlel{name = <<"input">>,
+ attrs =
+ [{<<"type">>, <<"hidden">>}, {<<"name">>, <<"id">>},
+ {<<"value">>, Id}],
+ children = []},
+ KeyEl = #xmlel{name = <<"input">>,
+ attrs =
+ [{<<"type">>, <<"text">>}, {<<"name">>, <<"key">>},
+ {<<"size">>, <<"10">>}],
+ children = []},
+ FormEl = #xmlel{name = <<"form">>,
+ attrs =
+ [{<<"action">>, get_url(Id)},
+ {<<"name">>, <<"captcha">>},
+ {<<"method">>, <<"POST">>}],
+ children =
+ [ImgEl,
+ #xmlel{name = <<"br">>, attrs = [],
+ children = []},
+ TextEl,
+ #xmlel{name = <<"br">>, attrs = [],
+ children = []},
+ IdEl, KeyEl,
+ #xmlel{name = <<"br">>, attrs = [],
+ children = []},
+ #xmlel{name = <<"input">>,
+ attrs =
+ [{<<"type">>, <<"submit">>},
+ {<<"name">>, <<"enter">>},
+ {<<"value">>, <<"OK">>}],
+ children = []}]},
+ {FormEl, {ImgEl, TextEl, IdEl, KeyEl}};
+ _ -> captcha_not_found
end.
%% @spec (Id::string(), ProvidedKey::string()) -> captcha_valid | captcha_non_valid | captcha_not_found
-check_captcha(Id, ProvidedKey) ->
- ?T(case mnesia:read(captcha, Id, write) of
- [#captcha{pid=Pid, args=Args, key=StoredKey, tref=Tref}] ->
- mnesia:delete({captcha, Id}),
- erlang:cancel_timer(Tref),
- if StoredKey == ProvidedKey ->
- if is_pid(Pid) ->
- Pid ! {captcha_succeed, Args};
- true ->
- ok
- end,
- captcha_valid;
- true ->
- if is_pid(Pid) ->
- Pid ! {captcha_failed, Args};
- true ->
- ok
- end,
- captcha_non_valid
- end;
- _ ->
- captcha_not_found
- end).
-
-
-process_reply({xmlelement, _, _, _} = El) ->
- case xml:get_subtag(El, "x") of
- false ->
- {error, malformed};
- Xdata ->
- Fields = jlib:parse_xdata_submit(Xdata),
- case catch {proplists:get_value("challenge", Fields),
- proplists:get_value("ocr", Fields)} of
- {[Id|_], [OCR|_]} ->
- ?T(case mnesia:read(captcha, Id, write) of
- [#captcha{pid=Pid, args=Args, key=Key, tref=Tref}] ->
- mnesia:delete({captcha, Id}),
- erlang:cancel_timer(Tref),
- if OCR == Key ->
- if is_pid(Pid) ->
- Pid ! {captcha_succeed, Args};
- true ->
- ok
- end,
- ok;
- true ->
- if is_pid(Pid) ->
- Pid ! {captcha_failed, Args};
- true ->
- ok
- end,
- {error, bad_match}
- end;
- _ ->
- {error, not_found}
- end);
- _ ->
- {error, malformed}
- end
+-spec check_captcha(binary(), binary()) -> captcha_not_found |
+ captcha_valid |
+ captcha_non_valid.
+
+
+-spec process_reply(xmlel()) -> ok | {error, bad_match | not_found | malformed}.
+
+process_reply(#xmlel{} = El) ->
+ case xml:get_subtag(El, <<"x">>) of
+ false -> {error, malformed};
+ Xdata ->
+ Fields = jlib:parse_xdata_submit(Xdata),
+ case catch {proplists:get_value(<<"challenge">>,
+ Fields),
+ proplists:get_value(<<"ocr">>, Fields)}
+ of
+ {[Id | _], [OCR | _]} ->
+ case check_captcha(Id, OCR) of
+ captcha_valid -> ok;
+ captcha_non_valid -> {error, bad_match};
+ captcha_not_found -> {error, not_found}
+ end;
+ _ -> {error, malformed}
+ end
end;
-process_reply(_) ->
- {error, malformed}.
-
+process_reply(_) -> {error, malformed}.
-process(_Handlers, #request{method='GET', lang=Lang, path=[_, Id]}) ->
+process(_Handlers,
+ #request{method = 'GET', lang = Lang,
+ path = [_, Id]}) ->
case build_captcha_html(Id, Lang) of
- {FormEl, _} when is_tuple(FormEl) ->
- Form =
- {xmlelement, "div", [{"align", "center"}],
- [FormEl]},
- ejabberd_web:make_xhtml([Form]);
- captcha_not_found ->
- ejabberd_web:error(not_found)
+ {FormEl, _} when is_tuple(FormEl) ->
+ Form = #xmlel{name = <<"div">>,
+ attrs = [{<<"align">>, <<"center">>}],
+ children = [FormEl]},
+ ejabberd_web:make_xhtml([Form]);
+ captcha_not_found -> ejabberd_web:error(not_found)
end;
-
-process(_Handlers, #request{method='GET', path=[_, Id, "image"], ip = IP}) ->
+process(_Handlers,
+ #request{method = 'GET', path = [_, Id, <<"image">>],
+ ip = IP}) ->
{Addr, _Port} = IP,
- case mnesia:dirty_read(captcha, Id) of
- [#captcha{key=Key}] ->
- case create_image(Addr, Key) of
- {ok, Type, _, Img} ->
- {200,
- [{"Content-Type", Type},
- {"Cache-Control", "no-cache"},
- {"Last-Modified", httpd_util:rfc1123_date()}],
- Img};
- {error, limit} ->
- ejabberd_web:error(not_allowed);
- _ ->
- ejabberd_web:error(not_found)
- end;
- _ ->
- ejabberd_web:error(not_found)
+ case lookup_captcha(Id) of
+ {ok, #captcha{key = Key}} ->
+ case create_image(Addr, Key) of
+ {ok, Type, _, Img} ->
+ {200,
+ [{<<"Content-Type">>, Type},
+ {<<"Cache-Control">>, <<"no-cache">>},
+ {<<"Last-Modified">>, list_to_binary(httpd_util:rfc1123_date())}],
+ Img};
+ {error, limit} -> ejabberd_web:error(not_allowed);
+ _ -> ejabberd_web:error(not_found)
+ end;
+ _ -> ejabberd_web:error(not_found)
end;
-
-process(_Handlers, #request{method='POST', q=Q, lang=Lang, path=[_, Id]}) ->
- ProvidedKey = proplists:get_value("key", Q, none),
+process(_Handlers,
+ #request{method = 'POST', q = Q, lang = Lang,
+ path = [_, Id]}) ->
+ ProvidedKey = proplists:get_value(<<"key">>, Q, none),
case check_captcha(Id, ProvidedKey) of
- captcha_valid ->
- Form =
- {xmlelement, "p", [],
- [{xmlcdata,
- translate:translate(Lang, "The CAPTCHA is valid.")
- }]},
- ejabberd_web:make_xhtml([Form]);
- captcha_non_valid ->
- ejabberd_web:error(not_allowed);
- captcha_not_found ->
- ejabberd_web:error(not_found)
+ captcha_valid ->
+ Form = #xmlel{name = <<"p">>, attrs = [],
+ children =
+ [{xmlcdata,
+ translate:translate(Lang,
+ <<"The CAPTCHA is valid.">>)}]},
+ ejabberd_web:make_xhtml([Form]);
+ captcha_non_valid -> ejabberd_web:error(not_allowed);
+ captcha_not_found -> ejabberd_web:error(not_found)
end;
-
process(_Handlers, _Request) ->
ejabberd_web:error(not_found).
-
%%====================================================================
%% gen_server callbacks
%%====================================================================
init([]) ->
- mnesia:create_table(captcha,
- [{ram_copies, [node()]},
- {attributes, record_info(fields, captcha)}]),
- mnesia:add_table_copy(captcha, node(), ram_copies),
+ mnesia:delete_table(captcha),
+ ets:new(captcha,
+ [named_table, public, {keypos, #captcha.id}]),
check_captcha_setup(),
{ok, #state{}}.
-handle_call({is_limited, Limiter, RateLimit}, _From, State) ->
+handle_call({is_limited, Limiter, RateLimit}, _From,
+ State) ->
NowPriority = now_priority(),
- CleanPriority = NowPriority + ?LIMIT_PERIOD,
+ CleanPriority = NowPriority + (?LIMIT_PERIOD),
Limits = clean_treap(State#state.limits, CleanPriority),
case treap:lookup(Limiter, Limits) of
- {ok, _, Rate} when Rate >= RateLimit ->
- {reply, true, State#state{limits = Limits}};
- {ok, Priority, Rate} ->
- NewLimits = treap:insert(Limiter, Priority, Rate+1, Limits),
- {reply, false, State#state{limits = NewLimits}};
- _ ->
- NewLimits = treap:insert(Limiter, NowPriority, 1, Limits),
- {reply, false, State#state{limits = NewLimits}}
+ {ok, _, Rate} when Rate >= RateLimit ->
+ {reply, true, State#state{limits = Limits}};
+ {ok, Priority, Rate} ->
+ NewLimits = treap:insert(Limiter, Priority, Rate + 1,
+ Limits),
+ {reply, false, State#state{limits = NewLimits}};
+ _ ->
+ NewLimits = treap:insert(Limiter, NowPriority, 1,
+ Limits),
+ {reply, false, State#state{limits = NewLimits}}
end;
handle_call(_Request, _From, State) ->
{reply, bad_request, State}.
-handle_cast(_Msg, State) ->
- {noreply, State}.
+handle_cast(_Msg, State) -> {noreply, State}.
handle_info({remove_id, Id}, State) ->
?DEBUG("captcha ~p timed out", [Id]),
- _ = ?T(case mnesia:read(captcha, Id, write) of
- [#captcha{args=Args, pid=Pid}] ->
- if is_pid(Pid) ->
- Pid ! {captcha_failed, Args};
- true ->
- ok
- end,
- mnesia:delete({captcha, Id});
- _ ->
- ok
- end),
+ case ets:lookup(captcha, Id) of
+ [#captcha{args = Args, pid = Pid}] ->
+ if is_pid(Pid) -> Pid ! {captcha_failed, Args};
+ true -> ok
+ end,
+ ets:delete(captcha, Id);
+ _ -> ok
+ end,
{noreply, State};
+handle_info(_Info, State) -> {noreply, State}.
-handle_info(_Info, State) ->
- {noreply, State}.
-
-terminate(_Reason, _State) ->
- ok.
+terminate(_Reason, _State) -> ok.
-code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
+code_change(_OldVsn, State, _Extra) -> {ok, State}.
%%--------------------------------------------------------------------
%%% Internal functions
@@ -382,126 +460,136 @@ code_change(_OldVsn, State, _Extra) ->
%% Image = binary()
%% Reason = atom()
%%--------------------------------------------------------------------
-create_image() ->
- create_image(undefined).
+create_image() -> create_image(undefined).
create_image(Limiter) ->
- %% Six numbers from 1 to 9.
- Key = string:substr(randoms:get_string(), 1, 6),
+ Key = str:substr(randoms:get_string(), 1, 6),
create_image(Limiter, Key).
create_image(Limiter, Key) ->
case is_limited(Limiter) of
- true ->
- {error, limit};
- false ->
- do_create_image(Key)
+ true -> {error, limit};
+ false -> do_create_image(Key)
end.
do_create_image(Key) ->
FileName = get_prog_name(),
Cmd = lists:flatten(io_lib:format("~s ~s", [FileName, Key])),
case cmd(Cmd) of
- {ok, <<16#89, $P, $N, $G, $\r, $\n, 16#1a, $\n, _/binary>> = Img} ->
- {ok, "image/png", Key, Img};
- {ok, <<16#ff, 16#d8, _/binary>> = Img} ->
- {ok, "image/jpeg", Key, Img};
- {ok, <<$G, $I, $F, $8, X, $a, _/binary>> = Img} when X==$7; X==$9 ->
- {ok, "image/gif", Key, Img};
- {error, enodata = Reason} ->
- ?ERROR_MSG("Failed to process output from \"~s\". "
- "Maybe ImageMagick's Convert program is not installed.",
- [Cmd]),
- {error, Reason};
- {error, Reason} ->
- ?ERROR_MSG("Failed to process an output from \"~s\": ~p",
- [Cmd, Reason]),
- {error, Reason};
- _ ->
- Reason = malformed_image,
- ?ERROR_MSG("Failed to process an output from \"~s\": ~p",
- [Cmd, Reason]),
- {error, Reason}
+ {ok,
+ <<137, $P, $N, $G, $\r, $\n, 26, $\n, _/binary>> =
+ Img} ->
+ {ok, <<"image/png">>, Key, Img};
+ {ok, <<255, 216, _/binary>> = Img} ->
+ {ok, <<"image/jpeg">>, Key, Img};
+ {ok, <<$G, $I, $F, $8, X, $a, _/binary>> = Img}
+ when X == $7; X == $9 ->
+ {ok, <<"image/gif">>, Key, Img};
+ {error, enodata = Reason} ->
+ ?ERROR_MSG("Failed to process output from \"~s\". "
+ "Maybe ImageMagick's Convert program "
+ "is not installed.",
+ [Cmd]),
+ {error, Reason};
+ {error, Reason} ->
+ ?ERROR_MSG("Failed to process an output from \"~s\": ~p",
+ [Cmd, Reason]),
+ {error, Reason};
+ _ ->
+ Reason = malformed_image,
+ ?ERROR_MSG("Failed to process an output from \"~s\": ~p",
+ [Cmd, Reason]),
+ {error, Reason}
end.
get_prog_name() ->
- case ejabberd_config:get_local_option(captcha_cmd) of
- FileName when is_list(FileName) ->
- FileName;
- Value when (Value == undefined) or (Value == "") ->
- ?DEBUG("The option captcha_cmd is not configured, but some "
- "module wants to use the CAPTCHA feature.", []),
- false
+ case ejabberd_config:get_local_option(
+ captcha_cmd,
+ fun(FileName) ->
+ F = iolist_to_binary(FileName),
+ if F /= <<"">> -> F end
+ end) of
+ undefined ->
+ ?DEBUG("The option captcha_cmd is not configured, "
+ "but some module wants to use the CAPTCHA "
+ "feature.",
+ []),
+ false;
+ FileName ->
+ FileName
end.
get_url(Str) ->
- CaptchaHost = ejabberd_config:get_local_option(captcha_host),
- case string:tokens(CaptchaHost, ":") of
- [Host] ->
- "http://" ++ Host ++ "/captcha/" ++ Str;
- ["http"++_ = TransferProt, Host] ->
- TransferProt ++ ":" ++ Host ++ "/captcha/" ++ Str;
- [Host, PortString] ->
- TransferProt = atom_to_list(get_transfer_protocol(PortString)),
- TransferProt ++ "://" ++ Host ++ ":" ++ PortString ++ "/captcha/" ++ Str;
- [TransferProt, Host, PortString] ->
- TransferProt ++ ":" ++ Host ++ ":" ++ PortString ++ "/captcha/" ++ Str;
- _ ->
- "http://" ++ ?MYNAME ++ "/captcha/" ++ Str
+ CaptchaHost = ejabberd_config:get_local_option(
+ captcha_host,
+ fun iolist_to_binary/1,
+ <<"">>),
+ case str:tokens(CaptchaHost, <<":">>) of
+ [Host] ->
+ <<"http://", Host/binary, "/captcha/", Str/binary>>;
+ [<<"http", _/binary>> = TransferProt, Host] ->
+ <<TransferProt/binary, ":", Host/binary, "/captcha/",
+ Str/binary>>;
+ [Host, PortString] ->
+ TransferProt =
+ iolist_to_binary(atom_to_list(get_transfer_protocol(PortString))),
+ <<TransferProt/binary, "://", Host/binary, ":",
+ PortString/binary, "/captcha/", Str/binary>>;
+ [TransferProt, Host, PortString] ->
+ <<TransferProt/binary, ":", Host/binary, ":",
+ PortString/binary, "/captcha/", Str/binary>>;
+ _ ->
+ <<"http://", (?MYNAME)/binary, "/captcha/", Str/binary>>
end.
get_transfer_protocol(PortString) ->
- PortNumber = list_to_integer(PortString),
+ PortNumber = jlib:binary_to_integer(PortString),
PortListeners = get_port_listeners(PortNumber),
get_captcha_transfer_protocol(PortListeners).
get_port_listeners(PortNumber) ->
- AllListeners = ejabberd_config:get_local_option(listen),
- lists:filter(
- fun({{Port, _Ip, _Netp}, _Module1, _Opts1}) when Port == PortNumber ->
- true;
- (_) ->
- false
- end,
- AllListeners).
+ AllListeners = ejabberd_config:get_local_option(listen, fun(V) -> V end),
+ lists:filter(fun ({{Port, _Ip, _Netp}, _Module1,
+ _Opts1})
+ when Port == PortNumber ->
+ true;
+ (_) -> false
+ end,
+ AllListeners).
get_captcha_transfer_protocol([]) ->
- throw("The port number mentioned in captcha_host is not "
- "a ejabberd_http listener with 'captcha' option. "
- "Change the port number or specify http:// in that option.");
-get_captcha_transfer_protocol([{{_Port, _Ip, tcp}, ejabberd_http, Opts}
+ throw(<<"The port number mentioned in captcha_host "
+ "is not a ejabberd_http listener with "
+ "'captcha' option. Change the port number "
+ "or specify http:// in that option.">>);
+get_captcha_transfer_protocol([{{_Port, _Ip, tcp},
+ ejabberd_http, Opts}
| Listeners]) ->
case lists:member(captcha, Opts) of
- true ->
- case lists:member(tls, Opts) of
- true ->
- https;
- false ->
- http
- end;
- false ->
- get_captcha_transfer_protocol(Listeners)
+ true ->
+ case lists:member(tls, Opts) of
+ true -> https;
+ false -> http
+ end;
+ false -> get_captcha_transfer_protocol(Listeners)
end;
get_captcha_transfer_protocol([_ | Listeners]) ->
get_captcha_transfer_protocol(Listeners).
-is_limited(undefined) ->
- false;
+is_limited(undefined) -> false;
is_limited(Limiter) ->
- case ejabberd_config:get_local_option(captcha_limit) of
- Int when is_integer(Int), Int > 0 ->
- case catch gen_server:call(?MODULE, {is_limited, Limiter, Int},
- 5000) of
- true ->
- true;
- false ->
- false;
- Err ->
- ?ERROR_MSG("Call failed: ~p", [Err]),
- false
- end;
- _ ->
- false
+ case ejabberd_config:get_local_option(
+ captcha_limit,
+ fun(I) when is_integer(I), I > 0 -> I end) of
+ undefined -> false;
+ Int ->
+ case catch gen_server:call(?MODULE,
+ {is_limited, Limiter, Int}, 5000)
+ of
+ true -> true;
+ false -> false;
+ Err -> ?ERROR_MSG("Call failed: ~p", [Err]), false
+ end
end.
%%--------------------------------------------------------------------
@@ -511,82 +599,97 @@ is_limited(Limiter) ->
%% Description: os:cmd/1 replacement
%%--------------------------------------------------------------------
-define(CMD_TIMEOUT, 5000).
--define(MAX_FILE_SIZE, 64*1024).
+
+-define(MAX_FILE_SIZE, 64 * 1024).
cmd(Cmd) ->
Port = open_port({spawn, Cmd}, [stream, eof, binary]),
- TRef = erlang:start_timer(?CMD_TIMEOUT, self(), timeout),
+ TRef = erlang:start_timer(?CMD_TIMEOUT, self(),
+ timeout),
recv_data(Port, TRef, <<>>).
recv_data(Port, TRef, Buf) ->
receive
- {Port, {data, Bytes}} ->
- NewBuf = <<Buf/binary, Bytes/binary>>,
- if size(NewBuf) > ?MAX_FILE_SIZE ->
- return(Port, TRef, {error, efbig});
- true ->
- recv_data(Port, TRef, NewBuf)
- end;
- {Port, {data, _}} ->
- return(Port, TRef, {error, efbig});
- {Port, eof} when Buf /= <<>> ->
- return(Port, TRef, {ok, Buf});
- {Port, eof} ->
- return(Port, TRef, {error, enodata});
- {timeout, TRef, _} ->
- return(Port, TRef, {error, timeout})
+ {Port, {data, Bytes}} ->
+ NewBuf = <<Buf/binary, Bytes/binary>>,
+ if byte_size(NewBuf) > (?MAX_FILE_SIZE) ->
+ return(Port, TRef, {error, efbig});
+ true -> recv_data(Port, TRef, NewBuf)
+ end;
+ {Port, {data, _}} -> return(Port, TRef, {error, efbig});
+ {Port, eof} when Buf /= <<>> ->
+ return(Port, TRef, {ok, Buf});
+ {Port, eof} -> return(Port, TRef, {error, enodata});
+ {timeout, TRef, _} ->
+ return(Port, TRef, {error, timeout})
end.
return(Port, TRef, Result) ->
case erlang:cancel_timer(TRef) of
- false ->
- receive
- {timeout, TRef, _} ->
- ok
- after 0 ->
- ok
- end;
- _ ->
- ok
+ false ->
+ receive {timeout, TRef, _} -> ok after 0 -> ok end;
+ _ -> ok
end,
catch port_close(Port),
Result.
is_feature_available() ->
case get_prog_name() of
- Prog when is_list(Prog) -> true;
- false -> false
+ Prog when is_binary(Prog) -> true;
+ false -> false
end.
check_captcha_setup() ->
case is_feature_available() of
- true ->
- case create_image() of
- {ok, _, _, _} ->
- ok;
- _Err ->
- ?CRITICAL_MSG("Captcha is enabled in the option captcha_cmd, "
- "but it can't generate images.", []),
- throw({error, captcha_cmd_enabled_but_fails})
- end;
- false ->
- ok
+ true ->
+ case create_image() of
+ {ok, _, _, _} -> ok;
+ _Err ->
+ ?CRITICAL_MSG("Captcha is enabled in the option captcha_cmd, "
+ "but it can't generate images.",
+ []),
+ throw({error, captcha_cmd_enabled_but_fails})
+ end;
+ false -> ok
+ end.
+
+lookup_captcha(Id) ->
+ case ets:lookup(captcha, Id) of
+ [C] -> {ok, C};
+ _ -> {error, enoent}
+ end.
+
+check_captcha(Id, ProvidedKey) ->
+ case ets:lookup(captcha, Id) of
+ [#captcha{pid = Pid, args = Args, key = ValidKey,
+ tref = Tref}] ->
+ ets:delete(captcha, Id),
+ erlang:cancel_timer(Tref),
+ if ValidKey == ProvidedKey ->
+ if is_pid(Pid) -> Pid ! {captcha_succeed, Args};
+ true -> ok
+ end,
+ captcha_valid;
+ true ->
+ if is_pid(Pid) -> Pid ! {captcha_failed, Args};
+ true -> ok
+ end,
+ captcha_non_valid
+ end;
+ _ -> captcha_not_found
end.
clean_treap(Treap, CleanPriority) ->
case treap:is_empty(Treap) of
- true ->
- Treap;
- false ->
- {_Key, Priority, _Value} = treap:get_root(Treap),
- if
- Priority > CleanPriority ->
- clean_treap(treap:delete_root(Treap), CleanPriority);
- true ->
- Treap
- end
+ true -> Treap;
+ false ->
+ {_Key, Priority, _Value} = treap:get_root(Treap),
+ if Priority > CleanPriority ->
+ clean_treap(treap:delete_root(Treap), CleanPriority);
+ true -> Treap
+ end
end.
now_priority() ->
{MSec, Sec, USec} = now(),
- -((MSec*1000000 + Sec)*1000000 + USec).
+ -((MSec * 1000000 + Sec) * 1000000 + USec).
diff --git a/src/ejabberd_check.erl b/src/ejabberd_check.erl
index ddb2cbdb0..352251806 100644
--- a/src/ejabberd_check.erl
+++ b/src/ejabberd_check.erl
@@ -31,8 +31,6 @@
-include("ejabberd.hrl").
-include("ejabberd_config.hrl").
--compile([export_all]).
-
%% TODO:
%% We want to implement library checking at launch time to issue
%% human readable user messages.
@@ -87,7 +85,7 @@ get_db_used() ->
fun([Domain, DB], Acc) ->
case check_odbc_option(
ejabberd_config:get_local_option(
- {auth_method, Domain})) of
+ {auth_method, Domain}, fun(V) -> V end)) of
true -> [get_db_type(DB)|Acc];
_ -> Acc
end
diff --git a/src/ejabberd_commands.erl b/src/ejabberd_commands.erl
index b61ef46de..3b8abf97b 100644
--- a/src/ejabberd_commands.erl
+++ b/src/ejabberd_commands.erl
@@ -228,7 +228,8 @@ init() ->
ets:new(ejabberd_commands, [named_table, set, public,
{keypos, #ejabberd_commands.name}]).
-%% @spec ([ejabberd_commands()]) -> ok
+-spec register_commands([ejabberd_commands()]) -> ok.
+
%% @doc Register ejabberd commands.
%% If a command is already registered, a warning is printed and the old command is preserved.
register_commands(Commands) ->
@@ -243,7 +244,8 @@ register_commands(Commands) ->
end,
Commands).
-%% @spec ([ejabberd_commands()]) -> ok
+-spec unregister_commands([ejabberd_commands()]) -> ok.
+
%% @doc Unregister ejabberd commands.
unregister_commands(Commands) ->
lists:foreach(
@@ -252,7 +254,8 @@ unregister_commands(Commands) ->
end,
Commands).
-%% @spec () -> [{Name::atom(), Args::[aterm()], Desc::string()}]
+-spec list_commands() -> [{atom(), [aterm()], string()}].
+
%% @doc Get a list of all the available commands, arguments and description.
list_commands() ->
Commands = ets:match(ejabberd_commands,
@@ -262,7 +265,8 @@ list_commands() ->
_ = '_'}),
[{A, B, C} || [A, B, C] <- Commands].
-%% @spec (Name::atom()) -> {Args::[aterm()], Result::rterm()} | {error, command_unknown}
+-spec get_command_format(atom()) -> {[aterm()], rterm()} | {error, command_unknown}.
+
%% @doc Get the format of arguments and result of a command.
get_command_format(Name) ->
Matched = ets:match(ejabberd_commands,
@@ -277,7 +281,8 @@ get_command_format(Name) ->
{Args, Result}
end.
-%% @spec (Name::atom()) -> ejabberd_commands() | command_not_found
+-spec get_command_definition(atom()) -> ejabberd_commands() | command_not_found.
+
%% @doc Get the definition record of a command.
get_command_definition(Name) ->
case ets:lookup(ejabberd_commands, Name) of
@@ -314,6 +319,8 @@ execute_command2(Command, Arguments) ->
?DEBUG("Executing command ~p:~p with Args=~p", [Module, Function, Arguments]),
apply(Module, Function, Arguments).
+-spec get_tags_commands() -> [{string(), [string()]}].
+
%% @spec () -> [{Tag::string(), [CommandName::string()]}]
%% @doc Get all the tags and associated commands.
get_tags_commands() ->
@@ -377,6 +384,9 @@ check_access_commands(AccessCommands, Auth, Method, Command, Arguments) ->
L when is_list(L) -> ok
end.
+-spec check_auth(noauth) -> noauth_provided;
+ ({binary(), binary(), binary()}) -> {ok, binary(), binary()}.
+
check_auth(noauth) ->
no_auth_provided;
check_auth({User, Server, Password}) ->
@@ -391,7 +401,7 @@ check_access(all, _) ->
check_access(Access, Auth) ->
{ok, User, Server} = check_auth(Auth),
%% Check this user has access permission
- case acl:match_rule(Server, Access, jlib:make_jid(User, Server, "")) of
+ case acl:match_rule(Server, Access, jlib:make_jid(User, Server, <<"">>)) of
allow -> true;
deny -> false
end.
diff --git a/src/ejabberd_commands.hrl b/src/ejabberd_commands.hrl
index 1ababc8be..116bb7357 100644
--- a/src/ejabberd_commands.hrl
+++ b/src/ejabberd_commands.hrl
@@ -19,10 +19,32 @@
%%%
%%%----------------------------------------------------------------------
--record(ejabberd_commands, {name, tags = [],
- desc = "", longdesc = "",
- module, function,
- args = [], result = rescode}).
+-type aterm() :: {atom(), atype()}.
+-type atype() :: integer | string | binary |
+ {tuple, [aterm()]} | {list, aterm()}.
+-type rterm() :: {atom(), rtype()}.
+-type rtype() :: integer | string | atom |
+ {tuple, [rterm()]} | {list, rterm()} |
+ rescode | restuple.
+
+-record(ejabberd_commands,
+ {name :: atom(),
+ tags = [] :: [atom()] | '_' | '$2',
+ desc = "" :: string() | '_' | '$3',
+ longdesc = "" :: string() | '_',
+ module :: atom(),
+ function :: atom(),
+ args = [] :: [aterm()] | '_' | '$1' | '$2',
+ result = {res, rescode} :: rterm() | '_' | '$2'}).
+
+-type ejabberd_commands() :: #ejabberd_commands{name :: atom(),
+ tags :: [atom()],
+ desc :: string(),
+ longdesc :: string(),
+ module :: atom(),
+ function :: atom(),
+ args :: [aterm()],
+ result :: rterm()}.
%% @type ejabberd_commands() = #ejabberd_commands{
%% name = atom(),
@@ -50,3 +72,4 @@
%% @type rterm() = {Name::atom(), Type::rtype()}.
%% A result term is a tuple with the term name and the term type.
+
diff --git a/src/ejabberd_config.erl b/src/ejabberd_config.erl
index e2a8633ee..0a4208b7f 100644
--- a/src/ejabberd_config.erl
+++ b/src/ejabberd_config.erl
@@ -29,9 +29,13 @@
-export([start/0, load_file/1,
add_global_option/2, add_local_option/2,
- get_global_option/1, get_local_option/1]).
+ get_global_option/2, get_local_option/2,
+ get_global_option/3, get_local_option/3]).
-export([get_vh_by_auth_method/1]).
-export([is_file_readable/1]).
+-export([get_version/0, get_myhosts/0, get_mylang/0]).
+-export([prepare_opt_val/4]).
+-export([convert_table_to_binary/5]).
-include("ejabberd.hrl").
-include("ejabberd_config.hrl").
@@ -96,11 +100,15 @@ load_file(File) ->
%% in which the options 'include_config_file' were parsed
%% and the terms in those files were included.
%% @spec(string()) -> [term()]
+%% @spec(iolist()) -> [term()]
+get_plain_terms_file(File) when is_binary(File) ->
+ get_plain_terms_file(binary_to_list(File));
get_plain_terms_file(File1) ->
File = get_absolute_path(File1),
case file:consult(File) of
{ok, Terms} ->
- include_config_files(Terms);
+ BinTerms = strings_to_binary(Terms),
+ include_config_files(BinTerms);
{error, {LineNumber, erl_parse, _ParseMessage} = Reason} ->
ExitText = describe_config_problem(File, Reason, LineNumber),
?ERROR_MSG(ExitText, []),
@@ -159,7 +167,7 @@ normalize_hosts(Hosts) ->
normalize_hosts([], PrepHosts) ->
lists:reverse(PrepHosts);
normalize_hosts([Host|Hosts], PrepHosts) ->
- case jlib:nodeprep(Host) of
+ case jlib:nodeprep(iolist_to_binary(Host)) of
error ->
?ERROR_MSG("Can't load config file: "
"invalid host name [~p]", [Host]),
@@ -564,7 +572,6 @@ set_opts(State) ->
exit("Error reading Mnesia database")
end.
-
add_global_option(Opt, Val) ->
mnesia:transaction(fun() ->
mnesia:write(#config{key = Opt,
@@ -577,23 +584,63 @@ add_local_option(Opt, Val) ->
value = Val})
end).
+-spec prepare_opt_val(any(), any(), check_fun(), any()) -> any().
+
+prepare_opt_val(Opt, Val, F, Default) ->
+ Res = case F of
+ {Mod, Fun} ->
+ catch Mod:Fun(Val);
+ _ ->
+ catch F(Val)
+ end,
+ case Res of
+ {'EXIT', _} ->
+ ?INFO_MSG("Configuration problem:~n"
+ "** Option: ~s~n"
+ "** Invalid value: ~s~n"
+ "** Using as fallback: ~s",
+ [format_term(Opt),
+ format_term(Val),
+ format_term(Default)]),
+ Default;
+ _ ->
+ Res
+ end.
+
+-type check_fun() :: fun((any()) -> any()) | {module(), atom()}.
+
+-spec get_global_option(any(), check_fun()) -> any().
+
+get_global_option(Opt, F) ->
+ get_global_option(Opt, F, undefined).
+
+-spec get_global_option(any(), check_fun(), any()) -> any().
-get_global_option(Opt) ->
+get_global_option(Opt, F, Default) ->
case ets:lookup(config, Opt) of
[#config{value = Val}] ->
- Val;
+ prepare_opt_val(Opt, Val, F, Default);
_ ->
- undefined
+ Default
end.
-get_local_option(Opt) ->
+-spec get_local_option(any(), check_fun()) -> any().
+
+get_local_option(Opt, F) ->
+ get_local_option(Opt, F, undefined).
+
+-spec get_local_option(any(), check_fun(), any()) -> any().
+
+get_local_option(Opt, F, Default) ->
case ets:lookup(local_config, Opt) of
[#local_config{value = Val}] ->
- Val;
+ prepare_opt_val(Opt, Val, F, Default);
_ ->
- undefined
+ Default
end.
+-spec get_vh_by_auth_method(atom()) -> [binary()].
+
%% Return the list of hosts handled by a given module
get_vh_by_auth_method(AuthMethod) ->
mnesia:dirty_select(local_config,
@@ -613,8 +660,25 @@ is_file_readable(Path) ->
false
end.
+get_version() ->
+ list_to_binary(element(2, application:get_key(ejabberd, vsn))).
+
+-spec get_myhosts() -> [binary()].
+
+get_myhosts() ->
+ ejabberd_config:get_global_option(hosts, fun(V) -> V end).
+
+-spec get_mylang() -> binary().
+
+get_mylang() ->
+ ejabberd_config:get_global_option(
+ language,
+ fun iolist_to_binary/1,
+ <<"en">>).
+
replace_module(mod_announce_odbc) -> {mod_announce, odbc};
replace_module(mod_blocking_odbc) -> {mod_blocking, odbc};
+replace_module(mod_caps_odbc) -> {mod_caps, odbc};
replace_module(mod_irc_odbc) -> {mod_irc, odbc};
replace_module(mod_last_odbc) -> {mod_last, odbc};
replace_module(mod_muc_odbc) -> {mod_muc, odbc};
@@ -632,10 +696,161 @@ replace_modules(Modules) ->
fun({Module, Opts}) ->
case replace_module(Module) of
{NewModule, DBType} ->
+ emit_deprecation_warning(Module, NewModule, DBType),
NewOpts = [{db_type, DBType} |
lists:keydelete(db_type, 1, Opts)],
{NewModule, NewOpts};
NewModule ->
+ if Module /= NewModule ->
+ emit_deprecation_warning(Module, NewModule);
+ true ->
+ ok
+ end,
{NewModule, Opts}
end
end, Modules).
+
+strings_to_binary([]) ->
+ [];
+strings_to_binary(L) when is_list(L) ->
+ case is_string(L) of
+ true ->
+ list_to_binary(L);
+ false ->
+ strings_to_binary1(L)
+ end;
+strings_to_binary(T) when is_tuple(T) ->
+ list_to_tuple(strings_to_binary(tuple_to_list(T)));
+strings_to_binary(X) ->
+ X.
+
+strings_to_binary1([El|L]) ->
+ [strings_to_binary(El)|strings_to_binary1(L)];
+strings_to_binary1([]) ->
+ [];
+strings_to_binary1(T) ->
+ T.
+
+is_string([C|T]) when (C >= 0) and (C =< 255) ->
+ is_string(T);
+is_string([]) ->
+ true;
+is_string(_) ->
+ false.
+
+binary_to_strings(B) when is_binary(B) ->
+ binary_to_list(B);
+binary_to_strings([H|T]) ->
+ [binary_to_strings(H)|binary_to_strings(T)];
+binary_to_strings(T) when is_tuple(T) ->
+ list_to_tuple(binary_to_strings(tuple_to_list(T)));
+binary_to_strings(T) ->
+ T.
+
+format_term(Bin) when is_binary(Bin) ->
+ io_lib:format("\"~s\"", [Bin]);
+format_term(S) when is_list(S), S /= [] ->
+ case lists:all(fun(C) -> (C>=0) and (C=<255) end, S) of
+ true ->
+ io_lib:format("\"~s\"", [S]);
+ false ->
+ io_lib:format("~p", [binary_to_strings(S)])
+ end;
+format_term(T) ->
+ io_lib:format("~p", [binary_to_strings(T)]).
+
+-spec convert_table_to_binary(atom(), [atom()], atom(),
+ fun(), fun()) -> ok.
+
+convert_table_to_binary(Tab, Fields, Type, DetectFun, ConvertFun) ->
+ case is_table_still_list(Tab, DetectFun) of
+ true ->
+ ?INFO_MSG("Converting '~s' table from strings to binaries.", [Tab]),
+ TmpTab = list_to_atom(atom_to_list(Tab) ++ "_tmp_table"),
+ catch mnesia:delete_table(TmpTab),
+ case mnesia:create_table(TmpTab,
+ [{disc_only_copies, [node()]},
+ {type, Type},
+ {local_content, true},
+ {record_name, Tab},
+ {attributes, Fields}]) of
+ {atomic, ok} ->
+ mnesia:transform_table(Tab, ignore, Fields),
+ case mnesia:transaction(
+ fun() ->
+ mnesia:write_lock_table(TmpTab),
+ mnesia:foldl(
+ fun(R, _) ->
+ NewR = ConvertFun(R),
+ mnesia:dirty_write(TmpTab, NewR)
+ end, ok, Tab)
+ end) of
+ {atomic, ok} ->
+ mnesia:clear_table(Tab),
+ case mnesia:transaction(
+ fun() ->
+ mnesia:write_lock_table(Tab),
+ mnesia:foldl(
+ fun(R, _) ->
+ mnesia:dirty_write(R)
+ end, ok, TmpTab)
+ end) of
+ {atomic, ok} ->
+ mnesia:delete_table(TmpTab);
+ Err ->
+ report_and_stop(Tab, Err)
+ end;
+ Err ->
+ report_and_stop(Tab, Err)
+ end;
+ Err ->
+ report_and_stop(Tab, Err)
+ end;
+ false ->
+ ok
+ end.
+
+is_table_still_list(Tab, DetectFun) ->
+ is_table_still_list(Tab, DetectFun, mnesia:dirty_first(Tab)).
+
+is_table_still_list(_Tab, _DetectFun, '$end_of_table') ->
+ false;
+is_table_still_list(Tab, DetectFun, Key) ->
+ Rs = mnesia:dirty_read(Tab, Key),
+ Res = lists:foldl(fun(_, true) ->
+ true;
+ (_, false) ->
+ false;
+ (R, _) ->
+ case DetectFun(R) of
+ '$next' ->
+ '$next';
+ El ->
+ is_list(El)
+ end
+ end, '$next', Rs),
+ case Res of
+ true ->
+ true;
+ false ->
+ false;
+ '$next' ->
+ is_table_still_list(Tab, DetectFun, mnesia:dirty_next(Tab, Key))
+ end.
+
+report_and_stop(Tab, Err) ->
+ ErrTxt = lists:flatten(
+ io_lib:format(
+ "Failed to convert '~s' table to binary: ~p",
+ [Tab, Err])),
+ ?CRITICAL_MSG(ErrTxt, []),
+ timer:sleep(1000),
+ halt(string:substr(ErrTxt, 1, 199)).
+
+emit_deprecation_warning(Module, NewModule, DBType) ->
+ ?WARNING_MSG("Module ~s is deprecated, use {~s, [{db_type, ~s}, ...]}"
+ " instead", [Module, NewModule, DBType]).
+
+emit_deprecation_warning(Module, NewModule) ->
+ ?WARNING_MSG("Module ~s is deprecated, use ~s instead",
+ [Module, NewModule]).
diff --git a/src/ejabberd_config.hrl b/src/ejabberd_config.hrl
index b0fa46aca..bf749dd19 100644
--- a/src/ejabberd_config.hrl
+++ b/src/ejabberd_config.hrl
@@ -19,10 +19,16 @@
%%%
%%%----------------------------------------------------------------------
--record(config, {key, value}).
--record(local_config, {key, value}).
--record(state, {opts = [],
- hosts = [],
- override_local = false,
- override_global = false,
- override_acls = false}).
+-record(config, {key :: any(), value :: any()}).
+
+-record(local_config, {key :: any(), value :: any()}).
+
+-type config() :: #config{}.
+-type local_config() :: #local_config{}.
+
+-record(state,
+ {opts = [] :: [acl:acl() | config() | local_config()],
+ hosts = [] :: [binary()],
+ override_local = false :: boolean(),
+ override_global = false :: boolean(),
+ override_acls = false :: boolean()}).
diff --git a/src/ejabberd_ctl.erl b/src/ejabberd_ctl.erl
index 9b41b1463..2b4702176 100644
--- a/src/ejabberd_ctl.erl
+++ b/src/ejabberd_ctl.erl
@@ -72,10 +72,10 @@ start() ->
_ ->
case net_kernel:longnames() of
true ->
- SNode ++ "@" ++ inet_db:gethostname() ++
- "." ++ inet_db:res_option(domain);
+ lists:flatten([SNode, "@", inet_db:gethostname(),
+ ".", inet_db:res_option(domain)]);
false ->
- SNode ++ "@" ++ inet_db:gethostname();
+ lists:flatten([SNode, "@", inet_db:gethostname()]);
_ ->
SNode
end
@@ -124,6 +124,8 @@ unregister_commands(CmdDescs, Module, Function) ->
%% Process
%%-----------------------------
+-spec process([string()]) -> non_neg_integer().
+
%% The commands status, stop and restart are defined here to ensure
%% they are usable even if ejabberd is completely stopped.
process(["status"]) ->
@@ -159,7 +161,7 @@ process(["mnesia", "info"]) ->
mnesia:info(),
?STATUS_SUCCESS;
-process(["mnesia", Arg]) when is_list(Arg) ->
+process(["mnesia", Arg]) ->
case catch mnesia:system_info(list_to_atom(Arg)) of
{'EXIT', Error} -> ?PRINT("Error: ~p~n", [Error]);
Return -> ?PRINT("~p~n", [Return])
@@ -190,8 +192,9 @@ process(["help" | Mode]) ->
print_usage_help(MaxC, ShCode),
?STATUS_SUCCESS;
[CmdString | _] ->
- CmdStringU = ejabberd_regexp:greplace(CmdString, "-", "_"),
- print_usage_commands(CmdStringU, MaxC, ShCode),
+ CmdStringU = ejabberd_regexp:greplace(
+ list_to_binary(CmdString), <<"-">>, <<"_">>),
+ print_usage_commands(binary_to_list(CmdStringU), MaxC, ShCode),
?STATUS_SUCCESS
end;
@@ -214,30 +217,27 @@ process2(Args, AccessCommands) ->
process2(Args, Auth, AccessCommands) ->
case try_run_ctp(Args, Auth, AccessCommands) of
{String, wrong_command_arguments}
- when is_list(String) ->
+ when is_list(String) ->
io:format(lists:flatten(["\n" | String]++["\n"])),
[CommandString | _] = Args,
process(["help" | [CommandString]]),
{lists:flatten(String), ?STATUS_ERROR};
{String, Code}
- when is_list(String) and is_integer(Code) ->
+ when is_list(String) and is_integer(Code) ->
{lists:flatten(String), Code};
String
- when is_list(String) ->
+ when is_list(String) ->
{lists:flatten(String), ?STATUS_SUCCESS};
Code
- when is_integer(Code) ->
+ when is_integer(Code) ->
{"", Code};
Other ->
{"Erroneous result: " ++ io_lib:format("~p", [Other]), ?STATUS_ERROR}
end.
get_accesscommands() ->
- case ejabberd_config:get_local_option(ejabberdctl_access_commands) of
- ACs when is_list(ACs) -> ACs;
- _ -> []
- end.
-
+ ejabberd_config:get_local_option(ejabberdctl_access_commands,
+ fun(V) when is_list(V) -> V end, []).
%%-----------------------------
%% Command calling
@@ -281,8 +281,9 @@ try_call_command(Args, Auth, AccessCommands) ->
%% @spec (Args::[string()], Auth, AccessCommands) -> string() | integer() | {string(), integer()} | {error, ErrorType}
call_command([CmdString | Args], Auth, AccessCommands) ->
- CmdStringU = ejabberd_regexp:greplace(CmdString, "-", "_"),
- Command = list_to_atom(CmdStringU),
+ CmdStringU = ejabberd_regexp:greplace(
+ list_to_binary(CmdString), <<"-">>, <<"_">>),
+ Command = list_to_atom(binary_to_list(CmdStringU)),
case ejabberd_commands:get_command_format(Command) of
{error, command_unknown} ->
{error, command_unknown};
@@ -331,10 +332,12 @@ format_args(Args, ArgsFormat) ->
format_arg(Arg, integer) ->
format_arg2(Arg, "~d");
+format_arg(Arg, binary) ->
+ list_to_binary(format_arg(Arg, string));
format_arg("", string) ->
"";
format_arg(Arg, string) ->
- NumChars = integer_to_list(string:len(Arg)),
+ NumChars = integer_to_list(length(Arg)),
Parse = "~" ++ NumChars ++ "c",
format_arg2(Arg, Parse).
@@ -540,24 +543,25 @@ split_desc_segments(MaxL, Words) ->
join(L, Words) ->
join(L, Words, 0, [], []).
-join(_L, [], _LenLastSeg, LastSeg, ResSeg) ->
- ResSeg2 = [lists:reverse(LastSeg) | ResSeg],
- lists:reverse(ResSeg2);
-join(L, [Word | Words], LenLastSeg, LastSeg, ResSeg) ->
- LWord = length(Word),
- case LWord + LenLastSeg < L of
- true ->
- %% This word fits in the last segment
- %% If this word ends with "\n", reset column counter
- case string:str(Word, "\n") of
- 0 ->
- join(L, Words, LenLastSeg+LWord+1, [" ", Word | LastSeg], ResSeg);
- _ ->
- join(L, Words, LWord+1, [" ", Word | LastSeg], ResSeg)
- end;
- false ->
- join(L, Words, LWord, [" ", Word], [lists:reverse(LastSeg) | ResSeg])
- end.
+join(_Len, [], _CurSegLen, CurSeg, AllSegs) ->
+ lists:reverse([CurSeg | AllSegs]);
+join(Len, [Word | Tail], CurSegLen, CurSeg, AllSegs) ->
+ WordLen = length(Word),
+ SegSize = WordLen + CurSegLen + 1,
+ {NewCurSeg, NewAllSegs, NewCurSegLen} =
+ if SegSize < Len ->
+ {[CurSeg, " ", Word], AllSegs, SegSize};
+ true ->
+ {Word, [CurSeg | AllSegs], WordLen}
+ end,
+ NewLen = case string:str(Word, "\n") of
+ 0 ->
+ NewCurSegLen;
+ _ ->
+ 0
+ end,
+ join(Len, Tail, NewLen, NewCurSeg, NewAllSegs).
+
format_command_lines(CALD, MaxCmdLen, MaxC, ShCode, dual)
when MaxC - MaxCmdLen < 40 ->
@@ -568,7 +572,8 @@ format_command_lines(CALD, MaxCmdLen, MaxC, ShCode, dual) ->
lists:map(
fun({Cmd, Args, CmdArgsL, Desc}) ->
DescFmt = prepare_description(MaxCmdLen+4, MaxC, Desc),
- [" ", ?B(Cmd), " ", [[?U(Arg), " "] || Arg <- Args], string:chars($\s, MaxCmdLen - CmdArgsL + 1),
+ [" ", ?B(Cmd), " ", [[?U(Arg), " "] || Arg <- Args],
+ string:chars($\s, MaxCmdLen - CmdArgsL + 1),
DescFmt, "\n"]
end, CALD);
@@ -608,7 +613,8 @@ print_usage_tags(Tag, MaxC, ShCode) ->
end,
CommandsList = lists:map(
fun(NameString) ->
- C = ejabberd_commands:get_command_definition(list_to_atom(NameString)),
+ C = ejabberd_commands:get_command_definition(
+ list_to_atom(NameString)),
#ejabberd_commands{name = Name,
args = Args,
desc = Desc} = C,
@@ -689,10 +695,10 @@ filter_commands(All, SubString) ->
end.
filter_commands_regexp(All, Glob) ->
- RegExp = ejabberd_regexp:sh_to_awk(Glob),
+ RegExp = ejabberd_regexp:sh_to_awk(list_to_binary(Glob)),
lists:filter(
fun(Command) ->
- case ejabberd_regexp:run(Command, RegExp) of
+ case ejabberd_regexp:run(list_to_binary(Command), RegExp) of
match ->
true;
nomatch ->
diff --git a/src/ejabberd_ctl.hrl b/src/ejabberd_ctl.hrl
index 27bb2489c..09a5287ee 100644
--- a/src/ejabberd_ctl.hrl
+++ b/src/ejabberd_ctl.hrl
@@ -20,6 +20,9 @@
%%%----------------------------------------------------------------------
-define(STATUS_SUCCESS, 0).
--define(STATUS_ERROR, 1).
--define(STATUS_USAGE, 2).
--define(STATUS_BADRPC, 3).
+
+-define(STATUS_ERROR, 1).
+
+-define(STATUS_USAGE, 2).
+
+-define(STATUS_BADRPC, 3).
diff --git a/src/ejabberd_frontend_socket.erl b/src/ejabberd_frontend_socket.erl
index 93df685c7..98f305536 100644
--- a/src/ejabberd_frontend_socket.erl
+++ b/src/ejabberd_frontend_socket.erl
@@ -25,6 +25,7 @@
%%%----------------------------------------------------------------------
-module(ejabberd_frontend_socket).
+
-author('alexey@process-one.net').
-behaviour(gen_server).
@@ -48,8 +49,8 @@
sockname/1, peername/1]).
%% gen_server callbacks
--export([init/1, handle_call/3, handle_cast/2, handle_info/2,
- terminate/2, code_change/3]).
+-export([init/1, handle_call/3, handle_cast/2,
+ handle_info/2, terminate/2, code_change/3]).
-record(state, {sockmod, socket, receiver}).
@@ -68,30 +69,30 @@ start_link(Module, SockMod, Socket, Opts, Receiver) ->
start(Module, SockMod, Socket, Opts) ->
case Module:socket_type() of
- xml_stream ->
- MaxStanzaSize =
- case lists:keysearch(max_stanza_size, 1, Opts) of
- {value, {_, Size}} -> Size;
- _ -> infinity
- end,
- Receiver = ejabberd_receiver:start(Socket, SockMod, none, MaxStanzaSize),
- case SockMod:controlling_process(Socket, Receiver) of
- ok ->
- ok;
- {error, _Reason} ->
- SockMod:close(Socket)
- end,
- supervisor:start_child(ejabberd_frontend_socket_sup,
- [Module, SockMod, Socket, Opts, Receiver]);
- raw ->
- %{ok, Pid} = Module:start({SockMod, Socket}, Opts),
- %case SockMod:controlling_process(Socket, Pid) of
- % ok ->
- % ok;
- % {error, _Reason} ->
- % SockMod:close(Socket)
- %end
- todo
+ xml_stream ->
+ MaxStanzaSize = case lists:keysearch(max_stanza_size, 1,
+ Opts)
+ of
+ {value, {_, Size}} -> Size;
+ _ -> infinity
+ end,
+ Receiver = ejabberd_receiver:start(Socket, SockMod,
+ none, MaxStanzaSize),
+ case SockMod:controlling_process(Socket, Receiver) of
+ ok -> ok;
+ {error, _Reason} -> SockMod:close(Socket)
+ end,
+ supervisor:start_child(ejabberd_frontend_socket_sup,
+ [Module, SockMod, Socket, Opts, Receiver]);
+ raw ->
+ %{ok, Pid} = Module:start({SockMod, Socket}, Opts),
+ %case SockMod:controlling_process(Socket, Pid) of
+ % ok ->
+ % ok;
+ % {error, _Reason} ->
+ % SockMod:close(Socket)
+ %end
+ todo
end.
starttls(FsmRef, _TLSOpts) ->
@@ -108,8 +109,7 @@ compress(FsmRef) ->
FsmRef.
compress(FsmRef, Data) ->
- gen_server:call(FsmRef, {compress, Data}),
- FsmRef.
+ gen_server:call(FsmRef, {compress, Data}), FsmRef.
reset_stream(FsmRef) ->
gen_server:call(FsmRef, reset_stream).
@@ -120,8 +120,7 @@ send(FsmRef, Data) ->
change_shaper(FsmRef, Shaper) ->
gen_server:call(FsmRef, {change_shaper, Shaper}).
-monitor(FsmRef) ->
- erlang:monitor(process, FsmRef).
+monitor(FsmRef) -> erlang:monitor(process, FsmRef).
get_sockmod(FsmRef) ->
gen_server:call(FsmRef, get_sockmod).
@@ -132,11 +131,9 @@ get_peer_certificate(FsmRef) ->
get_verify_result(FsmRef) ->
gen_server:call(FsmRef, get_verify_result).
-close(FsmRef) ->
- gen_server:call(FsmRef, close).
+close(FsmRef) -> gen_server:call(FsmRef, close).
-sockname(FsmRef) ->
- gen_server:call(FsmRef, sockname).
+sockname(FsmRef) -> gen_server:call(FsmRef, sockname).
peername(_FsmRef) ->
%% TODO: Frontend improvements planned by Aleksey
@@ -156,7 +153,6 @@ peername(_FsmRef) ->
%% Description: Initiates the server
%%--------------------------------------------------------------------
init([Module, SockMod, Socket, Opts, Receiver]) ->
- %% TODO: monitor the receiver
Node = ejabberd_node_groups:get_closest_node(backend),
{SockMod2, Socket2} = check_starttls(SockMod, Socket, Receiver, Opts),
{ok, Pid} =
@@ -188,7 +184,8 @@ handle_call({starttls, TLSOpts, Data}, _From, State) ->
catch (State#state.sockmod):send(
State#state.socket, Data),
Reply = ok,
- {reply, Reply, State#state{socket = TLSSocket, sockmod = tls},
+ {reply, Reply,
+ State#state{socket = TLSSocket, sockmod = tls},
?HIBERNATE_TIMEOUT};
handle_call(compress, _From, State) ->
@@ -208,42 +205,35 @@ handle_call({compress, Data}, _From, State) ->
catch (State#state.sockmod):send(
State#state.socket, Data),
Reply = ok,
- {reply, Reply, State#state{socket = ZlibSocket, sockmod = ejabberd_zlib},
+ {reply, Reply,
+ State#state{socket = ZlibSocket, sockmod = ejabberd_zlib},
?HIBERNATE_TIMEOUT};
-
handle_call(reset_stream, _From, State) ->
ejabberd_receiver:reset_stream(State#state.receiver),
Reply = ok,
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
-
handle_call({send, Data}, _From, State) ->
- catch (State#state.sockmod):send(
- State#state.socket, Data),
+ catch (State#state.sockmod):send(State#state.socket, Data),
Reply = ok,
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
-
handle_call({change_shaper, Shaper}, _From, State) ->
- ejabberd_receiver:change_shaper(State#state.receiver, Shaper),
+ ejabberd_receiver:change_shaper(State#state.receiver,
+ Shaper),
Reply = ok,
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
-
handle_call(get_sockmod, _From, State) ->
Reply = State#state.sockmod,
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
-
handle_call(get_peer_certificate, _From, State) ->
Reply = tls:get_peer_certificate(State#state.socket),
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
-
handle_call(get_verify_result, _From, State) ->
Reply = tls:get_verify_result(State#state.socket),
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
-
handle_call(close, _From, State) ->
ejabberd_receiver:close(State#state.receiver),
Reply = ok,
{stop, normal, Reply, State};
-
handle_call(sockname, _From, State) ->
#state{sockmod = SockMod, socket = Socket} = State,
Reply =
@@ -254,21 +244,15 @@ handle_call(sockname, _From, State) ->
SockMod:sockname(Socket)
end,
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
-
handle_call(peername, _From, State) ->
#state{sockmod = SockMod, socket = Socket} = State,
- Reply =
- case SockMod of
- gen_tcp ->
- inet:peername(Socket);
- _ ->
- SockMod:peername(Socket)
- end,
+ Reply = case SockMod of
+ gen_tcp -> inet:peername(Socket);
+ _ -> SockMod:peername(Socket)
+ end,
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
-
handle_call(_Request, _From, State) ->
- Reply = ok,
- {reply, Reply, State, ?HIBERNATE_TIMEOUT}.
+ Reply = ok, {reply, Reply, State, ?HIBERNATE_TIMEOUT}.
%%--------------------------------------------------------------------
%% Function: handle_cast(Msg, State) -> {noreply, State} |
@@ -286,7 +270,8 @@ handle_cast(_Msg, State) ->
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------
handle_info(timeout, State) ->
- proc_lib:hibernate(gen_server, enter_loop, [?MODULE, [], State]),
+ proc_lib:hibernate(gen_server, enter_loop,
+ [?MODULE, [], State]),
{noreply, State, ?HIBERNATE_TIMEOUT};
handle_info(_Info, State) ->
{noreply, State, ?HIBERNATE_TIMEOUT}.
@@ -298,15 +283,13 @@ handle_info(_Info, State) ->
%% cleaning up. When it returns, the gen_server terminates with Reason.
%% The return value is ignored.
%%--------------------------------------------------------------------
-terminate(_Reason, _State) ->
- ok.
+terminate(_Reason, _State) -> ok.
%%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
%% Description: Convert process state when code is changed
%%--------------------------------------------------------------------
-code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
+code_change(_OldVsn, State, _Extra) -> {ok, State}.
%%--------------------------------------------------------------------
%%% Internal functions
diff --git a/src/ejabberd_hooks.erl b/src/ejabberd_hooks.erl
index 26573aa7d..e4f9f597b 100644
--- a/src/ejabberd_hooks.erl
+++ b/src/ejabberd_hooks.erl
@@ -67,58 +67,76 @@
start_link() ->
gen_server:start_link({local, ejabberd_hooks}, ejabberd_hooks, [], []).
-%% @spec (Hook::atom(), Function::function(), Seq::integer()) -> ok
+-spec add(atom(), fun(), number()) -> any().
+
%% @doc See add/4.
add(Hook, Function, Seq) when is_function(Function) ->
add(Hook, global, undefined, Function, Seq).
+-spec add(atom(), binary() | atom(), fun() | atom() , number()) -> any().
add(Hook, Host, Function, Seq) when is_function(Function) ->
add(Hook, Host, undefined, Function, Seq);
-%% @spec (Hook::atom(), Module::atom(), Function::atom(), Seq::integer()) -> ok
%% @doc Add a module and function to this hook.
%% The integer sequence is used to sort the calls: low number is called before high number.
add(Hook, Module, Function, Seq) ->
add(Hook, global, Module, Function, Seq).
+-spec add(atom(), binary() | global, atom(), atom() | fun(), number()) -> any().
+
add(Hook, Host, Module, Function, Seq) ->
gen_server:call(ejabberd_hooks, {add, Hook, Host, Module, Function, Seq}).
+-spec add_dist(atom(), atom(), atom(), atom() | fun(), number()) -> any().
+
add_dist(Hook, Node, Module, Function, Seq) ->
gen_server:call(ejabberd_hooks, {add, Hook, global, Node, Module, Function, Seq}).
+-spec add_dist(atom(), binary() | global, atom(), atom(), atom() | fun(), number()) -> any().
+
add_dist(Hook, Host, Node, Module, Function, Seq) ->
gen_server:call(ejabberd_hooks, {add, Hook, Host, Node, Module, Function, Seq}).
-%% @spec (Hook::atom(), Function::function(), Seq::integer()) -> ok
+-spec delete(atom(), fun(), number()) -> ok.
+
%% @doc See del/4.
delete(Hook, Function, Seq) when is_function(Function) ->
delete(Hook, global, undefined, Function, Seq).
+-spec delete(atom(), binary() | atom(), atom() | fun(), number()) -> ok.
+
delete(Hook, Host, Function, Seq) when is_function(Function) ->
delete(Hook, Host, undefined, Function, Seq);
-%% @spec (Hook::atom(), Module::atom(), Function::atom(), Seq::integer()) -> ok
%% @doc Delete a module and function from this hook.
%% It is important to indicate exactly the same information than when the call was added.
delete(Hook, Module, Function, Seq) ->
delete(Hook, global, Module, Function, Seq).
+-spec delete(atom(), binary() | global, atom(), atom() | fun(), number()) -> ok.
+
delete(Hook, Host, Module, Function, Seq) ->
gen_server:call(ejabberd_hooks, {delete, Hook, Host, Module, Function, Seq}).
+-spec delete_dist(atom(), atom(), atom(), atom() | fun(), number()) -> ok.
+
delete_dist(Hook, Node, Module, Function, Seq) ->
delete_dist(Hook, global, Node, Module, Function, Seq).
+-spec delete_dist(atom(), binary() | global, atom(), atom(), atom() | fun(), number()) -> ok.
+
delete_dist(Hook, Host, Node, Module, Function, Seq) ->
gen_server:call(ejabberd_hooks, {delete, Hook, Host, Node, Module, Function, Seq}).
-%% @spec (Hook::atom(), Args) -> ok
+-spec run(atom(), list()) -> ok.
+
%% @doc Run the calls of this hook in order, don't care about function results.
%% If a call returns stop, no more calls are performed.
run(Hook, Args) ->
run(Hook, global, Args).
+-spec run(atom(), binary() | global, list()) -> ok.
+
run(Hook, Host, Args) ->
case ets:lookup(hooks, {Hook, Host}) of
[{_, Ls}] ->
@@ -127,7 +145,8 @@ run(Hook, Host, Args) ->
ok
end.
-%% @spec (Hook::atom(), Val, Args) -> Val | stopped | NewVal
+-spec run_fold(atom(), any(), list()) -> any().
+
%% @doc Run the calls of this hook in order.
%% The arguments passed to the function are: [Val | Args].
%% The result of a call is used as Val for the next call.
@@ -136,6 +155,8 @@ run(Hook, Host, Args) ->
run_fold(Hook, Val, Args) ->
run_fold(Hook, global, Val, Args).
+-spec run_fold(atom(), binary() | global, any(), list()) -> any().
+
run_fold(Hook, Host, Val, Args) ->
case ets:lookup(hooks, {Hook, Host}) of
[{_, Ls}] ->
diff --git a/src/ejabberd_listener.erl b/src/ejabberd_listener.erl
index 78f1691e1..9df8678b0 100644
--- a/src/ejabberd_listener.erl
+++ b/src/ejabberd_listener.erl
@@ -35,7 +35,8 @@
stop_listener/2,
parse_listener_portip/2,
add_listener/3,
- delete_listener/2
+ delete_listener/2,
+ validate_cfg/1
]).
-include("ejabberd.hrl").
@@ -53,7 +54,7 @@ init(_) ->
{ok, {{one_for_one, 10, 1}, []}}.
bind_tcp_ports() ->
- case ejabberd_config:get_local_option(listen) of
+ case ejabberd_config:get_local_option(listen, fun validate_cfg/1) of
undefined ->
ignore;
Ls ->
@@ -77,7 +78,8 @@ bind_tcp_port(PortIP, Module, RawOpts) ->
udp -> ok;
_ ->
ListenSocket = listen_tcp(PortIP, Module, SockOpts, Port, IPS),
- ets:insert(listen_sockets, {PortIP, ListenSocket})
+ ets:insert(listen_sockets, {PortIP, ListenSocket}),
+ ok
end
catch
throw:{error, Error} ->
@@ -85,7 +87,7 @@ bind_tcp_port(PortIP, Module, RawOpts) ->
end.
start_listeners() ->
- case ejabberd_config:get_local_option(listen) of
+ case ejabberd_config:get_local_option(listen, fun validate_cfg/1) of
undefined ->
ignore;
Ls ->
@@ -215,17 +217,17 @@ parse_listener_portip(PortIP, Opts) ->
case add_proto(PortIP, Opts) of
{P, Prot} ->
T = get_ip_tuple(IPOpt, IPVOpt),
- S = inet_parse:ntoa(T),
+ S = jlib:ip_to_list(T),
{P, T, S, Prot};
{P, T, Prot} when is_integer(P) and is_tuple(T) ->
- S = inet_parse:ntoa(T),
+ S = jlib:ip_to_list(T),
{P, T, S, Prot};
- {P, S, Prot} when is_integer(P) and is_list(S) ->
- [S | _] = string:tokens(S, "/"),
- {ok, T} = inet_parse:address(S),
+ {P, S, Prot} when is_integer(P) and is_binary(S) ->
+ [S | _] = str:tokens(S, <<"/">>),
+ {ok, T} = inet_parse:address(binary_to_list(S)),
{P, T, S, Prot}
end,
- IPV = case size(IPT) of
+ IPV = case tuple_size(IPT) of
4 -> inet;
8 -> inet6
end,
@@ -337,7 +339,7 @@ start_listener2(Port, Module, Opts) ->
start_listener_sup(Port, Module, Opts).
start_module_sup(_Port, Module) ->
- Proc1 = gen_mod:get_module_proc("sup", Module),
+ Proc1 = gen_mod:get_module_proc(<<"sup">>, Module),
ChildSpec1 =
{Proc1,
{ejabberd_tmp_sup, start_link, [Proc1, strip_frontend(Module)]},
@@ -357,7 +359,7 @@ start_listener_sup(Port, Module, Opts) ->
supervisor:start_child(ejabberd_listeners, ChildSpec).
stop_listeners() ->
- Ports = ejabberd_config:get_local_option(listen),
+ Ports = ejabberd_config:get_local_option(listen, fun validate_cfg/1),
lists:foreach(
fun({PortIpNetp, Module, _Opts}) ->
delete_listener(PortIpNetp, Module)
@@ -390,7 +392,8 @@ add_listener(PortIP, Module, Opts) ->
PortIP1 = {Port, IPT, Proto},
case start_listener(PortIP1, Module, Opts) of
{ok, _Pid} ->
- Ports = case ejabberd_config:get_local_option(listen) of
+ Ports = case ejabberd_config:get_local_option(
+ listen, fun validate_cfg/1) of
undefined ->
[];
Ls ->
@@ -420,7 +423,8 @@ delete_listener(PortIP, Module) ->
delete_listener(PortIP, Module, Opts) ->
{Port, IPT, _, _, Proto, _} = parse_listener_portip(PortIP, Opts),
PortIP1 = {Port, IPT, Proto},
- Ports = case ejabberd_config:get_local_option(listen) of
+ Ports = case ejabberd_config:get_local_option(
+ listen, fun validate_cfg/1) of
undefined ->
[];
Ls ->
@@ -430,11 +434,16 @@ delete_listener(PortIP, Module, Opts) ->
ejabberd_config:add_local_option(listen, Ports1),
stop_listener(PortIP1, Module).
+
+-spec is_frontend({frontend, module} | module()) -> boolean().
+
is_frontend({frontend, _Module}) -> true;
is_frontend(_) -> false.
%% @doc(FrontMod) -> atom()
%% where FrontMod = atom() | {frontend, atom()}
+-spec strip_frontend({frontend, module()} | module()) -> module().
+
strip_frontend({frontend, Module}) -> Module;
strip_frontend(Module) when is_atom(Module) -> Module.
@@ -505,7 +514,7 @@ socket_error(Reason, PortIP, Module, SockOpts, Port, IPS) ->
"IP address not available: " ++ IPS;
eaddrinuse ->
"IP address and port number already used: "
- ++IPS++" "++integer_to_list(Port);
+ ++binary_to_list(IPS)++" "++integer_to_list(Port);
_ ->
format_error(Reason)
end,
@@ -520,3 +529,44 @@ format_error(Reason) ->
ReasonStr ->
ReasonStr
end.
+
+-define(IS_CHAR(C), (is_integer(C) and (C >= 0) and (C =< 255))).
+-define(IS_UINT(U), (is_integer(U) and (U >= 0) and (U =< 65535))).
+-define(IS_PORT(P), (is_integer(P) and (P > 0) and (P =< 65535))).
+-define(IS_TRANSPORT(T), ((T == tcp) or (T == udp))).
+
+-type transport() :: udp | tcp.
+-type port_ip_transport() :: inet:port_number() |
+ {inet:port_number(), transport()} |
+ {inet:port_number(), inet:ip_address()} |
+ {inet:port_number(), inet:ip_address(),
+ transport()}.
+-spec validate_cfg(list()) -> [{port_ip_transport(), module(), list()}].
+
+validate_cfg(L) ->
+ lists:map(
+ fun({PortIPTransport, Mod, Opts}) when is_atom(Mod), is_list(Opts) ->
+ case PortIPTransport of
+ Port when ?IS_PORT(Port) ->
+ {Port, Mod, Opts};
+ {Port, Trans} when ?IS_PORT(Port) and ?IS_TRANSPORT(Trans) ->
+ {{Port, Trans}, Mod, Opts};
+ {Port, IP} when ?IS_PORT(Port) ->
+ {{Port, prepare_ip(IP)}, Mod, Opts};
+ {Port, IP, Trans} when ?IS_PORT(Port) and ?IS_TRANSPORT(Trans) ->
+ {{Port, prepare_ip(IP), Trans}, Mod, Opts}
+ end
+ end, L).
+
+prepare_ip({A, B, C, D} = IP)
+ when ?IS_CHAR(A) and ?IS_CHAR(B) and ?IS_CHAR(C) and ?IS_CHAR(D) ->
+ IP;
+prepare_ip({A, B, C, D, E, F, G, H} = IP)
+ when ?IS_UINT(A) and ?IS_UINT(B) and ?IS_UINT(C) and ?IS_UINT(D)
+ and ?IS_UINT(E) and ?IS_UINT(F) and ?IS_UINT(G) and ?IS_UINT(H) ->
+ IP;
+prepare_ip(IP) when is_list(IP) ->
+ {ok, Addr} = inet_parse:address(IP),
+ Addr;
+prepare_ip(IP) when is_binary(IP) ->
+ prepare_ip(binary_to_list(IP)).
diff --git a/src/ejabberd_local.erl b/src/ejabberd_local.erl
index 1fe7cb0a4..12dfea0c8 100644
--- a/src/ejabberd_local.erl
+++ b/src/ejabberd_local.erl
@@ -25,6 +25,7 @@
%%%----------------------------------------------------------------------
-module(ejabberd_local).
+
-author('alexey@process-one.net').
-behaviour(gen_server).
@@ -32,30 +33,27 @@
%% API
-export([start_link/0]).
--export([route/3,
- route_iq/4,
- route_iq/5,
- process_iq_reply/3,
- register_iq_handler/4,
- register_iq_handler/5,
- register_iq_response_handler/4,
- register_iq_response_handler/5,
- unregister_iq_handler/2,
- unregister_iq_response_handler/2,
- refresh_iq_handlers/0,
- bounce_resource_packet/3
- ]).
+-export([route/3, route_iq/4, route_iq/5,
+ process_iq_reply/3, register_iq_handler/4,
+ register_iq_handler/5, register_iq_response_handler/4,
+ register_iq_response_handler/5, unregister_iq_handler/2,
+ unregister_iq_response_handler/2, refresh_iq_handlers/0,
+ bounce_resource_packet/3]).
%% gen_server callbacks
--export([init/1, handle_call/3, handle_cast/2, handle_info/2,
- terminate/2, code_change/3]).
+-export([init/1, handle_call/3, handle_cast/2,
+ handle_info/2, terminate/2, code_change/3]).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
-record(state, {}).
--record(iq_response, {id, module, function, timer}).
+-record(iq_response, {id = <<"">> :: binary(),
+ module :: atom(),
+ function :: atom() | fun(),
+ timer = make_ref() :: reference()}).
-define(IQTABLE, local_iqtable).
@@ -70,65 +68,59 @@
%% Description: Starts the server
%%--------------------------------------------------------------------
start_link() ->
- gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
+ gen_server:start_link({local, ?MODULE}, ?MODULE, [],
+ []).
process_iq(From, To, Packet) ->
IQ = jlib:iq_query_info(Packet),
case IQ of
- #iq{xmlns = XMLNS} ->
- Host = To#jid.lserver,
- case ets:lookup(?IQTABLE, {XMLNS, Host}) of
- [{_, Module, Function}] ->
- ResIQ = Module:Function(From, To, IQ),
- if
- ResIQ /= ignore ->
- ejabberd_router:route(
- To, From, jlib:iq_to_xml(ResIQ));
- true ->
- ok
- end;
- [{_, Module, Function, Opts}] ->
- gen_iq_handler:handle(Host, Module, Function, Opts,
- From, To, IQ);
- [] ->
- Err = jlib:make_error_reply(
- Packet, ?ERR_FEATURE_NOT_IMPLEMENTED),
- ejabberd_router:route(To, From, Err)
- end;
- reply ->
- IQReply = jlib:iq_query_or_response_info(Packet),
- process_iq_reply(From, To, IQReply);
- _ ->
- Err = jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST),
- ejabberd_router:route(To, From, Err),
- ok
+ #iq{xmlns = XMLNS} ->
+ Host = To#jid.lserver,
+ case ets:lookup(?IQTABLE, {XMLNS, Host}) of
+ [{_, Module, Function}] ->
+ ResIQ = Module:Function(From, To, IQ),
+ if ResIQ /= ignore ->
+ ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ));
+ true -> ok
+ end;
+ [{_, Module, Function, Opts}] ->
+ gen_iq_handler:handle(Host, Module, Function, Opts,
+ From, To, IQ);
+ [] ->
+ Err = jlib:make_error_reply(Packet,
+ ?ERR_FEATURE_NOT_IMPLEMENTED),
+ ejabberd_router:route(To, From, Err)
+ end;
+ reply ->
+ IQReply = jlib:iq_query_or_response_info(Packet),
+ process_iq_reply(From, To, IQReply);
+ _ ->
+ Err = jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST),
+ ejabberd_router:route(To, From, Err),
+ ok
end.
process_iq_reply(From, To, #iq{id = ID} = IQ) ->
case get_iq_callback(ID) of
- {ok, undefined, Function} ->
- Function(IQ),
- ok;
- {ok, Module, Function} ->
- Module:Function(From, To, IQ),
- ok;
- _ ->
- nothing
+ {ok, undefined, Function} -> Function(IQ), ok;
+ {ok, Module, Function} ->
+ Module:Function(From, To, IQ), ok;
+ _ -> nothing
end.
route(From, To, Packet) ->
case catch do_route(From, To, Packet) of
- {'EXIT', Reason} ->
- ?ERROR_MSG("~p~nwhen processing: ~p",
- [Reason, {From, To, Packet}]);
- _ ->
- ok
+ {'EXIT', Reason} ->
+ ?ERROR_MSG("~p~nwhen processing: ~p",
+ [Reason, {From, To, Packet}]);
+ _ -> ok
end.
route_iq(From, To, IQ, F) ->
route_iq(From, To, IQ, F, undefined).
-route_iq(From, To, #iq{type = Type} = IQ, F, Timeout) when is_function(F) ->
+route_iq(From, To, #iq{type = Type} = IQ, F, Timeout)
+ when is_function(F) ->
Packet = if Type == set; Type == get ->
ID = randoms:get_string(),
Host = From#jid.lserver,
@@ -139,15 +131,16 @@ route_iq(From, To, #iq{type = Type} = IQ, F, Timeout) when is_function(F) ->
end,
ejabberd_router:route(From, To, Packet).
-register_iq_response_handler(Host, ID, Module, Function) ->
- register_iq_response_handler(Host, ID, Module, Function, undefined).
+register_iq_response_handler(Host, ID, Module,
+ Function) ->
+ register_iq_response_handler(Host, ID, Module, Function,
+ undefined).
-register_iq_response_handler(_Host, ID, Module, Function, Timeout0) ->
+register_iq_response_handler(_Host, ID, Module,
+ Function, Timeout0) ->
Timeout = case Timeout0 of
- undefined ->
- ?IQ_TIMEOUT;
- N when is_integer(N), N > 0 ->
- N
+ undefined -> ?IQ_TIMEOUT;
+ N when is_integer(N), N > 0 -> N
end,
TRef = erlang:start_timer(Timeout, ejabberd_local, ID),
mnesia:dirty_write(#iq_response{id = ID,
@@ -156,14 +149,15 @@ register_iq_response_handler(_Host, ID, Module, Function, Timeout0) ->
timer = TRef}).
register_iq_handler(Host, XMLNS, Module, Fun) ->
- ejabberd_local ! {register_iq_handler, Host, XMLNS, Module, Fun}.
+ ejabberd_local !
+ {register_iq_handler, Host, XMLNS, Module, Fun}.
register_iq_handler(Host, XMLNS, Module, Fun, Opts) ->
- ejabberd_local ! {register_iq_handler, Host, XMLNS, Module, Fun, Opts}.
+ ejabberd_local !
+ {register_iq_handler, Host, XMLNS, Module, Fun, Opts}.
unregister_iq_response_handler(_Host, ID) ->
- catch get_iq_callback(ID),
- ok.
+ catch get_iq_callback(ID), ok.
unregister_iq_handler(Host, XMLNS) ->
ejabberd_local ! {unregister_iq_handler, Host, XMLNS}.
@@ -172,7 +166,8 @@ refresh_iq_handlers() ->
ejabberd_local ! refresh_iq_handlers.
bounce_resource_packet(From, To, Packet) ->
- Err = jlib:make_error_reply(Packet, ?ERR_ITEM_NOT_FOUND),
+ Err = jlib:make_error_reply(Packet,
+ ?ERR_ITEM_NOT_FOUND),
ejabberd_router:route(To, From, Err),
stop.
@@ -188,12 +183,15 @@ bounce_resource_packet(From, To, Packet) ->
%% Description: Initiates the server
%%--------------------------------------------------------------------
init([]) ->
- lists:foreach(
- fun(Host) ->
- ejabberd_router:register_route(Host, {apply, ?MODULE, route}),
- ejabberd_hooks:add(local_send_to_resource_hook, Host,
- ?MODULE, bounce_resource_packet, 100)
- end, ?MYHOSTS),
+ lists:foreach(fun (Host) ->
+ ejabberd_router:register_route(Host,
+ {apply, ?MODULE,
+ route}),
+ ejabberd_hooks:add(local_send_to_resource_hook, Host,
+ ?MODULE, bounce_resource_packet,
+ 100)
+ end,
+ ?MYHOSTS),
catch ets:new(?IQTABLE, [named_table, public]),
update_table(),
mnesia:create_table(iq_response,
@@ -212,70 +210,68 @@ init([]) ->
%% Description: Handling call messages
%%--------------------------------------------------------------------
handle_call(_Request, _From, State) ->
- Reply = ok,
- {reply, Reply, State}.
-
%%--------------------------------------------------------------------
%% Function: handle_cast(Msg, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State}
%% Description: Handling cast messages
%%--------------------------------------------------------------------
-handle_cast(_Msg, State) ->
- {noreply, State}.
-
%%--------------------------------------------------------------------
%% Function: handle_info(Info, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State}
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------
+ Reply = ok, {reply, Reply, State}.
+
+handle_cast(_Msg, State) -> {noreply, State}.
+
handle_info({route, From, To, Packet}, State) ->
case catch do_route(From, To, Packet) of
- {'EXIT', Reason} ->
- ?ERROR_MSG("~p~nwhen processing: ~p",
- [Reason, {From, To, Packet}]);
- _ ->
- ok
+ {'EXIT', Reason} ->
+ ?ERROR_MSG("~p~nwhen processing: ~p",
+ [Reason, {From, To, Packet}]);
+ _ -> ok
end,
{noreply, State};
-handle_info({register_iq_handler, Host, XMLNS, Module, Function}, State) ->
+handle_info({register_iq_handler, Host, XMLNS, Module,
+ Function},
+ State) ->
ets:insert(?IQTABLE, {{XMLNS, Host}, Module, Function}),
catch mod_disco:register_feature(Host, XMLNS),
{noreply, State};
-handle_info({register_iq_handler, Host, XMLNS, Module, Function, Opts}, State) ->
- ets:insert(?IQTABLE, {{XMLNS, Host}, Module, Function, Opts}),
+handle_info({register_iq_handler, Host, XMLNS, Module,
+ Function, Opts},
+ State) ->
+ ets:insert(?IQTABLE,
+ {{XMLNS, Host}, Module, Function, Opts}),
catch mod_disco:register_feature(Host, XMLNS),
{noreply, State};
-handle_info({unregister_iq_handler, Host, XMLNS}, State) ->
+handle_info({unregister_iq_handler, Host, XMLNS},
+ State) ->
case ets:lookup(?IQTABLE, {XMLNS, Host}) of
- [{_, Module, Function, Opts}] ->
- gen_iq_handler:stop_iq_handler(Module, Function, Opts);
- _ ->
- ok
+ [{_, Module, Function, Opts}] ->
+ gen_iq_handler:stop_iq_handler(Module, Function, Opts);
+ _ -> ok
end,
ets:delete(?IQTABLE, {XMLNS, Host}),
catch mod_disco:unregister_feature(Host, XMLNS),
{noreply, State};
handle_info(refresh_iq_handlers, State) ->
- lists:foreach(
- fun(T) ->
- case T of
- {{XMLNS, Host}, _Module, _Function, _Opts} ->
- catch mod_disco:register_feature(Host, XMLNS);
- {{XMLNS, Host}, _Module, _Function} ->
- catch mod_disco:register_feature(Host, XMLNS);
- _ ->
- ok
- end
- end, ets:tab2list(?IQTABLE)),
+ lists:foreach(fun (T) ->
+ case T of
+ {{XMLNS, Host}, _Module, _Function, _Opts} ->
+ catch mod_disco:register_feature(Host, XMLNS);
+ {{XMLNS, Host}, _Module, _Function} ->
+ catch mod_disco:register_feature(Host, XMLNS);
+ _ -> ok
+ end
+ end,
+ ets:tab2list(?IQTABLE)),
{noreply, State};
handle_info({timeout, _TRef, ID}, State) ->
process_iq_timeout(ID),
{noreply, State};
-handle_info(_Info, State) ->
- {noreply, State}.
-
%%--------------------------------------------------------------------
%% Function: terminate(Reason, State) -> void()
%% Description: This function is called by a gen_server when it is about to
@@ -283,48 +279,43 @@ handle_info(_Info, State) ->
%% cleaning up. When it returns, the gen_server terminates with Reason.
%% The return value is ignored.
%%--------------------------------------------------------------------
-terminate(_Reason, _State) ->
- ok.
-
%%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
%% Description: Convert process state when code is changed
%%--------------------------------------------------------------------
-code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
-
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
+handle_info(_Info, State) -> {noreply, State}.
+
+terminate(_Reason, _State) -> ok.
+
+code_change(_OldVsn, State, _Extra) -> {ok, State}.
+
do_route(From, To, Packet) ->
- ?DEBUG("local route~n\tfrom ~p~n\tto ~p~n\tpacket ~P~n",
+ ?DEBUG("local route~n\tfrom ~p~n\tto ~p~n\tpacket "
+ "~P~n",
[From, To, Packet, 8]),
- if
- To#jid.luser /= "" ->
- ejabberd_sm:route(From, To, Packet);
- To#jid.lresource == "" ->
- {xmlelement, Name, _Attrs, _Els} = Packet,
- case Name of
- "iq" ->
- process_iq(From, To, Packet);
- "message" ->
- ok;
- "presence" ->
- ok;
- _ ->
- ok
- end;
- true ->
- {xmlelement, _Name, Attrs, _Els} = Packet,
- case xml:get_attr_s("type", Attrs) of
- "error" -> ok;
- "result" -> ok;
- _ ->
- ejabberd_hooks:run(local_send_to_resource_hook,
- To#jid.lserver,
- [From, To, Packet])
- end
- end.
+ if To#jid.luser /= <<"">> ->
+ ejabberd_sm:route(From, To, Packet);
+ To#jid.lresource == <<"">> ->
+ #xmlel{name = Name} = Packet,
+ case Name of
+ <<"iq">> -> process_iq(From, To, Packet);
+ <<"message">> -> ok;
+ <<"presence">> -> ok;
+ _ -> ok
+ end;
+ true ->
+ #xmlel{attrs = Attrs} = Packet,
+ case xml:get_attr_s(<<"type">>, Attrs) of
+ <<"error">> -> ok;
+ <<"result">> -> ok;
+ _ ->
+ ejabberd_hooks:run(local_send_to_resource_hook,
+ To#jid.lserver, [From, To, Packet])
+ end
+ end.
update_table() ->
case catch mnesia:table_info(iq_response, attributes) of
@@ -365,13 +356,7 @@ process_iq_timeout() ->
cancel_timer(TRef) ->
case erlang:cancel_timer(TRef) of
- false ->
- receive
- {timeout, TRef, _} ->
- ok
- after 0 ->
- ok
- end;
- _ ->
- ok
+ false ->
+ receive {timeout, TRef, _} -> ok after 0 -> ok end;
+ _ -> ok
end.
diff --git a/src/ejabberd_node_groups.erl b/src/ejabberd_node_groups.erl
index 84c1d69ca..371a1bc28 100644
--- a/src/ejabberd_node_groups.erl
+++ b/src/ejabberd_node_groups.erl
@@ -60,20 +60,20 @@ start_link() ->
join(Name) ->
PG = {?MODULE, Name},
- ?PG2:create(PG),
- ?PG2:join(PG, whereis(?MODULE)).
+ pg2:create(PG),
+ pg2:join(PG, whereis(?MODULE)).
leave(Name) ->
PG = {?MODULE, Name},
- ?PG2:leave(PG, whereis(?MODULE)).
+ pg2:leave(PG, whereis(?MODULE)).
get_members(Name) ->
PG = {?MODULE, Name},
- [node(P) || P <- ?PG2:get_members(PG)].
+ [node(P) || P <- pg2:get_members(PG)].
get_closest_node(Name) ->
PG = {?MODULE, Name},
- node(?PG2:get_closest_pid(PG)).
+ node(pg2:get_closest_pid(PG)).
%%====================================================================
%% gen_server callbacks
@@ -88,7 +88,7 @@ get_closest_node(Name) ->
%%--------------------------------------------------------------------
init([]) ->
{FE, BE} =
- case ejabberd_config:get_local_option(node_type) of
+ case ejabberd_config:get_local_option(node_type, fun(N) -> N end) of
frontend ->
{true, false};
backend ->
diff --git a/src/ejabberd_piefxis.erl b/src/ejabberd_piefxis.erl
index b193dc67c..668f1ba62 100644
--- a/src/ejabberd_piefxis.erl
+++ b/src/ejabberd_piefxis.erl
@@ -3,6 +3,10 @@
%%% Author : Pablo Polvorin, Vidal Santiago Martinez
%%% Purpose : XEP-0227: Portable Import/Export Format for XMPP-IM Servers
%%% Created : 17 Jul 2008 by Pablo Polvorin <pablo.polvorin@process-one.net>
+%%%-------------------------------------------------------------------
+%%% @author Evgeniy Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2012, Evgeniy Khramtsov
+%%% @doc
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
@@ -31,239 +35,85 @@
%%% - XEP-227: 6. Security Considerations
%%% - Other schemas of XInclude are not tested, and may not be imported correctly.
%%% - If a host has many users, split that host in XML files with 50 users each.
-
%%%% Headers
-module(ejabberd_piefxis).
+%% API
-export([import_file/1, export_server/1, export_host/2]).
--record(parsing_state, {parser, host, dir}).
+-define(CHUNK_SIZE, 1024*20). %20k
-include("ejabberd.hrl").
+-include("jlib.hrl").
+-include("mod_privacy.hrl").
+-include("mod_roster.hrl").
%%-include_lib("exmpp/include/exmpp.hrl").
%%-include_lib("exmpp/include/exmpp_client.hrl").
%% Copied from exmpp header files:
--define(NS_ROSTER, "jabber:iq:roster").
--define(NS_VCARD, "vcard-temp").
--record(xmlcdata, {
- cdata = <<>>
- }).
--record(xmlattr, {
- ns = undefined,
- name,
- value
- }).
--record(xmlel, {
- ns = undefined,
- declared_ns = [],
- name,
- attrs = [],
- children = []
- }).
--record(iq, {
- kind,
- type,
- id,
- ns,
- payload,
- error,
- lang,
- iq_ns
- }).
--record(xmlendtag, {
- ns = undefined,
- name
- }).
-
-
%% Copied from mod_private.erl
--record(private_storage, {usns, xml}).
-
%%-define(ERROR_MSG(M,Args),io:format(M,Args)).
%%-define(INFO_MSG(M,Args),ok).
-
--define(CHUNK_SIZE,1024*20). %20k
-
--define(BTL, binary_to_list).
--define(LTB, list_to_binary).
-
--define(NS_XINCLUDE, 'http://www.w3.org/2001/XInclude').
-
%%%==================================
-
%%%% Import file
+-define(NS_PIE, <<"urn:xmpp:pie:0">>).
+-define(NS_PIEFXIS, <<"http://www.xmpp.org/extensions/xep-0227.html#ns">>).
+-define(NS_XI, <<"http://www.w3.org/2001/XInclude">>).
-import_file(FileName) ->
- _ = #xmlattr{}, %% this stupid line is only to prevent compilation warning about "recod xmlattr is unused"
- import_file(FileName, 2).
-
-import_file(FileName, RootDepth) ->
- try_start_exmpp(),
- Dir = filename:dirname(FileName),
- {ok, IO} = try_open_file(FileName),
- Parser = exmpp_xml:start_parser([{max_size,infinity},
- {root_depth, RootDepth},
- {emit_endtag,true}]),
- read_chunks(IO, #parsing_state{parser=Parser, dir=Dir}),
- file:close(IO),
- exmpp_xml:stop_parser(Parser).
-
-try_start_exmpp() ->
- try exmpp:start()
- catch
- error:{already_started, exmpp} -> ok;
- error:undef -> throw({error, exmpp_not_installed})
- end.
+-record(state, {xml_stream_state :: xml_stream:xml_stream_state(),
+ user = <<"">> :: binary(),
+ server = <<"">> :: binary(),
+ fd :: file:io_device(),
+ dir = <<"">> :: binary()}).
-try_open_file(FileName) ->
- case file:open(FileName,[read,binary]) of
- {ok, IO} -> {ok, IO};
- {error, enoent} -> throw({error, {file_not_found, FileName}})
- end.
+-type state() :: #state{}.
%%File could be large.. we read it in chunks
-read_chunks(IO,State) ->
- case file:read(IO,?CHUNK_SIZE) of
- {ok,Chunk} ->
- NewState = process_chunk(Chunk,State),
- read_chunks(IO,NewState);
- eof ->
- ok
- end.
-
-process_chunk(Chunk,S =#parsing_state{parser=Parser}) ->
- case exmpp_xml:parse(Parser,Chunk) of
- continue ->
- S;
- XMLElements ->
- process_elements(XMLElements,S)
+%%%===================================================================
+%%% API
+%%%===================================================================
+import_file(FileName) ->
+ import_file(FileName, #state{}).
+
+-spec import_file(binary(), state()) -> ok | {error, atom()}.
+
+import_file(FileName, State) ->
+ case file:open(FileName, [read, binary]) of
+ {ok, Fd} ->
+ Dir = filename:dirname(FileName),
+ XMLStreamState = xml_stream:new(self(), infinity),
+ Res = process(State#state{xml_stream_state = XMLStreamState,
+ fd = Fd,
+ dir = Dir}),
+ file:close(Fd),
+ Res;
+ {error, Reason} ->
+ ErrTxt = file:format_error(Reason),
+ ?ERROR_MSG("Failed to open file '~s': ~s", [FileName, ErrTxt]),
+ {error, Reason}
end.
%%%==================================
%%%% Process Elements
-
-process_elements(Elements,State) ->
- lists:foldl(fun process_element/2,State,Elements).
-
%%%==================================
%%%% Process Element
-
-process_element(El=#xmlel{name=user, ns=_XMLNS},
- State=#parsing_state{host=Host}) ->
- case add_user(El,Host) of
- ok -> ok;
- {error, _Other} -> error
- end,
- State;
-
-process_element(H=#xmlel{name=host},State) ->
- State#parsing_state{host=?BTL(exmpp_xml:get_attribute(H, <<"jid">>, none))};
-
-process_element(#xmlel{name='server-data'},State) ->
- State;
-
-process_element(El=#xmlel{name=include, ns=?NS_XINCLUDE}, State=#parsing_state{dir=Dir}) ->
- case exmpp_xml:get_attribute(El, <<"href">>, none) of
- none ->
- ok;
- HrefB ->
- Href = binary_to_list(HrefB),
- %%?INFO_MSG("Parse also this file: ~n~p", [Href]),
- FileName = filename:join([Dir, Href]),
- import_file(FileName, 1),
- Href
- end,
- State;
-
-process_element(#xmlcdata{cdata = _CData},State) ->
- State;
-
-process_element(#xmlendtag{ns = _NS, name='server-data'},State) ->
- State;
-
-process_element(#xmlendtag{ns = _NS, name=_Name},State) ->
- State;
-
-process_element(El,State) ->
- io:format("Warning!: unknown element found: ~p ~n",[El]),
- State.
-
%%%==================================
%%%% Add user
-
-add_user(El, Domain) ->
- User = exmpp_xml:get_attribute(El, <<"name">>, none),
- PasswordFormat = exmpp_xml:get_attribute(El, <<"password-format">>, <<"plaintext">>),
- Password = exmpp_xml:get_attribute(El, <<"password">>, none),
- add_user(El, Domain, User, PasswordFormat, Password).
-
%% @spec (El::xmlel(), Domain::string(), User::binary(), Password::binary() | none)
%% -> ok | {error, ErrorText::string()}
%% @doc Add a new user to the database.
%% If user already exists, it will be only updated.
-add_user(El, Domain, UserBinary, <<"plaintext">>, none) ->
- User = ?BTL(UserBinary),
- io:format("Account ~s@~s will not be created, updating it...~n",
- [User, Domain]),
- io:format(""),
- populate_user_with_elements(El, Domain, User),
- ok;
-add_user(El, Domain, UserBinary, PasswordFormat, PasswordBinary) ->
- User = ?BTL(UserBinary),
- Password2 = prepare_password(PasswordFormat, PasswordBinary, El),
- case create_user(User,Password2,Domain) of
- ok ->
- populate_user_with_elements(El, Domain, User),
- ok;
- {atomic, exists} ->
- io:format("Account ~s@~s already exists, updating it...~n",
- [User, Domain]),
- io:format(""),
- populate_user_with_elements(El, Domain, User),
- ok;
- {error, Other} ->
- ?ERROR_MSG("Error adding user ~s@~s: ~p~n", [User, Domain, Other]),
- {error, Other}
- end.
-
-prepare_password(<<"plaintext">>, PasswordBinary, _El) ->
- ?BTL(PasswordBinary);
-prepare_password(<<"scram">>, none, El) ->
- ScramEl = exmpp_xml:get_element(El, 'scram-hash'),
- #scram{storedkey = base64:decode(exmpp_xml:get_attribute(
- ScramEl, <<"stored-key">>, none)),
- serverkey = base64:decode(exmpp_xml:get_attribute(
- ScramEl, <<"server-key">>, none)),
- salt = base64:decode(exmpp_xml:get_attribute(
- ScramEl, <<"salt">>, none)),
- iterationcount = list_to_integer(exmpp_xml:get_attribute_as_list(
- ScramEl, <<"iteration-count">>,
- ?SCRAM_DEFAULT_ITERATION_COUNT))
- }.
-
-populate_user_with_elements(El, Domain, User) ->
- exmpp_xml:foreach(
- fun (_,Child) ->
- populate_user(User,Domain,Child)
- end,
- El).
+-spec export_server(binary()) -> any().
%% @spec (User::string(), Password::string(), Domain::string())
%% -> ok | {atomic, exists} | {error, not_allowed}
%% @doc Create a new user
-create_user(User,Password,Domain) ->
- case ejabberd_auth:try_register(User,Domain,Password) of
- {atomic,ok} -> ok;
- {atomic, exists} -> {atomic, exists};
- {error, not_allowed} -> {error, not_allowed};
- Other -> {error, Other}
- end.
+export_server(Dir) ->
+ export_hosts(?MYHOSTS, Dir).
%%%==================================
%%%% Populate user
-
%% @spec (User::string(), Domain::string(), El::xml())
%% -> ok | {error, not_found}
%%
@@ -286,28 +136,10 @@ create_user(User,Password,Domain) ->
%% </host>
%% </server-data>
%% '''
+-spec export_host(binary(), binary()) -> any().
-populate_user(User,Domain,El=#xmlel{name='query', ns='jabber:iq:roster'}) ->
- io:format("Trying to add/update roster list...",[]),
- case loaded_module(Domain, mod_roster) of
- {ok, _DBType} ->
- case mod_roster:set_items(User, Domain,
- exmpp_xml:xmlel_to_xmlelement(El)) of
- {atomic, ok} ->
- io:format(" DONE.~n",[]),
- ok;
- _ ->
- io:format(" ERROR.~n",[]),
- ?ERROR_MSG("Error trying to add a new user: ~s ~n",
- [exmpp_xml:document_to_list(El)]),
- {error, not_found}
- end;
- E -> io:format(" ERROR: ~p~n",[E]),
- ?ERROR_MSG("No modules loaded [mod_roster] ~s ~n",
- [exmpp_xml:document_to_list(El)]),
- {error, not_found}
- end;
-
+export_host(Dir, Host) ->
+ export_hosts([Host], Dir).
%% @spec User = String with the user name
%% Domain = String with a domain name
@@ -328,180 +160,471 @@ populate_user(User,Domain,El=#xmlel{name='query', ns='jabber:iq:roster'}) ->
%% </host>
%% </server-data>
%% '''
-
-populate_user(User,Domain,El=#xmlel{name='vCard', ns='vcard-temp'}) ->
- io:format("Trying to add/update vCards...",[]),
- case loaded_module(Domain, mod_vcard) of
- {ok, _} -> FullUser = jid_to_old_jid(exmpp_jid:make(User, Domain)),
- IQ = iq_to_old_iq(#iq{type = set, payload = El}),
- case mod_vcard:process_sm_iq(FullUser, FullUser , IQ) of
- {error,_Err} ->
- io:format(" ERROR.~n",[]),
- ?ERROR_MSG("Error processing vcard ~s : ~p ~n",
- [exmpp_xml:document_to_list(El), _Err]);
- _ ->
- io:format(" DONE.~n",[]), ok
- end;
- _ ->
- io:format(" ERROR.~n",[]),
- ?ERROR_MSG("No modules loaded [mod_vcard] ~s ~n",
- [exmpp_xml:document_to_list(El)]),
- {error, not_found}
- end;
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+export_hosts(Hosts, Dir) ->
+ FnT = make_filename_template(),
+ DFn = make_main_basefilename(Dir, FnT),
+ case file:open(DFn, [raw, write]) of
+ {ok, Fd} ->
+ print(Fd, make_piefxis_xml_head()),
+ print(Fd, make_piefxis_server_head()),
+ FilesAndHosts = [{make_host_filename(FnT, Host), Host}
+ || Host <- Hosts],
+ lists:foreach(
+ fun({FnH, _}) ->
+ print(Fd, make_xinclude(FnH))
+ end, FilesAndHosts),
+ print(Fd, make_piefxis_server_tail()),
+ print(Fd, make_piefxis_xml_tail()),
+ file:close(Fd),
+ lists:foldl(
+ fun({FnH, Host}, ok) ->
+ export_host(Dir, FnH, Host);
+ (_, Err) ->
+ Err
+ end, ok, FilesAndHosts);
+ {error, Reason} ->
+ ErrTxt = file:format_error(Reason),
+ ?ERROR_MSG("Failed to open file '~s': ~s", [DFn, ErrTxt]),
+ {error, Reason}
+ end.
%% @spec User = String with the user name
%% Domain = String with a domain name
%% El = Sub XML element with offline messages values
%% @ret ok | {error, not_found}
%% @doc Read off-line message from the XML and send it to the server
-
-populate_user(User,Domain,El=#xmlel{name='offline-messages'}) ->
- io:format("Trying to add/update offline-messages...",[]),
- case loaded_module(Domain, mod_offline) of
- {ok, _DBType} ->
- ok = exmpp_xml:foreach(
- fun (_Element, {xmlcdata, _}) ->
- ok;
- (_Element, Child) ->
- From = exmpp_xml:get_attribute(Child, <<"from">>,none),
- FullFrom = jid_to_old_jid(exmpp_jid:parse(From)),
- FullUser = jid_to_old_jid(exmpp_jid:make(User,
- Domain)),
- OldChild = exmpp_xml:xmlel_to_xmlelement(Child),
- _R = mod_offline:store_packet(FullFrom, FullUser, OldChild)
- end, El), io:format(" DONE.~n",[]);
- _ ->
- io:format(" ERROR.~n",[]),
- ?ERROR_MSG("No modules loaded [mod_offline] ~s ~n",
- [exmpp_xml:document_to_list(El)]),
- {error, not_found}
- end;
+export_host(Dir, FnH, Host) ->
+ DFn = make_host_basefilename(Dir, FnH),
+ case file:open(DFn, [raw, write]) of
+ {ok, Fd} ->
+ print(Fd, make_piefxis_xml_head()),
+ print(Fd, make_piefxis_host_head(Host)),
+ Users = ejabberd_auth:get_vh_registered_users(Host),
+ case export_users(Users, Host, Fd) of
+ ok ->
+ print(Fd, make_piefxis_host_tail()),
+ print(Fd, make_piefxis_xml_tail()),
+ file:close(Fd),
+ ok;
+ Err ->
+ file:close(Fd),
+ file:delete(DFn),
+ Err
+ end;
+ {error, Reason} ->
+ ErrTxt = file:format_error(Reason),
+ ?ERROR_MSG("Failed to open file '~s': ~s", [DFn, ErrTxt]),
+ {error, Reason}
+ end.
%% @spec User = String with the user name
%% Domain = String with a domain name
%% El = Sub XML element with private storage values
%% @ret ok | {error, not_found}
%% @doc Private storage parsing
-
-populate_user(User,Domain,El=#xmlel{name='query', ns='jabber:iq:private'}) ->
- io:format("Trying to add/update private storage...",[]),
- case loaded_module(Domain, mod_private) of
- {ok, _DBType} ->
- FullUser = jid_to_old_jid(exmpp_jid:make(User, Domain)),
- IQ = iq_to_old_iq(#iq{type = set,
- ns = 'jabber:iq:private',
- kind = request,
- iq_ns = 'jabberd:client',
- payload = El}),
- case mod_private:process_sm_iq(FullUser, FullUser, IQ ) of
- {error, _Err} ->
- io:format(" ERROR.~n",[]),
- ?ERROR_MSG("Error processing private storage ~s : ~p ~n",
- [exmpp_xml:document_to_list(El), _Err]);
- _ -> io:format(" DONE.~n",[]), ok
- end;
- _ ->
- io:format(" ERROR.~n",[]),
- ?ERROR_MSG("No modules loaded [mod_private] ~s ~n",
- [exmpp_xml:document_to_list(El)]),
- {error, not_found}
+export_users([{User, _S}|Users], Server, Fd) ->
+ case export_user(User, Server, Fd) of
+ ok ->
+ export_users(Users, Server, Fd);
+ Err ->
+ Err
end;
-
-populate_user(_User, _Domain, #xmlcdata{cdata = _CData}) ->
- ok;
-
-populate_user(_User, _Domain, _El) ->
+export_users([], _Server, _Fd) ->
ok.
%%%==================================
%%%% Utilities
-
-loaded_module(Domain, Module) ->
- case gen_mod:is_loaded(Domain, Module) of
- true ->
- {ok, gen_mod:db_type(Domain, Module)};
- false ->
- {error, not_found}
+export_user(User, Server, Fd) ->
+ Pass = ejabberd_auth:get_password_s(User, Server),
+ Els = get_offline(User, Server) ++
+ get_vcard(User, Server) ++
+ get_privacy(User, Server) ++
+ get_roster(User, Server) ++
+ get_private(User, Server),
+ print(Fd, xml:element_to_binary(
+ #xmlel{name = <<"user">>,
+ attrs = [{<<"name">>, User},
+ {<<"password">>, Pass}],
+ children = Els})).
+
+get_vcard(User, Server) ->
+ JID = jlib:make_jid(User, Server, <<>>),
+ case mod_vcard:process_sm_iq(JID, JID, #iq{type = get}) of
+ #iq{type = result, sub_el = [_|_] = VCardEls} ->
+ VCardEls;
+ _ ->
+ []
end.
-jid_to_old_jid(Jid) ->
- {jid, to_list(exmpp_jid:node_as_list(Jid)),
- to_list(exmpp_jid:domain_as_list(Jid)),
- to_list(exmpp_jid:resource_as_list(Jid)),
- to_list(exmpp_jid:prep_node_as_list(Jid)),
- to_list(exmpp_jid:prep_domain_as_list(Jid)),
- to_list(exmpp_jid:prep_resource_as_list(Jid))}.
-
-iq_to_old_iq(#iq{id = ID, type = Type, lang = Lang, ns= NS, payload = El }) ->
- {iq, to_list(ID), Type, to_list(NS), to_list(Lang),
- exmpp_xml:xmlel_to_xmlelement(El)}.
-
-to_list(L) when is_list(L) -> L;
-to_list(B) when is_binary(B) -> binary_to_list(B);
-to_list(undefined) -> "";
-to_list(B) when is_atom(B) -> atom_to_list(B).
-
%%%==================================
+get_offline(User, Server) ->
+ case mod_offline:get_offline_els(User, Server) of
+ [] ->
+ [];
+ Els ->
+ NewEls = lists:map(
+ fun(#xmlel{attrs = Attrs} = El) ->
+ NewAttrs = lists:keystore(<<"xmlns">>, 1,
+ Attrs,
+ {<<"xmlns">>,
+ <<"jabber:client">>}),
+ El#xmlel{attrs = NewAttrs}
+ end, Els),
+ [#xmlel{name = <<"offline-messages">>, children = NewEls}]
+ end.
%%%% Export hosts
+get_privacy(User, Server) ->
+ case mod_privacy:get_user_lists(User, Server) of
+ {ok, #privacy{default = Default,
+ lists = [_|_] = Lists}} ->
+ XLists = lists:map(
+ fun({Name, Items}) ->
+ XItems = lists:map(
+ fun mod_privacy:item_to_xml/1, Items),
+ #xmlel{name = <<"list">>,
+ attrs = [{<<"name">>, Name}],
+ children = XItems}
+ end, Lists),
+ DefaultEl = case Default of
+ none ->
+ [];
+ _ ->
+ [#xmlel{name = <<"default">>,
+ attrs = [{<<"name">>, Default}]}]
+ end,
+ [#xmlel{name = <<"query">>,
+ attrs = [{<<"xmlns">>, ?NS_PRIVACY}],
+ children = DefaultEl ++ XLists}];
+ _ ->
+ []
+ end.
%% @spec (Dir::string(), Hosts::[string()]) -> ok
-export_hosts(Dir, Hosts) ->
- try_start_exmpp(),
+get_roster(User, Server) ->
+ JID = jlib:make_jid(User, Server, <<>>),
+ case mod_roster:get_roster(User, Server) of
+ [_|_] = Items ->
+ Subs =
+ lists:flatmap(
+ fun(#roster{ask = Ask,
+ askmessage = Msg} = R)
+ when Ask == in; Ask == both ->
+ Status = if is_binary(Msg) -> (Msg);
+ true -> <<"">>
+ end,
+ [#xmlel{name = <<"presence">>,
+ attrs =
+ [{<<"from">>,
+ jlib:jid_to_string(R#roster.jid)},
+ {<<"to">>, jlib:jid_to_string(JID)},
+ {<<"xmlns">>, <<"jabber:client">>},
+ {<<"type">>, <<"subscribe">>}],
+ children =
+ [#xmlel{name = <<"status">>,
+ attrs = [],
+ children =
+ [{xmlcdata, Status}]}]}];
+ (_) ->
+ []
+ end, Items),
+ Rs = lists:flatmap(
+ fun(#roster{ask = in, subscription = none}) ->
+ [];
+ (R) ->
+ [mod_roster:item_to_xml(R)]
+ end, Items),
+ [#xmlel{name = <<"query">>,
+ attrs = [{<<"xmlns">>, ?NS_ROSTER}],
+ children = Rs} | Subs];
+ _ ->
+ []
+ end.
- FnT = make_filename_template(),
- DFn = make_main_basefilename(Dir, FnT),
+get_private(User, Server) ->
+ case mod_private:get_data(User, Server) of
+ [_|_] = Els ->
+ [#xmlel{name = <<"query">>,
+ attrs = [{<<"xmlns">>, ?NS_PRIVATE}],
+ children = Els}];
+ _ ->
+ []
+ end.
- {ok, Fd} = file_open(DFn),
- print(Fd, make_piefxis_xml_head()),
- print(Fd, make_piefxis_server_head()),
+process(#state{xml_stream_state = XMLStreamState, fd = Fd} = State) ->
+ case file:read(Fd, ?CHUNK_SIZE) of
+ {ok, Data} ->
+ NewXMLStreamState = xml_stream:parse(XMLStreamState, Data),
+ case process_els(State#state{xml_stream_state =
+ NewXMLStreamState}) of
+ {ok, NewState} ->
+ process(NewState);
+ Err ->
+ xml_stream:close(NewXMLStreamState),
+ Err
+ end;
+ eof ->
+ xml_stream:close(XMLStreamState),
+ ok
+ end.
- FilesAndHosts = [{make_host_filename(FnT, Host), Host} || Host <- Hosts],
- [print(Fd, make_xinclude(FnH)) || {FnH, _Host} <- FilesAndHosts],
+process_els(State) ->
+ receive
+ {'$gen_event', El} ->
+ case process_el(El, State) of
+ {ok, NewState} ->
+ process_els(NewState);
+ Err ->
+ Err
+ end
+ after 0 ->
+ {ok, State}
+ end.
- print(Fd, make_piefxis_server_tail()),
- print(Fd, make_piefxis_xml_tail()),
- file_close(Fd),
+process_el({xmlstreamstart, <<"server-data">>, Attrs}, State) ->
+ case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ ?NS_PIEFXIS ->
+ {ok, State};
+ ?NS_PIE ->
+ {ok, State};
+ NS ->
+ stop("Unknown 'server-data' namespace = ~s", [NS])
+ end;
+process_el({xmlstreamend, _}, State) ->
+ {ok, State};
+process_el({xmlstreamcdata, _}, State) ->
+ {ok, State};
+process_el({xmlstreamelement, #xmlel{name = <<"xi:include">>,
+ attrs = Attrs}},
+ #state{dir = Dir, user = <<"">>} = State) ->
+ FileName = xml:get_attr_s(<<"href">>, Attrs),
+ case import_file(filename:join([Dir, FileName]), State) of
+ ok ->
+ {ok, State};
+ Err ->
+ Err
+ end;
+process_el({xmlstreamstart, <<"host">>, Attrs}, State) ->
+ process_el({xmlstreamelement, #xmlel{name = <<"host">>,
+ attrs = Attrs}}, State);
+process_el({xmlstreamelement, #xmlel{name = <<"host">>,
+ attrs = Attrs,
+ children = Els}}, State) ->
+ JIDS = xml:get_attr_s(<<"jid">>, Attrs),
+ case jlib:string_to_jid(JIDS) of
+ #jid{lserver = S} ->
+ case lists:member(S, ?MYHOSTS) of
+ true ->
+ process_users(Els, State#state{server = S});
+ false ->
+ stop("Unknown host: ~s", [S])
+ end;
+ error ->
+ stop("Invalid 'jid': ~s", [JIDS])
+ end;
+process_el({xmlstreamstart, <<"user">>, Attrs}, State = #state{server = S})
+ when S /= <<"">> ->
+ process_el({xmlstreamelement, #xmlel{name = <<"user">>, attrs = Attrs}},
+ State);
+process_el({xmlstreamelement, #xmlel{name = <<"user">>} = El},
+ State = #state{server = S}) when S /= <<"">> ->
+ process_user(El, State);
+process_el({xmlstreamelement, El}, State = #state{server = S, user = U})
+ when S /= <<"">>, U /= <<"">> ->
+ process_user_el(El, State);
+process_el({xmlstreamelement, El}, _State) ->
+ stop("Unexpected tag: ~p", [El]);
+process_el({xmlstreamstart, El, Attrs}, _State) ->
+ stop("Unexpected payload: ~p", [{El, Attrs}]);
+process_el({xmlstreamerror, Err}, _State) ->
+ stop("Failed to process element = ~p", [Err]).
+
+process_users([#xmlel{} = El|Els], State) ->
+ case process_user(El, State) of
+ {ok, NewState} ->
+ process_users(Els, NewState);
+ Err ->
+ Err
+ end;
+process_users([_|Els], State) ->
+ process_users(Els, State);
+process_users([], State) ->
+ {ok, State}.
+
+process_user(#xmlel{name = <<"user">>, attrs = Attrs, children = Els},
+ #state{server = LServer} = State) ->
+ Name = xml:get_attr_s(<<"name">>, Attrs),
+ Pass = xml:get_attr_s(<<"password">>, Attrs),
+ case jlib:nodeprep(Name) of
+ error ->
+ stop("Invalid 'user': ~s", [Name]);
+ LUser ->
+ case ejabberd_auth:try_register(LUser, LServer, Pass) of
+ {atomic, _} ->
+ process_user_els(Els, State#state{user = LUser});
+ Err ->
+ stop("Failed to create user '~s': ~p", [Name, Err])
+ end
+ end.
- [export_host(Dir, FnH, Host) || {FnH, Host} <- FilesAndHosts],
+process_user_els([#xmlel{} = El|Els], State) ->
+ case process_user_el(El, State) of
+ {ok, NewState} ->
+ process_user_els(Els, NewState);
+ Err ->
+ Err
+ end;
+process_user_els([_|Els], State) ->
+ process_user_els(Els, State);
+process_user_els([], State) ->
+ {ok, State}.
+
+process_user_el(#xmlel{name = Name, attrs = Attrs, children = Els} = El,
+ State) ->
+ case {Name, xml:get_attr_s(<<"xmlns">>, Attrs)} of
+ {<<"query">>, ?NS_ROSTER} ->
+ process_roster(El, State);
+ {<<"query">>, ?NS_PRIVACY} ->
+ %% Make sure <list/> elements go before <active/> and <default/>
+ NewEls = lists:reverse(lists:keysort(#xmlel.name, Els)),
+ process_privacy_el(El#xmlel{children = NewEls}, State);
+ {<<"query">>, ?NS_PRIVATE} ->
+ process_private(El, State);
+ {<<"vCard">>, ?NS_VCARD} ->
+ process_vcard(El, State);
+ {<<"offline-messages">>, _} ->
+ process_offline_msgs(Els, State);
+ {<<"presence">>, <<"jabber:client">>} ->
+ process_presence(El, State);
+ _ ->
+ {ok, State}
+ end.
- ok.
+process_privacy_el(#xmlel{children = [#xmlel{} = SubEl|SubEls]} = El, State) ->
+ case process_privacy(#xmlel{children = [SubEl]}, State) of
+ {ok, NewState} ->
+ process_privacy_el(El#xmlel{children = SubEls}, NewState);
+ Err ->
+ Err
+ end;
+process_privacy_el(#xmlel{children = [_|SubEls]} = El, State) ->
+ process_privacy_el(El#xmlel{children = SubEls}, State);
+process_privacy_el(#xmlel{children = []}, State) ->
+ {ok, State}.
+
+process_offline_msgs([#xmlel{} = El|Els], State) ->
+ case process_offline_msg(El, State) of
+ {ok, NewState} ->
+ process_offline_msgs(Els, NewState);
+ Err ->
+ Err
+ end;
+process_offline_msgs([_|Els], State) ->
+ process_offline_msgs(Els, State);
+process_offline_msgs([], State) ->
+ {ok, State}.
+
+process_roster(El, State = #state{user = U, server = S}) ->
+ case mod_roster:set_items(U, S, El) of
+ {atomic, _} ->
+ {ok, State};
+ Err ->
+ stop("Failed to write roster: ~p", [Err])
+ end.
%%%==================================
%%%% Export server
+process_privacy(El, State = #state{user = U, server = S}) ->
+ JID = jlib:make_jid(U, S, <<"">>),
+ case mod_privacy:process_iq_set(
+ [], JID, JID, #iq{type = set, sub_el = El}) of
+ {error, _} = Err ->
+ stop("Failed to write privacy: ~p", [Err]);
+ _ ->
+ {ok, State}
+ end.
%% @spec (Dir::string()) -> ok
-export_server(Dir) ->
- Hosts = ?MYHOSTS,
- export_hosts(Dir, Hosts).
+process_private(El, State = #state{user = U, server = S}) ->
+ JID = jlib:make_jid(U, S, <<"">>),
+ case mod_private:process_sm_iq(
+ JID, JID, #iq{type = set, sub_el = El}) of
+ #iq{type = result} ->
+ {ok, State};
+ Err ->
+ stop("Failed to write private: ~p", [Err])
+ end.
%%%==================================
%%%% Export host
+process_vcard(El, State = #state{user = U, server = S}) ->
+ JID = jlib:make_jid(U, S, <<"">>),
+ case mod_vcard:process_sm_iq(
+ JID, JID, #iq{type = set, sub_el = El}) of
+ #iq{type = result} ->
+ {ok, State};
+ Err ->
+ stop("Failed to write vcard: ~p", [Err])
+ end.
%% @spec (Dir::string(), Host::string()) -> ok
-export_host(Dir, Host) ->
- Hosts = [Host],
- export_hosts(Dir, Hosts).
+process_offline_msg(El, State = #state{user = U, server = S}) ->
+ FromS = xml:get_attr_s(<<"from">>, El#xmlel.attrs),
+ case jlib:string_to_jid(FromS) of
+ #jid{} = From ->
+ To = jlib:make_jid(U, S, <<>>),
+ NewEl = jlib:replace_from_to(From, To, El),
+ case catch mod_offline:store_packet(From, To, NewEl) of
+ {'EXIT', _} = Err ->
+ stop("Failed to store offline message: ~p", [Err]);
+ _ ->
+ {ok, State}
+ end;
+ _ ->
+ stop("Invalid 'from' = ~s", [FromS])
+ end.
%% @spec (Dir::string(), Fn::string(), Host::string()) -> ok
-export_host(Dir, FnH, Host) ->
+process_presence(El, #state{user = U, server = S} = State) ->
+ FromS = xml:get_attr_s(<<"from">>, El#xmlel.attrs),
+ case jlib:string_to_jid(FromS) of
+ #jid{} = From ->
+ To = jlib:make_jid(U, S, <<>>),
+ NewEl = jlib:replace_from_to(From, To, El),
+ ejabberd_router:route(From, To, NewEl),
+ {ok, State};
+ _ ->
+ stop("Invalid 'from' = ~s", [FromS])
+ end.
- DFn = make_host_basefilename(Dir, FnH),
+stop(Fmt, Args) ->
+ ?ERROR_MSG(Fmt, Args),
+ {error, import_failed}.
- {ok, Fd} = file_open(DFn),
- print(Fd, make_piefxis_xml_head()),
- print(Fd, make_piefxis_host_head(Host)),
+make_filename_template() ->
+ {{Year, Month, Day}, {Hour, Minute, Second}} = calendar:local_time(),
+ list_to_binary(
+ io_lib:format("~4..0w~2..0w~2..0w-~2..0w~2..0w~2..0w",
+ [Year, Month, Day, Hour, Minute, Second])).
- Users = ejabberd_auth:get_vh_registered_users(Host),
- [export_user(Fd, Username, Host) || {Username, _Host} <- Users],
- timer:sleep(500), % Delay to ensure ERROR_MSG are displayed in the shell
+make_main_basefilename(Dir, FnT) ->
+ Filename2 = <<FnT/binary, ".xml">>,
+ filename:join([Dir, Filename2]).
- print(Fd, make_piefxis_host_tail()),
- print(Fd, make_piefxis_xml_tail()),
- file_close(Fd).
+%% @doc Make the filename for the host.
+%% Example: ``(<<"20080804-231550">>, <<"jabber.example.org">>) ->
+%% <<"20080804-231550_jabber_example_org.xml">>''
+make_host_filename(FnT, Host) ->
+ Host2 = str:join(str:tokens(Host, <<".">>), <<"_">>),
+ <<FnT/binary, "_", Host2/binary, ".xml">>.
%%%==================================
%%%% PIEFXIS formatting
+make_host_basefilename(Dir, FnT) ->
+ filename:join([Dir, FnT]).
%% @spec () -> string()
make_piefxis_xml_head() ->
@@ -513,9 +636,8 @@ make_piefxis_xml_tail() ->
%% @spec () -> string()
make_piefxis_server_head() ->
- "<server-data"
- " xmlns='http://www.xmpp.org/extensions/xep-0227.html#ns'"
- " xmlns:xi='http://www.w3.org/2001/XInclude'>".
+ io_lib:format("<server-data xmlns='~s' xmlns:xi='~s'>",
+ [?NS_PIE, ?NS_XI]).
%% @spec () -> string()
make_piefxis_server_tail() ->
@@ -523,10 +645,8 @@ make_piefxis_server_tail() ->
%% @spec (Host::string()) -> string()
make_piefxis_host_head(Host) ->
- NSString =
- " xmlns='http://www.xmpp.org/extensions/xep-0227.html#ns'"
- " xmlns:xi='http://www.w3.org/2001/XInclude'",
- io_lib:format("<host~s jid='~s'>", [NSString, Host]).
+ io_lib:format("<host xmlns='~s' xmlns:xi='~s' jid='~s'>",
+ [?NS_PIE, ?NS_XI, Host]).
%% @spec () -> string()
make_piefxis_host_tail() ->
@@ -539,196 +659,26 @@ make_xinclude(Fn) ->
%%%==================================
%%%% Export user
-
%% @spec (Fd, Username::string(), Host::string()) -> ok
%% @doc Extract user information and print it.
-export_user(Fd, Username, Host) ->
- try extract_user(Username, Host) of
- UserString ->
- print(Fd, UserString)
- catch
- E1:E2 ->
- ?ERROR_MSG("The account ~s@~s is not exported because a problem "
- "was found in it:~n~p: ~p", [Username, Host, E1, E2])
- end.
-
%% @spec (Username::string(), Host::string()) -> string()
-extract_user(Username, Host) ->
- Password = ejabberd_auth:get_password(Username, Host),
- PasswordStr = build_password_string(Password),
- UserInfo = [extract_user_info(InfoName, Username, Host) || InfoName <- [roster, offline, private, vcard]],
- UserInfoString = lists:flatten(UserInfo),
- io_lib:format("<user name='~s' ~s ~s</user>",
- [Username, PasswordStr, UserInfoString]).
-
-build_password_string({StoredKey, ServerKey, Salt, IterationCount}) ->
- io_lib:format("password-format='scram'>"
- "<scram-hash stored-key='~s' server-key='~s' "
- "salt='~s' iteration-count='~w'/> ",
- [base64:encode_to_string(StoredKey),
- base64:encode_to_string(ServerKey),
- base64:encode_to_string(Salt),
- IterationCount]);
-build_password_string(Password) when is_list(Password) ->
- io_lib:format("password-format='plaintext' password='~s'>", [Password]).
-
%% @spec (InfoName::atom(), Username::string(), Host::string()) -> string()
-extract_user_info(roster, Username, Host) ->
- case loaded_module(Host, mod_roster) of
- {ok, _DBType} ->
- From = To = jlib:make_jid(Username, Host, ""),
- SubelGet = {xmlelement, "query", [{"xmlns",?NS_ROSTER}], []},
- %%IQGet = #iq{type=get, xmlns=?NS_ROSTER, payload=SubelGet}, % this is for 3.0.0 version
- IQGet = {iq, "", get, ?NS_ROSTER, "" , SubelGet},
- Res = mod_roster:process_local_iq(From, To, IQGet),
- %%[El] = Res#iq.payload, % this is for 3.0.0 version
- {iq, _, result, _, _, Els} = Res,
- case Els of
- [El] -> exmpp_xml:document_to_list(El);
- [] -> ""
- end;
- _E ->
- ""
- end;
-
-extract_user_info(offline, Username, Host) ->
- case loaded_module(Host, mod_offline) of
- {ok, mnesia} ->
- Els = mnesia_pop_offline_messages([], Username, Host),
- case Els of
- [] -> "";
- Els ->
- OfEl = {xmlelement, "offline-messages", [], Els},
- exmpp_xml:document_to_list(OfEl)
- end;
- {ok, odbc} ->
- "";
- _E ->
- ""
- end;
-
-extract_user_info(private, Username, Host) ->
- case loaded_module(Host, mod_private) of
- {ok, mnesia} ->
- get_user_private_mnesia(Username, Host);
- {ok, odbc} ->
- "";
- _E ->
- ""
- end;
-
-extract_user_info(vcard, Username, Host) ->
- case loaded_module(Host, mod_vcard) of
- {ok, _DBType} ->
- From = To = jlib:make_jid(Username, Host, ""),
- SubelGet = {xmlelement, "vCard", [{"xmlns",?NS_VCARD}], []},
- %%IQGet = #iq{type=get, xmlns=?NS_VCARD, payload=SubelGet}, % this is for 3.0.0 version
- IQGet = {iq, "", get, ?NS_VCARD, "" , SubelGet},
- Res = mod_vcard:process_sm_iq(From, To, IQGet),
- %%[El] = Res#iq.payload, % this is for 3.0.0 version
- {iq, _, result, _, _, Els} = Res,
- case Els of
- [El] -> exmpp_xml:document_to_list(El);
- [] -> ""
- end;
- _E ->
- ""
- end.
-
%%%==================================
%%%% Interface with ejabberd offline storage
-
%% Copied from mod_offline.erl and customized
--record(offline_msg, {us, timestamp, expire, from, to, packet}).
-mnesia_pop_offline_messages(Ls, User, Server) ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
- US = {LUser, LServer},
- F = fun() ->
- Rs = mnesia:wread({offline_msg, US}),
- %%mnesia:delete({offline_msg, US}),
- Rs
- end,
- case mnesia:transaction(F) of
- {atomic, Rs} ->
- TS = now(),
- Ls ++ lists:map(
- fun(R) ->
- {xmlelement, Name, Attrs, Els} = R#offline_msg.packet,
- FromString = jlib:jid_to_string(R#offline_msg.from),
- Attrs2 = lists:keystore("from", 1, Attrs, {"from", FromString}),
- Attrs3 = lists:keystore("xmlns", 1, Attrs2, {"xmlns", "jabber:client"}),
- {xmlelement, Name, Attrs3,
- Els ++
- [jlib:timestamp_to_xml(
- calendar:now_to_universal_time(
- R#offline_msg.timestamp))]}
- end,
- lists:filter(
- fun(R) ->
- case R#offline_msg.expire of
- never ->
- true;
- TimeStamp ->
- TS < TimeStamp
- end
- end,
- lists:keysort(#offline_msg.timestamp, Rs)));
- _ ->
- Ls
- end.
-
%%%==================================
%%%% Interface with ejabberd private storage
-
-get_user_private_mnesia(Username, Host) ->
- ListNsEl = mnesia:dirty_select(private_storage,
- [{#private_storage{usns={Username, Host, '$1'}, xml = '$2'},
- [], ['$$']}]),
- Els = [exmpp_xml:document_to_list(El) || [_Ns, El] <- ListNsEl],
- case lists:flatten(Els) of
- "" -> "";
- ElsString ->
- io_lib:format("<query xmlns='jabber:iq:private'>~s</query>", [ElsString])
- end.
-
%%%==================================
%%%% Disk file access
-
%% @spec () -> string()
-make_filename_template() ->
- {{Year, Month, Day}, {Hour, Minute, Second}} = calendar:local_time(),
- lists:flatten(
- io_lib:format("~4..0w~2..0w~2..0w-~2..0w~2..0w~2..0w",
- [Year, Month, Day, Hour, Minute, Second])).
-
%% @spec (Dir::string(), FnT::string()) -> string()
-make_main_basefilename(Dir, FnT) ->
- Filename2 = filename:flatten([FnT, ".xml"]),
- filename:join([Dir, Filename2]).
-
%% @spec (FnT::string(), Host::string()) -> FnH::string()
%% @doc Make the filename for the host.
%% Example: ``("20080804-231550", "jabber.example.org") -> "20080804-231550_jabber_example_org.xml"''
-make_host_filename(FnT, Host) ->
- Host2 = string:join(string:tokens(Host, "."), "_"),
- filename:flatten([FnT, "_", Host2, ".xml"]).
-
-make_host_basefilename(Dir, FnT) ->
- filename:join([Dir, FnT]).
-
%% @spec (Fn::string()) -> {ok, Fd}
-file_open(Fn) ->
- file:open(Fn, [write]).
-
%% @spec (Fd) -> ok
-file_close(Fd) ->
- file:close(Fd).
-
%% @spec (Fd, String::string()) -> ok
print(Fd, String) ->
- io:format(Fd, String, []).
-
%%%==================================
-
%%% vim: set filetype=erlang tabstop=8 foldmarker=%%%%,%%%= foldmethod=marker:
+ file:write(Fd, String).
diff --git a/src/ejabberd_rdbms.erl b/src/ejabberd_rdbms.erl
index d0b20e6f7..abb17974c 100644
--- a/src/ejabberd_rdbms.erl
+++ b/src/ejabberd_rdbms.erl
@@ -25,54 +25,51 @@
%%%----------------------------------------------------------------------
-module(ejabberd_rdbms).
+
-author('alexey@process-one.net').
-export([start/0]).
+
-include("ejabberd.hrl").
start() ->
- %% Check if ejabberd has been compiled with ODBC
case catch ejabberd_odbc_sup:module_info() of
- {'EXIT',{undef,_}} ->
- ?INFO_MSG("ejabberd has not been compiled with relational database support. Skipping database startup.", []);
- _ ->
- %% If compiled with ODBC, start ODBC on the needed host
- start_hosts()
+ {'EXIT', {undef, _}} ->
+ ?INFO_MSG("ejabberd has not been compiled with "
+ "relational database support. Skipping "
+ "database startup.",
+ []);
+ _ -> start_hosts()
end.
%% Start relationnal DB module on the nodes where it is needed
start_hosts() ->
- lists:foreach(
- fun(Host) ->
- case needs_odbc(Host) of
- true -> start_odbc(Host);
- false -> ok
- end
- end, ?MYHOSTS).
+ lists:foreach(fun (Host) ->
+ case needs_odbc(Host) of
+ true -> start_odbc(Host);
+ false -> ok
+ end
+ end,
+ ?MYHOSTS).
%% Start the ODBC module on the given host
start_odbc(Host) ->
- Supervisor_name = gen_mod:get_module_proc(Host, ejabberd_odbc_sup),
- ChildSpec =
- {Supervisor_name,
- {ejabberd_odbc_sup, start_link, [Host]},
- transient,
- infinity,
- supervisor,
- [ejabberd_odbc_sup]},
+ Supervisor_name = gen_mod:get_module_proc(Host,
+ ejabberd_odbc_sup),
+ ChildSpec = {Supervisor_name,
+ {ejabberd_odbc_sup, start_link, [Host]}, transient,
+ infinity, supervisor, [ejabberd_odbc_sup]},
case supervisor:start_child(ejabberd_sup, ChildSpec) of
- {ok, _PID} ->
- ok;
- _Error ->
- ?ERROR_MSG("Start of supervisor ~p failed:~n~p~nRetrying...~n", [Supervisor_name, _Error]),
- start_odbc(Host)
+ {ok, _PID} -> ok;
+ _Error ->
+ ?ERROR_MSG("Start of supervisor ~p failed:~n~p~nRetrying."
+ "..~n",
+ [Supervisor_name, _Error]),
+ start_odbc(Host)
end.
%% Returns true if we have configured odbc_server for the given host
needs_odbc(Host) ->
LHost = jlib:nameprep(Host),
- case ejabberd_config:get_local_option({odbc_server, LHost}) of
- undefined ->
- false;
- _ -> true
- end.
+ ejabberd_config:get_local_option(
+ {odbc_server, LHost}, fun(_) -> true end, false).
diff --git a/src/ejabberd_receiver.erl b/src/ejabberd_receiver.erl
index 7e93feeb9..c9ed6b350 100644
--- a/src/ejabberd_receiver.erl
+++ b/src/ejabberd_receiver.erl
@@ -25,6 +25,7 @@
%%%----------------------------------------------------------------------
-module(ejabberd_receiver).
+
-author('alexey@process-one.net').
-behaviour(gen_server).
@@ -41,18 +42,19 @@
close/1]).
%% gen_server callbacks
--export([init/1, handle_call/3, handle_cast/2, handle_info/2,
- terminate/2, code_change/3]).
+-export([init/1, handle_call/3, handle_cast/2,
+ handle_info/2, terminate/2, code_change/3]).
-include("ejabberd.hrl").
--record(state, {socket,
- sock_mod,
- shaper_state,
- c2s_pid,
- max_stanza_size,
- xml_stream_state,
- timeout}).
+-record(state,
+ {socket :: inet:socket() | tls:tls_socket() | ejabberd_zlib:zlib_socket(),
+ sock_mod = gen_tcp :: gen_tcp | tls | ejabberd_zlib,
+ shaper_state = none :: shaper:shaper(),
+ c2s_pid :: pid(),
+ max_stanza_size = infinity :: non_neg_integer() | infinity,
+ xml_stream_state :: xml_stream:xml_stream_state(),
+ timeout = infinity:: timeout()}).
-define(HIBERNATE_TIMEOUT, 90000).
@@ -63,9 +65,16 @@
%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
%% Description: Starts the server
%%--------------------------------------------------------------------
+-spec start_link(inet:socket(), atom(), shaper:shaper(),
+ non_neg_integer() | infinity) -> ignore |
+ {error, any()} |
+ {ok, pid()}.
+
start_link(Socket, SockMod, Shaper, MaxStanzaSize) ->
- gen_server:start_link(
- ?MODULE, [Socket, SockMod, Shaper, MaxStanzaSize], []).
+ gen_server:start_link(?MODULE,
+ [Socket, SockMod, Shaper, MaxStanzaSize], []).
+
+-spec start(inet:socket(), atom(), shaper:shaper()) -> undefined | pid().
%%--------------------------------------------------------------------
%% Function: start() -> {ok,Pid} | ignore | {error,Error}
@@ -74,30 +83,46 @@ start_link(Socket, SockMod, Shaper, MaxStanzaSize) ->
start(Socket, SockMod, Shaper) ->
start(Socket, SockMod, Shaper, infinity).
+-spec start(inet:socket(), atom(), shaper:shaper(),
+ non_neg_integer() | infinity) -> undefined | pid().
+
start(Socket, SockMod, Shaper, MaxStanzaSize) ->
- {ok, Pid} = supervisor:start_child(
- ejabberd_receiver_sup,
- [Socket, SockMod, Shaper, MaxStanzaSize]),
+ {ok, Pid} =
+ supervisor:start_child(ejabberd_receiver_sup,
+ [Socket, SockMod, Shaper, MaxStanzaSize]),
Pid.
+-spec change_shaper(pid(), shaper:shaper()) -> ok.
+
change_shaper(Pid, Shaper) ->
gen_server:cast(Pid, {change_shaper, Shaper}).
-reset_stream(Pid) ->
- do_call(Pid, reset_stream).
+-spec reset_stream(pid()) -> ok | {error, any()}.
+
+reset_stream(Pid) -> do_call(Pid, reset_stream).
+
+-spec starttls(pid(), iodata()) -> {ok, tls:tls_socket()} | {error, any()}.
starttls(Pid, TLSSocket) ->
do_call(Pid, {starttls, TLSSocket}).
+-spec compress(pid(), iodata() | undefined) -> {error, any()} |
+ {ok, ejabberd_zlib:zlib_socket()}.
+
compress(Pid, ZlibSocket) ->
do_call(Pid, {compress, ZlibSocket}).
+-spec become_controller(pid(), pid()) -> ok | {error, any()}.
+
become_controller(Pid, C2SPid) ->
do_call(Pid, {become_controller, C2SPid}).
+-spec close(pid()) -> ok.
+
close(Pid) ->
gen_server:cast(Pid, close).
+
%%====================================================================
%% gen_server callbacks
%%====================================================================
@@ -112,16 +137,13 @@ close(Pid) ->
init([Socket, SockMod, Shaper, MaxStanzaSize]) ->
ShaperState = shaper:new(Shaper),
Timeout = case SockMod of
- ssl ->
- 20;
- _ ->
- infinity
+ ssl -> 20;
+ _ -> infinity
end,
- {ok, #state{socket = Socket,
- sock_mod = SockMod,
- shaper_state = ShaperState,
- max_stanza_size = MaxStanzaSize,
- timeout = Timeout}}.
+ {ok,
+ #state{socket = Socket, sock_mod = SockMod,
+ shaper_state = ShaperState,
+ max_stanza_size = MaxStanzaSize, timeout = Timeout}}.
%%--------------------------------------------------------------------
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
@@ -137,11 +159,12 @@ handle_call({starttls, TLSSocket}, _From,
c2s_pid = C2SPid,
max_stanza_size = MaxStanzaSize} = State) ->
close_stream(XMLStreamState),
- NewXMLStreamState = xml_stream:new(C2SPid, MaxStanzaSize),
+ NewXMLStreamState = xml_stream:new(C2SPid,
+ MaxStanzaSize),
NewState = State#state{socket = TLSSocket,
sock_mod = tls,
xml_stream_state = NewXMLStreamState},
- case tls:recv_data(TLSSocket, "") of
+ case tls:recv_data(TLSSocket, <<"">>) of
{ok, TLSData} ->
{reply, ok, process_data(TLSData, NewState), ?HIBERNATE_TIMEOUT};
{error, _Reason} ->
@@ -152,11 +175,12 @@ handle_call({compress, ZlibSocket}, _From,
c2s_pid = C2SPid,
max_stanza_size = MaxStanzaSize} = State) ->
close_stream(XMLStreamState),
- NewXMLStreamState = xml_stream:new(C2SPid, MaxStanzaSize),
+ NewXMLStreamState = xml_stream:new(C2SPid,
+ MaxStanzaSize),
NewState = State#state{socket = ZlibSocket,
sock_mod = ejabberd_zlib,
xml_stream_state = NewXMLStreamState},
- case ejabberd_zlib:recv_data(ZlibSocket, "") of
+ case ejabberd_zlib:recv_data(ZlibSocket, <<"">>) of
{ok, ZlibData} ->
{reply, ok, process_data(ZlibData, NewState), ?HIBERNATE_TIMEOUT};
{error, _Reason} ->
@@ -164,12 +188,14 @@ handle_call({compress, ZlibSocket}, _From,
end;
handle_call(reset_stream, _From,
#state{xml_stream_state = XMLStreamState,
- c2s_pid = C2SPid,
- max_stanza_size = MaxStanzaSize} = State) ->
+ c2s_pid = C2SPid, max_stanza_size = MaxStanzaSize} =
+ State) ->
close_stream(XMLStreamState),
- NewXMLStreamState = xml_stream:new(C2SPid, MaxStanzaSize),
+ NewXMLStreamState = xml_stream:new(C2SPid,
+ MaxStanzaSize),
Reply = ok,
- {reply, Reply, State#state{xml_stream_state = NewXMLStreamState},
+ {reply, Reply,
+ State#state{xml_stream_state = NewXMLStreamState},
?HIBERNATE_TIMEOUT};
handle_call({become_controller, C2SPid}, _From, State) ->
XMLStreamState = xml_stream:new(C2SPid, State#state.max_stanza_size),
@@ -179,8 +205,7 @@ handle_call({become_controller, C2SPid}, _From, State) ->
Reply = ok,
{reply, Reply, NewState, ?HIBERNATE_TIMEOUT};
handle_call(_Request, _From, State) ->
- Reply = ok,
- {reply, Reply, State, ?HIBERNATE_TIMEOUT}.
+ Reply = ok, {reply, Reply, State, ?HIBERNATE_TIMEOUT}.
%%--------------------------------------------------------------------
%% Function: handle_cast(Msg, State) -> {noreply, State} |
@@ -190,9 +215,9 @@ handle_call(_Request, _From, State) ->
%%--------------------------------------------------------------------
handle_cast({change_shaper, Shaper}, State) ->
NewShaperState = shaper:new(Shaper),
- {noreply, State#state{shaper_state = NewShaperState}, ?HIBERNATE_TIMEOUT};
-handle_cast(close, State) ->
- {stop, normal, State};
+ {noreply, State#state{shaper_state = NewShaperState},
+ ?HIBERNATE_TIMEOUT};
+handle_cast(close, State) -> {stop, normal, State};
handle_cast(_Msg, State) ->
{noreply, State, ?HIBERNATE_TIMEOUT}.
@@ -203,45 +228,42 @@ handle_cast(_Msg, State) ->
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------
handle_info({Tag, _TCPSocket, Data},
- #state{socket = Socket,
- sock_mod = SockMod} = State)
- when (Tag == tcp) or (Tag == ssl) or (Tag == ejabberd_xml) ->
+ #state{socket = Socket, sock_mod = SockMod} = State)
+ when (Tag == tcp) or (Tag == ssl) or
+ (Tag == ejabberd_xml) ->
case SockMod of
- tls ->
- case tls:recv_data(Socket, Data) of
- {ok, TLSData} ->
- {noreply, process_data(TLSData, State),
- ?HIBERNATE_TIMEOUT};
- {error, _Reason} ->
- {stop, normal, State}
- end;
- ejabberd_zlib ->
- case ejabberd_zlib:recv_data(Socket, Data) of
- {ok, ZlibData} ->
- {noreply, process_data(ZlibData, State),
- ?HIBERNATE_TIMEOUT};
- {error, _Reason} ->
- {stop, normal, State}
- end;
- _ ->
- {noreply, process_data(Data, State), ?HIBERNATE_TIMEOUT}
+ tls ->
+ case tls:recv_data(Socket, Data) of
+ {ok, TLSData} ->
+ {noreply, process_data(TLSData, State),
+ ?HIBERNATE_TIMEOUT};
+ {error, _Reason} -> {stop, normal, State}
+ end;
+ ejabberd_zlib ->
+ case ejabberd_zlib:recv_data(Socket, Data) of
+ {ok, ZlibData} ->
+ {noreply, process_data(ZlibData, State),
+ ?HIBERNATE_TIMEOUT};
+ {error, _Reason} -> {stop, normal, State}
+ end;
+ _ ->
+ {noreply, process_data(Data, State), ?HIBERNATE_TIMEOUT}
end;
handle_info({Tag, _TCPSocket}, State)
- when (Tag == tcp_closed) or (Tag == ssl_closed) ->
+ when (Tag == tcp_closed) or (Tag == ssl_closed) ->
{stop, normal, State};
handle_info({Tag, _TCPSocket, Reason}, State)
- when (Tag == tcp_error) or (Tag == ssl_error) ->
+ when (Tag == tcp_error) or (Tag == ssl_error) ->
case Reason of
- timeout ->
- {noreply, State, ?HIBERNATE_TIMEOUT};
- _ ->
- {stop, normal, State}
+ timeout -> {noreply, State, ?HIBERNATE_TIMEOUT};
+ _ -> {stop, normal, State}
end;
handle_info({timeout, _Ref, activate}, State) ->
activate_socket(State),
{noreply, State, ?HIBERNATE_TIMEOUT};
handle_info(timeout, State) ->
- proc_lib:hibernate(gen_server, enter_loop, [?MODULE, [], State]),
+ proc_lib:hibernate(gen_server, enter_loop,
+ [?MODULE, [], State]),
{noreply, State, ?HIBERNATE_TIMEOUT};
handle_info(_Info, State) ->
{noreply, State, ?HIBERNATE_TIMEOUT}.
@@ -253,14 +275,14 @@ handle_info(_Info, State) ->
%% cleaning up. When it returns, the gen_server terminates with Reason.
%% The return value is ignored.
%%--------------------------------------------------------------------
-terminate(_Reason, #state{xml_stream_state = XMLStreamState,
- c2s_pid = C2SPid} = State) ->
+terminate(_Reason,
+ #state{xml_stream_state = XMLStreamState,
+ c2s_pid = C2SPid} =
+ State) ->
close_stream(XMLStreamState),
- if
- C2SPid /= undefined ->
- gen_fsm:send_event(C2SPid, closed);
- true ->
- ok
+ if C2SPid /= undefined ->
+ gen_fsm:send_event(C2SPid, closed);
+ true -> ok
end,
catch (State#state.sock_mod):close(State#state.socket),
ok.
@@ -269,8 +291,7 @@ terminate(_Reason, #state{xml_stream_state = XMLStreamState,
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
%% Description: Convert process state when code is changed
%%--------------------------------------------------------------------
-code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
+code_change(_OldVsn, State, _Extra) -> {ok, State}.
%%--------------------------------------------------------------------
%%% Internal functions
@@ -278,48 +299,44 @@ code_change(_OldVsn, State, _Extra) ->
activate_socket(#state{socket = Socket,
sock_mod = SockMod}) ->
- PeerName =
- case SockMod of
- gen_tcp ->
- inet:setopts(Socket, [{active, once}]),
- inet:peername(Socket);
- _ ->
- SockMod:setopts(Socket, [{active, once}]),
- SockMod:peername(Socket)
- end,
+ PeerName = case SockMod of
+ gen_tcp ->
+ inet:setopts(Socket, [{active, once}]),
+ inet:peername(Socket);
+ _ ->
+ SockMod:setopts(Socket, [{active, once}]),
+ SockMod:peername(Socket)
+ end,
case PeerName of
- {error, _Reason} ->
- self() ! {tcp_closed, Socket};
- {ok, _} ->
- ok
+ {error, _Reason} -> self() ! {tcp_closed, Socket};
+ {ok, _} -> ok
end.
%% Data processing for connectors directly generating xmlelement in
%% Erlang data structure.
%% WARNING: Shaper does not work with Erlang data structure.
process_data([], State) ->
- activate_socket(State),
- State;
-process_data([Element|Els], #state{c2s_pid = C2SPid} = State)
- when element(1, Element) == xmlelement;
- element(1, Element) == xmlstreamstart;
- element(1, Element) == xmlstreamelement;
- element(1, Element) == xmlstreamend ->
- if
- C2SPid == undefined ->
- State;
- true ->
- catch gen_fsm:send_event(C2SPid, element_wrapper(Element)),
- process_data(Els, State)
+ activate_socket(State), State;
+process_data([Element | Els],
+ #state{c2s_pid = C2SPid} = State)
+ when element(1, Element) == xmlel;
+ element(1, Element) == xmlstreamstart;
+ element(1, Element) == xmlstreamelement;
+ element(1, Element) == xmlstreamend ->
+ if C2SPid == undefined -> State;
+ true ->
+ catch gen_fsm:send_event(C2SPid,
+ element_wrapper(Element)),
+ process_data(Els, State)
end;
%% Data processing for connectors receivind data as string.
process_data(Data,
#state{xml_stream_state = XMLStreamState,
- shaper_state = ShaperState,
- c2s_pid = C2SPid} = State) ->
- ?DEBUG("Received XML on stream = ~p", [binary_to_list(Data)]),
+ shaper_state = ShaperState, c2s_pid = C2SPid} =
+ State) ->
+ ?DEBUG("Received XML on stream = ~p", [(Data)]),
XMLStreamState1 = xml_stream:parse(XMLStreamState, Data),
- {NewShaperState, Pause} = shaper:update(ShaperState, size(Data)),
+ {NewShaperState, Pause} = shaper:update(ShaperState, byte_size(Data)),
if
C2SPid == undefined ->
ok;
@@ -336,20 +353,16 @@ process_data(Data,
%% speaking directly Erlang XML), we wrap it inside the same
%% xmlstreamelement coming from the XML parser.
element_wrapper(XMLElement)
- when element(1, XMLElement) == xmlelement ->
+ when element(1, XMLElement) == xmlel ->
{xmlstreamelement, XMLElement};
-element_wrapper(Element) ->
- Element.
+element_wrapper(Element) -> Element.
-close_stream(undefined) ->
- ok;
+close_stream(undefined) -> ok;
close_stream(XMLStreamState) ->
xml_stream:close(XMLStreamState).
do_call(Pid, Msg) ->
case catch gen_server:call(Pid, Msg) of
- {'EXIT', Why} ->
- {error, Why};
- Res ->
- Res
+ {'EXIT', Why} -> {error, Why};
+ Res -> Res
end.
diff --git a/src/ejabberd_regexp.erl b/src/ejabberd_regexp.erl
index d6210b562..6603ec626 100644
--- a/src/ejabberd_regexp.erl
+++ b/src/ejabberd_regexp.erl
@@ -25,48 +25,72 @@
%%%----------------------------------------------------------------------
-module(ejabberd_regexp).
+
-compile([export_all]).
-exec(ReM, ReF, ReA, RgM, RgF, RgA) ->
- try apply(ReM, ReF, ReA)
- catch
- error:undef ->
- apply(RgM, RgF, RgA);
- A:B ->
- {error, {A, B}}
+exec({ReM, ReF, ReA}, {RgM, RgF, RgA}) ->
+ try apply(ReM, ReF, ReA) catch
+ error:undef -> apply(RgM, RgF, RgA);
+ A:B -> {error, {A, B}}
end.
+-spec run(binary(), binary()) -> match | nomatch | {error, any()}.
+
run(String, Regexp) ->
- case exec(re, run, [String, Regexp, [{capture, none}]], regexp, first_match, [String, Regexp]) of
- {match, _, _} -> match;
- {match, _} -> match;
- match -> match;
- nomatch -> nomatch;
- {error, Error} -> {error, Error}
+ case exec({re, run, [String, Regexp, [{capture, none}]]},
+ {regexp, first_match, [binary_to_list(String),
+ binary_to_list(Regexp)]})
+ of
+ {match, _, _} -> match;
+ {match, _} -> match;
+ match -> match;
+ nomatch -> nomatch;
+ {error, Error} -> {error, Error}
end.
+-spec split(binary(), binary()) -> [binary()].
+
split(String, Regexp) ->
- case exec(re, split, [String, Regexp, [{return, list}]], regexp, split, [String, Regexp]) of
- {ok, FieldList} -> FieldList;
- {error, Error} -> throw(Error);
- A -> A
+ case exec({re, split, [String, Regexp, [{return, binary}]]},
+ {regexp, split, [binary_to_list(String),
+ binary_to_list(Regexp)]})
+ of
+ {ok, FieldList} -> [iolist_to_binary(F) || F <- FieldList];
+ {error, Error} -> throw(Error);
+ A -> A
end.
+-spec replace(binary(), binary(), binary()) -> binary().
+
replace(String, Regexp, New) ->
- case exec(re, replace, [String, Regexp, New, [{return, list}]], regexp, sub, [String, Regexp, New]) of
- {ok, NewString, _RepCount} -> NewString;
- {error, Error} -> throw(Error);
- A -> A
+ case exec({re, replace, [String, Regexp, New, [{return, binary}]]},
+ {regexp, sub, [binary_to_list(String),
+ binary_to_list(Regexp),
+ binary_to_list(New)]})
+ of
+ {ok, NewString, _RepCount} -> iolist_to_binary(NewString);
+ {error, Error} -> throw(Error);
+ A -> A
end.
+-spec greplace(binary(), binary(), binary()) -> binary().
+
greplace(String, Regexp, New) ->
- case exec(re, replace, [String, Regexp, New, [global, {return, list}]], regexp, sub, [String, Regexp, New]) of
- {ok, NewString, _RepCount} -> NewString;
- {error, Error} -> throw(Error);
- A -> A
+ case exec({re, replace, [String, Regexp, New, [global, {return, binary}]]},
+ {regexp, sub, [binary_to_list(String),
+ binary_to_list(Regexp),
+ binary_to_list(New)]})
+ of
+ {ok, NewString, _RepCount} -> iolist_to_binary(NewString);
+ {error, Error} -> throw(Error);
+ A -> A
end.
+-spec sh_to_awk(binary()) -> binary().
+
sh_to_awk(ShRegExp) ->
- case exec(xmerl_regexp, sh_to_awk, [ShRegExp], regexp, sh_to_awk, [ShRegExp]) of
- A -> A
+ case exec({xmerl_regexp, sh_to_awk, [binary_to_list(ShRegExp)]},
+ {regexp, sh_to_awk, [binary_to_list(ShRegExp)]})
+ of
+ A -> iolist_to_binary(A)
end.
diff --git a/src/ejabberd_router.erl b/src/ejabberd_router.erl
index f1e70ad0f..8577d81ad 100644
--- a/src/ejabberd_router.erl
+++ b/src/ejabberd_router.erl
@@ -25,6 +25,7 @@
%%%----------------------------------------------------------------------
-module(ejabberd_router).
+
-author('alexey@process-one.net').
-behaviour(gen_server).
@@ -44,13 +45,17 @@
-export([start_link/0]).
%% gen_server callbacks
--export([init/1, handle_call/3, handle_cast/2, handle_info/2,
- terminate/2, code_change/3]).
+-export([init/1, handle_call/3, handle_cast/2,
+ handle_info/2, terminate/2, code_change/3]).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
+-type local_hint() :: undefined | integer() | {apply, atom(), atom()}.
+
-record(route, {domain, pid, local_hint}).
+
-record(state, {}).
%%====================================================================
@@ -63,6 +68,7 @@
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
+-spec route(jid(), jid(), xmlel()) -> ok.
route(From, To, Packet) ->
case catch do_route(From, To, Packet) of
@@ -75,119 +81,131 @@ route(From, To, Packet) ->
%% Route the error packet only if the originating packet is not an error itself.
%% RFC3920 9.3.1
+-spec route_error(jid(), jid(), xmlel(), xmlel()) -> ok.
+
route_error(From, To, ErrPacket, OrigPacket) ->
- {xmlelement, _Name, Attrs, _Els} = OrigPacket,
- case "error" == xml:get_attr_s("type", Attrs) of
- false ->
- route(From, To, ErrPacket);
- true ->
- ok
+ #xmlel{attrs = Attrs} = OrigPacket,
+ case <<"error">> == xml:get_attr_s(<<"type">>, Attrs) of
+ false -> route(From, To, ErrPacket);
+ true -> ok
end.
+-spec register_route(binary()) -> term().
+
register_route(Domain) ->
register_route(Domain, undefined).
+-spec register_route(binary(), local_hint()) -> term().
+
register_route(Domain, LocalHint) ->
case jlib:nameprep(Domain) of
- error ->
- erlang:error({invalid_domain, Domain});
- LDomain ->
- Pid = self(),
- case get_component_number(LDomain) of
- undefined ->
- F = fun() ->
- mnesia:write(#route{domain = LDomain,
- pid = Pid,
- local_hint = LocalHint})
- end,
- mnesia:transaction(F);
- N ->
- F = fun() ->
- case mnesia:wread({route, LDomain}) of
- [] ->
- mnesia:write(
- #route{domain = LDomain,
- pid = Pid,
- local_hint = 1}),
- lists:foreach(
- fun(I) ->
- mnesia:write(
- #route{domain = LDomain,
- pid = undefined,
- local_hint = I})
- end, lists:seq(2, N));
- Rs ->
- lists:any(
- fun(#route{pid = undefined,
- local_hint = I} = R) ->
- mnesia:write(
- #route{domain = LDomain,
- pid = Pid,
- local_hint = I}),
- mnesia:delete_object(R),
- true;
- (_) ->
- false
- end, Rs)
- end
- end,
- mnesia:transaction(F)
- end
+ error -> erlang:error({invalid_domain, Domain});
+ LDomain ->
+ Pid = self(),
+ case get_component_number(LDomain) of
+ undefined ->
+ F = fun () ->
+ mnesia:write(#route{domain = LDomain, pid = Pid,
+ local_hint = LocalHint})
+ end,
+ mnesia:transaction(F);
+ N ->
+ F = fun () ->
+ case mnesia:wread({route, LDomain}) of
+ [] ->
+ mnesia:write(#route{domain = LDomain,
+ pid = Pid,
+ local_hint = 1}),
+ lists:foreach(fun (I) ->
+ mnesia:write(#route{domain
+ =
+ LDomain,
+ pid
+ =
+ undefined,
+ local_hint
+ =
+ I})
+ end,
+ lists:seq(2, N));
+ Rs ->
+ lists:any(fun (#route{pid = undefined,
+ local_hint = I} =
+ R) ->
+ mnesia:write(#route{domain =
+ LDomain,
+ pid =
+ Pid,
+ local_hint
+ =
+ I}),
+ mnesia:delete_object(R),
+ true;
+ (_) -> false
+ end,
+ Rs)
+ end
+ end,
+ mnesia:transaction(F)
+ end
end.
+-spec register_routes([binary()]) -> ok.
+
register_routes(Domains) ->
- lists:foreach(fun(Domain) ->
- register_route(Domain)
- end, Domains).
+ lists:foreach(fun (Domain) -> register_route(Domain)
+ end,
+ Domains).
+
+-spec unregister_route(binary()) -> term().
unregister_route(Domain) ->
case jlib:nameprep(Domain) of
- error ->
- erlang:error({invalid_domain, Domain});
- LDomain ->
- Pid = self(),
- case get_component_number(LDomain) of
- undefined ->
- F = fun() ->
- case mnesia:match_object(
- #route{domain = LDomain,
- pid = Pid,
- _ = '_'}) of
- [R] ->
- mnesia:delete_object(R);
- _ ->
- ok
- end
- end,
- mnesia:transaction(F);
- _ ->
- F = fun() ->
- case mnesia:match_object(#route{domain=LDomain,
- pid = Pid,
- _ = '_'}) of
- [R] ->
- I = R#route.local_hint,
- mnesia:write(
- #route{domain = LDomain,
- pid = undefined,
- local_hint = I}),
- mnesia:delete_object(R);
- _ ->
- ok
- end
- end,
- mnesia:transaction(F)
- end
+ error -> erlang:error({invalid_domain, Domain});
+ LDomain ->
+ Pid = self(),
+ case get_component_number(LDomain) of
+ undefined ->
+ F = fun () ->
+ case mnesia:match_object(#route{domain = LDomain,
+ pid = Pid, _ = '_'})
+ of
+ [R] -> mnesia:delete_object(R);
+ _ -> ok
+ end
+ end,
+ mnesia:transaction(F);
+ _ ->
+ F = fun () ->
+ case mnesia:match_object(#route{domain = LDomain,
+ pid = Pid, _ = '_'})
+ of
+ [R] ->
+ I = R#route.local_hint,
+ mnesia:write(#route{domain = LDomain,
+ pid = undefined,
+ local_hint = I}),
+ mnesia:delete_object(R);
+ _ -> ok
+ end
+ end,
+ mnesia:transaction(F)
+ end
end.
+-spec unregister_routes([binary()]) -> ok.
+
unregister_routes(Domains) ->
- lists:foreach(fun(Domain) ->
- unregister_route(Domain)
- end, Domains).
+ lists:foreach(fun (Domain) -> unregister_route(Domain)
+ end,
+ Domains).
+-spec dirty_get_all_routes() -> [binary()].
dirty_get_all_routes() ->
- lists:usort(mnesia:dirty_all_keys(route)) -- ?MYHOSTS.
+ lists:usort(mnesia:dirty_all_keys(route)) -- (?MYHOSTS).
+
+-spec dirty_get_all_domains() -> [binary()].
dirty_get_all_domains() ->
lists:usort(mnesia:dirty_all_keys(route)).
@@ -207,17 +225,14 @@ dirty_get_all_domains() ->
init([]) ->
update_tables(),
mnesia:create_table(route,
- [{ram_copies, [node()]},
- {type, bag},
- {attributes,
- record_info(fields, route)}]),
+ [{ram_copies, [node()]}, {type, bag},
+ {attributes, record_info(fields, route)}]),
mnesia:add_table_copy(route, node(), ram_copies),
mnesia:subscribe({table, route, simple}),
- lists:foreach(
- fun(Pid) ->
- erlang:monitor(process, Pid)
- end,
- mnesia:dirty_select(route, [{{route, '_', '$1', '_'}, [], ['$1']}])),
+ lists:foreach(fun (Pid) -> erlang:monitor(process, Pid)
+ end,
+ mnesia:dirty_select(route,
+ [{{route, '_', '$1', '_'}, [], ['$1']}])),
{ok, #state{}}.
%%--------------------------------------------------------------------
@@ -230,8 +245,7 @@ init([]) ->
%% Description: Handling call messages
%%--------------------------------------------------------------------
handle_call(_Request, _From, State) ->
- Reply = ok,
- {reply, Reply, State}.
+ Reply = ok, {reply, Reply, State}.
%%--------------------------------------------------------------------
%% Function: handle_cast(Msg, State) -> {noreply, State} |
@@ -239,8 +253,7 @@ handle_call(_Request, _From, State) ->
%% {stop, Reason, State}
%% Description: Handling cast messages
%%--------------------------------------------------------------------
-handle_cast(_Msg, State) ->
- {noreply, State}.
+handle_cast(_Msg, State) -> {noreply, State}.
%%--------------------------------------------------------------------
%% Function: handle_info(Info, State) -> {noreply, State} |
@@ -250,39 +263,35 @@ handle_cast(_Msg, State) ->
%%--------------------------------------------------------------------
handle_info({route, From, To, Packet}, State) ->
case catch do_route(From, To, Packet) of
- {'EXIT', Reason} ->
- ?ERROR_MSG("~p~nwhen processing: ~p",
- [Reason, {From, To, Packet}]);
- _ ->
- ok
+ {'EXIT', Reason} ->
+ ?ERROR_MSG("~p~nwhen processing: ~p",
+ [Reason, {From, To, Packet}]);
+ _ -> ok
end,
{noreply, State};
-handle_info({mnesia_table_event, {write, #route{pid = Pid}, _ActivityId}},
+handle_info({mnesia_table_event,
+ {write, #route{pid = Pid}, _ActivityId}},
State) ->
- erlang:monitor(process, Pid),
- {noreply, State};
+ erlang:monitor(process, Pid), {noreply, State};
handle_info({'DOWN', _Ref, _Type, Pid, _Info}, State) ->
- F = fun() ->
- Es = mnesia:select(
- route,
- [{#route{pid = Pid, _ = '_'},
- [],
- ['$_']}]),
- lists:foreach(
- fun(E) ->
- if
- is_integer(E#route.local_hint) ->
- LDomain = E#route.domain,
- I = E#route.local_hint,
- mnesia:write(
- #route{domain = LDomain,
- pid = undefined,
- local_hint = I}),
- mnesia:delete_object(E);
- true ->
- mnesia:delete_object(E)
- end
- end, Es)
+ F = fun () ->
+ Es = mnesia:select(route,
+ [{#route{pid = Pid, _ = '_'}, [], ['$_']}]),
+ lists:foreach(fun (E) ->
+ if is_integer(E#route.local_hint) ->
+ LDomain = E#route.domain,
+ I = E#route.local_hint,
+ mnesia:write(#route{domain =
+ LDomain,
+ pid =
+ undefined,
+ local_hint =
+ I}),
+ mnesia:delete_object(E);
+ true -> mnesia:delete_object(E)
+ end
+ end,
+ Es)
end,
mnesia:transaction(F),
{noreply, State};
@@ -310,107 +319,93 @@ code_change(_OldVsn, State, _Extra) ->
%%% Internal functions
%%--------------------------------------------------------------------
do_route(OrigFrom, OrigTo, OrigPacket) ->
- ?DEBUG("route~n\tfrom ~p~n\tto ~p~n\tpacket ~p~n",
+ ?DEBUG("route~n\tfrom ~p~n\tto ~p~n\tpacket "
+ "~p~n",
[OrigFrom, OrigTo, OrigPacket]),
case ejabberd_hooks:run_fold(filter_packet,
- {OrigFrom, OrigTo, OrigPacket}, []) of
- {From, To, Packet} ->
- LDstDomain = To#jid.lserver,
- case mnesia:dirty_read(route, LDstDomain) of
- [] ->
- ejabberd_s2s:route(From, To, Packet);
- [R] ->
- Pid = R#route.pid,
- if
- node(Pid) == node() ->
- case R#route.local_hint of
- {apply, Module, Function} ->
- Module:Function(From, To, Packet);
- _ ->
- Pid ! {route, From, To, Packet}
- end;
- is_pid(Pid) ->
- Pid ! {route, From, To, Packet};
- true ->
- drop
- end;
- Rs ->
- Value = case ejabberd_config:get_local_option(
- {domain_balancing, LDstDomain}) of
- undefined -> now();
- random -> now();
- source -> jlib:jid_tolower(From);
- destination -> jlib:jid_tolower(To);
- bare_source ->
- jlib:jid_remove_resource(
- jlib:jid_tolower(From));
- bare_destination ->
- jlib:jid_remove_resource(
- jlib:jid_tolower(To))
- end,
- case get_component_number(LDstDomain) of
- undefined ->
- case [R || R <- Rs, node(R#route.pid) == node()] of
- [] ->
- R = lists:nth(erlang:phash(Value, length(Rs)), Rs),
- Pid = R#route.pid,
- if
- is_pid(Pid) ->
- Pid ! {route, From, To, Packet};
- true ->
- drop
- end;
- LRs ->
- R = lists:nth(erlang:phash(Value, length(LRs)), LRs),
- Pid = R#route.pid,
- case R#route.local_hint of
- {apply, Module, Function} ->
- Module:Function(From, To, Packet);
- _ ->
- Pid ! {route, From, To, Packet}
- end
+ {OrigFrom, OrigTo, OrigPacket}, [])
+ of
+ {From, To, Packet} ->
+ LDstDomain = To#jid.lserver,
+ case mnesia:dirty_read(route, LDstDomain) of
+ [] -> ejabberd_s2s:route(From, To, Packet);
+ [R] ->
+ Pid = R#route.pid,
+ if node(Pid) == node() ->
+ case R#route.local_hint of
+ {apply, Module, Function} ->
+ Module:Function(From, To, Packet);
+ _ -> Pid ! {route, From, To, Packet}
+ end;
+ is_pid(Pid) -> Pid ! {route, From, To, Packet};
+ true -> drop
+ end;
+ Rs ->
+ Value = case
+ ejabberd_config:get_local_option({domain_balancing,
+ LDstDomain}, fun(D) when is_atom(D) -> D end)
+ of
+ undefined -> now();
+ random -> now();
+ source -> jlib:jid_tolower(From);
+ destination -> jlib:jid_tolower(To);
+ bare_source ->
+ jlib:jid_remove_resource(jlib:jid_tolower(From));
+ bare_destination ->
+ jlib:jid_remove_resource(jlib:jid_tolower(To))
+ end,
+ case get_component_number(LDstDomain) of
+ undefined ->
+ case [R || R <- Rs, node(R#route.pid) == node()] of
+ [] ->
+ R = lists:nth(erlang:phash(Value, str:len(Rs)), Rs),
+ Pid = R#route.pid,
+ if is_pid(Pid) -> Pid ! {route, From, To, Packet};
+ true -> drop
end;
- _ ->
- SRs = lists:ukeysort(#route.local_hint, Rs),
- R = lists:nth(erlang:phash(Value, length(SRs)), SRs),
+ LRs ->
+ R = lists:nth(erlang:phash(Value, str:len(LRs)),
+ LRs),
Pid = R#route.pid,
- if
- is_pid(Pid) ->
- Pid ! {route, From, To, Packet};
- true ->
- drop
+ case R#route.local_hint of
+ {apply, Module, Function} ->
+ Module:Function(From, To, Packet);
+ _ -> Pid ! {route, From, To, Packet}
end
- end
- end;
- drop ->
- ok
+ end;
+ _ ->
+ SRs = lists:ukeysort(#route.local_hint, Rs),
+ R = lists:nth(erlang:phash(Value, str:len(SRs)), SRs),
+ Pid = R#route.pid,
+ if is_pid(Pid) -> Pid ! {route, From, To, Packet};
+ true -> drop
+ end
+ end
+ end;
+ drop -> ok
end.
get_component_number(LDomain) ->
- case ejabberd_config:get_local_option(
- {domain_balancing_component_number, LDomain}) of
- N when is_integer(N),
- N > 1 ->
- N;
- _ ->
- undefined
+ case
+ ejabberd_config:get_local_option({domain_balancing_component_number,
+ LDomain}, fun(D) -> D end)
+ of
+ N when is_integer(N), N > 1 -> N;
+ _ -> undefined
end.
+
update_tables() ->
case catch mnesia:table_info(route, attributes) of
- [domain, node, pid] ->
- mnesia:delete_table(route);
- [domain, pid] ->
- mnesia:delete_table(route);
- [domain, pid, local_hint] ->
- ok;
- {'EXIT', _} ->
- ok
+ [domain, node, pid] -> mnesia:delete_table(route);
+ [domain, pid] -> mnesia:delete_table(route);
+ [domain, pid, local_hint] -> ok;
+ {'EXIT', _} -> ok
end,
- case lists:member(local_route, mnesia:system_info(tables)) of
- true ->
- mnesia:delete_table(local_route);
- false ->
- ok
+ case lists:member(local_route,
+ mnesia:system_info(tables))
+ of
+ true -> mnesia:delete_table(local_route);
+ false -> ok
end.
diff --git a/src/ejabberd_s2s.erl b/src/ejabberd_s2s.erl
index b06a7ab6c..0832d1dfd 100644
--- a/src/ejabberd_s2s.erl
+++ b/src/ejabberd_s2s.erl
@@ -25,49 +25,52 @@
%%%----------------------------------------------------------------------
-module(ejabberd_s2s).
+
-author('alexey@process-one.net').
-behaviour(gen_server).
%% API
--export([start_link/0,
- route/3,
- have_connection/1,
- has_key/2,
- get_connections_pids/1,
- try_register/1,
- remove_connection/3,
- find_connection/2,
- dirty_get_connections/0,
- allow_host/2,
- incoming_s2s_number/0,
- outgoing_s2s_number/0,
+-export([start_link/0, route/3, have_connection/1,
+ has_key/2, get_connections_pids/1, try_register/1,
+ remove_connection/3, find_connection/2,
+ dirty_get_connections/0, allow_host/2,
+ incoming_s2s_number/0, outgoing_s2s_number/0,
clean_temporarily_blocked_table/0,
list_temporarily_blocked_hosts/0,
- external_host_overloaded/1,
- is_temporarly_blocked/1
- ]).
+ external_host_overloaded/1, is_temporarly_blocked/1]).
%% gen_server callbacks
--export([init/1, handle_call/3, handle_cast/2, handle_info/2,
- terminate/2, code_change/3]).
+-export([init/1, handle_call/3, handle_cast/2,
+ handle_info/2, terminate/2, code_change/3]).
+
%% ejabberd API
-export([get_info_s2s_connections/1]).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
+
-include("ejabberd_commands.hrl").
-define(DEFAULT_MAX_S2S_CONNECTIONS_NUMBER, 1).
+
-define(DEFAULT_MAX_S2S_CONNECTIONS_NUMBER_PER_NODE, 1).
-define(S2S_OVERLOAD_BLOCK_PERIOD, 60).
+
%% once a server is temporarly blocked, it stay blocked for 60 seconds
--record(s2s, {fromto, pid, key}).
+-record(s2s, {fromto = {<<"">>, <<"">>} :: {binary(), binary()},
+ pid = self() :: pid() | '_',
+ key = <<"">> :: binary() | '_'}).
+
-record(state, {}).
--record(temporarily_blocked, {host, timestamp}).
+-record(temporarily_blocked, {host = <<"">> :: binary(),
+ timestamp = now() :: erlang:timestamp()}).
+
+-type temporarily_blocked() :: #temporarily_blocked{}.
%%====================================================================
%% API
@@ -77,57 +80,73 @@
%% Description: Starts the server
%%--------------------------------------------------------------------
start_link() ->
- gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
+ gen_server:start_link({local, ?MODULE}, ?MODULE, [],
+ []).
+
+-spec route(jid(), jid(), xmlel()) -> ok.
route(From, To, Packet) ->
case catch do_route(From, To, Packet) of
- {'EXIT', Reason} ->
- ?ERROR_MSG("~p~nwhen processing: ~p",
- [Reason, {From, To, Packet}]);
- _ ->
- ok
+ {'EXIT', Reason} ->
+ ?ERROR_MSG("~p~nwhen processing: ~p",
+ [Reason, {From, To, Packet}]);
+ _ -> ok
end.
clean_temporarily_blocked_table() ->
- mnesia:clear_table(temporarily_blocked).
+ mnesia:clear_table(temporarily_blocked).
+
+-spec list_temporarily_blocked_hosts() -> [temporarily_blocked()].
+
list_temporarily_blocked_hosts() ->
- ets:tab2list(temporarily_blocked).
+ ets:tab2list(temporarily_blocked).
+
+-spec external_host_overloaded(binary()) -> {aborted, any()} | {atomic, ok}.
external_host_overloaded(Host) ->
- ?INFO_MSG("Disabling connections from ~s for ~p seconds", [Host, ?S2S_OVERLOAD_BLOCK_PERIOD]),
- mnesia:transaction( fun() ->
- mnesia:write(#temporarily_blocked{host = Host, timestamp = now()})
- end).
+ ?INFO_MSG("Disabling connections from ~s for ~p "
+ "seconds",
+ [Host, ?S2S_OVERLOAD_BLOCK_PERIOD]),
+ mnesia:transaction(fun () ->
+ mnesia:write(#temporarily_blocked{host = Host,
+ timestamp =
+ now()})
+ end).
+
+-spec is_temporarly_blocked(binary()) -> boolean().
is_temporarly_blocked(Host) ->
- case mnesia:dirty_read(temporarily_blocked, Host) of
- [] -> false;
- [#temporarily_blocked{timestamp = T}=Entry] ->
- case timer:now_diff(now(), T) of
- N when N > ?S2S_OVERLOAD_BLOCK_PERIOD * 1000 * 1000 ->
- mnesia:dirty_delete_object(Entry),
- false;
- _ ->
- true
- end
- end.
+ case mnesia:dirty_read(temporarily_blocked, Host) of
+ [] -> false;
+ [#temporarily_blocked{timestamp = T} = Entry] ->
+ case timer:now_diff(now(), T) of
+ N when N > (?S2S_OVERLOAD_BLOCK_PERIOD) * 1000 * 1000 ->
+ mnesia:dirty_delete_object(Entry), false;
+ _ -> true
+ end
+ end.
+-spec remove_connection({binary(), binary()},
+ pid(), binary()) -> {atomic, ok} |
+ ok |
+ {aborted, any()}.
remove_connection(FromTo, Pid, Key) ->
- case catch mnesia:dirty_match_object(s2s, #s2s{fromto = FromTo,
- pid = Pid,
- _ = '_'}) of
- [#s2s{pid = Pid, key = Key}] ->
- F = fun() ->
- mnesia:delete_object(#s2s{fromto = FromTo,
- pid = Pid,
- key = Key})
- end,
- mnesia:transaction(F);
- _ ->
- ok
+ case catch mnesia:dirty_match_object(s2s,
+ #s2s{fromto = FromTo, pid = Pid,
+ _ = '_'})
+ of
+ [#s2s{pid = Pid, key = Key}] ->
+ F = fun () ->
+ mnesia:delete_object(#s2s{fromto = FromTo, pid = Pid,
+ key = Key})
+ end,
+ mnesia:transaction(F);
+ _ -> ok
end.
+-spec have_connection({binary(), binary()}) -> boolean().
+
have_connection(FromTo) ->
case catch mnesia:dirty_read(s2s, FromTo) of
[_] ->
@@ -136,6 +155,8 @@ have_connection(FromTo) ->
false
end.
+-spec has_key({binary(), binary()}, binary()) -> boolean().
+
has_key(FromTo, Key) ->
case mnesia:dirty_select(s2s,
[{#s2s{fromto = FromTo, key = Key, _ = '_'},
@@ -147,6 +168,8 @@ has_key(FromTo, Key) ->
true
end.
+-spec get_connections_pids({binary(), binary()}) -> [pid()].
+
get_connections_pids(FromTo) ->
case catch mnesia:dirty_read(s2s, FromTo) of
L when is_list(L) ->
@@ -155,33 +178,32 @@ get_connections_pids(FromTo) ->
[]
end.
+-spec try_register({binary(), binary()}) -> {key, binary()} | false.
+
try_register(FromTo) ->
Key = randoms:get_string(),
MaxS2SConnectionsNumber = max_s2s_connections_number(FromTo),
MaxS2SConnectionsNumberPerNode =
max_s2s_connections_number_per_node(FromTo),
- F = fun() ->
+ F = fun () ->
L = mnesia:read({s2s, FromTo}),
- NeededConnections = needed_connections_number(
- L, MaxS2SConnectionsNumber,
- MaxS2SConnectionsNumberPerNode),
- if
- NeededConnections > 0 ->
- mnesia:write(#s2s{fromto = FromTo,
- pid = self(),
- key = Key}),
- {key, Key};
- true ->
- false
+ NeededConnections = needed_connections_number(L,
+ MaxS2SConnectionsNumber,
+ MaxS2SConnectionsNumberPerNode),
+ if NeededConnections > 0 ->
+ mnesia:write(#s2s{fromto = FromTo, pid = self(),
+ key = Key}),
+ {key, Key};
+ true -> false
end
end,
case mnesia:transaction(F) of
- {atomic, Res} ->
- Res;
- _ ->
- false
+ {atomic, Res} -> Res;
+ _ -> false
end.
+-spec dirty_get_connections() -> [{binary(), binary()}].
+
dirty_get_connections() ->
mnesia:dirty_all_keys(s2s).
@@ -239,15 +261,13 @@ handle_info({mnesia_system_event, {mnesia_down, Node}}, State) ->
{noreply, State};
handle_info({route, From, To, Packet}, State) ->
case catch do_route(From, To, Packet) of
- {'EXIT', Reason} ->
- ?ERROR_MSG("~p~nwhen processing: ~p",
- [Reason, {From, To, Packet}]);
- _ ->
- ok
+ {'EXIT', Reason} ->
+ ?ERROR_MSG("~p~nwhen processing: ~p",
+ [Reason, {From, To, Packet}]);
+ _ -> ok
end,
{noreply, State};
-handle_info(_Info, State) ->
- {noreply, State}.
+handle_info(_Info, State) -> {noreply, State}.
%%--------------------------------------------------------------------
%% Function: terminate(Reason, State) -> void()
@@ -284,76 +304,79 @@ clean_table_from_bad_node(Node) ->
mnesia:async_dirty(F).
do_route(From, To, Packet) ->
- ?DEBUG("s2s manager~n\tfrom ~p~n\tto ~p~n\tpacket ~P~n",
- [From, To, Packet, 8]),
+ ?DEBUG("s2s manager~n\tfrom ~p~n\tto ~p~n\tpacket "
+ "~P~n",
+ [From, To, Packet, 8]),
case find_connection(From, To) of
- {atomic, Pid} when is_pid(Pid) ->
- ?DEBUG("sending to process ~p~n", [Pid]),
- {xmlelement, Name, Attrs, Els} = Packet,
- NewAttrs = jlib:replace_from_to_attrs(jlib:jid_to_string(From),
- jlib:jid_to_string(To),
- Attrs),
- #jid{lserver = MyServer} = From,
- ejabberd_hooks:run(
- s2s_send_packet,
- MyServer,
- [From, To, Packet]),
- send_element(Pid, {xmlelement, Name, NewAttrs, Els}),
- ok;
- {aborted, _Reason} ->
- case xml:get_tag_attr_s("type", Packet) of
- "error" -> ok;
- "result" -> ok;
- _ ->
- Err = jlib:make_error_reply(
- Packet, ?ERR_SERVICE_UNAVAILABLE),
- ejabberd_router:route(To, From, Err)
- end,
- false
+ {atomic, Pid} when is_pid(Pid) ->
+ ?DEBUG("sending to process ~p~n", [Pid]),
+ #xmlel{name = Name, attrs = Attrs, children = Els} =
+ Packet,
+ NewAttrs =
+ jlib:replace_from_to_attrs(jlib:jid_to_string(From),
+ jlib:jid_to_string(To), Attrs),
+ #jid{lserver = MyServer} = From,
+ ejabberd_hooks:run(s2s_send_packet, MyServer,
+ [From, To, Packet]),
+ send_element(Pid,
+ #xmlel{name = Name, attrs = NewAttrs, children = Els}),
+ ok;
+ {aborted, _Reason} ->
+ case xml:get_tag_attr_s(<<"type">>, Packet) of
+ <<"error">> -> ok;
+ <<"result">> -> ok;
+ _ ->
+ Err = jlib:make_error_reply(Packet,
+ ?ERR_SERVICE_UNAVAILABLE),
+ ejabberd_router:route(To, From, Err)
+ end,
+ false
end.
+-spec find_connection(jid(), jid()) -> {aborted, any()} | {atomic, pid()}.
+
find_connection(From, To) ->
#jid{lserver = MyServer} = From,
#jid{lserver = Server} = To,
FromTo = {MyServer, Server},
- MaxS2SConnectionsNumber = max_s2s_connections_number(FromTo),
+ MaxS2SConnectionsNumber =
+ max_s2s_connections_number(FromTo),
MaxS2SConnectionsNumberPerNode =
max_s2s_connections_number_per_node(FromTo),
?DEBUG("Finding connection for ~p~n", [FromTo]),
case catch mnesia:dirty_read(s2s, FromTo) of
- {'EXIT', Reason} ->
- {aborted, Reason};
- [] ->
- %% We try to establish all the connections if the host is not a
- %% service and if the s2s host is not blacklisted or
- %% is in whitelist:
- case not is_service(From, To) andalso allow_host(MyServer, Server) of
- true ->
- NeededConnections = needed_connections_number(
- [], MaxS2SConnectionsNumber,
- MaxS2SConnectionsNumberPerNode),
- open_several_connections(
- NeededConnections, MyServer,
- Server, From, FromTo,
- MaxS2SConnectionsNumber, MaxS2SConnectionsNumberPerNode);
- false ->
- {aborted, error}
- end;
- L when is_list(L) ->
- NeededConnections = needed_connections_number(
- L, MaxS2SConnectionsNumber,
- MaxS2SConnectionsNumberPerNode),
- if
- NeededConnections > 0 ->
- %% We establish the missing connections for this pair.
- open_several_connections(
- NeededConnections, MyServer,
- Server, From, FromTo,
- MaxS2SConnectionsNumber, MaxS2SConnectionsNumberPerNode);
- true ->
- %% We choose a connexion from the pool of opened ones.
- {atomic, choose_connection(From, L)}
- end
+ {'EXIT', Reason} -> {aborted, Reason};
+ [] ->
+ %% We try to establish all the connections if the host is not a
+ %% service and if the s2s host is not blacklisted or
+ %% is in whitelist:
+ case not is_service(From, To) andalso
+ allow_host(MyServer, Server)
+ of
+ true ->
+ NeededConnections = needed_connections_number([],
+ MaxS2SConnectionsNumber,
+ MaxS2SConnectionsNumberPerNode),
+ open_several_connections(NeededConnections, MyServer,
+ Server, From, FromTo,
+ MaxS2SConnectionsNumber,
+ MaxS2SConnectionsNumberPerNode);
+ false -> {aborted, error}
+ end;
+ L when is_list(L) ->
+ NeededConnections = needed_connections_number(L,
+ MaxS2SConnectionsNumber,
+ MaxS2SConnectionsNumberPerNode),
+ if NeededConnections > 0 ->
+ %% We establish the missing connections for this pair.
+ open_several_connections(NeededConnections, MyServer,
+ Server, From, FromTo,
+ MaxS2SConnectionsNumber,
+ MaxS2SConnectionsNumberPerNode);
+ true ->
+ %% We choose a connexion from the pool of opened ones.
+ {atomic, choose_connection(From, L)}
+ end
end.
choose_connection(From, Connections) ->
@@ -361,29 +384,26 @@ choose_connection(From, Connections) ->
choose_pid(From, Pids) ->
Pids1 = case [P || P <- Pids, node(P) == node()] of
- [] -> Pids;
- Ps -> Ps
+ [] -> Pids;
+ Ps -> Ps
end,
- % Use sticky connections based on the JID of the sender (whithout
- % the resource to ensure that a muc room always uses the same
- % connection)
- Pid = lists:nth(erlang:phash(jlib:jid_remove_resource(From), length(Pids1)),
- Pids1),
+ Pid =
+ lists:nth(erlang:phash(jlib:jid_remove_resource(From),
+ length(Pids1)),
+ Pids1),
?DEBUG("Using ejabberd_s2s_out ~p~n", [Pid]),
Pid.
-open_several_connections(N, MyServer, Server, From, FromTo,
- MaxS2SConnectionsNumber,
+open_several_connections(N, MyServer, Server, From,
+ FromTo, MaxS2SConnectionsNumber,
MaxS2SConnectionsNumberPerNode) ->
- ConnectionsResult =
- [new_connection(MyServer, Server, From, FromTo,
- MaxS2SConnectionsNumber, MaxS2SConnectionsNumberPerNode)
- || _N <- lists:seq(1, N)],
+ ConnectionsResult = [new_connection(MyServer, Server,
+ From, FromTo, MaxS2SConnectionsNumber,
+ MaxS2SConnectionsNumberPerNode)
+ || _N <- lists:seq(1, N)],
case [PID || {atomic, PID} <- ConnectionsResult] of
- [] ->
- hd(ConnectionsResult);
- PIDs ->
- {atomic, choose_pid(From, PIDs)}
+ [] -> hd(ConnectionsResult);
+ PIDs -> {atomic, choose_pid(From, PIDs)}
end.
new_connection(MyServer, Server, From, FromTo,
@@ -393,41 +413,38 @@ new_connection(MyServer, Server, From, FromTo,
MyServer, Server, {new, Key}),
F = fun() ->
L = mnesia:read({s2s, FromTo}),
- NeededConnections = needed_connections_number(
- L, MaxS2SConnectionsNumber,
- MaxS2SConnectionsNumberPerNode),
- if
- NeededConnections > 0 ->
- mnesia:write(#s2s{fromto = FromTo,
- pid = Pid,
- key = Key}),
- ?INFO_MSG("New s2s connection started ~p", [Pid]),
- Pid;
- true ->
- choose_connection(From, L)
+ NeededConnections = needed_connections_number(L,
+ MaxS2SConnectionsNumber,
+ MaxS2SConnectionsNumberPerNode),
+ if NeededConnections > 0 ->
+ mnesia:write(#s2s{fromto = FromTo, pid = Pid,
+ key = Key}),
+ ?INFO_MSG("New s2s connection started ~p", [Pid]),
+ Pid;
+ true -> choose_connection(From, L)
end
end,
TRes = mnesia:transaction(F),
case TRes of
- {atomic, Pid} ->
- ejabberd_s2s_out:start_connection(Pid);
- _ ->
- ejabberd_s2s_out:stop_connection(Pid)
+ {atomic, Pid} -> ejabberd_s2s_out:start_connection(Pid);
+ _ -> ejabberd_s2s_out:stop_connection(Pid)
end,
TRes.
max_s2s_connections_number({From, To}) ->
- case acl:match_rule(
- From, max_s2s_connections, jlib:make_jid("", To, "")) of
- Max when is_integer(Max) -> Max;
- _ -> ?DEFAULT_MAX_S2S_CONNECTIONS_NUMBER
+ case acl:match_rule(From, max_s2s_connections,
+ jlib:make_jid(<<"">>, To, <<"">>))
+ of
+ Max when is_integer(Max) -> Max;
+ _ -> ?DEFAULT_MAX_S2S_CONNECTIONS_NUMBER
end.
max_s2s_connections_number_per_node({From, To}) ->
- case acl:match_rule(
- From, max_s2s_connections_per_node, jlib:make_jid("", To, "")) of
- Max when is_integer(Max) -> Max;
- _ -> ?DEFAULT_MAX_S2S_CONNECTIONS_NUMBER_PER_NODE
+ case acl:match_rule(From, max_s2s_connections_per_node,
+ jlib:make_jid(<<"">>, To, <<"">>))
+ of
+ Max when is_integer(Max) -> Max;
+ _ -> ?DEFAULT_MAX_S2S_CONNECTIONS_NUMBER_PER_NODE
end.
needed_connections_number(Ls, MaxS2SConnectionsNumber,
@@ -443,45 +460,46 @@ needed_connections_number(Ls, MaxS2SConnectionsNumber,
%% --------------------------------------------------------------------
is_service(From, To) ->
LFromDomain = From#jid.lserver,
- case ejabberd_config:get_local_option({route_subdomains, LFromDomain}) of
- s2s -> % bypass RFC 3920 10.3
- false;
- _ ->
- Hosts = ?MYHOSTS,
- P = fun(ParentDomain) -> lists:member(ParentDomain, Hosts) end,
- lists:any(P, parent_domains(To#jid.lserver))
+ case ejabberd_config:get_local_option(
+ {route_subdomains, LFromDomain},
+ fun(s2s) -> s2s end) of
+ s2s -> % bypass RFC 3920 10.3
+ false;
+ undefined ->
+ Hosts = (?MYHOSTS),
+ P = fun (ParentDomain) ->
+ lists:member(ParentDomain, Hosts)
+ end,
+ lists:any(P, parent_domains(To#jid.lserver))
end.
parent_domains(Domain) ->
- lists:foldl(
- fun(Label, []) ->
- [Label];
- (Label, [Head | Tail]) ->
- [Label ++ "." ++ Head, Head | Tail]
- end, [], lists:reverse(string:tokens(Domain, "."))).
-
-send_element(Pid, El) ->
- Pid ! {send_element, El}.
+ lists:foldl(fun (Label, []) -> [Label];
+ (Label, [Head | Tail]) ->
+ [<<Label/binary, ".", Head/binary>>, Head | Tail]
+ end,
+ [], lists:reverse(str:tokens(Domain, <<".">>))).
+send_element(Pid, El) -> Pid ! {send_element, El}.
%%%----------------------------------------------------------------------
%%% ejabberd commands
commands() ->
- [
- #ejabberd_commands{name = incoming_s2s_number,
- tags = [stats, s2s],
- desc = "Number of incoming s2s connections on the node",
- module = ?MODULE, function = incoming_s2s_number,
- args = [],
- result = {s2s_incoming, integer}},
+ [#ejabberd_commands{name = incoming_s2s_number,
+ tags = [stats, s2s],
+ desc =
+ "Number of incoming s2s connections on "
+ "the node",
+ module = ?MODULE, function = incoming_s2s_number,
+ args = [], result = {s2s_incoming, integer}},
#ejabberd_commands{name = outgoing_s2s_number,
- tags = [stats, s2s],
- desc = "Number of outgoing s2s connections on the node",
- module = ?MODULE, function = outgoing_s2s_number,
- args = [],
- result = {s2s_outgoing, integer}}
- ].
+ tags = [stats, s2s],
+ desc =
+ "Number of outgoing s2s connections on "
+ "the node",
+ module = ?MODULE, function = outgoing_s2s_number,
+ args = [], result = {s2s_outgoing, integer}}].
incoming_s2s_number() ->
length(supervisor:which_children(ejabberd_s2s_in_sup)).
@@ -489,28 +507,21 @@ incoming_s2s_number() ->
outgoing_s2s_number() ->
length(supervisor:which_children(ejabberd_s2s_out_sup)).
-
%%%----------------------------------------------------------------------
%%% Update Mnesia tables
update_tables() ->
case catch mnesia:table_info(s2s, type) of
- bag ->
- ok;
- {'EXIT', _} ->
- ok;
- _ ->
- % XXX TODO convert it ?
- mnesia:delete_table(s2s)
+ bag -> ok;
+ {'EXIT', _} -> ok;
+ _ -> mnesia:delete_table(s2s)
end,
case catch mnesia:table_info(s2s, attributes) of
- [fromto, node, key] ->
- mnesia:transform_table(s2s, ignore, [fromto, pid, key]),
- mnesia:clear_table(s2s);
- [fromto, pid, key] ->
- ok;
- {'EXIT', _} ->
- ok
+ [fromto, node, key] ->
+ mnesia:transform_table(s2s, ignore, [fromto, pid, key]),
+ mnesia:clear_table(s2s);
+ [fromto, pid, key] -> ok;
+ {'EXIT', _} -> ok
end,
case lists:member(local_s2s, mnesia:system_info(tables)) of
true ->
@@ -521,62 +532,74 @@ update_tables() ->
%% Check if host is in blacklist or white list
allow_host(MyServer, S2SHost) ->
- allow_host2(MyServer, S2SHost) andalso (not is_temporarly_blocked(S2SHost)).
+ allow_host2(MyServer, S2SHost) andalso
+ not is_temporarly_blocked(S2SHost).
allow_host2(MyServer, S2SHost) ->
- Hosts = ?MYHOSTS,
- case lists:dropwhile(
- fun(ParentDomain) ->
- not lists:member(ParentDomain, Hosts)
- end, parent_domains(MyServer)) of
- [MyHost|_] ->
- allow_host1(MyHost, S2SHost);
- [] ->
- allow_host1(MyServer, S2SHost)
+ Hosts = (?MYHOSTS),
+ case lists:dropwhile(fun (ParentDomain) ->
+ not lists:member(ParentDomain, Hosts)
+ end,
+ parent_domains(MyServer))
+ of
+ [MyHost | _] -> allow_host1(MyHost, S2SHost);
+ [] -> allow_host1(MyServer, S2SHost)
end.
allow_host1(MyHost, S2SHost) ->
- case ejabberd_config:get_local_option({{s2s_host, S2SHost}, MyHost}) of
- deny -> false;
- allow -> true;
- _ ->
- case ejabberd_config:get_local_option({s2s_default_policy, MyHost}) of
- deny -> false;
- _ ->
- case ejabberd_hooks:run_fold(s2s_allow_host, MyHost,
- allow, [MyHost, S2SHost]) of
- deny -> false;
- allow -> true;
- _ -> true
- end
- end
+ case ejabberd_config:get_local_option(
+ {{s2s_host, S2SHost}, MyHost},
+ fun(deny) -> deny; (allow) -> allow end)
+ of
+ deny -> false;
+ allow -> true;
+ undefined ->
+ case ejabberd_config:get_local_option(
+ {s2s_default_policy, MyHost},
+ fun(deny) -> deny; (allow) -> allow end)
+ of
+ deny -> false;
+ _ ->
+ case ejabberd_hooks:run_fold(s2s_allow_host, MyHost,
+ allow, [MyHost, S2SHost])
+ of
+ deny -> false;
+ allow -> true;
+ _ -> true
+ end
+ end
end.
%% Get information about S2S connections of the specified type.
%% @spec (Type) -> [Info]
%% where Type = in | out
%% Info = [{InfoName::atom(), InfoValue::any()}]
+
get_info_s2s_connections(Type) ->
ChildType = case Type of
- in -> ejabberd_s2s_in_sup;
- out -> ejabberd_s2s_out_sup
+ in -> ejabberd_s2s_in_sup;
+ out -> ejabberd_s2s_out_sup
end,
Connections = supervisor:which_children(ChildType),
- get_s2s_info(Connections,Type).
-
-get_s2s_info(Connections,Type)->
- complete_s2s_info(Connections,Type,[]).
-complete_s2s_info([],_,Result)->
- Result;
-complete_s2s_info([Connection|T],Type,Result)->
- {_,PID,_,_}=Connection,
+ get_s2s_info(Connections, Type).
+
+get_s2s_info(Connections, Type) ->
+ complete_s2s_info(Connections, Type, []).
+
+complete_s2s_info([], _, Result) -> Result;
+complete_s2s_info([Connection | T], Type, Result) ->
+ {_, PID, _, _} = Connection,
State = get_s2s_state(PID),
- complete_s2s_info(T,Type,[State|Result]).
+ complete_s2s_info(T, Type, [State | Result]).
+
+-spec get_s2s_state(pid()) -> [{status, open | closed | error} | {s2s_pid, pid()}].
-get_s2s_state(S2sPid)->
- Infos = case gen_fsm:sync_send_all_state_event(S2sPid,get_state_infos) of
- {state_infos, Is} -> [{status, open} | Is];
- {noproc,_} -> [{status, closed}]; %% Connection closed
- {badrpc,_} -> [{status, error}]
+get_s2s_state(S2sPid) ->
+ Infos = case gen_fsm:sync_send_all_state_event(S2sPid,
+ get_state_infos)
+ of
+ {state_infos, Is} -> [{status, open} | Is];
+ {noproc, _} -> [{status, closed}]; %% Connection closed
+ {badrpc, _} -> [{status, error}]
end,
[{s2s_pid, S2sPid} | Infos].
diff --git a/src/ejabberd_s2s_in.erl b/src/ejabberd_s2s_in.erl
index 6ae4f3446..2dc7c86b2 100644
--- a/src/ejabberd_s2s_in.erl
+++ b/src/ejabberd_s2s_in.erl
@@ -25,119 +25,110 @@
%%%----------------------------------------------------------------------
-module(ejabberd_s2s_in).
+
-author('alexey@process-one.net').
-behaviour(p1_fsm).
%% External exports
--export([start/2,
- start_link/2,
- match_domain/2,
+-export([start/2, start_link/2, match_domain/2,
socket_type/0]).
%% gen_fsm callbacks
--export([init/1,
- wait_for_stream/2,
- wait_for_feature_request/2,
- stream_established/2,
- handle_event/3,
- handle_sync_event/4,
- code_change/4,
- handle_info/3,
- print_state/1,
- terminate/3]).
+-export([init/1, wait_for_stream/2,
+ wait_for_feature_request/2, stream_established/2,
+ handle_event/3, handle_sync_event/4, code_change/4,
+ handle_info/3, print_state/1, terminate/3]).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
--ifdef(SSL40).
--include_lib("public_key/include/public_key.hrl").
+
+-include_lib("public_key/include/public_key.hrl").
+
-define(PKIXEXPLICIT, 'OTP-PUB-KEY').
+
-define(PKIXIMPLICIT, 'OTP-PUB-KEY').
--else.
--ifdef(SSL39).
--include_lib("ssl/include/ssl_pkix.hrl").
--define(PKIXEXPLICIT, 'OTP-PKIX').
--define(PKIXIMPLICIT, 'OTP-PKIX').
--else.
--include_lib("ssl/include/PKIX1Explicit88.hrl").
--include_lib("ssl/include/PKIX1Implicit88.hrl").
--define(PKIXEXPLICIT, 'PKIX1Explicit88').
--define(PKIXIMPLICIT, 'PKIX1Implicit88').
--endif.
--endif.
+
-include("XmppAddr.hrl").
-define(DICT, dict).
--record(state, {socket,
- sockmod,
- streamid,
- shaper,
- tls = false,
- tls_enabled = false,
- tls_required = false,
- tls_certverify = false,
- tls_options = [],
- server,
- authenticated = false,
- auth_domain,
- connections = ?DICT:new(),
- timer}).
-
+-record(state,
+ {socket :: ejabberd_socket:socket_state(),
+ sockmod = ejabberd_socket :: ejabberd_socket | ejabberd_frontend_socket,
+ streamid = <<"">> :: binary(),
+ shaper = none :: shaper:shaper(),
+ tls = false :: boolean(),
+ tls_enabled = false :: boolean(),
+ tls_required = false :: boolean(),
+ tls_certverify = false :: boolean(),
+ tls_options = [] :: list(),
+ server = <<"">> :: binary(),
+ authenticated = false :: boolean(),
+ auth_domain = <<"">> :: binary(),
+ connections = (?DICT):new() :: dict(),
+ timer = make_ref() :: reference()}).
%-define(DBGFSM, true).
-ifdef(DBGFSM).
+
-define(FSMOPTS, [{debug, [trace]}]).
+
-else.
+
-define(FSMOPTS, []).
--endif.
--define(FSMLIMITS, [{max_queue, 2000}]). %% if queue grows more than this, we shutdown this connection.
+-endif.
%% Module start with or without supervisor:
-ifdef(NO_TRANSIENT_SUPERVISORS).
--define(SUPERVISOR_START, p1_fsm:start(ejabberd_s2s_in, [SockData, Opts],
- ?FSMOPTS ++ ?FSMLIMITS)).
+
+-define(SUPERVISOR_START,
+ p1_fsm:start(ejabberd_s2s_in, [SockData, Opts],
+ ?FSMOPTS ++ fsm_limit_opts(Opts)).
+
-else.
--define(SUPERVISOR_START, supervisor:start_child(ejabberd_s2s_in_sup,
- [SockData, Opts])).
+
+-define(SUPERVISOR_START,
+ supervisor:start_child(ejabberd_s2s_in_sup,
+ [SockData, Opts])).
+
-endif.
-define(STREAM_HEADER(Version),
- ("<?xml version='1.0'?>"
- "<stream:stream "
- "xmlns:stream='http://etherx.jabber.org/streams' "
- "xmlns='jabber:server' "
- "xmlns:db='jabber:server:dialback' "
- "id='" ++ StateData#state.streamid ++ "'" ++ Version ++ ">")
- ).
+ <<"<?xml version='1.0'?><stream:stream "
+ "xmlns:stream='http://etherx.jabber.org/stream"
+ "s' xmlns='jabber:server' xmlns:db='jabber:ser"
+ "ver:dialback' id='",
+ (StateData#state.streamid)/binary, "'", Version/binary,
+ ">">>).
--define(STREAM_TRAILER, "</stream:stream>").
+-define(STREAM_TRAILER, <<"</stream:stream>">>).
-define(INVALID_NAMESPACE_ERR,
- xml:element_to_string(?SERR_INVALID_NAMESPACE)).
+ xml:element_to_binary(?SERR_INVALID_NAMESPACE)).
-define(HOST_UNKNOWN_ERR,
- xml:element_to_string(?SERR_HOST_UNKNOWN)).
+ xml:element_to_binary(?SERR_HOST_UNKNOWN)).
-define(INVALID_FROM_ERR,
- xml:element_to_string(?SERR_INVALID_FROM)).
+ xml:element_to_binary(?SERR_INVALID_FROM)).
-define(INVALID_XML_ERR,
- xml:element_to_string(?SERR_XML_NOT_WELL_FORMED)).
+ xml:element_to_binary(?SERR_XML_NOT_WELL_FORMED)).
%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
-start(SockData, Opts) ->
- ?SUPERVISOR_START.
+start(SockData, Opts) -> ?SUPERVISOR_START.
start_link(SockData, Opts) ->
- p1_fsm:start_link(ejabberd_s2s_in, [SockData, Opts], ?FSMOPTS ++ ?FSMLIMITS).
+ p1_fsm:start_link(ejabberd_s2s_in, [SockData, Opts],
+ ?FSMOPTS ++ fsm_limit_opts(Opts)).
-socket_type() ->
- xml_stream.
+socket_type() -> xml_stream.
%%%----------------------------------------------------------------------
%%% Callback functions from gen_fsm
@@ -153,36 +144,44 @@ socket_type() ->
init([{SockMod, Socket}, Opts]) ->
?DEBUG("started: ~p", [{SockMod, Socket}]),
Shaper = case lists:keysearch(shaper, 1, Opts) of
- {value, {_, S}} -> S;
- _ -> none
+ {value, {_, S}} -> S;
+ _ -> none
end,
- {StartTLS, TLSRequired, TLSCertverify} = case ejabberd_config:get_local_option(s2s_use_starttls) of
- UseTls when (UseTls==undefined) or (UseTls==false) ->
- {false, false, false};
- UseTls when (UseTls==true) or (UseTls==optional) ->
- {true, false, false};
- required ->
- {true, true, false};
- required_trusted ->
- {true, true, true}
- end,
- TLSOpts = case ejabberd_config:get_local_option(s2s_certfile) of
- undefined ->
- [];
- CertFile ->
- [{certfile, CertFile}]
+ {StartTLS, TLSRequired, TLSCertverify} =
+ case ejabberd_config:get_local_option(
+ s2s_use_starttls,
+ fun(false) -> false;
+ (true) -> true;
+ (optional) -> optional;
+ (required) -> required;
+ (required_trusted) -> required_trusted
+ end,
+ false) of
+ UseTls
+ when (UseTls == undefined) or
+ (UseTls == false) ->
+ {false, false, false};
+ UseTls
+ when (UseTls == true) or
+ (UseTls ==
+ optional) ->
+ {true, false, false};
+ required -> {true, true, false};
+ required_trusted ->
+ {true, true, true}
+ end,
+ TLSOpts = case ejabberd_config:get_local_option(
+ s2s_certfile,
+ fun iolist_to_binary/1) of
+ undefined -> [];
+ CertFile -> [{certfile, CertFile}]
end,
Timer = erlang:start_timer(?S2STIMEOUT, self(), []),
{ok, wait_for_stream,
- #state{socket = Socket,
- sockmod = SockMod,
- streamid = new_id(),
- shaper = Shaper,
- tls = StartTLS,
- tls_enabled = false,
- tls_required = TLSRequired,
- tls_certverify = TLSCertverify,
- tls_options = TLSOpts,
+ #state{socket = Socket, sockmod = SockMod,
+ streamid = new_id(), shaper = Shaper, tls = StartTLS,
+ tls_enabled = false, tls_required = TLSRequired,
+ tls_certverify = TLSCertverify, tls_options = TLSOpts,
timer = Timer}}.
%%----------------------------------------------------------------------
@@ -192,373 +191,371 @@ init([{SockMod, Socket}, Opts]) ->
%% {stop, Reason, NewStateData}
%%----------------------------------------------------------------------
-wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
- case {xml:get_attr_s("xmlns", Attrs),
- xml:get_attr_s("xmlns:db", Attrs),
- xml:get_attr_s("to", Attrs),
- xml:get_attr_s("version", Attrs) == "1.0"} of
- {"jabber:server", _, Server, true} when
- StateData#state.tls and (not StateData#state.authenticated) ->
- send_text(StateData, ?STREAM_HEADER(" version='1.0'")),
- SASL =
- if
- StateData#state.tls_enabled ->
- case (StateData#state.sockmod):get_peer_certificate(
- StateData#state.socket) of
- {ok, Cert} ->
- case (StateData#state.sockmod):get_verify_result(StateData#state.socket) of
- 0 ->
- [{xmlelement, "mechanisms",
- [{"xmlns", ?NS_SASL}],
- [{xmlelement, "mechanism", [],
- [{xmlcdata, "EXTERNAL"}]}]}];
- CertVerifyRes ->
- case StateData#state.tls_certverify of
- true -> {error_cert_verif, CertVerifyRes, Cert};
- false -> []
- end
- end;
- error ->
- []
+wait_for_stream({xmlstreamstart, _Name, Attrs},
+ StateData) ->
+ case {xml:get_attr_s(<<"xmlns">>, Attrs),
+ xml:get_attr_s(<<"xmlns:db">>, Attrs),
+ xml:get_attr_s(<<"to">>, Attrs),
+ xml:get_attr_s(<<"version">>, Attrs) == <<"1.0">>}
+ of
+ {<<"jabber:server">>, _, Server, true}
+ when StateData#state.tls and
+ not StateData#state.authenticated ->
+ send_text(StateData,
+ ?STREAM_HEADER(<<" version='1.0'">>)),
+ SASL = if StateData#state.tls_enabled ->
+ case
+ (StateData#state.sockmod):get_peer_certificate(StateData#state.socket)
+ of
+ {ok, Cert} ->
+ case
+ (StateData#state.sockmod):get_verify_result(StateData#state.socket)
+ of
+ 0 ->
+ [#xmlel{name = <<"mechanisms">>,
+ attrs = [{<<"xmlns">>, ?NS_SASL}],
+ children =
+ [#xmlel{name = <<"mechanism">>,
+ attrs = [],
+ children =
+ [{xmlcdata,
+ <<"EXTERNAL">>}]}]}];
+ CertVerifyRes ->
+ case StateData#state.tls_certverify of
+ true ->
+ {error_cert_verif, CertVerifyRes,
+ Cert};
+ false -> []
+ end
+ end;
+ error -> []
end;
- true ->
- []
- end,
- StartTLS = if
- StateData#state.tls_enabled ->
- [];
- (not StateData#state.tls_enabled) and (not StateData#state.tls_required) ->
- [{xmlelement, "starttls", [{"xmlns", ?NS_TLS}], []}];
- (not StateData#state.tls_enabled) and StateData#state.tls_required ->
- [{xmlelement, "starttls", [{"xmlns", ?NS_TLS}],
- [{xmlelement, "required", [], []}]
- }]
- end,
- case SASL of
- {error_cert_verif, CertVerifyResult, Certificate} ->
- CertError = tls:get_cert_verify_string(CertVerifyResult, Certificate),
- RemoteServer = xml:get_attr_s("from", Attrs),
- ?INFO_MSG("Closing s2s connection: ~s <--> ~s (~s)", [StateData#state.server, RemoteServer, CertError]),
- send_text(StateData, xml:element_to_string(?SERRT_POLICY_VIOLATION("en", CertError))),
- {atomic, Pid} = ejabberd_s2s:find_connection(jlib:make_jid("", Server, ""), jlib:make_jid("", RemoteServer, "")),
- ejabberd_s2s_out:stop_connection(Pid),
-
- {stop, normal, StateData};
- _ ->
- send_element(StateData,
- {xmlelement, "stream:features", [],
- SASL ++ StartTLS ++
- ejabberd_hooks:run_fold(
- s2s_stream_features,
- Server,
- [], [Server])}),
- {next_state, wait_for_feature_request, StateData#state{server = Server}}
- end;
- {"jabber:server", _, Server, true} when
- StateData#state.authenticated ->
- send_text(StateData, ?STREAM_HEADER(" version='1.0'")),
- send_element(StateData,
- {xmlelement, "stream:features", [],
- ejabberd_hooks:run_fold(
- s2s_stream_features,
- Server,
- [], [Server])}),
- {next_state, stream_established, StateData};
- {"jabber:server", "jabber:server:dialback", _Server, _} ->
- send_text(StateData, ?STREAM_HEADER("")),
- {next_state, stream_established, StateData};
- _ ->
- send_text(StateData, ?INVALID_NAMESPACE_ERR),
- {stop, normal, StateData}
+ true -> []
+ end,
+ StartTLS = if StateData#state.tls_enabled -> [];
+ not StateData#state.tls_enabled and
+ not StateData#state.tls_required ->
+ [#xmlel{name = <<"starttls">>,
+ attrs = [{<<"xmlns">>, ?NS_TLS}],
+ children = []}];
+ not StateData#state.tls_enabled and
+ StateData#state.tls_required ->
+ [#xmlel{name = <<"starttls">>,
+ attrs = [{<<"xmlns">>, ?NS_TLS}],
+ children =
+ [#xmlel{name = <<"required">>,
+ attrs = [], children = []}]}]
+ end,
+ case SASL of
+ {error_cert_verif, CertVerifyResult, Certificate} ->
+ CertError = tls:get_cert_verify_string(CertVerifyResult,
+ Certificate),
+ RemoteServer = xml:get_attr_s(<<"from">>, Attrs),
+ ?INFO_MSG("Closing s2s connection: ~s <--> ~s (~s)",
+ [StateData#state.server, RemoteServer, CertError]),
+ send_text(StateData,
+ xml:element_to_binary(?SERRT_POLICY_VIOLATION(<<"en">>,
+ CertError))),
+ {atomic, Pid} =
+ ejabberd_s2s:find_connection(jlib:make_jid(<<"">>,
+ Server, <<"">>),
+ jlib:make_jid(<<"">>,
+ RemoteServer,
+ <<"">>)),
+ ejabberd_s2s_out:stop_connection(Pid),
+ {stop, normal, StateData};
+ _ ->
+ send_element(StateData,
+ #xmlel{name = <<"stream:features">>, attrs = [],
+ children =
+ SASL ++
+ StartTLS ++
+ ejabberd_hooks:run_fold(s2s_stream_features,
+ Server, [],
+ [Server])}),
+ {next_state, wait_for_feature_request,
+ StateData#state{server = Server}}
+ end;
+ {<<"jabber:server">>, _, Server, true}
+ when StateData#state.authenticated ->
+ send_text(StateData,
+ ?STREAM_HEADER(<<" version='1.0'">>)),
+ send_element(StateData,
+ #xmlel{name = <<"stream:features">>, attrs = [],
+ children =
+ ejabberd_hooks:run_fold(s2s_stream_features,
+ Server, [],
+ [Server])}),
+ {next_state, stream_established, StateData};
+ {<<"jabber:server">>, <<"jabber:server:dialback">>,
+ _Server, _} ->
+ send_text(StateData, ?STREAM_HEADER(<<"">>)),
+ {next_state, stream_established, StateData};
+ _ ->
+ send_text(StateData, ?INVALID_NAMESPACE_ERR),
+ {stop, normal, StateData}
end;
-
wait_for_stream({xmlstreamerror, _}, StateData) ->
send_text(StateData,
- ?STREAM_HEADER("") ++ ?INVALID_XML_ERR ++ ?STREAM_TRAILER),
+ <<(?STREAM_HEADER(<<"">>))/binary,
+ (?INVALID_XML_ERR)/binary, (?STREAM_TRAILER)/binary>>),
{stop, normal, StateData};
-
wait_for_stream(timeout, StateData) ->
{stop, normal, StateData};
-
wait_for_stream(closed, StateData) ->
{stop, normal, StateData}.
-
-wait_for_feature_request({xmlstreamelement, El}, StateData) ->
- {xmlelement, Name, Attrs, Els} = El,
+wait_for_feature_request({xmlstreamelement, El},
+ StateData) ->
+ #xmlel{name = Name, attrs = Attrs, children = Els} = El,
TLS = StateData#state.tls,
TLSEnabled = StateData#state.tls_enabled,
- SockMod = (StateData#state.sockmod):get_sockmod(StateData#state.socket),
- case {xml:get_attr_s("xmlns", Attrs), Name} of
- {?NS_TLS, "starttls"} when TLS == true,
- TLSEnabled == false,
- SockMod == gen_tcp ->
- ?DEBUG("starttls", []),
- Socket = StateData#state.socket,
- TLSOpts = case ejabberd_config:get_local_option(
- {domain_certfile,
- StateData#state.server}) of
- undefined ->
- StateData#state.tls_options;
- CertFile ->
- [{certfile, CertFile} |
- lists:keydelete(
- certfile, 1,
- StateData#state.tls_options)]
- end,
- TLSSocket = (StateData#state.sockmod):starttls(
- Socket, TLSOpts,
- xml:element_to_binary(
- {xmlelement, "proceed", [{"xmlns", ?NS_TLS}], []})),
- {next_state, wait_for_stream,
- StateData#state{socket = TLSSocket,
- streamid = new_id(),
- tls_enabled = true,
- tls_options = TLSOpts
- }};
- {?NS_SASL, "auth"} when TLSEnabled ->
- Mech = xml:get_attr_s("mechanism", Attrs),
- case Mech of
- "EXTERNAL" ->
- Auth = jlib:decode_base64(xml:get_cdata(Els)),
- AuthDomain = jlib:nameprep(Auth),
- AuthRes =
- case (StateData#state.sockmod):get_peer_certificate(
- StateData#state.socket) of
+ SockMod =
+ (StateData#state.sockmod):get_sockmod(StateData#state.socket),
+ case {xml:get_attr_s(<<"xmlns">>, Attrs), Name} of
+ {?NS_TLS, <<"starttls">>}
+ when TLS == true, TLSEnabled == false,
+ SockMod == gen_tcp ->
+ ?DEBUG("starttls", []),
+ Socket = StateData#state.socket,
+ TLSOpts = case
+ ejabberd_config:get_local_option(
+ {domain_certfile, StateData#state.server},
+ fun iolist_to_binary/1) of
+ undefined -> StateData#state.tls_options;
+ CertFile ->
+ [{certfile, CertFile} | lists:keydelete(certfile, 1,
+ StateData#state.tls_options)]
+ end,
+ TLSSocket = (StateData#state.sockmod):starttls(Socket,
+ TLSOpts,
+ xml:element_to_binary(#xmlel{name
+ =
+ <<"proceed">>,
+ attrs
+ =
+ [{<<"xmlns">>,
+ ?NS_TLS}],
+ children
+ =
+ []})),
+ {next_state, wait_for_stream,
+ StateData#state{socket = TLSSocket, streamid = new_id(),
+ tls_enabled = true, tls_options = TLSOpts}};
+ {?NS_SASL, <<"auth">>} when TLSEnabled ->
+ Mech = xml:get_attr_s(<<"mechanism">>, Attrs),
+ case Mech of
+ <<"EXTERNAL">> ->
+ Auth = jlib:decode_base64(xml:get_cdata(Els)),
+ AuthDomain = jlib:nameprep(Auth),
+ AuthRes = case
+ (StateData#state.sockmod):get_peer_certificate(StateData#state.socket)
+ of
{ok, Cert} ->
- case (StateData#state.sockmod):get_verify_result(
- StateData#state.socket) of
- 0 ->
- case AuthDomain of
- error ->
- false;
- _ ->
- case idna:domain_utf8_to_ascii(AuthDomain) of
- false ->
- false;
- PCAuthDomain ->
- lists:any(
- fun(D) ->
- match_domain(
- PCAuthDomain, D)
- end, get_cert_domains(Cert))
- end
- end;
- _ ->
- false
+ case
+ (StateData#state.sockmod):get_verify_result(StateData#state.socket)
+ of
+ 0 ->
+ case AuthDomain of
+ error -> false;
+ _ ->
+ case
+ idna:domain_utf8_to_ascii(AuthDomain)
+ of
+ false -> false;
+ PCAuthDomain ->
+ lists:any(fun (D) ->
+ match_domain(PCAuthDomain,
+ D)
+ end,
+ get_cert_domains(Cert))
+ end
+ end;
+ _ -> false
end;
- error ->
- false
- end,
- AllowRemoteHost = ejabberd_s2s:allow_host("", AuthDomain),
- if
- AuthRes andalso AllowRemoteHost ->
- (StateData#state.sockmod):reset_stream(
- StateData#state.socket),
- send_element(StateData,
- {xmlelement, "success",
- [{"xmlns", ?NS_SASL}], []}),
- ?DEBUG("(~w) Accepted s2s authentication for ~s",
- [StateData#state.socket, AuthDomain]),
-
- %% acess rules are first checked against the globally defined ones, that have precedence over
- %% domain-specific ones.. http://www.process-one.net/docs/ejabberd/guide_en.html#AccessRights
- %% since there is allways a shaper defined globally for s2s, it doesn't matter the actual
- %% local host, since the globall one will be used, even if this domain has a special rule
- change_shaper(StateData, "", jlib:make_jid("", AuthDomain, "")),
- {next_state, wait_for_stream,
- StateData#state{streamid = new_id(),
- authenticated = true,
- auth_domain = AuthDomain
- }};
- true ->
- send_element(StateData,
- {xmlelement, "failure",
- [{"xmlns", ?NS_SASL}], []}),
- send_text(StateData, ?STREAM_TRAILER),
- {stop, normal, StateData}
- end;
- _ ->
- send_element(StateData,
- {xmlelement, "failure",
- [{"xmlns", ?NS_SASL}],
- [{xmlelement, "invalid-mechanism", [], []}]}),
- {stop, normal, StateData}
- end;
- _ ->
- stream_established({xmlstreamelement, El}, StateData)
+ error -> false
+ end,
+ AllowRemoteHost = ejabberd_s2s:allow_host(<<"">>,
+ AuthDomain),
+ if AuthRes andalso AllowRemoteHost ->
+ (StateData#state.sockmod):reset_stream(StateData#state.socket),
+ send_element(StateData,
+ #xmlel{name = <<"success">>,
+ attrs = [{<<"xmlns">>, ?NS_SASL}],
+ children = []}),
+ ?DEBUG("(~w) Accepted s2s authentication for ~s",
+ [StateData#state.socket, AuthDomain]),
+ change_shaper(StateData, <<"">>,
+ jlib:make_jid(<<"">>, AuthDomain, <<"">>)),
+ {next_state, wait_for_stream,
+ StateData#state{streamid = new_id(),
+ authenticated = true,
+ auth_domain = AuthDomain}};
+ true ->
+ send_element(StateData,
+ #xmlel{name = <<"failure">>,
+ attrs = [{<<"xmlns">>, ?NS_SASL}],
+ children = []}),
+ send_text(StateData, ?STREAM_TRAILER),
+ {stop, normal, StateData}
+ end;
+ _ ->
+ send_element(StateData,
+ #xmlel{name = <<"failure">>,
+ attrs = [{<<"xmlns">>, ?NS_SASL}],
+ children =
+ [#xmlel{name = <<"invalid-mechanism">>,
+ attrs = [], children = []}]}),
+ {stop, normal, StateData}
+ end;
+ _ ->
+ stream_established({xmlstreamelement, El}, StateData)
end;
-
-wait_for_feature_request({xmlstreamend, _Name}, StateData) ->
+wait_for_feature_request({xmlstreamend, _Name},
+ StateData) ->
send_text(StateData, ?STREAM_TRAILER),
{stop, normal, StateData};
-
-wait_for_feature_request({xmlstreamerror, _}, StateData) ->
- send_text(StateData, ?INVALID_XML_ERR ++ ?STREAM_TRAILER),
+wait_for_feature_request({xmlstreamerror, _},
+ StateData) ->
+ send_text(StateData,
+ <<(?INVALID_XML_ERR)/binary,
+ (?STREAM_TRAILER)/binary>>),
{stop, normal, StateData};
-
wait_for_feature_request(closed, StateData) ->
{stop, normal, StateData}.
-
stream_established({xmlstreamelement, El}, StateData) ->
cancel_timer(StateData#state.timer),
Timer = erlang:start_timer(?S2STIMEOUT, self(), []),
case is_key_packet(El) of
- {key, To, From, Id, Key} ->
- ?DEBUG("GET KEY: ~p", [{To, From, Id, Key}]),
- LTo = jlib:nameprep(To),
- LFrom = jlib:nameprep(From),
- %% Checks if the from domain is allowed and if the to
- %% domain is handled by this server:
- case {ejabberd_s2s:allow_host(LTo, LFrom),
- lists:member(LTo, ejabberd_router:dirty_get_all_domains())} of
- {true, true} ->
- ejabberd_s2s_out:terminate_if_waiting_delay(LTo, LFrom),
- ejabberd_s2s_out:start(LTo, LFrom,
- {verify, self(),
- Key, StateData#state.streamid}),
- Conns = ?DICT:store({LFrom, LTo}, wait_for_verification,
- StateData#state.connections),
- change_shaper(StateData, LTo, jlib:make_jid("", LFrom, "")),
- {next_state,
- stream_established,
- StateData#state{connections = Conns,
- timer = Timer}};
- {_, false} ->
- send_text(StateData, ?HOST_UNKNOWN_ERR),
- {stop, normal, StateData};
- {false, _} ->
- send_text(StateData, ?INVALID_FROM_ERR),
- {stop, normal, StateData}
- end;
- {verify, To, From, Id, Key} ->
- ?DEBUG("VERIFY KEY: ~p", [{To, From, Id, Key}]),
- LTo = jlib:nameprep(To),
- LFrom = jlib:nameprep(From),
- Type = case ejabberd_s2s:has_key({LTo, LFrom}, Key) of
- true -> "valid";
- _ -> "invalid"
- end,
- %Type = if Key == Key1 -> "valid";
- % true -> "invalid"
- % end,
- send_element(StateData,
- {xmlelement,
- "db:verify",
- [{"from", To},
- {"to", From},
- {"id", Id},
- {"type", Type}],
- []}),
- {next_state, stream_established, StateData#state{timer = Timer}};
- _ ->
- NewEl = jlib:remove_attr("xmlns", El),
- {xmlelement, Name, Attrs, _Els} = NewEl,
- From_s = xml:get_attr_s("from", Attrs),
- From = jlib:string_to_jid(From_s),
- To_s = xml:get_attr_s("to", Attrs),
- To = jlib:string_to_jid(To_s),
- if
- (To /= error) and (From /= error) ->
- LFrom = From#jid.lserver,
- LTo = To#jid.lserver,
- if
- StateData#state.authenticated ->
- case (LFrom == StateData#state.auth_domain)
- andalso
- lists:member(
- LTo,
- ejabberd_router:dirty_get_all_domains()) of
- true ->
- if ((Name == "iq") or
- (Name == "message") or
- (Name == "presence")) ->
- ejabberd_hooks:run(
- s2s_receive_packet,
- LTo,
- [From, To, NewEl]),
- ejabberd_router:route(
- From, To, NewEl);
- true ->
- error
- end;
- false ->
- error
- end;
- true ->
- case ?DICT:find({LFrom, LTo},
- StateData#state.connections) of
- {ok, established} ->
- if ((Name == "iq") or
- (Name == "message") or
- (Name == "presence")) ->
- ejabberd_hooks:run(
- s2s_receive_packet,
- LTo,
- [From, To, NewEl]),
- ejabberd_router:route(
- From, To, NewEl);
- true ->
- error
- end;
- _ ->
- error
- end
- end;
- true ->
- error
- end,
- ejabberd_hooks:run(s2s_loop_debug, [{xmlstreamelement, El}]),
- {next_state, stream_established, StateData#state{timer = Timer}}
+ {key, To, From, Id, Key} ->
+ ?DEBUG("GET KEY: ~p", [{To, From, Id, Key}]),
+ LTo = jlib:nameprep(To),
+ LFrom = jlib:nameprep(From),
+ case {ejabberd_s2s:allow_host(LTo, LFrom),
+ lists:member(LTo,
+ ejabberd_router:dirty_get_all_domains())}
+ of
+ {true, true} ->
+ ejabberd_s2s_out:terminate_if_waiting_delay(LTo, LFrom),
+ ejabberd_s2s_out:start(LTo, LFrom,
+ {verify, self(), Key,
+ StateData#state.streamid}),
+ Conns = (?DICT):store({LFrom, LTo},
+ wait_for_verification,
+ StateData#state.connections),
+ change_shaper(StateData, LTo,
+ jlib:make_jid(<<"">>, LFrom, <<"">>)),
+ {next_state, stream_established,
+ StateData#state{connections = Conns, timer = Timer}};
+ {_, false} ->
+ send_text(StateData, ?HOST_UNKNOWN_ERR),
+ {stop, normal, StateData};
+ {false, _} ->
+ send_text(StateData, ?INVALID_FROM_ERR),
+ {stop, normal, StateData}
+ end;
+ {verify, To, From, Id, Key} ->
+ ?DEBUG("VERIFY KEY: ~p", [{To, From, Id, Key}]),
+ LTo = jlib:nameprep(To),
+ LFrom = jlib:nameprep(From),
+ Type = case ejabberd_s2s:has_key({LTo, LFrom}, Key) of
+ true -> <<"valid">>;
+ _ -> <<"invalid">>
+ end,
+ send_element(StateData,
+ #xmlel{name = <<"db:verify">>,
+ attrs =
+ [{<<"from">>, To}, {<<"to">>, From},
+ {<<"id">>, Id}, {<<"type">>, Type}],
+ children = []}),
+ {next_state, stream_established,
+ StateData#state{timer = Timer}};
+ _ ->
+ NewEl = jlib:remove_attr(<<"xmlns">>, El),
+ #xmlel{name = Name, attrs = Attrs} = NewEl,
+ From_s = xml:get_attr_s(<<"from">>, Attrs),
+ From = jlib:string_to_jid(From_s),
+ To_s = xml:get_attr_s(<<"to">>, Attrs),
+ To = jlib:string_to_jid(To_s),
+ if (To /= error) and (From /= error) ->
+ LFrom = From#jid.lserver,
+ LTo = To#jid.lserver,
+ if StateData#state.authenticated ->
+ case LFrom == StateData#state.auth_domain andalso
+ lists:member(LTo,
+ ejabberd_router:dirty_get_all_domains())
+ of
+ true ->
+ if (Name == <<"iq">>) or (Name == <<"message">>)
+ or (Name == <<"presence">>) ->
+ ejabberd_hooks:run(s2s_receive_packet, LTo,
+ [From, To, NewEl]),
+ ejabberd_router:route(From, To, NewEl);
+ true -> error
+ end;
+ false -> error
+ end;
+ true ->
+ case (?DICT):find({LFrom, LTo},
+ StateData#state.connections)
+ of
+ {ok, established} ->
+ if (Name == <<"iq">>) or (Name == <<"message">>)
+ or (Name == <<"presence">>) ->
+ ejabberd_hooks:run(s2s_receive_packet, LTo,
+ [From, To, NewEl]),
+ ejabberd_router:route(From, To, NewEl);
+ true -> error
+ end;
+ _ -> error
+ end
+ end;
+ true -> error
+ end,
+ ejabberd_hooks:run(s2s_loop_debug,
+ [{xmlstreamelement, El}]),
+ {next_state, stream_established,
+ StateData#state{timer = Timer}}
end;
-
stream_established({valid, From, To}, StateData) ->
send_element(StateData,
- {xmlelement,
- "db:result",
- [{"from", To},
- {"to", From},
- {"type", "valid"}],
- []}),
+ #xmlel{name = <<"db:result">>,
+ attrs =
+ [{<<"from">>, To}, {<<"to">>, From},
+ {<<"type">>, <<"valid">>}],
+ children = []}),
LFrom = jlib:nameprep(From),
LTo = jlib:nameprep(To),
- NSD = StateData#state{
- connections = ?DICT:store({LFrom, LTo}, established,
- StateData#state.connections)},
+ NSD = StateData#state{connections =
+ (?DICT):store({LFrom, LTo}, established,
+ StateData#state.connections)},
{next_state, stream_established, NSD};
-
stream_established({invalid, From, To}, StateData) ->
send_element(StateData,
- {xmlelement,
- "db:result",
- [{"from", To},
- {"to", From},
- {"type", "invalid"}],
- []}),
+ #xmlel{name = <<"db:result">>,
+ attrs =
+ [{<<"from">>, To}, {<<"to">>, From},
+ {<<"type">>, <<"invalid">>}],
+ children = []}),
LFrom = jlib:nameprep(From),
LTo = jlib:nameprep(To),
- NSD = StateData#state{
- connections = ?DICT:erase({LFrom, LTo},
- StateData#state.connections)},
+ NSD = StateData#state{connections =
+ (?DICT):erase({LFrom, LTo},
+ StateData#state.connections)},
{next_state, stream_established, NSD};
-
stream_established({xmlstreamend, _Name}, StateData) ->
{stop, normal, StateData};
-
stream_established({xmlstreamerror, _}, StateData) ->
send_text(StateData,
- ?INVALID_XML_ERR ++ ?STREAM_TRAILER),
+ <<(?INVALID_XML_ERR)/binary,
+ (?STREAM_TRAILER)/binary>>),
{stop, normal, StateData};
-
stream_established(timeout, StateData) ->
{stop, normal, StateData};
-
stream_established(closed, StateData) ->
{stop, normal, StateData}.
-
-
%%----------------------------------------------------------------------
%% Func: StateName/3
%% Returns: {next_state, NextStateName, NextStateData} |
@@ -586,32 +583,30 @@ handle_event(_Event, StateName, StateData) ->
%% {reply, Reply, NextStateName, NextStateData}
%% Reply = {state_infos, [{InfoName::atom(), InfoValue::any()]
%%----------------------------------------------------------------------
-handle_sync_event(get_state_infos, _From, StateName, StateData) ->
+
+handle_sync_event(get_state_infos, _From, StateName,
+ StateData) ->
SockMod = StateData#state.sockmod,
- {Addr,Port} = try SockMod:peername(StateData#state.socket) of
- {ok, {A,P}} -> {A,P};
- {error, _} -> {unknown,unknown}
- catch
- _:_ -> {unknown,unknown}
- end,
- Domains = get_external_hosts(StateData),
- Infos = [
- {direction, in},
- {statename, StateName},
- {addr, Addr},
- {port, Port},
+ {Addr, Port} = try
+ SockMod:peername(StateData#state.socket)
+ of
+ {ok, {A, P}} -> {A, P};
+ {error, _} -> {unknown, unknown}
+ catch
+ _:_ -> {unknown, unknown}
+ end,
+ Domains = get_external_hosts(StateData),
+ Infos = [{direction, in}, {statename, StateName},
+ {addr, Addr}, {port, Port},
{streamid, StateData#state.streamid},
{tls, StateData#state.tls},
{tls_enabled, StateData#state.tls_enabled},
{tls_options, StateData#state.tls_options},
{authenticated, StateData#state.authenticated},
- {shaper, StateData#state.shaper},
- {sockmod, SockMod},
- {domains, Domains}
- ],
+ {shaper, StateData#state.shaper}, {sockmod, SockMod},
+ {domains, Domains}],
Reply = {state_infos, Infos},
- {reply,Reply,StateName,StateData};
-
+ {reply, Reply, StateName, StateData};
%%----------------------------------------------------------------------
%% Func: handle_sync_event/4
%% Returns: {next_state, NextStateName, NextStateData} |
@@ -621,9 +616,9 @@ handle_sync_event(get_state_infos, _From, StateName, StateData) ->
%% {stop, Reason, NewStateData} |
%% {stop, Reason, Reply, NewStateData}
%%----------------------------------------------------------------------
-handle_sync_event(_Event, _From, StateName, StateData) ->
- Reply = ok,
- {reply, Reply, StateName, StateData}.
+handle_sync_event(_Event, _From, StateName,
+ StateData) ->
+ Reply = ok, {reply, Reply, StateName, StateData}.
code_change(_OldVsn, StateName, StateData, _Extra) ->
{ok, StateName, StateData}.
@@ -637,15 +632,12 @@ code_change(_OldVsn, StateName, StateData, _Extra) ->
handle_info({send_text, Text}, StateName, StateData) ->
send_text(StateData, Text),
{next_state, StateName, StateData};
-
handle_info({timeout, Timer, _}, _StateName,
#state{timer = Timer} = StateData) ->
{stop, normal, StateData};
-
handle_info(_, StateName, StateData) ->
{next_state, StateName, StateData}.
-
%%----------------------------------------------------------------------
%% Func: terminate/3
%% Purpose: Shutdown the fsm
@@ -654,21 +646,21 @@ handle_info(_, StateName, StateData) ->
terminate(Reason, _StateName, StateData) ->
?DEBUG("terminated: ~p", [Reason]),
case Reason of
- {process_limit, _} ->
- [ejabberd_s2s:external_host_overloaded(Host) || Host <- get_external_hosts(StateData)];
- _ ->
- ok
+ {process_limit, _} ->
+ [ejabberd_s2s:external_host_overloaded(Host)
+ || Host <- get_external_hosts(StateData)];
+ _ -> ok
end,
(StateData#state.sockmod):close(StateData#state.socket),
ok.
get_external_hosts(StateData) ->
case StateData#state.authenticated of
- true ->
- [StateData#state.auth_domain];
- false ->
- Connections = StateData#state.connections,
- [D || {{D, _}, established} <- dict:to_list(Connections)]
+ true -> [StateData#state.auth_domain];
+ false ->
+ Connections = StateData#state.connections,
+ [D
+ || {{D, _}, established} <- dict:to_list(Connections)]
end.
%%----------------------------------------------------------------------
@@ -676,168 +668,172 @@ get_external_hosts(StateData) ->
%% Purpose: Prepare the state to be printed on error log
%% Returns: State to print
%%----------------------------------------------------------------------
-print_state(State) ->
- State.
+print_state(State) -> State.
%%%----------------------------------------------------------------------
%%% Internal functions
%%%----------------------------------------------------------------------
send_text(StateData, Text) ->
- (StateData#state.sockmod):send(StateData#state.socket, Text).
+ (StateData#state.sockmod):send(StateData#state.socket,
+ Text).
send_element(StateData, El) ->
send_text(StateData, xml:element_to_binary(El)).
-
change_shaper(StateData, Host, JID) ->
- Shaper = acl:match_rule(Host, StateData#state.shaper, JID),
- (StateData#state.sockmod):change_shaper(StateData#state.socket, Shaper).
-
+ Shaper = acl:match_rule(Host, StateData#state.shaper,
+ JID),
+ (StateData#state.sockmod):change_shaper(StateData#state.socket,
+ Shaper).
-new_id() ->
- randoms:get_string().
+new_id() -> randoms:get_string().
cancel_timer(Timer) ->
erlang:cancel_timer(Timer),
- receive
- {timeout, Timer, _} ->
- ok
- after 0 ->
- ok
- end.
-
-
-is_key_packet({xmlelement, Name, Attrs, Els}) when Name == "db:result" ->
- {key,
- xml:get_attr_s("to", Attrs),
- xml:get_attr_s("from", Attrs),
- xml:get_attr_s("id", Attrs),
- xml:get_cdata(Els)};
-is_key_packet({xmlelement, Name, Attrs, Els}) when Name == "db:verify" ->
- {verify,
- xml:get_attr_s("to", Attrs),
- xml:get_attr_s("from", Attrs),
- xml:get_attr_s("id", Attrs),
- xml:get_cdata(Els)};
-is_key_packet(_) ->
- false.
-
+ receive {timeout, Timer, _} -> ok after 0 -> ok end.
+
+is_key_packet(#xmlel{name = Name, attrs = Attrs,
+ children = Els})
+ when Name == <<"db:result">> ->
+ {key, xml:get_attr_s(<<"to">>, Attrs),
+ xml:get_attr_s(<<"from">>, Attrs),
+ xml:get_attr_s(<<"id">>, Attrs), xml:get_cdata(Els)};
+is_key_packet(#xmlel{name = Name, attrs = Attrs,
+ children = Els})
+ when Name == <<"db:verify">> ->
+ {verify, xml:get_attr_s(<<"to">>, Attrs),
+ xml:get_attr_s(<<"from">>, Attrs),
+ xml:get_attr_s(<<"id">>, Attrs), xml:get_cdata(Els)};
+is_key_packet(_) -> false.
get_cert_domains(Cert) ->
{rdnSequence, Subject} =
(Cert#'Certificate'.tbsCertificate)#'TBSCertificate'.subject,
Extensions =
(Cert#'Certificate'.tbsCertificate)#'TBSCertificate'.extensions,
- lists:flatmap(
- fun(#'AttributeTypeAndValue'{type = ?'id-at-commonName',
- value = Val}) ->
- case ?PKIXEXPLICIT:decode('X520CommonName', Val) of
- {ok, {_, D1}} ->
- D = if
- is_list(D1) -> D1;
- is_binary(D1) -> binary_to_list(D1);
- true -> error
- end,
- if
- D /= error ->
- case jlib:string_to_jid(D) of
- #jid{luser = "",
- lserver = LD,
- lresource = ""} ->
- [LD];
- _ ->
- []
- end;
- true ->
- []
- end;
- _ ->
- []
- end;
- (_) ->
- []
- end, lists:flatten(Subject)) ++
- lists:flatmap(
- fun(#'Extension'{extnID = ?'id-ce-subjectAltName',
- extnValue = Val}) ->
- BVal = if
- is_list(Val) -> list_to_binary(Val);
- is_binary(Val) -> Val;
- true -> Val
- end,
- case ?PKIXIMPLICIT:decode('SubjectAltName', BVal) of
- {ok, SANs} ->
- lists:flatmap(
- fun({otherName,
- #'AnotherName'{'type-id' = ?'id-on-xmppAddr',
- value = XmppAddr
- }}) ->
- case 'XmppAddr':decode(
- 'XmppAddr', XmppAddr) of
- {ok, D} when is_binary(D) ->
- case jlib:string_to_jid(
- binary_to_list(D)) of
- #jid{luser = "",
- lserver = LD,
- lresource = ""} ->
- case idna:domain_utf8_to_ascii(LD) of
- false ->
- [];
- PCLD ->
- [PCLD]
- end;
- _ ->
- []
- end;
- _ ->
- []
- end;
- ({dNSName, D}) when is_list(D) ->
- case jlib:string_to_jid(D) of
- #jid{luser = "",
- lserver = LD,
- lresource = ""} ->
- [LD];
- _ ->
- []
- end;
- (_) ->
- []
- end, SANs);
- _ ->
- []
- end;
- (_) ->
- []
- end, Extensions).
-
-match_domain(Domain, Domain) ->
- true;
+ lists:flatmap(fun (#'AttributeTypeAndValue'{type =
+ ?'id-at-commonName',
+ value = Val}) ->
+ case 'OTP-PUB-KEY':decode('X520CommonName', Val) of
+ {ok, {_, D1}} ->
+ D = if is_binary(D1) -> D1;
+ is_binary(D1) -> (D1);
+ true -> error
+ end,
+ if D /= error ->
+ case jlib:string_to_jid(D) of
+ #jid{luser = <<"">>, lserver = LD,
+ lresource = <<"">>} ->
+ [LD];
+ _ -> []
+ end;
+ true -> []
+ end;
+ _ -> []
+ end;
+ (_) -> []
+ end,
+ lists:flatten(Subject))
+ ++
+ lists:flatmap(fun (#'Extension'{extnID =
+ ?'id-ce-subjectAltName',
+ extnValue = Val}) ->
+ BVal = if is_binary(Val) -> iolist_to_binary(Val);
+ is_binary(Val) -> Val;
+ true -> Val
+ end,
+ case 'OTP-PUB-KEY':decode('SubjectAltName', BVal)
+ of
+ {ok, SANs} ->
+ lists:flatmap(fun ({otherName,
+ #'AnotherName'{'type-id' =
+ ?'id-on-xmppAddr',
+ value =
+ XmppAddr}}) ->
+ case
+ 'XmppAddr':decode('XmppAddr',
+ XmppAddr)
+ of
+ {ok, D}
+ when
+ is_binary(D) ->
+ case
+ jlib:string_to_jid((D))
+ of
+ #jid{luser =
+ <<"">>,
+ lserver =
+ LD,
+ lresource =
+ <<"">>} ->
+ case
+ idna:domain_utf8_to_ascii(LD)
+ of
+ false ->
+ [];
+ PCLD ->
+ [PCLD]
+ end;
+ _ -> []
+ end;
+ _ -> []
+ end;
+ ({dNSName, D})
+ when is_binary(D) ->
+ case
+ jlib:string_to_jid(D)
+ of
+ #jid{luser = <<"">>,
+ lserver = LD,
+ lresource =
+ <<"">>} ->
+ [LD];
+ _ -> []
+ end;
+ (_) -> []
+ end,
+ SANs);
+ _ -> []
+ end;
+ (_) -> []
+ end,
+ Extensions).
+
+match_domain(Domain, Domain) -> true;
match_domain(Domain, Pattern) ->
- DLabels = string:tokens(Domain, "."),
- PLabels = string:tokens(Pattern, "."),
+ DLabels = str:tokens(Domain, <<".">>),
+ PLabels = str:tokens(Pattern, <<".">>),
match_labels(DLabels, PLabels).
-match_labels([], []) ->
- true;
-match_labels([], [_ | _]) ->
- false;
-match_labels([_ | _], []) ->
- false;
+match_labels([], []) -> true;
+match_labels([], [_ | _]) -> false;
+match_labels([_ | _], []) -> false;
match_labels([DL | DLabels], [PL | PLabels]) ->
- case lists:all(fun(C) -> (($a =< C) andalso (C =< $z))
- orelse (($0 =< C) andalso (C =< $9))
- orelse (C == $-) orelse (C == $*)
- end, PL) of
- true ->
- Regexp = ejabberd_regexp:sh_to_awk(PL),
- case ejabberd_regexp:run(DL, Regexp) of
- match ->
- match_labels(DLabels, PLabels);
- nomatch ->
- false
- end;
- false ->
- false
+ case lists:all(fun (C) ->
+ $a =< C andalso C =< $z orelse
+ $0 =< C andalso C =< $9 orelse
+ C == $- orelse C == $*
+ end,
+ binary_to_list(PL))
+ of
+ true ->
+ Regexp = ejabberd_regexp:sh_to_awk(PL),
+ case ejabberd_regexp:run(DL, Regexp) of
+ match -> match_labels(DLabels, PLabels);
+ nomatch -> false
+ end;
+ false -> false
+ end.
+
+fsm_limit_opts(Opts) ->
+ case lists:keysearch(max_fsm_queue, 1, Opts) of
+ {value, {_, N}} when is_integer(N) -> [{max_queue, N}];
+ _ ->
+ case ejabberd_config:get_local_option(
+ max_fsm_queue,
+ fun(I) when is_integer(I), I > 0 -> I end) of
+ undefined -> [];
+ N -> [{max_queue, N}]
+ end
end.
diff --git a/src/ejabberd_s2s_out.erl b/src/ejabberd_s2s_out.erl
index 0dedb4c26..4bfb0e732 100644
--- a/src/ejabberd_s2s_out.erl
+++ b/src/ejabberd_s2s_out.erl
@@ -25,6 +25,7 @@
%%%----------------------------------------------------------------------
-module(ejabberd_s2s_out).
+
-author('alexey@process-one.net').
-behaviour(p1_fsm).
@@ -58,30 +59,39 @@
get_addr_port/1]).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
--record(state, {socket,
- streamid,
- use_v10,
- tls = false,
- tls_required = false,
- tls_enabled = false,
- tls_options = [connect],
- authenticated = false,
- db_enabled = true,
- try_auth = true,
- myname, server, queue,
- delay_to_retry = undefined_delay,
- new = false, verify = false,
- bridge,
- timer}).
+-record(state,
+ {socket :: ejabberd_socket:socket_state(),
+ streamid = <<"">> :: binary(),
+ use_v10 = true :: boolean(),
+ tls = false :: boolean(),
+ tls_required = false :: boolean(),
+ tls_enabled = false :: boolean(),
+ tls_options = [connect] :: list(),
+ authenticated = false :: boolean(),
+ db_enabled = true :: boolean(),
+ try_auth = true :: boolean(),
+ myname = <<"">> :: binary(),
+ server = <<"">> :: binary(),
+ queue = queue:new() :: queue(),
+ delay_to_retry = undefined_delay :: undefined_delay | non_neg_integer(),
+ new = false :: false | binary(),
+ verify = false :: false | {pid(), binary(), binary()},
+ bridge :: {atom(), atom()},
+ timer = make_ref() :: reference()}).
%%-define(DBGFSM, true).
-ifdef(DBGFSM).
+
-define(FSMOPTS, [{debug, [trace]}]).
+
-else.
+
-define(FSMOPTS, []).
+
-endif.
%% Module start with or without supervisor:
@@ -103,25 +113,21 @@
-define(MAX_RETRY_DELAY, 300000).
-define(STREAM_HEADER,
- "<?xml version='1.0'?>"
- "<stream:stream "
- "xmlns:stream='http://etherx.jabber.org/streams' "
- "xmlns='jabber:server' "
- "xmlns:db='jabber:server:dialback' "
- "from='~s' "
- "to='~s'~s>"
- ).
+ <<"<?xml version='1.0'?><stream:stream "
+ "xmlns:stream='http://etherx.jabber.org/stream"
+ "s' xmlns='jabber:server' xmlns:db='jabber:ser"
+ "ver:dialback' from='~s' to='~s'~s>">>).
--define(STREAM_TRAILER, "</stream:stream>").
+-define(STREAM_TRAILER, <<"</stream:stream>">>).
-define(INVALID_NAMESPACE_ERR,
- xml:element_to_string(?SERR_INVALID_NAMESPACE)).
+ xml:element_to_binary(?SERR_INVALID_NAMESPACE)).
-define(HOST_UNKNOWN_ERR,
- xml:element_to_string(?SERR_HOST_UNKNOWN)).
+ xml:element_to_binary(?SERR_HOST_UNKNOWN)).
-define(INVALID_XML_ERR,
- xml:element_to_string(?SERR_XML_NOT_WELL_FORMED)).
+ xml:element_to_binary(?SERR_XML_NOT_WELL_FORMED)).
-define(SOCKET_DEFAULT_RESULT, {error, badarg}).
@@ -133,13 +139,11 @@ start(From, Host, Type) ->
start_link(From, Host, Type) ->
p1_fsm:start_link(ejabberd_s2s_out, [From, Host, Type],
- fsm_limit_opts() ++ ?FSMOPTS).
+ fsm_limit_opts() ++ (?FSMOPTS)).
-start_connection(Pid) ->
- p1_fsm:send_event(Pid, init).
+start_connection(Pid) -> p1_fsm:send_event(Pid, init).
-stop_connection(Pid) ->
- p1_fsm:send_event(Pid, closed).
+stop_connection(Pid) -> p1_fsm:send_event(Pid, closed).
%%%----------------------------------------------------------------------
%%% Callback functions from p1_fsm
@@ -155,39 +159,47 @@ stop_connection(Pid) ->
init([From, Server, Type]) ->
process_flag(trap_exit, true),
?DEBUG("started: ~p", [{From, Server, Type}]),
- {TLS, TLSRequired} = case ejabberd_config:get_local_option(s2s_use_starttls) of
- UseTls when (UseTls==undefined) or (UseTls==false) ->
- {false, false};
- UseTls when (UseTls==true) or (UseTls==optional) ->
- {true, false};
- UseTls when (UseTls==required) or (UseTls==required_trusted) ->
- {true, true}
- end,
+ {TLS, TLSRequired} = case
+ ejabberd_config:get_local_option(
+ s2s_use_starttls,
+ fun(true) -> true;
+ (false) -> false;
+ (optional) -> optional;
+ (required) -> required;
+ (required_trusted) -> required_trusted
+ end)
+ of
+ UseTls
+ when (UseTls == undefined) or
+ (UseTls == false) ->
+ {false, false};
+ UseTls
+ when (UseTls == true) or (UseTls == optional) ->
+ {true, false};
+ UseTls
+ when (UseTls == required) or
+ (UseTls == required_trusted) ->
+ {true, true}
+ end,
UseV10 = TLS,
- TLSOpts = case ejabberd_config:get_local_option(s2s_certfile) of
- undefined ->
- [connect];
- CertFile ->
- [{certfile, CertFile}, connect]
+ TLSOpts = case
+ ejabberd_config:get_local_option(
+ s2s_certfile, fun iolist_to_binary/1)
+ of
+ undefined -> [connect];
+ CertFile -> [{certfile, CertFile}, connect]
end,
{New, Verify} = case Type of
- {new, Key} ->
- {Key, false};
- {verify, Pid, Key, SID} ->
- start_connection(self()),
- {false, {Pid, Key, SID}}
+ {new, Key} -> {Key, false};
+ {verify, Pid, Key, SID} ->
+ start_connection(self()), {false, {Pid, Key, SID}}
end,
Timer = erlang:start_timer(?S2STIMEOUT, self(), []),
- {ok, open_socket, #state{use_v10 = UseV10,
- tls = TLS,
- tls_required = TLSRequired,
- tls_options = TLSOpts,
- queue = queue:new(),
- myname = From,
- server = Server,
- new = New,
- verify = Verify,
- timer = Timer}}.
+ {ok, open_socket,
+ #state{use_v10 = UseV10, tls = TLS,
+ tls_required = TLSRequired, tls_options = TLSOpts,
+ queue = queue:new(), myname = From, server = Server,
+ new = New, verify = Verify, timer = Timer}}.
%%----------------------------------------------------------------------
%% Func: StateName/2
@@ -196,64 +208,64 @@ init([From, Server, Type]) ->
%% {stop, Reason, NewStateData}
%%----------------------------------------------------------------------
open_socket(init, StateData) ->
- log_s2s_out(StateData#state.new,
- StateData#state.myname,
- StateData#state.server,
- StateData#state.tls),
- ?DEBUG("open_socket: ~p", [{StateData#state.myname,
- StateData#state.server,
- StateData#state.new,
- StateData#state.verify}]),
- AddrList = case idna:domain_utf8_to_ascii(StateData#state.server) of
- false -> [];
- ASCIIAddr ->
- get_addr_port(ASCIIAddr)
+ log_s2s_out(StateData#state.new, StateData#state.myname,
+ StateData#state.server, StateData#state.tls),
+ ?DEBUG("open_socket: ~p",
+ [{StateData#state.myname, StateData#state.server,
+ StateData#state.new, StateData#state.verify}]),
+ AddrList = case
+ idna:domain_utf8_to_ascii(StateData#state.server)
+ of
+ false -> [];
+ ASCIIAddr -> get_addr_port(ASCIIAddr)
end,
- case lists:foldl(fun({Addr, Port}, Acc) ->
+ case lists:foldl(fun ({Addr, Port}, Acc) ->
case Acc of
- {ok, Socket} ->
- {ok, Socket};
- _ ->
- open_socket1(Addr, Port)
+ {ok, Socket} -> {ok, Socket};
+ _ -> open_socket1(Addr, Port)
end
- end, ?SOCKET_DEFAULT_RESULT, AddrList) of
- {ok, Socket} ->
- Version = if
- StateData#state.use_v10 ->
- " version='1.0'";
- true ->
- ""
- end,
- NewStateData = StateData#state{socket = Socket,
- tls_enabled = false,
- streamid = new_id()},
- send_text(NewStateData, io_lib:format(?STREAM_HEADER,
- [StateData#state.myname, StateData#state.server,
- Version])),
- {next_state, wait_for_stream, NewStateData, ?FSMTIMEOUT};
- {error, _Reason} ->
- ?INFO_MSG("s2s connection: ~s -> ~s (remote server not found)",
- [StateData#state.myname, StateData#state.server]),
- case ejabberd_hooks:run_fold(find_s2s_bridge,
- undefined,
- [StateData#state.myname,
- StateData#state.server]) of
- {Mod, Fun, Type} ->
- ?INFO_MSG("found a bridge to ~s for: ~s -> ~s",
- [Type, StateData#state.myname,
- StateData#state.server]),
- NewStateData = StateData#state{bridge={Mod, Fun}},
- {next_state, relay_to_bridge, NewStateData};
- _ ->
- wait_before_reconnect(StateData)
- end
+ end,
+ ?SOCKET_DEFAULT_RESULT, AddrList)
+ of
+ {ok, Socket} ->
+ Version = if StateData#state.use_v10 ->
+ <<" version='1.0'">>;
+ true -> <<"">>
+ end,
+ NewStateData = StateData#state{socket = Socket,
+ tls_enabled = false,
+ streamid = new_id()},
+ send_text(NewStateData,
+ io_lib:format(?STREAM_HEADER,
+ [StateData#state.myname,
+ StateData#state.server, Version])),
+ {next_state, wait_for_stream, NewStateData,
+ ?FSMTIMEOUT};
+ {error, _Reason} ->
+ ?INFO_MSG("s2s connection: ~s -> ~s (remote server "
+ "not found)",
+ [StateData#state.myname, StateData#state.server]),
+ case ejabberd_hooks:run_fold(find_s2s_bridge, undefined,
+ [StateData#state.myname,
+ StateData#state.server])
+ of
+ {Mod, Fun, Type} ->
+ ?INFO_MSG("found a bridge to ~s for: ~s -> ~s",
+ [Type, StateData#state.myname,
+ StateData#state.server]),
+ NewStateData = StateData#state{bridge = {Mod, Fun}},
+ {next_state, relay_to_bridge, NewStateData};
+ _ -> wait_before_reconnect(StateData)
+ end
end;
open_socket(closed, StateData) ->
- ?INFO_MSG("s2s connection: ~s -> ~s (stopped in open socket)",
+ ?INFO_MSG("s2s connection: ~s -> ~s (stopped in "
+ "open socket)",
[StateData#state.myname, StateData#state.server]),
{stop, normal, StateData};
open_socket(timeout, StateData) ->
- ?INFO_MSG("s2s connection: ~s -> ~s (timeout in open socket)",
+ ?INFO_MSG("s2s connection: ~s -> ~s (timeout in "
+ "open socket)",
[StateData#state.myname, StateData#state.server]),
{stop, normal, StateData};
open_socket(_, StateData) ->
@@ -261,432 +273,483 @@ open_socket(_, StateData) ->
%%----------------------------------------------------------------------
%% IPv4
-open_socket1({_,_,_,_} = Addr, Port) ->
+open_socket1({_, _, _, _} = Addr, Port) ->
open_socket2(inet, Addr, Port);
-
%% IPv6
-open_socket1({_,_,_,_,_,_,_,_} = Addr, Port) ->
+open_socket1({_, _, _, _, _, _, _, _} = Addr, Port) ->
open_socket2(inet6, Addr, Port);
-
%% Hostname
open_socket1(Host, Port) ->
- lists:foldl(fun(_Family, {ok, _Socket} = R) ->
- R;
- (Family, _) ->
+ lists:foldl(fun (_Family, {ok, _Socket} = R) -> R;
+ (Family, _) ->
Addrs = get_addrs(Host, Family),
- lists:foldl(fun(_Addr, {ok, _Socket} = R) ->
- R;
- (Addr, _) ->
- open_socket1(Addr, Port)
- end, ?SOCKET_DEFAULT_RESULT, Addrs)
- end, ?SOCKET_DEFAULT_RESULT, outgoing_s2s_families()).
+ lists:foldl(fun (_Addr, {ok, _Socket} = R) -> R;
+ (Addr, _) -> open_socket1(Addr, Port)
+ end,
+ ?SOCKET_DEFAULT_RESULT, Addrs)
+ end,
+ ?SOCKET_DEFAULT_RESULT, outgoing_s2s_families()).
open_socket2(Type, Addr, Port) ->
?DEBUG("s2s_out: connecting to ~p:~p~n", [Addr, Port]),
Timeout = outgoing_s2s_timeout(),
- SockOpts = try erlang:system_info(otp_release) >= "R13B" of
- true -> [{send_timeout_close, true}];
- false -> []
+ SockOpts = try erlang:system_info(otp_release) >= "R13B"
+ of
+ true -> [{send_timeout_close, true}];
+ false -> []
catch
- _:_ -> []
+ _:_ -> []
end,
- case (catch ejabberd_socket:connect(Addr, Port,
- [binary, {packet, 0},
- {send_timeout, ?TCP_SEND_TIMEOUT},
- {active, false}, Type | SockOpts],
- Timeout)) of
- {ok, _Socket} = R -> R;
- {error, Reason} = R ->
- ?DEBUG("s2s_out: connect return ~p~n", [Reason]),
- R;
- {'EXIT', Reason} ->
- ?DEBUG("s2s_out: connect crashed ~p~n", [Reason]),
- {error, Reason}
+ case catch ejabberd_socket:connect(Addr, Port,
+ [binary, {packet, 0},
+ {send_timeout, ?TCP_SEND_TIMEOUT},
+ {active, false}, Type
+ | SockOpts],
+ Timeout)
+ of
+ {ok, _Socket} = R -> R;
+ {error, Reason} = R ->
+ ?DEBUG("s2s_out: connect return ~p~n", [Reason]), R;
+ {'EXIT', Reason} ->
+ ?DEBUG("s2s_out: connect crashed ~p~n", [Reason]),
+ {error, Reason}
end.
%%----------------------------------------------------------------------
-
-wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
- case {xml:get_attr_s("xmlns", Attrs),
- xml:get_attr_s("xmlns:db", Attrs),
- xml:get_attr_s("version", Attrs) == "1.0"} of
- {"jabber:server", "jabber:server:dialback", false} ->
- send_db_request(StateData);
- {"jabber:server", "jabber:server:dialback", true} when
- StateData#state.use_v10 ->
- {next_state, wait_for_features, StateData, ?FSMTIMEOUT};
- %% Clause added to handle Tigase's workaround for an old ejabberd bug:
- {"jabber:server", "jabber:server:dialback", true} when
- not StateData#state.use_v10 ->
- send_db_request(StateData);
- {"jabber:server", "", true} when StateData#state.use_v10 ->
- {next_state, wait_for_features, StateData#state{db_enabled = false}, ?FSMTIMEOUT};
- {NSProvided, DB, _} ->
- send_text(StateData, ?INVALID_NAMESPACE_ERR),
- ?INFO_MSG("Closing s2s connection: ~s -> ~s (invalid namespace).~n"
- "Namespace provided: ~p~nNamespace expected: \"jabber:server\"~n"
- "xmlns:db provided: ~p~nAll attributes: ~p",
- [StateData#state.myname, StateData#state.server, NSProvided, DB, Attrs]),
- {stop, normal, StateData}
+wait_for_stream({xmlstreamstart, _Name, Attrs},
+ StateData) ->
+ case {xml:get_attr_s(<<"xmlns">>, Attrs),
+ xml:get_attr_s(<<"xmlns:db">>, Attrs),
+ xml:get_attr_s(<<"version">>, Attrs) == <<"1.0">>}
+ of
+ {<<"jabber:server">>, <<"jabber:server:dialback">>,
+ false} ->
+ send_db_request(StateData);
+ {<<"jabber:server">>, <<"jabber:server:dialback">>,
+ true}
+ when StateData#state.use_v10 ->
+ {next_state, wait_for_features, StateData, ?FSMTIMEOUT};
+ %% Clause added to handle Tigase's workaround for an old ejabberd bug:
+ {<<"jabber:server">>, <<"jabber:server:dialback">>,
+ true}
+ when not StateData#state.use_v10 ->
+ send_db_request(StateData);
+ {<<"jabber:server">>, <<"">>, true}
+ when StateData#state.use_v10 ->
+ {next_state, wait_for_features,
+ StateData#state{db_enabled = false}, ?FSMTIMEOUT};
+ {NSProvided, DB, _} ->
+ send_text(StateData, ?INVALID_NAMESPACE_ERR),
+ ?INFO_MSG("Closing s2s connection: ~s -> ~s (invalid "
+ "namespace).~nNamespace provided: ~p~nNamespac"
+ "e expected: \"jabber:server\"~nxmlns:db "
+ "provided: ~p~nAll attributes: ~p",
+ [StateData#state.myname, StateData#state.server,
+ NSProvided, DB, Attrs]),
+ {stop, normal, StateData}
end;
-
wait_for_stream({xmlstreamerror, _}, StateData) ->
send_text(StateData,
- ?INVALID_XML_ERR ++ ?STREAM_TRAILER),
- ?INFO_MSG("Closing s2s connection: ~s -> ~s (invalid xml)",
+ <<(?INVALID_XML_ERR)/binary,
+ (?STREAM_TRAILER)/binary>>),
+ ?INFO_MSG("Closing s2s connection: ~s -> ~s (invalid "
+ "xml)",
[StateData#state.myname, StateData#state.server]),
{stop, normal, StateData};
-
-wait_for_stream({xmlstreamend,_Name}, StateData) ->
+wait_for_stream({xmlstreamend, _Name}, StateData) ->
?INFO_MSG("Closing s2s connection: ~s -> ~s (xmlstreamend)",
[StateData#state.myname, StateData#state.server]),
{stop, normal, StateData};
-
wait_for_stream(timeout, StateData) ->
- ?INFO_MSG("Closing s2s connection: ~s -> ~s (timeout in wait_for_stream)",
+ ?INFO_MSG("Closing s2s connection: ~s -> ~s (timeout "
+ "in wait_for_stream)",
[StateData#state.myname, StateData#state.server]),
{stop, normal, StateData};
-
wait_for_stream(closed, StateData) ->
- ?INFO_MSG("Closing s2s connection: ~s -> ~s (close in wait_for_stream)",
+ ?INFO_MSG("Closing s2s connection: ~s -> ~s (close "
+ "in wait_for_stream)",
[StateData#state.myname, StateData#state.server]),
{stop, normal, StateData}.
-
-
-wait_for_validation({xmlstreamelement, El}, StateData) ->
+wait_for_validation({xmlstreamelement, El},
+ StateData) ->
case is_verify_res(El) of
- {result, To, From, Id, Type} ->
- ?DEBUG("recv result: ~p", [{From, To, Id, Type}]),
- case {Type, StateData#state.tls_enabled, StateData#state.tls_required} of
- {"valid", Enabled, Required} when (Enabled==true) or (Required==false) ->
- send_queue(StateData, StateData#state.queue),
- ?INFO_MSG("Connection established: ~s -> ~s with TLS=~p",
- [StateData#state.myname, StateData#state.server, StateData#state.tls_enabled]),
- ejabberd_hooks:run(s2s_connect_hook,
- [StateData#state.myname,
- StateData#state.server]),
- {next_state, stream_established,
- StateData#state{queue = queue:new()}};
- {"valid", Enabled, Required} when (Enabled==false) and (Required==true) ->
- %% TODO: bounce packets
- ?INFO_MSG("Closing s2s connection: ~s -> ~s (TLS is required but unavailable)",
- [StateData#state.myname, StateData#state.server]),
- {stop, normal, StateData};
- _ ->
- %% TODO: bounce packets
- ?INFO_MSG("Closing s2s connection: ~s -> ~s (invalid dialback key)",
- [StateData#state.myname, StateData#state.server]),
- {stop, normal, StateData}
- end;
- {verify, To, From, Id, Type} ->
- ?DEBUG("recv verify: ~p", [{From, To, Id, Type}]),
- case StateData#state.verify of
- false ->
- NextState = wait_for_validation,
- %% TODO: Should'nt we close the connection here ?
- {next_state, NextState, StateData,
- get_timeout_interval(NextState)};
- {Pid, _Key, _SID} ->
- case Type of
- "valid" ->
- p1_fsm:send_event(
- Pid, {valid,
- StateData#state.server,
- StateData#state.myname});
- _ ->
- p1_fsm:send_event(
- Pid, {invalid,
- StateData#state.server,
- StateData#state.myname})
- end,
- if
- StateData#state.verify == false ->
- {stop, normal, StateData};
- true ->
- NextState = wait_for_validation,
- {next_state, NextState, StateData,
- get_timeout_interval(NextState)}
- end
- end;
- _ ->
- {next_state, wait_for_validation, StateData, ?FSMTIMEOUT*3}
+ {result, To, From, Id, Type} ->
+ ?DEBUG("recv result: ~p", [{From, To, Id, Type}]),
+ case {Type, StateData#state.tls_enabled,
+ StateData#state.tls_required}
+ of
+ {<<"valid">>, Enabled, Required}
+ when (Enabled == true) or (Required == false) ->
+ send_queue(StateData, StateData#state.queue),
+ ?INFO_MSG("Connection established: ~s -> ~s with "
+ "TLS=~p",
+ [StateData#state.myname, StateData#state.server,
+ StateData#state.tls_enabled]),
+ ejabberd_hooks:run(s2s_connect_hook,
+ [StateData#state.myname,
+ StateData#state.server]),
+ {next_state, stream_established,
+ StateData#state{queue = queue:new()}};
+ {<<"valid">>, Enabled, Required}
+ when (Enabled == false) and (Required == true) ->
+ ?INFO_MSG("Closing s2s connection: ~s -> ~s (TLS "
+ "is required but unavailable)",
+ [StateData#state.myname, StateData#state.server]),
+ {stop, normal, StateData};
+ _ ->
+ ?INFO_MSG("Closing s2s connection: ~s -> ~s (invalid "
+ "dialback key)",
+ [StateData#state.myname, StateData#state.server]),
+ {stop, normal, StateData}
+ end;
+ {verify, To, From, Id, Type} ->
+ ?DEBUG("recv verify: ~p", [{From, To, Id, Type}]),
+ case StateData#state.verify of
+ false ->
+ NextState = wait_for_validation,
+ {next_state, NextState, StateData,
+ get_timeout_interval(NextState)};
+ {Pid, _Key, _SID} ->
+ case Type of
+ <<"valid">> ->
+ p1_fsm:send_event(Pid,
+ {valid, StateData#state.server,
+ StateData#state.myname});
+ _ ->
+ p1_fsm:send_event(Pid,
+ {invalid, StateData#state.server,
+ StateData#state.myname})
+ end,
+ if StateData#state.verify == false ->
+ {stop, normal, StateData};
+ true ->
+ NextState = wait_for_validation,
+ {next_state, NextState, StateData,
+ get_timeout_interval(NextState)}
+ end
+ end;
+ _ ->
+ {next_state, wait_for_validation, StateData,
+ (?FSMTIMEOUT) * 3}
end;
-
wait_for_validation({xmlstreamend, _Name}, StateData) ->
?INFO_MSG("wait for validation: ~s -> ~s (xmlstreamend)",
[StateData#state.myname, StateData#state.server]),
{stop, normal, StateData};
-
wait_for_validation({xmlstreamerror, _}, StateData) ->
?INFO_MSG("wait for validation: ~s -> ~s (xmlstreamerror)",
[StateData#state.myname, StateData#state.server]),
send_text(StateData,
- ?INVALID_XML_ERR ++ ?STREAM_TRAILER),
+ <<(?INVALID_XML_ERR)/binary,
+ (?STREAM_TRAILER)/binary>>),
{stop, normal, StateData};
-
-wait_for_validation(timeout, #state{verify = {VPid, VKey, SID}} = StateData)
- when is_pid(VPid) and is_list(VKey) and is_list(SID) ->
- %% This is an auxiliary s2s connection for dialback.
- %% This timeout is normal and doesn't represent a problem.
- ?DEBUG("wait_for_validation: ~s -> ~s (timeout in verify connection)",
+wait_for_validation(timeout,
+ #state{verify = {VPid, VKey, SID}} = StateData)
+ when is_pid(VPid) and is_binary(VKey) and
+ is_binary(SID) ->
+ ?DEBUG("wait_for_validation: ~s -> ~s (timeout "
+ "in verify connection)",
[StateData#state.myname, StateData#state.server]),
{stop, normal, StateData};
-
wait_for_validation(timeout, StateData) ->
- ?INFO_MSG("wait_for_validation: ~s -> ~s (connect timeout)",
+ ?INFO_MSG("wait_for_validation: ~s -> ~s (connect "
+ "timeout)",
[StateData#state.myname, StateData#state.server]),
{stop, normal, StateData};
-
wait_for_validation(closed, StateData) ->
?INFO_MSG("wait for validation: ~s -> ~s (closed)",
[StateData#state.myname, StateData#state.server]),
{stop, normal, StateData}.
-
wait_for_features({xmlstreamelement, El}, StateData) ->
case El of
- {xmlelement, "stream:features", _Attrs, Els} ->
- {SASLEXT, StartTLS, StartTLSRequired} =
- lists:foldl(
- fun({xmlelement, "mechanisms", Attrs1, Els1} = _El1,
- {_SEXT, STLS, STLSReq} = Acc) ->
- case xml:get_attr_s("xmlns", Attrs1) of
- ?NS_SASL ->
- NewSEXT =
- lists:any(
- fun({xmlelement, "mechanism", _, Els2}) ->
- case xml:get_cdata(Els2) of
- "EXTERNAL" -> true;
- _ -> false
- end;
- (_) -> false
- end, Els1),
- {NewSEXT, STLS, STLSReq};
- _ ->
- Acc
- end;
- ({xmlelement, "starttls", Attrs1, _Els1} = El1,
- {SEXT, _STLS, _STLSReq} = Acc) ->
- case xml:get_attr_s("xmlns", Attrs1) of
- ?NS_TLS ->
- Req = case xml:get_subtag(El1, "required") of
- {xmlelement, _, _, _} -> true;
- false -> false
- end,
- {SEXT, true, Req};
- _ ->
- Acc
- end;
- (_, Acc) ->
- Acc
- end, {false, false, false}, Els),
- if
- (not SASLEXT) and (not StartTLS) and
- StateData#state.authenticated ->
- send_queue(StateData, StateData#state.queue),
- ?INFO_MSG("Connection established: ~s -> ~s",
- [StateData#state.myname, StateData#state.server]),
- ejabberd_hooks:run(s2s_connect_hook,
- [StateData#state.myname,
- StateData#state.server]),
- {next_state, stream_established,
- StateData#state{queue = queue:new()}};
- SASLEXT and StateData#state.try_auth and
- (StateData#state.new /= false) ->
- send_element(StateData,
- {xmlelement, "auth",
- [{"xmlns", ?NS_SASL},
- {"mechanism", "EXTERNAL"}],
- [{xmlcdata,
- jlib:encode_base64(
- StateData#state.myname)}]}),
- {next_state, wait_for_auth_result,
- StateData#state{try_auth = false}, ?FSMTIMEOUT};
- StartTLS and StateData#state.tls and
- (not StateData#state.tls_enabled) ->
- send_element(StateData,
- {xmlelement, "starttls",
- [{"xmlns", ?NS_TLS}], []}),
- {next_state, wait_for_starttls_proceed, StateData,
- ?FSMTIMEOUT};
- StartTLSRequired and (not StateData#state.tls) ->
- ?DEBUG("restarted: ~p", [{StateData#state.myname,
- StateData#state.server}]),
- ejabberd_socket:close(StateData#state.socket),
- {next_state, reopen_socket,
- StateData#state{socket = undefined,
- use_v10 = false}, ?FSMTIMEOUT};
- StateData#state.db_enabled ->
- send_db_request(StateData);
- true ->
- ?DEBUG("restarted: ~p", [{StateData#state.myname,
- StateData#state.server}]),
- % TODO: clear message queue
- ejabberd_socket:close(StateData#state.socket),
- {next_state, reopen_socket, StateData#state{socket = undefined,
- use_v10 = false}, ?FSMTIMEOUT}
- end;
- _ ->
- send_text(StateData,
- xml:element_to_string(?SERR_BAD_FORMAT) ++
- ?STREAM_TRAILER),
- ?INFO_MSG("Closing s2s connection: ~s -> ~s (bad format)",
- [StateData#state.myname, StateData#state.server]),
- {stop, normal, StateData}
+ #xmlel{name = <<"stream:features">>, children = Els} ->
+ {SASLEXT, StartTLS, StartTLSRequired} = lists:foldl(fun
+ (#xmlel{name =
+ <<"mechanisms">>,
+ attrs =
+ Attrs1,
+ children
+ =
+ Els1} =
+ _El1,
+ {_SEXT, STLS,
+ STLSReq} =
+ Acc) ->
+ case
+ xml:get_attr_s(<<"xmlns">>,
+ Attrs1)
+ of
+ ?NS_SASL ->
+ NewSEXT =
+ lists:any(fun
+ (#xmlel{name
+ =
+ <<"mechanism">>,
+ children
+ =
+ Els2}) ->
+ case
+ xml:get_cdata(Els2)
+ of
+ <<"EXTERNAL">> ->
+ true;
+ _ ->
+ false
+ end;
+ (_) ->
+ false
+ end,
+ Els1),
+ {NewSEXT,
+ STLS,
+ STLSReq};
+ _ -> Acc
+ end;
+ (#xmlel{name =
+ <<"starttls">>,
+ attrs =
+ Attrs1} =
+ El1,
+ {SEXT, _STLS,
+ _STLSReq} =
+ Acc) ->
+ case
+ xml:get_attr_s(<<"xmlns">>,
+ Attrs1)
+ of
+ ?NS_TLS ->
+ Req =
+ case
+ xml:get_subtag(El1,
+ <<"required">>)
+ of
+ #xmlel{} ->
+ true;
+ false ->
+ false
+ end,
+ {SEXT,
+ true,
+ Req};
+ _ -> Acc
+ end;
+ (_, Acc) -> Acc
+ end,
+ {false, false,
+ false},
+ Els),
+ if not SASLEXT and not StartTLS and
+ StateData#state.authenticated ->
+ send_queue(StateData, StateData#state.queue),
+ ?INFO_MSG("Connection established: ~s -> ~s",
+ [StateData#state.myname, StateData#state.server]),
+ ejabberd_hooks:run(s2s_connect_hook,
+ [StateData#state.myname,
+ StateData#state.server]),
+ {next_state, stream_established,
+ StateData#state{queue = queue:new()}};
+ SASLEXT and StateData#state.try_auth and
+ (StateData#state.new /= false) ->
+ send_element(StateData,
+ #xmlel{name = <<"auth">>,
+ attrs =
+ [{<<"xmlns">>, ?NS_SASL},
+ {<<"mechanism">>, <<"EXTERNAL">>}],
+ children =
+ [{xmlcdata,
+ jlib:encode_base64(StateData#state.myname)}]}),
+ {next_state, wait_for_auth_result,
+ StateData#state{try_auth = false}, ?FSMTIMEOUT};
+ StartTLS and StateData#state.tls and
+ not StateData#state.tls_enabled ->
+ send_element(StateData,
+ #xmlel{name = <<"starttls">>,
+ attrs = [{<<"xmlns">>, ?NS_TLS}],
+ children = []}),
+ {next_state, wait_for_starttls_proceed, StateData,
+ ?FSMTIMEOUT};
+ StartTLSRequired and not StateData#state.tls ->
+ ?DEBUG("restarted: ~p",
+ [{StateData#state.myname, StateData#state.server}]),
+ ejabberd_socket:close(StateData#state.socket),
+ {next_state, reopen_socket,
+ StateData#state{socket = undefined, use_v10 = false},
+ ?FSMTIMEOUT};
+ StateData#state.db_enabled ->
+ send_db_request(StateData);
+ true ->
+ ?DEBUG("restarted: ~p",
+ [{StateData#state.myname, StateData#state.server}]),
+ ejabberd_socket:close(StateData#state.socket),
+ {next_state, reopen_socket,
+ StateData#state{socket = undefined, use_v10 = false},
+ ?FSMTIMEOUT}
+ end;
+ _ ->
+ send_text(StateData,
+ <<(xml:element_to_binary(?SERR_BAD_FORMAT))/binary,
+ (?STREAM_TRAILER)/binary>>),
+ ?INFO_MSG("Closing s2s connection: ~s -> ~s (bad "
+ "format)",
+ [StateData#state.myname, StateData#state.server]),
+ {stop, normal, StateData}
end;
-
wait_for_features({xmlstreamend, _Name}, StateData) ->
?INFO_MSG("wait_for_features: xmlstreamend", []),
{stop, normal, StateData};
-
wait_for_features({xmlstreamerror, _}, StateData) ->
send_text(StateData,
- ?INVALID_XML_ERR ++ ?STREAM_TRAILER),
+ <<(?INVALID_XML_ERR)/binary,
+ (?STREAM_TRAILER)/binary>>),
?INFO_MSG("wait for features: xmlstreamerror", []),
{stop, normal, StateData};
-
wait_for_features(timeout, StateData) ->
?INFO_MSG("wait for features: timeout", []),
{stop, normal, StateData};
-
wait_for_features(closed, StateData) ->
?INFO_MSG("wait for features: closed", []),
{stop, normal, StateData}.
-
-wait_for_auth_result({xmlstreamelement, El}, StateData) ->
+wait_for_auth_result({xmlstreamelement, El},
+ StateData) ->
case El of
- {xmlelement, "success", Attrs, _Els} ->
- case xml:get_attr_s("xmlns", Attrs) of
- ?NS_SASL ->
- ?DEBUG("auth: ~p", [{StateData#state.myname,
- StateData#state.server}]),
- ejabberd_socket:reset_stream(StateData#state.socket),
- send_text(StateData,
- io_lib:format(?STREAM_HEADER,
- [StateData#state.myname, StateData#state.server,
- " version='1.0'"])),
- {next_state, wait_for_stream,
- StateData#state{streamid = new_id(),
- authenticated = true
- }, ?FSMTIMEOUT};
- _ ->
- send_text(StateData,
- xml:element_to_string(?SERR_BAD_FORMAT) ++
- ?STREAM_TRAILER),
- ?INFO_MSG("Closing s2s connection: ~s -> ~s (bad format)",
- [StateData#state.myname, StateData#state.server]),
- {stop, normal, StateData}
- end;
- {xmlelement, "failure", Attrs, _Els} ->
- case xml:get_attr_s("xmlns", Attrs) of
- ?NS_SASL ->
- ?DEBUG("restarted: ~p", [{StateData#state.myname,
- StateData#state.server}]),
- ejabberd_socket:close(StateData#state.socket),
- {next_state, reopen_socket,
- StateData#state{socket = undefined}, ?FSMTIMEOUT};
- _ ->
- send_text(StateData,
- xml:element_to_string(?SERR_BAD_FORMAT) ++
- ?STREAM_TRAILER),
- ?INFO_MSG("Closing s2s connection: ~s -> ~s (bad format)",
- [StateData#state.myname, StateData#state.server]),
- {stop, normal, StateData}
- end;
- _ ->
- send_text(StateData,
- xml:element_to_string(?SERR_BAD_FORMAT) ++
- ?STREAM_TRAILER),
- ?INFO_MSG("Closing s2s connection: ~s -> ~s (bad format)",
- [StateData#state.myname, StateData#state.server]),
- {stop, normal, StateData}
+ #xmlel{name = <<"success">>, attrs = Attrs} ->
+ case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ ?NS_SASL ->
+ ?DEBUG("auth: ~p",
+ [{StateData#state.myname, StateData#state.server}]),
+ ejabberd_socket:reset_stream(StateData#state.socket),
+ send_text(StateData,
+ io_lib:format(?STREAM_HEADER,
+ [StateData#state.myname,
+ StateData#state.server,
+ <<" version='1.0'">>])),
+ {next_state, wait_for_stream,
+ StateData#state{streamid = new_id(),
+ authenticated = true},
+ ?FSMTIMEOUT};
+ _ ->
+ send_text(StateData,
+ <<(xml:element_to_binary(?SERR_BAD_FORMAT))/binary,
+ (?STREAM_TRAILER)/binary>>),
+ ?INFO_MSG("Closing s2s connection: ~s -> ~s (bad "
+ "format)",
+ [StateData#state.myname, StateData#state.server]),
+ {stop, normal, StateData}
+ end;
+ #xmlel{name = <<"failure">>, attrs = Attrs} ->
+ case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ ?NS_SASL ->
+ ?DEBUG("restarted: ~p",
+ [{StateData#state.myname, StateData#state.server}]),
+ ejabberd_socket:close(StateData#state.socket),
+ {next_state, reopen_socket,
+ StateData#state{socket = undefined}, ?FSMTIMEOUT};
+ _ ->
+ send_text(StateData,
+ <<(xml:element_to_binary(?SERR_BAD_FORMAT))/binary,
+ (?STREAM_TRAILER)/binary>>),
+ ?INFO_MSG("Closing s2s connection: ~s -> ~s (bad "
+ "format)",
+ [StateData#state.myname, StateData#state.server]),
+ {stop, normal, StateData}
+ end;
+ _ ->
+ send_text(StateData,
+ <<(xml:element_to_binary(?SERR_BAD_FORMAT))/binary,
+ (?STREAM_TRAILER)/binary>>),
+ ?INFO_MSG("Closing s2s connection: ~s -> ~s (bad "
+ "format)",
+ [StateData#state.myname, StateData#state.server]),
+ {stop, normal, StateData}
end;
-
-wait_for_auth_result({xmlstreamend, _Name}, StateData) ->
+wait_for_auth_result({xmlstreamend, _Name},
+ StateData) ->
?INFO_MSG("wait for auth result: xmlstreamend", []),
{stop, normal, StateData};
-
wait_for_auth_result({xmlstreamerror, _}, StateData) ->
send_text(StateData,
- ?INVALID_XML_ERR ++ ?STREAM_TRAILER),
+ <<(?INVALID_XML_ERR)/binary,
+ (?STREAM_TRAILER)/binary>>),
?INFO_MSG("wait for auth result: xmlstreamerror", []),
{stop, normal, StateData};
-
wait_for_auth_result(timeout, StateData) ->
?INFO_MSG("wait for auth result: timeout", []),
{stop, normal, StateData};
-
wait_for_auth_result(closed, StateData) ->
?INFO_MSG("wait for auth result: closed", []),
{stop, normal, StateData}.
-
-wait_for_starttls_proceed({xmlstreamelement, El}, StateData) ->
+wait_for_starttls_proceed({xmlstreamelement, El},
+ StateData) ->
case El of
- {xmlelement, "proceed", Attrs, _Els} ->
- case xml:get_attr_s("xmlns", Attrs) of
- ?NS_TLS ->
- ?DEBUG("starttls: ~p", [{StateData#state.myname,
- StateData#state.server}]),
- Socket = StateData#state.socket,
- TLSOpts = case ejabberd_config:get_local_option(
- {domain_certfile,
- StateData#state.myname}) of
- undefined ->
- StateData#state.tls_options;
- CertFile ->
- [{certfile, CertFile} |
- lists:keydelete(
- certfile, 1,
- StateData#state.tls_options)]
- end,
- TLSSocket = ejabberd_socket:starttls(Socket, TLSOpts),
- NewStateData = StateData#state{socket = TLSSocket,
- streamid = new_id(),
- tls_enabled = true,
- tls_options = TLSOpts
- },
- send_text(NewStateData,
- io_lib:format(?STREAM_HEADER,
- [StateData#state.myname, StateData#state.server,
- " version='1.0'"])),
- {next_state, wait_for_stream, NewStateData, ?FSMTIMEOUT};
- _ ->
- send_text(StateData,
- xml:element_to_string(?SERR_BAD_FORMAT) ++
- ?STREAM_TRAILER),
- ?INFO_MSG("Closing s2s connection: ~s -> ~s (bad format)",
- [StateData#state.myname, StateData#state.server]),
- {stop, normal, StateData}
- end;
- _ ->
- ?INFO_MSG("Closing s2s connection: ~s -> ~s (bad format)",
- [StateData#state.myname, StateData#state.server]),
- {stop, normal, StateData}
+ #xmlel{name = <<"proceed">>, attrs = Attrs} ->
+ case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ ?NS_TLS ->
+ ?DEBUG("starttls: ~p",
+ [{StateData#state.myname, StateData#state.server}]),
+ Socket = StateData#state.socket,
+ TLSOpts = case
+ ejabberd_config:get_local_option(
+ {domain_certfile, StateData#state.myname},
+ fun iolist_to_binary/1)
+ of
+ undefined -> StateData#state.tls_options;
+ CertFile ->
+ [{certfile, CertFile}
+ | lists:keydelete(certfile, 1,
+ StateData#state.tls_options)]
+ end,
+ TLSSocket = ejabberd_socket:starttls(Socket, TLSOpts),
+ NewStateData = StateData#state{socket = TLSSocket,
+ streamid = new_id(),
+ tls_enabled = true,
+ tls_options = TLSOpts},
+ send_text(NewStateData,
+ io_lib:format(?STREAM_HEADER,
+ [StateData#state.myname,
+ StateData#state.server,
+ <<" version='1.0'">>])),
+ {next_state, wait_for_stream, NewStateData,
+ ?FSMTIMEOUT};
+ _ ->
+ send_text(StateData,
+ <<(xml:element_to_binary(?SERR_BAD_FORMAT))/binary,
+ (?STREAM_TRAILER)/binary>>),
+ ?INFO_MSG("Closing s2s connection: ~s -> ~s (bad "
+ "format)",
+ [StateData#state.myname, StateData#state.server]),
+ {stop, normal, StateData}
+ end;
+ _ ->
+ ?INFO_MSG("Closing s2s connection: ~s -> ~s (bad "
+ "format)",
+ [StateData#state.myname, StateData#state.server]),
+ {stop, normal, StateData}
end;
-
-wait_for_starttls_proceed({xmlstreamend, _Name}, StateData) ->
- ?INFO_MSG("wait for starttls proceed: xmlstreamend", []),
+wait_for_starttls_proceed({xmlstreamend, _Name},
+ StateData) ->
+ ?INFO_MSG("wait for starttls proceed: xmlstreamend",
+ []),
{stop, normal, StateData};
-
-wait_for_starttls_proceed({xmlstreamerror, _}, StateData) ->
+wait_for_starttls_proceed({xmlstreamerror, _},
+ StateData) ->
send_text(StateData,
- ?INVALID_XML_ERR ++ ?STREAM_TRAILER),
- ?INFO_MSG("wait for starttls proceed: xmlstreamerror", []),
+ <<(?INVALID_XML_ERR)/binary,
+ (?STREAM_TRAILER)/binary>>),
+ ?INFO_MSG("wait for starttls proceed: xmlstreamerror",
+ []),
{stop, normal, StateData};
-
wait_for_starttls_proceed(timeout, StateData) ->
?INFO_MSG("wait for starttls proceed: timeout", []),
{stop, normal, StateData};
-
wait_for_starttls_proceed(closed, StateData) ->
?INFO_MSG("wait for starttls proceed: closed", []),
{stop, normal, StateData}.
-
reopen_socket({xmlstreamelement, _El}, StateData) ->
{next_state, reopen_socket, StateData, ?FSMTIMEOUT};
reopen_socket({xmlstreamend, _Name}, StateData) ->
@@ -716,54 +779,46 @@ relay_to_bridge(_Event, StateData) ->
stream_established({xmlstreamelement, El}, StateData) ->
?DEBUG("s2S stream established", []),
case is_verify_res(El) of
- {verify, VTo, VFrom, VId, VType} ->
- ?DEBUG("recv verify: ~p", [{VFrom, VTo, VId, VType}]),
- case StateData#state.verify of
- {VPid, _VKey, _SID} ->
- case VType of
- "valid" ->
- p1_fsm:send_event(
- VPid, {valid,
- StateData#state.server,
- StateData#state.myname});
- _ ->
- p1_fsm:send_event(
- VPid, {invalid,
- StateData#state.server,
- StateData#state.myname})
- end;
- _ ->
- ok
- end;
- _ ->
- ok
+ {verify, VTo, VFrom, VId, VType} ->
+ ?DEBUG("recv verify: ~p", [{VFrom, VTo, VId, VType}]),
+ case StateData#state.verify of
+ {VPid, _VKey, _SID} ->
+ case VType of
+ <<"valid">> ->
+ p1_fsm:send_event(VPid,
+ {valid, StateData#state.server,
+ StateData#state.myname});
+ _ ->
+ p1_fsm:send_event(VPid,
+ {invalid, StateData#state.server,
+ StateData#state.myname})
+ end;
+ _ -> ok
+ end;
+ _ -> ok
end,
{next_state, stream_established, StateData};
-
stream_established({xmlstreamend, _Name}, StateData) ->
- ?INFO_MSG("Connection closed in stream established: ~s -> ~s (xmlstreamend)",
+ ?INFO_MSG("Connection closed in stream established: "
+ "~s -> ~s (xmlstreamend)",
[StateData#state.myname, StateData#state.server]),
{stop, normal, StateData};
-
stream_established({xmlstreamerror, _}, StateData) ->
send_text(StateData,
- ?INVALID_XML_ERR ++ ?STREAM_TRAILER),
+ <<(?INVALID_XML_ERR)/binary,
+ (?STREAM_TRAILER)/binary>>),
?INFO_MSG("stream established: ~s -> ~s (xmlstreamerror)",
[StateData#state.myname, StateData#state.server]),
{stop, normal, StateData};
-
stream_established(timeout, StateData) ->
?INFO_MSG("stream established: ~s -> ~s (timeout)",
[StateData#state.myname, StateData#state.server]),
{stop, normal, StateData};
-
stream_established(closed, StateData) ->
?INFO_MSG("stream established: ~s -> ~s (closed)",
[StateData#state.myname, StateData#state.server]),
{stop, normal, StateData}.
-
-
%%----------------------------------------------------------------------
%% Func: StateName/3
%% Returns: {next_state, NextStateName, NextStateData} |
@@ -784,27 +839,27 @@ stream_established(closed, StateData) ->
%% {stop, Reason, NewStateData}
%%----------------------------------------------------------------------
handle_event(_Event, StateName, StateData) ->
- {next_state, StateName, StateData, get_timeout_interval(StateName)}.
-
%%----------------------------------------------------------------------
%% Func: handle_sync_event/4
%% Returns: The associated StateData for this connection
%% {reply, Reply, NextStateName, NextStateData}
%% Reply = {state_infos, [{InfoName::atom(), InfoValue::any()]
%%----------------------------------------------------------------------
-handle_sync_event(get_state_infos, _From, StateName, StateData) ->
- {Addr,Port} = try ejabberd_socket:peername(StateData#state.socket) of
- {ok, {A,P}} -> {A,P};
- {error, _} -> {unknown,unknown}
- catch
- _:_ ->
- {unknown,unknown}
- end,
- Infos = [
- {direction, out},
- {statename, StateName},
- {addr, Addr},
- {port, Port},
+ {next_state, StateName, StateData,
+ get_timeout_interval(StateName)}.
+
+handle_sync_event(get_state_infos, _From, StateName,
+ StateData) ->
+ {Addr, Port} = try
+ ejabberd_socket:peername(StateData#state.socket)
+ of
+ {ok, {A, P}} -> {A, P};
+ {error, _} -> {unknown, unknown}
+ catch
+ _:_ -> {unknown, unknown}
+ end,
+ Infos = [{direction, out}, {statename, StateName},
+ {addr, Addr}, {port, Port},
{streamid, StateData#state.streamid},
{use_v10, StateData#state.use_v10},
{tls, StateData#state.tls},
@@ -817,11 +872,9 @@ handle_sync_event(get_state_infos, _From, StateName, StateData) ->
{myname, StateData#state.myname},
{server, StateData#state.server},
{delay_to_retry, StateData#state.delay_to_retry},
- {verify, StateData#state.verify}
- ],
+ {verify, StateData#state.verify}],
Reply = {state_infos, Infos},
- {reply,Reply,StateName,StateData};
-
+ {reply, Reply, StateName, StateData};
%%----------------------------------------------------------------------
%% Func: handle_sync_event/4
%% Returns: {next_state, NextStateName, NextStateData} |
@@ -831,9 +884,11 @@ handle_sync_event(get_state_infos, _From, StateName, StateData) ->
%% {stop, Reason, NewStateData} |
%% {stop, Reason, Reply, NewStateData}
%%----------------------------------------------------------------------
-handle_sync_event(_Event, _From, StateName, StateData) ->
+handle_sync_event(_Event, _From, StateName,
+ StateData) ->
Reply = ok,
- {reply, Reply, StateName, StateData, get_timeout_interval(StateName)}.
+ {reply, Reply, StateName, StateData,
+ get_timeout_interval(StateName)}.
code_change(_OldVsn, StateName, StateData, _Extra) ->
{ok, StateName, StateData}.
@@ -850,56 +905,55 @@ handle_info({send_text, Text}, StateName, StateData) ->
Timer = erlang:start_timer(?S2STIMEOUT, self(), []),
{next_state, StateName, StateData#state{timer = Timer},
get_timeout_interval(StateName)};
-
handle_info({send_element, El}, StateName, StateData) ->
case StateName of
- stream_established ->
- cancel_timer(StateData#state.timer),
- Timer = erlang:start_timer(?S2STIMEOUT, self(), []),
- send_element(StateData, El),
- {next_state, StateName, StateData#state{timer = Timer}};
- %% In this state we bounce all message: We are waiting before
- %% trying to reconnect
- wait_before_retry ->
- bounce_element(El, ?ERR_REMOTE_SERVER_NOT_FOUND),
- {next_state, StateName, StateData};
- relay_to_bridge ->
- %% In this state we relay all outbound messages
- %% to a foreign protocol bridge such as SMTP, SIP, etc.
- {Mod, Fun} = StateData#state.bridge,
- ?DEBUG("relaying stanza via ~p:~p/1", [Mod, Fun]),
- case catch Mod:Fun(El) of
- {'EXIT', Reason} ->
- ?ERROR_MSG("Error while relaying to bridge: ~p", [Reason]),
- bounce_element(El, ?ERR_INTERNAL_SERVER_ERROR),
- wait_before_reconnect(StateData);
- _ ->
- {next_state, StateName, StateData}
- end;
- _ ->
- Q = queue:in(El, StateData#state.queue),
- {next_state, StateName, StateData#state{queue = Q},
- get_timeout_interval(StateName)}
+ stream_established ->
+ cancel_timer(StateData#state.timer),
+ Timer = erlang:start_timer(?S2STIMEOUT, self(), []),
+ send_element(StateData, El),
+ {next_state, StateName, StateData#state{timer = Timer}};
+ %% In this state we bounce all message: We are waiting before
+ %% trying to reconnect
+ wait_before_retry ->
+ bounce_element(El, ?ERR_REMOTE_SERVER_NOT_FOUND),
+ {next_state, StateName, StateData};
+ relay_to_bridge ->
+ {Mod, Fun} = StateData#state.bridge,
+ ?DEBUG("relaying stanza via ~p:~p/1", [Mod, Fun]),
+ case catch Mod:Fun(El) of
+ {'EXIT', Reason} ->
+ ?ERROR_MSG("Error while relaying to bridge: ~p",
+ [Reason]),
+ bounce_element(El, ?ERR_INTERNAL_SERVER_ERROR),
+ wait_before_reconnect(StateData);
+ _ -> {next_state, StateName, StateData}
+ end;
+ _ ->
+ Q = queue:in(El, StateData#state.queue),
+ {next_state, StateName, StateData#state{queue = Q},
+ get_timeout_interval(StateName)}
end;
-
handle_info({timeout, Timer, _}, wait_before_retry,
#state{timer = Timer} = StateData) ->
- ?INFO_MSG("Reconnect delay expired: Will now retry to connect to ~s when needed.", [StateData#state.server]),
+ ?INFO_MSG("Reconnect delay expired: Will now retry "
+ "to connect to ~s when needed.",
+ [StateData#state.server]),
{stop, normal, StateData};
-
handle_info({timeout, Timer, _}, _StateName,
#state{timer = Timer} = StateData) ->
- ?INFO_MSG("Closing connection with ~s: timeout", [StateData#state.server]),
+ ?INFO_MSG("Closing connection with ~s: timeout",
+ [StateData#state.server]),
{stop, normal, StateData};
-
-handle_info(terminate_if_waiting_before_retry, wait_before_retry, StateData) ->
+handle_info(terminate_if_waiting_before_retry,
+ wait_before_retry, StateData) ->
{stop, normal, StateData};
-
-handle_info(terminate_if_waiting_before_retry, StateName, StateData) ->
- {next_state, StateName, StateData, get_timeout_interval(StateName)};
-
+handle_info(terminate_if_waiting_before_retry,
+ StateName, StateData) ->
+ {next_state, StateName, StateData,
+ get_timeout_interval(StateName)};
handle_info(_, StateName, StateData) ->
- {next_state, StateName, StateData, get_timeout_interval(StateName)}.
+ {next_state, StateName, StateData,
+ get_timeout_interval(StateName)}.
%%----------------------------------------------------------------------
%% Func: terminate/3
@@ -909,20 +963,18 @@ handle_info(_, StateName, StateData) ->
terminate(Reason, StateName, StateData) ->
?DEBUG("terminated: ~p", [{Reason, StateName}]),
case StateData#state.new of
- false ->
- ok;
- Key ->
- ejabberd_s2s:remove_connection(
- {StateData#state.myname, StateData#state.server}, self(), Key)
+ false -> ok;
+ Key ->
+ ejabberd_s2s:remove_connection({StateData#state.myname,
+ StateData#state.server},
+ self(), Key)
end,
- %% bounce queue manage by process and Erlang message queue
- bounce_queue(StateData#state.queue, ?ERR_REMOTE_SERVER_NOT_FOUND),
+ bounce_queue(StateData#state.queue,
+ ?ERR_REMOTE_SERVER_NOT_FOUND),
bounce_messages(?ERR_REMOTE_SERVER_NOT_FOUND),
case StateData#state.socket of
- undefined ->
- ok;
- _Socket ->
- ejabberd_socket:close(StateData#state.socket)
+ undefined -> ok;
+ _Socket -> ejabberd_socket:close(StateData#state.socket)
end,
ok.
@@ -931,8 +983,7 @@ terminate(Reason, StateName, StateData) ->
%% Purpose: Prepare the state to be printed on error log
%% Returns: State to print
%%----------------------------------------------------------------------
-print_state(State) ->
- State.
+print_state(State) -> State.
%%%----------------------------------------------------------------------
%%% Internal functions
@@ -946,325 +997,298 @@ send_element(StateData, El) ->
send_queue(StateData, Q) ->
case queue:out(Q) of
- {{value, El}, Q1} ->
- send_element(StateData, El),
- send_queue(StateData, Q1);
- {empty, _Q1} ->
- ok
+ {{value, El}, Q1} ->
+ send_element(StateData, El), send_queue(StateData, Q1);
+ {empty, _Q1} -> ok
end.
%% Bounce a single message (xmlelement)
bounce_element(El, Error) ->
- {xmlelement, _Name, Attrs, _SubTags} = El,
- case xml:get_attr_s("type", Attrs) of
- "error" -> ok;
- "result" -> ok;
- _ ->
- Err = jlib:make_error_reply(El, Error),
- From = jlib:string_to_jid(xml:get_tag_attr_s("from", El)),
- To = jlib:string_to_jid(xml:get_tag_attr_s("to", El)),
- ejabberd_router:route(To, From, Err)
+ #xmlel{attrs = Attrs} = El,
+ case xml:get_attr_s(<<"type">>, Attrs) of
+ <<"error">> -> ok;
+ <<"result">> -> ok;
+ _ ->
+ Err = jlib:make_error_reply(El, Error),
+ From = jlib:string_to_jid(xml:get_tag_attr_s(<<"from">>,
+ El)),
+ To = jlib:string_to_jid(xml:get_tag_attr_s(<<"to">>,
+ El)),
+ ejabberd_router:route(To, From, Err)
end.
bounce_queue(Q, Error) ->
case queue:out(Q) of
- {{value, El}, Q1} ->
- bounce_element(El, Error),
- bounce_queue(Q1, Error);
- {empty, _} ->
- ok
+ {{value, El}, Q1} ->
+ bounce_element(El, Error), bounce_queue(Q1, Error);
+ {empty, _} -> ok
end.
-new_id() ->
- randoms:get_string().
+new_id() -> randoms:get_string().
cancel_timer(Timer) ->
erlang:cancel_timer(Timer),
- receive
- {timeout, Timer, _} ->
- ok
- after 0 ->
- ok
- end.
+ receive {timeout, Timer, _} -> ok after 0 -> ok end.
bounce_messages(Error) ->
receive
- {send_element, El} ->
- bounce_element(El, Error),
- bounce_messages(Error)
- after 0 ->
- ok
+ {send_element, El} ->
+ bounce_element(El, Error), bounce_messages(Error)
+ after 0 -> ok
end.
-
send_db_request(StateData) ->
Server = StateData#state.server,
New = case StateData#state.new of
- false ->
- case ejabberd_s2s:try_register(
- {StateData#state.myname, Server}) of
- {key, Key} ->
- Key;
- false ->
- false
- end;
- Key ->
- Key
+ false ->
+ case ejabberd_s2s:try_register({StateData#state.myname,
+ Server})
+ of
+ {key, Key} -> Key;
+ false -> false
+ end;
+ Key -> Key
end,
NewStateData = StateData#state{new = New},
- try
- case New of
- false ->
- ok;
- Key1 ->
- send_element(StateData,
- {xmlelement,
- "db:result",
- [{"from", StateData#state.myname},
- {"to", Server}],
- [{xmlcdata, Key1}]})
+ try case New of
+ false -> ok;
+ Key1 ->
+ send_element(StateData,
+ #xmlel{name = <<"db:result">>,
+ attrs =
+ [{<<"from">>, StateData#state.myname},
+ {<<"to">>, Server}],
+ children = [{xmlcdata, Key1}]})
end,
case StateData#state.verify of
- false ->
- ok;
- {_Pid, Key2, SID} ->
- send_element(StateData,
- {xmlelement,
- "db:verify",
- [{"from", StateData#state.myname},
- {"to", StateData#state.server},
- {"id", SID}],
- [{xmlcdata, Key2}]})
+ false -> ok;
+ {_Pid, Key2, SID} ->
+ send_element(StateData,
+ #xmlel{name = <<"db:verify">>,
+ attrs =
+ [{<<"from">>, StateData#state.myname},
+ {<<"to">>, StateData#state.server},
+ {<<"id">>, SID}],
+ children = [{xmlcdata, Key2}]})
end,
- {next_state, wait_for_validation, NewStateData, ?FSMTIMEOUT*6}
+ {next_state, wait_for_validation, NewStateData,
+ (?FSMTIMEOUT) * 6}
catch
- _:_ ->
- {stop, normal, NewStateData}
+ _:_ -> {stop, normal, NewStateData}
end.
-
-is_verify_res({xmlelement, Name, Attrs, _Els}) when Name == "db:result" ->
- {result,
- xml:get_attr_s("to", Attrs),
- xml:get_attr_s("from", Attrs),
- xml:get_attr_s("id", Attrs),
- xml:get_attr_s("type", Attrs)};
-is_verify_res({xmlelement, Name, Attrs, _Els}) when Name == "db:verify" ->
- {verify,
- xml:get_attr_s("to", Attrs),
- xml:get_attr_s("from", Attrs),
- xml:get_attr_s("id", Attrs),
- xml:get_attr_s("type", Attrs)};
-is_verify_res(_) ->
- false.
-
+is_verify_res(#xmlel{name = Name, attrs = Attrs})
+ when Name == <<"db:result">> ->
+ {result, xml:get_attr_s(<<"to">>, Attrs),
+ xml:get_attr_s(<<"from">>, Attrs),
+ xml:get_attr_s(<<"id">>, Attrs),
+ xml:get_attr_s(<<"type">>, Attrs)};
+is_verify_res(#xmlel{name = Name, attrs = Attrs})
+ when Name == <<"db:verify">> ->
+ {verify, xml:get_attr_s(<<"to">>, Attrs),
+ xml:get_attr_s(<<"from">>, Attrs),
+ xml:get_attr_s(<<"id">>, Attrs),
+ xml:get_attr_s(<<"type">>, Attrs)};
+is_verify_res(_) -> false.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% SRV support
-include_lib("kernel/include/inet.hrl").
+-spec get_addr_port(binary()) -> [{binary(), inet:port_number()}].
get_addr_port(Server) ->
Res = srv_lookup(Server),
case Res of
- {error, Reason} ->
- ?DEBUG("srv lookup of '~s' failed: ~p~n", [Server, Reason]),
- [{Server, outgoing_s2s_port()}];
- {ok, HEnt} ->
- ?DEBUG("srv lookup of '~s': ~p~n",
- [Server, HEnt#hostent.h_addr_list]),
- AddrList = HEnt#hostent.h_addr_list,
- %% Probabilities are not exactly proportional to weights
- %% for simplicity (higher weigths are overvalued)
- {A1, A2, A3} = now(),
- random:seed(A1, A2, A3),
- case (catch lists:map(
- fun({Priority, Weight, Port, Host}) ->
- N = case Weight of
- 0 -> 0;
- _ -> (Weight + 1) * random:uniform()
- end,
- {Priority * 65536 - N, Host, Port}
- end, AddrList)) of
- SortedList = [_|_] ->
- List = lists:map(
- fun({_, Host, Port}) ->
- {Host, Port}
- end, lists:keysort(1, SortedList)),
- ?DEBUG("srv lookup of '~s': ~p~n", [Server, List]),
- List;
- _ ->
- [{Server, outgoing_s2s_port()}]
- end
+ {error, Reason} ->
+ ?DEBUG("srv lookup of '~s' failed: ~p~n",
+ [Server, Reason]),
+ [{Server, outgoing_s2s_port()}];
+ {ok, HEnt} ->
+ ?DEBUG("srv lookup of '~s': ~p~n",
+ [Server, HEnt#hostent.h_addr_list]),
+ AddrList = HEnt#hostent.h_addr_list,
+ {A1, A2, A3} = now(),
+ random:seed(A1, A2, A3),
+ case catch lists:map(fun ({Priority, Weight, Port,
+ Host}) ->
+ N = case Weight of
+ 0 -> 0;
+ _ ->
+ (Weight + 1) * random:uniform()
+ end,
+ {Priority * 65536 - N, Host, Port}
+ end,
+ AddrList)
+ of
+ SortedList = [_ | _] ->
+ List = lists:map(fun ({_, Host, Port}) ->
+ {list_to_binary(Host), Port}
+ end,
+ lists:keysort(1, SortedList)),
+ ?DEBUG("srv lookup of '~s': ~p~n", [Server, List]),
+ List;
+ _ -> [{Server, outgoing_s2s_port()}]
+ end
end.
srv_lookup(Server) ->
- Options = case ejabberd_config:get_local_option(s2s_dns_options) of
- L when is_list(L) -> L;
- _ -> []
- end,
- TimeoutMs = timer:seconds(proplists:get_value(timeout, Options, 10)),
+ Options = case
+ ejabberd_config:get_local_option(
+ s2s_dns_options, fun(L) when is_list(L) -> L end)
+ of
+ undefined -> [];
+ L -> L
+ end,
+ TimeoutMs = timer:seconds(proplists:get_value(timeout,
+ Options, 10)),
Retries = proplists:get_value(retries, Options, 2),
- srv_lookup(Server, TimeoutMs, Retries).
+ srv_lookup(binary_to_list(Server), TimeoutMs, Retries).
%% XXX - this behaviour is suboptimal in the case that the domain
%% has a "_xmpp-server._tcp." but not a "_jabber._tcp." record and
%% we don't get a DNS reply for the "_xmpp-server._tcp." lookup. In this
%% case we'll give up when we get the "_jabber._tcp." nxdomain reply.
-srv_lookup(_Server, _Timeout, Retries) when Retries < 1 ->
+srv_lookup(_Server, _Timeout, Retries)
+ when Retries < 1 ->
{error, timeout};
srv_lookup(Server, Timeout, Retries) ->
- case inet_res:getbyname("_xmpp-server._tcp." ++ Server, srv, Timeout) of
- {error, _Reason} ->
- case inet_res:getbyname("_jabber._tcp." ++ Server, srv, Timeout) of
- {error, timeout} ->
- ?ERROR_MSG("The DNS servers~n ~p~ntimed out on request"
- " for ~p IN SRV."
- " You should check your DNS configuration.",
- [inet_db:res_option(nameserver), Server]),
- srv_lookup(Server, Timeout, Retries - 1);
- R -> R
- end;
- {ok, _HEnt} = R -> R
+ case inet_res:getbyname("_xmpp-server._tcp." ++ Server,
+ srv, Timeout)
+ of
+ {error, _Reason} ->
+ case inet_res:getbyname("_jabber._tcp." ++ Server, srv,
+ Timeout)
+ of
+ {error, timeout} ->
+ ?ERROR_MSG("The DNS servers~n ~p~ntimed out on "
+ "request for ~p IN SRV. You should check "
+ "your DNS configuration.",
+ [inet_db:res_option(nameserver), Server]),
+ srv_lookup(Server, Timeout, Retries - 1);
+ R -> R
+ end;
+ {ok, _HEnt} = R -> R
end.
test_get_addr_port(Server) ->
- lists:foldl(
- fun(_, Acc) ->
- [HostPort | _] = get_addr_port(Server),
- case lists:keysearch(HostPort, 1, Acc) of
- false ->
- [{HostPort, 1} | Acc];
- {value, {_, Num}} ->
- lists:keyreplace(HostPort, 1, Acc, {HostPort, Num + 1})
- end
- end, [], lists:seq(1, 100000)).
+ lists:foldl(fun (_, Acc) ->
+ [HostPort | _] = get_addr_port(Server),
+ case lists:keysearch(HostPort, 1, Acc) of
+ false -> [{HostPort, 1} | Acc];
+ {value, {_, Num}} ->
+ lists:keyreplace(HostPort, 1, Acc,
+ {HostPort, Num + 1})
+ end
+ end,
+ [], lists:seq(1, 100000)).
get_addrs(Host, Family) ->
Type = case Family of
- inet4 -> inet;
- ipv4 -> inet;
- inet6 -> inet6;
- ipv6 -> inet6
+ inet4 -> inet;
+ ipv4 -> inet;
+ inet6 -> inet6;
+ ipv6 -> inet6
end,
- case inet:gethostbyname(Host, Type) of
- {ok, #hostent{h_addr_list = Addrs}} ->
- ?DEBUG("~s of ~s resolved to: ~p~n", [Type, Host, Addrs]),
- Addrs;
- {error, Reason} ->
- ?DEBUG("~s lookup of '~s' failed: ~p~n", [Type, Host, Reason]),
- []
+ case inet:gethostbyname(binary_to_list(Host), Type) of
+ {ok, #hostent{h_addr_list = Addrs}} ->
+ ?DEBUG("~s of ~s resolved to: ~p~n",
+ [Type, Host, Addrs]),
+ Addrs;
+ {error, Reason} ->
+ ?DEBUG("~s lookup of '~s' failed: ~p~n",
+ [Type, Host, Reason]),
+ []
end.
-
outgoing_s2s_port() ->
- case ejabberd_config:get_local_option(outgoing_s2s_port) of
- Port when is_integer(Port) ->
- Port;
- undefined ->
- 5269
- end.
+ ejabberd_config:get_local_option(
+ outgoing_s2s_port,
+ fun(I) when is_integer(I), I > 0, I =< 65536 -> I end,
+ 5269).
outgoing_s2s_families() ->
- case ejabberd_config:get_local_option(outgoing_s2s_options) of
- {Families, _} when is_list(Families) ->
- Families;
- undefined ->
- %% DISCUSSION: Why prefer IPv4 first?
- %%
- %% IPv4 connectivity will be available for everyone for
- %% many years to come. So, there's absolutely no benefit
- %% in preferring IPv6 connections which are flaky at best
- %% nowadays.
- %%
- %% On the other hand content providers hesitate putting up
- %% AAAA records for their sites due to the mentioned
- %% quality of current IPv6 connectivity. Making IPv6 the a
- %% `fallback' may avoid these problems elegantly.
- [ipv4, ipv6]
- end.
+ ejabberd_config:get_local_option(
+ outgoing_s2s_options,
+ fun({Families, _}) ->
+ true = lists:all(
+ fun(ipv4) -> true;
+ (ipv6) -> true
+ end, Families),
+ Families
+ end, [ipv4, ipv6]).
outgoing_s2s_timeout() ->
- case ejabberd_config:get_local_option(outgoing_s2s_options) of
- {_, Timeout} when is_integer(Timeout) ->
- Timeout;
- {_, infinity} ->
- infinity;
- undefined ->
- %% 10 seconds
- 10000
- end.
+ ejabberd_config:get_local_option(
+ outgoing_s2s_options,
+ fun({_, TimeOut}) when is_integer(TimeOut), TimeOut > 0 ->
+ TimeOut;
+ ({_, infinity}) ->
+ infinity
+ end, 10000).
%% Human readable S2S logging: Log only new outgoing connections as INFO
%% Do not log dialback
log_s2s_out(false, _, _, _) -> ok;
%% Log new outgoing connections:
log_s2s_out(_, Myname, Server, Tls) ->
- ?INFO_MSG("Trying to open s2s connection: ~s -> ~s with TLS=~p", [Myname, Server, Tls]).
+ ?INFO_MSG("Trying to open s2s connection: ~s -> "
+ "~s with TLS=~p",
+ [Myname, Server, Tls]).
%% Calculate timeout depending on which state we are in:
%% Can return integer > 0 | infinity
get_timeout_interval(StateName) ->
case StateName of
- %% Validation implies dialback: Networking can take longer:
- wait_for_validation ->
- ?FSMTIMEOUT*6;
- %% When stream is established, we only rely on S2S Timeout timer:
- stream_established ->
- infinity;
- _ ->
- ?FSMTIMEOUT
+ %% Validation implies dialback: Networking can take longer:
+ wait_for_validation -> (?FSMTIMEOUT) * 6;
+ %% When stream is established, we only rely on S2S Timeout timer:
+ stream_established -> infinity;
+ _ -> ?FSMTIMEOUT
end.
%% This function is intended to be called at the end of a state
%% function that want to wait for a reconnect delay before stopping.
wait_before_reconnect(StateData) ->
- %% bounce queue manage by process and Erlang message queue
- bounce_queue(StateData#state.queue, ?ERR_REMOTE_SERVER_NOT_FOUND),
+ bounce_queue(StateData#state.queue,
+ ?ERR_REMOTE_SERVER_NOT_FOUND),
bounce_messages(?ERR_REMOTE_SERVER_NOT_FOUND),
cancel_timer(StateData#state.timer),
Delay = case StateData#state.delay_to_retry of
- undefined_delay ->
- %% The initial delay is random between 1 and 15 seconds
- %% Return a random integer between 1000 and 15000
- {_, _, MicroSecs} = now(),
- (MicroSecs rem 14000) + 1000;
- D1 ->
- %% Duplicate the delay with each successive failed
- %% reconnection attempt, but don't exceed the max
- lists:min([D1 * 2, get_max_retry_delay()])
+ undefined_delay ->
+ {_, _, MicroSecs} = now(), MicroSecs rem 14000 + 1000;
+ D1 -> lists:min([D1 * 2, get_max_retry_delay()])
end,
Timer = erlang:start_timer(Delay, self(), []),
- {next_state, wait_before_retry, StateData#state{timer=Timer,
- delay_to_retry = Delay,
- queue = queue:new()}}.
-
%% @doc Get the maximum allowed delay for retry to reconnect (in miliseconds).
%% The default value is 5 minutes.
%% The option {s2s_max_retry_delay, Seconds} can be used (in seconds).
%% @spec () -> integer()
+ {next_state, wait_before_retry,
+ StateData#state{timer = Timer, delay_to_retry = Delay,
+ queue = queue:new()}}.
+
get_max_retry_delay() ->
- case ejabberd_config:get_local_option(s2s_max_retry_delay) of
- Seconds when is_integer(Seconds) ->
- Seconds*1000;
- _ ->
- ?MAX_RETRY_DELAY
+ case ejabberd_config:get_local_option(
+ s2s_max_retry_delay,
+ fun(I) when is_integer(I), I > 0 -> I end) of
+ undefined -> ?MAX_RETRY_DELAY;
+ Seconds -> Seconds * 1000
end.
%% Terminate s2s_out connections that are in state wait_before_retry
terminate_if_waiting_delay(From, To) ->
FromTo = {From, To},
Pids = ejabberd_s2s:get_connections_pids(FromTo),
- lists:foreach(
- fun(Pid) ->
- Pid ! terminate_if_waiting_before_retry
- end,
- Pids).
+ lists:foreach(fun (Pid) ->
+ Pid ! terminate_if_waiting_before_retry
+ end,
+ Pids).
fsm_limit_opts() ->
- case ejabberd_config:get_local_option(max_fsm_queue) of
- N when is_integer(N) ->
- [{max_queue, N}];
- _ ->
- []
+ case ejabberd_config:get_local_option(
+ max_fsm_queue,
+ fun(I) when is_integer(I), I > 0 -> I end) of
+ undefined -> [];
+ N -> [{max_queue, N}]
end.
diff --git a/src/ejabberd_service.erl b/src/ejabberd_service.erl
index 449cba68c..5a80fcd90 100644
--- a/src/ejabberd_service.erl
+++ b/src/ejabberd_service.erl
@@ -25,6 +25,7 @@
%%%----------------------------------------------------------------------
-module(ejabberd_service).
+
-author('alexey@process-one.net').
-define(GEN_FSM, p1_fsm).
@@ -32,82 +33,78 @@
-behaviour(?GEN_FSM).
%% External exports
--export([start/2,
- start_link/2,
- send_text/2,
- send_element/2,
- socket_type/0]).
+-export([start/2, start_link/2, send_text/2,
+ send_element/2, socket_type/0]).
%% gen_fsm callbacks
--export([init/1,
- wait_for_stream/2,
- wait_for_handshake/2,
- stream_established/2,
- handle_event/3,
- handle_sync_event/4,
- code_change/4,
- handle_info/3,
- terminate/3,
- print_state/1]).
+-export([init/1, wait_for_stream/2,
+ wait_for_handshake/2, stream_established/2,
+ handle_event/3, handle_sync_event/4, code_change/4,
+ handle_info/3, terminate/3, print_state/1]).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
--record(state, {socket, sockmod, streamid,
- hosts, password, access,
- check_from}).
+-record(state,
+ {socket :: ejabberd_socket:socket_state(),
+ sockmod = ejabberd_socket :: ejabberd_socket | ejabberd_frontend_socket,
+ streamid = <<"">> :: binary(),
+ hosts = [] :: [binary()],
+ password = <<"">> :: binary(),
+ access :: atom(),
+ check_from = true :: boolean()}).
%-define(DBGFSM, true).
-ifdef(DBGFSM).
+
-define(FSMOPTS, [{debug, [trace]}]).
+
-else.
+
-define(FSMOPTS, []).
+
-endif.
-define(STREAM_HEADER,
- "<?xml version='1.0'?>"
- "<stream:stream "
- "xmlns:stream='http://etherx.jabber.org/streams' "
- "xmlns='jabber:component:accept' "
- "id='~s' from='~s'>"
- ).
+ <<"<?xml version='1.0'?><stream:stream "
+ "xmlns:stream='http://etherx.jabber.org/stream"
+ "s' xmlns='jabber:component:accept' id='~s' "
+ "from='~s'>">>).
--define(STREAM_TRAILER, "</stream:stream>").
+-define(STREAM_TRAILER, <<"</stream:stream>">>).
-define(INVALID_HEADER_ERR,
- "<stream:stream "
- "xmlns:stream='http://etherx.jabber.org/streams'>"
- "<stream:error>Invalid Stream Header</stream:error>"
- "</stream:stream>"
- ).
+ <<"<stream:stream xmlns:stream='http://etherx.ja"
+ "bber.org/streams'><stream:error>Invalid "
+ "Stream Header</stream:error></stream:stream>">>).
-define(INVALID_HANDSHAKE_ERR,
- "<stream:error>"
- "<not-authorized xmlns='urn:ietf:params:xml:ns:xmpp-streams'/>"
- "<text xmlns='urn:ietf:params:xml:ns:xmpp-streams' xml:lang='en'>"
- "Invalid Handshake</text>"
- "</stream:error>"
- "</stream:stream>"
- ).
+ <<"<stream:error><not-authorized xmlns='urn:ietf"
+ ":params:xml:ns:xmpp-streams'/><text "
+ "xmlns='urn:ietf:params:xml:ns:xmpp-streams' "
+ "xml:lang='en'>Invalid Handshake</text></strea"
+ "m:error></stream:stream>">>).
-define(INVALID_XML_ERR,
- xml:element_to_string(?SERR_XML_NOT_WELL_FORMED)).
+ xml:element_to_binary(?SERR_XML_NOT_WELL_FORMED)).
+
-define(INVALID_NS_ERR,
- xml:element_to_string(?SERR_INVALID_NAMESPACE)).
+ xml:element_to_binary(?SERR_INVALID_NAMESPACE)).
%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
start(SockData, Opts) ->
- supervisor:start_child(ejabberd_service_sup, [SockData, Opts]).
+ supervisor:start_child(ejabberd_service_sup,
+ [SockData, Opts]).
start_link(SockData, Opts) ->
- ?GEN_FSM:start_link(ejabberd_service, [SockData, Opts],
- fsm_limit_opts(Opts) ++ ?FSMOPTS).
+ (?GEN_FSM):start_link(ejabberd_service,
+ [SockData, Opts], fsm_limit_opts(Opts) ++ (?FSMOPTS)).
-socket_type() ->
- xml_stream.
+socket_type() -> xml_stream.
%%%----------------------------------------------------------------------
%%% Callback functions from gen_fsm
@@ -123,51 +120,47 @@ socket_type() ->
init([{SockMod, Socket}, Opts]) ->
?INFO_MSG("(~w) External service connected", [Socket]),
Access = case lists:keysearch(access, 1, Opts) of
- {value, {_, A}} -> A;
- _ -> all
+ {value, {_, A}} -> A;
+ _ -> all
end,
- {Hosts, Password} =
- case lists:keysearch(hosts, 1, Opts) of
- {value, {_, Hs, HOpts}} ->
- case lists:keysearch(password, 1, HOpts) of
- {value, {_, P}} ->
- {Hs, P};
- _ ->
- % TODO: generate error
- false
- end;
- _ ->
- case lists:keysearch(host, 1, Opts) of
- {value, {_, H, HOpts}} ->
- case lists:keysearch(password, 1, HOpts) of
- {value, {_, P}} ->
- {[H], P};
- _ ->
- % TODO: generate error
- false
- end;
- _ ->
- % TODO: generate error
- false
- end
- end,
+ {Hosts, Password} = case lists:keysearch(hosts, 1, Opts)
+ of
+ {value, {_, Hs, HOpts}} ->
+ case lists:keysearch(password, 1, HOpts) of
+ {value, {_, P}} -> {Hs, P};
+ _ ->
+ % TODO: generate error
+ false
+ end;
+ _ ->
+ case lists:keysearch(host, 1, Opts) of
+ {value, {_, H, HOpts}} ->
+ case lists:keysearch(password, 1, HOpts) of
+ {value, {_, P}} -> {[H], P};
+ _ ->
+ % TODO: generate error
+ false
+ end;
+ _ ->
+ % TODO: generate error
+ false
+ end
+ end,
Shaper = case lists:keysearch(shaper_rule, 1, Opts) of
- {value, {_, S}} -> S;
- _ -> none
- end,
- CheckFrom = case lists:keysearch(service_check_from, 1, Opts) of
- {value, {_, CF}} -> CF;
- _ -> true
+ {value, {_, S}} -> S;
+ _ -> none
end,
+ CheckFrom = case lists:keysearch(service_check_from, 1,
+ Opts)
+ of
+ {value, {_, CF}} -> CF;
+ _ -> true
+ end,
SockMod:change_shaper(Socket, Shaper),
- {ok, wait_for_stream, #state{socket = Socket,
- sockmod = SockMod,
- streamid = new_id(),
- hosts = Hosts,
- password = Password,
- access = Access,
- check_from = CheckFrom
- }}.
+ {ok, wait_for_stream,
+ #state{socket = Socket, sockmod = SockMod,
+ streamid = new_id(), hosts = Hosts, password = Password,
+ access = Access, check_from = CheckFrom}}.
%%----------------------------------------------------------------------
%% Func: StateName/2
@@ -176,120 +169,109 @@ init([{SockMod, Socket}, Opts]) ->
%% {stop, Reason, NewStateData}
%%----------------------------------------------------------------------
-wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
- case xml:get_attr_s("xmlns", Attrs) of
- "jabber:component:accept" ->
- %% Note: XEP-0114 requires to check that destination is a Jabber
- %% component served by this Jabber server.
- %% However several transports don't respect that,
- %% so ejabberd doesn't check 'to' attribute (EJAB-717)
- To = xml:get_attr_s("to", Attrs),
- Header = io_lib:format(?STREAM_HEADER,
- [StateData#state.streamid, xml:crypt(To)]),
- send_text(StateData, Header),
- {next_state, wait_for_handshake, StateData};
- _ ->
- send_text(StateData, ?INVALID_HEADER_ERR),
- {stop, normal, StateData}
+wait_for_stream({xmlstreamstart, _Name, Attrs},
+ StateData) ->
+ case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ <<"jabber:component:accept">> ->
+ To = xml:get_attr_s(<<"to">>, Attrs),
+ Header = io_lib:format(?STREAM_HEADER,
+ [StateData#state.streamid, xml:crypt(To)]),
+ send_text(StateData, Header),
+ {next_state, wait_for_handshake, StateData};
+ _ ->
+ send_text(StateData, ?INVALID_HEADER_ERR),
+ {stop, normal, StateData}
end;
-
wait_for_stream({xmlstreamerror, _}, StateData) ->
Header = io_lib:format(?STREAM_HEADER,
- ["none", ?MYNAME]),
+ [<<"none">>, ?MYNAME]),
send_text(StateData,
- Header ++ ?INVALID_XML_ERR ++ ?STREAM_TRAILER),
+ <<(list_to_binary(Header))/binary, (?INVALID_XML_ERR)/binary,
+ (?STREAM_TRAILER)/binary>>),
{stop, normal, StateData};
-
wait_for_stream(closed, StateData) ->
{stop, normal, StateData}.
-
wait_for_handshake({xmlstreamelement, El}, StateData) ->
- {xmlelement, Name, _Attrs, Els} = El,
+ #xmlel{name = Name, children = Els} = El,
case {Name, xml:get_cdata(Els)} of
- {"handshake", Digest} ->
- case sha:sha(StateData#state.streamid ++
- StateData#state.password) of
- Digest ->
- send_text(StateData, "<handshake/>"),
- lists:foreach(
- fun(H) ->
- ejabberd_router:register_route(H),
- ?INFO_MSG("Route registered for service ~p~n", [H])
- end, StateData#state.hosts),
- {next_state, stream_established, StateData};
- _ ->
- send_text(StateData, ?INVALID_HANDSHAKE_ERR),
- {stop, normal, StateData}
- end;
- _ ->
- {next_state, wait_for_handshake, StateData}
+ {<<"handshake">>, Digest} ->
+ case sha:sha(<<(StateData#state.streamid)/binary,
+ (StateData#state.password)/binary>>)
+ of
+ Digest ->
+ send_text(StateData, <<"<handshake/>">>),
+ lists:foreach(fun (H) ->
+ ejabberd_router:register_route(H),
+ ?INFO_MSG("Route registered for service ~p~n",
+ [H])
+ end,
+ StateData#state.hosts),
+ {next_state, stream_established, StateData};
+ _ ->
+ send_text(StateData, ?INVALID_HANDSHAKE_ERR),
+ {stop, normal, StateData}
+ end;
+ _ -> {next_state, wait_for_handshake, StateData}
end;
-
wait_for_handshake({xmlstreamend, _Name}, StateData) ->
{stop, normal, StateData};
-
wait_for_handshake({xmlstreamerror, _}, StateData) ->
- send_text(StateData, ?INVALID_XML_ERR ++ ?STREAM_TRAILER),
+ send_text(StateData,
+ <<(?INVALID_XML_ERR)/binary,
+ (?STREAM_TRAILER)/binary>>),
{stop, normal, StateData};
-
wait_for_handshake(closed, StateData) ->
{stop, normal, StateData}.
-
stream_established({xmlstreamelement, El}, StateData) ->
- NewEl = jlib:remove_attr("xmlns", El),
- {xmlelement, Name, Attrs, _Els} = NewEl,
- From = xml:get_attr_s("from", Attrs),
+ NewEl = jlib:remove_attr(<<"xmlns">>, El),
+ #xmlel{name = Name, attrs = Attrs} = NewEl,
+ From = xml:get_attr_s(<<"from">>, Attrs),
FromJID = case StateData#state.check_from of
- %% If the admin does not want to check the from field
- %% when accept packets from any address.
- %% In this case, the component can send packet of
- %% behalf of the server users.
- false -> jlib:string_to_jid(From);
- %% The default is the standard behaviour in XEP-0114
- _ ->
- FromJID1 = jlib:string_to_jid(From),
- case FromJID1 of
- #jid{lserver = Server} ->
- case lists:member(Server, StateData#state.hosts) of
- true -> FromJID1;
- false -> error
- end;
- _ -> error
- end
+ %% If the admin does not want to check the from field
+ %% when accept packets from any address.
+ %% In this case, the component can send packet of
+ %% behalf of the server users.
+ false -> jlib:string_to_jid(From);
+ %% The default is the standard behaviour in XEP-0114
+ _ ->
+ FromJID1 = jlib:string_to_jid(From),
+ case FromJID1 of
+ #jid{lserver = Server} ->
+ case lists:member(Server, StateData#state.hosts) of
+ true -> FromJID1;
+ false -> error
+ end;
+ _ -> error
+ end
end,
- To = xml:get_attr_s("to", Attrs),
+ To = xml:get_attr_s(<<"to">>, Attrs),
ToJID = case To of
- "" -> error;
- _ -> jlib:string_to_jid(To)
+ <<"">> -> error;
+ _ -> jlib:string_to_jid(To)
end,
- if ((Name == "iq") or
- (Name == "message") or
- (Name == "presence")) and
- (ToJID /= error) and (FromJID /= error) ->
- ejabberd_router:route(FromJID, ToJID, NewEl);
+ if ((Name == <<"iq">>) or (Name == <<"message">>) or
+ (Name == <<"presence">>))
+ and (ToJID /= error)
+ and (FromJID /= error) ->
+ ejabberd_router:route(FromJID, ToJID, NewEl);
true ->
- Err = jlib:make_error_reply(NewEl, ?ERR_BAD_REQUEST),
- send_element(StateData, Err),
- error
+ Err = jlib:make_error_reply(NewEl, ?ERR_BAD_REQUEST),
+ send_element(StateData, Err),
+ error
end,
{next_state, stream_established, StateData};
-
stream_established({xmlstreamend, _Name}, StateData) ->
- % TODO
{stop, normal, StateData};
-
stream_established({xmlstreamerror, _}, StateData) ->
- send_text(StateData, ?INVALID_XML_ERR ++ ?STREAM_TRAILER),
+ send_text(StateData,
+ <<(?INVALID_XML_ERR)/binary,
+ (?STREAM_TRAILER)/binary>>),
{stop, normal, StateData};
-
stream_established(closed, StateData) ->
- % TODO
{stop, normal, StateData}.
-
-
%%----------------------------------------------------------------------
%% Func: StateName/3
%% Returns: {next_state, NextStateName, NextStateData} |
@@ -321,9 +303,9 @@ handle_event(_Event, StateName, StateData) ->
%% {stop, Reason, NewStateData} |
%% {stop, Reason, Reply, NewStateData}
%%----------------------------------------------------------------------
-handle_sync_event(_Event, _From, StateName, StateData) ->
- Reply = ok,
- {reply, Reply, StateName, StateData}.
+handle_sync_event(_Event, _From, StateName,
+ StateData) ->
+ Reply = ok, {reply, Reply, StateName, StateData}.
code_change(_OldVsn, StateName, StateData, _Extra) ->
{ok, StateName, StateData}.
@@ -340,25 +322,29 @@ handle_info({send_text, Text}, StateName, StateData) ->
handle_info({send_element, El}, StateName, StateData) ->
send_element(StateData, El),
{next_state, StateName, StateData};
-handle_info({route, From, To, Packet}, StateName, StateData) ->
- case acl:match_rule(global, StateData#state.access, From) of
- allow ->
- {xmlelement, Name, Attrs, Els} = Packet,
- Attrs2 = jlib:replace_from_to_attrs(jlib:jid_to_string(From),
- jlib:jid_to_string(To),
- Attrs),
- Text = xml:element_to_binary({xmlelement, Name, Attrs2, Els}),
- send_text(StateData, Text);
- deny ->
- Err = jlib:make_error_reply(Packet, ?ERR_NOT_ALLOWED),
- ejabberd_router:route_error(To, From, Err, Packet)
+handle_info({route, From, To, Packet}, StateName,
+ StateData) ->
+ case acl:match_rule(global, StateData#state.access,
+ From)
+ of
+ allow ->
+ #xmlel{name = Name, attrs = Attrs, children = Els} =
+ Packet,
+ Attrs2 =
+ jlib:replace_from_to_attrs(jlib:jid_to_string(From),
+ jlib:jid_to_string(To), Attrs),
+ Text = xml:element_to_binary(#xmlel{name = Name,
+ attrs = Attrs2, children = Els}),
+ send_text(StateData, Text);
+ deny ->
+ Err = jlib:make_error_reply(Packet, ?ERR_NOT_ALLOWED),
+ ejabberd_router:route_error(To, From, Err, Packet)
end,
{next_state, StateName, StateData};
handle_info(Info, StateName, StateData) ->
?ERROR_MSG("Unexpected info: ~p", [Info]),
{next_state, StateName, StateData}.
-
%%----------------------------------------------------------------------
%% Func: terminate/3
%% Purpose: Shutdown the fsm
@@ -367,13 +353,12 @@ handle_info(Info, StateName, StateData) ->
terminate(Reason, StateName, StateData) ->
?INFO_MSG("terminated: ~p", [Reason]),
case StateName of
- stream_established ->
- lists:foreach(
- fun(H) ->
- ejabberd_router:unregister_route(H)
- end, StateData#state.hosts);
- _ ->
- ok
+ stream_established ->
+ lists:foreach(fun (H) ->
+ ejabberd_router:unregister_route(H)
+ end,
+ StateData#state.hosts);
+ _ -> ok
end,
(StateData#state.sockmod):close(StateData#state.socket),
ok.
@@ -383,31 +368,30 @@ terminate(Reason, StateName, StateData) ->
%% Purpose: Prepare the state to be printed on error log
%% Returns: State to print
%%----------------------------------------------------------------------
-print_state(State) ->
- State.
+print_state(State) -> State.
%%%----------------------------------------------------------------------
%%% Internal functions
%%%----------------------------------------------------------------------
send_text(StateData, Text) ->
- (StateData#state.sockmod):send(StateData#state.socket, Text).
+ (StateData#state.sockmod):send(StateData#state.socket,
+ Text).
send_element(StateData, El) ->
send_text(StateData, xml:element_to_binary(El)).
-new_id() ->
- randoms:get_string().
+new_id() -> randoms:get_string().
fsm_limit_opts(Opts) ->
case lists:keysearch(max_fsm_queue, 1, Opts) of
- {value, {_, N}} when is_integer(N) ->
- [{max_queue, N}];
- _ ->
- case ejabberd_config:get_local_option(max_fsm_queue) of
- N when is_integer(N) ->
- [{max_queue, N}];
- _ ->
- []
- end
+ {value, {_, N}} when is_integer(N) ->
+ [{max_queue, N}];
+ _ ->
+ case ejabberd_config:get_local_option(
+ max_fsm_queue,
+ fun(I) when is_integer(I), I > 0 -> I end) of
+ undefined -> [];
+ N -> [{max_queue, N}]
+ end
end.
diff --git a/src/ejabberd_sm.erl b/src/ejabberd_sm.erl
index 6a41be0ae..4ab5975fa 100644
--- a/src/ejabberd_sm.erl
+++ b/src/ejabberd_sm.erl
@@ -25,6 +25,7 @@
%%%----------------------------------------------------------------------
-module(ejabberd_sm).
+
-author('alexey@process-one.net').
-behaviour(gen_server).
@@ -58,12 +59,15 @@
]).
%% gen_server callbacks
--export([init/1, handle_call/3, handle_cast/2, handle_info/2,
- terminate/2, code_change/3]).
+-export([init/1, handle_call/3, handle_cast/2,
+ handle_info/2, terminate/2, code_change/3]).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
+
-include("ejabberd_commands.hrl").
+
-include("mod_privacy.hrl").
-record(session, {sid, usr, us, priority, info}).
@@ -80,26 +84,40 @@
%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
%% Description: Starts the server
%%--------------------------------------------------------------------
+-type sid() :: {erlang:timestamp(), pid()}.
+-type ip() :: {inet:ip_address(), inet:port_number()} | undefined.
+-type info() :: [{conn, atom()} | {ip, ip()} | {node, atom()}
+ | {oor, boolean()} | {auth_module, atom()}].
+-type prio() :: undefined | integer().
+
+-export_type([sid/0]).
+
start_link() ->
- gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
+ gen_server:start_link({local, ?MODULE}, ?MODULE, [],
+ []).
+
+-spec route(jid(), jid(), xmlel() | broadcast()) -> ok.
route(From, To, Packet) ->
case catch do_route(From, To, Packet) of
- {'EXIT', Reason} ->
- ?ERROR_MSG("~p~nwhen processing: ~p",
- [Reason, {From, To, Packet}]);
- _ ->
- ok
+ {'EXIT', Reason} ->
+ ?ERROR_MSG("~p~nwhen processing: ~p",
+ [Reason, {From, To, Packet}]);
+ _ -> ok
end.
+-spec open_session(sid(), binary(), binary(), binary(), info()) -> ok.
+
open_session(SID, User, Server, Resource, Info) ->
set_session(SID, User, Server, Resource, undefined, Info),
mnesia:dirty_update_counter(session_counter,
jlib:nameprep(Server), 1),
check_for_sessions_to_replace(User, Server, Resource),
JID = jlib:make_jid(User, Server, Resource),
- ejabberd_hooks:run(sm_register_connection_hook, JID#jid.lserver,
- [SID, JID, Info]).
+ ejabberd_hooks:run(sm_register_connection_hook,
+ JID#jid.lserver, [SID, JID, Info]).
+
+-spec close_session(sid(), binary(), binary(), binary()) -> ok.
close_session(SID, User, Server, Resource) ->
Info = case mnesia:dirty_read({session, SID}) of
@@ -113,27 +131,29 @@ close_session(SID, User, Server, Resource) ->
end,
mnesia:sync_dirty(F),
JID = jlib:make_jid(User, Server, Resource),
- ejabberd_hooks:run(sm_remove_connection_hook, JID#jid.lserver,
- [SID, JID, Info]).
+ ejabberd_hooks:run(sm_remove_connection_hook,
+ JID#jid.lserver, [SID, JID, Info]).
check_in_subscription(Acc, User, Server, _JID, _Type, _Reason) ->
case ejabberd_auth:is_user_exists(User, Server) of
- true ->
- Acc;
- false ->
- {stop, false}
+ true -> Acc;
+ false -> {stop, false}
end.
+-spec bounce_offline_message(jid(), jid(), xmlel()) -> stop.
+
bounce_offline_message(From, To, Packet) ->
- Err = jlib:make_error_reply(Packet, ?ERR_SERVICE_UNAVAILABLE),
+ Err = jlib:make_error_reply(Packet,
+ ?ERR_SERVICE_UNAVAILABLE),
ejabberd_router:route(To, From, Err),
stop.
+-spec disconnect_removed_user(binary(), binary()) -> ok.
+
disconnect_removed_user(User, Server) ->
- ejabberd_sm:route(jlib:make_jid("", "", ""),
- jlib:make_jid(User, Server, ""),
- {xmlelement, "broadcast", [],
- [{exit, "User removed"}]}).
+ ejabberd_sm:route(jlib:make_jid(<<"">>, <<"">>, <<"">>),
+ jlib:make_jid(User, Server, <<"">>),
+ {broadcast, {exit, <<"User removed">>}}).
get_user_resources(User, Server) ->
LUser = jlib:nodeprep(User),
@@ -146,6 +166,8 @@ get_user_resources(User, Server) ->
[element(3, S#session.usr) || S <- clean_session_list(Ss)]
end.
+-spec get_user_ip(binary(), binary(), binary()) -> ip().
+
get_user_ip(User, Server, Resource) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
@@ -159,6 +181,8 @@ get_user_ip(User, Server, Resource) ->
proplists:get_value(ip, Session#session.info)
end.
+-spec get_user_info(binary(), binary(), binary()) -> info() | offline.
+
get_user_info(User, Server, Resource) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
@@ -175,21 +199,40 @@ get_user_info(User, Server, Resource) ->
[{node, Node}, {conn, Conn}, {ip, IP}]
end.
-set_presence(SID, User, Server, Resource, Priority, Presence, Info) ->
- set_session(SID, User, Server, Resource, Priority, Info),
- ejabberd_hooks:run(set_presence_hook, jlib:nameprep(Server),
+-spec set_presence(sid(), binary(), binary(), binary(),
+ prio(), xmlel(), info()) -> ok.
+
+set_presence(SID, User, Server, Resource, Priority,
+ Presence, Info) ->
+ set_session(SID, User, Server, Resource, Priority,
+ Info),
+ ejabberd_hooks:run(set_presence_hook,
+ jlib:nameprep(Server),
[User, Server, Resource, Presence]).
-unset_presence(SID, User, Server, Resource, Status, Info) ->
- set_session(SID, User, Server, Resource, undefined, Info),
- ejabberd_hooks:run(unset_presence_hook, jlib:nameprep(Server),
+-spec unset_presence(sid(), binary(), binary(),
+ binary(), binary(), info()) -> ok.
+
+unset_presence(SID, User, Server, Resource, Status,
+ Info) ->
+ set_session(SID, User, Server, Resource, undefined,
+ Info),
+ ejabberd_hooks:run(unset_presence_hook,
+ jlib:nameprep(Server),
[User, Server, Resource, Status]).
-close_session_unset_presence(SID, User, Server, Resource, Status) ->
+-spec close_session_unset_presence(sid(), binary(), binary(),
+ binary(), binary()) -> ok.
+
+close_session_unset_presence(SID, User, Server,
+ Resource, Status) ->
close_session(SID, User, Server, Resource),
- ejabberd_hooks:run(unset_presence_hook, jlib:nameprep(Server),
+ ejabberd_hooks:run(unset_presence_hook,
+ jlib:nameprep(Server),
[User, Server, Resource, Status]).
+-spec get_session_pid(binary(), binary(), binary()) -> none | pid().
+
get_session_pid(User, Server, Resource) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
@@ -200,6 +243,8 @@ get_session_pid(User, Server, Resource) ->
_ -> none
end.
+-spec dirty_get_sessions_list() -> [ljid()].
+
dirty_get_sessions_list() ->
mnesia:dirty_select(
session,
@@ -216,11 +261,11 @@ dirty_get_my_sessions_list() ->
get_vh_session_list(Server) ->
LServer = jlib:nameprep(Server),
- mnesia:dirty_select(
- session,
- [{#session{usr = '$1', _ = '_'},
- [{'==', {element, 2, '$1'}, LServer}],
- ['$1']}]).
+ mnesia:dirty_select(session,
+ [{#session{usr = '$1', _ = '_'},
+ [{'==', {element, 2, '$1'}, LServer}], ['$1']}]).
+
+-spec get_vh_session_list(binary()) -> [ljid()].
get_vh_session_number(Server) ->
LServer = jlib:nameprep(Server),
@@ -234,12 +279,18 @@ get_vh_session_number(Server) ->
Count;
_ -> 0
end.
-
+
register_iq_handler(Host, XMLNS, Module, Fun) ->
- ejabberd_sm ! {register_iq_handler, Host, XMLNS, Module, Fun}.
+ ejabberd_sm !
+ {register_iq_handler, Host, XMLNS, Module, Fun}.
+
+-spec register_iq_handler(binary(), binary(), atom(), atom(), list()) -> any().
register_iq_handler(Host, XMLNS, Module, Fun, Opts) ->
- ejabberd_sm ! {register_iq_handler, Host, XMLNS, Module, Fun, Opts}.
+ ejabberd_sm !
+ {register_iq_handler, Host, XMLNS, Module, Fun, Opts}.
+
+-spec unregister_iq_handler(binary(), binary()) -> any().
unregister_iq_handler(Host, XMLNS) ->
ejabberd_sm ! {unregister_iq_handler, Host, XMLNS}.
@@ -293,8 +344,7 @@ init([]) ->
%% Description: Handling call messages
%%--------------------------------------------------------------------
handle_call(_Request, _From, State) ->
- Reply = ok,
- {reply, Reply, State}.
+ Reply = ok, {reply, Reply, State}.
%%--------------------------------------------------------------------
%% Function: handle_cast(Msg, State) -> {noreply, State} |
@@ -302,8 +352,7 @@ handle_call(_Request, _From, State) ->
%% {stop, Reason, State}
%% Description: Handling cast messages
%%--------------------------------------------------------------------
-handle_cast(_Msg, State) ->
- {noreply, State}.
+handle_cast(_Msg, State) -> {noreply, State}.
%%--------------------------------------------------------------------
%% Function: handle_info(Info, State) -> {noreply, State} |
@@ -326,20 +375,22 @@ handle_info({mnesia_system_event, {mnesia_down, Node}}, State) ->
handle_info({register_iq_handler, Host, XMLNS, Module, Function}, State) ->
ets:insert(sm_iqtable, {{XMLNS, Host}, Module, Function}),
{noreply, State};
-handle_info({register_iq_handler, Host, XMLNS, Module, Function, Opts}, State) ->
- ets:insert(sm_iqtable, {{XMLNS, Host}, Module, Function, Opts}),
+handle_info({register_iq_handler, Host, XMLNS, Module,
+ Function, Opts},
+ State) ->
+ ets:insert(sm_iqtable,
+ {{XMLNS, Host}, Module, Function, Opts}),
{noreply, State};
-handle_info({unregister_iq_handler, Host, XMLNS}, State) ->
+handle_info({unregister_iq_handler, Host, XMLNS},
+ State) ->
case ets:lookup(sm_iqtable, {XMLNS, Host}) of
- [{_, Module, Function, Opts}] ->
- gen_iq_handler:stop_iq_handler(Module, Function, Opts);
- _ ->
- ok
+ [{_, Module, Function, Opts}] ->
+ gen_iq_handler:stop_iq_handler(Module, Function, Opts);
+ _ -> ok
end,
ets:delete(sm_iqtable, {XMLNS, Host}),
{noreply, State};
-handle_info(_Info, State) ->
- {noreply, State}.
+handle_info(_Info, State) -> {noreply, State}.
%%--------------------------------------------------------------------
%% Function: terminate(Reason, State) -> void()
@@ -356,8 +407,7 @@ terminate(_Reason, _State) ->
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
%% Description: Convert process state when code is changed
%%--------------------------------------------------------------------
-code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
+code_change(_OldVsn, State, _Extra) -> {ok, State}.
%%--------------------------------------------------------------------
%%% Internal functions
@@ -369,12 +419,9 @@ set_session(SID, User, Server, Resource, Priority, Info) ->
LResource = jlib:resourceprep(Resource),
US = {LUser, LServer},
USR = {LUser, LServer, LResource},
- F = fun() ->
- mnesia:write(#session{sid = SID,
- usr = USR,
- us = US,
- priority = Priority,
- info = Info})
+ F = fun () ->
+ mnesia:write(#session{sid = SID, usr = USR, us = US,
+ priority = Priority, info = Info})
end,
mnesia:sync_dirty(F).
@@ -408,108 +455,107 @@ recount_session_table(Node) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
do_route(From, To, Packet) ->
- ?DEBUG("session manager~n\tfrom ~p~n\tto ~p~n\tpacket ~P~n",
+ ?DEBUG("session manager~n\tfrom ~p~n\tto ~p~n\tpacket "
+ "~P~n",
[From, To, Packet, 8]),
#jid{user = User, server = Server,
luser = LUser, lserver = LServer, lresource = LResource} = To,
- {xmlelement, Name, Attrs, _Els} = Packet,
+ #xmlel{name = Name, attrs = Attrs} = Packet,
case LResource of
- "" ->
- case Name of
- "presence" ->
- {Pass, _Subsc} =
- case xml:get_attr_s("type", Attrs) of
- "subscribe" ->
- Reason = xml:get_path_s(
- Packet,
- [{elem, "status"}, cdata]),
- {is_privacy_allow(From, To, Packet) andalso
- ejabberd_hooks:run_fold(
- roster_in_subscription,
- LServer,
- false,
- [User, Server, From, subscribe, Reason]),
- true};
- "subscribed" ->
- {is_privacy_allow(From, To, Packet) andalso
- ejabberd_hooks:run_fold(
- roster_in_subscription,
- LServer,
- false,
- [User, Server, From, subscribed, ""]),
- true};
- "unsubscribe" ->
- {is_privacy_allow(From, To, Packet) andalso
- ejabberd_hooks:run_fold(
- roster_in_subscription,
- LServer,
- false,
- [User, Server, From, unsubscribe, ""]),
- true};
- "unsubscribed" ->
- {is_privacy_allow(From, To, Packet) andalso
- ejabberd_hooks:run_fold(
- roster_in_subscription,
- LServer,
- false,
- [User, Server, From, unsubscribed, ""]),
- true};
- _ ->
- {true, false}
- end,
- if Pass ->
- PResources = get_user_present_resources(
- LUser, LServer),
- lists:foreach(
- fun({_, R}) ->
- do_route(
- From,
- jlib:jid_replace_resource(To, R),
- Packet)
- end, PResources);
- true ->
- ok
- end;
- "message" ->
- route_message(From, To, Packet);
- "iq" ->
- process_iq(From, To, Packet);
- "broadcast" ->
- lists:foreach(
- fun(R) ->
- do_route(From,
- jlib:jid_replace_resource(To, R),
- Packet)
- end, get_user_resources(User, Server));
- _ ->
- ok
- end;
- _ ->
- USR = {LUser, LServer, LResource},
- case mnesia:dirty_index_read(session, USR, #session.usr) of
- [] ->
- case Name of
- "message" ->
- route_message(From, To, Packet);
- "iq" ->
- case xml:get_attr_s("type", Attrs) of
- "error" -> ok;
- "result" -> ok;
- _ ->
- Err =
- jlib:make_error_reply(
- Packet, ?ERR_SERVICE_UNAVAILABLE),
- ejabberd_router:route(To, From, Err)
- end;
+ <<"">> ->
+ case Name of
+ <<"presence">> ->
+ {Pass, _Subsc} = case xml:get_attr_s(<<"type">>, Attrs)
+ of
+ <<"subscribe">> ->
+ Reason = xml:get_path_s(Packet,
+ [{elem,
+ <<"status">>},
+ cdata]),
+ {is_privacy_allow(From, To, Packet)
+ andalso
+ ejabberd_hooks:run_fold(roster_in_subscription,
+ LServer,
+ false,
+ [User, Server,
+ From,
+ subscribe,
+ Reason]),
+ true};
+ <<"subscribed">> ->
+ {is_privacy_allow(From, To, Packet)
+ andalso
+ ejabberd_hooks:run_fold(roster_in_subscription,
+ LServer,
+ false,
+ [User, Server,
+ From,
+ subscribed,
+ <<"">>]),
+ true};
+ <<"unsubscribe">> ->
+ {is_privacy_allow(From, To, Packet)
+ andalso
+ ejabberd_hooks:run_fold(roster_in_subscription,
+ LServer,
+ false,
+ [User, Server,
+ From,
+ unsubscribe,
+ <<"">>]),
+ true};
+ <<"unsubscribed">> ->
+ {is_privacy_allow(From, To, Packet)
+ andalso
+ ejabberd_hooks:run_fold(roster_in_subscription,
+ LServer,
+ false,
+ [User, Server,
+ From,
+ unsubscribed,
+ <<"">>]),
+ true};
+ _ -> {true, false}
+ end,
+ if Pass ->
+ PResources = get_user_present_resources(LUser, LServer),
+ lists:foreach(fun ({_, R}) ->
+ do_route(From,
+ jlib:jid_replace_resource(To,
+ R),
+ Packet)
+ end,
+ PResources);
+ true -> ok
+ end;
+ <<"message">> -> route_message(From, To, Packet);
+ <<"iq">> -> process_iq(From, To, Packet);
+ _ -> ok
+ end;
+ _ ->
+ USR = {LUser, LServer, LResource},
+ case mnesia:dirty_index_read(session, USR, #session.usr)
+ of
+ [] ->
+ case Name of
+ <<"message">> -> route_message(From, To, Packet);
+ <<"iq">> ->
+ case xml:get_attr_s(<<"type">>, Attrs) of
+ <<"error">> -> ok;
+ <<"result">> -> ok;
_ ->
- ?DEBUG("packet droped~n", [])
- end;
- Ss ->
- Session = lists:max(Ss),
- Pid = element(2, Session#session.sid),
- ?DEBUG("sending to process ~p~n", [Pid]),
- Pid ! {route, From, To, Packet}
- end
+ Err = jlib:make_error_reply(Packet,
+ ?ERR_SERVICE_UNAVAILABLE),
+ ejabberd_router:route(To, From, Err)
+ end;
+ _ -> ?DEBUG("packet droped~n", [])
+ end;
+ Ss ->
+ Session = lists:max(Ss),
+ Pid = element(2, Session#session.sid),
+ ?DEBUG("sending to process ~p~n", [Pid]),
+ Pid ! {route, From, To, Packet}
+ end
end.
%% The default list applies to the user as a whole,
@@ -519,8 +565,9 @@ do_route(From, To, Packet) ->
is_privacy_allow(From, To, Packet) ->
User = To#jid.user,
Server = To#jid.server,
- PrivacyList = ejabberd_hooks:run_fold(privacy_get_user_list, Server,
- #userlist{}, [User, Server]),
+ PrivacyList =
+ ejabberd_hooks:run_fold(privacy_get_user_list, Server,
+ #userlist{}, [User, Server]),
is_privacy_allow(From, To, Packet, PrivacyList).
%% Check if privacy rules allow this delivery
@@ -528,102 +575,89 @@ is_privacy_allow(From, To, Packet) ->
is_privacy_allow(From, To, Packet, PrivacyList) ->
User = To#jid.user,
Server = To#jid.server,
- allow == ejabberd_hooks:run_fold(
- privacy_check_packet, Server,
- allow,
- [User,
- Server,
- PrivacyList,
- {From, To, Packet},
- in]).
+ allow ==
+ ejabberd_hooks:run_fold(privacy_check_packet, Server,
+ allow,
+ [User, Server, PrivacyList, {From, To, Packet},
+ in]).
route_message(From, To, Packet) ->
LUser = To#jid.luser,
LServer = To#jid.lserver,
PrioRes = get_user_present_resources(LUser, LServer),
case catch lists:max(PrioRes) of
- {Priority, _R} when is_integer(Priority), Priority >= 0 ->
- lists:foreach(
- %% Route messages to all priority that equals the max, if
- %% positive
- fun({P, R}) when P == Priority ->
- LResource = jlib:resourceprep(R),
- USR = {LUser, LServer, LResource},
- case mnesia:dirty_index_read(session, USR, #session.usr) of
- [] ->
- ok; % Race condition
- Ss ->
- Session = lists:max(Ss),
- Pid = element(2, Session#session.sid),
- ?DEBUG("sending to process ~p~n", [Pid]),
- Pid ! {route, From, To, Packet}
- end;
- %% Ignore other priority:
- ({_Prio, _Res}) ->
- ok
- end,
- PrioRes);
- _ ->
- case xml:get_tag_attr_s("type", Packet) of
- "error" ->
- ok;
- "groupchat" ->
- bounce_offline_message(From, To, Packet);
- "headline" ->
- bounce_offline_message(From, To, Packet);
- _ ->
- case ejabberd_auth:is_user_exists(LUser, LServer) of
+ {Priority, _R}
+ when is_integer(Priority), Priority >= 0 ->
+ lists:foreach(fun ({P, R}) when P == Priority ->
+ LResource = jlib:resourceprep(R),
+ USR = {LUser, LServer, LResource},
+ case mnesia:dirty_index_read(session, USR,
+ #session.usr)
+ of
+ [] ->
+ ok; % Race condition
+ Ss ->
+ Session = lists:max(Ss),
+ Pid = element(2, Session#session.sid),
+ ?DEBUG("sending to process ~p~n", [Pid]),
+ Pid ! {route, From, To, Packet}
+ end;
+ %% Ignore other priority:
+ ({_Prio, _Res}) -> ok
+ end,
+ PrioRes);
+ _ ->
+ case xml:get_tag_attr_s(<<"type">>, Packet) of
+ <<"error">> -> ok;
+ <<"groupchat">> ->
+ bounce_offline_message(From, To, Packet);
+ <<"headline">> ->
+ bounce_offline_message(From, To, Packet);
+ _ ->
+ case ejabberd_auth:is_user_exists(LUser, LServer) of
+ true ->
+ case is_privacy_allow(From, To, Packet) of
true ->
- case is_privacy_allow(From, To, Packet) of
- true ->
- ejabberd_hooks:run(offline_message_hook,
- LServer,
- [From, To, Packet]);
- false ->
- ok
- end;
- _ ->
- Err = jlib:make_error_reply(
- Packet, ?ERR_SERVICE_UNAVAILABLE),
- ejabberd_router:route(To, From, Err)
- end
- end
+ ejabberd_hooks:run(offline_message_hook, LServer,
+ [From, To, Packet]);
+ false -> ok
+ end;
+ _ ->
+ Err = jlib:make_error_reply(Packet,
+ ?ERR_SERVICE_UNAVAILABLE),
+ ejabberd_router:route(To, From, Err)
+ end
+ end
end.
-
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
clean_session_list(Ss) ->
clean_session_list(lists:keysort(#session.usr, Ss), []).
-clean_session_list([], Res) ->
- Res;
-clean_session_list([S], Res) ->
- [S | Res];
+clean_session_list([], Res) -> Res;
+clean_session_list([S], Res) -> [S | Res];
clean_session_list([S1, S2 | Rest], Res) ->
- if
- S1#session.usr == S2#session.usr ->
- if
- S1#session.sid > S2#session.sid ->
- clean_session_list([S1 | Rest], Res);
- true ->
- clean_session_list([S2 | Rest], Res)
- end;
- true ->
- clean_session_list([S2 | Rest], [S1 | Res])
+ if S1#session.usr == S2#session.usr ->
+ if S1#session.sid > S2#session.sid ->
+ clean_session_list([S1 | Rest], Res);
+ true -> clean_session_list([S2 | Rest], Res)
+ end;
+ true -> clean_session_list([S2 | Rest], [S1 | Res])
end.
-
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
get_user_present_resources(LUser, LServer) ->
US = {LUser, LServer},
- case catch mnesia:dirty_index_read(session, US, #session.us) of
- {'EXIT', _Reason} ->
- [];
- Ss ->
- [{S#session.priority, element(3, S#session.usr)} ||
- S <- clean_session_list(Ss), is_integer(S#session.priority)]
+ case catch mnesia:dirty_index_read(session, US,
+ #session.us)
+ of
+ {'EXIT', _Reason} -> [];
+ Ss ->
+ [{S#session.priority, element(3, S#session.usr)}
+ || S <- clean_session_list(Ss),
+ is_integer(S#session.priority)]
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -633,62 +667,54 @@ check_for_sessions_to_replace(User, Server, Resource) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
LResource = jlib:resourceprep(Resource),
-
- %% TODO: Depending on how this is executed, there could be an unneeded
- %% replacement for max_sessions. We need to check this at some point.
check_existing_resources(LUser, LServer, LResource),
check_max_sessions(LUser, LServer).
check_existing_resources(LUser, LServer, LResource) ->
SIDs = get_resource_sessions(LUser, LServer, LResource),
- if
- SIDs == [] -> ok;
- true ->
- %% A connection exist with the same resource. We replace it:
- MaxSID = lists:max(SIDs),
- lists:foreach(
- fun({_, Pid} = S) when S /= MaxSID ->
- Pid ! replaced;
- (_) -> ok
- end, SIDs)
+ if SIDs == [] -> ok;
+ true ->
+ MaxSID = lists:max(SIDs),
+ lists:foreach(fun ({_, Pid} = S) when S /= MaxSID ->
+ Pid ! replaced;
+ (_) -> ok
+ end,
+ SIDs)
end.
+-spec is_existing_resource(binary(), binary(), binary()) -> boolean().
+
is_existing_resource(LUser, LServer, LResource) ->
[] /= get_resource_sessions(LUser, LServer, LResource).
get_resource_sessions(User, Server, Resource) ->
- USR = {jlib:nodeprep(User), jlib:nameprep(Server), jlib:resourceprep(Resource)},
- mnesia:dirty_select(
- session,
- [{#session{sid = '$1', usr = USR, _ = '_'}, [], ['$1']}]).
+ USR = {jlib:nodeprep(User), jlib:nameprep(Server),
+ jlib:resourceprep(Resource)},
+ mnesia:dirty_select(session,
+ [{#session{sid = '$1', usr = USR, _ = '_'}, [],
+ ['$1']}]).
check_max_sessions(LUser, LServer) ->
- %% If the max number of sessions for a given is reached, we replace the
- %% first one
- SIDs = mnesia:dirty_select(
- session,
- [{#session{sid = '$1', us = {LUser, LServer}, _ = '_'}, [],
- ['$1']}]),
+ SIDs = mnesia:dirty_select(session,
+ [{#session{sid = '$1', us = {LUser, LServer},
+ _ = '_'},
+ [], ['$1']}]),
MaxSessions = get_max_user_sessions(LUser, LServer),
- if
- length(SIDs) =< MaxSessions ->
- ok;
- true ->
- {_, Pid} = lists:min(SIDs),
- Pid ! replaced
+ if length(SIDs) =< MaxSessions -> ok;
+ true -> {_, Pid} = lists:min(SIDs), Pid ! replaced
end.
-
%% Get the user_max_session setting
%% This option defines the max number of time a given users are allowed to
%% log in
%% Defaults to infinity
get_max_user_sessions(LUser, Host) ->
- case acl:match_rule(
- Host, max_user_sessions, jlib:make_jid(LUser, Host, "")) of
- Max when is_integer(Max) -> Max;
- infinity -> infinity;
- _ -> ?MAX_USER_SESSIONS
+ case acl:match_rule(Host, max_user_sessions,
+ jlib:make_jid(LUser, Host, <<"">>))
+ of
+ Max when is_integer(Max) -> Max;
+ infinity -> infinity;
+ _ -> ?MAX_USER_SESSIONS
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -696,80 +722,78 @@ get_max_user_sessions(LUser, Host) ->
process_iq(From, To, Packet) ->
IQ = jlib:iq_query_info(Packet),
case IQ of
- #iq{xmlns = XMLNS} ->
- Host = To#jid.lserver,
- case ets:lookup(sm_iqtable, {XMLNS, Host}) of
- [{_, Module, Function}] ->
- ResIQ = Module:Function(From, To, IQ),
- if
- ResIQ /= ignore ->
- ejabberd_router:route(To, From,
- jlib:iq_to_xml(ResIQ));
- true ->
- ok
- end;
- [{_, Module, Function, Opts}] ->
- gen_iq_handler:handle(Host, Module, Function, Opts,
- From, To, IQ);
- [] ->
- Err = jlib:make_error_reply(
- Packet, ?ERR_SERVICE_UNAVAILABLE),
- ejabberd_router:route(To, From, Err)
- end;
- reply ->
- ok;
- _ ->
- Err = jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST),
- ejabberd_router:route(To, From, Err),
- ok
+ #iq{xmlns = XMLNS} ->
+ Host = To#jid.lserver,
+ case ets:lookup(sm_iqtable, {XMLNS, Host}) of
+ [{_, Module, Function}] ->
+ ResIQ = Module:Function(From, To, IQ),
+ if ResIQ /= ignore ->
+ ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ));
+ true -> ok
+ end;
+ [{_, Module, Function, Opts}] ->
+ gen_iq_handler:handle(Host, Module, Function, Opts,
+ From, To, IQ);
+ [] ->
+ Err = jlib:make_error_reply(Packet,
+ ?ERR_SERVICE_UNAVAILABLE),
+ ejabberd_router:route(To, From, Err)
+ end;
+ reply -> ok;
+ _ ->
+ Err = jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST),
+ ejabberd_router:route(To, From, Err),
+ ok
end.
+-spec force_update_presence({binary(), binary()}) -> any().
+
force_update_presence({LUser, _LServer} = US) ->
- case catch mnesia:dirty_index_read(session, US, #session.us) of
- {'EXIT', _Reason} ->
- ok;
- Ss ->
- lists:foreach(fun(#session{sid = {_, Pid}}) ->
- Pid ! {force_update_presence, LUser}
- end, Ss)
+ case catch mnesia:dirty_index_read(session, US,
+ #session.us)
+ of
+ {'EXIT', _Reason} -> ok;
+ Ss ->
+ lists:foreach(fun (#session{sid = {_, Pid}}) ->
+ Pid ! {force_update_presence, LUser}
+ end,
+ Ss)
end.
-
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% ejabberd commands
commands() ->
- [
- #ejabberd_commands{name = connected_users,
- tags = [session],
- desc = "List all established sessions",
- module = ?MODULE, function = connected_users,
- args = [],
- result = {connected_users, {list, {sessions, string}}}},
+ [#ejabberd_commands{name = connected_users,
+ tags = [session],
+ desc = "List all established sessions",
+ module = ?MODULE, function = connected_users, args = [],
+ result = {connected_users, {list, {sessions, string}}}},
#ejabberd_commands{name = connected_users_number,
- tags = [session, stats],
- desc = "Get the number of established sessions",
- module = ?MODULE, function = connected_users_number,
- args = [],
- result = {num_sessions, integer}},
+ tags = [session, stats],
+ desc = "Get the number of established sessions",
+ module = ?MODULE, function = connected_users_number,
+ args = [], result = {num_sessions, integer}},
#ejabberd_commands{name = user_resources,
- tags = [session],
- desc = "List user's connected resources",
- module = ?MODULE, function = user_resources,
- args = [{user, string}, {host, string}],
- result = {resources, {list, {resource, string}}}}
- ].
+ tags = [session],
+ desc = "List user's connected resources",
+ module = ?MODULE, function = user_resources,
+ args = [{user, string}, {host, string}],
+ result = {resources, {list, {resource, string}}}}].
+
+-spec connected_users() -> [binary()].
connected_users() ->
USRs = dirty_get_sessions_list(),
SUSRs = lists:sort(USRs),
- lists:map(fun({U, S, R}) -> [U, $@, S, $/, R] end, SUSRs).
+ lists:map(fun ({U, S, R}) -> <<U/binary, $@, S/binary, $/, R/binary>> end,
+ SUSRs).
connected_users_number() ->
length(dirty_get_sessions_list()).
user_resources(User, Server) ->
- Resources = get_user_resources(User, Server),
+ Resources = get_user_resources(User, Server),
lists:sort(Resources).
@@ -778,24 +802,18 @@ user_resources(User, Server) ->
update_tables() ->
case catch mnesia:table_info(session, attributes) of
- [ur, user, node] ->
- mnesia:delete_table(session);
- [ur, user, pid] ->
- mnesia:delete_table(session);
- [usr, us, pid] ->
- mnesia:delete_table(session);
- [sid, usr, us, priority] ->
- mnesia:delete_table(session);
- [sid, usr, us, priority, info] ->
- ok;
- {'EXIT', _} ->
- ok
+ [ur, user, node] -> mnesia:delete_table(session);
+ [ur, user, pid] -> mnesia:delete_table(session);
+ [usr, us, pid] -> mnesia:delete_table(session);
+ [sid, usr, us, priority] ->
+ mnesia:delete_table(session);
+ [sid, usr, us, priority, info] -> ok;
+ {'EXIT', _} -> ok
end,
- case lists:member(presence, mnesia:system_info(tables)) of
- true ->
- mnesia:delete_table(presence);
- false ->
- ok
+ case lists:member(presence, mnesia:system_info(tables))
+ of
+ true -> mnesia:delete_table(presence);
+ false -> ok
end,
case lists:member(local_session, mnesia:system_info(tables)) of
true ->
diff --git a/src/ejabberd_socket.erl b/src/ejabberd_socket.erl
index 836e7d9f9..189718a35 100644
--- a/src/ejabberd_socket.erl
+++ b/src/ejabberd_socket.erl
@@ -25,6 +25,7 @@
%%%----------------------------------------------------------------------
-module(ejabberd_socket).
+
-author('alexey@process-one.net').
%% API
@@ -47,8 +48,29 @@
sockname/1, peername/1]).
-include("ejabberd.hrl").
+-include("jlib.hrl").
+
+-type sockmod() :: ejabberd_http_poll | ejabberd_bosh |
+ ejabberd_http_bind | ejabberd_http_bindjson |
+ ejabberd_http_ws | ejabberd_http_wsjson |
+ gen_tcp | tls | ejabberd_zlib.
+-type receiver() :: pid () | atom().
+-type socket() :: pid() | inet:socket() |
+ tls:tls_socket() |
+ ejabberd_zlib:zlib_socket() |
+ ejabberd_bosh:bosh_socket() |
+ ejabberd_http_ws:ws_socket() |
+ ejabberd_http_poll:poll_socket().
+
+-record(socket_state, {sockmod = gen_tcp :: sockmod(),
+ socket = self() :: socket(),
+ receiver = self() :: receiver()}).
+
+-type socket_state() :: #socket_state{}.
--record(socket_state, {sockmod, socket, receiver}).
+-export_type([socket_state/0, sockmod/0]).
+
+-spec start(atom(), sockmod(), socket(), [{atom(), any()}]) -> any().
%%====================================================================
%% API
@@ -59,56 +81,53 @@
%%--------------------------------------------------------------------
start(Module, SockMod, Socket, Opts) ->
case Module:socket_type() of
- xml_stream ->
- MaxStanzaSize =
- case lists:keysearch(max_stanza_size, 1, Opts) of
- {value, {_, Size}} -> Size;
- _ -> infinity
- end,
- {ReceiverMod, Receiver, RecRef} =
- case catch SockMod:custom_receiver(Socket) of
- {receiver, RecMod, RecPid} ->
- {RecMod, RecPid, RecMod};
- _ ->
- RecPid = ejabberd_receiver:start(
- Socket, SockMod, none, MaxStanzaSize),
- {ejabberd_receiver, RecPid, RecPid}
+ xml_stream ->
+ MaxStanzaSize = case lists:keysearch(max_stanza_size, 1,
+ Opts)
+ of
+ {value, {_, Size}} -> Size;
+ _ -> infinity
+ end,
+ {ReceiverMod, Receiver, RecRef} = case catch
+ SockMod:custom_receiver(Socket)
+ of
+ {receiver, RecMod, RecPid} ->
+ {RecMod, RecPid, RecMod};
+ _ ->
+ RecPid =
+ ejabberd_receiver:start(Socket,
+ SockMod,
+ none,
+ MaxStanzaSize),
+ {ejabberd_receiver, RecPid,
+ RecPid}
+ end,
+ SocketData = #socket_state{sockmod = SockMod,
+ socket = Socket, receiver = RecRef},
+ case Module:start({?MODULE, SocketData}, Opts) of
+ {ok, Pid} ->
+ case SockMod:controlling_process(Socket, Receiver) of
+ ok -> ok;
+ {error, _Reason} -> SockMod:close(Socket)
end,
- SocketData = #socket_state{sockmod = SockMod,
- socket = Socket,
- receiver = RecRef},
- case Module:start({?MODULE, SocketData}, Opts) of
- {ok, Pid} ->
- case SockMod:controlling_process(Socket, Receiver) of
- ok ->
- ok;
- {error, _Reason} ->
- SockMod:close(Socket)
- end,
- ReceiverMod:become_controller(Receiver, Pid);
- {error, _Reason} ->
- SockMod:close(Socket),
- case ReceiverMod of
- ejabberd_receiver ->
- ReceiverMod:close(Receiver);
- _ ->
- ok
- end
- end;
- independent ->
- ok;
- raw ->
- case Module:start({SockMod, Socket}, Opts) of
- {ok, Pid} ->
- case SockMod:controlling_process(Socket, Pid) of
- ok ->
- ok;
- {error, _Reason} ->
- SockMod:close(Socket)
- end;
- {error, _Reason} ->
- SockMod:close(Socket)
- end
+ ReceiverMod:become_controller(Receiver, Pid);
+ {error, _Reason} ->
+ SockMod:close(Socket),
+ case ReceiverMod of
+ ejabberd_receiver -> ReceiverMod:close(Receiver);
+ _ -> ok
+ end
+ end;
+ independent -> ok;
+ raw ->
+ case Module:start({SockMod, Socket}, Opts) of
+ {ok, Pid} ->
+ case SockMod:controlling_process(Socket, Pid) of
+ ok -> ok;
+ {error, _Reason} -> SockMod:close(Socket)
+ end;
+ {error, _Reason} -> SockMod:close(Socket)
+ end
end.
connect(Addr, Port, Opts) ->
@@ -116,22 +135,19 @@ connect(Addr, Port, Opts) ->
connect(Addr, Port, Opts, Timeout) ->
case gen_tcp:connect(Addr, Port, Opts, Timeout) of
- {ok, Socket} ->
- Receiver = ejabberd_receiver:start(Socket, gen_tcp, none),
- SocketData = #socket_state{sockmod = gen_tcp,
- socket = Socket,
- receiver = Receiver},
- Pid = self(),
- case gen_tcp:controlling_process(Socket, Receiver) of
- ok ->
- ejabberd_receiver:become_controller(Receiver, Pid),
- {ok, SocketData};
- {error, _Reason} = Error ->
- gen_tcp:close(Socket),
- Error
- end;
- {error, _Reason} = Error ->
- Error
+ {ok, Socket} ->
+ Receiver = ejabberd_receiver:start(Socket, gen_tcp,
+ none),
+ SocketData = #socket_state{sockmod = gen_tcp,
+ socket = Socket, receiver = Receiver},
+ Pid = self(),
+ case gen_tcp:controlling_process(Socket, Receiver) of
+ ok ->
+ ejabberd_receiver:become_controller(Receiver, Pid),
+ {ok, SocketData};
+ {error, _Reason} = Error -> gen_tcp:close(Socket), Error
+ end;
+ {error, _Reason} = Error -> Error
end.
starttls(SocketData, TLSOpts) ->
@@ -160,11 +176,12 @@ compress(SocketData, Data) ->
send(SocketData, Data),
SocketData#socket_state{socket = ZlibSocket, sockmod = ejabberd_zlib}.
-reset_stream(SocketData) when is_pid(SocketData#socket_state.receiver) ->
+reset_stream(SocketData)
+ when is_pid(SocketData#socket_state.receiver) ->
ejabberd_receiver:reset_stream(SocketData#socket_state.receiver);
-reset_stream(SocketData) when is_atom(SocketData#socket_state.receiver) ->
- (SocketData#socket_state.receiver):reset_stream(
- SocketData#socket_state.socket).
+reset_stream(SocketData)
+ when is_atom(SocketData#socket_state.receiver) ->
+ (SocketData#socket_state.receiver):reset_stream(SocketData#socket_state.socket).
%% sockmod=gen_tcp|tls|ejabberd_zlib
send(SocketData, Data) ->
@@ -182,23 +199,29 @@ send(SocketData, Data) ->
%% Can only be called when in c2s StateData#state.xml_socket is true
%% This function is used for HTTP bind
%% sockmod=ejabberd_http_poll|ejabberd_http_bind or any custom module
+-spec send_xml(socket_state(), xmlel()) -> any().
+
send_xml(SocketData, Data) ->
- catch (SocketData#socket_state.sockmod):send_xml(
- SocketData#socket_state.socket, Data).
+ catch
+ (SocketData#socket_state.sockmod):send_xml(SocketData#socket_state.socket,
+ Data).
change_shaper(SocketData, Shaper)
- when is_pid(SocketData#socket_state.receiver) ->
- ejabberd_receiver:change_shaper(SocketData#socket_state.receiver, Shaper);
+ when is_pid(SocketData#socket_state.receiver) ->
+ ejabberd_receiver:change_shaper(SocketData#socket_state.receiver,
+ Shaper);
change_shaper(SocketData, Shaper)
- when is_atom(SocketData#socket_state.receiver) ->
- (SocketData#socket_state.receiver):change_shaper(
- SocketData#socket_state.socket, Shaper).
-
-monitor(SocketData) when is_pid(SocketData#socket_state.receiver) ->
- erlang:monitor(process, SocketData#socket_state.receiver);
-monitor(SocketData) when is_atom(SocketData#socket_state.receiver) ->
- (SocketData#socket_state.receiver):monitor(
- SocketData#socket_state.socket).
+ when is_atom(SocketData#socket_state.receiver) ->
+ (SocketData#socket_state.receiver):change_shaper(SocketData#socket_state.socket,
+ Shaper).
+
+monitor(SocketData)
+ when is_pid(SocketData#socket_state.receiver) ->
+ erlang:monitor(process,
+ SocketData#socket_state.receiver);
+monitor(SocketData)
+ when is_atom(SocketData#socket_state.receiver) ->
+ (SocketData#socket_state.receiver):monitor(SocketData#socket_state.socket).
get_sockmod(SocketData) ->
SocketData#socket_state.sockmod.
@@ -212,22 +235,21 @@ get_verify_result(SocketData) ->
close(SocketData) ->
ejabberd_receiver:close(SocketData#socket_state.receiver).
-sockname(#socket_state{sockmod = SockMod, socket = Socket}) ->
+sockname(#socket_state{sockmod = SockMod,
+ socket = Socket}) ->
case SockMod of
- gen_tcp ->
- inet:sockname(Socket);
- _ ->
- SockMod:sockname(Socket)
+ gen_tcp -> inet:sockname(Socket);
+ _ -> SockMod:sockname(Socket)
end.
-peername(#socket_state{sockmod = SockMod, socket = Socket}) ->
+peername(#socket_state{sockmod = SockMod,
+ socket = Socket}) ->
case SockMod of
- gen_tcp ->
- inet:peername(Socket);
- _ ->
- SockMod:peername(Socket)
+ gen_tcp -> inet:peername(Socket);
+ _ -> SockMod:peername(Socket)
end.
%%====================================================================
%% Internal functions
%%====================================================================
+%====================================================================
diff --git a/src/ejabberd_sup.erl b/src/ejabberd_sup.erl
index 7dcd29232..80d845fcb 100644
--- a/src/ejabberd_sup.erl
+++ b/src/ejabberd_sup.erl
@@ -63,6 +63,13 @@ init([]) ->
brutal_kill,
worker,
[ejabberd_router]},
+ Router_multicast =
+ {ejabberd_router_multicast,
+ {ejabberd_router_multicast, start_link, []},
+ permanent,
+ brutal_kill,
+ worker,
+ [ejabberd_router_multicast]},
SM =
{ejabberd_sm,
{ejabberd_sm, start_link, []},
@@ -189,6 +196,7 @@ init([]) ->
NodeGroups,
SystemMonitor,
Router,
+ Router_multicast,
SM,
S2S,
Local,
diff --git a/src/ejabberd_system_monitor.erl b/src/ejabberd_system_monitor.erl
index d55fdb84b..1273226c4 100644
--- a/src/ejabberd_system_monitor.erl
+++ b/src/ejabberd_system_monitor.erl
@@ -25,20 +25,21 @@
%%%-------------------------------------------------------------------
-module(ejabberd_system_monitor).
+
-author('alexey@process-one.net').
-behaviour(gen_server).
%% API
--export([start_link/0,
- process_command/3,
+-export([start_link/0, process_command/3,
process_remote_command/1]).
%% gen_server callbacks
--export([init/1, handle_call/3, handle_cast/2, handle_info/2,
- terminate/2, code_change/3]).
+-export([init/1, handle_call/3, handle_cast/2,
+ handle_info/2, terminate/2, code_change/3]).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
-record(state, {}).
@@ -51,37 +52,36 @@
%% Description: Starts the server
%%--------------------------------------------------------------------
start_link() ->
- LH = case ejabberd_config:get_local_option(watchdog_large_heap) of
- I when is_integer(I) -> I;
- _ -> 1000000
-end,
+ LH = ejabberd_config:get_local_option(
+ watchdog_large_heap,
+ fun(I) when is_integer(I), I > 0 -> I end,
+ 1000000),
Opts = [{large_heap, LH}],
- gen_server:start_link({local, ?MODULE}, ?MODULE, Opts, []).
+ gen_server:start_link({local, ?MODULE}, ?MODULE, Opts,
+ []).
process_command(From, To, Packet) ->
case To of
- #jid{luser = "", lresource = "watchdog"} ->
- {xmlelement, Name, _Attrs, _Els} = Packet,
- case Name of
- "message" ->
- LFrom = jlib:jid_tolower(jlib:jid_remove_resource(From)),
- case lists:member(LFrom, get_admin_jids()) of
- true ->
- Body = xml:get_path_s(
- Packet, [{elem, "body"}, cdata]),
- spawn(fun() ->
- process_flag(priority, high),
- process_command1(From, To, Body)
- end),
- stop;
- false ->
- ok
- end;
- _ ->
- ok
- end;
- _ ->
- ok
+ #jid{luser = <<"">>, lresource = <<"watchdog">>} ->
+ #xmlel{name = Name} = Packet,
+ case Name of
+ <<"message">> ->
+ LFrom =
+ jlib:jid_tolower(jlib:jid_remove_resource(From)),
+ case lists:member(LFrom, get_admin_jids()) of
+ true ->
+ Body = xml:get_path_s(Packet,
+ [{elem, <<"body">>}, cdata]),
+ spawn(fun () ->
+ process_flag(priority, high),
+ process_command1(From, To, Body)
+ end),
+ stop;
+ false -> ok
+ end;
+ _ -> ok
+ end;
+ _ -> ok
end.
%%====================================================================
@@ -99,11 +99,11 @@ init(Opts) ->
LH = proplists:get_value(large_heap, Opts),
process_flag(priority, high),
erlang:system_monitor(self(), [{large_heap, LH}]),
- lists:foreach(
- fun(Host) ->
- ejabberd_hooks:add(local_send_to_resource_hook, Host,
- ?MODULE, process_command, 50)
- end, ?MYHOSTS),
+ lists:foreach(fun (Host) ->
+ ejabberd_hooks:add(local_send_to_resource_hook, Host,
+ ?MODULE, process_command, 50)
+ end,
+ ?MYHOSTS),
{ok, #state{}}.
%%--------------------------------------------------------------------
@@ -117,18 +117,20 @@ init(Opts) ->
%%--------------------------------------------------------------------
handle_call({get, large_heap}, _From, State) ->
{reply, get_large_heap(), State};
-handle_call({set, large_heap, NewValue}, _From, State) ->
- MonSettings = erlang:system_monitor(self(), [{large_heap, NewValue}]),
+handle_call({set, large_heap, NewValue}, _From,
+ State) ->
+ MonSettings = erlang:system_monitor(self(),
+ [{large_heap, NewValue}]),
OldLH = get_large_heap(MonSettings),
NewLH = get_large_heap(),
{reply, {lh_changed, OldLH, NewLH}, State};
handle_call(_Request, _From, State) ->
- Reply = ok,
- {reply, Reply, State}.
+ Reply = ok, {reply, Reply, State}.
get_large_heap() ->
MonSettings = erlang:system_monitor(),
get_large_heap(MonSettings).
+
get_large_heap(MonSettings) ->
{_MonitorPid, Options} = MonSettings,
proplists:get_value(large_heap, Options).
@@ -139,8 +141,7 @@ get_large_heap(MonSettings) ->
%% {stop, Reason, State}
%% Description: Handling cast messages
%%--------------------------------------------------------------------
-handle_cast(_Msg, State) ->
- {noreply, State}.
+handle_cast(_Msg, State) -> {noreply, State}.
%%--------------------------------------------------------------------
%% Function: handle_info(Info, State) -> {noreply, State} |
@@ -149,13 +150,12 @@ handle_cast(_Msg, State) ->
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------
handle_info({monitor, Pid, large_heap, Info}, State) ->
- spawn(fun() ->
+ spawn(fun () ->
process_flag(priority, high),
process_large_heap(Pid, Info)
end),
{noreply, State};
-handle_info(_Info, State) ->
- {noreply, State}.
+handle_info(_Info, State) -> {noreply, State}.
%%--------------------------------------------------------------------
%% Function: terminate(Reason, State) -> void()
@@ -164,204 +164,171 @@ handle_info(_Info, State) ->
%% cleaning up. When it returns, the gen_server terminates with Reason.
%% The return value is ignored.
%%--------------------------------------------------------------------
-terminate(_Reason, _State) ->
- ok.
+terminate(_Reason, _State) -> ok.
%%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
%% Description: Convert process state when code is changed
%%--------------------------------------------------------------------
-code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
+code_change(_OldVsn, State, _Extra) -> {ok, State}.
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
process_large_heap(Pid, Info) ->
- Host = ?MYNAME,
- case ejabberd_config:get_local_option(watchdog_admins) of
- JIDs when is_list(JIDs),
- JIDs /= [] ->
- DetailedInfo = detailed_info(Pid),
- Body = io_lib:format(
- "(~w) The process ~w is consuming too much memory:~n~p~n"
- "~s",
- [node(), Pid, Info, DetailedInfo]),
- From = jlib:make_jid("", Host, "watchdog"),
- lists:foreach(
- fun(S) ->
- case jlib:string_to_jid(S) of
- error -> ok;
- JID ->
- send_message(From, JID, Body)
- end
- end, JIDs);
- _ ->
- ok
- end.
+ Host = (?MYNAME),
+ JIDs = get_admin_jids(),
+ DetailedInfo = detailed_info(Pid),
+ Body = iolist_to_binary(
+ io_lib:format("(~w) The process ~w is consuming too "
+ "much memory:~n~p~n~s",
+ [node(), Pid, Info, DetailedInfo])),
+ From = jlib:make_jid(<<"">>, Host, <<"watchdog">>),
+ lists:foreach(fun (JID) ->
+ send_message(From, jlib:make_jid(JID), Body)
+ end, JIDs).
send_message(From, To, Body) ->
- ejabberd_router:route(
- From, To,
- {xmlelement, "message", [{"type", "chat"}],
- [{xmlelement, "body", [],
- [{xmlcdata, lists:flatten(Body)}]}]}).
+ ejabberd_router:route(From, To,
+ #xmlel{name = <<"message">>,
+ attrs = [{<<"type">>, <<"chat">>}],
+ children =
+ [#xmlel{name = <<"body">>, attrs = [],
+ children =
+ [{xmlcdata, Body}]}]}).
get_admin_jids() ->
- case ejabberd_config:get_local_option(watchdog_admins) of
- JIDs when is_list(JIDs) ->
- lists:flatmap(
- fun(S) ->
- case jlib:string_to_jid(S) of
- error -> [];
- JID -> [jlib:jid_tolower(JID)]
- end
- end, JIDs);
- _ ->
- []
- end.
+ ejabberd_config:get_local_option(
+ watchdog_admins,
+ fun(JIDs) ->
+ [jlib:jid_tolower(
+ jlib:string_to_jid(
+ iolist_to_binary(S))) || S <- JIDs]
+ end, []).
detailed_info(Pid) ->
case process_info(Pid, dictionary) of
- {dictionary, Dict} ->
- case lists:keysearch('$ancestors', 1, Dict) of
- {value, {'$ancestors', [Sup | _]}} ->
- case Sup of
- ejabberd_c2s_sup ->
- c2s_info(Pid);
- ejabberd_s2s_out_sup ->
- s2s_out_info(Pid);
- ejabberd_service_sup ->
- service_info(Pid);
- _ ->
- detailed_info1(Pid)
- end;
- _ ->
- detailed_info1(Pid)
- end;
- _ ->
- detailed_info1(Pid)
+ {dictionary, Dict} ->
+ case lists:keysearch('$ancestors', 1, Dict) of
+ {value, {'$ancestors', [Sup | _]}} ->
+ case Sup of
+ ejabberd_c2s_sup -> c2s_info(Pid);
+ ejabberd_s2s_out_sup -> s2s_out_info(Pid);
+ ejabberd_service_sup -> service_info(Pid);
+ _ -> detailed_info1(Pid)
+ end;
+ _ -> detailed_info1(Pid)
+ end;
+ _ -> detailed_info1(Pid)
end.
detailed_info1(Pid) ->
- io_lib:format(
- "~p", [[process_info(Pid, current_function),
- process_info(Pid, initial_call),
- process_info(Pid, message_queue_len),
- process_info(Pid, links),
- process_info(Pid, dictionary),
- process_info(Pid, heap_size),
- process_info(Pid, stack_size)
- ]]).
+ io_lib:format("~p",
+ [[process_info(Pid, current_function),
+ process_info(Pid, initial_call),
+ process_info(Pid, message_queue_len),
+ process_info(Pid, links), process_info(Pid, dictionary),
+ process_info(Pid, heap_size),
+ process_info(Pid, stack_size)]]).
c2s_info(Pid) ->
- ["Process type: c2s",
- check_send_queue(Pid),
- "\n",
+ [<<"Process type: c2s">>, check_send_queue(Pid),
+ <<"\n">>,
io_lib:format("Command to kill this process: kill ~s ~w",
- [atom_to_list(node()), Pid])].
+ [iolist_to_binary(atom_to_list(node())), Pid])].
s2s_out_info(Pid) ->
- FromTo = mnesia:dirty_select(
- s2s, [{{s2s, '$1', Pid, '_'}, [], ['$1']}]),
- ["Process type: s2s_out",
+ FromTo = mnesia:dirty_select(s2s,
+ [{{s2s, '$1', Pid, '_'}, [], ['$1']}]),
+ [<<"Process type: s2s_out">>,
case FromTo of
- [{From, To}] ->
- "\n" ++ io_lib:format("S2S connection: from ~s to ~s",
- [From, To]);
- _ ->
- ""
+ [{From, To}] ->
+ <<"\n",
+ (io_lib:format("S2S connection: from ~s to ~s",
+ [From, To]))/binary>>;
+ _ -> <<"">>
end,
- check_send_queue(Pid),
- "\n",
+ check_send_queue(Pid), <<"\n">>,
io_lib:format("Command to kill this process: kill ~s ~w",
- [atom_to_list(node()), Pid])].
+ [iolist_to_binary(atom_to_list(node())), Pid])].
service_info(Pid) ->
- Routes = mnesia:dirty_select(
- route, [{{route, '$1', Pid, '_'}, [], ['$1']}]),
- ["Process type: s2s_out",
+ Routes = mnesia:dirty_select(route,
+ [{{route, '$1', Pid, '_'}, [], ['$1']}]),
+ [<<"Process type: s2s_out">>,
case Routes of
- [Route] ->
- "\nServiced domain: " ++ Route;
- _ ->
- ""
+ [Route] -> <<"\nServiced domain: ", Route/binary>>;
+ _ -> <<"">>
end,
- check_send_queue(Pid),
- "\n",
+ check_send_queue(Pid), <<"\n">>,
io_lib:format("Command to kill this process: kill ~s ~w",
- [atom_to_list(node()), Pid])].
+ [iolist_to_binary(atom_to_list(node())), Pid])].
check_send_queue(Pid) ->
case {process_info(Pid, current_function),
- process_info(Pid, message_queue_len)} of
- {{current_function, MFA}, {message_queue_len, MLen}} ->
- if
- MLen > 100 ->
- case MFA of
- {prim_inet, send, 2} ->
- "\nPossible reason: the process is blocked "
- "trying to send data over its TCP connection.";
- {M, F, A} ->
- ["\nPossible reason: the process can't process "
- "messages faster than they arrive. ",
- io_lib:format("Current function is ~w:~w/~w",
- [M, F, A])
- ]
- end;
- true ->
- ""
- end;
- _ ->
- ""
+ process_info(Pid, message_queue_len)}
+ of
+ {{current_function, MFA}, {message_queue_len, MLen}} ->
+ if MLen > 100 ->
+ case MFA of
+ {prim_inet, send, 2} ->
+ <<"\nPossible reason: the process is blocked "
+ "trying to send data over its TCP connection.">>;
+ {M, F, A} ->
+ [<<"\nPossible reason: the process can't "
+ "process messages faster than they arrive. ">>,
+ io_lib:format("Current function is ~w:~w/~w",
+ [M, F, A])]
+ end;
+ true -> <<"">>
+ end;
+ _ -> <<"">>
end.
process_command1(From, To, Body) ->
- process_command2(string:tokens(Body, " "), From, To).
+ process_command2(str:tokens(Body, <<" ">>), From, To).
-process_command2(["kill", SNode, SPid], From, To) ->
- Node = list_to_atom(SNode),
+process_command2([<<"kill">>, SNode, SPid], From, To) ->
+ Node = jlib:binary_to_atom(SNode),
remote_command(Node, [kill, SPid], From, To);
-process_command2(["showlh", SNode], From, To) ->
- Node = list_to_atom(SNode),
+process_command2([<<"showlh">>, SNode], From, To) ->
+ Node = jlib:binary_to_atom(SNode),
remote_command(Node, [showlh], From, To);
-process_command2(["setlh", SNode, NewValueString], From, To) ->
- Node = list_to_atom(SNode),
- NewValue = list_to_integer(NewValueString),
+process_command2([<<"setlh">>, SNode, NewValueString],
+ From, To) ->
+ Node = jlib:binary_to_atom(SNode),
+ NewValue = jlib:binary_to_integer(NewValueString),
remote_command(Node, [setlh, NewValue], From, To);
-process_command2(["help"], From, To) ->
+process_command2([<<"help">>], From, To) ->
send_message(To, From, help());
process_command2(_, From, To) ->
send_message(To, From, help()).
-
help() ->
- "Commands:\n"
- " kill <node> <pid>\n"
- " showlh <node>\n"
- " setlh <node> <integer>".
-
+ <<"Commands:\n kill <node> <pid>\n showlh "
+ "<node>\n setlh <node> <integer>">>.
remote_command(Node, Args, From, To) ->
- Message =
- case rpc:call(Node, ?MODULE, process_remote_command, [Args]) of
- {badrpc, Reason} ->
- io_lib:format("Command failed:~n~p", [Reason]);
- Result ->
- Result
- end,
- send_message(To, From, Message).
+ Message = case rpc:call(Node, ?MODULE,
+ process_remote_command, [Args])
+ of
+ {badrpc, Reason} ->
+ io_lib:format("Command failed:~n~p", [Reason]);
+ Result -> Result
+ end,
+ send_message(To, From, iolist_to_binary(Message)).
process_remote_command([kill, SPid]) ->
- exit(list_to_pid(SPid), kill),
- "ok";
+ exit(list_to_pid(SPid), kill), <<"ok">>;
process_remote_command([showlh]) ->
- Res = gen_server:call(ejabberd_system_monitor, {get, large_heap}),
+ Res = gen_server:call(ejabberd_system_monitor,
+ {get, large_heap}),
io_lib:format("Current large heap: ~p", [Res]);
process_remote_command([setlh, NewValue]) ->
- {lh_changed, OldLH, NewLH} = gen_server:call(ejabberd_system_monitor, {set, large_heap, NewValue}),
- io_lib:format("Result of set large heap: ~p --> ~p", [OldLH, NewLH]);
-process_remote_command(_) ->
- throw(unknown_command).
-
+ {lh_changed, OldLH, NewLH} =
+ gen_server:call(ejabberd_system_monitor,
+ {set, large_heap, NewValue}),
+ io_lib:format("Result of set large heap: ~p --> ~p",
+ [OldLH, NewLH]);
+process_remote_command(_) -> throw(unknown_command).
diff --git a/src/ejabberd_tmp_sup.erl b/src/ejabberd_tmp_sup.erl
index 04855ce48..c3d2a186e 100644
--- a/src/ejabberd_tmp_sup.erl
+++ b/src/ejabberd_tmp_sup.erl
@@ -25,6 +25,7 @@
%%%----------------------------------------------------------------------
-module(ejabberd_tmp_sup).
+
-author('alexey@process-one.net').
-export([start_link/2, init/1]).
@@ -32,8 +33,8 @@
start_link(Name, Module) ->
supervisor:start_link({local, Name}, ?MODULE, Module).
-
init(Module) ->
- {ok, {{simple_one_for_one, 10, 1},
- [{undefined, {Module, start_link, []},
- temporary, brutal_kill, worker, [Module]}]}}.
+ {ok,
+ {{simple_one_for_one, 10, 1},
+ [{undefined, {Module, start_link, []}, temporary,
+ brutal_kill, worker, [Module]}]}}.
diff --git a/src/ejabberd_update.erl b/src/ejabberd_update.erl
index 43dcf31a0..6a7f8bc9a 100644
--- a/src/ejabberd_update.erl
+++ b/src/ejabberd_update.erl
@@ -71,12 +71,7 @@ update(ModulesToUpdate) ->
%% But OTP R14B04 and newer provide release_handler_1:eval_script/5
%% Dialyzer reports a call to missing function; don't worry.
eval_script(Script, Apps, LibDirs) ->
- case lists:member({eval_script, 5}, release_handler_1:module_info(exports)) of
- true ->
- release_handler_1:eval_script(Script, Apps, LibDirs, [], []);
- false ->
- release_handler_1:eval_script(Script, Apps, LibDirs)
- end.
+ release_handler_1:eval_script(Script, Apps, LibDirs, [], []).
%% Get information about the modified modules
update_info() ->
diff --git a/src/ejabberd_zlib/Makefile.in b/src/ejabberd_zlib/Makefile.in
index 9b8ac7658..b572c1169 100644
--- a/src/ejabberd_zlib/Makefile.in
+++ b/src/ejabberd_zlib/Makefile.in
@@ -26,7 +26,7 @@ EFLAGS += -pz ..
# make debug=true to compile Erlang module with debug informations.
ifdef debug
- EFLAGS+=+debug_info +export_all
+ EFLAGS+=+debug_info
endif
ERLSHLIBS = ../ejabberd_zlib_drv.so
diff --git a/src/ejabberd_zlib/ejabberd_zlib.erl b/src/ejabberd_zlib/ejabberd_zlib.erl
index e894a5c33..3dee8d687 100644
--- a/src/ejabberd_zlib/ejabberd_zlib.erl
+++ b/src/ejabberd_zlib/ejabberd_zlib.erl
@@ -25,169 +25,184 @@
%%%----------------------------------------------------------------------
-module(ejabberd_zlib).
+
-author('alexey@process-one.net').
-behaviour(gen_server).
--export([start/0, start_link/0,
- enable_zlib/2, disable_zlib/1,
- send/2,
- recv/2, recv/3, recv_data/2,
- setopts/2,
- sockname/1, peername/1,
- get_sockmod/1,
- controlling_process/2,
- close/1]).
+-export([start/0, start_link/0, enable_zlib/2,
+ disable_zlib/1, send/2, recv/2, recv/3, recv_data/2,
+ setopts/2, sockname/1, peername/1, get_sockmod/1,
+ controlling_process/2, close/1]).
%% Internal exports, call-back functions.
--export([init/1,
- handle_call/3,
- handle_cast/2,
- handle_info/2,
- code_change/3,
- terminate/2]).
+-export([init/1, handle_call/3, handle_cast/2,
+ handle_info/2, code_change/3, terminate/2]).
-define(DEFLATE, 1).
+
-define(INFLATE, 2).
--record(zlibsock, {sockmod, socket, zlibport}).
+-record(zlibsock, {sockmod :: atom(),
+ socket :: inet:socket(),
+ zlibport :: port()}).
+
+-type zlib_socket() :: #zlibsock{}.
+
+-export_type([zlib_socket/0]).
start() ->
gen_server:start({local, ?MODULE}, ?MODULE, [], []).
start_link() ->
- gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
+ gen_server:start_link({local, ?MODULE}, ?MODULE, [],
+ []).
init([]) ->
- case erl_ddll:load_driver(ejabberd:get_so_path(), ejabberd_zlib_drv) of
- ok -> ok;
- {error, already_loaded} -> ok
+ case erl_ddll:load_driver(ejabberd:get_so_path(),
+ ejabberd_zlib_drv)
+ of
+ ok -> ok;
+ {error, already_loaded} -> ok
end,
- Port = open_port({spawn, "ejabberd_zlib_drv"}, [binary]),
+ Port = open_port({spawn, "ejabberd_zlib_drv"},
+ [binary]),
{ok, Port}.
-
%%% --------------------------------------------------------
%%% The call-back functions.
%%% --------------------------------------------------------
-handle_call(_, _, State) ->
- {noreply, State}.
+handle_call(_, _, State) -> {noreply, State}.
-handle_cast(_, State) ->
- {noreply, State}.
+handle_cast(_, State) -> {noreply, State}.
handle_info({'EXIT', Port, Reason}, Port) ->
{stop, {port_died, Reason}, Port};
-
handle_info({'EXIT', _Pid, _Reason}, Port) ->
{noreply, Port};
+handle_info(_, State) -> {noreply, State}.
-handle_info(_, State) ->
- {noreply, State}.
-
-code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
+code_change(_OldVsn, State, _Extra) -> {ok, State}.
-terminate(_Reason, Port) ->
- Port ! {self, close},
- ok.
+terminate(_Reason, Port) -> Port ! {self, close}, ok.
+-spec enable_zlib(atom(), inet:socket()) -> {ok, zlib_socket()}.
enable_zlib(SockMod, Socket) ->
- case erl_ddll:load_driver(ejabberd:get_so_path(), ejabberd_zlib_drv) of
- ok -> ok;
- {error, already_loaded} -> ok
+ case erl_ddll:load_driver(ejabberd:get_so_path(),
+ ejabberd_zlib_drv)
+ of
+ ok -> ok;
+ {error, already_loaded} -> ok
end,
- Port = open_port({spawn, "ejabberd_zlib_drv"}, [binary]),
- {ok, #zlibsock{sockmod = SockMod, socket = Socket, zlibport = Port}}.
+ Port = open_port({spawn, "ejabberd_zlib_drv"},
+ [binary]),
+ {ok,
+ #zlibsock{sockmod = SockMod, socket = Socket,
+ zlibport = Port}}.
+
+-spec disable_zlib(zlib_socket()) -> {atom(), inet:socket()}.
+
+disable_zlib(#zlibsock{sockmod = SockMod,
+ socket = Socket, zlibport = Port}) ->
+ port_close(Port), {SockMod, Socket}.
-disable_zlib(#zlibsock{sockmod = SockMod, socket = Socket, zlibport = Port}) ->
- port_close(Port),
- {SockMod, Socket}.
+-spec recv(zlib_socket(), number()) -> {ok, binary()} | {error, any()}.
-recv(Socket, Length) ->
- recv(Socket, Length, infinity).
-recv(#zlibsock{sockmod = SockMod, socket = Socket} = ZlibSock,
+recv(Socket, Length) -> recv(Socket, Length, infinity).
+
+-spec recv(zlib_socket(), number(), timeout()) -> {ok, binary()} |
+ {error, any()}.
+
+recv(#zlibsock{sockmod = SockMod, socket = Socket} =
+ ZlibSock,
Length, Timeout) ->
case SockMod:recv(Socket, Length, Timeout) of
- {ok, Packet} ->
- recv_data(ZlibSock, Packet);
- {error, _Reason} = Error ->
- Error
+ {ok, Packet} -> recv_data(ZlibSock, Packet);
+ {error, _Reason} = Error -> Error
end.
-recv_data(#zlibsock{sockmod = SockMod, socket = Socket} = ZlibSock, Packet) ->
+-spec recv_data(zlib_socket(), iodata()) -> {ok, binary()} | {error, any()}.
+
+recv_data(#zlibsock{sockmod = SockMod,
+ socket = Socket} =
+ ZlibSock,
+ Packet) ->
case SockMod of
- gen_tcp ->
- recv_data2(ZlibSock, Packet);
- _ ->
- case SockMod:recv_data(Socket, Packet) of
- {ok, Packet2} ->
- recv_data2(ZlibSock, Packet2);
- Error ->
- Error
- end
+ gen_tcp -> recv_data2(ZlibSock, Packet);
+ _ ->
+ case SockMod:recv_data(Socket, Packet) of
+ {ok, Packet2} -> recv_data2(ZlibSock, Packet2);
+ Error -> Error
+ end
end.
recv_data2(ZlibSock, Packet) ->
case catch recv_data1(ZlibSock, Packet) of
- {'EXIT', Reason} ->
- {error, Reason};
- Res ->
- Res
+ {'EXIT', Reason} -> {error, Reason};
+ Res -> Res
end.
-recv_data1(#zlibsock{zlibport = Port} = _ZlibSock, Packet) ->
+recv_data1(#zlibsock{zlibport = Port} = _ZlibSock,
+ Packet) ->
case port_control(Port, ?INFLATE, Packet) of
- <<0, In/binary>> ->
- {ok, In};
- <<1, Error/binary>> ->
- {error, binary_to_list(Error)}
+ <<0, In/binary>> -> {ok, In};
+ <<1, Error/binary>> -> {error, (Error)}
end.
-send(#zlibsock{sockmod = SockMod, socket = Socket, zlibport = Port},
+-spec send(zlib_socket(), iodata()) -> ok | {error, binary() | inet:posix()}.
+
+send(#zlibsock{sockmod = SockMod, socket = Socket,
+ zlibport = Port},
Packet) ->
case port_control(Port, ?DEFLATE, Packet) of
- <<0, Out/binary>> ->
- SockMod:send(Socket, Out);
- <<1, Error/binary>> ->
- {error, binary_to_list(Error)}
+ <<0, Out/binary>> -> SockMod:send(Socket, Out);
+ <<1, Error/binary>> -> {error, (Error)}
end.
+-spec setopts(zlib_socket(), list()) -> ok | {error, inet:posix()}.
-setopts(#zlibsock{sockmod = SockMod, socket = Socket}, Opts) ->
+setopts(#zlibsock{sockmod = SockMod, socket = Socket},
+ Opts) ->
case SockMod of
- gen_tcp ->
- inet:setopts(Socket, Opts);
- _ ->
- SockMod:setopts(Socket, Opts)
+ gen_tcp -> inet:setopts(Socket, Opts);
+ _ -> SockMod:setopts(Socket, Opts)
end.
-sockname(#zlibsock{sockmod = SockMod, socket = Socket}) ->
+-spec sockname(zlib_socket()) -> {ok, {inet:ip_address(), inet:port_number()}} |
+ {error, inet:posix()}.
+
+sockname(#zlibsock{sockmod = SockMod,
+ socket = Socket}) ->
case SockMod of
- gen_tcp ->
- inet:sockname(Socket);
- _ ->
- SockMod:sockname(Socket)
+ gen_tcp -> inet:sockname(Socket);
+ _ -> SockMod:sockname(Socket)
end.
-get_sockmod(#zlibsock{sockmod = SockMod}) ->
- SockMod.
+-spec get_sockmod(zlib_socket()) -> atom().
-peername(#zlibsock{sockmod = SockMod, socket = Socket}) ->
+get_sockmod(#zlibsock{sockmod = SockMod}) -> SockMod.
+
+-spec peername(zlib_socket()) -> {ok, {inet:ip_address(), inet:port_number()}} |
+ {error, inet:posix()}.
+
+peername(#zlibsock{sockmod = SockMod,
+ socket = Socket}) ->
case SockMod of
- gen_tcp ->
- inet:peername(Socket);
- _ ->
- SockMod:peername(Socket)
+ gen_tcp -> inet:peername(Socket);
+ _ -> SockMod:peername(Socket)
end.
-controlling_process(#zlibsock{sockmod = SockMod, socket = Socket}, Pid) ->
- SockMod:controlling_process(Socket, Pid).
+-spec controlling_process(zlib_socket(), pid()) -> ok | {error, atom()}.
-close(#zlibsock{sockmod = SockMod, socket = Socket, zlibport = Port}) ->
- SockMod:close(Socket),
- port_close(Port).
+controlling_process(#zlibsock{sockmod = SockMod,
+ socket = Socket},
+ Pid) ->
+ SockMod:controlling_process(Socket, Pid).
+-spec close(zlib_socket()) -> true.
+close(#zlibsock{sockmod = SockMod, socket = Socket,
+ zlibport = Port}) ->
+ SockMod:close(Socket), port_close(Port).
diff --git a/src/ejabberdctl.template b/src/ejabberdctl.template
index 461ec1a6f..b6dfd4fc4 100644
--- a/src/ejabberdctl.template
+++ b/src/ejabberdctl.template
@@ -53,6 +53,7 @@ if [ "$EJABBERD_DOC_PATH" = "" ] ; then
fi
if [ "$ERLANG_NODE_ARG" != "" ] ; then
ERLANG_NODE=$ERLANG_NODE_ARG
+ NODE=${ERLANG_NODE%@*}
fi
# check the proper system user is used
diff --git a/src/ejd2odbc.erl b/src/ejd2odbc.erl
index 68ebdfd5f..b96503dbe 100644
--- a/src/ejd2odbc.erl
+++ b/src/ejd2odbc.erl
@@ -25,59 +25,12 @@
%%%----------------------------------------------------------------------
-module(ejd2odbc).
--author('alexey@process-one.net').
-
-%% External exports
--export([export_passwd/2,
- export_roster/2,
- export_offline/2,
- export_last/2,
- export_vcard/2,
- export_vcard_search/2,
- export_vcard_xupdate/2,
- export_private_storage/2,
- export_privacy/2,
- export_motd/2,
- export_motd_users/2,
- export_irc_custom/2,
- export_sr_group/2,
- export_sr_user/2,
- export_muc_room/2,
- export_muc_registered/2]).
--include("ejabberd.hrl").
--include("jlib.hrl").
--include("mod_roster.hrl").
--include("mod_privacy.hrl").
+-author('alexey@process-one.net').
--record(offline_msg, {us, timestamp, expire, from, to, packet}).
--record(last_activity, {us, timestamp, status}).
--record(vcard, {us, vcard}).
--record(vcard_xupdate, {us, hash}).
--record(vcard_search, {us,
- user, luser,
- fn, lfn,
- family, lfamily,
- given, lgiven,
- middle, lmiddle,
- nickname, lnickname,
- bday, lbday,
- ctry, lctry,
- locality, llocality,
- email, lemail,
- orgname, lorgname,
- orgunit, lorgunit
- }).
--record(private_storage, {usns, xml}).
--record(irc_custom, {us_host, data}).
--record(muc_room, {name_host, opts}).
--record(muc_registered, {us_host, nick}).
--record(sr_group, {group_host, opts}).
--record(sr_user, {us, group_host}).
--record(motd, {server, packet}).
--record(motd_users, {us, dummy = []}).
+-export([export/2, export/3]).
--define(MAX_RECORDS_PER_TRANSACTION, 1000).
+-define(MAX_RECORDS_PER_TRANSACTION, 100).
%%%----------------------------------------------------------------------
%%% API
@@ -89,481 +42,98 @@
%%% - Output can be either odbc to export to the configured relational
%%% database or "Filename" to export to text file.
-export_passwd(Server, Output) ->
- export_common(
- Server, passwd, Output,
- fun(_Host, {passwd, {LUser, LServer}, {scram, _, _, _, _}} = _R) ->
- ?INFO_MSG("You are trying to export the authentication "
- "information of the account ~s@~s, but his password "
- "is stored as SCRAM, and ejabberd ODBC authentication "
- "doesn't support SCRAM.", [LUser, LServer]),
- [];
- (Host, {passwd, {LUser, LServer}, Password} = _R)
- when LServer == Host ->
- Username = ejabberd_odbc:escape(LUser),
- Pass = ejabberd_odbc:escape(Password),
- ["delete from users where username='", Username ,"';"
- "insert into users(username, password) "
- "values ('", Username, "', '", Pass, "');"];
- (_Host, _R) ->
- []
- end).
-
-export_roster(Server, Output) ->
- export_common(
- Server, roster, Output,
- fun(Host, #roster{usj = {LUser, LServer, LJID}} = R)
- when LServer == Host ->
- Username = ejabberd_odbc:escape(LUser),
- SJID = ejabberd_odbc:escape(jlib:jid_to_string(LJID)),
- ItemVals = record_to_string(R),
- ItemGroups = groups_to_string(R),
- ["delete from rosterusers "
- " where username='", Username, "' "
- " and jid='", SJID, "';"
- "insert into rosterusers("
- " username, jid, nick, "
- " subscription, ask, askmessage, "
- " server, subscribe, type) "
- " values ", ItemVals, ";"
- "delete from rostergroups "
- " where username='", Username, "' "
- " and jid='", SJID, "';",
- [["insert into rostergroups("
- " username, jid, grp) "
- " values ", ItemGroup, ";"] ||
- ItemGroup <- ItemGroups]];
- (_Host, _R) ->
- []
- end).
-
-export_offline(Server, Output) ->
- export_common(
- Server, offline_msg, Output,
- fun(Host, #offline_msg{us = {LUser, LServer},
- timestamp = TimeStamp,
- from = From,
- to = To,
- packet = Packet})
- when LServer == Host ->
- Username = ejabberd_odbc:escape(LUser),
- {xmlelement, Name, Attrs, Els} = Packet,
- Attrs2 = jlib:replace_from_to_attrs(
- jlib:jid_to_string(From),
- jlib:jid_to_string(To),
- Attrs),
- NewPacket = {xmlelement, Name, Attrs2,
- Els ++
- [jlib:timestamp_to_xml(
- calendar:now_to_universal_time(TimeStamp),
- utc,
- jlib:make_jid("", Server, ""),
- "Offline Storage"),
- %% TODO: Delete the next three lines once XEP-0091 is Obsolete
- jlib:timestamp_to_xml(
- calendar:now_to_universal_time(
- TimeStamp))]},
- XML =
- ejabberd_odbc:escape(
- xml:element_to_binary(NewPacket)),
- ["insert into spool(username, xml) "
- "values ('", Username, "', '",
- XML,
- "');"];
- (_Host, _R) ->
- []
- end).
-
-export_last(Server, Output) ->
- export_common(
- Server, last_activity, Output,
- fun(Host, #last_activity{us = {LUser, LServer},
- timestamp = TimeStamp,
- status = Status})
- when LServer == Host ->
- Username = ejabberd_odbc:escape(LUser),
- Seconds = ejabberd_odbc:escape(integer_to_list(TimeStamp)),
- State = ejabberd_odbc:escape(Status),
- ["delete from last where username='", Username, "';"
- "insert into last(username, seconds, state) "
- "values ('", Username, "', '", Seconds, "', '", State, "');"];
- (_Host, _R) ->
- []
- end).
-
-export_vcard(Server, Output) ->
- export_common(
- Server, vcard, Output,
- fun(Host, #vcard{us = {LUser, LServer},
- vcard = VCARD})
- when LServer == Host ->
- Username = ejabberd_odbc:escape(LUser),
- SVCARD = ejabberd_odbc:escape(
- xml:element_to_binary(VCARD)),
- ["delete from vcard where username='", Username, "';"
- "insert into vcard(username, vcard) "
- "values ('", Username, "', '", SVCARD, "');"];
- (_Host, _R) ->
- []
- end).
-
-export_vcard_search(Server, Output) ->
- export_common(
- Server, vcard_search, Output,
- fun(Host, #vcard_search{user = {User, LServer},
- luser = LUser,
- fn = FN, lfn = LFN,
- family = Family, lfamily = LFamily,
- given = Given, lgiven = LGiven,
- middle = Middle, lmiddle = LMiddle,
- nickname = Nickname, lnickname = LNickname,
- bday = BDay, lbday = LBDay,
- ctry = CTRY, lctry = LCTRY,
- locality = Locality, llocality = LLocality,
- email = EMail, lemail = LEMail,
- orgname = OrgName, lorgname = LOrgName,
- orgunit = OrgUnit, lorgunit = LOrgUnit
- })
- when LServer == Host ->
- Username = ejabberd_odbc:escape(User),
- LUsername = ejabberd_odbc:escape(LUser),
-
- SFN = ejabberd_odbc:escape(FN),
- SLFN = ejabberd_odbc:escape(LFN),
- SFamily = ejabberd_odbc:escape(Family),
- SLFamily = ejabberd_odbc:escape(LFamily),
- SGiven = ejabberd_odbc:escape(Given),
- SLGiven = ejabberd_odbc:escape(LGiven),
- SMiddle = ejabberd_odbc:escape(Middle),
- SLMiddle = ejabberd_odbc:escape(LMiddle),
- SNickname = ejabberd_odbc:escape(Nickname),
- SLNickname = ejabberd_odbc:escape(LNickname),
- SBDay = ejabberd_odbc:escape(BDay),
- SLBDay = ejabberd_odbc:escape(LBDay),
- SCTRY = ejabberd_odbc:escape(CTRY),
- SLCTRY = ejabberd_odbc:escape(LCTRY),
- SLocality = ejabberd_odbc:escape(Locality),
- SLLocality = ejabberd_odbc:escape(LLocality),
- SEMail = ejabberd_odbc:escape(EMail),
- SLEMail = ejabberd_odbc:escape(LEMail),
- SOrgName = ejabberd_odbc:escape(OrgName),
- SLOrgName = ejabberd_odbc:escape(LOrgName),
- SOrgUnit = ejabberd_odbc:escape(OrgUnit),
- SLOrgUnit = ejabberd_odbc:escape(LOrgUnit),
-
- ["delete from vcard_search where lusername='", LUsername, "';"
- "insert into vcard_search("
- " username, lusername, fn, lfn, family, lfamily,"
- " given, lgiven, middle, lmiddle, nickname, lnickname,"
- " bday, lbday, ctry, lctry, locality, llocality,"
- " email, lemail, orgname, lorgname, orgunit, lorgunit)"
- "values (",
- " '", Username, "', '", LUsername, "',"
- " '", SFN, "', '", SLFN, "',"
- " '", SFamily, "', '", SLFamily, "',"
- " '", SGiven, "', '", SLGiven, "',"
- " '", SMiddle, "', '", SLMiddle, "',"
- " '", SNickname, "', '", SLNickname, "',"
- " '", SBDay, "', '", SLBDay, "',"
- " '", SCTRY, "', '", SLCTRY, "',"
- " '", SLocality, "', '", SLLocality, "',"
- " '", SEMail, "', '", SLEMail, "',"
- " '", SOrgName, "', '", SLOrgName, "',"
- " '", SOrgUnit, "', '", SLOrgUnit, "');"];
- (_Host, _R) ->
- []
- end).
-
-export_vcard_xupdate(Server, Output) ->
- export_common(
- Server, vcard_xupdate, Output,
- fun(Host, #vcard_xupdate{us = {LUser, LServer}, hash = Hash})
- when LServer == Host ->
- Username = ejabberd_odbc:escape(LUser),
- SHash = ejabberd_odbc:escape(Hash),
- ["delete from vcard_xupdate where username='", Username, "';"
- "insert into vcard_xupdate(username, hash) "
- "values ('", Username, "', '", SHash, "');"];
- (_Host, _R) ->
- []
- end).
-
-export_private_storage(Server, Output) ->
- export_common(
- Server, private_storage, Output,
- fun(Host, #private_storage{usns = {LUser, LServer, XMLNS},
- xml = Data})
- when LServer == Host ->
- Username = ejabberd_odbc:escape(LUser),
- LXMLNS = ejabberd_odbc:escape(XMLNS),
- SData = ejabberd_odbc:escape(
- xml:element_to_binary(Data)),
- odbc_queries:set_private_data_sql(Username, LXMLNS, SData);
- (_Host, _R) ->
- []
- end).
-
-export_muc_room(Server, Output) ->
- export_common(
- Server, muc_room, Output,
- fun(Host, #muc_room{name_host = {Name, RoomHost}, opts = Opts}) ->
- case lists:suffix(Host, RoomHost) of
- true ->
- SName = ejabberd_odbc:escape(Name),
- SRoomHost = ejabberd_odbc:escape(RoomHost),
- SOpts = ejabberd_odbc:encode_term(Opts),
- ["delete from muc_room where name='", SName,
- "' and host='", SRoomHost, "';",
- "insert into muc_room(name, host, opts) values (",
- "'", SName, "', '", SRoomHost, "', '", SOpts, "');"];
- false ->
- []
- end
- end).
-
-export_muc_registered(Server, Output) ->
- export_common(
- Server, muc_registered, Output,
- fun(Host, #muc_registered{us_host = {{U, S}, RoomHost}, nick = Nick}) ->
- case lists:suffix(Host, RoomHost) of
- true ->
- SJID = ejabberd_odbc:escape(
- jlib:jid_to_string(
- jlib:make_jid(U, S, ""))),
- SNick = ejabberd_odbc:escape(Nick),
- SRoomHost = ejabberd_odbc:escape(RoomHost),
- ["delete from muc_registered where jid='", SJID,
- "' and host='", SRoomHost, "';"
- "insert into muc_registered(jid, host, nick) values ("
- "'", SJID, "', '", SRoomHost, "', '", SNick, "');"];
- false ->
- []
- end
- end).
-
-export_irc_custom(Server, Output) ->
- export_common(
- Server, irc_custom, Output,
- fun(Host, #irc_custom{us_host = {{U, S}, IRCHost}, data = Data}) ->
- case lists:suffix(Host, IRCHost) of
- true ->
- SJID = ejabberd_odbc:escape(
- jlib:jid_to_string(
- jlib:make_jid(U, S, ""))),
- SIRCHost = ejabberd_odbc:escape(IRCHost),
- SData = ejabberd_odbc:encode_term(Data),
- ["delete from irc_custom where jid='", SJID,
- "' and host='", SIRCHost, "';"
- "insert into irc_custom(jid, host, data) values ("
- "'", SJID, "', '", SIRCHost, "', '", SData, "');"];
- false ->
- []
- end
- end).
-
-export_privacy(Server, Output) ->
- case ejabberd_odbc:sql_query(
- jlib:nameprep(Server),
- ["select id from privacy_list order by id desc limit 1;"]) of
- {selected, ["id"], [{I}]} ->
- put(id, list_to_integer(I));
- _ ->
- put(id, 0)
- end,
- export_common(
- Server, privacy, Output,
- fun(Host, #privacy{us = {LUser, LServer},
- lists = Lists,
- default = Default}) when LServer == Host ->
- Username = ejabberd_odbc:escape(LUser),
- if Default /= none ->
- SDefault = ejabberd_odbc:escape(Default),
- ["delete from privacy_default_list where ",
- "username='", Username, "';",
- "insert into privacy_default_list(username, name) ",
- "values ('", Username, "', '", SDefault, "');"];
- true ->
- []
- end ++
- lists:flatmap(
- fun({Name, List}) ->
- SName = ejabberd_odbc:escape(Name),
- RItems = lists:map(
- fun mod_privacy:item_to_raw/1,
- List),
- ID = integer_to_list(get_id()),
- ["delete from privacy_list "
- "where username='", Username, "' and name='", SName, "';"
- "insert into privacy_list(username, name, id) "
- "values ('", Username, "', '", SName, "', '", ID, "');",
- "delete from privacy_list_data where id='", ID, "';"
- |[["insert into privacy_list_data("
- "id, t, value, action, ord, match_all, match_iq, "
- "match_message, match_presence_in, "
- "match_presence_out) values ('", ID, "', '",
- string:join(Items, "', '"), "');"] || Items <- RItems]]
- end, Lists);
- (_Host, _R) ->
- []
- end).
-
-export_sr_group(Server, Output) ->
- export_common(
- Server, sr_group, Output,
- fun(Host, #sr_group{group_host = {Group, LServer}, opts = Opts})
- when LServer == Host ->
- SGroup = ejabberd_odbc:escape(Group),
- SOpts = ejabberd_odbc:encode_term(Opts),
- ["delete from sr_group where name='", Group, "';"
- "insert into sr_group(name, opts) values ('",
- SGroup, "', '", SOpts, "');"];
- (_Host, _R) ->
- []
- end).
-
-export_sr_user(Server, Output) ->
- export_common(
- Server, sr_user, Output,
- fun(Host, #sr_user{us = {U, S}, group_host = {Group, LServer}})
- when LServer == Host ->
- SGroup = ejabberd_odbc:escape(Group),
- SJID = ejabberd_odbc:escape(
- jlib:jid_to_string(
- jlib:jid_tolower(
- jlib:make_jid(U, S, "")))),
- ["delete from sr_user where jid='", SJID,
- "'and grp='", Group, "';"
- "insert into sr_user(jid, grp) values ('",
- SJID, "', '", SGroup, "');"];
- (_Host, _R) ->
- []
- end).
-
-export_motd(Server, Output) ->
- export_common(
- Server, motd, Output,
- fun(Host, #motd{server = LServer, packet = El})
- when LServer == Host ->
- ["delete from motd where username='';"
- "insert into motd(username, xml) values ('', '",
- ejabberd_odbc:escape(xml:element_to_binary(El)), "');"];
- (_Host, _R) ->
- []
- end).
-
-export_motd_users(Server, Output) ->
- export_common(
- Server, motd_users, Output,
- fun(Host, #motd_users{us = {LUser, LServer}})
- when LServer == Host, LUser /= "" ->
- Username = ejabberd_odbc:escape(LUser),
- ["delete from motd where username='", Username, "';"
- "insert into motd(username, xml) values ('",
- Username, "', '');"];
- (_Host, _R) ->
- []
- end).
+export(Server, Output) ->
+ LServer = jlib:nameprep(iolist_to_binary(Server)),
+ Modules = [ejabberd_auth,
+ mod_announce,
+ mod_caps,
+ mod_irc,
+ mod_last,
+ mod_muc,
+ mod_offline,
+ mod_privacy,
+ mod_private,
+ mod_roster,
+ mod_shared_roster,
+ mod_vcard,
+ mod_vcard_xupdate],
+ IO = prepare_output(Output),
+ lists:foreach(
+ fun(Module) ->
+ export(LServer, IO, Module)
+ end, Modules),
+ close_output(Output, IO).
+
+export(Server, Output, Module) ->
+ LServer = jlib:nameprep(iolist_to_binary(Server)),
+ IO = prepare_output(Output),
+ lists:foreach(
+ fun({Table, ConvertFun}) ->
+ export(LServer, Table, IO, ConvertFun)
+ end, Module:export(Server)),
+ close_output(Output, IO).
%%%----------------------------------------------------------------------
%%% Internal functions
%%%----------------------------------------------------------------------
-
-export_common(Server, Table, Output, ConvertFun) ->
- IO = case Output of
- odbc ->
- odbc;
- _ ->
- {ok, IODevice} = file:open(Output, [write, raw]),
- IODevice
- end,
- mnesia:transaction(
- fun() ->
- mnesia:read_lock_table(Table),
- LServer = jlib:nameprep(Server),
- {_N, SQLs} =
- mnesia:foldl(
- fun(R, {N, SQLs} = Acc) ->
- case ConvertFun(LServer, R) of
- [] ->
- Acc;
- SQL ->
- if
- N < ?MAX_RECORDS_PER_TRANSACTION - 1 ->
- {N + 1, [SQL | SQLs]};
- true ->
- %% Execute full SQL transaction
- output(LServer, IO,
- ["begin;",
- lists:reverse([SQL | SQLs]),
- "commit"]),
- {0, []}
- end
- end
- end, {0, []}, Table),
- %% Execute SQL transaction with remaining records
- output(LServer, IO,
- ["begin;",
- lists:reverse(SQLs),
- "commit"])
- end).
-
-output(LServer, IO, SQL) ->
- case IO of
- odbc ->
- catch ejabberd_odbc:sql_query(LServer, SQL);
- _ ->
- file:write(IO, [SQL, $;, $\n])
- end.
-
-record_to_string(#roster{usj = {User, _Server, JID},
- name = Name,
- subscription = Subscription,
- ask = Ask,
- askmessage = AskMessage}) ->
- Username = ejabberd_odbc:escape(User),
- SJID = ejabberd_odbc:escape(jlib:jid_to_string(JID)),
- Nick = ejabberd_odbc:escape(Name),
- SSubscription = case Subscription of
- both -> "B";
- to -> "T";
- from -> "F";
- none -> "N"
- end,
- SAsk = case Ask of
- subscribe -> "S";
- unsubscribe -> "U";
- both -> "B";
- out -> "O";
- in -> "I";
- none -> "N"
- end,
- SAskMessage =
- case catch ejabberd_odbc:escape(
- binary_to_list(list_to_binary([AskMessage]))) of
- {'EXIT', _Reason} ->
- [];
- SAM ->
- SAM
- end,
- ["("
- "'", Username, "',"
- "'", SJID, "',"
- "'", Nick, "',"
- "'", SSubscription, "',"
- "'", SAsk, "',"
- "'", SAskMessage, "',"
- "'N', '', 'item')"].
-
-groups_to_string(#roster{usj = {User, _Server, JID},
- groups = Groups}) ->
- Username = ejabberd_odbc:escape(User),
- SJID = ejabberd_odbc:escape(jlib:jid_to_string(JID)),
- [["("
- "'", Username, "',"
- "'", SJID, "',"
- "'", ejabberd_odbc:escape(Group), "')"] || Group <- Groups].
-
-get_id() ->
- ID = get(id),
- put(id, ID+1),
- ID+1.
+export(LServer, Table, IO, ConvertFun) ->
+ F = fun () ->
+ mnesia:read_lock_table(Table),
+ {_N, SQLs} =
+ mnesia:foldl(
+ fun(R, {N, SQLs} = Acc) ->
+ case ConvertFun(LServer, R) of
+ [] ->
+ Acc;
+ SQL ->
+ if N < (?MAX_RECORDS_PER_TRANSACTION) - 1 ->
+ {N + 1, [SQL | SQLs]};
+ true ->
+ output(LServer,
+ Table, IO,
+ flatten([SQL | SQLs])),
+ {0, []}
+ end
+ end
+ end,
+ {0, []}, Table),
+ output(LServer, Table, IO, flatten(SQLs))
+ end,
+ mnesia:transaction(F).
+
+output(_LServer, _Table, _IO, []) ->
+ ok;
+output(LServer, _Table, odbc, SQLs) ->
+ ejabberd_odbc:sql_transaction(LServer, SQLs);
+output(_LServer, Table, Fd, SQLs) ->
+ file:write(Fd, ["-- \n-- Mnesia table: ", atom_to_list(Table),
+ "\n--\n", SQLs]).
+
+prepare_output(FileName) when is_list(FileName); is_binary(FileName) ->
+ case file:open(FileName, [write, raw]) of
+ {ok, Fd} ->
+ Fd;
+ Err ->
+ exit(Err)
+ end;
+prepare_output(Output) ->
+ Output.
+
+close_output(FileName, Fd) when FileName /= Fd ->
+ file:close(Fd),
+ ok;
+close_output(_, _) ->
+ ok.
+
+flatten(SQLs) ->
+ flatten(SQLs, []).
+
+flatten([L|Ls], Acc) ->
+ flatten(Ls, flatten1(lists:reverse(L), Acc));
+flatten([], Acc) ->
+ Acc.
+
+flatten1([H|T], Acc) ->
+ flatten1(T, [[H, $\n]|Acc]);
+flatten1([], Acc) ->
+ Acc.
diff --git a/src/eldap/Makefile.in b/src/eldap/Makefile.in
index 8a0a0d768..a44bee595 100644
--- a/src/eldap/Makefile.in
+++ b/src/eldap/Makefile.in
@@ -6,7 +6,7 @@ CPPFLAGS = @CPPFLAGS@
LDFLAGS = @LDFLAGS@
LIBS = @LIBS@
-ASN_FLAGS = -bber_bin +optimize
+ASN_FLAGS = -bber_bin +optimize +binary_strings
ERLANG_CFLAGS = @ERLANG_CFLAGS@
ERLANG_LIBS = @ERLANG_LIBS@
@@ -17,7 +17,7 @@ EFLAGS += -pz ..
# make debug=true to compile Erlang module with debug informations.
ifdef debug
- EFLAGS+=+debug_info +export_all
+ EFLAGS+=+debug_info
endif
OUTDIR = ..
@@ -31,6 +31,8 @@ ELDAPv3.beam: ELDAPv3.erl
ELDAPv3.erl: ELDAPv3.asn
@ERLC@ $(ASN_FLAGS) -W $(EFLAGS) $<
+ @ERL@ -noinput +B -eval \
+ 'case file:read_file("ELDAPv3.erl") of {ok, Data} -> NewData = re:replace(Data, "\\?RT_BER:decode_octet_string", "eldap_utils:decode_octet_string", [global]), file:write_file("ELDAPv3.erl", NewData), halt(0); _Err -> halt(1) end'
eldap_filter_yecc.beam: eldap_filter_yecc.erl
diff --git a/src/eldap/eldap.erl b/src/eldap/eldap.erl
index e18d2e22a..4df7d00eb 100644
--- a/src/eldap/eldap.erl
+++ b/src/eldap/eldap.erl
@@ -7,6 +7,7 @@
%%%
%%% Copyright (C) 2000 Torbjorn Tornkvist, tnt@home.se
%%%
+%%%
%%% 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
@@ -21,7 +22,6 @@
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-
%%% Modified by Sean Hinde <shinde@iee.org> 7th Dec 2000
%%% Turned into gen_fsm, made non-blocking, added timers etc to support this.
%%% Now has the concept of a name (string() or atom()) per instance which allows
@@ -30,7 +30,6 @@
%%% Can be configured with start_link parameters or use a config file to get
%%% host to connect to, dn, password, log function etc.
-
%%% Modified by Alexey Shchepin <alexey@sevcom.net>
%%% Modified by Evgeniy Khramtsov <ekhramtsov@process-one.net>
@@ -55,7 +54,6 @@
%%% --------------------------------------------------------------------
-vc('$Id$ ').
-
%%%----------------------------------------------------------------------
%%% LDAP Client state machine.
%%% Possible states are:
@@ -72,68 +70,95 @@
%% External exports
-export([start_link/1, start_link/6]).
--export([baseObject/0,singleLevel/0,wholeSubtree/0,close/1,
- equalityMatch/2,greaterOrEqual/2,lessOrEqual/2,
- approxMatch/2,search/2,substrings/2,present/1,extensibleMatch/2,
- 'and'/1,'or'/1,'not'/1,modify/3, mod_add/2, mod_delete/2,
- mod_replace/2, add/3, delete/2, modify_dn/5, modify_passwd/3, bind/3]).
+-export([baseObject/0, singleLevel/0, wholeSubtree/0,
+ close/1, equalityMatch/2, greaterOrEqual/2,
+ lessOrEqual/2, approxMatch/2, search/2, substrings/2,
+ present/1, extensibleMatch/2, 'and'/1, 'or'/1, 'not'/1,
+ modify/3, mod_add/2, mod_delete/2, mod_replace/2, add/3,
+ delete/2, modify_dn/5, modify_passwd/3, bind/3]).
+
-export([get_status/1]).
%% gen_fsm callbacks
--export([init/1, connecting/2,
- connecting/3, wait_bind_response/3, active/3, active_bind/3, handle_event/3,
- handle_sync_event/4, handle_info/3, terminate/3, code_change/4]).
-
+-export([init/1, connecting/2, connecting/3,
+ wait_bind_response/3, active/3, active_bind/3,
+ handle_event/3, handle_sync_event/4, handle_info/3,
+ terminate/3, code_change/4]).
--import(lists,[concat/1]).
+-export_type([filter/0]).
-include("ELDAPv3.hrl").
+
-include("eldap.hrl").
-define(LDAP_VERSION, 3).
+
-define(RETRY_TIMEOUT, 500).
+
-define(BIND_TIMEOUT, 10000).
+
-define(CMD_TIMEOUT, 100000).
%% Used in gen_fsm sync calls.
--define(CALL_TIMEOUT, ?CMD_TIMEOUT + ?BIND_TIMEOUT + ?RETRY_TIMEOUT).
%% Used as a timeout for gen_tcp:send/2
+
+-define(CALL_TIMEOUT,
+ (?CMD_TIMEOUT) + (?BIND_TIMEOUT) + (?RETRY_TIMEOUT)).
+
-define(SEND_TIMEOUT, 30000).
+
-define(MAX_TRANSACTION_ID, 65535).
+
-define(MIN_TRANSACTION_ID, 0).
%% Grace period after "soft" LDAP bind errors:
+
-define(GRACEFUL_RETRY_TIMEOUT, 5000).
--define(SUPPORTEDEXTENSION, "1.3.6.1.4.1.1466.101.120.7").
--define(SUPPORTEDEXTENSIONSYNTAX, "1.3.6.1.4.1.1466.115.121.1.38").
--define(STARTTLS, "1.3.6.1.4.1.1466.20037").
-
--record(eldap, {version = ?LDAP_VERSION,
- hosts, % Possible hosts running LDAP servers
- host = null, % Connected Host LDAP server
- port = 389, % The LDAP server port
- sockmod, % SockMod (gen_tcp|tls)
- tls = none, % LDAP/LDAPS (none|tls)
- tls_options = [],
- fd = null, % Socket filedescriptor.
- rootdn = "", % Name of the entry to bind as
- passwd, % Password for (above) entry
- id = 0, % LDAP Request ID
- bind_timer, % Ref to bind timeout
- dict, % dict holding operation params and results
- req_q % Queue for requests
- }).
+-define(SUPPORTEDEXTENSION,
+ <<"1.3.6.1.4.1.1466.101.120.7">>).
+
+-define(SUPPORTEDEXTENSIONSYNTAX,
+ <<"1.3.6.1.4.1.1466.115.121.1.38">>).
+
+-define(STARTTLS, <<"1.3.6.1.4.1.1466.20037">>).
+
+-type handle() :: pid() | atom() | binary().
+
+-record(eldap,
+ {version = ?LDAP_VERSION :: non_neg_integer(),
+ hosts = [] :: [binary()],
+ host :: binary(),
+ port = 389 :: inet:port_number(),
+ sockmod = gen_tcp :: ssl | gen_tcp,
+ tls = none :: none | tls,
+ tls_options = [] :: [{cacertfile, string()} |
+ {depth, non_neg_integer()} |
+ {verify, non_neg_integer()}],
+ fd,
+ rootdn = <<"">> :: binary(),
+ passwd = <<"">> :: binary(),
+ id = 0 :: non_neg_integer(),
+ bind_timer = make_ref() :: reference(),
+ dict = dict:new() :: dict(),
+ req_q = queue:new() :: queue()}).
%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
start_link(Name) ->
- Reg_name = list_to_atom("eldap_" ++ Name),
+ Reg_name = jlib:binary_to_atom(<<"eldap_",
+ Name/binary>>),
gen_fsm:start_link({local, Reg_name}, ?MODULE, [], []).
+-spec start_link(binary(), [binary()], inet:port_number(), binary(),
+ binary(), tlsopts()) -> any().
+
start_link(Name, Hosts, Port, Rootdn, Passwd, Opts) ->
- Reg_name = list_to_atom("eldap_" ++ Name),
+ Reg_name = jlib:binary_to_atom(<<"eldap_",
+ Name/binary>>),
gen_fsm:start_link({local, Reg_name}, ?MODULE,
- {Hosts, Port, Rootdn, Passwd, Opts}, []).
+ [Hosts, Port, Rootdn, Passwd, Opts], []).
+
+-spec get_status(handle()) -> any().
%%% --------------------------------------------------------------------
%%% Get status of connection.
@@ -145,6 +170,8 @@ get_status(Handle) ->
%%% --------------------------------------------------------------------
%%% Shutdown connection (and process) asynchronous.
%%% --------------------------------------------------------------------
+-spec close(handle()) -> any().
+
close(Handle) ->
Handle1 = get_handle(Handle),
gen_fsm:send_all_state_event(Handle1, close).
@@ -162,23 +189,21 @@ close(Handle) ->
%%% {"telephoneNumber", ["545 555 00"]}]
%%% )
%%% --------------------------------------------------------------------
-add(Handle, Entry, Attributes) when is_list(Entry), is_list(Attributes) ->
+add(Handle, Entry, Attributes) ->
Handle1 = get_handle(Handle),
- gen_fsm:sync_send_event(Handle1, {add, Entry, add_attrs(Attributes)},
- ?CALL_TIMEOUT).
+ gen_fsm:sync_send_event(Handle1,
+ {add, Entry, add_attrs(Attributes)}, ?CALL_TIMEOUT).
%%% Do sanity check !
add_attrs(Attrs) ->
- F = fun({Type,Vals}) when is_list(Type), is_list(Vals) ->
- %% Confused ? Me too... :-/
- {'AddRequest_attributes',Type, Vals}
+ F = fun ({Type, Vals}) ->
+ {'AddRequest_attributes', Type, Vals}
end,
case catch lists:map(F, Attrs) of
- {'EXIT', _} -> throw({error, attribute_values});
- Else -> Else
+ {'EXIT', _} -> throw({error, attribute_values});
+ Else -> Else
end.
-
%%% --------------------------------------------------------------------
%%% Delete an entry. The entry consists of the DN of
%%% the entry to be deleted.
@@ -188,9 +213,10 @@ add_attrs(Attrs) ->
%%% "cn=Bill Valentine, ou=people, o=Bluetail AB, dc=bluetail, dc=com"
%%% )
%%% --------------------------------------------------------------------
-delete(Handle, Entry) when is_list(Entry) ->
+delete(Handle, Entry) ->
Handle1 = get_handle(Handle),
- gen_fsm:sync_send_event(Handle1, {delete, Entry}, ?CALL_TIMEOUT).
+ gen_fsm:sync_send_event(Handle1, {delete, Entry},
+ ?CALL_TIMEOUT).
%%% --------------------------------------------------------------------
%%% Modify an entry. Given an entry a number of modification
@@ -203,25 +229,23 @@ delete(Handle, Entry) when is_list(Entry) ->
%%% add("description", ["LDAP hacker"])]
%%% )
%%% --------------------------------------------------------------------
-modify(Handle, Object, Mods) when is_list(Object), is_list(Mods) ->
+-spec modify(handle(), any(), [add | delete | replace]) -> any().
+
+modify(Handle, Object, Mods) ->
Handle1 = get_handle(Handle),
- gen_fsm:sync_send_event(Handle1, {modify, Object, Mods}, ?CALL_TIMEOUT).
+ gen_fsm:sync_send_event(Handle1, {modify, Object, Mods},
+ ?CALL_TIMEOUT).
%%%
%%% Modification operations.
%%% Example:
%%% replace("telephoneNumber", ["555 555 00"])
%%%
-mod_add(Type, Values) when is_list(Type), is_list(Values) -> m(add, Type, Values).
-mod_delete(Type, Values) when is_list(Type), is_list(Values) -> m(delete, Type, Values).
-mod_replace(Type, Values) when is_list(Type), is_list(Values) -> m(replace, Type, Values).
+mod_add(Type, Values) ->
+ m(add, Type, Values).
-m(Operation, Type, Values) ->
- #'ModifyRequest_modification_SEQOF'{
- operation = Operation,
- modification = #'AttributeTypeAndValues'{
- type = Type,
- vals = Values}}.
+mod_delete(Type, Values) ->
+ m(delete, Type, Values).
%%% --------------------------------------------------------------------
%%% Modify an entry. Given an entry a number of modification
@@ -235,18 +259,31 @@ m(Operation, Type, Values) ->
%%% ""
%%% )
%%% --------------------------------------------------------------------
-modify_dn(Handle, Entry, NewRDN, DelOldRDN, NewSup)
- when is_list(Entry), is_list(NewRDN), is_atom(DelOldRDN), is_list(NewSup) ->
+mod_replace(Type, Values) ->
+ m(replace, Type, Values).
+
+m(Operation, Type, Values) ->
+ #'ModifyRequest_modification_SEQOF'{operation =
+ Operation,
+ modification =
+ #'AttributeTypeAndValues'{type =
+ Type,
+ vals =
+ Values}}.
+
+modify_dn(Handle, Entry, NewRDN, DelOldRDN, NewSup) ->
Handle1 = get_handle(Handle),
- gen_fsm:sync_send_event(
- Handle1,
- {modify_dn, Entry, NewRDN, bool_p(DelOldRDN), optional(NewSup)},
- ?CALL_TIMEOUT).
+ gen_fsm:sync_send_event(Handle1,
+ {modify_dn, Entry, NewRDN, bool_p(DelOldRDN),
+ optional(NewSup)},
+ ?CALL_TIMEOUT).
+
+-spec modify_passwd(handle(), binary(), binary()) -> any().
-modify_passwd(Handle, DN, Passwd) when is_list(DN), is_list(Passwd) ->
+modify_passwd(Handle, DN, Passwd) ->
Handle1 = get_handle(Handle),
- gen_fsm:sync_send_event(
- Handle1, {modify_passwd, DN, Passwd}, ?CALL_TIMEOUT).
+ gen_fsm:sync_send_event(Handle1,
+ {modify_passwd, DN, Passwd}, ?CALL_TIMEOUT).
%%% --------------------------------------------------------------------
%%% Bind.
@@ -256,16 +293,18 @@ modify_passwd(Handle, DN, Passwd) when is_list(DN), is_list(Passwd) ->
%%% "cn=Bill Valentine, ou=people, o=Bluetail AB, dc=bluetail, dc=com",
%%% "secret")
%%% --------------------------------------------------------------------
-bind(Handle, RootDN, Passwd)
- when is_list(RootDN), is_list(Passwd) ->
+-spec bind(handle(), binary(), binary()) -> any().
+
+bind(Handle, RootDN, Passwd) ->
Handle1 = get_handle(Handle),
- gen_fsm:sync_send_event(Handle1, {bind, RootDN, Passwd}, ?CALL_TIMEOUT).
+ gen_fsm:sync_send_event(Handle1, {bind, RootDN, Passwd},
+ ?CALL_TIMEOUT).
%%% Sanity checks !
-bool_p(Bool) when Bool==true;Bool==false -> Bool.
+bool_p(Bool) when Bool == true; Bool == false -> Bool.
-optional([]) -> asn1_NOVALUE;
+optional([]) -> asn1_NOVALUE;
optional(Value) -> Value.
%%% --------------------------------------------------------------------
@@ -293,82 +332,149 @@ optional(Value) -> Value.
%%% []}}
%%%
%%% --------------------------------------------------------------------
+-type search_args() :: [{base, binary()} |
+ {filter, filter()} |
+ {scope, scope()} |
+ {attributes, [binary()]} |
+ {types_only, boolean()} |
+ {timeout, non_neg_integer()} |
+ {limit, non_neg_integer()} |
+ {deref_aliases, never | searching | finding | always}].
+
+-spec search(handle(), eldap_search() | search_args()) -> any().
+
search(Handle, A) when is_record(A, eldap_search) ->
call_search(Handle, A);
search(Handle, L) when is_list(L) ->
case catch parse_search_args(L) of
- {error, Emsg} -> {error, Emsg};
- {'EXIT', Emsg} -> {error, Emsg};
- A when is_record(A, eldap_search) -> call_search(Handle, A)
+ {error, Emsg} -> {error, Emsg};
+ {'EXIT', Emsg} -> {error, Emsg};
+ A when is_record(A, eldap_search) ->
+ call_search(Handle, A)
end.
call_search(Handle, A) ->
Handle1 = get_handle(Handle),
- gen_fsm:sync_send_event(Handle1, {search, A}, ?CALL_TIMEOUT).
+ gen_fsm:sync_send_event(Handle1, {search, A},
+ ?CALL_TIMEOUT).
+
+-spec parse_search_args(search_args()) -> eldap_search().
parse_search_args(Args) ->
- parse_search_args(Args, #eldap_search{scope = wholeSubtree}).
-
-parse_search_args([{base, Base}|T],A) ->
- parse_search_args(T,A#eldap_search{base = Base});
-parse_search_args([{filter, Filter}|T],A) ->
- parse_search_args(T,A#eldap_search{filter = Filter});
-parse_search_args([{scope, Scope}|T],A) ->
- parse_search_args(T,A#eldap_search{scope = Scope});
-parse_search_args([{attributes, Attrs}|T],A) ->
- parse_search_args(T,A#eldap_search{attributes = Attrs});
-parse_search_args([{types_only, TypesOnly}|T],A) ->
- parse_search_args(T,A#eldap_search{types_only = TypesOnly});
-parse_search_args([{timeout, Timeout}|T],A) when is_integer(Timeout) ->
- parse_search_args(T,A#eldap_search{timeout = Timeout});
-parse_search_args([{limit, Limit}|T],A) when is_integer(Limit) ->
- parse_search_args(T,A#eldap_search{limit = Limit});
-parse_search_args([{deref_aliases, never}|T],A) ->
- parse_search_args(T,A#eldap_search{deref_aliases = neverDerefAliases});
-parse_search_args([{deref_aliases, searching}|T],A) ->
- parse_search_args(T,A#eldap_search{deref_aliases = derefInSearching});
-parse_search_args([{deref_aliases, finding}|T],A) ->
- parse_search_args(T,A#eldap_search{deref_aliases = derefFindingBaseObj});
-parse_search_args([{deref_aliases, always}|T],A) ->
- parse_search_args(T,A#eldap_search{deref_aliases = derefAlways});
-parse_search_args([H|_],_) ->
- throw({error,{unknown_arg, H}});
-parse_search_args([],A) ->
- A.
+ parse_search_args(Args,
+ #eldap_search{scope = wholeSubtree}).
+
+parse_search_args([{base, Base} | T], A) ->
+ parse_search_args(T, A#eldap_search{base = Base});
+parse_search_args([{filter, Filter} | T], A) ->
+ parse_search_args(T, A#eldap_search{filter = Filter});
+parse_search_args([{scope, Scope} | T], A) ->
+ parse_search_args(T, A#eldap_search{scope = Scope});
+parse_search_args([{attributes, Attrs} | T], A) ->
+ parse_search_args(T,
+ A#eldap_search{attributes = Attrs});
+parse_search_args([{types_only, TypesOnly} | T], A) ->
+ parse_search_args(T,
+ A#eldap_search{types_only = TypesOnly});
+parse_search_args([{timeout, Timeout} | T], A)
+ when is_integer(Timeout) ->
+ parse_search_args(T, A#eldap_search{timeout = Timeout});
+parse_search_args([{limit, Limit} | T], A)
+ when is_integer(Limit) ->
+ parse_search_args(T, A#eldap_search{limit = Limit});
+parse_search_args([{deref_aliases, never} | T], A) ->
+ parse_search_args(T,
+ A#eldap_search{deref_aliases = neverDerefAliases});
+parse_search_args([{deref_aliases, searching} | T],
+ A) ->
+ parse_search_args(T,
+ A#eldap_search{deref_aliases = derefInSearching});
+parse_search_args([{deref_aliases, finding} | T], A) ->
+ parse_search_args(T,
+ A#eldap_search{deref_aliases = derefFindingBaseObj});
+parse_search_args([{deref_aliases, always} | T], A) ->
+ parse_search_args(T,
+ A#eldap_search{deref_aliases = derefAlways});
+parse_search_args([H | _], _) ->
+ throw({error, {unknown_arg, H}});
+parse_search_args([], A) -> A.
+
+baseObject() -> baseObject.
+
+singleLevel() -> singleLevel.
%%%
%%% The Scope parameter
%%%
-baseObject() -> baseObject.
-singleLevel() -> singleLevel.
wholeSubtree() -> wholeSubtree.
%%%
%%% Boolean filter operations
%%%
-'and'(ListOfFilters) when is_list(ListOfFilters) -> {'and',ListOfFilters}.
-'or'(ListOfFilters) when is_list(ListOfFilters) -> {'or', ListOfFilters}.
-'not'(Filter) when is_tuple(Filter) -> {'not',Filter}.
+-type filter() :: 'and'() | 'or'() | 'not'() | equalityMatch() |
+ greaterOrEqual() | lessOrEqual() | approxMatch() |
+ present() | substrings() | extensibleMatch().
%%%
%%% The following Filter parameters consist of an attribute
%%% and an attribute value. Example: F("uid","tobbe")
%%%
-equalityMatch(Desc, Value) -> {equalityMatch, av_assert(Desc, Value)}.
-greaterOrEqual(Desc, Value) -> {greaterOrEqual, av_assert(Desc, Value)}.
-lessOrEqual(Desc, Value) -> {lessOrEqual, av_assert(Desc, Value)}.
-approxMatch(Desc, Value) -> {approxMatch, av_assert(Desc, Value)}.
+-type 'and'() :: {'and', [filter()]}.
+-spec 'and'([filter()]) -> 'and'().
+
+'and'(ListOfFilters) when is_list(ListOfFilters) ->
+ {'and', ListOfFilters}.
+
+-type 'or'() :: {'or', [filter()]}.
+-spec 'or'([filter()]) -> 'or'().
+
+'or'(ListOfFilters) when is_list(ListOfFilters) ->
+ {'or', ListOfFilters}.
+
+-type 'not'() :: {'not', filter()}.
+-spec 'not'(filter()) -> 'not'().
+
+'not'(Filter) when is_tuple(Filter) -> {'not', Filter}.
+
+-type equalityMatch() :: {equalityMatch, 'AttributeValueAssertion'()}.
+-spec equalityMatch(binary(), binary()) -> equalityMatch().
+
+equalityMatch(Desc, Value) ->
+ {equalityMatch, av_assert(Desc, Value)}.
+
+-type greaterOrEqual() :: {greaterOrEqual, 'AttributeValueAssertion'()}.
+-spec greaterOrEqual(binary(), binary()) -> greaterOrEqual().
+
+greaterOrEqual(Desc, Value) ->
+ {greaterOrEqual, av_assert(Desc, Value)}.
+
+-type lessOrEqual() :: {lessOrEqual, 'AttributeValueAssertion'()}.
+-spec lessOrEqual(binary(), binary()) -> lessOrEqual().
+
+lessOrEqual(Desc, Value) ->
+ {lessOrEqual, av_assert(Desc, Value)}.
+
+-type approxMatch() :: {approxMatch, 'AttributeValueAssertion'()}.
+-spec approxMatch(binary(), binary()) -> approxMatch().
+
+approxMatch(Desc, Value) ->
+ {approxMatch, av_assert(Desc, Value)}.
+
+-type 'AttributeValueAssertion'() ::
+ #'AttributeValueAssertion'{attributeDesc :: binary(),
+ assertionValue :: binary()}.
+
+-spec av_assert(binary(), binary()) -> 'AttributeValueAssertion'().
av_assert(Desc, Value) ->
- #'AttributeValueAssertion'{attributeDesc = Desc,
+ #'AttributeValueAssertion'{attributeDesc = Desc,
assertionValue = Value}.
%%%
%%% Filter to check for the presence of an attribute
%%%
-present(Attribute) when is_list(Attribute) ->
- {present, Attribute}.
-
+-type present() :: {present, binary()}.
+-spec present(binary()) -> present().
%%%
%%% A substring filter seem to be based on a pattern:
@@ -385,10 +491,8 @@ present(Attribute) when is_list(Attribute) ->
%%% Example: substrings("sn",[{initial,"To"},{any,"kv"},{final,"st"}])
%%% will match entries containing: 'sn: Tornkvist'
%%%
-substrings(Type, SubStr) when is_list(Type), is_list(SubStr) ->
- Ss = {'SubstringFilter_substrings',v_substr(SubStr)},
- {substrings,#'SubstringFilter'{type = Type,
- substrings = Ss}}.
+present(Attribute) ->
+ {present, Attribute}.
%%%
%%% extensibleMatch filter.
@@ -399,24 +503,56 @@ substrings(Type, SubStr) when is_list(Type), is_list(SubStr) ->
%%%
%%% Example: extensibleMatch("Fred", [{matchingRule, "1.2.3.4.5"}, {type, "cn"}]).
%%%
-extensibleMatch(Value, Opts) when is_list(Value), is_list(Opts) ->
- MRA = #'MatchingRuleAssertion'{matchValue=Value},
- {extensibleMatch, extensibleMatch_opts(Opts, MRA)}.
-
-extensibleMatch_opts([{matchingRule, Rule} | Opts], MRA) when is_list(Rule) ->
- extensibleMatch_opts(Opts, MRA#'MatchingRuleAssertion'{matchingRule=Rule});
-extensibleMatch_opts([{type, Desc} | Opts], MRA) when is_list(Desc) ->
- extensibleMatch_opts(Opts, MRA#'MatchingRuleAssertion'{type=Desc});
-extensibleMatch_opts([{dnAttributes, true} | Opts], MRA) ->
- extensibleMatch_opts(Opts, MRA#'MatchingRuleAssertion'{dnAttributes=true});
+-type substr() :: [{initial | any | final, binary()}].
+-type 'SubstringFilter'() ::
+ #'SubstringFilter'{type :: binary(),
+ substrings :: {'SubstringFilter_substrings',
+ substr()}}.
+
+-type substrings() :: {substrings, 'SubstringFilter'()}.
+-spec substrings(binary(), substr()) -> substrings().
+
+substrings(Type, SubStr) ->
+ Ss = {'SubstringFilter_substrings', SubStr},
+ {substrings,
+ #'SubstringFilter'{type = Type, substrings = Ss}}.
+
+-type match_opts() :: [{matchingRule | type, binary()} |
+ {dnAttributes, boolean()}].
+
+-type 'MatchingRuleAssertion'() ::
+ #'MatchingRuleAssertion'{matchValue :: binary(),
+ type :: asn1_NOVALUE | binary(),
+ matchingRule :: asn1_NOVALUE | binary(),
+ dnAttributes :: asn1_DEFAULT | true}.
+
+-type extensibleMatch() :: {extensibleMatch, 'MatchingRuleAssertion'()}.
+-spec extensibleMatch(binary(), match_opts()) -> extensibleMatch().
+
+extensibleMatch(Value, Opts) ->
+ MRA = #'MatchingRuleAssertion'{matchValue = Value},
+ {extensibleMatch, extensibleMatch_opts(Opts, MRA)}.
+
+extensibleMatch_opts([{matchingRule, Rule} | Opts], MRA) ->
+ extensibleMatch_opts(Opts,
+ MRA#'MatchingRuleAssertion'{matchingRule = Rule});
+extensibleMatch_opts([{type, Desc} | Opts], MRA) ->
+ extensibleMatch_opts(Opts,
+ MRA#'MatchingRuleAssertion'{type = Desc});
+extensibleMatch_opts([{dnAttributes, true} | Opts],
+ MRA) ->
+ extensibleMatch_opts(Opts,
+ MRA#'MatchingRuleAssertion'{dnAttributes = true});
extensibleMatch_opts([_ | Opts], MRA) ->
- extensibleMatch_opts(Opts, MRA);
-extensibleMatch_opts([], MRA) ->
- MRA.
+ extensibleMatch_opts(Opts, MRA);
+extensibleMatch_opts([], MRA) -> MRA.
-get_handle(Pid) when is_pid(Pid) -> Pid;
+get_handle(Pid) when is_pid(Pid) -> Pid;
get_handle(Atom) when is_atom(Atom) -> Atom;
-get_handle(Name) when is_list(Name) -> list_to_atom("eldap_" ++ Name).
+get_handle(Name) when is_binary(Name) ->
+ jlib:binary_to_atom(<<"eldap_",
+ Name/binary>>).
+
%%%----------------------------------------------------------------------
%%% Callback functions from gen_fsm
%%%----------------------------------------------------------------------
@@ -430,63 +566,71 @@ get_handle(Name) when is_list(Name) -> list_to_atom("eldap_" ++ Name).
%% I use the trick of setting a timeout of 0 to pass control into the
%% process.
%%----------------------------------------------------------------------
-init([]) ->
- case get_config() of
- {ok, Hosts, Port, Rootdn, Passwd, Opts} ->
- init({Hosts, Port, Rootdn, Passwd, Opts});
- {error, Reason} ->
- {stop, Reason}
- end;
-init({Hosts, Port, Rootdn, Passwd, Opts}) ->
+init([Hosts, Port, Rootdn, Passwd, Opts]) ->
catch ssl:start(),
- %% ssl:seed was removed in OTP R14B04, newer Dialyzer will complain
- catch ssl:seed(randoms:get_string()),
- Encrypt = case proplists:get_value(encrypt, Opts) of
- tls -> tls;
- _ -> none
+ Encrypt = case gen_mod:get_opt(encrypt, Opts,
+ fun(tls) -> tls;
+ (starttls) -> starttls;
+ (none) -> none
+ end) of
+ tls -> tls;
+ _ -> none
end,
PortTemp = case Port of
- undefined ->
- case Encrypt of
- tls ->
- ?LDAPS_PORT;
- _ ->
- ?LDAP_PORT
- end;
- PT -> PT
+ undefined ->
+ case Encrypt of
+ tls -> ?LDAPS_PORT;
+ _ -> ?LDAP_PORT
+ end;
+ PT -> PT
end,
- CacertOpts = case proplists:get_value(tls_cacertfile, Opts) of
- [_|_] = Path -> [{cacertfile, Path}];
- _ -> []
+ CacertOpts = case gen_mod:get_opt(
+ tls_cacertfile, Opts,
+ fun(S) when is_binary(S) ->
+ binary_to_list(S);
+ (undefined) ->
+ undefined
+ end) of
+ undefined ->
+ [];
+ Path ->
+ [{cacertfile, Path}]
end,
- DepthOpts = case proplists:get_value(tls_depth, Opts) of
- Depth when is_integer(Depth), Depth >= 0 ->
- [{depth, Depth}];
- _ -> []
+ DepthOpts = case gen_mod:get_opt(
+ tls_depth, Opts,
+ fun(I) when is_integer(I), I>=0 ->
+ I;
+ (undefined) ->
+ undefined
+ end) of
+ undefined ->
+ [];
+ Depth ->
+ [{depth, Depth}]
end,
- Verify = proplists:get_value(tls_verify, Opts),
+ Verify = gen_mod:get_opt(tls_verify, Opts,
+ fun(hard) -> hard;
+ (soft) -> soft;
+ (false) -> false
+ end, false),
TLSOpts = if (Verify == hard orelse Verify == soft)
- andalso CacertOpts == [] ->
- ?WARNING_MSG("TLS verification is enabled "
- "but no CA certfiles configured, so "
- "verification is disabled.", []),
- [];
- Verify == soft ->
- [{verify, 1}] ++ CacertOpts ++ DepthOpts;
- Verify == hard ->
- [{verify, 2}] ++ CacertOpts ++ DepthOpts;
- true ->
- []
- end,
- {ok, connecting, #eldap{hosts = Hosts,
- port = PortTemp,
- rootdn = Rootdn,
- passwd = Passwd,
- tls = Encrypt,
- tls_options = TLSOpts,
- id = 0,
- dict = dict:new(),
- req_q = queue:new()}, 0}.
+ andalso CacertOpts == [] ->
+ ?WARNING_MSG("TLS verification is enabled but no CA "
+ "certfiles configured, so verification "
+ "is disabled.",
+ []),
+ [];
+ Verify == soft ->
+ [{verify, 1}] ++ CacertOpts ++ DepthOpts;
+ Verify == hard ->
+ [{verify, 2}] ++ CacertOpts ++ DepthOpts;
+ true -> []
+ end,
+ {ok, connecting,
+ #eldap{hosts = Hosts, port = PortTemp, rootdn = Rootdn,
+ passwd = Passwd, tls = Encrypt, tls_options = TLSOpts,
+ id = 0, dict = dict:new(), req_q = queue:new()},
+ 0}.
%%----------------------------------------------------------------------
%% Func: StateName/2
@@ -511,15 +655,15 @@ connecting(timeout, S) ->
%%----------------------------------------------------------------------
connecting(Event, From, S) ->
Q = queue:in({Event, From}, S#eldap.req_q),
- {next_state, connecting, S#eldap{req_q=Q}}.
+ {next_state, connecting, S#eldap{req_q = Q}}.
wait_bind_response(Event, From, S) ->
Q = queue:in({Event, From}, S#eldap.req_q),
- {next_state, wait_bind_response, S#eldap{req_q=Q}}.
+ {next_state, wait_bind_response, S#eldap{req_q = Q}}.
active_bind(Event, From, S) ->
Q = queue:in({Event, From}, S#eldap.req_q),
- {next_state, active_bind, S#eldap{req_q=Q}}.
+ {next_state, active_bind, S#eldap{req_q = Q}}.
active(Event, From, S) ->
process_command(S, Event, From).
@@ -534,7 +678,6 @@ active(Event, From, S) ->
handle_event(close, _StateName, S) ->
catch (S#eldap.sockmod):close(S#eldap.fd),
{stop, normal, S};
-
handle_event(_Event, StateName, S) ->
{next_state, StateName, S}.
@@ -556,6 +699,7 @@ handle_sync_event(_Event, _From, StateName, S) ->
%% Returns: {next_state, NextStateName, NextStateData} |
%% {next_state, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData}
+%% {stop, Reason, NewStateData}
%%----------------------------------------------------------------------
%%
@@ -563,83 +707,78 @@ handle_sync_event(_Event, _From, StateName, S) ->
%%
handle_info({Tag, _Socket, Data}, connecting, S)
when Tag == tcp; Tag == ssl ->
- ?DEBUG("tcp packet received when disconnected!~n~p", [Data]),
+ ?DEBUG("tcp packet received when disconnected!~n~p",
+ [Data]),
{next_state, connecting, S};
-
handle_info({Tag, _Socket, Data}, wait_bind_response, S)
when Tag == tcp; Tag == ssl ->
cancel_timer(S#eldap.bind_timer),
case catch recvd_wait_bind_response(Data, S) of
- bound ->
- dequeue_commands(S);
- {fail_bind, Reason} ->
- report_bind_failure(S#eldap.host, S#eldap.port, Reason),
- {next_state, connecting, close_and_retry(S, ?GRACEFUL_RETRY_TIMEOUT)};
- {'EXIT', Reason} ->
- report_bind_failure(S#eldap.host, S#eldap.port, Reason),
- {next_state, connecting, close_and_retry(S)};
- {error, Reason} ->
- report_bind_failure(S#eldap.host, S#eldap.port, Reason),
- {next_state, connecting, close_and_retry(S)}
+ bound -> dequeue_commands(S);
+ {fail_bind, Reason} ->
+ report_bind_failure(S#eldap.host, S#eldap.port, Reason),
+ {next_state, connecting,
+ close_and_retry(S, ?GRACEFUL_RETRY_TIMEOUT)};
+ {'EXIT', Reason} ->
+ report_bind_failure(S#eldap.host, S#eldap.port, Reason),
+ {next_state, connecting, close_and_retry(S)};
+ {error, Reason} ->
+ report_bind_failure(S#eldap.host, S#eldap.port, Reason),
+ {next_state, connecting, close_and_retry(S)}
end;
-
handle_info({Tag, _Socket, Data}, StateName, S)
- when (StateName == active orelse StateName == active_bind) andalso
- (Tag == tcp orelse Tag == ssl) ->
+ when (StateName == active orelse
+ StateName == active_bind)
+ andalso (Tag == tcp orelse Tag == ssl) ->
case catch recvd_packet(Data, S) of
- {response, Response, RequestType} ->
- NewS = case Response of
- {reply, Reply, To, S1} ->
- gen_fsm:reply(To, Reply),
- S1;
- {ok, S1} ->
- S1
- end,
- if (StateName == active_bind andalso
- RequestType == bindRequest) orelse
- (StateName == active) ->
- dequeue_commands(NewS);
- true ->
- {next_state, StateName, NewS}
- end;
- _ ->
- {next_state, StateName, S}
+ {response, Response, RequestType} ->
+ NewS = case Response of
+ {reply, Reply, To, S1} -> gen_fsm:reply(To, Reply), S1;
+ {ok, S1} -> S1
+ end,
+ if StateName == active_bind andalso
+ RequestType == bindRequest
+ orelse StateName == active ->
+ dequeue_commands(NewS);
+ true -> {next_state, StateName, NewS}
+ end;
+ _ -> {next_state, StateName, S}
end;
-
handle_info({Tag, _Socket}, Fsm_state, S)
when Tag == tcp_closed; Tag == ssl_closed ->
- ?WARNING_MSG("LDAP server closed the connection: ~s:~p~nIn State: ~p",
- [S#eldap.host, S#eldap.port ,Fsm_state]),
+ ?WARNING_MSG("LDAP server closed the connection: ~s:~p~nIn "
+ "State: ~p",
+ [S#eldap.host, S#eldap.port, Fsm_state]),
{next_state, connecting, close_and_retry(S)};
-
handle_info({Tag, _Socket, Reason}, Fsm_state, S)
when Tag == tcp_error; Tag == ssl_error ->
- ?DEBUG("eldap received tcp_error: ~p~nIn State: ~p", [Reason, Fsm_state]),
+ ?DEBUG("eldap received tcp_error: ~p~nIn State: ~p",
+ [Reason, Fsm_state]),
{next_state, connecting, close_and_retry(S)};
-
%%
%% Timers
%%
-handle_info({timeout, Timer, {cmd_timeout, Id}}, StateName, S) ->
+handle_info({timeout, Timer, {cmd_timeout, Id}},
+ StateName, S) ->
case cmd_timeout(Timer, Id, S) of
- {reply, To, Reason, NewS} -> gen_fsm:reply(To, Reason),
- {next_state, StateName, NewS};
- {error, _Reason} -> {next_state, StateName, S}
+ {reply, To, Reason, NewS} ->
+ gen_fsm:reply(To, Reason),
+ {next_state, StateName, NewS};
+ {error, _Reason} -> {next_state, StateName, S}
end;
-
handle_info({timeout, retry_connect}, connecting, S) ->
- {ok, NextState, NewS} = connect_bind(S),
+ {ok, NextState, NewS} = connect_bind(S),
{next_state, NextState, NewS};
-
-handle_info({timeout, _Timer, bind_timeout}, wait_bind_response, S) ->
+handle_info({timeout, _Timer, bind_timeout},
+ wait_bind_response, S) ->
{next_state, connecting, close_and_retry(S)};
-
%%
%% Make sure we don't fill the message queue with rubbish
%%
handle_info(Info, StateName, S) ->
- ?DEBUG("eldap. Unexpected Info: ~p~nIn state: ~p~n when StateData is: ~p",
- [Info, StateName, S]),
+ ?DEBUG("eldap. Unexpected Info: ~p~nIn state: "
+ "~p~n when StateData is: ~p",
+ [Info, StateName, S]),
{next_state, StateName, S}.
%%----------------------------------------------------------------------
@@ -647,8 +786,7 @@ handle_info(Info, StateName, S) ->
%% Purpose: Shutdown the fsm
%% Returns: any
%%----------------------------------------------------------------------
-terminate(_Reason, _StateName, _StatData) ->
- ok.
+terminate(_Reason, _StateName, _StatData) -> ok.
%%----------------------------------------------------------------------
%% Func: code_change/4
@@ -663,91 +801,79 @@ code_change(_OldVsn, StateName, S, _Extra) ->
%%%----------------------------------------------------------------------
dequeue_commands(S) ->
case queue:out(S#eldap.req_q) of
- {{value, {Event, From}}, Q} ->
- case process_command(S#eldap{req_q=Q}, Event, From) of
- {_, active, NewS} ->
- dequeue_commands(NewS);
- Res ->
- Res
- end;
- {empty, _} ->
- {next_state, active, S}
+ {{value, {Event, From}}, Q} ->
+ case process_command(S#eldap{req_q = Q}, Event, From) of
+ {_, active, NewS} -> dequeue_commands(NewS);
+ Res -> Res
+ end;
+ {empty, _} -> {next_state, active, S}
end.
process_command(S, Event, From) ->
case send_command(Event, From, S) of
- {ok, NewS} ->
- case Event of
- {bind, _, _} ->
- {next_state, active_bind, NewS};
- _ ->
- {next_state, active, NewS}
- end;
- {error, _Reason} ->
- Q = queue:in_r({Event, From}, S#eldap.req_q),
- NewS = close_and_retry(S#eldap{req_q=Q}),
- {next_state, connecting, NewS}
+ {ok, NewS} ->
+ case Event of
+ {bind, _, _} -> {next_state, active_bind, NewS};
+ _ -> {next_state, active, NewS}
+ end;
+ {error, _Reason} ->
+ Q = queue:in_r({Event, From}, S#eldap.req_q),
+ NewS = close_and_retry(S#eldap{req_q = Q}),
+ {next_state, connecting, NewS}
end.
send_command(Command, From, S) ->
Id = bump_id(S),
{Name, Request} = gen_req(Command),
- Message = #'LDAPMessage'{messageID = Id,
+ Message = #'LDAPMessage'{messageID = Id,
protocolOp = {Name, Request}},
- ?DEBUG("~p~n",[{Name, Request}]),
- {ok, Bytes} = asn1rt:encode('ELDAPv3', 'LDAPMessage', Message),
+ ?DEBUG("~p~n", [{Name, Request}]),
+ {ok, Bytes} = asn1rt:encode('ELDAPv3', 'LDAPMessage',
+ Message),
case (S#eldap.sockmod):send(S#eldap.fd, Bytes) of
- ok ->
- Timer = erlang:start_timer(?CMD_TIMEOUT, self(), {cmd_timeout, Id}),
- New_dict = dict:store(Id, [{Timer, Command, From, Name}], S#eldap.dict),
- {ok, S#eldap{id = Id, dict = New_dict}};
- Error ->
- Error
+ ok ->
+ Timer = erlang:start_timer(?CMD_TIMEOUT, self(),
+ {cmd_timeout, Id}),
+ New_dict = dict:store(Id,
+ [{Timer, Command, From, Name}], S#eldap.dict),
+ {ok, S#eldap{id = Id, dict = New_dict}};
+ Error -> Error
end.
gen_req({search, A}) ->
{searchRequest,
- #'SearchRequest'{baseObject = A#eldap_search.base,
- scope = v_scope(A#eldap_search.scope),
+ #'SearchRequest'{baseObject = A#eldap_search.base,
+ scope = A#eldap_search.scope,
derefAliases = A#eldap_search.deref_aliases,
- sizeLimit = A#eldap_search.limit,
- timeLimit = v_timeout(A#eldap_search.timeout),
- typesOnly = v_bool(A#eldap_search.types_only),
- filter = v_filter(A#eldap_search.filter),
- attributes = v_attributes(A#eldap_search.attributes)
- }};
+ sizeLimit = A#eldap_search.limit,
+ timeLimit = A#eldap_search.timeout,
+ typesOnly = A#eldap_search.types_only,
+ filter = A#eldap_search.filter,
+ attributes = A#eldap_search.attributes}};
gen_req({add, Entry, Attrs}) ->
{addRequest,
- #'AddRequest'{entry = Entry,
- attributes = Attrs}};
-gen_req({delete, Entry}) ->
- {delRequest, Entry};
+ #'AddRequest'{entry = Entry, attributes = Attrs}};
+gen_req({delete, Entry}) -> {delRequest, Entry};
gen_req({modify, Obj, Mod}) ->
- v_modifications(Mod),
- {modifyRequest,
- #'ModifyRequest'{object = Obj,
- modification = Mod}};
-gen_req({modify_dn, Entry, NewRDN, DelOldRDN, NewSup}) ->
+ {modifyRequest,
+ #'ModifyRequest'{object = Obj, modification = Mod}};
+gen_req({modify_dn, Entry, NewRDN, DelOldRDN,
+ NewSup}) ->
{modDNRequest,
- #'ModifyDNRequest'{entry = Entry,
- newrdn = NewRDN,
- deleteoldrdn = DelOldRDN,
- newSuperior = NewSup}};
-
+ #'ModifyDNRequest'{entry = Entry, newrdn = NewRDN,
+ deleteoldrdn = DelOldRDN, newSuperior = NewSup}};
gen_req({modify_passwd, DN, Passwd}) ->
- {ok, ReqVal} = asn1rt:encode(
- 'ELDAPv3', 'PasswdModifyRequestValue',
- #'PasswdModifyRequestValue'{
- userIdentity = DN,
- newPasswd = Passwd}),
+ {ok, ReqVal} = asn1rt:encode('ELDAPv3',
+ 'PasswdModifyRequestValue',
+ #'PasswdModifyRequestValue'{userIdentity = DN,
+ newPasswd =
+ Passwd}),
{extendedReq,
#'ExtendedRequest'{requestName = ?passwdModifyOID,
- requestValue = list_to_binary(ReqVal)}};
-
+ requestValue = iolist_to_binary(ReqVal)}};
gen_req({bind, RootDN, Passwd}) ->
{bindRequest,
- #'BindRequest'{version = ?LDAP_VERSION,
- name = RootDN,
+ #'BindRequest'{version = ?LDAP_VERSION, name = RootDN,
authentication = {simple, Passwd}}}.
%%-----------------------------------------------------------------------
@@ -762,105 +888,111 @@ gen_req({bind, RootDN, Passwd}) ->
%%-----------------------------------------------------------------------
recvd_packet(Pkt, S) ->
case asn1rt:decode('ELDAPv3', 'LDAPMessage', Pkt) of
- {ok,Msg} ->
- Op = Msg#'LDAPMessage'.protocolOp,
- ?DEBUG("~p",[Op]),
- Dict = S#eldap.dict,
- Id = Msg#'LDAPMessage'.messageID,
- {Timer, From, Name, Result_so_far} = get_op_rec(Id, Dict),
- Answer =
- case {Name, Op} of
- {searchRequest, {searchResEntry, R}} when
- is_record(R,'SearchResultEntry') ->
- New_dict = dict:append(Id, R, Dict),
- {ok, S#eldap{dict = New_dict}};
- {searchRequest, {searchResDone, Result}} ->
- Reason = Result#'LDAPResult'.resultCode,
- if
- Reason==success; Reason=='sizeLimitExceeded' ->
- {Res, Ref} = polish(Result_so_far),
- New_dict = dict:erase(Id, Dict),
- cancel_timer(Timer),
- {reply, #eldap_search_result{entries = Res,
- referrals = Ref}, From,
- S#eldap{dict = New_dict}};
- true ->
- New_dict = dict:erase(Id, Dict),
- cancel_timer(Timer),
- {reply, {error, Reason}, From, S#eldap{dict = New_dict}}
- end;
- {searchRequest, {searchResRef, R}} ->
- New_dict = dict:append(Id, R, Dict),
- {ok, S#eldap{dict = New_dict}};
- {addRequest, {addResponse, Result}} ->
- New_dict = dict:erase(Id, Dict),
- cancel_timer(Timer),
- Reply = check_reply(Result, From),
- {reply, Reply, From, S#eldap{dict = New_dict}};
- {delRequest, {delResponse, Result}} ->
- New_dict = dict:erase(Id, Dict),
- cancel_timer(Timer),
- Reply = check_reply(Result, From),
- {reply, Reply, From, S#eldap{dict = New_dict}};
- {modifyRequest, {modifyResponse, Result}} ->
- New_dict = dict:erase(Id, Dict),
- cancel_timer(Timer),
- Reply = check_reply(Result, From),
- {reply, Reply, From, S#eldap{dict = New_dict}};
- {modDNRequest, {modDNResponse, Result}} ->
- New_dict = dict:erase(Id, Dict),
- cancel_timer(Timer),
- Reply = check_reply(Result, From),
- {reply, Reply, From, S#eldap{dict = New_dict}};
- {bindRequest, {bindResponse, Result}} ->
- New_dict = dict:erase(Id, Dict),
- cancel_timer(Timer),
- Reply = check_bind_reply(Result, From),
- {reply, Reply, From, S#eldap{dict = New_dict}};
- {extendedReq, {extendedResp, Result}} ->
- New_dict = dict:erase(Id, Dict),
- cancel_timer(Timer),
- Reply = check_extended_reply(Result, From),
- {reply, Reply, From, S#eldap{dict = New_dict}};
- {OtherName, OtherResult} ->
- New_dict = dict:erase(Id, Dict),
- cancel_timer(Timer),
- {reply, {error, {invalid_result, OtherName, OtherResult}},
- From, S#eldap{dict = New_dict}}
- end,
- {response, Answer, Name};
- Error -> Error
+ {ok, Msg} ->
+ Op = Msg#'LDAPMessage'.protocolOp,
+ ?DEBUG("~p", [Op]),
+ Dict = S#eldap.dict,
+ Id = Msg#'LDAPMessage'.messageID,
+ {Timer, From, Name, Result_so_far} = get_op_rec(Id,
+ Dict),
+ Answer = case {Name, Op} of
+ {searchRequest, {searchResEntry, R}}
+ when is_record(R, 'SearchResultEntry') ->
+ New_dict = dict:append(Id, R, Dict),
+ {ok, S#eldap{dict = New_dict}};
+ {searchRequest, {searchResDone, Result}} ->
+ Reason = Result#'LDAPResult'.resultCode,
+ if Reason == success; Reason == sizeLimitExceeded ->
+ {Res, Ref} = polish(Result_so_far),
+ New_dict = dict:erase(Id, Dict),
+ cancel_timer(Timer),
+ {reply,
+ #eldap_search_result{entries = Res,
+ referrals = Ref},
+ From, S#eldap{dict = New_dict}};
+ true ->
+ New_dict = dict:erase(Id, Dict),
+ cancel_timer(Timer),
+ {reply, {error, Reason}, From,
+ S#eldap{dict = New_dict}}
+ end;
+ {searchRequest, {searchResRef, R}} ->
+ New_dict = dict:append(Id, R, Dict),
+ {ok, S#eldap{dict = New_dict}};
+ {addRequest, {addResponse, Result}} ->
+ New_dict = dict:erase(Id, Dict),
+ cancel_timer(Timer),
+ Reply = check_reply(Result, From),
+ {reply, Reply, From, S#eldap{dict = New_dict}};
+ {delRequest, {delResponse, Result}} ->
+ New_dict = dict:erase(Id, Dict),
+ cancel_timer(Timer),
+ Reply = check_reply(Result, From),
+ {reply, Reply, From, S#eldap{dict = New_dict}};
+ {modifyRequest, {modifyResponse, Result}} ->
+ New_dict = dict:erase(Id, Dict),
+ cancel_timer(Timer),
+ Reply = check_reply(Result, From),
+ {reply, Reply, From, S#eldap{dict = New_dict}};
+ {modDNRequest, {modDNResponse, Result}} ->
+ New_dict = dict:erase(Id, Dict),
+ cancel_timer(Timer),
+ Reply = check_reply(Result, From),
+ {reply, Reply, From, S#eldap{dict = New_dict}};
+ {bindRequest, {bindResponse, Result}} ->
+ New_dict = dict:erase(Id, Dict),
+ cancel_timer(Timer),
+ Reply = check_bind_reply(Result, From),
+ {reply, Reply, From, S#eldap{dict = New_dict}};
+ {extendedReq, {extendedResp, Result}} ->
+ New_dict = dict:erase(Id, Dict),
+ cancel_timer(Timer),
+ Reply = check_extended_reply(Result, From),
+ {reply, Reply, From, S#eldap{dict = New_dict}};
+ {OtherName, OtherResult} ->
+ New_dict = dict:erase(Id, Dict),
+ cancel_timer(Timer),
+ {reply,
+ {error, {invalid_result, OtherName, OtherResult}},
+ From, S#eldap{dict = New_dict}}
+ end,
+ {response, Answer, Name};
+ Error -> Error
end.
-check_reply(#'LDAPResult'{resultCode = success}, _From) ->
+check_reply(#'LDAPResult'{resultCode = success},
+ _From) ->
ok;
-check_reply(#'LDAPResult'{resultCode = Reason}, _From) ->
+check_reply(#'LDAPResult'{resultCode = Reason},
+ _From) ->
{error, Reason};
-check_reply(Other, _From) ->
- {error, Other}.
+check_reply(Other, _From) -> {error, Other}.
-check_bind_reply(#'BindResponse'{resultCode = success}, _From) ->
+check_bind_reply(#'BindResponse'{resultCode = success},
+ _From) ->
ok;
-check_bind_reply(#'BindResponse'{resultCode = Reason}, _From) ->
+check_bind_reply(#'BindResponse'{resultCode = Reason},
+ _From) ->
{error, Reason};
-check_bind_reply(Other, _From) ->
- {error, Other}.
+check_bind_reply(Other, _From) -> {error, Other}.
%% TODO: process reply depending on requestName:
%% this requires BER-decoding of #'ExtendedResponse'.response
-check_extended_reply(#'ExtendedResponse'{resultCode = success}, _From) ->
+check_extended_reply(#'ExtendedResponse'{resultCode =
+ success},
+ _From) ->
ok;
-check_extended_reply(#'ExtendedResponse'{resultCode = Reason}, _From) ->
+check_extended_reply(#'ExtendedResponse'{resultCode =
+ Reason},
+ _From) ->
{error, Reason};
-check_extended_reply(Other, _From) ->
- {error, Other}.
+check_extended_reply(Other, _From) -> {error, Other}.
get_op_rec(Id, Dict) ->
case dict:find(Id, Dict) of
- {ok, [{Timer, _Command, From, Name}|Res]} ->
- {Timer, From, Name, Res};
- error ->
- throw({error, unkown_id})
+ {ok, [{Timer, _Command, From, Name} | Res]} ->
+ {Timer, From, Name, Res};
+ error -> throw({error, unkown_id})
end.
%%-----------------------------------------------------------------------
@@ -874,22 +1006,21 @@ get_op_rec(Id, Dict) ->
%%-----------------------------------------------------------------------
recvd_wait_bind_response(Pkt, S) ->
case asn1rt:decode('ELDAPv3', 'LDAPMessage', Pkt) of
- {ok,Msg} ->
- ?DEBUG("~p", [Msg]),
- check_id(S#eldap.id, Msg#'LDAPMessage'.messageID),
- case Msg#'LDAPMessage'.protocolOp of
- {bindResponse, Result} ->
- case Result#'BindResponse'.resultCode of
- success -> bound;
- Error -> {fail_bind, Error}
- end
- end;
- Else ->
- {fail_bind, Else}
+ {ok, Msg} ->
+ ?DEBUG("~p", [Msg]),
+ check_id(S#eldap.id, Msg#'LDAPMessage'.messageID),
+ case Msg#'LDAPMessage'.protocolOp of
+ {bindResponse, Result} ->
+ case Result#'BindResponse'.resultCode of
+ success -> bound;
+ Error -> {fail_bind, Error}
+ end
+ end;
+ Else -> {fail_bind, Else}
end.
check_id(Id, Id) -> ok;
-check_id(_, _) -> throw({error, wrong_bind_id}).
+check_id(_, _) -> throw({error, wrong_bind_id}).
%%-----------------------------------------------------------------------
%% General Helpers
@@ -897,32 +1028,28 @@ check_id(_, _) -> throw({error, wrong_bind_id}).
cancel_timer(Timer) ->
erlang:cancel_timer(Timer),
- receive
- {timeout, Timer, _} ->
- ok
- after 0 ->
- ok
- end.
+ receive {timeout, Timer, _} -> ok after 0 -> ok end.
close_and_retry(S, Timeout) ->
catch (S#eldap.sockmod):close(S#eldap.fd),
- Queue = dict:fold(
- fun(_Id, [{Timer, Command, From, _Name}|_], Q) ->
- cancel_timer(Timer),
- queue:in_r({Command, From}, Q);
- (_, _, Q) ->
- Q
- end, S#eldap.req_q, S#eldap.dict),
- erlang:send_after(Timeout, self(), {timeout, retry_connect}),
- S#eldap{fd=null, req_q=Queue, dict=dict:new()}.
+ Queue = dict:fold(fun (_Id,
+ [{Timer, Command, From, _Name} | _], Q) ->
+ cancel_timer(Timer),
+ queue:in_r({Command, From}, Q);
+ (_, _, Q) -> Q
+ end,
+ S#eldap.req_q, S#eldap.dict),
+ erlang:send_after(Timeout, self(),
+ {timeout, retry_connect}),
+ S#eldap{fd = undefined, req_q = Queue, dict = dict:new()}.
close_and_retry(S) ->
close_and_retry(S, ?RETRY_TIMEOUT).
report_bind_failure(Host, Port, Reason) ->
?WARNING_MSG("LDAP bind failed on ~s:~p~nReason: ~p",
- [Host, Port, Reason]).
+ [Host, Port, Reason]).
%%-----------------------------------------------------------------------
%% Sort out timed out commands
@@ -930,21 +1057,21 @@ report_bind_failure(Host, Port, Reason) ->
cmd_timeout(Timer, Id, S) ->
Dict = S#eldap.dict,
case dict:find(Id, Dict) of
- {ok, [{Timer, _Command, From, Name}|Res]} ->
- case Name of
- searchRequest ->
- {Res1, Ref1} = polish(Res),
- New_dict = dict:erase(Id, Dict),
- {reply, From, {timeout,
- #eldap_search_result{entries = Res1,
- referrals = Ref1}},
- S#eldap{dict = New_dict}};
- _ ->
- New_dict = dict:erase(Id, Dict),
- {reply, From, {error, timeout}, S#eldap{dict = New_dict}}
- end;
- error ->
- {error, timed_out_cmd_not_in_dict}
+ {ok, [{Timer, _Command, From, Name} | Res]} ->
+ case Name of
+ searchRequest ->
+ {Res1, Ref1} = polish(Res),
+ New_dict = dict:erase(Id, Dict),
+ {reply, From,
+ {timeout,
+ #eldap_search_result{entries = Res1, referrals = Ref1}},
+ S#eldap{dict = New_dict}};
+ _ ->
+ New_dict = dict:erase(Id, Dict),
+ {reply, From, {error, timeout},
+ S#eldap{dict = New_dict}}
+ end;
+ error -> {error, timed_out_cmd_not_in_dict}
end.
%%-----------------------------------------------------------------------
@@ -954,191 +1081,97 @@ cmd_timeout(Timer, Id, S) ->
%%% Polish the returned search result
%%%
-polish(Entries) ->
- polish(Entries, [], []).
+polish(Entries) -> polish(Entries, [], []).
-polish([H|T], Res, Ref) when is_record(H, 'SearchResultEntry') ->
+polish([H | T], Res, Ref)
+ when is_record(H, 'SearchResultEntry') ->
ObjectName = H#'SearchResultEntry'.objectName,
- F = fun({_,A,V}) -> {A,V} end,
+ F = fun ({_, A, V}) -> {A, V} end,
Attrs = lists:map(F, H#'SearchResultEntry'.attributes),
- polish(T, [#eldap_entry{object_name = ObjectName,
- attributes = Attrs}|Res], Ref);
-polish([H|T], Res, Ref) -> % No special treatment of referrals at the moment.
- polish(T, Res, [H|Ref]);
-polish([], Res, Ref) ->
- {Res, Ref}.
+ polish(T,
+ [#eldap_entry{object_name = ObjectName,
+ attributes = Attrs}
+ | Res],
+ Ref);
+polish([H | T], Res,
+ Ref) -> % No special treatment of referrals at the moment.
+ polish(T, Res, [H | Ref]);
+polish([], Res, Ref) -> {Res, Ref}.
%%-----------------------------------------------------------------------
%% Connect to next server in list and attempt to bind to it.
%%-----------------------------------------------------------------------
connect_bind(S) ->
Host = next_host(S#eldap.host, S#eldap.hosts),
- ?INFO_MSG("LDAP connection on ~s:~p", [Host, S#eldap.port]),
+ ?INFO_MSG("LDAP connection on ~s:~p",
+ [Host, S#eldap.port]),
Opts = if S#eldap.tls == tls ->
- [{packet, asn1}, {active, true}, {keepalive, true},
- binary | S#eldap.tls_options];
- true ->
- [{packet, asn1}, {active, true}, {keepalive, true},
- {send_timeout, ?SEND_TIMEOUT}, binary]
- end,
+ [{packet, asn1}, {active, true}, {keepalive, true},
+ binary
+ | S#eldap.tls_options];
+ true ->
+ [{packet, asn1}, {active, true}, {keepalive, true},
+ {send_timeout, ?SEND_TIMEOUT}, binary]
+ end,
+ HostS = binary_to_list(Host),
SocketData = case S#eldap.tls of
- tls ->
- SockMod = ssl,
- ssl:connect(Host, S#eldap.port, Opts);
- %% starttls -> %% TODO: Implement STARTTLS;
- _ ->
- SockMod = gen_tcp,
- gen_tcp:connect(Host, S#eldap.port, Opts)
+ tls ->
+ SockMod = ssl, ssl:connect(HostS, S#eldap.port, Opts);
+ %% starttls -> %% TODO: Implement STARTTLS;
+ _ ->
+ SockMod = gen_tcp,
+ gen_tcp:connect(HostS, S#eldap.port, Opts)
end,
case SocketData of
- {ok, Socket} ->
- case bind_request(Socket, S#eldap{sockmod = SockMod}) of
- {ok, NewS} ->
- Timer = erlang:start_timer(?BIND_TIMEOUT, self(),
- {timeout, bind_timeout}),
- {ok, wait_bind_response, NewS#eldap{fd = Socket,
- sockmod = SockMod,
- host = Host,
- bind_timer = Timer}};
- {error, Reason} ->
- report_bind_failure(Host, S#eldap.port, Reason),
- NewS = close_and_retry(S),
- {ok, connecting, NewS#eldap{host = Host}}
- end;
- {error, Reason} ->
- ?ERROR_MSG("LDAP connection failed:~n"
- "** Server: ~s:~p~n"
- "** Reason: ~p~n"
- "** Socket options: ~p",
- [Host, S#eldap.port, Reason, Opts]),
- NewS = close_and_retry(S),
- {ok, connecting, NewS#eldap{host = Host}}
+ {ok, Socket} ->
+ case bind_request(Socket, S#eldap{sockmod = SockMod}) of
+ {ok, NewS} ->
+ Timer = erlang:start_timer(?BIND_TIMEOUT, self(),
+ {timeout, bind_timeout}),
+ {ok, wait_bind_response,
+ NewS#eldap{fd = Socket, sockmod = SockMod, host = Host,
+ bind_timer = Timer}};
+ {error, Reason} ->
+ report_bind_failure(Host, S#eldap.port, Reason),
+ NewS = close_and_retry(S),
+ {ok, connecting, NewS#eldap{host = Host}}
+ end;
+ {error, Reason} ->
+ ?ERROR_MSG("LDAP connection failed:~n** Server: "
+ "~s:~p~n** Reason: ~p~n** Socket options: ~p",
+ [Host, S#eldap.port, Reason, Opts]),
+ NewS = close_and_retry(S),
+ {ok, connecting, NewS#eldap{host = Host}}
end.
bind_request(Socket, S) ->
Id = bump_id(S),
- Req = #'BindRequest'{version = S#eldap.version,
- name = S#eldap.rootdn,
+ Req = #'BindRequest'{version = S#eldap.version,
+ name = S#eldap.rootdn,
authentication = {simple, S#eldap.passwd}},
- Message = #'LDAPMessage'{messageID = Id,
+ Message = #'LDAPMessage'{messageID = Id,
protocolOp = {bindRequest, Req}},
- ?DEBUG("Bind Request Message:~p~n",[Message]),
- {ok, Bytes} = asn1rt:encode('ELDAPv3', 'LDAPMessage', Message),
+ ?DEBUG("Bind Request Message:~p~n", [Message]),
+ {ok, Bytes} = asn1rt:encode('ELDAPv3', 'LDAPMessage',
+ Message),
case (S#eldap.sockmod):send(Socket, Bytes) of
- ok -> {ok, S#eldap{id = Id}};
- Error -> Error
+ ok -> {ok, S#eldap{id = Id}};
+ Error -> Error
end.
%% Given last tried Server, find next one to try
-next_host(null, [H|_]) -> H; % First time, take first
-next_host(Host, Hosts) -> % Find next in turn
+next_host(undefined, [H | _]) ->
+ H; % First time, take first
+next_host(Host,
+ Hosts) -> % Find next in turn
next_host(Host, Hosts, Hosts).
-next_host(Host, [Host], Hosts) -> hd(Hosts); % Wrap back to first
-next_host(Host, [Host|Tail], _Hosts) -> hd(Tail); % Take next
-next_host(_Host, [], Hosts) -> hd(Hosts); % Never connected before? (shouldn't happen)
-next_host(Host, [_|T], Hosts) -> next_host(Host, T, Hosts).
-
-
%%% --------------------------------------------------------------------
%%% Verify the input data
%%% --------------------------------------------------------------------
-
-v_filter({'and',L}) -> {'and',L};
-v_filter({'or', L}) -> {'or',L};
-v_filter({'not',L}) -> {'not',L};
-v_filter({equalityMatch,AV}) -> {equalityMatch,AV};
-v_filter({greaterOrEqual,AV}) -> {greaterOrEqual,AV};
-v_filter({lessOrEqual,AV}) -> {lessOrEqual,AV};
-v_filter({approxMatch,AV}) -> {approxMatch,AV};
-v_filter({present,A}) -> {present,A};
-v_filter({substrings,S}) when is_record(S,'SubstringFilter') -> {substrings,S};
-v_filter({extensibleMatch, S}) when is_record(S, 'MatchingRuleAssertion') ->
- {extensibleMatch, S};
-v_filter(_Filter) -> throw({error,concat(["unknown filter: ",_Filter])}).
-
-v_modifications(Mods) ->
- F = fun({_,Op,_}) ->
- case lists:member(Op,[add,delete,replace]) of
- true -> true;
- _ -> throw({error,{mod_operation,Op}})
- end
- end,
- lists:foreach(F, Mods).
-
-v_substr([{Key,Str}|T]) when is_list(Str),Key==initial;Key==any;Key==final ->
- [{Key,Str}|v_substr(T)];
-v_substr([H|_]) ->
- throw({error,{substring_arg,H}});
-v_substr([]) ->
- [].
-v_scope(baseObject) -> baseObject;
-v_scope(singleLevel) -> singleLevel;
-v_scope(wholeSubtree) -> wholeSubtree;
-v_scope(_Scope) -> throw({error,concat(["unknown scope: ",_Scope])}).
-
-v_bool(true) -> true;
-v_bool(false) -> false;
-v_bool(_Bool) -> throw({error,concat(["not Boolean: ",_Bool])}).
-
-v_timeout(I) when is_integer(I), I>=0 -> I;
-v_timeout(_I) -> throw({error,concat(["timeout not positive integer: ",_I])}).
-
-v_attributes(Attrs) ->
- F = fun(A) when is_list(A) -> A;
- (A) -> throw({error,concat(["attribute not String: ",A])})
- end,
- lists:map(F,Attrs).
-
-
%%% --------------------------------------------------------------------
%%% Get and Validate the initial configuration
%%% --------------------------------------------------------------------
-get_config() ->
- Priv_dir = code:priv_dir(eldap),
- File = filename:join(Priv_dir, "eldap.conf"),
- case file:consult(File) of
- {ok, Entries} ->
- case catch parse(Entries) of
- {ok, Hosts, Port, Rootdn, Passwd, Opts} ->
- {ok, Hosts, Port, Rootdn, Passwd, Opts};
- {error, Reason} ->
- {error, Reason};
- {'EXIT', Reason} ->
- {error, Reason}
- end;
- {error, Reason} ->
- {error, Reason}
- end.
-
-parse(Entries) ->
- {ok,
- get_hosts(host, Entries),
- get_integer(port, Entries),
- get_list(rootdn, Entries),
- get_list(passwd, Entries),
- get_list(options, Entries)}.
-
-get_integer(Key, List) ->
- case lists:keysearch(Key, 1, List) of
- {value, {Key, Value}} when is_integer(Value) ->
- Value;
- {value, {Key, _Value}} ->
- throw({error, "Bad Value in Config for " ++ atom_to_list(Key)});
- false ->
- throw({error, "No Entry in Config for " ++ atom_to_list(Key)})
- end.
-
-get_list(Key, List) ->
- case lists:keysearch(Key, 1, List) of
- {value, {Key, Value}} when is_list(Value) ->
- Value;
- {value, {Key, _Value}} ->
- throw({error, "Bad Value in Config for " ++ atom_to_list(Key)});
- false ->
- throw({error, "No Entry in Config for " ++ atom_to_list(Key)})
- end.
-
%% get_atom(Key, List) ->
%% case lists:keysearch(Key, 1, List) of
%% {value, {Key, Value}} when is_atom(Value) ->
@@ -1148,25 +1181,19 @@ get_list(Key, List) ->
%% false ->
%% throw({error, "No Entry in Config for " ++ atom_to_list(Key)})
%% end.
-
-get_hosts(Key, List) ->
- lists:map(fun({Key1, {A,B,C,D}}) when is_integer(A),
- is_integer(B),
- is_integer(C),
- is_integer(D),
- Key == Key1->
- {A,B,C,D};
- ({Key1, Value}) when is_list(Value),
- Key == Key1->
- Value;
- ({_Else, _Value}) ->
- throw({error, "Bad Hostname in config"})
- end, List).
-
%%% --------------------------------------------------------------------
%%% Other Stuff
%%% --------------------------------------------------------------------
-bump_id(#eldap{id = Id}) when Id > ?MAX_TRANSACTION_ID ->
+next_host(Host, [Host], Hosts) ->
+ hd(Hosts); % Wrap back to first
+next_host(Host, [Host | Tail], _Hosts) ->
+ hd(Tail); % Take next
+next_host(_Host, [], Hosts) ->
+ hd(Hosts); % Never connected before? (shouldn't happen)
+next_host(Host, [_ | T], Hosts) ->
+ next_host(Host, T, Hosts).
+
+bump_id(#eldap{id = Id})
+ when Id > (?MAX_TRANSACTION_ID) ->
?MIN_TRANSACTION_ID;
-bump_id(#eldap{id = Id}) ->
- Id + 1.
+bump_id(#eldap{id = Id}) -> Id + 1.
diff --git a/src/eldap/eldap.hrl b/src/eldap/eldap.hrl
index 90d794fb8..30ec0e954 100644
--- a/src/eldap/eldap.hrl
+++ b/src/eldap/eldap.hrl
@@ -20,20 +20,45 @@
%%%----------------------------------------------------------------------
-define(LDAP_PORT, 389).
+
-define(LDAPS_PORT, 636).
--record(eldap_search, {scope = wholeSubtree,
- base = [],
- filter,
- limit = 0,
- attributes = [],
- types_only = false,
- deref_aliases = neverDerefAliases,
- timeout = 0}).
+-type scope() :: baseObject | singleLevel | wholeSubtree.
+
+-record(eldap_search,
+ {scope = wholeSubtree :: scope(),
+ base = <<"">> :: binary(),
+ filter :: eldap:filter(),
+ limit = 0 :: non_neg_integer(),
+ attributes = [] :: [binary()],
+ types_only = false :: boolean(),
+ deref_aliases = neverDerefAliases :: neverDerefAliases |
+ derefInSearching |
+ derefFindingBaseObj |
+ derefAlways,
+ timeout = 0 :: non_neg_integer()}).
+
+-record(eldap_search_result, {entries = [] :: [eldap_entry()],
+ referrals = [] :: list()}).
+
+-record(eldap_entry, {object_name = <<>> :: binary(),
+ attributes = [] :: [{binary(), [binary()]}]}).
+-type tlsopts() :: [{encrypt, tls | starttls | none} |
+ {tls_cacertfile, binary() | undefined} |
+ {tls_depth, non_neg_integer() | undefined} |
+ {tls_verify, hard | soft | false}].
--record(eldap_search_result, {entries,
- referrals}).
+-record(eldap_config, {servers = [] :: [binary()],
+ backups = [] :: [binary()],
+ tls_options = [] :: tlsopts(),
+ port = ?LDAP_PORT :: inet:port_number(),
+ dn = <<"">> :: binary(),
+ password = <<"">> :: binary(),
+ base = <<"">> :: binary(),
+ deref_aliases = never :: never | searching |
+ finding | always}).
--record(eldap_entry, {object_name,
- attributes}).
+-type eldap_config() :: #eldap_config{}.
+-type eldap_search() :: #eldap_search{}.
+-type eldap_entry() :: #eldap_entry{}.
diff --git a/src/eldap/eldap_filter.erl b/src/eldap/eldap_filter.erl
index 11a37fe20..6771fc2af 100644
--- a/src/eldap/eldap_filter.erl
+++ b/src/eldap/eldap_filter.erl
@@ -27,8 +27,6 @@
-module(eldap_filter).
%% TODO: remove this when new regexp module will be used
--compile({nowarn_deprecated_function, {regexp, sub, 3}}).
-
-export([parse/1, parse/2, do_sub/2]).
%%====================================================================
@@ -50,7 +48,9 @@
%%% {ok,{'and',[{'not',{lessOrEqual,{'AttributeValueAssertion',"uid","100"}}},
%%% {present,"mail"}]}}
%%%-------------------------------------------------------------------
-parse(L) when is_list(L) ->
+-spec parse(binary()) -> {error, any()} | {ok, eldap:filter()}.
+
+parse(L) ->
parse(L, []).
%%%-------------------------------------------------------------------
@@ -80,8 +80,12 @@ parse(L) when is_list(L) ->
%%% "jid",
%%% "xramtsov@gmail.com"}}]}}
%%%-------------------------------------------------------------------
-parse(L, SList) when is_list(L), is_list(SList) ->
- case catch eldap_filter_yecc:parse(scan(L, SList)) of
+-spec parse(binary(), [{binary(), binary()} |
+ {binary(), binary(), pos_integer()}]) ->
+ {error, any()} | {ok, eldap:filter()}.
+
+parse(L, SList) ->
+ case catch eldap_filter_yecc:parse(scan(binary_to_list(L), SList)) of
{'EXIT', _} = Err ->
{error, Err};
{error, {_, _, Msg}} ->
@@ -95,13 +99,13 @@ parse(L, SList) when is_list(L), is_list(SList) ->
%%====================================================================
%% Internal functions
%%====================================================================
--define(do_scan(L), scan(Rest, [], [{L, 1} | check(Buf, S) ++ Result], L, S)).
+-define(do_scan(L), scan(Rest, <<>>, [{L, 1} | check(Buf, S) ++ Result], L, S)).
scan(L, SList) ->
- scan(L, "", [], undefined, SList).
+ scan(L, <<"">>, [], undefined, SList).
scan("=*)" ++ Rest, Buf, Result, '(', S) ->
- scan(Rest, [], [{')', 1}, {'=*', 1} | check(Buf, S) ++ Result], ')', S);
+ scan(Rest, <<>>, [{')', 1}, {'=*', 1} | check(Buf, S) ++ Result], ')', S);
scan(":dn" ++ Rest, Buf, Result, '(', S) -> ?do_scan(':dn');
scan(":=" ++ Rest, Buf, Result, '(', S) -> ?do_scan(':=');
scan(":=" ++ Rest, Buf, Result, ':dn', S) -> ?do_scan(':=');
@@ -112,35 +116,35 @@ scan("<=" ++ Rest, Buf, Result, '(', S) -> ?do_scan('<=');
scan("=" ++ Rest, Buf, Result, '(', S) -> ?do_scan('=');
scan(":" ++ Rest, Buf, Result, '(', S) -> ?do_scan(':');
scan(":" ++ Rest, Buf, Result, ':dn', S) -> ?do_scan(':');
-scan("&" ++ Rest, Buf, Result, '(', S) when Buf=="" -> ?do_scan('&');
-scan("|" ++ Rest, Buf, Result, '(', S) when Buf=="" -> ?do_scan('|');
-scan("!" ++ Rest, Buf, Result, '(', S) when Buf=="" -> ?do_scan('!');
+scan("&" ++ Rest, Buf, Result, '(', S) when Buf==<<"">> -> ?do_scan('&');
+scan("|" ++ Rest, Buf, Result, '(', S) when Buf==<<"">> -> ?do_scan('|');
+scan("!" ++ Rest, Buf, Result, '(', S) when Buf==<<"">> -> ?do_scan('!');
scan("*" ++ Rest, Buf, Result, '*', S) -> ?do_scan('*');
scan("*" ++ Rest, Buf, Result, '=', S) -> ?do_scan('*');
scan("(" ++ Rest, Buf, Result, _, S) -> ?do_scan('(');
scan(")" ++ Rest, Buf, Result, _, S) -> ?do_scan(')');
scan([Letter | Rest], Buf, Result, PreviosAtom, S) ->
- scan(Rest, [Letter|Buf], Result, PreviosAtom, S);
+ scan(Rest, <<Buf/binary, Letter>>, Result, PreviosAtom, S);
scan([], Buf, Result, _, S) ->
lists:reverse(check(Buf, S) ++ Result).
-check([], _) ->
+check(<<>>, _) ->
[];
check(Buf, S) ->
- [{str, 1, do_sub(lists:reverse(Buf), S)}].
+ [{str, 1, binary_to_list(do_sub(Buf, S))}].
-define(MAX_RECURSION, 100).
+-spec do_sub(binary(), [{binary(), binary()} |
+ {binary(), binary(), pos_integer()}]) -> binary().
+
do_sub(S, []) ->
S;
-
-do_sub([], _) ->
- [];
-
+do_sub(<<>>, _) ->
+ <<>>;
do_sub(S, [{RegExp, New} | T]) ->
Result = do_sub(S, {RegExp, replace_amps(New)}, 1),
do_sub(Result, T);
-
do_sub(S, [{RegExp, New, Times} | T]) ->
Result = do_sub(S, {RegExp, replace_amps(New), Times}, 1),
do_sub(Result, T).
@@ -178,9 +182,10 @@ do_sub(S, {RegExp, New, Times}, Iter) ->
erlang:error(bad_regexp)
end.
-replace_amps(String) ->
- lists:flatmap(
- fun($&) -> "\\&";
- ($\\) -> "\\\\";
- (Chr) -> [Chr]
- end, String).
+replace_amps(Bin) ->
+ list_to_binary(
+ lists:flatmap(
+ fun($&) -> "\\&";
+ ($\\) -> "\\\\";
+ (Chr) -> [Chr]
+ end, binary_to_list(Bin))).
diff --git a/src/eldap/eldap_filter_yecc.yrl b/src/eldap/eldap_filter_yecc.yrl
index a8f7970bf..a70ea3e74 100644
--- a/src/eldap/eldap_filter_yecc.yrl
+++ b/src/eldap/eldap_filter_yecc.yrl
@@ -67,5 +67,5 @@ final(Value) -> {final, Value}.
'any'(Token, Value) -> [Token, {any, Value}].
xattr(Value) -> {type, Value}.
matchingrule(Value) -> {matchingRule, Value}.
-value_of(Token) -> element(3, Token).
+value_of(Token) -> iolist_to_binary(element(3, Token)).
flatten(List) -> lists:flatten(List).
diff --git a/src/eldap/eldap_pool.erl b/src/eldap/eldap_pool.erl
index d256ca0a9..1f52999ef 100644
--- a/src/eldap/eldap_pool.erl
+++ b/src/eldap/eldap_pool.erl
@@ -25,24 +25,15 @@
%%%-------------------------------------------------------------------
-module(eldap_pool).
+
-author('xram@jabber.ru').
%% API
--export([
- start_link/7,
- bind/3,
- search/2,
- modify_passwd/3
- ]).
+-export([start_link/7, bind/3, search/2,
+ modify_passwd/3]).
-include("ejabberd.hrl").
--ifdef(SSL40).
--define(PG2, pg2).
--else.
--define(PG2, pg2_backport).
--endif.
-
%%====================================================================
%% API
%%====================================================================
@@ -55,40 +46,41 @@ search(PoolName, Opts) ->
modify_passwd(PoolName, DN, Passwd) ->
do_request(PoolName, {modify_passwd, [DN, Passwd]}).
-start_link(Name, Hosts, Backups, Port, Rootdn, Passwd, Opts) ->
+start_link(Name, Hosts, Backups, Port, Rootdn, Passwd,
+ Opts) ->
PoolName = make_id(Name),
- ?PG2:create(PoolName),
- lists:foreach(
- fun(Host) ->
- ID = erlang:ref_to_list(make_ref()),
- case catch eldap:start_link(ID, [Host|Backups], Port,
- Rootdn, Passwd, Opts) of
- {ok, Pid} ->
- ?PG2:join(PoolName, Pid);
- _ ->
- error
- end
- end, Hosts).
+ pg2:create(PoolName),
+ lists:foreach(fun (Host) ->
+ ID = list_to_binary(erlang:ref_to_list(make_ref())),
+ case catch eldap:start_link(ID, [Host | Backups],
+ Port, Rootdn, Passwd,
+ Opts)
+ of
+ {ok, Pid} -> pg2:join(PoolName, Pid);
+ Err ->
+ ?INFO_MSG("Err = ~p", [Err]),
+ error
+ end
+ end,
+ Hosts).
%%====================================================================
%% Internal functions
%%====================================================================
do_request(Name, {F, Args}) ->
- case ?PG2:get_closest_pid(make_id(Name)) of
- Pid when is_pid(Pid) ->
- case catch apply(eldap, F, [Pid | Args]) of
- {'EXIT', {timeout, _}} ->
- ?ERROR_MSG("LDAP request failed: timed out", []);
- {'EXIT', Reason} ->
- ?ERROR_MSG("LDAP request failed: eldap:~p(~p)~nReason: ~p",
- [F, Args, Reason]),
- {error, Reason};
- Reply ->
- Reply
- end;
- Err ->
- Err
+ case pg2:get_closest_pid(make_id(Name)) of
+ Pid when is_pid(Pid) ->
+ case catch apply(eldap, F, [Pid | Args]) of
+ {'EXIT', {timeout, _}} ->
+ ?ERROR_MSG("LDAP request failed: timed out", []);
+ {'EXIT', Reason} ->
+ ?ERROR_MSG("LDAP request failed: eldap:~p(~p)~nReason: ~p",
+ [F, Args, Reason]),
+ {error, Reason};
+ Reply -> Reply
+ end;
+ Err -> Err
end.
make_id(Name) ->
- list_to_atom("eldap_pool_" ++ Name).
+ jlib:binary_to_atom(<<"eldap_pool_", Name/binary>>).
diff --git a/src/eldap/eldap_utils.erl b/src/eldap/eldap_utils.erl
index 6c1c43e90..2e149d8b6 100644
--- a/src/eldap/eldap_utils.erl
+++ b/src/eldap/eldap_utils.erl
@@ -30,15 +30,18 @@
-export([generate_subfilter/1,
find_ldap_attrs/2,
get_ldap_attr/2,
- usort_attrs/1,
get_user_part/2,
make_filter/2,
get_state/2,
case_insensitive_match/2,
- check_filter/1,
+ get_opt/3,
+ get_opt/4,
+ get_config/2,
+ decode_octet_string/3,
uids_domain_subst/2]).
-include("ejabberd.hrl").
+-include("eldap.hrl").
%% Generate an 'or' LDAP query on one or several attributes
%% If there is only one attribute
@@ -46,27 +49,34 @@ generate_subfilter([UID]) ->
subfilter(UID);
%% If there is several attributes
generate_subfilter(UIDs) ->
- "(|" ++ [subfilter(UID) || UID <- UIDs] ++ ")".
+ iolist_to_binary(["(|", [subfilter(UID) || UID <- UIDs], ")"]).
%% Subfilter for a single attribute
+
subfilter({UIDAttr, UIDAttrFormat}) ->
- "(" ++ UIDAttr ++ "=" ++ UIDAttrFormat ++ ")";
%% The default UiDAttrFormat is %u
+ <<$(, UIDAttr/binary, $=, UIDAttrFormat/binary, $)>>;
+%% The default UiDAttrFormat is <<"%u">>
subfilter({UIDAttr}) ->
- "(" ++ UIDAttr ++ "=" ++ "%u)".
+ <<$(, UIDAttr/binary, $=, "%u)">>.
%% Not tail-recursive, but it is not very terribly.
%% It stops finding on the first not empty value.
+-spec find_ldap_attrs([{binary()} | {binary(), binary()}],
+ [{binary(), [binary()]}]) -> <<>> | {binary(), binary()}.
+
find_ldap_attrs([{Attr} | Rest], Attributes) ->
- find_ldap_attrs([{Attr, "%u"} | Rest], Attributes);
+ find_ldap_attrs([{Attr, <<"%u">>} | Rest], Attributes);
find_ldap_attrs([{Attr, Format} | Rest], Attributes) ->
case get_ldap_attr(Attr, Attributes) of
- Value when is_list(Value), Value /= "" ->
+ Value when is_binary(Value), Value /= <<>> ->
{Value, Format};
_ ->
find_ldap_attrs(Rest, Attributes)
end;
find_ldap_attrs([], _) ->
- "".
+ <<>>.
+
+-spec get_ldap_attr(binary(), [{binary(), [binary()]}]) -> binary().
get_ldap_attr(LDAPAttr, Attributes) ->
Res = lists:filter(
@@ -75,31 +85,26 @@ get_ldap_attr(LDAPAttr, Attributes) ->
end, Attributes),
case Res of
[{_, [Value|_]}] -> Value;
- _ -> ""
+ _ -> <<>>
end.
-
-usort_attrs(Attrs) when is_list(Attrs) ->
- lists:usort(Attrs);
-usort_attrs(_) ->
- [].
+-spec get_user_part(binary(), binary()) -> {ok, binary()} | {error, badmatch}.
get_user_part(String, Pattern) ->
F = fun(S, P) ->
- First = string:str(P, "%u"),
- TailLength = length(P) - (First+1),
- string:sub_string(S, First, length(S) - TailLength)
+ First = str:str(P, <<"%u">>),
+ TailLength = byte_size(P) - (First+1),
+ str:sub_string(S, First, byte_size(S) - TailLength)
end,
case catch F(String, Pattern) of
{'EXIT', _} ->
{error, badmatch};
Result ->
- case catch ejabberd_regexp:replace(Pattern, "%u", Result) of
+ case catch ejabberd_regexp:replace(Pattern, <<"%u">>, Result) of
{'EXIT', _} ->
{error, badmatch};
StringRes ->
- case (string:to_lower(StringRes) ==
- string:to_lower(String)) of
+ case case_insensitive_match(StringRes, String) of
true ->
{ok, Result};
false ->
@@ -108,20 +113,25 @@ get_user_part(String, Pattern) ->
end
end.
+-spec make_filter([{binary(), [binary()]}], [{binary(), binary()}]) -> any().
+
make_filter(Data, UIDs) ->
- NewUIDs = [{U, eldap_filter:do_sub(UF, [{"%u", "*%u*", 1}])} || {U, UF} <- UIDs],
+ NewUIDs = [{U, eldap_filter:do_sub(
+ UF, [{<<"%u">>, <<"*%u*">>, 1}])} || {U, UF} <- UIDs],
Filter = lists:flatmap(
fun({Name, [Value | _]}) ->
case Name of
- "%u" when Value /= "" ->
+ <<"%u">> when Value /= <<"">> ->
case eldap_filter:parse(
- lists:flatten(generate_subfilter(NewUIDs)),
- [{"%u", Value}]) of
+ generate_subfilter(NewUIDs),
+ [{<<"%u">>, Value}]) of
{ok, F} -> [F];
_ -> []
end;
- _ when Value /= "" ->
- [eldap:substrings(Name, [{any, Value}])];
+ _ when Value /= <<"">> ->
+ [eldap:substrings(
+ Name,
+ [{any, Value}])];
_ ->
[]
end
@@ -133,9 +143,11 @@ make_filter(Data, UIDs) ->
eldap:'and'(Filter)
end.
+-spec case_insensitive_match(binary(), binary()) -> boolean().
+
case_insensitive_match(X, Y) ->
- X1 = stringprep:tolower(X),
- Y1 = stringprep:tolower(Y),
+ X1 = str:to_lower(X),
+ Y1 = str:to_lower(Y),
if
X1 == Y1 -> true;
true -> false
@@ -149,22 +161,194 @@ get_state(Server, Module) ->
%% we look from alias domain (%d) and make the substitution
%% with the actual host domain
%% This help when you need to configure many virtual domains.
+-spec uids_domain_subst(binary(), [{binary(), binary()}]) ->
+ [{binary(), binary()}].
+
uids_domain_subst(Host, UIDs) ->
lists:map(fun({U,V}) ->
- {U, eldap_filter:do_sub(V,[{"%d", Host}])};
+ {U, eldap_filter:do_sub(V,[{<<"%d">>, Host}])};
(A) -> A
end,
UIDs).
-check_filter(undefined) ->
- ok;
-check_filter(Filter) ->
- case eldap_filter:parse(Filter) of
- {ok, _} ->
- ok;
- Err ->
- ?ERROR_MSG("failed to parse LDAP filter:~n"
- "** Filter: ~p~n"
- "** Reason: ~p",
- [Filter, Err])
+-spec get_opt({atom(), binary()}, list(), fun()) -> any().
+
+get_opt({Key, Host}, Opts, F) ->
+ get_opt({Key, Host}, Opts, F, undefined).
+
+-spec get_opt({atom(), binary()}, list(), fun(), any()) -> any().
+
+get_opt({Key, Host}, Opts, F, Default) ->
+ case gen_mod:get_opt(Key, Opts, F, undefined) of
+ undefined ->
+ ejabberd_config:get_local_option(
+ {Key, Host}, F, Default);
+ Val ->
+ Val
end.
+
+-spec get_config(binary(), list()) -> eldap_config().
+
+get_config(Host, Opts) ->
+ Servers = get_opt({ldap_servers, Host}, Opts,
+ fun(L) ->
+ [iolist_to_binary(H) || H <- L]
+ end, [<<"localhost">>]),
+ Backups = get_opt({ldap_backups, Host}, Opts,
+ fun(L) ->
+ [iolist_to_binary(H) || H <- L]
+ end, []),
+ Encrypt = get_opt({ldap_encrypt, Host}, Opts,
+ fun(tls) -> tls;
+ (starttls) -> starttls;
+ (none) -> none
+ end, none),
+ TLSVerify = get_opt({ldap_tls_verify, Host}, Opts,
+ fun(hard) -> hard;
+ (soft) -> soft;
+ (false) -> false
+ end, false),
+ TLSCAFile = get_opt({ldap_tls_cacertfile, Host}, Opts,
+ fun iolist_to_binary/1),
+ TLSDepth = get_opt({ldap_tls_depth, Host}, Opts,
+ fun(I) when is_integer(I), I>=0 -> I end),
+ Port = get_opt({ldap_port, Host}, Opts,
+ fun(I) when is_integer(I), I>0 -> I end,
+ case Encrypt of
+ tls -> ?LDAPS_PORT;
+ starttls -> ?LDAP_PORT;
+ _ -> ?LDAP_PORT
+ end),
+ RootDN = get_opt({ldap_rootdn, Host}, Opts,
+ fun iolist_to_binary/1,
+ <<"">>),
+ Password = get_opt({ldap_password, Host}, Opts,
+ fun iolist_to_binary/1,
+ <<"">>),
+ Base = get_opt({ldap_base, Host}, Opts,
+ fun iolist_to_binary/1,
+ <<"">>),
+ DerefAliases = get_opt({deref_aliases, Host}, Opts,
+ fun(never) -> never;
+ (searching) -> searching;
+ (finding) -> finding;
+ (always) -> always
+ end, never),
+ #eldap_config{servers = Servers,
+ backups = Backups,
+ tls_options = [{encrypt, Encrypt},
+ {tls_verify, TLSVerify},
+ {tls_cacertfile, TLSCAFile},
+ {tls_depth, TLSDepth}],
+ port = Port,
+ dn = RootDN,
+ password = Password,
+ base = Base,
+ deref_aliases = DerefAliases}.
+
+%%----------------------------------------
+%% Borrowed from asn1rt_ber_bin_v2.erl
+%%----------------------------------------
+
+%%% The tag-number for universal types
+-define(N_BOOLEAN, 1).
+-define(N_INTEGER, 2).
+-define(N_BIT_STRING, 3).
+-define(N_OCTET_STRING, 4).
+-define(N_NULL, 5).
+-define(N_OBJECT_IDENTIFIER, 6).
+-define(N_OBJECT_DESCRIPTOR, 7).
+-define(N_EXTERNAL, 8).
+-define(N_REAL, 9).
+-define(N_ENUMERATED, 10).
+-define(N_EMBEDDED_PDV, 11).
+-define(N_SEQUENCE, 16).
+-define(N_SET, 17).
+-define(N_NumericString, 18).
+-define(N_PrintableString, 19).
+-define(N_TeletexString, 20).
+-define(N_VideotexString, 21).
+-define(N_IA5String, 22).
+-define(N_UTCTime, 23).
+-define(N_GeneralizedTime, 24).
+-define(N_GraphicString, 25).
+-define(N_VisibleString, 26).
+-define(N_GeneralString, 27).
+-define(N_UniversalString, 28).
+-define(N_BMPString, 30).
+
+decode_octet_string(Buffer, Range, Tags) ->
+% NewTags = new_tags(HasTag,#tag{class=?UNIVERSAL,number=?N_OCTET_STRING}),
+ decode_restricted_string(Buffer, Range, Tags).
+
+decode_restricted_string(Tlv, Range, TagsIn) ->
+ Val = match_tags(Tlv, TagsIn),
+ Val2 =
+ case Val of
+ PartList = [_H|_T] -> % constructed val
+ collect_parts(PartList);
+ Bin ->
+ Bin
+ end,
+ check_and_convert_restricted_string(Val2, Range).
+
+check_and_convert_restricted_string(Val, Range) ->
+ {StrLen,NewVal} = if is_binary(Val) ->
+ {size(Val), Val};
+ true ->
+ {length(Val), list_to_binary(Val)}
+ end,
+ case Range of
+ [] -> % No length constraint
+ NewVal;
+ {Lb,Ub} when StrLen >= Lb, Ub >= StrLen -> % variable length constraint
+ NewVal;
+ {{Lb,_Ub},[]} when StrLen >= Lb ->
+ NewVal;
+ {{Lb,_Ub},_Ext=[Min|_]} when StrLen >= Lb; StrLen >= Min ->
+ NewVal;
+ {{Lb1,Ub1},{Lb2,Ub2}} when StrLen >= Lb1, StrLen =< Ub1;
+ StrLen =< Ub2, StrLen >= Lb2 ->
+ NewVal;
+ StrLen -> % fixed length constraint
+ NewVal;
+ {_,_} ->
+ exit({error,{asn1,{length,Range,Val}}});
+ _Len when is_integer(_Len) ->
+ exit({error,{asn1,{length,Range,Val}}});
+ _ -> % some strange constraint that we don't support yet
+ NewVal
+ end.
+
+%%----------------------------------------
+%% Decode the in buffer to bits
+%%----------------------------------------
+match_tags({T,V},[T]) ->
+ V;
+match_tags({T,V}, [T|Tt]) ->
+ match_tags(V,Tt);
+match_tags([{T,V}],[T|Tt]) ->
+ match_tags(V, Tt);
+match_tags(Vlist = [{T,_V}|_], [T]) ->
+ Vlist;
+match_tags(Tlv, []) ->
+ Tlv;
+match_tags({Tag,_V},[T|_Tt]) ->
+ {error,{asn1,{wrong_tag,{Tag,T}}}}.
+
+collect_parts(TlvList) ->
+ collect_parts(TlvList,[]).
+
+collect_parts([{_,L}|Rest],Acc) when is_list(L) ->
+ collect_parts(Rest,[collect_parts(L)|Acc]);
+collect_parts([{?N_BIT_STRING,<<Unused,Bits/binary>>}|Rest],_Acc) ->
+ collect_parts_bit(Rest,[Bits],Unused);
+collect_parts([{_T,V}|Rest],Acc) ->
+ collect_parts(Rest,[V|Acc]);
+collect_parts([],Acc) ->
+ list_to_binary(lists:reverse(Acc)).
+
+collect_parts_bit([{?N_BIT_STRING,<<Unused,Bits/binary>>}|Rest],Acc,Uacc) ->
+ collect_parts_bit(Rest,[Bits|Acc],Unused+Uacc);
+collect_parts_bit([],Acc,Uacc) ->
+ list_to_binary([Uacc|lists:reverse(Acc)]).
diff --git a/src/expat_erl.c b/src/expat_erl.c
index b2462243c..02d41ed33 100644
--- a/src/expat_erl.c
+++ b/src/expat_erl.c
@@ -70,13 +70,13 @@ void encode_name(const XML_Char *name)
memcpy(buf, prefix_start+1, prefix_len);
memcpy(buf+prefix_len, name_start, name_len);
buf[prefix_len] = ':';
- ei_x_encode_string_len(&event_buf, buf, buf_len);
+ ei_x_encode_binary(&event_buf, buf, buf_len);
driver_free(buf);
} else {
- ei_x_encode_string(&event_buf, name_start+1);
+ ei_x_encode_binary(&event_buf, name_start+1, strlen(name_start+1));
};
} else {
- ei_x_encode_string(&event_buf, name);
+ ei_x_encode_binary(&event_buf, name, strlen(name));
}
}
@@ -105,7 +105,7 @@ void *erlXML_StartElementHandler(expat_data *d,
{
ei_x_encode_tuple_header(&event_buf, 2);
encode_name(atts[i]);
- ei_x_encode_string(&event_buf, atts[i+1]);
+ ei_x_encode_binary(&event_buf, atts[i+1], strlen(atts[i+1]));
}
}
@@ -159,12 +159,12 @@ void *erlXML_StartNamespaceDeclHandler(expat_data *d,
buf = driver_alloc(7 + prefix_len);
strcpy(buf, "xmlns:");
strcpy(buf+6, prefix);
- ei_x_encode_string(&xmlns_buf, buf);
+ ei_x_encode_binary(&xmlns_buf, buf, strlen(buf));
driver_free(buf);
} else {
- ei_x_encode_string(&xmlns_buf, "xmlns");
+ ei_x_encode_binary(&xmlns_buf, "xmlns", strlen("xmlns"));
};
- ei_x_encode_string(&xmlns_buf, uri);
+ ei_x_encode_binary(&xmlns_buf, uri, strlen(uri));
return NULL;
}
@@ -229,7 +229,7 @@ static ErlDrvSSizeT expat_erl_control(ErlDrvData drv_data,
ei_x_encode_long(&event_buf, XML_ERROR);
ei_x_encode_tuple_header(&event_buf, 2);
ei_x_encode_long(&event_buf, errcode);
- ei_x_encode_string(&event_buf, errstring);
+ ei_x_encode_binary(&event_buf, errstring, strlen(errstring));
}
ei_x_encode_empty_list(&event_buf);
diff --git a/src/extauth.erl b/src/extauth.erl
index 32398f7e5..6a7f8d7b9 100644
--- a/src/extauth.erl
+++ b/src/extauth.erl
@@ -25,30 +25,24 @@
%%%----------------------------------------------------------------------
-module(extauth).
+
-author('leifj@it.su.se').
--export([start/2,
- stop/1,
- init/2,
- check_password/3,
- set_password/3,
- try_register/3,
- remove_user/2,
- remove_user/3,
- is_user_exists/2]).
+-export([start/2, stop/1, init/2, check_password/3,
+ set_password/3, try_register/3, remove_user/2,
+ remove_user/3, is_user_exists/2]).
-include("ejabberd.hrl").
--define(INIT_TIMEOUT, 60000). % Timeout is in milliseconds: 60 seconds == 60000
--define(CALL_TIMEOUT, 10000). % Timeout is in milliseconds: 10 seconds == 10000
+-define(INIT_TIMEOUT, 60000).
+
+-define(CALL_TIMEOUT, 10000).
start(Host, ExtPrg) ->
- lists:foreach(
- fun(This) ->
- start_instance(get_process_name(Host, This), ExtPrg)
- end,
- lists:seq(0, get_instances(Host)-1)
- ).
+ lists:foreach(fun (This) ->
+ start_instance(get_process_name(Host, This), ExtPrg)
+ end,
+ lists:seq(0, get_instances(Host) - 1)).
start_instance(ProcessName, ExtPrg) ->
spawn(?MODULE, init, [ProcessName, ExtPrg]).
@@ -59,20 +53,20 @@ restart_instance(ProcessName, ExtPrg) ->
init(ProcessName, ExtPrg) ->
register(ProcessName, self()),
- process_flag(trap_exit,true),
- Port = open_port({spawn, ExtPrg}, [{packet,2}]),
+ process_flag(trap_exit, true),
+ Port = open_port({spawn, ExtPrg}, [{packet, 2}]),
loop(Port, ?INIT_TIMEOUT, ProcessName, ExtPrg).
stop(Host) ->
- lists:foreach(
- fun(This) ->
- get_process_name(Host, This) ! stop
- end,
- lists:seq(0, get_instances(Host)-1)
- ).
+ lists:foreach(fun (This) ->
+ get_process_name(Host, This) ! stop
+ end,
+ lists:seq(0, get_instances(Host) - 1)).
get_process_name(Host, Integer) ->
- gen_mod:get_module_proc(lists:append([Host, integer_to_list(Integer)]), eauth).
+ gen_mod:get_module_proc(iolist_to_binary([Host,
+ integer_to_list(Integer)]),
+ eauth).
check_password(User, Server, Password) ->
call_port(Server, ["auth", User, Server, Password]).
@@ -84,90 +78,88 @@ set_password(User, Server, Password) ->
call_port(Server, ["setpass", User, Server, Password]).
try_register(User, Server, Password) ->
- case call_port(Server, ["tryregister", User, Server, Password]) of
- true -> {atomic, ok};
- false -> {error, not_allowed}
+ case call_port(Server,
+ ["tryregister", User, Server, Password])
+ of
+ true -> {atomic, ok};
+ false -> {error, not_allowed}
end.
remove_user(User, Server) ->
call_port(Server, ["removeuser", User, Server]).
remove_user(User, Server, Password) ->
- call_port(Server, ["removeuser3", User, Server, Password]).
+ call_port(Server,
+ ["removeuser3", User, Server, Password]).
call_port(Server, Msg) ->
LServer = jlib:nameprep(Server),
- ProcessName = get_process_name(LServer, random_instance(get_instances(LServer))),
+ ProcessName = get_process_name(LServer,
+ random_instance(get_instances(LServer))),
ProcessName ! {call, self(), Msg},
- receive
- {eauth,Result} ->
- Result
- end.
+ receive {eauth, Result} -> Result end.
random_instance(MaxNum) ->
- {A1,A2,A3} = now(),
+ {A1, A2, A3} = now(),
random:seed(A1, A2, A3),
random:uniform(MaxNum) - 1.
get_instances(Server) ->
- case ejabberd_config:get_local_option({extauth_instances, Server}) of
- Num when is_integer(Num) -> Num;
- _ -> 1
- end.
+ ejabberd_config:get_local_option(
+ {extauth_instances, Server},
+ fun(V) when is_integer(V), V > 0 ->
+ V
+ end, 1).
loop(Port, Timeout, ProcessName, ExtPrg) ->
receive
- {call, Caller, Msg} ->
- port_command(Port, encode(Msg)),
- receive
- {Port, {data, Data}} ->
- ?DEBUG("extauth call '~p' received data response:~n~p", [Msg, Data]),
- Caller ! {eauth, decode(Data)},
- loop(Port, ?CALL_TIMEOUT, ProcessName, ExtPrg);
- {Port, Other} ->
- ?ERROR_MSG("extauth call '~p' received strange response:~n~p", [Msg, Other]),
- Caller ! {eauth, false},
- loop(Port, ?CALL_TIMEOUT, ProcessName, ExtPrg)
- after
- Timeout ->
- ?ERROR_MSG("extauth call '~p' didn't receive response", [Msg]),
- Caller ! {eauth, false},
- Pid = restart_instance(ProcessName, ExtPrg),
- flush_buffer_and_forward_messages(Pid),
- exit(port_terminated)
- end;
- stop ->
- Port ! {self(), close},
- receive
- {Port, closed} ->
- exit(normal)
- end;
- {'EXIT', Port, Reason} ->
- ?CRITICAL_MSG("extauth script has exitted abruptly with reason '~p'", [Reason]),
- Pid = restart_instance(ProcessName, ExtPrg),
- flush_buffer_and_forward_messages(Pid),
- exit(port_terminated)
+ {call, Caller, Msg} ->
+ port_command(Port, encode(Msg)),
+ receive
+ {Port, {data, Data}} ->
+ ?DEBUG("extauth call '~p' received data response:~n~p",
+ [Msg, Data]),
+ Caller ! {eauth, decode(Data)},
+ loop(Port, ?CALL_TIMEOUT, ProcessName, ExtPrg);
+ {Port, Other} ->
+ ?ERROR_MSG("extauth call '~p' received strange response:~n~p",
+ [Msg, Other]),
+ Caller ! {eauth, false},
+ loop(Port, ?CALL_TIMEOUT, ProcessName, ExtPrg)
+ after Timeout ->
+ ?ERROR_MSG("extauth call '~p' didn't receive response",
+ [Msg]),
+ Caller ! {eauth, false},
+ Pid = restart_instance(ProcessName, ExtPrg),
+ flush_buffer_and_forward_messages(Pid),
+ exit(port_terminated)
+ end;
+ stop ->
+ Port ! {self(), close},
+ receive {Port, closed} -> exit(normal) end;
+ {'EXIT', Port, Reason} ->
+ ?CRITICAL_MSG("extauth script has exitted abruptly "
+ "with reason '~p'",
+ [Reason]),
+ Pid = restart_instance(ProcessName, ExtPrg),
+ flush_buffer_and_forward_messages(Pid),
+ exit(port_terminated)
end.
flush_buffer_and_forward_messages(Pid) ->
receive
- Message ->
- Pid ! Message,
- flush_buffer_and_forward_messages(Pid)
- after 0 ->
- true
+ Message ->
+ Pid ! Message, flush_buffer_and_forward_messages(Pid)
+ after 0 -> true
end.
join(List, Sep) ->
- lists:foldl(fun(A, "") -> A;
- (A, Acc) -> Acc ++ Sep ++ A
- end, "", List).
-
-encode(L) ->
- join(L,":").
+ lists:foldl(fun (A, "") -> A;
+ (A, Acc) -> Acc ++ Sep ++ A
+ end,
+ "", List).
-decode([0,0]) ->
- false;
-decode([0,1]) ->
- true.
+encode(L) -> join(L, ":").
+decode([0, 0]) -> false;
+decode([0, 1]) -> true.
diff --git a/src/gen_iq_handler.erl b/src/gen_iq_handler.erl
index e04f53afe..20efee7bc 100644
--- a/src/gen_iq_handler.erl
+++ b/src/gen_iq_handler.erl
@@ -25,27 +25,23 @@
%%%----------------------------------------------------------------------
-module(gen_iq_handler).
+
-author('alexey@process-one.net').
-behaviour(gen_server).
%% API
--export([start_link/3,
- add_iq_handler/6,
- remove_iq_handler/3,
- stop_iq_handler/3,
- handle/7,
+-export([start_link/3, add_iq_handler/6,
+ remove_iq_handler/3, stop_iq_handler/3, handle/7,
process_iq/6]).
%% gen_server callbacks
--export([init/1, handle_call/3, handle_cast/2, handle_info/2,
- terminate/2, code_change/3]).
+-export([init/1, handle_call/3, handle_cast/2,
+ handle_info/2, terminate/2, code_change/3]).
-include("ejabberd.hrl").
--record(state, {host,
- module,
- function}).
+-record(state, {host, module, function}).
%%====================================================================
%% API
@@ -55,30 +51,34 @@
%% Description: Starts the server
%%--------------------------------------------------------------------
start_link(Host, Module, Function) ->
- gen_server:start_link(?MODULE, [Host, Module, Function], []).
+ gen_server:start_link(?MODULE, [Host, Module, Function],
+ []).
-add_iq_handler(Component, Host, NS, Module, Function, Type) ->
+add_iq_handler(Component, Host, NS, Module, Function,
+ Type) ->
case Type of
- no_queue ->
- Component:register_iq_handler(Host, NS, Module, Function, no_queue);
- one_queue ->
- {ok, Pid} = supervisor:start_child(ejabberd_iq_sup,
- [Host, Module, Function]),
- Component:register_iq_handler(Host, NS, Module, Function,
- {one_queue, Pid});
- {queues, N} ->
- Pids =
- lists:map(
- fun(_) ->
- {ok, Pid} = supervisor:start_child(
- ejabberd_iq_sup,
- [Host, Module, Function]),
- Pid
- end, lists:seq(1, N)),
- Component:register_iq_handler(Host, NS, Module, Function,
- {queues, Pids});
- parallel ->
- Component:register_iq_handler(Host, NS, Module, Function, parallel)
+ no_queue ->
+ Component:register_iq_handler(Host, NS, Module,
+ Function, no_queue);
+ one_queue ->
+ {ok, Pid} = supervisor:start_child(ejabberd_iq_sup,
+ [Host, Module, Function]),
+ Component:register_iq_handler(Host, NS, Module,
+ Function, {one_queue, Pid});
+ {queues, N} ->
+ Pids = lists:map(fun (_) ->
+ {ok, Pid} =
+ supervisor:start_child(ejabberd_iq_sup,
+ [Host, Module,
+ Function]),
+ Pid
+ end,
+ lists:seq(1, N)),
+ Component:register_iq_handler(Host, NS, Module,
+ Function, {queues, Pids});
+ parallel ->
+ Component:register_iq_handler(Host, NS, Module,
+ Function, parallel)
end.
remove_iq_handler(Component, Host, NS) ->
@@ -86,44 +86,38 @@ remove_iq_handler(Component, Host, NS) ->
stop_iq_handler(_Module, _Function, Opts) ->
case Opts of
- {one_queue, Pid} ->
- gen_server:call(Pid, stop);
- {queues, Pids} ->
- lists:foreach(fun(Pid) ->
- catch gen_server:call(Pid, stop)
- end, Pids);
- _ ->
- ok
+ {one_queue, Pid} -> gen_server:call(Pid, stop);
+ {queues, Pids} ->
+ lists:foreach(fun (Pid) ->
+ catch gen_server:call(Pid, stop)
+ end,
+ Pids);
+ _ -> ok
end.
handle(Host, Module, Function, Opts, From, To, IQ) ->
case Opts of
- no_queue ->
- process_iq(Host, Module, Function, From, To, IQ);
- {one_queue, Pid} ->
- Pid ! {process_iq, From, To, IQ};
- {queues, Pids} ->
- Pid = lists:nth(erlang:phash(now(), length(Pids)), Pids),
- Pid ! {process_iq, From, To, IQ};
- parallel ->
- spawn(?MODULE, process_iq, [Host, Module, Function, From, To, IQ]);
- _ ->
- todo
+ no_queue ->
+ process_iq(Host, Module, Function, From, To, IQ);
+ {one_queue, Pid} -> Pid ! {process_iq, From, To, IQ};
+ {queues, Pids} ->
+ Pid = lists:nth(erlang:phash(now(), str:len(Pids)),
+ Pids),
+ Pid ! {process_iq, From, To, IQ};
+ parallel ->
+ spawn(?MODULE, process_iq,
+ [Host, Module, Function, From, To, IQ]);
+ _ -> todo
end.
-
process_iq(_Host, Module, Function, From, To, IQ) ->
case catch Module:Function(From, To, IQ) of
- {'EXIT', Reason} ->
- ?ERROR_MSG("~p", [Reason]);
- ResIQ ->
- if
- ResIQ /= ignore ->
- ejabberd_router:route(To, From,
- jlib:iq_to_xml(ResIQ));
- true ->
- ok
- end
+ {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]);
+ ResIQ ->
+ if ResIQ /= ignore ->
+ ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ));
+ true -> ok
+ end
end.
%%====================================================================
@@ -138,9 +132,9 @@ process_iq(_Host, Module, Function, From, To, IQ) ->
%% Description: Initiates the server
%%--------------------------------------------------------------------
init([Host, Module, Function]) ->
- {ok, #state{host = Host,
- module = Module,
- function = Function}}.
+ {ok,
+ #state{host = Host, module = Module,
+ function = Function}}.
%%--------------------------------------------------------------------
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
@@ -152,8 +146,7 @@ init([Host, Module, Function]) ->
%% Description: Handling call messages
%%--------------------------------------------------------------------
handle_call(stop, _From, State) ->
- Reply = ok,
- {stop, normal, Reply, State}.
+ Reply = ok, {stop, normal, Reply, State}.
%%--------------------------------------------------------------------
%% Function: handle_cast(Msg, State) -> {noreply, State} |
@@ -161,8 +154,7 @@ handle_call(stop, _From, State) ->
%% {stop, Reason, State}
%% Description: Handling cast messages
%%--------------------------------------------------------------------
-handle_cast(_Msg, State) ->
- {noreply, State}.
+handle_cast(_Msg, State) -> {noreply, State}.
%%--------------------------------------------------------------------
%% Function: handle_info(Info, State) -> {noreply, State} |
@@ -171,13 +163,12 @@ handle_cast(_Msg, State) ->
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------
handle_info({process_iq, From, To, IQ},
- #state{host = Host,
- module = Module,
- function = Function} = State) ->
+ #state{host = Host, module = Module,
+ function = Function} =
+ State) ->
process_iq(Host, Module, Function, From, To, IQ),
{noreply, State};
-handle_info(_Info, State) ->
- {noreply, State}.
+handle_info(_Info, State) -> {noreply, State}.
%%--------------------------------------------------------------------
%% Function: terminate(Reason, State) -> void()
@@ -186,16 +177,15 @@ handle_info(_Info, State) ->
%% cleaning up. When it returns, the gen_server terminates with Reason.
%% The return value is ignored.
%%--------------------------------------------------------------------
-terminate(_Reason, _State) ->
- ok.
+terminate(_Reason, _State) -> ok.
%%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
%% Description: Convert process state when code is changed
%%--------------------------------------------------------------------
-code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
+code_change(_OldVsn, State, _Extra) -> {ok, State}.
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
+
diff --git a/src/gen_mod.erl b/src/gen_mod.erl
index c9fdceb15..f824ff740 100644
--- a/src/gen_mod.erl
+++ b/src/gen_mod.erl
@@ -2,6 +2,7 @@
%%% File : gen_mod.erl
%%% Author : Alexey Shchepin <alexey@process-one.net>
%%% Purpose :
+%%% Purpose :
%%% Created : 24 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
@@ -25,78 +26,81 @@
%%%----------------------------------------------------------------------
-module(gen_mod).
+
-author('alexey@process-one.net').
--export([start/0,
- start_module/3,
- stop_module/2,
- stop_module_keep_config/2,
- get_opt/2,
- get_opt/3,
- get_opt_host/3,
- db_type/1,
- db_type/2,
- get_module_opt/4,
- get_module_opt_host/3,
- loaded_modules/1,
- loaded_modules_with_opts/1,
- get_hosts/2,
- get_module_proc/2,
- is_loaded/2]).
-
--export([behaviour_info/1]).
+-export([start/0, start_module/3, stop_module/2,
+ stop_module_keep_config/2, get_opt/3, get_opt/4,
+ get_opt_host/3, db_type/1, db_type/2, get_module_opt/5,
+ get_module_opt_host/3, loaded_modules/1,
+ loaded_modules_with_opts/1, get_hosts/2,
+ get_module_proc/2, is_loaded/2]).
+
+%%-export([behaviour_info/1]).
-include("ejabberd.hrl").
--record(ejabberd_module, {module_host, opts}).
+-record(ejabberd_module,
+ {module_host = {undefined, <<"">>} :: {atom(), binary()},
+ opts = [] :: opts() | '_' | '$2'}).
+
+-type opts() :: [{atom(), any()}].
+
+-callback start(binary(), opts()) -> any().
+-callback stop(binary()) -> any().
-behaviour_info(callbacks) ->
- [{start, 2},
- {stop, 1}];
-behaviour_info(_Other) ->
- undefined.
+-export_type([opts/0]).
+
+%%behaviour_info(callbacks) -> [{start, 2}, {stop, 1}];
+%%behaviour_info(_Other) -> undefined.
start() ->
- ets:new(ejabberd_modules, [named_table,
- public,
- {keypos, #ejabberd_module.module_host}]),
+ ets:new(ejabberd_modules,
+ [named_table, public,
+ {keypos, #ejabberd_module.module_host}]),
ok.
+-spec start_module(binary(), atom(), opts()) -> any().
start_module(Host, Module, Opts) ->
set_module_opts_mnesia(Host, Module, Opts),
ets:insert(ejabberd_modules,
#ejabberd_module{module_host = {Module, Host},
opts = Opts}),
- try Module:start(Host, Opts)
- catch Class:Reason ->
- del_module_mnesia(Host, Module),
- ets:delete(ejabberd_modules, {Module, Host}),
- ErrorText = io_lib:format("Problem starting the module ~p for host ~p ~n options: ~p~n ~p: ~p",
- [Module, Host, Opts, Class, Reason]),
- ?CRITICAL_MSG(ErrorText, []),
- case is_app_running(ejabberd) of
- true ->
- erlang:raise(Class, Reason, erlang:get_stacktrace());
- false ->
- ?CRITICAL_MSG("ejabberd initialization was aborted because a module start failed.", []),
- timer:sleep(3000),
- erlang:halt(string:substr(lists:flatten(ErrorText), 1, 199))
- end
+ try Module:start(Host, Opts) catch
+ Class:Reason ->
+ del_module_mnesia(Host, Module),
+ ets:delete(ejabberd_modules, {Module, Host}),
+ ErrorText =
+ io_lib:format("Problem starting the module ~p for host "
+ "~p ~n options: ~p~n ~p: ~p~n~p",
+ [Module, Host, Opts, Class, Reason,
+ erlang:get_stacktrace()]),
+ ?CRITICAL_MSG(ErrorText, []),
+ case is_app_running(ejabberd) of
+ true ->
+ erlang:raise(Class, Reason, erlang:get_stacktrace());
+ false ->
+ ?CRITICAL_MSG("ejabberd initialization was aborted "
+ "because a module start failed.",
+ []),
+ timer:sleep(3000),
+ erlang:halt(string:substr(lists:flatten(ErrorText), 1, 199))
+ end
end.
is_app_running(AppName) ->
- %% Use a high timeout to prevent a false positive in a high load system
Timeout = 15000,
- lists:keymember(AppName, 1, application:which_applications(Timeout)).
+ lists:keymember(AppName, 1,
+ application:which_applications(Timeout)).
+
+-spec stop_module(binary(), atom()) -> error | {aborted, any()} | {atomic, any()}.
%% @doc Stop the module in a host, and forget its configuration.
stop_module(Host, Module) ->
case stop_module_keep_config(Host, Module) of
- error ->
- error;
- ok ->
- del_module_mnesia(Host, Module)
+ error -> error;
+ ok -> del_module_mnesia(Host, Module)
end.
%% @doc Stop the module in a host, but keep its configuration.
@@ -104,22 +108,20 @@ stop_module(Host, Module) ->
%% when ejabberd is restarted the module will be started again.
%% This function is useful when ejabberd is being stopped
%% and it stops all modules.
+-spec stop_module_keep_config(binary(), atom()) -> error | ok.
+
stop_module_keep_config(Host, Module) ->
case catch Module:stop(Host) of
- {'EXIT', Reason} ->
- ?ERROR_MSG("~p", [Reason]),
- error;
- {wait, ProcList} when is_list(ProcList) ->
- lists:foreach(fun wait_for_process/1, ProcList),
- ets:delete(ejabberd_modules, {Module, Host}),
- ok;
- {wait, Process} ->
- wait_for_process(Process),
- ets:delete(ejabberd_modules, {Module, Host}),
- ok;
- _ ->
- ets:delete(ejabberd_modules, {Module, Host}),
- ok
+ {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]), error;
+ {wait, ProcList} when is_list(ProcList) ->
+ lists:foreach(fun wait_for_process/1, ProcList),
+ ets:delete(ejabberd_modules, {Module, Host}),
+ ok;
+ {wait, Process} ->
+ wait_for_process(Process),
+ ets:delete(ejabberd_modules, {Module, Host}),
+ ok;
+ _ -> ets:delete(ejabberd_modules, {Module, Host}), ok
end.
wait_for_process(Process) ->
@@ -128,136 +130,153 @@ wait_for_process(Process) ->
wait_for_stop(Process, MonitorReference) ->
receive
- {'DOWN', MonitorReference, _Type, _Object, _Info} ->
- ok
- after 5000 ->
- catch exit(whereis(Process), kill),
- wait_for_stop1(MonitorReference)
+ {'DOWN', MonitorReference, _Type, _Object, _Info} -> ok
+ after 5000 ->
+ catch exit(whereis(Process), kill),
+ wait_for_stop1(MonitorReference)
end.
wait_for_stop1(MonitorReference) ->
receive
- {'DOWN', MonitorReference, _Type, _Object, _Info} ->
- ok
- after 5000 ->
- ok
+ {'DOWN', MonitorReference, _Type, _Object, _Info} -> ok
+ after 5000 -> ok
end.
-get_opt(Opt, Opts) ->
- case lists:keysearch(Opt, 1, Opts) of
- false ->
- % TODO: replace with more appropriate function
- throw({undefined_option, Opt});
- {value, {_, Val}} ->
- Val
- end.
+-type check_fun() :: fun((any()) -> any()) | {module(), atom()}.
+
+-spec get_opt(atom(), opts(), check_fun()) -> any().
-get_opt(Opt, Opts, Default) ->
+get_opt(Opt, Opts, F) ->
+ get_opt(Opt, Opts, F, undefined).
+
+-spec get_opt(atom(), opts(), check_fun(), any()) -> any().
+
+get_opt(Opt, Opts, F, Default) ->
case lists:keysearch(Opt, 1, Opts) of
- false ->
- Default;
- {value, {_, Val}} ->
- Val
+ false ->
+ Default;
+ {value, {_, Val}} ->
+ ejabberd_config:prepare_opt_val(Opt, Val, F, Default)
end.
-get_module_opt(global, Module, Opt, Default) ->
- Hosts = ?MYHOSTS,
- [Value | Values] = lists:map(
- fun(Host) ->
- get_module_opt(Host, Module, Opt, Default)
- end,
- Hosts),
- Same_all = lists:all(
- fun(Other_value) ->
- Other_value == Value
- end,
- Values),
- case Same_all of
- true -> Value;
- false -> Default
- end;
-
-get_module_opt(Host, Module, Opt, Default) ->
+-spec get_module_opt(global | binary(), atom(), atom(), check_fun(), any()) -> any().
+
+get_module_opt(global, Module, Opt, F, Default) ->
+ Hosts = (?MYHOSTS),
+ [Value | Values] = lists:map(fun (Host) ->
+ get_module_opt(Host, Module, Opt,
+ F, Default)
+ end,
+ Hosts),
+ Same_all = lists:all(fun (Other_value) ->
+ Other_value == Value
+ end,
+ Values),
+ case Same_all of
+ true -> Value;
+ false -> Default
+ end;
+get_module_opt(Host, Module, Opt, F, Default) ->
OptsList = ets:lookup(ejabberd_modules, {Module, Host}),
case OptsList of
- [] ->
- Default;
- [#ejabberd_module{opts = Opts} | _] ->
- get_opt(Opt, Opts, Default)
+ [] -> Default;
+ [#ejabberd_module{opts = Opts} | _] ->
+ get_opt(Opt, Opts, F, Default)
end.
+-spec get_module_opt_host(global | binary(), atom(), binary()) -> binary().
+
get_module_opt_host(Host, Module, Default) ->
- Val = get_module_opt(Host, Module, host, Default),
- ejabberd_regexp:greplace(Val, "@HOST@", Host).
+ Val = get_module_opt(Host, Module, host,
+ fun iolist_to_binary/1,
+ Default),
+ ejabberd_regexp:greplace(Val, <<"@HOST@">>, Host).
+
+-spec get_opt_host(binary(), opts(), binary()) -> binary().
get_opt_host(Host, Opts, Default) ->
- Val = get_opt(host, Opts, Default),
- ejabberd_regexp:greplace(Val, "@HOST@", Host).
+ Val = get_opt(host, Opts, fun iolist_to_binary/1, Default),
+ ejabberd_regexp:greplace(Val, <<"@HOST@">>, Host).
+
+-spec db_type(opts()) -> odbc | mnesia.
db_type(Opts) ->
- case get_opt(db_type, Opts, mnesia) of
- odbc -> odbc;
- _ -> mnesia
- end.
+ get_opt(db_type, Opts,
+ fun(odbc) -> odbc;
+ (internal) -> mnesia;
+ (mnesia) -> mnesia end,
+ mnesia).
+
+-spec db_type(binary(), atom()) -> odbc | mnesia.
db_type(Host, Module) ->
- case get_module_opt(Host, Module, db_type, mnesia) of
- odbc -> odbc;
- _ -> mnesia
- end.
+ get_module_opt(Host, Module, db_type,
+ fun(odbc) -> odbc;
+ (internal) -> mnesia;
+ (mnesia) -> mnesia end,
+ mnesia).
+
+-spec loaded_modules(binary()) -> [atom()].
loaded_modules(Host) ->
ets:select(ejabberd_modules,
[{#ejabberd_module{_ = '_', module_host = {'$1', Host}},
- [],
- ['$1']}]).
+ [], ['$1']}]).
+
+-spec loaded_modules_with_opts(binary()) -> [{atom(), opts()}].
loaded_modules_with_opts(Host) ->
ets:select(ejabberd_modules,
[{#ejabberd_module{_ = '_', module_host = {'$1', Host},
opts = '$2'},
- [],
- [{{'$1', '$2'}}]}]).
+ [], [{{'$1', '$2'}}]}]).
set_module_opts_mnesia(Host, Module, Opts) ->
- Modules = case ejabberd_config:get_local_option({modules, Host}) of
- undefined ->
- [];
- Ls ->
- Ls
- end,
+ Modules = ejabberd_config:get_local_option(
+ {modules, Host},
+ fun(Ls) when is_list(Ls) -> Ls end,
+ []),
Modules1 = lists:keydelete(Module, 1, Modules),
Modules2 = [{Module, Opts} | Modules1],
- ejabberd_config:add_local_option({modules, Host}, Modules2).
+ ejabberd_config:add_local_option({modules, Host},
+ Modules2).
del_module_mnesia(Host, Module) ->
- Modules = case ejabberd_config:get_local_option({modules, Host}) of
- undefined ->
- [];
- Ls ->
- Ls
- end,
+ Modules = ejabberd_config:get_local_option(
+ {modules, Host},
+ fun(Ls) when is_list(Ls) -> Ls end,
+ []),
Modules1 = lists:keydelete(Module, 1, Modules),
- ejabberd_config:add_local_option({modules, Host}, Modules1).
+ ejabberd_config:add_local_option({modules, Host},
+ Modules1).
+
+-spec get_hosts(opts(), binary()) -> [binary()].
get_hosts(Opts, Prefix) ->
- case catch gen_mod:get_opt(hosts, Opts) of
- {'EXIT', _Error1} ->
- case catch gen_mod:get_opt(host, Opts) of
- {'EXIT', _Error2} ->
- [Prefix ++ Host || Host <- ?MYHOSTS];
- Host ->
- [Host]
- end;
- Hosts ->
- Hosts
+ case get_opt(hosts, Opts,
+ fun(Hs) -> [iolist_to_binary(H) || H <- Hs] end) of
+ undefined ->
+ case get_opt(host, Opts,
+ fun iolist_to_binary/1) of
+ undefined ->
+ [<<Prefix/binary, Host/binary>> || Host <- ?MYHOSTS];
+ Host ->
+ [Host]
+ end;
+ Hosts ->
+ Hosts
end.
+-spec get_module_proc(binary(), {frontend, atom()} | atom()) -> atom().
+
get_module_proc(Host, {frontend, Base}) ->
- get_module_proc("frontend_" ++ Host, Base);
+ get_module_proc(<<"frontend_", Host/binary>>, Base);
get_module_proc(Host, Base) ->
- list_to_atom(atom_to_list(Base) ++ "_" ++ Host).
+ binary_to_atom(
+ <<(erlang:atom_to_binary(Base, latin1))/binary, "_", Host/binary>>,
+ latin1).
+
+-spec is_loaded(binary(), atom()) -> boolean().
is_loaded(Host, Module) ->
ets:member(ejabberd_modules, {Module, Host}).
-
diff --git a/src/idna.erl b/src/idna.erl
index a73724812..ee4c8cb4a 100644
--- a/src/idna.erl
+++ b/src/idna.erl
@@ -25,172 +25,182 @@
%%%----------------------------------------------------------------------
-module(idna).
+
-author('alexey@process-one.net').
%%-compile(export_all).
-export([domain_utf8_to_ascii/1,
- domain_ucs2_to_ascii/1]).
+ domain_ucs2_to_ascii/1,
+ utf8_to_ucs2/1]).
+-spec domain_utf8_to_ascii(binary()) -> false | binary().
domain_utf8_to_ascii(Domain) ->
domain_ucs2_to_ascii(utf8_to_ucs2(Domain)).
utf8_to_ucs2(S) ->
- utf8_to_ucs2(S, "").
+ list_to_binary(utf8_to_ucs2(binary_to_list(S), "")).
-utf8_to_ucs2([], R) ->
- lists:reverse(R);
-utf8_to_ucs2([C | S], R) when C < 16#80 ->
+utf8_to_ucs2([], R) -> lists:reverse(R);
+utf8_to_ucs2([C | S], R) when C < 128 ->
utf8_to_ucs2(S, [C | R]);
-utf8_to_ucs2([C1, C2 | S], R) when C1 < 16#E0 ->
- utf8_to_ucs2(S, [((C1 band 16#1F) bsl 6) bor
- (C2 band 16#3F) | R]);
-utf8_to_ucs2([C1, C2, C3 | S], R) when C1 < 16#F0 ->
- utf8_to_ucs2(S, [((C1 band 16#0F) bsl 12) bor
- ((C2 band 16#3F) bsl 6) bor
- (C3 band 16#3F) | R]).
+utf8_to_ucs2([C1, C2 | S], R) when C1 < 224 ->
+ utf8_to_ucs2(S, [C1 band 31 bsl 6 bor C2 band 63 | R]);
+utf8_to_ucs2([C1, C2, C3 | S], R) when C1 < 240 ->
+ utf8_to_ucs2(S,
+ [C1 band 15 bsl 12 bor (C2 band 63 bsl 6) bor C3 band 63
+ | R]).
+-spec domain_ucs2_to_ascii(binary()) -> false | binary().
domain_ucs2_to_ascii(Domain) ->
- case catch domain_ucs2_to_ascii1(Domain) of
- {'EXIT', _Reason} ->
- false;
- Res ->
- Res
+ case catch domain_ucs2_to_ascii1(binary_to_list(Domain)) of
+ {'EXIT', _Reason} -> false;
+ Res -> iolist_to_binary(Res)
end.
domain_ucs2_to_ascii1(Domain) ->
- Parts = string:tokens(Domain, [16#002E, 16#3002, 16#FF0E, 16#FF61]),
- ASCIIParts = lists:map(fun(P) ->
- to_ascii(P)
- end, Parts),
- string:strip(lists:flatmap(fun(P) -> [$. | P] end, ASCIIParts),
+ Parts = string:tokens(Domain,
+ [46, 12290, 65294, 65377]),
+ ASCIIParts = lists:map(fun (P) -> to_ascii(P) end,
+ Parts),
+ string:strip(lists:flatmap(fun (P) -> [$. | P] end,
+ ASCIIParts),
left, $.).
%% Domain names are already nameprep'ed in ejabberd, so we skiping this step
to_ascii(Name) ->
- false = lists:any(
- fun(C) when
- ( 0 =< C) and (C =< 16#2C) or
- (16#2E =< C) and (C =< 16#2F) or
- (16#3A =< C) and (C =< 16#40) or
- (16#5B =< C) and (C =< 16#60) or
- (16#7B =< C) and (C =< 16#7F) ->
- true;
- (_) ->
- false
- end, Name),
+ false = lists:any(fun (C)
+ when (0 =< C) and (C =< 44) or
+ (46 =< C) and (C =< 47)
+ or (58 =< C) and (C =< 64)
+ or (91 =< C) and (C =< 96)
+ or (123 =< C) and (C =< 127) ->
+ true;
+ (_) -> false
+ end,
+ Name),
case Name of
- [H | _] when H /= $- ->
- true = lists:last(Name) /= $-
+ [H | _] when H /= $- -> true = lists:last(Name) /= $-
end,
- ASCIIName = case lists:any(fun(C) -> C > 16#7F end, Name) of
- true ->
- true = case Name of
- "xn--" ++ _ -> false;
- _ -> true
- end,
- "xn--" ++ punycode_encode(Name);
- false ->
- Name
+ ASCIIName = case lists:any(fun (C) -> C > 127 end, Name)
+ of
+ true ->
+ true = case Name of
+ "xn--" ++ _ -> false;
+ _ -> true
+ end,
+ "xn--" ++ punycode_encode(Name);
+ false -> Name
end,
L = length(ASCIIName),
true = (1 =< L) and (L =< 63),
ASCIIName.
-
%%% PUNYCODE (RFC3492)
--define(BASE, 36).
--define(TMIN, 1).
--define(TMAX, 26).
--define(SKEW, 38).
--define(DAMP, 700).
+-define(BASE, 36).
+
+-define(TMIN, 1).
+
+-define(TMAX, 26).
+
+-define(SKEW, 38).
+
+-define(DAMP, 700).
+
-define(INITIAL_BIAS, 72).
--define(INITIAL_N, 128).
+
+-define(INITIAL_N, 128).
punycode_encode(Input) ->
- N = ?INITIAL_N,
+ N = (?INITIAL_N),
Delta = 0,
- Bias = ?INITIAL_BIAS,
- Basic = lists:filter(fun(C) -> C =< 16#7f end, Input),
- NonBasic = lists:filter(fun(C) -> C > 16#7f end, Input),
+ Bias = (?INITIAL_BIAS),
+ Basic = lists:filter(fun (C) -> C =< 127 end, Input),
+ NonBasic = lists:filter(fun (C) -> C > 127 end, Input),
L = length(Input),
B = length(Basic),
SNonBasic = lists:usort(NonBasic),
- Output1 = if
- B > 0 -> Basic ++ "-";
- true -> ""
+ Output1 = if B > 0 -> Basic ++ "-";
+ true -> ""
end,
- Output2 = punycode_encode1(Input, SNonBasic, B, B, L, N, Delta, Bias, ""),
+ Output2 = punycode_encode1(Input, SNonBasic, B, B, L, N,
+ Delta, Bias, ""),
Output1 ++ Output2.
-
-punycode_encode1(Input, [M | SNonBasic], B, H, L, N, Delta, Bias, Out)
- when H < L ->
+punycode_encode1(Input, [M | SNonBasic], B, H, L, N,
+ Delta, Bias, Out)
+ when H < L ->
Delta1 = Delta + (M - N) * (H + 1),
- % let n = m
- {NewDelta, NewBias, NewH, NewOut} =
- lists:foldl(
- fun(C, {ADelta, ABias, AH, AOut}) ->
- if
- C < M ->
- {ADelta + 1, ABias, AH, AOut};
- C == M ->
- NewOut = punycode_encode_delta(ADelta, ABias, AOut),
- NewBias = adapt(ADelta, H + 1, H == B),
- {0, NewBias, AH + 1, NewOut};
- true ->
- {ADelta, ABias, AH, AOut}
- end
- end, {Delta1, Bias, H, Out}, Input),
- punycode_encode1(
- Input, SNonBasic, B, NewH, L, M + 1, NewDelta + 1, NewBias, NewOut);
-
-punycode_encode1(_Input, _SNonBasic, _B, _H, _L, _N, _Delta, _Bias, Out) ->
+ % let n = m
+ {NewDelta, NewBias, NewH, NewOut} = lists:foldl(fun (C,
+ {ADelta, ABias, AH,
+ AOut}) ->
+ if C < M ->
+ {ADelta + 1,
+ ABias, AH,
+ AOut};
+ C == M ->
+ NewOut =
+ punycode_encode_delta(ADelta,
+ ABias,
+ AOut),
+ NewBias =
+ adapt(ADelta,
+ H +
+ 1,
+ H
+ ==
+ B),
+ {0, NewBias,
+ AH + 1,
+ NewOut};
+ true ->
+ {ADelta,
+ ABias, AH,
+ AOut}
+ end
+ end,
+ {Delta1, Bias, H, Out},
+ Input),
+ punycode_encode1(Input, SNonBasic, B, NewH, L, M + 1,
+ NewDelta + 1, NewBias, NewOut);
+punycode_encode1(_Input, _SNonBasic, _B, _H, _L, _N,
+ _Delta, _Bias, Out) ->
lists:reverse(Out).
-
punycode_encode_delta(Delta, Bias, Out) ->
punycode_encode_delta(Delta, Bias, Out, ?BASE).
punycode_encode_delta(Delta, Bias, Out, K) ->
- T = if
- K =< Bias -> ?TMIN;
- K >= Bias + ?TMAX -> ?TMAX;
- true -> K - Bias
+ T = if K =< Bias -> ?TMIN;
+ K >= Bias + (?TMAX) -> ?TMAX;
+ true -> K - Bias
end,
- if
- Delta < T ->
- [codepoint(Delta) | Out];
- true ->
- C = T + ((Delta - T) rem (?BASE - T)),
- punycode_encode_delta((Delta - T) div (?BASE - T), Bias,
- [codepoint(C) | Out], K + ?BASE)
+ if Delta < T -> [codepoint(Delta) | Out];
+ true ->
+ C = T + (Delta - T) rem ((?BASE) - T),
+ punycode_encode_delta((Delta - T) div ((?BASE) - T),
+ Bias, [codepoint(C) | Out], K + (?BASE))
end.
-
adapt(Delta, NumPoints, FirstTime) ->
- Delta1 = if
- FirstTime -> Delta div ?DAMP;
- true -> Delta div 2
+ Delta1 = if FirstTime -> Delta div (?DAMP);
+ true -> Delta div 2
end,
- Delta2 = Delta1 + (Delta1 div NumPoints),
+ Delta2 = Delta1 + Delta1 div NumPoints,
adapt1(Delta2, 0).
adapt1(Delta, K) ->
- if
- Delta > ((?BASE - ?TMIN) * ?TMAX) div 2 ->
- adapt1(Delta div (?BASE - ?TMIN), K + ?BASE);
- true ->
- K + (((?BASE - ?TMIN + 1) * Delta) div (Delta + ?SKEW))
+ if Delta > ((?BASE) - (?TMIN)) * (?TMAX) div 2 ->
+ adapt1(Delta div ((?BASE) - (?TMIN)), K + (?BASE));
+ true ->
+ K +
+ ((?BASE) - (?TMIN) + 1) * Delta div (Delta + (?SKEW))
end.
-
codepoint(C) ->
- if
- (0 =< C) and (C =< 25) ->
- C + 97;
- (26 =< C) and (C =< 35) ->
- C + 22
+ if (0 =< C) and (C =< 25) -> C + 97;
+ (26 =< C) and (C =< 35) -> C + 22
end.
diff --git a/src/jd2ejd.erl b/src/jd2ejd.erl
index 233c1b034..a5d2f9961 100644
--- a/src/jd2ejd.erl
+++ b/src/jd2ejd.erl
@@ -25,16 +25,15 @@
%%%----------------------------------------------------------------------
-module(jd2ejd).
+
-author('alexey@process-one.net').
%% External exports
--export([import_file/1,
- import_dir/1]).
+-export([import_file/1, import_dir/1]).
-include("ejabberd.hrl").
--include("jlib.hrl").
-
+-include("jlib.hrl").
%%%----------------------------------------------------------------------
%%% API
@@ -43,140 +42,135 @@
import_file(File) ->
User = filename:rootname(filename:basename(File)),
Server = filename:basename(filename:dirname(File)),
- case (jlib:nodeprep(User) /= error) andalso
- (jlib:nameprep(Server) /= error) of
- true ->
- case file:read_file(File) of
- {ok, Text} ->
- case xml_stream:parse_element(Text) of
- El when element(1, El) == xmlelement ->
- case catch process_xdb(User, Server, El) of
- {'EXIT', Reason} ->
- ?ERROR_MSG(
- "Error while processing file \"~s\": ~p~n",
- [File, Reason]),
- {error, Reason};
- _ ->
- ok
- end;
- {error, Reason} ->
- ?ERROR_MSG("Can't parse file \"~s\": ~p~n",
+ case jlib:nodeprep(User) /= error andalso
+ jlib:nameprep(Server) /= error
+ of
+ true ->
+ case file:read_file(File) of
+ {ok, Text} ->
+ case xml_stream:parse_element(Text) of
+ El when is_record(El, xmlel) ->
+ case catch process_xdb(User, Server, El) of
+ {'EXIT', Reason} ->
+ ?ERROR_MSG("Error while processing file \"~s\": "
+ "~p~n",
[File, Reason]),
- {error, Reason}
- end;
- {error, Reason} ->
- ?ERROR_MSG("Can't read file \"~s\": ~p~n", [File, Reason]),
- {error, Reason}
- end;
- false ->
- ?ERROR_MSG("Illegal user/server name in file \"~s\"~n", [File]),
- {error, "illegal user/server"}
+ {error, Reason};
+ _ -> ok
+ end;
+ {error, Reason} ->
+ ?ERROR_MSG("Can't parse file \"~s\": ~p~n",
+ [File, Reason]),
+ {error, Reason}
+ end;
+ {error, Reason} ->
+ ?ERROR_MSG("Can't read file \"~s\": ~p~n",
+ [File, Reason]),
+ {error, Reason}
+ end;
+ false ->
+ ?ERROR_MSG("Illegal user/server name in file \"~s\"~n",
+ [File]),
+ {error, <<"illegal user/server">>}
end.
-
import_dir(Dir) ->
{ok, Files} = file:list_dir(Dir),
- MsgFiles = lists:filter(
- fun(FN) ->
- case string:len(FN) > 4 of
- true ->
- string:substr(FN,
- string:len(FN) - 3) == ".xml";
- _ ->
- false
- end
- end, Files),
- lists:foldl(
- fun(FN, A) ->
- Res = import_file(filename:join([Dir, FN])),
- case {A, Res} of
- {ok, ok} -> ok;
- {ok, _} -> {error, "see ejabberd log for details"};
- _ -> A
- end
- end, ok, MsgFiles).
+ MsgFiles = lists:filter(fun (FN) ->
+ case length(FN) > 4 of
+ true ->
+ string:substr(FN, length(FN) - 3) ==
+ ".xml";
+ _ -> false
+ end
+ end,
+ Files),
+ lists:foldl(fun (FN, A) ->
+ Res = import_file(filename:join([Dir, FN])),
+ case {A, Res} of
+ {ok, ok} -> ok;
+ {ok, _} ->
+ {error, <<"see ejabberd log for details">>};
+ _ -> A
+ end
+ end,
+ ok, MsgFiles).
%%%----------------------------------------------------------------------
%%% Internal functions
%%%----------------------------------------------------------------------
-process_xdb(User, Server, {xmlelement, Name, _Attrs, Els}) ->
+process_xdb(User, Server,
+ #xmlel{name = Name, children = Els}) ->
case Name of
- "xdb" ->
- lists:foreach(
- fun(El) ->
- xdb_data(User, Server, El)
- end, Els);
- _ ->
- ok
+ <<"xdb">> ->
+ lists:foreach(fun (El) -> xdb_data(User, Server, El)
+ end,
+ Els);
+ _ -> ok
end.
-
-xdb_data(_User, _Server, {xmlcdata, _CData}) ->
- ok;
-xdb_data(User, Server, {xmlelement, _Name, Attrs, _Els} = El) ->
- From = jlib:make_jid(User, Server, ""),
- case xml:get_attr_s("xmlns", Attrs) of
- ?NS_AUTH ->
- Password = xml:get_tag_cdata(El),
- ejabberd_auth:set_password(User, Server, Password),
- ok;
- ?NS_ROSTER ->
- catch mod_roster:set_items(User, Server, El),
- ok;
- ?NS_LAST ->
- TimeStamp = xml:get_attr_s("last", Attrs),
- Status = xml:get_tag_cdata(El),
- catch mod_last:store_last_info(
- User,
- Server,
- list_to_integer(TimeStamp),
- Status),
- ok;
- ?NS_VCARD ->
- catch mod_vcard:process_sm_iq(
- From,
- jlib:make_jid("", Server, ""),
- #iq{type = set, xmlns = ?NS_VCARD, sub_el = El}),
- ok;
- "jabber:x:offline" ->
- process_offline(Server, From, El),
- ok;
- XMLNS ->
- case xml:get_attr_s("j_private_flag", Attrs) of
- "1" ->
- catch mod_private:process_sm_iq(
- From,
- jlib:make_jid("", Server, ""),
- #iq{type = set, xmlns = ?NS_PRIVATE,
- sub_el = {xmlelement, "query", [],
- [jlib:remove_attr(
- "j_private_flag",
- jlib:remove_attr("xdbns", El))]}});
- _ ->
- ?DEBUG("jd2ejd: Unknown namespace \"~s\"~n", [XMLNS])
- end,
- ok
+xdb_data(_User, _Server, {xmlcdata, _CData}) -> ok;
+xdb_data(User, Server, #xmlel{attrs = Attrs} = El) ->
+ From = jlib:make_jid(User, Server, <<"">>),
+ case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ ?NS_AUTH ->
+ Password = xml:get_tag_cdata(El),
+ ejabberd_auth:set_password(User, Server, Password),
+ ok;
+ ?NS_ROSTER ->
+ catch mod_roster:set_items(User, Server, El), ok;
+ ?NS_LAST ->
+ TimeStamp = xml:get_attr_s(<<"last">>, Attrs),
+ Status = xml:get_tag_cdata(El),
+ catch mod_last:store_last_info(User, Server,
+ jlib:binary_to_integer(TimeStamp),
+ Status),
+ ok;
+ ?NS_VCARD ->
+ catch mod_vcard:process_sm_iq(From,
+ jlib:make_jid(<<"">>, Server, <<"">>),
+ #iq{type = set, xmlns = ?NS_VCARD,
+ sub_el = El}),
+ ok;
+ <<"jabber:x:offline">> ->
+ process_offline(Server, From, El), ok;
+ XMLNS ->
+ case xml:get_attr_s(<<"j_private_flag">>, Attrs) of
+ <<"1">> ->
+ catch mod_private:process_sm_iq(From,
+ jlib:make_jid(<<"">>, Server,
+ <<"">>),
+ #iq{type = set,
+ xmlns = ?NS_PRIVATE,
+ sub_el =
+ #xmlel{name =
+ <<"query">>,
+ attrs = [],
+ children =
+ [jlib:remove_attr(<<"j_private_flag">>,
+ jlib:remove_attr(<<"xdbns">>,
+ El))]}});
+ _ ->
+ ?DEBUG("jd2ejd: Unknown namespace \"~s\"~n", [XMLNS])
+ end,
+ ok
end.
-
-process_offline(Server, To, {xmlelement, _, _, Els}) ->
+process_offline(Server, To, #xmlel{children = Els}) ->
LServer = jlib:nameprep(Server),
- lists:foreach(fun({xmlelement, _, Attrs, _} = El) ->
- FromS = xml:get_attr_s("from", Attrs),
+ lists:foreach(fun (#xmlel{attrs = Attrs} = El) ->
+ FromS = xml:get_attr_s(<<"from">>, Attrs),
From = case FromS of
- "" ->
- jlib:make_jid("", Server, "");
- _ ->
- jlib:string_to_jid(FromS)
+ <<"">> ->
+ jlib:make_jid(<<"">>, Server, <<"">>);
+ _ -> jlib:string_to_jid(FromS)
end,
case From of
- error ->
- ok;
- _ ->
- ejabberd_hooks:run(offline_message_hook,
- LServer,
- [From, To, El])
+ error -> ok;
+ _ ->
+ ejabberd_hooks:run(offline_message_hook,
+ LServer, [From, To, El])
end
- end, Els).
-
+ end,
+ Els).
diff --git a/src/jlib.erl b/src/jlib.erl
index ce99f95eb..bf08a476d 100644
--- a/src/jlib.erl
+++ b/src/jlib.erl
@@ -25,238 +25,248 @@
%%%----------------------------------------------------------------------
-module(jlib).
+
-author('alexey@process-one.net').
--export([make_result_iq_reply/1,
- make_error_reply/3,
- make_error_reply/2,
- make_error_element/2,
- make_correct_from_to_attrs/3,
- replace_from_to_attrs/3,
- replace_from_to/3,
- replace_from_attrs/2,
- replace_from/2,
- remove_attr/2,
- make_jid/3,
- make_jid/1,
- string_to_jid/1,
- jid_to_string/1,
- is_nodename/1,
- tolower/1,
- nodeprep/1,
- nameprep/1,
- resourceprep/1,
- jid_tolower/1,
- jid_remove_resource/1,
- jid_replace_resource/2,
- get_iq_namespace/1,
- iq_query_info/1,
- iq_query_or_response_info/1,
- is_iq_request_type/1,
- iq_to_xml/1,
- parse_xdata_submit/1,
- timestamp_to_iso/1, % TODO: Remove once XEP-0091 is Obsolete
- timestamp_to_iso/2,
- timestamp_to_xml/4,
- timestamp_to_xml/1, % TODO: Remove once XEP-0091 is Obsolete
- now_to_utc_string/1,
- now_to_local_string/1,
- datetime_string_to_timestamp/1,
- decode_base64/1,
- encode_base64/1,
- ip_to_list/1,
- rsm_encode/1,
- rsm_encode/2,
- rsm_decode/1]).
+-compile({no_auto_import, [{atom_to_binary, 2}]}).
+
+-export([make_result_iq_reply/1, make_error_reply/3,
+ make_error_reply/2, make_error_element/2,
+ make_correct_from_to_attrs/3, replace_from_to_attrs/3,
+ replace_from_to/3, replace_from_attrs/2, replace_from/2,
+ remove_attr/2, make_jid/3, make_jid/1, string_to_jid/1,
+ jid_to_string/1, is_nodename/1, tolower/1, nodeprep/1,
+ nameprep/1, resourceprep/1, jid_tolower/1,
+ jid_remove_resource/1, jid_replace_resource/2,
+ get_iq_namespace/1, iq_query_info/1,
+ iq_query_or_response_info/1, is_iq_request_type/1,
+ iq_to_xml/1, parse_xdata_submit/1, timestamp_to_iso/1,
+ timestamp_to_iso/2, timestamp_to_xml/4,
+ timestamp_to_xml/1, now_to_utc_string/1,
+ now_to_local_string/1, datetime_string_to_timestamp/1,
+ decode_base64/1, encode_base64/1, ip_to_list/1,
+ rsm_encode/1, rsm_encode/2, rsm_decode/1,
+ binary_to_integer/1, binary_to_integer/2,
+ integer_to_binary/1, integer_to_binary/2,
+ atom_to_binary/1, binary_to_atom/1, tuple_to_binary/1]).
+
+%% TODO: Remove once XEP-0091 is Obsolete
+%% TODO: Remove once XEP-0091 is Obsolete
-include("jlib.hrl").
+-export_type([jid/0]).
+
%send_iq(From, To, ID, SubTags) ->
% ok.
-make_result_iq_reply({xmlelement, Name, Attrs, SubTags}) ->
+-spec make_result_iq_reply(xmlel()) -> xmlel().
+
+make_result_iq_reply(#xmlel{name = Name, attrs = Attrs,
+ children = SubTags}) ->
NewAttrs = make_result_iq_reply_attrs(Attrs),
- {xmlelement, Name, NewAttrs, SubTags}.
+ #xmlel{name = Name, attrs = NewAttrs,
+ children = SubTags}.
+
+-spec make_result_iq_reply_attrs([attr()]) -> [attr()].
make_result_iq_reply_attrs(Attrs) ->
- To = xml:get_attr("to", Attrs),
- From = xml:get_attr("from", Attrs),
- Attrs1 = lists:keydelete("to", 1, Attrs),
- Attrs2 = lists:keydelete("from", 1, Attrs1),
+ To = xml:get_attr(<<"to">>, Attrs),
+ From = xml:get_attr(<<"from">>, Attrs),
+ Attrs1 = lists:keydelete(<<"to">>, 1, Attrs),
+ Attrs2 = lists:keydelete(<<"from">>, 1, Attrs1),
Attrs3 = case To of
- {value, ToVal} ->
- [{"from", ToVal} | Attrs2];
- _ ->
- Attrs2
+ {value, ToVal} -> [{<<"from">>, ToVal} | Attrs2];
+ _ -> Attrs2
end,
Attrs4 = case From of
- {value, FromVal} ->
- [{"to", FromVal} | Attrs3];
- _ ->
- Attrs3
+ {value, FromVal} -> [{<<"to">>, FromVal} | Attrs3];
+ _ -> Attrs3
end,
- Attrs5 = lists:keydelete("type", 1, Attrs4),
- Attrs6 = [{"type", "result"} | Attrs5],
+ Attrs5 = lists:keydelete(<<"type">>, 1, Attrs4),
+ Attrs6 = [{<<"type">>, <<"result">>} | Attrs5],
Attrs6.
-make_error_reply({xmlelement, Name, Attrs, SubTags}, Code, Desc) ->
- NewAttrs = make_error_reply_attrs(Attrs),
- {xmlelement, Name, NewAttrs, SubTags ++ [{xmlelement, "error",
- [{"code", Code}],
- [{xmlcdata, Desc}]}]}.
+-spec make_error_reply(xmlel(), binary(), binary()) -> xmlel().
-make_error_reply({xmlelement, Name, Attrs, SubTags}, Error) ->
+make_error_reply(#xmlel{name = Name, attrs = Attrs,
+ children = SubTags},
+ Code, Desc) ->
+ NewAttrs = make_error_reply_attrs(Attrs),
+ #xmlel{name = Name, attrs = NewAttrs,
+ children =
+ SubTags ++
+ [#xmlel{name = <<"error">>,
+ attrs = [{<<"code">>, Code}],
+ children = [{xmlcdata, Desc}]}]}.
+
+-spec make_error_reply(xmlel(), xmlel()) -> xmlel().
+
+make_error_reply(#xmlel{name = Name, attrs = Attrs,
+ children = SubTags},
+ Error) ->
NewAttrs = make_error_reply_attrs(Attrs),
- {xmlelement, Name, NewAttrs, SubTags ++ [Error]}.
+ #xmlel{name = Name, attrs = NewAttrs,
+ children = SubTags ++ [Error]}.
+
+-spec make_error_reply_attrs([attr()]) -> [attr()].
make_error_reply_attrs(Attrs) ->
- To = xml:get_attr("to", Attrs),
- From = xml:get_attr("from", Attrs),
- Attrs1 = lists:keydelete("to", 1, Attrs),
- Attrs2 = lists:keydelete("from", 1, Attrs1),
+ To = xml:get_attr(<<"to">>, Attrs),
+ From = xml:get_attr(<<"from">>, Attrs),
+ Attrs1 = lists:keydelete(<<"to">>, 1, Attrs),
+ Attrs2 = lists:keydelete(<<"from">>, 1, Attrs1),
Attrs3 = case To of
- {value, ToVal} ->
- [{"from", ToVal} | Attrs2];
- _ ->
- Attrs2
+ {value, ToVal} -> [{<<"from">>, ToVal} | Attrs2];
+ _ -> Attrs2
end,
Attrs4 = case From of
- {value, FromVal} ->
- [{"to", FromVal} | Attrs3];
- _ ->
- Attrs3
+ {value, FromVal} -> [{<<"to">>, FromVal} | Attrs3];
+ _ -> Attrs3
end,
- Attrs5 = lists:keydelete("type", 1, Attrs4),
- Attrs6 = [{"type", "error"} | Attrs5],
+ Attrs5 = lists:keydelete(<<"type">>, 1, Attrs4),
+ Attrs6 = [{<<"type">>, <<"error">>} | Attrs5],
Attrs6.
+-spec make_error_element(binary(), binary()) -> xmlel().
+
make_error_element(Code, Desc) ->
- {xmlelement, "error",
- [{"code", Code}],
- [{xmlcdata, Desc}]}.
+ #xmlel{name = <<"error">>, attrs = [{<<"code">>, Code}],
+ children = [{xmlcdata, Desc}]}.
+
+-spec make_correct_from_to_attrs(binary(), binary(), [attr()]) -> [attr()].
make_correct_from_to_attrs(From, To, Attrs) ->
- Attrs1 = lists:keydelete("from", 1, Attrs),
- Attrs2 = case xml:get_attr("to", Attrs) of
- {value, _} ->
- Attrs1;
- _ ->
- [{"to", To} | Attrs1]
+ Attrs1 = lists:keydelete(<<"from">>, 1, Attrs),
+ Attrs2 = case xml:get_attr(<<"to">>, Attrs) of
+ {value, _} -> Attrs1;
+ _ -> [{<<"to">>, To} | Attrs1]
end,
- Attrs3 = [{"from", From} | Attrs2],
+ Attrs3 = [{<<"from">>, From} | Attrs2],
Attrs3.
+-spec replace_from_to_attrs(binary(), binary(), [attr()]) -> [attr()].
replace_from_to_attrs(From, To, Attrs) ->
- Attrs1 = lists:keydelete("to", 1, Attrs),
- Attrs2 = lists:keydelete("from", 1, Attrs1),
- Attrs3 = [{"to", To} | Attrs2],
- Attrs4 = [{"from", From} | Attrs3],
+ Attrs1 = lists:keydelete(<<"to">>, 1, Attrs),
+ Attrs2 = lists:keydelete(<<"from">>, 1, Attrs1),
+ Attrs3 = [{<<"to">>, To} | Attrs2],
+ Attrs4 = [{<<"from">>, From} | Attrs3],
Attrs4.
-replace_from_to(From, To, {xmlelement, Name, Attrs, Els}) ->
- NewAttrs = replace_from_to_attrs(jlib:jid_to_string(From),
- jlib:jid_to_string(To),
- Attrs),
- {xmlelement, Name, NewAttrs, Els}.
+-spec replace_from_to(jid(), jid(), xmlel()) -> xmlel().
+
+replace_from_to(From, To,
+ #xmlel{name = Name, attrs = Attrs, children = Els}) ->
+ NewAttrs =
+ replace_from_to_attrs(jlib:jid_to_string(From),
+ jlib:jid_to_string(To), Attrs),
+ #xmlel{name = Name, attrs = NewAttrs, children = Els}.
+
+-spec replace_from_attrs(binary(), [attr()]) -> [attr()].
replace_from_attrs(From, Attrs) ->
- Attrs1 = lists:keydelete("from", 1, Attrs),
- [{"from", From} | Attrs1].
+ Attrs1 = lists:keydelete(<<"from">>, 1, Attrs),
+ [{<<"from">>, From} | Attrs1].
+
+-spec replace_from(jid(), xmlel()) -> xmlel().
-replace_from(From, {xmlelement, Name, Attrs, Els}) ->
- NewAttrs = replace_from_attrs(jlib:jid_to_string(From), Attrs),
- {xmlelement, Name, NewAttrs, Els}.
+replace_from(From,
+ #xmlel{name = Name, attrs = Attrs, children = Els}) ->
+ NewAttrs = replace_from_attrs(jlib:jid_to_string(From),
+ Attrs),
+ #xmlel{name = Name, attrs = NewAttrs, children = Els}.
-remove_attr(Attr, {xmlelement, Name, Attrs, Els}) ->
+-spec remove_attr(binary(), xmlel()) -> xmlel().
+
+remove_attr(Attr,
+ #xmlel{name = Name, attrs = Attrs, children = Els}) ->
NewAttrs = lists:keydelete(Attr, 1, Attrs),
- {xmlelement, Name, NewAttrs, Els}.
+ #xmlel{name = Name, attrs = NewAttrs, children = Els}.
+-spec make_jid(binary(), binary(), binary()) -> jid() | error.
make_jid(User, Server, Resource) ->
case nodeprep(User) of
- error -> error;
- LUser ->
- case nameprep(Server) of
- error -> error;
- LServer ->
- case resourceprep(Resource) of
- error -> error;
- LResource ->
- #jid{user = User,
- server = Server,
- resource = Resource,
- luser = LUser,
- lserver = LServer,
- lresource = LResource}
- end
- end
+ error -> error;
+ LUser ->
+ case nameprep(Server) of
+ error -> error;
+ LServer ->
+ case resourceprep(Resource) of
+ error -> error;
+ LResource ->
+ #jid{user = User, server = Server, resource = Resource,
+ luser = LUser, lserver = LServer,
+ lresource = LResource}
+ end
+ end
end.
+-spec make_jid({binary(), binary(), binary()}) -> jid() | error.
+
make_jid({User, Server, Resource}) ->
make_jid(User, Server, Resource).
-string_to_jid(J) ->
- string_to_jid1(J, "").
+-spec string_to_jid(binary()) -> jid() | error.
-string_to_jid1([$@ | _J], "") ->
- error;
+string_to_jid(S) ->
+ string_to_jid1(binary_to_list(S), "").
+
+string_to_jid1([$@ | _J], "") -> error;
string_to_jid1([$@ | J], N) ->
string_to_jid2(J, lists:reverse(N), "");
-string_to_jid1([$/ | _J], "") ->
- error;
+string_to_jid1([$/ | _J], "") -> error;
string_to_jid1([$/ | J], N) ->
string_to_jid3(J, "", lists:reverse(N), "");
string_to_jid1([C | J], N) ->
string_to_jid1(J, [C | N]);
-string_to_jid1([], "") ->
- error;
+string_to_jid1([], "") -> error;
string_to_jid1([], N) ->
- make_jid("", lists:reverse(N), "").
+ make_jid(<<"">>, list_to_binary(lists:reverse(N)), <<"">>).
%% Only one "@" is admitted per JID
-string_to_jid2([$@ | _J], _N, _S) ->
- error;
-string_to_jid2([$/ | _J], _N, "") ->
- error;
+string_to_jid2([$@ | _J], _N, _S) -> error;
+string_to_jid2([$/ | _J], _N, "") -> error;
string_to_jid2([$/ | J], N, S) ->
string_to_jid3(J, N, lists:reverse(S), "");
string_to_jid2([C | J], N, S) ->
string_to_jid2(J, N, [C | S]);
-string_to_jid2([], _N, "") ->
- error;
+string_to_jid2([], _N, "") -> error;
string_to_jid2([], N, S) ->
- make_jid(N, lists:reverse(S), "").
+ make_jid(list_to_binary(N), list_to_binary(lists:reverse(S)), <<"">>).
string_to_jid3([C | J], N, S, R) ->
string_to_jid3(J, N, S, [C | R]);
string_to_jid3([], N, S, R) ->
- make_jid(N, S, lists:reverse(R)).
+ make_jid(list_to_binary(N), list_to_binary(S),
+ list_to_binary(lists:reverse(R))).
+
+-spec jid_to_string(jid() | ljid()) -> binary().
-jid_to_string(#jid{user = User, server = Server, resource = Resource}) ->
+jid_to_string(#jid{user = User, server = Server,
+ resource = Resource}) ->
jid_to_string({User, Server, Resource});
-jid_to_string({Node, Server, Resource}) ->
+jid_to_string({N, S, R}) ->
+ Node = iolist_to_binary(N),
+ Server = iolist_to_binary(S),
+ Resource = iolist_to_binary(R),
S1 = case Node of
- "" ->
- "";
- _ ->
- Node ++ "@"
+ <<"">> -> <<"">>;
+ _ -> <<Node/binary, "@">>
end,
- S2 = S1 ++ Server,
+ S2 = <<S1/binary, Server/binary>>,
S3 = case Resource of
- "" ->
- S2;
- _ ->
- S2 ++ "/" ++ Resource
+ <<"">> -> S2;
+ _ -> <<S2/binary, "/", Resource/binary>>
end,
S3.
+-spec is_nodename(binary()) -> boolean().
-is_nodename([]) ->
- false;
-is_nodename(J) ->
- nodeprep(J) /= error.
-
+is_nodename(Node) ->
+ N = nodeprep(Node),
+ (N /= error) and (N /= <<>>).
%tolower_c(C) when C >= $A, C =< $Z ->
% C + 32;
@@ -264,12 +274,9 @@ is_nodename(J) ->
% C.
-define(LOWER(Char),
- if
- Char >= $A, Char =< $Z ->
- Char + 32;
- true ->
- Char
- end).
+ if Char >= $A, Char =< $Z -> Char + 32;
+ true -> Char
+ end).
%tolower(S) ->
% lists:map(fun tolower_c/1, S).
@@ -277,16 +284,16 @@ is_nodename(J) ->
%tolower(S) ->
% [?LOWER(Char) || Char <- S].
-% Not tail-recursive but it seems works faster than variants above
-tolower([C | Cs]) ->
- if
- C >= $A, C =< $Z ->
- [C + 32 | tolower(Cs)];
- true ->
- [C | tolower(Cs)]
+-spec tolower(binary()) -> binary().
+
+tolower(B) ->
+ iolist_to_binary(tolower_s(binary_to_list(B))).
+
+tolower_s([C | Cs]) ->
+ if C >= $A, C =< $Z -> [C + 32 | tolower_s(Cs)];
+ true -> [C | tolower_s(Cs)]
end;
-tolower([]) ->
- [].
+tolower_s([]) -> [].
%tolower([C | Cs]) when C >= $A, C =< $Z ->
% [C + 32 | tolower(Cs)];
@@ -295,514 +302,579 @@ tolower([]) ->
%tolower([]) ->
% [].
+-spec nodeprep(binary()) -> binary() | error.
-nodeprep(S) when length(S) < 1024 ->
+nodeprep("") -> <<>>;
+nodeprep(S) when byte_size(S) < 1024 ->
R = stringprep:nodeprep(S),
- if
- length(R) < 1024 -> R;
- true -> error
+ if byte_size(R) < 1024 -> R;
+ true -> error
end;
-nodeprep(_) ->
- error.
+nodeprep(_) -> error.
+
+-spec nameprep(binary()) -> binary() | error.
-nameprep(S) when length(S) < 1024 ->
+nameprep(S) when byte_size(S) < 1024 ->
R = stringprep:nameprep(S),
- if
- length(R) < 1024 -> R;
- true -> error
+ if byte_size(R) < 1024 -> R;
+ true -> error
end;
-nameprep(_) ->
- error.
+nameprep(_) -> error.
-resourceprep(S) when length(S) < 1024 ->
+-spec resourceprep(binary()) -> binary() | error.
+
+resourceprep(S) when byte_size(S) < 1024 ->
R = stringprep:resourceprep(S),
- if
- length(R) < 1024 -> R;
- true -> error
+ if byte_size(R) < 1024 -> R;
+ true -> error
end;
-resourceprep(_) ->
- error.
+resourceprep(_) -> error.
+-spec jid_tolower(jid() | ljid()) -> error | ljid().
-jid_tolower(#jid{luser = U, lserver = S, lresource = R}) ->
+jid_tolower(#jid{luser = U, lserver = S,
+ lresource = R}) ->
{U, S, R};
jid_tolower({U, S, R}) ->
case nodeprep(U) of
- error -> error;
- LUser ->
- case nameprep(S) of
- error -> error;
- LServer ->
- case resourceprep(R) of
- error -> error;
- LResource ->
- {LUser, LServer, LResource}
- end
- end
+ error -> error;
+ LUser ->
+ case nameprep(S) of
+ error -> error;
+ LServer ->
+ case resourceprep(R) of
+ error -> error;
+ LResource -> {LUser, LServer, LResource}
+ end
+ end
end.
+-spec jid_remove_resource(jid()) -> jid();
+ (ljid()) -> ljid().
+
jid_remove_resource(#jid{} = JID) ->
- JID#jid{resource = "", lresource = ""};
-jid_remove_resource({U, S, _R}) ->
- {U, S, ""}.
+ JID#jid{resource = <<"">>, lresource = <<"">>};
+jid_remove_resource({U, S, _R}) -> {U, S, <<"">>}.
+
+-spec jid_replace_resource(jid(), binary()) -> error | jid().
jid_replace_resource(JID, Resource) ->
case resourceprep(Resource) of
- error -> error;
- LResource ->
- JID#jid{resource = Resource, lresource = LResource}
+ error -> error;
+ LResource ->
+ JID#jid{resource = Resource, lresource = LResource}
end.
+-spec get_iq_namespace(xmlel()) -> binary().
-get_iq_namespace({xmlelement, Name, _Attrs, Els}) when Name == "iq" ->
+get_iq_namespace(#xmlel{name = <<"iq">>, children = Els}) ->
case xml:remove_cdata(Els) of
- [{xmlelement, _Name2, Attrs2, _Els2}] ->
- xml:get_attr_s("xmlns", Attrs2);
- _ ->
- ""
+ [#xmlel{attrs = Attrs}] -> xml:get_attr_s(<<"xmlns">>, Attrs);
+ _ -> <<"">>
end;
-get_iq_namespace(_) ->
- "".
+get_iq_namespace(_) -> <<"">>.
+
+%%
+-spec(iq_query_info/1 ::
+(
+ Xmlel :: xmlel())
+ -> iq_request() | 'reply' | 'invalid' | 'not_iq'
+).
%% @spec (xmlelement()) -> iq() | reply | invalid | not_iq
+iq_query_info(El) -> iq_info_internal(El, request).
-iq_query_info(El) ->
- iq_info_internal(El, request).
+%%
+-spec(iq_query_or_response_info/1 ::
+(
+ Xmlel :: xmlel())
+ -> iq_request() | iq_reply() | 'reply' | 'invalid' | 'not_iq'
+).
iq_query_or_response_info(El) ->
iq_info_internal(El, any).
-iq_info_internal({xmlelement, Name, Attrs, Els}, Filter) when Name == "iq" ->
- %% Filter is either request or any. If it is request, any replies
- %% are converted to the atom reply.
- ID = xml:get_attr_s("id", Attrs),
- Type = xml:get_attr_s("type", Attrs),
- Lang = xml:get_attr_s("xml:lang", Attrs),
- {Type1, Class} = case Type of
- "set" -> {set, request};
- "get" -> {get, request};
- "result" -> {result, reply};
- "error" -> {error, reply};
- _ -> {invalid, invalid}
- end,
- if
- Type1 == invalid ->
- invalid;
- Class == request; Filter == any ->
- %% The iq record is a bit strange. The sub_el field is an
- %% XML tuple for requests, but a list of XML tuples for
- %% responses.
- FilteredEls = xml:remove_cdata(Els),
- {XMLNS, SubEl} =
- case {Class, FilteredEls} of
- {request, [{xmlelement, _Name2, Attrs2, _Els2}]} ->
- {xml:get_attr_s("xmlns", Attrs2),
- hd(FilteredEls)};
- {reply, _} ->
- %% Find the namespace of the first non-error
- %% element, if there is one.
- NonErrorEls = [El ||
- {xmlelement, SubName, _, _} = El
- <- FilteredEls,
- SubName /= "error"],
- {case NonErrorEls of
- [NonErrorEl] ->
- xml:get_tag_attr_s("xmlns", NonErrorEl);
- _ ->
- ""
- end,
- FilteredEls};
- _ ->
- {"", []}
- end,
- if XMLNS == "", Class == request ->
- invalid;
- true ->
- #iq{id = ID,
- type = Type1,
- xmlns = XMLNS,
- lang = Lang,
- sub_el = SubEl}
- end;
- Class == reply, Filter /= any ->
- reply
+iq_info_internal(#xmlel{name = <<"iq">>, attrs = Attrs, children = Els}, Filter) ->
+ ID = xml:get_attr_s(<<"id">>, Attrs),
+ Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
+ {Type, Class} = case xml:get_attr_s(<<"type">>, Attrs) of
+ <<"set">> -> {set, request};
+ <<"get">> -> {get, request};
+ <<"result">> -> {result, reply};
+ <<"error">> -> {error, reply};
+ _ -> {invalid, invalid}
+ end,
+ if Type == invalid -> invalid; Class == request; Filter == any ->
+ FilteredEls = xml:remove_cdata(Els),
+ {XMLNS, SubEl} = case {Class, FilteredEls} of
+ {request, [#xmlel{attrs = Attrs2}]} ->
+ {xml:get_attr_s(<<"xmlns">>, Attrs2), hd(FilteredEls)};
+ {reply, _} ->
+ NonErrorEls = [El || #xmlel{name = SubName} = El <- FilteredEls,
+ SubName /= <<"error">>],
+ {case NonErrorEls of
+ [NonErrorEl] -> xml:get_tag_attr_s(<<"xmlns">>, NonErrorEl);
+ _ -> <<"">>
+ end,
+ FilteredEls};
+ _ ->
+ {<<"">>, []}
+ end,
+ if XMLNS == <<"">>, Class == request ->
+ invalid;
+ true ->
+ #iq{id = ID, type = Type, xmlns = XMLNS, lang = Lang, sub_el = SubEl}
+ end;
+ Class == reply, Filter /= any ->
+ reply
end;
-iq_info_internal(_, _) ->
- not_iq.
+iq_info_internal(_, _) -> not_iq.
+
+-spec is_iq_request_type(set | get | result | error) -> boolean().
is_iq_request_type(set) -> true;
is_iq_request_type(get) -> true;
is_iq_request_type(_) -> false.
-iq_type_to_string(set) -> "set";
-iq_type_to_string(get) -> "get";
-iq_type_to_string(result) -> "result";
-iq_type_to_string(error) -> "error";
-iq_type_to_string(_) -> invalid.
+iq_type_to_string(set) -> <<"set">>;
+iq_type_to_string(get) -> <<"get">>;
+iq_type_to_string(result) -> <<"result">>;
+iq_type_to_string(error) -> <<"error">>.
+-spec(iq_to_xml/1 ::
+(
+ IQ :: iq())
+ -> xmlel()
+).
iq_to_xml(#iq{id = ID, type = Type, sub_el = SubEl}) ->
- if
- ID /= "" ->
- {xmlelement, "iq",
- [{"id", ID}, {"type", iq_type_to_string(Type)}], SubEl};
- true ->
- {xmlelement, "iq",
- [{"type", iq_type_to_string(Type)}], SubEl}
+ if ID /= <<"">> ->
+ #xmlel{name = <<"iq">>,
+ attrs =
+ [{<<"id">>, ID}, {<<"type">>, iq_type_to_string(Type)}],
+ children = SubEl};
+ true ->
+ #xmlel{name = <<"iq">>,
+ attrs = [{<<"type">>, iq_type_to_string(Type)}],
+ children = SubEl}
end.
-
-parse_xdata_submit(El) ->
- {xmlelement, _Name, Attrs, Els} = El,
- case xml:get_attr_s("type", Attrs) of
- "submit" ->
- lists:reverse(parse_xdata_fields(Els, []));
- "form" -> %% This is a workaround to accept Psi's wrong forms
- lists:reverse(parse_xdata_fields(Els, []));
- _ ->
- invalid
+-spec(parse_xdata_submit/1 ::
+(
+ El :: xmlel())
+ -> [{Var::binary(), Values::[binary()]}]
+ %%
+ | 'invalid'
+).
+
+parse_xdata_submit(#xmlel{attrs = Attrs, children = Els}) ->
+ case xml:get_attr_s(<<"type">>, Attrs) of
+ <<"submit">> ->
+ lists:reverse(parse_xdata_fields(Els, []));
+ <<"form">> -> %% This is a workaround to accept Psi's wrong forms
+ lists:reverse(parse_xdata_fields(Els, []));
+ _ ->
+ invalid
end.
-parse_xdata_fields([], Res) ->
- Res;
-parse_xdata_fields([{xmlelement, Name, Attrs, SubEls} | Els], Res) ->
- case Name of
- "field" ->
- case xml:get_attr_s("var", Attrs) of
- "" ->
- parse_xdata_fields(Els, Res);
- Var ->
- Field =
- {Var, lists:reverse(parse_xdata_values(SubEls, []))},
- parse_xdata_fields(Els, [Field | Res])
- end;
- _ ->
- parse_xdata_fields(Els, Res)
+-spec(parse_xdata_fields/2 ::
+(
+ Xmlels :: [xmlel() | cdata()],
+ Res :: [{Var::binary(), Values :: [binary()]}])
+ -> [{Var::binary(), Values::[binary()]}]
+).
+
+parse_xdata_fields([], Res) -> Res;
+parse_xdata_fields([#xmlel{name = <<"field">>, attrs = Attrs, children = SubEls}
+ | Els], Res) ->
+ case xml:get_attr_s(<<"var">>, Attrs) of
+ <<>> ->
+ parse_xdata_fields(Els, Res);
+ Var ->
+ Field = {Var, lists:reverse(parse_xdata_values(SubEls, []))},
+ parse_xdata_fields(Els, [Field | Res])
end;
parse_xdata_fields([_ | Els], Res) ->
parse_xdata_fields(Els, Res).
-parse_xdata_values([], Res) ->
- Res;
-parse_xdata_values([{xmlelement, Name, _Attrs, SubEls} | Els], Res) ->
- case Name of
- "value" ->
- Val = xml:get_cdata(SubEls),
- parse_xdata_values(Els, [Val | Res]);
- _ ->
- parse_xdata_values(Els, Res)
- end;
+-spec(parse_xdata_values/2 ::
+(
+ Xmlels :: [xmlel() | cdata()],
+ Res :: [binary()])
+ -> [binary()]
+).
+
+parse_xdata_values([], Res) -> Res;
+parse_xdata_values([#xmlel{name = <<"value">>, children = SubEls} | Els], Res) ->
+ Val = xml:get_cdata(SubEls),
+ parse_xdata_values(Els, [Val | Res]);
parse_xdata_values([_ | Els], Res) ->
parse_xdata_values(Els, Res).
-rsm_decode(#iq{sub_el=SubEl})->
- rsm_decode(SubEl);
-rsm_decode({xmlelement, _,_,_}=SubEl)->
- case xml:get_subtag(SubEl,"set") of
- false ->
- none;
- {xmlelement, "set", _Attrs, SubEls}->
- lists:foldl(fun rsm_parse_element/2, #rsm_in{}, SubEls)
- end.
-
-rsm_parse_element({xmlelement, "max",[], _}=Elem, RsmIn)->
- CountStr = xml:get_tag_cdata(Elem),
- {Count, _} = string:to_integer(CountStr),
- RsmIn#rsm_in{max=Count};
+-spec rsm_decode(iq() | xmlel()) -> none | rsm_in().
-rsm_parse_element({xmlelement, "before", [], _}=Elem, RsmIn)->
- UID = xml:get_tag_cdata(Elem),
- RsmIn#rsm_in{direction=before, id=UID};
+rsm_decode(#iq{sub_el = SubEl}) -> rsm_decode(SubEl);
+rsm_decode(#xmlel{} = SubEl) ->
+ case xml:get_subtag(SubEl, <<"set">>) of
+ false -> none;
+ #xmlel{name = <<"set">>, children = SubEls} ->
+ lists:foldl(fun rsm_parse_element/2, #rsm_in{}, SubEls)
+ end.
-rsm_parse_element({xmlelement, "after", [], _}=Elem, RsmIn)->
+rsm_parse_element(#xmlel{name = <<"max">>, attrs = []} =
+ Elem,
+ RsmIn) ->
+ CountStr = xml:get_tag_cdata(Elem),
+ {Count, _} = str:to_integer(CountStr),
+ RsmIn#rsm_in{max = Count};
+rsm_parse_element(#xmlel{name = <<"before">>,
+ attrs = []} =
+ Elem,
+ RsmIn) ->
UID = xml:get_tag_cdata(Elem),
- RsmIn#rsm_in{direction=aft, id=UID};
-
-rsm_parse_element({xmlelement, "index",[], _}=Elem, RsmIn)->
+ RsmIn#rsm_in{direction = before, id = UID};
+rsm_parse_element(#xmlel{name = <<"after">>,
+ attrs = []} =
+ Elem,
+ RsmIn) ->
+ UID = xml:get_tag_cdata(Elem),
+ RsmIn#rsm_in{direction = aft, id = UID};
+rsm_parse_element(#xmlel{name = <<"index">>,
+ attrs = []} =
+ Elem,
+ RsmIn) ->
IndexStr = xml:get_tag_cdata(Elem),
- {Index, _} = string:to_integer(IndexStr),
- RsmIn#rsm_in{index=Index};
-
-
-rsm_parse_element(_, RsmIn)->
- RsmIn.
-
-rsm_encode(#iq{sub_el=SubEl}=IQ,RsmOut)->
- Set = {xmlelement, "set", [{"xmlns", ?NS_RSM}],
- lists:reverse(rsm_encode_out(RsmOut))},
- {xmlelement, Name, Attrs, SubEls} = SubEl,
- New = {xmlelement, Name, Attrs, [Set | SubEls]},
- IQ#iq{sub_el=New}.
-
-rsm_encode(none)->
- [];
-rsm_encode(RsmOut)->
- [{xmlelement, "set", [{"xmlns", ?NS_RSM}], lists:reverse(rsm_encode_out(RsmOut))}].
-rsm_encode_out(#rsm_out{count=Count, index=Index, first=First, last=Last})->
+ {Index, _} = str:to_integer(IndexStr),
+ RsmIn#rsm_in{index = Index};
+rsm_parse_element(_, RsmIn) -> RsmIn.
+
+-spec rsm_encode(iq(), rsm_out()) -> iq().
+
+rsm_encode(#iq{sub_el = SubEl} = IQ, RsmOut) ->
+ Set = #xmlel{name = <<"set">>,
+ attrs = [{<<"xmlns">>, ?NS_RSM}],
+ children = lists:reverse(rsm_encode_out(RsmOut))},
+ #xmlel{name = Name, attrs = Attrs, children = SubEls} =
+ SubEl,
+ New = #xmlel{name = Name, attrs = Attrs,
+ children = [Set | SubEls]},
+ IQ#iq{sub_el = New}.
+
+-spec rsm_encode(none | rsm_out()) -> [xmlel()].
+
+rsm_encode(none) -> [];
+rsm_encode(RsmOut) ->
+ [#xmlel{name = <<"set">>,
+ attrs = [{<<"xmlns">>, ?NS_RSM}],
+ children = lists:reverse(rsm_encode_out(RsmOut))}].
+
+rsm_encode_out(#rsm_out{count = Count, index = Index,
+ first = First, last = Last}) ->
El = rsm_encode_first(First, Index, []),
- El2 = rsm_encode_last(Last,El),
+ El2 = rsm_encode_last(Last, El),
rsm_encode_count(Count, El2).
-rsm_encode_first(undefined, undefined, Arr) ->
- Arr;
+rsm_encode_first(undefined, undefined, Arr) -> Arr;
rsm_encode_first(First, undefined, Arr) ->
- [{xmlelement, "first",[], [{xmlcdata, First}]}|Arr];
+ [#xmlel{name = <<"first">>, attrs = [],
+ children = [{xmlcdata, First}]}
+ | Arr];
rsm_encode_first(First, Index, Arr) ->
- [{xmlelement, "first",[{"index", i2l(Index)}], [{xmlcdata, First}]}|Arr].
+ [#xmlel{name = <<"first">>,
+ attrs = [{<<"index">>, i2l(Index)}],
+ children = [{xmlcdata, First}]}
+ | Arr].
rsm_encode_last(undefined, Arr) -> Arr;
rsm_encode_last(Last, Arr) ->
- [{xmlelement, "last",[], [{xmlcdata, Last}]}|Arr].
+ [#xmlel{name = <<"last">>, attrs = [],
+ children = [{xmlcdata, Last}]}
+ | Arr].
-rsm_encode_count(undefined, Arr)-> Arr;
-rsm_encode_count(Count, Arr)->
- [{xmlelement, "count",[], [{xmlcdata, i2l(Count)}]} | Arr].
+rsm_encode_count(undefined, Arr) -> Arr;
+rsm_encode_count(Count, Arr) ->
+ [#xmlel{name = <<"count">>, attrs = [],
+ children = [{xmlcdata, i2l(Count)}]}
+ | Arr].
-i2l(I) when is_integer(I) -> integer_to_list(I);
-i2l(L) when is_list(L) -> L.
+i2l(I) when is_integer(I) -> integer_to_binary(I).
+
+-type tz() :: {binary(), {integer(), integer()}} | {integer(), integer()} | utc.
%% Timezone = utc | {Sign::string(), {Hours, Minutes}} | {Hours, Minutes}
%% Hours = integer()
%% Minutes = integer()
-timestamp_to_iso({{Year, Month, Day}, {Hour, Minute, Second}}, Timezone) ->
+-spec timestamp_to_iso(calendar:datetime(), tz()) -> {binary(), binary()}.
+
+timestamp_to_iso({{Year, Month, Day},
+ {Hour, Minute, Second}},
+ Timezone) ->
Timestamp_string =
- lists:flatten(
- io_lib:format("~4..0w-~2..0w-~2..0wT~2..0w:~2..0w:~2..0w",
- [Year, Month, Day, Hour, Minute, Second])),
- Timezone_string =
- case Timezone of
- utc -> "Z";
- {Sign, {TZh, TZm}} ->
- io_lib:format("~s~2..0w:~2..0w", [Sign, TZh, TZm]);
- {TZh, TZm} ->
- Sign = case TZh >= 0 of
- true -> "+";
- false -> "-"
- end,
- io_lib:format("~s~2..0w:~2..0w", [Sign, abs(TZh),TZm])
- end,
- {Timestamp_string, Timezone_string}.
-
-timestamp_to_iso({{Year, Month, Day}, {Hour, Minute, Second}}) ->
- lists:flatten(
- io_lib:format("~4..0w~2..0w~2..0wT~2..0w:~2..0w:~2..0w",
- [Year, Month, Day, Hour, Minute, Second])).
+ lists:flatten(io_lib:format("~4..0w-~2..0w-~2..0wT~2..0w:~2..0w:~2..0w",
+ [Year, Month, Day, Hour, Minute, Second])),
+ Timezone_string = case Timezone of
+ utc -> "Z";
+ {Sign, {TZh, TZm}} ->
+ io_lib:format("~s~2..0w:~2..0w", [Sign, TZh, TZm]);
+ {TZh, TZm} ->
+ Sign = case TZh >= 0 of
+ true -> "+";
+ false -> "-"
+ end,
+ io_lib:format("~s~2..0w:~2..0w",
+ [Sign, abs(TZh), TZm])
+ end,
+ {iolist_to_binary(Timestamp_string), iolist_to_binary(Timezone_string)}.
+
+-spec timestamp_to_iso(calendar:datetime()) -> binary().
+
+timestamp_to_iso({{Year, Month, Day},
+ {Hour, Minute, Second}}) ->
+ iolist_to_binary(io_lib:format("~4..0w~2..0w~2..0wT~2..0w:~2..0w:~2..0w",
+ [Year, Month, Day, Hour, Minute, Second])).
+
+-spec timestamp_to_xml(calendar:datetime(), tz(), jid(), binary()) -> xmlel().
timestamp_to_xml(DateTime, Timezone, FromJID, Desc) ->
- {T_string, Tz_string} = timestamp_to_iso(DateTime, Timezone),
+ {T_string, Tz_string} = timestamp_to_iso(DateTime,
+ Timezone),
Text = [{xmlcdata, Desc}],
From = jlib:jid_to_string(FromJID),
- {xmlelement, "delay",
- [{"xmlns", ?NS_DELAY},
- {"from", From},
- {"stamp", T_string ++ Tz_string}],
- Text}.
-
%% TODO: Remove this function once XEP-0091 is Obsolete
-timestamp_to_xml({{Year, Month, Day}, {Hour, Minute, Second}}) ->
- {xmlelement, "x",
- [{"xmlns", ?NS_DELAY91},
- {"stamp", lists:flatten(
- io_lib:format("~4..0w~2..0w~2..0wT~2..0w:~2..0w:~2..0w",
- [Year, Month, Day, Hour, Minute, Second]))}],
- []}.
+ #xmlel{name = <<"delay">>,
+ attrs =
+ [{<<"xmlns">>, ?NS_DELAY}, {<<"from">>, From},
+ {<<"stamp">>, <<T_string/binary, Tz_string/binary>>}],
+ children = Text}.
+
+-spec timestamp_to_xml(calendar:datetime()) -> xmlel().
+
+timestamp_to_xml({{Year, Month, Day},
+ {Hour, Minute, Second}}) ->
+ #xmlel{name = <<"x">>,
+ attrs =
+ [{<<"xmlns">>, ?NS_DELAY91},
+ {<<"stamp">>,
+ iolist_to_binary(io_lib:format("~4..0w~2..0w~2..0wT~2..0w:~2..0w:~2..0w",
+ [Year, Month, Day, Hour, Minute,
+ Second]))}],
+ children = []}.
+
+-spec now_to_utc_string(erlang:timestamp()) -> binary().
now_to_utc_string({MegaSecs, Secs, MicroSecs}) ->
{{Year, Month, Day}, {Hour, Minute, Second}} =
- calendar:now_to_universal_time({MegaSecs, Secs, MicroSecs}),
- lists:flatten(
- io_lib:format("~4..0w-~2..0w-~2..0wT~2..0w:~2..0w:~2..0w.~6..0wZ",
- [Year, Month, Day, Hour, Minute, Second, MicroSecs])).
+ calendar:now_to_universal_time({MegaSecs, Secs,
+ MicroSecs}),
+ list_to_binary(io_lib:format("~4..0w-~2..0w-~2..0wT~2..0w:~2..0w:~2..0w.~6."
+ ".0wZ",
+ [Year, Month, Day, Hour, Minute, Second,
+ MicroSecs])).
+
+-spec now_to_local_string(erlang:timestamp()) -> binary().
now_to_local_string({MegaSecs, Secs, MicroSecs}) ->
- LocalTime = calendar:now_to_local_time({MegaSecs, Secs, MicroSecs}),
- UTCTime = calendar:now_to_universal_time({MegaSecs, Secs, MicroSecs}),
- Seconds = calendar:datetime_to_gregorian_seconds(LocalTime) -
- calendar:datetime_to_gregorian_seconds(UTCTime),
- {{H, M, _}, Sign} = if
- Seconds < 0 ->
- {calendar:seconds_to_time(-Seconds), "-"};
- true ->
- {calendar:seconds_to_time(Seconds), "+"}
- end,
- {{Year, Month, Day}, {Hour, Minute, Second}} = LocalTime,
- lists:flatten(
- io_lib:format("~4..0w-~2..0w-~2..0wT~2..0w:~2..0w:~2..0w.~6..0w~s~2..0w:~2..0w",
- [Year, Month, Day, Hour, Minute, Second, MicroSecs, Sign, H, M])).
+ LocalTime = calendar:now_to_local_time({MegaSecs, Secs,
+ MicroSecs}),
+ UTCTime = calendar:now_to_universal_time({MegaSecs,
+ Secs, MicroSecs}),
+ Seconds =
+ calendar:datetime_to_gregorian_seconds(LocalTime) -
+ calendar:datetime_to_gregorian_seconds(UTCTime),
+ {{H, M, _}, Sign} = if Seconds < 0 ->
+ {calendar:seconds_to_time(-Seconds), "-"};
+ true -> {calendar:seconds_to_time(Seconds), "+"}
+ end,
+ {{Year, Month, Day}, {Hour, Minute, Second}} =
+ LocalTime,
+ list_to_binary(io_lib:format("~4..0w-~2..0w-~2..0wT~2..0w:~2..0w:~2..0w.~6."
+ ".0w~s~2..0w:~2..0w",
+ [Year, Month, Day, Hour, Minute, Second,
+ MicroSecs, Sign, H, M])).
+-spec datetime_string_to_timestamp(binary()) -> undefined | erlang:timestamp().
-% yyyy-mm-ddThh:mm:ss[.sss]{Z|{+|-}hh:mm} -> {MegaSecs, Secs, MicroSecs}
datetime_string_to_timestamp(TimeStr) ->
case catch parse_datetime(TimeStr) of
- {'EXIT', _Err} ->
- undefined;
- TimeStamp ->
- TimeStamp
+ {'EXIT', _Err} -> undefined;
+ TimeStamp -> TimeStamp
end.
parse_datetime(TimeStr) ->
- [Date, Time] = string:tokens(TimeStr, "T"),
+ [Date, Time] = str:tokens(TimeStr, <<"T">>),
D = parse_date(Date),
{T, MS, TZH, TZM} = parse_time(Time),
S = calendar:datetime_to_gregorian_seconds({D, T}),
- S1 = calendar:datetime_to_gregorian_seconds({{1970, 1, 1}, {0, 0, 0}}),
- Seconds = (S - S1) - TZH * 60 * 60 - TZM * 60,
+ S1 = calendar:datetime_to_gregorian_seconds({{1970, 1,
+ 1},
+ {0, 0, 0}}),
+ Seconds = S - S1 - TZH * 60 * 60 - TZM * 60,
{Seconds div 1000000, Seconds rem 1000000, MS}.
% yyyy-mm-dd
parse_date(Date) ->
- [Y, M, D] = string:tokens(Date, "-"),
- Date1 = {list_to_integer(Y), list_to_integer(M), list_to_integer(D)},
+ [Y, M, D] = str:tokens(Date, <<"-">>),
+ Date1 = {binary_to_integer(Y), binary_to_integer(M),
+ binary_to_integer(D)},
case calendar:valid_date(Date1) of
- true ->
- Date1;
- _ ->
- false
+ true -> Date1;
+ _ -> false
end.
% hh:mm:ss[.sss]TZD
parse_time(Time) ->
- case string:str(Time, "Z") of
- 0 ->
- parse_time_with_timezone(Time);
- _ ->
- [T | _] = string:tokens(Time, "Z"),
- {TT, MS} = parse_time1(T),
- {TT, MS, 0, 0}
+ case str:str(Time, <<"Z">>) of
+ 0 -> parse_time_with_timezone(Time);
+ _ ->
+ [T | _] = str:tokens(Time, <<"Z">>),
+ {TT, MS} = parse_time1(T),
+ {TT, MS, 0, 0}
end.
parse_time_with_timezone(Time) ->
- case string:str(Time, "+") of
- 0 ->
- case string:str(Time, "-") of
- 0 ->
- false;
- _ ->
- parse_time_with_timezone(Time, "-")
- end;
- _ ->
- parse_time_with_timezone(Time, "+")
+ case str:str(Time, <<"+">>) of
+ 0 ->
+ case str:str(Time, <<"-">>) of
+ 0 -> false;
+ _ -> parse_time_with_timezone(Time, <<"-">>)
+ end;
+ _ -> parse_time_with_timezone(Time, <<"+">>)
end.
parse_time_with_timezone(Time, Delim) ->
- [T, TZ] = string:tokens(Time, Delim),
+ [T, TZ] = str:tokens(Time, Delim),
{TZH, TZM} = parse_timezone(TZ),
{TT, MS} = parse_time1(T),
case Delim of
- "-" ->
- {TT, MS, -TZH, -TZM};
- "+" ->
- {TT, MS, TZH, TZM}
+ <<"-">> -> {TT, MS, -TZH, -TZM};
+ <<"+">> -> {TT, MS, TZH, TZM}
end.
parse_timezone(TZ) ->
- [H, M] = string:tokens(TZ, ":"),
+ [H, M] = str:tokens(TZ, <<":">>),
{[H1, M1], true} = check_list([{H, 12}, {M, 60}]),
{H1, M1}.
parse_time1(Time) ->
- [HMS | T] = string:tokens(Time, "."),
+ [HMS | T] = str:tokens(Time, <<".">>),
MS = case T of
- [] ->
- 0;
- [Val] ->
- list_to_integer(string:left(Val, 6, $0))
+ [] -> 0;
+ [Val] -> binary_to_integer(str:left(Val, 6, $0))
end,
- [H, M, S] = string:tokens(HMS, ":"),
- {[H1, M1, S1], true} = check_list([{H, 24}, {M, 60}, {S, 60}]),
+ [H, M, S] = str:tokens(HMS, <<":">>),
+ {[H1, M1, S1], true} = check_list([{H, 24}, {M, 60},
+ {S, 60}]),
{{H1, M1, S1}, MS}.
check_list(List) ->
- lists:mapfoldl(
- fun({L, N}, B)->
- V = list_to_integer(L),
- if
- (V >= 0) and (V =< N) ->
- {V, B};
- true ->
- {false, false}
- end
- end, true, List).
-
+ lists:mapfoldl(fun ({L, N}, B) ->
+ V = binary_to_integer(L),
+ if (V >= 0) and (V =< N) -> {V, B};
+ true -> {false, false}
+ end
+ end,
+ true, List).
%
% Base64 stuff (based on httpd_util.erl)
%
+-spec decode_base64(binary()) -> binary().
+
decode_base64(S) ->
- decode1_base64([C || C <- S,
- C /= $ ,
- C /= $\t,
- C /= $\n,
- C /= $\r]).
-
-decode1_base64([]) ->
- [];
-decode1_base64([Sextet1,Sextet2,$=,$=|Rest]) ->
- Bits2x6=
- (d(Sextet1) bsl 18) bor
- (d(Sextet2) bsl 12),
- Octet1=Bits2x6 bsr 16,
- [Octet1|decode1_base64(Rest)];
-decode1_base64([Sextet1,Sextet2,Sextet3,$=|Rest]) ->
- Bits3x6=
- (d(Sextet1) bsl 18) bor
- (d(Sextet2) bsl 12) bor
- (d(Sextet3) bsl 6),
- Octet1=Bits3x6 bsr 16,
- Octet2=(Bits3x6 bsr 8) band 16#ff,
- [Octet1,Octet2|decode1_base64(Rest)];
-decode1_base64([Sextet1,Sextet2,Sextet3,Sextet4|Rest]) ->
- Bits4x6=
- (d(Sextet1) bsl 18) bor
- (d(Sextet2) bsl 12) bor
- (d(Sextet3) bsl 6) bor
- d(Sextet4),
- Octet1=Bits4x6 bsr 16,
- Octet2=(Bits4x6 bsr 8) band 16#ff,
- Octet3=Bits4x6 band 16#ff,
- [Octet1,Octet2,Octet3|decode1_base64(Rest)];
-decode1_base64(_CatchAll) ->
- "".
-
-d(X) when X >= $A, X =<$Z ->
- X-65;
-d(X) when X >= $a, X =<$z ->
- X-71;
-d(X) when X >= $0, X =<$9 ->
- X+4;
+ decode_base64_bin(S, <<>>).
+
+take_without_spaces(Bin, Count) ->
+ take_without_spaces(Bin, Count, <<>>).
+
+take_without_spaces(Bin, 0, Acc) ->
+ {Acc, Bin};
+take_without_spaces(<<>>, _, Acc) ->
+ {Acc, <<>>};
+take_without_spaces(<<$\s, Tail/binary>>, Count, Acc) ->
+ take_without_spaces(Tail, Count, Acc);
+take_without_spaces(<<$\t, Tail/binary>>, Count, Acc) ->
+ take_without_spaces(Tail, Count, Acc);
+take_without_spaces(<<$\n, Tail/binary>>, Count, Acc) ->
+ take_without_spaces(Tail, Count, Acc);
+take_without_spaces(<<$\r, Tail/binary>>, Count, Acc) ->
+ take_without_spaces(Tail, Count, Acc);
+take_without_spaces(<<Char:8, Tail/binary>>, Count, Acc) ->
+ take_without_spaces(Tail, Count-1, <<Acc/binary, Char:8>>).
+
+decode_base64_bin(<<>>, Acc) ->
+ Acc;
+decode_base64_bin(Bin, Acc) ->
+ case take_without_spaces(Bin, 4) of
+ {<<A, B, $=, $=>>, _} ->
+ <<Acc/binary, (d(A)):6, (d(B) bsr 4):2>>;
+ {<<A, B, C, $=>>, _} ->
+ <<Acc/binary, (d(A)):6, (d(B)):6, (d(C) bsr 2):4>>;
+ {<<A, B, C, D>>, Tail} ->
+ Acc2 = <<Acc/binary, (d(A)):6, (d(B)):6, (d(C)):6, (d(D)):6>>,
+ decode_base64_bin(Tail, Acc2);
+ _ ->
+ <<"">>
+ end.
+
+d(X) when X >= $A, X =< $Z -> X - 65;
+d(X) when X >= $a, X =< $z -> X - 71;
+d(X) when X >= $0, X =< $9 -> X + 4;
d($+) -> 62;
d($/) -> 63;
d(_) -> 63.
-encode_base64([]) ->
- [];
-encode_base64([A]) ->
- [e(A bsr 2), e((A band 3) bsl 4), $=, $=];
-encode_base64([A,B]) ->
- [e(A bsr 2), e(((A band 3) bsl 4) bor (B bsr 4)), e((B band 15) bsl 2), $=];
-encode_base64([A,B,C|Ls]) ->
- encode_base64_do(A,B,C, Ls).
-encode_base64_do(A,B,C, Rest) ->
- BB = (A bsl 16) bor (B bsl 8) bor C,
- [e(BB bsr 18), e((BB bsr 12) band 63),
- e((BB bsr 6) band 63), e(BB band 63)|encode_base64(Rest)].
-
-e(X) when X >= 0, X < 26 -> X+65;
-e(X) when X>25, X<52 -> X+71;
-e(X) when X>51, X<62 -> X-4;
-e(62) -> $+;
-e(63) -> $/;
-e(X) -> exit({bad_encode_base64_token, X}).
-
%% Convert Erlang inet IP to list
+-spec encode_base64(binary()) -> binary().
+
+encode_base64(Data) ->
+ encode_base64_bin(Data, <<>>).
+
+encode_base64_bin(<<A:6, B:6, C:6, D:6, Tail/binary>>, Acc) ->
+ encode_base64_bin(Tail, <<Acc/binary, (e(A)):8, (e(B)):8, (e(C)):8, (e(D)):8>>);
+encode_base64_bin(<<A:6, B:6, C:4>>, Acc) ->
+ <<Acc/binary, (e(A)):8, (e(B)):8, (e(C bsl 2)):8, $=>>;
+encode_base64_bin(<<A:6, B:2>>, Acc) ->
+ <<Acc/binary, (e(A)):8, (e(B bsl 4)):8, $=, $=>>;
+encode_base64_bin(<<>>, Acc) ->
+ Acc.
+
+e(X) when X >= 0, X < 26 -> X + 65;
+e(X) when X > 25, X < 52 -> X + 71;
+e(X) when X > 51, X < 62 -> X - 4;
+e(62) -> $+;
+e(63) -> $/;
+e(X) -> exit({bad_encode_base64_token, X}).
+
+-spec ip_to_list(inet:ip_address() | undefined |
+ {inet:ip_address(), inet:port_number()}) -> binary().
+
ip_to_list({IP, _Port}) ->
ip_to_list(IP);
-ip_to_list({_,_,_,_,_,_,_,_} = Ipv6Address) ->
- inet_parse:ntoa(Ipv6Address);
%% This function clause could use inet_parse too:
-ip_to_list({A,B,C,D}) ->
- lists:flatten(io_lib:format("~w.~w.~w.~w",[A,B,C,D]));
+ip_to_list(undefined) ->
+ <<"unknown">>;
ip_to_list(IP) ->
- lists:flatten(io_lib:format("~w", [IP])).
+ list_to_binary(inet_parse:ntoa(IP)).
+
+binary_to_atom(Bin) ->
+ erlang:binary_to_atom(Bin, utf8).
+
+binary_to_integer(Bin) ->
+ list_to_integer(binary_to_list(Bin)).
+
+binary_to_integer(Bin, Base) ->
+ list_to_integer(binary_to_list(Bin), Base).
+
+integer_to_binary(I) ->
+ list_to_binary(integer_to_list(I)).
+
+integer_to_binary(I, Base) ->
+ list_to_binary(erlang:integer_to_list(I, Base)).
+
+tuple_to_binary(T) ->
+ iolist_to_binary(tuple_to_list(T)).
+
+atom_to_binary(A) ->
+ erlang:atom_to_binary(A, utf8).
diff --git a/src/jlib.hrl b/src/jlib.hrl
index 0e5c8322b..b0f59e288 100644
--- a/src/jlib.hrl
+++ b/src/jlib.hrl
@@ -19,318 +19,687 @@
%%%
%%%----------------------------------------------------------------------
--define(NS_DISCO_ITEMS, "http://jabber.org/protocol/disco#items").
--define(NS_DISCO_INFO, "http://jabber.org/protocol/disco#info").
--define(NS_VCARD, "vcard-temp").
--define(NS_VCARD_UPDATE, "vcard-temp:x:update").
--define(NS_AUTH, "jabber:iq:auth").
--define(NS_AUTH_ERROR, "jabber:iq:auth:error").
--define(NS_REGISTER, "jabber:iq:register").
--define(NS_SEARCH, "jabber:iq:search").
--define(NS_ROSTER, "jabber:iq:roster").
--define(NS_ROSTER_VER, "urn:xmpp:features:rosterver").
--define(NS_PRIVACY, "jabber:iq:privacy").
--define(NS_BLOCKING, "urn:xmpp:blocking").
--define(NS_PRIVATE, "jabber:iq:private").
--define(NS_VERSION, "jabber:iq:version").
--define(NS_TIME90, "jabber:iq:time"). % TODO: Remove once XEP-0090 is Obsolete
--define(NS_TIME, "urn:xmpp:time").
--define(NS_LAST, "jabber:iq:last").
--define(NS_XDATA, "jabber:x:data").
--define(NS_IQDATA, "jabber:iq:data").
--define(NS_DELAY91, "jabber:x:delay"). % TODO: Remove once XEP-0091 is Obsolete
--define(NS_DELAY, "urn:xmpp:delay").
--define(NS_EXPIRE, "jabber:x:expire").
--define(NS_EVENT, "jabber:x:event").
--define(NS_CHATSTATES, "http://jabber.org/protocol/chatstates").
--define(NS_XCONFERENCE, "jabber:x:conference").
--define(NS_STATS, "http://jabber.org/protocol/stats").
--define(NS_MUC, "http://jabber.org/protocol/muc").
--define(NS_MUC_USER, "http://jabber.org/protocol/muc#user").
--define(NS_MUC_ADMIN, "http://jabber.org/protocol/muc#admin").
--define(NS_MUC_OWNER, "http://jabber.org/protocol/muc#owner").
--define(NS_MUC_UNIQUE, "http://jabber.org/protocol/muc#unique").
--define(NS_PUBSUB, "http://jabber.org/protocol/pubsub").
--define(NS_PUBSUB_EVENT, "http://jabber.org/protocol/pubsub#event").
--define(NS_PUBSUB_OWNER, "http://jabber.org/protocol/pubsub#owner").
--define(NS_PUBSUB_NMI, "http://jabber.org/protocol/pubsub#node-meta-info").
--define(NS_PUBSUB_ERRORS,"http://jabber.org/protocol/pubsub#errors").
--define(NS_PUBSUB_NODE_CONFIG, "http://jabber.org/protocol/pubsub#node_config").
--define(NS_PUBSUB_SUB_OPTIONS, "http://jabber.org/protocol/pubsub#subscribe_options").
--define(NS_PUBSUB_SUB_AUTH, "http://jabber.org/protocol/pubsub#subscribe_authorization").
--define(NS_PUBSUB_GET_PENDING, "http://jabber.org/protocol/pubsub#get-pending").
--define(NS_COMMANDS, "http://jabber.org/protocol/commands").
--define(NS_BYTESTREAMS, "http://jabber.org/protocol/bytestreams").
--define(NS_ADMIN, "http://jabber.org/protocol/admin").
--define(NS_SERVERINFO, "http://jabber.org/network/serverinfo").
-
--define(NS_RSM, "http://jabber.org/protocol/rsm").
--define(NS_EJABBERD_CONFIG, "ejabberd:config").
-
--define(NS_STREAM, "http://etherx.jabber.org/streams").
-
--define(NS_STANZAS, "urn:ietf:params:xml:ns:xmpp-stanzas").
--define(NS_STREAMS, "urn:ietf:params:xml:ns:xmpp-streams").
-
--define(NS_TLS, "urn:ietf:params:xml:ns:xmpp-tls").
--define(NS_SASL, "urn:ietf:params:xml:ns:xmpp-sasl").
--define(NS_SESSION, "urn:ietf:params:xml:ns:xmpp-session").
--define(NS_BIND, "urn:ietf:params:xml:ns:xmpp-bind").
-
--define(NS_FEATURE_IQAUTH, "http://jabber.org/features/iq-auth").
--define(NS_FEATURE_IQREGISTER, "http://jabber.org/features/iq-register").
--define(NS_FEATURE_COMPRESS, "http://jabber.org/features/compress").
--define(NS_FEATURE_MSGOFFLINE, "msgoffline").
-
--define(NS_COMPRESS, "http://jabber.org/protocol/compress").
-
--define(NS_CAPS, "http://jabber.org/protocol/caps").
--define(NS_SHIM, "http://jabber.org/protocol/shim").
--define(NS_ADDRESS, "http://jabber.org/protocol/address").
-
-%% CAPTCHA related NSes.
--define(NS_OOB, "jabber:x:oob").
--define(NS_CAPTCHA, "urn:xmpp:captcha").
--define(NS_MEDIA, "urn:xmpp:media-element").
--define(NS_BOB, "urn:xmpp:bob").
-
-% TODO: remove "code" attribute (currently it used for backward-compatibility)
+
+-define(NS_DISCO_ITEMS,
+ <<"http://jabber.org/protocol/disco#items">>).
+
+-define(NS_DISCO_INFO,
+ <<"http://jabber.org/protocol/disco#info">>).
+
+-define(NS_VCARD, <<"vcard-temp">>).
+
+-define(NS_VCARD_UPDATE, <<"vcard-temp:x:update">>).
+
+-define(NS_AUTH, <<"jabber:iq:auth">>).
+
+-define(NS_AUTH_ERROR, <<"jabber:iq:auth:error">>).
+
+-define(NS_REGISTER, <<"jabber:iq:register">>).
+
+-define(NS_SEARCH, <<"jabber:iq:search">>).
+
+-define(NS_ROSTER, <<"jabber:iq:roster">>).
+
+-define(NS_ROSTER_VER,
+ <<"urn:xmpp:features:rosterver">>).
+
+-define(NS_PRIVACY, <<"jabber:iq:privacy">>).
+
+-define(NS_BLOCKING, <<"urn:xmpp:blocking">>).
+
+-define(NS_PRIVATE, <<"jabber:iq:private">>).
+
+-define(NS_VERSION, <<"jabber:iq:version">>).
+
+-define(NS_TIME90, <<"jabber:iq:time">>).
+
+-define(NS_TIME, <<"urn:xmpp:time">>).
+
+-define(NS_LAST, <<"jabber:iq:last">>).
+
+-define(NS_XDATA, <<"jabber:x:data">>).
+
+-define(NS_IQDATA, <<"jabber:iq:data">>).
+
+-define(NS_DELAY91, <<"jabber:x:delay">>).
+
+-define(NS_DELAY, <<"urn:xmpp:delay">>).
+
+-define(NS_EXPIRE, <<"jabber:x:expire">>).
+
+-define(NS_EVENT, <<"jabber:x:event">>).
+
+-define(NS_CHATSTATES,
+ <<"http://jabber.org/protocol/chatstates">>).
+
+-define(NS_XCONFERENCE, <<"jabber:x:conference">>).
+
+-define(NS_STATS,
+ <<"http://jabber.org/protocol/stats">>).
+
+-define(NS_MUC, <<"http://jabber.org/protocol/muc">>).
+
+-define(NS_MUC_USER,
+ <<"http://jabber.org/protocol/muc#user">>).
+
+-define(NS_MUC_ADMIN,
+ <<"http://jabber.org/protocol/muc#admin">>).
+
+-define(NS_MUC_OWNER,
+ <<"http://jabber.org/protocol/muc#owner">>).
+
+-define(NS_MUC_UNIQUE,
+ <<"http://jabber.org/protocol/muc#unique">>).
+
+-define(NS_PUBSUB,
+ <<"http://jabber.org/protocol/pubsub">>).
+
+-define(NS_PUBSUB_EVENT,
+ <<"http://jabber.org/protocol/pubsub#event">>).
+
+-define(NS_PUBSUB_META_DATA,
+ <<"http://jabber.org/protocol/pubsub#meta-data">>).
+
+-define(NS_PUBSUB_OWNER,
+ <<"http://jabber.org/protocol/pubsub#owner">>).
+
+-define(NS_PUBSUB_NMI,
+ <<"http://jabber.org/protocol/pubsub#node-meta-info">>).
+
+-define(NS_PUBSUB_ERRORS,
+ <<"http://jabber.org/protocol/pubsub#errors">>).
+
+-define(NS_PUBSUB_NODE_CONFIG,
+ <<"http://jabber.org/protocol/pubsub#node_config">>).
+
+-define(NS_PUBSUB_SUB_OPTIONS,
+ <<"http://jabber.org/protocol/pubsub#subscribe_options">>).
+
+-define(NS_PUBSUB_SUBSCRIBE_OPTIONS,
+ <<"http://jabber.org/protocol/pubsub#subscribe_options">>).
+
+-define(NS_PUBSUB_PUBLISH_OPTIONS,
+ <<"http://jabber.org/protocol/pubsub#publish_options">>).
+
+-define(NS_PUBSUB_SUB_AUTH,
+ <<"http://jabber.org/protocol/pubsub#subscribe_authorization">>).
+
+-define(NS_PUBSUB_GET_PENDING,
+ <<"http://jabber.org/protocol/pubsub#get-pending">>).
+
+-define(NS_COMMANDS,
+ <<"http://jabber.org/protocol/commands">>).
+
+-define(NS_BYTESTREAMS,
+ <<"http://jabber.org/protocol/bytestreams">>).
+
+-define(NS_ADMIN,
+ <<"http://jabber.org/protocol/admin">>).
+-define(NS_ADMIN_ANNOUNCE,
+ <<"http://jabber.org/protocol/admin#announce">>).
+-define(NS_ADMIN_ANNOUNCE_ALL,
+ <<"http://jabber.org/protocol/admin#announce-all">>).
+-define(NS_ADMIN_SET_MOTD,
+ <<"http://jabber.org/protocol/admin#set-motd">>).
+-define(NS_ADMIN_EDIT_MOTD,
+ <<"http://jabber.org/protocol/admin#edit-motd">>).
+-define(NS_ADMIN_DELETE_MOTD,
+ <<"http://jabber.org/protocol/admin#delete-motd">>).
+-define(NS_ADMIN_ANNOUNCE_ALLHOSTS,
+ <<"http://jabber.org/protocol/admin#announce-allhosts">>).
+-define(NS_ADMIN_ANNOUNCE_ALL_ALLHOSTS,
+ <<"http://jabber.org/protocol/admin#announce-all-allhosts">>).
+-define(NS_ADMIN_SET_MOTD_ALLHOSTS,
+ <<"http://jabber.org/protocol/admin#set-motd-allhosts">>).
+-define(NS_ADMIN_EDIT_MOTD_ALLHOSTS,
+ <<"http://jabber.org/protocol/admin#edit-motd-allhosts">>).
+-define(NS_ADMIN_DELETE_MOTD_ALLHOSTS,
+ <<"http://jabber.org/protocol/admin#delete-motd-allhosts">>).
+
+-define(NS_SERVERINFO,
+ <<"http://jabber.org/network/serverinfo">>).
+
+-define(NS_RSM, <<"http://jabber.org/protocol/rsm">>).
+
+-define(NS_EJABBERD_CONFIG, <<"ejabberd:config">>).
+
+-define(NS_STREAM,
+ <<"http://etherx.jabber.org/streams">>).
+
+-define(NS_STANZAS,
+ <<"urn:ietf:params:xml:ns:xmpp-stanzas">>).
+
+-define(NS_STREAMS,
+ <<"urn:ietf:params:xml:ns:xmpp-streams">>).
+
+-define(NS_TLS, <<"urn:ietf:params:xml:ns:xmpp-tls">>).
+
+-define(NS_SASL,
+ <<"urn:ietf:params:xml:ns:xmpp-sasl">>).
+
+-define(NS_SESSION,
+ <<"urn:ietf:params:xml:ns:xmpp-session">>).
+
+-define(NS_BIND,
+ <<"urn:ietf:params:xml:ns:xmpp-bind">>).
+
+-define(NS_FEATURE_IQAUTH,
+ <<"http://jabber.org/features/iq-auth">>).
+
+-define(NS_FEATURE_IQREGISTER,
+ <<"http://jabber.org/features/iq-register">>).
+
+-define(NS_FEATURE_COMPRESS,
+ <<"http://jabber.org/features/compress">>).
+
+-define(NS_FEATURE_MSGOFFLINE, <<"msgoffline">>).
+
+-define(NS_COMPRESS,
+ <<"http://jabber.org/protocol/compress">>).
+
+-define(NS_CAPS, <<"http://jabber.org/protocol/caps">>).
+
+-define(NS_SHIM, <<"http://jabber.org/protocol/shim">>).
+
+-define(NS_ADDRESS,
+ <<"http://jabber.org/protocol/address">>).
+
+-define(NS_OOB, <<"jabber:x:oob">>).
+
+-define(NS_CAPTCHA, <<"urn:xmpp:captcha">>).
+
+-define(NS_MEDIA, <<"urn:xmpp:media-element">>).
+
+-define(NS_BOB, <<"urn:xmpp:bob">>).
+
-define(STANZA_ERROR(Code, Type, Condition),
- {xmlelement, "error",
- [{"code", Code}, {"type", Type}],
- [{xmlelement, Condition, [{"xmlns", ?NS_STANZAS}], []}]}).
+ #xmlel{name = <<"error">>,
+ attrs = [{<<"code">>, Code}, {<<"type">>, Type}],
+ children =
+ [#xmlel{name = Condition,
+ attrs = [{<<"xmlns">>, ?NS_STANZAS}],
+ children = []}]}).
-define(ERR_BAD_FORMAT,
- ?STANZA_ERROR("406", "modify", "bad-format")).
+ ?STANZA_ERROR(<<"406">>, <<"modify">>,
+ <<"bad-format">>)).
+
-define(ERR_BAD_REQUEST,
- ?STANZA_ERROR("400", "modify", "bad-request")).
+ ?STANZA_ERROR(<<"400">>, <<"modify">>,
+ <<"bad-request">>)).
+
-define(ERR_CONFLICT,
- ?STANZA_ERROR("409", "cancel", "conflict")).
+ ?STANZA_ERROR(<<"409">>, <<"cancel">>, <<"conflict">>)).
+
-define(ERR_FEATURE_NOT_IMPLEMENTED,
- ?STANZA_ERROR("501", "cancel", "feature-not-implemented")).
+ ?STANZA_ERROR(<<"501">>, <<"cancel">>,
+ <<"feature-not-implemented">>)).
+
-define(ERR_FORBIDDEN,
- ?STANZA_ERROR("403", "auth", "forbidden")).
+ ?STANZA_ERROR(<<"403">>, <<"auth">>, <<"forbidden">>)).
+
-define(ERR_GONE,
- ?STANZA_ERROR("302", "modify", "gone")).
+ ?STANZA_ERROR(<<"302">>, <<"modify">>, <<"gone">>)).
+
-define(ERR_INTERNAL_SERVER_ERROR,
- ?STANZA_ERROR("500", "wait", "internal-server-error")).
+ ?STANZA_ERROR(<<"500">>, <<"wait">>,
+ <<"internal-server-error">>)).
+
-define(ERR_ITEM_NOT_FOUND,
- ?STANZA_ERROR("404", "cancel", "item-not-found")).
+ ?STANZA_ERROR(<<"404">>, <<"cancel">>,
+ <<"item-not-found">>)).
+
-define(ERR_JID_MALFORMED,
- ?STANZA_ERROR("400", "modify", "jid-malformed")).
+ ?STANZA_ERROR(<<"400">>, <<"modify">>,
+ <<"jid-malformed">>)).
+
-define(ERR_NOT_ACCEPTABLE,
- ?STANZA_ERROR("406", "modify", "not-acceptable")).
+ ?STANZA_ERROR(<<"406">>, <<"modify">>,
+ <<"not-acceptable">>)).
+
-define(ERR_NOT_ALLOWED,
- ?STANZA_ERROR("405", "cancel", "not-allowed")).
+ ?STANZA_ERROR(<<"405">>, <<"cancel">>,
+ <<"not-allowed">>)).
+
-define(ERR_NOT_AUTHORIZED,
- ?STANZA_ERROR("401", "auth", "not-authorized")).
+ ?STANZA_ERROR(<<"401">>, <<"auth">>,
+ <<"not-authorized">>)).
+
-define(ERR_PAYMENT_REQUIRED,
- ?STANZA_ERROR("402", "auth", "payment-required")).
+ ?STANZA_ERROR(<<"402">>, <<"auth">>,
+ <<"payment-required">>)).
+
-define(ERR_RECIPIENT_UNAVAILABLE,
- ?STANZA_ERROR("404", "wait", "recipient-unavailable")).
+ ?STANZA_ERROR(<<"404">>, <<"wait">>,
+ <<"recipient-unavailable">>)).
+
-define(ERR_REDIRECT,
- ?STANZA_ERROR("302", "modify", "redirect")).
+ ?STANZA_ERROR(<<"302">>, <<"modify">>, <<"redirect">>)).
+
-define(ERR_REGISTRATION_REQUIRED,
- ?STANZA_ERROR("407", "auth", "registration-required")).
+ ?STANZA_ERROR(<<"407">>, <<"auth">>,
+ <<"registration-required">>)).
+
-define(ERR_REMOTE_SERVER_NOT_FOUND,
- ?STANZA_ERROR("404", "cancel", "remote-server-not-found")).
+ ?STANZA_ERROR(<<"404">>, <<"cancel">>,
+ <<"remote-server-not-found">>)).
+
-define(ERR_REMOTE_SERVER_TIMEOUT,
- ?STANZA_ERROR("504", "wait", "remote-server-timeout")).
+ ?STANZA_ERROR(<<"504">>, <<"wait">>,
+ <<"remote-server-timeout">>)).
+
-define(ERR_RESOURCE_CONSTRAINT,
- ?STANZA_ERROR("500", "wait", "resource-constraint")).
+ ?STANZA_ERROR(<<"500">>, <<"wait">>,
+ <<"resource-constraint">>)).
+
-define(ERR_SERVICE_UNAVAILABLE,
- ?STANZA_ERROR("503", "cancel", "service-unavailable")).
+ ?STANZA_ERROR(<<"503">>, <<"cancel">>,
+ <<"service-unavailable">>)).
+
-define(ERR_SUBSCRIPTION_REQUIRED,
- ?STANZA_ERROR("407", "auth", "subscription-required")).
+ ?STANZA_ERROR(<<"407">>, <<"auth">>,
+ <<"subscription-required">>)).
+
-define(ERR_UNEXPECTED_REQUEST,
- ?STANZA_ERROR("400", "wait", "unexpected-request")).
+ ?STANZA_ERROR(<<"400">>, <<"wait">>,
+ <<"unexpected-request">>)).
+
-define(ERR_UNEXPECTED_REQUEST_CANCEL,
- ?STANZA_ERROR("401", "cancel", "unexpected-request")).
+ ?STANZA_ERROR(<<"401">>, <<"cancel">>,
+ <<"unexpected-request">>)).
+
%-define(ERR_,
% ?STANZA_ERROR("", "", "")).
--define(STANZA_ERRORT(Code, Type, Condition, Lang, Text),
- {xmlelement, "error",
- [{"code", Code}, {"type", Type}],
- [{xmlelement, Condition, [{"xmlns", ?NS_STANZAS}], []},
- {xmlelement, "text", [{"xmlns", ?NS_STANZAS}],
- [{xmlcdata, translate:translate(Lang, Text)}]}]}).
+-define(STANZA_ERRORT(Code, Type, Condition, Lang,
+ Text),
+ #xmlel{name = <<"error">>,
+ attrs = [{<<"code">>, Code}, {<<"type">>, Type}],
+ children =
+ [#xmlel{name = Condition,
+ attrs = [{<<"xmlns">>, ?NS_STANZAS}], children = []},
+ #xmlel{name = <<"text">>,
+ attrs = [{<<"xmlns">>, ?NS_STANZAS}],
+ children =
+ [{xmlcdata,
+ translate:translate(Lang, Text)}]}]}).
-define(ERRT_BAD_FORMAT(Lang, Text),
- ?STANZA_ERRORT("406", "modify", "bad-format", Lang, Text)).
+ ?STANZA_ERRORT(<<"406">>, <<"modify">>,
+ <<"bad-format">>, Lang, Text)).
+
-define(ERRT_BAD_REQUEST(Lang, Text),
- ?STANZA_ERRORT("400", "modify", "bad-request", Lang, Text)).
+ ?STANZA_ERRORT(<<"400">>, <<"modify">>,
+ <<"bad-request">>, Lang, Text)).
+
-define(ERRT_CONFLICT(Lang, Text),
- ?STANZA_ERRORT("409", "cancel", "conflict", Lang, Text)).
+ ?STANZA_ERRORT(<<"409">>, <<"cancel">>, <<"conflict">>,
+ Lang, Text)).
+
-define(ERRT_FEATURE_NOT_IMPLEMENTED(Lang, Text),
- ?STANZA_ERRORT("501", "cancel", "feature-not-implemented", Lang, Text)).
+ ?STANZA_ERRORT(<<"501">>, <<"cancel">>,
+ <<"feature-not-implemented">>, Lang, Text)).
+
-define(ERRT_FORBIDDEN(Lang, Text),
- ?STANZA_ERRORT("403", "auth", "forbidden", Lang, Text)).
+ ?STANZA_ERRORT(<<"403">>, <<"auth">>, <<"forbidden">>,
+ Lang, Text)).
+
-define(ERRT_GONE(Lang, Text),
- ?STANZA_ERRORT("302", "modify", "gone", Lang, Text)).
+ ?STANZA_ERRORT(<<"302">>, <<"modify">>, <<"gone">>,
+ Lang, Text)).
+
-define(ERRT_INTERNAL_SERVER_ERROR(Lang, Text),
- ?STANZA_ERRORT("500", "wait", "internal-server-error", Lang, Text)).
+ ?STANZA_ERRORT(<<"500">>, <<"wait">>,
+ <<"internal-server-error">>, Lang, Text)).
+
-define(ERRT_ITEM_NOT_FOUND(Lang, Text),
- ?STANZA_ERRORT("404", "cancel", "item-not-found", Lang, Text)).
+ ?STANZA_ERRORT(<<"404">>, <<"cancel">>,
+ <<"item-not-found">>, Lang, Text)).
+
-define(ERRT_JID_MALFORMED(Lang, Text),
- ?STANZA_ERRORT("400", "modify", "jid-malformed", Lang, Text)).
+ ?STANZA_ERRORT(<<"400">>, <<"modify">>,
+ <<"jid-malformed">>, Lang, Text)).
+
-define(ERRT_NOT_ACCEPTABLE(Lang, Text),
- ?STANZA_ERRORT("406", "modify", "not-acceptable", Lang, Text)).
+ ?STANZA_ERRORT(<<"406">>, <<"modify">>,
+ <<"not-acceptable">>, Lang, Text)).
+
-define(ERRT_NOT_ALLOWED(Lang, Text),
- ?STANZA_ERRORT("405", "cancel", "not-allowed", Lang, Text)).
+ ?STANZA_ERRORT(<<"405">>, <<"cancel">>,
+ <<"not-allowed">>, Lang, Text)).
+
-define(ERRT_NOT_AUTHORIZED(Lang, Text),
- ?STANZA_ERRORT("401", "auth", "not-authorized", Lang, Text)).
+ ?STANZA_ERRORT(<<"401">>, <<"auth">>,
+ <<"not-authorized">>, Lang, Text)).
+
-define(ERRT_PAYMENT_REQUIRED(Lang, Text),
- ?STANZA_ERRORT("402", "auth", "payment-required", Lang, Text)).
+ ?STANZA_ERRORT(<<"402">>, <<"auth">>,
+ <<"payment-required">>, Lang, Text)).
+
-define(ERRT_RECIPIENT_UNAVAILABLE(Lang, Text),
- ?STANZA_ERRORT("404", "wait", "recipient-unavailable", Lang, Text)).
+ ?STANZA_ERRORT(<<"404">>, <<"wait">>,
+ <<"recipient-unavailable">>, Lang, Text)).
+
-define(ERRT_REDIRECT(Lang, Text),
- ?STANZA_ERRORT("302", "modify", "redirect", Lang, Text)).
+ ?STANZA_ERRORT(<<"302">>, <<"modify">>, <<"redirect">>,
+ Lang, Text)).
+
-define(ERRT_REGISTRATION_REQUIRED(Lang, Text),
- ?STANZA_ERRORT("407", "auth", "registration-required", Lang, Text)).
+ ?STANZA_ERRORT(<<"407">>, <<"auth">>,
+ <<"registration-required">>, Lang, Text)).
+
-define(ERRT_REMOTE_SERVER_NOT_FOUND(Lang, Text),
- ?STANZA_ERRORT("404", "cancel", "remote-server-not-found", Lang, Text)).
+ ?STANZA_ERRORT(<<"404">>, <<"cancel">>,
+ <<"remote-server-not-found">>, Lang, Text)).
+
-define(ERRT_REMOTE_SERVER_TIMEOUT(Lang, Text),
- ?STANZA_ERRORT("504", "wait", "remote-server-timeout", Lang, Text)).
+ ?STANZA_ERRORT(<<"504">>, <<"wait">>,
+ <<"remote-server-timeout">>, Lang, Text)).
+
-define(ERRT_RESOURCE_CONSTRAINT(Lang, Text),
- ?STANZA_ERRORT("500", "wait", "resource-constraint", Lang, Text)).
+ ?STANZA_ERRORT(<<"500">>, <<"wait">>,
+ <<"resource-constraint">>, Lang, Text)).
+
-define(ERRT_SERVICE_UNAVAILABLE(Lang, Text),
- ?STANZA_ERRORT("503", "cancel", "service-unavailable", Lang, Text)).
+ ?STANZA_ERRORT(<<"503">>, <<"cancel">>,
+ <<"service-unavailable">>, Lang, Text)).
+
-define(ERRT_SUBSCRIPTION_REQUIRED(Lang, Text),
- ?STANZA_ERRORT("407", "auth", "subscription-required", Lang, Text)).
+ ?STANZA_ERRORT(<<"407">>, <<"auth">>,
+ <<"subscription-required">>, Lang, Text)).
+
-define(ERRT_UNEXPECTED_REQUEST(Lang, Text),
- ?STANZA_ERRORT("400", "wait", "unexpected-request", Lang, Text)).
+ ?STANZA_ERRORT(<<"400">>, <<"wait">>,
+ <<"unexpected-request">>, Lang, Text)).
-% Auth stanza errors
-define(ERR_AUTH_NO_RESOURCE_PROVIDED(Lang),
- ?ERRT_NOT_ACCEPTABLE(Lang, "No resource provided")).
+ ?ERRT_NOT_ACCEPTABLE(Lang, <<"No resource provided">>)).
+
-define(ERR_AUTH_BAD_RESOURCE_FORMAT(Lang),
- ?ERRT_NOT_ACCEPTABLE(Lang, "Illegal resource format")).
--define(ERR_AUTH_RESOURCE_CONFLICT(Lang),
- ?ERRT_CONFLICT(Lang, "Resource conflict")).
+ ?ERRT_NOT_ACCEPTABLE(Lang,
+ <<"Illegal resource format">>)).
+-define(ERR_AUTH_RESOURCE_CONFLICT(Lang),
+ ?ERRT_CONFLICT(Lang, <<"Resource conflict">>)).
--define(STREAM_ERROR(Condition),
- {xmlelement, "stream:error",
- [],
- [{xmlelement, Condition, [{"xmlns", ?NS_STREAMS}], []}]}).
+-define(STREAM_ERROR(Condition, Cdata),
+ #xmlel{name = <<"stream:error">>, attrs = [],
+ children =
+ [#xmlel{name = Condition,
+ attrs = [{<<"xmlns">>, ?NS_STREAMS}],
+ children = [{xmlcdata, Cdata}]}]}).
-define(SERR_BAD_FORMAT,
- ?STREAM_ERROR("bad-format")).
+ ?STREAM_ERROR(<<"bad-format">>, <<"">>)).
+
-define(SERR_BAD_NAMESPACE_PREFIX,
- ?STREAM_ERROR("bad-namespace-prefix")).
+ ?STREAM_ERROR(<<"bad-namespace-prefix">>, <<"">>)).
+
-define(SERR_CONFLICT,
- ?STREAM_ERROR("conflict")).
+ ?STREAM_ERROR(<<"conflict">>, <<"">>)).
+
-define(SERR_CONNECTION_TIMEOUT,
- ?STREAM_ERROR("connection-timeout")).
+ ?STREAM_ERROR(<<"connection-timeout">>, <<"">>)).
+
-define(SERR_HOST_GONE,
- ?STREAM_ERROR("host-gone")).
+ ?STREAM_ERROR(<<"host-gone">>, <<"">>)).
+
-define(SERR_HOST_UNKNOWN,
- ?STREAM_ERROR("host-unknown")).
+ ?STREAM_ERROR(<<"host-unknown">>, <<"">>)).
+
-define(SERR_IMPROPER_ADDRESSING,
- ?STREAM_ERROR("improper-addressing")).
+ ?STREAM_ERROR(<<"improper-addressing">>, <<"">>)).
+
-define(SERR_INTERNAL_SERVER_ERROR,
- ?STREAM_ERROR("internal-server-error")).
+ ?STREAM_ERROR(<<"internal-server-error">>, <<"">>)).
+
-define(SERR_INVALID_FROM,
- ?STREAM_ERROR("invalid-from")).
+ ?STREAM_ERROR(<<"invalid-from">>, <<"">>)).
+
-define(SERR_INVALID_ID,
- ?STREAM_ERROR("invalid-id")).
+ ?STREAM_ERROR(<<"invalid-id">>, <<"">>)).
+
-define(SERR_INVALID_NAMESPACE,
- ?STREAM_ERROR("invalid-namespace")).
+ ?STREAM_ERROR(<<"invalid-namespace">>, <<"">>)).
+
-define(SERR_INVALID_XML,
- ?STREAM_ERROR("invalid-xml")).
+ ?STREAM_ERROR(<<"invalid-xml">>, <<"">>)).
+
-define(SERR_NOT_AUTHORIZED,
- ?STREAM_ERROR("not-authorized")).
+ ?STREAM_ERROR(<<"not-authorized">>, <<"">>)).
+
-define(SERR_POLICY_VIOLATION,
- ?STREAM_ERROR("policy-violation")).
+ ?STREAM_ERROR(<<"policy-violation">>, <<"">>)).
+
-define(SERR_REMOTE_CONNECTION_FAILED,
- ?STREAM_ERROR("remote-connection-failed")).
+ ?STREAM_ERROR(<<"remote-connection-failed">>, <<"">>)).
+
-define(SERR_RESOURSE_CONSTRAINT,
- ?STREAM_ERROR("resource-constraint")).
+ ?STREAM_ERROR(<<"resource-constraint">>, <<"">>)).
+
-define(SERR_RESTRICTED_XML,
- ?STREAM_ERROR("restricted-xml")).
-% TODO: include hostname or IP
--define(SERR_SEE_OTHER_HOST,
- ?STREAM_ERROR("see-other-host")).
+ ?STREAM_ERROR(<<"restricted-xml">>, <<"">>)).
+
+-define(SERR_SEE_OTHER_HOST(Host),
+ ?STREAM_ERROR(<<"see-other-host">>, Host)).
+
-define(SERR_SYSTEM_SHUTDOWN,
- ?STREAM_ERROR("system-shutdown")).
+ ?STREAM_ERROR(<<"system-shutdown">>, <<"">>)).
+
-define(SERR_UNSUPPORTED_ENCODING,
- ?STREAM_ERROR("unsupported-encoding")).
+ ?STREAM_ERROR(<<"unsupported-encoding">>, <<"">>)).
+
-define(SERR_UNSUPPORTED_STANZA_TYPE,
- ?STREAM_ERROR("unsupported-stanza-type")).
+ ?STREAM_ERROR(<<"unsupported-stanza-type">>, <<"">>)).
+
-define(SERR_UNSUPPORTED_VERSION,
- ?STREAM_ERROR("unsupported-version")).
+ ?STREAM_ERROR(<<"unsupported-version">>, <<"">>)).
+
-define(SERR_XML_NOT_WELL_FORMED,
- ?STREAM_ERROR("xml-not-well-formed")).
+ ?STREAM_ERROR(<<"xml-not-well-formed">>, <<"">>)).
+
%-define(SERR_,
-% ?STREAM_ERROR("")).
+% ?STREAM_ERROR("", "")).
--define(STREAM_ERRORT(Condition, Lang, Text),
- {xmlelement, "stream:error",
- [],
- [{xmlelement, Condition, [{"xmlns", ?NS_STREAMS}], []},
- {xmlelement, "text", [{"xml:lang", Lang}, {"xmlns", ?NS_STREAMS}],
- [{xmlcdata, translate:translate(Lang, Text)}]}]}).
+-define(STREAM_ERRORT(Condition, Cdata, Lang, Text),
+ #xmlel{name = <<"stream:error">>, attrs = [],
+ children =
+ [#xmlel{name = Condition,
+ attrs = [{<<"xmlns">>, ?NS_STREAMS}],
+ children = [{xmlcdata, Cdata}]},
+ #xmlel{name = <<"text">>,
+ attrs =
+ [{<<"xml:lang">>, Lang},
+ {<<"xmlns">>, ?NS_STREAMS}],
+ children =
+ [{xmlcdata,
+ translate:translate(Lang, Text)}]}]}).
-define(SERRT_BAD_FORMAT(Lang, Text),
- ?STREAM_ERRORT("bad-format", Lang, Text)).
+ ?STREAM_ERRORT(<<"bad-format">>, <<"">>, Lang, Text)).
+
-define(SERRT_BAD_NAMESPACE_PREFIX(Lang, Text),
- ?STREAM_ERRORT("bad-namespace-prefix", Lang, Text)).
+ ?STREAM_ERRORT(<<"bad-namespace-prefix">>, <<"">>, Lang,
+ Text)).
+
-define(SERRT_CONFLICT(Lang, Text),
- ?STREAM_ERRORT("conflict", Lang, Text)).
+ ?STREAM_ERRORT(<<"conflict">>, <<"">>, Lang, Text)).
+
-define(SERRT_CONNECTION_TIMEOUT(Lang, Text),
- ?STREAM_ERRORT("connection-timeout", Lang, Text)).
+ ?STREAM_ERRORT(<<"connection-timeout">>, <<"">>, Lang,
+ Text)).
+
-define(SERRT_HOST_GONE(Lang, Text),
- ?STREAM_ERRORT("host-gone", Lang, Text)).
+ ?STREAM_ERRORT(<<"host-gone">>, <<"">>, Lang, Text)).
+
-define(SERRT_HOST_UNKNOWN(Lang, Text),
- ?STREAM_ERRORT("host-unknown", Lang, Text)).
+ ?STREAM_ERRORT(<<"host-unknown">>, <<"">>, Lang, Text)).
+
-define(SERRT_IMPROPER_ADDRESSING(Lang, Text),
- ?STREAM_ERRORT("improper-addressing", Lang, Text)).
+ ?STREAM_ERRORT(<<"improper-addressing">>, <<"">>, Lang,
+ Text)).
+
-define(SERRT_INTERNAL_SERVER_ERROR(Lang, Text),
- ?STREAM_ERRORT("internal-server-error", Lang, Text)).
+ ?STREAM_ERRORT(<<"internal-server-error">>, <<"">>,
+ Lang, Text)).
+
-define(SERRT_INVALID_FROM(Lang, Text),
- ?STREAM_ERRORT("invalid-from", Lang, Text)).
+ ?STREAM_ERRORT(<<"invalid-from">>, <<"">>, Lang, Text)).
+
-define(SERRT_INVALID_ID(Lang, Text),
- ?STREAM_ERRORT("invalid-id", Lang, Text)).
+ ?STREAM_ERRORT(<<"invalid-id">>, <<"">>, Lang, Text)).
+
-define(SERRT_INVALID_NAMESPACE(Lang, Text),
- ?STREAM_ERRORT("invalid-namespace", Lang, Text)).
+ ?STREAM_ERRORT(<<"invalid-namespace">>, <<"">>, Lang,
+ Text)).
+
-define(SERRT_INVALID_XML(Lang, Text),
- ?STREAM_ERRORT("invalid-xml", Lang, Text)).
+ ?STREAM_ERRORT(<<"invalid-xml">>, <<"">>, Lang, Text)).
+
-define(SERRT_NOT_AUTHORIZED(Lang, Text),
- ?STREAM_ERRORT("not-authorized", Lang, Text)).
+ ?STREAM_ERRORT(<<"not-authorized">>, <<"">>, Lang,
+ Text)).
+
-define(SERRT_POLICY_VIOLATION(Lang, Text),
- ?STREAM_ERRORT("policy-violation", Lang, Text)).
+ ?STREAM_ERRORT(<<"policy-violation">>, <<"">>, Lang,
+ Text)).
+
-define(SERRT_REMOTE_CONNECTION_FAILED(Lang, Text),
- ?STREAM_ERRORT("remote-connection-failed", Lang, Text)).
+ ?STREAM_ERRORT(<<"remote-connection-failed">>, <<"">>,
+ Lang, Text)).
+
-define(SERRT_RESOURSE_CONSTRAINT(Lang, Text),
- ?STREAM_ERRORT("resource-constraint", Lang, Text)).
+ ?STREAM_ERRORT(<<"resource-constraint">>, <<"">>, Lang,
+ Text)).
+
-define(SERRT_RESTRICTED_XML(Lang, Text),
- ?STREAM_ERRORT("restricted-xml", Lang, Text)).
-% TODO: include hostname or IP
--define(SERRT_SEE_OTHER_HOST(Lang, Text),
- ?STREAM_ERRORT("see-other-host", Lang, Text)).
+ ?STREAM_ERRORT(<<"restricted-xml">>, <<"">>, Lang,
+ Text)).
+
+-define(SERRT_SEE_OTHER_HOST(Host, Lang, Text),
+ ?STREAM_ERRORT(<<"see-other-host">>, Host, Lang, Text)).
+
-define(SERRT_SYSTEM_SHUTDOWN(Lang, Text),
- ?STREAM_ERRORT("system-shutdown", Lang, Text)).
+ ?STREAM_ERRORT(<<"system-shutdown">>, <<"">>, Lang,
+ Text)).
+
-define(SERRT_UNSUPPORTED_ENCODING(Lang, Text),
- ?STREAM_ERRORT("unsupported-encoding", Lang, Text)).
+ ?STREAM_ERRORT(<<"unsupported-encoding">>, <<"">>, Lang,
+ Text)).
+
-define(SERRT_UNSUPPORTED_STANZA_TYPE(Lang, Text),
- ?STREAM_ERRORT("unsupported-stanza-type", Lang, Text)).
+ ?STREAM_ERRORT(<<"unsupported-stanza-type">>, <<"">>,
+ Lang, Text)).
+
-define(SERRT_UNSUPPORTED_VERSION(Lang, Text),
- ?STREAM_ERRORT("unsupported-version", Lang, Text)).
+ ?STREAM_ERRORT(<<"unsupported-version">>, <<"">>, Lang,
+ Text)).
+
-define(SERRT_XML_NOT_WELL_FORMED(Lang, Text),
- ?STREAM_ERRORT("xml-not-well-formed", Lang, Text)).
-%-define(SERRT_(Lang, Text),
-% ?STREAM_ERRORT("", Lang, Text)).
+ ?STREAM_ERRORT(<<"xml-not-well-formed">>, <<"">>, Lang,
+ Text)).
+
+-record(jid, {user = <<"">> :: binary(),
+ server = <<"">> :: binary(),
+ resource = <<"">> :: binary(),
+ luser = <<"">> :: binary(),
+ lserver = <<"">> :: binary(),
+ lresource = <<"">> :: binary()}).
+
+-type(jid() :: #jid{}).
+
+-type(ljid() :: {binary(), binary(), binary()}).
+
+-record(xmlel,
+{
+ name = <<"">> :: binary(),
+ attrs = [] :: [attr()],
+ children = [] :: [xmlel() | cdata()]
+}).
+
+-type(cdata() :: {xmlcdata, CData::binary()}).
+
+-type(attr() :: {Name::binary(), Value::binary()}).
+
+-type(xmlel() :: #xmlel{}).
+
+-record(iq, {id = <<"">> :: binary(),
+ type = get :: get | set | result | error,
+ xmlns = <<"">> :: binary(),
+ lang = <<"">> :: binary(),
+ sub_el = #xmlel{} :: xmlel() | [xmlel()]}).
+
+-type(iq_get()
+ :: #iq{
+ id :: binary(),
+ type :: get,
+ xmlns :: binary(),
+ lang :: binary(),
+ sub_el :: xmlel()
+ }
+).
+
+-type(iq_set()
+ :: #iq{
+ id :: binary(),
+ type :: set,
+ xmlns :: binary(),
+ lang :: binary(),
+ sub_el :: xmlel()
+ }
+).
+
+-type iq_request() :: iq_get() | iq_set().
+
+-type(iq_result()
+ :: #iq{
+ id :: binary(),
+ type :: result,
+ xmlns :: binary(),
+ lang :: binary(),
+ sub_el :: [xmlel()]
+ }
+).
+
+-type(iq_error()
+ :: #iq{
+ id :: binary(),
+ type :: error,
+ xmlns :: binary(),
+ lang :: binary(),
+ sub_el :: [xmlel()]
+ }
+).
+
+-type iq_reply() :: iq_result() | iq_error() .
+
+-type(iq() :: iq_request() | iq_reply()).
+
+-record(rsm_in, {max :: integer(),
+ direction :: before | aft,
+ id :: binary(),
+ index :: integer()}).
+
+-record(rsm_out, {count :: integer(),
+ index :: integer(),
+ first :: binary(),
+ last :: binary()}).
+
+-type(rsm_in() :: #rsm_in{}).
+
+-type(rsm_out() :: #rsm_out{}).
+-type broadcast() :: {broadcast, broadcast_data()}.
--record(jid, {user, server, resource,
- luser, lserver, lresource}).
+-type broadcast_data() ::
+ {rebind, pid(), binary()} | %% ejabberd_c2s
+ {item, ljid(), mod_roster:subscription()} | %% mod_roster/mod_shared_roster
+ {exit, binary()} | %% mod_roster/mod_shared_roster
+ {privacy_list, mod_privacy:userlist(), binary()} | %% mod_privacy
+ {blocking, unblock_all | {block | unblock, [ljid()]}}. %% mod_blocking
--record(iq, {id = "",
- type,
- xmlns = "",
- lang = "",
- sub_el}).
+-record(xmlelement, {name = "" :: string(),
+ attrs = [] :: [{string(), string()}],
+ children = [] :: [{xmlcdata, iodata()} | xmlelement()]}).
--record(rsm_in, {max, direction, id, index}).
--record(rsm_out, {count, index, first, last}).
+-type xmlelement() :: #xmlelement{}.
diff --git a/src/mod_adhoc.erl b/src/mod_adhoc.erl
index 5c9039673..2eaf36840 100644
--- a/src/mod_adhoc.erl
+++ b/src/mod_adhoc.erl
@@ -25,129 +25,155 @@
%%%----------------------------------------------------------------------
-module(mod_adhoc).
+
-author('henoch@dtek.chalmers.se').
-behaviour(gen_mod).
--export([start/2,
- stop/1,
- process_local_iq/3,
- process_sm_iq/3,
- get_local_commands/5,
- get_local_identity/5,
- get_local_features/5,
- get_sm_commands/5,
- get_sm_identity/5,
- get_sm_features/5,
- ping_item/4,
- ping_command/4]).
+-export([start/2, stop/1, process_local_iq/3,
+ process_sm_iq/3, get_local_commands/5,
+ get_local_identity/5, get_local_features/5,
+ get_sm_commands/5, get_sm_identity/5, get_sm_features/5,
+ ping_item/4, ping_command/4]).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
+
-include("adhoc.hrl").
start(Host, Opts) ->
- IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
-
- gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_COMMANDS,
- ?MODULE, process_local_iq, IQDisc),
- gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_COMMANDS,
- ?MODULE, process_sm_iq, IQDisc),
-
- ejabberd_hooks:add(disco_local_identity, Host, ?MODULE, get_local_identity, 99),
- ejabberd_hooks:add(disco_local_features, Host, ?MODULE, get_local_features, 99),
- ejabberd_hooks:add(disco_local_items, Host, ?MODULE, get_local_commands, 99),
- ejabberd_hooks:add(disco_sm_identity, Host, ?MODULE, get_sm_identity, 99),
- ejabberd_hooks:add(disco_sm_features, Host, ?MODULE, get_sm_features, 99),
- ejabberd_hooks:add(disco_sm_items, Host, ?MODULE, get_sm_commands, 99),
- ejabberd_hooks:add(adhoc_local_items, Host, ?MODULE, ping_item, 100),
- ejabberd_hooks:add(adhoc_local_commands, Host, ?MODULE, ping_command, 100).
+ IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
+ one_queue),
+ gen_iq_handler:add_iq_handler(ejabberd_local, Host,
+ ?NS_COMMANDS, ?MODULE, process_local_iq,
+ IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
+ ?NS_COMMANDS, ?MODULE, process_sm_iq, IQDisc),
+ ejabberd_hooks:add(disco_local_identity, Host, ?MODULE,
+ get_local_identity, 99),
+ ejabberd_hooks:add(disco_local_features, Host, ?MODULE,
+ get_local_features, 99),
+ ejabberd_hooks:add(disco_local_items, Host, ?MODULE,
+ get_local_commands, 99),
+ ejabberd_hooks:add(disco_sm_identity, Host, ?MODULE,
+ get_sm_identity, 99),
+ ejabberd_hooks:add(disco_sm_features, Host, ?MODULE,
+ get_sm_features, 99),
+ ejabberd_hooks:add(disco_sm_items, Host, ?MODULE,
+ get_sm_commands, 99),
+ ejabberd_hooks:add(adhoc_local_items, Host, ?MODULE,
+ ping_item, 100),
+ ejabberd_hooks:add(adhoc_local_commands, Host, ?MODULE,
+ ping_command, 100).
stop(Host) ->
- ejabberd_hooks:delete(adhoc_local_commands, Host, ?MODULE, ping_command, 100),
- ejabberd_hooks:delete(adhoc_local_items, Host, ?MODULE, ping_item, 100),
- ejabberd_hooks:delete(disco_sm_items, Host, ?MODULE, get_sm_commands, 99),
- ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, get_sm_features, 99),
- ejabberd_hooks:delete(disco_sm_identity, Host, ?MODULE, get_sm_identity, 99),
- ejabberd_hooks:delete(disco_local_items, Host, ?MODULE, get_local_commands, 99),
- ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, get_local_features, 99),
- ejabberd_hooks:delete(disco_local_identity, Host, ?MODULE, get_local_identity, 99),
-
- gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_COMMANDS),
- gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_COMMANDS).
+ ejabberd_hooks:delete(adhoc_local_commands, Host,
+ ?MODULE, ping_command, 100),
+ ejabberd_hooks:delete(adhoc_local_items, Host, ?MODULE,
+ ping_item, 100),
+ ejabberd_hooks:delete(disco_sm_items, Host, ?MODULE,
+ get_sm_commands, 99),
+ ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE,
+ get_sm_features, 99),
+ ejabberd_hooks:delete(disco_sm_identity, Host, ?MODULE,
+ get_sm_identity, 99),
+ ejabberd_hooks:delete(disco_local_items, Host, ?MODULE,
+ get_local_commands, 99),
+ ejabberd_hooks:delete(disco_local_features, Host,
+ ?MODULE, get_local_features, 99),
+ ejabberd_hooks:delete(disco_local_identity, Host,
+ ?MODULE, get_local_identity, 99),
+ gen_iq_handler:remove_iq_handler(ejabberd_sm, Host,
+ ?NS_COMMANDS),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, Host,
+ ?NS_COMMANDS).
%-------------------------------------------------------------------------
-get_local_commands(Acc, _From, #jid{server = Server, lserver = LServer} = _To, "", Lang) ->
- Display = gen_mod:get_module_opt(LServer, ?MODULE, report_commands_node, false),
+get_local_commands(Acc, _From,
+ #jid{server = Server, lserver = LServer} = _To, <<"">>,
+ Lang) ->
+ Display = gen_mod:get_module_opt(LServer, ?MODULE,
+ report_commands_node,
+ fun(B) when is_boolean(B) -> B end,
+ false),
case Display of
- false ->
- Acc;
- _ ->
- Items = case Acc of
- {result, I} -> I;
- _ -> []
- end,
- Nodes = [{xmlelement,
- "item",
- [{"jid", Server},
- {"node", ?NS_COMMANDS},
- {"name", translate:translate(Lang, "Commands")}],
- []}],
- {result, Items ++ Nodes}
+ false -> Acc;
+ _ ->
+ Items = case Acc of
+ {result, I} -> I;
+ _ -> []
+ end,
+ Nodes = [#xmlel{name = <<"item">>,
+ attrs =
+ [{<<"jid">>, Server}, {<<"node">>, ?NS_COMMANDS},
+ {<<"name">>,
+ translate:translate(Lang, <<"Commands">>)}],
+ children = []}],
+ {result, Items ++ Nodes}
end;
-
-get_local_commands(_Acc, From, #jid{lserver = LServer} = To, ?NS_COMMANDS, Lang) ->
- ejabberd_hooks:run_fold(adhoc_local_items, LServer, {result, []}, [From, To, Lang]);
-
-get_local_commands(_Acc, _From, _To, "ping", _Lang) ->
+get_local_commands(_Acc, From,
+ #jid{lserver = LServer} = To, ?NS_COMMANDS, Lang) ->
+ ejabberd_hooks:run_fold(adhoc_local_items, LServer,
+ {result, []}, [From, To, Lang]);
+get_local_commands(_Acc, _From, _To, <<"ping">>,
+ _Lang) ->
{result, []};
-
get_local_commands(Acc, _From, _To, _Node, _Lang) ->
Acc.
%-------------------------------------------------------------------------
-get_sm_commands(Acc, _From, #jid{lserver = LServer} = To, "", Lang) ->
- Display = gen_mod:get_module_opt(LServer, ?MODULE, report_commands_node, false),
+get_sm_commands(Acc, _From,
+ #jid{lserver = LServer} = To, <<"">>, Lang) ->
+ Display = gen_mod:get_module_opt(LServer, ?MODULE,
+ report_commands_node,
+ fun(B) when is_boolean(B) -> B end,
+ false),
case Display of
- false ->
- Acc;
- _ ->
- Items = case Acc of
- {result, I} -> I;
- _ -> []
- end,
- Nodes = [{xmlelement,
- "item",
- [{"jid", jlib:jid_to_string(To)},
- {"node", ?NS_COMMANDS},
- {"name", translate:translate(Lang, "Commands")}],
- []}],
- {result, Items ++ Nodes}
+ false -> Acc;
+ _ ->
+ Items = case Acc of
+ {result, I} -> I;
+ _ -> []
+ end,
+ Nodes = [#xmlel{name = <<"item">>,
+ attrs =
+ [{<<"jid">>, jlib:jid_to_string(To)},
+ {<<"node">>, ?NS_COMMANDS},
+ {<<"name">>,
+ translate:translate(Lang, <<"Commands">>)}],
+ children = []}],
+ {result, Items ++ Nodes}
end;
-
-get_sm_commands(_Acc, From, #jid{lserver = LServer} = To, ?NS_COMMANDS, Lang) ->
- ejabberd_hooks:run_fold(adhoc_sm_items, LServer, {result, []}, [From, To, Lang]);
-
-get_sm_commands(Acc, _From, _To, _Node, _Lang) ->
- Acc.
+get_sm_commands(_Acc, From,
+ #jid{lserver = LServer} = To, ?NS_COMMANDS, Lang) ->
+ ejabberd_hooks:run_fold(adhoc_sm_items, LServer,
+ {result, []}, [From, To, Lang]);
+get_sm_commands(Acc, _From, _To, _Node, _Lang) -> Acc.
%-------------------------------------------------------------------------
%% On disco info request to the ad-hoc node, return automation/command-list.
-get_local_identity(Acc, _From, _To, ?NS_COMMANDS, Lang) ->
- [{xmlelement, "identity",
- [{"category", "automation"},
- {"type", "command-list"},
- {"name", translate:translate(Lang, "Commands")}], []} | Acc];
-
-get_local_identity(Acc, _From, _To, "ping", Lang) ->
- [{xmlelement, "identity",
- [{"category", "automation"},
- {"type", "command-node"},
- {"name", translate:translate(Lang, "Ping")}], []} | Acc];
-
+get_local_identity(Acc, _From, _To, ?NS_COMMANDS,
+ Lang) ->
+ [#xmlel{name = <<"identity">>,
+ attrs =
+ [{<<"category">>, <<"automation">>},
+ {<<"type">>, <<"command-list">>},
+ {<<"name">>,
+ translate:translate(Lang, <<"Commands">>)}],
+ children = []}
+ | Acc];
+get_local_identity(Acc, _From, _To, <<"ping">>, Lang) ->
+ [#xmlel{name = <<"identity">>,
+ attrs =
+ [{<<"category">>, <<"automation">>},
+ {<<"type">>, <<"command-node">>},
+ {<<"name">>, translate:translate(Lang, <<"Ping">>)}],
+ children = []}
+ | Acc];
get_local_identity(Acc, _From, _To, _Node, _Lang) ->
Acc.
@@ -155,113 +181,100 @@ get_local_identity(Acc, _From, _To, _Node, _Lang) ->
%% On disco info request to the ad-hoc node, return automation/command-list.
get_sm_identity(Acc, _From, _To, ?NS_COMMANDS, Lang) ->
- [{xmlelement, "identity",
- [{"category", "automation"},
- {"type", "command-list"},
- {"name", translate:translate(Lang, "Commands")}], []} | Acc];
-
-get_sm_identity(Acc, _From, _To, _Node, _Lang) ->
- Acc.
+ [#xmlel{name = <<"identity">>,
+ attrs =
+ [{<<"category">>, <<"automation">>},
+ {<<"type">>, <<"command-list">>},
+ {<<"name">>,
+ translate:translate(Lang, <<"Commands">>)}],
+ children = []}
+ | Acc];
+get_sm_identity(Acc, _From, _To, _Node, _Lang) -> Acc.
%-------------------------------------------------------------------------
-get_local_features(Acc, _From, _To, "", _Lang) ->
+get_local_features(Acc, _From, _To, <<"">>, _Lang) ->
Feats = case Acc of
- {result, I} -> I;
- _ -> []
+ {result, I} -> I;
+ _ -> []
end,
{result, Feats ++ [?NS_COMMANDS]};
-
-get_local_features(_Acc, _From, _To, ?NS_COMMANDS, _Lang) ->
- %% override all lesser features...
+get_local_features(_Acc, _From, _To, ?NS_COMMANDS,
+ _Lang) ->
{result, []};
-
-get_local_features(_Acc, _From, _To, "ping", _Lang) ->
- %% override all lesser features...
+get_local_features(_Acc, _From, _To, <<"ping">>,
+ _Lang) ->
{result, [?NS_COMMANDS]};
-
get_local_features(Acc, _From, _To, _Node, _Lang) ->
Acc.
%-------------------------------------------------------------------------
-get_sm_features(Acc, _From, _To, "", _Lang) ->
+get_sm_features(Acc, _From, _To, <<"">>, _Lang) ->
Feats = case Acc of
- {result, I} -> I;
- _ -> []
+ {result, I} -> I;
+ _ -> []
end,
{result, Feats ++ [?NS_COMMANDS]};
-
-get_sm_features(_Acc, _From, _To, ?NS_COMMANDS, _Lang) ->
- %% override all lesser features...
+get_sm_features(_Acc, _From, _To, ?NS_COMMANDS,
+ _Lang) ->
{result, []};
-
-get_sm_features(Acc, _From, _To, _Node, _Lang) ->
- Acc.
+get_sm_features(Acc, _From, _To, _Node, _Lang) -> Acc.
%-------------------------------------------------------------------------
process_local_iq(From, To, IQ) ->
- process_adhoc_request(From, To, IQ, adhoc_local_commands).
-
+ process_adhoc_request(From, To, IQ,
+ adhoc_local_commands).
process_sm_iq(From, To, IQ) ->
process_adhoc_request(From, To, IQ, adhoc_sm_commands).
-
-process_adhoc_request(From, To, #iq{sub_el = SubEl} = IQ, Hook) ->
+process_adhoc_request(From, To,
+ #iq{sub_el = SubEl} = IQ, Hook) ->
?DEBUG("About to parse ~p...", [IQ]),
case adhoc:parse_request(IQ) of
- {error, Error} ->
- IQ#iq{type = error, sub_el = [SubEl, Error]};
- #adhoc_request{} = AdhocRequest ->
- Host = To#jid.lserver,
- case ejabberd_hooks:run_fold(Hook, Host, empty,
- [From, To, AdhocRequest]) of
- ignore ->
- ignore;
- empty ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_ITEM_NOT_FOUND]};
- {error, Error} ->
- IQ#iq{type = error, sub_el = [SubEl, Error]};
- Command ->
- IQ#iq{type = result, sub_el = [Command]}
- end
+ {error, Error} ->
+ IQ#iq{type = error, sub_el = [SubEl, Error]};
+ #adhoc_request{} = AdhocRequest ->
+ Host = To#jid.lserver,
+ case ejabberd_hooks:run_fold(Hook, Host, empty,
+ [From, To, AdhocRequest])
+ of
+ ignore -> ignore;
+ empty ->
+ IQ#iq{type = error,
+ sub_el = [SubEl, ?ERR_ITEM_NOT_FOUND]};
+ {error, Error} ->
+ IQ#iq{type = error, sub_el = [SubEl, Error]};
+ Command -> IQ#iq{type = result, sub_el = [Command]}
+ end
end.
-
-ping_item(Acc, _From, #jid{server = Server} = _To, Lang) ->
+ping_item(Acc, _From, #jid{server = Server} = _To,
+ Lang) ->
Items = case Acc of
- {result, I} ->
- I;
- _ ->
- []
+ {result, I} -> I;
+ _ -> []
end,
- Nodes = [{xmlelement, "item",
- [{"jid", Server},
- {"node", "ping"},
- {"name", translate:translate(Lang, "Ping")}],
- []}],
+ Nodes = [#xmlel{name = <<"item">>,
+ attrs =
+ [{<<"jid">>, Server}, {<<"node">>, <<"ping">>},
+ {<<"name">>, translate:translate(Lang, <<"Ping">>)}],
+ children = []}],
{result, Items ++ Nodes}.
-
ping_command(_Acc, _From, _To,
- #adhoc_request{lang = Lang,
- node = "ping",
- sessionid = _Sessionid,
- action = Action} = Request) ->
- if
- Action == ""; Action == "execute" ->
- adhoc:produce_response(
- Request,
- #adhoc_response{status = completed,
- notes = [{"info", translate:translate(
- Lang,
- "Pong")}]});
- true ->
- {error, ?ERR_BAD_REQUEST}
+ #adhoc_request{lang = Lang, node = <<"ping">>,
+ sessionid = _Sessionid, action = Action} =
+ Request) ->
+ if Action == <<"">>; Action == <<"execute">> ->
+ adhoc:produce_response(Request,
+ #adhoc_response{status = completed,
+ notes =
+ [{<<"info">>,
+ translate:translate(Lang,
+ <<"Pong">>)}]});
+ true -> {error, ?ERR_BAD_REQUEST}
end;
-
-ping_command(Acc, _From, _To, _Request) ->
- Acc.
-
+ping_command(Acc, _From, _To, _Request) -> Acc.
diff --git a/src/mod_announce.erl b/src/mod_announce.erl
index 7e0053462..2f6394076 100644
--- a/src/mod_announce.erl
+++ b/src/mod_announce.erl
@@ -35,6 +35,7 @@
-export([start/2,
init/0,
stop/1,
+ export/1,
announce/3,
send_motd/1,
disco_identity/5,
@@ -48,13 +49,17 @@
-include("jlib.hrl").
-include("adhoc.hrl").
--record(motd, {server, packet}).
--record(motd_users, {us, dummy = []}).
+-record(motd, {server = <<"">> :: binary(),
+ packet = #xmlel{} :: xmlel()}).
+-record(motd_users, {us = {<<"">>, <<"">>} :: {binary(), binary()} | '$1',
+ dummy = [] :: [] | '_'}).
-define(PROCNAME, ejabberd_announce).
--define(NS_ADMINL(Sub), ["http:","jabber.org","protocol","admin", Sub]).
-tokenize(Node) -> string:tokens(Node, "/#").
+-define(NS_ADMINL(Sub), [<<"http:">>, <<"jabber.org">>, <<"protocol">>,
+ <<"admin">>, <<Sub>>]).
+
+tokenize(Node) -> str:tokens(Node, <<"/#">>).
start(Host, Opts) ->
case gen_mod:db_type(Opts) of
@@ -137,56 +142,52 @@ stop(Host) ->
{wait, Proc}.
%% Announcing via messages to a custom resource
-announce(From, To, Packet) ->
- case To of
- #jid{luser = "", lresource = Res} ->
- {xmlelement, Name, _Attrs, _Els} = Packet,
- Proc = gen_mod:get_module_proc(To#jid.lserver, ?PROCNAME),
- case {Res, Name} of
- {"announce/all", "message"} ->
- Proc ! {announce_all, From, To, Packet},
- stop;
- {"announce/all-hosts/all", "message"} ->
- Proc ! {announce_all_hosts_all, From, To, Packet},
- stop;
- {"announce/online", "message"} ->
- Proc ! {announce_online, From, To, Packet},
- stop;
- {"announce/all-hosts/online", "message"} ->
- Proc ! {announce_all_hosts_online, From, To, Packet},
- stop;
- {"announce/motd", "message"} ->
- Proc ! {announce_motd, From, To, Packet},
- stop;
- {"announce/all-hosts/motd", "message"} ->
- Proc ! {announce_all_hosts_motd, From, To, Packet},
- stop;
- {"announce/motd/update", "message"} ->
- Proc ! {announce_motd_update, From, To, Packet},
- stop;
- {"announce/all-hosts/motd/update", "message"} ->
- Proc ! {announce_all_hosts_motd_update, From, To, Packet},
- stop;
- {"announce/motd/delete", "message"} ->
- Proc ! {announce_motd_delete, From, To, Packet},
- stop;
- {"announce/all-hosts/motd/delete", "message"} ->
- Proc ! {announce_all_hosts_motd_delete, From, To, Packet},
- stop;
- _ ->
- ok
- end;
- _ ->
- ok
- end.
+announce(From, #jid{luser = <<>>} = To, #xmlel{name = <<"message">>} = Packet) ->
+ Proc = gen_mod:get_module_proc(To#jid.lserver, ?PROCNAME),
+ case To#jid.lresource of
+ <<"announce/all">> ->
+ Proc ! {announce_all, From, To, Packet},
+ stop;
+ <<"announce/all-hosts/all">> ->
+ Proc ! {announce_all_hosts_all, From, To, Packet},
+ stop;
+ <<"announce/online">> ->
+ Proc ! {announce_online, From, To, Packet},
+ stop;
+ <<"announce/all-hosts/online">> ->
+ Proc ! {announce_all_hosts_online, From, To, Packet},
+ stop;
+ <<"announce/motd">> ->
+ Proc ! {announce_motd, From, To, Packet},
+ stop;
+ <<"announce/all-hosts/motd">> ->
+ Proc ! {announce_all_hosts_motd, From, To, Packet},
+ stop;
+ <<"announce/motd/update">> ->
+ Proc ! {announce_motd_update, From, To, Packet},
+ stop;
+ <<"announce/all-hosts/motd/update">> ->
+ Proc ! {announce_all_hosts_motd_update, From, To, Packet},
+ stop;
+ <<"announce/motd/delete">> ->
+ Proc ! {announce_motd_delete, From, To, Packet},
+ stop;
+ <<"announce/all-hosts/motd/delete">> ->
+ Proc ! {announce_all_hosts_motd_delete, From, To, Packet},
+ stop;
+ _ ->
+ ok
+ end;
+announce(_From, _To, _Packet) ->
+ ok.
%%-------------------------------------------------------------------------
%% Announcing via ad-hoc commands
-define(INFO_COMMAND(Lang, Node),
- [{xmlelement, "identity",
- [{"category", "automation"},
- {"type", "command-node"},
- {"name", get_title(Lang, Node)}], []}]).
+ [#xmlel{name = <<"identity">>,
+ attrs = [{<<"category">>, <<"automation">>},
+ {<<"type">>, <<"command-node">>},
+ {<<"name">>, get_title(Lang, Node)}]}]).
disco_identity(Acc, _From, _To, Node, Lang) ->
LNode = tokenize(Node),
@@ -225,14 +226,13 @@ disco_identity(Acc, _From, _To, Node, Lang) ->
{result, Feats}
end).
-disco_features(Acc, From, #jid{lserver = LServer} = _To,
- "announce", _Lang) ->
+disco_features(Acc, From, #jid{lserver = LServer} = _To, <<"announce">>, _Lang) ->
case gen_mod:is_loaded(LServer, mod_adhoc) of
false ->
Acc;
_ ->
- Access1 = gen_mod:get_module_opt(LServer, ?MODULE, access, none),
- Access2 = gen_mod:get_module_opt(global, ?MODULE, access, none),
+ Access1 = get_access(LServer),
+ Access2 = get_access(global),
case {acl:match_rule(LServer, Access1, From),
acl:match_rule(global, Access2, From)} of
{deny, deny} ->
@@ -242,36 +242,35 @@ disco_features(Acc, From, #jid{lserver = LServer} = _To,
end
end;
-disco_features(Acc, From, #jid{lserver = LServer} = _To,
- Node, _Lang) ->
+disco_features(Acc, From, #jid{lserver = LServer} = _To, Node, _Lang) ->
case gen_mod:is_loaded(LServer, mod_adhoc) of
false ->
Acc;
_ ->
- Access = gen_mod:get_module_opt(LServer, ?MODULE, access, none),
+ Access = get_access(LServer),
Allow = acl:match_rule(LServer, Access, From),
- AccessGlobal = gen_mod:get_module_opt(global, ?MODULE, access, none),
+ AccessGlobal = get_access(global),
AllowGlobal = acl:match_rule(global, AccessGlobal, From),
case Node of
- ?NS_ADMIN ++ "#announce" ->
+ ?NS_ADMIN_ANNOUNCE ->
?INFO_RESULT(Allow, [?NS_COMMANDS]);
- ?NS_ADMIN ++ "#announce-all" ->
+ ?NS_ADMIN_ANNOUNCE_ALL ->
?INFO_RESULT(Allow, [?NS_COMMANDS]);
- ?NS_ADMIN ++ "#set-motd" ->
+ ?NS_ADMIN_SET_MOTD ->
?INFO_RESULT(Allow, [?NS_COMMANDS]);
- ?NS_ADMIN ++ "#edit-motd" ->
+ ?NS_ADMIN_EDIT_MOTD ->
?INFO_RESULT(Allow, [?NS_COMMANDS]);
- ?NS_ADMIN ++ "#delete-motd" ->
+ ?NS_ADMIN_DELETE_MOTD ->
?INFO_RESULT(Allow, [?NS_COMMANDS]);
- ?NS_ADMIN ++ "#announce-allhosts" ->
+ ?NS_ADMIN_ANNOUNCE_ALLHOSTS ->
?INFO_RESULT(AllowGlobal, [?NS_COMMANDS]);
- ?NS_ADMIN ++ "#announce-all-allhosts" ->
+ ?NS_ADMIN_ANNOUNCE_ALL_ALLHOSTS ->
?INFO_RESULT(AllowGlobal, [?NS_COMMANDS]);
- ?NS_ADMIN ++ "#set-motd-allhosts" ->
+ ?NS_ADMIN_SET_MOTD_ALLHOSTS ->
?INFO_RESULT(AllowGlobal, [?NS_COMMANDS]);
- ?NS_ADMIN ++ "#edit-motd-allhosts" ->
+ ?NS_ADMIN_EDIT_MOTD_ALLHOSTS ->
?INFO_RESULT(AllowGlobal, [?NS_COMMANDS]);
- ?NS_ADMIN ++ "#delete-motd-allhosts" ->
+ ?NS_ADMIN_DELETE_MOTD_ALLHOSTS ->
?INFO_RESULT(AllowGlobal, [?NS_COMMANDS]);
_ ->
Acc
@@ -279,13 +278,17 @@ disco_features(Acc, From, #jid{lserver = LServer} = _To,
end.
%%-------------------------------------------------------------------------
-
-define(NODE_TO_ITEM(Lang, Server, Node),
- {xmlelement, "item",
- [{"jid", Server},
- {"node", Node},
- {"name", get_title(Lang, Node)}],
- []}).
+(
+ #xmlel{
+ name = <<"item">>,
+ attrs = [
+ {<<"jid">>, Server},
+ {<<"node">>, Node},
+ {<<"name">>, get_title(Lang, Node)}
+ ]
+ }
+)).
-define(ITEMS_RESULT(Allow, Items),
case Allow of
@@ -295,14 +298,13 @@ disco_features(Acc, From, #jid{lserver = LServer} = _To,
{result, Items}
end).
-disco_items(Acc, From, #jid{lserver = LServer, server = Server} = _To,
- "", Lang) ->
+disco_items(Acc, From, #jid{lserver = LServer, server = Server} = _To, <<>>, Lang) ->
case gen_mod:is_loaded(LServer, mod_adhoc) of
false ->
Acc;
_ ->
- Access1 = gen_mod:get_module_opt(LServer, ?MODULE, access, none),
- Access2 = gen_mod:get_module_opt(global, ?MODULE, access, none),
+ Access1 = get_access(LServer),
+ Access2 = get_access(global),
case {acl:match_rule(LServer, Access1, From),
acl:match_rule(global, Access2, From)} of
{deny, deny} ->
@@ -312,12 +314,12 @@ disco_items(Acc, From, #jid{lserver = LServer, server = Server} = _To,
{result, I} -> I;
_ -> []
end,
- Nodes = [?NODE_TO_ITEM(Lang, Server, "announce")],
+ Nodes = [?NODE_TO_ITEM(Lang, Server, <<"announce">>)],
{result, Items ++ Nodes}
end
end;
-disco_items(Acc, From, #jid{lserver = LServer} = To, "announce", Lang) ->
+disco_items(Acc, From, #jid{lserver = LServer} = To, <<"announce">>, Lang) ->
case gen_mod:is_loaded(LServer, mod_adhoc) of
false ->
Acc;
@@ -330,30 +332,30 @@ disco_items(Acc, From, #jid{lserver = LServer} = _To, Node, _Lang) ->
false ->
Acc;
_ ->
- Access = gen_mod:get_module_opt(LServer, ?MODULE, access, none),
+ Access = get_access(LServer),
Allow = acl:match_rule(LServer, Access, From),
- AccessGlobal = gen_mod:get_module_opt(global, ?MODULE, access, none),
+ AccessGlobal = get_access(global),
AllowGlobal = acl:match_rule(global, AccessGlobal, From),
case Node of
- ?NS_ADMIN ++ "#announce" ->
+ ?NS_ADMIN_ANNOUNCE ->
?ITEMS_RESULT(Allow, []);
- ?NS_ADMIN ++ "#announce-all" ->
+ ?NS_ADMIN_ANNOUNCE_ALL ->
?ITEMS_RESULT(Allow, []);
- ?NS_ADMIN ++ "#set-motd" ->
+ ?NS_ADMIN_SET_MOTD ->
?ITEMS_RESULT(Allow, []);
- ?NS_ADMIN ++ "#edit-motd" ->
+ ?NS_ADMIN_EDIT_MOTD ->
?ITEMS_RESULT(Allow, []);
- ?NS_ADMIN ++ "#delete-motd" ->
+ ?NS_ADMIN_DELETE_MOTD ->
?ITEMS_RESULT(Allow, []);
- ?NS_ADMIN ++ "#announce-allhosts" ->
+ ?NS_ADMIN_ANNOUNCE_ALLHOSTS ->
?ITEMS_RESULT(AllowGlobal, []);
- ?NS_ADMIN ++ "#announce-all-allhosts" ->
+ ?NS_ADMIN_ANNOUNCE_ALL_ALLHOSTS ->
?ITEMS_RESULT(AllowGlobal, []);
- ?NS_ADMIN ++ "#set-motd-allhosts" ->
+ ?NS_ADMIN_SET_MOTD_ALLHOSTS ->
?ITEMS_RESULT(AllowGlobal, []);
- ?NS_ADMIN ++ "#edit-motd-allhosts" ->
+ ?NS_ADMIN_EDIT_MOTD_ALLHOSTS ->
?ITEMS_RESULT(AllowGlobal, []);
- ?NS_ADMIN ++ "#delete-motd-allhosts" ->
+ ?NS_ADMIN_DELETE_MOTD_ALLHOSTS ->
?ITEMS_RESULT(AllowGlobal, []);
_ ->
Acc
@@ -363,25 +365,25 @@ disco_items(Acc, From, #jid{lserver = LServer} = _To, Node, _Lang) ->
%%-------------------------------------------------------------------------
announce_items(Acc, From, #jid{lserver = LServer, server = Server} = _To, Lang) ->
- Access1 = gen_mod:get_module_opt(LServer, ?MODULE, access, none),
+ Access1 = get_access(LServer),
Nodes1 = case acl:match_rule(LServer, Access1, From) of
allow ->
- [?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN ++ "#announce"),
- ?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN ++ "#announce-all"),
- ?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN ++ "#set-motd"),
- ?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN ++ "#edit-motd"),
- ?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN ++ "#delete-motd")];
+ [?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN_ANNOUNCE),
+ ?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN_ANNOUNCE_ALL),
+ ?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN_SET_MOTD),
+ ?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN_EDIT_MOTD),
+ ?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN_DELETE_MOTD)];
deny ->
[]
end,
- Access2 = gen_mod:get_module_opt(global, ?MODULE, access, none),
+ Access2 = get_access(global),
Nodes2 = case acl:match_rule(global, Access2, From) of
allow ->
- [?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN ++ "#announce-allhosts"),
- ?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN ++ "#announce-all-allhosts"),
- ?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN ++ "#set-motd-allhosts"),
- ?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN ++ "#edit-motd-allhosts"),
- ?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN ++ "#delete-motd-allhosts")];
+ [?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN_ANNOUNCE_ALLHOSTS),
+ ?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN_ANNOUNCE_ALL_ALLHOSTS),
+ ?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN_SET_MOTD_ALLHOSTS),
+ ?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN_EDIT_MOTD_ALLHOSTS),
+ ?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN_DELETE_MOTD_ALLHOSTS)];
deny ->
[]
end,
@@ -411,7 +413,7 @@ announce_commands(Acc, From, #jid{lserver = LServer} = To,
#adhoc_request{ node = Node} = Request) ->
LNode = tokenize(Node),
F = fun() ->
- Access = gen_mod:get_module_opt(global, ?MODULE, access, none),
+ Access = get_access(global),
Allow = acl:match_rule(global, Access, From),
commands_result(Allow, From, To, Request)
end,
@@ -422,7 +424,7 @@ announce_commands(Acc, From, #jid{lserver = LServer} = To,
?NS_ADMINL("edit-motd-allhosts") -> F();
?NS_ADMINL("delete-motd-allhosts") -> F();
_ ->
- Access = gen_mod:get_module_opt(LServer, ?MODULE, access, none),
+ Access = get_access(LServer),
Allow = acl:match_rule(LServer, Access, From),
case LNode of
?NS_ADMINL("announce") ->
@@ -455,19 +457,15 @@ announce_commands(From, To,
%% understood as "execute". If there was no <actions/>
%% element in the first response (which there isn't in our
%% case), "execute" and "complete" are equivalent.
- ActionIsExecute = lists:member(Action,
- ["", "execute", "complete"]),
- if Action == "cancel" ->
+ ActionIsExecute = lists:member(Action, [<<>>, <<"execute">>, <<"complete">>]),
+ if Action == <<"cancel">> ->
%% User cancels request
- adhoc:produce_response(Request,
- #adhoc_response{status = canceled});
+ adhoc:produce_response(Request, #adhoc_response{status = canceled});
XData == false, ActionIsExecute ->
%% User requests form
Elements = generate_adhoc_form(Lang, Node, To#jid.lserver),
- adhoc:produce_response(
- Request,
- #adhoc_response{status = executing,
- elements = [Elements]});
+ adhoc:produce_response(Request,
+ #adhoc_response{status = executing,elements = [Elements]});
XData /= false, ActionIsExecute ->
%% User returns form.
case jlib:parse_xdata_submit(XData) of
@@ -481,16 +479,27 @@ announce_commands(From, To,
end.
-define(VVALUE(Val),
- {xmlelement, "value", [], [{xmlcdata, Val}]}).
+(
+ #xmlel{
+ name = <<"value">>,
+ children = [{xmlcdata, Val}]
+ }
+)).
+
-define(TVFIELD(Type, Var, Val),
- {xmlelement, "field", [{"type", Type},
- {"var", Var}],
- vvaluel(Val)}).
--define(HFIELD(), ?TVFIELD("hidden", "FORM_TYPE", ?NS_ADMIN)).
+(
+ #xmlel{
+ name = <<"field">>,
+ attrs = [{<<"type">>, Type}, {<<"var">>, Var}],
+ children = vvaluel(Val)
+ }
+)).
+
+-define(HFIELD(), ?TVFIELD(<<"hidden">>, <<"FORM_TYPE">>, ?NS_ADMIN)).
vvaluel(Val) ->
case Val of
- "" -> [];
+ <<>> -> [];
_ -> [?VVALUE(Val)]
end.
@@ -502,136 +511,152 @@ generate_adhoc_form(Lang, Node, ServerHost) ->
true ->
{[], []}
end,
- {xmlelement, "x",
- [{"xmlns", ?NS_XDATA},
- {"type", "form"}],
- [?HFIELD(),
- {xmlelement, "title", [], [{xmlcdata, get_title(Lang, Node)}]}]
- ++
- if (LNode == ?NS_ADMINL("delete-motd"))
- or (LNode == ?NS_ADMINL("delete-motd-allhosts")) ->
- [{xmlelement, "field",
- [{"var", "confirm"},
- {"type", "boolean"},
- {"label", translate:translate(Lang, "Really delete message of the day?")}],
- [{xmlelement, "value",
- [],
- [{xmlcdata, "true"}]}]}];
- true ->
- [{xmlelement, "field",
- [{"var", "subject"},
- {"type", "text-single"},
- {"label", translate:translate(Lang, "Subject")}],
- vvaluel(OldSubject)},
- {xmlelement, "field",
- [{"var", "body"},
- {"type", "text-multi"},
- {"label", translate:translate(Lang, "Message body")}],
- vvaluel(OldBody)}]
- end}.
+ #xmlel{
+ name = <<"x">>,
+ attrs = [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"form">>}],
+ children = [
+ ?HFIELD(),
+ #xmlel{name = <<"title">>, children = [{xmlcdata, get_title(Lang, Node)}]}
+ ]
+ ++
+ if (LNode == ?NS_ADMINL("delete-motd"))
+ or (LNode == ?NS_ADMINL("delete-motd-allhosts")) ->
+ [#xmlel{
+ name = <<"field">>,
+ attrs = [
+ {<<"var">>, <<"confirm">>},
+ {<<"type">>, <<"boolean">>},
+ {<<"label">>,
+ translate:translate(Lang, <<"Really delete message of the day?">>)}
+ ],
+ children = [
+ #xmlel{name = <<"value">>, children = [{xmlcdata, <<"true">>}]}
+ ]
+ }
+ ];
+ true ->
+ [#xmlel{
+ name = <<"field">>,
+ attrs = [
+ {<<"var">>, <<"subject">>},
+ {<<"type">>, <<"text-single">>},
+ {<<"label">>, translate:translate(Lang, <<"Subject">>)}],
+ children = vvaluel(OldSubject)
+ },
+ #xmlel{
+ name = <<"field">>,
+ attrs = [
+ {<<"var">>, <<"body">>},
+ {<<"type">>, <<"text-multi">>},
+ {<<"label">>, translate:translate(Lang, <<"Message body">>)}],
+ children = vvaluel(OldBody)
+ }
+ ]
+
+ end}.
join_lines([]) ->
- [];
+ <<>>;
join_lines(Lines) ->
join_lines(Lines, []).
join_lines([Line|Lines], Acc) ->
- join_lines(Lines, ["\n",Line|Acc]);
+ join_lines(Lines, [<<"\n">>,Line|Acc]);
join_lines([], Acc) ->
%% Remove last newline
- lists:flatten(lists:reverse(tl(Acc))).
+ iolist_to_binary(lists:reverse(tl(Acc))).
handle_adhoc_form(From, #jid{lserver = LServer} = To,
#adhoc_request{lang = Lang,
node = Node,
sessionid = SessionID},
Fields) ->
- Confirm = case lists:keysearch("confirm", 1, Fields) of
- {value, {"confirm", ["true"]}} ->
+ Confirm = case lists:keysearch(<<"confirm">>, 1, Fields) of
+ {value, {<<"confirm">>, [<<"true">>]}} ->
true;
- {value, {"confirm", ["1"]}} ->
+ {value, {<<"confirm">>, [<<"1">>]}} ->
true;
_ ->
false
end,
- Subject = case lists:keysearch("subject", 1, Fields) of
- {value, {"subject", SubjectLines}} ->
+ Subject = case lists:keysearch(<<"subject">>, 1, Fields) of
+ {value, {<<"subject">>, SubjectLines}} ->
%% There really shouldn't be more than one
%% subject line, but can we stop them?
join_lines(SubjectLines);
_ ->
- []
+ <<>>
end,
- Body = case lists:keysearch("body", 1, Fields) of
- {value, {"body", BodyLines}} ->
+ Body = case lists:keysearch(<<"body">>, 1, Fields) of
+ {value, {<<"body">>, BodyLines}} ->
join_lines(BodyLines);
_ ->
- []
+ <<>>
end,
Response = #adhoc_response{lang = Lang,
node = Node,
sessionid = SessionID,
status = completed},
- Packet = {xmlelement, "message", [{"type", "headline"}],
- if Subject /= [] ->
- [{xmlelement, "subject", [],
- [{xmlcdata, Subject}]}];
- true ->
- []
- end ++
- if Body /= [] ->
- [{xmlelement, "body", [],
- [{xmlcdata, Body}]}];
- true ->
- []
- end},
-
+ Packet = #xmlel{
+ name = <<"message">>,
+ attrs = [{<<"type">>, <<"headline">>}],
+ children = if Subject /= <<>> ->
+ [#xmlel{name = <<"subject">>, children = [{xmlcdata, Subject}]}];
+ true ->
+ []
+ end
+ ++
+ if Body /= <<>> ->
+ [#xmlel{name = <<"body">>, children = [{xmlcdata, Body}]}];
+ true ->
+ []
+ end
+ },
Proc = gen_mod:get_module_proc(LServer, ?PROCNAME),
case {Node, Body} of
- {?NS_ADMIN ++ "#delete-motd", _} ->
+ {?NS_ADMIN_DELETE_MOTD, _} ->
if Confirm ->
Proc ! {announce_motd_delete, From, To, Packet},
adhoc:produce_response(Response);
true ->
adhoc:produce_response(Response)
end;
- {?NS_ADMIN ++ "#delete-motd-allhosts", _} ->
+ {?NS_ADMIN_DELETE_MOTD_ALLHOSTS, _} ->
if Confirm ->
Proc ! {announce_all_hosts_motd_delete, From, To, Packet},
adhoc:produce_response(Response);
true ->
adhoc:produce_response(Response)
end;
- {_, []} ->
+ {_, <<>>} ->
%% An announce message with no body is definitely an operator error.
%% Throw an error and give him/her a chance to send message again.
- {error, ?ERRT_NOT_ACCEPTABLE(
- Lang,
- "No body provided for announce message")};
+ {error, ?ERRT_NOT_ACCEPTABLE(Lang,
+ <<"No body provided for announce message">>)};
%% Now send the packet to ?PROCNAME.
%% We don't use direct announce_* functions because it
%% leads to large delay in response and <iq/> queries processing
- {?NS_ADMIN ++ "#announce", _} ->
+ {?NS_ADMIN_ANNOUNCE, _} ->
Proc ! {announce_online, From, To, Packet},
adhoc:produce_response(Response);
- {?NS_ADMIN ++ "#announce-allhosts", _} ->
+ {?NS_ADMIN_ANNOUNCE_ALLHOSTS, _} ->
Proc ! {announce_all_hosts_online, From, To, Packet},
adhoc:produce_response(Response);
- {?NS_ADMIN ++ "#announce-all", _} ->
+ {?NS_ADMIN_ANNOUNCE_ALL, _} ->
Proc ! {announce_all, From, To, Packet},
adhoc:produce_response(Response);
- {?NS_ADMIN ++ "#announce-all-allhosts", _} ->
+ {?NS_ADMIN_ANNOUNCE_ALL_ALLHOSTS, _} ->
Proc ! {announce_all_hosts_all, From, To, Packet},
adhoc:produce_response(Response);
- {?NS_ADMIN ++ "#set-motd", _} ->
+ {?NS_ADMIN_SET_MOTD, _} ->
Proc ! {announce_motd, From, To, Packet},
adhoc:produce_response(Response);
- {?NS_ADMIN ++ "#set-motd-allhosts", _} ->
+ {?NS_ADMIN_SET_MOTD_ALLHOSTS, _} ->
Proc ! {announce_all_hosts_motd, From, To, Packet},
adhoc:produce_response(Response);
- {?NS_ADMIN ++ "#edit-motd", _} ->
+ {?NS_ADMIN_EDIT_MOTD, _} ->
Proc ! {announce_motd_update, From, To, Packet},
adhoc:produce_response(Response);
- {?NS_ADMIN ++ "#edit-motd-allhosts", _} ->
+ {?NS_ADMIN_EDIT_MOTD_ALLHOSTS, _} ->
Proc ! {announce_all_hosts_motd_update, From, To, Packet},
adhoc:produce_response(Response);
_ ->
@@ -640,65 +665,65 @@ handle_adhoc_form(From, #jid{lserver = LServer} = To,
{error, ?ERR_INTERNAL_SERVER_ERROR}
end.
-get_title(Lang, "announce") ->
- translate:translate(Lang, "Announcements");
-get_title(Lang, ?NS_ADMIN ++ "#announce-all") ->
- translate:translate(Lang, "Send announcement to all users");
-get_title(Lang, ?NS_ADMIN ++ "#announce-all-allhosts") ->
- translate:translate(Lang, "Send announcement to all users on all hosts");
-get_title(Lang, ?NS_ADMIN ++ "#announce") ->
- translate:translate(Lang, "Send announcement to all online users");
-get_title(Lang, ?NS_ADMIN ++ "#announce-allhosts") ->
- translate:translate(Lang, "Send announcement to all online users on all hosts");
-get_title(Lang, ?NS_ADMIN ++ "#set-motd") ->
- translate:translate(Lang, "Set message of the day and send to online users");
-get_title(Lang, ?NS_ADMIN ++ "#set-motd-allhosts") ->
- translate:translate(Lang, "Set message of the day on all hosts and send to online users");
-get_title(Lang, ?NS_ADMIN ++ "#edit-motd") ->
- translate:translate(Lang, "Update message of the day (don't send)");
-get_title(Lang, ?NS_ADMIN ++ "#edit-motd-allhosts") ->
- translate:translate(Lang, "Update message of the day on all hosts (don't send)");
-get_title(Lang, ?NS_ADMIN ++ "#delete-motd") ->
- translate:translate(Lang, "Delete message of the day");
-get_title(Lang, ?NS_ADMIN ++ "#delete-motd-allhosts") ->
- translate:translate(Lang, "Delete message of the day on all hosts").
+get_title(Lang, <<"announce">>) ->
+ translate:translate(Lang, <<"Announcements">>);
+get_title(Lang, ?NS_ADMIN_ANNOUNCE_ALL) ->
+ translate:translate(Lang, <<"Send announcement to all users">>);
+get_title(Lang, ?NS_ADMIN_ANNOUNCE_ALL_ALLHOSTS) ->
+ translate:translate(Lang, <<"Send announcement to all users on all hosts">>);
+get_title(Lang, ?NS_ADMIN_ANNOUNCE) ->
+ translate:translate(Lang, <<"Send announcement to all online users">>);
+get_title(Lang, ?NS_ADMIN_ANNOUNCE_ALLHOSTS) ->
+ translate:translate(Lang, <<"Send announcement to all online users on all hosts">>);
+get_title(Lang, ?NS_ADMIN_SET_MOTD) ->
+ translate:translate(Lang, <<"Set message of the day and send to online users">>);
+get_title(Lang, ?NS_ADMIN_SET_MOTD_ALLHOSTS) ->
+ translate:translate(Lang, <<"Set message of the day on all hosts and send to online users">>);
+get_title(Lang, ?NS_ADMIN_EDIT_MOTD) ->
+ translate:translate(Lang, <<"Update message of the day (don't send)">>);
+get_title(Lang, ?NS_ADMIN_EDIT_MOTD_ALLHOSTS) ->
+ translate:translate(Lang, <<"Update message of the day on all hosts (don't send)">>);
+get_title(Lang, ?NS_ADMIN_DELETE_MOTD) ->
+ translate:translate(Lang, <<"Delete message of the day">>);
+get_title(Lang, ?NS_ADMIN_DELETE_MOTD_ALLHOSTS) ->
+ translate:translate(Lang, <<"Delete message of the day on all hosts">>).
%%-------------------------------------------------------------------------
announce_all(From, To, Packet) ->
Host = To#jid.lserver,
- Access = gen_mod:get_module_opt(Host, ?MODULE, access, none),
+ Access = get_access(Host),
case acl:match_rule(Host, Access, From) of
deny ->
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
ejabberd_router:route(To, From, Err);
allow ->
- Local = jlib:make_jid("", To#jid.server, ""),
+ Local = jlib:make_jid(<<>>, To#jid.server, <<>>),
lists:foreach(
fun({User, Server}) ->
- Dest = jlib:make_jid(User, Server, ""),
+ Dest = jlib:make_jid(User, Server, <<>>),
ejabberd_router:route(Local, Dest, Packet)
end, ejabberd_auth:get_vh_registered_users(Host))
end.
announce_all_hosts_all(From, To, Packet) ->
- Access = gen_mod:get_module_opt(global, ?MODULE, access, none),
+ Access = get_access(global),
case acl:match_rule(global, Access, From) of
deny ->
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
ejabberd_router:route(To, From, Err);
allow ->
- Local = jlib:make_jid("", To#jid.server, ""),
+ Local = jlib:make_jid(<<>>, To#jid.server, <<>>),
lists:foreach(
fun({User, Server}) ->
- Dest = jlib:make_jid(User, Server, ""),
+ Dest = jlib:make_jid(User, Server, <<>>),
ejabberd_router:route(Local, Dest, Packet)
end, ejabberd_auth:dirty_get_registered_users())
end.
announce_online(From, To, Packet) ->
Host = To#jid.lserver,
- Access = gen_mod:get_module_opt(Host, ?MODULE, access, none),
+ Access = get_access(Host),
case acl:match_rule(Host, Access, From) of
deny ->
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
@@ -710,7 +735,7 @@ announce_online(From, To, Packet) ->
end.
announce_all_hosts_online(From, To, Packet) ->
- Access = gen_mod:get_module_opt(global, ?MODULE, access, none),
+ Access = get_access(global),
case acl:match_rule(global, Access, From) of
deny ->
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
@@ -722,7 +747,7 @@ announce_all_hosts_online(From, To, Packet) ->
end.
announce_online1(Sessions, Server, Packet) ->
- Local = jlib:make_jid("", Server, ""),
+ Local = jlib:make_jid(<<>>, Server, <<>>),
lists:foreach(
fun({U, S, R}) ->
Dest = jlib:make_jid(U, S, R),
@@ -731,7 +756,7 @@ announce_online1(Sessions, Server, Packet) ->
announce_motd(From, To, Packet) ->
Host = To#jid.lserver,
- Access = gen_mod:get_module_opt(Host, ?MODULE, access, none),
+ Access = get_access(Host),
case acl:match_rule(Host, Access, From) of
deny ->
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
@@ -741,7 +766,7 @@ announce_motd(From, To, Packet) ->
end.
announce_all_hosts_motd(From, To, Packet) ->
- Access = gen_mod:get_module_opt(global, ?MODULE, access, none),
+ Access = get_access(global),
case acl:match_rule(global, Access, From) of
deny ->
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
@@ -771,10 +796,10 @@ announce_motd(Host, Packet) ->
fun({U, _S, _R}) ->
Username = ejabberd_odbc:escape(U),
odbc_queries:update_t(
- "motd",
- ["username", "xml"],
- [Username, ""],
- ["username='", Username, "'"])
+ <<"motd">>,
+ [<<"username">>, <<"xml">>],
+ [Username, <<"">>],
+ [<<"username='">>, Username, <<"'">>])
end, Sessions)
end,
ejabberd_odbc:sql_transaction(LServer, F)
@@ -782,7 +807,7 @@ announce_motd(Host, Packet) ->
announce_motd_update(From, To, Packet) ->
Host = To#jid.lserver,
- Access = gen_mod:get_module_opt(Host, ?MODULE, access, none),
+ Access = get_access(Host),
case acl:match_rule(Host, Access, From) of
deny ->
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
@@ -792,7 +817,7 @@ announce_motd_update(From, To, Packet) ->
end.
announce_all_hosts_motd_update(From, To, Packet) ->
- Access = gen_mod:get_module_opt(global, ?MODULE, access, none),
+ Access = get_access(global),
case acl:match_rule(global, Access, From) of
deny ->
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
@@ -814,17 +839,17 @@ announce_motd_update(LServer, Packet) ->
XML = ejabberd_odbc:escape(xml:element_to_binary(Packet)),
F = fun() ->
odbc_queries:update_t(
- "motd",
- ["username", "xml"],
- ["", XML],
- ["username=''"])
+ <<"motd">>,
+ [<<"username">>, <<"xml">>],
+ [<<"">>, XML],
+ [<<"username=''">>])
end,
ejabberd_odbc:sql_transaction(LServer, F)
end.
announce_motd_delete(From, To, Packet) ->
Host = To#jid.lserver,
- Access = gen_mod:get_module_opt(Host, ?MODULE, access, none),
+ Access = get_access(Host),
case acl:match_rule(Host, Access, From) of
deny ->
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
@@ -834,7 +859,7 @@ announce_motd_delete(From, To, Packet) ->
end.
announce_all_hosts_motd_delete(From, To, Packet) ->
- Access = gen_mod:get_module_opt(global, ?MODULE, access, none),
+ Access = get_access(global),
case acl:match_rule(global, Access, From) of
deny ->
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
@@ -862,7 +887,7 @@ announce_motd_delete(LServer) ->
mnesia:transaction(F);
odbc ->
F = fun() ->
- ejabberd_odbc:sql_query_t(["delete from motd;"])
+ ejabberd_odbc:sql_query_t([<<"delete from motd;">>])
end,
ejabberd_odbc:sql_transaction(LServer, F)
end.
@@ -878,7 +903,7 @@ send_motd(#jid{luser = LUser, lserver = LServer} = JID, mnesia) ->
[#motd_users{}] ->
ok;
_ ->
- Local = jlib:make_jid("", LServer, ""),
+ Local = jlib:make_jid(<<>>, LServer, <<>>),
ejabberd_router:route(Local, JID, Packet),
F = fun() ->
mnesia:write(#motd_users{us = US})
@@ -888,10 +913,10 @@ send_motd(#jid{luser = LUser, lserver = LServer} = JID, mnesia) ->
_ ->
ok
end;
-send_motd(#jid{luser = LUser, lserver = LServer} = JID, odbc) when LUser /= "" ->
+send_motd(#jid{luser = LUser, lserver = LServer} = JID, odbc) when LUser /= <<>> ->
case catch ejabberd_odbc:sql_query(
- LServer, ["select xml from motd where username='';"]) of
- {selected, ["xml"], [{XML}]} ->
+ LServer, [<<"select xml from motd where username='';">>]) of
+ {selected, [<<"xml">>], [[XML]]} ->
case xml_stream:parse_element(XML) of
{error, _} ->
ok;
@@ -899,17 +924,17 @@ send_motd(#jid{luser = LUser, lserver = LServer} = JID, odbc) when LUser /= "" -
Username = ejabberd_odbc:escape(LUser),
case catch ejabberd_odbc:sql_query(
LServer,
- ["select username from motd "
- "where username='", Username, "';"]) of
- {selected, ["username"], []} ->
- Local = jlib:make_jid("", LServer, ""),
+ [<<"select username from motd "
+ "where username='">>, Username, <<"';">>]) of
+ {selected, [<<"username">>], []} ->
+ Local = jlib:make_jid(<<"">>, LServer, <<"">>),
ejabberd_router:route(Local, JID, Packet),
F = fun() ->
odbc_queries:update_t(
- "motd",
- ["username", "xml"],
- [Username, ""],
- ["username='", Username, "'"])
+ <<"motd">>,
+ [<<"username">>, <<"xml">>],
+ [Username, <<"">>],
+ [<<"username='">>, Username, <<"'">>])
end,
ejabberd_odbc:sql_transaction(LServer, F);
_ ->
@@ -925,10 +950,10 @@ send_motd(_, odbc) ->
get_stored_motd(LServer) ->
case get_stored_motd_packet(LServer, gen_mod:db_type(LServer, ?MODULE)) of
{ok, Packet} ->
- {xml:get_subtag_cdata(Packet, "subject"),
- xml:get_subtag_cdata(Packet, "body")};
+ {xml:get_subtag_cdata(Packet, <<"subject">>),
+ xml:get_subtag_cdata(Packet, <<"body">>)};
error ->
- {"", ""}
+ {<<>>, <<>>}
end.
get_stored_motd_packet(LServer, mnesia) ->
@@ -940,8 +965,8 @@ get_stored_motd_packet(LServer, mnesia) ->
end;
get_stored_motd_packet(LServer, odbc) ->
case catch ejabberd_odbc:sql_query(
- LServer, ["select xml from motd where username='';"]) of
- {selected, ["xml"], [{XML}]} ->
+ LServer, [<<"select xml from motd where username='';">>]) of
+ {selected, [<<"xml">>], [[XML]]} ->
case xml_stream:parse_element(XML) of
{error, _} ->
error;
@@ -954,25 +979,36 @@ get_stored_motd_packet(LServer, odbc) ->
%% This function is similar to others, but doesn't perform any ACL verification
send_announcement_to_all(Host, SubjectS, BodyS) ->
- SubjectEls = if SubjectS /= [] ->
- [{xmlelement, "subject", [], [{xmlcdata, SubjectS}]}];
- true ->
- []
- end,
- BodyEls = if BodyS /= [] ->
- [{xmlelement, "body", [], [{xmlcdata, BodyS}]}];
- true ->
- []
- end,
- Packet = {xmlelement, "message", [{"type", "headline"}], SubjectEls ++ BodyEls},
+ SubjectEls = if SubjectS /= <<>> ->
+ [#xmlel{name = <<"subject">>, children = [{xmlcdata, SubjectS}]}];
+ true ->
+ []
+ end,
+ BodyEls = if BodyS /= <<>> ->
+ [#xmlel{name = <<"body">>, children = [{xmlcdata, BodyS}]}];
+ true ->
+ []
+ end,
+ Packet = #xmlel{
+ name = <<"message">>,
+ attrs = [{<<"type">>, <<"headline">>}],
+ children = SubjectEls ++ BodyEls
+ },
Sessions = ejabberd_sm:dirty_get_sessions_list(),
- Local = jlib:make_jid("", Host, ""),
+ Local = jlib:make_jid(<<>>, Host, <<>>),
lists:foreach(
fun({U, S, R}) ->
Dest = jlib:make_jid(U, S, R),
ejabberd_router:route(Local, Dest, Packet)
end, Sessions).
+-spec get_access(global | binary()) -> atom().
+
+get_access(Host) ->
+ gen_mod:get_module_opt(Host, ?MODULE, access,
+ fun(A) when is_atom(A) -> A end,
+ none).
+
%%-------------------------------------------------------------------------
update_tables() ->
@@ -983,39 +1019,14 @@ update_motd_table() ->
Fields = record_info(fields, motd),
case mnesia:table_info(motd, attributes) of
Fields ->
- ok;
- [id, packet] ->
- ?INFO_MSG("Converting motd table from "
- "{id, packet} format", []),
- Host = ?MYNAME,
- {atomic, ok} = mnesia:create_table(
- mod_announce_tmp_table,
- [{disc_only_copies, [node()]},
- {type, bag},
- {local_content, true},
- {record_name, motd},
- {attributes, record_info(fields, motd)}]),
- mnesia:transform_table(motd, ignore, Fields),
- F1 = fun() ->
- mnesia:write_lock_table(mod_announce_tmp_table),
- mnesia:foldl(
- fun(#motd{server = _} = R, _) ->
- mnesia:dirty_write(
- mod_announce_tmp_table,
- R#motd{server = Host})
- end, ok, motd)
- end,
- mnesia:transaction(F1),
- mnesia:clear_table(motd),
- F2 = fun() ->
- mnesia:write_lock_table(motd),
- mnesia:foldl(
- fun(R, _) ->
- mnesia:dirty_write(R)
- end, ok, mod_announce_tmp_table)
- end,
- mnesia:transaction(F2),
- mnesia:delete_table(mod_announce_tmp_table);
+ ejabberd_config:convert_table_to_binary(
+ motd, Fields, set,
+ fun(#motd{server = S}) -> S end,
+ fun(#motd{server = S, packet = P} = R) ->
+ NewS = iolist_to_binary(S),
+ NewP = xml:to_xmlel(P),
+ R#motd{server = NewS, packet = NewP}
+ end);
_ ->
?INFO_MSG("Recreating motd table", []),
mnesia:transform_table(motd, ignore, Fields)
@@ -1026,40 +1037,37 @@ update_motd_users_table() ->
Fields = record_info(fields, motd_users),
case mnesia:table_info(motd_users, attributes) of
Fields ->
- ok;
- [luser, dummy] ->
- ?INFO_MSG("Converting motd_users table from "
- "{luser, dummy} format", []),
- Host = ?MYNAME,
- {atomic, ok} = mnesia:create_table(
- mod_announce_tmp_table,
- [{disc_only_copies, [node()]},
- {type, bag},
- {local_content, true},
- {record_name, motd_users},
- {attributes, record_info(fields, motd_users)}]),
- mnesia:transform_table(motd_users, ignore, Fields),
- F1 = fun() ->
- mnesia:write_lock_table(mod_announce_tmp_table),
- mnesia:foldl(
- fun(#motd_users{us = U} = R, _) ->
- mnesia:dirty_write(
- mod_announce_tmp_table,
- R#motd_users{us = {U, Host}})
- end, ok, motd_users)
- end,
- mnesia:transaction(F1),
- mnesia:clear_table(motd_users),
- F2 = fun() ->
- mnesia:write_lock_table(motd_users),
- mnesia:foldl(
- fun(R, _) ->
- mnesia:dirty_write(R)
- end, ok, mod_announce_tmp_table)
- end,
- mnesia:transaction(F2),
- mnesia:delete_table(mod_announce_tmp_table);
+ ejabberd_config:convert_table_to_binary(
+ motd_users, Fields, set,
+ fun(#motd_users{us = {U, _}}) -> U end,
+ fun(#motd_users{us = {U, S}} = R) ->
+ NewUS = {iolist_to_binary(U),
+ iolist_to_binary(S)},
+ R#motd_users{us = NewUS}
+ end);
_ ->
?INFO_MSG("Recreating motd_users table", []),
mnesia:transform_table(motd_users, ignore, Fields)
end.
+
+export(_Server) ->
+ [{motd,
+ fun(Host, #motd{server = LServer, packet = El})
+ when LServer == Host ->
+ [[<<"delete from motd where username='';">>],
+ [<<"insert into motd(username, xml) values ('', '">>,
+ ejabberd_odbc:escape(xml:element_to_binary(El)),
+ <<"');">>]];
+ (_Host, _R) ->
+ []
+ end},
+ {motd_users,
+ fun(Host, #motd_users{us = {LUser, LServer}})
+ when LServer == Host, LUser /= <<"">> ->
+ Username = ejabberd_odbc:escape(LUser),
+ [[<<"delete from motd where username='">>, Username, <<"';">>],
+ [<<"insert into motd(username, xml) values ('">>,
+ Username, <<"', '');">>]];
+ (_Host, _R) ->
+ []
+ end}].
diff --git a/src/mod_blocking.erl b/src/mod_blocking.erl
index db5fcdb4e..5d5f33e99 100644
--- a/src/mod_blocking.erl
+++ b/src/mod_blocking.erl
@@ -28,32 +28,34 @@
-behaviour(gen_mod).
--export([start/2, stop/1,
- process_iq/3,
- process_iq_set/4,
- process_iq_get/5]).
+-export([start/2, stop/1, process_iq/3,
+ process_iq_set/4, process_iq_get/5]).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
+
-include("mod_privacy.hrl").
start(Host, Opts) ->
- IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
- ejabberd_hooks:add(privacy_iq_get, Host,
- ?MODULE, process_iq_get, 40),
- ejabberd_hooks:add(privacy_iq_set, Host,
- ?MODULE, process_iq_set, 40),
+ IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
+ one_queue),
+ ejabberd_hooks:add(privacy_iq_get, Host, ?MODULE,
+ process_iq_get, 40),
+ ejabberd_hooks:add(privacy_iq_set, Host, ?MODULE,
+ process_iq_set, 40),
mod_disco:register_feature(Host, ?NS_BLOCKING),
- gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_BLOCKING,
- ?MODULE, process_iq, IQDisc).
+ gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
+ ?NS_BLOCKING, ?MODULE, process_iq, IQDisc).
stop(Host) ->
- ejabberd_hooks:delete(privacy_iq_get, Host,
- ?MODULE, process_iq_get, 40),
- ejabberd_hooks:delete(privacy_iq_set, Host,
- ?MODULE, process_iq_set, 40),
+ ejabberd_hooks:delete(privacy_iq_get, Host, ?MODULE,
+ process_iq_get, 40),
+ ejabberd_hooks:delete(privacy_iq_set, Host, ?MODULE,
+ process_iq_set, 40),
mod_disco:unregister_feature(Host, ?NS_BLOCKING),
- gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_BLOCKING).
+ gen_iq_handler:remove_iq_handler(ejabberd_sm, Host,
+ ?NS_BLOCKING).
process_iq(_From, _To, IQ) ->
SubEl = IQ#iq.sub_el,
@@ -61,138 +63,118 @@ process_iq(_From, _To, IQ) ->
process_iq_get(_, From, _To,
#iq{xmlns = ?NS_BLOCKING,
- sub_el = {xmlelement, "blocklist", _, _}},
+ sub_el = #xmlel{name = <<"blocklist">>}},
_) ->
#jid{luser = LUser, lserver = LServer} = From,
{stop, process_blocklist_get(LUser, LServer)};
+process_iq_get(Acc, _, _, _, _) -> Acc.
-process_iq_get(Acc, _, _, _, _) ->
- Acc.
-
-process_iq_set(_, From, _To, #iq{xmlns = ?NS_BLOCKING,
- sub_el = {xmlelement, SubElName, _, SubEls}}) ->
+process_iq_set(_, From, _To,
+ #iq{xmlns = ?NS_BLOCKING,
+ sub_el =
+ #xmlel{name = SubElName, children = SubEls}}) ->
#jid{luser = LUser, lserver = LServer} = From,
- Res =
- case {SubElName, xml:remove_cdata(SubEls)} of
- {"block", []} ->
- {error, ?ERR_BAD_REQUEST};
- {"block", Els} ->
- JIDs = parse_blocklist_items(Els, []),
- process_blocklist_block(LUser, LServer, JIDs);
- {"unblock", []} ->
- process_blocklist_unblock_all(LUser, LServer);
- {"unblock", Els} ->
- JIDs = parse_blocklist_items(Els, []),
- process_blocklist_unblock(LUser, LServer, JIDs);
- _ ->
- {error, ?ERR_BAD_REQUEST}
- end,
+ Res = case {SubElName, xml:remove_cdata(SubEls)} of
+ {<<"block">>, []} -> {error, ?ERR_BAD_REQUEST};
+ {<<"block">>, Els} ->
+ JIDs = parse_blocklist_items(Els, []),
+ process_blocklist_block(LUser, LServer, JIDs);
+ {<<"unblock">>, []} ->
+ process_blocklist_unblock_all(LUser, LServer);
+ {<<"unblock">>, Els} ->
+ JIDs = parse_blocklist_items(Els, []),
+ process_blocklist_unblock(LUser, LServer, JIDs);
+ _ -> {error, ?ERR_BAD_REQUEST}
+ end,
{stop, Res};
+process_iq_set(Acc, _, _, _) -> Acc.
-process_iq_set(Acc, _, _, _) ->
- Acc.
-
-list_to_blocklist_jids([], JIDs) ->
- JIDs;
-
+list_to_blocklist_jids([], JIDs) -> JIDs;
list_to_blocklist_jids([#listitem{type = jid,
- action = deny,
- value = JID} = Item | Items], JIDs) ->
+ action = deny, value = JID} =
+ Item
+ | Items],
+ JIDs) ->
case Item of
- #listitem{match_all = true} ->
- Match = true;
- #listitem{match_iq = true,
- match_message = true,
- match_presence_in = true,
- match_presence_out = true} ->
- Match = true;
- _ ->
- Match = false
+ #listitem{match_all = true} -> Match = true;
+ #listitem{match_iq = true, match_message = true,
+ match_presence_in = true, match_presence_out = true} ->
+ Match = true;
+ _ -> Match = false
end,
- if
- Match ->
- list_to_blocklist_jids(Items, [JID | JIDs]);
- true ->
- list_to_blocklist_jids(Items, JIDs)
+ if Match -> list_to_blocklist_jids(Items, [JID | JIDs]);
+ true -> list_to_blocklist_jids(Items, JIDs)
end;
-
% Skip Privacy List items than cannot be mapped to Blocking items
list_to_blocklist_jids([_ | Items], JIDs) ->
list_to_blocklist_jids(Items, JIDs).
-parse_blocklist_items([], JIDs) ->
- JIDs;
-
-parse_blocklist_items([{xmlelement, "item", Attrs, _} | Els], JIDs) ->
- case xml:get_attr("jid", Attrs) of
- {value, JID1} ->
- JID = jlib:jid_tolower(jlib:string_to_jid(JID1)),
- parse_blocklist_items(Els, [JID | JIDs]);
- false ->
- % Tolerate missing jid attribute
- parse_blocklist_items(Els, JIDs)
+parse_blocklist_items([], JIDs) -> JIDs;
+parse_blocklist_items([#xmlel{name = <<"item">>,
+ attrs = Attrs}
+ | Els],
+ JIDs) ->
+ case xml:get_attr(<<"jid">>, Attrs) of
+ {value, JID1} ->
+ JID = jlib:jid_tolower(jlib:string_to_jid(JID1)),
+ parse_blocklist_items(Els, [JID | JIDs]);
+ false -> parse_blocklist_items(Els, JIDs)
end;
-
parse_blocklist_items([_ | Els], JIDs) ->
- % Tolerate unknown elements
parse_blocklist_items(Els, JIDs).
process_blocklist_block(LUser, LServer, JIDs) ->
- Filter = fun(List) ->
- AlreadyBlocked = list_to_blocklist_jids(List, []),
- lists:foldr(
- fun(JID, List1) ->
- case lists:member(JID, AlreadyBlocked) of
- true ->
- List1;
- false ->
- [#listitem{type = jid,
- value = JID,
- action = deny,
- order = 0,
- match_all = true}
- | List1]
- end
- end, List, JIDs)
- end,
+ Filter = fun (List) ->
+ AlreadyBlocked = list_to_blocklist_jids(List, []),
+ lists:foldr(fun (JID, List1) ->
+ case lists:member(JID, AlreadyBlocked)
+ of
+ true -> List1;
+ false ->
+ [#listitem{type = jid,
+ value = JID,
+ action = deny,
+ order = 0,
+ match_all = true}
+ | List1]
+ end
+ end,
+ List, JIDs)
+ end,
case process_blocklist_block(LUser, LServer, Filter,
- gen_mod:db_type(LServer, mod_privacy)) of
- {atomic, {ok, Default, List}} ->
- UserList = make_userlist(Default, List),
- broadcast_list_update(LUser, LServer, Default, UserList),
- broadcast_blocklist_event(LUser, LServer, {block, JIDs}),
- {result, [], UserList};
- _ ->
- {error, ?ERR_INTERNAL_SERVER_ERROR}
+ gen_mod:db_type(LServer, mod_privacy))
+ of
+ {atomic, {ok, Default, List}} ->
+ UserList = make_userlist(Default, List),
+ broadcast_list_update(LUser, LServer, Default,
+ UserList),
+ broadcast_blocklist_event(LUser, LServer,
+ {block, JIDs}),
+ {result, [], UserList};
+ _ -> {error, ?ERR_INTERNAL_SERVER_ERROR}
end.
-process_blocklist_block(LUser, LServer, Filter, mnesia) ->
- F =
- fun() ->
+process_blocklist_block(LUser, LServer, Filter,
+ mnesia) ->
+ F = fun () ->
case mnesia:wread({privacy, {LUser, LServer}}) of
- [] ->
- % No lists yet
- P = #privacy{us = {LUser, LServer}},
- % TODO: i18n here:
- NewDefault = "Blocked contacts",
- NewLists1 = [],
- List = [];
- [#privacy{default = Default,
- lists = Lists} = P] ->
- case lists:keysearch(Default, 1, Lists) of
- {value, {_, List}} ->
- % Default list exists
- NewDefault = Default,
- NewLists1 = lists:keydelete(Default, 1, Lists);
- false ->
- % No default list yet, create one
- % TODO: i18n here:
- NewDefault = "Blocked contacts",
- NewLists1 = Lists,
- List = []
- end
+ [] ->
+ P = #privacy{us = {LUser, LServer}},
+ NewDefault = <<"Blocked contacts">>,
+ NewLists1 = [],
+ List = [];
+ [#privacy{default = Default, lists = Lists} = P] ->
+ case lists:keysearch(Default, 1, Lists) of
+ {value, {_, List}} ->
+ NewDefault = Default,
+ NewLists1 = lists:keydelete(Default, 1, Lists);
+ false ->
+ NewDefault = <<"Blocked contacts">>,
+ NewLists1 = Lists,
+ List = []
+ end
end,
- NewList = Filter(List),
+ NewList = Filter(List),
NewLists = [{NewDefault, NewList} | NewLists1],
mnesia:write(P#privacy{default = NewDefault,
lists = NewLists}),
@@ -200,205 +182,183 @@ process_blocklist_block(LUser, LServer, Filter, mnesia) ->
end,
mnesia:transaction(F);
process_blocklist_block(LUser, LServer, Filter, odbc) ->
- F = fun() ->
- Default =
- case mod_privacy:sql_get_default_privacy_list_t(LUser) of
- {selected, ["name"], []} ->
- Name = "Blocked contacts",
- mod_privacy:sql_add_privacy_list(LUser, Name),
- mod_privacy:sql_set_default_privacy_list(
- LUser, Name),
- Name;
- {selected, ["name"], [{Name}]} ->
- Name
- end,
- {selected, ["id"], [{ID}]} =
- mod_privacy:sql_get_privacy_list_id_t(LUser, Default),
- case mod_privacy:sql_get_privacy_list_data_by_id_t(ID) of
- {selected,
- ["t", "value", "action", "ord",
- "match_all", "match_iq", "match_message",
- "match_presence_in",
- "match_presence_out"],
- RItems = [_|_]} ->
- List = lists:map(
- fun mod_privacy:raw_to_item/1,
- RItems);
- _ ->
- List = []
- end,
- NewList = Filter(List),
- NewRItems = lists:map(
- fun mod_privacy:item_to_raw/1,
- NewList),
- mod_privacy:sql_set_privacy_list(
- ID, NewRItems),
- {ok, Default, NewList}
- end,
+ F = fun () ->
+ Default = case
+ mod_privacy:sql_get_default_privacy_list_t(LUser)
+ of
+ {selected, [<<"name">>], []} ->
+ Name = <<"Blocked contacts">>,
+ mod_privacy:sql_add_privacy_list(LUser, Name),
+ mod_privacy:sql_set_default_privacy_list(LUser,
+ Name),
+ Name;
+ {selected, [<<"name">>], [[Name]]} -> Name
+ end,
+ {selected, [<<"id">>], [[ID]]} =
+ mod_privacy:sql_get_privacy_list_id_t(LUser, Default),
+ case mod_privacy:sql_get_privacy_list_data_by_id_t(ID)
+ of
+ {selected,
+ [<<"t">>, <<"value">>, <<"action">>, <<"ord">>,
+ <<"match_all">>, <<"match_iq">>, <<"match_message">>,
+ <<"match_presence_in">>, <<"match_presence_out">>],
+ RItems = [_ | _]} ->
+ List = lists:map(fun mod_privacy:raw_to_item/1, RItems);
+ _ -> List = []
+ end,
+ NewList = Filter(List),
+ NewRItems = lists:map(fun mod_privacy:item_to_raw/1,
+ NewList),
+ mod_privacy:sql_set_privacy_list(ID, NewRItems),
+ {ok, Default, NewList}
+ end,
ejabberd_odbc:sql_transaction(LServer, F).
process_blocklist_unblock_all(LUser, LServer) ->
- Filter = fun(List) ->
- lists:filter(
- fun(#listitem{action = A}) ->
- A =/= deny
- end, List)
- end,
- case process_blocklist_unblock_all(
- LUser, LServer, Filter, gen_mod:db_type(LServer, mod_privacy)) of
- {atomic, ok} ->
- {result, []};
- {atomic, {ok, Default, List}} ->
- UserList = make_userlist(Default, List),
- broadcast_list_update(LUser, LServer, Default, UserList),
- broadcast_blocklist_event(LUser, LServer, unblock_all),
- {result, [], UserList};
- _ ->
- {error, ?ERR_INTERNAL_SERVER_ERROR}
+ Filter = fun (List) ->
+ lists:filter(fun (#listitem{action = A}) -> A =/= deny
+ end,
+ List)
+ end,
+ case process_blocklist_unblock_all(LUser, LServer,
+ Filter,
+ gen_mod:db_type(LServer, mod_privacy))
+ of
+ {atomic, ok} -> {result, []};
+ {atomic, {ok, Default, List}} ->
+ UserList = make_userlist(Default, List),
+ broadcast_list_update(LUser, LServer, Default,
+ UserList),
+ broadcast_blocklist_event(LUser, LServer, unblock_all),
+ {result, [], UserList};
+ _ -> {error, ?ERR_INTERNAL_SERVER_ERROR}
end.
-process_blocklist_unblock_all(LUser, LServer, Filter, mnesia) ->
- F =
- fun() ->
+process_blocklist_unblock_all(LUser, LServer, Filter,
+ mnesia) ->
+ F = fun () ->
case mnesia:read({privacy, {LUser, LServer}}) of
- [] ->
- % No lists, nothing to unblock
- ok;
- [#privacy{default = Default,
- lists = Lists} = P] ->
- case lists:keysearch(Default, 1, Lists) of
- {value, {_, List}} ->
- % Default list, remove all deny items
- NewList = Filter(List),
- NewLists1 = lists:keydelete(Default, 1, Lists),
- NewLists = [{Default, NewList} | NewLists1],
- mnesia:write(P#privacy{lists = NewLists}),
-
- {ok, Default, NewList};
- false ->
- % No default list, nothing to unblock
- ok
- end
+ [] ->
+ % No lists, nothing to unblock
+ ok;
+ [#privacy{default = Default, lists = Lists} = P] ->
+ case lists:keysearch(Default, 1, Lists) of
+ {value, {_, List}} ->
+ NewList = Filter(List),
+ NewLists1 = lists:keydelete(Default, 1, Lists),
+ NewLists = [{Default, NewList} | NewLists1],
+ mnesia:write(P#privacy{lists = NewLists}),
+ {ok, Default, NewList};
+ false ->
+ % No default list, nothing to unblock
+ ok
+ end
end
end,
mnesia:transaction(F);
-process_blocklist_unblock_all(LUser, LServer, Filter, odbc) ->
- F = fun() ->
- case mod_privacy:sql_get_default_privacy_list_t(LUser) of
- {selected, ["name"], []} ->
- ok;
- {selected, ["name"], [{Default}]} ->
- {selected, ["id"], [{ID}]} =
- mod_privacy:sql_get_privacy_list_id_t(
- LUser, Default),
- case mod_privacy:sql_get_privacy_list_data_by_id_t(ID) of
- {selected,
- ["t", "value", "action", "ord",
- "match_all", "match_iq", "match_message",
- "match_presence_in",
- "match_presence_out"],
- RItems = [_|_]} ->
- List = lists:map(
- fun mod_privacy:raw_to_item/1,
- RItems),
- NewList = Filter(List),
- NewRItems = lists:map(
- fun mod_privacy:item_to_raw/1,
- NewList),
- mod_privacy:sql_set_privacy_list(
- ID, NewRItems),
- {ok, Default, NewList};
- _ ->
- ok
- end;
- _ ->
- ok
- end
- end,
+process_blocklist_unblock_all(LUser, LServer, Filter,
+ odbc) ->
+ F = fun () ->
+ case mod_privacy:sql_get_default_privacy_list_t(LUser)
+ of
+ {selected, [<<"name">>], []} -> ok;
+ {selected, [<<"name">>], [[Default]]} ->
+ {selected, [<<"id">>], [[ID]]} =
+ mod_privacy:sql_get_privacy_list_id_t(LUser, Default),
+ case mod_privacy:sql_get_privacy_list_data_by_id_t(ID)
+ of
+ {selected,
+ [<<"t">>, <<"value">>, <<"action">>, <<"ord">>,
+ <<"match_all">>, <<"match_iq">>, <<"match_message">>,
+ <<"match_presence_in">>, <<"match_presence_out">>],
+ RItems = [_ | _]} ->
+ List = lists:map(fun mod_privacy:raw_to_item/1,
+ RItems),
+ NewList = Filter(List),
+ NewRItems = lists:map(fun mod_privacy:item_to_raw/1,
+ NewList),
+ mod_privacy:sql_set_privacy_list(ID, NewRItems),
+ {ok, Default, NewList};
+ _ -> ok
+ end;
+ _ -> ok
+ end
+ end,
ejabberd_odbc:sql_transaction(LServer, F).
process_blocklist_unblock(LUser, LServer, JIDs) ->
- Filter = fun(List) ->
- lists:filter(
- fun(#listitem{action = deny,
- type = jid,
- value = JID}) ->
- not(lists:member(JID, JIDs));
- (_) ->
- true
- end, List)
- end,
+ Filter = fun (List) ->
+ lists:filter(fun (#listitem{action = deny, type = jid,
+ value = JID}) ->
+ not lists:member(JID, JIDs);
+ (_) -> true
+ end,
+ List)
+ end,
case process_blocklist_unblock(LUser, LServer, Filter,
- gen_mod:db_type(LServer, mod_privacy)) of
- {atomic, ok} ->
- {result, []};
- {atomic, {ok, Default, List}} ->
- UserList = make_userlist(Default, List),
- broadcast_list_update(LUser, LServer, Default, UserList),
- broadcast_blocklist_event(LUser, LServer, {unblock, JIDs}),
- {result, [], UserList};
- _ ->
- {error, ?ERR_INTERNAL_SERVER_ERROR}
+ gen_mod:db_type(LServer, mod_privacy))
+ of
+ {atomic, ok} -> {result, []};
+ {atomic, {ok, Default, List}} ->
+ UserList = make_userlist(Default, List),
+ broadcast_list_update(LUser, LServer, Default,
+ UserList),
+ broadcast_blocklist_event(LUser, LServer,
+ {unblock, JIDs}),
+ {result, [], UserList};
+ _ -> {error, ?ERR_INTERNAL_SERVER_ERROR}
end.
-process_blocklist_unblock(LUser, LServer, Filter, mnesia) ->
- F =
- fun() ->
+process_blocklist_unblock(LUser, LServer, Filter,
+ mnesia) ->
+ F = fun () ->
case mnesia:read({privacy, {LUser, LServer}}) of
- [] ->
- % No lists, nothing to unblock
- ok;
- [#privacy{default = Default,
- lists = Lists} = P] ->
- case lists:keysearch(Default, 1, Lists) of
- {value, {_, List}} ->
- % Default list, remove matching deny items
- NewList = Filter(List),
- NewLists1 = lists:keydelete(Default, 1, Lists),
- NewLists = [{Default, NewList} | NewLists1],
- mnesia:write(P#privacy{lists = NewLists}),
-
- {ok, Default, NewList};
- false ->
- % No default list, nothing to unblock
- ok
- end
+ [] ->
+ % No lists, nothing to unblock
+ ok;
+ [#privacy{default = Default, lists = Lists} = P] ->
+ case lists:keysearch(Default, 1, Lists) of
+ {value, {_, List}} ->
+ NewList = Filter(List),
+ NewLists1 = lists:keydelete(Default, 1, Lists),
+ NewLists = [{Default, NewList} | NewLists1],
+ mnesia:write(P#privacy{lists = NewLists}),
+ {ok, Default, NewList};
+ false ->
+ % No default list, nothing to unblock
+ ok
+ end
end
end,
mnesia:transaction(F);
-process_blocklist_unblock(LUser, LServer, Filter, odbc) ->
- F = fun() ->
- case mod_privacy:sql_get_default_privacy_list_t(LUser) of
- {selected, ["name"], []} ->
- ok;
- {selected, ["name"], [{Default}]} ->
- {selected, ["id"], [{ID}]} =
- mod_privacy:sql_get_privacy_list_id_t(
- LUser, Default),
- case mod_privacy:sql_get_privacy_list_data_by_id_t(ID) of
- {selected,
- ["t", "value", "action", "ord",
- "match_all", "match_iq", "match_message",
- "match_presence_in",
- "match_presence_out"],
- RItems = [_|_]} ->
- List = lists:map(
- fun mod_privacy:raw_to_item/1,
- RItems),
- NewList = Filter(List),
- NewRItems = lists:map(
- fun mod_privacy:item_to_raw/1,
- NewList),
- mod_privacy:sql_set_privacy_list(
- ID, NewRItems),
- {ok, Default, NewList};
- _ ->
- ok
- end;
- _ ->
- ok
- end
- end,
+process_blocklist_unblock(LUser, LServer, Filter,
+ odbc) ->
+ F = fun () ->
+ case mod_privacy:sql_get_default_privacy_list_t(LUser)
+ of
+ {selected, [<<"name">>], []} -> ok;
+ {selected, [<<"name">>], [[Default]]} ->
+ {selected, [<<"id">>], [[ID]]} =
+ mod_privacy:sql_get_privacy_list_id_t(LUser, Default),
+ case mod_privacy:sql_get_privacy_list_data_by_id_t(ID)
+ of
+ {selected,
+ [<<"t">>, <<"value">>, <<"action">>, <<"ord">>,
+ <<"match_all">>, <<"match_iq">>, <<"match_message">>,
+ <<"match_presence_in">>, <<"match_presence_out">>],
+ RItems = [_ | _]} ->
+ List = lists:map(fun mod_privacy:raw_to_item/1,
+ RItems),
+ NewList = Filter(List),
+ NewRItems = lists:map(fun mod_privacy:item_to_raw/1,
+ NewList),
+ mod_privacy:sql_set_privacy_list(ID, NewRItems),
+ {ok, Default, NewList};
+ _ -> ok
+ end;
+ _ -> ok
+ end
+ end,
ejabberd_odbc:sql_transaction(LServer, F).
make_userlist(Name, List) ->
@@ -406,66 +366,65 @@ make_userlist(Name, List) ->
#userlist{name = Name, list = List, needdb = NeedDb}.
broadcast_list_update(LUser, LServer, Name, UserList) ->
- ejabberd_router:route(
- jlib:make_jid(LUser, LServer, ""),
- jlib:make_jid(LUser, LServer, ""),
- {xmlelement, "broadcast", [],
- [{privacy_list, UserList, Name}]}).
+ ejabberd_sm:route(jlib:make_jid(LUser, LServer,
+ <<"">>),
+ jlib:make_jid(LUser, LServer, <<"">>),
+ {broadcast, {privacy_list, UserList, Name}}).
broadcast_blocklist_event(LUser, LServer, Event) ->
- JID = jlib:make_jid(LUser, LServer, ""),
- ejabberd_router:route(
- JID, JID,
- {xmlelement, "broadcast", [],
- [{blocking, Event}]}).
+ JID = jlib:make_jid(LUser, LServer, <<"">>),
+ ejabberd_sm:route(JID, JID,
+ {broadcast, {blocking, Event}}).
process_blocklist_get(LUser, LServer) ->
- case process_blocklist_get(
- LUser, LServer, gen_mod:db_type(LServer, mod_privacy)) of
- error ->
- {error, ?ERR_INTERNAL_SERVER_ERROR};
- List ->
- JIDs = list_to_blocklist_jids(List, []),
- Items = lists:map(
- fun(JID) ->
- ?DEBUG("JID: ~p",[JID]),
- {xmlelement, "item",
- [{"jid", jlib:jid_to_string(JID)}], []}
- end, JIDs),
- {result,
- [{xmlelement, "blocklist", [{"xmlns", ?NS_BLOCKING}],
- Items}]}
+ case process_blocklist_get(LUser, LServer,
+ gen_mod:db_type(LServer, mod_privacy))
+ of
+ error -> {error, ?ERR_INTERNAL_SERVER_ERROR};
+ List ->
+ JIDs = list_to_blocklist_jids(List, []),
+ Items = lists:map(fun (JID) ->
+ ?DEBUG("JID: ~p", [JID]),
+ #xmlel{name = <<"item">>,
+ attrs =
+ [{<<"jid">>,
+ jlib:jid_to_string(JID)}],
+ children = []}
+ end,
+ JIDs),
+ {result,
+ [#xmlel{name = <<"blocklist">>,
+ attrs = [{<<"xmlns">>, ?NS_BLOCKING}],
+ children = Items}]}
end.
process_blocklist_get(LUser, LServer, mnesia) ->
- case catch mnesia:dirty_read(privacy, {LUser, LServer}) of
- {'EXIT', _Reason} ->
- error;
- [] ->
- [];
- [#privacy{default = Default, lists = Lists}] ->
- case lists:keysearch(Default, 1, Lists) of
- {value, {_, List}} ->
- List;
- _ ->
- []
- end
+ case catch mnesia:dirty_read(privacy, {LUser, LServer})
+ of
+ {'EXIT', _Reason} -> error;
+ [] -> [];
+ [#privacy{default = Default, lists = Lists}] ->
+ case lists:keysearch(Default, 1, Lists) of
+ {value, {_, List}} -> List;
+ _ -> []
+ end
end;
process_blocklist_get(LUser, LServer, odbc) ->
- case catch mod_privacy:sql_get_default_privacy_list(LUser, LServer) of
- {selected, ["name"], []} ->
- [];
- {selected, ["name"], [{Default}]} ->
- case catch mod_privacy:sql_get_privacy_list_data(
- LUser, LServer, Default) of
- {selected, ["t", "value", "action", "ord", "match_all",
- "match_iq", "match_message",
- "match_presence_in", "match_presence_out"],
- RItems} ->
- lists:map(fun mod_privacy:raw_to_item/1, RItems);
- {'EXIT', _} ->
- error
- end;
- {'EXIT', _} ->
- error
+ case catch
+ mod_privacy:sql_get_default_privacy_list(LUser, LServer)
+ of
+ {selected, [<<"name">>], []} -> [];
+ {selected, [<<"name">>], [[Default]]} ->
+ case catch mod_privacy:sql_get_privacy_list_data(LUser,
+ LServer, Default)
+ of
+ {selected,
+ [<<"t">>, <<"value">>, <<"action">>, <<"ord">>,
+ <<"match_all">>, <<"match_iq">>, <<"match_message">>,
+ <<"match_presence_in">>, <<"match_presence_out">>],
+ RItems} ->
+ lists:map(fun mod_privacy:raw_to_item/1, RItems);
+ {'EXIT', _} -> error
+ end;
+ {'EXIT', _} -> error
end.
diff --git a/src/mod_caps.erl b/src/mod_caps.erl
index 6641e956f..4bec5f29e 100644
--- a/src/mod_caps.erl
+++ b/src/mod_caps.erl
@@ -27,30 +27,23 @@
%%%----------------------------------------------------------------------
-module(mod_caps).
+
-author('henoch@dtek.chalmers.se').
-behaviour(gen_server).
+
-behaviour(gen_mod).
--export([read_caps/1,
- caps_stream_features/2,
- disco_features/5,
- disco_identity/5,
- disco_info/5,
+-export([read_caps/1, caps_stream_features/2,
+ disco_features/5, disco_identity/5, disco_info/5,
get_features/1]).
%% gen_mod callbacks
--export([start/2, start_link/2,
- stop/1]).
+-export([start/2, start_link/2, stop/1]).
%% gen_server callbacks
--export([init/1,
- handle_info/2,
- handle_call/3,
- handle_cast/2,
- terminate/2,
- code_change/3
- ]).
+-export([init/1, handle_info/2, handle_call/3,
+ handle_cast/2, terminate/2, code_change/3]).
%% hook handlers
-export([user_send_packet/3,
@@ -59,32 +52,45 @@
c2s_broadcast_recipients/5]).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
-define(PROCNAME, ejabberd_mod_caps).
--define(BAD_HASH_LIFETIME, 600). %% in seconds
--record(caps, {node, version, hash, exts}).
--record(caps_features, {node_pair, features = []}).
+-define(BAD_HASH_LIFETIME, 600).
+
+-record(caps,
+{
+ node = <<"">> :: binary(),
+ version = <<"">> :: binary(),
+ hash = <<"">> :: binary(),
+ exts = [] :: [binary()]
+}).
--record(state, {host}).
+-type caps() :: #caps{}.
+
+-export_type([caps/0]).
+
+-record(caps_features,
+{
+ node_pair = {<<"">>, <<"">>} :: {binary(), binary()},
+ features = [] :: [binary()] | pos_integer()
+}).
+
+-record(state, {host = <<"">> :: binary()}).
%%====================================================================
%% API
%%====================================================================
start_link(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
- gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
+ gen_server:start_link({local, Proc}, ?MODULE,
+ [Host, Opts], []).
start(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
- ChildSpec =
- {Proc,
- {?MODULE, start_link, [Host, Opts]},
- transient,
- 1000,
- worker,
- [?MODULE]},
+ ChildSpec = {Proc, {?MODULE, start_link, [Host, Opts]},
+ transient, 1000, worker, [?MODULE]},
supervisor:start_child(ejabberd_sup, ChildSpec).
stop(Host) ->
@@ -96,164 +102,170 @@ stop(Host) ->
%% get_features returns a list of features implied by the given caps
%% record (as extracted by read_caps) or 'unknown' if features are
%% not completely collected at the moment.
-get_features(nothing) ->
- [];
+get_features(nothing) -> [];
get_features(#caps{node = Node, version = Version, exts = Exts}) ->
SubNodes = [Version | Exts],
- lists:foldl(
- fun(SubNode, Acc) ->
- BinaryNode = node_to_binary(Node, SubNode),
- case cache_tab:lookup(caps_features, BinaryNode,
- caps_read_fun(BinaryNode)) of
- {ok, Features} when is_list(Features) ->
- binary_to_features(Features) ++ Acc;
- _ ->
- Acc
- end
- end, [], SubNodes).
-
%% read_caps takes a list of XML elements (the child elements of a
%% <presence/> stanza) and returns an opaque value representing the
%% Entity Capabilities contained therein, or the atom nothing if no
%% capabilities are advertised.
-read_caps(Els) ->
- read_caps(Els, nothing).
-
-read_caps([{xmlelement, "c", Attrs, _Els} | Tail], Result) ->
- case xml:get_attr_s("xmlns", Attrs) of
- ?NS_CAPS ->
- Node = xml:get_attr_s("node", Attrs),
- Version = xml:get_attr_s("ver", Attrs),
- Hash = xml:get_attr_s("hash", Attrs),
- Exts = string:tokens(xml:get_attr_s("ext", Attrs), " "),
- read_caps(Tail, #caps{node = Node, hash = Hash,
- version = Version, exts = Exts});
- _ ->
- read_caps(Tail, Result)
+ lists:foldl(fun (SubNode, Acc) ->
+ NodePair = {Node, SubNode},
+ case cache_tab:lookup(caps_features, NodePair,
+ caps_read_fun(NodePair))
+ of
+ {ok, Features} when is_list(Features) ->
+ Features ++ Acc;
+ _ -> Acc
+ end
+ end,
+ [], SubNodes).
+
+read_caps(Els) -> read_caps(Els, nothing).
+
+read_caps([#xmlel{name = <<"c">>, attrs = Attrs}
+ | Tail],
+ Result) ->
+ case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ ?NS_CAPS ->
+ Node = xml:get_attr_s(<<"node">>, Attrs),
+ Version = xml:get_attr_s(<<"ver">>, Attrs),
+ Hash = xml:get_attr_s(<<"hash">>, Attrs),
+ Exts = str:tokens(xml:get_attr_s(<<"ext">>, Attrs),
+ <<" ">>),
+ read_caps(Tail,
+ #caps{node = Node, hash = Hash, version = Version,
+ exts = Exts});
+ _ -> read_caps(Tail, Result)
end;
-read_caps([{xmlelement, "x", Attrs, _Els} | Tail], Result) ->
- case xml:get_attr_s("xmlns", Attrs) of
- ?NS_MUC_USER ->
- nothing;
- _ ->
- read_caps(Tail, Result)
+read_caps([#xmlel{name = <<"x">>, attrs = Attrs}
+ | Tail],
+ Result) ->
+ case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ ?NS_MUC_USER -> nothing;
+ _ -> read_caps(Tail, Result)
end;
read_caps([_ | Tail], Result) ->
read_caps(Tail, Result);
-read_caps([], Result) ->
- Result.
+read_caps([], Result) -> Result.
%%====================================================================
%% Hooks
%%====================================================================
-user_send_packet(#jid{luser = User, lserver = Server} = From,
- #jid{luser = User, lserver = Server, lresource = ""},
- {xmlelement, "presence", Attrs, Els}) ->
- Type = xml:get_attr_s("type", Attrs),
- if Type == ""; Type == "available" ->
- case read_caps(Els) of
- nothing ->
- ok;
- #caps{version = Version, exts = Exts} = Caps ->
- feature_request(Server, From, Caps, [Version | Exts])
- end;
- true ->
- ok
+user_send_packet(
+ #jid{luser = User, lserver = Server} = From,
+ #jid{luser = User, lserver = Server, lresource = <<"">>},
+ #xmlel{name = <<"presence">>, attrs = Attrs, children = Els}) ->
+ Type = xml:get_attr_s(<<"type">>, Attrs),
+ if Type == <<"">>; Type == <<"available">> ->
+ case read_caps(Els) of
+ nothing -> ok;
+ #caps{version = Version, exts = Exts} = Caps ->
+ feature_request(Server, From, Caps, [Version | Exts])
+ end;
+ true -> ok
end;
-user_send_packet(_From, _To, _Packet) ->
- ok.
+user_send_packet(_From, _To, _Packet) -> ok.
user_receive_packet(#jid{lserver = Server}, From, _To,
- {xmlelement, "presence", Attrs, Els}) ->
- Type = xml:get_attr_s("type", Attrs),
- if Type == ""; Type == "available" ->
- case read_caps(Els) of
- nothing ->
- ok;
- #caps{version = Version, exts = Exts} = Caps ->
- feature_request(Server, From, Caps, [Version | Exts])
- end;
- true ->
- ok
+ #xmlel{name = <<"presence">>, attrs = Attrs,
+ children = Els}) ->
+ Type = xml:get_attr_s(<<"type">>, Attrs),
+ IsRemote = not lists:member(From#jid.lserver, ?MYHOSTS),
+ if IsRemote and
+ ((Type == <<"">>) or (Type == <<"available">>)) ->
+ case read_caps(Els) of
+ nothing -> ok;
+ #caps{version = Version, exts = Exts} = Caps ->
+ feature_request(Server, From, Caps, [Version | Exts])
+ end;
+ true -> ok
end;
user_receive_packet(_JID, _From, _To, _Packet) ->
ok.
caps_stream_features(Acc, MyHost) ->
case make_my_disco_hash(MyHost) of
- "" ->
- Acc;
- Hash ->
- [{xmlelement, "c", [{"xmlns", ?NS_CAPS},
- {"hash", "sha-1"},
- {"node", ?EJABBERD_URI},
- {"ver", Hash}], []} | Acc]
+ <<"">> -> Acc;
+ Hash ->
+ [#xmlel{name = <<"c">>,
+ attrs =
+ [{<<"xmlns">>, ?NS_CAPS}, {<<"hash">>, <<"sha-1">>},
+ {<<"node">>, ?EJABBERD_URI}, {<<"ver">>, Hash}],
+ children = []}
+ | Acc]
end.
-disco_features(_Acc, From, To, ?EJABBERD_URI ++ "#" ++ [_|_], Lang) ->
- ejabberd_hooks:run_fold(disco_local_features,
- To#jid.lserver,
- empty,
- [From, To, "", Lang]);
-disco_features(Acc, _From, _To, _Node, _Lang) ->
- Acc.
+disco_features(Acc, From, To, Node, Lang) ->
+ case is_valid_node(Node) of
+ true ->
+ ejabberd_hooks:run_fold(disco_local_features,
+ To#jid.lserver, empty,
+ [From, To, <<"">>, Lang]);
+ false ->
+ Acc
+ end.
-disco_identity(_Acc, From, To, ?EJABBERD_URI ++ "#" ++ [_|_], Lang) ->
- ejabberd_hooks:run_fold(disco_local_identity,
- To#jid.lserver,
- [],
- [From, To, "", Lang]);
-disco_identity(Acc, _From, _To, _Node, _Lang) ->
- Acc.
+disco_identity(Acc, From, To, Node, Lang) ->
+ case is_valid_node(Node) of
+ true ->
+ ejabberd_hooks:run_fold(disco_local_identity,
+ To#jid.lserver, [],
+ [From, To, <<"">>, Lang]);
+ false ->
+ Acc
+ end.
-disco_info(_Acc, Host, Module, ?EJABBERD_URI ++ "#" ++ [_|_], Lang) ->
- ejabberd_hooks:run_fold(disco_info,
- Host,
- [],
- [Host, Module, "", Lang]);
-disco_info(Acc, _Host, _Module, _Node, _Lang) ->
- Acc.
+disco_info(Acc, Host, Module, Node, Lang) ->
+ case is_valid_node(Node) of
+ true ->
+ ejabberd_hooks:run_fold(disco_info, Host, [],
+ [Host, Module, <<"">>, Lang]);
+ false ->
+ Acc
+ end.
-c2s_presence_in(C2SState, {From, To, {_, _, Attrs, Els}}) ->
- Type = xml:get_attr_s("type", Attrs),
- Subscription = ejabberd_c2s:get_subscription(From, C2SState),
- Insert = ((Type == "") or (Type == "available"))
- and ((Subscription == both) or (Subscription == to)),
- Delete = (Type == "unavailable") or (Type == "error") or (Type == "invisible"),
+c2s_presence_in(C2SState,
+ {From, To, {_, _, Attrs, Els}}) ->
+ Type = xml:get_attr_s(<<"type">>, Attrs),
+ Subscription = ejabberd_c2s:get_subscription(From,
+ C2SState),
+ Insert = ((Type == <<"">>) or (Type == <<"available">>))
+ and ((Subscription == both) or (Subscription == to)),
+ Delete = (Type == <<"unavailable">>) or
+ (Type == <<"error">>),
if Insert or Delete ->
- LFrom = jlib:jid_tolower(From),
- Rs = case ejabberd_c2s:get_aux_field(caps_resources, C2SState) of
- {ok, Rs1} ->
- Rs1;
- error ->
- gb_trees:empty()
- end,
- Caps = read_caps(Els),
- {CapsUpdated, NewRs} =
- case Caps of
- nothing when Insert == true ->
- {false, Rs};
- _ when Insert == true ->
- case gb_trees:lookup(LFrom, Rs) of
- {value, Caps} ->
- {false, Rs};
- none ->
- {true, gb_trees:insert(LFrom, Caps, Rs)};
- _ ->
- {true, gb_trees:update(LFrom, Caps, Rs)}
- end;
- _ ->
- {false, gb_trees:delete_any(LFrom, Rs)}
+ LFrom = jlib:jid_tolower(From),
+ Rs = case ejabberd_c2s:get_aux_field(caps_resources,
+ C2SState)
+ of
+ {ok, Rs1} -> Rs1;
+ error -> gb_trees:empty()
end,
- if CapsUpdated ->
- ejabberd_hooks:run(caps_update, To#jid.lserver,
- [From, To, get_features(Caps)]);
- true ->
- ok
- end,
- ejabberd_c2s:set_aux_field(caps_resources, NewRs, C2SState);
- true ->
- C2SState
+ Caps = read_caps(Els),
+ {CapsUpdated, NewRs} = case Caps of
+ nothing when Insert == true -> {false, Rs};
+ _ when Insert == true ->
+ case gb_trees:lookup(LFrom, Rs) of
+ {value, Caps} -> {false, Rs};
+ none ->
+ {true,
+ gb_trees:insert(LFrom, Caps,
+ Rs)};
+ _ ->
+ {true,
+ gb_trees:update(LFrom, Caps, Rs)}
+ end;
+ _ -> {false, gb_trees:delete_any(LFrom, Rs)}
+ end,
+ if CapsUpdated ->
+ ejabberd_hooks:run(caps_update, To#jid.lserver,
+ [From, To, get_features(Caps)]);
+ true -> ok
+ end,
+ ejabberd_c2s:set_aux_field(caps_resources, NewRs,
+ C2SState);
+ true -> C2SState
end.
c2s_broadcast_recipients(InAcc, C2SState, {pep_message, Feature},
@@ -292,8 +304,8 @@ init([Host, Opts]) ->
{local_content, true},
{attributes, record_info(fields, caps_features)}]),
mnesia:add_table_copy(caps_features, node(), disc_only_copies),
- MaxSize = gen_mod:get_opt(cache_size, Opts, 1000),
- LifeTime = gen_mod:get_opt(cache_life_time, Opts, timer:hours(24) div 1000),
+ MaxSize = gen_mod:get_opt(cache_size, Opts, fun(CS) when is_integer(CS) -> CS end, 1000),
+ LifeTime = gen_mod:get_opt(cache_life_time, Opts, fun(CL) when is_integer(CL) -> CL end, timer:hours(24) div 1000),
cache_tab:new(caps_features, [{max_size, MaxSize}, {life_time, LifeTime}]),
ejabberd_hooks:add(c2s_presence_in, Host,
?MODULE, c2s_presence_in, 75),
@@ -320,20 +332,18 @@ handle_call(stop, _From, State) ->
handle_call(_Req, _From, State) ->
{reply, {error, badarg}, State}.
-handle_cast(_Msg, State) ->
- {noreply, State}.
+handle_cast(_Msg, State) -> {noreply, State}.
-handle_info(_Info, State) ->
- {noreply, State}.
+handle_info(_Info, State) -> {noreply, State}.
terminate(_Reason, State) ->
Host = State#state.host,
- ejabberd_hooks:delete(c2s_presence_in, Host,
- ?MODULE, c2s_presence_in, 75),
+ ejabberd_hooks:delete(c2s_presence_in, Host, ?MODULE,
+ c2s_presence_in, 75),
ejabberd_hooks:delete(c2s_broadcast_recipients, Host,
?MODULE, c2s_broadcast_recipients, 75),
- ejabberd_hooks:delete(user_send_packet, Host,
- ?MODULE, user_send_packet, 75),
+ ejabberd_hooks:delete(user_send_packet, Host, ?MODULE,
+ user_send_packet, 75),
ejabberd_hooks:delete(user_receive_packet, Host,
?MODULE, user_receive_packet, 75),
ejabberd_hooks:delete(c2s_stream_features, Host,
@@ -344,267 +354,251 @@ terminate(_Reason, State) ->
?MODULE, disco_features, 75),
ejabberd_hooks:delete(disco_local_identity, Host,
?MODULE, disco_identity, 75),
- ejabberd_hooks:delete(disco_info, Host,
- ?MODULE, disco_info, 75),
+ ejabberd_hooks:delete(disco_info, Host, ?MODULE,
+ disco_info, 75),
ok.
-code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
+code_change(_OldVsn, State, _Extra) -> {ok, State}.
%%====================================================================
%% Aux functions
%%====================================================================
-feature_request(Host, From, Caps, [SubNode | Tail] = SubNodes) ->
+feature_request(Host, From, Caps,
+ [SubNode | Tail] = SubNodes) ->
Node = Caps#caps.node,
- BinaryNode = node_to_binary(Node, SubNode),
- case cache_tab:lookup(caps_features, BinaryNode,
- caps_read_fun(BinaryNode)) of
- {ok, Fs} when is_list(Fs) ->
- feature_request(Host, From, Caps, Tail);
- Other ->
- NeedRequest = case Other of
- {ok, TS} ->
- now_ts() >= TS + ?BAD_HASH_LIFETIME;
- _ ->
- true
- end,
- if NeedRequest ->
- IQ = #iq{type = get,
- xmlns = ?NS_DISCO_INFO,
- sub_el = [{xmlelement, "query",
- [{"xmlns", ?NS_DISCO_INFO},
- {"node", Node ++ "#" ++ SubNode}],
- []}]},
- %% We cache current timestamp in order to avoid
- %% caps requests flood
- cache_tab:insert(caps_features, BinaryNode, now_ts(),
- caps_write_fun(BinaryNode, now_ts())),
- F = fun(IQReply) ->
- feature_response(
- IQReply, Host, From, Caps, SubNodes)
+ NodePair = {Node, SubNode},
+ case cache_tab:lookup(caps_features, NodePair,
+ caps_read_fun(NodePair))
+ of
+ {ok, Fs} when is_list(Fs) ->
+ feature_request(Host, From, Caps, Tail);
+ Other ->
+ NeedRequest = case Other of
+ {ok, TS} -> now_ts() >= TS + (?BAD_HASH_LIFETIME);
+ _ -> true
end,
- ejabberd_local:route_iq(
- jlib:make_jid("", Host, ""), From, IQ, F);
- true ->
- feature_request(Host, From, Caps, Tail)
- end
+ if NeedRequest ->
+ IQ = #iq{type = get, xmlns = ?NS_DISCO_INFO,
+ sub_el =
+ [#xmlel{name = <<"query">>,
+ attrs =
+ [{<<"xmlns">>, ?NS_DISCO_INFO},
+ {<<"node">>,
+ <<Node/binary, "#",
+ SubNode/binary>>}],
+ children = []}]},
+ cache_tab:insert(caps_features, NodePair, now_ts(),
+ caps_write_fun(NodePair, now_ts())),
+ F = fun (IQReply) ->
+ feature_response(IQReply, Host, From, Caps,
+ SubNodes)
+ end,
+ ejabberd_local:route_iq(jlib:make_jid(<<"">>, Host,
+ <<"">>),
+ From, IQ, F);
+ true -> feature_request(Host, From, Caps, Tail)
+ end
end;
-feature_request(_Host, _From, _Caps, []) ->
- ok.
+feature_request(_Host, _From, _Caps, []) -> ok.
feature_response(#iq{type = result,
- sub_el = [{xmlelement, _, _, Els}]},
+ sub_el = [#xmlel{children = Els}]},
Host, From, Caps, [SubNode | SubNodes]) ->
- BinaryNode = node_to_binary(Caps#caps.node, SubNode),
+ NodePair = {Caps#caps.node, SubNode},
case check_hash(Caps, Els) of
- true ->
- Features = lists:flatmap(
- fun({xmlelement, "feature", FAttrs, _}) ->
- [xml:get_attr_s("var", FAttrs)];
- (_) ->
- []
- end, Els),
- BinaryFeatures = features_to_binary(Features),
- cache_tab:insert(
- caps_features, BinaryNode, BinaryFeatures,
- caps_write_fun(BinaryNode, BinaryFeatures));
- false ->
- ok
+ true ->
+ Features = lists:flatmap(fun (#xmlel{name =
+ <<"feature">>,
+ attrs = FAttrs}) ->
+ [xml:get_attr_s(<<"var">>, FAttrs)];
+ (_) -> []
+ end,
+ Els),
+ cache_tab:insert(caps_features, NodePair,
+ Features,
+ caps_write_fun(NodePair, Features));
+ false -> ok
end,
feature_request(Host, From, Caps, SubNodes);
-feature_response(_IQResult, Host, From, Caps, [_SubNode | SubNodes]) ->
- %% We got type=error or invalid type=result stanza or timeout.
+feature_response(_IQResult, Host, From, Caps,
+ [_SubNode | SubNodes]) ->
feature_request(Host, From, Caps, SubNodes).
-node_to_binary(Node, SubNode) ->
- {list_to_binary(Node), list_to_binary(SubNode)}.
-
-features_to_binary(L) -> [list_to_binary(I) || I <- L].
-binary_to_features(L) -> [binary_to_list(I) || I <- L].
-
caps_read_fun(Node) ->
- fun() ->
+ fun () ->
case mnesia:dirty_read({caps_features, Node}) of
- [#caps_features{features = Features}] ->
- {ok, Features};
- _ ->
- error
+ [#caps_features{features = Features}] -> {ok, Features};
+ _ -> error
end
end.
caps_write_fun(Node, Features) ->
- fun() ->
- mnesia:dirty_write(
- #caps_features{node_pair = Node,
- features = Features})
+ fun () ->
+ mnesia:dirty_write(#caps_features{node_pair = Node,
+ features = Features})
end.
make_my_disco_hash(Host) ->
- JID = jlib:make_jid("", Host, ""),
+ JID = jlib:make_jid(<<"">>, Host, <<"">>),
case {ejabberd_hooks:run_fold(disco_local_features,
- Host,
- empty,
- [JID, JID, "", ""]),
- ejabberd_hooks:run_fold(disco_local_identity,
- Host,
- [],
- [JID, JID, "", ""]),
- ejabberd_hooks:run_fold(disco_info,
- Host,
- [],
- [Host, undefined, "", ""])} of
- {{result, Features}, Identities, Info} ->
- Feats = lists:map(
- fun({{Feat, _Host}}) ->
- {xmlelement, "feature", [{"var", Feat}], []};
- (Feat) ->
- {xmlelement, "feature", [{"var", Feat}], []}
- end, Features),
- make_disco_hash(Identities ++ Info ++ Feats, sha1);
- _Err ->
- ""
+ Host, empty, [JID, JID, <<"">>, <<"">>]),
+ ejabberd_hooks:run_fold(disco_local_identity, Host, [],
+ [JID, JID, <<"">>, <<"">>]),
+ ejabberd_hooks:run_fold(disco_info, Host, [],
+ [Host, undefined, <<"">>, <<"">>])}
+ of
+ {{result, Features}, Identities, Info} ->
+ Feats = lists:map(fun ({{Feat, _Host}}) ->
+ #xmlel{name = <<"feature">>,
+ attrs = [{<<"var">>, Feat}],
+ children = []};
+ (Feat) ->
+ #xmlel{name = <<"feature">>,
+ attrs = [{<<"var">>, Feat}],
+ children = []}
+ end,
+ Features),
+ make_disco_hash(Identities ++ Info ++ Feats, sha1);
+ _Err -> <<"">>
end.
-ifdef(HAVE_MD2).
+
make_disco_hash(DiscoEls, Algo) ->
- Concat = [concat_identities(DiscoEls),
- concat_features(DiscoEls),
- concat_info(DiscoEls)],
- base64:encode_to_string(
- if Algo == md2 ->
- sha:md2(Concat);
- Algo == md5 ->
- crypto:md5(Concat);
- Algo == sha1 ->
- crypto:sha(Concat);
- Algo == sha224 ->
- sha:sha224(Concat);
- Algo == sha256 ->
- sha:sha256(Concat);
- Algo == sha384 ->
- sha:sha384(Concat);
- Algo == sha512 ->
- sha:sha512(Concat)
- end).
+ Concat = list_to_binary([concat_identities(DiscoEls),
+ concat_features(DiscoEls), concat_info(DiscoEls)]),
+ jlib:encode_base64(case Algo of
+ md2 -> sha:md2(Concat);
+ md5 -> crypto:md5(Concat);
+ sha1 -> crypto:sha(Concat);
+ sha224 -> sha:sha224(Concat);
+ sha256 -> sha:sha256(Concat);
+ sha384 -> sha:sha384(Concat);
+ sha512 -> sha:sha512(Concat)
+ end).
check_hash(Caps, Els) ->
case Caps#caps.hash of
- "md2" ->
- Caps#caps.version == make_disco_hash(Els, md2);
- "md5" ->
- Caps#caps.version == make_disco_hash(Els, md5);
- "sha-1" ->
- Caps#caps.version == make_disco_hash(Els, sha1);
- "sha-224" ->
- Caps#caps.version == make_disco_hash(Els, sha224);
- "sha-256" ->
- Caps#caps.version == make_disco_hash(Els, sha256);
- "sha-384" ->
- Caps#caps.version == make_disco_hash(Els, sha384);
- "sha-512" ->
- Caps#caps.version == make_disco_hash(Els, sha512);
- _ ->
- true
+ <<"md2">> ->
+ Caps#caps.version == make_disco_hash(Els, md2);
+ <<"md5">> ->
+ Caps#caps.version == make_disco_hash(Els, md5);
+ <<"sha-1">> ->
+ Caps#caps.version == make_disco_hash(Els, sha1);
+ <<"sha-224">> ->
+ Caps#caps.version == make_disco_hash(Els, sha224);
+ <<"sha-256">> ->
+ Caps#caps.version == make_disco_hash(Els, sha256);
+ <<"sha-384">> ->
+ Caps#caps.version == make_disco_hash(Els, sha384);
+ <<"sha-512">> ->
+ Caps#caps.version == make_disco_hash(Els, sha512);
+ _ -> true
end.
+
-else.
+
make_disco_hash(DiscoEls, Algo) ->
- Concat = [concat_identities(DiscoEls),
- concat_features(DiscoEls),
- concat_info(DiscoEls)],
- base64:encode_to_string(
- if Algo == md5 ->
- crypto:md5(Concat);
- Algo == sha1 ->
- crypto:sha(Concat);
- Algo == sha224 ->
- sha:sha224(Concat);
- Algo == sha256 ->
- sha:sha256(Concat);
- Algo == sha384 ->
- sha:sha384(Concat);
- Algo == sha512 ->
- sha:sha512(Concat)
- end).
+ Concat = list_to_binary([concat_identities(DiscoEls),
+ concat_features(DiscoEls), concat_info(DiscoEls)]),
+ jlib:encode_base64(case Algo of
+ md5 -> crypto:md5(Concat);
+ sha1 -> crypto:sha(Concat);
+ sha224 -> sha:sha224(Concat);
+ sha256 -> sha:sha256(Concat);
+ sha384 -> sha:sha384(Concat);
+ sha512 -> sha:sha512(Concat)
+ end).
check_hash(Caps, Els) ->
case Caps#caps.hash of
- "md5" ->
- Caps#caps.version == make_disco_hash(Els, md5);
- "sha-1" ->
- Caps#caps.version == make_disco_hash(Els, sha1);
- "sha-224" ->
- Caps#caps.version == make_disco_hash(Els, sha224);
- "sha-256" ->
- Caps#caps.version == make_disco_hash(Els, sha256);
- "sha-384" ->
- Caps#caps.version == make_disco_hash(Els, sha384);
- "sha-512" ->
- Caps#caps.version == make_disco_hash(Els, sha512);
- _ ->
- true
+ <<"md5">> ->
+ Caps#caps.version == make_disco_hash(Els, md5);
+ <<"sha-1">> ->
+ Caps#caps.version == make_disco_hash(Els, sha1);
+ <<"sha-224">> ->
+ Caps#caps.version == make_disco_hash(Els, sha224);
+ <<"sha-256">> ->
+ Caps#caps.version == make_disco_hash(Els, sha256);
+ <<"sha-384">> ->
+ Caps#caps.version == make_disco_hash(Els, sha384);
+ <<"sha-512">> ->
+ Caps#caps.version == make_disco_hash(Els, sha512);
+ _ -> true
end.
+
-endif.
concat_features(Els) ->
- lists:usort(
- lists:flatmap(
- fun({xmlelement, "feature", Attrs, _}) ->
- [[xml:get_attr_s("var", Attrs), $<]];
- (_) ->
- []
- end, Els)).
+ lists:usort(lists:flatmap(fun (#xmlel{name =
+ <<"feature">>,
+ attrs = Attrs}) ->
+ [[xml:get_attr_s(<<"var">>, Attrs), $<]];
+ (_) -> []
+ end,
+ Els)).
concat_identities(Els) ->
- lists:sort(
- lists:flatmap(
- fun({xmlelement, "identity", Attrs, _}) ->
- [[xml:get_attr_s("category", Attrs), $/,
- xml:get_attr_s("type", Attrs), $/,
- xml:get_attr_s("xml:lang", Attrs), $/,
- xml:get_attr_s("name", Attrs), $<]];
- (_) ->
- []
- end, Els)).
+ lists:sort(lists:flatmap(fun (#xmlel{name =
+ <<"identity">>,
+ attrs = Attrs}) ->
+ [[xml:get_attr_s(<<"category">>, Attrs),
+ $/, xml:get_attr_s(<<"type">>, Attrs),
+ $/,
+ xml:get_attr_s(<<"xml:lang">>, Attrs),
+ $/, xml:get_attr_s(<<"name">>, Attrs),
+ $<]];
+ (_) -> []
+ end,
+ Els)).
concat_info(Els) ->
- lists:sort(
- lists:flatmap(
- fun({xmlelement, "x", Attrs, Fields}) ->
- case {xml:get_attr_s("xmlns", Attrs),
- xml:get_attr_s("type", Attrs)} of
- {?NS_XDATA, "result"} ->
- [concat_xdata_fields(Fields)];
- _ ->
- []
- end;
- (_) ->
- []
- end, Els)).
+ lists:sort(lists:flatmap(fun (#xmlel{name = <<"x">>,
+ attrs = Attrs, children = Fields}) ->
+ case {xml:get_attr_s(<<"xmlns">>, Attrs),
+ xml:get_attr_s(<<"type">>, Attrs)}
+ of
+ {?NS_XDATA, <<"result">>} ->
+ [concat_xdata_fields(Fields)];
+ _ -> []
+ end;
+ (_) -> []
+ end,
+ Els)).
concat_xdata_fields(Fields) ->
- [Form, Res] =
- lists:foldl(
- fun({xmlelement, "field", Attrs, Els} = El,
- [FormType, VarFields] = Acc) ->
- case xml:get_attr_s("var", Attrs) of
- "" ->
- Acc;
- "FORM_TYPE" ->
- [xml:get_subtag_cdata(El, "value"), VarFields];
- Var ->
- [FormType,
- [[[Var, $<],
- lists:sort(
- lists:flatmap(
- fun({xmlelement, "value", _, VEls}) ->
- [[xml:get_cdata(VEls), $<]];
- (_) ->
- []
- end, Els))] | VarFields]]
- end;
- (_, Acc) ->
- Acc
- end, ["", []], Fields),
+ [Form, Res] = lists:foldl(fun (#xmlel{name =
+ <<"field">>,
+ attrs = Attrs, children = Els} =
+ El,
+ [FormType, VarFields] = Acc) ->
+ case xml:get_attr_s(<<"var">>, Attrs) of
+ <<"">> -> Acc;
+ <<"FORM_TYPE">> ->
+ [xml:get_subtag_cdata(El,
+ <<"value">>),
+ VarFields];
+ Var ->
+ [FormType,
+ [[[Var, $<],
+ lists:sort(lists:flatmap(fun
+ (#xmlel{name
+ =
+ <<"value">>,
+ children
+ =
+ VEls}) ->
+ [[xml:get_cdata(VEls),
+ $<]];
+ (_) ->
+ []
+ end,
+ Els))]
+ | VarFields]]
+ end;
+ (_, Acc) -> Acc
+ end,
+ [<<"">>, []], Fields),
[Form, $<, lists:sort(Res)].
gb_trees_fold(F, Acc, Tree) ->
@@ -613,13 +607,19 @@ gb_trees_fold(F, Acc, Tree) ->
gb_trees_fold_iter(F, Acc, Iter) ->
case gb_trees:next(Iter) of
- {Key, Val, NewIter} ->
- NewAcc = F(Key, Val, Acc),
- gb_trees_fold_iter(F, NewAcc, NewIter);
- _ ->
- Acc
+ {Key, Val, NewIter} ->
+ NewAcc = F(Key, Val, Acc),
+ gb_trees_fold_iter(F, NewAcc, NewIter);
+ _ -> Acc
end.
now_ts() ->
- {MegaSecs, Secs, _} = now(),
- MegaSecs*1000000 + Secs.
+ {MegaSecs, Secs, _} = now(), MegaSecs * 1000000 + Secs.
+
+is_valid_node(Node) ->
+ case str:tokens(Node, <<"#">>) of
+ [?EJABBERD_URI|_] ->
+ true;
+ _ ->
+ false
+ end.
diff --git a/src/mod_configure.erl b/src/mod_configure.erl
index 091ea6071..3d2b7d267 100644
--- a/src/mod_configure.erl
+++ b/src/mod_configure.erl
@@ -28,25 +28,21 @@
%%% (2005-08-19)
-module(mod_configure).
+
-author('alexey@process-one.net').
-behaviour(gen_mod).
--export([start/2,
- stop/1,
- get_local_identity/5,
- get_local_features/5,
- get_local_items/5,
- adhoc_local_items/4,
- adhoc_local_commands/4,
- get_sm_identity/5,
- get_sm_features/5,
- get_sm_items/5,
- adhoc_sm_items/4,
- adhoc_sm_commands/4]).
+-export([start/2, stop/1, get_local_identity/5,
+ get_local_features/5, get_local_items/5,
+ adhoc_local_items/4, adhoc_local_commands/4,
+ get_sm_identity/5, get_sm_features/5, get_sm_items/5,
+ adhoc_sm_items/4, adhoc_sm_commands/4]).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
+
-include("adhoc.hrl").
-define(T(Lang, Text), translate:translate(Lang, Text)).
@@ -55,437 +51,466 @@
-record(session, {sid, usr, us, priority, info}).
start(Host, _Opts) ->
- ejabberd_hooks:add(disco_local_items, Host, ?MODULE, get_local_items, 50),
- ejabberd_hooks:add(disco_local_features, Host, ?MODULE, get_local_features, 50),
- ejabberd_hooks:add(disco_local_identity, Host, ?MODULE, get_local_identity, 50),
- ejabberd_hooks:add(disco_sm_items, Host, ?MODULE, get_sm_items, 50),
- ejabberd_hooks:add(disco_sm_features, Host, ?MODULE, get_sm_features, 50),
- ejabberd_hooks:add(disco_sm_identity, Host, ?MODULE, get_sm_identity, 50),
- ejabberd_hooks:add(adhoc_local_items, Host, ?MODULE, adhoc_local_items, 50),
- ejabberd_hooks:add(adhoc_local_commands, Host, ?MODULE, adhoc_local_commands, 50),
- ejabberd_hooks:add(adhoc_sm_items, Host, ?MODULE, adhoc_sm_items, 50),
- ejabberd_hooks:add(adhoc_sm_commands, Host, ?MODULE, adhoc_sm_commands, 50),
+ ejabberd_hooks:add(disco_local_items, Host, ?MODULE,
+ get_local_items, 50),
+ ejabberd_hooks:add(disco_local_features, Host, ?MODULE,
+ get_local_features, 50),
+ ejabberd_hooks:add(disco_local_identity, Host, ?MODULE,
+ get_local_identity, 50),
+ ejabberd_hooks:add(disco_sm_items, Host, ?MODULE,
+ get_sm_items, 50),
+ ejabberd_hooks:add(disco_sm_features, Host, ?MODULE,
+ get_sm_features, 50),
+ ejabberd_hooks:add(disco_sm_identity, Host, ?MODULE,
+ get_sm_identity, 50),
+ ejabberd_hooks:add(adhoc_local_items, Host, ?MODULE,
+ adhoc_local_items, 50),
+ ejabberd_hooks:add(adhoc_local_commands, Host, ?MODULE,
+ adhoc_local_commands, 50),
+ ejabberd_hooks:add(adhoc_sm_items, Host, ?MODULE,
+ adhoc_sm_items, 50),
+ ejabberd_hooks:add(adhoc_sm_commands, Host, ?MODULE,
+ adhoc_sm_commands, 50),
ok.
stop(Host) ->
- ejabberd_hooks:delete(adhoc_sm_commands, Host, ?MODULE, adhoc_sm_commands, 50),
- ejabberd_hooks:delete(adhoc_sm_items, Host, ?MODULE, adhoc_sm_items, 50),
- ejabberd_hooks:delete(adhoc_local_commands, Host, ?MODULE, adhoc_local_commands, 50),
- ejabberd_hooks:delete(adhoc_local_items, Host, ?MODULE, adhoc_local_items, 50),
- ejabberd_hooks:delete(disco_sm_identity, Host, ?MODULE, get_sm_identity, 50),
- ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, get_sm_features, 50),
- ejabberd_hooks:delete(disco_sm_items, Host, ?MODULE, get_sm_items, 50),
- ejabberd_hooks:delete(disco_local_identity, Host, ?MODULE, get_local_identity, 50),
- ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, get_local_features, 50),
- ejabberd_hooks:delete(disco_local_items, Host, ?MODULE, get_local_items, 50),
- gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_COMMANDS),
- gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_COMMANDS).
+ ejabberd_hooks:delete(adhoc_sm_commands, Host, ?MODULE,
+ adhoc_sm_commands, 50),
+ ejabberd_hooks:delete(adhoc_sm_items, Host, ?MODULE,
+ adhoc_sm_items, 50),
+ ejabberd_hooks:delete(adhoc_local_commands, Host,
+ ?MODULE, adhoc_local_commands, 50),
+ ejabberd_hooks:delete(adhoc_local_items, Host, ?MODULE,
+ adhoc_local_items, 50),
+ ejabberd_hooks:delete(disco_sm_identity, Host, ?MODULE,
+ get_sm_identity, 50),
+ ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE,
+ get_sm_features, 50),
+ ejabberd_hooks:delete(disco_sm_items, Host, ?MODULE,
+ get_sm_items, 50),
+ ejabberd_hooks:delete(disco_local_identity, Host,
+ ?MODULE, get_local_identity, 50),
+ ejabberd_hooks:delete(disco_local_features, Host,
+ ?MODULE, get_local_features, 50),
+ ejabberd_hooks:delete(disco_local_items, Host, ?MODULE,
+ get_local_items, 50),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, Host,
+ ?NS_COMMANDS),
+ gen_iq_handler:remove_iq_handler(ejabberd_sm, Host,
+ ?NS_COMMANDS).
%%%-----------------------------------------------------------------------
-define(INFO_IDENTITY(Category, Type, Name, Lang),
- [{xmlelement, "identity",
- [{"category", Category},
- {"type", Type},
- {"name", ?T(Lang, Name)}], []}]).
+ [#xmlel{name = <<"identity">>,
+ attrs =
+ [{<<"category">>, Category}, {<<"type">>, Type},
+ {<<"name">>, ?T(Lang, Name)}],
+ children = []}]).
-define(INFO_COMMAND(Name, Lang),
- ?INFO_IDENTITY("automation", "command-node", Name, Lang)).
+ ?INFO_IDENTITY(<<"automation">>, <<"command-node">>,
+ Name, Lang)).
-define(NODEJID(To, Name, Node),
- {xmlelement, "item",
- [{"jid", jlib:jid_to_string(To)},
- {"name", ?T(Lang, Name)},
- {"node", Node}], []}).
+ #xmlel{name = <<"item">>,
+ attrs =
+ [{<<"jid">>, jlib:jid_to_string(To)},
+ {<<"name">>, ?T(Lang, Name)}, {<<"node">>, Node}],
+ children = []}).
-define(NODE(Name, Node),
- {xmlelement, "item",
- [{"jid", Server},
- {"name", ?T(Lang, Name)},
- {"node", Node}], []}).
+ #xmlel{name = <<"item">>,
+ attrs =
+ [{<<"jid">>, Server}, {<<"name">>, ?T(Lang, Name)},
+ {<<"node">>, Node}],
+ children = []}).
+
+-define(NS_ADMINX(Sub),
+ <<(?NS_ADMIN)/binary, "#", Sub/binary>>).
--define(NS_ADMINX(Sub), ?NS_ADMIN++"#"++Sub).
--define(NS_ADMINL(Sub), ["http:","jabber.org","protocol","admin", Sub]).
-tokenize(Node) -> string:tokens(Node, "/#").
+-define(NS_ADMINL(Sub),
+ [<<"http:">>, <<"jabber.org">>, <<"protocol">>,
+ <<"admin">>, Sub]).
+
+tokenize(Node) -> str:tokens(Node, <<"/#">>).
get_sm_identity(Acc, _From, _To, Node, Lang) ->
case Node of
- "config" ->
- ?INFO_COMMAND("Configuration", Lang);
- _ ->
- Acc
+ <<"config">> ->
+ ?INFO_COMMAND(<<"Configuration">>, Lang);
+ _ -> Acc
end.
get_local_identity(Acc, _From, _To, Node, Lang) ->
LNode = tokenize(Node),
case LNode of
- ["running nodes", ENode] ->
- ?INFO_IDENTITY("ejabberd", "node", ENode, Lang);
- ["running nodes", _ENode, "DB"] ->
- ?INFO_COMMAND("Database", Lang);
- ["running nodes", _ENode, "modules", "start"] ->
- ?INFO_COMMAND("Start Modules", Lang);
- ["running nodes", _ENode, "modules", "stop"] ->
- ?INFO_COMMAND("Stop Modules", Lang);
- ["running nodes", _ENode, "backup", "backup"] ->
- ?INFO_COMMAND("Backup", Lang);
- ["running nodes", _ENode, "backup", "restore"] ->
- ?INFO_COMMAND("Restore", Lang);
- ["running nodes", _ENode, "backup", "textfile"] ->
- ?INFO_COMMAND("Dump to Text File", Lang);
- ["running nodes", _ENode, "import", "file"] ->
- ?INFO_COMMAND("Import File", Lang);
- ["running nodes", _ENode, "import", "dir"] ->
- ?INFO_COMMAND("Import Directory", Lang);
- ["running nodes", _ENode, "restart"] ->
- ?INFO_COMMAND("Restart Service", Lang);
- ["running nodes", _ENode, "shutdown"] ->
- ?INFO_COMMAND("Shut Down Service", Lang);
- ?NS_ADMINL("add-user") ->
- ?INFO_COMMAND("Add User", Lang);
- ?NS_ADMINL("delete-user") ->
- ?INFO_COMMAND("Delete User", Lang);
- ?NS_ADMINL("end-user-session") ->
- ?INFO_COMMAND("End User Session", Lang);
- ?NS_ADMINL("get-user-password") ->
- ?INFO_COMMAND("Get User Password", Lang);
- ?NS_ADMINL("change-user-password") ->
- ?INFO_COMMAND("Change User Password", Lang);
- ?NS_ADMINL("get-user-lastlogin") ->
- ?INFO_COMMAND("Get User Last Login Time", Lang);
- ?NS_ADMINL("user-stats") ->
- ?INFO_COMMAND("Get User Statistics", Lang);
- ?NS_ADMINL("get-registered-users-num") ->
- ?INFO_COMMAND("Get Number of Registered Users", Lang);
- ?NS_ADMINL("get-online-users-num") ->
- ?INFO_COMMAND("Get Number of Online Users", Lang);
- ["config", "acls"] ->
- ?INFO_COMMAND("Access Control Lists", Lang);
- ["config", "access"] ->
- ?INFO_COMMAND("Access Rules", Lang);
- _ ->
- Acc
+ [<<"running nodes">>, ENode] ->
+ ?INFO_IDENTITY(<<"ejabberd">>, <<"node">>, ENode, Lang);
+ [<<"running nodes">>, _ENode, <<"DB">>] ->
+ ?INFO_COMMAND(<<"Database">>, Lang);
+ [<<"running nodes">>, _ENode, <<"modules">>,
+ <<"start">>] ->
+ ?INFO_COMMAND(<<"Start Modules">>, Lang);
+ [<<"running nodes">>, _ENode, <<"modules">>,
+ <<"stop">>] ->
+ ?INFO_COMMAND(<<"Stop Modules">>, Lang);
+ [<<"running nodes">>, _ENode, <<"backup">>,
+ <<"backup">>] ->
+ ?INFO_COMMAND(<<"Backup">>, Lang);
+ [<<"running nodes">>, _ENode, <<"backup">>,
+ <<"restore">>] ->
+ ?INFO_COMMAND(<<"Restore">>, Lang);
+ [<<"running nodes">>, _ENode, <<"backup">>,
+ <<"textfile">>] ->
+ ?INFO_COMMAND(<<"Dump to Text File">>, Lang);
+ [<<"running nodes">>, _ENode, <<"import">>,
+ <<"file">>] ->
+ ?INFO_COMMAND(<<"Import File">>, Lang);
+ [<<"running nodes">>, _ENode, <<"import">>,
+ <<"dir">>] ->
+ ?INFO_COMMAND(<<"Import Directory">>, Lang);
+ [<<"running nodes">>, _ENode, <<"restart">>] ->
+ ?INFO_COMMAND(<<"Restart Service">>, Lang);
+ [<<"running nodes">>, _ENode, <<"shutdown">>] ->
+ ?INFO_COMMAND(<<"Shut Down Service">>, Lang);
+ ?NS_ADMINL(<<"add-user">>) ->
+ ?INFO_COMMAND(<<"Add User">>, Lang);
+ ?NS_ADMINL(<<"delete-user">>) ->
+ ?INFO_COMMAND(<<"Delete User">>, Lang);
+ ?NS_ADMINL(<<"end-user-session">>) ->
+ ?INFO_COMMAND(<<"End User Session">>, Lang);
+ ?NS_ADMINL(<<"get-user-password">>) ->
+ ?INFO_COMMAND(<<"Get User Password">>, Lang);
+ ?NS_ADMINL(<<"change-user-password">>) ->
+ ?INFO_COMMAND(<<"Change User Password">>, Lang);
+ ?NS_ADMINL(<<"get-user-lastlogin">>) ->
+ ?INFO_COMMAND(<<"Get User Last Login Time">>, Lang);
+ ?NS_ADMINL(<<"user-stats">>) ->
+ ?INFO_COMMAND(<<"Get User Statistics">>, Lang);
+ ?NS_ADMINL(<<"get-registered-users-num">>) ->
+ ?INFO_COMMAND(<<"Get Number of Registered Users">>,
+ Lang);
+ ?NS_ADMINL(<<"get-online-users-num">>) ->
+ ?INFO_COMMAND(<<"Get Number of Online Users">>, Lang);
+ [<<"config">>, <<"acls">>] ->
+ ?INFO_COMMAND(<<"Access Control Lists">>, Lang);
+ [<<"config">>, <<"access">>] ->
+ ?INFO_COMMAND(<<"Access Rules">>, Lang);
+ _ -> Acc
end.
%%%-----------------------------------------------------------------------
-define(INFO_RESULT(Allow, Feats),
case Allow of
- deny ->
- {error, ?ERR_FORBIDDEN};
- allow ->
- {result, Feats}
+ deny -> {error, ?ERR_FORBIDDEN};
+ allow -> {result, Feats}
end).
-get_sm_features(Acc, From, #jid{lserver = LServer} = _To, Node, _Lang) ->
+get_sm_features(Acc, From,
+ #jid{lserver = LServer} = _To, Node, _Lang) ->
case gen_mod:is_loaded(LServer, mod_adhoc) of
- false ->
- Acc;
- _ ->
- Allow = acl:match_rule(LServer, configure, From),
- case Node of
- "config" ->
- ?INFO_RESULT(Allow, [?NS_COMMANDS]);
- _ ->
- Acc
- end
+ false -> Acc;
+ _ ->
+ Allow = acl:match_rule(LServer, configure, From),
+ case Node of
+ <<"config">> -> ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ _ -> Acc
+ end
end.
-get_local_features(Acc, From, #jid{lserver = LServer} = _To, Node, _Lang) ->
+get_local_features(Acc, From,
+ #jid{lserver = LServer} = _To, Node, _Lang) ->
case gen_mod:is_loaded(LServer, mod_adhoc) of
- false ->
- Acc;
- _ ->
- LNode = tokenize(Node),
- Allow = acl:match_rule(LServer, configure, From),
- case LNode of
- ["config"] ->
- ?INFO_RESULT(Allow, []);
- ["user"] ->
- ?INFO_RESULT(Allow, []);
- ["online users"] ->
- ?INFO_RESULT(Allow, []);
- ["all users"] ->
- ?INFO_RESULT(Allow, []);
- ["all users", [$@ | _]] ->
- ?INFO_RESULT(Allow, []);
- ["outgoing s2s" | _] ->
- ?INFO_RESULT(Allow, []);
- ["running nodes"] ->
- ?INFO_RESULT(Allow, []);
- ["stopped nodes"] ->
- ?INFO_RESULT(Allow, []);
- ["running nodes", _ENode] ->
- ?INFO_RESULT(Allow, [?NS_STATS]);
- ["running nodes", _ENode, "DB"] ->
- ?INFO_RESULT(Allow, [?NS_COMMANDS]);
- ["running nodes", _ENode, "modules"] ->
- ?INFO_RESULT(Allow, []);
- ["running nodes", _ENode, "modules", _] ->
- ?INFO_RESULT(Allow, [?NS_COMMANDS]);
- ["running nodes", _ENode, "backup"] ->
- ?INFO_RESULT(Allow, []);
- ["running nodes", _ENode, "backup", _] ->
- ?INFO_RESULT(Allow, [?NS_COMMANDS]);
- ["running nodes", _ENode, "import"] ->
- ?INFO_RESULT(Allow, []);
- ["running nodes", _ENode, "import", _] ->
- ?INFO_RESULT(Allow, [?NS_COMMANDS]);
- ["running nodes", _ENode, "restart"] ->
- ?INFO_RESULT(Allow, [?NS_COMMANDS]);
- ["running nodes", _ENode, "shutdown"] ->
- ?INFO_RESULT(Allow, [?NS_COMMANDS]);
- ["config", _] ->
- ?INFO_RESULT(Allow, [?NS_COMMANDS]);
- ?NS_ADMINL("add-user") ->
- ?INFO_RESULT(Allow, [?NS_COMMANDS]);
- ?NS_ADMINL("delete-user") ->
- ?INFO_RESULT(Allow, [?NS_COMMANDS]);
- ?NS_ADMINL("end-user-session") ->
- ?INFO_RESULT(Allow, [?NS_COMMANDS]);
- ?NS_ADMINL("get-user-password") ->
- ?INFO_RESULT(Allow, [?NS_COMMANDS]);
- ?NS_ADMINL("change-user-password") ->
- ?INFO_RESULT(Allow, [?NS_COMMANDS]);
- ?NS_ADMINL("get-user-lastlogin") ->
- ?INFO_RESULT(Allow, [?NS_COMMANDS]);
- ?NS_ADMINL("user-stats") ->
- ?INFO_RESULT(Allow, [?NS_COMMANDS]);
- ?NS_ADMINL("get-registered-users-num") ->
- ?INFO_RESULT(Allow, [?NS_COMMANDS]);
- ?NS_ADMINL("get-online-users-num") ->
- ?INFO_RESULT(Allow, [?NS_COMMANDS]);
- _ ->
- Acc
- end
+ false -> Acc;
+ _ ->
+ LNode = tokenize(Node),
+ Allow = acl:match_rule(LServer, configure, From),
+ case LNode of
+ [<<"config">>] -> ?INFO_RESULT(Allow, []);
+ [<<"user">>] -> ?INFO_RESULT(Allow, []);
+ [<<"online users">>] -> ?INFO_RESULT(Allow, []);
+ [<<"all users">>] -> ?INFO_RESULT(Allow, []);
+ [<<"all users">>, <<$@, _/binary>>] ->
+ ?INFO_RESULT(Allow, []);
+ [<<"outgoing s2s">> | _] -> ?INFO_RESULT(Allow, []);
+ [<<"running nodes">>] -> ?INFO_RESULT(Allow, []);
+ [<<"stopped nodes">>] -> ?INFO_RESULT(Allow, []);
+ [<<"running nodes">>, _ENode] ->
+ ?INFO_RESULT(Allow, [?NS_STATS]);
+ [<<"running nodes">>, _ENode, <<"DB">>] ->
+ ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ [<<"running nodes">>, _ENode, <<"modules">>] ->
+ ?INFO_RESULT(Allow, []);
+ [<<"running nodes">>, _ENode, <<"modules">>, _] ->
+ ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ [<<"running nodes">>, _ENode, <<"backup">>] ->
+ ?INFO_RESULT(Allow, []);
+ [<<"running nodes">>, _ENode, <<"backup">>, _] ->
+ ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ [<<"running nodes">>, _ENode, <<"import">>] ->
+ ?INFO_RESULT(Allow, []);
+ [<<"running nodes">>, _ENode, <<"import">>, _] ->
+ ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ [<<"running nodes">>, _ENode, <<"restart">>] ->
+ ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ [<<"running nodes">>, _ENode, <<"shutdown">>] ->
+ ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ [<<"config">>, _] ->
+ ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ ?NS_ADMINL(<<"add-user">>) ->
+ ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ ?NS_ADMINL(<<"delete-user">>) ->
+ ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ ?NS_ADMINL(<<"end-user-session">>) ->
+ ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ ?NS_ADMINL(<<"get-user-password">>) ->
+ ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ ?NS_ADMINL(<<"change-user-password">>) ->
+ ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ ?NS_ADMINL(<<"get-user-lastlogin">>) ->
+ ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ ?NS_ADMINL(<<"user-stats">>) ->
+ ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ ?NS_ADMINL(<<"get-registered-users-num">>) ->
+ ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ ?NS_ADMINL(<<"get-online-users-num">>) ->
+ ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ _ -> Acc
+ end
end.
%%%-----------------------------------------------------------------------
-adhoc_sm_items(Acc, From, #jid{lserver = LServer} = To, Lang) ->
+adhoc_sm_items(Acc, From, #jid{lserver = LServer} = To,
+ Lang) ->
case acl:match_rule(LServer, configure, From) of
- allow ->
- Items = case Acc of
- {result, Its} -> Its;
- empty -> []
- end,
- Nodes = [{xmlelement, "item",
- [{"jid", jlib:jid_to_string(To)},
- {"name", ?T(Lang, "Configuration")},
- {"node", "config"}], []}],
- {result, Items ++ Nodes};
- _ ->
- Acc
+ allow ->
+ Items = case Acc of
+ {result, Its} -> Its;
+ empty -> []
+ end,
+ Nodes = [#xmlel{name = <<"item">>,
+ attrs =
+ [{<<"jid">>, jlib:jid_to_string(To)},
+ {<<"name">>, ?T(Lang, <<"Configuration">>)},
+ {<<"node">>, <<"config">>}],
+ children = []}],
+ {result, Items ++ Nodes};
+ _ -> Acc
end.
%%%-----------------------------------------------------------------------
get_sm_items(Acc, From,
- #jid{user = User, server = Server, lserver = LServer} = To,
+ #jid{user = User, server = Server, lserver = LServer} =
+ To,
Node, Lang) ->
case gen_mod:is_loaded(LServer, mod_adhoc) of
- false ->
- Acc;
- _ ->
- Items = case Acc of
- {result, Its} -> Its;
- empty -> []
- end,
- case {acl:match_rule(LServer, configure, From), Node} of
- {allow, ""} ->
- Nodes = [?NODEJID(To, "Configuration", "config"),
- ?NODEJID(To, "User Management", "user")],
- {result, Items ++ Nodes ++ get_user_resources(User, Server)};
- {allow, "config"} ->
- {result, []};
- {_, "config"} ->
- {error, ?ERR_FORBIDDEN};
- _ ->
- Acc
- end
+ false -> Acc;
+ _ ->
+ Items = case Acc of
+ {result, Its} -> Its;
+ empty -> []
+ end,
+ case {acl:match_rule(LServer, configure, From), Node} of
+ {allow, <<"">>} ->
+ Nodes = [?NODEJID(To, <<"Configuration">>,
+ <<"config">>),
+ ?NODEJID(To, <<"User Management">>, <<"user">>)],
+ {result,
+ Items ++ Nodes ++ get_user_resources(User, Server)};
+ {allow, <<"config">>} -> {result, []};
+ {_, <<"config">>} -> {error, ?ERR_FORBIDDEN};
+ _ -> Acc
+ end
end.
get_user_resources(User, Server) ->
Rs = ejabberd_sm:get_user_resources(User, Server),
- lists:map(fun(R) ->
- {xmlelement, "item",
- [{"jid", User ++ "@" ++ Server ++ "/" ++ R},
- {"name", User}], []}
- end, lists:sort(Rs)).
+ lists:map(fun (R) ->
+ #xmlel{name = <<"item">>,
+ attrs =
+ [{<<"jid">>,
+ <<User/binary, "@", Server/binary, "/",
+ R/binary>>},
+ {<<"name">>, User}],
+ children = []}
+ end,
+ lists:sort(Rs)).
%%%-----------------------------------------------------------------------
-adhoc_local_items(Acc, From, #jid{lserver = LServer, server = Server} = To,
- Lang) ->
+adhoc_local_items(Acc, From,
+ #jid{lserver = LServer, server = Server} = To, Lang) ->
case acl:match_rule(LServer, configure, From) of
- allow ->
- Items = case Acc of
- {result, Its} -> Its;
- empty -> []
- end,
- PermLev = get_permission_level(From),
- %% Recursively get all configure commands
- Nodes = recursively_get_local_items(PermLev, LServer, "", Server,
- Lang),
- Nodes1 = lists:filter(
- fun(N) ->
- Nd = xml:get_tag_attr_s("node", N),
- F = get_local_features([], From, To, Nd, Lang),
- case F of
- {result, [?NS_COMMANDS]} ->
- true;
- _ ->
- false
- end
- end, Nodes),
- {result, Items ++ Nodes1};
- _ ->
- Acc
+ allow ->
+ Items = case Acc of
+ {result, Its} -> Its;
+ empty -> []
+ end,
+ PermLev = get_permission_level(From),
+ Nodes = recursively_get_local_items(PermLev, LServer,
+ <<"">>, Server, Lang),
+ Nodes1 = lists:filter(fun (N) ->
+ Nd = xml:get_tag_attr_s(<<"node">>, N),
+ F = get_local_features([], From, To, Nd,
+ Lang),
+ case F of
+ {result, [?NS_COMMANDS]} -> true;
+ _ -> false
+ end
+ end,
+ Nodes),
+ {result, Items ++ Nodes1};
+ _ -> Acc
end.
-recursively_get_local_items(_PermLev, _LServer, "online users", _Server, _Lang) ->
+recursively_get_local_items(_PermLev, _LServer,
+ <<"online users">>, _Server, _Lang) ->
[];
-
-recursively_get_local_items(_PermLev, _LServer, "all users", _Server, _Lang) ->
+recursively_get_local_items(_PermLev, _LServer,
+ <<"all users">>, _Server, _Lang) ->
[];
-
-recursively_get_local_items(PermLev, LServer, Node, Server, Lang) ->
+recursively_get_local_items(PermLev, LServer, Node,
+ Server, Lang) ->
LNode = tokenize(Node),
- Items = case get_local_items({PermLev, LServer}, LNode, Server, Lang) of
- {result, Res} ->
- Res;
- {error, _Error} ->
- []
+ Items = case get_local_items({PermLev, LServer}, LNode,
+ Server, Lang)
+ of
+ {result, Res} -> Res;
+ {error, _Error} -> []
end,
- Nodes = lists:flatten(
- lists:map(
- fun(N) ->
- S = xml:get_tag_attr_s("jid", N),
- Nd = xml:get_tag_attr_s("node", N),
- if (S /= Server) or (Nd == "") ->
- [];
- true ->
- [N, recursively_get_local_items(
- PermLev, LServer, Nd, Server, Lang)]
- end
- end, Items)),
+ Nodes = lists:flatten(lists:map(fun (N) ->
+ S = xml:get_tag_attr_s(<<"jid">>,
+ N),
+ Nd = xml:get_tag_attr_s(<<"node">>,
+ N),
+ if (S /= Server) or
+ (Nd == <<"">>) ->
+ [];
+ true ->
+ [N,
+ recursively_get_local_items(PermLev,
+ LServer,
+ Nd,
+ Server,
+ Lang)]
+ end
+ end,
+ Items)),
Nodes.
get_permission_level(JID) ->
case acl:match_rule(global, configure, JID) of
- allow -> global;
- deny -> vhost
+ allow -> global;
+ deny -> vhost
end.
%%%-----------------------------------------------------------------------
-define(ITEMS_RESULT(Allow, LNode, Fallback),
case Allow of
- deny ->
- Fallback;
- allow ->
- PermLev = get_permission_level(From),
- case get_local_items({PermLev, LServer}, LNode,
- jlib:jid_to_string(To), Lang) of
- {result, Res} ->
- {result, Res};
- {error, Error} ->
- {error, Error}
- end
+ deny -> Fallback;
+ allow ->
+ PermLev = get_permission_level(From),
+ case get_local_items({PermLev, LServer}, LNode,
+ jlib:jid_to_string(To), Lang)
+ of
+ {result, Res} -> {result, Res};
+ {error, Error} -> {error, Error}
+ end
end).
-get_local_items(Acc, From, #jid{lserver = LServer} = To, "", Lang) ->
+get_local_items(Acc, From, #jid{lserver = LServer} = To,
+ <<"">>, Lang) ->
case gen_mod:is_loaded(LServer, mod_adhoc) of
- false ->
- Acc;
- _ ->
- Items = case Acc of
- {result, Its} -> Its;
- empty -> []
- end,
- Allow = acl:match_rule(LServer, configure, From),
- case Allow of
- deny ->
- {result, Items};
- allow ->
- PermLev = get_permission_level(From),
- case get_local_items({PermLev, LServer}, [],
- jlib:jid_to_string(To), Lang) of
- {result, Res} ->
- {result, Items ++ Res};
- {error, _Error} ->
- {result, Items}
- end
- end
+ false -> Acc;
+ _ ->
+ Items = case Acc of
+ {result, Its} -> Its;
+ empty -> []
+ end,
+ Allow = acl:match_rule(LServer, configure, From),
+ case Allow of
+ deny -> {result, Items};
+ allow ->
+ PermLev = get_permission_level(From),
+ case get_local_items({PermLev, LServer}, [],
+ jlib:jid_to_string(To), Lang)
+ of
+ {result, Res} -> {result, Items ++ Res};
+ {error, _Error} -> {result, Items}
+ end
+ end
end;
-
-get_local_items(Acc, From, #jid{lserver = LServer} = To, Node, Lang) ->
+get_local_items(Acc, From, #jid{lserver = LServer} = To,
+ Node, Lang) ->
case gen_mod:is_loaded(LServer, mod_adhoc) of
- false ->
- Acc;
- _ ->
- LNode = tokenize(Node),
- Allow = acl:match_rule(LServer, configure, From),
- case LNode of
- ["config"] ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
- ["user"] ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
- ["online users"] ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
- ["all users"] ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
- ["all users", [$@ | _]] ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
- ["outgoing s2s" | _] ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
- ["running nodes"] ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
- ["stopped nodes"] ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
- ["running nodes", _ENode] ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
- ["running nodes", _ENode, "DB"] ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
- ["running nodes", _ENode, "modules"] ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
- ["running nodes", _ENode, "modules", _] ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
- ["running nodes", _ENode, "backup"] ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
- ["running nodes", _ENode, "backup", _] ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
- ["running nodes", _ENode, "import"] ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
- ["running nodes", _ENode, "import", _] ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
- ["running nodes", _ENode, "restart"] ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
- ["running nodes", _ENode, "shutdown"] ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
- ["config", _] ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
- ?NS_ADMINL("add-user") ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
- ?NS_ADMINL("delete-user") ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
- ?NS_ADMINL("end-user-session") ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
- ?NS_ADMINL("get-user-password") ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
- ?NS_ADMINL("change-user-password") ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
- ?NS_ADMINL("get-user-lastlogin") ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
- ?NS_ADMINL("user-stats") ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
- ?NS_ADMINL("get-registered-users-num") ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
- ?NS_ADMINL("get-online-users-num") ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
- _ ->
- Acc
- end
+ false -> Acc;
+ _ ->
+ LNode = tokenize(Node),
+ Allow = acl:match_rule(LServer, configure, From),
+ case LNode of
+ [<<"config">>] ->
+ ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ [<<"user">>] ->
+ ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ [<<"online users">>] ->
+ ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ [<<"all users">>] ->
+ ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ [<<"all users">>, <<$@, _/binary>>] ->
+ ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ [<<"outgoing s2s">> | _] ->
+ ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ [<<"running nodes">>] ->
+ ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ [<<"stopped nodes">>] ->
+ ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ [<<"running nodes">>, _ENode] ->
+ ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ [<<"running nodes">>, _ENode, <<"DB">>] ->
+ ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ [<<"running nodes">>, _ENode, <<"modules">>] ->
+ ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ [<<"running nodes">>, _ENode, <<"modules">>, _] ->
+ ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ [<<"running nodes">>, _ENode, <<"backup">>] ->
+ ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ [<<"running nodes">>, _ENode, <<"backup">>, _] ->
+ ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ [<<"running nodes">>, _ENode, <<"import">>] ->
+ ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ [<<"running nodes">>, _ENode, <<"import">>, _] ->
+ ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ [<<"running nodes">>, _ENode, <<"restart">>] ->
+ ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ [<<"running nodes">>, _ENode, <<"shutdown">>] ->
+ ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ [<<"config">>, _] ->
+ ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?NS_ADMINL(<<"add-user">>) ->
+ ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?NS_ADMINL(<<"delete-user">>) ->
+ ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?NS_ADMINL(<<"end-user-session">>) ->
+ ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?NS_ADMINL(<<"get-user-password">>) ->
+ ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?NS_ADMINL(<<"change-user-password">>) ->
+ ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?NS_ADMINL(<<"get-user-lastlogin">>) ->
+ ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?NS_ADMINL(<<"user-stats">>) ->
+ ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?NS_ADMINL(<<"get-registered-users-num">>) ->
+ ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?NS_ADMINL(<<"get-online-users-num">>) ->
+ ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ _ -> Acc
+ end
end.
%%%-----------------------------------------------------------------------
@@ -495,1403 +520,1633 @@ get_local_items(Acc, From, #jid{lserver = LServer} = To, Node, Lang) ->
%% PermissionLevel = global | vhost
get_local_items(_Host, [], Server, Lang) ->
{result,
- [?NODE("Configuration", "config"),
- ?NODE("User Management", "user"),
- ?NODE("Online Users", "online users"),
- ?NODE("All Users", "all users"),
- ?NODE("Outgoing s2s Connections", "outgoing s2s"),
- ?NODE("Running Nodes", "running nodes"),
- ?NODE("Stopped Nodes", "stopped nodes")
- ]};
-
-get_local_items(_Host, ["config"], Server, Lang) ->
+ [?NODE(<<"Configuration">>, <<"config">>),
+ ?NODE(<<"User Management">>, <<"user">>),
+ ?NODE(<<"Online Users">>, <<"online users">>),
+ ?NODE(<<"All Users">>, <<"all users">>),
+ ?NODE(<<"Outgoing s2s Connections">>,
+ <<"outgoing s2s">>),
+ ?NODE(<<"Running Nodes">>, <<"running nodes">>),
+ ?NODE(<<"Stopped Nodes">>, <<"stopped nodes">>)]};
+get_local_items(_Host, [<<"config">>], Server, Lang) ->
{result,
- [?NODE("Access Control Lists", "config/acls"),
- ?NODE("Access Rules", "config/access")
- ]};
-
-get_local_items(_Host, ["config", _], _Server, _Lang) ->
+ [?NODE(<<"Access Control Lists">>, <<"config/acls">>),
+ ?NODE(<<"Access Rules">>, <<"config/access">>)]};
+get_local_items(_Host, [<<"config">>, _], _Server,
+ _Lang) ->
{result, []};
-
-get_local_items(_Host, ["user"], Server, Lang) ->
+get_local_items(_Host, [<<"user">>], Server, Lang) ->
{result,
- [?NODE("Add User", ?NS_ADMINX("add-user")),
- ?NODE("Delete User", ?NS_ADMINX("delete-user")),
- ?NODE("End User Session", ?NS_ADMINX("end-user-session")),
- ?NODE("Get User Password", ?NS_ADMINX("get-user-password")),
- ?NODE("Change User Password",?NS_ADMINX("change-user-password")),
- ?NODE("Get User Last Login Time", ?NS_ADMINX("get-user-lastlogin")),
- ?NODE("Get User Statistics", ?NS_ADMINX("user-stats")),
- ?NODE("Get Number of Registered Users",?NS_ADMINX("get-registered-users-num")),
- ?NODE("Get Number of Online Users",?NS_ADMINX("get-online-users-num"))
- ]};
-
-get_local_items(_Host, ["http:" | _], _Server, _Lang) ->
+ [?NODE(<<"Add User">>, (?NS_ADMINX(<<"add-user">>))),
+ ?NODE(<<"Delete User">>,
+ (?NS_ADMINX(<<"delete-user">>))),
+ ?NODE(<<"End User Session">>,
+ (?NS_ADMINX(<<"end-user-session">>))),
+ ?NODE(<<"Get User Password">>,
+ (?NS_ADMINX(<<"get-user-password">>))),
+ ?NODE(<<"Change User Password">>,
+ (?NS_ADMINX(<<"change-user-password">>))),
+ ?NODE(<<"Get User Last Login Time">>,
+ (?NS_ADMINX(<<"get-user-lastlogin">>))),
+ ?NODE(<<"Get User Statistics">>,
+ (?NS_ADMINX(<<"user-stats">>))),
+ ?NODE(<<"Get Number of Registered Users">>,
+ (?NS_ADMINX(<<"get-registered-users-num">>))),
+ ?NODE(<<"Get Number of Online Users">>,
+ (?NS_ADMINX(<<"get-online-users-num">>)))]};
+get_local_items(_Host, [<<"http:">> | _], _Server,
+ _Lang) ->
{result, []};
-
-get_local_items({_, Host}, ["online users"], _Server, _Lang) ->
+get_local_items({_, Host}, [<<"online users">>],
+ _Server, _Lang) ->
{result, get_online_vh_users(Host)};
-
-get_local_items({_, Host}, ["all users"], _Server, _Lang) ->
+get_local_items({_, Host}, [<<"all users">>], _Server,
+ _Lang) ->
{result, get_all_vh_users(Host)};
-
-get_local_items({_, Host}, ["all users", [$@ | Diap]], _Server, _Lang) ->
- case catch ejabberd_auth:get_vh_registered_users(Host) of
- {'EXIT', _Reason} ->
- ?ERR_INTERNAL_SERVER_ERROR;
- Users ->
- SUsers = lists:sort([{S, U} || {U, S} <- Users]),
- case catch begin
- [S1, S2] = ejabberd_regexp:split(Diap, "-"),
- N1 = list_to_integer(S1),
- N2 = list_to_integer(S2),
- Sub = lists:sublist(SUsers, N1, N2 - N1 + 1),
- lists:map(fun({S, U}) ->
- {xmlelement, "item",
- [{"jid", U ++ "@" ++ S},
- {"name", U ++ "@" ++ S}], []}
- end, Sub)
- end of
- {'EXIT', _Reason} ->
- ?ERR_NOT_ACCEPTABLE;
- Res ->
- {result, Res}
- end
+get_local_items({_, Host},
+ [<<"all users">>, <<$@, Diap/binary>>], _Server,
+ _Lang) ->
+ case catch ejabberd_auth:get_vh_registered_users(Host)
+ of
+ {'EXIT', _Reason} -> ?ERR_INTERNAL_SERVER_ERROR;
+ Users ->
+ SUsers = lists:sort([{S, U} || {U, S} <- Users]),
+ case catch begin
+ [S1, S2] = ejabberd_regexp:split(Diap, <<"-">>),
+ N1 = jlib:binary_to_integer(S1),
+ N2 = jlib:binary_to_integer(S2),
+ Sub = lists:sublist(SUsers, N1, N2 - N1 + 1),
+ lists:map(fun ({S, U}) ->
+ #xmlel{name = <<"item">>,
+ attrs =
+ [{<<"jid">>,
+ <<U/binary, "@",
+ S/binary>>},
+ {<<"name">>,
+ <<U/binary, "@",
+ S/binary>>}],
+ children = []}
+ end,
+ Sub)
+ end
+ of
+ {'EXIT', _Reason} -> ?ERR_NOT_ACCEPTABLE;
+ Res -> {result, Res}
+ end
end;
-
-get_local_items({_, Host}, ["outgoing s2s"], _Server, Lang) ->
+get_local_items({_, Host}, [<<"outgoing s2s">>],
+ _Server, Lang) ->
{result, get_outgoing_s2s(Host, Lang)};
-
-get_local_items({_, Host}, ["outgoing s2s", To], _Server, Lang) ->
+get_local_items({_, Host}, [<<"outgoing s2s">>, To],
+ _Server, Lang) ->
{result, get_outgoing_s2s(Host, Lang, To)};
-
-get_local_items(_Host, ["running nodes"], Server, Lang) ->
+get_local_items(_Host, [<<"running nodes">>], Server,
+ Lang) ->
{result, get_running_nodes(Server, Lang)};
-
-get_local_items(_Host, ["stopped nodes"], _Server, Lang) ->
+get_local_items(_Host, [<<"stopped nodes">>], _Server,
+ Lang) ->
{result, get_stopped_nodes(Lang)};
-
-get_local_items({global, _Host}, ["running nodes", ENode], Server, Lang) ->
+get_local_items({global, _Host},
+ [<<"running nodes">>, ENode], Server, Lang) ->
{result,
- [?NODE("Database", "running nodes/" ++ ENode ++ "/DB"),
- ?NODE("Modules", "running nodes/" ++ ENode ++ "/modules"),
- ?NODE("Backup Management", "running nodes/" ++ ENode ++ "/backup"),
- ?NODE("Import Users From jabberd14 Spool Files",
- "running nodes/" ++ ENode ++ "/import"),
- ?NODE("Restart Service", "running nodes/" ++ ENode ++ "/restart"),
- ?NODE("Shut Down Service", "running nodes/" ++ ENode ++ "/shutdown")
- ]};
-
-get_local_items({vhost, _Host}, ["running nodes", ENode], Server, Lang) ->
+ [?NODE(<<"Database">>,
+ <<"running nodes/", ENode/binary, "/DB">>),
+ ?NODE(<<"Modules">>,
+ <<"running nodes/", ENode/binary, "/modules">>),
+ ?NODE(<<"Backup Management">>,
+ <<"running nodes/", ENode/binary, "/backup">>),
+ ?NODE(<<"Import Users From jabberd14 Spool Files">>,
+ <<"running nodes/", ENode/binary, "/import">>),
+ ?NODE(<<"Restart Service">>,
+ <<"running nodes/", ENode/binary, "/restart">>),
+ ?NODE(<<"Shut Down Service">>,
+ <<"running nodes/", ENode/binary, "/shutdown">>)]};
+get_local_items({vhost, _Host},
+ [<<"running nodes">>, ENode], Server, Lang) ->
{result,
- [?NODE("Modules", "running nodes/" ++ ENode ++ "/modules")
- ]};
-
-get_local_items(_Host, ["running nodes", _ENode, "DB"], _Server, _Lang) ->
+ [?NODE(<<"Modules">>,
+ <<"running nodes/", ENode/binary, "/modules">>)]};
+get_local_items(_Host,
+ [<<"running nodes">>, _ENode, <<"DB">>], _Server,
+ _Lang) ->
{result, []};
-
-get_local_items(_Host, ["running nodes", ENode, "modules"], Server, Lang) ->
+get_local_items(_Host,
+ [<<"running nodes">>, ENode, <<"modules">>], Server,
+ Lang) ->
{result,
- [?NODE("Start Modules", "running nodes/" ++ ENode ++ "/modules/start"),
- ?NODE("Stop Modules", "running nodes/" ++ ENode ++ "/modules/stop")
- ]};
-
-get_local_items(_Host, ["running nodes", _ENode, "modules", _], _Server, _Lang) ->
+ [?NODE(<<"Start Modules">>,
+ <<"running nodes/", ENode/binary, "/modules/start">>),
+ ?NODE(<<"Stop Modules">>,
+ <<"running nodes/", ENode/binary, "/modules/stop">>)]};
+get_local_items(_Host,
+ [<<"running nodes">>, _ENode, <<"modules">>, _],
+ _Server, _Lang) ->
{result, []};
-
-get_local_items(_Host, ["running nodes", ENode, "backup"], Server, Lang) ->
+get_local_items(_Host,
+ [<<"running nodes">>, ENode, <<"backup">>], Server,
+ Lang) ->
{result,
- [?NODE("Backup", "running nodes/" ++ ENode ++ "/backup/backup"),
- ?NODE("Restore", "running nodes/" ++ ENode ++ "/backup/restore"),
- ?NODE("Dump to Text File",
- "running nodes/" ++ ENode ++ "/backup/textfile")
- ]};
-
-get_local_items(_Host, ["running nodes", _ENode, "backup", _], _Server, _Lang) ->
+ [?NODE(<<"Backup">>,
+ <<"running nodes/", ENode/binary, "/backup/backup">>),
+ ?NODE(<<"Restore">>,
+ <<"running nodes/", ENode/binary, "/backup/restore">>),
+ ?NODE(<<"Dump to Text File">>,
+ <<"running nodes/", ENode/binary,
+ "/backup/textfile">>)]};
+get_local_items(_Host,
+ [<<"running nodes">>, _ENode, <<"backup">>, _], _Server,
+ _Lang) ->
{result, []};
-
-get_local_items(_Host, ["running nodes", ENode, "import"], Server, Lang) ->
+get_local_items(_Host,
+ [<<"running nodes">>, ENode, <<"import">>], Server,
+ Lang) ->
{result,
- [?NODE("Import File", "running nodes/" ++ ENode ++ "/import/file"),
- ?NODE("Import Directory", "running nodes/" ++ ENode ++ "/import/dir")
- ]};
-
-get_local_items(_Host, ["running nodes", _ENode, "import", _], _Server, _Lang) ->
+ [?NODE(<<"Import File">>,
+ <<"running nodes/", ENode/binary, "/import/file">>),
+ ?NODE(<<"Import Directory">>,
+ <<"running nodes/", ENode/binary, "/import/dir">>)]};
+get_local_items(_Host,
+ [<<"running nodes">>, _ENode, <<"import">>, _], _Server,
+ _Lang) ->
{result, []};
-
-get_local_items(_Host, ["running nodes", _ENode, "restart"], _Server, _Lang) ->
+get_local_items(_Host,
+ [<<"running nodes">>, _ENode, <<"restart">>], _Server,
+ _Lang) ->
{result, []};
-
-get_local_items(_Host, ["running nodes", _ENode, "shutdown"], _Server, _Lang) ->
+get_local_items(_Host,
+ [<<"running nodes">>, _ENode, <<"shutdown">>], _Server,
+ _Lang) ->
{result, []};
-
get_local_items(_Host, _, _Server, _Lang) ->
{error, ?ERR_ITEM_NOT_FOUND}.
-
get_online_vh_users(Host) ->
case catch ejabberd_sm:get_vh_session_list(Host) of
- {'EXIT', _Reason} ->
- [];
- USRs ->
- SURs = lists:sort([{S, U, R} || {U, S, R} <- USRs]),
- lists:map(fun({S, U, R}) ->
- {xmlelement, "item",
- [{"jid", U ++ "@" ++ S ++ "/" ++ R},
- {"name", U ++ "@" ++ S}], []}
- end, SURs)
+ {'EXIT', _Reason} -> [];
+ USRs ->
+ SURs = lists:sort([{S, U, R} || {U, S, R} <- USRs]),
+ lists:map(fun ({S, U, R}) ->
+ #xmlel{name = <<"item">>,
+ attrs =
+ [{<<"jid">>,
+ <<U/binary, "@", S/binary, "/",
+ R/binary>>},
+ {<<"name">>,
+ <<U/binary, "@", S/binary>>}],
+ children = []}
+ end,
+ SURs)
end.
get_all_vh_users(Host) ->
- case catch ejabberd_auth:get_vh_registered_users(Host) of
- {'EXIT', _Reason} ->
- [];
- Users ->
- SUsers = lists:sort([{S, U} || {U, S} <- Users]),
- case length(SUsers) of
- N when N =< 100 ->
- lists:map(fun({S, U}) ->
- {xmlelement, "item",
- [{"jid", U ++ "@" ++ S},
- {"name", U ++ "@" ++ S}], []}
- end, SUsers);
- N ->
- NParts = trunc(math:sqrt(N * 0.618)) + 1,
- M = trunc(N / NParts) + 1,
- lists:map(fun(K) ->
- L = K + M - 1,
- Node =
- "@" ++ integer_to_list(K) ++
- "-" ++ integer_to_list(L),
- {FS, FU} = lists:nth(K, SUsers),
- {LS, LU} =
- if L < N -> lists:nth(L, SUsers);
- true -> lists:last(SUsers)
- end,
- Name =
- FU ++ "@" ++ FS ++
- " -- " ++
- LU ++ "@" ++ LS,
- {xmlelement, "item",
- [{"jid", Host},
- {"node", "all users/" ++ Node},
- {"name", Name}], []}
- end, lists:seq(1, N, M))
- end
+ case catch ejabberd_auth:get_vh_registered_users(Host)
+ of
+ {'EXIT', _Reason} -> [];
+ Users ->
+ SUsers = lists:sort([{S, U} || {U, S} <- Users]),
+ case length(SUsers) of
+ N when N =< 100 ->
+ lists:map(fun ({S, U}) ->
+ #xmlel{name = <<"item">>,
+ attrs =
+ [{<<"jid">>,
+ <<U/binary, "@", S/binary>>},
+ {<<"name">>,
+ <<U/binary, "@", S/binary>>}],
+ children = []}
+ end,
+ SUsers);
+ N ->
+ NParts = trunc(math:sqrt(N * 6.17999999999999993783e-1))
+ + 1,
+ M = trunc(N / NParts) + 1,
+ lists:map(fun (K) ->
+ L = K + M - 1,
+ Node = <<"@",
+ (iolist_to_binary(integer_to_list(K)))/binary,
+ "-",
+ (iolist_to_binary(integer_to_list(L)))/binary>>,
+ {FS, FU} = lists:nth(K, SUsers),
+ {LS, LU} = if L < N -> lists:nth(L, SUsers);
+ true -> lists:last(SUsers)
+ end,
+ Name = <<FU/binary, "@", FS/binary, " -- ",
+ LU/binary, "@", LS/binary>>,
+ #xmlel{name = <<"item">>,
+ attrs =
+ [{<<"jid">>, Host},
+ {<<"node">>,
+ <<"all users/", Node/binary>>},
+ {<<"name">>, Name}],
+ children = []}
+ end,
+ lists:seq(1, N, M))
+ end
end.
get_outgoing_s2s(Host, Lang) ->
case catch ejabberd_s2s:dirty_get_connections() of
- {'EXIT', _Reason} ->
- [];
- Connections ->
- DotHost = "." ++ Host,
- TConns = [TH || {FH, TH} <- Connections,
- Host == FH orelse lists:suffix(DotHost, FH)],
- lists:map(
- fun(T) ->
- {xmlelement, "item",
- [{"jid", Host},
- {"node", "outgoing s2s/" ++ T},
- {"name",
- lists:flatten(
- io_lib:format(
- ?T(Lang, "To ~s"), [T]))}],
- []}
- end, lists:usort(TConns))
+ {'EXIT', _Reason} -> [];
+ Connections ->
+ DotHost = <<".", Host/binary>>,
+ TConns = [TH
+ || {FH, TH} <- Connections,
+ Host == FH orelse str:suffix(DotHost, FH)],
+ lists:map(fun (T) ->
+ #xmlel{name = <<"item">>,
+ attrs =
+ [{<<"jid">>, Host},
+ {<<"node">>,
+ <<"outgoing s2s/", T/binary>>},
+ {<<"name">>,
+ iolist_to_binary(io_lib:format(?T(Lang,
+ <<"To ~s">>),
+ [T]))}],
+ children = []}
+ end,
+ lists:usort(TConns))
end.
get_outgoing_s2s(Host, Lang, To) ->
case catch ejabberd_s2s:dirty_get_connections() of
- {'EXIT', _Reason} ->
- [];
- Connections ->
- lists:map(
- fun({F, _T}) ->
- {xmlelement, "item",
- [{"jid", Host},
- {"node", "outgoing s2s/" ++ To ++ "/" ++ F},
- {"name",
- lists:flatten(
- io_lib:format(
- ?T(Lang, "From ~s"), [F]))}],
- []}
- end, lists:keysort(1, lists:filter(fun(E) ->
- element(2, E) == To
- end, Connections)))
+ {'EXIT', _Reason} -> [];
+ Connections ->
+ lists:map(fun ({F, _T}) ->
+ #xmlel{name = <<"item">>,
+ attrs =
+ [{<<"jid">>, Host},
+ {<<"node">>,
+ <<"outgoing s2s/", To/binary, "/",
+ F/binary>>},
+ {<<"name">>,
+ iolist_to_binary(io_lib:format(?T(Lang,
+ <<"From ~s">>),
+ [F]))}],
+ children = []}
+ end,
+ lists:keysort(1,
+ lists:filter(fun (E) -> element(2, E) == To
+ end,
+ Connections)))
end.
-
get_running_nodes(Server, _Lang) ->
case catch mnesia:system_info(running_db_nodes) of
- {'EXIT', _Reason} ->
- [];
- DBNodes ->
- lists:map(
- fun(N) ->
- S = atom_to_list(N),
- {xmlelement, "item",
- [{"jid", Server},
- {"node", "running nodes/" ++ S},
- {"name", S}],
- []}
- end, lists:sort(DBNodes))
+ {'EXIT', _Reason} -> [];
+ DBNodes ->
+ lists:map(fun (N) ->
+ S = iolist_to_binary(atom_to_list(N)),
+ #xmlel{name = <<"item">>,
+ attrs =
+ [{<<"jid">>, Server},
+ {<<"node">>,
+ <<"running nodes/", S/binary>>},
+ {<<"name">>, S}],
+ children = []}
+ end,
+ lists:sort(DBNodes))
end.
get_stopped_nodes(_Lang) ->
- case catch (lists:usort(mnesia:system_info(db_nodes) ++
- mnesia:system_info(extra_db_nodes)) --
- mnesia:system_info(running_db_nodes)) of
- {'EXIT', _Reason} ->
- [];
- DBNodes ->
- lists:map(
- fun(N) ->
- S = atom_to_list(N),
- {xmlelement, "item",
- [{"jid", ?MYNAME},
- {"node", "stopped nodes/" ++ S},
- {"name", S}],
- []}
- end, lists:sort(DBNodes))
+ case catch lists:usort(mnesia:system_info(db_nodes) ++
+ mnesia:system_info(extra_db_nodes))
+ -- mnesia:system_info(running_db_nodes)
+ of
+ {'EXIT', _Reason} -> [];
+ DBNodes ->
+ lists:map(fun (N) ->
+ S = iolist_to_binary(atom_to_list(N)),
+ #xmlel{name = <<"item">>,
+ attrs =
+ [{<<"jid">>, ?MYNAME},
+ {<<"node">>,
+ <<"stopped nodes/", S/binary>>},
+ {<<"name">>, S}],
+ children = []}
+ end,
+ lists:sort(DBNodes))
end.
%%-------------------------------------------------------------------------
--define(COMMANDS_RESULT(LServerOrGlobal, From, To, Request),
+-define(COMMANDS_RESULT(LServerOrGlobal, From, To,
+ Request),
case acl:match_rule(LServerOrGlobal, configure, From) of
- deny ->
- {error, ?ERR_FORBIDDEN};
- allow ->
- adhoc_local_commands(From, To, Request)
+ deny -> {error, ?ERR_FORBIDDEN};
+ allow -> adhoc_local_commands(From, To, Request)
end).
-adhoc_local_commands(Acc, From, #jid{lserver = LServer} = To,
+adhoc_local_commands(Acc, From,
+ #jid{lserver = LServer} = To,
#adhoc_request{node = Node} = Request) ->
LNode = tokenize(Node),
case LNode of
- ["running nodes", _ENode, "DB"] ->
- ?COMMANDS_RESULT(global, From, To, Request);
- ["running nodes", _ENode, "modules", _] ->
- ?COMMANDS_RESULT(LServer, From, To, Request);
- ["running nodes", _ENode, "backup", _] ->
- ?COMMANDS_RESULT(global, From, To, Request);
- ["running nodes", _ENode, "import", _] ->
- ?COMMANDS_RESULT(global, From, To, Request);
- ["running nodes", _ENode, "restart"] ->
- ?COMMANDS_RESULT(global, From, To, Request);
- ["running nodes", _ENode, "shutdown"] ->
- ?COMMANDS_RESULT(global, From, To, Request);
- ["config", _] ->
- ?COMMANDS_RESULT(LServer, From, To, Request);
- ?NS_ADMINL(_) ->
- ?COMMANDS_RESULT(LServer, From, To, Request);
- _ ->
- Acc
+ [<<"running nodes">>, _ENode, <<"DB">>] ->
+ ?COMMANDS_RESULT(global, From, To, Request);
+ [<<"running nodes">>, _ENode, <<"modules">>, _] ->
+ ?COMMANDS_RESULT(LServer, From, To, Request);
+ [<<"running nodes">>, _ENode, <<"backup">>, _] ->
+ ?COMMANDS_RESULT(global, From, To, Request);
+ [<<"running nodes">>, _ENode, <<"import">>, _] ->
+ ?COMMANDS_RESULT(global, From, To, Request);
+ [<<"running nodes">>, _ENode, <<"restart">>] ->
+ ?COMMANDS_RESULT(global, From, To, Request);
+ [<<"running nodes">>, _ENode, <<"shutdown">>] ->
+ ?COMMANDS_RESULT(global, From, To, Request);
+ [<<"config">>, _] ->
+ ?COMMANDS_RESULT(LServer, From, To, Request);
+ ?NS_ADMINL(_) ->
+ ?COMMANDS_RESULT(LServer, From, To, Request);
+ _ -> Acc
end.
-adhoc_local_commands(From, #jid{lserver = LServer} = _To,
- #adhoc_request{lang = Lang,
- node = Node,
- sessionid = SessionID,
- action = Action,
- xdata = XData} = Request) ->
+adhoc_local_commands(From,
+ #jid{lserver = LServer} = _To,
+ #adhoc_request{lang = Lang, node = Node,
+ sessionid = SessionID, action = Action,
+ xdata = XData} =
+ Request) ->
LNode = tokenize(Node),
- %% If the "action" attribute is not present, it is
- %% understood as "execute". If there was no <actions/>
- %% element in the first response (which there isn't in our
- %% case), "execute" and "complete" are equivalent.
ActionIsExecute = lists:member(Action,
- ["", "execute", "complete"]),
- if Action == "cancel" ->
- %% User cancels request
- adhoc:produce_response(
- Request,
- #adhoc_response{status = canceled});
- XData == false, ActionIsExecute ->
- %% User requests form
- case get_form(LServer, LNode, Lang) of
- {result, Form} ->
- adhoc:produce_response(
- Request,
- #adhoc_response{status = executing,
- elements = Form});
- {result, Status, Form} ->
- adhoc:produce_response(
- Request,
- #adhoc_response{status = Status,
- elements = Form});
- {error, Error} ->
- {error, Error}
- end;
- XData /= false, ActionIsExecute ->
- %% User returns form.
- case jlib:parse_xdata_submit(XData) of
- invalid ->
- {error, ?ERR_BAD_REQUEST};
- Fields ->
- case catch set_form(From, LServer, LNode, Lang, Fields) of
- {result, Res} ->
- adhoc:produce_response(
- #adhoc_response{lang = Lang,
- node = Node,
- sessionid = SessionID,
- elements = Res,
- status = completed});
- {'EXIT', _} ->
- {error, ?ERR_BAD_REQUEST};
- {error, Error} ->
- {error, Error}
- end
- end;
- true ->
- {error, ?ERR_BAD_REQUEST}
+ [<<"">>, <<"execute">>, <<"complete">>]),
+ if Action == <<"cancel">> ->
+ adhoc:produce_response(Request,
+ #adhoc_response{status = canceled});
+ XData == false, ActionIsExecute ->
+ case get_form(LServer, LNode, Lang) of
+ {result, Form} ->
+ adhoc:produce_response(Request,
+ #adhoc_response{status = executing,
+ elements = Form});
+ {result, Status, Form} ->
+ adhoc:produce_response(Request,
+ #adhoc_response{status = Status,
+ elements = Form});
+ {error, Error} -> {error, Error}
+ end;
+ XData /= false, ActionIsExecute ->
+ case jlib:parse_xdata_submit(XData) of
+ invalid -> {error, ?ERR_BAD_REQUEST};
+ Fields ->
+ case catch set_form(From, LServer, LNode, Lang, Fields)
+ of
+ {result, Res} ->
+ adhoc:produce_response(#adhoc_response{lang = Lang,
+ node = Node,
+ sessionid =
+ SessionID,
+ elements = Res,
+ status =
+ completed});
+ {'EXIT', _} -> {error, ?ERR_BAD_REQUEST};
+ {error, Error} -> {error, Error}
+ end
+ end;
+ true -> {error, ?ERR_BAD_REQUEST}
end.
-
-define(TVFIELD(Type, Var, Val),
- {xmlelement, "field", [{"type", Type},
- {"var", Var}],
- [{xmlelement, "value", [], [{xmlcdata, Val}]}]}).
--define(HFIELD(), ?TVFIELD("hidden", "FORM_TYPE", ?NS_ADMIN)).
+ #xmlel{name = <<"field">>,
+ attrs = [{<<"type">>, Type}, {<<"var">>, Var}],
+ children =
+ [#xmlel{name = <<"value">>, attrs = [],
+ children = [{xmlcdata, Val}]}]}).
+
+-define(HFIELD(),
+ ?TVFIELD(<<"hidden">>, <<"FORM_TYPE">>, (?NS_ADMIN))).
-define(TLFIELD(Type, Label, Var),
- {xmlelement, "field", [{"type", Type},
- {"label", ?T(Lang, Label)},
- {"var", Var}], []}).
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"type">>, Type}, {<<"label">>, ?T(Lang, Label)},
+ {<<"var">>, Var}],
+ children = []}).
-define(XFIELD(Type, Label, Var, Val),
- {xmlelement, "field", [{"type", Type},
- {"label", ?T(Lang, Label)},
- {"var", Var}],
- [{xmlelement, "value", [], [{xmlcdata, Val}]}]}).
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"type">>, Type}, {<<"label">>, ?T(Lang, Label)},
+ {<<"var">>, Var}],
+ children =
+ [#xmlel{name = <<"value">>, attrs = [],
+ children = [{xmlcdata, Val}]}]}).
-define(XMFIELD(Type, Label, Var, Vals),
- {xmlelement, "field", [{"type", Type},
- {"label", ?T(Lang, Label)},
- {"var", Var}],
- [{xmlelement, "value", [], [{xmlcdata,Val}]} || Val <- Vals]}).
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"type">>, Type}, {<<"label">>, ?T(Lang, Label)},
+ {<<"var">>, Var}],
+ children =
+ [#xmlel{name = <<"value">>, attrs = [],
+ children = [{xmlcdata, Val}]}
+ || Val <- Vals]}).
-define(TABLEFIELD(Table, Val),
- {xmlelement, "field", [{"type", "list-single"},
- {"label", atom_to_list(Table)},
- {"var", atom_to_list(Table)}],
- [{xmlelement, "value", [], [{xmlcdata, atom_to_list(Val)}]},
- {xmlelement, "option", [{"label",
- ?T(Lang, "RAM copy")}],
- [{xmlelement, "value", [], [{xmlcdata, "ram_copies"}]}]},
- {xmlelement, "option", [{"label",
- ?T(Lang,
- "RAM and disc copy")}],
- [{xmlelement, "value", [], [{xmlcdata, "disc_copies"}]}]},
- {xmlelement, "option", [{"label",
- ?T(Lang,
- "Disc only copy")}],
- [{xmlelement, "value", [], [{xmlcdata, "disc_only_copies"}]}]},
- {xmlelement, "option", [{"label",
- ?T(Lang, "Remote copy")}],
- [{xmlelement, "value", [], [{xmlcdata, "unknown"}]}]}
- ]}).
-
-
-
-get_form(_Host, ["running nodes", ENode, "DB"], Lang) ->
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"type">>, <<"list-single">>},
+ {<<"label">>, iolist_to_binary(atom_to_list(Table))},
+ {<<"var">>, iolist_to_binary(atom_to_list(Table))}],
+ children =
+ [#xmlel{name = <<"value">>, attrs = [],
+ children =
+ [{xmlcdata,
+ iolist_to_binary(atom_to_list(Val))}]},
+ #xmlel{name = <<"option">>,
+ attrs = [{<<"label">>, ?T(Lang, <<"RAM copy">>)}],
+ children =
+ [#xmlel{name = <<"value">>, attrs = [],
+ children =
+ [{xmlcdata, <<"ram_copies">>}]}]},
+ #xmlel{name = <<"option">>,
+ attrs =
+ [{<<"label">>,
+ ?T(Lang, <<"RAM and disc copy">>)}],
+ children =
+ [#xmlel{name = <<"value">>, attrs = [],
+ children =
+ [{xmlcdata, <<"disc_copies">>}]}]},
+ #xmlel{name = <<"option">>,
+ attrs =
+ [{<<"label">>, ?T(Lang, <<"Disc only copy">>)}],
+ children =
+ [#xmlel{name = <<"value">>, attrs = [],
+ children =
+ [{xmlcdata,
+ <<"disc_only_copies">>}]}]},
+ #xmlel{name = <<"option">>,
+ attrs = [{<<"label">>, ?T(Lang, <<"Remote copy">>)}],
+ children =
+ [#xmlel{name = <<"value">>, attrs = [],
+ children =
+ [{xmlcdata, <<"unknown">>}]}]}]}).
+
+get_form(_Host, [<<"running nodes">>, ENode, <<"DB">>],
+ Lang) ->
case search_running_node(ENode) of
- false ->
- {error, ?ERR_ITEM_NOT_FOUND};
- Node ->
- case rpc:call(Node, mnesia, system_info, [tables]) of
- {badrpc, _Reason} ->
- {error, ?ERR_INTERNAL_SERVER_ERROR};
- Tables ->
- STables = lists:sort(Tables),
- {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}],
- [?HFIELD(),
- {xmlelement, "title", [],
- [{xmlcdata,
- ?T(
- Lang, "Database Tables Configuration at ") ++
- ENode}]},
- {xmlelement, "instructions", [],
- [{xmlcdata,
- ?T(
- Lang, "Choose storage type of tables")}]} |
- lists:map(
- fun(Table) ->
- case rpc:call(Node,
- mnesia,
- table_info,
- [Table, storage_type]) of
- {badrpc, _} ->
- ?TABLEFIELD(Table, unknown);
- Type ->
- ?TABLEFIELD(Table, Type)
- end
- end, STables)
- ]}]}
- end
+ false -> {error, ?ERR_ITEM_NOT_FOUND};
+ Node ->
+ case rpc:call(Node, mnesia, system_info, [tables]) of
+ {badrpc, _Reason} ->
+ {error, ?ERR_INTERNAL_SERVER_ERROR};
+ Tables ->
+ STables = lists:sort(Tables),
+ {result,
+ [#xmlel{name = <<"x">>,
+ attrs = [{<<"xmlns">>, ?NS_XDATA}],
+ children =
+ [?HFIELD(),
+ #xmlel{name = <<"title">>, attrs = [],
+ children =
+ [{xmlcdata,
+ <<(?T(Lang,
+ <<"Database Tables Configuration at ">>))/binary,
+ ENode/binary>>}]},
+ #xmlel{name = <<"instructions">>, attrs = [],
+ children =
+ [{xmlcdata,
+ ?T(Lang,
+ <<"Choose storage type of tables">>)}]}
+ | lists:map(fun (Table) ->
+ case rpc:call(Node, mnesia,
+ table_info,
+ [Table,
+ storage_type])
+ of
+ {badrpc, _} ->
+ ?TABLEFIELD(Table,
+ unknown);
+ Type ->
+ ?TABLEFIELD(Table, Type)
+ end
+ end,
+ STables)]}]}
+ end
end;
-
-get_form(Host, ["running nodes", ENode, "modules", "stop"], Lang) ->
+get_form(Host,
+ [<<"running nodes">>, ENode, <<"modules">>, <<"stop">>],
+ Lang) ->
case search_running_node(ENode) of
- false ->
- {error, ?ERR_ITEM_NOT_FOUND};
- Node ->
- case rpc:call(Node, gen_mod, loaded_modules, [Host]) of
- {badrpc, _Reason} ->
- {error, ?ERR_INTERNAL_SERVER_ERROR};
- Modules ->
- SModules = lists:sort(Modules),
- {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}],
- [?HFIELD(),
- {xmlelement, "title", [],
- [{xmlcdata,
- ?T(
- Lang, "Stop Modules at ") ++ ENode}]},
- {xmlelement, "instructions", [],
- [{xmlcdata,
- ?T(
- Lang, "Choose modules to stop")}]} |
- lists:map(fun(M) ->
- S = atom_to_list(M),
- ?XFIELD("boolean", S, S, "0")
- end, SModules)
- ]}]}
- end
+ false -> {error, ?ERR_ITEM_NOT_FOUND};
+ Node ->
+ case rpc:call(Node, gen_mod, loaded_modules, [Host]) of
+ {badrpc, _Reason} ->
+ {error, ?ERR_INTERNAL_SERVER_ERROR};
+ Modules ->
+ SModules = lists:sort(Modules),
+ {result,
+ [#xmlel{name = <<"x">>,
+ attrs = [{<<"xmlns">>, ?NS_XDATA}],
+ children =
+ [?HFIELD(),
+ #xmlel{name = <<"title">>, attrs = [],
+ children =
+ [{xmlcdata,
+ <<(?T(Lang,
+ <<"Stop Modules at ">>))/binary,
+ ENode/binary>>}]},
+ #xmlel{name = <<"instructions">>, attrs = [],
+ children =
+ [{xmlcdata,
+ ?T(Lang,
+ <<"Choose modules to stop">>)}]}
+ | lists:map(fun (M) ->
+ S = jlib:atom_to_binary(M),
+ ?XFIELD(<<"boolean">>, S, S,
+ <<"0">>)
+ end,
+ SModules)]}]}
+ end
end;
-
-get_form(_Host, ["running nodes", ENode, "modules", "start"], Lang) ->
- {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}],
- [?HFIELD(),
- {xmlelement, "title", [],
- [{xmlcdata,
- ?T(
- Lang, "Start Modules at ") ++ ENode}]},
- {xmlelement, "instructions", [],
- [{xmlcdata,
- ?T(
- Lang, "Enter list of {Module, [Options]}")}]},
- ?XFIELD("text-multi", "List of modules to start", "modules", "[].")
- ]}]};
-
-get_form(_Host, ["running nodes", ENode, "backup", "backup"], Lang) ->
- {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}],
- [?HFIELD(),
- {xmlelement, "title", [],
- [{xmlcdata,
- ?T(
- Lang, "Backup to File at ") ++ ENode}]},
- {xmlelement, "instructions", [],
- [{xmlcdata,
- ?T(
- Lang, "Enter path to backup file")}]},
- ?XFIELD("text-single", "Path to File", "path", "")
- ]}]};
-
-get_form(_Host, ["running nodes", ENode, "backup", "restore"], Lang) ->
- {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}],
- [?HFIELD(),
- {xmlelement, "title", [],
- [{xmlcdata,
- ?T(
- Lang, "Restore Backup from File at ") ++ ENode}]},
- {xmlelement, "instructions", [],
- [{xmlcdata,
- ?T(
- Lang, "Enter path to backup file")}]},
- ?XFIELD("text-single", "Path to File", "path", "")
- ]}]};
-
-get_form(_Host, ["running nodes", ENode, "backup", "textfile"], Lang) ->
- {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}],
- [?HFIELD(),
- {xmlelement, "title", [],
- [{xmlcdata,
- ?T(
- Lang, "Dump Backup to Text File at ") ++ ENode}]},
- {xmlelement, "instructions", [],
- [{xmlcdata,
- ?T(
- Lang, "Enter path to text file")}]},
- ?XFIELD("text-single", "Path to File", "path", "")
- ]}]};
-
-get_form(_Host, ["running nodes", ENode, "import", "file"], Lang) ->
- {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}],
- [?HFIELD(),
- {xmlelement, "title", [],
- [{xmlcdata,
- ?T(
- Lang, "Import User from File at ") ++ ENode}]},
- {xmlelement, "instructions", [],
- [{xmlcdata,
- ?T(
- Lang, "Enter path to jabberd14 spool file")}]},
- ?XFIELD("text-single", "Path to File", "path", "")
- ]}]};
-
-get_form(_Host, ["running nodes", ENode, "import", "dir"], Lang) ->
- {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}],
- [?HFIELD(),
- {xmlelement, "title", [],
- [{xmlcdata,
- ?T(
- Lang, "Import Users from Dir at ") ++ ENode}]},
- {xmlelement, "instructions", [],
- [{xmlcdata,
- ?T(
- Lang, "Enter path to jabberd14 spool dir")}]},
- ?XFIELD("text-single", "Path to Dir", "path", "")
- ]}]};
-
-get_form(_Host, ["running nodes", _ENode, "restart"], Lang) ->
- Make_option =
- fun(LabelNum, LabelUnit, Value)->
- {xmlelement, "option",
- [{"label", LabelNum ++ ?T(Lang, LabelUnit)}],
- [{xmlelement, "value", [], [{xmlcdata, Value}]}]}
- end,
- {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}],
- [?HFIELD(),
- {xmlelement, "title", [],
- [{xmlcdata, ?T(Lang, "Restart Service")}]},
- {xmlelement, "field",
- [{"type", "list-single"},
- {"label", ?T(Lang, "Time delay")},
- {"var", "delay"}],
- [Make_option("", "immediately", "1"),
- Make_option("15 ", "seconds", "15"),
- Make_option("30 ", "seconds", "30"),
- Make_option("60 ", "seconds", "60"),
- Make_option("90 ", "seconds", "90"),
- Make_option("2 ", "minutes", "120"),
- Make_option("3 ", "minutes", "180"),
- Make_option("4 ", "minutes", "240"),
- Make_option("5 ", "minutes", "300"),
- Make_option("10 ", "minutes", "600"),
- Make_option("15 ", "minutes", "900"),
- Make_option("30 ", "minutes", "1800"),
- {xmlelement, "required", [], []}
- ]},
- {xmlelement, "field",
- [{"type", "fixed"},
- {"label", ?T(Lang, "Send announcement to all online users on all hosts")}],
- []},
- {xmlelement, "field",
- [{"var", "subject"},
- {"type", "text-single"},
- {"label", ?T(Lang, "Subject")}],
- []},
- {xmlelement, "field",
- [{"var", "announcement"},
- {"type", "text-multi"},
- {"label", ?T(Lang, "Message body")}],
- []}
- ]}]};
-
-get_form(_Host, ["running nodes", _ENode, "shutdown"], Lang) ->
- Make_option =
- fun(LabelNum, LabelUnit, Value)->
- {xmlelement, "option",
- [{"label", LabelNum ++ ?T(Lang, LabelUnit)}],
- [{xmlelement, "value", [], [{xmlcdata, Value}]}]}
- end,
- {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}],
- [?HFIELD(),
- {xmlelement, "title", [],
- [{xmlcdata, ?T(Lang, "Shut Down Service")}]},
- {xmlelement, "field",
- [{"type", "list-single"},
- {"label", ?T(Lang, "Time delay")},
- {"var", "delay"}],
- [Make_option("", "immediately", "1"),
- Make_option("15 ", "seconds", "15"),
- Make_option("30 ", "seconds", "30"),
- Make_option("60 ", "seconds", "60"),
- Make_option("90 ", "seconds", "90"),
- Make_option("2 ", "minutes", "120"),
- Make_option("3 ", "minutes", "180"),
- Make_option("4 ", "minutes", "240"),
- Make_option("5 ", "minutes", "300"),
- Make_option("10 ", "minutes", "600"),
- Make_option("15 ", "minutes", "900"),
- Make_option("30 ", "minutes", "1800"),
- {xmlelement, "required", [], []}
- ]},
- {xmlelement, "field",
- [{"type", "fixed"},
- {"label", ?T(Lang, "Send announcement to all online users on all hosts")}],
- []},
- {xmlelement, "field",
- [{"var", "subject"},
- {"type", "text-single"},
- {"label", ?T(Lang, "Subject")}],
- []},
- {xmlelement, "field",
- [{"var", "announcement"},
- {"type", "text-multi"},
- {"label", ?T(Lang, "Message body")}],
- []}
- ]}]};
-
-get_form(Host, ["config", "acls"], Lang) ->
- {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}],
- [?HFIELD(),
- {xmlelement, "title", [],
- [{xmlcdata,
- ?T(
- Lang, "Access Control List Configuration")}]},
- {xmlelement, "field", [{"type", "text-multi"},
- {"label",
- ?T(
- Lang, "Access control lists")},
- {"var", "acls"}],
- lists:map(fun(S) ->
- {xmlelement, "value", [], [{xmlcdata, S}]}
- end,
- string:tokens(
- lists:flatten(
- io_lib:format(
- "~p.",
- [ets:select(acl,
- [{{acl, {'$1', '$2'}, '$3'},
- [{'==', '$2', Host}],
- [{{acl, '$1', '$3'}}]}])
- ])),
- "\n"))
- }
- ]}]};
-
-get_form(Host, ["config", "access"], Lang) ->
- {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}],
- [?HFIELD(),
- {xmlelement, "title", [],
- [{xmlcdata,
- ?T(
- Lang, "Access Configuration")}]},
- {xmlelement, "field", [{"type", "text-multi"},
- {"label",
- ?T(
- Lang, "Access rules")},
- {"var", "access"}],
- lists:map(fun(S) ->
- {xmlelement, "value", [], [{xmlcdata, S}]}
- end,
- string:tokens(
- lists:flatten(
- io_lib:format(
- "~p.",
- [ets:select(config,
- [{{config, {access, '$1', '$2'}, '$3'},
- [{'==', '$2', Host}],
- [{{access, '$1', '$3'}}]}])
- ])),
- "\n"))
- }
- ]}]};
-
-get_form(_Host, ?NS_ADMINL("add-user"), Lang) ->
- {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}],
- [?HFIELD(),
- {xmlelement, "title", [],
- [{xmlcdata, ?T(Lang, "Add User")}]},
- {xmlelement, "field",
- [{"type", "jid-single"},
- {"label", ?T(Lang, "Jabber ID")},
- {"var", "accountjid"}],
- [{xmlelement, "required", [], []}]},
- {xmlelement, "field",
- [{"type", "text-private"},
- {"label", ?T(Lang, "Password")},
- {"var", "password"}],
- [{xmlelement, "required", [], []}]},
- {xmlelement, "field",
- [{"type", "text-private"},
- {"label", ?T(Lang, "Password Verification")},
- {"var", "password-verify"}],
- [{xmlelement, "required", [], []}]}
- ]}]};
-
-get_form(_Host, ?NS_ADMINL("delete-user"), Lang) ->
- {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}],
- [?HFIELD(),
- {xmlelement, "title", [],
- [{xmlcdata, ?T(Lang, "Delete User")}]},
- {xmlelement, "field",
- [{"type", "jid-multi"},
- {"label", ?T(Lang, "Jabber ID")},
- {"var", "accountjids"}],
- [{xmlelement, "required", [], []}]}
- ]}]};
-
-get_form(_Host, ?NS_ADMINL("end-user-session"), Lang) ->
- {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}],
- [?HFIELD(),
- {xmlelement, "title", [],
- [{xmlcdata, ?T(Lang, "End User Session")}]},
- {xmlelement, "field",
- [{"type", "jid-single"},
- {"label", ?T(Lang, "Jabber ID")},
- {"var", "accountjid"}],
- [{xmlelement, "required", [], []}]}
- ]}]};
-
-get_form(_Host, ?NS_ADMINL("get-user-password"), Lang) ->
- {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}],
- [?HFIELD(),
- {xmlelement, "title", [],
- [{xmlcdata, ?T(Lang, "Get User Password")}]},
- {xmlelement, "field",
- [{"type", "jid-single"},
- {"label", ?T(Lang, "Jabber ID")},
- {"var", "accountjid"}],
- [{xmlelement, "required", [], []}]}
- ]}]};
-
-get_form(_Host, ?NS_ADMINL("change-user-password"), Lang) ->
- {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}],
- [?HFIELD(),
- {xmlelement, "title", [],
- [{xmlcdata, ?T(Lang, "Get User Password")}]},
- {xmlelement, "field",
- [{"type", "jid-single"},
- {"label", ?T(Lang, "Jabber ID")},
- {"var", "accountjid"}],
- [{xmlelement, "required", [], []}]},
- {xmlelement, "field",
- [{"type", "text-private"},
- {"label", ?T(Lang, "Password")},
- {"var", "password"}],
- [{xmlelement, "required", [], []}]}
- ]}]};
-
-get_form(_Host, ?NS_ADMINL("get-user-lastlogin"), Lang) ->
- {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}],
- [?HFIELD(),
- {xmlelement, "title", [],
- [{xmlcdata, ?T(Lang, "Get User Last Login Time")}]},
- {xmlelement, "field",
- [{"type", "jid-single"},
- {"label", ?T(Lang, "Jabber ID")},
- {"var", "accountjid"}],
- [{xmlelement, "required", [], []}]}
- ]}]};
-
-get_form(_Host, ?NS_ADMINL("user-stats"), Lang) ->
- {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}],
- [?HFIELD(),
- {xmlelement, "title", [],
- [{xmlcdata, ?T(Lang, "Get User Statistics")}]},
- {xmlelement, "field",
- [{"type", "jid-single"},
- {"label", ?T(Lang, "Jabber ID")},
- {"var", "accountjid"}],
- [{xmlelement, "required", [], []}]}
- ]}]};
-
-get_form(Host, ?NS_ADMINL("get-registered-users-num"), Lang) ->
- [Num] = io_lib:format("~p", [ejabberd_auth:get_vh_registered_users_number(Host)]),
+get_form(_Host,
+ [<<"running nodes">>, ENode, <<"modules">>,
+ <<"start">>],
+ Lang) ->
+ {result,
+ [#xmlel{name = <<"x">>,
+ attrs = [{<<"xmlns">>, ?NS_XDATA}],
+ children =
+ [?HFIELD(),
+ #xmlel{name = <<"title">>, attrs = [],
+ children =
+ [{xmlcdata,
+ <<(?T(Lang, <<"Start Modules at ">>))/binary,
+ ENode/binary>>}]},
+ #xmlel{name = <<"instructions">>, attrs = [],
+ children =
+ [{xmlcdata,
+ ?T(Lang,
+ <<"Enter list of {Module, [Options]}">>)}]},
+ ?XFIELD(<<"text-multi">>,
+ <<"List of modules to start">>, <<"modules">>,
+ <<"[].">>)]}]};
+get_form(_Host,
+ [<<"running nodes">>, ENode, <<"backup">>,
+ <<"backup">>],
+ Lang) ->
+ {result,
+ [#xmlel{name = <<"x">>,
+ attrs = [{<<"xmlns">>, ?NS_XDATA}],
+ children =
+ [?HFIELD(),
+ #xmlel{name = <<"title">>, attrs = [],
+ children =
+ [{xmlcdata,
+ <<(?T(Lang, <<"Backup to File at ">>))/binary,
+ ENode/binary>>}]},
+ #xmlel{name = <<"instructions">>, attrs = [],
+ children =
+ [{xmlcdata,
+ ?T(Lang, <<"Enter path to backup file">>)}]},
+ ?XFIELD(<<"text-single">>, <<"Path to File">>,
+ <<"path">>, <<"">>)]}]};
+get_form(_Host,
+ [<<"running nodes">>, ENode, <<"backup">>,
+ <<"restore">>],
+ Lang) ->
+ {result,
+ [#xmlel{name = <<"x">>,
+ attrs = [{<<"xmlns">>, ?NS_XDATA}],
+ children =
+ [?HFIELD(),
+ #xmlel{name = <<"title">>, attrs = [],
+ children =
+ [{xmlcdata,
+ <<(?T(Lang,
+ <<"Restore Backup from File at ">>))/binary,
+ ENode/binary>>}]},
+ #xmlel{name = <<"instructions">>, attrs = [],
+ children =
+ [{xmlcdata,
+ ?T(Lang, <<"Enter path to backup file">>)}]},
+ ?XFIELD(<<"text-single">>, <<"Path to File">>,
+ <<"path">>, <<"">>)]}]};
+get_form(_Host,
+ [<<"running nodes">>, ENode, <<"backup">>,
+ <<"textfile">>],
+ Lang) ->
+ {result,
+ [#xmlel{name = <<"x">>,
+ attrs = [{<<"xmlns">>, ?NS_XDATA}],
+ children =
+ [?HFIELD(),
+ #xmlel{name = <<"title">>, attrs = [],
+ children =
+ [{xmlcdata,
+ <<(?T(Lang,
+ <<"Dump Backup to Text File at ">>))/binary,
+ ENode/binary>>}]},
+ #xmlel{name = <<"instructions">>, attrs = [],
+ children =
+ [{xmlcdata,
+ ?T(Lang, <<"Enter path to text file">>)}]},
+ ?XFIELD(<<"text-single">>, <<"Path to File">>,
+ <<"path">>, <<"">>)]}]};
+get_form(_Host,
+ [<<"running nodes">>, ENode, <<"import">>, <<"file">>],
+ Lang) ->
+ {result,
+ [#xmlel{name = <<"x">>,
+ attrs = [{<<"xmlns">>, ?NS_XDATA}],
+ children =
+ [?HFIELD(),
+ #xmlel{name = <<"title">>, attrs = [],
+ children =
+ [{xmlcdata,
+ <<(?T(Lang,
+ <<"Import User from File at ">>))/binary,
+ ENode/binary>>}]},
+ #xmlel{name = <<"instructions">>, attrs = [],
+ children =
+ [{xmlcdata,
+ ?T(Lang,
+ <<"Enter path to jabberd14 spool file">>)}]},
+ ?XFIELD(<<"text-single">>, <<"Path to File">>,
+ <<"path">>, <<"">>)]}]};
+get_form(_Host,
+ [<<"running nodes">>, ENode, <<"import">>, <<"dir">>],
+ Lang) ->
+ {result,
+ [#xmlel{name = <<"x">>,
+ attrs = [{<<"xmlns">>, ?NS_XDATA}],
+ children =
+ [?HFIELD(),
+ #xmlel{name = <<"title">>, attrs = [],
+ children =
+ [{xmlcdata,
+ <<(?T(Lang,
+ <<"Import Users from Dir at ">>))/binary,
+ ENode/binary>>}]},
+ #xmlel{name = <<"instructions">>, attrs = [],
+ children =
+ [{xmlcdata,
+ ?T(Lang,
+ <<"Enter path to jabberd14 spool dir">>)}]},
+ ?XFIELD(<<"text-single">>, <<"Path to Dir">>,
+ <<"path">>, <<"">>)]}]};
+get_form(_Host,
+ [<<"running nodes">>, _ENode, <<"restart">>], Lang) ->
+ Make_option = fun (LabelNum, LabelUnit, Value) ->
+ #xmlel{name = <<"option">>,
+ attrs =
+ [{<<"label">>,
+ <<LabelNum/binary,
+ (?T(Lang, LabelUnit))/binary>>}],
+ children =
+ [#xmlel{name = <<"value">>, attrs = [],
+ children = [{xmlcdata, Value}]}]}
+ end,
+ {result,
+ [#xmlel{name = <<"x">>,
+ attrs = [{<<"xmlns">>, ?NS_XDATA}],
+ children =
+ [?HFIELD(),
+ #xmlel{name = <<"title">>, attrs = [],
+ children =
+ [{xmlcdata, ?T(Lang, <<"Restart Service">>)}]},
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"type">>, <<"list-single">>},
+ {<<"label">>, ?T(Lang, <<"Time delay">>)},
+ {<<"var">>, <<"delay">>}],
+ children =
+ [Make_option(<<"">>, <<"immediately">>, <<"1">>),
+ Make_option(<<"15 ">>, <<"seconds">>, <<"15">>),
+ Make_option(<<"30 ">>, <<"seconds">>, <<"30">>),
+ Make_option(<<"60 ">>, <<"seconds">>, <<"60">>),
+ Make_option(<<"90 ">>, <<"seconds">>, <<"90">>),
+ Make_option(<<"2 ">>, <<"minutes">>, <<"120">>),
+ Make_option(<<"3 ">>, <<"minutes">>, <<"180">>),
+ Make_option(<<"4 ">>, <<"minutes">>, <<"240">>),
+ Make_option(<<"5 ">>, <<"minutes">>, <<"300">>),
+ Make_option(<<"10 ">>, <<"minutes">>, <<"600">>),
+ Make_option(<<"15 ">>, <<"minutes">>, <<"900">>),
+ Make_option(<<"30 ">>, <<"minutes">>, <<"1800">>),
+ #xmlel{name = <<"required">>, attrs = [],
+ children = []}]},
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"type">>, <<"fixed">>},
+ {<<"label">>,
+ ?T(Lang,
+ <<"Send announcement to all online users "
+ "on all hosts">>)}],
+ children = []},
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"var">>, <<"subject">>},
+ {<<"type">>, <<"text-single">>},
+ {<<"label">>, ?T(Lang, <<"Subject">>)}],
+ children = []},
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"var">>, <<"announcement">>},
+ {<<"type">>, <<"text-multi">>},
+ {<<"label">>, ?T(Lang, <<"Message body">>)}],
+ children = []}]}]};
+get_form(_Host,
+ [<<"running nodes">>, _ENode, <<"shutdown">>], Lang) ->
+ Make_option = fun (LabelNum, LabelUnit, Value) ->
+ #xmlel{name = <<"option">>,
+ attrs =
+ [{<<"label">>,
+ <<LabelNum/binary,
+ (?T(Lang, LabelUnit))/binary>>}],
+ children =
+ [#xmlel{name = <<"value">>, attrs = [],
+ children = [{xmlcdata, Value}]}]}
+ end,
+ {result,
+ [#xmlel{name = <<"x">>,
+ attrs = [{<<"xmlns">>, ?NS_XDATA}],
+ children =
+ [?HFIELD(),
+ #xmlel{name = <<"title">>, attrs = [],
+ children =
+ [{xmlcdata, ?T(Lang, <<"Shut Down Service">>)}]},
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"type">>, <<"list-single">>},
+ {<<"label">>, ?T(Lang, <<"Time delay">>)},
+ {<<"var">>, <<"delay">>}],
+ children =
+ [Make_option(<<"">>, <<"immediately">>, <<"1">>),
+ Make_option(<<"15 ">>, <<"seconds">>, <<"15">>),
+ Make_option(<<"30 ">>, <<"seconds">>, <<"30">>),
+ Make_option(<<"60 ">>, <<"seconds">>, <<"60">>),
+ Make_option(<<"90 ">>, <<"seconds">>, <<"90">>),
+ Make_option(<<"2 ">>, <<"minutes">>, <<"120">>),
+ Make_option(<<"3 ">>, <<"minutes">>, <<"180">>),
+ Make_option(<<"4 ">>, <<"minutes">>, <<"240">>),
+ Make_option(<<"5 ">>, <<"minutes">>, <<"300">>),
+ Make_option(<<"10 ">>, <<"minutes">>, <<"600">>),
+ Make_option(<<"15 ">>, <<"minutes">>, <<"900">>),
+ Make_option(<<"30 ">>, <<"minutes">>, <<"1800">>),
+ #xmlel{name = <<"required">>, attrs = [],
+ children = []}]},
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"type">>, <<"fixed">>},
+ {<<"label">>,
+ ?T(Lang,
+ <<"Send announcement to all online users "
+ "on all hosts">>)}],
+ children = []},
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"var">>, <<"subject">>},
+ {<<"type">>, <<"text-single">>},
+ {<<"label">>, ?T(Lang, <<"Subject">>)}],
+ children = []},
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"var">>, <<"announcement">>},
+ {<<"type">>, <<"text-multi">>},
+ {<<"label">>, ?T(Lang, <<"Message body">>)}],
+ children = []}]}]};
+get_form(Host, [<<"config">>, <<"acls">>], Lang) ->
+ {result,
+ [#xmlel{name = <<"x">>,
+ attrs = [{<<"xmlns">>, ?NS_XDATA}],
+ children =
+ [?HFIELD(),
+ #xmlel{name = <<"title">>, attrs = [],
+ children =
+ [{xmlcdata,
+ ?T(Lang,
+ <<"Access Control List Configuration">>)}]},
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"type">>, <<"text-multi">>},
+ {<<"label">>,
+ ?T(Lang, <<"Access control lists">>)},
+ {<<"var">>, <<"acls">>}],
+ children =
+ lists:map(fun (S) ->
+ #xmlel{name = <<"value">>,
+ attrs = [],
+ children =
+ [{xmlcdata, S}]}
+ end,
+ str:tokens(iolist_to_binary(io_lib:format("~p.",
+ [ets:select(acl,
+ [{{acl,
+ {'$1',
+ '$2'},
+ '$3'},
+ [{'==',
+ '$2',
+ Host}],
+ [{{acl,
+ '$1',
+ '$3'}}]}])])),
+ <<"\n">>))}]}]};
+get_form(Host, [<<"config">>, <<"access">>], Lang) ->
+ {result,
+ [#xmlel{name = <<"x">>,
+ attrs = [{<<"xmlns">>, ?NS_XDATA}],
+ children =
+ [?HFIELD(),
+ #xmlel{name = <<"title">>, attrs = [],
+ children =
+ [{xmlcdata,
+ ?T(Lang, <<"Access Configuration">>)}]},
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"type">>, <<"text-multi">>},
+ {<<"label">>, ?T(Lang, <<"Access rules">>)},
+ {<<"var">>, <<"access">>}],
+ children =
+ lists:map(fun (S) ->
+ #xmlel{name = <<"value">>,
+ attrs = [],
+ children =
+ [{xmlcdata, S}]}
+ end,
+ str:tokens(iolist_to_binary(io_lib:format("~p.",
+ [ets:select(config,
+ [{{config,
+ {access,
+ '$1',
+ '$2'},
+ '$3'},
+ [{'==',
+ '$2',
+ Host}],
+ [{{access,
+ '$1',
+ '$3'}}]}])])),
+ <<"\n">>))}]}]};
+get_form(_Host, ?NS_ADMINL(<<"add-user">>), Lang) ->
+ {result,
+ [#xmlel{name = <<"x">>,
+ attrs = [{<<"xmlns">>, ?NS_XDATA}],
+ children =
+ [?HFIELD(),
+ #xmlel{name = <<"title">>, attrs = [],
+ children = [{xmlcdata, ?T(Lang, <<"Add User">>)}]},
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"type">>, <<"jid-single">>},
+ {<<"label">>, ?T(Lang, <<"Jabber ID">>)},
+ {<<"var">>, <<"accountjid">>}],
+ children =
+ [#xmlel{name = <<"required">>, attrs = [],
+ children = []}]},
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"type">>, <<"text-private">>},
+ {<<"label">>, ?T(Lang, <<"Password">>)},
+ {<<"var">>, <<"password">>}],
+ children =
+ [#xmlel{name = <<"required">>, attrs = [],
+ children = []}]},
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"type">>, <<"text-private">>},
+ {<<"label">>,
+ ?T(Lang, <<"Password Verification">>)},
+ {<<"var">>, <<"password-verify">>}],
+ children =
+ [#xmlel{name = <<"required">>, attrs = [],
+ children = []}]}]}]};
+get_form(_Host, ?NS_ADMINL(<<"delete-user">>), Lang) ->
+ {result,
+ [#xmlel{name = <<"x">>,
+ attrs = [{<<"xmlns">>, ?NS_XDATA}],
+ children =
+ [?HFIELD(),
+ #xmlel{name = <<"title">>, attrs = [],
+ children = [{xmlcdata, ?T(Lang, <<"Delete User">>)}]},
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"type">>, <<"jid-multi">>},
+ {<<"label">>, ?T(Lang, <<"Jabber ID">>)},
+ {<<"var">>, <<"accountjids">>}],
+ children =
+ [#xmlel{name = <<"required">>, attrs = [],
+ children = []}]}]}]};
+get_form(_Host, ?NS_ADMINL(<<"end-user-session">>),
+ Lang) ->
+ {result,
+ [#xmlel{name = <<"x">>,
+ attrs = [{<<"xmlns">>, ?NS_XDATA}],
+ children =
+ [?HFIELD(),
+ #xmlel{name = <<"title">>, attrs = [],
+ children =
+ [{xmlcdata, ?T(Lang, <<"End User Session">>)}]},
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"type">>, <<"jid-single">>},
+ {<<"label">>, ?T(Lang, <<"Jabber ID">>)},
+ {<<"var">>, <<"accountjid">>}],
+ children =
+ [#xmlel{name = <<"required">>, attrs = [],
+ children = []}]}]}]};
+get_form(_Host, ?NS_ADMINL(<<"get-user-password">>),
+ Lang) ->
+ {result,
+ [#xmlel{name = <<"x">>,
+ attrs = [{<<"xmlns">>, ?NS_XDATA}],
+ children =
+ [?HFIELD(),
+ #xmlel{name = <<"title">>, attrs = [],
+ children =
+ [{xmlcdata, ?T(Lang, <<"Get User Password">>)}]},
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"type">>, <<"jid-single">>},
+ {<<"label">>, ?T(Lang, <<"Jabber ID">>)},
+ {<<"var">>, <<"accountjid">>}],
+ children =
+ [#xmlel{name = <<"required">>, attrs = [],
+ children = []}]}]}]};
+get_form(_Host, ?NS_ADMINL(<<"change-user-password">>),
+ Lang) ->
+ {result,
+ [#xmlel{name = <<"x">>,
+ attrs = [{<<"xmlns">>, ?NS_XDATA}],
+ children =
+ [?HFIELD(),
+ #xmlel{name = <<"title">>, attrs = [],
+ children =
+ [{xmlcdata, ?T(Lang, <<"Get User Password">>)}]},
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"type">>, <<"jid-single">>},
+ {<<"label">>, ?T(Lang, <<"Jabber ID">>)},
+ {<<"var">>, <<"accountjid">>}],
+ children =
+ [#xmlel{name = <<"required">>, attrs = [],
+ children = []}]},
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"type">>, <<"text-private">>},
+ {<<"label">>, ?T(Lang, <<"Password">>)},
+ {<<"var">>, <<"password">>}],
+ children =
+ [#xmlel{name = <<"required">>, attrs = [],
+ children = []}]}]}]};
+get_form(_Host, ?NS_ADMINL(<<"get-user-lastlogin">>),
+ Lang) ->
+ {result,
+ [#xmlel{name = <<"x">>,
+ attrs = [{<<"xmlns">>, ?NS_XDATA}],
+ children =
+ [?HFIELD(),
+ #xmlel{name = <<"title">>, attrs = [],
+ children =
+ [{xmlcdata,
+ ?T(Lang, <<"Get User Last Login Time">>)}]},
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"type">>, <<"jid-single">>},
+ {<<"label">>, ?T(Lang, <<"Jabber ID">>)},
+ {<<"var">>, <<"accountjid">>}],
+ children =
+ [#xmlel{name = <<"required">>, attrs = [],
+ children = []}]}]}]};
+get_form(_Host, ?NS_ADMINL(<<"user-stats">>), Lang) ->
+ {result,
+ [#xmlel{name = <<"x">>,
+ attrs = [{<<"xmlns">>, ?NS_XDATA}],
+ children =
+ [?HFIELD(),
+ #xmlel{name = <<"title">>, attrs = [],
+ children =
+ [{xmlcdata, ?T(Lang, <<"Get User Statistics">>)}]},
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"type">>, <<"jid-single">>},
+ {<<"label">>, ?T(Lang, <<"Jabber ID">>)},
+ {<<"var">>, <<"accountjid">>}],
+ children =
+ [#xmlel{name = <<"required">>, attrs = [],
+ children = []}]}]}]};
+get_form(Host,
+ ?NS_ADMINL(<<"get-registered-users-num">>), Lang) ->
+ Num = list_to_binary(
+ io_lib:format("~p",
+ [ejabberd_auth:get_vh_registered_users_number(Host)])),
{result, completed,
- [{xmlelement, "x",
- [{"xmlns", ?NS_XDATA}],
- [?HFIELD(),
- {xmlelement,
- "field",
- [{"type", "text-single"},
- {"label", ?T(Lang, "Number of registered users")},
- {"var", "registeredusersnum"}],
- [{xmlelement, "value", [], [{xmlcdata, Num}]}]
- }]}]};
-
-get_form(Host, ?NS_ADMINL("get-online-users-num"), Lang) ->
- Num = io_lib:format("~p", [length(ejabberd_sm:get_vh_session_list(Host))]),
+ [#xmlel{name = <<"x">>,
+ attrs = [{<<"xmlns">>, ?NS_XDATA}],
+ children =
+ [?HFIELD(),
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"type">>, <<"text-single">>},
+ {<<"label">>,
+ ?T(Lang, <<"Number of registered users">>)},
+ {<<"var">>, <<"registeredusersnum">>}],
+ children =
+ [#xmlel{name = <<"value">>, attrs = [],
+ children = [{xmlcdata, Num}]}]}]}]};
+get_form(Host, ?NS_ADMINL(<<"get-online-users-num">>),
+ Lang) ->
+ Num = list_to_binary(
+ io_lib:format("~p",
+ [length(ejabberd_sm:get_vh_session_list(Host))])),
{result, completed,
- [{xmlelement, "x",
- [{"xmlns", ?NS_XDATA}],
- [?HFIELD(),
- {xmlelement,
- "field",
- [{"type", "text-single"},
- {"label", ?T(Lang, "Number of online users")},
- {"var", "onlineusersnum"}],
- [{xmlelement, "value", [], [{xmlcdata, Num}]}]
- }]}]};
-
+ [#xmlel{name = <<"x">>,
+ attrs = [{<<"xmlns">>, ?NS_XDATA}],
+ children =
+ [?HFIELD(),
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"type">>, <<"text-single">>},
+ {<<"label">>,
+ ?T(Lang, <<"Number of online users">>)},
+ {<<"var">>, <<"onlineusersnum">>}],
+ children =
+ [#xmlel{name = <<"value">>, attrs = [],
+ children = [{xmlcdata, Num}]}]}]}]};
get_form(_Host, _, _Lang) ->
{error, ?ERR_SERVICE_UNAVAILABLE}.
-
-
-set_form(_From, _Host, ["running nodes", ENode, "DB"], _Lang, XData) ->
+set_form(_From, _Host,
+ [<<"running nodes">>, ENode, <<"DB">>], _Lang, XData) ->
case search_running_node(ENode) of
- false ->
- {error, ?ERR_ITEM_NOT_FOUND};
- Node ->
- lists:foreach(
- fun({SVar, SVals}) ->
- %% We believe that this is allowed only for good people
- Table = list_to_atom(SVar),
- Type = case SVals of
- ["unknown"] -> unknown;
- ["ram_copies"] -> ram_copies;
- ["disc_copies"] -> disc_copies;
- ["disc_only_copies"] -> disc_only_copies;
- _ -> false
- end,
- if
- Type == false ->
- ok;
- Type == unknown ->
- mnesia:del_table_copy(Table, Node);
- true ->
- case mnesia:add_table_copy(Table, Node, Type) of
- {aborted, _} ->
- mnesia:change_table_copy_type(
- Table, Node, Type);
- _ ->
- ok
- end
- end
- end, XData),
- {result, []}
+ false -> {error, ?ERR_ITEM_NOT_FOUND};
+ Node ->
+ lists:foreach(fun ({SVar, SVals}) ->
+ Table = jlib:binary_to_atom(SVar),
+ Type = case SVals of
+ [<<"unknown">>] -> unknown;
+ [<<"ram_copies">>] -> ram_copies;
+ [<<"disc_copies">>] -> disc_copies;
+ [<<"disc_only_copies">>] ->
+ disc_only_copies;
+ _ -> false
+ end,
+ if Type == false -> ok;
+ Type == unknown ->
+ mnesia:del_table_copy(Table, Node);
+ true ->
+ case mnesia:add_table_copy(Table, Node,
+ Type)
+ of
+ {aborted, _} ->
+ mnesia:change_table_copy_type(Table,
+ Node,
+ Type);
+ _ -> ok
+ end
+ end
+ end,
+ XData),
+ {result, []}
end;
-
-set_form(_From, Host, ["running nodes", ENode, "modules", "stop"], _Lang, XData) ->
+set_form(_From, Host,
+ [<<"running nodes">>, ENode, <<"modules">>, <<"stop">>],
+ _Lang, XData) ->
case search_running_node(ENode) of
- false ->
- {error, ?ERR_ITEM_NOT_FOUND};
- Node ->
- lists:foreach(
- fun({Var, Vals}) ->
- case Vals of
- ["1"] ->
- Module = list_to_atom(Var),
- rpc:call(Node, gen_mod, stop_module, [Host, Module]);
- _ ->
- ok
- end
- end, XData),
- {result, []}
+ false -> {error, ?ERR_ITEM_NOT_FOUND};
+ Node ->
+ lists:foreach(fun ({Var, Vals}) ->
+ case Vals of
+ [<<"1">>] ->
+ Module = jlib:binary_to_atom(Var),
+ rpc:call(Node, gen_mod, stop_module,
+ [Host, Module]);
+ _ -> ok
+ end
+ end,
+ XData),
+ {result, []}
end;
-
-set_form(_From, Host, ["running nodes", ENode, "modules", "start"], _Lang, XData) ->
+set_form(_From, Host,
+ [<<"running nodes">>, ENode, <<"modules">>,
+ <<"start">>],
+ _Lang, XData) ->
case search_running_node(ENode) of
- false ->
- {error, ?ERR_ITEM_NOT_FOUND};
- Node ->
- case lists:keysearch("modules", 1, XData) of
- false ->
- {error, ?ERR_BAD_REQUEST};
- {value, {_, Strings}} ->
- String = lists:foldl(fun(S, Res) ->
- Res ++ S ++ "\n"
- end, "", Strings),
- case erl_scan:string(String) of
- {ok, Tokens, _} ->
- case erl_parse:parse_term(Tokens) of
- {ok, Modules} ->
- lists:foreach(
- fun({Module, Args}) ->
- rpc:call(Node,
- gen_mod,
- start_module,
- [Host, Module, Args])
- end, Modules),
- {result, []};
- _ ->
- {error, ?ERR_BAD_REQUEST}
- end;
- _ ->
- {error, ?ERR_BAD_REQUEST}
- end
- end
+ false -> {error, ?ERR_ITEM_NOT_FOUND};
+ Node ->
+ case lists:keysearch(<<"modules">>, 1, XData) of
+ false -> {error, ?ERR_BAD_REQUEST};
+ {value, {_, Strings}} ->
+ String = lists:foldl(fun (S, Res) ->
+ <<Res/binary, S/binary, "\n">>
+ end,
+ <<"">>, Strings),
+ case erl_scan:string(binary_to_list(String)) of
+ {ok, Tokens, _} ->
+ case erl_parse:parse_term(Tokens) of
+ {ok, Modules} ->
+ lists:foreach(fun ({Module, Args}) ->
+ rpc:call(Node, gen_mod,
+ start_module,
+ [Host, Module, Args])
+ end,
+ Modules),
+ {result, []};
+ _ -> {error, ?ERR_BAD_REQUEST}
+ end;
+ _ -> {error, ?ERR_BAD_REQUEST}
+ end
+ end
end;
-
-
-set_form(_From, _Host, ["running nodes", ENode, "backup", "backup"], _Lang, XData) ->
+set_form(_From, _Host,
+ [<<"running nodes">>, ENode, <<"backup">>,
+ <<"backup">>],
+ _Lang, XData) ->
case search_running_node(ENode) of
- false ->
- {error, ?ERR_ITEM_NOT_FOUND};
- Node ->
- case lists:keysearch("path", 1, XData) of
- false ->
- {error, ?ERR_BAD_REQUEST};
- {value, {_, [String]}} ->
- case rpc:call(Node, mnesia, backup, [String]) of
- {badrpc, _Reason} ->
- {error, ?ERR_INTERNAL_SERVER_ERROR};
- {error, _Reason} ->
- {error, ?ERR_INTERNAL_SERVER_ERROR};
- _ ->
- {result, []}
- end;
- _ ->
- {error, ?ERR_BAD_REQUEST}
- end
+ false -> {error, ?ERR_ITEM_NOT_FOUND};
+ Node ->
+ case lists:keysearch(<<"path">>, 1, XData) of
+ false -> {error, ?ERR_BAD_REQUEST};
+ {value, {_, [String]}} ->
+ case rpc:call(Node, mnesia, backup, [String]) of
+ {badrpc, _Reason} ->
+ {error, ?ERR_INTERNAL_SERVER_ERROR};
+ {error, _Reason} -> {error, ?ERR_INTERNAL_SERVER_ERROR};
+ _ -> {result, []}
+ end;
+ _ -> {error, ?ERR_BAD_REQUEST}
+ end
end;
-
-
-set_form(_From, _Host, ["running nodes", ENode, "backup", "restore"], _Lang, XData) ->
+set_form(_From, _Host,
+ [<<"running nodes">>, ENode, <<"backup">>,
+ <<"restore">>],
+ _Lang, XData) ->
case search_running_node(ENode) of
- false ->
- {error, ?ERR_ITEM_NOT_FOUND};
- Node ->
- case lists:keysearch("path", 1, XData) of
- false ->
- {error, ?ERR_BAD_REQUEST};
- {value, {_, [String]}} ->
- case rpc:call(Node, ejabberd_admin, restore, [String]) of
- {badrpc, _Reason} ->
- {error, ?ERR_INTERNAL_SERVER_ERROR};
- {error, _Reason} ->
- {error, ?ERR_INTERNAL_SERVER_ERROR};
- _ ->
- {result, []}
- end;
- _ ->
- {error, ?ERR_BAD_REQUEST}
- end
+ false -> {error, ?ERR_ITEM_NOT_FOUND};
+ Node ->
+ case lists:keysearch(<<"path">>, 1, XData) of
+ false -> {error, ?ERR_BAD_REQUEST};
+ {value, {_, [String]}} ->
+ case rpc:call(Node, ejabberd_admin, restore, [String])
+ of
+ {badrpc, _Reason} ->
+ {error, ?ERR_INTERNAL_SERVER_ERROR};
+ {error, _Reason} -> {error, ?ERR_INTERNAL_SERVER_ERROR};
+ _ -> {result, []}
+ end;
+ _ -> {error, ?ERR_BAD_REQUEST}
+ end
end;
-
-
-set_form(_From, _Host, ["running nodes", ENode, "backup", "textfile"], _Lang, XData) ->
+set_form(_From, _Host,
+ [<<"running nodes">>, ENode, <<"backup">>,
+ <<"textfile">>],
+ _Lang, XData) ->
case search_running_node(ENode) of
- false ->
- {error, ?ERR_ITEM_NOT_FOUND};
- Node ->
- case lists:keysearch("path", 1, XData) of
- false ->
- {error, ?ERR_BAD_REQUEST};
- {value, {_, [String]}} ->
- case rpc:call(Node, ejabberd_admin, dump_to_textfile, [String]) of
- {badrpc, _Reason} ->
- {error, ?ERR_INTERNAL_SERVER_ERROR};
- {error, _Reason} ->
- {error, ?ERR_INTERNAL_SERVER_ERROR};
- _ ->
- {result, []}
- end;
- _ ->
- {error, ?ERR_BAD_REQUEST}
- end
+ false -> {error, ?ERR_ITEM_NOT_FOUND};
+ Node ->
+ case lists:keysearch(<<"path">>, 1, XData) of
+ false -> {error, ?ERR_BAD_REQUEST};
+ {value, {_, [String]}} ->
+ case rpc:call(Node, ejabberd_admin, dump_to_textfile,
+ [String])
+ of
+ {badrpc, _Reason} ->
+ {error, ?ERR_INTERNAL_SERVER_ERROR};
+ {error, _Reason} -> {error, ?ERR_INTERNAL_SERVER_ERROR};
+ _ -> {result, []}
+ end;
+ _ -> {error, ?ERR_BAD_REQUEST}
+ end
end;
-
-
-set_form(_From, _Host, ["running nodes", ENode, "import", "file"], _Lang, XData) ->
+set_form(_From, _Host,
+ [<<"running nodes">>, ENode, <<"import">>, <<"file">>],
+ _Lang, XData) ->
case search_running_node(ENode) of
- false ->
- {error, ?ERR_ITEM_NOT_FOUND};
- Node ->
- case lists:keysearch("path", 1, XData) of
- false ->
- {error, ?ERR_BAD_REQUEST};
- {value, {_, [String]}} ->
- rpc:call(Node, jd2ejd, import_file, [String]),
- {result, []};
- _ ->
- {error, ?ERR_BAD_REQUEST}
- end
+ false -> {error, ?ERR_ITEM_NOT_FOUND};
+ Node ->
+ case lists:keysearch(<<"path">>, 1, XData) of
+ false -> {error, ?ERR_BAD_REQUEST};
+ {value, {_, [String]}} ->
+ rpc:call(Node, jd2ejd, import_file, [String]),
+ {result, []};
+ _ -> {error, ?ERR_BAD_REQUEST}
+ end
end;
-
-
-set_form(_From, _Host, ["running nodes", ENode, "import", "dir"], _Lang, XData) ->
+set_form(_From, _Host,
+ [<<"running nodes">>, ENode, <<"import">>, <<"dir">>],
+ _Lang, XData) ->
case search_running_node(ENode) of
- false ->
- {error, ?ERR_ITEM_NOT_FOUND};
- Node ->
- case lists:keysearch("path", 1, XData) of
- false ->
- {error, ?ERR_BAD_REQUEST};
- {value, {_, [String]}} ->
- rpc:call(Node, jd2ejd, import_dir, [String]),
- {result, []};
- _ ->
- {error, ?ERR_BAD_REQUEST}
- end
+ false -> {error, ?ERR_ITEM_NOT_FOUND};
+ Node ->
+ case lists:keysearch(<<"path">>, 1, XData) of
+ false -> {error, ?ERR_BAD_REQUEST};
+ {value, {_, [String]}} ->
+ rpc:call(Node, jd2ejd, import_dir, [String]),
+ {result, []};
+ _ -> {error, ?ERR_BAD_REQUEST}
+ end
end;
-
-set_form(From, Host, ["running nodes", ENode, "restart"], _Lang, XData) ->
+set_form(From, Host,
+ [<<"running nodes">>, ENode, <<"restart">>], _Lang,
+ XData) ->
stop_node(From, Host, ENode, restart, XData);
-
-set_form(From, Host, ["running nodes", ENode, "shutdown"], _Lang, XData) ->
+set_form(From, Host,
+ [<<"running nodes">>, ENode, <<"shutdown">>], _Lang,
+ XData) ->
stop_node(From, Host, ENode, stop, XData);
-
-set_form(_From, Host, ["config", "acls"], _Lang, XData) ->
- case lists:keysearch("acls", 1, XData) of
- {value, {_, Strings}} ->
- String = lists:foldl(fun(S, Res) ->
- Res ++ S ++ "\n"
- end, "", Strings),
- case erl_scan:string(String) of
- {ok, Tokens, _} ->
- case erl_parse:parse_term(Tokens) of
- {ok, ACLs} ->
- case acl:add_list(Host, ACLs, true) of
- ok ->
- {result, []};
- _ ->
- {error, ?ERR_BAD_REQUEST}
- end;
- _ ->
- {error, ?ERR_BAD_REQUEST}
- end;
- _ ->
- {error, ?ERR_BAD_REQUEST}
- end;
- _ ->
- {error, ?ERR_BAD_REQUEST}
+set_form(_From, Host, [<<"config">>, <<"acls">>], _Lang,
+ XData) ->
+ case lists:keysearch(<<"acls">>, 1, XData) of
+ {value, {_, Strings}} ->
+ String = lists:foldl(fun (S, Res) ->
+ <<Res/binary, S/binary, "\n">>
+ end,
+ <<"">>, Strings),
+ case erl_scan:string(binary_to_list(String)) of
+ {ok, Tokens, _} ->
+ case erl_parse:parse_term(Tokens) of
+ {ok, ACLs} ->
+ case acl:add_list(Host, ACLs, true) of
+ ok -> {result, []};
+ _ -> {error, ?ERR_BAD_REQUEST}
+ end;
+ _ -> {error, ?ERR_BAD_REQUEST}
+ end;
+ _ -> {error, ?ERR_BAD_REQUEST}
+ end;
+ _ -> {error, ?ERR_BAD_REQUEST}
end;
-
-set_form(_From, Host, ["config", "access"], _Lang, XData) ->
- SetAccess =
- fun(Rs) ->
- mnesia:transaction(
- fun() ->
- Os = mnesia:select(config,
- [{{config, {access, '$1', '$2'}, '$3'},
- [{'==', '$2', Host}],
- ['$_']}]),
- lists:foreach(fun(O) ->
- mnesia:delete_object(O)
- end, Os),
- lists:foreach(
- fun({access, Name, Rules}) ->
- mnesia:write({config,
- {access, Name, Host},
- Rules})
- end, Rs)
- end)
- end,
- case lists:keysearch("access", 1, XData) of
- {value, {_, Strings}} ->
- String = lists:foldl(fun(S, Res) ->
- Res ++ S ++ "\n"
- end, "", Strings),
- case erl_scan:string(String) of
- {ok, Tokens, _} ->
- case erl_parse:parse_term(Tokens) of
- {ok, Rs} ->
- case SetAccess(Rs) of
- {atomic, _} ->
- {result, []};
- _ ->
- {error, ?ERR_BAD_REQUEST}
- end;
- _ ->
- {error, ?ERR_BAD_REQUEST}
- end;
- _ ->
- {error, ?ERR_BAD_REQUEST}
- end;
- _ ->
- {error, ?ERR_BAD_REQUEST}
+set_form(_From, Host, [<<"config">>, <<"access">>],
+ _Lang, XData) ->
+ SetAccess = fun (Rs) ->
+ mnesia:transaction(fun () ->
+ Os = mnesia:select(config,
+ [{{config,
+ {access,
+ '$1',
+ '$2'},
+ '$3'},
+ [{'==',
+ '$2',
+ Host}],
+ ['$_']}]),
+ lists:foreach(fun (O) ->
+ mnesia:delete_object(O)
+ end,
+ Os),
+ lists:foreach(fun ({access,
+ Name,
+ Rules}) ->
+ mnesia:write({config,
+ {access,
+ Name,
+ Host},
+ Rules})
+ end,
+ Rs)
+ end)
+ end,
+ case lists:keysearch(<<"access">>, 1, XData) of
+ {value, {_, Strings}} ->
+ String = lists:foldl(fun (S, Res) ->
+ <<Res/binary, S/binary, "\n">>
+ end,
+ <<"">>, Strings),
+ case erl_scan:string(binary_to_list(String)) of
+ {ok, Tokens, _} ->
+ case erl_parse:parse_term(Tokens) of
+ {ok, Rs} ->
+ case SetAccess(Rs) of
+ {atomic, _} -> {result, []};
+ _ -> {error, ?ERR_BAD_REQUEST}
+ end;
+ _ -> {error, ?ERR_BAD_REQUEST}
+ end;
+ _ -> {error, ?ERR_BAD_REQUEST}
+ end;
+ _ -> {error, ?ERR_BAD_REQUEST}
end;
-
-set_form(From, Host, ?NS_ADMINL("add-user"), _Lang, XData) ->
- AccountString = get_value("accountjid", XData),
- Password = get_value("password", XData),
- Password = get_value("password-verify", XData),
+set_form(From, Host, ?NS_ADMINL(<<"add-user">>), _Lang,
+ XData) ->
+ AccountString = get_value(<<"accountjid">>, XData),
+ Password = get_value(<<"password">>, XData),
+ Password = get_value(<<"password-verify">>, XData),
AccountJID = jlib:string_to_jid(AccountString),
User = AccountJID#jid.luser,
Server = AccountJID#jid.lserver,
true = lists:member(Server, ?MYHOSTS),
- true = (Server == Host) orelse (get_permission_level(From) == global),
+ true = Server == Host orelse
+ get_permission_level(From) == global,
ejabberd_auth:try_register(User, Server, Password),
{result, []};
-
-set_form(From, Host, ?NS_ADMINL("delete-user"), _Lang, XData) ->
- AccountStringList = get_values("accountjids", XData),
- [_|_] = AccountStringList,
- ASL2 = lists:map(
- fun(AccountString) ->
- JID = jlib:string_to_jid(AccountString),
- [_|_] = JID#jid.luser,
- User = JID#jid.luser,
- Server = JID#jid.lserver,
- true = (Server == Host) orelse (get_permission_level(From) == global),
- true = ejabberd_auth:is_user_exists(User, Server),
- {User, Server}
- end,
- AccountStringList),
- [ejabberd_auth:remove_user(User, Server) || {User, Server} <- ASL2],
+set_form(From, Host, ?NS_ADMINL(<<"delete-user">>),
+ _Lang, XData) ->
+ AccountStringList = get_values(<<"accountjids">>,
+ XData),
+ [_ | _] = AccountStringList,
+ ASL2 = lists:map(fun (AccountString) ->
+ JID = jlib:string_to_jid(AccountString),
+ User = JID#jid.luser,
+ Server = JID#jid.lserver,
+ true = Server == Host orelse
+ get_permission_level(From) == global,
+ true = ejabberd_auth:is_user_exists(User, Server),
+ {User, Server}
+ end,
+ AccountStringList),
+ [ejabberd_auth:remove_user(User, Server)
+ || {User, Server} <- ASL2],
{result, []};
-
-set_form(From, Host, ?NS_ADMINL("end-user-session"), _Lang, XData) ->
- AccountString = get_value("accountjid", XData),
+set_form(From, Host, ?NS_ADMINL(<<"end-user-session">>),
+ _Lang, XData) ->
+ AccountString = get_value(<<"accountjid">>, XData),
JID = jlib:string_to_jid(AccountString),
- [_|_] = JID#jid.luser,
- LUser = JID#jid.luser,
- LServer = JID#jid.lserver,
- true = (LServer == Host) orelse (get_permission_level(From) == global),
- %% Code copied from ejabberd_sm.erl
+ LUser = JID#jid.luser,
+ LServer = JID#jid.lserver,
+ true = LServer == Host orelse
+ get_permission_level(From) == global,
case JID#jid.lresource of
- [] ->
- SIDs = mnesia:dirty_select(session,
- [{#session{sid = '$1', usr = {LUser, LServer, '_'}, _ = '_'}, [], ['$1']}]),
- [Pid ! replaced || {_, Pid} <- SIDs];
- R ->
- [{_, Pid}] = mnesia:dirty_select(session,
- [{#session{sid = '$1', usr = {LUser, LServer, R}, _ = '_'}, [], ['$1']}]),
- Pid ! replaced
- end,
+ <<>> ->
+ SIDs = mnesia:dirty_select(session,
+ [{#session{sid = '$1',
+ usr = {LUser, LServer, '_'},
+ _ = '_'},
+ [], ['$1']}]),
+ [Pid ! replaced || {_, Pid} <- SIDs];
+ R ->
+ [{_, Pid}] = mnesia:dirty_select(session,
+ [{#session{sid = '$1',
+ usr = {LUser, LServer, R},
+ _ = '_'},
+ [], ['$1']}]),
+ Pid ! replaced
+ end,
{result, []};
-
-set_form(From, Host, ?NS_ADMINL("get-user-password"), Lang, XData) ->
- AccountString = get_value("accountjid", XData),
+set_form(From, Host,
+ ?NS_ADMINL(<<"get-user-password">>), Lang, XData) ->
+ AccountString = get_value(<<"accountjid">>, XData),
JID = jlib:string_to_jid(AccountString),
- [_|_] = JID#jid.luser,
- User = JID#jid.luser,
- Server = JID#jid.lserver,
- true = (Server == Host) orelse (get_permission_level(From) == global),
+ User = JID#jid.luser,
+ Server = JID#jid.lserver,
+ true = Server == Host orelse
+ get_permission_level(From) == global,
Password = ejabberd_auth:get_password(User, Server),
- true = is_list(Password),
- {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}],
- [?HFIELD(),
- ?XFIELD("jid-single", "Jabber ID", "accountjid", AccountString),
- ?XFIELD("text-single", "Password", "password", Password)
- ]}]};
-
-set_form(From, Host, ?NS_ADMINL("change-user-password"), _Lang, XData) ->
- AccountString = get_value("accountjid", XData),
- Password = get_value("password", XData),
+ true = is_binary(Password),
+ {result,
+ [#xmlel{name = <<"x">>,
+ attrs = [{<<"xmlns">>, ?NS_XDATA}],
+ children =
+ [?HFIELD(),
+ ?XFIELD(<<"jid-single">>, <<"Jabber ID">>,
+ <<"accountjid">>, AccountString),
+ ?XFIELD(<<"text-single">>, <<"Password">>,
+ <<"password">>, Password)]}]};
+set_form(From, Host,
+ ?NS_ADMINL(<<"change-user-password">>), _Lang, XData) ->
+ AccountString = get_value(<<"accountjid">>, XData),
+ Password = get_value(<<"password">>, XData),
JID = jlib:string_to_jid(AccountString),
- [_|_] = JID#jid.luser,
- User = JID#jid.luser,
- Server = JID#jid.lserver,
- true = (Server == Host) orelse (get_permission_level(From) == global),
+ User = JID#jid.luser,
+ Server = JID#jid.lserver,
+ true = Server == Host orelse
+ get_permission_level(From) == global,
true = ejabberd_auth:is_user_exists(User, Server),
ejabberd_auth:set_password(User, Server, Password),
{result, []};
-
-set_form(From, Host, ?NS_ADMINL("get-user-lastlogin"), Lang, XData) ->
- AccountString = get_value("accountjid", XData),
+set_form(From, Host,
+ ?NS_ADMINL(<<"get-user-lastlogin">>), Lang, XData) ->
+ AccountString = get_value(<<"accountjid">>, XData),
JID = jlib:string_to_jid(AccountString),
- [_|_] = JID#jid.luser,
- User = JID#jid.luser,
- Server = JID#jid.lserver,
- true = (Server == Host) orelse (get_permission_level(From) == global),
-
- %% Code copied from web/ejabberd_web_admin.erl
- %% TODO: Update time format to XEP-0202: Entity Time
- FLast =
- case ejabberd_sm:get_user_resources(User, Server) of
- [] ->
- _US = {User, Server},
- case get_last_info(User, Server) of
- not_found ->
- ?T(Lang, "Never");
+ User = JID#jid.luser,
+ Server = JID#jid.lserver,
+ true = Server == Host orelse
+ get_permission_level(From) == global,
+ FLast = case ejabberd_sm:get_user_resources(User,
+ Server)
+ of
+ [] ->
+ _US = {User, Server},
+ case get_last_info(User, Server) of
+ not_found -> ?T(Lang, <<"Never">>);
{ok, Timestamp, _Status} ->
Shift = Timestamp,
- TimeStamp = {Shift div 1000000,
- Shift rem 1000000,
- 0},
+ TimeStamp = {Shift div 1000000, Shift rem 1000000, 0},
{{Year, Month, Day}, {Hour, Minute, Second}} =
calendar:now_to_local_time(TimeStamp),
- lists:flatten(
- io_lib:format(
- "~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
- [Year, Month, Day, Hour, Minute, Second]))
- end;
- _ ->
- ?T(Lang, "Online")
- end,
- {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}, {"type", "result"}],
- [?HFIELD(),
- ?XFIELD("jid-single", "Jabber ID", "accountjid", AccountString),
- ?XFIELD("text-single", "Last login", "lastlogin", FLast)
- ]}]};
-
-set_form(From, Host, ?NS_ADMINL("user-stats"), Lang, XData) ->
- AccountString = get_value("accountjid", XData),
+ iolist_to_binary(io_lib:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
+ [Year, Month, Day, Hour,
+ Minute, Second]))
+ end;
+ _ -> ?T(Lang, <<"Online">>)
+ end,
+ {result,
+ [#xmlel{name = <<"x">>,
+ attrs =
+ [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"result">>}],
+ children =
+ [?HFIELD(),
+ ?XFIELD(<<"jid-single">>, <<"Jabber ID">>,
+ <<"accountjid">>, AccountString),
+ ?XFIELD(<<"text-single">>, <<"Last login">>,
+ <<"lastlogin">>, FLast)]}]};
+set_form(From, Host, ?NS_ADMINL(<<"user-stats">>), Lang,
+ XData) ->
+ AccountString = get_value(<<"accountjid">>, XData),
JID = jlib:string_to_jid(AccountString),
- [_|_] = JID#jid.luser,
- User = JID#jid.luser,
- Server = JID#jid.lserver,
- true = (Server == Host) orelse (get_permission_level(From) == global),
-
- Resources = ejabberd_sm:get_user_resources(User, Server),
- IPs1 = [ejabberd_sm:get_user_ip(User, Server, Resource) || Resource <- Resources],
- IPs = [inet_parse:ntoa(IP)++":"++integer_to_list(Port) || {IP, Port} <- IPs1],
-
- Items = ejabberd_hooks:run_fold(roster_get, Server, [], [{User, Server}]),
- Rostersize = integer_to_list(erlang:length(Items)),
-
- {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}],
- [?HFIELD(),
- ?XFIELD("jid-single", "Jabber ID", "accountjid", AccountString),
- ?XFIELD("text-single", "Roster size", "rostersize", Rostersize),
- ?XMFIELD("text-multi", "IP addresses", "ipaddresses", IPs),
- ?XMFIELD("text-multi", "Resources", "onlineresources", Resources)
- ]}]};
-
+ User = JID#jid.luser,
+ Server = JID#jid.lserver,
+ true = Server == Host orelse
+ get_permission_level(From) == global,
+ Resources = ejabberd_sm:get_user_resources(User,
+ Server),
+ IPs1 = [ejabberd_sm:get_user_ip(User, Server, Resource)
+ || Resource <- Resources],
+ IPs = [<<(jlib:ip_to_list(IP))/binary, ":",
+ (jlib:integer_to_binary(Port))/binary>>
+ || {IP, Port} <- IPs1],
+ Items = ejabberd_hooks:run_fold(roster_get, Server, [],
+ [{User, Server}]),
+ Rostersize = jlib:integer_to_binary(erlang:length(Items)),
+ {result,
+ [#xmlel{name = <<"x">>,
+ attrs = [{<<"xmlns">>, ?NS_XDATA}],
+ children =
+ [?HFIELD(),
+ ?XFIELD(<<"jid-single">>, <<"Jabber ID">>,
+ <<"accountjid">>, AccountString),
+ ?XFIELD(<<"text-single">>, <<"Roster size">>,
+ <<"rostersize">>, Rostersize),
+ ?XMFIELD(<<"text-multi">>, <<"IP addresses">>,
+ <<"ipaddresses">>, IPs),
+ ?XMFIELD(<<"text-multi">>, <<"Resources">>,
+ <<"onlineresources">>, Resources)]}]};
set_form(_From, _Host, _, _Lang, _XData) ->
{error, ?ERR_SERVICE_UNAVAILABLE}.
-get_value(Field, XData) ->
- hd(get_values(Field, XData)).
-get_values(Field, XData) ->
- {value, {_, ValueList}} = lists:keysearch(Field, 1, XData),
- ValueList.
+get_value(Field, XData) -> hd(get_values(Field, XData)).
+get_values(Field, XData) ->
+ {value, {_, ValueList}} = lists:keysearch(Field, 1,
+ XData),
+ ValueList.
search_running_node(SNode) ->
- search_running_node(SNode, mnesia:system_info(running_db_nodes)).
+ search_running_node(SNode,
+ mnesia:system_info(running_db_nodes)).
-search_running_node(_, []) ->
- false;
+search_running_node(_, []) -> false;
search_running_node(SNode, [Node | Nodes]) ->
- case atom_to_list(Node) of
- SNode ->
- Node;
- _ ->
- search_running_node(SNode, Nodes)
+ case iolist_to_binary(atom_to_list(Node)) of
+ SNode -> Node;
+ _ -> search_running_node(SNode, Nodes)
end.
stop_node(From, Host, ENode, Action, XData) ->
- Delay = list_to_integer(get_value("delay", XData)),
- Subject = case get_value("subject", XData) of
- [] -> [];
- S -> [{xmlelement, "field", [{"var","subject"}],
- [{xmlelement,"value",[],[{xmlcdata,S}]}]}]
+ Delay = jlib:binary_to_integer(get_value(<<"delay">>,
+ XData)),
+ Subject = case get_value(<<"subject">>, XData) of
+ <<"">> -> [];
+ S ->
+ [#xmlel{name = <<"field">>,
+ attrs = [{<<"var">>, <<"subject">>}],
+ children =
+ [#xmlel{name = <<"value">>, attrs = [],
+ children = [{xmlcdata, S}]}]}]
end,
- Announcement = case get_values("announcement", XData) of
- [] -> [];
- As -> [{xmlelement, "field", [{"var","body"}],
- [{xmlelement,"value",[],[{xmlcdata,Line}]} || Line <- As] }]
+ Announcement = case get_values(<<"announcement">>,
+ XData)
+ of
+ [] -> [];
+ As ->
+ [#xmlel{name = <<"field">>,
+ attrs = [{<<"var">>, <<"body">>}],
+ children =
+ [#xmlel{name = <<"value">>, attrs = [],
+ children = [{xmlcdata, Line}]}
+ || Line <- As]}]
end,
case Subject ++ Announcement of
- [] -> ok;
- SubEls ->
- Request = #adhoc_request{
- node = ?NS_ADMINX("announce-allhosts"),
- action = "complete",
- xdata = {xmlelement, "x",
- [{"xmlns","jabber:x:data"},{"type","submit"}],
- SubEls},
- others= [{xmlelement, "x",
- [{"xmlns","jabber:x:data"},{"type","submit"}],
- SubEls}]
- },
- To = jlib:make_jid("", Host, ""),
- mod_announce:announce_commands(empty, From, To, Request)
+ [] -> ok;
+ SubEls ->
+ Request = #adhoc_request{node =
+ ?NS_ADMINX(<<"announce-allhosts">>),
+ action = <<"complete">>,
+ xdata =
+ #xmlel{name = <<"x">>,
+ attrs =
+ [{<<"xmlns">>,
+ <<"jabber:x:data">>},
+ {<<"type">>, <<"submit">>}],
+ children = SubEls},
+ others =
+ [#xmlel{name = <<"x">>,
+ attrs =
+ [{<<"xmlns">>,
+ <<"jabber:x:data">>},
+ {<<"type">>, <<"submit">>}],
+ children = SubEls}]},
+ To = jlib:make_jid(<<"">>, Host, <<"">>),
+ mod_announce:announce_commands(empty, From, To, Request)
end,
Time = timer:seconds(Delay),
- Node = list_to_atom(ENode),
- {ok, _} = timer:apply_after(Time, rpc, call, [Node, init, Action, []]),
+ Node = jlib:binary_to_atom(ENode),
+ {ok, _} = timer:apply_after(Time, rpc, call,
+ [Node, init, Action, []]),
{result, []}.
-
get_last_info(User, Server) ->
case gen_mod:is_loaded(Server, mod_last) of
- true ->
- mod_last:get_last_info(User, Server);
- false ->
- not_found
+ true -> mod_last:get_last_info(User, Server);
+ false -> not_found
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
adhoc_sm_commands(_Acc, From,
- #jid{user = User, server = Server, lserver = LServer} = _To,
- #adhoc_request{lang = Lang,
- node = "config",
- action = Action,
- xdata = XData} = Request) ->
+ #jid{user = User, server = Server, lserver = LServer} =
+ _To,
+ #adhoc_request{lang = Lang, node = <<"config">>,
+ action = Action, xdata = XData} =
+ Request) ->
case acl:match_rule(LServer, configure, From) of
- deny ->
- {error, ?ERR_FORBIDDEN};
- allow ->
- %% If the "action" attribute is not present, it is
- %% understood as "execute". If there was no <actions/>
- %% element in the first response (which there isn't in our
- %% case), "execute" and "complete" are equivalent.
- ActionIsExecute = lists:member(Action,
- ["", "execute", "complete"]),
- if Action == "cancel" ->
- %% User cancels request
- adhoc:produce_response(
- Request,
- #adhoc_response{status = canceled});
- XData == false, ActionIsExecute ->
- %% User requests form
- case get_sm_form(User, Server, "config", Lang) of
- {result, Form} ->
- adhoc:produce_response(
- Request,
- #adhoc_response{status = executing,
- elements = Form});
- {error, Error} ->
- {error, Error}
- end;
- XData /= false, ActionIsExecute ->
- %% User returns form.
- case jlib:parse_xdata_submit(XData) of
- invalid ->
- {error, ?ERR_BAD_REQUEST};
- Fields ->
- set_sm_form(User, Server, "config", Request, Fields)
- end;
- true ->
- {error, ?ERR_BAD_REQUEST}
- end
+ deny -> {error, ?ERR_FORBIDDEN};
+ allow ->
+ ActionIsExecute = lists:member(Action,
+ [<<"">>, <<"execute">>,
+ <<"complete">>]),
+ if Action == <<"cancel">> ->
+ adhoc:produce_response(Request,
+ #adhoc_response{status = canceled});
+ XData == false, ActionIsExecute ->
+ case get_sm_form(User, Server, <<"config">>, Lang) of
+ {result, Form} ->
+ adhoc:produce_response(Request,
+ #adhoc_response{status =
+ executing,
+ elements = Form});
+ {error, Error} -> {error, Error}
+ end;
+ XData /= false, ActionIsExecute ->
+ case jlib:parse_xdata_submit(XData) of
+ invalid -> {error, ?ERR_BAD_REQUEST};
+ Fields ->
+ set_sm_form(User, Server, <<"config">>, Request, Fields)
+ end;
+ true -> {error, ?ERR_BAD_REQUEST}
+ end
end;
+adhoc_sm_commands(Acc, _From, _To, _Request) -> Acc.
-adhoc_sm_commands(Acc, _From, _To, _Request) ->
- Acc.
-
-get_sm_form(User, Server, "config", Lang) ->
- {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}],
- [?HFIELD(),
- {xmlelement, "title", [],
- [{xmlcdata,
- ?T(
- Lang, "Administration of ") ++ User}]},
- {xmlelement, "field",
- [{"type", "list-single"},
- {"label", ?T(Lang, "Action on user")},
- {"var", "action"}],
- [{xmlelement, "value", [], [{xmlcdata, "edit"}]},
- {xmlelement, "option",
- [{"label", ?T(Lang, "Edit Properties")}],
- [{xmlelement, "value", [], [{xmlcdata, "edit"}]}]},
- {xmlelement, "option",
- [{"label", ?T(Lang, "Remove User")}],
- [{xmlelement, "value", [], [{xmlcdata, "remove"}]}]}
- ]},
- ?XFIELD("text-private", "Password", "password",
- ejabberd_auth:get_password_s(User, Server))
- ]}]};
-
+get_sm_form(User, Server, <<"config">>, Lang) ->
+ {result,
+ [#xmlel{name = <<"x">>,
+ attrs = [{<<"xmlns">>, ?NS_XDATA}],
+ children =
+ [?HFIELD(),
+ #xmlel{name = <<"title">>, attrs = [],
+ children =
+ [{xmlcdata,
+ <<(?T(Lang, <<"Administration of ">>))/binary,
+ User/binary>>}]},
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"type">>, <<"list-single">>},
+ {<<"label">>, ?T(Lang, <<"Action on user">>)},
+ {<<"var">>, <<"action">>}],
+ children =
+ [#xmlel{name = <<"value">>, attrs = [],
+ children = [{xmlcdata, <<"edit">>}]},
+ #xmlel{name = <<"option">>,
+ attrs =
+ [{<<"label">>,
+ ?T(Lang, <<"Edit Properties">>)}],
+ children =
+ [#xmlel{name = <<"value">>, attrs = [],
+ children =
+ [{xmlcdata,
+ <<"edit">>}]}]},
+ #xmlel{name = <<"option">>,
+ attrs =
+ [{<<"label">>,
+ ?T(Lang, <<"Remove User">>)}],
+ children =
+ [#xmlel{name = <<"value">>, attrs = [],
+ children =
+ [{xmlcdata,
+ <<"remove">>}]}]}]},
+ ?XFIELD(<<"text-private">>, <<"Password">>,
+ <<"password">>,
+ (ejabberd_auth:get_password_s(User, Server)))]}]};
get_sm_form(_User, _Server, _Node, _Lang) ->
{error, ?ERR_SERVICE_UNAVAILABLE}.
-
-set_sm_form(User, Server, "config",
- #adhoc_request{lang = Lang,
- node = Node,
- sessionid = SessionID}, XData) ->
- Response = #adhoc_response{lang = Lang,
- node = Node,
- sessionid = SessionID,
- status = completed},
- case lists:keysearch("action", 1, XData) of
- {value, {_, ["edit"]}} ->
- case lists:keysearch("password", 1, XData) of
- {value, {_, [Password]}} ->
- ejabberd_auth:set_password(User, Server, Password),
- adhoc:produce_response(Response);
- _ ->
- {error, ?ERR_NOT_ACCEPTABLE}
- end;
- {value, {_, ["remove"]}} ->
- catch ejabberd_auth:remove_user(User, Server),
- adhoc:produce_response(Response);
- _ ->
- {error, ?ERR_NOT_ACCEPTABLE}
+set_sm_form(User, Server, <<"config">>,
+ #adhoc_request{lang = Lang, node = Node,
+ sessionid = SessionID},
+ XData) ->
+ Response = #adhoc_response{lang = Lang, node = Node,
+ sessionid = SessionID, status = completed},
+ case lists:keysearch(<<"action">>, 1, XData) of
+ {value, {_, [<<"edit">>]}} ->
+ case lists:keysearch(<<"password">>, 1, XData) of
+ {value, {_, [Password]}} ->
+ ejabberd_auth:set_password(User, Server, Password),
+ adhoc:produce_response(Response);
+ _ -> {error, ?ERR_NOT_ACCEPTABLE}
+ end;
+ {value, {_, [<<"remove">>]}} ->
+ catch ejabberd_auth:remove_user(User, Server),
+ adhoc:produce_response(Response);
+ _ -> {error, ?ERR_NOT_ACCEPTABLE}
end;
-
set_sm_form(_User, _Server, _Node, _Request, _Fields) ->
{error, ?ERR_SERVICE_UNAVAILABLE}.
-
-
-
diff --git a/src/mod_configure2.erl b/src/mod_configure2.erl
index 77bbca57c..366538632 100644
--- a/src/mod_configure2.erl
+++ b/src/mod_configure2.erl
@@ -25,146 +25,173 @@
%%%----------------------------------------------------------------------
-module(mod_configure2).
+
-author('alexey@process-one.net').
-behaviour(gen_mod).
--export([start/2,
- stop/1,
- process_local_iq/3]).
+-export([start/2, stop/1, process_local_iq/3]).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
--define(NS_ECONFIGURE, "http://ejabberd.jabberstudio.org/protocol/configure").
+-define(NS_ECONFIGURE,
+ <<"http://ejabberd.jabberstudio.org/protocol/con"
+ "figure">>).
start(Host, Opts) ->
- IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
- gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_ECONFIGURE,
- ?MODULE, process_local_iq, IQDisc),
+ IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
+ one_queue),
+ gen_iq_handler:add_iq_handler(ejabberd_local, Host,
+ ?NS_ECONFIGURE, ?MODULE, process_local_iq,
+ IQDisc),
ok.
stop(Host) ->
- gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_ECONFIGURE).
-
+ gen_iq_handler:remove_iq_handler(ejabberd_local, Host,
+ ?NS_ECONFIGURE).
-process_local_iq(From, To, #iq{type = Type, lang = _Lang, sub_el = SubEl} = IQ) ->
+process_local_iq(From, To,
+ #iq{type = Type, lang = _Lang, sub_el = SubEl} = IQ) ->
case acl:match_rule(To#jid.lserver, configure, From) of
- deny ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
- allow ->
- case Type of
- set ->
- IQ#iq{type = error,
- sub_el = [SubEl, ?ERR_FEATURE_NOT_IMPLEMENTED]};
- %%case xml:get_tag_attr_s("type", SubEl) of
- %% "cancel" ->
- %% IQ#iq{type = result,
- %% sub_el = [{xmlelement, "query",
- %% [{"xmlns", XMLNS}], []}]};
- %% "submit" ->
- %% XData = jlib:parse_xdata_submit(SubEl),
- %% case XData of
- %% invalid ->
- %% IQ#iq{type = error,
- %% sub_el = [SubEl, ?ERR_BAD_REQUEST]};
- %% _ ->
- %% Node =
- %% string:tokens(
- %% xml:get_tag_attr_s("node", SubEl),
- %% "/"),
- %% case set_form(Node, Lang, XData) of
- %% {result, Res} ->
- %% IQ#iq{type = result,
- %% sub_el = [{xmlelement, "query",
- %% [{"xmlns", XMLNS}],
- %% Res
- %% }]};
- %% {error, Error} ->
- %% IQ#iq{type = error,
- %% sub_el = [SubEl, Error]}
- %% end
- %% end;
- %% _ ->
- %% IQ#iq{type = error,
- %% sub_el = [SubEl, ?ERR_NOT_ALLOWED]}
- %%end;
- get ->
- case process_get(SubEl) of
- {result, Res} ->
- IQ#iq{type = result, sub_el = [Res]};
- {error, Error} ->
- IQ#iq{type = error, sub_el = [SubEl, Error]}
- end
- end
+ deny ->
+ IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+ allow ->
+ case Type of
+ set ->
+ IQ#iq{type = error,
+ sub_el = [SubEl, ?ERR_FEATURE_NOT_IMPLEMENTED]};
+ %%case xml:get_tag_attr_s("type", SubEl) of
+ %% "cancel" ->
+ %% IQ#iq{type = result,
+ %% sub_el = [{xmlelement, "query",
+ %% [{"xmlns", XMLNS}], []}]};
+ %% "submit" ->
+ %% XData = jlib:parse_xdata_submit(SubEl),
+ %% case XData of
+ %% invalid ->
+ %% IQ#iq{type = error,
+ %% sub_el = [SubEl, ?ERR_BAD_REQUEST]};
+ %% _ ->
+ %% Node =
+ %% string:tokens(
+ %% xml:get_tag_attr_s("node", SubEl),
+ %% "/"),
+ %% case set_form(Node, Lang, XData) of
+ %% {result, Res} ->
+ %% IQ#iq{type = result,
+ %% sub_el = [{xmlelement, "query",
+ %% [{"xmlns", XMLNS}],
+ %% Res
+ %% }]};
+ %% {error, Error} ->
+ %% IQ#iq{type = error,
+ %% sub_el = [SubEl, Error]}
+ %% end
+ %% end;
+ %% _ ->
+ %% IQ#iq{type = error,
+ %% sub_el = [SubEl, ?ERR_NOT_ALLOWED]}
+ %%end;
+ get ->
+ case process_get(SubEl) of
+ {result, Res} -> IQ#iq{type = result, sub_el = [Res]};
+ {error, Error} ->
+ IQ#iq{type = error, sub_el = [SubEl, Error]}
+ end
+ end
end.
-
-process_get({xmlelement, "info", _Attrs, _SubEls}) ->
+process_get(#xmlel{name = <<"info">>}) ->
S2SConns = ejabberd_s2s:dirty_get_connections(),
TConns = lists:usort([element(2, C) || C <- S2SConns]),
- Attrs = [{"registered-users",
- integer_to_list(mnesia:table_info(passwd, size))},
- {"online-users",
- integer_to_list(mnesia:table_info(presence, size))},
- {"running-nodes",
- integer_to_list(length(mnesia:system_info(running_db_nodes)))},
- {"stopped-nodes",
- integer_to_list(
- length(lists:usort(mnesia:system_info(db_nodes) ++
- mnesia:system_info(extra_db_nodes)) --
- mnesia:system_info(running_db_nodes)))},
- {"outgoing-s2s-servers", integer_to_list(length(TConns))}],
- {result, {xmlelement, "info",
- [{"xmlns", ?NS_ECONFIGURE} | Attrs], []}};
-process_get({xmlelement, "welcome-message", Attrs, _SubEls}) ->
- {Subj, Body} = case ejabberd_config:get_local_option(welcome_message) of
- {_Subj, _Body} = SB -> SB;
- _ -> {"", ""}
- end,
- {result, {xmlelement, "welcome-message", Attrs,
- [{xmlelement, "subject", [], [{xmlcdata, Subj}]},
- {xmlelement, "body", [], [{xmlcdata, Body}]}]}};
-process_get({xmlelement, "registration-watchers", Attrs, _SubEls}) ->
- SubEls =
- case ejabberd_config:get_local_option(registration_watchers) of
- JIDs when is_list(JIDs) ->
- lists:map(fun(JID) ->
- {xmlelement, "jid", [], [{xmlcdata, JID}]}
- end, JIDs);
- _ ->
- []
- end,
- {result, {xmlelement, "registration_watchers", Attrs, SubEls}};
-process_get({xmlelement, "acls", Attrs, _SubEls}) ->
- Str = lists:flatten(io_lib:format("~p.", [ets:tab2list(acl)])),
- {result, {xmlelement, "acls", Attrs, [{xmlcdata, Str}]}};
-process_get({xmlelement, "access", Attrs, _SubEls}) ->
- Str =
- lists:flatten(
- io_lib:format(
- "~p.",
- [ets:select(config,
- [{{config, {access, '$1'}, '$2'},
- [],
- [{{access, '$1', '$2'}}]}])
- ])),
- {result, {xmlelement, "access", Attrs, [{xmlcdata, Str}]}};
-process_get({xmlelement, "last", Attrs, _SubEls}) ->
- case catch mnesia:dirty_select(
- last_activity, [{{last_activity, '_', '$1', '_'}, [], ['$1']}]) of
- {'EXIT', _Reason} ->
- {error, ?ERR_INTERNAL_SERVER_ERROR};
- Vals ->
- {MegaSecs, Secs, _MicroSecs} = now(),
- TimeStamp = MegaSecs * 1000000 + Secs,
- Str = lists:flatten(
- lists:append(
- [[integer_to_list(TimeStamp - V), " "] || V <- Vals])),
- {result, {xmlelement, "last", Attrs, [{xmlcdata, Str}]}}
+ Attrs = [{<<"registered-users">>,
+ iolist_to_binary(integer_to_list(mnesia:table_info(passwd,
+ size)))},
+ {<<"online-users">>,
+ iolist_to_binary(integer_to_list(mnesia:table_info(presence,
+ size)))},
+ {<<"running-nodes">>,
+ iolist_to_binary(integer_to_list(length(mnesia:system_info(running_db_nodes))))},
+ {<<"stopped-nodes">>,
+ iolist_to_binary(integer_to_list(length(lists:usort(mnesia:system_info(db_nodes)
+ ++
+ mnesia:system_info(extra_db_nodes))
+ --
+ mnesia:system_info(running_db_nodes))))},
+ {<<"outgoing-s2s-servers">>,
+ iolist_to_binary(integer_to_list(length(TConns)))}],
+ {result,
+ #xmlel{name = <<"info">>,
+ attrs = [{<<"xmlns">>, ?NS_ECONFIGURE} | Attrs],
+ children = []}};
+process_get(#xmlel{name = <<"welcome-message">>,
+ attrs = Attrs}) ->
+ {Subj, Body} = ejabberd_config:get_local_option(
+ welcome_message,
+ fun({Subj, Body}) ->
+ {iolist_to_binary(Subj),
+ iolist_to_binary(Body)}
+ end,
+ {<<"">>, <<"">>}),
+ {result,
+ #xmlel{name = <<"welcome-message">>, attrs = Attrs,
+ children =
+ [#xmlel{name = <<"subject">>, attrs = [],
+ children = [{xmlcdata, Subj}]},
+ #xmlel{name = <<"body">>, attrs = [],
+ children = [{xmlcdata, Body}]}]}};
+process_get(#xmlel{name = <<"registration-watchers">>,
+ attrs = Attrs}) ->
+ SubEls = ejabberd_config:get_local_option(
+ registration_watchers,
+ fun(JIDs) when is_list(JIDs) ->
+ lists:map(
+ fun(J) ->
+ #xmlel{name = <<"jid">>, attrs = [],
+ children = [{xmlcdata,
+ iolist_to_binary(J)}]}
+ end, JIDs)
+ end, []),
+ {result,
+ #xmlel{name = <<"registration_watchers">>,
+ attrs = Attrs, children = SubEls}};
+process_get(#xmlel{name = <<"acls">>, attrs = Attrs}) ->
+ Str = iolist_to_binary(io_lib:format("~p.",
+ [ets:tab2list(acl)])),
+ {result,
+ #xmlel{name = <<"acls">>, attrs = Attrs,
+ children = [{xmlcdata, Str}]}};
+process_get(#xmlel{name = <<"access">>,
+ attrs = Attrs}) ->
+ Str = iolist_to_binary(io_lib:format("~p.",
+ [ets:select(config,
+ [{{config, {access, '$1'},
+ '$2'},
+ [],
+ [{{access, '$1',
+ '$2'}}]}])])),
+ {result,
+ #xmlel{name = <<"access">>, attrs = Attrs,
+ children = [{xmlcdata, Str}]}};
+process_get(#xmlel{name = <<"last">>, attrs = Attrs}) ->
+ case catch mnesia:dirty_select(last_activity,
+ [{{last_activity, '_', '$1', '_'}, [],
+ ['$1']}])
+ of
+ {'EXIT', _Reason} ->
+ {error, ?ERR_INTERNAL_SERVER_ERROR};
+ Vals ->
+ {MegaSecs, Secs, _MicroSecs} = now(),
+ TimeStamp = MegaSecs * 1000000 + Secs,
+ Str = list_to_binary(
+ [[jlib:integer_to_binary(TimeStamp - V),
+ <<" ">>] || V <- Vals]),
+ {result,
+ #xmlel{name = <<"last">>, attrs = Attrs,
+ children = [{xmlcdata, Str}]}}
end;
%%process_get({xmlelement, Name, Attrs, SubEls}) ->
%% {result, };
-process_get(_) ->
- {error, ?ERR_BAD_REQUEST}.
-
+process_get(_) -> {error, ?ERR_BAD_REQUEST}.
diff --git a/src/mod_disco.erl b/src/mod_disco.erl
index 3d623e2b6..c883e0782 100644
--- a/src/mod_disco.erl
+++ b/src/mod_disco.erl
@@ -25,446 +25,469 @@
%%%----------------------------------------------------------------------
-module(mod_disco).
+
-author('alexey@process-one.net').
-behaviour(gen_mod).
--export([start/2,
- stop/1,
- process_local_iq_items/3,
- process_local_iq_info/3,
- get_local_identity/5,
- get_local_features/5,
- get_local_services/5,
- process_sm_iq_items/3,
- process_sm_iq_info/3,
- get_sm_identity/5,
- get_sm_features/5,
- get_sm_items/5,
- get_info/5,
- register_feature/2,
- unregister_feature/2,
- register_extra_domain/2,
- unregister_extra_domain/2]).
+-export([start/2, stop/1, process_local_iq_items/3,
+ process_local_iq_info/3, get_local_identity/5,
+ get_local_features/5, get_local_services/5,
+ process_sm_iq_items/3, process_sm_iq_info/3,
+ get_sm_identity/5, get_sm_features/5, get_sm_items/5,
+ get_info/5, register_feature/2, unregister_feature/2,
+ register_extra_domain/2, unregister_extra_domain/2]).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
+
-include("mod_roster.hrl").
start(Host, Opts) ->
ejabberd_local:refresh_iq_handlers(),
-
- IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
- gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS,
- ?MODULE, process_local_iq_items, IQDisc),
- gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO,
- ?MODULE, process_local_iq_info, IQDisc),
- gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_DISCO_ITEMS,
- ?MODULE, process_sm_iq_items, IQDisc),
- gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_DISCO_INFO,
- ?MODULE, process_sm_iq_info, IQDisc),
-
- catch ets:new(disco_features, [named_table, ordered_set, public]),
- register_feature(Host, "iq"),
- register_feature(Host, "presence"),
- register_feature(Host, "presence-invisible"),
-
- catch ets:new(disco_extra_domains, [named_table, ordered_set, public]),
- ExtraDomains = gen_mod:get_opt(extra_domains, Opts, []),
- lists:foreach(fun(Domain) -> register_extra_domain(Host, Domain) end,
+ IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
+ one_queue),
+ gen_iq_handler:add_iq_handler(ejabberd_local, Host,
+ ?NS_DISCO_ITEMS, ?MODULE,
+ process_local_iq_items, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_local, Host,
+ ?NS_DISCO_INFO, ?MODULE,
+ process_local_iq_info, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
+ ?NS_DISCO_ITEMS, ?MODULE, process_sm_iq_items,
+ IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
+ ?NS_DISCO_INFO, ?MODULE, process_sm_iq_info,
+ IQDisc),
+ catch ets:new(disco_features,
+ [named_table, ordered_set, public]),
+ register_feature(Host, <<"iq">>),
+ register_feature(Host, <<"presence">>),
+ catch ets:new(disco_extra_domains,
+ [named_table, ordered_set, public]),
+ ExtraDomains = gen_mod:get_opt(extra_domains, Opts,
+ fun(Hs) ->
+ [iolist_to_binary(H) || H <- Hs]
+ end, []),
+ lists:foreach(fun (Domain) ->
+ register_extra_domain(Host, Domain)
+ end,
ExtraDomains),
- catch ets:new(disco_sm_features, [named_table, ordered_set, public]),
- catch ets:new(disco_sm_nodes, [named_table, ordered_set, public]),
- ejabberd_hooks:add(disco_local_items, Host, ?MODULE, get_local_services, 100),
- ejabberd_hooks:add(disco_local_features, Host, ?MODULE, get_local_features, 100),
- ejabberd_hooks:add(disco_local_identity, Host, ?MODULE, get_local_identity, 100),
- ejabberd_hooks:add(disco_sm_items, Host, ?MODULE, get_sm_items, 100),
- ejabberd_hooks:add(disco_sm_features, Host, ?MODULE, get_sm_features, 100),
- ejabberd_hooks:add(disco_sm_identity, Host, ?MODULE, get_sm_identity, 100),
- ejabberd_hooks:add(disco_info, Host, ?MODULE, get_info, 100),
+ catch ets:new(disco_sm_features,
+ [named_table, ordered_set, public]),
+ catch ets:new(disco_sm_nodes,
+ [named_table, ordered_set, public]),
+ ejabberd_hooks:add(disco_local_items, Host, ?MODULE,
+ get_local_services, 100),
+ ejabberd_hooks:add(disco_local_features, Host, ?MODULE,
+ get_local_features, 100),
+ ejabberd_hooks:add(disco_local_identity, Host, ?MODULE,
+ get_local_identity, 100),
+ ejabberd_hooks:add(disco_sm_items, Host, ?MODULE,
+ get_sm_items, 100),
+ ejabberd_hooks:add(disco_sm_features, Host, ?MODULE,
+ get_sm_features, 100),
+ ejabberd_hooks:add(disco_sm_identity, Host, ?MODULE,
+ get_sm_identity, 100),
+ ejabberd_hooks:add(disco_info, Host, ?MODULE, get_info,
+ 100),
ok.
stop(Host) ->
- ejabberd_hooks:delete(disco_sm_identity, Host, ?MODULE, get_sm_identity, 100),
- ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, get_sm_features, 100),
- ejabberd_hooks:delete(disco_sm_items, Host, ?MODULE, get_sm_items, 100),
- ejabberd_hooks:delete(disco_local_identity, Host, ?MODULE, get_local_identity, 100),
- ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, get_local_features, 100),
- ejabberd_hooks:delete(disco_local_items, Host, ?MODULE, get_local_services, 100),
- ejabberd_hooks:delete(disco_info, Host, ?MODULE, get_info, 100),
- gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS),
- gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO),
- gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_DISCO_ITEMS),
- gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_DISCO_INFO),
+ ejabberd_hooks:delete(disco_sm_identity, Host, ?MODULE,
+ get_sm_identity, 100),
+ ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE,
+ get_sm_features, 100),
+ ejabberd_hooks:delete(disco_sm_items, Host, ?MODULE,
+ get_sm_items, 100),
+ ejabberd_hooks:delete(disco_local_identity, Host,
+ ?MODULE, get_local_identity, 100),
+ ejabberd_hooks:delete(disco_local_features, Host,
+ ?MODULE, get_local_features, 100),
+ ejabberd_hooks:delete(disco_local_items, Host, ?MODULE,
+ get_local_services, 100),
+ ejabberd_hooks:delete(disco_info, Host, ?MODULE,
+ get_info, 100),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, Host,
+ ?NS_DISCO_ITEMS),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, Host,
+ ?NS_DISCO_INFO),
+ gen_iq_handler:remove_iq_handler(ejabberd_sm, Host,
+ ?NS_DISCO_ITEMS),
+ gen_iq_handler:remove_iq_handler(ejabberd_sm, Host,
+ ?NS_DISCO_INFO),
catch ets:match_delete(disco_features, {{'_', Host}}),
- catch ets:match_delete(disco_extra_domains, {{'_', Host}}),
+ catch ets:match_delete(disco_extra_domains,
+ {{'_', Host}}),
ok.
-
register_feature(Host, Feature) ->
- catch ets:new(disco_features, [named_table, ordered_set, public]),
+ catch ets:new(disco_features,
+ [named_table, ordered_set, public]),
ets:insert(disco_features, {{Feature, Host}}).
unregister_feature(Host, Feature) ->
- catch ets:new(disco_features, [named_table, ordered_set, public]),
+ catch ets:new(disco_features,
+ [named_table, ordered_set, public]),
ets:delete(disco_features, {Feature, Host}).
register_extra_domain(Host, Domain) ->
- catch ets:new(disco_extra_domains, [named_table, ordered_set, public]),
+ catch ets:new(disco_extra_domains,
+ [named_table, ordered_set, public]),
ets:insert(disco_extra_domains, {{Domain, Host}}).
unregister_extra_domain(Host, Domain) ->
- catch ets:new(disco_extra_domains, [named_table, ordered_set, public]),
+ catch ets:new(disco_extra_domains,
+ [named_table, ordered_set, public]),
ets:delete(disco_extra_domains, {Domain, Host}).
-process_local_iq_items(From, To, #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
+process_local_iq_items(From, To,
+ #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
case Type of
- set ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
- get ->
- Node = xml:get_tag_attr_s("node", SubEl),
- Host = To#jid.lserver,
-
- case ejabberd_hooks:run_fold(disco_local_items,
- Host,
- empty,
- [From, To, Node, Lang]) of
- {result, Items} ->
- ANode = case Node of
- "" -> [];
- _ -> [{"node", Node}]
- end,
- IQ#iq{type = result,
- sub_el = [{xmlelement, "query",
- [{"xmlns", ?NS_DISCO_ITEMS} | ANode],
- Items
- }]};
- {error, Error} ->
- IQ#iq{type = error, sub_el = [SubEl, Error]}
- end
+ set ->
+ IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+ get ->
+ Node = xml:get_tag_attr_s(<<"node">>, SubEl),
+ Host = To#jid.lserver,
+ case ejabberd_hooks:run_fold(disco_local_items, Host,
+ empty, [From, To, Node, Lang])
+ of
+ {result, Items} ->
+ ANode = case Node of
+ <<"">> -> [];
+ _ -> [{<<"node">>, Node}]
+ end,
+ IQ#iq{type = result,
+ sub_el =
+ [#xmlel{name = <<"query">>,
+ attrs =
+ [{<<"xmlns">>, ?NS_DISCO_ITEMS} | ANode],
+ children = Items}]};
+ {error, Error} ->
+ IQ#iq{type = error, sub_el = [SubEl, Error]}
+ end
end.
-
-process_local_iq_info(From, To, #iq{type = Type, lang = Lang,
- sub_el = SubEl} = IQ) ->
+process_local_iq_info(From, To,
+ #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
case Type of
- set ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
- get ->
- Host = To#jid.lserver,
- Node = xml:get_tag_attr_s("node", SubEl),
- Identity = ejabberd_hooks:run_fold(disco_local_identity,
- Host,
- [],
- [From, To, Node, Lang]),
- Info = ejabberd_hooks:run_fold(disco_info, Host, [],
- [Host, ?MODULE, Node, Lang]),
- case ejabberd_hooks:run_fold(disco_local_features,
- Host,
- empty,
- [From, To, Node, Lang]) of
- {result, Features} ->
- ANode = case Node of
- "" -> [];
- _ -> [{"node", Node}]
- end,
- IQ#iq{type = result,
- sub_el = [{xmlelement, "query",
- [{"xmlns", ?NS_DISCO_INFO} | ANode],
- Identity ++
- Info ++
- features_to_xml(Features)
- }]};
- {error, Error} ->
- IQ#iq{type = error, sub_el = [SubEl, Error]}
- end
+ set ->
+ IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+ get ->
+ Host = To#jid.lserver,
+ Node = xml:get_tag_attr_s(<<"node">>, SubEl),
+ Identity = ejabberd_hooks:run_fold(disco_local_identity,
+ Host, [], [From, To, Node, Lang]),
+ Info = ejabberd_hooks:run_fold(disco_info, Host, [],
+ [Host, ?MODULE, Node, Lang]),
+ case ejabberd_hooks:run_fold(disco_local_features, Host,
+ empty, [From, To, Node, Lang])
+ of
+ {result, Features} ->
+ ANode = case Node of
+ <<"">> -> [];
+ _ -> [{<<"node">>, Node}]
+ end,
+ IQ#iq{type = result,
+ sub_el =
+ [#xmlel{name = <<"query">>,
+ attrs =
+ [{<<"xmlns">>, ?NS_DISCO_INFO} | ANode],
+ children =
+ Identity ++
+ Info ++ features_to_xml(Features)}]};
+ {error, Error} ->
+ IQ#iq{type = error, sub_el = [SubEl, Error]}
+ end
end.
-get_local_identity(Acc, _From, _To, [], _Lang) ->
- Acc ++ [{xmlelement, "identity",
- [{"category", "server"},
- {"type", "im"},
- {"name", "ejabberd"}], []}];
-
+get_local_identity(Acc, _From, _To, <<>>, _Lang) ->
+ Acc ++
+ [#xmlel{name = <<"identity">>,
+ attrs =
+ [{<<"category">>, <<"server">>}, {<<"type">>, <<"im">>},
+ {<<"name">>, <<"ejabberd">>}],
+ children = []}];
get_local_identity(Acc, _From, _To, _Node, _Lang) ->
Acc.
-get_local_features({error, _Error} = Acc, _From, _To, _Node, _Lang) ->
+get_local_features({error, _Error} = Acc, _From, _To,
+ _Node, _Lang) ->
Acc;
-
-get_local_features(Acc, _From, To, [], _Lang) ->
+get_local_features(Acc, _From, To, <<>>, _Lang) ->
Feats = case Acc of
- {result, Features} -> Features;
- empty -> []
+ {result, Features} -> Features;
+ empty -> []
end,
Host = To#jid.lserver,
{result,
- ets:select(disco_features, [{{{'_', Host}}, [], ['$_']}]) ++ Feats};
-
+ ets:select(disco_features,
+ [{{{'_', Host}}, [], ['$_']}])
+ ++ Feats};
get_local_features(Acc, _From, _To, _Node, _Lang) ->
case Acc of
- {result, _Features} ->
- Acc;
- empty ->
- {error, ?ERR_ITEM_NOT_FOUND}
+ {result, _Features} -> Acc;
+ empty -> {error, ?ERR_ITEM_NOT_FOUND}
end.
-
features_to_xml(FeatureList) ->
- %% Avoid duplicating features
- [{xmlelement, "feature", [{"var", Feat}], []} ||
- Feat <- lists:usort(
- lists:map(
- fun({{Feature, _Host}}) ->
- Feature;
- (Feature) when is_list(Feature) ->
- Feature
- end, FeatureList))].
+ [#xmlel{name = <<"feature">>,
+ attrs = [{<<"var">>, Feat}], children = []}
+ || Feat
+ <- lists:usort(lists:map(fun ({{Feature, _Host}}) ->
+ Feature;
+ (Feature) when is_binary(Feature) ->
+ Feature
+ end,
+ FeatureList))].
domain_to_xml({Domain}) ->
- {xmlelement, "item", [{"jid", Domain}], []};
+ #xmlel{name = <<"item">>, attrs = [{<<"jid">>, Domain}],
+ children = []};
domain_to_xml(Domain) ->
- {xmlelement, "item", [{"jid", Domain}], []}.
+ #xmlel{name = <<"item">>, attrs = [{<<"jid">>, Domain}],
+ children = []}.
-get_local_services({error, _Error} = Acc, _From, _To, _Node, _Lang) ->
+get_local_services({error, _Error} = Acc, _From, _To,
+ _Node, _Lang) ->
Acc;
-
-get_local_services(Acc, _From, To, [], _Lang) ->
+get_local_services(Acc, _From, To, <<>>, _Lang) ->
Items = case Acc of
- {result, Its} -> Its;
- empty -> []
+ {result, Its} -> Its;
+ empty -> []
end,
Host = To#jid.lserver,
{result,
- lists:usort(
- lists:map(fun domain_to_xml/1,
- get_vh_services(Host) ++
- ets:select(disco_extra_domains,
- [{{{'$1', Host}}, [], ['$1']}]))
- ) ++ Items};
-
-get_local_services({result, _} = Acc, _From, _To, _Node, _Lang) ->
+ lists:usort(lists:map(fun domain_to_xml/1,
+ get_vh_services(Host) ++
+ ets:select(disco_extra_domains,
+ [{{{'$1', Host}}, [], ['$1']}])))
+ ++ Items};
+get_local_services({result, _} = Acc, _From, _To, _Node,
+ _Lang) ->
Acc;
-
get_local_services(empty, _From, _To, _Node, _Lang) ->
{error, ?ERR_ITEM_NOT_FOUND}.
get_vh_services(Host) ->
- Hosts = lists:sort(fun(H1, H2) -> length(H1) >= length(H2) end, ?MYHOSTS),
- lists:filter(fun(H) ->
- case lists:dropwhile(
- fun(VH) ->
- not lists:suffix("." ++ VH, H)
- end, Hosts) of
- [] ->
- false;
- [VH | _] ->
- VH == Host
+ Hosts = lists:sort(fun (H1, H2) ->
+ byte_size(H1) >= byte_size(H2)
+ end,
+ ?MYHOSTS),
+ lists:filter(fun (H) ->
+ case lists:dropwhile(fun (VH) ->
+ not
+ str:suffix(
+ <<".", VH/binary>>,
+ H)
+ end,
+ Hosts)
+ of
+ [] -> false;
+ [VH | _] -> VH == Host
end
- end, ejabberd_router:dirty_get_all_routes()).
+ end,
+ ejabberd_router:dirty_get_all_routes()).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-process_sm_iq_items(From, To, #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
+process_sm_iq_items(From, To,
+ #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
case Type of
- set ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
- get ->
- case is_presence_subscribed(From, To) of
- true ->
- Host = To#jid.lserver,
- Node = xml:get_tag_attr_s("node", SubEl),
- case ejabberd_hooks:run_fold(disco_sm_items,
- Host,
- empty,
- [From, To, Node, Lang]) of
- {result, Items} ->
- ANode = case Node of
- "" -> [];
- _ -> [{"node", Node}]
- end,
- IQ#iq{type = result,
- sub_el = [{xmlelement, "query",
- [{"xmlns", ?NS_DISCO_ITEMS} | ANode],
- Items
- }]};
- {error, Error} ->
- IQ#iq{type = error, sub_el = [SubEl, Error]}
- end;
- false ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_SERVICE_UNAVAILABLE]}
- end
+ set ->
+ IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+ get ->
+ case is_presence_subscribed(From, To) of
+ true ->
+ Host = To#jid.lserver,
+ Node = xml:get_tag_attr_s(<<"node">>, SubEl),
+ case ejabberd_hooks:run_fold(disco_sm_items, Host,
+ empty, [From, To, Node, Lang])
+ of
+ {result, Items} ->
+ ANode = case Node of
+ <<"">> -> [];
+ _ -> [{<<"node">>, Node}]
+ end,
+ IQ#iq{type = result,
+ sub_el =
+ [#xmlel{name = <<"query">>,
+ attrs =
+ [{<<"xmlns">>, ?NS_DISCO_ITEMS}
+ | ANode],
+ children = Items}]};
+ {error, Error} ->
+ IQ#iq{type = error, sub_el = [SubEl, Error]}
+ end;
+ false ->
+ IQ#iq{type = error,
+ sub_el = [SubEl, ?ERR_SERVICE_UNAVAILABLE]}
+ end
end.
-get_sm_items({error, _Error} = Acc, _From, _To, _Node, _Lang) ->
+get_sm_items({error, _Error} = Acc, _From, _To, _Node,
+ _Lang) ->
Acc;
-
get_sm_items(Acc, From,
- #jid{user = User, server = Server} = To,
- [], _Lang) ->
+ #jid{user = User, server = Server} = To, <<>>, _Lang) ->
Items = case Acc of
- {result, Its} -> Its;
- empty -> []
+ {result, Its} -> Its;
+ empty -> []
end,
Items1 = case is_presence_subscribed(From, To) of
- true ->
- get_user_resources(User, Server);
- _ ->
- []
- end,
+ true -> get_user_resources(User, Server);
+ _ -> []
+ end,
{result, Items ++ Items1};
-
-get_sm_items({result, _} = Acc, _From, _To, _Node, _Lang) ->
+get_sm_items({result, _} = Acc, _From, _To, _Node,
+ _Lang) ->
Acc;
-
get_sm_items(empty, From, To, _Node, _Lang) ->
#jid{luser = LFrom, lserver = LSFrom} = From,
#jid{luser = LTo, lserver = LSTo} = To,
case {LFrom, LSFrom} of
- {LTo, LSTo} ->
- {error, ?ERR_ITEM_NOT_FOUND};
- _ ->
- {error, ?ERR_NOT_ALLOWED}
+ {LTo, LSTo} -> {error, ?ERR_ITEM_NOT_FOUND};
+ _ -> {error, ?ERR_NOT_ALLOWED}
end.
-is_presence_subscribed(#jid{luser=User, lserver=Server}, #jid{luser=LUser, lserver=LServer}) ->
- lists:any(fun(#roster{jid = {TUser, TServer, _}, subscription = S}) ->
- if
- LUser == TUser, LServer == TServer, S/=none ->
- true;
- true ->
- false
- end
- end,
- ejabberd_hooks:run_fold(roster_get, Server, [], [{User, Server}]))
- orelse User == LUser andalso Server == LServer.
-
-process_sm_iq_info(From, To, #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
+is_presence_subscribed(#jid{luser = User,
+ lserver = Server},
+ #jid{luser = LUser, lserver = LServer}) ->
+ lists:any(fun (#roster{jid = {TUser, TServer, _},
+ subscription = S}) ->
+ if LUser == TUser, LServer == TServer, S /= none ->
+ true;
+ true -> false
+ end
+ end,
+ ejabberd_hooks:run_fold(roster_get, Server, [],
+ [{User, Server}]))
+ orelse User == LUser andalso Server == LServer.
+
+process_sm_iq_info(From, To,
+ #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
case Type of
- set ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
- get ->
- case is_presence_subscribed(From, To) of
- true ->
- Host = To#jid.lserver,
- Node = xml:get_tag_attr_s("node", SubEl),
- Identity = ejabberd_hooks:run_fold(disco_sm_identity,
- Host,
- [],
- [From, To, Node, Lang]),
- case ejabberd_hooks:run_fold(disco_sm_features,
- Host,
- empty,
- [From, To, Node, Lang]) of
- {result, Features} ->
- ANode = case Node of
- "" -> [];
- _ -> [{"node", Node}]
- end,
- IQ#iq{type = result,
- sub_el = [{xmlelement, "query",
- [{"xmlns", ?NS_DISCO_INFO} | ANode],
- Identity ++
- features_to_xml(Features)
- }]};
- {error, Error} ->
- IQ#iq{type = error, sub_el = [SubEl, Error]}
- end;
- false ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_SERVICE_UNAVAILABLE]}
- end
- end.
-
-get_sm_identity(Acc, _From, #jid{luser = LUser, lserver=LServer}, _Node, _Lang) ->
- Acc ++ case ejabberd_auth:is_user_exists(LUser, LServer) of
- true ->
- [{xmlelement, "identity", [{"category", "account"},
- {"type", "registered"}], []}];
- _ ->
- []
+ set ->
+ IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+ get ->
+ case is_presence_subscribed(From, To) of
+ true ->
+ Host = To#jid.lserver,
+ Node = xml:get_tag_attr_s(<<"node">>, SubEl),
+ Identity = ejabberd_hooks:run_fold(disco_sm_identity,
+ Host, [],
+ [From, To, Node, Lang]),
+ case ejabberd_hooks:run_fold(disco_sm_features, Host,
+ empty, [From, To, Node, Lang])
+ of
+ {result, Features} ->
+ ANode = case Node of
+ <<"">> -> [];
+ _ -> [{<<"node">>, Node}]
+ end,
+ IQ#iq{type = result,
+ sub_el =
+ [#xmlel{name = <<"query">>,
+ attrs =
+ [{<<"xmlns">>, ?NS_DISCO_INFO}
+ | ANode],
+ children =
+ Identity ++
+ features_to_xml(Features)}]};
+ {error, Error} ->
+ IQ#iq{type = error, sub_el = [SubEl, Error]}
+ end;
+ false ->
+ IQ#iq{type = error,
+ sub_el = [SubEl, ?ERR_SERVICE_UNAVAILABLE]}
+ end
end.
+get_sm_identity(Acc, _From,
+ #jid{luser = LUser, lserver = LServer}, _Node, _Lang) ->
+ Acc ++
+ case ejabberd_auth:is_user_exists(LUser, LServer) of
+ true ->
+ [#xmlel{name = <<"identity">>,
+ attrs =
+ [{<<"category">>, <<"account">>},
+ {<<"type">>, <<"registered">>}],
+ children = []}];
+ _ -> []
+ end.
get_sm_features(empty, From, To, _Node, _Lang) ->
#jid{luser = LFrom, lserver = LSFrom} = From,
#jid{luser = LTo, lserver = LSTo} = To,
case {LFrom, LSFrom} of
- {LTo, LSTo} ->
- {error, ?ERR_ITEM_NOT_FOUND};
- _ ->
- {error, ?ERR_NOT_ALLOWED}
+ {LTo, LSTo} -> {error, ?ERR_ITEM_NOT_FOUND};
+ _ -> {error, ?ERR_NOT_ALLOWED}
end;
-
-get_sm_features(Acc, _From, _To, _Node, _Lang) ->
- Acc.
+get_sm_features(Acc, _From, _To, _Node, _Lang) -> Acc.
get_user_resources(User, Server) ->
Rs = ejabberd_sm:get_user_resources(User, Server),
- lists:map(fun(R) ->
- {xmlelement, "item",
- [{"jid", User ++ "@" ++ Server ++ "/" ++ R},
- {"name", User}], []}
- end, lists:sort(Rs)).
+ lists:map(fun (R) ->
+ #xmlel{name = <<"item">>,
+ attrs =
+ [{<<"jid">>,
+ <<User/binary, "@", Server/binary, "/",
+ R/binary>>},
+ {<<"name">>, User}],
+ children = []}
+ end,
+ lists:sort(Rs)).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Support for: XEP-0157 Contact Addresses for XMPP Services
-get_info(_A, Host, Mod, Node, _Lang) when Node == [] ->
+get_info(_A, Host, Mod, Node, _Lang) when Node == <<>> ->
Module = case Mod of
- undefined ->
- ?MODULE;
- _ ->
- Mod
+ undefined -> ?MODULE;
+ _ -> Mod
end,
Serverinfo_fields = get_fields_xml(Host, Module),
- [{xmlelement, "x",
- [{"xmlns", ?NS_XDATA}, {"type", "result"}],
- [{xmlelement, "field",
- [{"var", "FORM_TYPE"}, {"type", "hidden"}],
- [{xmlelement, "value",
- [],
- [{xmlcdata, ?NS_SERVERINFO}]
- }]
- }]
- ++ Serverinfo_fields
- }];
-
-get_info(Acc, _, _, _Node, _) ->
- Acc.
+ [#xmlel{name = <<"x">>,
+ attrs =
+ [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"result">>}],
+ children =
+ [#xmlel{name = <<"field">>,
+ attrs =
+ [{<<"var">>, <<"FORM_TYPE">>},
+ {<<"type">>, <<"hidden">>}],
+ children =
+ [#xmlel{name = <<"value">>, attrs = [],
+ children = [{xmlcdata, ?NS_SERVERINFO}]}]}]
+ ++ Serverinfo_fields}];
+get_info(Acc, _, _, _Node, _) -> Acc.
get_fields_xml(Host, Module) ->
- Fields = gen_mod:get_module_opt(Host, ?MODULE, server_info, []),
-
- %% filter, and get only the ones allowed for this module
- Fields_good = lists:filter(
- fun({Modules, _, _}) ->
- case Modules of
- all -> true;
- Modules -> lists:member(Module, Modules)
- end
- end,
- Fields),
-
+ Fields = gen_mod:get_module_opt(Host, ?MODULE, server_info,
+ fun(L) when is_list(L) -> L end,
+ []),
+ Fields_good = lists:filter(fun ({Modules, _, _}) ->
+ case Modules of
+ all -> true;
+ Modules ->
+ lists:member(Module, Modules)
+ end
+ end,
+ Fields),
fields_to_xml(Fields_good).
fields_to_xml(Fields) ->
- [ field_to_xml(Field) || Field <- Fields].
+ [field_to_xml(Field) || Field <- Fields].
field_to_xml({_, Var, Values}) ->
Values_xml = values_to_xml(Values),
- {xmlelement, "field",
- [{"var", Var}],
- Values_xml
- }.
+ #xmlel{name = <<"field">>, attrs = [{<<"var">>, Var}],
+ children = Values_xml}.
values_to_xml(Values) ->
- lists:map(
- fun(Value) ->
- {xmlelement, "value",
- [],
- [{xmlcdata, Value}]
- }
- end,
- Values
- ).
+ lists:map(fun (Value) ->
+ #xmlel{name = <<"value">>, attrs = [],
+ children = [{xmlcdata, Value}]}
+ end,
+ Values).
diff --git a/src/mod_echo.erl b/src/mod_echo.erl
index 9c7259481..15df69244 100644
--- a/src/mod_echo.erl
+++ b/src/mod_echo.erl
@@ -25,22 +25,26 @@
%%%----------------------------------------------------------------------
-module(mod_echo).
+
-author('alexey@process-one.net').
-behaviour(gen_server).
+
-behaviour(gen_mod).
%% API
--export([start_link/2, start/2, stop/1, do_client_version/3]).
+-export([start_link/2, start/2, stop/1,
+ do_client_version/3]).
%% gen_server callbacks
--export([init/1, handle_call/3, handle_cast/2, handle_info/2,
- terminate/2, code_change/3]).
+-export([init/1, handle_call/3, handle_cast/2,
+ handle_info/2, terminate/2, code_change/3]).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
--record(state, {host}).
+-record(state, {host = <<"">> :: binary()}).
-define(PROCNAME, ejabberd_mod_echo).
@@ -53,17 +57,13 @@
%%--------------------------------------------------------------------
start_link(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
- gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
+ gen_server:start_link({local, Proc}, ?MODULE,
+ [Host, Opts], []).
start(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
- ChildSpec =
- {Proc,
- {?MODULE, start_link, [Host, Opts]},
- temporary,
- 1000,
- worker,
- [?MODULE]},
+ ChildSpec = {Proc, {?MODULE, start_link, [Host, Opts]},
+ temporary, 1000, worker, [?MODULE]},
supervisor:start_child(ejabberd_sup, ChildSpec).
stop(Host) ->
@@ -72,7 +72,6 @@ stop(Host) ->
supervisor:terminate_child(ejabberd_sup, Proc),
supervisor:delete_child(ejabberd_sup, Proc).
-
%%====================================================================
%% gen_server callbacks
%%====================================================================
@@ -85,7 +84,8 @@ stop(Host) ->
%% Description: Initiates the server
%%--------------------------------------------------------------------
init([Host, Opts]) ->
- MyHost = gen_mod:get_opt_host(Host, Opts, "echo.@HOST@"),
+ MyHost = gen_mod:get_opt_host(Host, Opts,
+ <<"echo.@HOST@">>),
ejabberd_router:register_route(MyHost),
{ok, #state{host = MyHost}}.
@@ -107,8 +107,7 @@ handle_call(stop, _From, State) ->
%% {stop, Reason, State}
%% Description: Handling cast messages
%%--------------------------------------------------------------------
-handle_cast(_Msg, State) ->
- {noreply, State}.
+handle_cast(_Msg, State) -> {noreply, State}.
%%--------------------------------------------------------------------
%% Function: handle_info(Info, State) -> {noreply, State} |
@@ -117,15 +116,15 @@ handle_cast(_Msg, State) ->
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------
handle_info({route, From, To, Packet}, State) ->
- Packet2 = case From#jid.user of
- "" -> jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST);
+ Packet2 = case From#jid.user of
+ <<"">> ->
+ jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST);
_ -> Packet
- end,
- do_client_version(disabled, To, From), % Put 'enabled' to enable it
+ end,
+ do_client_version(disabled, To, From),
ejabberd_router:route(To, From, Packet2),
{noreply, State};
-handle_info(_Info, State) ->
- {noreply, State}.
+handle_info(_Info, State) -> {noreply, State}.
%%--------------------------------------------------------------------
%% Function: terminate(Reason, State) -> void()
@@ -135,15 +134,13 @@ handle_info(_Info, State) ->
%% The return value is ignored.
%%--------------------------------------------------------------------
terminate(_Reason, State) ->
- ejabberd_router:unregister_route(State#state.host),
- ok.
+ ejabberd_router:unregister_route(State#state.host), ok.
%%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
%% Description: Convert process state when code is changed
%%--------------------------------------------------------------------
-code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
+code_change(_OldVsn, State, _Extra) -> {ok, State}.
%%--------------------------------------------------------------------
%% Example of routing XMPP packets using Erlang's message passing
@@ -168,36 +165,34 @@ code_change(_OldVsn, State, _Extra) ->
%% using exactly the same JID. We add a (mostly) random resource to
%% try to guarantee that the received response matches the request sent.
%% Finally, the received response is printed in the ejabberd log file.
-do_client_version(disabled, _From, _To) ->
- ok;
+do_client_version(disabled, _From, _To) -> ok;
do_client_version(enabled, From, To) ->
ToS = jlib:jid_to_string(To),
- %% It is important to identify this process and packet
- Random_resource = integer_to_list(random:uniform(100000)),
+ Random_resource =
+ iolist_to_binary(integer_to_list(random:uniform(100000))),
From2 = From#jid{resource = Random_resource,
lresource = Random_resource},
-
- %% Build an iq:query request
- Packet = {xmlelement, "iq",
- [{"to", ToS}, {"type", "get"}],
- [{xmlelement, "query", [{"xmlns", ?NS_VERSION}], []}]},
-
- %% Send the request
- ejabberd_router:route(From2, To, Packet),
-
- %% Wait to receive the response
- %% It is very important to only accept a packet which is the
- %% response to the request that he sent
- Els = receive {route, To, From2, IQ} ->
- {xmlelement, "query", _, List} = xml:get_subtag(IQ, "query"),
- List
- after 5000 -> % Timeout in miliseconds: 5 seconds
- []
+ Packet = #xmlel{name = <<"iq">>,
+ attrs = [{<<"to">>, ToS}, {<<"type">>, <<"get">>}],
+ children =
+ [#xmlel{name = <<"query">>,
+ attrs = [{<<"xmlns">>, ?NS_VERSION}],
+ children = []}]},
+ ejabberd_router:route(From2, To, Packet),
+ Els = receive
+ {route, To, From2, IQ} ->
+ #xmlel{name = <<"query">>, children = List} =
+ xml:get_subtag(IQ, <<"query">>),
+ List
+ after 5000 -> % Timeout in miliseconds: 5 seconds
+ []
end,
- Values = [{Name, Value} || {xmlelement,Name,[],[{xmlcdata,Value}]} <- Els],
-
- %% Print in log
- Values_string1 = [io_lib:format("~n~s: ~p", [N, V]) || {N, V} <- Values],
- Values_string2 = lists:concat(Values_string1),
- ?INFO_MSG("Information of the client: ~s~s", [ToS, Values_string2]).
-
+ Values = [{Name, Value}
+ || #xmlel{name = Name, attrs = [],
+ children = [{xmlcdata, Value}]}
+ <- Els],
+ Values_string1 = [io_lib:format("~n~s: ~p", [N, V])
+ || {N, V} <- Values],
+ Values_string2 = iolist_to_binary(Values_string1),
+ ?INFO_MSG("Information of the client: ~s~s",
+ [ToS, Values_string2]).
diff --git a/src/mod_ip_blacklist.erl b/src/mod_ip_blacklist.erl
index 5d2380c6e..073dd5af0 100644
--- a/src/mod_ip_blacklist.erl
+++ b/src/mod_ip_blacklist.erl
@@ -27,65 +27,61 @@
%%%----------------------------------------------------------------------
-module(mod_ip_blacklist).
+
-author('mremond@process-one.net').
-behaviour(gen_mod).
%% API:
--export([start/2,
- preinit/2,
- init/1,
- stop/1]).
+-export([start/2, preinit/2, init/1, stop/1]).
+
-export([update_bl_c2s/0]).
+
%% Hooks:
-export([is_ip_in_c2s_blacklist/2]).
-include("ejabberd.hrl").
-define(PROCNAME, ?MODULE).
--define(BLC2S, "http://xaai.process-one.net/bl_c2s.txt").
--define(UPDATE_INTERVAL, 6). %% in hours
+
+-define(BLC2S,
+ <<"http://xaai.process-one.net/bl_c2s.txt">>).
+
+-define(UPDATE_INTERVAL, 6).
-record(state, {timer}).
--record(bl_c2s, {ip}).
%% Start once for all vhost
+-record(bl_c2s, {ip = <<"">> :: binary()}).
+
start(_Host, _Opts) ->
- Pid = spawn(?MODULE, preinit, [self(), #state{}]),
- receive {ok, Pid, PreinitResult} ->
- PreinitResult
- end.
+ Pid = spawn(?MODULE, preinit, [self(), #state{}]),
+ receive {ok, Pid, PreinitResult} -> PreinitResult end.
preinit(Parent, State) ->
Pid = self(),
try register(?PROCNAME, Pid) of
- true ->
- Parent ! {ok, Pid, true},
- init(State)
- catch error:_ ->
- Parent ! {ok, Pid, true}
+ true -> Parent ! {ok, Pid, true}, init(State)
+ catch
+ error:_ -> Parent ! {ok, Pid, true}
end.
%% TODO:
-stop(_Host) ->
- ok.
+stop(_Host) -> ok.
-init(State)->
+init(State) ->
inets:start(),
- ets:new(bl_c2s, [named_table, public, {keypos, #bl_c2s.ip}]),
+ ets:new(bl_c2s,
+ [named_table, public, {keypos, #bl_c2s.ip}]),
update_bl_c2s(),
- %% Register hooks for blacklist
- ejabberd_hooks:add(check_bl_c2s, ?MODULE, is_ip_in_c2s_blacklist, 50),
- %% Set timer: Download the blacklist file every 6 hours
- timer:apply_interval(timer:hours(?UPDATE_INTERVAL), ?MODULE, update_bl_c2s, []),
+ ejabberd_hooks:add(check_bl_c2s, ?MODULE,
+ is_ip_in_c2s_blacklist, 50),
+ timer:apply_interval(timer:hours(?UPDATE_INTERVAL),
+ ?MODULE, update_bl_c2s, []),
loop(State).
%% Remove timer when stop is received.
-loop(_State) ->
- receive
- stop ->
- ok
- end.
+loop(_State) -> receive stop -> ok end.
%% Download blacklist file from ProcessOne XAAI
%% and update the table internal table
@@ -93,16 +89,18 @@ loop(_State) ->
update_bl_c2s() ->
?INFO_MSG("Updating C2S Blacklist", []),
case httpc:request(?BLC2S) of
- {ok, {{_Version, 200, _Reason}, _Headers, Body}} ->
- IPs = string:tokens(Body,"\n"),
- ets:delete_all_objects(bl_c2s),
- lists:foreach(
- fun(IP) ->
- ets:insert(bl_c2s, #bl_c2s{ip=list_to_binary(IP)})
- end, IPs);
- {error, Reason} ->
- ?ERROR_MSG("Cannot download C2S blacklist file. Reason: ~p",
- [Reason])
+ {ok, 200, _Headers, Body} ->
+ IPs = str:tokens(Body, <<"\n">>),
+ ets:delete_all_objects(bl_c2s),
+ lists:foreach(fun (IP) ->
+ ets:insert(bl_c2s,
+ #bl_c2s{ip = IP})
+ end,
+ IPs);
+ {error, Reason} ->
+ ?ERROR_MSG("Cannot download C2S blacklist file. "
+ "Reason: ~p",
+ [Reason])
end.
%% Hook is run with:
@@ -111,16 +109,15 @@ update_bl_c2s() ->
%% true: IP is blacklisted
%% IPV4 IP tuple:
is_ip_in_c2s_blacklist(_Val, IP) when is_tuple(IP) ->
- BinaryIP = list_to_binary(jlib:ip_to_list(IP)),
+ BinaryIP = jlib:ip_to_list(IP),
case ets:lookup(bl_c2s, BinaryIP) of
- [] -> %% Not in blacklist
- false;
- [_] -> %% Blacklisted!
- {stop, true}
+ [] -> %% Not in blacklist
+ false;
+ [_] -> {stop, true}
end;
-is_ip_in_c2s_blacklist(_Val, _IP) ->
- false.
+is_ip_in_c2s_blacklist(_Val, _IP) -> false.
%% TODO:
%% - For now, we do not kick user already logged on a given IP after
%% we update the blacklist.
+
diff --git a/src/mod_irc/Makefile.in b/src/mod_irc/Makefile.in
index e1551f929..9dcf9f182 100644
--- a/src/mod_irc/Makefile.in
+++ b/src/mod_irc/Makefile.in
@@ -24,7 +24,7 @@ EFLAGS += -pz ..
# make debug=true to compile Erlang module with debug informations.
ifdef debug
- EFLAGS+=+debug_info +export_all
+ EFLAGS+=+debug_info
endif
ERLSHLIBS = ../iconv_erl.so
diff --git a/src/mod_irc/iconv.erl b/src/mod_irc/iconv.erl
index b93bb2ea3..4d8180539 100644
--- a/src/mod_irc/iconv.erl
+++ b/src/mod_irc/iconv.erl
@@ -25,6 +25,7 @@
%%%----------------------------------------------------------------------
-module(iconv).
+
-author('alexey@process-one.net').
-behaviour(gen_server).
@@ -32,63 +33,50 @@
-export([start/0, start_link/0, convert/3]).
%% Internal exports, call-back functions.
--export([init/1,
- handle_call/3,
- handle_cast/2,
- handle_info/2,
- code_change/3,
- terminate/2]).
-
-
+-export([init/1, handle_call/3, handle_cast/2,
+ handle_info/2, code_change/3, terminate/2]).
start() ->
gen_server:start({local, ?MODULE}, ?MODULE, [], []).
start_link() ->
- gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
+ gen_server:start_link({local, ?MODULE}, ?MODULE, [],
+ []).
init([]) ->
- case erl_ddll:load_driver(ejabberd:get_so_path(), iconv_erl) of
- ok -> ok;
- {error, already_loaded} -> ok
+ case erl_ddll:load_driver(ejabberd:get_so_path(),
+ iconv_erl)
+ of
+ ok -> ok;
+ {error, already_loaded} -> ok
end,
Port = open_port({spawn, "iconv_erl"}, []),
ets:new(iconv_table, [set, public, named_table]),
ets:insert(iconv_table, {port, Port}),
{ok, Port}.
-
%%% --------------------------------------------------------
%%% The call-back functions.
%%% --------------------------------------------------------
-handle_call(_, _, State) ->
- {noreply, State}.
+handle_call(_, _, State) -> {noreply, State}.
-handle_cast(_, State) ->
- {noreply, State}.
+handle_cast(_, State) -> {noreply, State}.
handle_info({'EXIT', Port, Reason}, Port) ->
{stop, {port_died, Reason}, Port};
handle_info({'EXIT', _Pid, _Reason}, Port) ->
{noreply, Port};
-handle_info(_, State) ->
- {noreply, State}.
+handle_info(_, State) -> {noreply, State}.
-code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
-
-terminate(_Reason, Port) ->
- Port ! {self, close},
- ok.
+code_change(_OldVsn, State, _Extra) -> {ok, State}.
+terminate(_Reason, Port) -> Port ! {self, close}, ok.
+-spec convert(binary(), binary(), binary()) -> binary().
convert(From, To, String) ->
[{port, Port} | _] = ets:lookup(iconv_table, port),
Bin = term_to_binary({From, To, String}),
BRes = port_control(Port, 1, Bin),
- binary_to_list(BRes).
-
-
-
+ (BRes).
diff --git a/src/mod_irc/mod_irc.erl b/src/mod_irc/mod_irc.erl
index 554a75c6b..53069671d 100644
--- a/src/mod_irc/mod_irc.erl
+++ b/src/mod_irc/mod_irc.erl
@@ -25,34 +25,52 @@
%%%----------------------------------------------------------------------
-module(mod_irc).
+
-author('alexey@process-one.net').
-behaviour(gen_server).
+
-behaviour(gen_mod).
%% API
--export([start_link/2,
- start/2,
- stop/1,
- closed_connection/3,
- get_connection_params/3]).
+-export([start_link/2, start/2, stop/1, export/1,
+ closed_connection/3, get_connection_params/3]).
%% gen_server callbacks
--export([init/1, handle_call/3, handle_cast/2, handle_info/2,
- terminate/2, code_change/3]).
+-export([init/1, handle_call/3, handle_cast/2,
+ handle_info/2, terminate/2, code_change/3]).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
+
-include("adhoc.hrl").
--define(DEFAULT_IRC_ENCODING, "iso8859-1").
+-define(DEFAULT_IRC_ENCODING, <<"iso8859-1">>).
+
-define(DEFAULT_IRC_PORT, 6667).
--define(POSSIBLE_ENCODINGS, ["koi8-r", "iso8859-1", "iso8859-2", "utf-8", "utf-8+latin-1"]).
--record(irc_connection, {jid_server_host, pid}).
--record(irc_custom, {us_host, data}).
+-define(POSSIBLE_ENCODINGS,
+ [<<"koi8-r">>, <<"iso8859-1">>, <<"iso8859-2">>,
+ <<"utf-8">>, <<"utf-8+latin-1">>]).
+
+-type conn_param() :: {binary(), binary(), inet:port_number(), binary()} |
+ {binary(), binary(), inet:port_number()} |
+ {binary(), binary()}.
+
+-record(irc_connection,
+ {jid_server_host = {#jid{}, <<"">>, <<"">>} :: {jid(), binary(), binary()},
+ pid = self() :: pid()}).
--record(state, {host, server_host, access}).
+-record(irc_custom,
+ {us_host = {{<<"">>, <<"">>}, <<"">>} :: {{binary(), binary()},
+ binary()},
+ data = [] :: [{username, binary()} |
+ {connections_params, [conn_param()]}]}).
+
+-record(state, {host = <<"">> :: binary(),
+ server_host = <<"">> :: binary(),
+ access = all :: atom()}).
-define(PROCNAME, ejabberd_mod_irc).
@@ -65,18 +83,14 @@
%%--------------------------------------------------------------------
start_link(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
- gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
+ gen_server:start_link({local, Proc}, ?MODULE,
+ [Host, Opts], []).
start(Host, Opts) ->
start_supervisor(Host),
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
- ChildSpec =
- {Proc,
- {?MODULE, start_link, [Host, Opts]},
- temporary,
- 1000,
- worker,
- [?MODULE]},
+ ChildSpec = {Proc, {?MODULE, start_link, [Host, Opts]},
+ temporary, 1000, worker, [?MODULE]},
supervisor:start_child(ejabberd_sup, ChildSpec).
stop(Host) ->
@@ -98,25 +112,26 @@ stop(Host) ->
%%--------------------------------------------------------------------
init([Host, Opts]) ->
iconv:start(),
- MyHost = gen_mod:get_opt_host(Host, Opts, "irc.@HOST@"),
+ MyHost = gen_mod:get_opt_host(Host, Opts,
+ <<"irc.@HOST@">>),
case gen_mod:db_type(Opts) of
- mnesia ->
- mnesia:create_table(irc_custom,
- [{disc_copies, [node()]},
- {attributes,
- record_info(fields, irc_custom)}]),
- update_table(MyHost);
- _ ->
- ok
+ mnesia ->
+ mnesia:create_table(irc_custom,
+ [{disc_copies, [node()]},
+ {attributes, record_info(fields, irc_custom)}]),
+ update_table();
+ _ -> ok
end,
- Access = gen_mod:get_opt(access, Opts, all),
- catch ets:new(irc_connection, [named_table,
- public,
- {keypos, #irc_connection.jid_server_host}]),
+ Access = gen_mod:get_opt(access, Opts,
+ fun(A) when is_atom(A) -> A end,
+ all),
+ catch ets:new(irc_connection,
+ [named_table, public,
+ {keypos, #irc_connection.jid_server_host}]),
ejabberd_router:register_route(MyHost),
- {ok, #state{host = MyHost,
- server_host = Host,
- access = Access}}.
+ {ok,
+ #state{host = MyHost, server_host = Host,
+ access = Access}}.
%%--------------------------------------------------------------------
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
@@ -136,8 +151,7 @@ handle_call(stop, _From, State) ->
%% {stop, Reason, State}
%% Description: Handling cast messages
%%--------------------------------------------------------------------
-handle_cast(_Msg, State) ->
- {noreply, State}.
+handle_cast(_Msg, State) -> {noreply, State}.
%%--------------------------------------------------------------------
%% Function: handle_info(Info, State) -> {noreply, State} |
@@ -146,18 +160,17 @@ handle_cast(_Msg, State) ->
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------
handle_info({route, From, To, Packet},
- #state{host = Host,
- server_host = ServerHost,
- access = Access} = State) ->
- case catch do_route(Host, ServerHost, Access, From, To, Packet) of
- {'EXIT', Reason} ->
- ?ERROR_MSG("~p", [Reason]);
- _ ->
- ok
+ #state{host = Host, server_host = ServerHost,
+ access = Access} =
+ State) ->
+ case catch do_route(Host, ServerHost, Access, From, To,
+ Packet)
+ of
+ {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]);
+ _ -> ok
end,
{noreply, State};
-handle_info(_Info, State) ->
- {noreply, State}.
+handle_info(_Info, State) -> {noreply, State}.
%%--------------------------------------------------------------------
%% Function: terminate(Reason, State) -> void()
@@ -167,919 +180,1110 @@ handle_info(_Info, State) ->
%% The return value is ignored.
%%--------------------------------------------------------------------
terminate(_Reason, State) ->
- ejabberd_router:unregister_route(State#state.host),
- ok.
+ ejabberd_router:unregister_route(State#state.host), ok.
%%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
%% Description: Convert process state when code is changed
%%--------------------------------------------------------------------
-code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
+code_change(_OldVsn, State, _Extra) -> {ok, State}.
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
start_supervisor(Host) ->
- Proc = gen_mod:get_module_proc(Host, ejabberd_mod_irc_sup),
- ChildSpec =
- {Proc,
- {ejabberd_tmp_sup, start_link,
- [Proc, mod_irc_connection]},
- permanent,
- infinity,
- supervisor,
- [ejabberd_tmp_sup]},
+ Proc = gen_mod:get_module_proc(Host,
+ ejabberd_mod_irc_sup),
+ ChildSpec = {Proc,
+ {ejabberd_tmp_sup, start_link,
+ [Proc, mod_irc_connection]},
+ permanent, infinity, supervisor, [ejabberd_tmp_sup]},
supervisor:start_child(ejabberd_sup, ChildSpec).
stop_supervisor(Host) ->
- Proc = gen_mod:get_module_proc(Host, ejabberd_mod_irc_sup),
+ Proc = gen_mod:get_module_proc(Host,
+ ejabberd_mod_irc_sup),
supervisor:terminate_child(ejabberd_sup, Proc),
supervisor:delete_child(ejabberd_sup, Proc).
do_route(Host, ServerHost, Access, From, To, Packet) ->
case acl:match_rule(ServerHost, Access, From) of
- allow ->
- do_route1(Host, ServerHost, From, To, Packet);
- _ ->
- {xmlelement, _Name, Attrs, _Els} = Packet,
- Lang = xml:get_attr_s("xml:lang", Attrs),
- ErrText = "Access denied by service policy",
- Err = jlib:make_error_reply(Packet,
- ?ERRT_FORBIDDEN(Lang, ErrText)),
- ejabberd_router:route(To, From, Err)
+ allow -> do_route1(Host, ServerHost, From, To, Packet);
+ _ ->
+ #xmlel{attrs = Attrs} = Packet,
+ Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
+ ErrText = <<"Access denied by service policy">>,
+ Err = jlib:make_error_reply(Packet,
+ ?ERRT_FORBIDDEN(Lang, ErrText)),
+ ejabberd_router:route(To, From, Err)
end.
do_route1(Host, ServerHost, From, To, Packet) ->
#jid{user = ChanServ, resource = Resource} = To,
- {xmlelement, _Name, _Attrs, _Els} = Packet,
+ #xmlel{} = Packet,
case ChanServ of
- "" ->
- case Resource of
- "" ->
- case jlib:iq_query_info(Packet) of
- #iq{type = get, xmlns = ?NS_DISCO_INFO = XMLNS,
- sub_el = SubEl, lang = Lang} = IQ ->
- Node = xml:get_tag_attr_s("node", SubEl),
- Info = ejabberd_hooks:run_fold(
- disco_info, ServerHost, [],
- [ServerHost, ?MODULE, "", ""]),
- case iq_disco(ServerHost, Node, Lang) of
- [] ->
- Res = IQ#iq{type = result,
- sub_el = [{xmlelement, "query",
- [{"xmlns", XMLNS}],
- []}]},
- ejabberd_router:route(To,
- From,
- jlib:iq_to_xml(Res));
- DiscoInfo ->
- Res = IQ#iq{type = result,
- sub_el = [{xmlelement, "query",
- [{"xmlns", XMLNS}],
- DiscoInfo ++ Info}]},
- ejabberd_router:route(To,
- From,
- jlib:iq_to_xml(Res))
- end;
- #iq{type = get, xmlns = ?NS_DISCO_ITEMS = XMLNS,
- sub_el = SubEl, lang = Lang} = IQ ->
- Node = xml:get_tag_attr_s("node", SubEl),
- case Node of
- [] ->
- ResIQ = IQ#iq{type = result,
- sub_el = [{xmlelement, "query",
- [{"xmlns", XMLNS}],
- []}]},
- Res = jlib:iq_to_xml(ResIQ);
- "join" ->
- ResIQ = IQ#iq{type = result,
- sub_el = [{xmlelement, "query",
- [{"xmlns", XMLNS}],
- []}]},
- Res = jlib:iq_to_xml(ResIQ);
- "register" ->
- ResIQ = IQ#iq{type = result,
- sub_el = [{xmlelement, "query",
- [{"xmlns", XMLNS}],
- []}]},
- Res = jlib:iq_to_xml(ResIQ);
- ?NS_COMMANDS ->
- ResIQ = IQ#iq{type = result,
- sub_el = [{xmlelement, "query",
- [{"xmlns", XMLNS},
- {"node", Node}],
- command_items(ServerHost,
- Host, Lang)}]},
- Res = jlib:iq_to_xml(ResIQ);
- _ ->
- Res = jlib:make_error_reply(
- Packet, ?ERR_ITEM_NOT_FOUND)
- end,
- ejabberd_router:route(To,
- From,
- Res);
- #iq{xmlns = ?NS_REGISTER} = IQ ->
- process_register(ServerHost, Host, From, To, IQ);
- #iq{type = get, xmlns = ?NS_VCARD = XMLNS,
- lang = Lang} = IQ ->
+ <<"">> ->
+ case Resource of
+ <<"">> ->
+ case jlib:iq_query_info(Packet) of
+ #iq{type = get, xmlns = (?NS_DISCO_INFO) = XMLNS,
+ sub_el = SubEl, lang = Lang} =
+ IQ ->
+ Node = xml:get_tag_attr_s(<<"node">>, SubEl),
+ Info = ejabberd_hooks:run_fold(disco_info, ServerHost,
+ [],
+ [ServerHost, ?MODULE,
+ <<"">>, <<"">>]),
+ case iq_disco(ServerHost, Node, Lang) of
+ [] ->
Res = IQ#iq{type = result,
sub_el =
- [{xmlelement, "vCard",
- [{"xmlns", XMLNS}],
- iq_get_vcard(Lang)}]},
- ejabberd_router:route(To,
- From,
- jlib:iq_to_xml(Res));
- #iq{type = set, xmlns = ?NS_COMMANDS,
- lang = _Lang, sub_el = SubEl} = IQ ->
- Request = adhoc:parse_request(IQ),
- case lists:keysearch(Request#adhoc_request.node,
- 1, commands(ServerHost)) of
- {value, {_, _, Function}} ->
- case catch Function(From, To, Request) of
- {'EXIT', Reason} ->
- ?ERROR_MSG("~p~nfor ad-hoc handler of ~p",
- [Reason, {From, To, IQ}]),
- Res = IQ#iq{type = error, sub_el = [SubEl,
- ?ERR_INTERNAL_SERVER_ERROR]};
- ignore ->
- Res = ignore;
- {error, Error} ->
- Res = IQ#iq{type = error, sub_el = [SubEl, Error]};
- Command ->
- Res = IQ#iq{type = result, sub_el = [Command]}
- end,
- if Res /= ignore ->
- ejabberd_router:route(To, From, jlib:iq_to_xml(Res));
- true ->
- ok
- end;
- _ ->
- Err = jlib:make_error_reply(
- Packet, ?ERR_ITEM_NOT_FOUND),
- ejabberd_router:route(To, From, Err)
+ [#xmlel{name = <<"query">>,
+ attrs =
+ [{<<"xmlns">>, XMLNS}],
+ children = []}]},
+ ejabberd_router:route(To, From,
+ jlib:iq_to_xml(Res));
+ DiscoInfo ->
+ Res = IQ#iq{type = result,
+ sub_el =
+ [#xmlel{name = <<"query">>,
+ attrs =
+ [{<<"xmlns">>, XMLNS}],
+ children =
+ DiscoInfo ++ Info}]},
+ ejabberd_router:route(To, From, jlib:iq_to_xml(Res))
+ end;
+ #iq{type = get, xmlns = (?NS_DISCO_ITEMS) = XMLNS,
+ sub_el = SubEl, lang = Lang} =
+ IQ ->
+ Node = xml:get_tag_attr_s(<<"node">>, SubEl),
+ case Node of
+ <<>> ->
+ ResIQ = IQ#iq{type = result,
+ sub_el =
+ [#xmlel{name = <<"query">>,
+ attrs =
+ [{<<"xmlns">>,
+ XMLNS}],
+ children = []}]},
+ Res = jlib:iq_to_xml(ResIQ);
+ <<"join">> ->
+ ResIQ = IQ#iq{type = result,
+ sub_el =
+ [#xmlel{name = <<"query">>,
+ attrs =
+ [{<<"xmlns">>,
+ XMLNS}],
+ children = []}]},
+ Res = jlib:iq_to_xml(ResIQ);
+ <<"register">> ->
+ ResIQ = IQ#iq{type = result,
+ sub_el =
+ [#xmlel{name = <<"query">>,
+ attrs =
+ [{<<"xmlns">>,
+ XMLNS}],
+ children = []}]},
+ Res = jlib:iq_to_xml(ResIQ);
+ ?NS_COMMANDS ->
+ ResIQ = IQ#iq{type = result,
+ sub_el =
+ [#xmlel{name = <<"query">>,
+ attrs =
+ [{<<"xmlns">>, XMLNS},
+ {<<"node">>, Node}],
+ children =
+ command_items(ServerHost,
+ Host,
+ Lang)}]},
+ Res = jlib:iq_to_xml(ResIQ);
+ _ ->
+ Res = jlib:make_error_reply(Packet,
+ ?ERR_ITEM_NOT_FOUND)
+ end,
+ ejabberd_router:route(To, From, Res);
+ #iq{xmlns = ?NS_REGISTER} = IQ ->
+ process_register(ServerHost, Host, From, To, IQ);
+ #iq{type = get, xmlns = (?NS_VCARD) = XMLNS,
+ lang = Lang} =
+ IQ ->
+ Res = IQ#iq{type = result,
+ sub_el =
+ [#xmlel{name = <<"vCard">>,
+ attrs = [{<<"xmlns">>, XMLNS}],
+ children = iq_get_vcard(Lang)}]},
+ ejabberd_router:route(To, From, jlib:iq_to_xml(Res));
+ #iq{type = set, xmlns = ?NS_COMMANDS, lang = _Lang,
+ sub_el = SubEl} =
+ IQ ->
+ Request = adhoc:parse_request(IQ),
+ case lists:keysearch(Request#adhoc_request.node, 1,
+ commands(ServerHost))
+ of
+ {value, {_, _, Function}} ->
+ case catch Function(From, To, Request) of
+ {'EXIT', Reason} ->
+ ?ERROR_MSG("~p~nfor ad-hoc handler of ~p",
+ [Reason, {From, To, IQ}]),
+ Res = IQ#iq{type = error,
+ sub_el =
+ [SubEl,
+ ?ERR_INTERNAL_SERVER_ERROR]};
+ ignore -> Res = ignore;
+ {error, Error} ->
+ Res = IQ#iq{type = error,
+ sub_el = [SubEl, Error]};
+ Command ->
+ Res = IQ#iq{type = result, sub_el = [Command]}
+ end,
+ if Res /= ignore ->
+ ejabberd_router:route(To, From,
+ jlib:iq_to_xml(Res));
+ true -> ok
end;
- #iq{} = _IQ ->
- Err = jlib:make_error_reply(
- Packet, ?ERR_FEATURE_NOT_IMPLEMENTED),
- ejabberd_router:route(To, From, Err);
_ ->
- ok
- end;
- _ ->
- Err = jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST),
- ejabberd_router:route(To, From, Err)
- end;
- _ ->
- case string:tokens(ChanServ, "%") of
- [[_ | _] = Channel, [_ | _] = Server] ->
- case ets:lookup(irc_connection, {From, Server, Host}) of
+ Err = jlib:make_error_reply(Packet,
+ ?ERR_ITEM_NOT_FOUND),
+ ejabberd_router:route(To, From, Err)
+ end;
+ #iq{} = _IQ ->
+ Err = jlib:make_error_reply(Packet,
+ ?ERR_FEATURE_NOT_IMPLEMENTED),
+ ejabberd_router:route(To, From, Err);
+ _ -> ok
+ end;
+ _ ->
+ Err = jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST),
+ ejabberd_router:route(To, From, Err)
+ end;
+ _ ->
+ case str:tokens(ChanServ, <<"%">>) of
+ [<<_, _/binary>> = Channel, <<_, _/binary>> = Server] ->
+ case ets:lookup(irc_connection, {From, Server, Host}) of
+ [] ->
+ ?DEBUG("open new connection~n", []),
+ {Username, Encoding, Port, Password} =
+ get_connection_params(Host, ServerHost, From, Server),
+ ConnectionUsername = case Packet of
+ %% If the user tries to join a
+ %% chatroom, the packet for sure
+ %% contains the desired username.
+ #xmlel{name = <<"presence">>} ->
+ Resource;
+ %% Otherwise, there is no firm
+ %% conclusion from the packet.
+ %% Better to use the configured
+ %% username (which defaults to the
+ %% username part of the JID).
+ _ -> Username
+ end,
+ {ok, Pid} = mod_irc_connection:start(From, Host,
+ ServerHost, Server,
+ ConnectionUsername,
+ Encoding, Port,
+ Password, ?MODULE),
+ ets:insert(irc_connection,
+ #irc_connection{jid_server_host =
+ {From, Server, Host},
+ pid = Pid}),
+ mod_irc_connection:route_chan(Pid, Channel, Resource,
+ Packet),
+ ok;
+ [R] ->
+ Pid = R#irc_connection.pid,
+ ?DEBUG("send to process ~p~n", [Pid]),
+ mod_irc_connection:route_chan(Pid, Channel, Resource,
+ Packet),
+ ok
+ end;
+ _ ->
+ case str:tokens(ChanServ, <<"!">>) of
+ [<<_, _/binary>> = Nick, <<_, _/binary>> = Server] ->
+ case ets:lookup(irc_connection, {From, Server, Host}) of
[] ->
- ?DEBUG("open new connection~n", []),
- {Username, Encoding, Port, Password} = get_connection_params(
- Host, ServerHost, From, Server),
- ConnectionUsername =
- case Packet of
- %% If the user tries to join a
- %% chatroom, the packet for sure
- %% contains the desired username.
- {xmlelement, "presence", _, _} ->
- Resource;
- %% Otherwise, there is no firm
- %% conclusion from the packet.
- %% Better to use the configured
- %% username (which defaults to the
- %% username part of the JID).
- _ ->
- Username
- end,
- {ok, Pid} = mod_irc_connection:start(
- From, Host, ServerHost, Server,
- ConnectionUsername, Encoding, Port,
- Password, ?MODULE),
- ets:insert(
- irc_connection,
- #irc_connection{jid_server_host = {From, Server, Host},
- pid = Pid}),
- mod_irc_connection:route_chan(
- Pid, Channel, Resource, Packet),
- ok;
+ Err = jlib:make_error_reply(Packet,
+ ?ERR_SERVICE_UNAVAILABLE),
+ ejabberd_router:route(To, From, Err);
[R] ->
Pid = R#irc_connection.pid,
- ?DEBUG("send to process ~p~n",
- [Pid]),
- mod_irc_connection:route_chan(
- Pid, Channel, Resource, Packet),
+ ?DEBUG("send to process ~p~n", [Pid]),
+ mod_irc_connection:route_nick(Pid, Nick, Packet),
ok
- end;
- _ ->
- case string:tokens(ChanServ, "!") of
- [[_ | _] = Nick, [_ | _] = Server] ->
- case ets:lookup(irc_connection, {From, Server, Host}) of
- [] ->
- Err = jlib:make_error_reply(
- Packet, ?ERR_SERVICE_UNAVAILABLE),
- ejabberd_router:route(To, From, Err);
- [R] ->
- Pid = R#irc_connection.pid,
- ?DEBUG("send to process ~p~n",
- [Pid]),
- mod_irc_connection:route_nick(
- Pid, Nick, Packet),
- ok
- end;
- _ ->
- Err = jlib:make_error_reply(
- Packet, ?ERR_BAD_REQUEST),
- ejabberd_router:route(To, From, Err)
- end
- end
+ end;
+ _ ->
+ Err = jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST),
+ ejabberd_router:route(To, From, Err)
+ end
+ end
end.
-
closed_connection(Host, From, Server) ->
ets:delete(irc_connection, {From, Server, Host}).
-
-iq_disco(_ServerHost, [], Lang) ->
- [{xmlelement, "identity",
- [{"category", "conference"},
- {"type", "irc"},
- {"name", translate:translate(Lang, "IRC Transport")}], []},
- {xmlelement, "feature", [{"var", ?NS_DISCO_INFO}], []},
- {xmlelement, "feature", [{"var", ?NS_MUC}], []},
- {xmlelement, "feature", [{"var", ?NS_REGISTER}], []},
- {xmlelement, "feature", [{"var", ?NS_VCARD}], []},
- {xmlelement, "feature", [{"var", ?NS_COMMANDS}], []}];
+iq_disco(_ServerHost, <<>>, Lang) ->
+ [#xmlel{name = <<"identity">>,
+ attrs =
+ [{<<"category">>, <<"conference">>},
+ {<<"type">>, <<"irc">>},
+ {<<"name">>,
+ translate:translate(Lang, <<"IRC Transport">>)}],
+ children = []},
+ #xmlel{name = <<"feature">>,
+ attrs = [{<<"var">>, ?NS_DISCO_INFO}], children = []},
+ #xmlel{name = <<"feature">>,
+ attrs = [{<<"var">>, ?NS_MUC}], children = []},
+ #xmlel{name = <<"feature">>,
+ attrs = [{<<"var">>, ?NS_REGISTER}], children = []},
+ #xmlel{name = <<"feature">>,
+ attrs = [{<<"var">>, ?NS_VCARD}], children = []},
+ #xmlel{name = <<"feature">>,
+ attrs = [{<<"var">>, ?NS_COMMANDS}], children = []}];
iq_disco(ServerHost, Node, Lang) ->
case lists:keysearch(Node, 1, commands(ServerHost)) of
- {value, {_, Name, _}} ->
- [{xmlelement, "identity",
- [{"category", "automation"},
- {"type", "command-node"},
- {"name", translate:translate(Lang, Name)}], []},
- {xmlelement, "feature",
- [{"var", ?NS_COMMANDS}], []},
- {xmlelement, "feature",
- [{"var", ?NS_XDATA}], []}];
- _ ->
- []
+ {value, {_, Name, _}} ->
+ [#xmlel{name = <<"identity">>,
+ attrs =
+ [{<<"category">>, <<"automation">>},
+ {<<"type">>, <<"command-node">>},
+ {<<"name">>, translate:translate(Lang, Name)}],
+ children = []},
+ #xmlel{name = <<"feature">>,
+ attrs = [{<<"var">>, ?NS_COMMANDS}], children = []},
+ #xmlel{name = <<"feature">>,
+ attrs = [{<<"var">>, ?NS_XDATA}], children = []}];
+ _ -> []
end.
iq_get_vcard(Lang) ->
- [{xmlelement, "FN", [],
- [{xmlcdata, "ejabberd/mod_irc"}]},
- {xmlelement, "URL", [],
- [{xmlcdata, ?EJABBERD_URI}]},
- {xmlelement, "DESC", [],
- [{xmlcdata, translate:translate(Lang, "ejabberd IRC module") ++
- "\nCopyright (c) 2003-2013 ProcessOne"}]}].
+ [#xmlel{name = <<"FN">>, attrs = [],
+ children = [{xmlcdata, <<"ejabberd/mod_irc">>}]},
+ #xmlel{name = <<"URL">>, attrs = [],
+ children = [{xmlcdata, ?EJABBERD_URI}]},
+ #xmlel{name = <<"DESC">>, attrs = [],
+ children =
+ [{xmlcdata,
+ <<(translate:translate(Lang,
+ <<"ejabberd IRC module">>))/binary,
+ "\nCopyright (c) 2003-2013 ProcessOne">>}]}].
command_items(ServerHost, Host, Lang) ->
- lists:map(fun({Node, Name, _Function})
- -> {xmlelement, "item",
- [{"jid", Host},
- {"node", Node},
- {"name", translate:translate(Lang, Name)}], []}
- end, commands(ServerHost)).
+ lists:map(fun ({Node, Name, _Function}) ->
+ #xmlel{name = <<"item">>,
+ attrs =
+ [{<<"jid">>, Host}, {<<"node">>, Node},
+ {<<"name">>,
+ translate:translate(Lang, Name)}],
+ children = []}
+ end,
+ commands(ServerHost)).
commands(ServerHost) ->
- [{"join", "Join channel", fun adhoc_join/3},
- {"register", "Configure username, encoding, port and password",
- fun(From, To, Request) ->
- adhoc_register(ServerHost, From, To, Request)
+ [{<<"join">>, <<"Join channel">>, fun adhoc_join/3},
+ {<<"register">>,
+ <<"Configure username, encoding, port and "
+ "password">>,
+ fun (From, To, Request) ->
+ adhoc_register(ServerHost, From, To, Request)
end}].
-process_register(ServerHost, Host, From, To, #iq{} = IQ) ->
- case catch process_irc_register(ServerHost, Host, From, To, IQ) of
- {'EXIT', Reason} ->
- ?ERROR_MSG("~p", [Reason]);
- ResIQ ->
- if
- ResIQ /= ignore ->
- ejabberd_router:route(To, From,
- jlib:iq_to_xml(ResIQ));
- true ->
- ok
- end
+process_register(ServerHost, Host, From, To,
+ #iq{} = IQ) ->
+ case catch process_irc_register(ServerHost, Host, From,
+ To, IQ)
+ of
+ {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]);
+ ResIQ ->
+ if ResIQ /= ignore ->
+ ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ));
+ true -> ok
+ end
end.
-find_xdata_el({xmlelement, _Name, _Attrs, SubEls}) ->
+find_xdata_el(#xmlel{children = SubEls}) ->
find_xdata_el1(SubEls).
-find_xdata_el1([]) ->
- false;
-
-find_xdata_el1([{xmlelement, Name, Attrs, SubEls} | Els]) ->
- case xml:get_attr_s("xmlns", Attrs) of
- ?NS_XDATA ->
- {xmlelement, Name, Attrs, SubEls};
- _ ->
- find_xdata_el1(Els)
+find_xdata_el1([]) -> false;
+find_xdata_el1([#xmlel{name = Name, attrs = Attrs,
+ children = SubEls}
+ | Els]) ->
+ case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ ?NS_XDATA ->
+ #xmlel{name = Name, attrs = Attrs, children = SubEls};
+ _ -> find_xdata_el1(Els)
end;
-
-find_xdata_el1([_ | Els]) ->
- find_xdata_el1(Els).
+find_xdata_el1([_ | Els]) -> find_xdata_el1(Els).
process_irc_register(ServerHost, Host, From, _To,
- #iq{type = Type, xmlns = XMLNS,
- lang = Lang, sub_el = SubEl} = IQ) ->
+ #iq{type = Type, xmlns = XMLNS, lang = Lang,
+ sub_el = SubEl} =
+ IQ) ->
case Type of
- set ->
- XDataEl = find_xdata_el(SubEl),
- case XDataEl of
- false ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ACCEPTABLE]};
- {xmlelement, _Name, Attrs, _SubEls} ->
- case xml:get_attr_s("type", Attrs) of
- "cancel" ->
- IQ#iq{type = result,
- sub_el = [{xmlelement, "query",
- [{"xmlns", XMLNS}], []}]};
- "submit" ->
- XData = jlib:parse_xdata_submit(XDataEl),
- case XData of
- invalid ->
- IQ#iq{type = error,
- sub_el = [SubEl, ?ERR_BAD_REQUEST]};
- _ ->
- Node = string:tokens(
- xml:get_tag_attr_s("node", SubEl),
- "/"),
- case set_form(
- ServerHost, Host, From,
- Node, Lang, XData) of
- {result, Res} ->
- IQ#iq{type = result,
- sub_el = [{xmlelement, "query",
- [{"xmlns", XMLNS}],
- Res
- }]};
- {error, Error} ->
- IQ#iq{type = error,
- sub_el = [SubEl, Error]}
- end
- end;
- _ ->
+ set ->
+ XDataEl = find_xdata_el(SubEl),
+ case XDataEl of
+ false ->
+ IQ#iq{type = error,
+ sub_el = [SubEl, ?ERR_NOT_ACCEPTABLE]};
+ #xmlel{attrs = Attrs} ->
+ case xml:get_attr_s(<<"type">>, Attrs) of
+ <<"cancel">> ->
+ IQ#iq{type = result,
+ sub_el =
+ [#xmlel{name = <<"query">>,
+ attrs = [{<<"xmlns">>, XMLNS}],
+ children = []}]};
+ <<"submit">> ->
+ XData = jlib:parse_xdata_submit(XDataEl),
+ case XData of
+ invalid ->
IQ#iq{type = error,
- sub_el = [SubEl, ?ERR_BAD_REQUEST]}
- end
- end;
- get ->
- Node =
- string:tokens(xml:get_tag_attr_s("node", SubEl), "/"),
- case get_form(ServerHost, Host, From, Node, Lang) of
- {result, Res} ->
- IQ#iq{type = result,
- sub_el = [{xmlelement, "query",
- [{"xmlns", XMLNS}],
- Res
- }]};
- {error, Error} ->
- IQ#iq{type = error,
- sub_el = [SubEl, Error]}
- end
+ sub_el = [SubEl, ?ERR_BAD_REQUEST]};
+ _ ->
+ Node = str:tokens(xml:get_tag_attr_s(<<"node">>,
+ SubEl),
+ <<"/">>),
+ case set_form(ServerHost, Host, From, Node, Lang,
+ XData)
+ of
+ {result, Res} ->
+ IQ#iq{type = result,
+ sub_el =
+ [#xmlel{name = <<"query">>,
+ attrs =
+ [{<<"xmlns">>, XMLNS}],
+ children = Res}]};
+ {error, Error} ->
+ IQ#iq{type = error, sub_el = [SubEl, Error]}
+ end
+ end;
+ _ ->
+ IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]}
+ end
+ end;
+ get ->
+ Node = str:tokens(xml:get_tag_attr_s(<<"node">>, SubEl),
+ <<"/">>),
+ case get_form(ServerHost, Host, From, Node, Lang) of
+ {result, Res} ->
+ IQ#iq{type = result,
+ sub_el =
+ [#xmlel{name = <<"query">>,
+ attrs = [{<<"xmlns">>, XMLNS}],
+ children = Res}]};
+ {error, Error} ->
+ IQ#iq{type = error, sub_el = [SubEl, Error]}
+ end
end.
get_data(ServerHost, Host, From) ->
LServer = jlib:nameprep(ServerHost),
- get_data(LServer, Host, From, gen_mod:db_type(LServer, ?MODULE)).
+ get_data(LServer, Host, From,
+ gen_mod:db_type(LServer, ?MODULE)).
get_data(_LServer, Host, From, mnesia) ->
#jid{luser = LUser, lserver = LServer} = From,
US = {LUser, LServer},
- case catch mnesia:dirty_read({irc_custom, {US, Host}}) of
- {'EXIT', _Reason} ->
- error;
- [] ->
- empty;
- [#irc_custom{data = Data}] ->
- Data
+ case catch mnesia:dirty_read({irc_custom, {US, Host}})
+ of
+ {'EXIT', _Reason} -> error;
+ [] -> empty;
+ [#irc_custom{data = Data}] -> Data
end;
get_data(LServer, Host, From, odbc) ->
- SJID = ejabberd_odbc:escape(
- jlib:jid_to_string(
- jlib:jid_tolower(
- jlib:jid_remove_resource(From)))),
+ SJID =
+ ejabberd_odbc:escape(jlib:jid_to_string(jlib:jid_tolower(jlib:jid_remove_resource(From)))),
SHost = ejabberd_odbc:escape(Host),
- case catch ejabberd_odbc:sql_query(
- LServer,
- ["select data from irc_custom where "
- "jid='", SJID, "' and host='", SHost, "';"]) of
- {selected, ["data"], [{SData}]} ->
- ejabberd_odbc:decode_term(SData);
- {'EXIT', _} ->
- error;
- {selected, _, _} ->
- empty
+ case catch ejabberd_odbc:sql_query(LServer,
+ [<<"select data from irc_custom where jid='">>,
+ SJID, <<"' and host='">>, SHost,
+ <<"';">>])
+ of
+ {selected, [<<"data">>], [[SData]]} ->
+ data_to_binary(ejabberd_odbc:decode_term(SData));
+ {'EXIT', _} -> error;
+ {selected, _, _} -> empty
end.
get_form(ServerHost, Host, From, [], Lang) ->
#jid{user = User, server = Server} = From,
DefaultEncoding = get_default_encoding(Host),
- Customs =
- case get_data(ServerHost, Host, From) of
- error ->
- {error, ?ERR_INTERNAL_SERVER_ERROR};
- empty ->
- {User, []};
- Data ->
- {xml:get_attr_s(username, Data),
- xml:get_attr_s(connections_params, Data)}
- end,
+ Customs = case get_data(ServerHost, Host, From) of
+ error -> {error, ?ERR_INTERNAL_SERVER_ERROR};
+ empty -> {User, []};
+ Data -> get_username_and_connection_params(Data)
+ end,
case Customs of
- {error, _Error} ->
- Customs;
- {Username, ConnectionsParams} ->
- {result,
- [{xmlelement, "instructions", [],
- [{xmlcdata,
- translate:translate(
- Lang,
- "You need an x:data capable client "
- "to configure mod_irc settings")}]},
- {xmlelement, "x", [{"xmlns", ?NS_XDATA}],
- [{xmlelement, "title", [],
- [{xmlcdata,
- translate:translate(
- Lang,
- "Registration in mod_irc for ") ++ User ++ "@" ++ Server}]},
- {xmlelement, "instructions", [],
- [{xmlcdata,
- translate:translate(
- Lang,
- "Enter username, encodings, ports and passwords you wish to use for "
- "connecting to IRC servers")}]},
- {xmlelement, "field", [{"type", "text-single"},
- {"label",
- translate:translate(
- Lang, "IRC Username")},
- {"var", "username"}],
- [{xmlelement, "value", [], [{xmlcdata, Username}]}]},
- {xmlelement, "field", [{"type", "fixed"}],
- [{xmlelement, "value", [],
- [{xmlcdata,
- lists:flatten(
- io_lib:format(
- translate:translate(
- Lang,
- "If you want to specify different ports, "
- "passwords, encodings for IRC servers, fill "
- "this list with values in format "
- "'{\"irc server\", \"encoding\", port, \"password\"}'. "
- "By default this service use \"~s\" encoding, port ~p, "
- "empty password."),
- [DefaultEncoding, ?DEFAULT_IRC_PORT]))}]}]},
- {xmlelement, "field", [{"type", "fixed"}],
- [{xmlelement, "value", [],
- [{xmlcdata,
- translate:translate(
- Lang,
- "Example: [{\"irc.lucky.net\", \"koi8-r\", 6667, \"secret\"}, "
- "{\"vendetta.fef.net\", \"iso8859-1\", 7000}, {\"irc.sometestserver.net\", \"utf-8\"}]."
- )}]}]},
- {xmlelement, "field", [{"type", "text-multi"},
- {"label",
- translate:translate(Lang, "Connections parameters")},
- {"var", "connections_params"}],
- lists:map(
- fun(S) ->
- {xmlelement, "value", [], [{xmlcdata, S}]}
- end,
- string:tokens(
- lists:flatten(
- io_lib:format("~p.", [ConnectionsParams])),
- "\n"))
- }
- ]}]}
+ {error, _Error} -> Customs;
+ {Username, ConnectionsParams} ->
+ {result,
+ [#xmlel{name = <<"instructions">>, attrs = [],
+ children =
+ [{xmlcdata,
+ translate:translate(Lang,
+ <<"You need an x:data capable client to "
+ "configure mod_irc settings">>)}]},
+ #xmlel{name = <<"x">>,
+ attrs = [{<<"xmlns">>, ?NS_XDATA}],
+ children =
+ [#xmlel{name = <<"title">>, attrs = [],
+ children =
+ [{xmlcdata,
+ <<(translate:translate(Lang,
+ <<"Registration in mod_irc for ">>))/binary,
+ User/binary, "@", Server/binary>>}]},
+ #xmlel{name = <<"instructions">>, attrs = [],
+ children =
+ [{xmlcdata,
+ translate:translate(Lang,
+ <<"Enter username, encodings, ports and "
+ "passwords you wish to use for connecting "
+ "to IRC servers">>)}]},
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"type">>, <<"text-single">>},
+ {<<"label">>,
+ translate:translate(Lang,
+ <<"IRC Username">>)},
+ {<<"var">>, <<"username">>}],
+ children =
+ [#xmlel{name = <<"value">>, attrs = [],
+ children = [{xmlcdata, Username}]}]},
+ #xmlel{name = <<"field">>,
+ attrs = [{<<"type">>, <<"fixed">>}],
+ children =
+ [#xmlel{name = <<"value">>, attrs = [],
+ children =
+ [{xmlcdata,
+ iolist_to_binary(
+ io_lib:format(
+ translate:translate(
+ Lang,
+ <<"If you want to specify"
+ " different ports, "
+ "passwords, encodings "
+ "for IRC servers, "
+ "fill this list with "
+ "values in format "
+ "'{\"irc server\", "
+ "\"encoding\", port, "
+ "\"password\"}'. "
+ "By default this "
+ "service use \"~s\" "
+ "encoding, port ~p, "
+ "empty password.">>),
+ [DefaultEncoding,
+ ?DEFAULT_IRC_PORT]))}]}]},
+ #xmlel{name = <<"field">>,
+ attrs = [{<<"type">>, <<"fixed">>}],
+ children =
+ [#xmlel{name = <<"value">>, attrs = [],
+ children =
+ [{xmlcdata,
+ translate:translate(Lang,
+ <<"Example: [{\"irc.lucky.net\", \"koi8-r\", "
+ "6667, \"secret\"}, {\"vendetta.fef.net\", "
+ "\"iso8859-1\", 7000}, {\"irc.sometestserver.n"
+ "et\", \"utf-8\"}].">>)}]}]},
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"type">>, <<"text-multi">>},
+ {<<"label">>,
+ translate:translate(Lang,
+ <<"Connections parameters">>)},
+ {<<"var">>, <<"connections_params">>}],
+ children =
+ lists:map(fun (S) ->
+ #xmlel{name = <<"value">>,
+ attrs = [],
+ children =
+ [{xmlcdata, S}]}
+ end,
+ str:tokens(list_to_binary(
+ io_lib:format(
+ "~p.",
+ [conn_params_to_list(
+ ConnectionsParams)])),
+ <<"\n">>))}]}]}
end;
-
get_form(_ServerHost, _Host, _, _, _Lang) ->
{error, ?ERR_SERVICE_UNAVAILABLE}.
-
set_data(ServerHost, Host, From, Data) ->
LServer = jlib:nameprep(ServerHost),
- set_data(LServer, Host, From, Data, gen_mod:db_type(LServer, ?MODULE)).
+ set_data(LServer, Host, From, data_to_binary(Data),
+ gen_mod:db_type(LServer, ?MODULE)).
set_data(_LServer, Host, From, Data, mnesia) ->
{LUser, LServer, _} = jlib:jid_tolower(From),
US = {LUser, LServer},
- F = fun() ->
- mnesia:write(#irc_custom{us_host = {US, Host}, data = Data})
- end,
+ F = fun () ->
+ mnesia:write(#irc_custom{us_host = {US, Host},
+ data = Data})
+ end,
mnesia:transaction(F);
set_data(LServer, Host, From, Data, odbc) ->
- SJID = ejabberd_odbc:escape(
- jlib:jid_to_string(
- jlib:jid_tolower(
- jlib:jid_remove_resource(From)))),
+ SJID =
+ ejabberd_odbc:escape(jlib:jid_to_string(jlib:jid_tolower(jlib:jid_remove_resource(From)))),
SHost = ejabberd_odbc:escape(Host),
SData = ejabberd_odbc:encode_term(Data),
- F = fun() ->
- odbc_queries:update_t(
- "irc_custom",
- ["jid", "host", "data"],
- [SJID, SHost, SData],
- ["jid='", SJID,
- "' and host='",
- SHost, "'"]),
- ok
- end,
+ F = fun () ->
+ odbc_queries:update_t(<<"irc_custom">>,
+ [<<"jid">>, <<"host">>, <<"data">>],
+ [SJID, SHost, SData],
+ [<<"jid='">>, SJID, <<"' and host='">>,
+ SHost, <<"'">>]),
+ ok
+ end,
ejabberd_odbc:sql_transaction(LServer, F).
set_form(ServerHost, Host, From, [], _Lang, XData) ->
- case {lists:keysearch("username", 1, XData),
- lists:keysearch("connections_params", 1, XData)} of
- {{value, {_, [Username]}}, {value, {_, Strings}}} ->
- EncString = lists:foldl(fun(S, Res) ->
- Res ++ S ++ "\n"
- end, "", Strings),
- case erl_scan:string(EncString) of
- {ok, Tokens, _} ->
- case erl_parse:parse_term(Tokens) of
- {ok, ConnectionsParams} ->
- case set_data(ServerHost, Host, From,
- [{username,
- Username},
- {connections_params,
- ConnectionsParams}]) of
- {atomic, _} ->
- {result, []};
- _ ->
- {error, ?ERR_NOT_ACCEPTABLE}
- end;
- _ ->
- {error, ?ERR_NOT_ACCEPTABLE}
- end;
- _ ->
- {error, ?ERR_NOT_ACCEPTABLE}
- end;
- _ ->
- {error, ?ERR_NOT_ACCEPTABLE}
+ case {lists:keysearch(<<"username">>, 1, XData),
+ lists:keysearch(<<"connections_params">>, 1, XData)}
+ of
+ {{value, {_, [Username]}}, {value, {_, Strings}}} ->
+ EncString = lists:foldl(fun (S, Res) ->
+ <<Res/binary, S/binary, "\n">>
+ end,
+ <<"">>, Strings),
+ case erl_scan:string(binary_to_list(EncString)) of
+ {ok, Tokens, _} ->
+ case erl_parse:parse_term(Tokens) of
+ {ok, ConnectionsParams} ->
+ case set_data(ServerHost, Host, From,
+ [{username, Username},
+ {connections_params, ConnectionsParams}])
+ of
+ {atomic, _} -> {result, []};
+ _ -> {error, ?ERR_NOT_ACCEPTABLE}
+ end;
+ _ -> {error, ?ERR_NOT_ACCEPTABLE}
+ end;
+ _ -> {error, ?ERR_NOT_ACCEPTABLE}
+ end;
+ _ -> {error, ?ERR_NOT_ACCEPTABLE}
end;
-
-
set_form(_ServerHost, _Host, _, _, _Lang, _XData) ->
{error, ?ERR_SERVICE_UNAVAILABLE}.
-
%% Host = "irc.example.com"
%% ServerHost = "example.com"
get_connection_params(Host, From, IRCServer) ->
- [_ | HostTail] = string:tokens(Host, "."),
- ServerHost = string:join(HostTail, "."),
- get_connection_params(Host, ServerHost, From, IRCServer).
+ [_ | HostTail] = str:tokens(Host, <<".">>),
+ ServerHost = str:join(HostTail, <<".">>),
+ get_connection_params(Host, ServerHost, From,
+ IRCServer).
get_default_encoding(ServerHost) ->
- Result = gen_mod:get_module_opt(
- ServerHost, ?MODULE, default_encoding,
- ?DEFAULT_IRC_ENCODING),
- ?INFO_MSG("The default_encoding configured for host ~p is: ~p~n", [ServerHost, Result]),
+ Result = gen_mod:get_module_opt(ServerHost, ?MODULE, default_encoding,
+ fun iolist_to_binary/1,
+ ?DEFAULT_IRC_ENCODING),
+ ?INFO_MSG("The default_encoding configured for "
+ "host ~p is: ~p~n",
+ [ServerHost, Result]),
Result.
-get_connection_params(Host, ServerHost, From, IRCServer) ->
+get_connection_params(Host, ServerHost, From,
+ IRCServer) ->
#jid{user = User, server = _Server} = From,
DefaultEncoding = get_default_encoding(ServerHost),
case get_data(ServerHost, Host, From) of
- error ->
- {User, DefaultEncoding, ?DEFAULT_IRC_PORT, ""};
- empty ->
- {User, DefaultEncoding, ?DEFAULT_IRC_PORT, ""};
- Data ->
- Username = xml:get_attr_s(username, Data),
- {NewUsername, NewEncoding, NewPort, NewPassword} =
- case lists:keysearch(IRCServer, 1, xml:get_attr_s(connections_params, Data)) of
- {value, {_, Encoding, Port, Password}} ->
- {Username, Encoding, Port, Password};
- {value, {_, Encoding, Port}} ->
- {Username, Encoding, Port, ""};
- {value, {_, Encoding}} ->
- {Username, Encoding, ?DEFAULT_IRC_PORT, ""};
- _ ->
- {Username, DefaultEncoding, ?DEFAULT_IRC_PORT, ""}
- end,
- {NewUsername,
- NewEncoding,
- if
- NewPort >= 0 andalso NewPort =< 65535 ->
- NewPort;
- true ->
- ?DEFAULT_IRC_PORT
- end,
- NewPassword}
- end.
+ error ->
+ {User, DefaultEncoding, ?DEFAULT_IRC_PORT, <<"">>};
+ empty ->
+ {User, DefaultEncoding, ?DEFAULT_IRC_PORT, <<"">>};
+ Data ->
+ {Username, ConnParams} = get_username_and_connection_params(Data),
+ {NewUsername, NewEncoding, NewPort, NewPassword} = case
+ lists:keysearch(IRCServer,
+ 1,
+ ConnParams)
+ of
+ {value,
+ {_, Encoding,
+ Port,
+ Password}} ->
+ {Username,
+ Encoding,
+ Port,
+ Password};
+ {value,
+ {_, Encoding,
+ Port}} ->
+ {Username,
+ Encoding,
+ Port,
+ <<"">>};
+ {value,
+ {_,
+ Encoding}} ->
+ {Username,
+ Encoding,
+ ?DEFAULT_IRC_PORT,
+ <<"">>};
+ _ ->
+ {Username,
+ DefaultEncoding,
+ ?DEFAULT_IRC_PORT,
+ <<"">>}
+ end,
+ {iolist_to_binary(NewUsername),
+ iolist_to_binary(NewEncoding),
+ if NewPort >= 0 andalso NewPort =< 65535 -> NewPort;
+ true -> ?DEFAULT_IRC_PORT
+ end,
+ iolist_to_binary(NewPassword)}
+ end.
-adhoc_join(_From, _To, #adhoc_request{action = "cancel"} = Request) ->
+adhoc_join(_From, _To,
+ #adhoc_request{action = <<"cancel">>} = Request) ->
adhoc:produce_response(Request,
#adhoc_response{status = canceled});
-adhoc_join(From, To, #adhoc_request{lang = Lang,
- node = _Node,
- action = _Action,
- xdata = XData} = Request) ->
- %% Access control has already been taken care of in do_route.
+adhoc_join(From, To,
+ #adhoc_request{lang = Lang, node = _Node,
+ action = _Action, xdata = XData} =
+ Request) ->
if XData == false ->
- Form =
- {xmlelement, "x",
- [{"xmlns", ?NS_XDATA},
- {"type", "form"}],
- [{xmlelement, "title", [], [{xmlcdata, translate:translate(Lang, "Join IRC channel")}]},
- {xmlelement, "field",
- [{"var", "channel"},
- {"type", "text-single"},
- {"label", translate:translate(Lang, "IRC channel (don't put the first #)")}],
- [{xmlelement, "required", [], []}]},
- {xmlelement, "field",
- [{"var", "server"},
- {"type", "text-single"},
- {"label", translate:translate(Lang, "IRC server")}],
- [{xmlelement, "required", [], []}]}]},
- adhoc:produce_response(Request,
- #adhoc_response{status = executing,
- elements = [Form]});
+ Form = #xmlel{name = <<"x">>,
+ attrs =
+ [{<<"xmlns">>, ?NS_XDATA},
+ {<<"type">>, <<"form">>}],
+ children =
+ [#xmlel{name = <<"title">>, attrs = [],
+ children =
+ [{xmlcdata,
+ translate:translate(Lang,
+ <<"Join IRC channel">>)}]},
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"var">>, <<"channel">>},
+ {<<"type">>, <<"text-single">>},
+ {<<"label">>,
+ translate:translate(Lang,
+ <<"IRC channel (don't put the first #)">>)}],
+ children =
+ [#xmlel{name = <<"required">>,
+ attrs = [], children = []}]},
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"var">>, <<"server">>},
+ {<<"type">>, <<"text-single">>},
+ {<<"label">>,
+ translate:translate(Lang,
+ <<"IRC server">>)}],
+ children =
+ [#xmlel{name = <<"required">>,
+ attrs = [], children = []}]}]},
+ adhoc:produce_response(Request,
+ #adhoc_response{status = executing,
+ elements = [Form]});
true ->
- case jlib:parse_xdata_submit(XData) of
- invalid ->
- {error, ?ERR_BAD_REQUEST};
- Fields ->
- Channel = case lists:keysearch("channel", 1, Fields) of
- {value, {"channel", C}} ->
- C;
- _ ->
- false
- end,
- Server = case lists:keysearch("server", 1, Fields) of
- {value, {"server", S}} ->
- S;
- _ ->
- false
- end,
- if Channel /= false,
- Server /= false ->
- RoomJID = Channel ++ "%" ++ Server ++ "@" ++ To#jid.server,
- Invite = {xmlelement, "message", [],
- [{xmlelement, "x",
- [{"xmlns", ?NS_MUC_USER}],
- [{xmlelement, "invite",
- [{"from", jlib:jid_to_string(From)}],
- [{xmlelement, "reason", [],
- [{xmlcdata,
- translate:translate(Lang,
- "Join the IRC channel here.")}]}]}]},
- {xmlelement, "x",
- [{"xmlns", ?NS_XCONFERENCE}],
- [{xmlcdata, translate:translate(Lang,
- "Join the IRC channel here.")}]},
- {xmlelement, "body", [],
- [{xmlcdata, io_lib:format(
- translate:translate(Lang,
- "Join the IRC channel in this Jabber ID: ~s"),
- [RoomJID])}]}]},
- ejabberd_router:route(jlib:string_to_jid(RoomJID), From, Invite),
- adhoc:produce_response(Request, #adhoc_response{status = completed});
- true ->
- {error, ?ERR_BAD_REQUEST}
- end
- end
+ case jlib:parse_xdata_submit(XData) of
+ invalid -> {error, ?ERR_BAD_REQUEST};
+ Fields ->
+ Channel = case lists:keysearch(<<"channel">>, 1, Fields)
+ of
+ {value, {<<"channel">>, [C]}} -> C;
+ _ -> false
+ end,
+ Server = case lists:keysearch(<<"server">>, 1, Fields)
+ of
+ {value, {<<"server">>, [S]}} -> S;
+ _ -> false
+ end,
+ if Channel /= false, Server /= false ->
+ RoomJID = <<Channel/binary, "%", Server/binary, "@",
+ (To#jid.server)/binary>>,
+ Invite = #xmlel{name = <<"message">>, attrs = [],
+ children =
+ [#xmlel{name = <<"x">>,
+ attrs =
+ [{<<"xmlns">>,
+ ?NS_MUC_USER}],
+ children =
+ [#xmlel{name =
+ <<"invite">>,
+ attrs =
+ [{<<"from">>,
+ jlib:jid_to_string(From)}],
+ children =
+ [#xmlel{name
+ =
+ <<"reason">>,
+ attrs
+ =
+ [],
+ children
+ =
+ [{xmlcdata,
+ translate:translate(Lang,
+ <<"Join the IRC channel here.">>)}]}]}]},
+ #xmlel{name = <<"x">>,
+ attrs =
+ [{<<"xmlns">>,
+ ?NS_XCONFERENCE}],
+ children =
+ [{xmlcdata,
+ translate:translate(Lang,
+ <<"Join the IRC channel here.">>)}]},
+ #xmlel{name = <<"body">>,
+ attrs = [],
+ children =
+ [{xmlcdata,
+ iolist_to_binary(
+ io_lib:format(
+ translate:translate(
+ Lang,
+ <<"Join the IRC channel in this Jabber ID: ~s">>),
+ [RoomJID]))}]}]},
+ ejabberd_router:route(jlib:string_to_jid(RoomJID), From,
+ Invite),
+ adhoc:produce_response(Request,
+ #adhoc_response{status =
+ completed});
+ true -> {error, ?ERR_BAD_REQUEST}
+ end
+ end
end.
-adhoc_register(_ServerHost, _From, _To, #adhoc_request{action = "cancel"} = Request) ->
+adhoc_register(_ServerHost, _From, _To,
+ #adhoc_request{action = <<"cancel">>} = Request) ->
adhoc:produce_response(Request,
#adhoc_response{status = canceled});
-adhoc_register(ServerHost, From, To, #adhoc_request{lang = Lang,
- node = _Node,
- xdata = XData,
- action = Action} = Request) ->
+adhoc_register(ServerHost, From, To,
+ #adhoc_request{lang = Lang, node = _Node, xdata = XData,
+ action = Action} =
+ Request) ->
#jid{user = User} = From,
#jid{lserver = Host} = To,
- %% Generate form for setting username and encodings. If the user
- %% hasn't begun to fill out the form, generate an initial form
- %% based on current values.
if XData == false ->
- case get_data(ServerHost, Host, From) of
- error ->
- Username = User,
- ConnectionsParams = [];
- empty ->
- Username = User,
- ConnectionsParams = [];
- Data ->
- Username = xml:get_attr_s(username, Data),
- ConnectionsParams = xml:get_attr_s(connections_params, Data)
- end,
- Error = false;
+ case get_data(ServerHost, Host, From) of
+ error -> Username = User, ConnectionsParams = [];
+ empty -> Username = User, ConnectionsParams = [];
+ Data ->
+ {Username, ConnectionsParams} =
+ get_username_and_connection_params(Data)
+ end,
+ Error = false;
true ->
- case jlib:parse_xdata_submit(XData) of
- invalid ->
- Error = {error, ?ERR_BAD_REQUEST},
- Username = false,
- ConnectionsParams = false;
- Fields ->
- Username = case lists:keysearch("username", 1, Fields) of
- {value, {"username", U}} ->
- U;
- _ ->
- User
- end,
- ConnectionsParams = parse_connections_params(Fields),
- Error = false
- end
+ case jlib:parse_xdata_submit(XData) of
+ invalid ->
+ Error = {error, ?ERR_BAD_REQUEST},
+ Username = false,
+ ConnectionsParams = false;
+ Fields ->
+ Username = case lists:keysearch(<<"username">>, 1,
+ Fields)
+ of
+ {value, {<<"username">>, U}} -> U;
+ _ -> User
+ end,
+ ConnectionsParams = parse_connections_params(Fields),
+ Error = false
+ end
end,
-
- if Error /= false ->
- Error;
- Action == "complete" ->
- case set_data(ServerHost, Host, From,
- [{username,
- Username},
- {connections_params,
- ConnectionsParams}]) of
- {atomic, _} ->
- adhoc:produce_response(Request, #adhoc_response{status = completed});
- _ ->
- {error, ?ERR_INTERNAL_SERVER_ERROR}
- end;
+ if Error /= false -> Error;
+ Action == <<"complete">> ->
+ case set_data(ServerHost, Host, From,
+ [{username, Username},
+ {connections_params, ConnectionsParams}])
+ of
+ {atomic, _} ->
+ adhoc:produce_response(Request,
+ #adhoc_response{status = completed});
+ _ -> {error, ?ERR_INTERNAL_SERVER_ERROR}
+ end;
true ->
- Form = generate_adhoc_register_form(Lang, Username, ConnectionsParams),
- adhoc:produce_response(Request,
- #adhoc_response{status = executing,
- elements = [Form],
- actions = ["next", "complete"]})
+ Form = generate_adhoc_register_form(Lang, Username,
+ ConnectionsParams),
+ adhoc:produce_response(Request,
+ #adhoc_response{status = executing,
+ elements = [Form],
+ actions =
+ [<<"next">>,
+ <<"complete">>]})
end.
-generate_adhoc_register_form(Lang, Username, ConnectionsParams) ->
- {xmlelement, "x",
- [{"xmlns", ?NS_XDATA},
- {"type", "form"}],
- [{xmlelement, "title", [], [{xmlcdata, translate:translate(Lang, "IRC settings")}]},
- {xmlelement, "instructions", [],
- [{xmlcdata,
- translate:translate(
- Lang,
- "Enter username and encodings you wish to use for "
- "connecting to IRC servers. Press 'Next' to get more fields "
- "to fill in. Press 'Complete' to save settings.")}]},
- {xmlelement, "field",
- [{"var", "username"},
- {"type", "text-single"},
- {"label", translate:translate(Lang, "IRC username")}],
- [{xmlelement, "required", [], []},
- {xmlelement, "value", [], [{xmlcdata, Username}]}]}] ++
- generate_connection_params_fields(Lang, ConnectionsParams, 1, [])}.
+generate_adhoc_register_form(Lang, Username,
+ ConnectionsParams) ->
+ #xmlel{name = <<"x">>,
+ attrs =
+ [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"form">>}],
+ children =
+ [#xmlel{name = <<"title">>, attrs = [],
+ children =
+ [{xmlcdata,
+ translate:translate(Lang, <<"IRC settings">>)}]},
+ #xmlel{name = <<"instructions">>, attrs = [],
+ children =
+ [{xmlcdata,
+ translate:translate(Lang,
+ <<"Enter username and encodings you wish "
+ "to use for connecting to IRC servers. "
+ " Press 'Next' to get more fields to "
+ "fill in. Press 'Complete' to save settings.">>)}]},
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"var">>, <<"username">>},
+ {<<"type">>, <<"text-single">>},
+ {<<"label">>,
+ translate:translate(Lang, <<"IRC username">>)}],
+ children =
+ [#xmlel{name = <<"required">>, attrs = [],
+ children = []},
+ #xmlel{name = <<"value">>, attrs = [],
+ children = [{xmlcdata, Username}]}]}]
+ ++
+ generate_connection_params_fields(Lang,
+ ConnectionsParams, 1, [])}.
-generate_connection_params_fields(Lang, [], Number, Acc) ->
- Field = generate_connection_params_field(Lang, "", "", -1, "", Number),
+generate_connection_params_fields(Lang, [], Number,
+ Acc) ->
+ Field = generate_connection_params_field(Lang, <<"">>,
+ <<"">>, -1, <<"">>, Number),
lists:reverse(Field ++ Acc);
-
-generate_connection_params_fields(Lang, [ConnectionParams | ConnectionsParams], Number, Acc) ->
+generate_connection_params_fields(Lang,
+ [ConnectionParams | ConnectionsParams],
+ Number, Acc) ->
case ConnectionParams of
- {Server, Encoding, Port, Password} ->
- Field = generate_connection_params_field(Lang, Server, Encoding, Port, Password, Number),
- generate_connection_params_fields(Lang, ConnectionsParams, Number + 1, Field ++ Acc);
- {Server, Encoding, Port} ->
- Field = generate_connection_params_field(Lang, Server, Encoding, Port, [], Number),
- generate_connection_params_fields(Lang, ConnectionsParams, Number + 1, Field ++ Acc);
- {Server, Encoding} ->
- Field = generate_connection_params_field(Lang, Server, Encoding, [], [], Number),
- generate_connection_params_fields(Lang, ConnectionsParams, Number + 1, Field ++ Acc);
- _ ->
- []
+ {Server, Encoding, Port, Password} ->
+ Field = generate_connection_params_field(Lang, Server,
+ Encoding, Port, Password,
+ Number),
+ generate_connection_params_fields(Lang,
+ ConnectionsParams, Number + 1,
+ Field ++ Acc);
+ {Server, Encoding, Port} ->
+ Field = generate_connection_params_field(Lang, Server,
+ Encoding, Port, <<"">>, Number),
+ generate_connection_params_fields(Lang,
+ ConnectionsParams, Number + 1,
+ Field ++ Acc);
+ {Server, Encoding} ->
+ Field = generate_connection_params_field(Lang, Server,
+ Encoding, -1, <<"">>, Number),
+ generate_connection_params_fields(Lang,
+ ConnectionsParams, Number + 1,
+ Field ++ Acc);
+ _ -> []
end.
-generate_connection_params_field(Lang, Server, Encoding, Port, Password, Number) ->
+generate_connection_params_field(Lang, Server, Encoding,
+ Port, Password, Number) ->
EncodingUsed = case Encoding of
- [] ->
- get_default_encoding(Server);
- _ ->
- Encoding
+ <<>> -> get_default_encoding(Server);
+ _ -> Encoding
end,
- PortUsedInt = if
- Port >= 0 andalso Port =< 65535 ->
- Port;
- true ->
- ?DEFAULT_IRC_PORT
- end,
- PortUsed = integer_to_list(PortUsedInt),
+ PortUsedInt = if Port >= 0 andalso Port =< 65535 ->
+ Port;
+ true -> ?DEFAULT_IRC_PORT
+ end,
+ PortUsed =
+ iolist_to_binary(integer_to_list(PortUsedInt)),
PasswordUsed = case Password of
- [] ->
- "";
- _ ->
- Password
- end,
- NumberString = integer_to_list(Number),
- %% Fields are in reverse order, as they will be reversed again later.
- [{xmlelement, "field",
- [{"var", "password" ++ NumberString},
- {"type", "text-single"},
- {"label", io_lib:format(translate:translate(Lang, "Password ~b"), [Number])}],
- [{xmlelement, "value", [], [{xmlcdata, PasswordUsed}]}]},
- {xmlelement, "field",
- [{"var", "port" ++ NumberString},
- {"type", "text-single"},
- {"label", io_lib:format(translate:translate(Lang, "Port ~b"), [Number])}],
- [{xmlelement, "value", [], [{xmlcdata, PortUsed}]}]},
- {xmlelement, "field",
- [{"var", "encoding" ++ NumberString},
- {"type", "list-single"},
- {"label", io_lib:format(translate:translate(Lang, "Encoding for server ~b"), [Number])}],
- [{xmlelement, "value", [], [{xmlcdata, EncodingUsed}]} |
- lists:map(fun(E) ->
- {xmlelement, "option", [{"label", E}],
- [{xmlelement, "value", [], [{xmlcdata, E}]}]}
- end, ?POSSIBLE_ENCODINGS)]},
- {xmlelement, "field",
- [{"var", "server" ++ NumberString},
- {"type", "text-single"},
- {"label", io_lib:format(translate:translate(Lang, "Server ~b"), [Number])}],
- [{xmlelement, "value", [], [{xmlcdata, Server}]}]}].
+ <<>> -> <<>>;
+ _ -> Password
+ end,
+ NumberString =
+ iolist_to_binary(integer_to_list(Number)),
+ [#xmlel{name = <<"field">>,
+ attrs =
+ [{<<"var">>, <<"password", NumberString/binary>>},
+ {<<"type">>, <<"text-single">>},
+ {<<"label">>,
+ iolist_to_binary(
+ io_lib:format(
+ translate:translate(Lang, <<"Password ~b">>),
+ [Number]))}],
+ children =
+ [#xmlel{name = <<"value">>, attrs = [],
+ children = [{xmlcdata, PasswordUsed}]}]},
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"var">>, <<"port", NumberString/binary>>},
+ {<<"type">>, <<"text-single">>},
+ {<<"label">>,
+ iolist_to_binary(
+ io_lib:format(translate:translate(Lang, <<"Port ~b">>),
+ [Number]))}],
+ children =
+ [#xmlel{name = <<"value">>, attrs = [],
+ children = [{xmlcdata, PortUsed}]}]},
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"var">>, <<"encoding", NumberString/binary>>},
+ {<<"type">>, <<"list-single">>},
+ {<<"label">>,
+ list_to_binary(
+ io_lib:format(translate:translate(
+ Lang,
+ <<"Encoding for server ~b">>),
+ [Number]))}],
+ children =
+ [#xmlel{name = <<"value">>, attrs = [],
+ children = [{xmlcdata, EncodingUsed}]}
+ | lists:map(fun (E) ->
+ #xmlel{name = <<"option">>,
+ attrs = [{<<"label">>, E}],
+ children =
+ [#xmlel{name = <<"value">>,
+ attrs = [],
+ children =
+ [{xmlcdata, E}]}]}
+ end,
+ ?POSSIBLE_ENCODINGS)]},
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"var">>, <<"server", NumberString/binary>>},
+ {<<"type">>, <<"text-single">>},
+ {<<"label">>,
+ list_to_binary(
+ io_lib:format(translate:translate(Lang, <<"Server ~b">>),
+ [Number]))}],
+ children =
+ [#xmlel{name = <<"value">>, attrs = [],
+ children = [{xmlcdata, Server}]}]}].
parse_connections_params(Fields) ->
- %% Find all fields staring with serverN, encodingN, portN and passwordN for any values
- %% of N, and generate lists of {"N", Value}.
- Servers = lists:sort(
- [{lists:nthtail(6, Var), lists:flatten(Value)} || {Var, Value} <- Fields,
- lists:prefix("server", Var)]),
- Encodings = lists:sort(
- [{lists:nthtail(8, Var), lists:flatten(Value)} || {Var, Value} <- Fields,
- lists:prefix("encoding", Var)]),
-
- Ports = lists:sort(
- [{lists:nthtail(4, Var), lists:flatten(Value)} || {Var, Value} <- Fields,
- lists:prefix("port", Var)]),
-
- Passwords = lists:sort(
- [{lists:nthtail(8, Var), lists:flatten(Value)} || {Var, Value} <- Fields,
- lists:prefix("password", Var)]),
-
- %% Now sort the lists, and find the corresponding pairs.
- parse_connections_params(Servers, Encodings, Ports, Passwords).
-
-retrieve_connections_params(ConnectionParams, ServerN) ->
+ Servers = lists:flatmap(
+ fun({<<"server", Var/binary>>, Value}) ->
+ [{Var, Value}];
+ (_) ->
+ []
+ end, Fields),
+ Encodings = lists:flatmap(
+ fun({<<"encoding", Var/binary>>, Value}) ->
+ [{Var, Value}];
+ (_) ->
+ []
+ end, Fields),
+ Ports = lists:flatmap(
+ fun({<<"port", Var/binary>>, Value}) ->
+ [{Var, Value}];
+ (_) ->
+ []
+ end, Fields),
+ Passwords = lists:flatmap(
+ fun({<<"password", Var/binary>>, Value}) ->
+ [{Var, Value}];
+ (_) ->
+ []
+ end, Fields),
+ parse_connections_params(Servers, Encodings, Ports,
+ Passwords).
+
+retrieve_connections_params(ConnectionParams,
+ ServerN) ->
case ConnectionParams of
- [{ConnectionParamN, ConnectionParam} | ConnectionParamsTail] ->
- if
- ServerN == ConnectionParamN ->
- {ConnectionParam, ConnectionParamsTail};
- ServerN < ConnectionParamN ->
- {[], [{ConnectionParamN, ConnectionParam} | ConnectionParamsTail]};
- ServerN > ConnectionParamN ->
- {[], ConnectionParamsTail}
- end;
- _ ->
- {[], []}
- end.
-
-parse_connections_params([], _, _, _) ->
- [];
-parse_connections_params(_, [], [], []) ->
- [];
+ [{ConnectionParamN, ConnectionParam}
+ | ConnectionParamsTail] ->
+ if ServerN == ConnectionParamN ->
+ {ConnectionParam, ConnectionParamsTail};
+ ServerN < ConnectionParamN ->
+ {[],
+ [{ConnectionParamN, ConnectionParam}
+ | ConnectionParamsTail]};
+ ServerN > ConnectionParamN -> {[], ConnectionParamsTail}
+ end;
+ _ -> {[], []}
+ end.
-parse_connections_params([{ServerN, Server} | Servers], Encodings, Ports, Passwords) ->
- %% Try to match matches of servers, ports, passwords and encodings, no matter what fields
- %% the client might have left out.
- {NewEncoding, NewEncodings} = retrieve_connections_params(Encodings, ServerN),
- {NewPort, NewPorts} = retrieve_connections_params(Ports, ServerN),
- {NewPassword, NewPasswords} = retrieve_connections_params(Passwords, ServerN),
- [{Server, NewEncoding, NewPort, NewPassword} | parse_connections_params(Servers, NewEncodings, NewPorts, NewPasswords)].
-
-update_table(Host) ->
+parse_connections_params([], _, _, _) -> [];
+parse_connections_params(_, [], [], []) -> [];
+parse_connections_params([{ServerN, Server} | Servers],
+ Encodings, Ports, Passwords) ->
+ {NewEncoding, NewEncodings} =
+ retrieve_connections_params(Encodings, ServerN),
+ {NewPort, NewPorts} = retrieve_connections_params(Ports,
+ ServerN),
+ {NewPassword, NewPasswords} =
+ retrieve_connections_params(Passwords, ServerN),
+ [{Server, NewEncoding, NewPort, NewPassword}
+ | parse_connections_params(Servers, NewEncodings,
+ NewPorts, NewPasswords)].
+
+get_username_and_connection_params(Data) ->
+ Username = case lists:keysearch(username, 1, Data) of
+ {value, {_, U}} when is_binary(U) ->
+ U;
+ _ ->
+ <<"">>
+ end,
+ ConnParams = case lists:keysearch(connections_params, 1, Data) of
+ {value, {_, L}} when is_list(L) ->
+ L;
+ _ ->
+ []
+ end,
+ {Username, ConnParams}.
+
+data_to_binary(Data) ->
+ lists:map(
+ fun({username, U}) ->
+ {username, iolist_to_binary(U)};
+ ({connections_params, Params}) ->
+ {connections_params,
+ lists:map(
+ fun({S, E}) ->
+ {iolist_to_binary(S), iolist_to_binary(E)};
+ ({S, E, Port}) ->
+ {iolist_to_binary(S), iolist_to_binary(E), Port};
+ ({S, E, Port, P}) ->
+ {iolist_to_binary(S), iolist_to_binary(E),
+ Port, iolist_to_binary(P)}
+ end, Params)};
+ (Opt) ->
+ Opt
+ end, Data).
+
+conn_params_to_list(Params) ->
+ lists:map(
+ fun({S, E}) ->
+ {binary_to_list(S), binary_to_list(E)};
+ ({S, E, Port}) ->
+ {binary_to_list(S), binary_to_list(E), Port};
+ ({S, E, Port, P}) ->
+ {binary_to_list(S), binary_to_list(E),
+ Port, binary_to_list(P)}
+ end, Params).
+
+update_table() ->
Fields = record_info(fields, irc_custom),
case mnesia:table_info(irc_custom, attributes) of
- Fields ->
- ok;
- [userserver, data] ->
- ?INFO_MSG("Converting irc_custom table from "
- "{userserver, data} format", []),
- {atomic, ok} = mnesia:create_table(
- mod_irc_tmp_table,
- [{disc_only_copies, [node()]},
- {type, bag},
- {local_content, true},
- {record_name, irc_custom},
- {attributes, record_info(fields, irc_custom)}]),
- mnesia:transform_table(irc_custom, ignore, Fields),
- F1 = fun() ->
- mnesia:write_lock_table(mod_irc_tmp_table),
- mnesia:foldl(
- fun(#irc_custom{us_host = US} = R, _) ->
- mnesia:dirty_write(
- mod_irc_tmp_table,
- R#irc_custom{us_host = {US, Host}})
- end, ok, irc_custom)
- end,
- mnesia:transaction(F1),
- mnesia:clear_table(irc_custom),
- F2 = fun() ->
- mnesia:write_lock_table(irc_custom),
- mnesia:foldl(
- fun(R, _) ->
- mnesia:dirty_write(R)
- end, ok, mod_irc_tmp_table)
- end,
- mnesia:transaction(F2),
- mnesia:delete_table(mod_irc_tmp_table);
- _ ->
- ?INFO_MSG("Recreating irc_custom table", []),
- mnesia:transform_table(irc_custom, ignore, Fields)
+ Fields ->
+ ejabberd_config:convert_table_to_binary(
+ irc_custom, Fields, set,
+ fun(#irc_custom{us_host = {_, H}}) -> H end,
+ fun(#irc_custom{us_host = {{U, S}, H},
+ data = Data} = R) ->
+ R#irc_custom{us_host = {{iolist_to_binary(U),
+ iolist_to_binary(S)},
+ iolist_to_binary(H)},
+ data = data_to_binary(Data)}
+ end);
+ _ ->
+ ?INFO_MSG("Recreating irc_custom table", []),
+ mnesia:transform_table(irc_custom, ignore, Fields)
end.
+
+export(_Server) ->
+ [{irc_custom,
+ fun(Host, #irc_custom{us_host = {{U, S}, IRCHost},
+ data = Data}) ->
+ case str:suffix(Host, IRCHost) of
+ true ->
+ SJID = ejabberd_odbc:escape(
+ jlib:jid_to_string(
+ jlib:make_jid(U, S, <<"">>))),
+ SIRCHost = ejabberd_odbc:escape(IRCHost),
+ SData = ejabberd_odbc:encode_term(Data),
+ [[<<"delete from irc_custom where jid='">>, SJID,
+ <<"' and host='">>, SIRCHost, <<"';">>],
+ [<<"insert into irc_custom(jid, host, "
+ "data) values ('">>,
+ SJID, <<"', '">>, SIRCHost, <<"', '">>, SData,
+ <<"');">>]];
+ false ->
+ []
+ end
+ end}].
diff --git a/src/mod_irc/mod_irc_connection.erl b/src/mod_irc/mod_irc_connection.erl
index 1c9bd0c95..ba0cb4345 100644
--- a/src/mod_irc/mod_irc_connection.erl
+++ b/src/mod_irc/mod_irc_connection.erl
@@ -25,53 +25,71 @@
%%%----------------------------------------------------------------------
-module(mod_irc_connection).
+
-author('alexey@process-one.net').
-behaviour(gen_fsm).
%% External exports
--export([start_link/8, start/9, route_chan/4, route_nick/3]).
+-export([start_link/8, start/9, route_chan/4,
+ route_nick/3]).
%% gen_fsm callbacks
--export([init/1,
- open_socket/2,
- wait_for_registration/2,
- stream_established/2,
- handle_event/3,
- handle_sync_event/4,
- handle_info/3,
- terminate/3,
+-export([init/1, open_socket/2, wait_for_registration/2,
+ stream_established/2, handle_event/3,
+ handle_sync_event/4, handle_info/3, terminate/3,
code_change/4]).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
-define(SETS, gb_sets).
--record(state, {socket, encoding, port, password,
- queue, user, host, server, nick,
- channels = dict:new(),
- nickchannel, mod,
- inbuf = "", outbuf = ""}).
+-record(state,
+ {socket :: inet:socket(),
+ encoding = <<"">> :: binary(),
+ port = 0 :: inet:port_number(),
+ password = <<"">> :: binary(),
+ queue = queue:new() :: queue(),
+ user = #jid{} :: jid(),
+ host = <<"">> :: binary(),
+ server = <<"">> :: binary(),
+ nick = <<"">> :: binary(),
+ channels = dict:new() :: dict(),
+ nickchannel :: binary(),
+ mod = mod_irc :: atom(),
+ inbuf = <<"">> :: binary(),
+ outbuf = <<"">> :: binary()}).
%-define(DBGFSM, true).
-ifdef(DBGFSM).
+
-define(FSMOPTS, [{debug, [trace]}]).
+
-else.
+
-define(FSMOPTS, []).
--endif.
%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
-start(From, Host, ServerHost, Server, Username, Encoding, Port, Password, Mod) ->
- Supervisor = gen_mod:get_module_proc(ServerHost, ejabberd_mod_irc_sup),
- supervisor:start_child(
- Supervisor, [From, Host, Server, Username, Encoding, Port, Password, Mod]).
+-endif.
-start_link(From, Host, Server, Username, Encoding, Port, Password, Mod) ->
- gen_fsm:start_link(?MODULE, [From, Host, Server, Username, Encoding, Port, Password, Mod],
+start(From, Host, ServerHost, Server, Username,
+ Encoding, Port, Password, Mod) ->
+ Supervisor = gen_mod:get_module_proc(ServerHost,
+ ejabberd_mod_irc_sup),
+ supervisor:start_child(Supervisor,
+ [From, Host, Server, Username, Encoding, Port,
+ Password, Mod]).
+
+start_link(From, Host, Server, Username, Encoding, Port,
+ Password, Mod) ->
+ gen_fsm:start_link(?MODULE,
+ [From, Host, Server, Username, Encoding, Port, Password,
+ Mod],
?FSMOPTS).
%%%----------------------------------------------------------------------
@@ -85,17 +103,14 @@ start_link(From, Host, Server, Username, Encoding, Port, Password, Mod) ->
%% ignore |
%% {stop, StopReason}
%%----------------------------------------------------------------------
-init([From, Host, Server, Username, Encoding, Port, Password, Mod]) ->
+init([From, Host, Server, Username, Encoding, Port,
+ Password, Mod]) ->
gen_fsm:send_event(self(), init),
- {ok, open_socket, #state{queue = queue:new(),
- mod = Mod,
- encoding = Encoding,
- port = Port,
- password = Password,
- user = From,
- nick = Username,
- host = Host,
- server = Server}}.
+ {ok, open_socket,
+ #state{queue = queue:new(), mod = Mod,
+ encoding = Encoding, port = Port, password = Password,
+ user = From, nick = Username, host = Host,
+ server = Server}}.
%%----------------------------------------------------------------------
%% Func: StateName/2
@@ -107,42 +122,42 @@ open_socket(init, StateData) ->
Addr = StateData#state.server,
Port = StateData#state.port,
?DEBUG("Connecting with IPv6 to ~s:~p", [Addr, Port]),
- Connect6 = gen_tcp:connect(Addr, Port, [inet6, binary, {packet, 0}]),
+ Connect6 = gen_tcp:connect(binary_to_list(Addr), Port,
+ [inet6, binary, {packet, 0}]),
Connect = case Connect6 of
- {error, _} ->
- ?DEBUG("Connection with IPv6 to ~s:~p failed. Now using IPv4.", [Addr, Port]),
- gen_tcp:connect(Addr, Port, [inet, binary, {packet, 0}]);
- _ ->
- Connect6
+ {error, _} ->
+ ?DEBUG("Connection with IPv6 to ~s:~p failed. "
+ "Now using IPv4.",
+ [Addr, Port]),
+ gen_tcp:connect(binary_to_list(Addr), Port,
+ [inet, binary, {packet, 0}]);
+ _ -> Connect6
end,
case Connect of
- {ok, Socket} ->
- NewStateData = StateData#state{socket = Socket},
- if
- StateData#state.password /= "" ->
- send_text(NewStateData,
- io_lib:format("PASS ~s\r\n", [StateData#state.password]));
- true -> true
- end,
- send_text(NewStateData,
- io_lib:format("NICK ~s\r\n", [StateData#state.nick])),
- send_text(NewStateData,
- io_lib:format(
- "USER ~s ~s ~s :~s\r\n",
- [StateData#state.nick,
- StateData#state.nick,
- StateData#state.host,
- StateData#state.nick])),
- {next_state, wait_for_registration,
- NewStateData};
- {error, Reason} ->
- ?DEBUG("connect return ~p~n", [Reason]),
- Text = case Reason of
- timeout -> "Server Connect Timeout";
- _ -> "Server Connect Failed"
- end,
- bounce_messages(Text),
- {stop, normal, StateData}
+ {ok, Socket} ->
+ NewStateData = StateData#state{socket = Socket},
+ if StateData#state.password /= <<"">> ->
+ send_text(NewStateData,
+ io_lib:format("PASS ~s\r\n",
+ [StateData#state.password]));
+ true -> true
+ end,
+ send_text(NewStateData,
+ io_lib:format("NICK ~s\r\n", [StateData#state.nick])),
+ send_text(NewStateData,
+ io_lib:format("USER ~s ~s ~s :~s\r\n",
+ [StateData#state.nick, StateData#state.nick,
+ StateData#state.host,
+ StateData#state.nick])),
+ {next_state, wait_for_registration, NewStateData};
+ {error, Reason} ->
+ ?DEBUG("connect return ~p~n", [Reason]),
+ Text = case Reason of
+ timeout -> <<"Server Connect Timeout">>;
+ _ -> <<"Server Connect Failed">>
+ end,
+ bounce_messages(Text),
+ {stop, normal, StateData}
end.
wait_for_registration(closed, StateData) ->
@@ -150,15 +165,11 @@ wait_for_registration(closed, StateData) ->
stream_established({xmlstreamend, _Name}, StateData) ->
{stop, normal, StateData};
-
stream_established(timeout, StateData) ->
{stop, normal, StateData};
-
stream_established(closed, StateData) ->
{stop, normal, StateData}.
-
-
%%----------------------------------------------------------------------
%% Func: StateName/3
%% Returns: {next_state, NextStateName, NextStateData} |
@@ -190,46 +201,43 @@ handle_event(_Event, StateName, StateData) ->
%% {stop, Reason, NewStateData} |
%% {stop, Reason, Reply, NewStateData}
%%----------------------------------------------------------------------
-handle_sync_event(_Event, _From, StateName, StateData) ->
- Reply = ok,
- {reply, Reply, StateName, StateData}.
+handle_sync_event(_Event, _From, StateName,
+ StateData) ->
+ Reply = ok, {reply, Reply, StateName, StateData}.
code_change(_OldVsn, StateName, StateData, _Extra) ->
{ok, StateName, StateData}.
-define(SEND(S),
- if
- StateName == stream_established ->
- send_text(StateData, S),
- StateData;
- true ->
- StateData#state{outbuf = StateData#state.outbuf ++ S}
+ if StateName == stream_established ->
+ send_text(StateData, S), StateData;
+ true ->
+ StateData#state{outbuf = <<(StateData#state.outbuf)/binary,
+ (iolist_to_binary(S))/binary>>}
end).
-get_password_from_presence({xmlelement, "presence", _Attrs, Els}) ->
- case lists:filter(fun(El) ->
- case El of
- {xmlelement, "x", Attrs, _Els} ->
- case xml:get_attr_s("xmlns", Attrs) of
- ?NS_MUC ->
- true;
- _ ->
- false
- end;
- _ ->
- false
- end
- end, Els) of
- [ElXMUC | _] ->
- case xml:get_subtag(ElXMUC, "password") of
- {xmlelement, "password", _, _} = PasswordTag ->
- {true, xml:get_tag_cdata(PasswordTag)};
- _ ->
- false
- end;
- _ ->
- false
- end.
+get_password_from_presence(#xmlel{name = <<"presence">>,
+ children = Els}) ->
+ case lists:filter(fun (El) ->
+ case El of
+ #xmlel{name = <<"x">>, attrs = Attrs} ->
+ case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ ?NS_MUC -> true;
+ _ -> false
+ end;
+ _ -> false
+ end
+ end,
+ Els)
+ of
+ [ElXMUC | _] ->
+ case xml:get_subtag(ElXMUC, <<"password">>) of
+ #xmlel{name = <<"password">>} = PasswordTag ->
+ {true, xml:get_tag_cdata(PasswordTag)};
+ _ -> false
+ end;
+ _ -> false
+ end.
%%----------------------------------------------------------------------
%% Func: handle_info/3
@@ -238,432 +246,438 @@ get_password_from_presence({xmlelement, "presence", _Attrs, Els}) ->
%% {stop, Reason, NewStateData}
%%----------------------------------------------------------------------
handle_info({route_chan, Channel, Resource,
- {xmlelement, "presence", Attrs, _Els} = Presence},
+ #xmlel{name = <<"presence">>, attrs = Attrs} =
+ Presence},
StateName, StateData) ->
- NewStateData =
- case xml:get_attr_s("type", Attrs) of
- "unavailable" ->
- send_stanza_unavailable(Channel, StateData),
- S1 = ?SEND(io_lib:format("PART #~s\r\n", [Channel])),
- S1#state{channels =
- dict:erase(Channel, S1#state.channels)};
- "subscribe" -> StateData;
- "subscribed" -> StateData;
- "unsubscribe" -> StateData;
- "unsubscribed" -> StateData;
- "error" -> stop;
- _ ->
- Nick = case Resource of
- "" ->
- StateData#state.nick;
+ NewStateData = case xml:get_attr_s(<<"type">>, Attrs) of
+ <<"unavailable">> ->
+ send_stanza_unavailable(Channel, StateData),
+ S1 = (?SEND((io_lib:format("PART #~s\r\n",
+ [Channel])))),
+ S1#state{channels =
+ dict:erase(Channel, S1#state.channels)};
+ <<"subscribe">> -> StateData;
+ <<"subscribed">> -> StateData;
+ <<"unsubscribe">> -> StateData;
+ <<"unsubscribed">> -> StateData;
+ <<"error">> -> stop;
+ _ ->
+ Nick = case Resource of
+ <<"">> -> StateData#state.nick;
+ _ -> Resource
+ end,
+ S1 = if Nick /= StateData#state.nick ->
+ S11 = (?SEND((io_lib:format("NICK ~s\r\n",
+ [Nick])))),
+ S11#state{nickchannel = Channel};
+ true -> StateData
+ end,
+ case dict:is_key(Channel, S1#state.channels) of
+ true -> S1;
_ ->
- Resource
- end,
- S1 = if
- Nick /= StateData#state.nick ->
- S11 = ?SEND(io_lib:format("NICK ~s\r\n", [Nick])),
- % The server reply will change the copy of the
- % nick in the state (or indicate a clash).
- S11#state{nickchannel = Channel};
- true ->
- StateData
- end,
- case dict:is_key(Channel, S1#state.channels) of
- true ->
- S1;
- _ ->
- case get_password_from_presence(Presence) of
- {true, Password} ->
- S2 = ?SEND(io_lib:format("JOIN #~s ~s\r\n", [Channel, Password]));
- _ ->
- S2 = ?SEND(io_lib:format("JOIN #~s\r\n", [Channel]))
- end,
- S2#state{channels =
- dict:store(Channel, ?SETS:new(),
- S1#state.channels)}
- end
- end,
- if
- NewStateData == stop ->
- {stop, normal, StateData};
- true ->
- case dict:fetch_keys(NewStateData#state.channels) of
- [] -> {stop, normal, NewStateData};
- _ -> {next_state, StateName, NewStateData}
- end
+ case get_password_from_presence(Presence) of
+ {true, Password} ->
+ S2 =
+ (?SEND((io_lib:format("JOIN #~s ~s\r\n",
+ [Channel,
+ Password]))));
+ _ ->
+ S2 = (?SEND((io_lib:format("JOIN #~s\r\n",
+ [Channel]))))
+ end,
+ S2#state{channels =
+ dict:store(Channel, (?SETS):new(),
+ S1#state.channels)}
+ end
+ end,
+ if NewStateData == stop -> {stop, normal, StateData};
+ true ->
+ case dict:fetch_keys(NewStateData#state.channels) of
+ [] -> {stop, normal, NewStateData};
+ _ -> {next_state, StateName, NewStateData}
+ end
end;
-
handle_info({route_chan, Channel, Resource,
- {xmlelement, "message", Attrs, _Els} = El},
+ #xmlel{name = <<"message">>, attrs = Attrs} = El},
StateName, StateData) ->
- NewStateData =
- case xml:get_attr_s("type", Attrs) of
- "groupchat" ->
- case xml:get_path_s(El, [{elem, "subject"}, cdata]) of
- "" ->
- ejabberd_router:route(
- jlib:make_jid(
- lists:concat(
- [Channel, "%", StateData#state.server]),
- StateData#state.host, StateData#state.nick),
- StateData#state.user, El),
- Body = xml:get_path_s(El, [{elem, "body"}, cdata]),
- case Body of
- "/quote " ++ Rest ->
- ?SEND(Rest ++ "\r\n");
- "/msg " ++ Rest ->
- ?SEND("PRIVMSG " ++ Rest ++ "\r\n");
- "/me " ++ Rest ->
- Strings = string:tokens(Rest, "\n"),
- Res = lists:concat(
- lists:map(
- fun(S) ->
- io_lib:format(
- "PRIVMSG #~s :\001ACTION ~s\001\r\n",
- [Channel, S])
- end, Strings)),
- ?SEND(Res);
- "/ctcp " ++ Rest ->
- Words = string:tokens(Rest, " "),
- case Words of
- [CtcpDest | _] ->
- CtcpCmd =
- toupper(
- string:substr(
- Rest,
- string:str(Rest, " ") + 1)),
- Res = io_lib:format(
- "PRIVMSG ~s :\001~s\001\r\n",
- [CtcpDest, CtcpCmd]),
- ?SEND(Res);
- _ ->
- ok
- end;
- _ ->
- Strings = string:tokens(Body, "\n"),
- Res = lists:concat(
- lists:map(
- fun(S) ->
- io_lib:format(
- "PRIVMSG #~s :~s\r\n",
- [Channel, S])
- end, Strings)),
- ?SEND(Res)
- end;
- Subject ->
- Strings = string:tokens(Subject, "\n"),
- Res = lists:concat(
- lists:map(
- fun(S) ->
- io_lib:format("TOPIC #~s :~s\r\n",
- [Channel, S])
- end, Strings)),
- ?SEND(Res)
- end;
- Type when Type == "chat"; Type == ""; Type == "normal" ->
- Body = xml:get_path_s(El, [{elem, "body"}, cdata]),
- case Body of
- "/quote " ++ Rest ->
- ?SEND(Rest ++ "\r\n");
- "/msg " ++ Rest ->
- ?SEND("PRIVMSG " ++ Rest ++ "\r\n");
- "/me " ++ Rest ->
- Strings = string:tokens(Rest, "\n"),
- Res = lists:concat(
- lists:map(
- fun(S) ->
- io_lib:format(
- "PRIVMSG ~s :\001ACTION ~s\001\r\n",
- [Resource, S])
- end, Strings)),
- ?SEND(Res);
- "/ctcp " ++ Rest ->
- Words = string:tokens(Rest, " "),
- case Words of
- [CtcpDest | _ ] ->
- CtcpCmd =
- toupper(
- string:substr(
- Rest, string:str(Rest, " ") + 1)),
- Res = io_lib:format(
- "PRIVMSG ~s :~s\r\n",
- [CtcpDest, "\001" ++ CtcpCmd ++ "\001"]),
- ?SEND(Res);
- _ ->
- ok
- end;
- _ ->
- Strings = string:tokens(Body, "\n"),
- Res = lists:concat(
- lists:map(
- fun(S) ->
- io_lib:format("PRIVMSG ~s :~s\r\n",
- [Resource, S])
- end, Strings)),
- ?SEND(Res)
- end;
- "error" ->
- stop;
- _ ->
- StateData
- end,
- if
- NewStateData == stop ->
- {stop, normal, StateData};
- true ->
- {next_state, StateName, NewStateData}
+ NewStateData = case xml:get_attr_s(<<"type">>, Attrs) of
+ <<"groupchat">> ->
+ case xml:get_path_s(El, [{elem, <<"subject">>}, cdata])
+ of
+ <<"">> ->
+ ejabberd_router:route(
+ jlib:make_jid(
+ iolist_to_binary([Channel,
+ <<"%">>,
+ StateData#state.server]),
+ StateData#state.host,
+ StateData#state.nick),
+ StateData#state.user, El),
+ Body = xml:get_path_s(El,
+ [{elem, <<"body">>},
+ cdata]),
+ case Body of
+ <<"/quote ", Rest/binary>> ->
+ ?SEND(<<Rest/binary, "\r\n">>);
+ <<"/msg ", Rest/binary>> ->
+ ?SEND(<<"PRIVMSG ", Rest/binary, "\r\n">>);
+ <<"/me ", Rest/binary>> ->
+ Strings = str:tokens(Rest, <<"\n">>),
+ Res = iolist_to_binary(
+ lists:map(
+ fun (S) ->
+ io_lib:format(
+ "PRIVMSG #~s :\001ACTION ~s\001\r\n",
+ [Channel, S])
+ end,
+ Strings)),
+ ?SEND(Res);
+ <<"/ctcp ", Rest/binary>> ->
+ Words = str:tokens(Rest, <<" ">>),
+ case Words of
+ [CtcpDest | _] ->
+ CtcpCmd = str:to_upper(
+ str:substr(Rest,
+ str:str(Rest,
+ <<" ">>)
+ + 1)),
+ Res =
+ io_lib:format("PRIVMSG ~s :\001~s\001\r\n",
+ [CtcpDest,
+ CtcpCmd]),
+ ?SEND(Res);
+ _ -> ok
+ end;
+ _ ->
+ Strings = str:tokens(Body, <<"\n">>),
+ Res = iolist_to_binary(
+ lists:map(
+ fun (S) ->
+ io_lib:format("PRIVMSG #~s :~s\r\n",
+ [Channel, S])
+ end,
+ Strings)),
+ ?SEND(Res)
+ end;
+ Subject ->
+ Strings = str:tokens(Subject, <<"\n">>),
+ Res = iolist_to_binary(
+ lists:map(
+ fun (S) ->
+ io_lib:format("TOPIC #~s :~s\r\n",
+ [Channel, S])
+ end,
+ Strings)),
+ ?SEND(Res)
+ end;
+ Type
+ when Type == <<"chat">>;
+ Type == <<"">>;
+ Type == <<"normal">> ->
+ Body = xml:get_path_s(El, [{elem, <<"body">>}, cdata]),
+ case Body of
+ <<"/quote ", Rest/binary>> ->
+ ?SEND(<<Rest/binary, "\r\n">>);
+ <<"/msg ", Rest/binary>> ->
+ ?SEND(<<"PRIVMSG ", Rest/binary, "\r\n">>);
+ <<"/me ", Rest/binary>> ->
+ Strings = str:tokens(Rest, <<"\n">>),
+ Res = iolist_to_binary(
+ lists:map(
+ fun (S) ->
+ io_lib:format(
+ "PRIVMSG ~s :\001ACTION ~s\001\r\n",
+ [Resource, S])
+ end,
+ Strings)),
+ ?SEND(Res);
+ <<"/ctcp ", Rest/binary>> ->
+ Words = str:tokens(Rest, <<" ">>),
+ case Words of
+ [CtcpDest | _] ->
+ CtcpCmd = str:to_upper(
+ str:substr(Rest,
+ str:str(Rest,
+ <<" ">>)
+ + 1)),
+ Res = io_lib:format("PRIVMSG ~s :~s\r\n",
+ [CtcpDest,
+ <<"\001",
+ CtcpCmd/binary,
+ "\001">>]),
+ ?SEND(Res);
+ _ -> ok
+ end;
+ _ ->
+ Strings = str:tokens(Body, <<"\n">>),
+ Res = iolist_to_binary(
+ lists:map(
+ fun (S) ->
+ io_lib:format(
+ "PRIVMSG ~s :~s\r\n",
+ [Resource, S])
+ end,
+ Strings)),
+ ?SEND(Res)
+ end;
+ <<"error">> -> stop;
+ _ -> StateData
+ end,
+ if NewStateData == stop -> {stop, normal, StateData};
+ true -> {next_state, StateName, NewStateData}
end;
-
-
handle_info({route_chan, Channel, Resource,
- {xmlelement, "iq", _Attrs, _Els} = El},
+ #xmlel{name = <<"iq">>} = El},
StateName, StateData) ->
From = StateData#state.user,
- To = jlib:make_jid(lists:concat([Channel, "%", StateData#state.server]),
+ To = jlib:make_jid(iolist_to_binary([Channel, <<"%">>,
+ StateData#state.server]),
StateData#state.host, StateData#state.nick),
_ = case jlib:iq_query_info(El) of
- #iq{xmlns = ?NS_MUC_ADMIN} = IQ ->
- iq_admin(StateData, Channel, From, To, IQ);
- #iq{xmlns = ?NS_VERSION} ->
- Res = io_lib:format("PRIVMSG ~s :\001VERSION\001\r\n",
- [Resource]),
- _ = ?SEND(Res),
- Err = jlib:make_error_reply(
- El, ?ERR_FEATURE_NOT_IMPLEMENTED),
- ejabberd_router:route(To, From, Err);
- #iq{xmlns = ?NS_TIME} ->
- Res = io_lib:format("PRIVMSG ~s :\001TIME\001\r\n",
- [Resource]),
- _ = ?SEND(Res),
- Err = jlib:make_error_reply(
- El, ?ERR_FEATURE_NOT_IMPLEMENTED),
- ejabberd_router:route(To, From, Err);
- #iq{xmlns = ?NS_VCARD} ->
- Res = io_lib:format("WHOIS ~s \r\n",
- [Resource]),
- _ = ?SEND(Res),
- Err = jlib:make_error_reply(
- El, ?ERR_FEATURE_NOT_IMPLEMENTED),
- ejabberd_router:route(To, From, Err);
- #iq{} ->
- Err = jlib:make_error_reply(
- El, ?ERR_FEATURE_NOT_IMPLEMENTED),
- ejabberd_router:route(To, From, Err);
- _ ->
- ok
- end,
+ #iq{xmlns = ?NS_MUC_ADMIN} = IQ ->
+ iq_admin(StateData, Channel, From, To, IQ);
+ #iq{xmlns = ?NS_VERSION} ->
+ Res = io_lib:format("PRIVMSG ~s :\001VERSION\001\r\n",
+ [Resource]),
+ _ = (?SEND(Res)),
+ Err = jlib:make_error_reply(El,
+ ?ERR_FEATURE_NOT_IMPLEMENTED),
+ ejabberd_router:route(To, From, Err);
+ #iq{xmlns = ?NS_TIME} ->
+ Res = io_lib:format("PRIVMSG ~s :\001TIME\001\r\n",
+ [Resource]),
+ _ = (?SEND(Res)),
+ Err = jlib:make_error_reply(El,
+ ?ERR_FEATURE_NOT_IMPLEMENTED),
+ ejabberd_router:route(To, From, Err);
+ #iq{xmlns = ?NS_VCARD} ->
+ Res = io_lib:format("WHOIS ~s \r\n", [Resource]),
+ _ = (?SEND(Res)),
+ Err = jlib:make_error_reply(El,
+ ?ERR_FEATURE_NOT_IMPLEMENTED),
+ ejabberd_router:route(To, From, Err);
+ #iq{} ->
+ Err = jlib:make_error_reply(El,
+ ?ERR_FEATURE_NOT_IMPLEMENTED),
+ ejabberd_router:route(To, From, Err);
+ _ -> ok
+ end,
{next_state, StateName, StateData};
-
-handle_info({route_chan, _Channel, _Resource, _Packet}, StateName, StateData) ->
+handle_info({route_chan, _Channel, _Resource, _Packet},
+ StateName, StateData) ->
{next_state, StateName, StateData};
-
-
handle_info({route_nick, Nick,
- {xmlelement, "message", Attrs, _Els} = El},
+ #xmlel{name = <<"message">>, attrs = Attrs} = El},
StateName, StateData) ->
- NewStateData =
- case xml:get_attr_s("type", Attrs) of
- "chat" ->
- Body = xml:get_path_s(El, [{elem, "body"}, cdata]),
- case Body of
- "/quote " ++ Rest ->
- ?SEND(Rest ++ "\r\n");
- "/msg " ++ Rest ->
- ?SEND("PRIVMSG " ++ Rest ++ "\r\n");
- "/me " ++ Rest ->
- Strings = string:tokens(Rest, "\n"),
- Res = lists:concat(
- lists:map(
- fun(S) ->
- io_lib:format(
- "PRIVMSG ~s :\001ACTION ~s\001\r\n",
- [Nick, S])
- end, Strings)),
- ?SEND(Res);
- "/ctcp " ++ Rest ->
- Words = string:tokens(Rest, " "),
- case Words of
- [CtcpDest | _ ] ->
- CtcpCmd = toupper(string:substr(Rest, string:str(Rest, " ")+1 )),
- Res = io_lib:format(
- "PRIVMSG ~s :~s\r\n",
- [CtcpDest, "\001" ++ CtcpCmd ++ "\001"]),
- ?SEND(Res);
- _ ->
- ok
- end;
- _ ->
- Strings = string:tokens(Body, "\n"),
- Res = lists:concat(
- lists:map(
- fun(S) ->
- io_lib:format("PRIVMSG ~s :~s\r\n",
- [Nick, S])
- end, Strings)),
- ?SEND(Res)
- end;
- "error" ->
- stop;
- _ ->
- StateData
- end,
- if
- NewStateData == stop ->
- {stop, normal, StateData};
- true ->
- {next_state, StateName, NewStateData}
+ NewStateData = case xml:get_attr_s(<<"type">>, Attrs) of
+ <<"chat">> ->
+ Body = xml:get_path_s(El, [{elem, <<"body">>}, cdata]),
+ case Body of
+ <<"/quote ", Rest/binary>> ->
+ ?SEND(<<Rest/binary, "\r\n">>);
+ <<"/msg ", Rest/binary>> ->
+ ?SEND(<<"PRIVMSG ", Rest/binary, "\r\n">>);
+ <<"/me ", Rest/binary>> ->
+ Strings = str:tokens(Rest, <<"\n">>),
+ Res = iolist_to_binary(
+ lists:map(
+ fun (S) ->
+ io_lib:format(
+ "PRIVMSG ~s :\001ACTION ~s\001\r\n",
+ [Nick, S])
+ end,
+ Strings)),
+ ?SEND(Res);
+ <<"/ctcp ", Rest/binary>> ->
+ Words = str:tokens(Rest, <<" ">>),
+ case Words of
+ [CtcpDest | _] ->
+ CtcpCmd = str:to_upper(
+ str:substr(Rest,
+ str:str(Rest,
+ <<" ">>)
+ + 1)),
+ Res = io_lib:format("PRIVMSG ~s :~s\r\n",
+ [CtcpDest,
+ <<"\001",
+ CtcpCmd/binary,
+ "\001">>]),
+ ?SEND(Res);
+ _ -> ok
+ end;
+ _ ->
+ Strings = str:tokens(Body, <<"\n">>),
+ Res = iolist_to_binary(
+ lists:map(
+ fun (S) ->
+ io_lib:format(
+ "PRIVMSG ~s :~s\r\n",
+ [Nick, S])
+ end,
+ Strings)),
+ ?SEND(Res)
+ end;
+ <<"error">> -> stop;
+ _ -> StateData
+ end,
+ if NewStateData == stop -> {stop, normal, StateData};
+ true -> {next_state, StateName, NewStateData}
end;
-
-handle_info({route_nick, _Nick, _Packet}, StateName, StateData) ->
+handle_info({route_nick, _Nick, _Packet}, StateName,
+ StateData) ->
{next_state, StateName, StateData};
-
-
-handle_info({ircstring, [$P, $I, $N, $G, $ | ID]}, StateName, StateData) ->
- send_text(StateData, "PONG " ++ ID ++ "\r\n"),
+handle_info({ircstring,
+ <<$P, $I, $N, $G, $\s, ID/binary>>},
+ StateName, StateData) ->
+ send_text(StateData, <<"PONG ", ID/binary, "\r\n">>),
{next_state, StateName, StateData};
-
-handle_info({ircstring, [$: | String]}, wait_for_registration, StateData) ->
- Words = string:tokens(String, " "),
- {NewState, NewStateData} =
- case Words of
- [_, "001" | _] ->
- send_text(StateData,
- io_lib:format("CODEPAGE ~s\r\n", [StateData#state.encoding])),
- {stream_established, StateData};
- [_, "433" | _] ->
- {error,
- {error, error_nick_in_use(StateData, String), StateData}};
- [_, [$4, _, _] | _] ->
- {error,
- {error, error_unknown_num(StateData, String, "cancel"),
- StateData}};
- [_, [$5, _, _] | _] ->
- {error,
- {error, error_unknown_num(StateData, String, "cancel"),
- StateData}};
- _ ->
- ?DEBUG("unknown irc command '~s'~n", [String]),
- {wait_for_registration, StateData}
- end,
- % Note that we don't send any data at this stage.
- if
- NewState == error ->
- {stop, normal, NewStateData};
- true ->
- {next_state, NewState, NewStateData}
+handle_info({ircstring, <<$:, String/binary>>},
+ wait_for_registration, StateData) ->
+ Words = str:tokens(String, <<" ">>),
+ {NewState, NewStateData} = case Words of
+ [_, <<"001">> | _] ->
+ send_text(StateData,
+ io_lib:format("CODEPAGE ~s\r\n",
+ [StateData#state.encoding])),
+ {stream_established, StateData};
+ [_, <<"433">> | _] ->
+ {error,
+ {error,
+ error_nick_in_use(StateData, String),
+ StateData}};
+ [_, <<$4, _, _>> | _] ->
+ {error,
+ {error,
+ error_unknown_num(StateData, String,
+ <<"cancel">>),
+ StateData}};
+ [_, <<$5, _, _>> | _] ->
+ {error,
+ {error,
+ error_unknown_num(StateData, String,
+ <<"cancel">>),
+ StateData}};
+ _ ->
+ ?DEBUG("unknown irc command '~s'~n",
+ [String]),
+ {wait_for_registration, StateData}
+ end,
+ if NewState == error -> {stop, normal, NewStateData};
+ true -> {next_state, NewState, NewStateData}
end;
-
-handle_info({ircstring, [$: | String]}, _StateName, StateData) ->
- Words = string:tokens(String, " "),
- NewStateData =
- case Words of
- [_, "353" | Items] ->
- process_channel_list(StateData, Items);
- [_, "332", _Nick, [$# | Chan] | _] ->
- process_channel_topic(StateData, Chan, String),
- StateData;
- [_, "333", _Nick, [$# | Chan] | _] ->
- process_channel_topic_who(StateData, Chan, String),
- StateData;
- [_, "318", _, Nick | _] ->
- process_endofwhois(StateData, String, Nick),
- StateData;
- [_, "311", _, Nick, Ident, Irchost | _ ] ->
- process_whois311(StateData, String, Nick, Ident, Irchost),
- StateData;
- [_, "312", _, Nick, Ircserver | _ ] ->
- process_whois312(StateData, String, Nick, Ircserver),
- StateData;
- [_, "319", _, Nick | _ ] ->
- process_whois319(StateData, String, Nick),
- StateData;
- [_, "433" | _] ->
- process_nick_in_use(StateData, String);
- % CODEPAGE isn't standard, so don't complain if it's not there.
- [_, "421", _, "CODEPAGE" | _] ->
- StateData;
- [_, [$4, _, _] | _] ->
- process_num_error(StateData, String);
- [_, [$5, _, _] | _] ->
- process_num_error(StateData, String);
- [From, "PRIVMSG", [$# | Chan] | _] ->
- process_chanprivmsg(StateData, Chan, From, String),
- StateData;
- [From, "NOTICE", [$# | Chan] | _] ->
- process_channotice(StateData, Chan, From, String),
- StateData;
- [From, "PRIVMSG", Nick, ":\001VERSION\001" | _] ->
- process_version(StateData, Nick, From),
- StateData;
- [From, "PRIVMSG", Nick, ":\001USERINFO\001" | _] ->
- process_userinfo(StateData, Nick, From),
- StateData;
- [From, "PRIVMSG", Nick | _] ->
- process_privmsg(StateData, Nick, From, String),
- StateData;
- [From, "NOTICE", Nick | _] ->
- process_notice(StateData, Nick, From, String),
- StateData;
- [From, "TOPIC", [$# | Chan] | _] ->
- process_topic(StateData, Chan, From, String),
- StateData;
- [From, "PART", [$# | Chan] | _] ->
- process_part(StateData, Chan, From, String);
- [From, "QUIT" | _] ->
- process_quit(StateData, From, String);
- [From, "JOIN", Chan | _] ->
- process_join(StateData, Chan, From, String);
- [From, "MODE", [$# | Chan], "+o", Nick | _] ->
- process_mode_o(StateData, Chan, From, Nick,
- "admin", "moderator"),
- StateData;
- [From, "MODE", [$# | Chan], "-o", Nick | _] ->
- process_mode_o(StateData, Chan, From, Nick,
- "member", "participant"),
- StateData;
- [From, "KICK", [$# | Chan], Nick | _] ->
- process_kick(StateData, Chan, From, Nick, String),
- StateData;
- [From, "NICK", Nick | _] ->
- process_nick(StateData, From, Nick);
- _ ->
- ?DEBUG("unknown irc command '~s'~n", [String]),
- StateData
- end,
- NewStateData1 =
- case StateData#state.outbuf of
- "" ->
- NewStateData;
- Data ->
- send_text(NewStateData, Data),
- NewStateData#state{outbuf = ""}
- end,
+handle_info({ircstring, <<$:, String/binary>>},
+ _StateName, StateData) ->
+ Words = str:tokens(String, <<" ">>),
+ NewStateData = case Words of
+ [_, <<"353">> | Items] ->
+ process_channel_list(StateData, Items);
+ [_, <<"332">>, _Nick, <<$#, Chan/binary>> | _] ->
+ process_channel_topic(StateData, Chan, String),
+ StateData;
+ [_, <<"333">>, _Nick, <<$#, Chan/binary>> | _] ->
+ process_channel_topic_who(StateData, Chan, String),
+ StateData;
+ [_, <<"318">>, _, Nick | _] ->
+ process_endofwhois(StateData, String, Nick), StateData;
+ [_, <<"311">>, _, Nick, Ident, Irchost | _] ->
+ process_whois311(StateData, String, Nick, Ident,
+ Irchost),
+ StateData;
+ [_, <<"312">>, _, Nick, Ircserver | _] ->
+ process_whois312(StateData, String, Nick, Ircserver),
+ StateData;
+ [_, <<"319">>, _, Nick | _] ->
+ process_whois319(StateData, String, Nick), StateData;
+ [_, <<"433">> | _] ->
+ process_nick_in_use(StateData, String);
+ % CODEPAGE isn't standard, so don't complain if it's not there.
+ [_, <<"421">>, _, <<"CODEPAGE">> | _] -> StateData;
+ [_, <<$4, _, _>> | _] ->
+ process_num_error(StateData, String);
+ [_, <<$5, _, _>> | _] ->
+ process_num_error(StateData, String);
+ [From, <<"PRIVMSG">>, <<$#, Chan/binary>> | _] ->
+ process_chanprivmsg(StateData, Chan, From, String),
+ StateData;
+ [From, <<"NOTICE">>, <<$#, Chan/binary>> | _] ->
+ process_channotice(StateData, Chan, From, String),
+ StateData;
+ [From, <<"PRIVMSG">>, Nick, <<":\001VERSION\001">>
+ | _] ->
+ process_version(StateData, Nick, From), StateData;
+ [From, <<"PRIVMSG">>, Nick, <<":\001USERINFO\001">>
+ | _] ->
+ process_userinfo(StateData, Nick, From), StateData;
+ [From, <<"PRIVMSG">>, Nick | _] ->
+ process_privmsg(StateData, Nick, From, String),
+ StateData;
+ [From, <<"NOTICE">>, Nick | _] ->
+ process_notice(StateData, Nick, From, String),
+ StateData;
+ [From, <<"TOPIC">>, <<$#, Chan/binary>> | _] ->
+ process_topic(StateData, Chan, From, String),
+ StateData;
+ [From, <<"PART">>, <<$#, Chan/binary>> | _] ->
+ process_part(StateData, Chan, From, String);
+ [From, <<"QUIT">> | _] ->
+ process_quit(StateData, From, String);
+ [From, <<"JOIN">>, Chan | _] ->
+ process_join(StateData, Chan, From, String);
+ [From, <<"MODE">>, <<$#, Chan/binary>>, <<"+o">>, Nick
+ | _] ->
+ process_mode_o(StateData, Chan, From, Nick,
+ <<"admin">>, <<"moderator">>),
+ StateData;
+ [From, <<"MODE">>, <<$#, Chan/binary>>, <<"-o">>, Nick
+ | _] ->
+ process_mode_o(StateData, Chan, From, Nick,
+ <<"member">>, <<"participant">>),
+ StateData;
+ [From, <<"KICK">>, <<$#, Chan/binary>>, Nick | _] ->
+ process_kick(StateData, Chan, From, Nick, String),
+ StateData;
+ [From, <<"NICK">>, Nick | _] ->
+ process_nick(StateData, From, Nick);
+ _ ->
+ ?DEBUG("unknown irc command '~s'~n", [String]),
+ StateData
+ end,
+ NewStateData1 = case StateData#state.outbuf of
+ <<"">> -> NewStateData;
+ Data ->
+ send_text(NewStateData, Data),
+ NewStateData#state{outbuf = <<"">>}
+ end,
{next_state, stream_established, NewStateData1};
-
-handle_info({ircstring, [$E, $R, $R, $O, $R | _] = String},
+handle_info({ircstring,
+ <<$E, $R, $R, $O, $R, _/binary>> = String},
StateName, StateData) ->
process_error(StateData, String),
{next_state, StateName, StateData};
-
-
-handle_info({ircstring, String}, StateName, StateData) ->
+handle_info({ircstring, String}, StateName,
+ StateData) ->
?DEBUG("unknown irc command '~s'~n", [String]),
{next_state, StateName, StateData};
-
-
handle_info({send_text, Text}, StateName, StateData) ->
send_text(StateData, Text),
{next_state, StateName, StateData};
-handle_info({tcp, _Socket, Data}, StateName, StateData) ->
- Buf = StateData#state.inbuf ++ binary_to_list(Data),
- Strings = ejabberd_regexp:split([C || C <- Buf, C /= $\r], "\n"),
+handle_info({tcp, _Socket, Data}, StateName,
+ StateData) ->
+ Buf = <<(StateData#state.inbuf)/binary, Data/binary>>,
+ Strings = ejabberd_regexp:split(<< <<C>>
+ || <<C>> <= Buf, C /= $\r >>,
+ <<"\n">>),
?DEBUG("strings=~p~n", [Strings]),
- NewBuf = process_lines(StateData#state.encoding, Strings),
- {next_state, StateName, StateData#state{inbuf = NewBuf}};
-handle_info({tcp_closed, _Socket}, StateName, StateData) ->
+ NewBuf = process_lines(StateData#state.encoding,
+ Strings),
+ {next_state, StateName,
+ StateData#state{inbuf = NewBuf}};
+handle_info({tcp_closed, _Socket}, StateName,
+ StateData) ->
gen_fsm:send_event(self(), closed),
{next_state, StateName, StateData};
-handle_info({tcp_error, _Socket, _Reason}, StateName, StateData) ->
+handle_info({tcp_error, _Socket, _Reason}, StateName,
+ StateData) ->
gen_fsm:send_event(self(), closed),
{next_state, StateName, StateData}.
@@ -673,67 +687,72 @@ handle_info({tcp_error, _Socket, _Reason}, StateName, StateData) ->
%% Returns: any
%%----------------------------------------------------------------------
terminate(_Reason, _StateName, FullStateData) ->
- % Extract error message if there was one.
{Error, StateData} = case FullStateData of
- {error, SError, SStateData} ->
- {SError, SStateData};
- _ ->
- {{xmlelement, "error", [{"code", "502"}],
- [{xmlcdata, "Server Connect Failed"}]},
- FullStateData}
- end,
+ {error, SError, SStateData} -> {SError, SStateData};
+ _ ->
+ {#xmlel{name = <<"error">>,
+ attrs = [{<<"code">>, <<"502">>}],
+ children =
+ [{xmlcdata,
+ <<"Server Connect Failed">>}]},
+ FullStateData}
+ end,
(FullStateData#state.mod):closed_connection(StateData#state.host,
- StateData#state.user,
- StateData#state.server),
- bounce_messages("Server Connect Failed"),
- lists:foreach(
- fun(Chan) ->
- Stanza = {xmlelement, "presence", [{"type", "error"}],
- [Error]},
- send_stanza(Chan, StateData, Stanza)
- end, dict:fetch_keys(StateData#state.channels)),
+ StateData#state.user,
+ StateData#state.server),
+ bounce_messages(<<"Server Connect Failed">>),
+ lists:foreach(fun (Chan) ->
+ Stanza = #xmlel{name = <<"presence">>,
+ attrs = [{<<"type">>, <<"error">>}],
+ children = [Error]},
+ send_stanza(Chan, StateData, Stanza)
+ end,
+ dict:fetch_keys(StateData#state.channels)),
case StateData#state.socket of
- undefined ->
- ok;
- Socket ->
- gen_tcp:close(Socket)
+ undefined -> ok;
+ Socket -> gen_tcp:close(Socket)
end,
ok.
send_stanza(Chan, StateData, Stanza) ->
ejabberd_router:route(
jlib:make_jid(
- lists:concat([Chan, "%", StateData#state.server]),
- StateData#state.host, StateData#state.nick),
- StateData#state.user,
- Stanza).
+ iolist_to_binary([Chan,
+ <<"%">>,
+ StateData#state.server]),
+ StateData#state.host,
+ StateData#state.nick),
+ StateData#state.user, Stanza).
send_stanza_unavailable(Chan, StateData) ->
- Affiliation = "member", % this is a simplification
- Role = "none",
- Stanza =
- {xmlelement, "presence", [{"type", "unavailable"}],
- [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}],
- [{xmlelement, "item",
- [{"affiliation", Affiliation},
- {"role", Role}],
- []},
- {xmlelement, "status",
- [{"code", "110"}],
- []}
- ]}]},
+ Affiliation = <<"member">>,
+ Role = <<"none">>,
+ Stanza = #xmlel{name = <<"presence">>,
+ attrs = [{<<"type">>, <<"unavailable">>}],
+ children =
+ [#xmlel{name = <<"x">>,
+ attrs = [{<<"xmlns">>, ?NS_MUC_USER}],
+ children =
+ [#xmlel{name = <<"item">>,
+ attrs =
+ [{<<"affiliation">>,
+ Affiliation},
+ {<<"role">>, Role}],
+ children = []},
+ #xmlel{name = <<"status">>,
+ attrs = [{<<"code">>, <<"110">>}],
+ children = []}]}]},
send_stanza(Chan, StateData, Stanza).
%%%----------------------------------------------------------------------
%%% Internal functions
%%%----------------------------------------------------------------------
-send_text(#state{socket = Socket, encoding = Encoding}, Text) ->
- CText = iconv:convert("utf-8", Encoding, lists:flatten(Text)),
- %?DEBUG("IRC OUTu: ~s~nIRC OUTk: ~s~n", [Text, CText]),
+send_text(#state{socket = Socket, encoding = Encoding},
+ Text) ->
+ CText = iconv:convert(<<"utf-8">>, Encoding, iolist_to_binary(Text)),
gen_tcp:send(Socket, CText).
-
%send_queue(Socket, Q) ->
% case queue:out(Q) of
% {{value, El}, Q1} ->
@@ -745,35 +764,32 @@ send_text(#state{socket = Socket, encoding = Encoding}, Text) ->
bounce_messages(Reason) ->
receive
- {send_element, El} ->
- {xmlelement, _Name, Attrs, _SubTags} = El,
- case xml:get_attr_s("type", Attrs) of
- "error" ->
- ok;
- _ ->
- Err = jlib:make_error_reply(El,
- "502", Reason),
- From = jlib:string_to_jid(xml:get_attr_s("from", Attrs)),
- To = jlib:string_to_jid(xml:get_attr_s("to", Attrs)),
- ejabberd_router:route(To, From, Err)
- end,
- bounce_messages(Reason)
- after 0 ->
- ok
+ {send_element, El} ->
+ #xmlel{attrs = Attrs} = El,
+ case xml:get_attr_s(<<"type">>, Attrs) of
+ <<"error">> -> ok;
+ _ ->
+ Err = jlib:make_error_reply(El, <<"502">>, Reason),
+ From = jlib:string_to_jid(xml:get_attr_s(<<"from">>,
+ Attrs)),
+ To = jlib:string_to_jid(xml:get_attr_s(<<"to">>,
+ Attrs)),
+ ejabberd_router:route(To, From, Err)
+ end,
+ bounce_messages(Reason)
+ after 0 -> ok
end.
-
route_chan(Pid, Channel, Resource, Packet) ->
Pid ! {route_chan, Channel, Resource, Packet}.
route_nick(Pid, Nick, Packet) ->
Pid ! {route_nick, Nick, Packet}.
-
-process_lines(_Encoding, [S]) ->
- S;
+process_lines(_Encoding, [S]) -> S;
process_lines(Encoding, [S | Ss]) ->
- self() ! {ircstring, iconv:convert(Encoding, "utf-8", S)},
+ self() !
+ {ircstring, iconv:convert(Encoding, <<"utf-8">>, S)},
process_lines(Encoding, Ss).
process_channel_list(StateData, Items) ->
@@ -781,587 +797,785 @@ process_channel_list(StateData, Items) ->
process_channel_list_find_chan(StateData, []) ->
StateData;
-process_channel_list_find_chan(StateData, [[$# | Chan] | Items]) ->
+process_channel_list_find_chan(StateData,
+ [<<$#, Chan/binary>> | Items]) ->
process_channel_list_users(StateData, Chan, Items);
-process_channel_list_find_chan(StateData, [_ | Items]) ->
+process_channel_list_find_chan(StateData,
+ [_ | Items]) ->
process_channel_list_find_chan(StateData, Items).
process_channel_list_users(StateData, _Chan, []) ->
StateData;
-process_channel_list_users(StateData, Chan, [User | Items]) ->
- NewStateData = process_channel_list_user(StateData, Chan, User),
+process_channel_list_users(StateData, Chan,
+ [User | Items]) ->
+ NewStateData = process_channel_list_user(StateData,
+ Chan, User),
process_channel_list_users(NewStateData, Chan, Items).
process_channel_list_user(StateData, Chan, User) ->
User1 = case User of
- [$: | U1] -> U1;
- _ -> User
+ <<$:, U1/binary>> -> U1;
+ _ -> User
end,
- {User2, Affiliation, Role} =
- case User1 of
- [$@ | U2] -> {U2, "admin", "moderator"};
- [$+ | U2] -> {U2, "member", "participant"};
- [$\% | U2] -> {U2, "admin", "moderator"};
- [$& | U2] -> {U2, "admin", "moderator"};
- [$~ | U2] -> {U2, "admin", "moderator"};
- _ -> {User1, "member", "participant"}
- end,
- ejabberd_router:route(
- jlib:make_jid(lists:concat([Chan, "%", StateData#state.server]),
- StateData#state.host, User2),
- StateData#state.user,
- {xmlelement, "presence", [],
- [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}],
- [{xmlelement, "item",
- [{"affiliation", Affiliation},
- {"role", Role}],
- []}]}]}),
+ {User2, Affiliation, Role} = case User1 of
+ <<$@, U2/binary>> ->
+ {U2, <<"admin">>, <<"moderator">>};
+ <<$+, U2/binary>> ->
+ {U2, <<"member">>, <<"participant">>};
+ <<$%, U2/binary>> ->
+ {U2, <<"admin">>, <<"moderator">>};
+ <<$&, U2/binary>> ->
+ {U2, <<"admin">>, <<"moderator">>};
+ <<$~, U2/binary>> ->
+ {U2, <<"admin">>, <<"moderator">>};
+ _ -> {User1, <<"member">>, <<"participant">>}
+ end,
+ ejabberd_router:route(jlib:make_jid(iolist_to_binary([Chan,
+ <<"%">>,
+ StateData#state.server]),
+ StateData#state.host, User2),
+ StateData#state.user,
+ #xmlel{name = <<"presence">>, attrs = [],
+ children =
+ [#xmlel{name = <<"x">>,
+ attrs =
+ [{<<"xmlns">>, ?NS_MUC_USER}],
+ children =
+ [#xmlel{name = <<"item">>,
+ attrs =
+ [{<<"affiliation">>,
+ Affiliation},
+ {<<"role">>,
+ Role}],
+ children = []}]}]}),
case catch dict:update(Chan,
- fun(Ps) ->
- ?SETS:add_element(User2, Ps)
- end, StateData#state.channels) of
- {'EXIT', _} ->
- StateData;
- NS ->
- StateData#state{channels = NS}
+ fun (Ps) -> (?SETS):add_element(User2, Ps) end,
+ StateData#state.channels)
+ of
+ {'EXIT', _} -> StateData;
+ NS -> StateData#state{channels = NS}
end.
-
process_channel_topic(StateData, Chan, String) ->
- Msg = ejabberd_regexp:replace(String, ".*332[^:]*:", ""),
+ Msg = ejabberd_regexp:replace(String, <<".*332[^:]*:">>,
+ <<"">>),
Msg1 = filter_message(Msg),
- ejabberd_router:route(
- jlib:make_jid(
- lists:concat([Chan, "%", StateData#state.server]),
- StateData#state.host, ""),
- StateData#state.user,
- {xmlelement, "message", [{"type", "groupchat"}],
- [{xmlelement, "subject", [], [{xmlcdata, Msg1}]},
- {xmlelement, "body", [], [{xmlcdata, "Topic for #" ++ Chan ++ ": " ++ Msg1}]}
- ]}).
+ ejabberd_router:route(jlib:make_jid(iolist_to_binary([Chan,
+ <<"%">>,
+ StateData#state.server]),
+ StateData#state.host, <<"">>),
+ StateData#state.user,
+ #xmlel{name = <<"message">>,
+ attrs = [{<<"type">>, <<"groupchat">>}],
+ children =
+ [#xmlel{name = <<"subject">>, attrs = [],
+ children = [{xmlcdata, Msg1}]},
+ #xmlel{name = <<"body">>, attrs = [],
+ children =
+ [{xmlcdata,
+ <<"Topic for #", Chan/binary,
+ ": ", Msg1/binary>>}]}]}).
process_channel_topic_who(StateData, Chan, String) ->
- Words = string:tokens(String, " "),
+ Words = str:tokens(String, <<" ">>),
Msg1 = case Words of
- [_, "333", _, _Chan, Whoset , Timeset] ->
- {Unixtimeset, _Rest} = string:to_integer(Timeset),
- "Topic for #" ++ Chan ++ " set by " ++ Whoset ++
- " at " ++ unixtime2string(Unixtimeset);
- [_, "333", _, _Chan, Whoset | _] ->
- "Topic for #" ++ Chan ++ " set by " ++ Whoset;
- _ ->
- String
+ [_, <<"333">>, _, _Chan, Whoset, Timeset] ->
+ {Unixtimeset, _Rest} = str:to_integer(Timeset),
+ <<"Topic for #", Chan/binary, " set by ", Whoset/binary,
+ " at ", (unixtime2string(Unixtimeset))/binary>>;
+ [_, <<"333">>, _, _Chan, Whoset | _] ->
+ <<"Topic for #", Chan/binary, " set by ",
+ Whoset/binary>>;
+ _ -> String
end,
Msg2 = filter_message(Msg1),
- ejabberd_router:route(
- jlib:make_jid(lists:concat([Chan, "%", StateData#state.server]),
- StateData#state.host, ""),
- StateData#state.user,
- {xmlelement, "message", [{"type", "groupchat"}],
- [{xmlelement, "body", [], [{xmlcdata, Msg2}]}]}).
-
+ ejabberd_router:route(jlib:make_jid(iolist_to_binary([Chan,
+ <<"%">>,
+ StateData#state.server]),
+ StateData#state.host, <<"">>),
+ StateData#state.user,
+ #xmlel{name = <<"message">>,
+ attrs = [{<<"type">>, <<"groupchat">>}],
+ children =
+ [#xmlel{name = <<"body">>, attrs = [],
+ children = [{xmlcdata, Msg2}]}]}).
error_nick_in_use(_StateData, String) ->
- Msg = ejabberd_regexp:replace(String, ".*433 +[^ ]* +", ""),
+ Msg = ejabberd_regexp:replace(String,
+ <<".*433 +[^ ]* +">>, <<"">>),
Msg1 = filter_message(Msg),
- {xmlelement, "error", [{"code", "409"}, {"type", "cancel"}],
- [{xmlelement, "conflict", [{"xmlns", ?NS_STANZAS}], []},
- {xmlelement, "text", [{"xmlns", ?NS_STANZAS}],
- [{xmlcdata, Msg1}]}]}.
+ #xmlel{name = <<"error">>,
+ attrs =
+ [{<<"code">>, <<"409">>}, {<<"type">>, <<"cancel">>}],
+ children =
+ [#xmlel{name = <<"conflict">>,
+ attrs = [{<<"xmlns">>, ?NS_STANZAS}], children = []},
+ #xmlel{name = <<"text">>,
+ attrs = [{<<"xmlns">>, ?NS_STANZAS}],
+ children = [{xmlcdata, Msg1}]}]}.
process_nick_in_use(StateData, String) ->
- % We can't use the jlib macro because we don't know the language of the
- % message.
Error = error_nick_in_use(StateData, String),
case StateData#state.nickchannel of
- undefined ->
- % Shouldn't happen with a well behaved server
- StateData;
- Chan ->
- ejabberd_router:route(
- jlib:make_jid(
- lists:concat([Chan, "%", StateData#state.server]),
- StateData#state.host, StateData#state.nick),
- StateData#state.user,
- {xmlelement, "presence", [{"type", "error"}], [Error]}),
- StateData#state{nickchannel = undefined}
+ undefined ->
+ % Shouldn't happen with a well behaved server
+ StateData;
+ Chan ->
+ ejabberd_router:route(jlib:make_jid(iolist_to_binary([Chan,
+ <<"%">>,
+ StateData#state.server]),
+ StateData#state.host,
+ StateData#state.nick),
+ StateData#state.user,
+ #xmlel{name = <<"presence">>,
+ attrs = [{<<"type">>, <<"error">>}],
+ children = [Error]}),
+ StateData#state{nickchannel = undefined}
end.
process_num_error(StateData, String) ->
- Error = error_unknown_num(StateData, String, "continue"),
- lists:foreach(
- fun(Chan) ->
- ejabberd_router:route(
- jlib:make_jid(
- lists:concat([Chan, "%", StateData#state.server]),
- StateData#state.host, StateData#state.nick),
- StateData#state.user,
- {xmlelement, "message", [{"type", "error"}],
- [Error]})
- end, dict:fetch_keys(StateData#state.channels)),
- StateData.
+ Error = error_unknown_num(StateData, String,
+ <<"continue">>),
+ lists:foreach(fun (Chan) ->
+ ejabberd_router:route(
+ jlib:make_jid(
+ iolist_to_binary(
+ [Chan,
+ <<"%">>,
+ StateData#state.server]),
+ StateData#state.host,
+ StateData#state.nick),
+ StateData#state.user,
+ #xmlel{name = <<"message">>,
+ attrs =
+ [{<<"type">>,
+ <<"error">>}],
+ children = [Error]})
+ end,
+ dict:fetch_keys(StateData#state.channels)),
+ StateData.
process_endofwhois(StateData, _String, Nick) ->
- ejabberd_router:route(
- jlib:make_jid(lists:concat([Nick, "!", StateData#state.server]),
- StateData#state.host, ""),
- StateData#state.user,
- {xmlelement, "message", [{"type", "chat"}],
- [{xmlelement, "body", [], [{xmlcdata, "End of WHOIS"}]}]}).
-
-process_whois311(StateData, String, Nick, Ident, Irchost) ->
- Fullname = ejabberd_regexp:replace(String, ".*311[^:]*:", ""),
- ejabberd_router:route(
- jlib:make_jid(lists:concat([Nick, "!", StateData#state.server]),
- StateData#state.host, ""),
- StateData#state.user,
- {xmlelement, "message", [{"type", "chat"}],
- [{xmlelement, "body", [],
- [{xmlcdata, lists:concat(
- ["WHOIS: ", Nick, " is ",
- Ident, "@" , Irchost, " : " , Fullname])}]}]}).
+ ejabberd_router:route(jlib:make_jid(iolist_to_binary([Nick,
+ <<"!">>,
+ StateData#state.server]),
+ StateData#state.host, <<"">>),
+ StateData#state.user,
+ #xmlel{name = <<"message">>,
+ attrs = [{<<"type">>, <<"chat">>}],
+ children =
+ [#xmlel{name = <<"body">>, attrs = [],
+ children =
+ [{xmlcdata,
+ <<"End of WHOIS">>}]}]}).
+
+process_whois311(StateData, String, Nick, Ident,
+ Irchost) ->
+ Fullname = ejabberd_regexp:replace(String,
+ <<".*311[^:]*:">>, <<"">>),
+ ejabberd_router:route(jlib:make_jid(iolist_to_binary([Nick,
+ <<"!">>,
+ StateData#state.server]),
+ StateData#state.host, <<"">>),
+ StateData#state.user,
+ #xmlel{name = <<"message">>,
+ attrs = [{<<"type">>, <<"chat">>}],
+ children =
+ [#xmlel{name = <<"body">>, attrs = [],
+ children =
+ [{xmlcdata,
+ iolist_to_binary(
+ [<<"WHOIS: ">>,
+ Nick,
+ <<" is ">>,
+ Ident,
+ <<"@">>,
+ Irchost,
+ <<" : ">>,
+ Fullname])}]}]}).
process_whois312(StateData, String, Nick, Ircserver) ->
- Ircserverdesc = ejabberd_regexp:replace(String, ".*312[^:]*:", ""),
- ejabberd_router:route(
- jlib:make_jid(lists:concat([Nick, "!", StateData#state.server]),
- StateData#state.host, ""),
- StateData#state.user,
- {xmlelement, "message", [{"type", "chat"}],
- [{xmlelement, "body", [],
- [{xmlcdata, lists:concat(["WHOIS: ", Nick, " use ",
- Ircserver, " : ", Ircserverdesc])}]}]}).
+ Ircserverdesc = ejabberd_regexp:replace(String,
+ <<".*312[^:]*:">>, <<"">>),
+ ejabberd_router:route(jlib:make_jid(iolist_to_binary([Nick,
+ <<"!">>,
+ StateData#state.server]),
+ StateData#state.host, <<"">>),
+ StateData#state.user,
+ #xmlel{name = <<"message">>,
+ attrs = [{<<"type">>, <<"chat">>}],
+ children =
+ [#xmlel{name = <<"body">>, attrs = [],
+ children =
+ [{xmlcdata,
+ iolist_to_binary(
+ [<<"WHOIS: ">>,
+ Nick,
+ <<" use ">>,
+ Ircserver,
+ <<" : ">>,
+ Ircserverdesc])}]}]}).
process_whois319(StateData, String, Nick) ->
- Chanlist = ejabberd_regexp:replace(String, ".*319[^:]*:", ""),
- ejabberd_router:route(
- jlib:make_jid(lists:concat([Nick, "!", StateData#state.server]),
- StateData#state.host, ""),
- StateData#state.user,
- {xmlelement, "message", [{"type", "chat"}],
- [{xmlelement, "body", [],
- [{xmlcdata, lists:concat(["WHOIS: ", Nick, " is on ",
- Chanlist])}]}]}).
-
-
+ Chanlist = ejabberd_regexp:replace(String,
+ <<".*319[^:]*:">>, <<"">>),
+ ejabberd_router:route(jlib:make_jid(iolist_to_binary(
+ [Nick,
+ <<"!">>,
+ StateData#state.server]),
+ StateData#state.host, <<"">>),
+ StateData#state.user,
+ #xmlel{name = <<"message">>,
+ attrs = [{<<"type">>, <<"chat">>}],
+ children =
+ [#xmlel{name = <<"body">>, attrs = [],
+ children =
+ [{xmlcdata,
+ iolist_to_binary(
+ [<<"WHOIS: ">>,
+ Nick,
+ <<" is on ">>,
+ Chanlist])}]}]}).
process_chanprivmsg(StateData, Chan, From, String) ->
- [FromUser | _] = string:tokens(From, "!"),
- Msg = ejabberd_regexp:replace(String, ".*PRIVMSG[^:]*:", ""),
+ [FromUser | _] = str:tokens(From, <<"!">>),
+ Msg = ejabberd_regexp:replace(String,
+ <<".*PRIVMSG[^:]*:">>, <<"">>),
Msg1 = case Msg of
- [1, $A, $C, $T, $I, $O, $N, $ | Rest] ->
- "/me " ++ Rest;
- _ ->
- Msg
+ <<1, $A, $C, $T, $I, $O, $N, $\s, Rest/binary>> ->
+ <<"/me ", Rest/binary>>;
+ _ -> Msg
end,
Msg2 = filter_message(Msg1),
- ejabberd_router:route(
- jlib:make_jid(lists:concat([Chan, "%", StateData#state.server]),
- StateData#state.host, FromUser),
- StateData#state.user,
- {xmlelement, "message", [{"type", "groupchat"}],
- [{xmlelement, "body", [], [{xmlcdata, Msg2}]}]}).
-
-
+ ejabberd_router:route(jlib:make_jid(iolist_to_binary(
+ [Chan,
+ <<"%">>,
+ StateData#state.server]),
+ StateData#state.host, FromUser),
+ StateData#state.user,
+ #xmlel{name = <<"message">>,
+ attrs = [{<<"type">>, <<"groupchat">>}],
+ children =
+ [#xmlel{name = <<"body">>, attrs = [],
+ children = [{xmlcdata, Msg2}]}]}).
process_channotice(StateData, Chan, From, String) ->
- [FromUser | _] = string:tokens(From, "!"),
- Msg = ejabberd_regexp:replace(String, ".*NOTICE[^:]*:", ""),
+ [FromUser | _] = str:tokens(From, <<"!">>),
+ Msg = ejabberd_regexp:replace(String,
+ <<".*NOTICE[^:]*:">>, <<"">>),
Msg1 = case Msg of
- [1, $A, $C, $T, $I, $O, $N, $ | Rest] ->
- "/me " ++ Rest;
- _ ->
- "/me NOTICE: " ++ Msg
+ <<1, $A, $C, $T, $I, $O, $N, $\s, Rest/binary>> ->
+ <<"/me ", Rest/binary>>;
+ _ -> <<"/me NOTICE: ", Msg/binary>>
end,
Msg2 = filter_message(Msg1),
- ejabberd_router:route(
- jlib:make_jid(lists:concat([Chan, "%", StateData#state.server]),
- StateData#state.host, FromUser),
- StateData#state.user,
- {xmlelement, "message", [{"type", "groupchat"}],
- [{xmlelement, "body", [], [{xmlcdata, Msg2}]}]}).
-
-
-
+ ejabberd_router:route(jlib:make_jid(iolist_to_binary(
+ [Chan,
+ <<"%">>,
+ StateData#state.server]),
+ StateData#state.host, FromUser),
+ StateData#state.user,
+ #xmlel{name = <<"message">>,
+ attrs = [{<<"type">>, <<"groupchat">>}],
+ children =
+ [#xmlel{name = <<"body">>, attrs = [],
+ children = [{xmlcdata, Msg2}]}]}).
process_privmsg(StateData, _Nick, From, String) ->
- [FromUser | _] = string:tokens(From, "!"),
- Msg = ejabberd_regexp:replace(String, ".*PRIVMSG[^:]*:", ""),
+ [FromUser | _] = str:tokens(From, <<"!">>),
+ Msg = ejabberd_regexp:replace(String,
+ <<".*PRIVMSG[^:]*:">>, <<"">>),
Msg1 = case Msg of
- [1, $A, $C, $T, $I, $O, $N, $ | Rest] ->
- "/me " ++ Rest;
- _ ->
- Msg
+ <<1, $A, $C, $T, $I, $O, $N, $\s, Rest/binary>> ->
+ <<"/me ", Rest/binary>>;
+ _ -> Msg
end,
Msg2 = filter_message(Msg1),
- ejabberd_router:route(
- jlib:make_jid(lists:concat([FromUser, "!", StateData#state.server]),
- StateData#state.host, ""),
- StateData#state.user,
- {xmlelement, "message", [{"type", "chat"}],
- [{xmlelement, "body", [], [{xmlcdata, Msg2}]}]}).
-
+ ejabberd_router:route(jlib:make_jid(iolist_to_binary(
+ [FromUser,
+ <<"!">>,
+ StateData#state.server]),
+ StateData#state.host, <<"">>),
+ StateData#state.user,
+ #xmlel{name = <<"message">>,
+ attrs = [{<<"type">>, <<"chat">>}],
+ children =
+ [#xmlel{name = <<"body">>, attrs = [],
+ children = [{xmlcdata, Msg2}]}]}).
process_notice(StateData, _Nick, From, String) ->
- [FromUser | _] = string:tokens(From, "!"),
- Msg = ejabberd_regexp:replace(String, ".*NOTICE[^:]*:", ""),
+ [FromUser | _] = str:tokens(From, <<"!">>),
+ Msg = ejabberd_regexp:replace(String,
+ <<".*NOTICE[^:]*:">>, <<"">>),
Msg1 = case Msg of
- [1, $A, $C, $T, $I, $O, $N, $ | Rest] ->
- "/me " ++ Rest;
- _ ->
- "/me NOTICE: " ++ Msg
+ <<1, $A, $C, $T, $I, $O, $N, $\s, Rest/binary>> ->
+ <<"/me ", Rest/binary>>;
+ _ -> <<"/me NOTICE: ", Msg/binary>>
end,
Msg2 = filter_message(Msg1),
- ejabberd_router:route(
- jlib:make_jid(lists:concat([FromUser, "!", StateData#state.server]),
- StateData#state.host, ""),
- StateData#state.user,
- {xmlelement, "message", [{"type", "chat"}],
- [{xmlelement, "body", [], [{xmlcdata, Msg2}]}]}).
-
+ ejabberd_router:route(jlib:make_jid(iolist_to_binary(
+ [FromUser,
+ <<"!">>,
+ StateData#state.server]),
+ StateData#state.host, <<"">>),
+ StateData#state.user,
+ #xmlel{name = <<"message">>,
+ attrs = [{<<"type">>, <<"chat">>}],
+ children =
+ [#xmlel{name = <<"body">>, attrs = [],
+ children = [{xmlcdata, Msg2}]}]}).
process_version(StateData, _Nick, From) ->
- [FromUser | _] = string:tokens(From, "!"),
- send_text(
- StateData,
- io_lib:format("NOTICE ~s :\001VERSION "
- "ejabberd IRC transport ~s (c) Alexey Shchepin"
- "\001\r\n",
- [FromUser, ?VERSION]) ++
- io_lib:format("NOTICE ~s :\001VERSION "
- "http://ejabberd.jabberstudio.org/"
- "\001\r\n",
- [FromUser])).
-
+ [FromUser | _] = str:tokens(From, <<"!">>),
+ send_text(StateData,
+ io_lib:format("NOTICE ~s :\001VERSION ejabberd IRC "
+ "transport ~s (c) Alexey Shchepin\001\r\n",
+ [FromUser, ?VERSION])
+ ++
+ io_lib:format("NOTICE ~s :\001VERSION http://ejabberd.jabber"
+ "studio.org/\001\r\n",
+ [FromUser])).
process_userinfo(StateData, _Nick, From) ->
- [FromUser | _] = string:tokens(From, "!"),
- send_text(
- StateData,
- io_lib:format("NOTICE ~s :\001USERINFO "
- "xmpp:~s"
- "\001\r\n",
- [FromUser,
- jlib:jid_to_string(StateData#state.user)])).
-
+ [FromUser | _] = str:tokens(From, <<"!">>),
+ send_text(StateData,
+ io_lib:format("NOTICE ~s :\001USERINFO xmpp:~s\001\r\n",
+ [FromUser,
+ jlib:jid_to_string(StateData#state.user)])).
process_topic(StateData, Chan, From, String) ->
- [FromUser | _] = string:tokens(From, "!"),
- Msg = ejabberd_regexp:replace(String, ".*TOPIC[^:]*:", ""),
+ [FromUser | _] = str:tokens(From, <<"!">>),
+ Msg = ejabberd_regexp:replace(String,
+ <<".*TOPIC[^:]*:">>, <<"">>),
Msg1 = filter_message(Msg),
- ejabberd_router:route(
- jlib:make_jid(lists:concat([Chan, "%", StateData#state.server]),
- StateData#state.host, FromUser),
- StateData#state.user,
- {xmlelement, "message", [{"type", "groupchat"}],
- [{xmlelement, "subject", [], [{xmlcdata, Msg1}]},
- {xmlelement, "body", [],
- [{xmlcdata, "/me has changed the subject to: " ++
- Msg1}]}]}).
+ ejabberd_router:route(jlib:make_jid(iolist_to_binary(
+ [Chan,
+ <<"%">>,
+ StateData#state.server]),
+ StateData#state.host, FromUser),
+ StateData#state.user,
+ #xmlel{name = <<"message">>,
+ attrs = [{<<"type">>, <<"groupchat">>}],
+ children =
+ [#xmlel{name = <<"subject">>, attrs = [],
+ children = [{xmlcdata, Msg1}]},
+ #xmlel{name = <<"body">>, attrs = [],
+ children =
+ [{xmlcdata,
+ <<"/me has changed the subject to: ",
+ Msg1/binary>>}]}]}).
process_part(StateData, Chan, From, String) ->
- [FromUser | FromIdent] = string:tokens(From, "!"),
- Msg = ejabberd_regexp:replace(String, ".*PART[^:]*:", ""),
+ [FromUser | FromIdent] = str:tokens(From, <<"!">>),
+ Msg = ejabberd_regexp:replace(String,
+ <<".*PART[^:]*:">>, <<"">>),
Msg1 = filter_message(Msg),
- ejabberd_router:route(
- jlib:make_jid(lists:concat([Chan, "%", StateData#state.server]),
- StateData#state.host, FromUser),
- StateData#state.user,
- {xmlelement, "presence", [{"type", "unavailable"}],
- [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}],
- [{xmlelement, "item",
- [{"affiliation", "member"},
- {"role", "none"}],
- []}]},
- {xmlelement, "status", [],
- [{xmlcdata, Msg1 ++ " (" ++ FromIdent ++ ")"}]}]
- }),
+ ejabberd_router:route(jlib:make_jid(iolist_to_binary(
+ [Chan,
+ <<"%">>,
+ StateData#state.server]),
+ StateData#state.host, FromUser),
+ StateData#state.user,
+ #xmlel{name = <<"presence">>,
+ attrs = [{<<"type">>, <<"unavailable">>}],
+ children =
+ [#xmlel{name = <<"x">>,
+ attrs =
+ [{<<"xmlns">>, ?NS_MUC_USER}],
+ children =
+ [#xmlel{name = <<"item">>,
+ attrs =
+ [{<<"affiliation">>,
+ <<"member">>},
+ {<<"role">>,
+ <<"none">>}],
+ children = []}]},
+ #xmlel{name = <<"status">>, attrs = [],
+ children =
+ [{xmlcdata,
+ list_to_binary(
+ [Msg1, " (",
+ FromIdent, ")"])}]}]}),
case catch dict:update(Chan,
- fun(Ps) ->
- remove_element(FromUser, Ps)
- end, StateData#state.channels) of
- {'EXIT', _} ->
- StateData;
- NS ->
- StateData#state{channels = NS}
+ fun (Ps) -> remove_element(FromUser, Ps) end,
+ StateData#state.channels)
+ of
+ {'EXIT', _} -> StateData;
+ NS -> StateData#state{channels = NS}
end.
-
process_quit(StateData, From, String) ->
- [FromUser | FromIdent] = string:tokens(From, "!"),
-
- Msg = ejabberd_regexp:replace(String, ".*QUIT[^:]*:", ""),
+ [FromUser | FromIdent] = str:tokens(From, <<"!">>),
+ Msg = ejabberd_regexp:replace(String,
+ <<".*QUIT[^:]*:">>, <<"">>),
Msg1 = filter_message(Msg),
- %%NewChans =
- dict:map(
- fun(Chan, Ps) ->
- case ?SETS:is_member(FromUser, Ps) of
- true ->
- ejabberd_router:route(
- jlib:make_jid(
- lists:concat([Chan, "%", StateData#state.server]),
- StateData#state.host, FromUser),
- StateData#state.user,
- {xmlelement, "presence", [{"type", "unavailable"}],
- [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}],
- [{xmlelement, "item",
- [{"affiliation", "member"},
- {"role", "none"}],
- []}]},
- {xmlelement, "status", [],
- [{xmlcdata, Msg1 ++ " (" ++ FromIdent ++ ")"}]}
- ]}),
- remove_element(FromUser, Ps);
- _ ->
- Ps
- end
- end, StateData#state.channels),
+ dict:map(fun (Chan, Ps) ->
+ case (?SETS):is_member(FromUser, Ps) of
+ true ->
+ ejabberd_router:route(jlib:make_jid(iolist_to_binary(
+ [Chan,
+ <<"%">>,
+ StateData#state.server]),
+ StateData#state.host,
+ FromUser),
+ StateData#state.user,
+ #xmlel{name = <<"presence">>,
+ attrs =
+ [{<<"type">>,
+ <<"unavailable">>}],
+ children =
+ [#xmlel{name =
+ <<"x">>,
+ attrs =
+ [{<<"xmlns">>,
+ ?NS_MUC_USER}],
+ children =
+ [#xmlel{name
+ =
+ <<"item">>,
+ attrs
+ =
+ [{<<"affiliation">>,
+ <<"member">>},
+ {<<"role">>,
+ <<"none">>}],
+ children
+ =
+ []}]},
+ #xmlel{name =
+ <<"status">>,
+ attrs = [],
+ children =
+ [{xmlcdata,
+ list_to_binary(
+ [Msg1, " (",
+ FromIdent,
+ ")"])}]}]}),
+ remove_element(FromUser, Ps);
+ _ -> Ps
+ end
+ end,
+ StateData#state.channels),
StateData.
-
process_join(StateData, Channel, From, _String) ->
- [FromUser | FromIdent] = string:tokens(From, "!"),
- Chan = lists:subtract(Channel, ":#"),
- ejabberd_router:route(
- jlib:make_jid(lists:concat([Chan, "%", StateData#state.server]),
- StateData#state.host, FromUser),
- StateData#state.user,
- {xmlelement, "presence", [],
- [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}],
- [{xmlelement, "item",
- [{"affiliation", "member"},
- {"role", "participant"}],
- []}]},
- {xmlelement, "status", [],
- [{xmlcdata, FromIdent}]}]}),
-
+ [FromUser | FromIdent] = str:tokens(From, <<"!">>),
+ [Chan | _] = binary:split(Channel, <<":#">>),
+ ejabberd_router:route(jlib:make_jid(iolist_to_binary(
+ [Chan,
+ <<"%">>,
+ StateData#state.server]),
+ StateData#state.host, FromUser),
+ StateData#state.user,
+ #xmlel{name = <<"presence">>, attrs = [],
+ children =
+ [#xmlel{name = <<"x">>,
+ attrs =
+ [{<<"xmlns">>, ?NS_MUC_USER}],
+ children =
+ [#xmlel{name = <<"item">>,
+ attrs =
+ [{<<"affiliation">>,
+ <<"member">>},
+ {<<"role">>,
+ <<"participant">>}],
+ children = []}]},
+ #xmlel{name = <<"status">>, attrs = [],
+ children =
+ [{xmlcdata,
+ list_to_binary(FromIdent)}]}]}),
case catch dict:update(Chan,
- fun(Ps) ->
- ?SETS:add_element(FromUser, Ps)
- end, StateData#state.channels) of
- {'EXIT', _} ->
- StateData;
- NS ->
- StateData#state{channels = NS}
+ fun (Ps) -> (?SETS):add_element(FromUser, Ps) end,
+ StateData#state.channels)
+ of
+ {'EXIT', _} -> StateData;
+ NS -> StateData#state{channels = NS}
end.
-
-
-process_mode_o(StateData, Chan, _From, Nick, Affiliation, Role) ->
- %Msg = lists:last(string:tokens(String, ":")),
- ejabberd_router:route(
- jlib:make_jid(lists:concat([Chan, "%", StateData#state.server]),
- StateData#state.host, Nick),
- StateData#state.user,
- {xmlelement, "presence", [],
- [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}],
- [{xmlelement, "item",
- [{"affiliation", Affiliation},
- {"role", Role}],
- []}]}]}).
+process_mode_o(StateData, Chan, _From, Nick,
+ Affiliation, Role) ->
+ ejabberd_router:route(jlib:make_jid(iolist_to_binary(
+ [Chan,
+ <<"%">>,
+ StateData#state.server]),
+ StateData#state.host, Nick),
+ StateData#state.user,
+ #xmlel{name = <<"presence">>, attrs = [],
+ children =
+ [#xmlel{name = <<"x">>,
+ attrs =
+ [{<<"xmlns">>, ?NS_MUC_USER}],
+ children =
+ [#xmlel{name = <<"item">>,
+ attrs =
+ [{<<"affiliation">>,
+ Affiliation},
+ {<<"role">>,
+ Role}],
+ children = []}]}]}).
process_kick(StateData, Chan, From, Nick, String) ->
- Msg = lists:last(string:tokens(String, ":")),
- Msg2 = Nick ++ " kicked by " ++ From ++ " (" ++ filter_message(Msg) ++ ")",
- ejabberd_router:route(
- jlib:make_jid(lists:concat([Chan, "%", StateData#state.server]),
- StateData#state.host, ""),
- StateData#state.user,
- {xmlelement, "message", [{"type", "groupchat"}],
- [{xmlelement, "body", [], [{xmlcdata, Msg2}]}]}),
- ejabberd_router:route(
- jlib:make_jid(lists:concat([Chan, "%", StateData#state.server]),
- StateData#state.host, Nick),
- StateData#state.user,
- {xmlelement, "presence", [{"type", "unavailable"}],
- [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}],
- [{xmlelement, "item",
- [{"affiliation", "none"},
- {"role", "none"}],
- []},
- {xmlelement, "status", [{"code", "307"}], []}
- ]}]}).
+ Msg = lists:last(str:tokens(String, <<":">>)),
+ Msg2 = <<Nick/binary, " kicked by ", From/binary, " (",
+ (filter_message(Msg))/binary, ")">>,
+ ejabberd_router:route(jlib:make_jid(iolist_to_binary(
+ [Chan,
+ <<"%">>,
+ StateData#state.server]),
+ StateData#state.host, <<"">>),
+ StateData#state.user,
+ #xmlel{name = <<"message">>,
+ attrs = [{<<"type">>, <<"groupchat">>}],
+ children =
+ [#xmlel{name = <<"body">>, attrs = [],
+ children = [{xmlcdata, Msg2}]}]}),
+ ejabberd_router:route(jlib:make_jid(iolist_to_binary(
+ [Chan,
+ <<"%">>,
+ StateData#state.server]),
+ StateData#state.host, Nick),
+ StateData#state.user,
+ #xmlel{name = <<"presence">>,
+ attrs = [{<<"type">>, <<"unavailable">>}],
+ children =
+ [#xmlel{name = <<"x">>,
+ attrs =
+ [{<<"xmlns">>, ?NS_MUC_USER}],
+ children =
+ [#xmlel{name = <<"item">>,
+ attrs =
+ [{<<"affiliation">>,
+ <<"none">>},
+ {<<"role">>,
+ <<"none">>}],
+ children = []},
+ #xmlel{name = <<"status">>,
+ attrs =
+ [{<<"code">>,
+ <<"307">>}],
+ children = []}]}]}).
process_nick(StateData, From, NewNick) ->
- [FromUser | _] = string:tokens(From, "!"),
- Nick = lists:subtract(NewNick, ":"),
- NewChans =
- dict:map(
- fun(Chan, Ps) ->
- case ?SETS:is_member(FromUser, Ps) of
- true ->
- ejabberd_router:route(
- jlib:make_jid(
- lists:concat([Chan, "%", StateData#state.server]),
- StateData#state.host, FromUser),
- StateData#state.user,
- {xmlelement, "presence", [{"type", "unavailable"}],
- [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}],
- [{xmlelement, "item",
- [{"affiliation", "member"},
- {"role", "participant"},
- {"nick", Nick}],
- []},
- {xmlelement, "status", [{"code", "303"}], []}
- ]}]}),
- ejabberd_router:route(
- jlib:make_jid(
- lists:concat([Chan, "%", StateData#state.server]),
- StateData#state.host, Nick),
- StateData#state.user,
- {xmlelement, "presence", [],
- [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}],
- [{xmlelement, "item",
- [{"affiliation", "member"},
- {"role", "participant"}],
- []}
- ]}]}),
- ?SETS:add_element(Nick,
- remove_element(FromUser, Ps));
- _ ->
- Ps
- end
- end, StateData#state.channels),
- if
- FromUser == StateData#state.nick ->
- StateData#state{nick = Nick,
- nickchannel = undefined,
- channels = NewChans};
- true ->
- StateData#state{channels = NewChans}
+ [FromUser | _] = str:tokens(From, <<"!">>),
+ [Nick | _] = binary:split(NewNick, <<":">>),
+ NewChans = dict:map(fun (Chan, Ps) ->
+ case (?SETS):is_member(FromUser, Ps) of
+ true ->
+ ejabberd_router:route(jlib:make_jid(
+ iolist_to_binary(
+ [Chan,
+ <<"%">>,
+ StateData#state.server]),
+ StateData#state.host,
+ FromUser),
+ StateData#state.user,
+ #xmlel{name =
+ <<"presence">>,
+ attrs =
+ [{<<"type">>,
+ <<"unavailable">>}],
+ children =
+ [#xmlel{name
+ =
+ <<"x">>,
+ attrs
+ =
+ [{<<"xmlns">>,
+ ?NS_MUC_USER}],
+ children
+ =
+ [#xmlel{name
+ =
+ <<"item">>,
+ attrs
+ =
+ [{<<"affiliation">>,
+ <<"member">>},
+ {<<"role">>,
+ <<"participant">>},
+ {<<"nick">>,
+ Nick}],
+ children
+ =
+ []},
+ #xmlel{name
+ =
+ <<"status">>,
+ attrs
+ =
+ [{<<"code">>,
+ <<"303">>}],
+ children
+ =
+ []}]}]}),
+ ejabberd_router:route(jlib:make_jid(
+ iolist_to_binary(
+ [Chan,
+ <<"%">>,
+ StateData#state.server]),
+ StateData#state.host,
+ Nick),
+ StateData#state.user,
+ #xmlel{name =
+ <<"presence">>,
+ attrs = [],
+ children =
+ [#xmlel{name
+ =
+ <<"x">>,
+ attrs
+ =
+ [{<<"xmlns">>,
+ ?NS_MUC_USER}],
+ children
+ =
+ [#xmlel{name
+ =
+ <<"item">>,
+ attrs
+ =
+ [{<<"affiliation">>,
+ <<"member">>},
+ {<<"role">>,
+ <<"participant">>}],
+ children
+ =
+ []}]}]}),
+ (?SETS):add_element(Nick,
+ remove_element(FromUser,
+ Ps));
+ _ -> Ps
+ end
+ end,
+ StateData#state.channels),
+ if FromUser == StateData#state.nick ->
+ StateData#state{nick = Nick, nickchannel = undefined,
+ channels = NewChans};
+ true -> StateData#state{channels = NewChans}
end.
-
process_error(StateData, String) ->
- lists:foreach(
- fun(Chan) ->
- ejabberd_router:route(
- jlib:make_jid(
- lists:concat([Chan, "%", StateData#state.server]),
- StateData#state.host, StateData#state.nick),
- StateData#state.user,
- {xmlelement, "presence", [{"type", "error"}],
- [{xmlelement, "error", [{"code", "502"}],
- [{xmlcdata, String}]}]})
- end, dict:fetch_keys(StateData#state.channels)).
+ lists:foreach(fun (Chan) ->
+ ejabberd_router:route(jlib:make_jid(
+ iolist_to_binary(
+ [Chan,
+ <<"%">>,
+ StateData#state.server]),
+ StateData#state.host,
+ StateData#state.nick),
+ StateData#state.user,
+ #xmlel{name = <<"presence">>,
+ attrs =
+ [{<<"type">>,
+ <<"error">>}],
+ children =
+ [#xmlel{name =
+ <<"error">>,
+ attrs =
+ [{<<"code">>,
+ <<"502">>}],
+ children =
+ [{xmlcdata,
+ String}]}]})
+ end,
+ dict:fetch_keys(StateData#state.channels)).
error_unknown_num(_StateData, String, Type) ->
- Msg = ejabberd_regexp:replace(String, ".*[45][0-9][0-9] +[^ ]* +", ""),
+ Msg = ejabberd_regexp:replace(String,
+ <<".*[45][0-9][0-9] +[^ ]* +">>, <<"">>),
Msg1 = filter_message(Msg),
- {xmlelement, "error", [{"code", "500"}, {"type", Type}],
- [{xmlelement, "undefined-condition", [{"xmlns", ?NS_STANZAS}], []},
- {xmlelement, "text", [{"xmlns", ?NS_STANZAS}],
- [{xmlcdata, Msg1}]}]}.
-
-
+ #xmlel{name = <<"error">>,
+ attrs = [{<<"code">>, <<"500">>}, {<<"type">>, Type}],
+ children =
+ [#xmlel{name = <<"undefined-condition">>,
+ attrs = [{<<"xmlns">>, ?NS_STANZAS}], children = []},
+ #xmlel{name = <<"text">>,
+ attrs = [{<<"xmlns">>, ?NS_STANZAS}],
+ children = [{xmlcdata, Msg1}]}]}.
remove_element(E, Set) ->
- case ?SETS:is_element(E, Set) of
- true ->
- ?SETS:del_element(E, Set);
- _ ->
- Set
+ case (?SETS):is_element(E, Set) of
+ true -> (?SETS):del_element(E, Set);
+ _ -> Set
end.
-
-
iq_admin(StateData, Channel, From, To,
#iq{type = Type, xmlns = XMLNS, sub_el = SubEl} = IQ) ->
- case catch process_iq_admin(StateData, Channel, Type, SubEl) of
- {'EXIT', Reason} ->
- ?ERROR_MSG("~p", [Reason]);
- Res ->
- if
- Res /= ignore ->
- ResIQ = case Res of
- {result, ResEls} ->
- IQ#iq{type = result,
- sub_el = [{xmlelement, "query",
- [{"xmlns", XMLNS}],
- ResEls
- }]};
- {error, Error} ->
- IQ#iq{type = error,
- sub_el = [SubEl, Error]}
- end,
- ejabberd_router:route(To, From,
- jlib:iq_to_xml(ResIQ));
- true ->
- ok
- end
+ case catch process_iq_admin(StateData, Channel, Type,
+ SubEl)
+ of
+ {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]);
+ Res ->
+ if Res /= ignore ->
+ ResIQ = case Res of
+ {result, ResEls} ->
+ IQ#iq{type = result,
+ sub_el =
+ [#xmlel{name = <<"query">>,
+ attrs = [{<<"xmlns">>, XMLNS}],
+ children = ResEls}]};
+ {error, Error} ->
+ IQ#iq{type = error, sub_el = [SubEl, Error]}
+ end,
+ ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ));
+ true -> ok
+ end
end.
-
process_iq_admin(StateData, Channel, set, SubEl) ->
- case xml:get_subtag(SubEl, "item") of
- false ->
- {error, ?ERR_BAD_REQUEST};
- ItemEl ->
- Nick = xml:get_tag_attr_s("nick", ItemEl),
- Affiliation = xml:get_tag_attr_s("affiliation", ItemEl),
- Role = xml:get_tag_attr_s("role", ItemEl),
- Reason = xml:get_path_s(ItemEl, [{elem, "reason"}, cdata]),
- process_admin(StateData, Channel, Nick, Affiliation, Role, Reason)
+ case xml:get_subtag(SubEl, <<"item">>) of
+ false -> {error, ?ERR_BAD_REQUEST};
+ ItemEl ->
+ Nick = xml:get_tag_attr_s(<<"nick">>, ItemEl),
+ Affiliation = xml:get_tag_attr_s(<<"affiliation">>,
+ ItemEl),
+ Role = xml:get_tag_attr_s(<<"role">>, ItemEl),
+ Reason = xml:get_path_s(ItemEl,
+ [{elem, <<"reason">>}, cdata]),
+ process_admin(StateData, Channel, Nick, Affiliation,
+ Role, Reason)
end;
process_iq_admin(_StateData, _Channel, get, _SubEl) ->
{error, ?ERR_FEATURE_NOT_IMPLEMENTED}.
-
-
-process_admin(_StateData, _Channel, "", _Affiliation, _Role, _Reason) ->
+process_admin(_StateData, _Channel, <<"">>,
+ _Affiliation, _Role, _Reason) ->
{error, ?ERR_FEATURE_NOT_IMPLEMENTED};
-
-process_admin(StateData, Channel, Nick, _Affiliation, "none", Reason) ->
+process_admin(StateData, Channel, Nick, _Affiliation,
+ <<"none">>, Reason) ->
case Reason of
- "" ->
- send_text(StateData,
- io_lib:format("KICK #~s ~s\r\n",
- [Channel, Nick]));
- _ ->
- send_text(StateData,
- io_lib:format("KICK #~s ~s :~s\r\n",
- [Channel, Nick, Reason]))
+ <<"">> ->
+ send_text(StateData,
+ io_lib:format("KICK #~s ~s\r\n", [Channel, Nick]));
+ _ ->
+ send_text(StateData,
+ io_lib:format("KICK #~s ~s :~s\r\n",
+ [Channel, Nick, Reason]))
end,
{result, []};
-
-
-
-process_admin(_StateData, _Channel, _Nick, _Affiliation, _Role, _Reason) ->
+process_admin(_StateData, _Channel, _Nick, _Affiliation,
+ _Role, _Reason) ->
{error, ?ERR_FEATURE_NOT_IMPLEMENTED}.
-
-
filter_message(Msg) ->
- lists:filter(
- fun(C) ->
- if (C < 32) and
- (C /= 9) and
- (C /= 10) and
- (C /= 13) ->
- false;
- true -> true
- end
- end, filter_mirc_colors(Msg)).
+ list_to_binary(
+ lists:filter(fun (C) ->
+ if (C < 32) and (C /= 9) and (C /= 10) and (C /= 13) ->
+ false;
+ true -> true
+ end
+ end,
+ binary_to_list(filter_mirc_colors(Msg)))).
filter_mirc_colors(Msg) ->
- ejabberd_regexp:greplace(Msg, "(\\003[0-9]+)(,[0-9]+)?", "").
+ ejabberd_regexp:greplace(Msg,
+ <<"(\\003[0-9]+)(,[0-9]+)?">>, <<"">>).
unixtime2string(Unixtime) ->
- Secs = Unixtime + calendar:datetime_to_gregorian_seconds(
- {{1970, 1, 1}, {0,0,0}}),
+ Secs = Unixtime +
+ calendar:datetime_to_gregorian_seconds({{1970, 1, 1},
+ {0, 0, 0}}),
{{Year, Month, Day}, {Hour, Minute, Second}} =
- calendar:universal_time_to_local_time(
- calendar:gregorian_seconds_to_datetime(Secs)),
- lists:flatten(
- io_lib:format("~4..0w-~2..0w-~2..0w ~2..0w:~2..0w:~2..0w",
- [Year, Month, Day, Hour, Minute, Second])).
-
-toupper([C | Cs]) ->
- if
- C >= $a, C =< $z ->
- [C - 32 | toupper(Cs)];
- true ->
- [C | toupper(Cs)]
- end;
-toupper([]) ->
- [].
+ calendar:universal_time_to_local_time(calendar:gregorian_seconds_to_datetime(Secs)),
+ iolist_to_binary(io_lib:format("~4..0w-~2..0w-~2..0w ~2..0w:~2..0w:~2..0w",
+ [Year, Month, Day, Hour, Minute, Second])).
diff --git a/src/mod_last.erl b/src/mod_last.erl
index 8403b76d0..e8df5a270 100644
--- a/src/mod_last.erl
+++ b/src/mod_last.erl
@@ -25,182 +25,198 @@
%%%----------------------------------------------------------------------
-module(mod_last).
+
-author('alexey@process-one.net').
-behaviour(gen_mod).
--export([start/2,
- stop/1,
- process_local_iq/3,
- process_sm_iq/3,
- on_presence_update/4,
- store_last_info/4,
- get_last_info/2,
- remove_user/2]).
+-export([start/2, stop/1, process_local_iq/3, export/1,
+ process_sm_iq/3, on_presence_update/4,
+ store_last_info/4, get_last_info/2, remove_user/2]).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
--include("mod_privacy.hrl").
--record(last_activity, {us, timestamp, status}).
+-include("mod_privacy.hrl").
+-record(last_activity, {us = {<<"">>, <<"">>} :: {binary(), binary()},
+ timestamp = 0 :: non_neg_integer(),
+ status = <<"">> :: binary()}).
start(Host, Opts) ->
- IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
+ IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
+ one_queue),
case gen_mod:db_type(Opts) of
- mnesia ->
- mnesia:create_table(last_activity,
- [{disc_copies, [node()]},
- {attributes,
- record_info(fields, last_activity)}]),
- update_table();
- _ ->
- ok
+ mnesia ->
+ mnesia:create_table(last_activity,
+ [{disc_copies, [node()]},
+ {attributes,
+ record_info(fields, last_activity)}]),
+ update_table();
+ _ -> ok
end,
- gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_LAST,
- ?MODULE, process_local_iq, IQDisc),
- gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_LAST,
- ?MODULE, process_sm_iq, IQDisc),
- ejabberd_hooks:add(remove_user, Host,
- ?MODULE, remove_user, 50),
- ejabberd_hooks:add(unset_presence_hook, Host,
- ?MODULE, on_presence_update, 50).
+ gen_iq_handler:add_iq_handler(ejabberd_local, Host,
+ ?NS_LAST, ?MODULE, process_local_iq, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
+ ?NS_LAST, ?MODULE, process_sm_iq, IQDisc),
+ ejabberd_hooks:add(remove_user, Host, ?MODULE,
+ remove_user, 50),
+ ejabberd_hooks:add(unset_presence_hook, Host, ?MODULE,
+ on_presence_update, 50).
stop(Host) ->
- ejabberd_hooks:delete(remove_user, Host,
- ?MODULE, remove_user, 50),
+ ejabberd_hooks:delete(remove_user, Host, ?MODULE,
+ remove_user, 50),
ejabberd_hooks:delete(unset_presence_hook, Host,
?MODULE, on_presence_update, 50),
- gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_LAST),
- gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_LAST).
+ gen_iq_handler:remove_iq_handler(ejabberd_local, Host,
+ ?NS_LAST),
+ gen_iq_handler:remove_iq_handler(ejabberd_sm, Host,
+ ?NS_LAST).
%%%
%%% Uptime of ejabberd node
%%%
-process_local_iq(_From, _To, #iq{type = Type, sub_el = SubEl} = IQ) ->
+process_local_iq(_From, _To,
+ #iq{type = Type, sub_el = SubEl} = IQ) ->
case Type of
- set ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
- get ->
- Sec = get_node_uptime(),
- IQ#iq{type = result,
- sub_el = [{xmlelement, "query",
- [{"xmlns", ?NS_LAST},
- {"seconds", integer_to_list(Sec)}],
- []}]}
+ set ->
+ IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+ get ->
+ Sec = get_node_uptime(),
+ IQ#iq{type = result,
+ sub_el =
+ [#xmlel{name = <<"query">>,
+ attrs =
+ [{<<"xmlns">>, ?NS_LAST},
+ {<<"seconds">>,
+ iolist_to_binary(integer_to_list(Sec))}],
+ children = []}]}
end.
%% @spec () -> integer()
%% @doc Get the uptime of the ejabberd node, expressed in seconds.
%% When ejabberd is starting, ejabberd_config:start/0 stores the datetime.
get_node_uptime() ->
- case ejabberd_config:get_local_option(node_start) of
- {_, _, _} = StartNow ->
- now_to_seconds(now()) - now_to_seconds(StartNow);
- _undefined ->
- trunc(element(1, erlang:statistics(wall_clock))/1000)
+ case ejabberd_config:get_local_option(
+ node_start,
+ fun({MegaSecs, Secs, MicroSecs} = Now)
+ when is_integer(MegaSecs), MegaSecs >= 0,
+ is_integer(Secs), Secs >= 0,
+ is_integer(MicroSecs), MicroSecs >= 0 ->
+ Now
+ end) of
+ undefined ->
+ trunc(element(1, erlang:statistics(wall_clock)) / 1000);
+ StartNow ->
+ now_to_seconds(now()) - now_to_seconds(StartNow)
end.
now_to_seconds({MegaSecs, Secs, _MicroSecs}) ->
MegaSecs * 1000000 + Secs.
-
%%%
%%% Serve queries about user last online
%%%
-process_sm_iq(From, To, #iq{type = Type, sub_el = SubEl} = IQ) ->
+process_sm_iq(From, To,
+ #iq{type = Type, sub_el = SubEl} = IQ) ->
case Type of
- set ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
- get ->
- User = To#jid.luser,
- Server = To#jid.lserver,
- {Subscription, _Groups} =
- ejabberd_hooks:run_fold(
- roster_get_jid_info, Server,
- {none, []}, [User, Server, From]),
- if
- (Subscription == both) or (Subscription == from)
- or ((From#jid.luser == To#jid.luser)
- and (From#jid.lserver == To#jid.lserver)) ->
- UserListRecord = ejabberd_hooks:run_fold(
- privacy_get_user_list, Server,
- #userlist{},
- [User, Server]),
- case ejabberd_hooks:run_fold(
- privacy_check_packet, Server,
- allow,
- [User, Server, UserListRecord,
- {To, From,
- {xmlelement, "presence", [], []}},
- out]) of
- allow ->
- get_last_iq(IQ, SubEl, User, Server);
- deny ->
- IQ#iq{type = error,
- sub_el = [SubEl, ?ERR_FORBIDDEN]}
- end;
- true ->
- IQ#iq{type = error,
- sub_el = [SubEl, ?ERR_FORBIDDEN]}
- end
+ set ->
+ IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+ get ->
+ User = To#jid.luser,
+ Server = To#jid.lserver,
+ {Subscription, _Groups} =
+ ejabberd_hooks:run_fold(roster_get_jid_info, Server,
+ {none, []}, [User, Server, From]),
+ if (Subscription == both) or (Subscription == from) or
+ (From#jid.luser == To#jid.luser) and
+ (From#jid.lserver == To#jid.lserver) ->
+ UserListRecord =
+ ejabberd_hooks:run_fold(privacy_get_user_list, Server,
+ #userlist{}, [User, Server]),
+ case ejabberd_hooks:run_fold(privacy_check_packet,
+ Server, allow,
+ [User, Server, UserListRecord,
+ {To, From,
+ #xmlel{name = <<"presence">>,
+ attrs = [],
+ children = []}},
+ out])
+ of
+ allow -> get_last_iq(IQ, SubEl, User, Server);
+ deny ->
+ IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]}
+ end;
+ true ->
+ IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]}
+ end
end.
%% @spec (LUser::string(), LServer::string()) ->
%% {ok, TimeStamp::integer(), Status::string()} | not_found | {error, Reason}
get_last(LUser, LServer) ->
- get_last(LUser, LServer, gen_mod:db_type(LServer, ?MODULE)).
+ get_last(LUser, LServer,
+ gen_mod:db_type(LServer, ?MODULE)).
get_last(LUser, LServer, mnesia) ->
- case catch mnesia:dirty_read(last_activity, {LUser, LServer}) of
- {'EXIT', Reason} ->
- {error, Reason};
- [] ->
- not_found;
- [#last_activity{timestamp = TimeStamp, status = Status}] ->
- {ok, TimeStamp, Status}
+ case catch mnesia:dirty_read(last_activity,
+ {LUser, LServer})
+ of
+ {'EXIT', Reason} -> {error, Reason};
+ [] -> not_found;
+ [#last_activity{timestamp = TimeStamp,
+ status = Status}] ->
+ {ok, TimeStamp, Status}
end;
get_last(LUser, LServer, odbc) ->
Username = ejabberd_odbc:escape(LUser),
case catch odbc_queries:get_last(LServer, Username) of
- {selected, ["seconds","state"], []} ->
- not_found;
- {selected, ["seconds","state"], [{STimeStamp, Status}]} ->
- case catch list_to_integer(STimeStamp) of
- TimeStamp when is_integer(TimeStamp) ->
- {ok, TimeStamp, Status};
- Reason ->
- {error, {invalid_timestamp, Reason}}
- end;
- Reason ->
- {error, {invalid_result, Reason}}
+ {selected, [<<"seconds">>, <<"state">>], []} ->
+ not_found;
+ {selected, [<<"seconds">>, <<"state">>],
+ [[STimeStamp, Status]]} ->
+ case catch jlib:binary_to_integer(STimeStamp) of
+ TimeStamp when is_integer(TimeStamp) ->
+ {ok, TimeStamp, Status};
+ Reason -> {error, {invalid_timestamp, Reason}}
+ end;
+ Reason -> {error, {invalid_result, Reason}}
end.
get_last_iq(IQ, SubEl, LUser, LServer) ->
case ejabberd_sm:get_user_resources(LUser, LServer) of
- [] ->
- case get_last(LUser, LServer) of
- {error, _Reason} ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]};
- not_found ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_SERVICE_UNAVAILABLE]};
- {ok, TimeStamp, Status} ->
- TimeStamp2 = now_to_seconds(now()),
- Sec = TimeStamp2 - TimeStamp,
- IQ#iq{type = result,
- sub_el = [{xmlelement, "query",
- [{"xmlns", ?NS_LAST},
- {"seconds", integer_to_list(Sec)}],
- [{xmlcdata, Status}]}]}
- end;
- _ ->
- IQ#iq{type = result,
- sub_el = [{xmlelement, "query",
- [{"xmlns", ?NS_LAST},
- {"seconds", "0"}],
- []}]}
+ [] ->
+ case get_last(LUser, LServer) of
+ {error, _Reason} ->
+ IQ#iq{type = error,
+ sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]};
+ not_found ->
+ IQ#iq{type = error,
+ sub_el = [SubEl, ?ERR_SERVICE_UNAVAILABLE]};
+ {ok, TimeStamp, Status} ->
+ TimeStamp2 = now_to_seconds(now()),
+ Sec = TimeStamp2 - TimeStamp,
+ IQ#iq{type = result,
+ sub_el =
+ [#xmlel{name = <<"query">>,
+ attrs =
+ [{<<"xmlns">>, ?NS_LAST},
+ {<<"seconds">>,
+ iolist_to_binary(integer_to_list(Sec))}],
+ children = [{xmlcdata, Status}]}]}
+ end;
+ _ ->
+ IQ#iq{type = result,
+ sub_el =
+ [#xmlel{name = <<"query">>,
+ attrs =
+ [{<<"xmlns">>, ?NS_LAST},
+ {<<"seconds">>, <<"0">>}],
+ children = []}]}
end.
on_presence_update(User, Server, _Resource, Status) ->
@@ -211,30 +227,33 @@ store_last_info(User, Server, TimeStamp, Status) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
DBType = gen_mod:db_type(LServer, ?MODULE),
- store_last_info(LUser, LServer, TimeStamp, Status, DBType).
+ store_last_info(LUser, LServer, TimeStamp, Status,
+ DBType).
-store_last_info(LUser, LServer, TimeStamp, Status, mnesia) ->
+store_last_info(LUser, LServer, TimeStamp, Status,
+ mnesia) ->
US = {LUser, LServer},
- F = fun() ->
+ F = fun () ->
mnesia:write(#last_activity{us = US,
timestamp = TimeStamp,
status = Status})
end,
mnesia:transaction(F);
-store_last_info(LUser, LServer, TimeStamp, Status, odbc) ->
+store_last_info(LUser, LServer, TimeStamp, Status,
+ odbc) ->
Username = ejabberd_odbc:escape(LUser),
- Seconds = ejabberd_odbc:escape(integer_to_list(TimeStamp)),
+ Seconds =
+ ejabberd_odbc:escape(iolist_to_binary(integer_to_list(TimeStamp))),
State = ejabberd_odbc:escape(Status),
- odbc_queries:set_last_t(LServer, Username, Seconds, State).
+ odbc_queries:set_last_t(LServer, Username, Seconds,
+ State).
%% @spec (LUser::string(), LServer::string()) ->
%% {ok, TimeStamp::integer(), Status::string()} | not_found
get_last_info(LUser, LServer) ->
case get_last(LUser, LServer) of
- {error, _Reason} ->
- not_found;
- Res ->
- Res
+ {error, _Reason} -> not_found;
+ Res -> Res
end.
remove_user(User, Server) ->
@@ -245,9 +264,7 @@ remove_user(User, Server) ->
remove_user(LUser, LServer, mnesia) ->
US = {LUser, LServer},
- F = fun() ->
- mnesia:delete({last_activity, US})
- end,
+ F = fun () -> mnesia:delete({last_activity, US}) end,
mnesia:transaction(F);
remove_user(LUser, LServer, odbc) ->
Username = ejabberd_odbc:escape(LUser),
@@ -256,48 +273,34 @@ remove_user(LUser, LServer, odbc) ->
update_table() ->
Fields = record_info(fields, last_activity),
case mnesia:table_info(last_activity, attributes) of
- Fields ->
- ok;
- [user, timestamp, status] ->
- ?INFO_MSG("Converting last_activity table from {user, timestamp, status} format", []),
- Host = ?MYNAME,
- mnesia:transform_table(last_activity, ignore, Fields),
- F = fun() ->
- mnesia:write_lock_table(last_activity),
- mnesia:foldl(
- fun({_, U, T, S} = R, _) ->
- mnesia:delete_object(R),
- mnesia:write(
- #last_activity{us = {U, Host},
- timestamp = T,
- status = S})
- end, ok, last_activity)
- end,
- mnesia:transaction(F);
- [user, timestamp] ->
- ?INFO_MSG("Converting last_activity table from {user, timestamp} format", []),
- Host = ?MYNAME,
- mnesia:transform_table(
- last_activity,
- fun({_, U, T}) ->
- #last_activity{us = U,
- timestamp = T,
- status = ""}
- end, Fields),
- F = fun() ->
- mnesia:write_lock_table(last_activity),
- mnesia:foldl(
- fun({_, U, T, S} = R, _) ->
- mnesia:delete_object(R),
- mnesia:write(
- #last_activity{us = {U, Host},
- timestamp = T,
- status = S})
- end, ok, last_activity)
- end,
- mnesia:transaction(F);
- _ ->
- ?INFO_MSG("Recreating last_activity table", []),
- mnesia:transform_table(last_activity, ignore, Fields)
+ Fields ->
+ ejabberd_config:convert_table_to_binary(
+ last_activity, Fields, set,
+ fun(#last_activity{us = {U, _}}) -> U end,
+ fun(#last_activity{us = {U, S}, status = Status} = R) ->
+ R#last_activity{us = {iolist_to_binary(U),
+ iolist_to_binary(S)},
+ status = iolist_to_binary(Status)}
+ end);
+ _ ->
+ ?INFO_MSG("Recreating last_activity table", []),
+ mnesia:transform_table(last_activity, ignore, Fields)
end.
+export(_Server) ->
+ [{last_activity,
+ fun(Host, #last_activity{us = {LUser, LServer},
+ timestamp = TimeStamp, status = Status})
+ when LServer == Host ->
+ Username = ejabberd_odbc:escape(LUser),
+ Seconds =
+ ejabberd_odbc:escape(jlib:integer_to_binary(TimeStamp)),
+ State = ejabberd_odbc:escape(Status),
+ [[<<"delete from last where username='">>, Username, <<"';">>],
+ [<<"insert into last(username, seconds, "
+ "state) values ('">>,
+ Username, <<"', '">>, Seconds, <<"', '">>, State,
+ <<"');">>]];
+ (_Host, _R) ->
+ []
+ end}].
diff --git a/src/mod_muc/Makefile.in b/src/mod_muc/Makefile.in
index 41315ad29..5ede5e521 100644
--- a/src/mod_muc/Makefile.in
+++ b/src/mod_muc/Makefile.in
@@ -14,7 +14,7 @@ EFLAGS += -pz ..
# make debug=true to compile Erlang module with debug informations.
ifdef debug
- EFLAGS+=+debug_info +export_all
+ EFLAGS+=+debug_info
endif
ifeq (@transient_supervisors@, false)
diff --git a/src/mod_muc/mod_muc.erl b/src/mod_muc/mod_muc.erl
index d6414a59e..6587cc5d0 100644
--- a/src/mod_muc/mod_muc.erl
+++ b/src/mod_muc/mod_muc.erl
@@ -25,9 +25,11 @@
%%%----------------------------------------------------------------------
-module(mod_muc).
+
-author('alexey@process-one.net').
-behaviour(gen_server).
+
-behaviour(gen_mod).
%% API
@@ -44,23 +46,32 @@
can_use_nick/4]).
%% gen_server callbacks
--export([init/1, handle_call/3, handle_cast/2, handle_info/2,
- terminate/2, code_change/3]).
+-export([init/1, handle_call/3, handle_cast/2,
+ handle_info/2, terminate/2, code_change/3]).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
+-record(muc_room, {name_host = {<<"">>, <<"">>} :: {binary(), binary()} |
+ {'_', binary()},
+ opts = [] :: list() | '_'}).
+
+-record(muc_online_room,
+ {name_host = {<<"">>, <<"">>} :: {binary(), binary()} | '$1',
+ pid = self() :: pid() | '$2' | '_'}).
--record(muc_room, {name_host, opts}).
--record(muc_online_room, {name_host, pid}).
--record(muc_registered, {us_host, nick}).
+-record(muc_registered,
+ {us_host = {{<<"">>, <<"">>}, <<"">>} :: {{binary(), binary()}, binary()} | '$1',
+ nick = <<"">> :: binary()}).
--record(state, {host,
- server_host,
- access,
- history_size,
- default_room_opts,
- room_shaper}).
+-record(state,
+ {host = <<"">> :: binary(),
+ server_host = <<"">> :: binary(),
+ access = {none, none, none, none} :: {atom(), atom(), atom(), atom()},
+ history_size = 20 :: non_neg_integer(),
+ default_room_opts = [] :: list(),
+ room_shaper = none :: shaper:shaper()}).
-define(PROCNAME, ejabberd_mod_muc).
@@ -73,18 +84,14 @@
%%--------------------------------------------------------------------
start_link(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
- gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
+ gen_server:start_link({local, Proc}, ?MODULE,
+ [Host, Opts], []).
start(Host, Opts) ->
start_supervisor(Host),
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
- ChildSpec =
- {Proc,
- {?MODULE, start_link, [Host, Opts]},
- temporary,
- 1000,
- worker,
- [?MODULE]},
+ ChildSpec = {Proc, {?MODULE, start_link, [Host, Opts]},
+ temporary, 1000, worker, [?MODULE]},
supervisor:start_child(ejabberd_sup, ChildSpec).
stop(Host) ->
@@ -101,7 +108,7 @@ stop(Host) ->
%% So the message sending must be catched
room_destroyed(Host, Room, Pid, ServerHost) ->
catch gen_mod:get_module_proc(ServerHost, ?PROCNAME) !
- {room_destroyed, {Room, Host}, Pid},
+ {room_destroyed, {Room, Host}, Pid},
ok.
%% @doc Create a room.
@@ -113,10 +120,11 @@ create_room(Host, Name, From, Nick, Opts) ->
store_room(ServerHost, Host, Name, Opts) ->
LServer = jlib:nameprep(ServerHost),
- store_room(LServer, Host, Name, Opts, gen_mod:db_type(LServer, ?MODULE)).
+ store_room(LServer, Host, Name, Opts,
+ gen_mod:db_type(LServer, ?MODULE)).
store_room(_LServer, Host, Name, Opts, mnesia) ->
- F = fun() ->
+ F = fun () ->
mnesia:write(#muc_room{name_host = {Name, Host},
opts = Opts})
end,
@@ -125,104 +133,98 @@ store_room(LServer, Host, Name, Opts, odbc) ->
SName = ejabberd_odbc:escape(Name),
SHost = ejabberd_odbc:escape(Host),
SOpts = ejabberd_odbc:encode_term(Opts),
- F = fun() ->
- odbc_queries:update_t(
- "muc_room",
- ["name", "host", "opts"],
- [SName, SHost, SOpts],
- ["name='", SName, "' and host='", SHost, "'"])
+ F = fun () ->
+ odbc_queries:update_t(<<"muc_room">>,
+ [<<"name">>, <<"host">>, <<"opts">>],
+ [SName, SHost, SOpts],
+ [<<"name='">>, SName, <<"' and host='">>,
+ SHost, <<"'">>])
end,
ejabberd_odbc:sql_transaction(LServer, F).
restore_room(ServerHost, Host, Name) ->
LServer = jlib:nameprep(ServerHost),
- restore_room(LServer, Host, Name, gen_mod:db_type(LServer, ?MODULE)).
+ restore_room(LServer, Host, Name,
+ gen_mod:db_type(LServer, ?MODULE)).
restore_room(_LServer, Host, Name, mnesia) ->
case catch mnesia:dirty_read(muc_room, {Name, Host}) of
- [#muc_room{opts = Opts}] ->
- Opts;
- _ ->
- error
+ [#muc_room{opts = Opts}] -> Opts;
+ _ -> error
end;
restore_room(LServer, Host, Name, odbc) ->
SName = ejabberd_odbc:escape(Name),
SHost = ejabberd_odbc:escape(Host),
- case catch ejabberd_odbc:sql_query(
- LServer, ["select opts from muc_room where name='",
- SName, "' and host='", SHost, "';"]) of
- {selected, ["opts"], [{Opts}]} ->
- ejabberd_odbc:decode_term(Opts);
- _ ->
- error
+ case catch ejabberd_odbc:sql_query(LServer,
+ [<<"select opts from muc_room where name='">>,
+ SName, <<"' and host='">>, SHost,
+ <<"';">>])
+ of
+ {selected, [<<"opts">>], [[Opts]]} ->
+ opts_to_binary(ejabberd_odbc:decode_term(Opts));
+ _ -> error
end.
forget_room(ServerHost, Host, Name) ->
LServer = jlib:nameprep(ServerHost),
- forget_room(LServer, Host, Name, gen_mod:db_type(LServer, ?MODULE)).
+ forget_room(LServer, Host, Name,
+ gen_mod:db_type(LServer, ?MODULE)).
forget_room(_LServer, Host, Name, mnesia) ->
- F = fun() ->
- mnesia:delete({muc_room, {Name, Host}})
+ F = fun () -> mnesia:delete({muc_room, {Name, Host}})
end,
mnesia:transaction(F);
forget_room(LServer, Host, Name, odbc) ->
SName = ejabberd_odbc:escape(Name),
SHost = ejabberd_odbc:escape(Host),
- F = fun() ->
- ejabberd_odbc:sql_query_t(
- ["delete from muc_room where name='",
- SName, "' and host='", SHost, "';"])
+ F = fun () ->
+ ejabberd_odbc:sql_query_t([<<"delete from muc_room where name='">>,
+ SName, <<"' and host='">>, SHost,
+ <<"';">>])
end,
ejabberd_odbc:sql_transaction(LServer, F).
-process_iq_disco_items(Host, From, To, #iq{lang = Lang} = IQ) ->
+process_iq_disco_items(Host, From, To,
+ #iq{lang = Lang} = IQ) ->
Rsm = jlib:rsm_decode(IQ),
Res = IQ#iq{type = result,
- sub_el = [{xmlelement, "query",
- [{"xmlns", ?NS_DISCO_ITEMS}],
- iq_disco_items(Host, From, Lang, Rsm)}]},
- ejabberd_router:route(To,
- From,
- jlib:iq_to_xml(Res)).
-
-can_use_nick(_ServerHost, _Host, _JID, "") ->
- false;
+ sub_el =
+ [#xmlel{name = <<"query">>,
+ attrs = [{<<"xmlns">>, ?NS_DISCO_ITEMS}],
+ children = iq_disco_items(Host, From, Lang, Rsm)}]},
+ ejabberd_router:route(To, From, jlib:iq_to_xml(Res)).
+
+can_use_nick(_ServerHost, _Host, _JID, <<"">>) -> false;
can_use_nick(ServerHost, Host, JID, Nick) ->
LServer = jlib:nameprep(ServerHost),
- can_use_nick(LServer, Host, JID, Nick, gen_mod:db_type(LServer, ?MODULE)).
+ can_use_nick(LServer, Host, JID, Nick,
+ gen_mod:db_type(LServer, ?MODULE)).
can_use_nick(_LServer, Host, JID, Nick, mnesia) ->
{LUser, LServer, _} = jlib:jid_tolower(JID),
LUS = {LUser, LServer},
- case catch mnesia:dirty_select(
- muc_registered,
- [{#muc_registered{us_host = '$1',
- nick = Nick,
- _ = '_'},
- [{'==', {element, 2, '$1'}, Host}],
- ['$_']}]) of
- {'EXIT', _Reason} ->
- true;
- [] ->
- true;
- [#muc_registered{us_host = {U, _Host}}] ->
- U == LUS
+ case catch mnesia:dirty_select(muc_registered,
+ [{#muc_registered{us_host = '$1',
+ nick = Nick, _ = '_'},
+ [{'==', {element, 2, '$1'}, Host}],
+ ['$_']}])
+ of
+ {'EXIT', _Reason} -> true;
+ [] -> true;
+ [#muc_registered{us_host = {U, _Host}}] -> U == LUS
end;
can_use_nick(LServer, Host, JID, Nick, odbc) ->
- SJID = jlib:jid_to_string(
- jlib:jid_tolower(
- jlib:jid_remove_resource(JID))),
+ SJID =
+ jlib:jid_to_string(jlib:jid_tolower(jlib:jid_remove_resource(JID))),
SNick = ejabberd_odbc:escape(Nick),
SHost = ejabberd_odbc:escape(Host),
- case catch ejabberd_odbc:sql_query(
- LServer, ["select jid from muc_registered ",
- "where nick='", SNick, "' and host='",
- SHost, "';"]) of
- {selected, ["jid"], [{SJID1}]} ->
- SJID == SJID1;
- _ ->
- true
+ case catch ejabberd_odbc:sql_query(LServer,
+ [<<"select jid from muc_registered ">>,
+ <<"where nick='">>, SNick,
+ <<"' and host='">>, SHost, <<"';">>])
+ of
+ {selected, [<<"jid">>], [[SJID1]]} -> SJID == SJID1;
+ _ -> true
end.
%%====================================================================
@@ -237,7 +239,8 @@ can_use_nick(LServer, Host, JID, Nick, odbc) ->
%% Description: Initiates the server
%%--------------------------------------------------------------------
init([Host, Opts]) ->
- MyHost = gen_mod:get_opt_host(Host, Opts, "conference.@HOST@"),
+ MyHost = gen_mod:get_opt_host(Host, Opts,
+ <<"conference.@HOST@">>),
case gen_mod:db_type(Opts) of
mnesia ->
mnesia:create_table(muc_room,
@@ -260,13 +263,13 @@ init([Host, Opts]) ->
catch ets:new(muc_online_users, [bag, named_table, public, {keypos, 2}]),
clean_table_from_bad_node(node(), MyHost),
mnesia:subscribe(system),
- Access = gen_mod:get_opt(access, Opts, all),
- AccessCreate = gen_mod:get_opt(access_create, Opts, all),
- AccessAdmin = gen_mod:get_opt(access_admin, Opts, none),
- AccessPersistent = gen_mod:get_opt(access_persistent, Opts, all),
- HistorySize = gen_mod:get_opt(history_size, Opts, 20),
- DefRoomOpts = gen_mod:get_opt(default_room_options, Opts, []),
- RoomShaper = gen_mod:get_opt(room_shaper, Opts, none),
+ Access = gen_mod:get_opt(access, Opts, fun(A) -> A end, all),
+ AccessCreate = gen_mod:get_opt(access_create, Opts, fun(A) -> A end, all),
+ AccessAdmin = gen_mod:get_opt(access_admin, Opts, fun(A) -> A end, none),
+ AccessPersistent = gen_mod:get_opt(access_persistent, Opts, fun(A) -> A end, all),
+ HistorySize = gen_mod:get_opt(history_size, Opts, fun(A) -> A end, 20),
+ DefRoomOpts = gen_mod:get_opt(default_room_options, Opts, fun(A) -> A end, []),
+ RoomShaper = gen_mod:get_opt(room_shaper, Opts, fun(A) -> A end, none),
ejabberd_router:register_route(MyHost),
load_permanent_rooms(MyHost, Host,
{Access, AccessCreate, AccessAdmin, AccessPersistent},
@@ -290,19 +293,15 @@ init([Host, Opts]) ->
%%--------------------------------------------------------------------
handle_call(stop, _From, State) ->
{stop, normal, ok, State};
-
-handle_call({create, Room, From, Nick, Opts},
- _From,
- #state{host = Host,
- server_host = ServerHost,
- access = Access,
- default_room_opts = DefOpts,
+handle_call({create, Room, From, Nick, Opts}, _From,
+ #state{host = Host, server_host = ServerHost,
+ access = Access, default_room_opts = DefOpts,
history_size = HistorySize,
room_shaper = RoomShaper} = State) ->
?DEBUG("MUC: create new room '~s'~n", [Room]),
NewOpts = case Opts of
- default -> DefOpts;
- _ -> Opts
+ default -> DefOpts;
+ _ -> Opts
end,
{ok, Pid} = mod_muc_room:start(
Host, ServerHost, Access,
@@ -318,8 +317,7 @@ handle_call({create, Room, From, Nick, Opts},
%% {stop, Reason, State}
%% Description: Handling cast messages
%%--------------------------------------------------------------------
-handle_cast(_Msg, State) ->
- {noreply, State}.
+handle_cast(_Msg, State) -> {noreply, State}.
%%--------------------------------------------------------------------
%% Function: handle_info(Info, State) -> {noreply, State} |
@@ -328,10 +326,8 @@ handle_cast(_Msg, State) ->
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------
handle_info({route, From, To, Packet},
- #state{host = Host,
- server_host = ServerHost,
- access = Access,
- default_room_opts = DefRoomOpts,
+ #state{host = Host, server_host = ServerHost,
+ access = Access, default_room_opts = DefRoomOpts,
history_size = HistorySize,
room_shaper = RoomShaper} = State) ->
case catch do_route(Host, ServerHost, Access, HistorySize, RoomShaper,
@@ -343,8 +339,9 @@ handle_info({route, From, To, Packet},
end,
{noreply, State};
handle_info({room_destroyed, RoomHost, Pid}, State) ->
- F = fun() ->
- mnesia:delete_object(#muc_online_room{name_host = RoomHost,
+ F = fun () ->
+ mnesia:delete_object(#muc_online_room{name_host =
+ RoomHost,
pid = Pid})
end,
mnesia:transaction(F),
@@ -352,8 +349,7 @@ handle_info({room_destroyed, RoomHost, Pid}, State) ->
handle_info({mnesia_system_event, {mnesia_down, Node}}, State) ->
clean_table_from_bad_node(Node),
{noreply, State};
-handle_info(_Info, State) ->
- {noreply, State}.
+handle_info(_Info, State) -> {noreply, State}.
%%--------------------------------------------------------------------
%% Function: terminate(Reason, State) -> void()
@@ -370,26 +366,22 @@ terminate(_Reason, State) ->
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
%% Description: Convert process state when code is changed
%%--------------------------------------------------------------------
-code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
+code_change(_OldVsn, State, _Extra) -> {ok, State}.
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
start_supervisor(Host) ->
- Proc = gen_mod:get_module_proc(Host, ejabberd_mod_muc_sup),
- ChildSpec =
- {Proc,
- {ejabberd_tmp_sup, start_link,
- [Proc, mod_muc_room]},
- permanent,
- infinity,
- supervisor,
- [ejabberd_tmp_sup]},
+ Proc = gen_mod:get_module_proc(Host,
+ ejabberd_mod_muc_sup),
+ ChildSpec = {Proc,
+ {ejabberd_tmp_sup, start_link, [Proc, mod_muc_room]},
+ permanent, infinity, supervisor, [ejabberd_tmp_sup]},
supervisor:start_child(ejabberd_sup, ChildSpec).
stop_supervisor(Host) ->
- Proc = gen_mod:get_module_proc(Host, ejabberd_mod_muc_sup),
+ Proc = gen_mod:get_module_proc(Host,
+ ejabberd_mod_muc_sup),
supervisor:terminate_child(ejabberd_sup, Proc),
supervisor:delete_child(ejabberd_sup, Proc).
@@ -401,9 +393,9 @@ do_route(Host, ServerHost, Access, HistorySize, RoomShaper,
do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
From, To, Packet, DefRoomOpts);
_ ->
- {xmlelement, _Name, Attrs, _Els} = Packet,
- Lang = xml:get_attr_s("xml:lang", Attrs),
- ErrText = "Access denied by service policy",
+ #xmlel{attrs = Attrs} = Packet,
+ Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
+ ErrText = <<"Access denied by service policy">>,
Err = jlib:make_error_reply(Packet,
?ERRT_FORBIDDEN(Lang, ErrText)),
ejabberd_router:route_error(To, From, Err, Packet)
@@ -414,128 +406,127 @@ do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
From, To, Packet, DefRoomOpts) ->
{_AccessRoute, AccessCreate, AccessAdmin, _AccessPersistent} = Access,
{Room, _, Nick} = jlib:jid_tolower(To),
- {xmlelement, Name, Attrs, _Els} = Packet,
+ #xmlel{name = Name, attrs = Attrs} = Packet,
case Room of
- "" ->
- case Nick of
- "" ->
- case Name of
- "iq" ->
- case jlib:iq_query_info(Packet) of
- #iq{type = get, xmlns = ?NS_DISCO_INFO = XMLNS,
- sub_el = _SubEl, lang = Lang} = IQ ->
- Info = ejabberd_hooks:run_fold(
- disco_info, ServerHost, [],
- [ServerHost, ?MODULE, "", ""]),
- Res = IQ#iq{type = result,
- sub_el = [{xmlelement, "query",
- [{"xmlns", XMLNS}],
- iq_disco_info(Lang)
- ++Info}]},
- ejabberd_router:route(To,
- From,
- jlib:iq_to_xml(Res));
- #iq{type = get,
- xmlns = ?NS_DISCO_ITEMS} = IQ ->
- spawn(?MODULE,
- process_iq_disco_items,
- [Host, From, To, IQ]);
- #iq{type = get,
- xmlns = ?NS_REGISTER = XMLNS,
- lang = Lang,
- sub_el = _SubEl} = IQ ->
- Res = IQ#iq{type = result,
- sub_el =
- [{xmlelement, "query",
- [{"xmlns", XMLNS}],
- iq_get_register_info(
- ServerHost, Host, From, Lang)}]},
- ejabberd_router:route(To,
- From,
- jlib:iq_to_xml(Res));
- #iq{type = set,
- xmlns = ?NS_REGISTER = XMLNS,
- lang = Lang,
- sub_el = SubEl} = IQ ->
- case process_iq_register_set(
- ServerHost, Host, From, SubEl, Lang) of
- {result, IQRes} ->
- Res = IQ#iq{type = result,
- sub_el =
- [{xmlelement, "query",
- [{"xmlns", XMLNS}],
- IQRes}]},
- ejabberd_router:route(
- To, From, jlib:iq_to_xml(Res));
- {error, Error} ->
- Err = jlib:make_error_reply(
- Packet, Error),
- ejabberd_router:route(
- To, From, Err)
- end;
- #iq{type = get,
- xmlns = ?NS_VCARD = XMLNS,
- lang = Lang,
- sub_el = _SubEl} = IQ ->
- Res = IQ#iq{type = result,
- sub_el =
- [{xmlelement, "vCard",
- [{"xmlns", XMLNS}],
- iq_get_vcard(Lang)}]},
- ejabberd_router:route(To,
- From,
- jlib:iq_to_xml(Res));
- #iq{type = get,
- xmlns = ?NS_MUC_UNIQUE
- } = IQ ->
- Res = IQ#iq{type = result,
- sub_el =
- [{xmlelement, "unique",
- [{"xmlns", ?NS_MUC_UNIQUE}],
- [iq_get_unique(From)]}]},
- ejabberd_router:route(To,
- From,
- jlib:iq_to_xml(Res));
- #iq{} ->
- Err = jlib:make_error_reply(
- Packet,
- ?ERR_FEATURE_NOT_IMPLEMENTED),
- ejabberd_router:route(To, From, Err);
- _ ->
- ok
- end;
- "message" ->
- case xml:get_attr_s("type", Attrs) of
- "error" ->
- ok;
- _ ->
- case acl:match_rule(ServerHost, AccessAdmin, From) of
- allow ->
- Msg = xml:get_path_s(
- Packet,
- [{elem, "body"}, cdata]),
- broadcast_service_message(Host, Msg);
- _ ->
- Lang = xml:get_attr_s("xml:lang", Attrs),
- ErrText = "Only service administrators "
- "are allowed to send service messages",
- Err = jlib:make_error_reply(
- Packet,
- ?ERRT_FORBIDDEN(Lang, ErrText)),
- ejabberd_router:route(
- To, From, Err)
- end
+ <<"">> ->
+ case Nick of
+ <<"">> ->
+ case Name of
+ <<"iq">> ->
+ case jlib:iq_query_info(Packet) of
+ #iq{type = get, xmlns = (?NS_DISCO_INFO) = XMLNS,
+ sub_el = _SubEl, lang = Lang} =
+ IQ ->
+ Info = ejabberd_hooks:run_fold(disco_info,
+ ServerHost, [],
+ [ServerHost, ?MODULE,
+ <<"">>, <<"">>]),
+ Res = IQ#iq{type = result,
+ sub_el =
+ [#xmlel{name = <<"query">>,
+ attrs =
+ [{<<"xmlns">>, XMLNS}],
+ children =
+ iq_disco_info(Lang) ++
+ Info}]},
+ ejabberd_router:route(To, From,
+ jlib:iq_to_xml(Res));
+ #iq{type = get, xmlns = ?NS_DISCO_ITEMS} = IQ ->
+ spawn(?MODULE, process_iq_disco_items,
+ [Host, From, To, IQ]);
+ #iq{type = get, xmlns = (?NS_REGISTER) = XMLNS,
+ lang = Lang, sub_el = _SubEl} =
+ IQ ->
+ Res = IQ#iq{type = result,
+ sub_el =
+ [#xmlel{name = <<"query">>,
+ attrs =
+ [{<<"xmlns">>, XMLNS}],
+ children =
+ iq_get_register_info(ServerHost,
+ Host,
+ From,
+ Lang)}]},
+ ejabberd_router:route(To, From,
+ jlib:iq_to_xml(Res));
+ #iq{type = set, xmlns = (?NS_REGISTER) = XMLNS,
+ lang = Lang, sub_el = SubEl} =
+ IQ ->
+ case process_iq_register_set(ServerHost, Host, From,
+ SubEl, Lang)
+ of
+ {result, IQRes} ->
+ Res = IQ#iq{type = result,
+ sub_el =
+ [#xmlel{name = <<"query">>,
+ attrs =
+ [{<<"xmlns">>,
+ XMLNS}],
+ children = IQRes}]},
+ ejabberd_router:route(To, From,
+ jlib:iq_to_xml(Res));
+ {error, Error} ->
+ Err = jlib:make_error_reply(Packet, Error),
+ ejabberd_router:route(To, From, Err)
end;
- "presence" ->
- ok
- end;
- _ ->
- case xml:get_attr_s("type", Attrs) of
- "error" ->
- ok;
- "result" ->
- ok;
+ #iq{type = get, xmlns = (?NS_VCARD) = XMLNS,
+ lang = Lang, sub_el = _SubEl} =
+ IQ ->
+ Res = IQ#iq{type = result,
+ sub_el =
+ [#xmlel{name = <<"vCard">>,
+ attrs =
+ [{<<"xmlns">>, XMLNS}],
+ children =
+ iq_get_vcard(Lang)}]},
+ ejabberd_router:route(To, From,
+ jlib:iq_to_xml(Res));
+ #iq{type = get, xmlns = ?NS_MUC_UNIQUE} = IQ ->
+ Res = IQ#iq{type = result,
+ sub_el =
+ [#xmlel{name = <<"unique">>,
+ attrs =
+ [{<<"xmlns">>,
+ ?NS_MUC_UNIQUE}],
+ children =
+ [iq_get_unique(From)]}]},
+ ejabberd_router:route(To, From,
+ jlib:iq_to_xml(Res));
+ #iq{} ->
+ Err = jlib:make_error_reply(Packet,
+ ?ERR_FEATURE_NOT_IMPLEMENTED),
+ ejabberd_router:route(To, From, Err);
+ _ -> ok
+ end;
+ <<"message">> ->
+ case xml:get_attr_s(<<"type">>, Attrs) of
+ <<"error">> -> ok;
_ ->
+ case acl:match_rule(ServerHost, AccessAdmin, From)
+ of
+ allow ->
+ Msg = xml:get_path_s(Packet,
+ [{elem, <<"body">>},
+ cdata]),
+ broadcast_service_message(Host, Msg);
+ _ ->
+ Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
+ ErrText =
+ <<"Only service administrators are allowed "
+ "to send service messages">>,
+ Err = jlib:make_error_reply(Packet,
+ ?ERRT_FORBIDDEN(Lang,
+ ErrText)),
+ ejabberd_router:route(To, From, Err)
+ end
+ end;
+ <<"presence">> -> ok
+ end;
+ _ ->
+ case xml:get_attr_s(<<"type">>, Attrs) of
+ <<"error">> -> ok;
+ <<"result">> -> ok;
+ _ ->
Err = jlib:make_error_reply(
Packet, ?ERR_ITEM_NOT_FOUND),
ejabberd_router:route(To, From, Err)
@@ -544,9 +535,9 @@ do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
_ ->
case mnesia:dirty_read(muc_online_room, {Room, Host}) of
[] ->
- Type = xml:get_attr_s("type", Attrs),
+ Type = xml:get_attr_s(<<"type">>, Attrs),
case {Name, Type} of
- {"presence", ""} ->
+ {<<"presence">>, <<"">>} ->
case check_user_can_create_room(ServerHost,
AccessCreate, From,
Room) of
@@ -560,65 +551,68 @@ do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
mod_muc_room:route(Pid, From, Nick, Packet),
ok;
false ->
- Lang = xml:get_attr_s("xml:lang", Attrs),
- ErrText = "Room creation is denied by service policy",
+ Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
+ ErrText = <<"Room creation is denied by service policy">>,
Err = jlib:make_error_reply(
Packet, ?ERRT_FORBIDDEN(Lang, ErrText)),
ejabberd_router:route(To, From, Err)
end;
_ ->
- Lang = xml:get_attr_s("xml:lang", Attrs),
- ErrText = "Conference room does not exist",
- Err = jlib:make_error_reply(
- Packet, ?ERRT_ITEM_NOT_FOUND(Lang, ErrText)),
- ejabberd_router:route(To, From, Err)
- end;
- [R] ->
- Pid = R#muc_online_room.pid,
- ?DEBUG("MUC: send to process ~p~n", [Pid]),
- mod_muc_room:route(Pid, From, Nick, Packet),
- ok
- end
+ Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
+ ErrText = <<"Conference room does not exist">>,
+ Err = jlib:make_error_reply(Packet,
+ ?ERRT_ITEM_NOT_FOUND(Lang,
+ ErrText)),
+ ejabberd_router:route(To, From, Err)
+ end;
+ [R] ->
+ Pid = R#muc_online_room.pid,
+ ?DEBUG("MUC: send to process ~p~n", [Pid]),
+ mod_muc_room:route(Pid, From, Nick, Packet),
+ ok
+ end
end.
-check_user_can_create_room(ServerHost, AccessCreate, From, RoomID) ->
+check_user_can_create_room(ServerHost, AccessCreate,
+ From, RoomID) ->
case acl:match_rule(ServerHost, AccessCreate, From) of
- allow ->
- (length(RoomID) =< gen_mod:get_module_opt(ServerHost, ?MODULE,
- max_room_id, infinite));
- _ ->
- false
+ allow ->
+ byte_size(RoomID) =<
+ gen_mod:get_module_opt(ServerHost, ?MODULE, max_room_id,
+ fun(infinity) -> infinity;
+ (I) when is_integer(I), I>0 -> I
+ end, infinity);
+ _ -> false
end.
get_rooms(ServerHost, Host) ->
LServer = jlib:nameprep(ServerHost),
- get_rooms(LServer, Host, gen_mod:db_type(LServer, ?MODULE)).
+ get_rooms(LServer, Host,
+ gen_mod:db_type(LServer, ?MODULE)).
get_rooms(_LServer, Host, mnesia) ->
- case catch mnesia:dirty_select(
- muc_room, [{#muc_room{name_host = {'_', Host}, _ = '_'},
- [],
- ['$_']}]) of
- {'EXIT', Reason} ->
- ?ERROR_MSG("~p", [Reason]),
- [];
- Rs ->
- Rs
+ case catch mnesia:dirty_select(muc_room,
+ [{#muc_room{name_host = {'_', Host},
+ _ = '_'},
+ [], ['$_']}])
+ of
+ {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]), [];
+ Rs -> Rs
end;
get_rooms(LServer, Host, odbc) ->
SHost = ejabberd_odbc:escape(Host),
- case catch ejabberd_odbc:sql_query(
- LServer, ["select name, opts from muc_room ",
- "where host='", SHost, "';"]) of
- {'EXIT', Reason} ->
- ?ERROR_MSG("~p", [Reason]),
- [];
- {selected, ["name", "opts"], RoomOpts} ->
- lists:map(
- fun({Room, Opts}) ->
- #muc_room{name_host = {Room, Host},
- opts = ejabberd_odbc:decode_term(Opts)}
- end, RoomOpts)
+ case catch ejabberd_odbc:sql_query(LServer,
+ [<<"select name, opts from muc_room ">>,
+ <<"where host='">>, SHost, <<"';">>])
+ of
+ {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]), [];
+ {selected, [<<"name">>, <<"opts">>], RoomOpts} ->
+ lists:map(fun ([Room, Opts]) ->
+ #muc_room{name_host = {Room, Host},
+ opts = opts_to_binary(
+ ejabberd_odbc:decode_term(Opts))}
+ end,
+ RoomOpts)
end.
load_permanent_rooms(Host, ServerHost, Access, HistorySize, RoomShaper) ->
@@ -667,50 +661,77 @@ register_room(Host, Room, Pid) ->
iq_disco_info(Lang) ->
- [{xmlelement, "identity",
- [{"category", "conference"},
- {"type", "text"},
- {"name", translate:translate(Lang, "Chatrooms")}], []},
- {xmlelement, "feature", [{"var", ?NS_DISCO_INFO}], []},
- {xmlelement, "feature", [{"var", ?NS_DISCO_ITEMS}], []},
- {xmlelement, "feature", [{"var", ?NS_MUC}], []},
- {xmlelement, "feature", [{"var", ?NS_MUC_UNIQUE}], []},
- {xmlelement, "feature", [{"var", ?NS_REGISTER}], []},
- {xmlelement, "feature", [{"var", ?NS_RSM}], []},
- {xmlelement, "feature", [{"var", ?NS_VCARD}], []}].
-
+ [#xmlel{name = <<"identity">>,
+ attrs =
+ [{<<"category">>, <<"conference">>},
+ {<<"type">>, <<"text">>},
+ {<<"name">>,
+ translate:translate(Lang, <<"Chatrooms">>)}],
+ children = []},
+ #xmlel{name = <<"feature">>,
+ attrs = [{<<"var">>, ?NS_DISCO_INFO}], children = []},
+ #xmlel{name = <<"feature">>,
+ attrs = [{<<"var">>, ?NS_DISCO_ITEMS}], children = []},
+ #xmlel{name = <<"feature">>,
+ attrs = [{<<"var">>, ?NS_MUC}], children = []},
+ #xmlel{name = <<"feature">>,
+ attrs = [{<<"var">>, ?NS_MUC_UNIQUE}], children = []},
+ #xmlel{name = <<"feature">>,
+ attrs = [{<<"var">>, ?NS_REGISTER}], children = []},
+ #xmlel{name = <<"feature">>,
+ attrs = [{<<"var">>, ?NS_RSM}], children = []},
+ #xmlel{name = <<"feature">>,
+ attrs = [{<<"var">>, ?NS_VCARD}], children = []}].
iq_disco_items(Host, From, Lang, none) ->
- lists:zf(fun(#muc_online_room{name_host = {Name, _Host}, pid = Pid}) ->
- case catch gen_fsm:sync_send_all_state_event(
- Pid, {get_disco_item, From, Lang}, 100) of
- {item, Desc} ->
- flush(),
- {true,
- {xmlelement, "item",
- [{"jid", jlib:jid_to_string({Name, Host, ""})},
- {"name", Desc}], []}};
- _ ->
- false
+ lists:zf(fun (#muc_online_room{name_host =
+ {Name, _Host},
+ pid = Pid}) ->
+ case catch gen_fsm:sync_send_all_state_event(Pid,
+ {get_disco_item,
+ From, Lang},
+ 100)
+ of
+ {item, Desc} ->
+ flush(),
+ {true,
+ #xmlel{name = <<"item">>,
+ attrs =
+ [{<<"jid">>,
+ jlib:jid_to_string({Name, Host,
+ <<"">>})},
+ {<<"name">>, Desc}],
+ children = []}};
+ _ -> false
end
end, get_vh_rooms(Host));
iq_disco_items(Host, From, Lang, Rsm) ->
{Rooms, RsmO} = get_vh_rooms(Host, Rsm),
RsmOut = jlib:rsm_encode(RsmO),
- lists:zf(fun(#muc_online_room{name_host = {Name, _Host}, pid = Pid}) ->
- case catch gen_fsm:sync_send_all_state_event(
- Pid, {get_disco_item, From, Lang}, 100) of
- {item, Desc} ->
- flush(),
- {true,
- {xmlelement, "item",
- [{"jid", jlib:jid_to_string({Name, Host, ""})},
- {"name", Desc}], []}};
- _ ->
- false
+ lists:zf(fun (#muc_online_room{name_host =
+ {Name, _Host},
+ pid = Pid}) ->
+ case catch gen_fsm:sync_send_all_state_event(Pid,
+ {get_disco_item,
+ From, Lang},
+ 100)
+ of
+ {item, Desc} ->
+ flush(),
+ {true,
+ #xmlel{name = <<"item">>,
+ attrs =
+ [{<<"jid">>,
+ jlib:jid_to_string({Name, Host,
+ <<"">>})},
+ {<<"name">>, Desc}],
+ children = []}};
+ _ -> false
end
- end, Rooms) ++ RsmOut.
+ end,
+ Rooms)
+ ++ RsmOut.
get_vh_rooms(Host, #rsm_in{max=M, direction=Direction, id=I, index=Index})->
AllRooms = lists:sort(get_vh_rooms(Host)),
@@ -736,16 +757,16 @@ get_vh_rooms(Host, #rsm_in{max=M, direction=Direction, id=I, index=Index})->
true ->
lists:sublist(L, Index+1, M)
end,
- if
- L2 == [] ->
- {L2, #rsm_out{count=Count}};
- true ->
- H = hd(L2),
- NewIndex = get_room_pos(H, AllRooms),
- T=lists:last(L2),
- {F, _}=H#muc_online_room.name_host,
- {Last, _}=T#muc_online_room.name_host,
- {L2, #rsm_out{first=F, last=Last, count=Count, index=NewIndex}}
+ if L2 == [] -> {L2, #rsm_out{count = Count}};
+ true ->
+ H = hd(L2),
+ NewIndex = get_room_pos(H, AllRooms),
+ T = lists:last(L2),
+ {F, _} = H#muc_online_room.name_host,
+ {Last, _} = T#muc_online_room.name_host,
+ {L2,
+ #rsm_out{first = F, last = Last, count = Count,
+ index = NewIndex}}
end.
%% @doc Return the position of desired room in the list of rooms.
@@ -753,27 +774,17 @@ get_vh_rooms(Host, #rsm_in{max=M, direction=Direction, id=I, index=Index})->
%% @spec (Desired::muc_online_room(), Rooms::[muc_online_room()]) -> integer()
get_room_pos(Desired, Rooms) ->
get_room_pos(Desired, Rooms, 0).
+
get_room_pos(Desired, [HeadRoom | _], HeadPosition)
- when (Desired#muc_online_room.name_host ==
- HeadRoom#muc_online_room.name_host) ->
+ when Desired#muc_online_room.name_host ==
+ HeadRoom#muc_online_room.name_host ->
HeadPosition;
get_room_pos(Desired, [_ | Rooms], HeadPosition) ->
get_room_pos(Desired, Rooms, HeadPosition + 1).
-flush() ->
- receive
- _ ->
- flush()
- after 0 ->
- ok
- end.
+flush() -> receive _ -> flush() after 0 -> ok end.
-define(XFIELD(Type, Label, Var, Val),
- {xmlelement, "field", [{"type", Type},
- {"label", translate:translate(Lang, Label)},
- {"var", Var}],
- [{xmlelement, "value", [], [{xmlcdata, Val}]}]}).
-
%% @doc Get a pseudo unique Room Name. The Room Name is generated as a hash of
%% the requester JID, the local time and a random salt.
%%
@@ -781,195 +792,216 @@ flush() ->
%% with the returned Name already created, nor mark the generated Name
%% as "already used". But in practice, it is unique enough. See
%% http://xmpp.org/extensions/xep-0045.html#createroom-unique
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"type">>, Type},
+ {<<"label">>, translate:translate(Lang, Label)},
+ {<<"var">>, Var}],
+ children =
+ [#xmlel{name = <<"value">>, attrs = [],
+ children = [{xmlcdata, Val}]}]}).
+
iq_get_unique(From) ->
- {xmlcdata, sha:sha(term_to_binary([From, now(), randoms:get_string()]))}.
+ {xmlcdata,
+ sha:sha(term_to_binary([From, now(),
+ randoms:get_string()]))}.
get_nick(ServerHost, Host, From) ->
LServer = jlib:nameprep(ServerHost),
- get_nick(LServer, Host, From, gen_mod:db_type(LServer, ?MODULE)).
+ get_nick(LServer, Host, From,
+ gen_mod:db_type(LServer, ?MODULE)).
get_nick(_LServer, Host, From, mnesia) ->
{LUser, LServer, _} = jlib:jid_tolower(From),
LUS = {LUser, LServer},
- case catch mnesia:dirty_read(muc_registered, {LUS, Host}) of
- {'EXIT', _Reason} ->
- error;
- [] ->
- error;
- [#muc_registered{nick = Nick}] ->
- Nick
+ case catch mnesia:dirty_read(muc_registered,
+ {LUS, Host})
+ of
+ {'EXIT', _Reason} -> error;
+ [] -> error;
+ [#muc_registered{nick = Nick}] -> Nick
end;
get_nick(LServer, Host, From, odbc) ->
- SJID = ejabberd_odbc:escape(
- jlib:jid_to_string(
- jlib:jid_tolower(
- jlib:jid_remove_resource(From)))),
+ SJID =
+ ejabberd_odbc:escape(jlib:jid_to_string(jlib:jid_tolower(jlib:jid_remove_resource(From)))),
SHost = ejabberd_odbc:escape(Host),
- case catch ejabberd_odbc:sql_query(
- LServer, ["select nick from muc_registered where "
- "jid='", SJID, "' and host='", SHost, "';"]) of
- {selected, ["nick"], [{Nick}]} ->
- Nick;
- _ ->
- error
+ case catch ejabberd_odbc:sql_query(LServer,
+ [<<"select nick from muc_registered where "
+ "jid='">>,
+ SJID, <<"' and host='">>, SHost,
+ <<"';">>])
+ of
+ {selected, [<<"nick">>], [[Nick]]} -> Nick;
+ _ -> error
end.
iq_get_register_info(ServerHost, Host, From, Lang) ->
- {Nick, Registered} =
- case get_nick(ServerHost, Host, From) of
- error ->
- {"", []};
- N ->
- {N, [{xmlelement, "registered", [], []}]}
- end,
+ {Nick, Registered} = case get_nick(ServerHost, Host,
+ From)
+ of
+ error -> {<<"">>, []};
+ N ->
+ {N,
+ [#xmlel{name = <<"registered">>, attrs = [],
+ children = []}]}
+ end,
Registered ++
- [{xmlelement, "instructions", [],
- [{xmlcdata,
- translate:translate(
- Lang, "You need a client that supports x:data to register the nickname")}]},
- {xmlelement, "x",
- [{"xmlns", ?NS_XDATA}],
- [{xmlelement, "title", [],
- [{xmlcdata,
- translate:translate(
- Lang, "Nickname Registration at ") ++ Host}]},
- {xmlelement, "instructions", [],
- [{xmlcdata,
- translate:translate(
- Lang, "Enter nickname you want to register")}]},
- ?XFIELD("text-single", "Nickname", "nick", Nick)]}].
+ [#xmlel{name = <<"instructions">>, attrs = [],
+ children =
+ [{xmlcdata,
+ translate:translate(Lang,
+ <<"You need a client that supports x:data "
+ "to register the nickname">>)}]},
+ #xmlel{name = <<"x">>,
+ attrs = [{<<"xmlns">>, ?NS_XDATA}],
+ children =
+ [#xmlel{name = <<"title">>, attrs = [],
+ children =
+ [{xmlcdata,
+ <<(translate:translate(Lang,
+ <<"Nickname Registration at ">>))/binary,
+ Host/binary>>}]},
+ #xmlel{name = <<"instructions">>, attrs = [],
+ children =
+ [{xmlcdata,
+ translate:translate(Lang,
+ <<"Enter nickname you want to register">>)}]},
+ ?XFIELD(<<"text-single">>, <<"Nickname">>, <<"nick">>,
+ Nick)]}].
set_nick(ServerHost, Host, From, Nick) ->
LServer = jlib:nameprep(ServerHost),
- set_nick(LServer, Host, From, Nick, gen_mod:db_type(LServer, ?MODULE)).
+ set_nick(LServer, Host, From, Nick,
+ gen_mod:db_type(LServer, ?MODULE)).
set_nick(_LServer, Host, From, Nick, mnesia) ->
{LUser, LServer, _} = jlib:jid_tolower(From),
LUS = {LUser, LServer},
- F = fun() ->
+ F = fun () ->
case Nick of
- "" ->
- mnesia:delete({muc_registered, {LUS, Host}}),
- ok;
- _ ->
- Allow =
- case mnesia:select(
- muc_registered,
- [{#muc_registered{us_host = '$1',
- nick = Nick,
- _ = '_'},
- [{'==', {element, 2, '$1'}, Host}],
- ['$_']}]) of
- [] ->
- true;
+ <<"">> ->
+ mnesia:delete({muc_registered, {LUS, Host}}), ok;
+ _ ->
+ Allow = case mnesia:select(muc_registered,
+ [{#muc_registered{us_host =
+ '$1',
+ nick = Nick,
+ _ = '_'},
+ [{'==', {element, 2, '$1'},
+ Host}],
+ ['$_']}])
+ of
+ [] -> true;
[#muc_registered{us_host = {U, _Host}}] ->
U == LUS
- end,
- if
- Allow ->
- mnesia:write(
- #muc_registered{us_host = {LUS, Host},
- nick = Nick}),
- ok;
- true ->
- false
- end
+ end,
+ if Allow ->
+ mnesia:write(#muc_registered{us_host = {LUS, Host},
+ nick = Nick}),
+ ok;
+ true -> false
+ end
end
end,
mnesia:transaction(F);
set_nick(LServer, Host, From, Nick, odbc) ->
- JID = jlib:jid_to_string(
- jlib:jid_tolower(
- jlib:jid_remove_resource(From))),
+ JID =
+ jlib:jid_to_string(jlib:jid_tolower(jlib:jid_remove_resource(From))),
SJID = ejabberd_odbc:escape(JID),
SNick = ejabberd_odbc:escape(Nick),
SHost = ejabberd_odbc:escape(Host),
- F = fun() ->
- case Nick of
- "" ->
- ejabberd_odbc:sql_query_t(
- ["delete from muc_registered where ",
- "jid='", SJID, "' and host='", Host, "';"]),
- ok;
- _ ->
- Allow =
- case ejabberd_odbc:sql_query_t(
- ["select jid from muc_registered ",
- "where nick='", SNick, "' and host='",
- SHost, "';"]) of
- {selected, ["jid"], [{J}]} ->
- J == JID;
- _ ->
- true
- end,
- if Allow ->
- odbc_queries:update_t(
- "muc_registered",
- ["jid", "host", "nick"],
- [SJID, SHost, SNick],
- ["jid='", SJID, "' and host='", SHost, "'"]),
- ok;
- true ->
- false
- end
- end
- end,
+ F = fun () ->
+ case Nick of
+ <<"">> ->
+ ejabberd_odbc:sql_query_t([<<"delete from muc_registered where ">>,
+ <<"jid='">>, SJID,
+ <<"' and host='">>, Host,
+ <<"';">>]),
+ ok;
+ _ ->
+ Allow = case
+ ejabberd_odbc:sql_query_t([<<"select jid from muc_registered ">>,
+ <<"where nick='">>,
+ SNick,
+ <<"' and host='">>,
+ SHost, <<"';">>])
+ of
+ {selected, [<<"jid">>], [[J]]} -> J == JID;
+ _ -> true
+ end,
+ if Allow ->
+ odbc_queries:update_t(<<"muc_registered">>,
+ [<<"jid">>, <<"host">>,
+ <<"nick">>],
+ [SJID, SHost, SNick],
+ [<<"jid='">>, SJID,
+ <<"' and host='">>, SHost,
+ <<"'">>]),
+ ok;
+ true -> false
+ end
+ end
+ end,
ejabberd_odbc:sql_transaction(LServer, F).
-iq_set_register_info(ServerHost, Host, From, Nick, Lang) ->
+iq_set_register_info(ServerHost, Host, From, Nick,
+ Lang) ->
case set_nick(ServerHost, Host, From, Nick) of
- {atomic, ok} ->
- {result, []};
- {atomic, false} ->
- ErrText = "That nickname is registered by another person",
- {error, ?ERRT_CONFLICT(Lang, ErrText)};
- _ ->
- {error, ?ERR_INTERNAL_SERVER_ERROR}
+ {atomic, ok} -> {result, []};
+ {atomic, false} ->
+ ErrText = <<"That nickname is registered by another "
+ "person">>,
+ {error, ?ERRT_CONFLICT(Lang, ErrText)};
+ _ -> {error, ?ERR_INTERNAL_SERVER_ERROR}
end.
-process_iq_register_set(ServerHost, Host, From, SubEl, Lang) ->
- {xmlelement, _Name, _Attrs, Els} = SubEl,
- case xml:get_subtag(SubEl, "remove") of
- false ->
- case xml:remove_cdata(Els) of
- [{xmlelement, "x", _Attrs1, _Els1} = XEl] ->
- case {xml:get_tag_attr_s("xmlns", XEl),
- xml:get_tag_attr_s("type", XEl)} of
- {?NS_XDATA, "cancel"} ->
- {result, []};
- {?NS_XDATA, "submit"} ->
- XData = jlib:parse_xdata_submit(XEl),
- case XData of
- invalid ->
- {error, ?ERR_BAD_REQUEST};
- _ ->
- case lists:keysearch("nick", 1, XData) of
- {value, {_, [Nick]}} when Nick /= "" ->
- iq_set_register_info(ServerHost, Host,
- From, Nick, Lang);
- _ ->
- ErrText = "You must fill in field \"Nickname\" in the form",
- {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)}
- end
- end;
+process_iq_register_set(ServerHost, Host, From, SubEl,
+ Lang) ->
+ #xmlel{children = Els} = SubEl,
+ case xml:get_subtag(SubEl, <<"remove">>) of
+ false ->
+ case xml:remove_cdata(Els) of
+ [#xmlel{name = <<"x">>} = XEl] ->
+ case {xml:get_tag_attr_s(<<"xmlns">>, XEl),
+ xml:get_tag_attr_s(<<"type">>, XEl)}
+ of
+ {?NS_XDATA, <<"cancel">>} -> {result, []};
+ {?NS_XDATA, <<"submit">>} ->
+ XData = jlib:parse_xdata_submit(XEl),
+ case XData of
+ invalid -> {error, ?ERR_BAD_REQUEST};
_ ->
- {error, ?ERR_BAD_REQUEST}
- end;
- _ ->
- {error, ?ERR_BAD_REQUEST}
- end;
- _ ->
- iq_set_register_info(ServerHost, Host, From, "", Lang)
+ case lists:keysearch(<<"nick">>, 1, XData) of
+ {value, {_, [Nick]}} when Nick /= <<"">> ->
+ iq_set_register_info(ServerHost, Host, From,
+ Nick, Lang);
+ _ ->
+ ErrText =
+ <<"You must fill in field \"Nickname\" "
+ "in the form">>,
+ {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)}
+ end
+ end;
+ _ -> {error, ?ERR_BAD_REQUEST}
+ end;
+ _ -> {error, ?ERR_BAD_REQUEST}
+ end;
+ _ ->
+ iq_set_register_info(ServerHost, Host, From, <<"">>,
+ Lang)
end.
iq_get_vcard(Lang) ->
- [{xmlelement, "FN", [],
- [{xmlcdata, "ejabberd/mod_muc"}]},
- {xmlelement, "URL", [],
- [{xmlcdata, ?EJABBERD_URI}]},
- {xmlelement, "DESC", [],
- [{xmlcdata, translate:translate(Lang, "ejabberd MUC module") ++
- "\nCopyright (c) 2003-2013 ProcessOne"}]}].
+ [#xmlel{name = <<"FN">>, attrs = [],
+ children = [{xmlcdata, <<"ejabberd/mod_muc">>}]},
+ #xmlel{name = <<"URL">>, attrs = [],
+ children = [{xmlcdata, ?EJABBERD_URI}]},
+ #xmlel{name = <<"DESC">>, attrs = [],
+ children =
+ [{xmlcdata,
+ <<(translate:translate(Lang,
+ <<"ejabberd MUC module">>))/binary,
+ "\nCopyright (c) 2003-2013 ProcessOne">>}]}].
broadcast_service_message(Host, Msg) ->
@@ -979,6 +1011,7 @@ broadcast_service_message(Host, Msg) ->
Pid, {service_message, Msg})
end, get_vh_rooms(Host)).
+
get_vh_rooms(Host) ->
mnesia:dirty_select(muc_online_room,
[{#muc_online_room{name_host = '$1', _ = '_'},
@@ -1014,6 +1047,44 @@ clean_table_from_bad_node(Node, Host) ->
end,
mnesia:async_dirty(F).
+opts_to_binary(Opts) ->
+ lists:map(
+ fun({title, Title}) ->
+ {title, iolist_to_binary(Title)};
+ ({description, Desc}) ->
+ {description, iolist_to_binary(Desc)};
+ ({password, Pass}) ->
+ {password, iolist_to_binary(Pass)};
+ ({subject, Subj}) ->
+ {subject, iolist_to_binary(Subj)};
+ ({subject_author, Author}) ->
+ {subject_author, iolist_to_binary(Author)};
+ ({affiliations, Affs}) ->
+ {affiliations, lists:map(
+ fun({{U, S, R}, Aff}) ->
+ NewAff =
+ case Aff of
+ {A, Reason} ->
+ {A, iolist_to_binary(Reason)};
+ _ ->
+ Aff
+ end,
+ {{iolist_to_binary(U),
+ iolist_to_binary(S),
+ iolist_to_binary(R)},
+ NewAff}
+ end, Affs)};
+ ({captcha_whitelist, CWList}) ->
+ {captcha_whitelist, lists:map(
+ fun({U, S, R}) ->
+ {iolist_to_binary(U),
+ iolist_to_binary(S),
+ iolist_to_binary(R)}
+ end, CWList)};
+ (Opt) ->
+ Opt
+ end, Opts).
+
update_tables(Host) ->
update_muc_room_table(Host),
update_muc_registered_table(Host).
@@ -1021,83 +1092,36 @@ update_tables(Host) ->
update_muc_room_table(Host) ->
Fields = record_info(fields, muc_room),
case mnesia:table_info(muc_room, attributes) of
- Fields ->
- ok;
- [name, opts] ->
- ?INFO_MSG("Converting muc_room table from "
- "{name, opts} format", []),
- {atomic, ok} = mnesia:create_table(
- mod_muc_tmp_table,
- [{disc_only_copies, [node()]},
- {type, bag},
- {local_content, true},
- {record_name, muc_room},
- {attributes, record_info(fields, muc_room)}]),
- mnesia:transform_table(muc_room, ignore, Fields),
- F1 = fun() ->
- mnesia:write_lock_table(mod_muc_tmp_table),
- mnesia:foldl(
- fun(#muc_room{name_host = Name} = R, _) ->
- mnesia:dirty_write(
- mod_muc_tmp_table,
- R#muc_room{name_host = {Name, Host}})
- end, ok, muc_room)
- end,
- mnesia:transaction(F1),
- mnesia:clear_table(muc_room),
- F2 = fun() ->
- mnesia:write_lock_table(muc_room),
- mnesia:foldl(
- fun(R, _) ->
- mnesia:dirty_write(R)
- end, ok, mod_muc_tmp_table)
- end,
- mnesia:transaction(F2),
- mnesia:delete_table(mod_muc_tmp_table);
- _ ->
- ?INFO_MSG("Recreating muc_room table", []),
- mnesia:transform_table(muc_room, ignore, Fields)
+ Fields ->
+ ejabberd_config:convert_table_to_binary(
+ muc_room, Fields, set,
+ fun(#muc_room{name_host = {N, _}}) -> N end,
+ fun(#muc_room{name_host = {N, H},
+ opts = Opts} = R) ->
+ R#muc_room{name_host = {iolist_to_binary(N),
+ iolist_to_binary(H)},
+ opts = opts_to_binary(Opts)}
+ end);
+ _ ->
+ ?INFO_MSG("Recreating muc_room table", []),
+ mnesia:transform_table(muc_room, ignore, Fields)
end.
-
update_muc_registered_table(Host) ->
Fields = record_info(fields, muc_registered),
case mnesia:table_info(muc_registered, attributes) of
- Fields ->
- ok;
- [user, nick] ->
- ?INFO_MSG("Converting muc_registered table from "
- "{user, nick} format", []),
- {atomic, ok} = mnesia:create_table(
- mod_muc_tmp_table,
- [{disc_only_copies, [node()]},
- {type, bag},
- {local_content, true},
- {record_name, muc_registered},
- {attributes, record_info(fields, muc_registered)}]),
- mnesia:del_table_index(muc_registered, nick),
- mnesia:transform_table(muc_registered, ignore, Fields),
- F1 = fun() ->
- mnesia:write_lock_table(mod_muc_tmp_table),
- mnesia:foldl(
- fun(#muc_registered{us_host = US} = R, _) ->
- mnesia:dirty_write(
- mod_muc_tmp_table,
- R#muc_registered{us_host = {US, Host}})
- end, ok, muc_registered)
- end,
- mnesia:transaction(F1),
- mnesia:clear_table(muc_registered),
- F2 = fun() ->
- mnesia:write_lock_table(muc_registered),
- mnesia:foldl(
- fun(R, _) ->
- mnesia:dirty_write(R)
- end, ok, mod_muc_tmp_table)
- end,
- mnesia:transaction(F2),
- mnesia:delete_table(mod_muc_tmp_table);
- _ ->
- ?INFO_MSG("Recreating muc_registered table", []),
- mnesia:transform_table(muc_registered, ignore, Fields)
+ Fields ->
+ ejabberd_config:convert_table_to_binary(
+ muc_registered, Fields, set,
+ fun(#muc_registered{us_host = {_, H}}) -> H end,
+ fun(#muc_registered{us_host = {{U, S}, H},
+ nick = Nick} = R) ->
+ R#muc_registered{us_host = {{iolist_to_binary(U),
+ iolist_to_binary(S)},
+ iolist_to_binary(H)},
+ nick = iolist_to_binary(Nick)}
+ end);
+ _ ->
+ ?INFO_MSG("Recreating muc_registered table", []),
+ mnesia:transform_table(muc_registered, ignore, Fields)
end.
diff --git a/src/mod_muc/mod_muc_log.erl b/src/mod_muc/mod_muc_log.erl
index 71dc8a0b2..ee9eb7a9d 100644
--- a/src/mod_muc/mod_muc_log.erl
+++ b/src/mod_muc/mod_muc_log.erl
@@ -25,35 +25,37 @@
%%%----------------------------------------------------------------------
-module(mod_muc_log).
+
-author('badlop@process-one.net').
-behaviour(gen_server).
+
-behaviour(gen_mod).
%% API
--export([start_link/2,
- start/2,
- stop/1,
- check_access_log/2,
- add_to_log/5]).
+-export([start_link/2, start/2, stop/1,
+ check_access_log/2, add_to_log/5]).
%% gen_server callbacks
--export([init/1, handle_call/3, handle_cast/2, handle_info/2,
- terminate/2, code_change/3]).
+-export([init/1, handle_call/3, handle_cast/2,
+ handle_info/2, terminate/2, code_change/3]).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
+
-include("mod_muc_room.hrl").
%% Copied from mod_muc/mod_muc.erl
--record(muc_online_room, {name_host, pid}).
+-record(muc_online_room, {name_host = {<<>>, <<>>} :: {binary(), binary()},
+ pid = self() :: pid()}).
-define(T(Text), translate:translate(Lang, Text)).
-define(PROCNAME, ejabberd_mod_muc_log).
-record(room, {jid, title, subject, subject_author, config}).
--define(PLAINTEXT_IN, "ZZIZZ").
--define(PLAINTEXT_OUT, "ZZOZZ").
+-define(PLAINTEXT_IN, <<"ZZIZZ">>).
+-define(PLAINTEXT_OUT, <<"ZZOZZ">>).
-record(logstate, {host,
out_dir,
@@ -101,11 +103,10 @@ add_to_log(Host, Type, Data, Room, Opts) ->
check_access_log(Host, From) ->
case catch gen_server:call(get_proc_name(Host),
- {check_access_log, Host, From}) of
- {'EXIT', _Error} ->
- deny;
- Res ->
- Res
+ {check_access_log, Host, From})
+ of
+ {'EXIT', _Error} -> deny;
+ Res -> Res
end.
%%====================================================================
@@ -120,36 +121,52 @@ check_access_log(Host, From) ->
%% Description: Initiates the server
%%--------------------------------------------------------------------
init([Host, Opts]) ->
- OutDir = gen_mod:get_opt(outdir, Opts, "www/muc"),
- DirType = gen_mod:get_opt(dirtype, Opts, subdirs),
- DirName = gen_mod:get_opt(dirname, Opts, room_jid),
- FileFormat = gen_mod:get_opt(file_format, Opts, html), % Allowed values: html|plaintext
- FilePermissions = gen_mod:get_opt(file_permissions, Opts, {644, 33}),
- CSSFile = gen_mod:get_opt(cssfile, Opts, false),
- AccessLog = gen_mod:get_opt(access_log, Opts, muc_admin),
- Timezone = gen_mod:get_opt(timezone, Opts, local),
- Top_link = gen_mod:get_opt(top_link, Opts, {"/", "Home"}),
- NoFollow = gen_mod:get_opt(spam_prevention, Opts, true),
- Lang = case ejabberd_config:get_local_option({language, Host}) of
- undefined ->
- case ejabberd_config:get_global_option(language) of
- undefined -> "en";
- L -> L
- end;
- L -> L
- end,
- {ok, #logstate{host = Host,
- out_dir = OutDir,
- dir_type = DirType,
- dir_name = DirName,
- file_format = FileFormat,
- file_permissions = FilePermissions,
- css_file = CSSFile,
- access = AccessLog,
- lang = Lang,
- timezone = Timezone,
- spam_prevention = NoFollow,
- top_link = Top_link}}.
+ OutDir = gen_mod:get_opt(outdir, Opts,
+ fun iolist_to_binary/1,
+ <<"www/muc">>),
+ DirType = gen_mod:get_opt(dirtype, Opts,
+ fun(subdirs) -> subdirs;
+ (plain) -> plain
+ end, subdirs),
+ DirName = gen_mod:get_opt(dirname, Opts,
+ fun(room_jid) -> room_jid;
+ (room_name) -> room_name
+ end, room_jid),
+ FileFormat = gen_mod:get_opt(file_format, Opts,
+ fun(html) -> html;
+ (plaintext) -> plaintext
+ end, html),
+ FilePermissions = gen_mod:get_opt(file_permissions, Opts,
+ fun({A, B}) -> {A, B}
+ end, {644, 33}),
+ CSSFile = gen_mod:get_opt(cssfile, Opts,
+ fun iolist_to_binary/1,
+ false),
+ AccessLog = gen_mod:get_opt(access_log, Opts,
+ fun(A) when is_atom(A) -> A end,
+ muc_admin),
+ Timezone = gen_mod:get_opt(timezone, Opts,
+ fun(local) -> local;
+ (universal) -> universal
+ end, local),
+ Top_link = gen_mod:get_opt(top_link, Opts,
+ fun({S1, S2}) ->
+ {iolist_to_binary(S1),
+ iolist_to_binary(S2)}
+ end, {<<"/">>, <<"Home">>}),
+ NoFollow = gen_mod:get_opt(spam_prevention, Opts,
+ fun(B) when is_boolean(B) -> B end,
+ true),
+ Lang = ejabberd_config:get_local_option(
+ {language, Host},
+ fun iolist_to_binary/1,
+ ?MYLANG),
+ {ok,
+ #logstate{host = Host, out_dir = OutDir,
+ dir_type = DirType, dir_name = DirName,
+ file_format = FileFormat, file_permissions = FilePermissions, css_file = CSSFile,
+ access = AccessLog, lang = Lang, timezone = Timezone,
+ spam_prevention = NoFollow, top_link = Top_link}}.
%%--------------------------------------------------------------------
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
@@ -174,14 +191,11 @@ handle_call(stop, _From, State) ->
%%--------------------------------------------------------------------
handle_cast({add_to_log, Type, Data, Room, Opts}, State) ->
case catch add_to_log2(Type, Data, Room, Opts, State) of
- {'EXIT', Reason} ->
- ?ERROR_MSG("~p", [Reason]);
- _ ->
- ok
+ {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]);
+ _ -> ok
end,
{noreply, State};
-handle_cast(_Msg, State) ->
- {noreply, State}.
+handle_cast(_Msg, State) -> {noreply, State}.
%%--------------------------------------------------------------------
%% Function: handle_info(Info, State) -> {noreply, State} |
@@ -189,8 +203,7 @@ handle_cast(_Msg, State) ->
%% {stop, Reason, State}
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------
-handle_info(_Info, State) ->
- {noreply, State}.
+handle_info(_Info, State) -> {noreply, State}.
%%--------------------------------------------------------------------
%% Function: terminate(Reason, State) -> void()
@@ -199,763 +212,982 @@ handle_info(_Info, State) ->
%% cleaning up. When it returns, the gen_server terminates with Reason.
%% The return value is ignored.
%%--------------------------------------------------------------------
-terminate(_Reason, _State) ->
- ok.
+terminate(_Reason, _State) -> ok.
%%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
%% Description: Convert process state when code is changed
%%--------------------------------------------------------------------
-code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
+code_change(_OldVsn, State, _Extra) -> {ok, State}.
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
add_to_log2(text, {Nick, Packet}, Room, Opts, State) ->
- case {xml:get_subtag(Packet, "subject"), xml:get_subtag(Packet, "body")} of
- {false, false} ->
- ok;
- {false, SubEl} ->
- Message = {body, xml:get_tag_cdata(SubEl)},
- add_message_to_log(Nick, Message, Room, Opts, State);
- {SubEl, _} ->
- Message = {subject, xml:get_tag_cdata(SubEl)},
- add_message_to_log(Nick, Message, Room, Opts, State)
+ case {xml:get_subtag(Packet, <<"subject">>),
+ xml:get_subtag(Packet, <<"body">>)}
+ of
+ {false, false} -> ok;
+ {false, SubEl} ->
+ Message = {body, xml:get_tag_cdata(SubEl)},
+ add_message_to_log(Nick, Message, Room, Opts, State);
+ {SubEl, _} ->
+ Message = {subject, xml:get_tag_cdata(SubEl)},
+ add_message_to_log(Nick, Message, Room, Opts, State)
end;
-
-add_to_log2(roomconfig_change, _Occupants, Room, Opts, State) ->
- add_message_to_log("", roomconfig_change, Room, Opts, State);
-
-add_to_log2(roomconfig_change_enabledlogging, Occupants, Room, Opts, State) ->
- add_message_to_log("", {roomconfig_change, Occupants}, Room, Opts, State);
-
-add_to_log2(room_existence, NewStatus, Room, Opts, State) ->
- add_message_to_log("", {room_existence, NewStatus}, Room, Opts, State);
-
-add_to_log2(nickchange, {OldNick, NewNick}, Room, Opts, State) ->
- add_message_to_log(NewNick, {nickchange, OldNick}, Room, Opts, State);
-
+add_to_log2(roomconfig_change, _Occupants, Room, Opts,
+ State) ->
+ add_message_to_log(<<"">>, roomconfig_change, Room,
+ Opts, State);
+add_to_log2(roomconfig_change_enabledlogging, Occupants,
+ Room, Opts, State) ->
+ add_message_to_log(<<"">>,
+ {roomconfig_change, Occupants}, Room, Opts, State);
+add_to_log2(room_existence, NewStatus, Room, Opts,
+ State) ->
+ add_message_to_log(<<"">>, {room_existence, NewStatus},
+ Room, Opts, State);
+add_to_log2(nickchange, {OldNick, NewNick}, Room, Opts,
+ State) ->
+ add_message_to_log(NewNick, {nickchange, OldNick}, Room,
+ Opts, State);
add_to_log2(join, Nick, Room, Opts, State) ->
add_message_to_log(Nick, join, Room, Opts, State);
-
add_to_log2(leave, {Nick, Reason}, Room, Opts, State) ->
case Reason of
- "" -> add_message_to_log(Nick, leave, Room, Opts, State);
- _ -> add_message_to_log(Nick, {leave, Reason}, Room, Opts, State)
+ <<"">> ->
+ add_message_to_log(Nick, leave, Room, Opts, State);
+ _ ->
+ add_message_to_log(Nick, {leave, Reason}, Room, Opts,
+ State)
end;
-
-add_to_log2(kickban, {Nick, Reason, Code}, Room, Opts, State) ->
- add_message_to_log(Nick, {kickban, Code, Reason}, Room, Opts, State).
-
+add_to_log2(kickban, {Nick, Reason, Code}, Room, Opts,
+ State) ->
+ add_message_to_log(Nick, {kickban, Code, Reason}, Room,
+ Opts, State).
%%----------------------------------------------------------------------
%% Core
-build_filename_string(TimeStamp, OutDir, RoomJID, DirType, DirName, FileFormat) ->
+build_filename_string(TimeStamp, OutDir, RoomJID,
+ DirType, DirName, FileFormat) ->
{{Year, Month, Day}, _Time} = TimeStamp,
-
- %% Directory and file names
- {Dir, Filename, Rel} =
- case DirType of
- subdirs ->
- SYear = lists:flatten(io_lib:format("~4..0w", [Year])),
- SMonth = lists:flatten(io_lib:format("~2..0w", [Month])),
- SDay = lists:flatten(io_lib:format("~2..0w", [Day])),
- {filename:join(SYear, SMonth), SDay, "../.."};
- plain ->
- Date = lists:flatten(
- io_lib:format("~4..0w-~2..0w-~2..0w",
- [Year, Month, Day])),
- {"", Date, "."}
- end,
-
+ {Dir, Filename, Rel} = case DirType of
+ subdirs ->
+ SYear =
+ iolist_to_binary(io_lib:format("~4..0w",
+ [Year])),
+ SMonth =
+ iolist_to_binary(io_lib:format("~2..0w",
+ [Month])),
+ SDay = iolist_to_binary(io_lib:format("~2..0w",
+ [Day])),
+ {fjoin([SYear, SMonth]), SDay,
+ <<"../..">>};
+ plain ->
+ Date =
+ iolist_to_binary(io_lib:format("~4..0w-~2..0w-~2..0w",
+ [Year,
+ Month,
+ Day])),
+ {<<"">>, Date, <<".">>}
+ end,
RoomString = case DirName of
- room_jid -> RoomJID;
- room_name -> get_room_name(RoomJID)
+ room_jid -> RoomJID;
+ room_name -> get_room_name(RoomJID)
end,
Extension = case FileFormat of
- html -> ".html";
- plaintext -> ".txt"
+ html -> <<".html">>;
+ plaintext -> <<".txt">>
end,
- Fd = filename:join([OutDir, RoomString, Dir]),
- Fn = filename:join([Fd, Filename ++ Extension]),
- Fnrel = filename:join([Rel, Dir, Filename ++ Extension]),
+ Fd = fjoin([OutDir, RoomString, Dir]),
+ Fn = fjoin([Fd, <<Filename/binary, Extension/binary>>]),
+ Fnrel = fjoin([Rel, Dir, <<Filename/binary, Extension/binary>>]),
{Fd, Fn, Fnrel}.
get_room_name(RoomJID) ->
- JID = jlib:string_to_jid(RoomJID),
- JID#jid.user.
+ JID = jlib:string_to_jid(RoomJID), JID#jid.user.
%% calculate day before
get_timestamp_daydiff(TimeStamp, Daydiff) ->
{Date1, HMS} = TimeStamp,
- Date2 = calendar:gregorian_days_to_date(
- calendar:date_to_gregorian_days(Date1) + Daydiff),
+ Date2 =
+ calendar:gregorian_days_to_date(calendar:date_to_gregorian_days(Date1)
+ + Daydiff),
{Date2, HMS}.
%% Try to close the previous day log, if it exists
close_previous_log(Fn, Images_dir, FileFormat) ->
case file:read_file_info(Fn) of
- {ok, _} ->
- {ok, F} = file:open(Fn, [append]),
- write_last_lines(F, Images_dir, FileFormat),
- file:close(F);
- _ -> ok
+ {ok, _} ->
+ {ok, F} = file:open(Fn, [append]),
+ write_last_lines(F, Images_dir, FileFormat),
+ file:close(F);
+ _ -> ok
end.
-write_last_lines(_, _, plaintext) ->
- ok;
+write_last_lines(_, _, plaintext) -> ok;
write_last_lines(F, Images_dir, _FileFormat) ->
- %%fw(F, "<div class=\"legend\">ejabberd/mod_muc log<span class=\"w3c\">"),
- fw(F, "<div class=\"legend\">"),
- fw(F, " <a href=\"http://www.ejabberd.im\"><img style=\"border:0\" src=\"~s/powered-by-ejabberd.png\" alt=\"Powered by ejabberd\"/></a>", [Images_dir]),
- fw(F, " <a href=\"http://www.erlang.org/\"><img style=\"border:0\" src=\"~s/powered-by-erlang.png\" alt=\"Powered by Erlang\"/></a>", [Images_dir]),
- fw(F, "<span class=\"w3c\">"),
- fw(F, " <a href=\"http://validator.w3.org/check?uri=referer\"><img style=\"border:0;width:88px;height:31px\" src=\"~s/valid-xhtml10.png\" alt=\"Valid XHTML 1.0 Transitional\" /></a>", [Images_dir]),
- fw(F, " <a href=\"http://jigsaw.w3.org/css-validator/\"><img style=\"border:0;width:88px;height:31px\" src=\"~s/vcss.png\" alt=\"Valid CSS!\"/></a>", [Images_dir]),
- fw(F, "</span></div></body></html>").
-
-htmlize_nick(Nick1, html) ->
- htmlize("<"++Nick1++">", html);
-htmlize_nick(Nick1, plaintext) ->
- htmlize(?PLAINTEXT_IN++Nick1++?PLAINTEXT_OUT, plaintext).
-
%% list_to_integer/2 was introduced in OTP R14
--ifdef(SSL40).
+ fw(F, <<"<div class=\"legend\">">>),
+ fw(F,
+ <<" <a href=\"http://www.ejabberd.im\"><img "
+ "style=\"border:0\" src=\"~s/powered-by-ejabbe"
+ "rd.png\" alt=\"Powered by ejabberd\"/></a>">>,
+ [Images_dir]),
+ fw(F,
+ <<" <a href=\"http://www.erlang.org/\"><img "
+ "style=\"border:0\" src=\"~s/powered-by-erlang"
+ ".png\" alt=\"Powered by Erlang\"/></a>">>,
+ [Images_dir]),
+ fw(F, <<"<span class=\"w3c\">">>),
+ fw(F,
+ <<" <a href=\"http://validator.w3.org/check?uri"
+ "=referer\"><img style=\"border:0;width:88px;h"
+ "eight:31px\" src=\"~s/valid-xhtml10.png\" "
+ "alt=\"Valid XHTML 1.0 Transitional\" "
+ "/></a>">>,
+ [Images_dir]),
+ fw(F,
+ <<" <a href=\"http://jigsaw.w3.org/css-validato"
+ "r/\"><img style=\"border:0;width:88px;height:"
+ "31px\" src=\"~s/vcss.png\" alt=\"Valid "
+ "CSS!\"/></a>">>,
+ [Images_dir]),
+ fw(F, <<"</span></div></body></html>">>).
+
set_filemode(Fn, {FileMode, FileGroup}) ->
- ok = file:change_mode(Fn, list_to_integer(integer_to_list(FileMode), 8)),
- ok = file:change_group(Fn, FileGroup).
--else.
-set_filemode(Fn, {_FileMode, FileGroup}) ->
+ ok = file:change_mode(Fn, list_to_integer(integer_to_list(FileMode), 8)),
ok = file:change_group(Fn, FileGroup).
--endif.
-
-add_message_to_log(Nick1, Message, RoomJID, Opts, State) ->
- #logstate{out_dir = OutDir,
- dir_type = DirType,
- dir_name = DirName,
- file_format = FileFormat,
- file_permissions = FilePermissions,
- css_file = CSSFile,
- lang = Lang,
- timezone = Timezone,
- spam_prevention = NoFollow,
- top_link = TopLink} = State,
+
+add_message_to_log(Nick1, Message, RoomJID, Opts,
+ State) ->
+ #logstate{out_dir = OutDir, dir_type = DirType,
+ dir_name = DirName, file_format = FileFormat,
+ file_permissions = FilePermissions,
+ css_file = CSSFile, lang = Lang, timezone = Timezone,
+ spam_prevention = NoFollow, top_link = TopLink} =
+ State,
Room = get_room_info(RoomJID, Opts),
Nick = htmlize(Nick1, FileFormat),
- Nick2 = htmlize_nick(Nick1, FileFormat),
+ Nick2 = htmlize(<<"<", Nick1/binary, ">">>, FileFormat),
Now = now(),
TimeStamp = case Timezone of
- local -> calendar:now_to_local_time(Now);
- universal -> calendar:now_to_universal_time(Now)
+ local -> calendar:now_to_local_time(Now);
+ universal -> calendar:now_to_universal_time(Now)
end,
- {Fd, Fn, _Dir} = build_filename_string(TimeStamp, OutDir, Room#room.jid, DirType, DirName, FileFormat),
+ {Fd, Fn, _Dir} = build_filename_string(TimeStamp,
+ OutDir, Room#room.jid, DirType,
+ DirName, FileFormat),
{Date, Time} = TimeStamp,
-
- %% Open file, create if it does not exist, create parent dirs if needed
case file:read_file_info(Fn) of
- {ok, _} ->
- {ok, F} = file:open(Fn, [append]);
- {error, enoent} ->
- make_dir_rec(Fd),
- {ok, F} = file:open(Fn, [append]),
- catch set_filemode(Fn, FilePermissions),
-
- Datestring = get_dateweek(Date, Lang),
-
- TimeStampYesterday = get_timestamp_daydiff(TimeStamp, -1),
- {_FdYesterday, FnYesterday, DatePrev} =
- build_filename_string(
- TimeStampYesterday, OutDir, Room#room.jid, DirType, DirName, FileFormat),
-
- TimeStampTomorrow = get_timestamp_daydiff(TimeStamp, 1),
- {_FdTomorrow, _FnTomorrow, DateNext} =
- build_filename_string(
- TimeStampTomorrow, OutDir, Room#room.jid, DirType, DirName, FileFormat),
-
- HourOffset = calc_hour_offset(TimeStamp),
- put_header(F, Room, Datestring, CSSFile, Lang,
- HourOffset, DatePrev, DateNext, TopLink, FileFormat),
-
- Images_dir = filename:join([OutDir, "images"]),
- file:make_dir(Images_dir),
- create_image_files(Images_dir),
- Images_url = case DirType of
- subdirs -> "../../../images";
- plain -> "../images"
- end,
- close_previous_log(FnYesterday, Images_url, FileFormat)
+ {ok, _} -> {ok, F} = file:open(Fn, [append]);
+ {error, enoent} ->
+ make_dir_rec(Fd),
+ {ok, F} = file:open(Fn, [append]),
+ catch set_filemode(Fn, FilePermissions),
+ Datestring = get_dateweek(Date, Lang),
+ TimeStampYesterday = get_timestamp_daydiff(TimeStamp,
+ -1),
+ {_FdYesterday, FnYesterday, DatePrev} =
+ build_filename_string(TimeStampYesterday, OutDir,
+ Room#room.jid, DirType, DirName,
+ FileFormat),
+ TimeStampTomorrow = get_timestamp_daydiff(TimeStamp, 1),
+ {_FdTomorrow, _FnTomorrow, DateNext} =
+ build_filename_string(TimeStampTomorrow, OutDir,
+ Room#room.jid, DirType, DirName,
+ FileFormat),
+ HourOffset = calc_hour_offset(TimeStamp),
+ put_header(F, Room, Datestring, CSSFile, Lang,
+ HourOffset, DatePrev, DateNext, TopLink, FileFormat),
+ Images_dir = fjoin([OutDir, <<"images">>]),
+ file:make_dir(Images_dir),
+ create_image_files(Images_dir),
+ Images_url = case DirType of
+ subdirs -> <<"../../../images">>;
+ plain -> <<"../images">>
+ end,
+ close_previous_log(FnYesterday, Images_url, FileFormat)
end,
-
- %% Build message
Text = case Message of
- roomconfig_change ->
- RoomConfig = roomconfig_to_string(Room#room.config, Lang, FileFormat),
- put_room_config(F, RoomConfig, Lang, FileFormat),
- io_lib:format("<font class=\"mrcm\">~s</font><br/>",
- [?T("Chatroom configuration modified")]);
- {roomconfig_change, Occupants} ->
- RoomConfig = roomconfig_to_string(Room#room.config, Lang, FileFormat),
- put_room_config(F, RoomConfig, Lang, FileFormat),
- RoomOccupants = roomoccupants_to_string(Occupants, FileFormat),
- put_room_occupants(F, RoomOccupants, Lang, FileFormat),
- io_lib:format("<font class=\"mrcm\">~s</font><br/>",
- [?T("Chatroom configuration modified")]);
- join ->
- io_lib:format("<font class=\"mj\">~s ~s</font><br/>",
- [Nick, ?T("joins the room")]);
- leave ->
- io_lib:format("<font class=\"ml\">~s ~s</font><br/>",
- [Nick, ?T("leaves the room")]);
- {leave, Reason} ->
- io_lib:format("<font class=\"ml\">~s ~s: ~s</font><br/>",
- [Nick, ?T("leaves the room"), htmlize(Reason,NoFollow,FileFormat)]);
- {kickban, "301", ""} ->
- io_lib:format("<font class=\"mb\">~s ~s</font><br/>",
- [Nick, ?T("has been banned")]);
- {kickban, "301", Reason} ->
- io_lib:format("<font class=\"mb\">~s ~s: ~s</font><br/>",
- [Nick, ?T("has been banned"), htmlize(Reason,FileFormat)]);
- {kickban, "307", ""} ->
- io_lib:format("<font class=\"mk\">~s ~s</font><br/>",
- [Nick, ?T("has been kicked")]);
- {kickban, "307", Reason} ->
- io_lib:format("<font class=\"mk\">~s ~s: ~s</font><br/>",
- [Nick, ?T("has been kicked"), htmlize(Reason,FileFormat)]);
- {kickban, "321", ""} ->
- io_lib:format("<font class=\"mk\">~s ~s</font><br/>",
- [Nick, ?T("has been kicked because of an affiliation change")]);
- {kickban, "322", ""} ->
- io_lib:format("<font class=\"mk\">~s ~s</font><br/>",
- [Nick, ?T("has been kicked because the room has been changed to members-only")]);
- {kickban, "332", ""} ->
- io_lib:format("<font class=\"mk\">~s ~s</font><br/>",
- [Nick, ?T("has been kicked because of a system shutdown")]);
- {nickchange, OldNick} ->
- io_lib:format("<font class=\"mnc\">~s ~s ~s</font><br/>",
- [htmlize(OldNick,FileFormat), ?T("is now known as"), Nick]);
- {subject, T} ->
- io_lib:format("<font class=\"msc\">~s~s~s</font><br/>",
- [Nick, ?T(" has set the subject to: "), htmlize(T,NoFollow,FileFormat)]);
- {body, T} ->
- case {ejabberd_regexp:run(T, "^/me\s"), Nick} of
- {_, ""} ->
- io_lib:format("<font class=\"msm\">~s</font><br/>",
- [htmlize(T,NoFollow,FileFormat)]);
- {match, _} ->
- io_lib:format("<font class=\"mne\">~s ~s</font><br/>",
- [Nick, string:substr(htmlize(T,FileFormat), 5)]);
- {nomatch, _} ->
- io_lib:format("<font class=\"mn\">~s</font> ~s<br/>",
- [Nick2, htmlize(T,NoFollow,FileFormat)])
- end;
- {room_existence, RoomNewExistence} ->
- io_lib:format("<font class=\"mrcm\">~s</font><br/>",
- [get_room_existence_string(RoomNewExistence, Lang)])
+ roomconfig_change ->
+ RoomConfig = roomconfig_to_string(Room#room.config,
+ Lang, FileFormat),
+ put_room_config(F, RoomConfig, Lang, FileFormat),
+ io_lib:format("<font class=\"mrcm\">~s</font><br/>",
+ [?T(<<"Chatroom configuration modified">>)]);
+ {roomconfig_change, Occupants} ->
+ RoomConfig = roomconfig_to_string(Room#room.config,
+ Lang, FileFormat),
+ put_room_config(F, RoomConfig, Lang, FileFormat),
+ RoomOccupants = roomoccupants_to_string(Occupants,
+ FileFormat),
+ put_room_occupants(F, RoomOccupants, Lang, FileFormat),
+ io_lib:format("<font class=\"mrcm\">~s</font><br/>",
+ [?T(<<"Chatroom configuration modified">>)]);
+ join ->
+ io_lib:format("<font class=\"mj\">~s ~s</font><br/>",
+ [Nick, ?T(<<"joins the room">>)]);
+ leave ->
+ io_lib:format("<font class=\"ml\">~s ~s</font><br/>",
+ [Nick, ?T(<<"leaves the room">>)]);
+ {leave, Reason} ->
+ io_lib:format("<font class=\"ml\">~s ~s: ~s</font><br/>",
+ [Nick, ?T(<<"leaves the room">>),
+ htmlize(Reason, NoFollow, FileFormat)]);
+ {kickban, <<"301">>, <<"">>} ->
+ io_lib:format("<font class=\"mb\">~s ~s</font><br/>",
+ [Nick, ?T(<<"has been banned">>)]);
+ {kickban, <<"301">>, Reason} ->
+ io_lib:format("<font class=\"mb\">~s ~s: ~s</font><br/>",
+ [Nick, ?T(<<"has been banned">>),
+ htmlize(Reason, FileFormat)]);
+ {kickban, <<"307">>, <<"">>} ->
+ io_lib:format("<font class=\"mk\">~s ~s</font><br/>",
+ [Nick, ?T(<<"has been kicked">>)]);
+ {kickban, <<"307">>, Reason} ->
+ io_lib:format("<font class=\"mk\">~s ~s: ~s</font><br/>",
+ [Nick, ?T(<<"has been kicked">>),
+ htmlize(Reason, FileFormat)]);
+ {kickban, <<"321">>, <<"">>} ->
+ io_lib:format("<font class=\"mk\">~s ~s</font><br/>",
+ [Nick,
+ ?T(<<"has been kicked because of an affiliation "
+ "change">>)]);
+ {kickban, <<"322">>, <<"">>} ->
+ io_lib:format("<font class=\"mk\">~s ~s</font><br/>",
+ [Nick,
+ ?T(<<"has been kicked because the room has "
+ "been changed to members-only">>)]);
+ {kickban, <<"332">>, <<"">>} ->
+ io_lib:format("<font class=\"mk\">~s ~s</font><br/>",
+ [Nick,
+ ?T(<<"has been kicked because of a system "
+ "shutdown">>)]);
+ {nickchange, OldNick} ->
+ io_lib:format("<font class=\"mnc\">~s ~s ~s</font><br/>",
+ [htmlize(OldNick, FileFormat),
+ ?T(<<"is now known as">>), Nick]);
+ {subject, T} ->
+ io_lib:format("<font class=\"msc\">~s~s~s</font><br/>",
+ [Nick, ?T(<<" has set the subject to: ">>),
+ htmlize(T, NoFollow, FileFormat)]);
+ {body, T} ->
+ case {ejabberd_regexp:run(T, <<"^/me ">>), Nick} of
+ {_, <<"">>} ->
+ io_lib:format("<font class=\"msm\">~s</font><br/>",
+ [htmlize(T, NoFollow, FileFormat)]);
+ {match, _} ->
+ io_lib:format("<font class=\"mne\">~s ~s</font><br/>",
+ [Nick,
+ str:substr(htmlize(T, FileFormat), 5)]);
+ {nomatch, _} ->
+ io_lib:format("<font class=\"mn\">~s</font> ~s<br/>",
+ [Nick2, htmlize(T, NoFollow, FileFormat)])
+ end;
+ {room_existence, RoomNewExistence} ->
+ io_lib:format("<font class=\"mrcm\">~s</font><br/>",
+ [get_room_existence_string(RoomNewExistence,
+ Lang)])
end,
{Hour, Minute, Second} = Time,
- STime = lists:flatten(
- io_lib:format("~2..0w:~2..0w:~2..0w", [Hour, Minute, Second])),
+ STime = io_lib:format("~2..0w:~2..0w:~2..0w",
+ [Hour, Minute, Second]),
{_, _, Microsecs} = Now,
- STimeUnique = io_lib:format("~s.~w", [STime, Microsecs]),
-
- %% Write message
- fw(F, io_lib:format("<a id=\"~s\" name=\"~s\" href=\"#~s\" class=\"ts\">[~s]</a> ",
- [STimeUnique, STimeUnique, STimeUnique, STime]) ++ Text, FileFormat),
-
- %% Close file
+ STimeUnique = io_lib:format("~s.~w",
+ [STime, Microsecs]),
+ fw(F,
+ list_to_binary(
+ io_lib:format("<a id=\"~s\" name=\"~s\" href=\"#~s\" "
+ "class=\"ts\">[~s]</a> ",
+ [STimeUnique, STimeUnique, STimeUnique, STime])
+ ++ Text),
+ FileFormat),
file:close(F),
ok.
-
%%----------------------------------------------------------------------
%% Utilities
-get_room_existence_string(created, Lang) -> ?T("Chatroom is created");
-get_room_existence_string(destroyed, Lang) -> ?T("Chatroom is destroyed");
-get_room_existence_string(started, Lang) -> ?T("Chatroom is started");
-get_room_existence_string(stopped, Lang) -> ?T("Chatroom is stopped").
+get_room_existence_string(created, Lang) ->
+ ?T(<<"Chatroom is created">>);
+get_room_existence_string(destroyed, Lang) ->
+ ?T(<<"Chatroom is destroyed">>);
+get_room_existence_string(started, Lang) ->
+ ?T(<<"Chatroom is started">>);
+get_room_existence_string(stopped, Lang) ->
+ ?T(<<"Chatroom is stopped">>).
get_dateweek(Date, Lang) ->
Weekday = case calendar:day_of_the_week(Date) of
- 1 -> ?T("Monday");
- 2 -> ?T("Tuesday");
- 3 -> ?T("Wednesday");
- 4 -> ?T("Thursday");
- 5 -> ?T("Friday");
- 6 -> ?T("Saturday");
- 7 -> ?T("Sunday")
+ 1 -> ?T(<<"Monday">>);
+ 2 -> ?T(<<"Tuesday">>);
+ 3 -> ?T(<<"Wednesday">>);
+ 4 -> ?T(<<"Thursday">>);
+ 5 -> ?T(<<"Friday">>);
+ 6 -> ?T(<<"Saturday">>);
+ 7 -> ?T(<<"Sunday">>)
end,
{Y, M, D} = Date,
Month = case M of
- 1 -> ?T("January");
- 2 -> ?T("February");
- 3 -> ?T("March");
- 4 -> ?T("April");
- 5 -> ?T("May");
- 6 -> ?T("June");
- 7 -> ?T("July");
- 8 -> ?T("August");
- 9 -> ?T("September");
- 10 -> ?T("October");
- 11 -> ?T("November");
- 12 -> ?T("December")
+ 1 -> ?T(<<"January">>);
+ 2 -> ?T(<<"February">>);
+ 3 -> ?T(<<"March">>);
+ 4 -> ?T(<<"April">>);
+ 5 -> ?T(<<"May">>);
+ 6 -> ?T(<<"June">>);
+ 7 -> ?T(<<"July">>);
+ 8 -> ?T(<<"August">>);
+ 9 -> ?T(<<"September">>);
+ 10 -> ?T(<<"October">>);
+ 11 -> ?T(<<"November">>);
+ 12 -> ?T(<<"December">>)
end,
- case Lang of
- "en" -> io_lib:format("~s, ~s ~w, ~w", [Weekday, Month, D, Y]);
- "es" -> io_lib:format("~s ~w de ~s de ~w", [Weekday, D, Month, Y]);
- _ -> io_lib:format("~s, ~w ~s ~w", [Weekday, D, Month, Y])
- end.
+ list_to_binary(
+ case Lang of
+ <<"en">> ->
+ io_lib:format("~s, ~s ~w, ~w", [Weekday, Month, D, Y]);
+ <<"es">> ->
+ io_lib:format("~s ~w de ~s de ~w",
+ [Weekday, D, Month, Y]);
+ _ ->
+ io_lib:format("~s, ~w ~s ~w", [Weekday, D, Month, Y])
+ end).
make_dir_rec(Dir) ->
- case file:read_file_info(Dir) of
- {ok, _} ->
- ok;
- {error, enoent} ->
- DirS = filename:split(Dir),
- DirR = lists:sublist(DirS, length(DirS)-1),
- ok = make_dir_rec(filename:join(DirR)),
- ok = file:make_dir(Dir),
- ok = file:change_mode(Dir, 8#00755) % -rwxr-xr-x
+ DirS = binary_to_list(Dir),
+ case file:read_file_info(DirS) of
+ {ok, _} -> ok;
+ {error, enoent} ->
+ DirL = [list_to_binary(F) || F <- filename:split(DirS)],
+ DirR = lists:sublist(DirL, length(DirL) - 1),
+ make_dir_rec(fjoin(DirR)),
+ file:make_dir(DirS),
+ file:change_mode(DirS, 8#00755) % -rwxr-xr-x
end.
-
%% {ok, F1}=file:open("valid-xhtml10.png", [read]).
%% {ok, F1b}=file:read(F1, 1000000).
%% c("../../ejabberd/src/jlib.erl").
%% jlib:encode_base64(F1b).
-image_base64("powered-by-erlang.png") ->
- "iVBORw0KGgoAAAANSUhEUgAAAGUAAAAfCAYAAAD+xQNoAAADN0lEQVRo3u1a"
- "P0waURz+rjGRRQ+nUyRCYmJyDPTapDARaSIbTUjt1gVSh8ZW69aBAR0cWLSx"
- "CXWp59LR1jbdqKnGxoQuRZZrSYyHEVM6iZMbHewROA7u3fHvkr5vOn737vcu"
- "33ffu9/vcQz+gef5Cij6CkmSGABgFEH29r5SVvqIsTEOHo8HkiQxDBXEOjg9"
- "PcHc3BxuUSqsI8jR0REAUFGsCCoKFYWCBAN6AxyO0Z7cyMXFb6oGqSgAsIrJ"
- "ut9hMQlvdNbUhKWshLd3HtTF4jihShgVpRaBxKKmIGX5HL920/hz/BM2+zAm"
- "pn2YioQaxnECj0BiEYcrG0Tzzc8/rfudSm02jaVSm9Vr1MdG8rSKKXlJ7lHr"
- "fjouCut2IrC82BDPbe/gc+xlXez7KxEz63H4lmIN473Rh8Si1BKhRY6aEJI8"
- "pLmbjSPN0xOnBBILmg5RC6Lg28preKOzsNmHG8R1Bf0o7GdMucUslDy1pJLG"
- "2sndVVG0lq3c9vum4zmBR1kuwiYMN5ybmCYXxQg57ThFOTYznzpPO+IQi+IK"
- "+jXjg/YhuIJ+cIIHg+wQJoJ+2N3jYN3Olvk4ge/IU98spne+FfGtlslm16nn"
- "a8fduntfDscoVjGJqUgIjz686ViFUdjP4N39x9Xq638viZVtlq2tLXKncLf5"
- "ticuZSWU5XOUshJKxxKtfdtdvs4OyNb/68urKvlluYizgwwu5SLK8jllu1t9"
- "ihYOlzdwdpBBKSvh+vKKzHkCj1JW3y1m+hSj13WjqOiJKK0qpXKhSFxJAYBv"
- "KYaZ9TjWRu4SiWi2LyDtb6wghGmn5HfTml16ILGA/G5al2DW7URYTFYrOU7g"
- "icQ020sYqYDM9CbdgqFd4vzHL03JfvLjk6ZgADAVCSEsJvHsdL+utNYrm2uf"
- "ZDVZSkzPKaQkW8kthpyS297BvRdRzR6DdTurJbPy9Ov1K6xr3HBPQuIMowR3"
- "asegUyDuU9SuUG+dmIGyZ0b7FBN9St3WunyC5yMsrVv7uXzRP58s/qKn6C4q"
- "lQoVxVIvd4YBwzBUFKs6ZaD27U9hEdcAN98Sx2IxykafIYrizbfESoB+dd9/"
- "KF/d/wX3cJvREzl1vAAAAABJRU5ErkJggg==";
-
-image_base64("valid-xhtml10.png") ->
- "iVBORw0KGgoAAAANSUhEUgAAAFgAAAAfCAMAAAEjEcpEAAACiFBMVEUAAADe"
- "5+fOezmtra3ejEKlhELvvWO9WlrehELOe3vepaWclHvetVLGc3PerVKcCAj3"
- "vVqUjHOUe1JjlL0xOUpjjL2UAAC91ueMrc7vrVKlvdbW3u+EpcbO3ufO1ucY"
- "WpSMKQi9SiF7e3taWkoQEAiMczkQSoxaUkpzc3O1lEoICACEazEhGAgIAACE"
- "YzFra2utjELWcznGnEr/7+9jY2POazHOYzGta2NShLVrlL05OUqctdacCADG"
- "a2ucAADGpVqUtc61ORg5OTmlUikYGAiUezl7YzEYEAiUczkxMTG9nEqtIRDe"
- "3t4AMXu9lEoQCACMazEAKXspKSmljFrW1ta1jELOzs7n7/fGxsa9pVqEOSkp"
- "Y5xznL29tZxahLXOpVr/99ZrY1L/79ZjUiljSikAOYTvxmMAMYScezmchFqU"
- "czGtlFp7c2utjFqUlJStxt73///39/9Ce61CSkq9xsZznMbW5+9Cc62MjIxC"
- "Qkrv9/fv7/fOzsbnlErWjIz/3mtCORhza1IpIRBzWjH/1mtCMRhzY1L/zmvn"
- "vVpSQiHOpVJrUinntVr3zmOEc1L3xmNaWlq1nFo5QkrGWim1lFoISpRSUlK1"
- "zt4hWpwASoz///////8xa6WUaykAQoxKe61KSkp7nMbWtWPe5+9jWlL39/f3"
- "9/fWrWNCQkLera3nvWPv7+85MRjntWPetVp7c1IxKRCUlHtKORh7a1IxIRCU"
- "jHtaSiHWrVIpIQhzWinvvVpaQiH/1mPWpVKMe1L/zmP/xmNrUiGErc4YGBj/"
- "73PG1ucQWpT/53O9nFoQUpS1SiEQEBC9zt69vb05c6UISoxSUko5a6UICAhS"
- "SkohUpS1tbXetWMAQoSUgD+kAAAA2HRSTlP/////////iP9sSf//dP//////"
- "//////////////////////////////////////////8M////////////ef//"
- "////////////////////////////////////////////////////////////"
- "//////////////////////9d////////////////////////////////////"
- "AP//////////////CP//RP//////////////////////////////////////"
- "//////////////////////9xPp1gAAAFvUlEQVR42pVWi18URRwfy7vsYUba"
- "iqBRBFmICUQGVKcZckQeaRJQUCLeycMSfKGH0uo5NELpIvGQGzokvTTA85VH"
- "KTpbRoeJnPno/p1+M7t3txj20e/Nzu7Ofve7v/k9Zg4Vc+wRQMW0eyLx1ZSA"
- "NeBDxVmxZZSwEUYkGAewm1eIBOMRvhv1UA+q8KXIVuxGdCelFYwxAnxOrxgb"
- "Y8Ti1t4VA0QHYz4x3FnVC8OVLXv9fkKGSWDoW/4lG6VbdtBblesOs+MjmEmz"
- "JKNIJWFEfEQTCWNPFKvcKEymjLO1b8bwYQd1hCiiDCl5KsrDCIlhj4fSuvcp"
- "fSpgJmyv6dzeZv+nMPx3dhbt94II07/JZliEtm1N2RIYPkTYshwYm245a/zk"
- "WjJwcyFh6ZIcYxxmqiaDSYxhOhFUsqngi3Fzcj3ljdYDNE9uzA1YD/5MhnzW"
- "1KRqF7mYG8jFYXLcfLpjOe2LA0fuGqQrQHl10sdK0sFcFSOSlzF0BgXQH9h3"
- "QZDBI0ccNEhftjXuippBDD2/eMRiETmwwNEYHyqhdDyo22w+3QHuNbdve5a7"
- "eOkHmDVJ0ixNmfbz1h0qo/Q6GuSB2wQJQbpOjOQAl7woWSRJ0m2ewhvAOUiY"
- "YtZtaZL0CZZmtmVOQttLfr/dbveLZodrfrL7W75wG/JjqkQxoNTtNsTKELQp"
- "QL6/D5loaSmyTT8TUhsmi8iFA0hZiyltf7OiNKdarRm5w2So2lTNdPLuIzR+"
- "AiLj8VTRJaj0LmX4VhJ27f/VJV/yycilWPOrk8NkXi7Qqmj5bHqVZlJKZIRk"
- "1wFzKrt0WUbnXMPJ1fk4TJ5oWBA61p1V76DeIs0MX+s3GxRlA1vtw83KhgNp"
- "hc1nyErLO5zcvbOsrq+scbZnpzc6QVFPenLwGxmC+BOfYI+DN55QYddh4Q/N"
- "E/yGYYj4TOGNngQavAZnzzTovEA+kcMJ+247uYexNA+4Fsvjmuv662jsWxPZ"
- "x2xg890bYMYnTgya7bjmCiEY0qgJ0vMF3c+NoFdPyzxz6V3Uxs3AOWCDchRv"
- "OsQtBrbFsrT2fhHEc7ByGzu/dA4IO0A3HdfeP9yMqAwP6NPEb6cbwn0PWVU1"
- "7/FDBQh/CPIrbfcg027IZrsAT/Bf3FNWyn9RSR4cvvwn3e4HFmYPDl/thYcR"
- "Vi8qPEoXVUWBl6FTBFTtnqmKKg5wnlF4wZ1yeLv7TiwXKektE+iDBNicWEyL"
- "pnFhfDkpJc3q2khSPyQBbE0dMJnOoDzTwGsI7cdyMkL5gWqUjCF6Txst/twx"
- "Cv1WzzHoy21ZDQ1xnuDzdPDWR4knr14v0tYn3IxaMFFdiMOlEOJHw1jOQ4sW"
- "t5rQopRkXZhMEi7pmeDCVWBlfUKwhMZ7rsF6elKsvbwiKxgxIdewa3ErsaYo"
- "mCVZFYJb0GUu3JqGUNoplBxYiYby8vLBFWef+Cri4/I1sbQ/1OtYTrNtdXS+"
- "rSe7kQ52eSObL99/iErCWUjCy5W4JLygmCouGfG9x9fmx17XhBuDCaOerbt5"
- "38erta7TFktLvdHghZcCbcPQO33zIJG9kxF5hoVXnzTzRz0r5js8oTj6uyPk"
- "GRf346HOLcasgFexueNUWFPtuFKzjoSFYYedhwVlhsRVYWWJpltv1XPQT1Rl"
- "0bjZIBlb1XujVDzY/Kj4k6Ku3+Z0jo1owjVzDpFTXe1juvBSWNFmNWGZy8Lv"
- "zUl5PN4JCwyNDzbQ0aAj4Zrjz0FatGJJYhvq4j7mGSpvytGFlZtHf2C4o/28"
- "Zu8z7wo7eYPfXysnF0i9NnPh1t1zR7VBb9GqaOXhtTmHQdgMFXE+Z608cnpO"
- "DdZdjL+TuDY44Q38kJXHhccWLoOd9uv1AwwvO+48uu+faCSJPJ1bmy6Thyvp"
- "ivBmYWgjxPDPAp7JTemY/yGKFEiRt/jG/2P79s8KCwoLCgoLC/khUBA5F0Sf"
- "QZ+RYfpNE/4Xosmq7jsZAJsAAAAASUVORK5CYII=";
-
-image_base64("vcss.png") ->
- "iVBORw0KGgoAAAANSUhEUgAAAFgAAAAfCAMAAABUFvrSAAABKVBMVEUAAAAj"
- "Ix8MR51ZVUqAdlmdnZ3ejEWLDAuNjY1kiMG0n2d9fX19Ghfrp1FtbW3y39+3"
- "Ph6lIRNdXV2qJBFcVUhcVUhPT0/dsmpUfLr57+/u7u4/PDWZAACZAADOp1Gd"
- "GxG+SyTgvnNdSySzk16+mkuxw+BOS0BOS0DOzs7MzMy4T09RRDwsJBG+vr73"
- "wV6fkG6eCQRFcLSurq6/X1+ht9nXfz5sepHuwV59ZTHetFjQ2+wMCQQ2ZK5t"
- "WCsmWajsz8+Sq9NMPh4hVaY8MRj///////////////////////9MTEyOp9Lu"
- "8vhXU1A8PDyjOSTBz+YLRJ2rLy8sLCwXTaKujEUcHByDn82dfz7/zGafDw+f"
- "Dw+zRSlzlMcMDAyNcji1tbXf5vIcFgvATJOjAAAAY3RSTlP/8///////////"
- "//////8A//////P/////ov//8//////////////z///T//////////+i////"
- "//////////8w/////6IA/xAgMP//////////8/////////8w0/////////+z"
- "ehebAAACkUlEQVR42u2VfVPTQBDG19VqC6LY+lKrRIxFQaFSBPuSvhBPF8SI"
- "UZK2J5Yav/+HcO8uZdLqTCsU/nKnyWwvk1/unnt2D9ZmH+8/cMAaTRFy+ng6"
- "9/yiwC/+gy8R3McGv5zHvGJEGAdR4eBgi1IbZwevIEZE24pFtBtzG1Q4AoD5"
- "zvw5pEDcJvIQV/TE3/l+H9GnNJwcdABS5wAbFQLMqI98/UReoAaOTlaJsp0z"
- "aHx7LwZvY0BUR2xpWTzqam0gzY8KGzG4MhBCNGucha4QbpETy+Yk/BP85nt7"
- "34AjpQLTsE4ZFpf/dnkUCglXVNYB+OfUZJHvAqAoa45OeuPgm4+Xjtv7xm4N"
- "7PMV4C61+Mrz3H2WImm3ATiWrAiwZRWcUA5Ej4dgIEMxDv6yxHHcNuAutnjv"
- "2HZ1NeuycoVPh0mwC834zZC9Ao5dkZZKwLVGwT+WdLw0YOZ1saEkUDoT+QGW"
- "KZ0E2xpcrPakVW2KXwyUtYEtlEAj3GXD/fYwrryAdeiyGqidQSw1eqtJcA8c"
- "Zq4zXqhPuCBYE1fKJjh/5X6MwRm9c2xf7WVdLf5oSdt64esVIwVAKC1HJ2ol"
- "i8vj3L0YzC4zjkMagt+arDAs6bApbL1RVlWIqrJbreqKZmh4y6VR7rAJeUYD"
- "VRj9VqRXkErpJ9lbEwtE83KlIfeG4p52t7zWIMO1XcaGz54uUyet+hBM7BXX"
- "DS8Xc5+8Gmmbu1xwSoGIokA3oTptQecQ4Iimm/Ew7jwbPfMi3TM91T9XVIGo"
- "+W9xC8oWpugVCXLuwXijjxJ3r/6PjX7nlFua8QmyM+TO/Gja2TTc2Z95C5ua"
- "ewGH6cJi6bJO6Z+TY276eH3tbgy+/3ly3Js+rj66osG/AV5htgaQ9SeRAAAA"
- "AElFTkSuQmCC";
-
-image_base64("powered-by-ejabberd.png") ->
- "iVBORw0KGgoAAAANSUhEUgAAAGUAAAAfCAMAAADJG/NaAAAAw1BMVEUAAAAj"
- "BgYtBAM5AwFCAAAYGAJNAABcAABIDQ5qAAAoJRV7AACFAAAoKSdJHByLAAAw"
- "Lwk1NQA1MzFJKyo4NxtDQQBEQT5KSCxSTgBSUBlgQ0JYSEpZWQJPUU5hYABb"
- "W0ZiYClcW1poaCVwbQRpaDhzYWNsakhuZ2VrbFZ8dwCEgAB3dnd4d2+OjACD"
- "hYKcmACJi4iQkpWspgCYmJm5swCmqazEwACwsbS4ub3X0QLExsPLyszW1Nnc"
- "3ODm5ugMBwAWAwPHm1IFAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJ"
- "cEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfVCRQOBA7VBkCMAAACcElEQVRI"
- "x72WjXKiMBSFQalIFbNiy1pdrJZaRVYR5deGwPs/VRNBSBB2OjvQO0oYjPfj"
- "5J6bCcdx8i2UldxKcDhk1HbIPwFBF/kHKJfjPSVAyIRHF9rRZ4sUX3EDdWOv"
- "1+u2tESaavpnYTbv9zvd0WwDy3/QcGQXlH5uTxB1l07MJlRpsUei0JF6Qi+O"
- "HyGK7ijXxPklHe/umIllim3iUBMJDIEULxxPP0TVWhhKJoN9fUpdmQLteV8a"
- "DgEAg9gIcTjL4F4L+r4WVKEF+rbJdwYYAoQHY+oQjnGootyKwxapoi73WkyF"
- "FySQBv988naEEp4+YMMec5VUCQDJTscEy7Kc0HsLmqNE7rovDjMpIHHGYeid"
- "Xn4TQcaxMYqP3RV3C8oCl2WvrlSPaNpGZadRnmPGCk8ylM2okAJ4i9TEe1Ke"
- "rsXxSl6jUt5uayiIodirtcKLOaWblj50wiyMv1F9lm9TUDArGAD0FmEpvCUs"
- "VoZy6dW81Fg0aDaHogQa36ekAPG5DDGsbdZrGsrzZUnzvBo1I2tLmuL69kSi"
- "tAweyHKN9b3leDfQMnu3nIIKWfmXnqGVKedJT6QpICbJvf2f8aOsvn68v+k7"
- "/cwUQdPoxaMoRTnKFHNlKsKQphCTOa84u64vpi8bH31CqsbF6lSONRTkTyQG"
- "Arq49/fEvjBwz4eDS2/JpaXRNOoXRD/VmOrDVTJJRIZCTLav3VrqbPvP3vdd"
- "uGEhQJzilncbpSA4F3vsihErO+dayv/sY5/yRE0GDEXCu2VoNiMlo5i+P2Kl"
- "gMEvTNk2eYa5XEyh12Ex17Z8vzQUR3KEPbYd6XG87eC4Ly75RneS5ZYHAAAA"
- "AElFTkSuQmCC".
+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) ->
- Filenames = ["powered-by-ejabberd.png",
- "powered-by-erlang.png",
- "valid-xhtml10.png",
- "vcss.png"
- ],
- lists:foreach(
- fun(Filename) ->
- Filename_full = filename:join([Images_dir, Filename]),
- {ok, F} = file:open(Filename_full, [write]),
- Image = jlib:decode_base64(image_base64(Filename)),
- io:format(F, "~s", [Image]),
- file:close(F)
- end,
- Filenames),
+ 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 = jlib:decode_base64(image_base64(Filename)),
+ io:format(F, <<"~s">>, [Image]),
+ file:close(F)
+ end,
+ Filenames),
ok.
fw(F, S) -> fw(F, S, [], html).
-fw(F, S, O) when is_list(O) ->
- fw(F, S, O, html);
+fw(F, S, O) when is_list(O) -> fw(F, S, O, html);
fw(F, S, FileFormat) when is_atom(FileFormat) ->
fw(F, S, [], FileFormat).
fw(F, S, O, FileFormat) ->
- S1 = io_lib:format(S ++ "~n", O),
+ S1 = list_to_binary(io_lib:format(binary_to_list(S) ++ "~n", O)),
S2 = case FileFormat of
html ->
S1;
plaintext ->
- S1x = ejabberd_regexp:greplace(S1, "<[^<^>]*>", ""),
- S1y = ejabberd_regexp:greplace(S1x, ?PLAINTEXT_IN, "<"),
- ejabberd_regexp:greplace(S1y, ?PLAINTEXT_OUT, ">")
+ S1x = ejabberd_regexp:greplace(S1, <<"<[^<^>]*>">>, <<"">>),
+ S1y = ejabberd_regexp:greplace(S1x, ?PLAINTEXT_IN, <<"<">>),
+ ejabberd_regexp:greplace(S1y, ?PLAINTEXT_OUT, <<">">>)
end,
io:format(F, S2, []).
-put_header(_, _, _, _, _, _, _, _, _, plaintext) ->
- ok;
-put_header(F, Room, Date, CSSFile, Lang, Hour_offset, Date_prev, Date_next, Top_link, FileFormat) ->
- fw(F, "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">"),
- fw(F, "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"~s\" lang=\"~s\">", [Lang, Lang]),
- fw(F, "<head>"),
- fw(F, "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />"),
- fw(F, "<title>~s - ~s</title>", [htmlize(Room#room.title), Date]),
+put_header(_, _, _, _, _, _, _, _, _, plaintext) -> ok;
+put_header(F, Room, Date, CSSFile, Lang, Hour_offset,
+ Date_prev, Date_next, Top_link, FileFormat) ->
+ fw(F,
+ <<"<!DOCTYPE html PUBLIC \"-//W3C//DTD "
+ "XHTML 1.0 Transitional//EN\" \"http://www.w3."
+ "org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">">>),
+ fw(F,
+ <<"<html xmlns=\"http://www.w3.org/1999/xhtml\" "
+ "xml:lang=\"~s\" lang=\"~s\">">>,
+ [Lang, Lang]),
+ fw(F, <<"<head>">>),
+ fw(F,
+ <<"<meta http-equiv=\"Content-Type\" content=\"t"
+ "ext/html; charset=utf-8\" />">>),
+ fw(F, <<"<title>~s - ~s</title>">>,
+ [htmlize(Room#room.title), Date]),
put_header_css(F, CSSFile),
put_header_script(F),
- fw(F, "</head>"),
- fw(F, "<body>"),
+ fw(F, <<"</head>">>),
+ fw(F, <<"<body>">>),
{Top_url, Top_text} = Top_link,
- fw(F, "<div style=\"text-align: right;\"><a style=\"color: #AAAAAA; font-family: monospace; text-decoration: none; font-weight: bold;\" href=\"~s\">~s</a></div>", [Top_url, Top_text]),
- fw(F, "<div class=\"roomtitle\">~s</div>", [htmlize(Room#room.title)]),
- fw(F, "<a class=\"roomjid\" href=\"xmpp:~s?join\">~s</a>", [Room#room.jid, Room#room.jid]),
- fw(F, "<div class=\"logdate\">~s<span class=\"w3c\"><a class=\"nav\" href=\"~s\">&lt;</a> <a class=\"nav\" href=\".\/\">^</a> <a class=\"nav\" href=\"~s\">&gt;</a></span></div>", [Date, Date_prev, Date_next]),
- case {htmlize(Room#room.subject_author), htmlize(Room#room.subject)} of
- {"", ""} -> ok;
- {SuA, Su} -> fw(F, "<div class=\"roomsubject\">~s~s~s</div>", [SuA, ?T(" has set the subject to: "), Su])
+ fw(F,
+ <<"<div style=\"text-align: right;\"><a "
+ "style=\"color: #AAAAAA; font-family: "
+ "monospace; text-decoration: none; font-weight"
+ ": bold;\" href=\"~s\">~s</a></div>">>,
+ [Top_url, Top_text]),
+ fw(F, <<"<div class=\"roomtitle\">~s</div>">>,
+ [htmlize(Room#room.title)]),
+ fw(F,
+ <<"<a class=\"roomjid\" href=\"xmpp:~s?join\">~s"
+ "</a>">>,
+ [Room#room.jid, Room#room.jid]),
+ fw(F,
+ <<"<div class=\"logdate\">~s<span class=\"w3c\">"
+ "<a class=\"nav\" href=\"~s\">&lt;</a> "
+ "<a class=\"nav\" href=\"./\">^</a> <a "
+ "class=\"nav\" href=\"~s\">&gt;</a></span></di"
+ "v>">>,
+ [Date, Date_prev, Date_next]),
+ case {htmlize(Room#room.subject_author),
+ htmlize(Room#room.subject)}
+ of
+ {<<"">>, <<"">>} -> ok;
+ {SuA, Su} ->
+ fw(F, <<"<div class=\"roomsubject\">~s~s~s</div>">>,
+ [SuA, ?T(<<" has set the subject to: ">>), Su])
end,
- RoomConfig = roomconfig_to_string(Room#room.config, Lang, FileFormat),
+ RoomConfig = roomconfig_to_string(Room#room.config,
+ Lang, FileFormat),
put_room_config(F, RoomConfig, Lang, FileFormat),
Occupants = get_room_occupants(Room#room.jid),
- RoomOccupants = roomoccupants_to_string(Occupants, FileFormat),
+ RoomOccupants = roomoccupants_to_string(Occupants,
+ FileFormat),
put_room_occupants(F, RoomOccupants, Lang, FileFormat),
- Time_offset_str = case Hour_offset<0 of
- true -> io_lib:format("~p", [Hour_offset]);
- false -> io_lib:format("+~p", [Hour_offset])
+ Time_offset_str = case Hour_offset < 0 of
+ true -> io_lib:format("~p", [Hour_offset]);
+ false -> io_lib:format("+~p", [Hour_offset])
end,
- fw(F, "<br/><a class=\"ts\">GMT~s</a><br/>", [Time_offset_str]).
+ fw(F, <<"<br/><a class=\"ts\">GMT~s</a><br/>">>,
+ [Time_offset_str]).
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-decoration: 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-decoration: 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-bottom: #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;}"),
- fw(F, "//-->"),
- fw(F, "</style>");
-
+ 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;}">>),
+ fw(F, <<"//-->">>),
+ fw(F, <<"</style>">>);
put_header_css(F, CSSFile) ->
- fw(F, "<link rel=\"stylesheet\" type=\"text/css\" href=\"~s\" media=\"all\">", [CSSFile]).
+ fw(F,
+ <<"<link rel=\"stylesheet\" type=\"text/css\" "
+ "href=\"~s\" media=\"all\">">>,
+ [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='block';}"),
- fw(F, "else {document.getElementById(e).style.display='none';}}"),
- fw(F, "</script>").
+ 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';}}">>),
+ fw(F, <<"</script>">>).
put_room_config(_F, _RoomConfig, _Lang, plaintext) ->
ok;
put_room_config(F, RoomConfig, Lang, _FileFormat) ->
{_, Now2, _} = now(),
- fw(F, "<div class=\"rc\">"),
- fw(F, "<div class=\"rct\" onclick=\"sh('a~p');return false;\">~s</div>", [Now2, ?T("Room Configuration")]),
- fw(F, "<div class=\"rcos\" id=\"a~p\" style=\"display: none;\" ><br/>~s</div>", [Now2, RoomConfig]),
- fw(F, "</div>").
-
-put_room_occupants(_F, _RoomOccupants, _Lang, plaintext) ->
+ fw(F, <<"<div class=\"rc\">">>),
+ fw(F,
+ <<"<div class=\"rct\" onclick=\"sh('a~p');return "
+ "false;\">~s</div>">>,
+ [Now2, ?T(<<"Room Configuration">>)]),
+ fw(F,
+ <<"<div class=\"rcos\" id=\"a~p\" style=\"displa"
+ "y: none;\" ><br/>~s</div>">>,
+ [Now2, RoomConfig]),
+ fw(F, <<"</div>">>).
+
+put_room_occupants(_F, _RoomOccupants, _Lang,
+ plaintext) ->
ok;
-put_room_occupants(F, RoomOccupants, Lang, _FileFormat) ->
+put_room_occupants(F, RoomOccupants, Lang,
+ _FileFormat) ->
{_, Now2, _} = now(),
- fw(F, "<div class=\"rc\">"),
- fw(F, "<div class=\"rct\" onclick=\"sh('o~p');return false;\">~s</div>", [Now2, ?T("Room Occupants")]),
- fw(F, "<div class=\"rcos\" id=\"o~p\" style=\"display: none;\" ><br/>~s</div>", [Now2, RoomOccupants]),
- fw(F, "</div>").
-
%% htmlize
%% The default behaviour is to ignore the nofollow spam prevention on links
%% (NoFollow=false)
-htmlize(S1) ->
- htmlize(S1, html).
-
-htmlize(S1, plaintext) ->
- S1;
+ fw(F, <<"<div class=\"rc\">">>),
+ fw(F,
+ <<"<div class=\"rct\" onclick=\"sh('o~p');return "
+ "false;\">~s</div>">>,
+ [Now2, ?T(<<"Room Occupants">>)]),
+ fw(F,
+ <<"<div class=\"rcos\" id=\"o~p\" style=\"displa"
+ "y: none;\" ><br/>~s</div>">>,
+ [Now2, RoomOccupants]),
+ fw(F, <<"</div>">>).
+
+htmlize(S1) -> htmlize(S1, html).
+
+htmlize(S1, plaintext) -> S1;
htmlize(S1, FileFormat) ->
htmlize(S1, false, FileFormat).
%% The NoFollow parameter tell if the spam prevention should be applied to the link found
%% true means 'apply nofollow on links'.
htmlize(S1, _NoFollow, plaintext) ->
- S1x = ejabberd_regexp:replace(S1, "<", ?PLAINTEXT_IN),
- ejabberd_regexp:replace(S1x, ">", ?PLAINTEXT_OUT);
+ S1x = ejabberd_regexp:replace(S1, <<"<">>, ?PLAINTEXT_IN),
+ ejabberd_regexp:replace(S1x, <<">">>, ?PLAINTEXT_OUT);
htmlize(S1, NoFollow, _FileFormat) ->
- S2_list = string:tokens(S1, "\n"),
- lists:foldl(
- fun(Si, Res) ->
- Si2 = htmlize2(Si, NoFollow),
- case Res of
- "" -> Si2;
- _ -> Res ++ "<br/>" ++ Si2
- end
- end,
- "",
- S2_list).
+ S2_list = str:tokens(S1, <<"\n">>),
+ lists:foldl(fun (Si, Res) ->
+ Si2 = htmlize2(Si, NoFollow),
+ case Res of
+ <<"">> -> Si2;
+ _ -> <<Res/binary, "<br/>", Si2/binary>>
+ end
+ end,
+ <<"">>, S2_list).
htmlize2(S1, NoFollow) ->
- S2 = ejabberd_regexp:greplace(S1, "\\&", "\\&amp;"),
- S3 = ejabberd_regexp:greplace(S2, "<", "\\&lt;"),
- S4 = ejabberd_regexp:greplace(S3, ">", "\\&gt;"),
- S5 = ejabberd_regexp:greplace(S4, "((http|https|ftp)://|(mailto|xmpp):)[^] )\'\"}]+",
- link_regexp(NoFollow)),
- %% Remove 'right-to-left override' unicode character 0x202e
- S6 = ejabberd_regexp:greplace(S5, " ", "\\&nbsp;\\&nbsp;"),
- S7 = ejabberd_regexp:greplace(S6, "\\t", "\\&nbsp;\\&nbsp;\\&nbsp;\\&nbsp;"),
- ejabberd_regexp:greplace(S7, [226,128,174], "[RLO]").
-
%% Regexp link
%% Add the nofollow rel attribute when required
-link_regexp(false) ->
- "<a href=\"&\">&</a>";
+ S2 = ejabberd_regexp:greplace(S1, <<"\\&">>,
+ <<"\\&amp;">>),
+ S3 = ejabberd_regexp:greplace(S2, <<"<">>,
+ <<"\\&lt;">>),
+ S4 = ejabberd_regexp:greplace(S3, <<">">>,
+ <<"\\&gt;">>),
+ S5 = ejabberd_regexp:greplace(S4,
+ <<"((http|https|ftp)://|(mailto|xmpp):)[^] "
+ ")'\"}]+">>,
+ link_regexp(NoFollow)),
+ S6 = ejabberd_regexp:greplace(S5, <<" ">>,
+ <<"\\&nbsp;\\&nbsp;">>),
+ S7 = ejabberd_regexp:greplace(S6, <<"\\t">>,
+ <<"\\&nbsp;\\&nbsp;\\&nbsp;\\&nbsp;">>),
+ ejabberd_regexp:greplace(S7, <<226, 128, 174>>,
+ <<"[RLO]">>).
+
+link_regexp(false) -> <<"<a href=\"&\">&</a>">>;
link_regexp(true) ->
- "<a href=\"&\" rel=\"nofollow\">&</a>".
+ <<"<a href=\"&\" rel=\"nofollow\">&</a>">>.
get_room_info(RoomJID, Opts) ->
- Title =
- case lists:keysearch(title, 1, Opts) of
- {value, {_, T}} -> T;
- false -> ""
- end,
- Subject =
- case lists:keysearch(subject, 1, Opts) of
- {value, {_, S}} -> S;
- false -> ""
- end,
- SubjectAuthor =
- case lists:keysearch(subject_author, 1, Opts) of
- {value, {_, SA}} -> SA;
- false -> ""
- end,
- #room{jid = jlib:jid_to_string(RoomJID),
- title = Title,
- subject = Subject,
- subject_author = SubjectAuthor,
- config = Opts
- }.
+ Title = case lists:keysearch(title, 1, Opts) of
+ {value, {_, T}} -> T;
+ false -> <<"">>
+ end,
+ Subject = case lists:keysearch(subject, 1, Opts) of
+ {value, {_, S}} -> S;
+ false -> <<"">>
+ end,
+ SubjectAuthor = case lists:keysearch(subject_author, 1,
+ Opts)
+ of
+ {value, {_, SA}} -> SA;
+ false -> <<"">>
+ end,
+ #room{jid = jlib:jid_to_string(RoomJID), title = Title,
+ subject = Subject, subject_author = SubjectAuthor,
+ config = Opts}.
roomconfig_to_string(Options, Lang, FileFormat) ->
- %% Get title, if available
Title = case lists:keysearch(title, 1, Options) of
- {value, Tuple} -> [Tuple];
- false -> []
+ {value, Tuple} -> [Tuple];
+ false -> []
end,
-
- %% Remove title from list
Os1 = lists:keydelete(title, 1, Options),
-
- %% Order list
Os2 = lists:sort(Os1),
-
- %% Add title to ordered list
Options2 = Title ++ Os2,
-
- lists:foldl(
- fun({Opt, Val}, R) ->
- case get_roomconfig_text(Opt) of
- undefined ->
- R;
- OptT ->
- OptText = ?T(OptT),
- R2 = case Val of
- false -> "<div class=\"rcod\">" ++ OptText ++ "</div>";
- true -> "<div class=\"rcoe\">" ++ OptText ++ "</div>";
- "" -> "<div class=\"rcod\">" ++ OptText ++ "</div>";
- T ->
- case Opt of
- password -> "<div class=\"rcoe\">" ++ OptText ++ "</div>";
- max_users -> "<div class=\"rcot\">" ++ OptText ++ ": \"" ++ htmlize(integer_to_list(T), FileFormat) ++ "\"</div>";
- title -> "<div class=\"rcot\">" ++ OptText ++ ": \"" ++ htmlize(T, FileFormat) ++ "\"</div>";
- description -> "<div class=\"rcot\">" ++ OptText ++ ": \"" ++ htmlize(T, FileFormat) ++ "\"</div>";
- allow_private_messages_from_visitors -> "<div class=\"rcot\">" ++ OptText ++ ": \"" ++ htmlize(?T(atom_to_list(T)), FileFormat) ++ "\"</div>";
- _ -> "\"" ++ T ++ "\""
- end
- end,
- R ++ R2
- end
- end,
- "",
- Options2).
-
-get_roomconfig_text(title) -> "Room title";
-get_roomconfig_text(persistent) -> "Make room persistent";
-get_roomconfig_text(public) -> "Make room public searchable";
-get_roomconfig_text(public_list) -> "Make participants list public";
-get_roomconfig_text(password_protected) -> "Make room password protected";
-get_roomconfig_text(password) -> "Password";
-get_roomconfig_text(anonymous) -> "This room is not anonymous";
-get_roomconfig_text(members_only) -> "Make room members-only";
-get_roomconfig_text(moderated) -> "Make room moderated";
-get_roomconfig_text(members_by_default) -> "Default users as participants";
-get_roomconfig_text(allow_change_subj) -> "Allow users to change the subject";
-get_roomconfig_text(allow_private_messages) -> "Allow users to send private messages";
-get_roomconfig_text(allow_private_messages_from_visitors) -> "Allow visitors to send private messages to";
-get_roomconfig_text(allow_query_users) -> "Allow users to query other users";
-get_roomconfig_text(allow_user_invites) -> "Allow users to send invites";
-get_roomconfig_text(logging) -> "Enable logging";
-get_roomconfig_text(allow_visitor_nickchange) -> "Allow visitors to change nickname";
-get_roomconfig_text(allow_visitor_status) -> "Allow visitors to send status text in presence updates";
-get_roomconfig_text(captcha_protected) -> "Make room captcha protected";
-get_roomconfig_text(description) -> "Room description";
+ lists:foldl(fun ({Opt, Val}, R) ->
+ case get_roomconfig_text(Opt) of
+ undefined -> R;
+ OptT ->
+ OptText = (?T(OptT)),
+ R2 = case Val of
+ false ->
+ <<"<div class=\"rcod\">",
+ OptText/binary, "</div>">>;
+ true ->
+ <<"<div class=\"rcoe\">",
+ OptText/binary, "</div>">>;
+ <<"">> ->
+ <<"<div class=\"rcod\">",
+ OptText/binary, "</div>">>;
+ T ->
+ case Opt of
+ password ->
+ <<"<div class=\"rcoe\">",
+ OptText/binary, "</div>">>;
+ max_users ->
+ <<"<div class=\"rcot\">",
+ OptText/binary, ": \"",
+ (htmlize(jlib:integer_to_binary(T),
+ FileFormat))/binary,
+ "\"</div>">>;
+ title ->
+ <<"<div class=\"rcot\">",
+ OptText/binary, ": \"",
+ (htmlize(T,
+ FileFormat))/binary,
+ "\"</div>">>;
+ description ->
+ <<"<div class=\"rcot\">",
+ OptText/binary, ": \"",
+ (htmlize(T,
+ FileFormat))/binary,
+ "\"</div>">>;
+ allow_private_messages_from_visitors ->
+ <<"<div class=\"rcot\">",
+ OptText/binary, ": \"",
+ (htmlize(?T((jlib:atom_to_binary(T))),
+ FileFormat))/binary,
+ "\"</div>">>;
+ _ -> <<"\"", T/binary, "\"">>
+ end
+ end,
+ <<R/binary, R2/binary>>
+ end
+ end,
+ <<"">>, Options2).
+
+get_roomconfig_text(title) -> <<"Room title">>;
+get_roomconfig_text(persistent) ->
+ <<"Make room persistent">>;
+get_roomconfig_text(public) ->
+ <<"Make room public searchable">>;
+get_roomconfig_text(public_list) ->
+ <<"Make participants list public">>;
+get_roomconfig_text(password_protected) ->
+ <<"Make room password protected">>;
+get_roomconfig_text(password) -> <<"Password">>;
+get_roomconfig_text(anonymous) ->
+ <<"This room is not anonymous">>;
+get_roomconfig_text(members_only) ->
+ <<"Make room members-only">>;
+get_roomconfig_text(moderated) ->
+ <<"Make room moderated">>;
+get_roomconfig_text(members_by_default) ->
+ <<"Default users as participants">>;
+get_roomconfig_text(allow_change_subj) ->
+ <<"Allow users to change the subject">>;
+get_roomconfig_text(allow_private_messages) ->
+ <<"Allow users to send private messages">>;
+get_roomconfig_text(allow_private_messages_from_visitors) ->
+ <<"Allow visitors to send private messages to">>;
+get_roomconfig_text(allow_query_users) ->
+ <<"Allow users to query other users">>;
+get_roomconfig_text(allow_user_invites) ->
+ <<"Allow users to send invites">>;
+get_roomconfig_text(logging) -> <<"Enable logging">>;
+get_roomconfig_text(allow_visitor_nickchange) ->
+ <<"Allow visitors to change nickname">>;
+get_roomconfig_text(allow_visitor_status) ->
+ <<"Allow visitors to send status text in "
+ "presence updates">>;
+get_roomconfig_text(captcha_protected) ->
+ <<"Make room captcha protected">>;
+get_roomconfig_text(description) ->
+ <<"Room description">>;
%% get_roomconfig_text(subject) -> "Subject";
%% get_roomconfig_text(subject_author) -> "Subject author";
-get_roomconfig_text(max_users) -> "Maximum Number of Occupants";
+get_roomconfig_text(max_users) ->
+ <<"Maximum Number of Occupants">>;
get_roomconfig_text(_) -> undefined.
%% Users = [{JID, Nick, Role}]
roomoccupants_to_string(Users, _FileFormat) ->
Res = [role_users_to_string(RoleS, Users1)
- || {RoleS, Users1} <- group_by_role(Users), Users1 /= []],
- lists:flatten(["<div class=\"rcot\">", Res, "</div>"]).
+ || {RoleS, Users1} <- group_by_role(Users),
+ Users1 /= []],
+ iolist_to_binary([<<"<div class=\"rcot\">">>, Res, <<"</div>">>]).
%% Users = [{JID, Nick, Role}]
group_by_role(Users) ->
- {Ms, Ps, Vs, Ns} =
- lists:foldl(
- fun({JID, Nick, moderator}, {Mod, Par, Vis, Non}) ->
- {[{JID, Nick}]++Mod, Par, Vis, Non};
- ({JID, Nick, participant}, {Mod, Par, Vis, Non}) ->
- {Mod, [{JID, Nick}]++Par, Vis, Non};
- ({JID, Nick, visitor}, {Mod, Par, Vis, Non}) ->
- {Mod, Par, [{JID, Nick}]++Vis, Non};
- ({JID, Nick, none}, {Mod, Par, Vis, Non}) ->
- {Mod, Par, Vis, [{JID, Nick}]++Non}
- end,
- {[], [], [], []},
- Users),
- case Ms of [] -> []; _ -> [{"Moderator", Ms}] end
- ++ case Ms of [] -> []; _ -> [{"Participant", Ps}] end
- ++ case Ms of [] -> []; _ -> [{"Visitor", Vs}] end
- ++ case Ms of [] -> []; _ -> [{"None", Ns}] end.
-
%% Role = atom()
%% Users = [{JID, Nick}]
+ {Ms, Ps, Vs, Ns} = lists:foldl(fun ({JID, Nick,
+ moderator},
+ {Mod, Par, Vis, Non}) ->
+ {[{JID, Nick}] ++ Mod, Par, Vis,
+ Non};
+ ({JID, Nick, participant},
+ {Mod, Par, Vis, Non}) ->
+ {Mod, [{JID, Nick}] ++ Par, Vis,
+ Non};
+ ({JID, Nick, visitor},
+ {Mod, Par, Vis, Non}) ->
+ {Mod, Par, [{JID, Nick}] ++ Vis,
+ Non};
+ ({JID, Nick, none},
+ {Mod, Par, Vis, Non}) ->
+ {Mod, Par, Vis, [{JID, Nick}] ++ Non}
+ end,
+ {[], [], [], []}, Users),
+ case Ms of
+ [] -> [];
+ _ -> [{<<"Moderator">>, Ms}]
+ end
+ ++
+ case Ms of
+ [] -> [];
+ _ -> [{<<"Participant">>, Ps}]
+ end
+ ++
+ case Ms of
+ [] -> [];
+ _ -> [{<<"Visitor">>, Vs}]
+ end
+ ++
+ case Ms of
+ [] -> [];
+ _ -> [{<<"None">>, Ns}]
+ end.
+
role_users_to_string(RoleS, Users) ->
SortedUsers = lists:keysort(2, Users),
- UsersString = [[Nick, "<br/>"] || {_JID, Nick} <- SortedUsers],
- [RoleS, ": ", UsersString].
+ UsersString = << <<Nick/binary, "<br/>">>
+ || {_JID, Nick} <- SortedUsers >>,
+ <<RoleS/binary, ": ", UsersString/binary>>.
get_room_occupants(RoomJIDString) ->
RoomJID = jlib:string_to_jid(RoomJIDString),
@@ -963,25 +1195,38 @@ get_room_occupants(RoomJIDString) ->
MucService = RoomJID#jid.lserver,
StateData = get_room_state(RoomName, MucService),
[{U#user.jid, U#user.nick, U#user.role}
- || {_, U} <- ?DICT:to_list(StateData#state.users)].
+ || {_, U} <- (?DICT):to_list(StateData#state.users)].
+
+-spec get_room_state(binary(), binary()) -> muc_room_state().
get_room_state(RoomName, MucService) ->
- case mnesia:dirty_read(muc_online_room, {RoomName, MucService}) of
- [R] ->
- RoomPid = R#muc_online_room.pid,
- get_room_state(RoomPid);
- [] ->
- #state{}
+ case mnesia:dirty_read(muc_online_room,
+ {RoomName, MucService})
+ of
+ [R] ->
+ RoomPid = R#muc_online_room.pid,
+ get_room_state(RoomPid);
+ [] -> #state{}
end.
+-spec get_room_state(pid()) -> muc_room_state().
+
get_room_state(RoomPid) ->
- {ok, R} = gen_fsm:sync_send_all_state_event(RoomPid, get_state),
+ {ok, R} = gen_fsm:sync_send_all_state_event(RoomPid,
+ get_state),
R.
get_proc_name(Host) -> gen_mod:get_module_proc(Host, ?PROCNAME).
calc_hour_offset(TimeHere) ->
TimeZero = calendar:now_to_universal_time(now()),
- TimeHereHour = calendar:datetime_to_gregorian_seconds(TimeHere) div 3600,
- TimeZeroHour = calendar:datetime_to_gregorian_seconds(TimeZero) div 3600,
+ TimeHereHour =
+ calendar:datetime_to_gregorian_seconds(TimeHere) div
+ 3600,
+ TimeZeroHour =
+ calendar:datetime_to_gregorian_seconds(TimeZero) div
+ 3600,
TimeHereHour - TimeZeroHour.
+
+fjoin(FileList) ->
+ list_to_binary(filename:join([binary_to_list(File) || File <- FileList])).
diff --git a/src/mod_muc/mod_muc_room.erl b/src/mod_muc/mod_muc_room.erl
index 02c83ed2c..ee9c52390 100644
--- a/src/mod_muc/mod_muc_room.erl
+++ b/src/mod_muc/mod_muc_room.erl
@@ -25,11 +25,11 @@
%%%----------------------------------------------------------------------
-module(mod_muc_room).
+
-author('alexey@process-one.net').
-behaviour(gen_fsm).
-
%% External exports
-export([start_link/9,
start_link/7,
@@ -47,7 +47,9 @@
code_change/4]).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
+
-include("mod_muc_room.hrl").
-define(MAX_USERS_DEFAULT_LIST,
@@ -56,9 +58,13 @@
%-define(DBGFSM, true).
-ifdef(DBGFSM).
+
-define(FSMOPTS, [{debug, [trace]}]).
+
-else.
+
-define(FSMOPTS, []).
+
-endif.
%% Module start with or without supervisor:
@@ -114,12 +120,10 @@ init([Host, ServerHost, Access, Room, HistorySize, RoomShaper, Creator, _Nick, D
process_flag(trap_exit, true),
Shaper = shaper:new(RoomShaper),
State = set_affiliation(Creator, owner,
- #state{host = Host,
- server_host = ServerHost,
- access = Access,
- room = Room,
+ #state{host = Host, server_host = ServerHost,
+ access = Access, room = Room,
history = lqueue_new(HistorySize),
- jid = jlib:make_jid(Room, Host, ""),
+ jid = jlib:make_jid(Room, Host, <<"">>),
just_created = true,
room_shaper = Shaper}),
State1 = set_opts(DefRoomOpts, State),
@@ -136,7 +140,7 @@ init([Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts]) ->
access = Access,
room = Room,
history = lqueue_new(HistorySize),
- jid = jlib:make_jid(Room, Host, ""),
+ jid = jlib:make_jid(Room, Host, <<"">>),
room_shaper = Shaper}),
add_to_log(room_existence, started, State),
{ok, normal_state, State}.
@@ -147,523 +151,526 @@ init([Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts]) ->
%% {next_state, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData}
%%----------------------------------------------------------------------
-normal_state({route, From, "",
- {xmlelement, "message", Attrs, Els} = Packet},
+normal_state({route, From, <<"">>,
+ #xmlel{name = <<"message">>, attrs = Attrs,
+ children = Els} =
+ Packet},
StateData) ->
- Lang = xml:get_attr_s("xml:lang", Attrs),
+ Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
case is_user_online(From, StateData) orelse
- is_user_allowed_message_nonparticipant(From, StateData) of
- true ->
- case xml:get_attr_s("type", Attrs) of
- "groupchat" ->
- Activity = get_user_activity(From, StateData),
- Now = now_to_usec(now()),
- MinMessageInterval =
- trunc(gen_mod:get_module_opt(
- StateData#state.server_host,
- mod_muc, min_message_interval, 0) * 1000000),
- Size = element_size(Packet),
- {MessageShaper, MessageShaperInterval} =
- shaper:update(Activity#activity.message_shaper, Size),
- if
- Activity#activity.message /= undefined ->
- ErrText = "Traffic rate limit is exceeded",
- Err = jlib:make_error_reply(
- Packet, ?ERRT_RESOURCE_CONSTRAINT(Lang, ErrText)),
- ejabberd_router:route(
- StateData#state.jid,
- From, Err),
- {next_state, normal_state, StateData};
- Now >= Activity#activity.message_time + MinMessageInterval,
- MessageShaperInterval == 0 ->
- {RoomShaper, RoomShaperInterval} =
- shaper:update(StateData#state.room_shaper, Size),
- RoomQueueEmpty = queue:is_empty(
- StateData#state.room_queue),
- if
- RoomShaperInterval == 0,
- RoomQueueEmpty ->
- NewActivity = Activity#activity{
- message_time = Now,
- message_shaper = MessageShaper},
- StateData1 =
- store_user_activity(
- From, NewActivity, StateData),
- StateData2 =
- StateData1#state{
- room_shaper = RoomShaper},
- process_groupchat_message(From, Packet, StateData2);
- true ->
- StateData1 =
- if
- RoomQueueEmpty ->
- erlang:send_after(
- RoomShaperInterval, self(),
- process_room_queue),
- StateData#state{
- room_shaper = RoomShaper};
- true ->
- StateData
- end,
- NewActivity = Activity#activity{
- message_time = Now,
- message_shaper = MessageShaper,
- message = Packet},
- RoomQueue = queue:in(
- {message, From},
- StateData#state.room_queue),
- StateData2 =
- store_user_activity(
- From, NewActivity, StateData1),
- StateData3 =
- StateData2#state{
- room_queue = RoomQueue},
- {next_state, normal_state, StateData3}
- end;
- true ->
- MessageInterval =
- (Activity#activity.message_time +
- MinMessageInterval - Now) div 1000,
- Interval = lists:max([MessageInterval,
- MessageShaperInterval]),
- erlang:send_after(
- Interval, self(), {process_user_message, From}),
- NewActivity = Activity#activity{
- message = Packet,
- message_shaper = MessageShaper},
- StateData1 =
- store_user_activity(
- From, NewActivity, StateData),
- {next_state, normal_state, StateData1}
- end;
- "error" ->
- case is_user_online(From, StateData) of
- true ->
- ErrorText = "This participant is kicked from the room because "
- "he sent an error message",
- NewState = expulse_participant(Packet, From, StateData,
- translate:translate(Lang, ErrorText)),
- {next_state, normal_state, NewState};
- _ ->
- {next_state, normal_state, StateData}
- end;
- "chat" ->
- ErrText = "It is not allowed to send private messages to the conference",
- Err = jlib:make_error_reply(
- Packet, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)),
- ejabberd_router:route(
- StateData#state.jid,
- From, Err),
- {next_state, normal_state, StateData};
- Type when (Type == "") or (Type == "normal") ->
- IsInvitation = is_invitation(Els),
- IsVoiceRequest = is_voice_request(Els)
- and is_visitor(From, StateData),
- IsVoiceApprovement = is_voice_approvement(Els)
- and not is_visitor(From, StateData),
- if IsInvitation ->
- case catch check_invitation(From, Els, Lang, StateData) of
- {error, Error} ->
- Err = jlib:make_error_reply(
- Packet, Error),
- ejabberd_router:route(
- StateData#state.jid,
- From, Err),
- {next_state, normal_state, StateData};
- IJID ->
- Config = StateData#state.config,
- case Config#config.members_only of
+ is_user_allowed_message_nonparticipant(From, StateData)
+ of
+ true ->
+ case xml:get_attr_s(<<"type">>, Attrs) of
+ <<"groupchat">> ->
+ Activity = get_user_activity(From, StateData),
+ Now = now_to_usec(now()),
+ MinMessageInterval =
+ trunc(gen_mod:get_module_opt(StateData#state.server_host,
+ mod_muc, min_message_interval, fun(MMI) when is_integer(MMI) -> MMI end, 0)
+ * 1000000),
+ Size = element_size(Packet),
+ {MessageShaper, MessageShaperInterval} =
+ shaper:update(Activity#activity.message_shaper, Size),
+ if Activity#activity.message /= undefined ->
+ ErrText = <<"Traffic rate limit is exceeded">>,
+ Err = jlib:make_error_reply(Packet,
+ ?ERRT_RESOURCE_CONSTRAINT(Lang,
+ ErrText)),
+ ejabberd_router:route(StateData#state.jid, From, Err),
+ {next_state, normal_state, StateData};
+ Now >=
+ Activity#activity.message_time + MinMessageInterval,
+ MessageShaperInterval == 0 ->
+ {RoomShaper, RoomShaperInterval} =
+ shaper:update(StateData#state.room_shaper, Size),
+ RoomQueueEmpty =
+ queue:is_empty(StateData#state.room_queue),
+ if RoomShaperInterval == 0, RoomQueueEmpty ->
+ NewActivity = Activity#activity{message_time =
+ Now,
+ message_shaper =
+ MessageShaper},
+ StateData1 = store_user_activity(From,
+ NewActivity,
+ StateData),
+ StateData2 = StateData1#state{room_shaper =
+ RoomShaper},
+ process_groupchat_message(From, Packet,
+ StateData2);
+ true ->
+ StateData1 = if RoomQueueEmpty ->
+ erlang:send_after(RoomShaperInterval,
+ self(),
+ process_room_queue),
+ StateData#state{room_shaper =
+ RoomShaper};
+ true -> StateData
+ end,
+ NewActivity = Activity#activity{message_time =
+ Now,
+ message_shaper =
+ MessageShaper,
+ message = Packet},
+ RoomQueue = queue:in({message, From},
+ StateData#state.room_queue),
+ StateData2 = store_user_activity(From,
+ NewActivity,
+ StateData1),
+ StateData3 = StateData2#state{room_queue =
+ RoomQueue},
+ {next_state, normal_state, StateData3}
+ end;
+ true ->
+ MessageInterval = (Activity#activity.message_time +
+ MinMessageInterval
+ - Now)
+ div 1000,
+ Interval = lists:max([MessageInterval,
+ MessageShaperInterval]),
+ erlang:send_after(Interval, self(),
+ {process_user_message, From}),
+ NewActivity = Activity#activity{message = Packet,
+ message_shaper =
+ MessageShaper},
+ StateData1 = store_user_activity(From, NewActivity,
+ StateData),
+ {next_state, normal_state, StateData1}
+ end;
+ <<"error">> ->
+ case is_user_online(From, StateData) of
+ true ->
+ ErrorText = <<"This participant is kicked from the "
+ "room because he sent an error message">>,
+ NewState = expulse_participant(Packet, From, StateData,
+ translate:translate(Lang,
+ ErrorText)),
+ {next_state, normal_state, NewState};
+ _ -> {next_state, normal_state, StateData}
+ end;
+ <<"chat">> ->
+ ErrText =
+ <<"It is not allowed to send private messages "
+ "to the conference">>,
+ Err = jlib:make_error_reply(Packet,
+ ?ERRT_NOT_ACCEPTABLE(Lang,
+ ErrText)),
+ ejabberd_router:route(StateData#state.jid, From, Err),
+ {next_state, normal_state, StateData};
+ Type when (Type == <<"">>) or (Type == <<"normal">>) ->
+ IsInvitation = is_invitation(Els),
+ IsVoiceRequest = is_voice_request(Els) and
+ is_visitor(From, StateData),
+ IsVoiceApprovement = is_voice_approvement(Els) and
+ not is_visitor(From, StateData),
+ if IsInvitation ->
+ case catch check_invitation(From, Els, Lang, StateData)
+ of
+ {error, Error} ->
+ Err = jlib:make_error_reply(Packet, Error),
+ ejabberd_router:route(StateData#state.jid, From, Err),
+ {next_state, normal_state, StateData};
+ IJID ->
+ Config = StateData#state.config,
+ case Config#config.members_only of
+ true ->
+ case get_affiliation(IJID, StateData) of
+ none ->
+ NSD = set_affiliation(IJID, member,
+ StateData),
+ case
+ (NSD#state.config)#config.persistent
+ of
+ true ->
+ mod_muc:store_room(NSD#state.server_host,
+ NSD#state.host,
+ NSD#state.room,
+ make_opts(NSD));
+ _ -> ok
+ end,
+ {next_state, normal_state, NSD};
+ _ -> {next_state, normal_state, StateData}
+ end;
+ false -> {next_state, normal_state, StateData}
+ end
+ end;
+ IsVoiceRequest ->
+ NewStateData = case
+ (StateData#state.config)#config.allow_voice_requests
+ of
true ->
- case get_affiliation(IJID, StateData) of
- none ->
- NSD = set_affiliation(
- IJID,
- member,
- StateData),
- case (NSD#state.config)#config.persistent of
- true ->
- mod_muc:store_room(
- NSD#state.server_host,
- NSD#state.host,
- NSD#state.room,
- make_opts(NSD));
- _ ->
- ok
- end,
- {next_state, normal_state, NSD};
- _ ->
- {next_state, normal_state,
- StateData}
- end;
+ MinInterval =
+ (StateData#state.config)#config.voice_request_min_interval,
+ BareFrom =
+ jlib:jid_remove_resource(jlib:jid_tolower(From)),
+ NowPriority = -now_to_usec(now()),
+ CleanPriority = NowPriority +
+ MinInterval *
+ 1000000,
+ Times =
+ clean_treap(StateData#state.last_voice_request_time,
+ CleanPriority),
+ case treap:lookup(BareFrom, Times)
+ of
+ error ->
+ Times1 =
+ treap:insert(BareFrom,
+ NowPriority,
+ true, Times),
+ NSD =
+ StateData#state{last_voice_request_time
+ =
+ Times1},
+ send_voice_request(From, NSD),
+ NSD;
+ {ok, _, _} ->
+ ErrText =
+ <<"Please, wait for a while before sending "
+ "new voice request">>,
+ Err =
+ jlib:make_error_reply(Packet,
+ ?ERRT_NOT_ACCEPTABLE(Lang,
+ ErrText)),
+ ejabberd_router:route(StateData#state.jid,
+ From, Err),
+ StateData#state{last_voice_request_time
+ = Times}
+ end;
false ->
- {next_state, normal_state, StateData}
- end
- end;
- IsVoiceRequest ->
- NewStateData =
- case (StateData#state.config)#config.allow_voice_requests of
- true ->
- MinInterval = (StateData#state.config)
- #config.voice_request_min_interval,
- BareFrom = jlib:jid_remove_resource(
- jlib:jid_tolower(From)),
- NowPriority = -now_to_usec(now()),
- CleanPriority =
- NowPriority + MinInterval*1000000,
- Times = clean_treap(
- StateData#state.last_voice_request_time,
- CleanPriority),
- case treap:lookup(BareFrom, Times) of
- error ->
- Times1 = treap:insert(
- BareFrom,
- NowPriority,
- true, Times),
- NSD = StateData#state{
- last_voice_request_time =
- Times1},
- send_voice_request(From, NSD),
- NSD;
- {ok, _, _} ->
- ErrText = "Please, wait for "
- "a while before sending "
- "new voice request",
- Err = jlib:make_error_reply(
- Packet,
- ?ERRT_NOT_ACCEPTABLE(
- Lang, ErrText)),
- ejabberd_router:route(
- StateData#state.jid,
- From, Err),
- StateData#state{
- last_voice_request_time =
- Times}
- end;
- false ->
- ErrText = "Voice requests are "
- "disabled in this conference",
- Err = jlib:make_error_reply(
- Packet,
- ?ERRT_FORBIDDEN(
- Lang, ErrText)),
- ejabberd_router:route(
- StateData#state.jid, From, Err),
- StateData
- end,
- {next_state, normal_state, NewStateData};
- IsVoiceApprovement ->
- NewStateData =
- case is_moderator(From, StateData) of
- true ->
- case extract_jid_from_voice_approvement(Els) of
- error ->
- ErrText = "Failed to extract "
- "JID from your voice "
- "request approval",
- Err = jlib:make_error_reply(
- Packet,
- ?ERRT_BAD_REQUEST(
- Lang, ErrText)),
- ejabberd_router:route(
- StateData#state.jid,
- From, Err),
- StateData;
- {ok, TargetJid} ->
- case is_visitor(
- TargetJid, StateData) of
- true ->
- Reason = [],
- NSD = set_role(
- TargetJid,
- participant,
- StateData),
- catch send_new_presence(
- TargetJid,
- Reason, NSD),
+ ErrText =
+ <<"Voice requests are disabled in this "
+ "conference">>,
+ Err = jlib:make_error_reply(Packet,
+ ?ERRT_FORBIDDEN(Lang,
+ ErrText)),
+ ejabberd_router:route(StateData#state.jid,
+ From, Err),
+ StateData
+ end,
+ {next_state, normal_state, NewStateData};
+ IsVoiceApprovement ->
+ NewStateData = case is_moderator(From, StateData) of
+ true ->
+ case
+ extract_jid_from_voice_approvement(Els)
+ of
+ error ->
+ ErrText =
+ <<"Failed to extract JID from your voice "
+ "request approval">>,
+ Err =
+ jlib:make_error_reply(Packet,
+ ?ERRT_BAD_REQUEST(Lang,
+ ErrText)),
+ ejabberd_router:route(StateData#state.jid,
+ From, Err),
+ StateData;
+ {ok, TargetJid} ->
+ case is_visitor(TargetJid,
+ StateData)
+ of
+ true ->
+ Reason = <<>>,
+ NSD =
+ set_role(TargetJid,
+ participant,
+ StateData),
+ catch
+ send_new_presence(TargetJid,
+ Reason,
+ NSD),
NSD;
- _ ->
- StateData
- end
- end;
- _ ->
- ErrText = "Only moderators can "
- "approve voice requests",
- Err = jlib:make_error_reply(
- Packet,
- ?ERRT_NOT_ALLOWED(
- Lang, ErrText)),
- ejabberd_router:route(
- StateData#state.jid, From, Err),
- StateData
- end,
- {next_state, normal_state, NewStateData};
- true ->
- {next_state, normal_state, StateData}
- end;
- _ ->
- ErrText = "Improper message type",
- Err = jlib:make_error_reply(
- Packet, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)),
- ejabberd_router:route(
- StateData#state.jid,
- From, Err),
- {next_state, normal_state, StateData}
- end;
- _ ->
- case xml:get_attr_s("type", Attrs) of
- "error" ->
- ok;
- _ ->
- handle_roommessage_from_nonparticipant(Packet, Lang, StateData, From)
- end,
- {next_state, normal_state, StateData}
+ _ -> StateData
+ end
+ end;
+ _ ->
+ ErrText =
+ <<"Only moderators can approve voice requests">>,
+ Err = jlib:make_error_reply(Packet,
+ ?ERRT_NOT_ALLOWED(Lang,
+ ErrText)),
+ ejabberd_router:route(StateData#state.jid,
+ From, Err),
+ StateData
+ end,
+ {next_state, normal_state, NewStateData};
+ true -> {next_state, normal_state, StateData}
+ end;
+ _ ->
+ ErrText = <<"Improper message type">>,
+ Err = jlib:make_error_reply(Packet,
+ ?ERRT_NOT_ACCEPTABLE(Lang,
+ ErrText)),
+ ejabberd_router:route(StateData#state.jid, From, Err),
+ {next_state, normal_state, StateData}
+ end;
+ _ ->
+ case xml:get_attr_s(<<"type">>, Attrs) of
+ <<"error">> -> ok;
+ _ ->
+ handle_roommessage_from_nonparticipant(Packet, Lang,
+ StateData, From)
+ end,
+ {next_state, normal_state, StateData}
end;
-
-normal_state({route, From, "",
- {xmlelement, "iq", _Attrs, _Els} = Packet},
+normal_state({route, From, <<"">>,
+ #xmlel{name = <<"iq">>} = Packet},
StateData) ->
case jlib:iq_query_info(Packet) of
- #iq{type = Type, xmlns = XMLNS, lang = Lang, sub_el = SubEl} = IQ when
- (XMLNS == ?NS_MUC_ADMIN) or
- (XMLNS == ?NS_MUC_OWNER) or
- (XMLNS == ?NS_DISCO_INFO) or
- (XMLNS == ?NS_DISCO_ITEMS) or
- (XMLNS == ?NS_CAPTCHA) ->
- Res1 = case XMLNS of
- ?NS_MUC_ADMIN ->
- process_iq_admin(From, Type, Lang, SubEl, StateData);
- ?NS_MUC_OWNER ->
- process_iq_owner(From, Type, Lang, SubEl, StateData);
- ?NS_DISCO_INFO ->
- process_iq_disco_info(From, Type, Lang, StateData);
- ?NS_DISCO_ITEMS ->
- process_iq_disco_items(From, Type, Lang, StateData);
- ?NS_CAPTCHA ->
- process_iq_captcha(From, Type, Lang, SubEl, StateData)
- end,
- {IQRes, NewStateData} =
- case Res1 of
- {result, Res, SD} ->
- {IQ#iq{type = result,
- sub_el = [{xmlelement, "query",
- [{"xmlns", XMLNS}],
- Res
- }]},
- SD};
- {error, Error} ->
- {IQ#iq{type = error,
- sub_el = [SubEl, Error]},
- StateData}
- end,
- ejabberd_router:route(StateData#state.jid,
- From,
- jlib:iq_to_xml(IQRes)),
- case NewStateData of
- stop ->
- {stop, normal, StateData};
- _ ->
- {next_state, normal_state, NewStateData}
- end;
- reply ->
- {next_state, normal_state, StateData};
- _ ->
- Err = jlib:make_error_reply(
- Packet, ?ERR_FEATURE_NOT_IMPLEMENTED),
- ejabberd_router:route(StateData#state.jid, From, Err),
- {next_state, normal_state, StateData}
+ #iq{type = Type, xmlns = XMLNS, lang = Lang,
+ sub_el = SubEl} =
+ IQ
+ when (XMLNS == (?NS_MUC_ADMIN)) or
+ (XMLNS == (?NS_MUC_OWNER))
+ or (XMLNS == (?NS_DISCO_INFO))
+ or (XMLNS == (?NS_DISCO_ITEMS))
+ or (XMLNS == (?NS_CAPTCHA)) ->
+ Res1 = case XMLNS of
+ ?NS_MUC_ADMIN ->
+ process_iq_admin(From, Type, Lang, SubEl, StateData);
+ ?NS_MUC_OWNER ->
+ process_iq_owner(From, Type, Lang, SubEl, StateData);
+ ?NS_DISCO_INFO ->
+ process_iq_disco_info(From, Type, Lang, StateData);
+ ?NS_DISCO_ITEMS ->
+ process_iq_disco_items(From, Type, Lang, StateData);
+ ?NS_CAPTCHA ->
+ process_iq_captcha(From, Type, Lang, SubEl, StateData)
+ end,
+ {IQRes, NewStateData} = case Res1 of
+ {result, Res, SD} ->
+ {IQ#iq{type = result,
+ sub_el =
+ [#xmlel{name = <<"query">>,
+ attrs =
+ [{<<"xmlns">>,
+ XMLNS}],
+ children = Res}]},
+ SD};
+ {error, Error} ->
+ {IQ#iq{type = error,
+ sub_el = [SubEl, Error]},
+ StateData}
+ end,
+ ejabberd_router:route(StateData#state.jid, From,
+ jlib:iq_to_xml(IQRes)),
+ case NewStateData of
+ stop -> {stop, normal, StateData};
+ _ -> {next_state, normal_state, NewStateData}
+ end;
+ reply -> {next_state, normal_state, StateData};
+ _ ->
+ Err = jlib:make_error_reply(Packet,
+ ?ERR_FEATURE_NOT_IMPLEMENTED),
+ ejabberd_router:route(StateData#state.jid, From, Err),
+ {next_state, normal_state, StateData}
end;
-
normal_state({route, From, Nick,
- {xmlelement, "presence", _Attrs, _Els} = Packet},
+ #xmlel{name = <<"presence">>} = Packet},
StateData) ->
Activity = get_user_activity(From, StateData),
Now = now_to_usec(now()),
MinPresenceInterval =
- trunc(gen_mod:get_module_opt(
- StateData#state.server_host,
- mod_muc, min_presence_interval, 0) * 1000000),
- if
- (Now >= Activity#activity.presence_time + MinPresenceInterval) and
- (Activity#activity.presence == undefined) ->
- NewActivity = Activity#activity{presence_time = Now},
- StateData1 = store_user_activity(From, NewActivity, StateData),
- process_presence(From, Nick, Packet, StateData1);
- true ->
- if
- Activity#activity.presence == undefined ->
- Interval = (Activity#activity.presence_time +
- MinPresenceInterval - Now) div 1000,
- erlang:send_after(
- Interval, self(), {process_user_presence, From});
- true ->
- ok
- end,
- NewActivity = Activity#activity{presence = {Nick, Packet}},
- StateData1 = store_user_activity(From, NewActivity, StateData),
- {next_state, normal_state, StateData1}
+ trunc(gen_mod:get_module_opt(StateData#state.server_host,
+ mod_muc, min_presence_interval,
+ fun(I) when is_number(I), I>=0 ->
+ I
+ end, 0)
+ * 1000000),
+ if (Now >=
+ Activity#activity.presence_time + MinPresenceInterval)
+ and (Activity#activity.presence == undefined) ->
+ NewActivity = Activity#activity{presence_time = Now},
+ StateData1 = store_user_activity(From, NewActivity,
+ StateData),
+ process_presence(From, Nick, Packet, StateData1);
+ true ->
+ if Activity#activity.presence == undefined ->
+ Interval = (Activity#activity.presence_time +
+ MinPresenceInterval
+ - Now)
+ div 1000,
+ erlang:send_after(Interval, self(),
+ {process_user_presence, From});
+ true -> ok
+ end,
+ NewActivity = Activity#activity{presence =
+ {Nick, Packet}},
+ StateData1 = store_user_activity(From, NewActivity,
+ StateData),
+ {next_state, normal_state, StateData1}
end;
-
normal_state({route, From, ToNick,
- {xmlelement, "message", Attrs, _} = Packet},
+ #xmlel{name = <<"message">>, attrs = Attrs} = Packet},
StateData) ->
- Type = xml:get_attr_s("type", Attrs),
- Lang = xml:get_attr_s("xml:lang", Attrs),
- case decide_fate_message(Type, Packet, From, StateData) of
- {expulse_sender, Reason} ->
- ?DEBUG(Reason, []),
- ErrorText = "This participant is kicked from the room because "
- "he sent an error message to another participant",
- NewState = expulse_participant(Packet, From, StateData,
- translate:translate(Lang, ErrorText)),
- {next_state, normal_state, NewState};
- forget_message ->
- {next_state, normal_state, StateData};
- continue_delivery ->
- case {(StateData#state.config)#config.allow_private_messages,
- is_user_online(From, StateData)} of
- {true, true} ->
- case Type of
- "groupchat" ->
- ErrText = "It is not allowed to send private "
- "messages of type \"groupchat\"",
- Err = jlib:make_error_reply(
- Packet, ?ERRT_BAD_REQUEST(Lang, ErrText)),
- ejabberd_router:route(
- jlib:jid_replace_resource(
- StateData#state.jid,
- ToNick),
- From, Err);
- _ ->
- case find_jids_by_nick(ToNick, StateData) of
- false ->
- ErrText = "Recipient is not in the conference room",
- Err = jlib:make_error_reply(
- Packet, ?ERRT_ITEM_NOT_FOUND(Lang, ErrText)),
- ejabberd_router:route(
- jlib:jid_replace_resource(
- StateData#state.jid,
- ToNick),
- From, Err);
- ToJIDs ->
- SrcIsVisitor = is_visitor(From, StateData),
- DstIsModerator = is_moderator(hd(ToJIDs), StateData),
- PmFromVisitors = (StateData#state.config)#config.allow_private_messages_from_visitors,
- if SrcIsVisitor == false;
- PmFromVisitors == anyone;
- (PmFromVisitors == moderators) and (DstIsModerator) ->
- {ok, #user{nick = FromNick}} =
- ?DICT:find(jlib:jid_tolower(From),
- StateData#state.users),
- FromNickJID = jlib:jid_replace_resource(StateData#state.jid, FromNick),
- [ejabberd_router:route(FromNickJID, ToJID, Packet) || ToJID <- ToJIDs];
- true ->
- ErrText = "It is not allowed to send private messages",
- Err = jlib:make_error_reply(
- Packet, ?ERRT_FORBIDDEN(Lang, ErrText)),
- ejabberd_router:route(
- jlib:jid_replace_resource(
- StateData#state.jid,
- ToNick),
- From, Err)
- end
+ Type = xml:get_attr_s(<<"type">>, Attrs),
+ Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
+ case decide_fate_message(Type, Packet, From, StateData)
+ of
+ {expulse_sender, Reason} ->
+ ?DEBUG(Reason, []),
+ ErrorText = <<"This participant is kicked from the "
+ "room because he sent an error message "
+ "to another participant">>,
+ NewState = expulse_participant(Packet, From, StateData,
+ translate:translate(Lang, ErrorText)),
+ {next_state, normal_state, NewState};
+ forget_message -> {next_state, normal_state, StateData};
+ continue_delivery ->
+ case
+ {(StateData#state.config)#config.allow_private_messages,
+ is_user_online(From, StateData)}
+ of
+ {true, true} ->
+ case Type of
+ <<"groupchat">> ->
+ ErrText =
+ <<"It is not allowed to send private messages "
+ "of type \"groupchat\"">>,
+ Err = jlib:make_error_reply(Packet,
+ ?ERRT_BAD_REQUEST(Lang,
+ ErrText)),
+ ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid,
+ ToNick),
+ From, Err);
+ _ ->
+ case find_jids_by_nick(ToNick, StateData) of
+ false ->
+ ErrText =
+ <<"Recipient is not in the conference room">>,
+ Err = jlib:make_error_reply(Packet,
+ ?ERRT_ITEM_NOT_FOUND(Lang,
+ ErrText)),
+ ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid,
+ ToNick),
+ From, Err);
+ ToJIDs ->
+ SrcIsVisitor = is_visitor(From, StateData),
+ DstIsModerator = is_moderator(hd(ToJIDs),
+ StateData),
+ PmFromVisitors =
+ (StateData#state.config)#config.allow_private_messages_from_visitors,
+ if SrcIsVisitor == false;
+ PmFromVisitors == anyone;
+ (PmFromVisitors == moderators) and
+ DstIsModerator ->
+ {ok, #user{nick = FromNick}} =
+ (?DICT):find(jlib:jid_tolower(From),
+ StateData#state.users),
+ FromNickJID =
+ jlib:jid_replace_resource(StateData#state.jid,
+ FromNick),
+ [ejabberd_router:route(FromNickJID, ToJID, Packet)
+ || ToJID <- ToJIDs];
+ true ->
+ ErrText =
+ <<"It is not allowed to send private messages">>,
+ Err = jlib:make_error_reply(Packet,
+ ?ERRT_FORBIDDEN(Lang,
+ ErrText)),
+ ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid,
+ ToNick),
+ From, Err)
end
- end;
- {true, false} ->
- ErrText = "Only occupants are allowed to send messages to the conference",
- Err = jlib:make_error_reply(
- Packet, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)),
- ejabberd_router:route(
- jlib:jid_replace_resource(
- StateData#state.jid,
- ToNick),
- From, Err);
- {false, _} ->
- ErrText = "It is not allowed to send private messages",
- Err = jlib:make_error_reply(
- Packet, ?ERRT_FORBIDDEN(Lang, ErrText)),
- ejabberd_router:route(
- jlib:jid_replace_resource(
- StateData#state.jid,
- ToNick),
- From, Err)
- end,
- {next_state, normal_state, StateData}
+ end
+ end;
+ {true, false} ->
+ ErrText =
+ <<"Only occupants are allowed to send messages "
+ "to the conference">>,
+ Err = jlib:make_error_reply(Packet,
+ ?ERRT_NOT_ACCEPTABLE(Lang,
+ ErrText)),
+ ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid,
+ ToNick),
+ From, Err);
+ {false, _} ->
+ ErrText =
+ <<"It is not allowed to send private messages">>,
+ Err = jlib:make_error_reply(Packet,
+ ?ERRT_FORBIDDEN(Lang, ErrText)),
+ ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid,
+ ToNick),
+ From, Err)
+ end,
+ {next_state, normal_state, StateData}
end;
-
normal_state({route, From, ToNick,
- {xmlelement, "iq", Attrs, _Els} = Packet},
+ #xmlel{name = <<"iq">>, attrs = Attrs} = Packet},
StateData) ->
- Lang = xml:get_attr_s("xml:lang", Attrs),
- StanzaId = xml:get_attr_s("id", Attrs),
+ Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
+ StanzaId = xml:get_attr_s(<<"id">>, Attrs),
case {(StateData#state.config)#config.allow_query_users,
- is_user_online_iq(StanzaId, From, StateData)} of
- {true, {true, NewId, FromFull}} ->
- case find_jid_by_nick(ToNick, StateData) of
- false ->
- case jlib:iq_query_info(Packet) of
- reply ->
- ok;
- _ ->
- ErrText = "Recipient is not in the conference room",
- Err = jlib:make_error_reply(
- Packet, ?ERRT_ITEM_NOT_FOUND(Lang, ErrText)),
- ejabberd_router:route(
- jlib:jid_replace_resource(
- StateData#state.jid, ToNick),
- From, Err)
- end;
- ToJID ->
- {ok, #user{nick = FromNick}} =
- ?DICT:find(jlib:jid_tolower(FromFull),
- StateData#state.users),
- {ToJID2, Packet2} = handle_iq_vcard(FromFull, ToJID,
- StanzaId, NewId,Packet),
- ejabberd_router:route(
- jlib:jid_replace_resource(StateData#state.jid, FromNick),
- ToJID2, Packet2)
- end;
- {_, {false, _, _}} ->
- case jlib:iq_query_info(Packet) of
- reply ->
- ok;
- _ ->
- ErrText = "Only occupants are allowed to send queries to the conference",
- Err = jlib:make_error_reply(
- Packet, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)),
- ejabberd_router:route(
- jlib:jid_replace_resource(StateData#state.jid, ToNick),
- From, Err)
- end;
- _ ->
- case jlib:iq_query_info(Packet) of
- reply ->
- ok;
- _ ->
- ErrText = "Queries to the conference members are not allowed in this room",
- Err = jlib:make_error_reply(
- Packet, ?ERRT_NOT_ALLOWED(Lang, ErrText)),
- ejabberd_router:route(
- jlib:jid_replace_resource(StateData#state.jid, ToNick),
- From, Err)
- end
+ is_user_online_iq(StanzaId, From, StateData)}
+ of
+ {true, {true, NewId, FromFull}} ->
+ case find_jid_by_nick(ToNick, StateData) of
+ false ->
+ case jlib:iq_query_info(Packet) of
+ reply -> ok;
+ _ ->
+ ErrText = <<"Recipient is not in the conference room">>,
+ Err = jlib:make_error_reply(Packet,
+ ?ERRT_ITEM_NOT_FOUND(Lang,
+ ErrText)),
+ ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid,
+ ToNick),
+ From, Err)
+ end;
+ ToJID ->
+ {ok, #user{nick = FromNick}} =
+ (?DICT):find(jlib:jid_tolower(FromFull),
+ StateData#state.users),
+ {ToJID2, Packet2} = handle_iq_vcard(FromFull, ToJID,
+ StanzaId, NewId, Packet),
+ ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid,
+ FromNick),
+ ToJID2, Packet2)
+ end;
+ {_, {false, _, _}} ->
+ case jlib:iq_query_info(Packet) of
+ reply -> ok;
+ _ ->
+ ErrText =
+ <<"Only occupants are allowed to send queries "
+ "to the conference">>,
+ Err = jlib:make_error_reply(Packet,
+ ?ERRT_NOT_ACCEPTABLE(Lang,
+ ErrText)),
+ ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid,
+ ToNick),
+ From, Err)
+ end;
+ _ ->
+ case jlib:iq_query_info(Packet) of
+ reply -> ok;
+ _ ->
+ ErrText = <<"Queries to the conference members are "
+ "not allowed in this room">>,
+ Err = jlib:make_error_reply(Packet,
+ ?ERRT_NOT_ALLOWED(Lang, ErrText)),
+ ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid,
+ ToNick),
+ From, Err)
+ end
end,
{next_state, normal_state, StateData};
-
normal_state(_Event, StateData) ->
{next_state, normal_state, StateData}.
-
-
%%----------------------------------------------------------------------
%% Func: handle_event/3
%% Returns: {next_state, NextStateName, NextStateData} |
%% {next_state, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData}
%%----------------------------------------------------------------------
-handle_event({service_message, Msg}, _StateName, StateData) ->
- MessagePkt = {xmlelement, "message",
- [{"type", "groupchat"}],
- [{xmlelement, "body", [], [{xmlcdata, Msg}]}]},
+handle_event({service_message, Msg}, _StateName,
+ StateData) ->
+ MessagePkt = #xmlel{name = <<"message">>,
+ attrs = [{<<"type">>, <<"groupchat">>}],
+ children =
+ [#xmlel{name = <<"body">>, attrs = [],
+ children = [{xmlcdata, Msg}]}]},
lists:foreach(
fun({_LJID, Info}) ->
ejabberd_router:route(
@@ -672,35 +679,39 @@ handle_event({service_message, Msg}, _StateName, StateData) ->
MessagePkt)
end,
?DICT:to_list(StateData#state.users)),
- NSD = add_message_to_history("",
- StateData#state.jid,
- MessagePkt,
- StateData),
+ NSD = add_message_to_history(<<"">>,
+ StateData#state.jid, MessagePkt, StateData),
{next_state, normal_state, NSD};
-
-handle_event({destroy, Reason}, _StateName, StateData) ->
- {result, [], stop} =
- destroy_room(
- {xmlelement, "destroy",
- [{"xmlns", ?NS_MUC_OWNER}],
- case Reason of
- none -> [];
- _Else ->
- [{xmlelement, "reason",
- [], [{xmlcdata, Reason}]}]
- end}, StateData),
- ?INFO_MSG("Destroyed MUC room ~s with reason: ~p",
+handle_event({destroy, Reason}, _StateName,
+ StateData) ->
+ {result, [], stop} = destroy_room(#xmlel{name =
+ <<"destroy">>,
+ attrs =
+ [{<<"xmlns">>, ?NS_MUC_OWNER}],
+ children =
+ case Reason of
+ none -> [];
+ _Else ->
+ [#xmlel{name =
+ <<"reason">>,
+ attrs = [],
+ children =
+ [{xmlcdata,
+ Reason}]}]
+ end},
+ StateData),
+ ?INFO_MSG("Destroyed MUC room ~s with reason: ~p",
[jlib:jid_to_string(StateData#state.jid), Reason]),
add_to_log(room_existence, destroyed, StateData),
{stop, shutdown, StateData};
handle_event(destroy, StateName, StateData) ->
- ?INFO_MSG("Destroyed MUC room ~s",
+ ?INFO_MSG("Destroyed MUC room ~s",
[jlib:jid_to_string(StateData#state.jid)]),
handle_event({destroy, none}, StateName, StateData);
-
-handle_event({set_affiliations, Affiliations}, StateName, StateData) ->
- {next_state, StateName, StateData#state{affiliations = Affiliations}};
-
+handle_event({set_affiliations, Affiliations},
+ StateName, StateData) ->
+ {next_state, StateName,
+ StateData#state{affiliations = Affiliations}};
handle_event(_Event, StateName, StateData) ->
{next_state, StateName, StateData}.
@@ -717,18 +728,23 @@ handle_sync_event({get_disco_item, JID, Lang}, _From, StateName, StateData) ->
Reply = get_roomdesc_reply(JID, StateData,
get_roomdesc_tail(StateData, Lang)),
{reply, Reply, StateName, StateData};
-handle_sync_event(get_config, _From, StateName, StateData) ->
- {reply, {ok, StateData#state.config}, StateName, StateData};
-handle_sync_event(get_state, _From, StateName, StateData) ->
+handle_sync_event(get_config, _From, StateName,
+ StateData) ->
+ {reply, {ok, StateData#state.config}, StateName,
+ StateData};
+handle_sync_event(get_state, _From, StateName,
+ StateData) ->
{reply, {ok, StateData}, StateName, StateData};
-handle_sync_event({change_config, Config}, _From, StateName, StateData) ->
+handle_sync_event({change_config, Config}, _From,
+ StateName, StateData) ->
{result, [], NSD} = change_config(Config, StateData),
{reply, {ok, NSD#state.config}, StateName, NSD};
-handle_sync_event({change_state, NewStateData}, _From, StateName, _StateData) ->
+handle_sync_event({change_state, NewStateData}, _From,
+ StateName, _StateData) ->
{reply, {ok, NewStateData}, StateName, NewStateData};
-handle_sync_event(_Event, _From, StateName, StateData) ->
- Reply = ok,
- {reply, Reply, StateName, StateData}.
+handle_sync_event(_Event, _From, StateName,
+ StateData) ->
+ Reply = ok, {reply, Reply, StateName, StateData}.
code_change(_OldVsn, StateName, StateData, _Extra) ->
{ok, StateName, StateData}.
@@ -743,75 +759,74 @@ handle_info({process_user_presence, From}, normal_state = _StateName, StateData)
RoomQueueEmpty = queue:is_empty(StateData#state.room_queue),
RoomQueue = queue:in({presence, From}, StateData#state.room_queue),
StateData1 = StateData#state{room_queue = RoomQueue},
- if
- RoomQueueEmpty ->
- StateData2 = prepare_room_queue(StateData1),
- {next_state, normal_state, StateData2};
- true ->
- {next_state, normal_state, StateData1}
+ if RoomQueueEmpty ->
+ StateData2 = prepare_room_queue(StateData1),
+ {next_state, normal_state, StateData2};
+ true -> {next_state, normal_state, StateData1}
end;
-handle_info({process_user_message, From}, normal_state = _StateName, StateData) ->
- RoomQueueEmpty = queue:is_empty(StateData#state.room_queue),
- RoomQueue = queue:in({message, From}, StateData#state.room_queue),
+handle_info({process_user_message, From},
+ normal_state = _StateName, StateData) ->
+ RoomQueueEmpty =
+ queue:is_empty(StateData#state.room_queue),
+ RoomQueue = queue:in({message, From},
+ StateData#state.room_queue),
StateData1 = StateData#state{room_queue = RoomQueue},
- if
- RoomQueueEmpty ->
- StateData2 = prepare_room_queue(StateData1),
- {next_state, normal_state, StateData2};
- true ->
- {next_state, normal_state, StateData1}
+ if RoomQueueEmpty ->
+ StateData2 = prepare_room_queue(StateData1),
+ {next_state, normal_state, StateData2};
+ true -> {next_state, normal_state, StateData1}
end;
-handle_info(process_room_queue, normal_state = StateName, StateData) ->
+handle_info(process_room_queue,
+ normal_state = StateName, StateData) ->
case queue:out(StateData#state.room_queue) of
- {{value, {message, From}}, RoomQueue} ->
- Activity = get_user_activity(From, StateData),
- Packet = Activity#activity.message,
- NewActivity = Activity#activity{message = undefined},
- StateData1 =
- store_user_activity(
- From, NewActivity, StateData),
- StateData2 =
- StateData1#state{
- room_queue = RoomQueue},
- StateData3 = prepare_room_queue(StateData2),
- process_groupchat_message(From, Packet, StateData3);
- {{value, {presence, From}}, RoomQueue} ->
- Activity = get_user_activity(From, StateData),
- {Nick, Packet} = Activity#activity.presence,
- NewActivity = Activity#activity{presence = undefined},
- StateData1 =
- store_user_activity(
- From, NewActivity, StateData),
- StateData2 =
- StateData1#state{
- room_queue = RoomQueue},
- StateData3 = prepare_room_queue(StateData2),
- process_presence(From, Nick, Packet, StateData3);
- {empty, _} ->
- {next_state, StateName, StateData}
+ {{value, {message, From}}, RoomQueue} ->
+ Activity = get_user_activity(From, StateData),
+ Packet = Activity#activity.message,
+ NewActivity = Activity#activity{message = undefined},
+ StateData1 = store_user_activity(From, NewActivity,
+ StateData),
+ StateData2 = StateData1#state{room_queue = RoomQueue},
+ StateData3 = prepare_room_queue(StateData2),
+ process_groupchat_message(From, Packet, StateData3);
+ {{value, {presence, From}}, RoomQueue} ->
+ Activity = get_user_activity(From, StateData),
+ {Nick, Packet} = Activity#activity.presence,
+ NewActivity = Activity#activity{presence = undefined},
+ StateData1 = store_user_activity(From, NewActivity,
+ StateData),
+ StateData2 = StateData1#state{room_queue = RoomQueue},
+ StateData3 = prepare_room_queue(StateData2),
+ process_presence(From, Nick, Packet, StateData3);
+ {empty, _} -> {next_state, StateName, StateData}
end;
-handle_info({captcha_succeed, From}, normal_state, StateData) ->
- NewState = case ?DICT:find(From, StateData#state.robots) of
- {ok, {Nick, Packet}} ->
- Robots = ?DICT:store(From, passed, StateData#state.robots),
- add_new_user(From, Nick, Packet, StateData#state{robots=Robots});
- _ ->
- StateData
+handle_info({captcha_succeed, From}, normal_state,
+ StateData) ->
+ NewState = case (?DICT):find(From,
+ StateData#state.robots)
+ of
+ {ok, {Nick, Packet}} ->
+ Robots = (?DICT):store(From, passed,
+ StateData#state.robots),
+ add_new_user(From, Nick, Packet,
+ StateData#state{robots = Robots});
+ _ -> StateData
end,
{next_state, normal_state, NewState};
-handle_info({captcha_failed, From}, normal_state, StateData) ->
- NewState = case ?DICT:find(From, StateData#state.robots) of
- {ok, {Nick, Packet}} ->
- Robots = ?DICT:erase(From, StateData#state.robots),
- Err = jlib:make_error_reply(
- Packet, ?ERR_NOT_AUTHORIZED),
- ejabberd_router:route( % TODO: s/Nick/""/
- jlib:jid_replace_resource(
- StateData#state.jid, Nick),
- From, Err),
- StateData#state{robots=Robots};
- _ ->
- StateData
+handle_info({captcha_failed, From}, normal_state,
+ StateData) ->
+ NewState = case (?DICT):find(From,
+ StateData#state.robots)
+ of
+ {ok, {Nick, Packet}} ->
+ Robots = (?DICT):erase(From, StateData#state.robots),
+ Err = jlib:make_error_reply(Packet,
+ ?ERR_NOT_AUTHORIZED),
+ ejabberd_router:route % TODO: s/Nick/""/
+ (jlib:jid_replace_resource(StateData#state.jid,
+ Nick),
+ From, Err),
+ StateData#state{robots = Robots};
+ _ -> StateData
end,
{next_state, normal_state, NewState};
handle_info(_Info, StateName, StateData) ->
@@ -826,30 +841,39 @@ terminate(Reason, _StateName, StateData) ->
?INFO_MSG("Stopping MUC room ~s@~s",
[StateData#state.room, StateData#state.host]),
ReasonT = case Reason of
- shutdown -> "You are being removed from the room because"
- " of a system shutdown";
- _ -> "Room terminates"
+ shutdown ->
+ <<"You are being removed from the room "
+ "because of a system shutdown">>;
+ _ -> <<"Room terminates">>
end,
- ItemAttrs = [{"affiliation", "none"}, {"role", "none"}],
- ReasonEl = {xmlelement, "reason", [], [{xmlcdata, ReasonT}]},
- Packet = {xmlelement, "presence", [{"type", "unavailable"}],
- [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}],
- [{xmlelement, "item", ItemAttrs, [ReasonEl]},
- {xmlelement, "status", [{"code", "332"}], []}
- ]}]},
- ?DICT:fold(
- fun(LJID, Info, _) ->
- Nick = Info#user.nick,
- case Reason of
- shutdown ->
- ejabberd_router:route(
- jlib:jid_replace_resource(StateData#state.jid, Nick),
- Info#user.jid,
- Packet);
- _ -> ok
- end,
- tab_remove_online_user(LJID, StateData)
- end, [], StateData#state.users),
+ ItemAttrs = [{<<"affiliation">>, <<"none">>},
+ {<<"role">>, <<"none">>}],
+ ReasonEl = #xmlel{name = <<"reason">>, attrs = [],
+ children = [{xmlcdata, ReasonT}]},
+ Packet = #xmlel{name = <<"presence">>,
+ attrs = [{<<"type">>, <<"unavailable">>}],
+ children =
+ [#xmlel{name = <<"x">>,
+ attrs = [{<<"xmlns">>, ?NS_MUC_USER}],
+ children =
+ [#xmlel{name = <<"item">>,
+ attrs = ItemAttrs,
+ children = [ReasonEl]},
+ #xmlel{name = <<"status">>,
+ attrs = [{<<"code">>, <<"332">>}],
+ children = []}]}]},
+ (?DICT):fold(fun (LJID, Info, _) ->
+ Nick = Info#user.nick,
+ case Reason of
+ shutdown ->
+ ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid,
+ Nick),
+ Info#user.jid, Packet);
+ _ -> ok
+ end,
+ tab_remove_online_user(LJID, StateData)
+ end,
+ [], StateData#state.users),
add_to_log(room_existence, stopped, StateData),
mod_muc:room_destroyed(StateData#state.host, StateData#state.room, self(),
StateData#state.server_host),
@@ -862,46 +886,50 @@ terminate(Reason, _StateName, StateData) ->
route(Pid, From, ToNick, Packet) ->
gen_fsm:send_event(Pid, {route, From, ToNick, Packet}).
-process_groupchat_message(From, {xmlelement, "message", Attrs, _Els} = Packet,
+process_groupchat_message(From,
+ #xmlel{name = <<"message">>, attrs = Attrs} = Packet,
StateData) ->
- Lang = xml:get_attr_s("xml:lang", Attrs),
+ Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
case is_user_online(From, StateData) orelse
- is_user_allowed_message_nonparticipant(From, StateData) of
- true ->
- {FromNick, Role} = get_participant_data(From, StateData),
- if
- (Role == moderator) or (Role == participant)
- or ((StateData#state.config)#config.moderated == false) ->
- {NewStateData1, IsAllowed} =
- case check_subject(Packet) of
- false ->
- {StateData, true};
- Subject ->
- case can_change_subject(Role,
- StateData) of
- true ->
- NSD =
- StateData#state{
- subject = Subject,
- subject_author =
- FromNick},
- case (NSD#state.config)#config.persistent of
- true ->
- mod_muc:store_room(
- NSD#state.server_host,
- NSD#state.host,
- NSD#state.room,
- make_opts(NSD));
- _ ->
- ok
- end,
- {NSD, true};
- _ ->
- {StateData, false}
- end
- end,
- case IsAllowed of
- true ->
+ is_user_allowed_message_nonparticipant(From, StateData)
+ of
+ true ->
+ {FromNick, Role} = get_participant_data(From,
+ StateData),
+ if (Role == moderator) or (Role == participant) or
+ ((StateData#state.config)#config.moderated == false) ->
+ {NewStateData1, IsAllowed} = case check_subject(Packet)
+ of
+ false -> {StateData, true};
+ Subject ->
+ case
+ can_change_subject(Role,
+ StateData)
+ of
+ true ->
+ NSD =
+ StateData#state{subject
+ =
+ Subject,
+ subject_author
+ =
+ FromNick},
+ case
+ (NSD#state.config)#config.persistent
+ of
+ true ->
+ mod_muc:store_room(NSD#state.server_host,
+ NSD#state.host,
+ NSD#state.room,
+ make_opts(NSD));
+ _ -> ok
+ end,
+ {NSD, true};
+ _ -> {StateData, false}
+ end
+ end,
+ case IsAllowed of
+ true ->
lists:foreach(
fun({_LJID, Info}) ->
ejabberd_router:route(
@@ -912,47 +940,44 @@ process_groupchat_message(From, {xmlelement, "message", Attrs, _Els} = Packet,
Packet)
end,
?DICT:to_list(StateData#state.users)),
- NewStateData2 =
- add_message_to_history(FromNick,
- From,
- Packet,
- NewStateData1),
- {next_state, normal_state, NewStateData2};
- _ ->
- Err =
- case (StateData#state.config)#config.allow_change_subj of
- true ->
- ?ERRT_FORBIDDEN(
- Lang,
- "Only moderators and participants "
- "are allowed to change the subject in this room");
- _ ->
- ?ERRT_FORBIDDEN(
- Lang,
- "Only moderators "
- "are allowed to change the subject in this room")
- end,
- ejabberd_router:route(
- StateData#state.jid,
- From,
- jlib:make_error_reply(Packet, Err)),
- {next_state, normal_state, StateData}
- end;
- true ->
- ErrText = "Visitors are not allowed to send messages to all occupants",
- Err = jlib:make_error_reply(
- Packet, ?ERRT_FORBIDDEN(Lang, ErrText)),
- ejabberd_router:route(
- StateData#state.jid,
- From, Err),
- {next_state, normal_state, StateData}
- end;
- false ->
- ErrText = "Only occupants are allowed to send messages to the conference",
- Err = jlib:make_error_reply(
- Packet, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)),
- ejabberd_router:route(StateData#state.jid, From, Err),
- {next_state, normal_state, StateData}
+ NewStateData2 = add_message_to_history(FromNick, From,
+ Packet,
+ NewStateData1),
+ {next_state, normal_state, NewStateData2};
+ _ ->
+ Err = case
+ (StateData#state.config)#config.allow_change_subj
+ of
+ true ->
+ ?ERRT_FORBIDDEN(Lang,
+ <<"Only moderators and participants are "
+ "allowed to change the subject in this "
+ "room">>);
+ _ ->
+ ?ERRT_FORBIDDEN(Lang,
+ <<"Only moderators are allowed to change "
+ "the subject in this room">>)
+ end,
+ ejabberd_router:route(StateData#state.jid, From,
+ jlib:make_error_reply(Packet, Err)),
+ {next_state, normal_state, StateData}
+ end;
+ true ->
+ ErrText = <<"Visitors are not allowed to send messages "
+ "to all occupants">>,
+ Err = jlib:make_error_reply(Packet,
+ ?ERRT_FORBIDDEN(Lang, ErrText)),
+ ejabberd_router:route(StateData#state.jid, From, Err),
+ {next_state, normal_state, StateData}
+ end;
+ false ->
+ ErrText =
+ <<"Only occupants are allowed to send messages "
+ "to the conference">>,
+ Err = jlib:make_error_reply(Packet,
+ ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)),
+ ejabberd_router:route(StateData#state.jid, From, Err),
+ {next_state, normal_state, StateData}
end.
%% @doc Check if this non participant can send message to room.
@@ -962,449 +987,466 @@ process_groupchat_message(From, {xmlelement, "message", Attrs, _Els} = Packet,
%% an implementation MAY allow users with certain privileges
%% (e.g., a room owner, room admin, or service-level admin)
%% to send messages to the room even if those users are not occupants.
-is_user_allowed_message_nonparticipant(JID, StateData) ->
+is_user_allowed_message_nonparticipant(JID,
+ StateData) ->
case get_service_affiliation(JID, StateData) of
- owner ->
- true;
- _ -> false
+ owner -> true;
+ _ -> false
end.
%% @doc Get information of this participant, or default values.
%% If the JID is not a participant, return values for a service message.
get_participant_data(From, StateData) ->
- case ?DICT:find(jlib:jid_tolower(From), StateData#state.users) of
- {ok, #user{nick = FromNick, role = Role}} ->
- {FromNick, Role};
- error ->
- {"", moderator}
+ case (?DICT):find(jlib:jid_tolower(From),
+ StateData#state.users)
+ of
+ {ok, #user{nick = FromNick, role = Role}} ->
+ {FromNick, Role};
+ error -> {<<"">>, moderator}
end.
-
-process_presence(From, Nick, {xmlelement, "presence", Attrs, _Els} = Packet,
+process_presence(From, Nick,
+ #xmlel{name = <<"presence">>, attrs = Attrs} = Packet,
StateData) ->
- Type = xml:get_attr_s("type", Attrs),
- Lang = xml:get_attr_s("xml:lang", Attrs),
- StateData1 =
- case Type of
- "unavailable" ->
- case is_user_online(From, StateData) of
- true ->
- NewPacket = case {(StateData#state.config)#config.allow_visitor_status,
- is_visitor(From, StateData)} of
- {false, true} ->
- strip_status(Packet);
- _ ->
- Packet
- end,
- NewState =
- add_user_presence_un(From, NewPacket, StateData),
- case ?DICT:find(Nick, StateData#state.nicks) of
- {ok, [_, _ | _]} -> ok;
- _ -> send_new_presence(From, NewState)
- end,
- Reason = case xml:get_subtag(NewPacket, "status") of
- false -> "";
- Status_el -> xml:get_tag_cdata(Status_el)
- end,
- remove_online_user(From, NewState, Reason);
- _ ->
- StateData
- end;
- "error" ->
- case is_user_online(From, StateData) of
- true ->
- ErrorText = "This participant is kicked from the room because "
- "he sent an error presence",
- expulse_participant(Packet, From, StateData,
- translate:translate(Lang, ErrorText));
- _ ->
- StateData
- end;
- "" ->
- case is_user_online(From, StateData) of
- true ->
- case is_nick_change(From, Nick, StateData) of
- true ->
- case {nick_collision(From, Nick, StateData),
- mod_muc:can_use_nick(
- StateData#state.server_host,
- StateData#state.host, From, Nick),
- {(StateData#state.config)#config.allow_visitor_nickchange,
- is_visitor(From, StateData)}} of
- {_, _, {false, true}} ->
- ErrText = "Visitors are not allowed to change their nicknames in this room",
- Err = jlib:make_error_reply(
- Packet,
- ?ERRT_NOT_ALLOWED(Lang, ErrText)),
- ejabberd_router:route(
- % TODO: s/Nick/""/
- jlib:jid_replace_resource(
- StateData#state.jid,
- Nick),
- From, Err),
- StateData;
- {true, _, _} ->
- Lang = xml:get_attr_s("xml:lang", Attrs),
- ErrText = "That nickname is already in use by another occupant",
- Err = jlib:make_error_reply(
- Packet,
- ?ERRT_CONFLICT(Lang, ErrText)),
- ejabberd_router:route(
- jlib:jid_replace_resource(
- StateData#state.jid,
- Nick), % TODO: s/Nick/""/
- From, Err),
- StateData;
- {_, false, _} ->
- ErrText = "That nickname is registered by another person",
- Err = jlib:make_error_reply(
- Packet,
- ?ERRT_CONFLICT(Lang, ErrText)),
- ejabberd_router:route(
- % TODO: s/Nick/""/
- jlib:jid_replace_resource(
- StateData#state.jid,
- Nick),
- From, Err),
- StateData;
- _ ->
- change_nick(From, Nick, StateData)
- end;
- _NotNickChange ->
- Stanza = case {(StateData#state.config)#config.allow_visitor_status,
- is_visitor(From, StateData)} of
- {false, true} ->
- strip_status(Packet);
- _Allowed ->
- Packet
- end,
- NewState = add_user_presence(From, Stanza, StateData),
- send_new_presence(From, NewState),
- NewState
- end;
- _ ->
- add_new_user(From, Nick, Packet, StateData)
- end;
- _ ->
- StateData
- end,
- case (not (StateData1#state.config)#config.persistent) andalso
- (?DICT:to_list(StateData1#state.users) == []) of
- true ->
- ?INFO_MSG("Destroyed MUC room ~s because it's temporary and empty",
- [jlib:jid_to_string(StateData#state.jid)]),
- add_to_log(room_existence, destroyed, StateData),
- {stop, normal, StateData1};
- _ ->
- {next_state, normal_state, StateData1}
+ Type = xml:get_attr_s(<<"type">>, Attrs),
+ Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
+ StateData1 = case Type of
+ <<"unavailable">> ->
+ case is_user_online(From, StateData) of
+ true ->
+ NewPacket = case
+ {(StateData#state.config)#config.allow_visitor_status,
+ is_visitor(From, StateData)}
+ of
+ {false, true} ->
+ strip_status(Packet);
+ _ -> Packet
+ end,
+ NewState = add_user_presence_un(From, NewPacket,
+ StateData),
+ case (?DICT):find(Nick, StateData#state.nicks) of
+ {ok, [_, _ | _]} -> ok;
+ _ -> send_new_presence(From, NewState)
+ end,
+ Reason = case xml:get_subtag(NewPacket,
+ <<"status">>)
+ of
+ false -> <<"">>;
+ Status_el ->
+ xml:get_tag_cdata(Status_el)
+ end,
+ remove_online_user(From, NewState, Reason);
+ _ -> StateData
+ end;
+ <<"error">> ->
+ case is_user_online(From, StateData) of
+ true ->
+ ErrorText =
+ <<"This participant is kicked from the "
+ "room because he sent an error presence">>,
+ expulse_participant(Packet, From, StateData,
+ translate:translate(Lang,
+ ErrorText));
+ _ -> StateData
+ end;
+ <<"">> ->
+ case is_user_online(From, StateData) of
+ true ->
+ case is_nick_change(From, Nick, StateData) of
+ true ->
+ case {nick_collision(From, Nick, StateData),
+ mod_muc:can_use_nick(StateData#state.server_host,
+ StateData#state.host,
+ From, Nick),
+ {(StateData#state.config)#config.allow_visitor_nickchange,
+ is_visitor(From, StateData)}}
+ of
+ {_, _, {false, true}} ->
+ ErrText =
+ <<"Visitors are not allowed to change their "
+ "nicknames in this room">>,
+ Err = jlib:make_error_reply(Packet,
+ ?ERRT_NOT_ALLOWED(Lang,
+ ErrText)),
+ ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid,
+ Nick),
+ From, Err),
+ StateData;
+ {true, _, _} ->
+ Lang = xml:get_attr_s(<<"xml:lang">>,
+ Attrs),
+ ErrText =
+ <<"That nickname is already in use by another "
+ "occupant">>,
+ Err = jlib:make_error_reply(Packet,
+ ?ERRT_CONFLICT(Lang,
+ ErrText)),
+ ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid,
+ Nick), % TODO: s/Nick/""/
+ From, Err),
+ StateData;
+ {_, false, _} ->
+ ErrText =
+ <<"That nickname is registered by another "
+ "person">>,
+ Err = jlib:make_error_reply(Packet,
+ ?ERRT_CONFLICT(Lang,
+ ErrText)),
+ ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid,
+ Nick),
+ From, Err),
+ StateData;
+ _ -> change_nick(From, Nick, StateData)
+ end;
+ _NotNickChange ->
+ Stanza = case
+ {(StateData#state.config)#config.allow_visitor_status,
+ is_visitor(From, StateData)}
+ of
+ {false, true} ->
+ strip_status(Packet);
+ _Allowed -> Packet
+ end,
+ NewState = add_user_presence(From, Stanza,
+ StateData),
+ send_new_presence(From, NewState),
+ NewState
+ end;
+ _ -> add_new_user(From, Nick, Packet, StateData)
+ end;
+ _ -> StateData
+ end,
+ case not (StateData1#state.config)#config.persistent
+ andalso (?DICT):to_list(StateData1#state.users) == []
+ of
+ true ->
+ ?INFO_MSG("Destroyed MUC room ~s because it's temporary "
+ "and empty",
+ [jlib:jid_to_string(StateData#state.jid)]),
+ add_to_log(room_existence, destroyed, StateData),
+ {stop, normal, StateData1};
+ _ -> {next_state, normal_state, StateData1}
end.
is_user_online(JID, StateData) ->
LJID = jlib:jid_tolower(JID),
- ?DICT:is_key(LJID, StateData#state.users).
+ (?DICT):is_key(LJID, StateData#state.users).
%% Check if the user is occupant of the room, or at least is an admin or owner.
is_occupant_or_admin(JID, StateData) ->
FAffiliation = get_affiliation(JID, StateData),
FRole = get_role(JID, StateData),
- case (FRole /= none) orelse
- (FAffiliation == admin) orelse
- (FAffiliation == owner) of
- true ->
- true;
- _ ->
- false
+ case FRole /= none orelse
+ FAffiliation == admin orelse FAffiliation == owner
+ of
+ true -> true;
+ _ -> false
end.
%%%
%%% Handle IQ queries of vCard
%%%
-is_user_online_iq(StanzaId, JID, StateData) when JID#jid.lresource /= "" ->
+is_user_online_iq(StanzaId, JID, StateData)
+ when JID#jid.lresource /= <<"">> ->
{is_user_online(JID, StateData), StanzaId, JID};
-is_user_online_iq(StanzaId, JID, StateData) when JID#jid.lresource == "" ->
+is_user_online_iq(StanzaId, JID, StateData)
+ when JID#jid.lresource == <<"">> ->
try stanzaid_unpack(StanzaId) of
- {OriginalId, Resource} ->
- JIDWithResource = jlib:jid_replace_resource(JID, Resource),
- {is_user_online(JIDWithResource, StateData),
- OriginalId, JIDWithResource}
+ {OriginalId, Resource} ->
+ JIDWithResource = jlib:jid_replace_resource(JID,
+ Resource),
+ {is_user_online(JIDWithResource, StateData), OriginalId,
+ JIDWithResource}
catch
- _:_ ->
- {is_user_online(JID, StateData), StanzaId, JID}
+ _:_ -> {is_user_online(JID, StateData), StanzaId, JID}
end.
-handle_iq_vcard(FromFull, ToJID, StanzaId, NewId, Packet) ->
+handle_iq_vcard(FromFull, ToJID, StanzaId, NewId,
+ Packet) ->
ToBareJID = jlib:jid_remove_resource(ToJID),
IQ = jlib:iq_query_info(Packet),
- handle_iq_vcard2(FromFull, ToJID, ToBareJID, StanzaId, NewId, IQ, Packet).
-handle_iq_vcard2(_FromFull, ToJID, ToBareJID, StanzaId, _NewId,
- #iq{type = get, xmlns = ?NS_VCARD}, Packet)
- when ToBareJID /= ToJID ->
+ handle_iq_vcard2(FromFull, ToJID, ToBareJID, StanzaId,
+ NewId, IQ, Packet).
+
+handle_iq_vcard2(_FromFull, ToJID, ToBareJID, StanzaId,
+ _NewId, #iq{type = get, xmlns = ?NS_VCARD}, Packet)
+ when ToBareJID /= ToJID ->
{ToBareJID, change_stanzaid(StanzaId, ToJID, Packet)};
-handle_iq_vcard2(_FromFull, ToJID, _ToBareJID, _StanzaId, NewId, _IQ, Packet) ->
+handle_iq_vcard2(_FromFull, ToJID, _ToBareJID,
+ _StanzaId, NewId, _IQ, Packet) ->
{ToJID, change_stanzaid(NewId, Packet)}.
stanzaid_pack(OriginalId, Resource) ->
- "berd"++base64:encode_to_string("ejab\0" ++ OriginalId ++ "\0" ++ Resource).
-stanzaid_unpack("berd"++StanzaIdBase64) ->
- StanzaId = base64:decode_to_string(StanzaIdBase64),
- ["ejab", OriginalId, Resource] = string:tokens(StanzaId, "\0"),
+ <<"berd",
+ (jlib:encode_base64(<<"ejab\000",
+ OriginalId/binary, "\000",
+ Resource/binary>>))/binary>>.
+
+stanzaid_unpack(<<"berd", StanzaIdBase64/binary>>) ->
+ StanzaId = jlib:decode_base64(StanzaIdBase64),
+ [<<"ejab">>, OriginalId, Resource] =
+ str:tokens(StanzaId, <<"\000">>),
{OriginalId, Resource}.
change_stanzaid(NewId, Packet) ->
- {xmlelement, Name, Attrs, Els} = jlib:remove_attr("id", Packet),
- {xmlelement, Name, [{"id", NewId} | Attrs], Els}.
+ #xmlel{name = Name, attrs = Attrs, children = Els} =
+ jlib:remove_attr(<<"id">>, Packet),
+ #xmlel{name = Name, attrs = [{<<"id">>, NewId} | Attrs],
+ children = Els}.
+
change_stanzaid(PreviousId, ToJID, Packet) ->
NewId = stanzaid_pack(PreviousId, ToJID#jid.lresource),
change_stanzaid(NewId, Packet).
+
%%%
%%%
role_to_list(Role) ->
case Role of
- moderator -> "moderator";
- participant -> "participant";
- visitor -> "visitor";
- none -> "none"
+ moderator -> <<"moderator">>;
+ participant -> <<"participant">>;
+ visitor -> <<"visitor">>;
+ none -> <<"none">>
end.
affiliation_to_list(Affiliation) ->
case Affiliation of
- owner -> "owner";
- admin -> "admin";
- member -> "member";
- outcast -> "outcast";
- none -> "none"
+ owner -> <<"owner">>;
+ admin -> <<"admin">>;
+ member -> <<"member">>;
+ outcast -> <<"outcast">>;
+ none -> <<"none">>
end.
list_to_role(Role) ->
case Role of
- "moderator" -> moderator;
- "participant" -> participant;
- "visitor" -> visitor;
- "none" -> none
+ <<"moderator">> -> moderator;
+ <<"participant">> -> participant;
+ <<"visitor">> -> visitor;
+ <<"none">> -> none
end.
list_to_affiliation(Affiliation) ->
case Affiliation of
- "owner" -> owner;
- "admin" -> admin;
- "member" -> member;
- "outcast" -> outcast;
- "none" -> none
+ <<"owner">> -> owner;
+ <<"admin">> -> admin;
+ <<"member">> -> member;
+ <<"outcast">> -> outcast;
+ <<"none">> -> none
end.
%% Decide the fate of the message and its sender
%% Returns: continue_delivery | forget_message | {expulse_sender, Reason}
-decide_fate_message("error", Packet, From, StateData) ->
- %% Make a preliminary decision
+decide_fate_message(<<"error">>, Packet, From,
+ StateData) ->
PD = case check_error_kick(Packet) of
- %% If this is an error stanza and its condition matches a criteria
- true ->
- Reason = io_lib:format("This participant is considered a ghost and is expulsed: ~s",
- [jlib:jid_to_string(From)]),
- {expulse_sender, Reason};
- false ->
- continue_delivery
+ %% If this is an error stanza and its condition matches a criteria
+ true ->
+ Reason =
+ io_lib:format("This participant is considered a ghost "
+ "and is expulsed: ~s",
+ [jlib:jid_to_string(From)]),
+ {expulse_sender, Reason};
+ false -> continue_delivery
end,
case PD of
- {expulse_sender, R} ->
- case is_user_online(From, StateData) of
- true ->
- {expulse_sender, R};
- false ->
- forget_message
- end;
- Other ->
- Other
+ {expulse_sender, R} ->
+ case is_user_online(From, StateData) of
+ true -> {expulse_sender, R};
+ false -> forget_message
+ end;
+ Other -> Other
end;
-
-decide_fate_message(_, _, _, _) ->
- continue_delivery.
+decide_fate_message(_, _, _, _) -> continue_delivery.
%% Check if the elements of this error stanza indicate
%% that the sender is a dead participant.
%% If so, return true to kick the participant.
check_error_kick(Packet) ->
case get_error_condition(Packet) of
- "gone" -> true;
- "internal-server-error" -> true;
- "item-not-found" -> true;
- "jid-malformed" -> true;
- "recipient-unavailable" -> true;
- "redirect" -> true;
- "remote-server-not-found" -> true;
- "remote-server-timeout" -> true;
- "service-unavailable" -> true;
- _ -> false
+ <<"gone">> -> true;
+ <<"internal-server-error">> -> true;
+ <<"item-not-found">> -> true;
+ <<"jid-malformed">> -> true;
+ <<"recipient-unavailable">> -> true;
+ <<"redirect">> -> true;
+ <<"remote-server-not-found">> -> true;
+ <<"remote-server-timeout">> -> true;
+ <<"service-unavailable">> -> true;
+ _ -> false
end.
get_error_condition(Packet) ->
- case catch get_error_condition2(Packet) of
- {condition, ErrorCondition} ->
- ErrorCondition;
- {'EXIT', _} ->
- "badformed error stanza"
- end.
+ case catch get_error_condition2(Packet) of
+ {condition, ErrorCondition} -> ErrorCondition;
+ {'EXIT', _} -> <<"badformed error stanza">>
+ end.
+
get_error_condition2(Packet) ->
- {xmlelement, _, _, EEls} = xml:get_subtag(Packet, "error"),
- [Condition] = [Name || {xmlelement, Name, [{"xmlns", ?NS_STANZAS}], []} <- EEls],
- {condition, Condition}.
+ #xmlel{children = EEls} = xml:get_subtag(Packet,
+ <<"error">>),
+ [Condition] = [Name
+ || #xmlel{name = Name,
+ attrs = [{<<"xmlns">>, ?NS_STANZAS}],
+ children = []}
+ <- EEls],
+ {condition, Condition}.
expulse_participant(Packet, From, StateData, Reason1) ->
- ErrorCondition = get_error_condition(Packet),
- Reason2 = io_lib:format(Reason1 ++ ": " ++ "~s", [ErrorCondition]),
- NewState = add_user_presence_un(
- From,
- {xmlelement, "presence",
- [{"type", "unavailable"}],
- [{xmlelement, "status", [],
- [{xmlcdata, Reason2}]
- }]},
- StateData),
- send_new_presence(From, NewState),
- remove_online_user(From, NewState).
-
+ ErrorCondition = get_error_condition(Packet),
+ Reason2 = iolist_to_binary(
+ io_lib:format(binary_to_list(Reason1) ++ ": " ++ "~s",
+ [ErrorCondition])),
+ NewState = add_user_presence_un(From,
+ #xmlel{name = <<"presence">>,
+ attrs =
+ [{<<"type">>,
+ <<"unavailable">>}],
+ children =
+ [#xmlel{name = <<"status">>,
+ attrs = [],
+ children =
+ [{xmlcdata,
+ Reason2}]}]},
+ StateData),
+ send_new_presence(From, NewState),
+ remove_online_user(From, NewState).
set_affiliation(JID, Affiliation, StateData) ->
- set_affiliation(JID, Affiliation, StateData, "").
+ set_affiliation(JID, Affiliation, StateData, <<"">>).
set_affiliation(JID, Affiliation, StateData, Reason) ->
LJID = jlib:jid_remove_resource(jlib:jid_tolower(JID)),
Affiliations = case Affiliation of
- none ->
- ?DICT:erase(LJID,
- StateData#state.affiliations);
- _ ->
- ?DICT:store(LJID,
- {Affiliation, Reason},
+ none ->
+ (?DICT):erase(LJID, StateData#state.affiliations);
+ _ ->
+ (?DICT):store(LJID, {Affiliation, Reason},
StateData#state.affiliations)
end,
StateData#state{affiliations = Affiliations}.
get_affiliation(JID, StateData) ->
- {_AccessRoute, _AccessCreate, AccessAdmin, _AccessPersistent} = StateData#state.access,
- Res =
- case acl:match_rule(StateData#state.server_host, AccessAdmin, JID) of
- allow ->
- owner;
+ {_AccessRoute, _AccessCreate, AccessAdmin,
+ _AccessPersistent} =
+ StateData#state.access,
+ Res = case acl:match_rule(StateData#state.server_host,
+ AccessAdmin, JID)
+ of
+ allow -> owner;
_ ->
LJID = jlib:jid_tolower(JID),
- case ?DICT:find(LJID, StateData#state.affiliations) of
- {ok, Affiliation} ->
- Affiliation;
- _ ->
- LJID1 = jlib:jid_remove_resource(LJID),
- case ?DICT:find(LJID1, StateData#state.affiliations) of
- {ok, Affiliation} ->
- Affiliation;
- _ ->
- LJID2 = setelement(1, LJID, ""),
- case ?DICT:find(LJID2, StateData#state.affiliations) of
- {ok, Affiliation} ->
- Affiliation;
- _ ->
- LJID3 = jlib:jid_remove_resource(LJID2),
- case ?DICT:find(LJID3, StateData#state.affiliations) of
- {ok, Affiliation} ->
- Affiliation;
- _ ->
- none
- end
- end
- end
+ case (?DICT):find(LJID, StateData#state.affiliations) of
+ {ok, Affiliation} -> Affiliation;
+ _ ->
+ LJID1 = jlib:jid_remove_resource(LJID),
+ case (?DICT):find(LJID1, StateData#state.affiliations)
+ of
+ {ok, Affiliation} -> Affiliation;
+ _ ->
+ LJID2 = setelement(1, LJID, <<"">>),
+ case (?DICT):find(LJID2,
+ StateData#state.affiliations)
+ of
+ {ok, Affiliation} -> Affiliation;
+ _ ->
+ LJID3 = jlib:jid_remove_resource(LJID2),
+ case (?DICT):find(LJID3,
+ StateData#state.affiliations)
+ of
+ {ok, Affiliation} -> Affiliation;
+ _ -> none
+ end
+ end
+ end
end
- end,
+ end,
case Res of
- {A, _Reason} ->
- A;
- _ ->
- Res
+ {A, _Reason} -> A;
+ _ -> Res
end.
get_service_affiliation(JID, StateData) ->
- {_AccessRoute, _AccessCreate, AccessAdmin, _AccessPersistent} =
+ {_AccessRoute, _AccessCreate, AccessAdmin,
+ _AccessPersistent} =
StateData#state.access,
- case acl:match_rule(StateData#state.server_host, AccessAdmin, JID) of
- allow ->
- owner;
- _ ->
- none
+ case acl:match_rule(StateData#state.server_host,
+ AccessAdmin, JID)
+ of
+ allow -> owner;
+ _ -> none
end.
set_role(JID, Role, StateData) ->
LJID = jlib:jid_tolower(JID),
LJIDs = case LJID of
- {U, S, ""} ->
- ?DICT:fold(
- fun(J, _, Js) ->
- case J of
- {U, S, _} ->
- [J | Js];
- _ ->
- Js
- end
- end, [], StateData#state.users);
- _ ->
- case ?DICT:is_key(LJID, StateData#state.users) of
- true ->
- [LJID];
- _ ->
- []
- end
- end,
- {Users, Nicks}
- = case Role of
- none ->
- lists:foldl(fun(J, {Us, Ns}) ->
- NewNs =
- case ?DICT:find(J, Us) of
- {ok, #user{nick = Nick}} ->
- ?DICT:erase(Nick, Ns);
- _ ->
- Ns
- end,
- {?DICT:erase(J, Us), NewNs}
- end,
- {StateData#state.users, StateData#state.nicks},
- LJIDs);
+ {U, S, <<"">>} ->
+ (?DICT):fold(fun (J, _, Js) ->
+ case J of
+ {U, S, _} -> [J | Js];
+ _ -> Js
+ end
+ end,
+ [], StateData#state.users);
_ ->
- {lists:foldl(fun(J, Us) ->
- {ok, User} = ?DICT:find(J, Us),
- ?DICT:store(J,
- User#user{role = Role},
- Us)
- end, StateData#state.users, LJIDs),
- StateData#state.nicks}
- end,
+ case (?DICT):is_key(LJID, StateData#state.users) of
+ true -> [LJID];
+ _ -> []
+ end
+ end,
+ {Users, Nicks} = case Role of
+ none ->
+ lists:foldl(fun (J, {Us, Ns}) ->
+ NewNs = case (?DICT):find(J, Us)
+ of
+ {ok,
+ #user{nick = Nick}} ->
+ (?DICT):erase(Nick,
+ Ns);
+ _ -> Ns
+ end,
+ {(?DICT):erase(J, Us), NewNs}
+ end,
+ {StateData#state.users,
+ StateData#state.nicks},
+ LJIDs);
+ _ ->
+ {lists:foldl(fun (J, Us) ->
+ {ok, User} = (?DICT):find(J,
+ Us),
+ (?DICT):store(J,
+ User#user{role =
+ Role},
+ Us)
+ end,
+ StateData#state.users, LJIDs),
+ StateData#state.nicks}
+ end,
StateData#state{users = Users, nicks = Nicks}.
get_role(JID, StateData) ->
LJID = jlib:jid_tolower(JID),
- case ?DICT:find(LJID, StateData#state.users) of
- {ok, #user{role = Role}} ->
- Role;
- _ ->
- none
+ case (?DICT):find(LJID, StateData#state.users) of
+ {ok, #user{role = Role}} -> Role;
+ _ -> none
end.
get_default_role(Affiliation, StateData) ->
case Affiliation of
- owner -> moderator;
- admin -> moderator;
- member -> participant;
- outcast -> none;
- none ->
- case (StateData#state.config)#config.members_only of
- true ->
- none;
- _ ->
- case (StateData#state.config)#config.members_by_default of
- true ->
- participant;
- _ ->
- visitor
- end
- end
+ owner -> moderator;
+ admin -> moderator;
+ member -> participant;
+ outcast -> none;
+ none ->
+ case (StateData#state.config)#config.members_only of
+ true -> none;
+ _ ->
+ case (StateData#state.config)#config.members_by_default
+ of
+ true -> participant;
+ _ -> visitor
+ end
+ end
end.
is_visitor(Jid, StateData) ->
@@ -1416,269 +1458,258 @@ is_moderator(Jid, StateData) ->
get_max_users(StateData) ->
MaxUsers = (StateData#state.config)#config.max_users,
ServiceMaxUsers = get_service_max_users(StateData),
- if
- MaxUsers =< ServiceMaxUsers -> MaxUsers;
- true -> ServiceMaxUsers
+ if MaxUsers =< ServiceMaxUsers -> MaxUsers;
+ true -> ServiceMaxUsers
end.
get_service_max_users(StateData) ->
gen_mod:get_module_opt(StateData#state.server_host,
- mod_muc, max_users, ?MAX_USERS_DEFAULT).
+ mod_muc, max_users,
+ fun(I) when is_integer(I), I>0 -> I end,
+ ?MAX_USERS_DEFAULT).
get_max_users_admin_threshold(StateData) ->
gen_mod:get_module_opt(StateData#state.server_host,
- mod_muc, max_users_admin_threshold, 5).
+ mod_muc, max_users_admin_threshold,
+ fun(I) when is_integer(I), I>0 -> I end,
+ 5).
get_user_activity(JID, StateData) ->
case treap:lookup(jlib:jid_tolower(JID),
- StateData#state.activity) of
- {ok, _P, A} -> A;
- error ->
- MessageShaper =
- shaper:new(gen_mod:get_module_opt(
- StateData#state.server_host,
- mod_muc, user_message_shaper, none)),
- PresenceShaper =
- shaper:new(gen_mod:get_module_opt(
- StateData#state.server_host,
- mod_muc, user_presence_shaper, none)),
- #activity{message_shaper = MessageShaper,
- presence_shaper = PresenceShaper}
+ StateData#state.activity)
+ of
+ {ok, _P, A} -> A;
+ error ->
+ MessageShaper =
+ shaper:new(gen_mod:get_module_opt(StateData#state.server_host,
+ mod_muc, user_message_shaper,
+ fun(A) when is_atom(A) -> A end,
+ none)),
+ PresenceShaper =
+ shaper:new(gen_mod:get_module_opt(StateData#state.server_host,
+ mod_muc, user_presence_shaper,
+ fun(A) when is_atom(A) -> A end,
+ none)),
+ #activity{message_shaper = MessageShaper,
+ presence_shaper = PresenceShaper}
end.
store_user_activity(JID, UserActivity, StateData) ->
MinMessageInterval =
- gen_mod:get_module_opt(
- StateData#state.server_host,
- mod_muc, min_message_interval, 0),
+ gen_mod:get_module_opt(StateData#state.server_host,
+ mod_muc, min_message_interval,
+ fun(I) when is_integer(I), I>=0 -> I end,
+ 0),
MinPresenceInterval =
- gen_mod:get_module_opt(
- StateData#state.server_host,
- mod_muc, min_presence_interval, 0),
+ gen_mod:get_module_opt(StateData#state.server_host,
+ mod_muc, min_presence_interval,
+ fun(I) when is_integer(I), I>=0 -> I end,
+ 0),
Key = jlib:jid_tolower(JID),
Now = now_to_usec(now()),
- Activity1 = clean_treap(StateData#state.activity, {1, -Now}),
- Activity =
- case treap:lookup(Key, Activity1) of
- {ok, _P, _A} ->
- treap:delete(Key, Activity1);
- error ->
- Activity1
- end,
- StateData1 =
- case (MinMessageInterval == 0) andalso
- (MinPresenceInterval == 0) andalso
- (UserActivity#activity.message_shaper == none) andalso
- (UserActivity#activity.presence_shaper == none) andalso
- (UserActivity#activity.message == undefined) andalso
- (UserActivity#activity.presence == undefined) of
- true ->
- StateData#state{activity = Activity};
- false ->
- case (UserActivity#activity.message == undefined) andalso
- (UserActivity#activity.presence == undefined) of
- true ->
- {_, MessageShaperInterval} =
- shaper:update(UserActivity#activity.message_shaper,
- 100000),
- {_, PresenceShaperInterval} =
- shaper:update(UserActivity#activity.presence_shaper,
- 100000),
- Delay = lists:max([MessageShaperInterval,
- PresenceShaperInterval,
- MinMessageInterval * 1000,
- MinPresenceInterval * 1000]) * 1000,
- Priority = {1, -(Now + Delay)},
- StateData#state{
- activity = treap:insert(
- Key,
- Priority,
- UserActivity,
- Activity)};
- false ->
- Priority = {0, 0},
- StateData#state{
- activity = treap:insert(
- Key,
- Priority,
- UserActivity,
- Activity)}
- end
- end,
+ Activity1 = clean_treap(StateData#state.activity,
+ {1, -Now}),
+ Activity = case treap:lookup(Key, Activity1) of
+ {ok, _P, _A} -> treap:delete(Key, Activity1);
+ error -> Activity1
+ end,
+ StateData1 = case MinMessageInterval == 0 andalso
+ MinPresenceInterval == 0 andalso
+ UserActivity#activity.message_shaper == none andalso
+ UserActivity#activity.presence_shaper == none
+ andalso
+ UserActivity#activity.message == undefined andalso
+ UserActivity#activity.presence == undefined
+ of
+ true -> StateData#state{activity = Activity};
+ false ->
+ case UserActivity#activity.message == undefined andalso
+ UserActivity#activity.presence == undefined
+ of
+ true ->
+ {_, MessageShaperInterval} =
+ shaper:update(UserActivity#activity.message_shaper,
+ 100000),
+ {_, PresenceShaperInterval} =
+ shaper:update(UserActivity#activity.presence_shaper,
+ 100000),
+ Delay = lists:max([MessageShaperInterval,
+ PresenceShaperInterval,
+ MinMessageInterval * 1000,
+ MinPresenceInterval * 1000])
+ * 1000,
+ Priority = {1, -(Now + Delay)},
+ StateData#state{activity =
+ treap:insert(Key, Priority,
+ UserActivity,
+ Activity)};
+ false ->
+ Priority = {0, 0},
+ StateData#state{activity =
+ treap:insert(Key, Priority,
+ UserActivity,
+ Activity)}
+ end
+ end,
StateData1.
clean_treap(Treap, CleanPriority) ->
case treap:is_empty(Treap) of
- true ->
- Treap;
- false ->
- {_Key, Priority, _Value} = treap:get_root(Treap),
- if
- Priority > CleanPriority ->
- clean_treap(treap:delete_root(Treap), CleanPriority);
- true ->
- Treap
- end
+ true -> Treap;
+ false ->
+ {_Key, Priority, _Value} = treap:get_root(Treap),
+ if Priority > CleanPriority ->
+ clean_treap(treap:delete_root(Treap), CleanPriority);
+ true -> Treap
+ end
end.
-
prepare_room_queue(StateData) ->
case queue:out(StateData#state.room_queue) of
- {{value, {message, From}}, _RoomQueue} ->
- Activity = get_user_activity(From, StateData),
- Packet = Activity#activity.message,
- Size = element_size(Packet),
- {RoomShaper, RoomShaperInterval} =
- shaper:update(StateData#state.room_shaper, Size),
- erlang:send_after(
- RoomShaperInterval, self(),
- process_room_queue),
- StateData#state{
- room_shaper = RoomShaper};
- {{value, {presence, From}}, _RoomQueue} ->
- Activity = get_user_activity(From, StateData),
- {_Nick, Packet} = Activity#activity.presence,
- Size = element_size(Packet),
- {RoomShaper, RoomShaperInterval} =
- shaper:update(StateData#state.room_shaper, Size),
- erlang:send_after(
- RoomShaperInterval, self(),
- process_room_queue),
- StateData#state{
- room_shaper = RoomShaper};
- {empty, _} ->
- StateData
+ {{value, {message, From}}, _RoomQueue} ->
+ Activity = get_user_activity(From, StateData),
+ Packet = Activity#activity.message,
+ Size = element_size(Packet),
+ {RoomShaper, RoomShaperInterval} =
+ shaper:update(StateData#state.room_shaper, Size),
+ erlang:send_after(RoomShaperInterval, self(),
+ process_room_queue),
+ StateData#state{room_shaper = RoomShaper};
+ {{value, {presence, From}}, _RoomQueue} ->
+ Activity = get_user_activity(From, StateData),
+ {_Nick, Packet} = Activity#activity.presence,
+ Size = element_size(Packet),
+ {RoomShaper, RoomShaperInterval} =
+ shaper:update(StateData#state.room_shaper, Size),
+ erlang:send_after(RoomShaperInterval, self(),
+ process_room_queue),
+ StateData#state{room_shaper = RoomShaper};
+ {empty, _} -> StateData
end.
-
add_online_user(JID, Nick, Role, StateData) ->
LJID = jlib:jid_tolower(JID),
- Users = ?DICT:store(LJID,
- #user{jid = JID,
- nick = Nick,
- role = Role},
- StateData#state.users),
+ Users = (?DICT):store(LJID,
+ #user{jid = JID, nick = Nick, role = Role},
+ StateData#state.users),
add_to_log(join, Nick, StateData),
- Nicks = ?DICT:update(Nick,
- fun(Entry) ->
- case lists:member(LJID, Entry) of
- true ->
- Entry;
- false ->
- [LJID|Entry]
- end
- end,
- [LJID],
- StateData#state.nicks),
+ Nicks = (?DICT):update(Nick,
+ fun (Entry) ->
+ case lists:member(LJID, Entry) of
+ true -> Entry;
+ false -> [LJID | Entry]
+ end
+ end,
+ [LJID], StateData#state.nicks),
tab_add_online_user(JID, StateData),
StateData#state{users = Users, nicks = Nicks}.
remove_online_user(JID, StateData) ->
- remove_online_user(JID, StateData, "").
+ remove_online_user(JID, StateData, <<"">>).
remove_online_user(JID, StateData, Reason) ->
LJID = jlib:jid_tolower(JID),
- {ok, #user{nick = Nick}} =
- ?DICT:find(LJID, StateData#state.users),
+ {ok, #user{nick = Nick}} = (?DICT):find(LJID,
+ StateData#state.users),
add_to_log(leave, {Nick, Reason}, StateData),
tab_remove_online_user(JID, StateData),
- Users = ?DICT:erase(LJID, StateData#state.users),
- Nicks = case ?DICT:find(Nick, StateData#state.nicks) of
- {ok, [LJID]} ->
- ?DICT:erase(Nick, StateData#state.nicks);
- {ok, U} ->
- ?DICT:store(Nick, U -- [LJID], StateData#state.nicks);
- error ->
- StateData#state.nicks
+ Users = (?DICT):erase(LJID, StateData#state.users),
+ Nicks = case (?DICT):find(Nick, StateData#state.nicks)
+ of
+ {ok, [LJID]} ->
+ (?DICT):erase(Nick, StateData#state.nicks);
+ {ok, U} ->
+ (?DICT):store(Nick, U -- [LJID], StateData#state.nicks);
+ error -> StateData#state.nicks
end,
StateData#state{users = Users, nicks = Nicks}.
-
-filter_presence({xmlelement, "presence", Attrs, Els}) ->
- FEls = lists:filter(
- fun(El) ->
- case El of
- {xmlcdata, _} ->
- false;
- {xmlelement, _Name1, Attrs1, _Els1} ->
- XMLNS = xml:get_attr_s("xmlns", Attrs1),
- case XMLNS of
- ?NS_MUC ++ _ ->
- false;
- _ ->
- true
- end
- end
- end, Els),
- {xmlelement, "presence", Attrs, FEls}.
-
-strip_status({xmlelement, "presence", Attrs, Els}) ->
- FEls = lists:filter(
- fun({xmlelement, "status", _Attrs1, _Els1}) ->
- false;
- (_) -> true
- end, Els),
- {xmlelement, "presence", Attrs, FEls}.
+filter_presence(#xmlel{name = <<"presence">>,
+ attrs = Attrs, children = Els}) ->
+ FEls = lists:filter(fun (El) ->
+ case El of
+ {xmlcdata, _} -> false;
+ #xmlel{attrs = Attrs1} ->
+ XMLNS = xml:get_attr_s(<<"xmlns">>,
+ Attrs1),
+ NS_MUC = ?NS_MUC,
+ Size = byte_size(NS_MUC),
+ case XMLNS of
+ <<NS_MUC:Size/binary, _/binary>> ->
+ false;
+ _ ->
+ true
+ end
+ end
+ end,
+ Els),
+ #xmlel{name = <<"presence">>, attrs = Attrs,
+ children = FEls}.
+
+strip_status(#xmlel{name = <<"presence">>,
+ attrs = Attrs, children = Els}) ->
+ FEls = lists:filter(fun (#xmlel{name = <<"status">>}) ->
+ false;
+ (_) -> true
+ end,
+ Els),
+ #xmlel{name = <<"presence">>, attrs = Attrs,
+ children = FEls}.
add_user_presence(JID, Presence, StateData) ->
LJID = jlib:jid_tolower(JID),
FPresence = filter_presence(Presence),
- Users =
- ?DICT:update(
- LJID,
- fun(#user{} = User) ->
- User#user{last_presence = FPresence}
- end, StateData#state.users),
+ Users = (?DICT):update(LJID,
+ fun (#user{} = User) ->
+ User#user{last_presence = FPresence}
+ end,
+ StateData#state.users),
StateData#state{users = Users}.
add_user_presence_un(JID, Presence, StateData) ->
LJID = jlib:jid_tolower(JID),
FPresence = filter_presence(Presence),
- Users =
- ?DICT:update(
- LJID,
- fun(#user{} = User) ->
- User#user{last_presence = FPresence,
- role = none}
- end, StateData#state.users),
+ Users = (?DICT):update(LJID,
+ fun (#user{} = User) ->
+ User#user{last_presence = FPresence,
+ role = none}
+ end,
+ StateData#state.users),
StateData#state{users = Users}.
-
%% Find and return a list of the full JIDs of the users of Nick.
%% Return jid record.
find_jids_by_nick(Nick, StateData) ->
- case ?DICT:find(Nick, StateData#state.nicks) of
- {ok, [User]} ->
- [jlib:make_jid(User)];
- {ok, Users} ->
- [jlib:make_jid(LJID) || LJID <- Users];
- error ->
- false
+ case (?DICT):find(Nick, StateData#state.nicks) of
+ {ok, [User]} -> [jlib:make_jid(User)];
+ {ok, Users} -> [jlib:make_jid(LJID) || LJID <- Users];
+ error -> false
end.
%% Find and return the full JID of the user of Nick with
%% highest-priority presence. Return jid record.
find_jid_by_nick(Nick, StateData) ->
- case ?DICT:find(Nick, StateData#state.nicks) of
- {ok, [User]} ->
- jlib:make_jid(User);
- {ok, [FirstUser|Users]} ->
- #user{last_presence = FirstPresence} =
- ?DICT:fetch(FirstUser, StateData#state.users),
- {LJID, _} =
- lists:foldl(fun(Compare, {HighestUser, HighestPresence}) ->
- #user{last_presence = P1} =
- ?DICT:fetch(Compare, StateData#state.users),
- case higher_presence(P1, HighestPresence) of
- true ->
- {Compare, P1};
- false ->
- {HighestUser, HighestPresence}
- end
- end, {FirstUser, FirstPresence}, Users),
- jlib:make_jid(LJID);
- error ->
- false
+ case (?DICT):find(Nick, StateData#state.nicks) of
+ {ok, [User]} -> jlib:make_jid(User);
+ {ok, [FirstUser | Users]} ->
+ #user{last_presence = FirstPresence} =
+ (?DICT):fetch(FirstUser, StateData#state.users),
+ {LJID, _} = lists:foldl(fun (Compare,
+ {HighestUser, HighestPresence}) ->
+ #user{last_presence = P1} =
+ (?DICT):fetch(Compare,
+ StateData#state.users),
+ case higher_presence(P1,
+ HighestPresence)
+ of
+ true -> {Compare, P1};
+ false ->
+ {HighestUser, HighestPresence}
+ end
+ end,
+ {FirstUser, FirstPresence}, Users),
+ jlib:make_jid(LJID);
+ error -> false
end.
higher_presence(Pres1, Pres2) ->
@@ -1687,1298 +1718,1354 @@ higher_presence(Pres1, Pres2) ->
Pri1 > Pri2.
get_priority_from_presence(PresencePacket) ->
- case xml:get_subtag(PresencePacket, "priority") of
- false ->
- 0;
- SubEl ->
- case catch list_to_integer(xml:get_tag_cdata(SubEl)) of
- P when is_integer(P) ->
- P;
- _ ->
- 0
- end
+ case xml:get_subtag(PresencePacket, <<"priority">>) of
+ false -> 0;
+ SubEl ->
+ case catch
+ jlib:binary_to_integer(xml:get_tag_cdata(SubEl))
+ of
+ P when is_integer(P) -> P;
+ _ -> 0
+ end
end.
find_nick_by_jid(Jid, StateData) ->
- [{_, #user{nick = Nick}}] = lists:filter(
- fun({_, #user{jid = FJid}}) -> FJid == Jid end,
- ?DICT:to_list(StateData#state.users)),
- Nick.
+ [{_, #user{nick = Nick}}] = lists:filter(fun ({_,
+ #user{jid = FJid}}) ->
+ FJid == Jid
+ end,
+ (?DICT):to_list(StateData#state.users)),
+ Nick.
is_nick_change(JID, Nick, StateData) ->
LJID = jlib:jid_tolower(JID),
case Nick of
- "" ->
- false;
- _ ->
- {ok, #user{nick = OldNick}} =
- ?DICT:find(LJID, StateData#state.users),
- Nick /= OldNick
+ <<"">> -> false;
+ _ ->
+ {ok, #user{nick = OldNick}} = (?DICT):find(LJID,
+ StateData#state.users),
+ Nick /= OldNick
end.
nick_collision(User, Nick, StateData) ->
UserOfNick = find_jid_by_nick(Nick, StateData),
- %% if nick is not used, or is used by another resource of the same
- %% user, it's ok.
UserOfNick /= false andalso
- jlib:jid_remove_resource(jlib:jid_tolower(UserOfNick)) /=
- jlib:jid_remove_resource(jlib:jid_tolower(User)).
+ jlib:jid_remove_resource(jlib:jid_tolower(UserOfNick))
+ /= jlib:jid_remove_resource(jlib:jid_tolower(User)).
-add_new_user(From, Nick, {xmlelement, _, Attrs, Els} = Packet, StateData) ->
- Lang = xml:get_attr_s("xml:lang", Attrs),
+add_new_user(From, Nick,
+ #xmlel{attrs = Attrs, children = Els} = Packet,
+ StateData) ->
+ Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
MaxUsers = get_max_users(StateData),
- MaxAdminUsers = MaxUsers + get_max_users_admin_threshold(StateData),
- NUsers = dict:fold(fun(_, _, Acc) -> Acc + 1 end, 0,
+ MaxAdminUsers = MaxUsers +
+ get_max_users_admin_threshold(StateData),
+ NUsers = dict:fold(fun (_, _, Acc) -> Acc + 1 end, 0,
StateData#state.users),
Affiliation = get_affiliation(From, StateData),
- ServiceAffiliation = get_service_affiliation(From, StateData),
+ ServiceAffiliation = get_service_affiliation(From,
+ StateData),
NConferences = tab_count_user(From),
- MaxConferences = gen_mod:get_module_opt(
- StateData#state.server_host,
- mod_muc, max_user_conferences, 10),
+ MaxConferences =
+ gen_mod:get_module_opt(StateData#state.server_host,
+ mod_muc, max_user_conferences,
+ fun(I) when is_integer(I), I>0 -> I end,
+ 10),
Collision = nick_collision(From, Nick, StateData),
case {(ServiceAffiliation == owner orelse
- ((Affiliation == admin orelse Affiliation == owner) andalso
- NUsers < MaxAdminUsers) orelse
- NUsers < MaxUsers) andalso
- NConferences < MaxConferences,
+ (Affiliation == admin orelse Affiliation == owner)
+ andalso NUsers < MaxAdminUsers
+ orelse NUsers < MaxUsers)
+ andalso NConferences < MaxConferences,
Collision,
- mod_muc:can_use_nick(
- StateData#state.server_host,
- StateData#state.host, From, Nick),
- get_default_role(Affiliation, StateData)} of
- {false, _, _, _} ->
- % max user reached and user is not admin or owner
- Err = jlib:make_error_reply(
- Packet,
- ?ERR_SERVICE_UNAVAILABLE),
- ejabberd_router:route( % TODO: s/Nick/""/
- jlib:jid_replace_resource(StateData#state.jid, Nick),
- From, Err),
- StateData;
- {_, _, _, none} ->
- Err = jlib:make_error_reply(
- Packet,
- case Affiliation of
- outcast ->
- ErrText = "You have been banned from this room",
- ?ERRT_FORBIDDEN(Lang, ErrText);
- _ ->
- ErrText = "Membership is required to enter this room",
- ?ERRT_REGISTRATION_REQUIRED(Lang, ErrText)
- end),
- ejabberd_router:route( % TODO: s/Nick/""/
- jlib:jid_replace_resource(StateData#state.jid, Nick),
- From, Err),
- StateData;
- {_, true, _, _} ->
- ErrText = "That nickname is already in use by another occupant",
- Err = jlib:make_error_reply(Packet, ?ERRT_CONFLICT(Lang, ErrText)),
- ejabberd_router:route(
- % TODO: s/Nick/""/
- jlib:jid_replace_resource(StateData#state.jid, Nick),
- From, Err),
- StateData;
- {_, _, false, _} ->
- ErrText = "That nickname is registered by another person",
- Err = jlib:make_error_reply(Packet, ?ERRT_CONFLICT(Lang, ErrText)),
- ejabberd_router:route(
- % TODO: s/Nick/""/
- jlib:jid_replace_resource(StateData#state.jid, Nick),
- From, Err),
- StateData;
- {_, _, _, Role} ->
- case check_password(ServiceAffiliation, Affiliation,
- Els, From, StateData) of
- true ->
- NewState =
- add_user_presence(
- From, Packet,
- add_online_user(From, Nick, Role, StateData)),
- if not (NewState#state.config)#config.anonymous ->
- WPacket = {xmlelement, "message", [{"type", "groupchat"}],
- [{xmlelement, "body", [],
- [{xmlcdata, translate:translate(
- Lang,
- "This room is not anonymous")}]},
- {xmlelement, "x", [{"xmlns", ?NS_MUC_USER}],
- [{xmlelement, "status", [{"code", "100"}], []}]}]},
- ejabberd_router:route(
- StateData#state.jid,
- From, WPacket);
- true ->
- ok
- end,
- send_existing_presences(From, NewState),
- send_new_presence(From, NewState),
- Shift = count_stanza_shift(Nick, Els, NewState),
- case send_history(From, Shift, NewState) of
- true ->
- ok;
- _ ->
- send_subject(From, Lang, StateData)
- end,
- case NewState#state.just_created of
- true ->
- NewState#state{just_created = false};
- false ->
- Robots = ?DICT:erase(From, StateData#state.robots),
- NewState#state{robots = Robots}
- end;
- nopass ->
- ErrText = "A password is required to enter this room",
- Err = jlib:make_error_reply(
- Packet, ?ERRT_NOT_AUTHORIZED(Lang, ErrText)),
- ejabberd_router:route( % TODO: s/Nick/""/
- jlib:jid_replace_resource(
- StateData#state.jid, Nick),
- From, Err),
- StateData;
- captcha_required ->
- SID = xml:get_attr_s("id", Attrs),
- RoomJID = StateData#state.jid,
- To = jlib:jid_replace_resource(RoomJID, Nick),
- Limiter = {From#jid.luser, From#jid.lserver},
- case ejabberd_captcha:create_captcha(
- SID, RoomJID, To, Lang, Limiter, From) of
- {ok, ID, CaptchaEls} ->
- MsgPkt = {xmlelement, "message", [{"id", ID}], CaptchaEls},
- Robots = ?DICT:store(From,
- {Nick, Packet}, StateData#state.robots),
- ejabberd_router:route(RoomJID, From, MsgPkt),
- StateData#state{robots = Robots};
- {error, limit} ->
- ErrText = "Too many CAPTCHA requests",
- Err = jlib:make_error_reply(
- Packet, ?ERRT_RESOURCE_CONSTRAINT(Lang, ErrText)),
- ejabberd_router:route( % TODO: s/Nick/""/
- jlib:jid_replace_resource(
- StateData#state.jid, Nick),
- From, Err),
- StateData;
- _ ->
- ErrText = "Unable to generate a CAPTCHA",
- Err = jlib:make_error_reply(
- Packet, ?ERRT_INTERNAL_SERVER_ERROR(Lang, ErrText)),
- ejabberd_router:route( % TODO: s/Nick/""/
- jlib:jid_replace_resource(
- StateData#state.jid, Nick),
- From, Err),
- StateData
- end;
- _ ->
- ErrText = "Incorrect password",
- Err = jlib:make_error_reply(
- Packet, ?ERRT_NOT_AUTHORIZED(Lang, ErrText)),
- ejabberd_router:route( % TODO: s/Nick/""/
- jlib:jid_replace_resource(
- StateData#state.jid, Nick),
- From, Err),
- StateData
- end
+ mod_muc:can_use_nick(StateData#state.server_host,
+ StateData#state.host, From, Nick),
+ get_default_role(Affiliation, StateData)}
+ of
+ {false, _, _, _} ->
+ Err = jlib:make_error_reply(Packet,
+ ?ERR_SERVICE_UNAVAILABLE),
+ ejabberd_router:route % TODO: s/Nick/""/
+ (jlib:jid_replace_resource(StateData#state.jid, Nick),
+ From, Err),
+ StateData;
+ {_, _, _, none} ->
+ Err = jlib:make_error_reply(Packet,
+ case Affiliation of
+ outcast ->
+ ErrText =
+ <<"You have been banned from this room">>,
+ ?ERRT_FORBIDDEN(Lang, ErrText);
+ _ ->
+ ErrText =
+ <<"Membership is required to enter this room">>,
+ ?ERRT_REGISTRATION_REQUIRED(Lang,
+ ErrText)
+ end),
+ ejabberd_router:route % TODO: s/Nick/""/
+ (jlib:jid_replace_resource(StateData#state.jid, Nick),
+ From, Err),
+ StateData;
+ {_, true, _, _} ->
+ ErrText = <<"That nickname is already in use by another occupant">>,
+ Err = jlib:make_error_reply(Packet,
+ ?ERRT_CONFLICT(Lang, ErrText)),
+ ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid,
+ Nick),
+ From, Err),
+ StateData;
+ {_, _, false, _} ->
+ ErrText = <<"That nickname is registered by another person">>,
+ Err = jlib:make_error_reply(Packet,
+ ?ERRT_CONFLICT(Lang, ErrText)),
+ ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid,
+ Nick),
+ From, Err),
+ StateData;
+ {_, _, _, Role} ->
+ case check_password(ServiceAffiliation, Affiliation,
+ Els, From, StateData)
+ of
+ true ->
+ NewState = add_user_presence(From, Packet,
+ add_online_user(From, Nick, Role,
+ StateData)),
+ if not (NewState#state.config)#config.anonymous ->
+ WPacket = #xmlel{name = <<"message">>,
+ attrs = [{<<"type">>, <<"groupchat">>}],
+ children =
+ [#xmlel{name = <<"body">>,
+ attrs = [],
+ children =
+ [{xmlcdata,
+ translate:translate(Lang,
+ <<"This room is not anonymous">>)}]},
+ #xmlel{name = <<"x">>,
+ attrs =
+ [{<<"xmlns">>,
+ ?NS_MUC_USER}],
+ children =
+ [#xmlel{name =
+ <<"status">>,
+ attrs =
+ [{<<"code">>,
+ <<"100">>}],
+ children =
+ []}]}]},
+ ejabberd_router:route(StateData#state.jid, From, WPacket);
+ true -> ok
+ end,
+ send_existing_presences(From, NewState),
+ send_new_presence(From, NewState),
+ Shift = count_stanza_shift(Nick, Els, NewState),
+ case send_history(From, Shift, NewState) of
+ true -> ok;
+ _ -> send_subject(From, Lang, StateData)
+ end,
+ case NewState#state.just_created of
+ true -> NewState#state{just_created = false};
+ false ->
+ Robots = (?DICT):erase(From, StateData#state.robots),
+ NewState#state{robots = Robots}
+ end;
+ nopass ->
+ ErrText = <<"A password is required to enter this room">>,
+ Err = jlib:make_error_reply(Packet,
+ ?ERRT_NOT_AUTHORIZED(Lang,
+ ErrText)),
+ ejabberd_router:route % TODO: s/Nick/""/
+ (jlib:jid_replace_resource(StateData#state.jid,
+ Nick),
+ From, Err),
+ StateData;
+ captcha_required ->
+ SID = xml:get_attr_s(<<"id">>, Attrs),
+ RoomJID = StateData#state.jid,
+ To = jlib:jid_replace_resource(RoomJID, Nick),
+ Limiter = {From#jid.luser, From#jid.lserver},
+ case ejabberd_captcha:create_captcha(SID, RoomJID, To,
+ Lang, Limiter, From)
+ of
+ {ok, ID, CaptchaEls} ->
+ MsgPkt = #xmlel{name = <<"message">>,
+ attrs = [{<<"id">>, ID}],
+ children = CaptchaEls},
+ Robots = (?DICT):store(From, {Nick, Packet},
+ StateData#state.robots),
+ ejabberd_router:route(RoomJID, From, MsgPkt),
+ StateData#state{robots = Robots};
+ {error, limit} ->
+ ErrText = <<"Too many CAPTCHA requests">>,
+ Err = jlib:make_error_reply(Packet,
+ ?ERRT_RESOURCE_CONSTRAINT(Lang,
+ ErrText)),
+ ejabberd_router:route % TODO: s/Nick/""/
+ (jlib:jid_replace_resource(StateData#state.jid,
+ Nick),
+ From, Err),
+ StateData;
+ _ ->
+ ErrText = <<"Unable to generate a CAPTCHA">>,
+ Err = jlib:make_error_reply(Packet,
+ ?ERRT_INTERNAL_SERVER_ERROR(Lang,
+ ErrText)),
+ ejabberd_router:route % TODO: s/Nick/""/
+ (jlib:jid_replace_resource(StateData#state.jid,
+ Nick),
+ From, Err),
+ StateData
+ end;
+ _ ->
+ ErrText = <<"Incorrect password">>,
+ Err = jlib:make_error_reply(Packet,
+ ?ERRT_NOT_AUTHORIZED(Lang,
+ ErrText)),
+ ejabberd_router:route % TODO: s/Nick/""/
+ (jlib:jid_replace_resource(StateData#state.jid,
+ Nick),
+ From, Err),
+ StateData
+ end
end.
-check_password(owner, _Affiliation, _Els, _From, _StateData) ->
+check_password(owner, _Affiliation, _Els, _From,
+ _StateData) ->
%% Don't check pass if user is owner in MUC service (access_admin option)
true;
-check_password(_ServiceAffiliation, Affiliation, Els, From, StateData) ->
- case (StateData#state.config)#config.password_protected of
- false ->
- check_captcha(Affiliation, From, StateData);
- true ->
- Pass = extract_password(Els),
- case Pass of
- false ->
- nopass;
- _ ->
- case (StateData#state.config)#config.password of
- Pass ->
- true;
- _ ->
- false
- end
- end
+check_password(_ServiceAffiliation, Affiliation, Els,
+ From, StateData) ->
+ case (StateData#state.config)#config.password_protected
+ of
+ false -> check_captcha(Affiliation, From, StateData);
+ true ->
+ Pass = extract_password(Els),
+ case Pass of
+ false -> nopass;
+ _ ->
+ case (StateData#state.config)#config.password of
+ Pass -> true;
+ _ -> false
+ end
+ end
end.
check_captcha(Affiliation, From, StateData) ->
case (StateData#state.config)#config.captcha_protected
- andalso ejabberd_captcha:is_feature_available() of
- true when Affiliation == none ->
- case ?DICT:find(From, StateData#state.robots) of
- {ok, passed} ->
- true;
- _ ->
- WList = (StateData#state.config)#config.captcha_whitelist,
- #jid{luser = U, lserver = S, lresource = R} = From,
- case ?SETS:is_element({U, S, R}, WList) of
- true ->
- true;
- false ->
- case ?SETS:is_element({U, S, ""}, WList) of
- true ->
- true;
- false ->
- case ?SETS:is_element({"", S, ""}, WList) of
- true ->
- true;
- false ->
- captcha_required
- end
- end
- end
- end;
- _ ->
- true
+ andalso ejabberd_captcha:is_feature_available()
+ of
+ true when Affiliation == none ->
+ case (?DICT):find(From, StateData#state.robots) of
+ {ok, passed} -> true;
+ _ ->
+ WList =
+ (StateData#state.config)#config.captcha_whitelist,
+ #jid{luser = U, lserver = S, lresource = R} = From,
+ case (?SETS):is_element({U, S, R}, WList) of
+ true -> true;
+ false ->
+ case (?SETS):is_element({U, S, <<"">>}, WList) of
+ true -> true;
+ false ->
+ case (?SETS):is_element({<<"">>, S, <<"">>}, WList)
+ of
+ true -> true;
+ false -> captcha_required
+ end
+ end
+ end
+ end;
+ _ -> true
end.
-extract_password([]) ->
- false;
-extract_password([{xmlelement, _Name, Attrs, _SubEls} = El | Els]) ->
- case xml:get_attr_s("xmlns", Attrs) of
- ?NS_MUC ->
- case xml:get_subtag(El, "password") of
- false ->
- false;
- SubEl ->
- xml:get_tag_cdata(SubEl)
- end;
- _ ->
- extract_password(Els)
+extract_password([]) -> false;
+extract_password([#xmlel{attrs = Attrs} = El | Els]) ->
+ case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ ?NS_MUC ->
+ case xml:get_subtag(El, <<"password">>) of
+ false -> false;
+ SubEl -> xml:get_tag_cdata(SubEl)
+ end;
+ _ -> extract_password(Els)
end;
-extract_password([_ | Els]) ->
- extract_password(Els).
+extract_password([_ | Els]) -> extract_password(Els).
count_stanza_shift(Nick, Els, StateData) ->
HL = lqueue_to_list(StateData#state.history),
- Since = extract_history(Els, "since"),
+ Since = extract_history(Els, <<"since">>),
Shift0 = case Since of
- false ->
- 0;
- _ ->
- Sin = calendar:datetime_to_gregorian_seconds(Since),
- count_seconds_shift(Sin, HL)
+ false -> 0;
+ _ ->
+ Sin = calendar:datetime_to_gregorian_seconds(Since),
+ count_seconds_shift(Sin, HL)
end,
- Seconds = extract_history(Els, "seconds"),
+ Seconds = extract_history(Els, <<"seconds">>),
Shift1 = case Seconds of
- false ->
- 0;
- _ ->
- Sec = calendar:datetime_to_gregorian_seconds(
- calendar:now_to_universal_time(now())) - Seconds,
- count_seconds_shift(Sec, HL)
+ false -> 0;
+ _ ->
+ Sec =
+ calendar:datetime_to_gregorian_seconds(calendar:now_to_universal_time(now()))
+ - Seconds,
+ count_seconds_shift(Sec, HL)
end,
- MaxStanzas = extract_history(Els, "maxstanzas"),
+ MaxStanzas = extract_history(Els, <<"maxstanzas">>),
Shift2 = case MaxStanzas of
- false ->
- 0;
- _ ->
- count_maxstanzas_shift(MaxStanzas, HL)
+ false -> 0;
+ _ -> count_maxstanzas_shift(MaxStanzas, HL)
end,
- MaxChars = extract_history(Els, "maxchars"),
+ MaxChars = extract_history(Els, <<"maxchars">>),
Shift3 = case MaxChars of
- false ->
- 0;
- _ ->
- count_maxchars_shift(Nick, MaxChars, HL)
+ false -> 0;
+ _ -> count_maxchars_shift(Nick, MaxChars, HL)
end,
lists:max([Shift0, Shift1, Shift2, Shift3]).
count_seconds_shift(Seconds, HistoryList) ->
- lists:sum(
- lists:map(
- fun({_Nick, _Packet, _HaveSubject, TimeStamp, _Size}) ->
- T = calendar:datetime_to_gregorian_seconds(TimeStamp),
- if
- T < Seconds ->
- 1;
- true ->
- 0
- end
- end, HistoryList)).
+ lists:sum(lists:map(fun ({_Nick, _Packet, _HaveSubject,
+ TimeStamp, _Size}) ->
+ T =
+ calendar:datetime_to_gregorian_seconds(TimeStamp),
+ if T < Seconds -> 1;
+ true -> 0
+ end
+ end,
+ HistoryList)).
count_maxstanzas_shift(MaxStanzas, HistoryList) ->
S = length(HistoryList) - MaxStanzas,
- if
- S =< 0 ->
- 0;
- true ->
- S
+ if S =< 0 -> 0;
+ true -> S
end.
count_maxchars_shift(Nick, MaxSize, HistoryList) ->
- NLen = string:len(Nick) + 1,
- Sizes = lists:map(
- fun({_Nick, _Packet, _HaveSubject, _TimeStamp, Size}) ->
- Size + NLen
- end, HistoryList),
+ NLen = byte_size(Nick) + 1,
+ Sizes = lists:map(fun ({_Nick, _Packet, _HaveSubject,
+ _TimeStamp, Size}) ->
+ Size + NLen
+ end,
+ HistoryList),
calc_shift(MaxSize, Sizes).
calc_shift(MaxSize, Sizes) ->
Total = lists:sum(Sizes),
calc_shift(MaxSize, Total, 0, Sizes).
-calc_shift(_MaxSize, _Size, Shift, []) ->
- Shift;
+calc_shift(_MaxSize, _Size, Shift, []) -> Shift;
calc_shift(MaxSize, Size, Shift, [S | TSizes]) ->
- if
- MaxSize >= Size ->
- Shift;
- true ->
- calc_shift(MaxSize, Size - S, Shift + 1, TSizes)
+ if MaxSize >= Size -> Shift;
+ true -> calc_shift(MaxSize, Size - S, Shift + 1, TSizes)
end.
-extract_history([], _Type) ->
- false;
-extract_history([{xmlelement, _Name, Attrs, _SubEls} = El | Els], Type) ->
- case xml:get_attr_s("xmlns", Attrs) of
- ?NS_MUC ->
- AttrVal = xml:get_path_s(El,
- [{elem, "history"}, {attr, Type}]),
- case Type of
- "since" ->
- case jlib:datetime_string_to_timestamp(AttrVal) of
- undefined ->
- false;
- TS ->
- calendar:now_to_universal_time(TS)
- end;
- _ ->
- case catch list_to_integer(AttrVal) of
- IntVal when is_integer(IntVal) and (IntVal >= 0) ->
- IntVal;
- _ ->
- false
- end
- end;
- _ ->
- extract_history(Els, Type)
+extract_history([], _Type) -> false;
+extract_history([#xmlel{attrs = Attrs} = El | Els],
+ Type) ->
+ case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ ?NS_MUC ->
+ AttrVal = xml:get_path_s(El,
+ [{elem, <<"history">>}, {attr, Type}]),
+ case Type of
+ <<"since">> ->
+ case jlib:datetime_string_to_timestamp(AttrVal) of
+ undefined -> false;
+ TS -> calendar:now_to_universal_time(TS)
+ end;
+ _ ->
+ case catch jlib:binary_to_integer(AttrVal) of
+ IntVal when is_integer(IntVal) and (IntVal >= 0) ->
+ IntVal;
+ _ -> false
+ end
+ end;
+ _ -> extract_history(Els, Type)
end;
extract_history([_ | Els], Type) ->
extract_history(Els, Type).
-
send_update_presence(JID, StateData) ->
- send_update_presence(JID, "", StateData).
+ send_update_presence(JID, <<"">>, StateData).
send_update_presence(JID, Reason, StateData) ->
LJID = jlib:jid_tolower(JID),
LJIDs = case LJID of
- {U, S, ""} ->
- ?DICT:fold(
- fun(J, _, Js) ->
- case J of
- {U, S, _} ->
- [J | Js];
- _ ->
- Js
- end
- end, [], StateData#state.users);
- _ ->
- case ?DICT:is_key(LJID, StateData#state.users) of
- true ->
- [LJID];
- _ ->
- []
- end
+ {U, S, <<"">>} ->
+ (?DICT):fold(fun (J, _, Js) ->
+ case J of
+ {U, S, _} -> [J | Js];
+ _ -> Js
+ end
+ end,
+ [], StateData#state.users);
+ _ ->
+ case (?DICT):is_key(LJID, StateData#state.users) of
+ true -> [LJID];
+ _ -> []
+ end
end,
- lists:foreach(fun(J) ->
+ lists:foreach(fun (J) ->
send_new_presence(J, Reason, StateData)
- end, LJIDs).
+ end,
+ LJIDs).
send_new_presence(NJID, StateData) ->
- send_new_presence(NJID, "", StateData).
+ send_new_presence(NJID, <<"">>, StateData).
send_new_presence(NJID, Reason, StateData) ->
- %% First, find the nick associated with this JID.
- #user{nick = Nick} = ?DICT:fetch(jlib:jid_tolower(NJID), StateData#state.users),
- %% Then find the JID using this nick with highest priority.
+ #user{nick = Nick} =
+ (?DICT):fetch(jlib:jid_tolower(NJID),
+ StateData#state.users),
LJID = find_jid_by_nick(Nick, StateData),
- %% Then we get the presence data we're supposed to send.
- {ok, #user{jid = RealJID,
- role = Role,
- last_presence = Presence}} =
- ?DICT:find(jlib:jid_tolower(LJID), StateData#state.users),
+ {ok,
+ #user{jid = RealJID, role = Role,
+ last_presence = Presence}} =
+ (?DICT):find(jlib:jid_tolower(LJID),
+ StateData#state.users),
Affiliation = get_affiliation(LJID, StateData),
SAffiliation = affiliation_to_list(Affiliation),
SRole = role_to_list(Role),
- lists:foreach(
- fun({_LJID, Info}) ->
- ItemAttrs =
- case (Info#user.role == moderator) orelse
- ((StateData#state.config)#config.anonymous == false) of
- true ->
- [{"jid", jlib:jid_to_string(RealJID)},
- {"affiliation", SAffiliation},
- {"role", SRole}];
- _ ->
- [{"affiliation", SAffiliation},
- {"role", SRole}]
+ lists:foreach(fun ({_LJID, Info}) ->
+ ItemAttrs = case Info#user.role == moderator orelse
+ (StateData#state.config)#config.anonymous
+ == false
+ of
+ true ->
+ [{<<"jid">>,
+ jlib:jid_to_string(RealJID)},
+ {<<"affiliation">>, SAffiliation},
+ {<<"role">>, SRole}];
+ _ ->
+ [{<<"affiliation">>, SAffiliation},
+ {<<"role">>, SRole}]
+ end,
+ ItemEls = case Reason of
+ <<"">> -> [];
+ _ ->
+ [#xmlel{name = <<"reason">>,
+ attrs = [],
+ children =
+ [{xmlcdata, Reason}]}]
+ end,
+ Status = case StateData#state.just_created of
+ true ->
+ [#xmlel{name = <<"status">>,
+ attrs =
+ [{<<"code">>, <<"201">>}],
+ children = []}];
+ false -> []
+ end,
+ Status2 = case
+ (StateData#state.config)#config.anonymous
+ == false
+ andalso NJID == Info#user.jid
+ of
+ true ->
+ [#xmlel{name = <<"status">>,
+ attrs =
+ [{<<"code">>, <<"100">>}],
+ children = []}
+ | Status];
+ false -> Status
+ end,
+ Status3 = case NJID == Info#user.jid of
+ true ->
+ [#xmlel{name = <<"status">>,
+ attrs =
+ [{<<"code">>, <<"110">>}],
+ children = []}
+ | Status2];
+ false -> Status2
+ end,
+ Packet = xml:append_subtags(Presence,
+ [#xmlel{name = <<"x">>,
+ attrs =
+ [{<<"xmlns">>,
+ ?NS_MUC_USER}],
+ children =
+ [#xmlel{name =
+ <<"item">>,
+ attrs
+ =
+ ItemAttrs,
+ children
+ =
+ ItemEls}
+ | Status3]}]),
+ ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid,
+ Nick),
+ Info#user.jid, Packet)
end,
- ItemEls = case Reason of
- "" ->
- [];
- _ ->
- [{xmlelement, "reason", [],
- [{xmlcdata, Reason}]}]
- end,
- Status = case StateData#state.just_created of
- true ->
- [{xmlelement, "status", [{"code", "201"}], []}];
- false ->
- []
- end,
- Status2 = case ((StateData#state.config)#config.anonymous==false)
- andalso (NJID == Info#user.jid) of
- true ->
- [{xmlelement, "status", [{"code", "100"}], []}
- | Status];
- false ->
- Status
- end,
- Status3 = case NJID == Info#user.jid of
- true ->
- [{xmlelement, "status", [{"code", "110"}], []}
- | Status2];
- false ->
- Status2
- end,
- Packet = xml:append_subtags(
- Presence,
- [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}],
- [{xmlelement, "item", ItemAttrs, ItemEls} | Status3]}]),
- ejabberd_router:route(
- jlib:jid_replace_resource(StateData#state.jid, Nick),
- Info#user.jid,
- Packet)
- end, ?DICT:to_list(StateData#state.users)).
-
+ (?DICT):to_list(StateData#state.users)).
send_existing_presences(ToJID, StateData) ->
LToJID = jlib:jid_tolower(ToJID),
- {ok, #user{jid = RealToJID,
- role = Role}} =
- ?DICT:find(LToJID, StateData#state.users),
- lists:foreach(
- fun({FromNick, _Users}) ->
- LJID = find_jid_by_nick(FromNick, StateData),
- #user{jid = FromJID,
- role = FromRole,
- last_presence = Presence
- } = ?DICT:fetch(jlib:jid_tolower(LJID), StateData#state.users),
- case RealToJID of
- FromJID ->
- ok;
- _ ->
- FromAffiliation = get_affiliation(LJID, StateData),
- ItemAttrs =
- case (Role == moderator) orelse
- ((StateData#state.config)#config.anonymous ==
- false) of
- true ->
- [{"jid", jlib:jid_to_string(FromJID)},
- {"affiliation",
- affiliation_to_list(FromAffiliation)},
- {"role", role_to_list(FromRole)}];
- _ ->
- [{"affiliation",
- affiliation_to_list(FromAffiliation)},
- {"role", role_to_list(FromRole)}]
- end,
- Packet = xml:append_subtags(
- Presence,
- [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}],
- [{xmlelement, "item", ItemAttrs, []}]}]),
- ejabberd_router:route(
- jlib:jid_replace_resource(
- StateData#state.jid, FromNick),
- RealToJID,
- Packet)
- end
- end, ?DICT:to_list(StateData#state.nicks)).
-
+ {ok, #user{jid = RealToJID, role = Role}} =
+ (?DICT):find(LToJID, StateData#state.users),
+ lists:foreach(fun ({FromNick, _Users}) ->
+ LJID = find_jid_by_nick(FromNick, StateData),
+ #user{jid = FromJID, role = FromRole,
+ last_presence = Presence} =
+ (?DICT):fetch(jlib:jid_tolower(LJID),
+ StateData#state.users),
+ case RealToJID of
+ FromJID -> ok;
+ _ ->
+ FromAffiliation = get_affiliation(LJID,
+ StateData),
+ ItemAttrs = case Role == moderator orelse
+ (StateData#state.config)#config.anonymous
+ == false
+ of
+ true ->
+ [{<<"jid">>,
+ jlib:jid_to_string(FromJID)},
+ {<<"affiliation">>,
+ affiliation_to_list(FromAffiliation)},
+ {<<"role">>,
+ role_to_list(FromRole)}];
+ _ ->
+ [{<<"affiliation">>,
+ affiliation_to_list(FromAffiliation)},
+ {<<"role">>,
+ role_to_list(FromRole)}]
+ end,
+ Packet = xml:append_subtags(Presence,
+ [#xmlel{name =
+ <<"x">>,
+ attrs =
+ [{<<"xmlns">>,
+ ?NS_MUC_USER}],
+ children =
+ [#xmlel{name
+ =
+ <<"item">>,
+ attrs
+ =
+ ItemAttrs,
+ children
+ =
+ []}]}]),
+ ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid,
+ FromNick),
+ RealToJID, Packet)
+ end
+ end,
+ (?DICT):to_list(StateData#state.nicks)).
now_to_usec({MSec, Sec, USec}) ->
- (MSec*1000000 + Sec)*1000000 + USec.
-
+ (MSec * 1000000 + Sec) * 1000000 + USec.
change_nick(JID, Nick, StateData) ->
LJID = jlib:jid_tolower(JID),
- {ok, #user{nick = OldNick}} =
- ?DICT:find(LJID, StateData#state.users),
- Users =
- ?DICT:update(
- LJID,
- fun(#user{} = User) ->
- User#user{nick = Nick}
- end, StateData#state.users),
- OldNickUsers = ?DICT:fetch(OldNick, StateData#state.nicks),
- NewNickUsers = case ?DICT:find(Nick, StateData#state.nicks) of
- {ok, U} -> U;
- error -> []
+ {ok, #user{nick = OldNick}} = (?DICT):find(LJID,
+ StateData#state.users),
+ Users = (?DICT):update(LJID,
+ fun (#user{} = User) -> User#user{nick = Nick} end,
+ StateData#state.users),
+ OldNickUsers = (?DICT):fetch(OldNick,
+ StateData#state.nicks),
+ NewNickUsers = case (?DICT):find(Nick,
+ StateData#state.nicks)
+ of
+ {ok, U} -> U;
+ error -> []
end,
- %% Send unavailable presence from the old nick if it's no longer
- %% used.
SendOldUnavailable = length(OldNickUsers) == 1,
- %% If we send unavailable presence from the old nick, we should
- %% probably send presence from the new nick, in order not to
- %% confuse clients. Otherwise, do it only if the new nick was
- %% unused.
SendNewAvailable = SendOldUnavailable orelse
- NewNickUsers == [],
- Nicks =
- case OldNickUsers of
- [LJID] ->
- ?DICT:store(Nick, [LJID|NewNickUsers],
- ?DICT:erase(OldNick, StateData#state.nicks));
- [_|_] ->
- ?DICT:store(Nick, [LJID|NewNickUsers],
- ?DICT:store(OldNick, OldNickUsers -- [LJID],
- StateData#state.nicks))
- end,
- NewStateData = StateData#state{users = Users, nicks = Nicks},
- send_nick_changing(JID, OldNick, NewStateData, SendOldUnavailable, SendNewAvailable),
+ NewNickUsers == [],
+ Nicks = case OldNickUsers of
+ [LJID] ->
+ (?DICT):store(Nick, [LJID | NewNickUsers],
+ (?DICT):erase(OldNick, StateData#state.nicks));
+ [_ | _] ->
+ (?DICT):store(Nick, [LJID | NewNickUsers],
+ (?DICT):store(OldNick, OldNickUsers -- [LJID],
+ StateData#state.nicks))
+ end,
+ NewStateData = StateData#state{users = Users,
+ nicks = Nicks},
+ send_nick_changing(JID, OldNick, NewStateData,
+ SendOldUnavailable, SendNewAvailable),
add_to_log(nickchange, {OldNick, Nick}, StateData),
NewStateData.
send_nick_changing(JID, OldNick, StateData,
- SendOldUnavailable, SendNewAvailable) ->
- {ok, #user{jid = RealJID,
- nick = Nick,
- role = Role,
- last_presence = Presence}} =
- ?DICT:find(jlib:jid_tolower(JID), StateData#state.users),
+ SendOldUnavailable, SendNewAvailable) ->
+ {ok,
+ #user{jid = RealJID, nick = Nick, role = Role,
+ last_presence = Presence}} =
+ (?DICT):find(jlib:jid_tolower(JID),
+ StateData#state.users),
Affiliation = get_affiliation(JID, StateData),
SAffiliation = affiliation_to_list(Affiliation),
SRole = role_to_list(Role),
- lists:foreach(
- fun({_LJID, Info}) ->
- ItemAttrs1 =
- case (Info#user.role == moderator) orelse
- ((StateData#state.config)#config.anonymous == false) of
- true ->
- [{"jid", jlib:jid_to_string(RealJID)},
- {"affiliation", SAffiliation},
- {"role", SRole},
- {"nick", Nick}];
- _ ->
- [{"affiliation", SAffiliation},
- {"role", SRole},
- {"nick", Nick}]
- end,
- ItemAttrs2 =
- case (Info#user.role == moderator) orelse
- ((StateData#state.config)#config.anonymous == false) of
- true ->
- [{"jid", jlib:jid_to_string(RealJID)},
- {"affiliation", SAffiliation},
- {"role", SRole}];
- _ ->
- [{"affiliation", SAffiliation},
- {"role", SRole}]
+ lists:foreach(fun ({_LJID, Info}) ->
+ ItemAttrs1 = case Info#user.role == moderator orelse
+ (StateData#state.config)#config.anonymous
+ == false
+ of
+ true ->
+ [{<<"jid">>,
+ jlib:jid_to_string(RealJID)},
+ {<<"affiliation">>, SAffiliation},
+ {<<"role">>, SRole},
+ {<<"nick">>, Nick}];
+ _ ->
+ [{<<"affiliation">>, SAffiliation},
+ {<<"role">>, SRole},
+ {<<"nick">>, Nick}]
+ end,
+ ItemAttrs2 = case Info#user.role == moderator orelse
+ (StateData#state.config)#config.anonymous
+ == false
+ of
+ true ->
+ [{<<"jid">>,
+ jlib:jid_to_string(RealJID)},
+ {<<"affiliation">>, SAffiliation},
+ {<<"role">>, SRole}];
+ _ ->
+ [{<<"affiliation">>, SAffiliation},
+ {<<"role">>, SRole}]
+ end,
+ Packet1 = #xmlel{name = <<"presence">>,
+ attrs =
+ [{<<"type">>,
+ <<"unavailable">>}],
+ children =
+ [#xmlel{name = <<"x">>,
+ attrs =
+ [{<<"xmlns">>,
+ ?NS_MUC_USER}],
+ children =
+ [#xmlel{name =
+ <<"item">>,
+ attrs =
+ ItemAttrs1,
+ children =
+ []},
+ #xmlel{name =
+ <<"status">>,
+ attrs =
+ [{<<"code">>,
+ <<"303">>}],
+ children =
+ []}]}]},
+ Packet2 = xml:append_subtags(Presence,
+ [#xmlel{name = <<"x">>,
+ attrs =
+ [{<<"xmlns">>,
+ ?NS_MUC_USER}],
+ children =
+ [#xmlel{name
+ =
+ <<"item">>,
+ attrs
+ =
+ ItemAttrs2,
+ children
+ =
+ []}]}]),
+ if SendOldUnavailable ->
+ ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid,
+ OldNick),
+ Info#user.jid, Packet1);
+ true -> ok
+ end,
+ if SendNewAvailable ->
+ ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid,
+ Nick),
+ Info#user.jid, Packet2);
+ true -> ok
+ end
end,
- Packet1 =
- {xmlelement, "presence", [{"type", "unavailable"}],
- [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}],
- [{xmlelement, "item", ItemAttrs1, []},
- {xmlelement, "status", [{"code", "303"}], []}]}]},
- Packet2 = xml:append_subtags(
- Presence,
- [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}],
- [{xmlelement, "item", ItemAttrs2, []}]}]),
- if SendOldUnavailable ->
- ejabberd_router:route(
- jlib:jid_replace_resource(StateData#state.jid, OldNick),
- Info#user.jid,
- Packet1);
- true ->
- ok
- end,
- if SendNewAvailable ->
- ejabberd_router:route(
- jlib:jid_replace_resource(StateData#state.jid, Nick),
- Info#user.jid,
- Packet2);
- true ->
- ok
- end
- end, ?DICT:to_list(StateData#state.users)).
-
+ (?DICT):to_list(StateData#state.users)).
lqueue_new(Max) ->
- #lqueue{queue = queue:new(),
- len = 0,
- max = Max}.
+ #lqueue{queue = queue:new(), len = 0, max = Max}.
%% If the message queue limit is set to 0, do not store messages.
-lqueue_in(_Item, LQ = #lqueue{max = 0}) ->
- LQ;
+lqueue_in(_Item, LQ = #lqueue{max = 0}) -> LQ;
%% Otherwise, rotate messages in the queue store.
-lqueue_in(Item, #lqueue{queue = Q1, len = Len, max = Max}) ->
+lqueue_in(Item,
+ #lqueue{queue = Q1, len = Len, max = Max}) ->
Q2 = queue:in(Item, Q1),
- if
- Len >= Max ->
- Q3 = lqueue_cut(Q2, Len - Max + 1),
- #lqueue{queue = Q3, len = Max, max = Max};
- true ->
- #lqueue{queue = Q2, len = Len + 1, max = Max}
+ if Len >= Max ->
+ Q3 = lqueue_cut(Q2, Len - Max + 1),
+ #lqueue{queue = Q3, len = Max, max = Max};
+ true -> #lqueue{queue = Q2, len = Len + 1, max = Max}
end.
-lqueue_cut(Q, 0) ->
- Q;
+lqueue_cut(Q, 0) -> Q;
lqueue_cut(Q, N) ->
- {_, Q1} = queue:out(Q),
- lqueue_cut(Q1, N - 1).
+ {_, Q1} = queue:out(Q), lqueue_cut(Q1, N - 1).
lqueue_to_list(#lqueue{queue = Q1}) ->
queue:to_list(Q1).
add_message_to_history(FromNick, FromJID, Packet, StateData) ->
- HaveSubject = case xml:get_subtag(Packet, "subject") of
- false ->
- false;
- _ ->
- true
+ HaveSubject = case xml:get_subtag(Packet, <<"subject">>)
+ of
+ false -> false;
+ _ -> true
end,
TimeStamp = calendar:now_to_universal_time(now()),
- %% Chatroom history is stored as XMPP packets, so
- %% the decision to include the original sender's JID or not is based on the
- %% chatroom configuration when the message was originally sent.
- %% Also, if the chatroom is anonymous, even moderators will not get the real JID
- SenderJid = case ((StateData#state.config)#config.anonymous) of
- true -> StateData#state.jid;
- false -> FromJID
- end,
+ SenderJid = case
+ (StateData#state.config)#config.anonymous
+ of
+ true -> StateData#state.jid;
+ false -> FromJID
+ end,
TSPacket = xml:append_subtags(Packet,
- [jlib:timestamp_to_xml(TimeStamp, utc, SenderJid, ""),
- %% TODO: Delete the next line once XEP-0091 is Obsolete
- jlib:timestamp_to_xml(TimeStamp)]),
- SPacket = jlib:replace_from_to(
- jlib:jid_replace_resource(StateData#state.jid, FromNick),
- StateData#state.jid,
- TSPacket),
+ [jlib:timestamp_to_xml(TimeStamp, utc,
+ SenderJid, <<"">>),
+ jlib:timestamp_to_xml(TimeStamp)]),
+ SPacket =
+ jlib:replace_from_to(jlib:jid_replace_resource(StateData#state.jid,
+ FromNick),
+ StateData#state.jid, TSPacket),
Size = element_size(SPacket),
- Q1 = lqueue_in({FromNick, TSPacket, HaveSubject, TimeStamp, Size},
+ Q1 = lqueue_in({FromNick, TSPacket, HaveSubject,
+ TimeStamp, Size},
StateData#state.history),
add_to_log(text, {FromNick, Packet}, StateData),
StateData#state{history = Q1}.
send_history(JID, Shift, StateData) ->
- lists:foldl(
- fun({Nick, Packet, HaveSubject, _TimeStamp, _Size}, B) ->
- ejabberd_router:route(
- jlib:jid_replace_resource(StateData#state.jid, Nick),
- JID,
- Packet),
- B or HaveSubject
- end, false, lists:nthtail(Shift, lqueue_to_list(StateData#state.history))).
-
+ lists:foldl(fun ({Nick, Packet, HaveSubject, _TimeStamp,
+ _Size},
+ B) ->
+ ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid,
+ Nick),
+ JID, Packet),
+ B or HaveSubject
+ end,
+ false,
+ lists:nthtail(Shift,
+ lqueue_to_list(StateData#state.history))).
send_subject(JID, Lang, StateData) ->
case StateData#state.subject_author of
- "" ->
- ok;
- Nick ->
- Subject = StateData#state.subject,
- Packet = {xmlelement, "message", [{"type", "groupchat"}],
- [{xmlelement, "subject", [], [{xmlcdata, Subject}]},
- {xmlelement, "body", [],
- [{xmlcdata,
- Nick ++
- translate:translate(Lang,
- " has set the subject to: ") ++
- Subject}]}]},
- ejabberd_router:route(
- StateData#state.jid,
- JID,
- Packet)
+ <<"">> -> ok;
+ Nick ->
+ Subject = StateData#state.subject,
+ Packet = #xmlel{name = <<"message">>,
+ attrs = [{<<"type">>, <<"groupchat">>}],
+ children =
+ [#xmlel{name = <<"subject">>, attrs = [],
+ children = [{xmlcdata, Subject}]},
+ #xmlel{name = <<"body">>, attrs = [],
+ children =
+ [{xmlcdata,
+ <<Nick/binary,
+ (translate:translate(Lang,
+ <<" has set the subject to: ">>))/binary,
+ Subject/binary>>}]}]},
+ ejabberd_router:route(StateData#state.jid, JID, Packet)
end.
check_subject(Packet) ->
- case xml:get_subtag(Packet, "subject") of
- false ->
- false;
- SubjEl ->
- xml:get_tag_cdata(SubjEl)
+ case xml:get_subtag(Packet, <<"subject">>) of
+ false -> false;
+ SubjEl -> xml:get_tag_cdata(SubjEl)
end.
can_change_subject(Role, StateData) ->
- case (StateData#state.config)#config.allow_change_subj of
- true ->
- (Role == moderator) orelse (Role == participant);
- _ ->
- Role == moderator
+ case (StateData#state.config)#config.allow_change_subj
+ of
+ true -> Role == moderator orelse Role == participant;
+ _ -> Role == moderator
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Admin stuff
process_iq_admin(From, set, Lang, SubEl, StateData) ->
- {xmlelement, _, _, Items} = SubEl,
+ #xmlel{children = Items} = SubEl,
process_admin_items_set(From, Items, Lang, StateData);
-
process_iq_admin(From, get, Lang, SubEl, StateData) ->
- case xml:get_subtag(SubEl, "item") of
- false ->
- {error, ?ERR_BAD_REQUEST};
- Item ->
- FAffiliation = get_affiliation(From, StateData),
- FRole = get_role(From, StateData),
- case xml:get_tag_attr("role", Item) of
- false ->
- case xml:get_tag_attr("affiliation", Item) of
- false ->
- {error, ?ERR_BAD_REQUEST};
- {value, StrAffiliation} ->
- case catch list_to_affiliation(StrAffiliation) of
- {'EXIT', _} ->
- {error, ?ERR_BAD_REQUEST};
- SAffiliation ->
- if
- (FAffiliation == owner) or
- (FAffiliation == admin) ->
- Items = items_with_affiliation(
- SAffiliation, StateData),
- {result, Items, StateData};
- true ->
- ErrText = "Administrator privileges required",
- {error, ?ERRT_FORBIDDEN(Lang, ErrText)}
- end
- end
- end;
- {value, StrRole} ->
- case catch list_to_role(StrRole) of
- {'EXIT', _} ->
- {error, ?ERR_BAD_REQUEST};
- SRole ->
- if
- FRole == moderator ->
- Items = items_with_role(SRole, StateData),
- {result, Items, StateData};
- true ->
- ErrText = "Moderator privileges required",
- {error, ?ERRT_FORBIDDEN(Lang, ErrText)}
+ case xml:get_subtag(SubEl, <<"item">>) of
+ false -> {error, ?ERR_BAD_REQUEST};
+ Item ->
+ FAffiliation = get_affiliation(From, StateData),
+ FRole = get_role(From, StateData),
+ case xml:get_tag_attr(<<"role">>, Item) of
+ false ->
+ case xml:get_tag_attr(<<"affiliation">>, Item) of
+ false -> {error, ?ERR_BAD_REQUEST};
+ {value, StrAffiliation} ->
+ case catch list_to_affiliation(StrAffiliation) of
+ {'EXIT', _} -> {error, ?ERR_BAD_REQUEST};
+ SAffiliation ->
+ if (FAffiliation == owner) or
+ (FAffiliation == admin) ->
+ Items = items_with_affiliation(SAffiliation,
+ StateData),
+ {result, Items, StateData};
+ true ->
+ ErrText =
+ <<"Administrator privileges required">>,
+ {error, ?ERRT_FORBIDDEN(Lang, ErrText)}
end
- end
- end
+ end
+ end;
+ {value, StrRole} ->
+ case catch list_to_role(StrRole) of
+ {'EXIT', _} -> {error, ?ERR_BAD_REQUEST};
+ SRole ->
+ if FRole == moderator ->
+ Items = items_with_role(SRole, StateData),
+ {result, Items, StateData};
+ true ->
+ ErrText = <<"Moderator privileges required">>,
+ {error, ?ERRT_FORBIDDEN(Lang, ErrText)}
+ end
+ end
+ end
end.
-
items_with_role(SRole, StateData) ->
- lists:map(
- fun({_, U}) ->
- user_to_item(U, StateData)
- end, search_role(SRole, StateData)).
+ lists:map(fun ({_, U}) -> user_to_item(U, StateData)
+ end,
+ search_role(SRole, StateData)).
items_with_affiliation(SAffiliation, StateData) ->
- lists:map(
- fun({JID, {Affiliation, Reason}}) ->
- {xmlelement, "item",
- [{"affiliation", affiliation_to_list(Affiliation)},
- {"jid", jlib:jid_to_string(JID)}],
- [{xmlelement, "reason", [], [{xmlcdata, Reason}]}]};
- ({JID, Affiliation}) ->
- {xmlelement, "item",
- [{"affiliation", affiliation_to_list(Affiliation)},
- {"jid", jlib:jid_to_string(JID)}],
- []}
- end, search_affiliation(SAffiliation, StateData)).
-
-user_to_item(#user{role = Role,
- nick = Nick,
- jid = JID
- }, StateData) ->
+ lists:map(fun ({JID, {Affiliation, Reason}}) ->
+ #xmlel{name = <<"item">>,
+ attrs =
+ [{<<"affiliation">>,
+ affiliation_to_list(Affiliation)},
+ {<<"jid">>, jlib:jid_to_string(JID)}],
+ children =
+ [#xmlel{name = <<"reason">>, attrs = [],
+ children = [{xmlcdata, Reason}]}]};
+ ({JID, Affiliation}) ->
+ #xmlel{name = <<"item">>,
+ attrs =
+ [{<<"affiliation">>,
+ affiliation_to_list(Affiliation)},
+ {<<"jid">>, jlib:jid_to_string(JID)}],
+ children = []}
+ end,
+ search_affiliation(SAffiliation, StateData)).
+
+user_to_item(#user{role = Role, nick = Nick, jid = JID},
+ StateData) ->
Affiliation = get_affiliation(JID, StateData),
- {xmlelement, "item",
- [{"role", role_to_list(Role)},
- {"affiliation", affiliation_to_list(Affiliation)},
- {"nick", Nick},
- {"jid", jlib:jid_to_string(JID)}],
- []}.
+ #xmlel{name = <<"item">>,
+ attrs =
+ [{<<"role">>, role_to_list(Role)},
+ {<<"affiliation">>, affiliation_to_list(Affiliation)},
+ {<<"nick">>, Nick},
+ {<<"jid">>, jlib:jid_to_string(JID)}],
+ children = []}.
search_role(Role, StateData) ->
- lists:filter(
- fun({_, #user{role = R}}) ->
- Role == R
- end, ?DICT:to_list(StateData#state.users)).
+ lists:filter(fun ({_, #user{role = R}}) -> Role == R
+ end,
+ (?DICT):to_list(StateData#state.users)).
search_affiliation(Affiliation, StateData) ->
- lists:filter(
- fun({_, A}) ->
- case A of
- {A1, _Reason} ->
- Affiliation == A1;
- _ ->
- Affiliation == A
- end
- end, ?DICT:to_list(StateData#state.affiliations)).
-
+ lists:filter(fun ({_, A}) ->
+ case A of
+ {A1, _Reason} -> Affiliation == A1;
+ _ -> Affiliation == A
+ end
+ end,
+ (?DICT):to_list(StateData#state.affiliations)).
process_admin_items_set(UJID, Items, Lang, StateData) ->
UAffiliation = get_affiliation(UJID, StateData),
URole = get_role(UJID, StateData),
- case find_changed_items(UJID, UAffiliation, URole, Items, Lang, StateData, []) of
- {result, Res} ->
- ?INFO_MSG("Processing MUC admin query from ~s in room ~s:~n ~p",
- [jlib:jid_to_string(UJID), jlib:jid_to_string(StateData#state.jid), Res]),
- NSD =
- lists:foldl(
- fun(E, SD) ->
- case catch (
- case E of
- {JID, affiliation, owner, _}
- when (JID#jid.luser == "") ->
- %% If the provided JID does not have username,
- %% forget the affiliation completely
- SD;
- {JID, role, none, Reason} ->
- catch send_kickban_presence(
- JID, Reason, "307", SD),
- set_role(JID, none, SD);
- {JID, affiliation, none, Reason} ->
- case (SD#state.config)#config.members_only of
- true ->
- catch send_kickban_presence(
- JID, Reason, "321", none, SD),
- SD1 = set_affiliation(JID, none, SD),
- set_role(JID, none, SD1);
- _ ->
- SD1 = set_affiliation(JID, none, SD),
- send_update_presence(JID, SD1),
- SD1
- end;
- {JID, affiliation, outcast, Reason} ->
- catch send_kickban_presence(
- JID, Reason, "301", outcast, SD),
- set_affiliation(
- JID, outcast,
- set_role(JID, none, SD), Reason);
- {JID, affiliation, A, Reason} when
- (A == admin) or (A == owner) ->
- SD1 = set_affiliation(JID, A, SD, Reason),
- SD2 = set_role(JID, moderator, SD1),
- send_update_presence(JID, Reason, SD2),
- SD2;
- {JID, affiliation, member, Reason} ->
- SD1 = set_affiliation(
- JID, member, SD, Reason),
- SD2 = set_role(JID, participant, SD1),
- send_update_presence(JID, Reason, SD2),
- SD2;
- {JID, role, Role, Reason} ->
- SD1 = set_role(JID, Role, SD),
- catch send_new_presence(JID, Reason, SD1),
- SD1;
- {JID, affiliation, A, _Reason} ->
- SD1 = set_affiliation(JID, A, SD),
- send_update_presence(JID, SD1),
- SD1
- end
- ) of
- {'EXIT', ErrReason} ->
- ?ERROR_MSG("MUC ITEMS SET ERR: ~p~n",
- [ErrReason]),
- SD;
- NSD ->
- NSD
- end
- end, StateData, lists:flatten(Res)),
- case (NSD#state.config)#config.persistent of
- true ->
- mod_muc:store_room(NSD#state.server_host,
- NSD#state.host, NSD#state.room,
- make_opts(NSD));
- _ ->
- ok
- end,
- {result, [], NSD};
- Err ->
- Err
+ case find_changed_items(UJID, UAffiliation, URole,
+ Items, Lang, StateData, [])
+ of
+ {result, Res} ->
+ ?INFO_MSG("Processing MUC admin query from ~s in "
+ "room ~s:~n ~p",
+ [jlib:jid_to_string(UJID),
+ jlib:jid_to_string(StateData#state.jid), Res]),
+ NSD = lists:foldl(fun (E, SD) ->
+ case catch case E of
+ {JID, affiliation, owner, _}
+ when JID#jid.luser ==
+ <<"">> ->
+ %% If the provided JID does not have username,
+ %% forget the affiliation completely
+ SD;
+ {JID, role, none, Reason} ->
+ catch
+ send_kickban_presence(JID,
+ Reason,
+ <<"307">>,
+ SD),
+ set_role(JID, none, SD);
+ {JID, affiliation, none,
+ Reason} ->
+ case
+ (SD#state.config)#config.members_only
+ of
+ true ->
+ catch
+ send_kickban_presence(JID,
+ Reason,
+ <<"321">>,
+ none,
+ SD),
+ SD1 =
+ set_affiliation(JID,
+ none,
+ SD),
+ set_role(JID, none,
+ SD1);
+ _ ->
+ SD1 =
+ set_affiliation(JID,
+ none,
+ SD),
+ send_update_presence(JID,
+ SD1),
+ SD1
+ end;
+ {JID, affiliation, outcast,
+ Reason} ->
+ catch
+ send_kickban_presence(JID,
+ Reason,
+ <<"301">>,
+ outcast,
+ SD),
+ set_affiliation(JID,
+ outcast,
+ set_role(JID,
+ none,
+ SD),
+ Reason);
+ {JID, affiliation, A, Reason}
+ when (A == admin) or
+ (A == owner) ->
+ SD1 = set_affiliation(JID,
+ A,
+ SD,
+ Reason),
+ SD2 = set_role(JID,
+ moderator,
+ SD1),
+ send_update_presence(JID,
+ Reason,
+ SD2),
+ SD2;
+ {JID, affiliation, member,
+ Reason} ->
+ SD1 = set_affiliation(JID,
+ member,
+ SD,
+ Reason),
+ SD2 = set_role(JID,
+ participant,
+ SD1),
+ send_update_presence(JID,
+ Reason,
+ SD2),
+ SD2;
+ {JID, role, Role, Reason} ->
+ SD1 = set_role(JID, Role,
+ SD),
+ catch
+ send_new_presence(JID,
+ Reason,
+ SD1),
+ SD1;
+ {JID, affiliation, A,
+ _Reason} ->
+ SD1 = set_affiliation(JID,
+ A,
+ SD),
+ send_update_presence(JID,
+ SD1),
+ SD1
+ end
+ of
+ {'EXIT', ErrReason} ->
+ ?ERROR_MSG("MUC ITEMS SET ERR: ~p~n",
+ [ErrReason]),
+ SD;
+ NSD -> NSD
+ end
+ end,
+ StateData, lists:flatten(Res)),
+ case (NSD#state.config)#config.persistent of
+ true ->
+ mod_muc:store_room(NSD#state.server_host,
+ NSD#state.host, NSD#state.room,
+ make_opts(NSD));
+ _ -> ok
+ end,
+ {result, [], NSD};
+ Err -> Err
end.
-
-find_changed_items(_UJID, _UAffiliation, _URole, [], _Lang, _StateData, Res) ->
+find_changed_items(_UJID, _UAffiliation, _URole, [],
+ _Lang, _StateData, Res) ->
{result, Res};
-find_changed_items(UJID, UAffiliation, URole, [{xmlcdata, _} | Items],
- Lang, StateData, Res) ->
- find_changed_items(UJID, UAffiliation, URole, Items, Lang, StateData, Res);
find_changed_items(UJID, UAffiliation, URole,
- [{xmlelement, "item", Attrs, _Els} = Item | Items],
+ [{xmlcdata, _} | Items], Lang, StateData, Res) ->
+ find_changed_items(UJID, UAffiliation, URole, Items,
+ Lang, StateData, Res);
+find_changed_items(UJID, UAffiliation, URole,
+ [#xmlel{name = <<"item">>, attrs = Attrs} = Item
+ | Items],
Lang, StateData, Res) ->
- TJID = case xml:get_attr("jid", Attrs) of
- {value, S} ->
- case jlib:string_to_jid(S) of
- error ->
- ErrText = io_lib:format(
- translate:translate(
- Lang,
- "Jabber ID ~s is invalid"), [S]),
- {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)};
- J ->
- {value, [J]}
- end;
- _ ->
- case xml:get_attr("nick", Attrs) of
- {value, N} ->
- case find_jids_by_nick(N, StateData) of
- false ->
- ErrText =
- io_lib:format(
- translate:translate(
- Lang,
- "Nickname ~s does not exist in the room"),
- [N]),
- {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)};
- J ->
- {value, J}
- end;
- _ ->
- {error, ?ERR_BAD_REQUEST}
- end
+ TJID = case xml:get_attr(<<"jid">>, Attrs) of
+ {value, S} ->
+ case jlib:string_to_jid(S) of
+ error ->
+ ErrText = iolist_to_binary(
+ io_lib:format(translate:translate(
+ Lang,
+ <<"Jabber ID ~s is invalid">>),
+ [S])),
+ {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)};
+ J -> {value, [J]}
+ end;
+ _ ->
+ case xml:get_attr(<<"nick">>, Attrs) of
+ {value, N} ->
+ case find_jids_by_nick(N, StateData) of
+ false ->
+ ErrText = iolist_to_binary(
+ io_lib:format(
+ translate:translate(
+ Lang,
+ <<"Nickname ~s does not exist in the room">>),
+ [N])),
+ {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)};
+ J -> {value, J}
+ end;
+ _ -> {error, ?ERR_BAD_REQUEST}
+ end
end,
case TJID of
- {value, [JID|_]=JIDs} ->
- TAffiliation = get_affiliation(JID, StateData),
- TRole = get_role(JID, StateData),
- case xml:get_attr("role", Attrs) of
- false ->
- case xml:get_attr("affiliation", Attrs) of
- false ->
- {error, ?ERR_BAD_REQUEST};
- {value, StrAffiliation} ->
- case catch list_to_affiliation(StrAffiliation) of
- {'EXIT', _} ->
- ErrText1 =
- io_lib:format(
- translate:translate(
- Lang,
- "Invalid affiliation: ~s"),
- [StrAffiliation]),
- {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText1)};
- SAffiliation ->
- ServiceAf = get_service_affiliation(JID, StateData),
- CanChangeRA =
- case can_change_ra(
- UAffiliation, URole,
- TAffiliation, TRole,
- affiliation, SAffiliation,
- ServiceAf) of
- nothing ->
- nothing;
- true ->
- true;
- check_owner ->
- case search_affiliation(
- owner, StateData) of
- [{OJID, _}] ->
- jlib:jid_remove_resource(OJID) /=
- jlib:jid_tolower(jlib:jid_remove_resource(UJID));
- _ ->
- true
- end;
- _ ->
- false
- end,
- case CanChangeRA of
- nothing ->
- find_changed_items(
- UJID,
- UAffiliation, URole,
- Items, Lang, StateData,
- Res);
- true ->
- Reason = xml:get_path_s(Item, [{elem, "reason"}, cdata]),
- MoreRes = [{jlib:jid_remove_resource(Jidx), affiliation, SAffiliation, Reason} || Jidx <- JIDs],
- find_changed_items(
- UJID,
- UAffiliation, URole,
- Items, Lang, StateData,
- [MoreRes | Res]);
- false ->
- {error, ?ERR_NOT_ALLOWED}
- end
- end
- end;
- {value, StrRole} ->
- case catch list_to_role(StrRole) of
+ {value, [JID | _] = JIDs} ->
+ TAffiliation = get_affiliation(JID, StateData),
+ TRole = get_role(JID, StateData),
+ case xml:get_attr(<<"role">>, Attrs) of
+ false ->
+ case xml:get_attr(<<"affiliation">>, Attrs) of
+ false -> {error, ?ERR_BAD_REQUEST};
+ {value, StrAffiliation} ->
+ case catch list_to_affiliation(StrAffiliation) of
{'EXIT', _} ->
- ErrText1 =
- io_lib:format(
- translate:translate(
- Lang,
- "Invalid role: ~s"),
- [StrRole]),
- {error, ?ERRT_BAD_REQUEST(Lang, ErrText1)};
- SRole ->
+ ErrText1 = iolist_to_binary(
+ io_lib:format(
+ translate:translate(
+ Lang,
+ <<"Invalid affiliation: ~s">>),
+ [StrAffiliation])),
+ {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText1)};
+ SAffiliation ->
ServiceAf = get_service_affiliation(JID, StateData),
- CanChangeRA =
- case can_change_ra(
- UAffiliation, URole,
- TAffiliation, TRole,
- role, SRole,
- ServiceAf) of
- nothing ->
- nothing;
- true ->
- true;
- check_owner ->
- case search_affiliation(
- owner, StateData) of
- [{OJID, _}] ->
- jlib:jid_remove_resource(OJID) /=
- jlib:jid_tolower(jlib:jid_remove_resource(UJID));
- _ ->
- true
- end;
- _ ->
- false
- end,
+ CanChangeRA = case can_change_ra(UAffiliation,
+ URole,
+ TAffiliation,
+ TRole, affiliation,
+ SAffiliation,
+ ServiceAf)
+ of
+ nothing -> nothing;
+ true -> true;
+ check_owner ->
+ case search_affiliation(owner,
+ StateData)
+ of
+ [{OJID, _}] ->
+ jlib:jid_remove_resource(OJID)
+ /=
+ jlib:jid_tolower(jlib:jid_remove_resource(UJID));
+ _ -> true
+ end;
+ _ -> false
+ end,
case CanChangeRA of
- nothing ->
- find_changed_items(
- UJID,
- UAffiliation, URole,
- Items, Lang, StateData,
- Res);
- true ->
- Reason = xml:get_path_s(Item, [{elem, "reason"}, cdata]),
- MoreRes = [{Jidx, role, SRole, Reason} || Jidx <- JIDs],
- find_changed_items(
- UJID,
- UAffiliation, URole,
- Items, Lang, StateData,
- [MoreRes | Res]);
- _ ->
- {error, ?ERR_NOT_ALLOWED}
+ nothing ->
+ find_changed_items(UJID, UAffiliation, URole,
+ Items, Lang, StateData,
+ Res);
+ true ->
+ Reason = xml:get_path_s(Item,
+ [{elem, <<"reason">>},
+ cdata]),
+ MoreRes = [{jlib:jid_remove_resource(Jidx),
+ affiliation, SAffiliation, Reason}
+ || Jidx <- JIDs],
+ find_changed_items(UJID, UAffiliation, URole,
+ Items, Lang, StateData,
+ [MoreRes | Res]);
+ false -> {error, ?ERR_NOT_ALLOWED}
end
- end
- end;
- Err ->
- Err
+ end
+ end;
+ {value, StrRole} ->
+ case catch list_to_role(StrRole) of
+ {'EXIT', _} ->
+ ErrText1 = iolist_to_binary(
+ io_lib:format(translate:translate(
+ Lang,
+ <<"Invalid role: ~s">>),
+ [StrRole])),
+ {error, ?ERRT_BAD_REQUEST(Lang, ErrText1)};
+ SRole ->
+ ServiceAf = get_service_affiliation(JID, StateData),
+ CanChangeRA = case can_change_ra(UAffiliation, URole,
+ TAffiliation, TRole,
+ role, SRole, ServiceAf)
+ of
+ nothing -> nothing;
+ true -> true;
+ check_owner ->
+ case search_affiliation(owner,
+ StateData)
+ of
+ [{OJID, _}] ->
+ jlib:jid_remove_resource(OJID)
+ /=
+ jlib:jid_tolower(jlib:jid_remove_resource(UJID));
+ _ -> true
+ end;
+ _ -> false
+ end,
+ case CanChangeRA of
+ nothing ->
+ find_changed_items(UJID, UAffiliation, URole, Items,
+ Lang, StateData, Res);
+ true ->
+ Reason = xml:get_path_s(Item,
+ [{elem, <<"reason">>},
+ cdata]),
+ MoreRes = [{Jidx, role, SRole, Reason}
+ || Jidx <- JIDs],
+ find_changed_items(UJID, UAffiliation, URole, Items,
+ Lang, StateData,
+ [MoreRes | Res]);
+ _ -> {error, ?ERR_NOT_ALLOWED}
+ end
+ end
+ end;
+ Err -> Err
end;
find_changed_items(_UJID, _UAffiliation, _URole, _Items,
_Lang, _StateData, _Res) ->
{error, ?ERR_BAD_REQUEST}.
-
-can_change_ra(_FAffiliation, _FRole,
- owner, _TRole,
+can_change_ra(_FAffiliation, _FRole, owner, _TRole,
affiliation, owner, owner) ->
%% A room owner tries to add as persistent owner a
%% participant that is already owner because he is MUC admin
true;
-can_change_ra(_FAffiliation, _FRole,
- _TAffiliation, _TRole,
- _RoleorAffiliation, _Value, owner) ->
+can_change_ra(_FAffiliation, _FRole, _TAffiliation,
+ _TRole, _RoleorAffiliation, _Value, owner) ->
%% Nobody can decrease MUC admin's role/affiliation
false;
-can_change_ra(_FAffiliation, _FRole,
- TAffiliation, _TRole,
- affiliation, Value, _ServiceAf)
- when (TAffiliation == Value) ->
+can_change_ra(_FAffiliation, _FRole, TAffiliation,
+ _TRole, affiliation, Value, _ServiceAf)
+ when TAffiliation == Value ->
nothing;
-can_change_ra(_FAffiliation, _FRole,
- _TAffiliation, TRole,
- role, Value, _ServiceAf)
- when (TRole == Value) ->
+can_change_ra(_FAffiliation, _FRole, _TAffiliation,
+ TRole, role, Value, _ServiceAf)
+ when TRole == Value ->
nothing;
-can_change_ra(FAffiliation, _FRole,
- outcast, _TRole,
+can_change_ra(FAffiliation, _FRole, outcast, _TRole,
affiliation, none, _ServiceAf)
- when (FAffiliation == owner) or (FAffiliation == admin) ->
+ when (FAffiliation == owner) or
+ (FAffiliation == admin) ->
true;
-can_change_ra(FAffiliation, _FRole,
- outcast, _TRole,
+can_change_ra(FAffiliation, _FRole, outcast, _TRole,
affiliation, member, _ServiceAf)
- when (FAffiliation == owner) or (FAffiliation == admin) ->
+ when (FAffiliation == owner) or
+ (FAffiliation == admin) ->
true;
-can_change_ra(owner, _FRole,
- outcast, _TRole,
+can_change_ra(owner, _FRole, outcast, _TRole,
affiliation, admin, _ServiceAf) ->
true;
-can_change_ra(owner, _FRole,
- outcast, _TRole,
+can_change_ra(owner, _FRole, outcast, _TRole,
affiliation, owner, _ServiceAf) ->
true;
-can_change_ra(FAffiliation, _FRole,
- none, _TRole,
+can_change_ra(FAffiliation, _FRole, none, _TRole,
affiliation, outcast, _ServiceAf)
- when (FAffiliation == owner) or (FAffiliation == admin) ->
+ when (FAffiliation == owner) or
+ (FAffiliation == admin) ->
true;
-can_change_ra(FAffiliation, _FRole,
- none, _TRole,
+can_change_ra(FAffiliation, _FRole, none, _TRole,
affiliation, member, _ServiceAf)
- when (FAffiliation == owner) or (FAffiliation == admin) ->
+ when (FAffiliation == owner) or
+ (FAffiliation == admin) ->
true;
-can_change_ra(owner, _FRole,
- none, _TRole,
- affiliation, admin, _ServiceAf) ->
+can_change_ra(owner, _FRole, none, _TRole, affiliation,
+ admin, _ServiceAf) ->
true;
-can_change_ra(owner, _FRole,
- none, _TRole,
- affiliation, owner, _ServiceAf) ->
+can_change_ra(owner, _FRole, none, _TRole, affiliation,
+ owner, _ServiceAf) ->
true;
-can_change_ra(FAffiliation, _FRole,
- member, _TRole,
+can_change_ra(FAffiliation, _FRole, member, _TRole,
affiliation, outcast, _ServiceAf)
- when (FAffiliation == owner) or (FAffiliation == admin) ->
+ when (FAffiliation == owner) or
+ (FAffiliation == admin) ->
true;
-can_change_ra(FAffiliation, _FRole,
- member, _TRole,
+can_change_ra(FAffiliation, _FRole, member, _TRole,
affiliation, none, _ServiceAf)
- when (FAffiliation == owner) or (FAffiliation == admin) ->
+ when (FAffiliation == owner) or
+ (FAffiliation == admin) ->
true;
-can_change_ra(owner, _FRole,
- member, _TRole,
+can_change_ra(owner, _FRole, member, _TRole,
affiliation, admin, _ServiceAf) ->
true;
-can_change_ra(owner, _FRole,
- member, _TRole,
+can_change_ra(owner, _FRole, member, _TRole,
affiliation, owner, _ServiceAf) ->
true;
-can_change_ra(owner, _FRole,
- admin, _TRole,
- affiliation, _Affiliation, _ServiceAf) ->
+can_change_ra(owner, _FRole, admin, _TRole, affiliation,
+ _Affiliation, _ServiceAf) ->
true;
-can_change_ra(owner, _FRole,
- owner, _TRole,
- affiliation, _Affiliation, _ServiceAf) ->
+can_change_ra(owner, _FRole, owner, _TRole, affiliation,
+ _Affiliation, _ServiceAf) ->
check_owner;
-can_change_ra(_FAffiliation, _FRole,
- _TAffiliation, _TRole,
- affiliation, _Value, _ServiceAf) ->
+can_change_ra(_FAffiliation, _FRole, _TAffiliation,
+ _TRole, affiliation, _Value, _ServiceAf) ->
false;
-can_change_ra(_FAffiliation, moderator,
- _TAffiliation, visitor,
- role, none, _ServiceAf) ->
+can_change_ra(_FAffiliation, moderator, _TAffiliation,
+ visitor, role, none, _ServiceAf) ->
true;
-can_change_ra(_FAffiliation, moderator,
- _TAffiliation, visitor,
- role, participant, _ServiceAf) ->
+can_change_ra(_FAffiliation, moderator, _TAffiliation,
+ visitor, role, participant, _ServiceAf) ->
true;
-can_change_ra(FAffiliation, _FRole,
- _TAffiliation, visitor,
- role, moderator, _ServiceAf)
- when (FAffiliation == owner) or (FAffiliation == admin) ->
+can_change_ra(FAffiliation, _FRole, _TAffiliation,
+ visitor, role, moderator, _ServiceAf)
+ when (FAffiliation == owner) or
+ (FAffiliation == admin) ->
true;
-can_change_ra(_FAffiliation, moderator,
- _TAffiliation, participant,
- role, none, _ServiceAf) ->
+can_change_ra(_FAffiliation, moderator, _TAffiliation,
+ participant, role, none, _ServiceAf) ->
true;
-can_change_ra(_FAffiliation, moderator,
- _TAffiliation, participant,
- role, visitor, _ServiceAf) ->
+can_change_ra(_FAffiliation, moderator, _TAffiliation,
+ participant, role, visitor, _ServiceAf) ->
true;
-can_change_ra(FAffiliation, _FRole,
- _TAffiliation, participant,
- role, moderator, _ServiceAf)
- when (FAffiliation == owner) or (FAffiliation == admin) ->
+can_change_ra(FAffiliation, _FRole, _TAffiliation,
+ participant, role, moderator, _ServiceAf)
+ when (FAffiliation == owner) or
+ (FAffiliation == admin) ->
true;
-can_change_ra(_FAffiliation, _FRole,
- owner, moderator,
+can_change_ra(_FAffiliation, _FRole, owner, moderator,
role, visitor, _ServiceAf) ->
false;
-can_change_ra(owner, _FRole,
- _TAffiliation, moderator,
+can_change_ra(owner, _FRole, _TAffiliation, moderator,
role, visitor, _ServiceAf) ->
true;
-can_change_ra(_FAffiliation, _FRole,
- admin, moderator,
+can_change_ra(_FAffiliation, _FRole, admin, moderator,
role, visitor, _ServiceAf) ->
false;
-can_change_ra(admin, _FRole,
- _TAffiliation, moderator,
+can_change_ra(admin, _FRole, _TAffiliation, moderator,
role, visitor, _ServiceAf) ->
true;
-can_change_ra(_FAffiliation, _FRole,
- owner, moderator,
+can_change_ra(_FAffiliation, _FRole, owner, moderator,
role, participant, _ServiceAf) ->
false;
-can_change_ra(owner, _FRole,
- _TAffiliation, moderator,
+can_change_ra(owner, _FRole, _TAffiliation, moderator,
role, participant, _ServiceAf) ->
true;
-can_change_ra(_FAffiliation, _FRole,
- admin, moderator,
+can_change_ra(_FAffiliation, _FRole, admin, moderator,
role, participant, _ServiceAf) ->
false;
-can_change_ra(admin, _FRole,
- _TAffiliation, moderator,
+can_change_ra(admin, _FRole, _TAffiliation, moderator,
role, participant, _ServiceAf) ->
true;
-can_change_ra(_FAffiliation, _FRole,
- _TAffiliation, _TRole,
- role, _Value, _ServiceAf) ->
+can_change_ra(_FAffiliation, _FRole, _TAffiliation,
+ _TRole, role, _Value, _ServiceAf) ->
false.
-
send_kickban_presence(JID, Reason, Code, StateData) ->
NewAffiliation = get_affiliation(JID, StateData),
- send_kickban_presence(JID, Reason, Code, NewAffiliation, StateData).
+ send_kickban_presence(JID, Reason, Code, NewAffiliation,
+ StateData).
-send_kickban_presence(JID, Reason, Code, NewAffiliation, StateData) ->
+send_kickban_presence(JID, Reason, Code, NewAffiliation,
+ StateData) ->
LJID = jlib:jid_tolower(JID),
LJIDs = case LJID of
- {U, S, ""} ->
- ?DICT:fold(
- fun(J, _, Js) ->
- case J of
- {U, S, _} ->
- [J | Js];
- _ ->
- Js
- end
- end, [], StateData#state.users);
- _ ->
- case ?DICT:is_key(LJID, StateData#state.users) of
- true ->
- [LJID];
- _ ->
- []
- end
+ {U, S, <<"">>} ->
+ (?DICT):fold(fun (J, _, Js) ->
+ case J of
+ {U, S, _} -> [J | Js];
+ _ -> Js
+ end
+ end,
+ [], StateData#state.users);
+ _ ->
+ case (?DICT):is_key(LJID, StateData#state.users) of
+ true -> [LJID];
+ _ -> []
+ end
end,
- lists:foreach(fun(J) ->
- {ok, #user{nick = Nick}} =
- ?DICT:find(J, StateData#state.users),
+ lists:foreach(fun (J) ->
+ {ok, #user{nick = Nick}} = (?DICT):find(J,
+ StateData#state.users),
add_to_log(kickban, {Nick, Reason, Code}, StateData),
tab_remove_online_user(J, StateData),
- send_kickban_presence1(J, Reason, Code, NewAffiliation, StateData)
- end, LJIDs).
+ send_kickban_presence1(J, Reason, Code,
+ NewAffiliation, StateData)
+ end,
+ LJIDs).
-send_kickban_presence1(UJID, Reason, Code, Affiliation, StateData) ->
- {ok, #user{jid = RealJID,
- nick = Nick}} =
- ?DICT:find(jlib:jid_tolower(UJID), StateData#state.users),
+send_kickban_presence1(UJID, Reason, Code, Affiliation,
+ StateData) ->
+ {ok, #user{jid = RealJID, nick = Nick}} =
+ (?DICT):find(jlib:jid_tolower(UJID),
+ StateData#state.users),
SAffiliation = affiliation_to_list(Affiliation),
BannedJIDString = jlib:jid_to_string(RealJID),
- lists:foreach(
- fun({_LJID, Info}) ->
- JidAttrList = case (Info#user.role == moderator) orelse
- ((StateData#state.config)#config.anonymous
- == false) of
- true -> [{"jid", BannedJIDString}];
- false -> []
- end,
- ItemAttrs = [{"affiliation", SAffiliation},
- {"role", "none"}] ++ JidAttrList,
- ItemEls = case Reason of
- "" ->
- [];
- _ ->
- [{xmlelement, "reason", [],
- [{xmlcdata, Reason}]}]
- end,
- Packet = {xmlelement, "presence", [{"type", "unavailable"}],
- [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}],
- [{xmlelement, "item", ItemAttrs, ItemEls},
- {xmlelement, "status", [{"code", Code}], []}]}]},
- ejabberd_router:route(
- jlib:jid_replace_resource(StateData#state.jid, Nick),
- Info#user.jid,
- Packet)
- end, ?DICT:to_list(StateData#state.users)).
-
-
+ lists:foreach(fun ({_LJID, Info}) ->
+ JidAttrList = case Info#user.role == moderator orelse
+ (StateData#state.config)#config.anonymous
+ == false
+ of
+ true ->
+ [{<<"jid">>, BannedJIDString}];
+ false -> []
+ end,
+ ItemAttrs = [{<<"affiliation">>, SAffiliation},
+ {<<"role">>, <<"none">>}]
+ ++ JidAttrList,
+ ItemEls = case Reason of
+ <<"">> -> [];
+ _ ->
+ [#xmlel{name = <<"reason">>,
+ attrs = [],
+ children =
+ [{xmlcdata, Reason}]}]
+ end,
+ Packet = #xmlel{name = <<"presence">>,
+ attrs =
+ [{<<"type">>, <<"unavailable">>}],
+ children =
+ [#xmlel{name = <<"x">>,
+ attrs =
+ [{<<"xmlns">>,
+ ?NS_MUC_USER}],
+ children =
+ [#xmlel{name =
+ <<"item">>,
+ attrs =
+ ItemAttrs,
+ children =
+ ItemEls},
+ #xmlel{name =
+ <<"status">>,
+ attrs =
+ [{<<"code">>,
+ Code}],
+ children =
+ []}]}]},
+ ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid,
+ Nick),
+ Info#user.jid, Packet)
+ end,
+ (?DICT):to_list(StateData#state.users)).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Owner stuff
@@ -2986,122 +3073,131 @@ send_kickban_presence1(UJID, Reason, Code, Affiliation, StateData) ->
process_iq_owner(From, set, Lang, SubEl, StateData) ->
FAffiliation = get_affiliation(From, StateData),
case FAffiliation of
- owner ->
- {xmlelement, _Name, _Attrs, Els} = SubEl,
- case xml:remove_cdata(Els) of
- [{xmlelement, "x", _Attrs1, _Els1} = XEl] ->
- case {xml:get_tag_attr_s("xmlns", XEl),
- xml:get_tag_attr_s("type", XEl)} of
- {?NS_XDATA, "cancel"} ->
- {result, [], StateData};
- {?NS_XDATA, "submit"} ->
- case is_allowed_log_change(XEl, StateData, From)
- andalso
- is_allowed_persistent_change(XEl, StateData,
- From)
- andalso
- is_allowed_room_name_desc_limits(XEl,
- StateData)
- andalso
- is_password_settings_correct(XEl, StateData) of
- true -> set_config(XEl, StateData);
- false -> {error, ?ERR_NOT_ACCEPTABLE}
- end;
- _ ->
- {error, ?ERR_BAD_REQUEST}
- end;
- [{xmlelement, "destroy", _Attrs1, _Els1} = SubEl1] ->
- ?INFO_MSG("Destroyed MUC room ~s by the owner ~s",
- [jlib:jid_to_string(StateData#state.jid), jlib:jid_to_string(From)]),
- add_to_log(room_existence, destroyed, StateData),
- destroy_room(SubEl1, StateData);
- Items ->
- process_admin_items_set(From, Items, Lang, StateData)
- end;
- _ ->
- ErrText = "Owner privileges required",
- {error, ?ERRT_FORBIDDEN(Lang, ErrText)}
+ owner ->
+ #xmlel{children = Els} = SubEl,
+ case xml:remove_cdata(Els) of
+ [#xmlel{name = <<"x">>} = XEl] ->
+ case {xml:get_tag_attr_s(<<"xmlns">>, XEl),
+ xml:get_tag_attr_s(<<"type">>, XEl)}
+ of
+ {?NS_XDATA, <<"cancel">>} -> {result, [], StateData};
+ {?NS_XDATA, <<"submit">>} ->
+ case is_allowed_log_change(XEl, StateData, From) andalso
+ is_allowed_persistent_change(XEl, StateData, From)
+ andalso
+ is_allowed_room_name_desc_limits(XEl, StateData)
+ andalso
+ is_password_settings_correct(XEl, StateData)
+ of
+ true -> set_config(XEl, StateData);
+ false -> {error, ?ERR_NOT_ACCEPTABLE}
+ end;
+ _ -> {error, ?ERR_BAD_REQUEST}
+ end;
+ [#xmlel{name = <<"destroy">>} = SubEl1] ->
+ ?INFO_MSG("Destroyed MUC room ~s by the owner ~s",
+ [jlib:jid_to_string(StateData#state.jid),
+ jlib:jid_to_string(From)]),
+ add_to_log(room_existence, destroyed, StateData),
+ destroy_room(SubEl1, StateData);
+ Items ->
+ process_admin_items_set(From, Items, Lang, StateData)
+ end;
+ _ ->
+ ErrText = <<"Owner privileges required">>,
+ {error, ?ERRT_FORBIDDEN(Lang, ErrText)}
end;
-
process_iq_owner(From, get, Lang, SubEl, StateData) ->
FAffiliation = get_affiliation(From, StateData),
case FAffiliation of
- owner ->
- {xmlelement, _Name, _Attrs, Els} = SubEl,
- case xml:remove_cdata(Els) of
- [] ->
- get_config(Lang, StateData, From);
- [Item] ->
- case xml:get_tag_attr("affiliation", Item) of
- false ->
- {error, ?ERR_BAD_REQUEST};
- {value, StrAffiliation} ->
- case catch list_to_affiliation(StrAffiliation) of
- {'EXIT', _} ->
- ErrText =
- io_lib:format(
- translate:translate(
- Lang,
- "Invalid affiliation: ~s"),
- [StrAffiliation]),
- {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)};
- SAffiliation ->
- Items = items_with_affiliation(
- SAffiliation, StateData),
- {result, Items, StateData}
- end
- end;
- _ ->
- {error, ?ERR_FEATURE_NOT_IMPLEMENTED}
- end;
- _ ->
- ErrText = "Owner privileges required",
- {error, ?ERRT_FORBIDDEN(Lang, ErrText)}
+ owner ->
+ #xmlel{children = Els} = SubEl,
+ case xml:remove_cdata(Els) of
+ [] -> get_config(Lang, StateData, From);
+ [Item] ->
+ case xml:get_tag_attr(<<"affiliation">>, Item) of
+ false -> {error, ?ERR_BAD_REQUEST};
+ {value, StrAffiliation} ->
+ case catch list_to_affiliation(StrAffiliation) of
+ {'EXIT', _} ->
+ ErrText = iolist_to_binary(
+ io_lib:format(
+ translate:translate(
+ Lang,
+ <<"Invalid affiliation: ~s">>),
+ [StrAffiliation])),
+ {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)};
+ SAffiliation ->
+ Items = items_with_affiliation(SAffiliation,
+ StateData),
+ {result, Items, StateData}
+ end
+ end;
+ _ -> {error, ?ERR_FEATURE_NOT_IMPLEMENTED}
+ end;
+ _ ->
+ ErrText = <<"Owner privileges required">>,
+ {error, ?ERRT_FORBIDDEN(Lang, ErrText)}
end.
is_allowed_log_change(XEl, StateData, From) ->
- case lists:keymember("muc#roomconfig_enablelogging", 1,
- jlib:parse_xdata_submit(XEl)) of
- false ->
- true;
- true ->
- (allow == mod_muc_log:check_access_log(
- StateData#state.server_host, From))
+ case lists:keymember(<<"muc#roomconfig_enablelogging">>,
+ 1, jlib:parse_xdata_submit(XEl))
+ of
+ false -> true;
+ true ->
+ allow ==
+ mod_muc_log:check_access_log(StateData#state.server_host,
+ From)
end.
is_allowed_persistent_change(XEl, StateData, From) ->
- case lists:keymember("muc#roomconfig_persistentroom", 1,
- jlib:parse_xdata_submit(XEl)) of
- false ->
- true;
- true ->
- {_AccessRoute, _AccessCreate, _AccessAdmin, AccessPersistent} = StateData#state.access,
- (allow == acl:match_rule(StateData#state.server_host, AccessPersistent, From))
+ case
+ lists:keymember(<<"muc#roomconfig_persistentroom">>, 1,
+ jlib:parse_xdata_submit(XEl))
+ of
+ false -> true;
+ true ->
+ {_AccessRoute, _AccessCreate, _AccessAdmin,
+ AccessPersistent} =
+ StateData#state.access,
+ allow ==
+ acl:match_rule(StateData#state.server_host,
+ AccessPersistent, From)
end.
%% Check if the Room Name and Room Description defined in the Data Form
%% are conformant to the configured limits
is_allowed_room_name_desc_limits(XEl, StateData) ->
- IsNameAccepted =
- case lists:keysearch("muc#roomconfig_roomname", 1,
- jlib:parse_xdata_submit(XEl)) of
- {value, {_, [N]}} ->
- length(N) =< gen_mod:get_module_opt(StateData#state.server_host,
+ IsNameAccepted = case
+ lists:keysearch(<<"muc#roomconfig_roomname">>, 1,
+ jlib:parse_xdata_submit(XEl))
+ of
+ {value, {_, [N]}} ->
+ byte_size(N) =<
+ gen_mod:get_module_opt(StateData#state.server_host,
mod_muc, max_room_name,
- infinite);
- _ ->
- true
- end,
- IsDescAccepted =
- case lists:keysearch("muc#roomconfig_roomdesc", 1,
- jlib:parse_xdata_submit(XEl)) of
- {value, {_, [D]}} ->
- length(D) =< gen_mod:get_module_opt(StateData#state.server_host,
+ fun(infinity) -> infinity;
+ (I) when is_integer(I),
+ I>0 -> I
+ end, infinity);
+ _ -> true
+ end,
+ IsDescAccepted = case
+ lists:keysearch(<<"muc#roomconfig_roomdesc">>, 1,
+ jlib:parse_xdata_submit(XEl))
+ of
+ {value, {_, [D]}} ->
+ byte_size(D) =<
+ gen_mod:get_module_opt(StateData#state.server_host,
mod_muc, max_room_desc,
- infinite);
- _ ->
- true
- end,
+ fun(infinity) -> infinity;
+ (I) when is_integer(I),
+ I>0 ->
+ I
+ end, infinity);
+ _ -> true
+ end,
IsNameAccepted and IsDescAccepted.
%% Return false if:
@@ -3110,477 +3206,684 @@ is_password_settings_correct(XEl, StateData) ->
Config = StateData#state.config,
OldProtected = Config#config.password_protected,
OldPassword = Config#config.password,
- NewProtected =
- case lists:keysearch("muc#roomconfig_passwordprotectedroom", 1,
- jlib:parse_xdata_submit(XEl)) of
- {value, {_, ["1"]}} ->
- true;
- {value, {_, ["0"]}} ->
- false;
- _ ->
- undefined
- end,
- NewPassword =
- case lists:keysearch("muc#roomconfig_roomsecret", 1,
- jlib:parse_xdata_submit(XEl)) of
- {value, {_, [P]}} ->
- P;
- _ ->
- undefined
- end,
- case {OldProtected, NewProtected, OldPassword, NewPassword} of
- {true, undefined, "", undefined} ->
- false;
- {true, undefined, _, ""} ->
- false;
- {_, true , "", undefined} ->
- false;
- {_, true, _, ""} ->
- false;
- _ ->
- true
+ NewProtected = case
+ lists:keysearch(<<"muc#roomconfig_passwordprotectedroom">>,
+ 1, jlib:parse_xdata_submit(XEl))
+ of
+ {value, {_, [<<"1">>]}} -> true;
+ {value, {_, [<<"0">>]}} -> false;
+ _ -> undefined
+ end,
+ NewPassword = case
+ lists:keysearch(<<"muc#roomconfig_roomsecret">>, 1,
+ jlib:parse_xdata_submit(XEl))
+ of
+ {value, {_, [P]}} -> P;
+ _ -> undefined
+ end,
+ case {OldProtected, NewProtected, OldPassword,
+ NewPassword}
+ of
+ {true, undefined, <<"">>, undefined} -> false;
+ {true, undefined, _, <<"">>} -> false;
+ {_, true, <<"">>, undefined} -> false;
+ {_, true, _, <<"">>} -> false;
+ _ -> true
end.
-
-define(XFIELD(Type, Label, Var, Val),
- {xmlelement, "field", [{"type", Type},
- {"label", translate:translate(Lang, Label)},
- {"var", Var}],
- [{xmlelement, "value", [], [{xmlcdata, Val}]}]}).
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"type">>, Type},
+ {<<"label">>, translate:translate(Lang, Label)},
+ {<<"var">>, Var}],
+ children =
+ [#xmlel{name = <<"value">>, attrs = [],
+ children = [{xmlcdata, Val}]}]}).
-define(BOOLXFIELD(Label, Var, Val),
- ?XFIELD("boolean", Label, Var,
+ ?XFIELD(<<"boolean">>, Label, Var,
case Val of
- true -> "1";
- _ -> "0"
+ true -> <<"1">>;
+ _ -> <<"0">>
end)).
-define(STRINGXFIELD(Label, Var, Val),
- ?XFIELD("text-single", Label, Var, Val)).
+ ?XFIELD(<<"text-single">>, Label, Var, Val)).
-define(PRIVATEXFIELD(Label, Var, Val),
- ?XFIELD("text-private", Label, Var, Val)).
+ ?XFIELD(<<"text-private">>, Label, Var, Val)).
-define(JIDMULTIXFIELD(Label, Var, JIDList),
- {xmlelement, "field", [{"type", "jid-multi"},
- {"label", translate:translate(Lang, Label)},
- {"var", Var}],
- [{xmlelement, "value", [], [{xmlcdata, jlib:jid_to_string(JID)}]}
- || JID <- JIDList]}).
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"type">>, <<"jid-multi">>},
+ {<<"label">>, translate:translate(Lang, Label)},
+ {<<"var">>, Var}],
+ children =
+ [#xmlel{name = <<"value">>, attrs = [],
+ children = [{xmlcdata, jlib:jid_to_string(JID)}]}
+ || JID <- JIDList]}).
get_default_room_maxusers(RoomState) ->
- DefRoomOpts = gen_mod:get_module_opt(RoomState#state.server_host, mod_muc, default_room_options, []),
+ DefRoomOpts =
+ gen_mod:get_module_opt(RoomState#state.server_host,
+ mod_muc, default_room_options,
+ fun(L) when is_list(L) -> L end,
+ []),
RoomState2 = set_opts(DefRoomOpts, RoomState),
(RoomState2#state.config)#config.max_users.
get_config(Lang, StateData, From) ->
- {_AccessRoute, _AccessCreate, _AccessAdmin, AccessPersistent} = StateData#state.access,
+ {_AccessRoute, _AccessCreate, _AccessAdmin,
+ AccessPersistent} =
+ StateData#state.access,
ServiceMaxUsers = get_service_max_users(StateData),
- DefaultRoomMaxUsers = get_default_room_maxusers(StateData),
+ DefaultRoomMaxUsers =
+ get_default_room_maxusers(StateData),
Config = StateData#state.config,
- {MaxUsersRoomInteger, MaxUsersRoomString} =
- case get_max_users(StateData) of
- N when is_integer(N) ->
- {N, erlang:integer_to_list(N)};
- _ -> {0, "none"}
- end,
- Res =
- [{xmlelement, "title", [],
- [{xmlcdata, io_lib:format(translate:translate(Lang, "Configuration of room ~s"), [jlib:jid_to_string(StateData#state.jid)])}]},
- {xmlelement, "field", [{"type", "hidden"},
- {"var", "FORM_TYPE"}],
- [{xmlelement, "value", [],
- [{xmlcdata, "http://jabber.org/protocol/muc#roomconfig"}]}]},
- ?STRINGXFIELD("Room title",
- "muc#roomconfig_roomname",
- Config#config.title),
- ?STRINGXFIELD("Room description",
- "muc#roomconfig_roomdesc",
- Config#config.description)
- ] ++
- case acl:match_rule(StateData#state.server_host, AccessPersistent, From) of
- allow ->
- [?BOOLXFIELD(
- "Make room persistent",
- "muc#roomconfig_persistentroom",
- Config#config.persistent)];
- _ -> []
- end ++ [
- ?BOOLXFIELD("Make room public searchable",
- "muc#roomconfig_publicroom",
- Config#config.public),
- ?BOOLXFIELD("Make participants list public",
- "public_list",
- Config#config.public_list),
- ?BOOLXFIELD("Make room password protected",
- "muc#roomconfig_passwordprotectedroom",
- Config#config.password_protected),
- ?PRIVATEXFIELD("Password",
- "muc#roomconfig_roomsecret",
- case Config#config.password_protected of
- true -> Config#config.password;
- false -> ""
- end),
- {xmlelement, "field",
- [{"type", "list-single"},
- {"label", translate:translate(Lang, "Maximum Number of Occupants")},
- {"var", "muc#roomconfig_maxusers"}],
- [{xmlelement, "value", [], [{xmlcdata, MaxUsersRoomString}]}] ++
- if
- is_integer(ServiceMaxUsers) -> [];
- true ->
- [{xmlelement, "option",
- [{"label", translate:translate(Lang, "No limit")}],
- [{xmlelement, "value", [], [{xmlcdata, "none"}]}]}]
- end ++
- [{xmlelement, "option", [{"label", erlang:integer_to_list(N)}],
- [{xmlelement, "value", [],
- [{xmlcdata, erlang:integer_to_list(N)}]}]} ||
- N <- lists:usort([ServiceMaxUsers, DefaultRoomMaxUsers, MaxUsersRoomInteger |
- ?MAX_USERS_DEFAULT_LIST]), N =< ServiceMaxUsers]
- },
- {xmlelement, "field",
- [{"type", "list-single"},
- {"label", translate:translate(Lang, "Present real Jabber IDs to")},
- {"var", "muc#roomconfig_whois"}],
- [{xmlelement, "value", [], [{xmlcdata,
- if Config#config.anonymous ->
- "moderators";
- true ->
- "anyone"
- end}]},
- {xmlelement, "option", [{"label", translate:translate(Lang, "moderators only")}],
- [{xmlelement, "value", [], [{xmlcdata, "moderators"}]}]},
- {xmlelement, "option", [{"label", translate:translate(Lang, "anyone")}],
- [{xmlelement, "value", [], [{xmlcdata, "anyone"}]}]}]},
- ?BOOLXFIELD("Make room members-only",
- "muc#roomconfig_membersonly",
- Config#config.members_only),
- ?BOOLXFIELD("Make room moderated",
- "muc#roomconfig_moderatedroom",
- Config#config.moderated),
- ?BOOLXFIELD("Default users as participants",
- "members_by_default",
- Config#config.members_by_default),
- ?BOOLXFIELD("Allow users to change the subject",
- "muc#roomconfig_changesubject",
- Config#config.allow_change_subj),
- ?BOOLXFIELD("Allow users to send private messages",
- "allow_private_messages",
- Config#config.allow_private_messages),
- {xmlelement, "field",
- [{"type", "list-single"},
- {"label", translate:translate(Lang, "Allow visitors to send private messages to")},
- {"var", "allow_private_messages_from_visitors"}],
- [{xmlelement, "value", [], [{xmlcdata,
- case Config#config.allow_private_messages_from_visitors of
- anyone ->
- "anyone";
- moderators ->
- "moderators";
- nobody ->
- "nobody"
- end}]},
- {xmlelement, "option", [{"label", translate:translate(Lang, "nobody")}],
- [{xmlelement, "value", [], [{xmlcdata, "nobody"}]}]},
- {xmlelement, "option", [{"label", translate:translate(Lang, "moderators only")}],
- [{xmlelement, "value", [], [{xmlcdata, "moderators"}]}]},
- {xmlelement, "option", [{"label", translate:translate(Lang, "anyone")}],
- [{xmlelement, "value", [], [{xmlcdata, "anyone"}]}]}]},
- ?BOOLXFIELD("Allow users to query other users",
- "allow_query_users",
- Config#config.allow_query_users),
- ?BOOLXFIELD("Allow users to send invites",
- "muc#roomconfig_allowinvites",
- Config#config.allow_user_invites),
- ?BOOLXFIELD("Allow visitors to send status text in presence updates",
- "muc#roomconfig_allowvisitorstatus",
- Config#config.allow_visitor_status),
- ?BOOLXFIELD("Allow visitors to change nickname",
- "muc#roomconfig_allowvisitornickchange",
- Config#config.allow_visitor_nickchange),
- ?BOOLXFIELD("Allow visitors to send voice requests",
- "muc#roomconfig_allowvoicerequests",
- Config#config.allow_voice_requests),
- ?STRINGXFIELD("Minimum interval between voice requests (in seconds)",
- "muc#roomconfig_voicerequestmininterval",
- erlang:integer_to_list(Config#config.voice_request_min_interval))
- ] ++
- case ejabberd_captcha:is_feature_available() of
- true ->
- [?BOOLXFIELD("Make room CAPTCHA protected",
- "captcha_protected",
- Config#config.captcha_protected)];
- false -> []
- end ++
- [?JIDMULTIXFIELD("Exclude Jabber IDs from CAPTCHA challenge",
- "muc#roomconfig_captcha_whitelist",
- ?SETS:to_list(Config#config.captcha_whitelist))] ++
- case mod_muc_log:check_access_log(
- StateData#state.server_host, From) of
- allow ->
- [?BOOLXFIELD(
- "Enable logging",
- "muc#roomconfig_enablelogging",
- Config#config.logging)];
- _ -> []
- end,
- {result, [{xmlelement, "instructions", [],
- [{xmlcdata,
- translate:translate(
- Lang, "You need an x:data capable client to configure room")}]},
- {xmlelement, "x", [{"xmlns", ?NS_XDATA},
- {"type", "form"}],
- Res}],
+ {MaxUsersRoomInteger, MaxUsersRoomString} = case
+ get_max_users(StateData)
+ of
+ N when is_integer(N) ->
+ {N,
+ erlang:integer_to_list(N)};
+ _ -> {0, <<"none">>}
+ end,
+ Res = [#xmlel{name = <<"title">>, attrs = [],
+ children =
+ [{xmlcdata,
+ iolist_to_binary(
+ io_lib:format(
+ translate:translate(
+ Lang,
+ <<"Configuration of room ~s">>),
+ [jlib:jid_to_string(StateData#state.jid)]))}]},
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"type">>, <<"hidden">>},
+ {<<"var">>, <<"FORM_TYPE">>}],
+ children =
+ [#xmlel{name = <<"value">>, attrs = [],
+ children =
+ [{xmlcdata,
+ <<"http://jabber.org/protocol/muc#roomconfig">>}]}]},
+ ?STRINGXFIELD(<<"Room title">>,
+ <<"muc#roomconfig_roomname">>, (Config#config.title)),
+ ?STRINGXFIELD(<<"Room description">>,
+ <<"muc#roomconfig_roomdesc">>,
+ (Config#config.description))]
+ ++
+ case acl:match_rule(StateData#state.server_host,
+ AccessPersistent, From)
+ of
+ allow ->
+ [?BOOLXFIELD(<<"Make room persistent">>,
+ <<"muc#roomconfig_persistentroom">>,
+ (Config#config.persistent))];
+ _ -> []
+ end
+ ++
+ [?BOOLXFIELD(<<"Make room public searchable">>,
+ <<"muc#roomconfig_publicroom">>,
+ (Config#config.public)),
+ ?BOOLXFIELD(<<"Make participants list public">>,
+ <<"public_list">>, (Config#config.public_list)),
+ ?BOOLXFIELD(<<"Make room password protected">>,
+ <<"muc#roomconfig_passwordprotectedroom">>,
+ (Config#config.password_protected)),
+ ?PRIVATEXFIELD(<<"Password">>,
+ <<"muc#roomconfig_roomsecret">>,
+ case Config#config.password_protected of
+ true -> Config#config.password;
+ false -> <<"">>
+ end),
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"type">>, <<"list-single">>},
+ {<<"label">>,
+ translate:translate(Lang,
+ <<"Maximum Number of Occupants">>)},
+ {<<"var">>, <<"muc#roomconfig_maxusers">>}],
+ children =
+ [#xmlel{name = <<"value">>, attrs = [],
+ children = [{xmlcdata, MaxUsersRoomString}]}]
+ ++
+ if is_integer(ServiceMaxUsers) -> [];
+ true ->
+ [#xmlel{name = <<"option">>,
+ attrs =
+ [{<<"label">>,
+ translate:translate(Lang,
+ <<"No limit">>)}],
+ children =
+ [#xmlel{name = <<"value">>,
+ attrs = [],
+ children =
+ [{xmlcdata,
+ <<"none">>}]}]}]
+ end
+ ++
+ [#xmlel{name = <<"option">>,
+ attrs =
+ [{<<"label">>,
+ jlib:integer_to_binary(N)}],
+ children =
+ [#xmlel{name = <<"value">>,
+ attrs = [],
+ children =
+ [{xmlcdata,
+ jlib:integer_to_binary(N)}]}]}
+ || N
+ <- lists:usort([ServiceMaxUsers,
+ DefaultRoomMaxUsers,
+ MaxUsersRoomInteger
+ | ?MAX_USERS_DEFAULT_LIST]),
+ N =< ServiceMaxUsers]},
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"type">>, <<"list-single">>},
+ {<<"label">>,
+ translate:translate(Lang,
+ <<"Present real Jabber IDs to">>)},
+ {<<"var">>, <<"muc#roomconfig_whois">>}],
+ children =
+ [#xmlel{name = <<"value">>, attrs = [],
+ children =
+ [{xmlcdata,
+ if Config#config.anonymous ->
+ <<"moderators">>;
+ true -> <<"anyone">>
+ end}]},
+ #xmlel{name = <<"option">>,
+ attrs =
+ [{<<"label">>,
+ translate:translate(Lang,
+ <<"moderators only">>)}],
+ children =
+ [#xmlel{name = <<"value">>, attrs = [],
+ children =
+ [{xmlcdata,
+ <<"moderators">>}]}]},
+ #xmlel{name = <<"option">>,
+ attrs =
+ [{<<"label">>,
+ translate:translate(Lang,
+ <<"anyone">>)}],
+ children =
+ [#xmlel{name = <<"value">>, attrs = [],
+ children =
+ [{xmlcdata,
+ <<"anyone">>}]}]}]},
+ ?BOOLXFIELD(<<"Make room members-only">>,
+ <<"muc#roomconfig_membersonly">>,
+ (Config#config.members_only)),
+ ?BOOLXFIELD(<<"Make room moderated">>,
+ <<"muc#roomconfig_moderatedroom">>,
+ (Config#config.moderated)),
+ ?BOOLXFIELD(<<"Default users as participants">>,
+ <<"members_by_default">>,
+ (Config#config.members_by_default)),
+ ?BOOLXFIELD(<<"Allow users to change the subject">>,
+ <<"muc#roomconfig_changesubject">>,
+ (Config#config.allow_change_subj)),
+ ?BOOLXFIELD(<<"Allow users to send private messages">>,
+ <<"allow_private_messages">>,
+ (Config#config.allow_private_messages)),
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"type">>, <<"list-single">>},
+ {<<"label">>,
+ translate:translate(Lang,
+ <<"Allow visitors to send private messages to">>)},
+ {<<"var">>,
+ <<"allow_private_messages_from_visitors">>}],
+ children =
+ [#xmlel{name = <<"value">>, attrs = [],
+ children =
+ [{xmlcdata,
+ case
+ Config#config.allow_private_messages_from_visitors
+ of
+ anyone -> <<"anyone">>;
+ moderators -> <<"moderators">>;
+ nobody -> <<"nobody">>
+ end}]},
+ #xmlel{name = <<"option">>,
+ attrs =
+ [{<<"label">>,
+ translate:translate(Lang,
+ <<"nobody">>)}],
+ children =
+ [#xmlel{name = <<"value">>, attrs = [],
+ children =
+ [{xmlcdata, <<"nobody">>}]}]},
+ #xmlel{name = <<"option">>,
+ attrs =
+ [{<<"label">>,
+ translate:translate(Lang,
+ <<"moderators only">>)}],
+ children =
+ [#xmlel{name = <<"value">>, attrs = [],
+ children =
+ [{xmlcdata,
+ <<"moderators">>}]}]},
+ #xmlel{name = <<"option">>,
+ attrs =
+ [{<<"label">>,
+ translate:translate(Lang,
+ <<"anyone">>)}],
+ children =
+ [#xmlel{name = <<"value">>, attrs = [],
+ children =
+ [{xmlcdata,
+ <<"anyone">>}]}]}]},
+ ?BOOLXFIELD(<<"Allow users to query other users">>,
+ <<"allow_query_users">>,
+ (Config#config.allow_query_users)),
+ ?BOOLXFIELD(<<"Allow users to send invites">>,
+ <<"muc#roomconfig_allowinvites">>,
+ (Config#config.allow_user_invites)),
+ ?BOOLXFIELD(<<"Allow visitors to send status text in "
+ "presence updates">>,
+ <<"muc#roomconfig_allowvisitorstatus">>,
+ (Config#config.allow_visitor_status)),
+ ?BOOLXFIELD(<<"Allow visitors to change nickname">>,
+ <<"muc#roomconfig_allowvisitornickchange">>,
+ (Config#config.allow_visitor_nickchange)),
+ ?BOOLXFIELD(<<"Allow visitors to send voice requests">>,
+ <<"muc#roomconfig_allowvoicerequests">>,
+ (Config#config.allow_voice_requests)),
+ ?STRINGXFIELD(<<"Minimum interval between voice requests "
+ "(in seconds)">>,
+ <<"muc#roomconfig_voicerequestmininterval">>,
+ (jlib:integer_to_binary(Config#config.voice_request_min_interval)))]
+ ++
+ case ejabberd_captcha:is_feature_available() of
+ true ->
+ [?BOOLXFIELD(<<"Make room CAPTCHA protected">>,
+ <<"captcha_protected">>,
+ (Config#config.captcha_protected))];
+ false -> []
+ end
+ ++
+ [?JIDMULTIXFIELD(<<"Exclude Jabber IDs from CAPTCHA challenge">>,
+ <<"muc#roomconfig_captcha_whitelist">>,
+ ((?SETS):to_list(Config#config.captcha_whitelist)))]
+ ++
+ case
+ mod_muc_log:check_access_log(StateData#state.server_host,
+ From)
+ of
+ allow ->
+ [?BOOLXFIELD(<<"Enable logging">>,
+ <<"muc#roomconfig_enablelogging">>,
+ (Config#config.logging))];
+ _ -> []
+ end,
+ {result,
+ [#xmlel{name = <<"instructions">>, attrs = [],
+ children =
+ [{xmlcdata,
+ translate:translate(Lang,
+ <<"You need an x:data capable client to "
+ "configure room">>)}]},
+ #xmlel{name = <<"x">>,
+ attrs =
+ [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"form">>}],
+ children = Res}],
StateData}.
-
-
set_config(XEl, StateData) ->
XData = jlib:parse_xdata_submit(XEl),
case XData of
- invalid ->
- {error, ?ERR_BAD_REQUEST};
- _ ->
- case set_xoption(XData, StateData#state.config) of
- #config{} = Config ->
- Res = change_config(Config, StateData),
- {result, _, NSD} = Res,
- Type = case {(StateData#state.config)#config.logging,
- Config#config.logging} of
- {true, false} ->
- roomconfig_change_disabledlogging;
- {false, true} ->
- roomconfig_change_enabledlogging;
- {_, _} ->
- roomconfig_change
- end,
- Users = [{U#user.jid, U#user.nick, U#user.role} ||
- {_, U} <- ?DICT:to_list(StateData#state.users)],
- add_to_log(Type, Users, NSD),
- Res;
- Err ->
- Err
- end
+ invalid -> {error, ?ERR_BAD_REQUEST};
+ _ ->
+ case set_xoption(XData, StateData#state.config) of
+ #config{} = Config ->
+ Res = change_config(Config, StateData),
+ {result, _, NSD} = Res,
+ Type = case {(StateData#state.config)#config.logging,
+ Config#config.logging}
+ of
+ {true, false} -> roomconfig_change_disabledlogging;
+ {false, true} -> roomconfig_change_enabledlogging;
+ {_, _} -> roomconfig_change
+ end,
+ Users = [{U#user.jid, U#user.nick, U#user.role}
+ || {_, U} <- (?DICT):to_list(StateData#state.users)],
+ add_to_log(Type, Users, NSD),
+ Res;
+ Err -> Err
+ end
end.
-define(SET_BOOL_XOPT(Opt, Val),
case Val of
- "0" -> set_xoption(Opts, Config#config{Opt = false});
- "false" -> set_xoption(Opts, Config#config{Opt = false});
- "1" -> set_xoption(Opts, Config#config{Opt = true});
- "true" -> set_xoption(Opts, Config#config{Opt = true});
- _ -> {error, ?ERR_BAD_REQUEST}
+ <<"0">> ->
+ set_xoption(Opts, Config#config{Opt = false});
+ <<"false">> ->
+ set_xoption(Opts, Config#config{Opt = false});
+ <<"1">> -> set_xoption(Opts, Config#config{Opt = true});
+ <<"true">> ->
+ set_xoption(Opts, Config#config{Opt = true});
+ _ -> {error, ?ERR_BAD_REQUEST}
end).
-define(SET_NAT_XOPT(Opt, Val),
- case catch list_to_integer(Val) of
- I when is_integer(I),
- I > 0 ->
- set_xoption(Opts, Config#config{Opt = I});
- _ ->
- {error, ?ERR_BAD_REQUEST}
+ case catch jlib:binary_to_integer(Val) of
+ I when is_integer(I), I > 0 ->
+ set_xoption(Opts, Config#config{Opt = I});
+ _ -> {error, ?ERR_BAD_REQUEST}
end).
-define(SET_STRING_XOPT(Opt, Val),
set_xoption(Opts, Config#config{Opt = Val})).
-define(SET_JIDMULTI_XOPT(Opt, Vals),
- begin
- Set = lists:foldl(
- fun({U, S, R}, Set1) ->
- ?SETS:add_element({U, S, R}, Set1);
- (#jid{luser = U, lserver = S, lresource = R}, Set1) ->
- ?SETS:add_element({U, S, R}, Set1);
- (_, Set1) ->
- Set1
- end, ?SETS:empty(), Vals),
- set_xoption(Opts, Config#config{Opt = Set})
- end).
-
-set_xoption([], Config) ->
- Config;
-set_xoption([{"muc#roomconfig_roomname", [Val]} | Opts], Config) ->
+ begin
+ Set = lists:foldl(fun ({U, S, R}, Set1) ->
+ (?SETS):add_element({U, S, R}, Set1);
+ (#jid{luser = U, lserver = S, lresource = R},
+ Set1) ->
+ (?SETS):add_element({U, S, R}, Set1);
+ (_, Set1) -> Set1
+ end,
+ (?SETS):empty(), Vals),
+ set_xoption(Opts, Config#config{Opt = Set})
+ end).
+
+set_xoption([], Config) -> Config;
+set_xoption([{<<"muc#roomconfig_roomname">>, [Val]}
+ | Opts],
+ Config) ->
?SET_STRING_XOPT(title, Val);
-set_xoption([{"muc#roomconfig_roomdesc", [Val]} | Opts], Config) ->
+set_xoption([{<<"muc#roomconfig_roomdesc">>, [Val]}
+ | Opts],
+ Config) ->
?SET_STRING_XOPT(description, Val);
-set_xoption([{"muc#roomconfig_changesubject", [Val]} | Opts], Config) ->
+set_xoption([{<<"muc#roomconfig_changesubject">>, [Val]}
+ | Opts],
+ Config) ->
?SET_BOOL_XOPT(allow_change_subj, Val);
-set_xoption([{"allow_query_users", [Val]} | Opts], Config) ->
+set_xoption([{<<"allow_query_users">>, [Val]} | Opts],
+ Config) ->
?SET_BOOL_XOPT(allow_query_users, Val);
-set_xoption([{"allow_private_messages", [Val]} | Opts], Config) ->
+set_xoption([{<<"allow_private_messages">>, [Val]}
+ | Opts],
+ Config) ->
?SET_BOOL_XOPT(allow_private_messages, Val);
-set_xoption([{"allow_private_messages_from_visitors", [Val]} | Opts], Config) ->
+set_xoption([{<<"allow_private_messages_from_visitors">>,
+ [Val]}
+ | Opts],
+ Config) ->
case Val of
- "anyone" ->
- ?SET_STRING_XOPT(allow_private_messages_from_visitors, anyone);
- "moderators" ->
- ?SET_STRING_XOPT(allow_private_messages_from_visitors, moderators);
- "nobody" ->
- ?SET_STRING_XOPT(allow_private_messages_from_visitors, nobody);
- _ ->
- {error, ?ERR_BAD_REQUEST}
+ <<"anyone">> ->
+ ?SET_STRING_XOPT(allow_private_messages_from_visitors,
+ anyone);
+ <<"moderators">> ->
+ ?SET_STRING_XOPT(allow_private_messages_from_visitors,
+ moderators);
+ <<"nobody">> ->
+ ?SET_STRING_XOPT(allow_private_messages_from_visitors,
+ nobody);
+ _ -> {error, ?ERR_BAD_REQUEST}
end;
-set_xoption([{"muc#roomconfig_allowvisitorstatus", [Val]} | Opts], Config) ->
+set_xoption([{<<"muc#roomconfig_allowvisitorstatus">>,
+ [Val]}
+ | Opts],
+ Config) ->
?SET_BOOL_XOPT(allow_visitor_status, Val);
-set_xoption([{"muc#roomconfig_allowvisitornickchange", [Val]} | Opts], Config) ->
+set_xoption([{<<"muc#roomconfig_allowvisitornickchange">>,
+ [Val]}
+ | Opts],
+ Config) ->
?SET_BOOL_XOPT(allow_visitor_nickchange, Val);
-set_xoption([{"muc#roomconfig_publicroom", [Val]} | Opts], Config) ->
+set_xoption([{<<"muc#roomconfig_publicroom">>, [Val]}
+ | Opts],
+ Config) ->
?SET_BOOL_XOPT(public, Val);
-set_xoption([{"public_list", [Val]} | Opts], Config) ->
+set_xoption([{<<"public_list">>, [Val]} | Opts],
+ Config) ->
?SET_BOOL_XOPT(public_list, Val);
-set_xoption([{"muc#roomconfig_persistentroom", [Val]} | Opts], Config) ->
+set_xoption([{<<"muc#roomconfig_persistentroom">>,
+ [Val]}
+ | Opts],
+ Config) ->
?SET_BOOL_XOPT(persistent, Val);
-set_xoption([{"muc#roomconfig_moderatedroom", [Val]} | Opts], Config) ->
+set_xoption([{<<"muc#roomconfig_moderatedroom">>, [Val]}
+ | Opts],
+ Config) ->
?SET_BOOL_XOPT(moderated, Val);
-set_xoption([{"members_by_default", [Val]} | Opts], Config) ->
+set_xoption([{<<"members_by_default">>, [Val]} | Opts],
+ Config) ->
?SET_BOOL_XOPT(members_by_default, Val);
-set_xoption([{"muc#roomconfig_membersonly", [Val]} | Opts], Config) ->
+set_xoption([{<<"muc#roomconfig_membersonly">>, [Val]}
+ | Opts],
+ Config) ->
?SET_BOOL_XOPT(members_only, Val);
-set_xoption([{"captcha_protected", [Val]} | Opts], Config) ->
+set_xoption([{<<"captcha_protected">>, [Val]} | Opts],
+ Config) ->
?SET_BOOL_XOPT(captcha_protected, Val);
-set_xoption([{"muc#roomconfig_allowinvites", [Val]} | Opts], Config) ->
+set_xoption([{<<"muc#roomconfig_allowinvites">>, [Val]}
+ | Opts],
+ Config) ->
?SET_BOOL_XOPT(allow_user_invites, Val);
-set_xoption([{"muc#roomconfig_passwordprotectedroom", [Val]} | Opts], Config) ->
+set_xoption([{<<"muc#roomconfig_passwordprotectedroom">>,
+ [Val]}
+ | Opts],
+ Config) ->
?SET_BOOL_XOPT(password_protected, Val);
-set_xoption([{"muc#roomconfig_roomsecret", [Val]} | Opts], Config) ->
+set_xoption([{<<"muc#roomconfig_roomsecret">>, [Val]}
+ | Opts],
+ Config) ->
?SET_STRING_XOPT(password, Val);
-set_xoption([{"anonymous", [Val]} | Opts], Config) ->
+set_xoption([{<<"anonymous">>, [Val]} | Opts],
+ Config) ->
?SET_BOOL_XOPT(anonymous, Val);
-set_xoption([{"muc#roomconfig_allowvoicerequests", [Val]} | Opts], Config) ->
- ?SET_BOOL_XOPT(allow_voice_requests, Val);
-set_xoption([{"muc#roomconfig_voicerequestmininterval", [Val]} | Opts], Config) ->
- ?SET_NAT_XOPT(voice_request_min_interval, Val);
-set_xoption([{"muc#roomconfig_whois", [Val]} | Opts], Config) ->
+set_xoption([{<<"muc#roomconfig_allowvoicerequests">>,
+ [Val]}
+ | Opts],
+ Config) ->
+ ?SET_BOOL_XOPT(allow_voice_requests, Val);
+set_xoption([{<<"muc#roomconfig_voicerequestmininterval">>,
+ [Val]}
+ | Opts],
+ Config) ->
+ ?SET_NAT_XOPT(voice_request_min_interval, Val);
+set_xoption([{<<"muc#roomconfig_whois">>, [Val]}
+ | Opts],
+ Config) ->
case Val of
- "moderators" ->
- ?SET_BOOL_XOPT(anonymous, integer_to_list(1));
- "anyone" ->
- ?SET_BOOL_XOPT(anonymous, integer_to_list(0));
- _ ->
- {error, ?ERR_BAD_REQUEST}
+ <<"moderators">> ->
+ ?SET_BOOL_XOPT(anonymous,
+ (iolist_to_binary(integer_to_list(1))));
+ <<"anyone">> ->
+ ?SET_BOOL_XOPT(anonymous,
+ (iolist_to_binary(integer_to_list(0))));
+ _ -> {error, ?ERR_BAD_REQUEST}
end;
-set_xoption([{"muc#roomconfig_maxusers", [Val]} | Opts], Config) ->
+set_xoption([{<<"muc#roomconfig_maxusers">>, [Val]}
+ | Opts],
+ Config) ->
case Val of
- "none" ->
- ?SET_STRING_XOPT(max_users, none);
- _ ->
- ?SET_NAT_XOPT(max_users, Val)
+ <<"none">> -> ?SET_STRING_XOPT(max_users, none);
+ _ -> ?SET_NAT_XOPT(max_users, Val)
end;
-set_xoption([{"muc#roomconfig_enablelogging", [Val]} | Opts], Config) ->
+set_xoption([{<<"muc#roomconfig_enablelogging">>, [Val]}
+ | Opts],
+ Config) ->
?SET_BOOL_XOPT(logging, Val);
-set_xoption([{"muc#roomconfig_captcha_whitelist", Vals} | Opts], Config) ->
+set_xoption([{<<"muc#roomconfig_captcha_whitelist">>,
+ Vals}
+ | Opts],
+ Config) ->
JIDs = [jlib:string_to_jid(Val) || Val <- Vals],
?SET_JIDMULTI_XOPT(captcha_whitelist, JIDs);
-set_xoption([{"FORM_TYPE", _} | Opts], Config) ->
- %% Ignore our FORM_TYPE
+set_xoption([{<<"FORM_TYPE">>, _} | Opts], Config) ->
set_xoption(Opts, Config);
set_xoption([_ | _Opts], _Config) ->
{error, ?ERR_BAD_REQUEST}.
-
change_config(Config, StateData) ->
NSD = StateData#state{config = Config},
case {(StateData#state.config)#config.persistent,
- Config#config.persistent} of
- {_, true} ->
- mod_muc:store_room(NSD#state.server_host, NSD#state.host,
- NSD#state.room, make_opts(NSD));
- {true, false} ->
- mod_muc:forget_room(NSD#state.server_host, NSD#state.host,
- NSD#state.room);
- {false, false} ->
- ok
+ Config#config.persistent}
+ of
+ {_, true} ->
+ mod_muc:store_room(NSD#state.server_host,
+ NSD#state.host, NSD#state.room, make_opts(NSD));
+ {true, false} ->
+ mod_muc:forget_room(NSD#state.server_host,
+ NSD#state.host, NSD#state.room);
+ {false, false} -> ok
end,
case {(StateData#state.config)#config.members_only,
- Config#config.members_only} of
- {false, true} ->
- NSD1 = remove_nonmembers(NSD),
- {result, [], NSD1};
- _ ->
- {result, [], NSD}
+ Config#config.members_only}
+ of
+ {false, true} ->
+ NSD1 = remove_nonmembers(NSD), {result, [], NSD1};
+ _ -> {result, [], NSD}
end.
remove_nonmembers(StateData) ->
- lists:foldl(
- fun({_LJID, #user{jid = JID}}, SD) ->
- Affiliation = get_affiliation(JID, SD),
- case Affiliation of
- none ->
- catch send_kickban_presence(
- JID, "", "322", SD),
- set_role(JID, none, SD);
- _ ->
- SD
- end
- end, StateData, ?DICT:to_list(StateData#state.users)).
-
-
--define(CASE_CONFIG_OPT(Opt),
- Opt -> StateData#state{
- config = (StateData#state.config)#config{Opt = Val}}).
+ lists:foldl(fun ({_LJID, #user{jid = JID}}, SD) ->
+ Affiliation = get_affiliation(JID, SD),
+ case Affiliation of
+ none ->
+ catch send_kickban_presence(JID, <<"">>,
+ <<"322">>, SD),
+ set_role(JID, none, SD);
+ _ -> SD
+ end
+ end,
+ StateData, (?DICT):to_list(StateData#state.users)).
-set_opts([], StateData) ->
- StateData;
+set_opts([], StateData) -> StateData;
set_opts([{Opt, Val} | Opts], StateData) ->
NSD = case Opt of
- title -> StateData#state{config = (StateData#state.config)#config{title = Val}};
- description -> StateData#state{config = (StateData#state.config)#config{description = Val}};
- allow_change_subj -> StateData#state{config = (StateData#state.config)#config{allow_change_subj = Val}};
- allow_query_users -> StateData#state{config = (StateData#state.config)#config{allow_query_users = Val}};
- allow_private_messages -> StateData#state{config = (StateData#state.config)#config{allow_private_messages = Val}};
- allow_private_messages_from_visitors -> StateData#state{config = (StateData#state.config)#config{allow_private_messages_from_visitors = Val}};
- allow_visitor_nickchange -> StateData#state{config = (StateData#state.config)#config{allow_visitor_nickchange = Val}};
- allow_visitor_status -> StateData#state{config = (StateData#state.config)#config{allow_visitor_status = Val}};
- public -> StateData#state{config = (StateData#state.config)#config{public = Val}};
- public_list -> StateData#state{config = (StateData#state.config)#config{public_list = Val}};
- persistent -> StateData#state{config = (StateData#state.config)#config{persistent = Val}};
- moderated -> StateData#state{config = (StateData#state.config)#config{moderated = Val}};
- members_by_default -> StateData#state{config = (StateData#state.config)#config{members_by_default = Val}};
- members_only -> StateData#state{config = (StateData#state.config)#config{members_only = Val}};
- allow_user_invites -> StateData#state{config = (StateData#state.config)#config{allow_user_invites = Val}};
- password_protected -> StateData#state{config = (StateData#state.config)#config{password_protected = Val}};
- captcha_protected -> StateData#state{config = (StateData#state.config)#config{captcha_protected = Val}};
- password -> StateData#state{config = (StateData#state.config)#config{password = Val}};
- anonymous -> StateData#state{config = (StateData#state.config)#config{anonymous = Val}};
- logging -> StateData#state{config = (StateData#state.config)#config{logging = Val}};
- captcha_whitelist -> StateData#state{config = (StateData#state.config)#config{captcha_whitelist = ?SETS:from_list(Val)}};
- allow_voice_requests -> StateData#state{config = (StateData#state.config)#config{allow_voice_requests = Val}};
- voice_request_min_interval -> StateData#state{config = (StateData#state.config)#config{voice_request_min_interval = Val}};
- max_users ->
- ServiceMaxUsers = get_service_max_users(StateData),
- MaxUsers = if
- Val =< ServiceMaxUsers -> Val;
- true -> ServiceMaxUsers
- end,
- StateData#state{
- config = (StateData#state.config)#config{
- max_users = MaxUsers}};
- affiliations ->
- StateData#state{affiliations = ?DICT:from_list(Val)};
- subject ->
- StateData#state{subject = Val};
- subject_author ->
- StateData#state{subject_author = Val};
- _ -> StateData
+ title ->
+ StateData#state{config =
+ (StateData#state.config)#config{title =
+ Val}};
+ description ->
+ StateData#state{config =
+ (StateData#state.config)#config{description
+ = Val}};
+ allow_change_subj ->
+ StateData#state{config =
+ (StateData#state.config)#config{allow_change_subj
+ = Val}};
+ allow_query_users ->
+ StateData#state{config =
+ (StateData#state.config)#config{allow_query_users
+ = Val}};
+ allow_private_messages ->
+ StateData#state{config =
+ (StateData#state.config)#config{allow_private_messages
+ = Val}};
+ allow_private_messages_from_visitors ->
+ StateData#state{config =
+ (StateData#state.config)#config{allow_private_messages_from_visitors
+ = Val}};
+ allow_visitor_nickchange ->
+ StateData#state{config =
+ (StateData#state.config)#config{allow_visitor_nickchange
+ = Val}};
+ allow_visitor_status ->
+ StateData#state{config =
+ (StateData#state.config)#config{allow_visitor_status
+ = Val}};
+ public ->
+ StateData#state{config =
+ (StateData#state.config)#config{public =
+ Val}};
+ public_list ->
+ StateData#state{config =
+ (StateData#state.config)#config{public_list
+ = Val}};
+ persistent ->
+ StateData#state{config =
+ (StateData#state.config)#config{persistent =
+ Val}};
+ moderated ->
+ StateData#state{config =
+ (StateData#state.config)#config{moderated =
+ Val}};
+ members_by_default ->
+ StateData#state{config =
+ (StateData#state.config)#config{members_by_default
+ = Val}};
+ members_only ->
+ StateData#state{config =
+ (StateData#state.config)#config{members_only
+ = Val}};
+ allow_user_invites ->
+ StateData#state{config =
+ (StateData#state.config)#config{allow_user_invites
+ = Val}};
+ password_protected ->
+ StateData#state{config =
+ (StateData#state.config)#config{password_protected
+ = Val}};
+ captcha_protected ->
+ StateData#state{config =
+ (StateData#state.config)#config{captcha_protected
+ = Val}};
+ password ->
+ StateData#state{config =
+ (StateData#state.config)#config{password =
+ Val}};
+ anonymous ->
+ StateData#state{config =
+ (StateData#state.config)#config{anonymous =
+ Val}};
+ logging ->
+ StateData#state{config =
+ (StateData#state.config)#config{logging =
+ Val}};
+ captcha_whitelist ->
+ StateData#state{config =
+ (StateData#state.config)#config{captcha_whitelist
+ =
+ (?SETS):from_list(Val)}};
+ allow_voice_requests ->
+ StateData#state{config =
+ (StateData#state.config)#config{allow_voice_requests
+ = Val}};
+ voice_request_min_interval ->
+ StateData#state{config =
+ (StateData#state.config)#config{voice_request_min_interval
+ = Val}};
+ max_users ->
+ ServiceMaxUsers = get_service_max_users(StateData),
+ MaxUsers = if Val =< ServiceMaxUsers -> Val;
+ true -> ServiceMaxUsers
+ end,
+ StateData#state{config =
+ (StateData#state.config)#config{max_users =
+ MaxUsers}};
+ affiliations ->
+ StateData#state{affiliations = (?DICT):from_list(Val)};
+ subject -> StateData#state{subject = Val};
+ subject_author -> StateData#state{subject_author = Val};
+ _ -> StateData
end,
set_opts(Opts, NSD).
-define(MAKE_CONFIG_OPT(Opt), {Opt, Config#config.Opt}).
+
make_opts(StateData) ->
Config = StateData#state.config,
- [
- ?MAKE_CONFIG_OPT(title),
- ?MAKE_CONFIG_OPT(description),
+ [?MAKE_CONFIG_OPT(title), ?MAKE_CONFIG_OPT(description),
?MAKE_CONFIG_OPT(allow_change_subj),
?MAKE_CONFIG_OPT(allow_query_users),
?MAKE_CONFIG_OPT(allow_private_messages),
?MAKE_CONFIG_OPT(allow_private_messages_from_visitors),
?MAKE_CONFIG_OPT(allow_visitor_status),
?MAKE_CONFIG_OPT(allow_visitor_nickchange),
- ?MAKE_CONFIG_OPT(public),
- ?MAKE_CONFIG_OPT(public_list),
+ ?MAKE_CONFIG_OPT(public), ?MAKE_CONFIG_OPT(public_list),
?MAKE_CONFIG_OPT(persistent),
?MAKE_CONFIG_OPT(moderated),
?MAKE_CONFIG_OPT(members_by_default),
@@ -3588,414 +3891,460 @@ make_opts(StateData) ->
?MAKE_CONFIG_OPT(allow_user_invites),
?MAKE_CONFIG_OPT(password_protected),
?MAKE_CONFIG_OPT(captcha_protected),
- ?MAKE_CONFIG_OPT(password),
- ?MAKE_CONFIG_OPT(anonymous),
- ?MAKE_CONFIG_OPT(logging),
- ?MAKE_CONFIG_OPT(max_users),
+ ?MAKE_CONFIG_OPT(password), ?MAKE_CONFIG_OPT(anonymous),
+ ?MAKE_CONFIG_OPT(logging), ?MAKE_CONFIG_OPT(max_users),
?MAKE_CONFIG_OPT(allow_voice_requests),
?MAKE_CONFIG_OPT(voice_request_min_interval),
{captcha_whitelist,
- ?SETS:to_list((StateData#state.config)#config.captcha_whitelist)},
- {affiliations, ?DICT:to_list(StateData#state.affiliations)},
+ (?SETS):to_list((StateData#state.config)#config.captcha_whitelist)},
+ {affiliations,
+ (?DICT):to_list(StateData#state.affiliations)},
{subject, StateData#state.subject},
- {subject_author, StateData#state.subject_author}
- ].
-
-
+ {subject_author, StateData#state.subject_author}].
destroy_room(DEl, StateData) ->
- lists:foreach(
- fun({_LJID, Info}) ->
- Nick = Info#user.nick,
- ItemAttrs = [{"affiliation", "none"},
- {"role", "none"}],
- Packet = {xmlelement, "presence", [{"type", "unavailable"}],
- [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}],
- [{xmlelement, "item", ItemAttrs, []}, DEl]}]},
- ejabberd_router:route(
- jlib:jid_replace_resource(StateData#state.jid, Nick),
- Info#user.jid,
- Packet)
- end, ?DICT:to_list(StateData#state.users)),
+ lists:foreach(fun ({_LJID, Info}) ->
+ Nick = Info#user.nick,
+ ItemAttrs = [{<<"affiliation">>, <<"none">>},
+ {<<"role">>, <<"none">>}],
+ Packet = #xmlel{name = <<"presence">>,
+ attrs =
+ [{<<"type">>, <<"unavailable">>}],
+ children =
+ [#xmlel{name = <<"x">>,
+ attrs =
+ [{<<"xmlns">>,
+ ?NS_MUC_USER}],
+ children =
+ [#xmlel{name =
+ <<"item">>,
+ attrs =
+ ItemAttrs,
+ children =
+ []},
+ DEl]}]},
+ ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid,
+ Nick),
+ Info#user.jid, Packet)
+ end,
+ (?DICT):to_list(StateData#state.users)),
case (StateData#state.config)#config.persistent of
- true ->
- mod_muc:forget_room(
- StateData#state.server_host,
- StateData#state.host, StateData#state.room);
- false ->
- ok
- end,
+ true ->
+ mod_muc:forget_room(StateData#state.server_host,
+ StateData#state.host, StateData#state.room);
+ false -> ok
+ end,
{result, [], stop}.
-
-
-
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Disco
--define(FEATURE(Var), {xmlelement, "feature", [{"var", Var}], []}).
+-define(FEATURE(Var),
+ #xmlel{name = <<"feature">>, attrs = [{<<"var">>, Var}],
+ children = []}).
-define(CONFIG_OPT_TO_FEATURE(Opt, Fiftrue, Fiffalse),
- case Opt of
- true ->
- ?FEATURE(Fiftrue);
- false ->
- ?FEATURE(Fiffalse)
- end).
+ case Opt of
+ true -> ?FEATURE(Fiftrue);
+ false -> ?FEATURE(Fiffalse)
+ end).
process_iq_disco_info(_From, set, _Lang, _StateData) ->
{error, ?ERR_NOT_ALLOWED};
-
process_iq_disco_info(_From, get, Lang, StateData) ->
Config = StateData#state.config,
- {result, [{xmlelement, "identity",
- [{"category", "conference"},
- {"type", "text"},
- {"name", get_title(StateData)}], []},
- {xmlelement, "feature",
- [{"var", ?NS_MUC}], []},
- ?CONFIG_OPT_TO_FEATURE(Config#config.public,
- "muc_public", "muc_hidden"),
- ?CONFIG_OPT_TO_FEATURE(Config#config.persistent,
- "muc_persistent", "muc_temporary"),
- ?CONFIG_OPT_TO_FEATURE(Config#config.members_only,
- "muc_membersonly", "muc_open"),
- ?CONFIG_OPT_TO_FEATURE(Config#config.anonymous,
- "muc_semianonymous", "muc_nonanonymous"),
- ?CONFIG_OPT_TO_FEATURE(Config#config.moderated,
- "muc_moderated", "muc_unmoderated"),
- ?CONFIG_OPT_TO_FEATURE(Config#config.password_protected,
- "muc_passwordprotected", "muc_unsecured")
- ] ++ iq_disco_info_extras(Lang, StateData), StateData}.
+ {result,
+ [#xmlel{name = <<"identity">>,
+ attrs =
+ [{<<"category">>, <<"conference">>},
+ {<<"type">>, <<"text">>},
+ {<<"name">>, get_title(StateData)}],
+ children = []},
+ #xmlel{name = <<"feature">>,
+ attrs = [{<<"var">>, ?NS_MUC}], children = []},
+ ?CONFIG_OPT_TO_FEATURE((Config#config.public),
+ <<"muc_public">>, <<"muc_hidden">>),
+ ?CONFIG_OPT_TO_FEATURE((Config#config.persistent),
+ <<"muc_persistent">>, <<"muc_temporary">>),
+ ?CONFIG_OPT_TO_FEATURE((Config#config.members_only),
+ <<"muc_membersonly">>, <<"muc_open">>),
+ ?CONFIG_OPT_TO_FEATURE((Config#config.anonymous),
+ <<"muc_semianonymous">>, <<"muc_nonanonymous">>),
+ ?CONFIG_OPT_TO_FEATURE((Config#config.moderated),
+ <<"muc_moderated">>, <<"muc_unmoderated">>),
+ ?CONFIG_OPT_TO_FEATURE((Config#config.password_protected),
+ <<"muc_passwordprotected">>, <<"muc_unsecured">>)]
+ ++ iq_disco_info_extras(Lang, StateData),
+ StateData}.
-define(RFIELDT(Type, Var, Val),
- {xmlelement, "field", [{"type", Type}, {"var", Var}],
- [{xmlelement, "value", [], [{xmlcdata, Val}]}]}).
+ #xmlel{name = <<"field">>,
+ attrs = [{<<"type">>, Type}, {<<"var">>, Var}],
+ children =
+ [#xmlel{name = <<"value">>, attrs = [],
+ children = [{xmlcdata, Val}]}]}).
-define(RFIELD(Label, Var, Val),
- {xmlelement, "field", [{"label", translate:translate(Lang, Label)},
- {"var", Var}],
- [{xmlelement, "value", [], [{xmlcdata, Val}]}]}).
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"label">>, translate:translate(Lang, Label)},
+ {<<"var">>, Var}],
+ children =
+ [#xmlel{name = <<"value">>, attrs = [],
+ children = [{xmlcdata, Val}]}]}).
iq_disco_info_extras(Lang, StateData) ->
- Len = ?DICT:size(StateData#state.users),
- RoomDescription = (StateData#state.config)#config.description,
- [{xmlelement, "x", [{"xmlns", ?NS_XDATA}, {"type", "result"}],
- [?RFIELDT("hidden", "FORM_TYPE",
- "http://jabber.org/protocol/muc#roominfo"),
- ?RFIELD("Room description", "muc#roominfo_description",
- RoomDescription),
- ?RFIELD("Number of occupants", "muc#roominfo_occupants",
- integer_to_list(Len))
- ]}].
+ Len = (?DICT):size(StateData#state.users),
+ RoomDescription =
+ (StateData#state.config)#config.description,
+ [#xmlel{name = <<"x">>,
+ attrs =
+ [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"result">>}],
+ children =
+ [?RFIELDT(<<"hidden">>, <<"FORM_TYPE">>,
+ <<"http://jabber.org/protocol/muc#roominfo">>),
+ ?RFIELD(<<"Room description">>,
+ <<"muc#roominfo_description">>, RoomDescription),
+ ?RFIELD(<<"Number of occupants">>,
+ <<"muc#roominfo_occupants">>,
+ (iolist_to_binary(integer_to_list(Len))))]}].
process_iq_disco_items(_From, set, _Lang, _StateData) ->
{error, ?ERR_NOT_ALLOWED};
-
process_iq_disco_items(From, get, _Lang, StateData) ->
case (StateData#state.config)#config.public_list of
- true ->
- {result, get_mucroom_disco_items(StateData), StateData};
- _ ->
- case is_occupant_or_admin(From, StateData) of
- true ->
- {result, get_mucroom_disco_items(StateData), StateData};
- _ ->
- {error, ?ERR_FORBIDDEN}
- end
+ true ->
+ {result, get_mucroom_disco_items(StateData), StateData};
+ _ ->
+ case is_occupant_or_admin(From, StateData) of
+ true ->
+ {result, get_mucroom_disco_items(StateData), StateData};
+ _ -> {error, ?ERR_FORBIDDEN}
+ end
end.
-process_iq_captcha(_From, get, _Lang, _SubEl, _StateData) ->
+process_iq_captcha(_From, get, _Lang, _SubEl,
+ _StateData) ->
{error, ?ERR_NOT_ALLOWED};
-
-process_iq_captcha(_From, set, _Lang, SubEl, StateData) ->
+process_iq_captcha(_From, set, _Lang, SubEl,
+ StateData) ->
case ejabberd_captcha:process_reply(SubEl) of
- ok ->
- {result, [], StateData};
- _ ->
- {error, ?ERR_NOT_ACCEPTABLE}
+ ok -> {result, [], StateData};
+ _ -> {error, ?ERR_NOT_ACCEPTABLE}
end.
get_title(StateData) ->
case (StateData#state.config)#config.title of
- "" ->
- StateData#state.room;
- Name ->
- Name
+ <<"">> -> StateData#state.room;
+ Name -> Name
end.
get_roomdesc_reply(JID, StateData, Tail) ->
- IsOccupantOrAdmin = is_occupant_or_admin(JID, StateData),
- if (StateData#state.config)#config.public or IsOccupantOrAdmin ->
- if (StateData#state.config)#config.public_list or IsOccupantOrAdmin ->
- {item, get_title(StateData) ++ Tail};
- true ->
- {item, get_title(StateData)}
- end;
- true ->
- false
+ IsOccupantOrAdmin = is_occupant_or_admin(JID,
+ StateData),
+ if (StateData#state.config)#config.public or
+ IsOccupantOrAdmin ->
+ if (StateData#state.config)#config.public_list or
+ IsOccupantOrAdmin ->
+ {item, <<(get_title(StateData))/binary,Tail/binary>>};
+ true -> {item, get_title(StateData)}
+ end;
+ true -> false
end.
get_roomdesc_tail(StateData, Lang) ->
Desc = case (StateData#state.config)#config.public of
- true ->
- "";
- _ ->
- translate:translate(Lang, "private, ")
+ true -> <<"">>;
+ _ -> translate:translate(Lang, <<"private, ">>)
end,
- Len = ?DICT:fold(fun(_, _, Acc) -> Acc + 1 end, 0, StateData#state.users),
- " (" ++ Desc ++ integer_to_list(Len) ++ ")".
+ Len = (?DICT):fold(fun (_, _, Acc) -> Acc + 1 end, 0,
+ StateData#state.users),
+ <<" (", Desc/binary,
+ (iolist_to_binary(integer_to_list(Len)))/binary, ")">>.
get_mucroom_disco_items(StateData) ->
- lists:map(
- fun({_LJID, Info}) ->
- Nick = Info#user.nick,
- {xmlelement, "item",
- [{"jid", jlib:jid_to_string({StateData#state.room,
- StateData#state.host, Nick})},
- {"name", Nick}], []}
- end,
- ?DICT:to_list(StateData#state.users)).
+ lists:map(fun ({_LJID, Info}) ->
+ Nick = Info#user.nick,
+ #xmlel{name = <<"item">>,
+ attrs =
+ [{<<"jid">>,
+ jlib:jid_to_string({StateData#state.room,
+ StateData#state.host,
+ Nick})},
+ {<<"name">>, Nick}],
+ children = []}
+ end,
+ (?DICT):to_list(StateData#state.users)).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Voice request support
is_voice_request(Els) ->
- lists:foldl(
- fun({xmlelement, "x", Attrs, _} = El, false) ->
- case xml:get_attr_s("xmlns", Attrs) of
- ?NS_XDATA ->
- case jlib:parse_xdata_submit(El) of
- [_|_] = Fields ->
- case {lists:keysearch("FORM_TYPE", 1, Fields),
- lists:keysearch("muc#role", 1, Fields)} of
- {{value,
- {_, ["http://jabber.org/protocol/muc#request"]}},
- {value, {_, ["participant"]}}} ->
- true;
- _ ->
- false
- end;
- _ ->
- false
- end;
- _ ->
- false
- end;
- (_, Acc) ->
- Acc
- end, false, Els).
+ lists:foldl(fun (#xmlel{name = <<"x">>, attrs = Attrs} =
+ El,
+ false) ->
+ case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ ?NS_XDATA ->
+ case jlib:parse_xdata_submit(El) of
+ [_ | _] = Fields ->
+ case {lists:keysearch(<<"FORM_TYPE">>, 1,
+ Fields),
+ lists:keysearch(<<"muc#role">>, 1,
+ Fields)}
+ of
+ {{value,
+ {_,
+ [<<"http://jabber.org/protocol/muc#request">>]}},
+ {value, {_, [<<"participant">>]}}} ->
+ true;
+ _ -> false
+ end;
+ _ -> false
+ end;
+ _ -> false
+ end;
+ (_, Acc) -> Acc
+ end,
+ false, Els).
prepare_request_form(Requester, Nick, Lang) ->
- {xmlelement, "message", [{"type", "normal"}],
- [{xmlelement, "x", [{"xmlns", ?NS_XDATA}, {"type", "form"}],
- [{xmlelement, "title", [],
- [{xmlcdata, translate:translate(Lang, "Voice request")}]},
- {xmlelement, "instructions", [],
- [{xmlcdata,
- translate:translate(
- Lang, "Either approve or decline the voice request.")}]},
- {xmlelement, "field", [{"var", "FORM_TYPE"}, {"type", "hidden"}],
- [{xmlelement, "value", [],
- [{xmlcdata, "http://jabber.org/protocol/muc#request"}]}]},
- {xmlelement, "field", [{"var", "muc#role"}, {"type", "hidden"}],
- [{xmlelement, "value", [], [{xmlcdata, "participant"}]}]},
- ?STRINGXFIELD("User JID", "muc#jid", jlib:jid_to_string(Requester)),
- ?STRINGXFIELD("Nickname", "muc#roomnick", Nick),
- ?BOOLXFIELD("Grant voice to this person?", "muc#request_allow",
- list_to_atom("false"))
- ]}]}.
+ #xmlel{name = <<"message">>,
+ attrs = [{<<"type">>, <<"normal">>}],
+ children =
+ [#xmlel{name = <<"x">>,
+ attrs =
+ [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"form">>}],
+ children =
+ [#xmlel{name = <<"title">>, attrs = [],
+ children =
+ [{xmlcdata,
+ translate:translate(Lang,
+ <<"Voice request">>)}]},
+ #xmlel{name = <<"instructions">>, attrs = [],
+ children =
+ [{xmlcdata,
+ translate:translate(Lang,
+ <<"Either approve or decline the voice "
+ "request.">>)}]},
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"var">>, <<"FORM_TYPE">>},
+ {<<"type">>, <<"hidden">>}],
+ children =
+ [#xmlel{name = <<"value">>, attrs = [],
+ children =
+ [{xmlcdata,
+ <<"http://jabber.org/protocol/muc#request">>}]}]},
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"var">>, <<"muc#role">>},
+ {<<"type">>, <<"hidden">>}],
+ children =
+ [#xmlel{name = <<"value">>, attrs = [],
+ children =
+ [{xmlcdata,
+ <<"participant">>}]}]},
+ ?STRINGXFIELD(<<"User JID">>, <<"muc#jid">>,
+ (jlib:jid_to_string(Requester))),
+ ?STRINGXFIELD(<<"Nickname">>, <<"muc#roomnick">>,
+ Nick),
+ ?BOOLXFIELD(<<"Grant voice to this person?">>,
+ <<"muc#request_allow">>,
+ (jlib:binary_to_atom(<<"false">>)))]}]}.
send_voice_request(From, StateData) ->
Moderators = search_role(moderator, StateData),
FromNick = find_nick_by_jid(From, StateData),
- lists:foreach(
- fun({_, User}) ->
- ejabberd_router:route(
- StateData#state.jid,
- User#user.jid,
- prepare_request_form(From, FromNick, ""))
- end, Moderators).
+ lists:foreach(fun ({_, User}) ->
+ ejabberd_router:route(StateData#state.jid, User#user.jid,
+ prepare_request_form(From, FromNick,
+ <<"">>))
+ end,
+ Moderators).
is_voice_approvement(Els) ->
- lists:foldl(
- fun({xmlelement, "x", Attrs, _} = El, false) ->
- case xml:get_attr_s("xmlns", Attrs) of
- ?NS_XDATA ->
- case jlib:parse_xdata_submit(El) of
- [_|_] = Fs ->
- case {lists:keysearch("FORM_TYPE", 1, Fs),
- lists:keysearch("muc#role", 1, Fs),
- lists:keysearch("muc#request_allow", 1, Fs)} of
- {{value,
- {_, ["http://jabber.org/protocol/muc#request"]}},
- {value, {_, ["participant"]}},
- {value, {_, [Flag]}}}
- when Flag == "true"; Flag == "1" ->
- true;
- _ ->
- false
- end;
- _ ->
- false
- end;
- _ ->
- false
- end;
- (_, Acc) ->
- Acc
- end, false, Els).
+ lists:foldl(fun (#xmlel{name = <<"x">>, attrs = Attrs} =
+ El,
+ false) ->
+ case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ ?NS_XDATA ->
+ case jlib:parse_xdata_submit(El) of
+ [_ | _] = Fs ->
+ case {lists:keysearch(<<"FORM_TYPE">>, 1,
+ Fs),
+ lists:keysearch(<<"muc#role">>, 1,
+ Fs),
+ lists:keysearch(<<"muc#request_allow">>,
+ 1, Fs)}
+ of
+ {{value,
+ {_,
+ [<<"http://jabber.org/protocol/muc#request">>]}},
+ {value, {_, [<<"participant">>]}},
+ {value, {_, [Flag]}}}
+ when Flag == <<"true">>;
+ Flag == <<"1">> ->
+ true;
+ _ -> false
+ end;
+ _ -> false
+ end;
+ _ -> false
+ end;
+ (_, Acc) -> Acc
+ end,
+ false, Els).
extract_jid_from_voice_approvement(Els) ->
- lists:foldl(
- fun({xmlelement, "x", _, _} = El, error) ->
- Fields = case jlib:parse_xdata_submit(El) of
- invalid -> [];
- Res -> Res
- end,
- lists:foldl(
- fun({"muc#jid", [JIDStr]}, error) ->
- case jlib:string_to_jid(JIDStr) of
- error -> error;
- J -> {ok, J}
- end;
- (_, Acc) ->
- Acc
- end, error, Fields);
- (_, Acc) ->
- Acc
- end, error, Els).
+ lists:foldl(fun (#xmlel{name = <<"x">>} = El, error) ->
+ Fields = case jlib:parse_xdata_submit(El) of
+ invalid -> [];
+ Res -> Res
+ end,
+ lists:foldl(fun ({<<"muc#jid">>, [JIDStr]}, error) ->
+ case jlib:string_to_jid(JIDStr) of
+ error -> error;
+ J -> {ok, J}
+ end;
+ (_, Acc) -> Acc
+ end,
+ error, Fields);
+ (_, Acc) -> Acc
+ end,
+ error, Els).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Invitation support
is_invitation(Els) ->
- lists:foldl(
- fun({xmlelement, "x", Attrs, _} = El, false) ->
- case xml:get_attr_s("xmlns", Attrs) of
- ?NS_MUC_USER ->
- case xml:get_subtag(El, "invite") of
- false ->
- false;
- _ ->
- true
- end;
- _ ->
- false
- end;
- (_, Acc) ->
- Acc
- end, false, Els).
+ lists:foldl(fun (#xmlel{name = <<"x">>, attrs = Attrs} =
+ El,
+ false) ->
+ case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ ?NS_MUC_USER ->
+ case xml:get_subtag(El, <<"invite">>) of
+ false -> false;
+ _ -> true
+ end;
+ _ -> false
+ end;
+ (_, Acc) -> Acc
+ end,
+ false, Els).
check_invitation(From, Els, Lang, StateData) ->
FAffiliation = get_affiliation(From, StateData),
- CanInvite = (StateData#state.config)#config.allow_user_invites
- orelse (FAffiliation == admin) orelse (FAffiliation == owner),
+ CanInvite =
+ (StateData#state.config)#config.allow_user_invites
+ orelse
+ FAffiliation == admin orelse FAffiliation == owner,
InviteEl = case xml:remove_cdata(Els) of
- [{xmlelement, "x", _Attrs1, Els1} = XEl] ->
- case xml:get_tag_attr_s("xmlns", XEl) of
- ?NS_MUC_USER ->
- ok;
- _ ->
- throw({error, ?ERR_BAD_REQUEST})
- end,
- case xml:remove_cdata(Els1) of
- [{xmlelement, "invite", _Attrs2, _Els2} = InviteEl1] ->
- InviteEl1;
- _ ->
- throw({error, ?ERR_BAD_REQUEST})
- end;
- _ ->
- throw({error, ?ERR_BAD_REQUEST})
+ [#xmlel{name = <<"x">>, children = Els1} = XEl] ->
+ case xml:get_tag_attr_s(<<"xmlns">>, XEl) of
+ ?NS_MUC_USER -> ok;
+ _ -> throw({error, ?ERR_BAD_REQUEST})
+ end,
+ case xml:remove_cdata(Els1) of
+ [#xmlel{name = <<"invite">>} = InviteEl1] -> InviteEl1;
+ _ -> throw({error, ?ERR_BAD_REQUEST})
+ end;
+ _ -> throw({error, ?ERR_BAD_REQUEST})
end,
- JID = case jlib:string_to_jid(
- xml:get_tag_attr_s("to", InviteEl)) of
- error ->
- throw({error, ?ERR_JID_MALFORMED});
- JID1 ->
- JID1
+ JID = case
+ jlib:string_to_jid(xml:get_tag_attr_s(<<"to">>,
+ InviteEl))
+ of
+ error -> throw({error, ?ERR_JID_MALFORMED});
+ JID1 -> JID1
end,
case CanInvite of
- false ->
- throw({error, ?ERR_NOT_ALLOWED});
- true ->
- Reason =
- xml:get_path_s(
- InviteEl,
- [{elem, "reason"}, cdata]),
- ContinueEl =
- case xml:get_path_s(
- InviteEl,
- [{elem, "continue"}]) of
- [] -> [];
- Continue1 -> [Continue1]
- end,
- IEl =
- [{xmlelement, "invite",
- [{"from",
- jlib:jid_to_string(From)}],
- [{xmlelement, "reason", [],
- [{xmlcdata, Reason}]}] ++ ContinueEl}],
- PasswdEl =
- case (StateData#state.config)#config.password_protected of
- true ->
- [{xmlelement, "password", [],
- [{xmlcdata, (StateData#state.config)#config.password}]}];
- _ ->
- []
- end,
- Body =
- {xmlelement, "body", [],
- [{xmlcdata,
- lists:flatten(
- io_lib:format(
- translate:translate(
- Lang,
- "~s invites you to the room ~s"),
- [jlib:jid_to_string(From),
- jlib:jid_to_string({StateData#state.room,
- StateData#state.host,
- ""})
- ])) ++
- case (StateData#state.config)#config.password_protected of
+ false -> throw({error, ?ERR_NOT_ALLOWED});
+ true ->
+ Reason = xml:get_path_s(InviteEl,
+ [{elem, <<"reason">>}, cdata]),
+ ContinueEl = case xml:get_path_s(InviteEl,
+ [{elem, <<"continue">>}])
+ of
+ <<>> -> [];
+ Continue1 -> [Continue1]
+ end,
+ IEl = [#xmlel{name = <<"invite">>,
+ attrs = [{<<"from">>, jlib:jid_to_string(From)}],
+ children =
+ [#xmlel{name = <<"reason">>, attrs = [],
+ children = [{xmlcdata, Reason}]}]
+ ++ ContinueEl}],
+ PasswdEl = case
+ (StateData#state.config)#config.password_protected
+ of
true ->
- ", " ++
- translate:translate(Lang, "the password is") ++
- " '" ++
- (StateData#state.config)#config.password ++ "'";
- _ ->
- ""
- end ++
- case Reason of
- "" -> "";
- _ -> " (" ++ Reason ++ ") "
- end
- }]},
- Msg =
- {xmlelement, "message",
- [{"type", "normal"}],
- [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}], IEl ++ PasswdEl},
- {xmlelement, "x",
- [{"xmlns", ?NS_XCONFERENCE},
- {"jid", jlib:jid_to_string(
- {StateData#state.room,
- StateData#state.host,
- ""})}],
- [{xmlcdata, Reason}]},
- Body]},
- ejabberd_router:route(StateData#state.jid, JID, Msg),
- JID
+ [#xmlel{name = <<"password">>, attrs = [],
+ children =
+ [{xmlcdata,
+ (StateData#state.config)#config.password}]}];
+ _ -> []
+ end,
+ Body = #xmlel{name = <<"body">>, attrs = [],
+ children =
+ [{xmlcdata,
+ iolist_to_binary(
+ [io_lib:format(
+ translate:translate(
+ Lang,
+ <<"~s invites you to the room ~s">>),
+ [jlib:jid_to_string(From),
+ jlib:jid_to_string({StateData#state.room,
+ StateData#state.host,
+ <<"">>})]),
+
+ case
+ (StateData#state.config)#config.password_protected
+ of
+ true ->
+ <<", ",
+ (translate:translate(Lang,
+ <<"the password is">>))/binary,
+ " '",
+ ((StateData#state.config)#config.password)/binary,
+ "'">>;
+ _ -> <<"">>
+ end
+ ,
+ case Reason of
+ <<"">> -> <<"">>;
+ _ -> <<" (", Reason/binary, ") ">>
+ end])}]},
+ Msg = #xmlel{name = <<"message">>,
+ attrs = [{<<"type">>, <<"normal">>}],
+ children =
+ [#xmlel{name = <<"x">>,
+ attrs = [{<<"xmlns">>, ?NS_MUC_USER}],
+ children = IEl ++ PasswdEl},
+ #xmlel{name = <<"x">>,
+ attrs =
+ [{<<"xmlns">>, ?NS_XCONFERENCE},
+ {<<"jid">>,
+ jlib:jid_to_string({StateData#state.room,
+ StateData#state.host,
+ <<"">>})}],
+ children = [{xmlcdata, Reason}]},
+ Body]},
+ ejabberd_router:route(StateData#state.jid, JID, Msg),
+ JID
end.
%% Handle a message sent to the room by a non-participant.
%% If it is a decline, send to the inviter.
%% Otherwise, an error message is sent to the sender.
-handle_roommessage_from_nonparticipant(Packet, Lang, StateData, From) ->
+handle_roommessage_from_nonparticipant(Packet, Lang,
+ StateData, From) ->
case catch check_decline_invitation(Packet) of
- {true, Decline_data} ->
- send_decline_invitation(Decline_data, StateData#state.jid, From);
- _ ->
- send_error_only_occupants(Packet, Lang, StateData#state.jid, From)
+ {true, Decline_data} ->
+ send_decline_invitation(Decline_data,
+ StateData#state.jid, From);
+ _ ->
+ send_error_only_occupants(Packet, Lang,
+ StateData#state.jid, From)
end.
%% Check in the packet is a decline.
@@ -4003,56 +4352,63 @@ handle_roommessage_from_nonparticipant(Packet, Lang, StateData, From) ->
%% This function must be catched,
%% because it crashes when the packet is not a decline message.
check_decline_invitation(Packet) ->
- {xmlelement, "message", _, _} = Packet,
- XEl = xml:get_subtag(Packet, "x"),
- ?NS_MUC_USER = xml:get_tag_attr_s("xmlns", XEl),
- DEl = xml:get_subtag(XEl, "decline"),
- ToString = xml:get_tag_attr_s("to", DEl),
+ #xmlel{name = <<"message">>} = Packet,
+ XEl = xml:get_subtag(Packet, <<"x">>),
+ (?NS_MUC_USER) = xml:get_tag_attr_s(<<"xmlns">>, XEl),
+ DEl = xml:get_subtag(XEl, <<"decline">>),
+ ToString = xml:get_tag_attr_s(<<"to">>, DEl),
ToJID = jlib:string_to_jid(ToString),
{true, {Packet, XEl, DEl, ToJID}}.
%% Send the decline to the inviter user.
%% The original stanza must be slightly modified.
-send_decline_invitation({Packet, XEl, DEl, ToJID}, RoomJID, FromJID) ->
- FromString = jlib:jid_to_string(jlib:jid_remove_resource(FromJID)),
- {xmlelement, "decline", DAttrs, DEls} = DEl,
- DAttrs2 = lists:keydelete("to", 1, DAttrs),
- DAttrs3 = [{"from", FromString} | DAttrs2],
- DEl2 = {xmlelement, "decline", DAttrs3, DEls},
+send_decline_invitation({Packet, XEl, DEl, ToJID},
+ RoomJID, FromJID) ->
+ FromString =
+ jlib:jid_to_string(jlib:jid_remove_resource(FromJID)),
+ #xmlel{name = <<"decline">>, attrs = DAttrs,
+ children = DEls} =
+ DEl,
+ DAttrs2 = lists:keydelete(<<"to">>, 1, DAttrs),
+ DAttrs3 = [{<<"from">>, FromString} | DAttrs2],
+ DEl2 = #xmlel{name = <<"decline">>, attrs = DAttrs3,
+ children = DEls},
XEl2 = replace_subelement(XEl, DEl2),
Packet2 = replace_subelement(Packet, XEl2),
ejabberd_router:route(RoomJID, ToJID, Packet2).
%% Given an element and a new subelement,
%% replace the instance of the subelement in element with the new subelement.
-replace_subelement({xmlelement, Name, Attrs, SubEls}, NewSubEl) ->
+replace_subelement(#xmlel{name = Name, attrs = Attrs,
+ children = SubEls},
+ NewSubEl) ->
{_, NameNewSubEl, _, _} = NewSubEl,
SubEls2 = lists:keyreplace(NameNewSubEl, 2, SubEls, NewSubEl),
- {xmlelement, Name, Attrs, SubEls2}.
+ #xmlel{name = Name, attrs = Attrs, children = SubEls2}.
send_error_only_occupants(Packet, Lang, RoomJID, From) ->
- ErrText = "Only occupants are allowed to send messages to the conference",
- Err = jlib:make_error_reply(Packet, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)),
+ ErrText =
+ <<"Only occupants are allowed to send messages "
+ "to the conference">>,
+ Err = jlib:make_error_reply(Packet,
+ ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)),
ejabberd_router:route(RoomJID, From, Err).
-
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Logging
add_to_log(Type, Data, StateData)
- when Type == roomconfig_change_disabledlogging ->
- %% When logging is disabled, the config change message must be logged:
- mod_muc_log:add_to_log(
- StateData#state.server_host, roomconfig_change, Data,
- StateData#state.jid, make_opts(StateData));
+ when Type == roomconfig_change_disabledlogging ->
+ mod_muc_log:add_to_log(StateData#state.server_host,
+ roomconfig_change, Data, StateData#state.jid,
+ make_opts(StateData));
add_to_log(Type, Data, StateData) ->
case (StateData#state.config)#config.logging of
- true ->
- mod_muc_log:add_to_log(
- StateData#state.server_host, Type, Data,
- StateData#state.jid, make_opts(StateData));
- false ->
- ok
+ true ->
+ mod_muc_log:add_to_log(StateData#state.server_host,
+ Type, Data, StateData#state.jid,
+ make_opts(StateData));
+ false -> ok
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -4063,31 +4419,28 @@ tab_add_online_user(JID, StateData) ->
US = {LUser, LServer},
Room = StateData#state.room,
Host = StateData#state.host,
- catch ets:insert(
- muc_online_users,
- #muc_online_users{us = US, resource = LResource, room = Room, host = Host}).
-
+ catch ets:insert(muc_online_users,
+ #muc_online_users{us = US, resource = LResource,
+ room = Room, host = Host}).
tab_remove_online_user(JID, StateData) ->
{LUser, LServer, LResource} = jlib:jid_tolower(JID),
US = {LUser, LServer},
Room = StateData#state.room,
Host = StateData#state.host,
- catch ets:delete_object(
- muc_online_users,
- #muc_online_users{us = US, resource = LResource, room = Room, host = Host}).
+ catch ets:delete_object(muc_online_users,
+ #muc_online_users{us = US, resource = LResource,
+ room = Room, host = Host}).
tab_count_user(JID) ->
{LUser, LServer, _} = jlib:jid_tolower(JID),
US = {LUser, LServer},
- case catch ets:select(
- muc_online_users,
- [{#muc_online_users{us = US, _ = '_'}, [], [[]]}]) of
- Res when is_list(Res) ->
- length(Res);
- _ ->
- 0
+ case catch ets:select(muc_online_users,
+ [{#muc_online_users{us = US, _ = '_'}, [], [[]]}])
+ of
+ Res when is_list(Res) -> length(Res);
+ _ -> 0
end.
element_size(El) ->
- size(xml:element_to_binary(El)).
+ byte_size(xml:element_to_binary(El)).
diff --git a/src/mod_muc/mod_muc_room.hrl b/src/mod_muc/mod_muc_room.hrl
index 523df3463..3bbc9318e 100644
--- a/src/mod_muc/mod_muc_room.hrl
+++ b/src/mod_muc/mod_muc_room.hrl
@@ -22,69 +22,95 @@
-define(MAX_USERS_DEFAULT, 200).
-define(SETS, gb_sets).
+
-define(DICT, dict).
--record(lqueue, {queue, len, max}).
+-record(lqueue,
+{
+ queue :: queue(),
+ len :: integer(),
+ max :: integer()
+}).
+
+-type lqueue() :: #lqueue{}.
+
+-record(config,
+{
+ title = <<"">> :: binary(),
+ description = <<"">> :: binary(),
+ allow_change_subj = true :: boolean(),
+ allow_query_users = true :: boolean(),
+ allow_private_messages = true :: boolean(),
+ allow_private_messages_from_visitors = anyone :: anyone | moderators | nobody ,
+ allow_visitor_status = true :: boolean(),
+ allow_visitor_nickchange = true :: boolean(),
+ public = true :: boolean(),
+ public_list = true :: boolean(),
+ persistent = false :: boolean(),
+ moderated = true :: boolean(),
+ captcha_protected = false :: boolean(),
+ members_by_default = true :: boolean(),
+ members_only = false :: boolean(),
+ allow_user_invites = false :: boolean(),
+ password_protected = false :: boolean(),
+ password = <<"">> :: binary(),
+ anonymous = true :: boolean(),
+ allow_voice_requests = true :: boolean(),
+ voice_request_min_interval = 1800 :: non_neg_integer(),
+ max_users = ?MAX_USERS_DEFAULT :: non_neg_integer() | none,
+ logging = false :: boolean(),
+ captcha_whitelist = (?SETS):empty() :: gb_set()
+}).
+
+-type config() :: #config{}.
+
+-type role() :: moderator | participant | visitor | none.
+
+-record(user,
+{
+ jid :: jid(),
+ nick :: binary(),
+ role :: role(),
+ last_presence :: xmlel()
+}).
--record(config, {title = "",
- description = "",
- allow_change_subj = true,
- allow_query_users = true,
- allow_private_messages = true,
- allow_private_messages_from_visitors = anyone,
- allow_visitor_status = true,
- allow_visitor_nickchange = true,
- public = true,
- public_list = true,
- persistent = false,
- moderated = true,
- captcha_protected = false,
- members_by_default = true,
- members_only = false,
- allow_user_invites = false,
- password_protected = false,
- password = "",
- anonymous = true,
- allow_voice_requests = true,
- voice_request_min_interval = 1800,
- max_users = ?MAX_USERS_DEFAULT,
- logging = false,
- captcha_whitelist = ?SETS:empty()
- }).
+-record(activity,
+{
+ message_time = 0 :: integer(),
+ presence_time = 0 :: integer(),
+ message_shaper :: shaper:shaper(),
+ presence_shaper :: shaper:shaper(),
+ message :: xmlel(),
+ presence :: {binary(), xmlel()}
+}).
--record(user, {jid,
- nick,
- role,
- last_presence}).
+-record(state,
+{
+ room = <<"">> :: binary(),
+ host = <<"">> :: binary(),
+ server_host = <<"">> :: binary(),
+ access = {none,none,none,none} :: {atom(), atom(), atom(), atom()},
+ jid = #jid{} :: jid(),
+ config = #config{} :: config(),
+ users = (?DICT):new() :: dict(),
+ last_voice_request_time = treap:empty() :: treap:treap(),
+ robots = (?DICT):new() :: dict(),
+ nicks = (?DICT):new() :: dict(),
+ affiliations = (?DICT):new() :: dict(),
+ history :: lqueue(),
+ subject = <<"">> :: binary(),
+ subject_author = <<"">> :: binary(),
+ just_created = false :: boolean(),
+ activity = treap:empty() :: treap:treap(),
+ room_shaper = none :: shaper:shaper(),
+ room_queue = queue:new() :: queue()
+}).
--record(activity, {message_time = 0,
- presence_time = 0,
- message_shaper,
- presence_shaper,
- message,
- presence}).
+-record(muc_online_users, {us = {<<>>, <<>>} :: {binary(), binary()},
+ resource = <<>> :: binary() | '_',
+ room = <<>> :: binary() | '_',
+ host = <<>> :: binary() | '_'}).
--record(state, {room,
- host,
- server_host,
- mod,
- access,
- jid,
- config = #config{},
- users = ?DICT:new(),
- last_voice_request_time = treap:empty(),
- robots = ?DICT:new(),
- nicks = ?DICT:new(),
- affiliations = ?DICT:new(),
- history,
- subject = "",
- subject_author = "",
- just_created = false,
- activity = treap:empty(),
- room_shaper,
- room_queue = queue:new()}).
+-type muc_online_users() :: #muc_online_users{}.
--record(muc_online_users, {us,
- resource,
- room,
- host}).
+-type muc_room_state() :: #state{}.
diff --git a/src/mod_offline.erl b/src/mod_offline.erl
index 77fbfcedd..a1bf3da04 100644
--- a/src/mod_offline.erl
+++ b/src/mod_offline.erl
@@ -25,6 +25,7 @@
%%%----------------------------------------------------------------------
-module(mod_offline).
+
-author('alexey@process-one.net').
-behaviour(gen_mod).
@@ -47,13 +48,23 @@
webadmin_user_parse_query/5]).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
+
-include("web/ejabberd_http.hrl").
+
-include("web/ejabberd_web_admin.hrl").
--record(offline_msg, {us, timestamp, expire, from, to, packet}).
+-record(offline_msg,
+ {us = {<<"">>, <<"">>} :: {binary(), binary()},
+ timestamp = now() :: erlang:timestamp() | '_',
+ expire = now() :: erlang:timestamp() | never | '_',
+ from = #jid{} :: jid() | '_',
+ to = #jid{} :: jid() | '_',
+ packet = #xmlel{} :: xmlel() | '_'}).
-define(PROCNAME, ejabberd_offline).
+
-define(OFFLINE_TABLE_LOCK_THRESHOLD, 1000).
%% default value for the maximum number of user messages
@@ -61,18 +72,15 @@
start(Host, Opts) ->
case gen_mod:db_type(Opts) of
- mnesia ->
- mnesia:create_table(offline_msg,
- [{disc_only_copies, [node()]},
- {type, bag},
- {attributes,
- record_info(fields, offline_msg)}]),
- update_table();
- _ ->
- ok
+ mnesia ->
+ mnesia:create_table(offline_msg,
+ [{disc_only_copies, [node()]}, {type, bag},
+ {attributes, record_info(fields, offline_msg)}]),
+ update_table();
+ _ -> ok
end,
- ejabberd_hooks:add(offline_message_hook, Host,
- ?MODULE, store_packet, 50),
+ ejabberd_hooks:add(offline_message_hook, Host, ?MODULE,
+ store_packet, 50),
ejabberd_hooks:add(resend_offline_messages_hook, Host,
?MODULE, pop_offline_messages, 50),
ejabberd_hooks:add(remove_user, Host,
@@ -89,7 +97,7 @@ start(Host, Opts) ->
?MODULE, webadmin_user, 50),
ejabberd_hooks:add(webadmin_user_parse_query, Host,
?MODULE, webadmin_user_parse_query, 50),
- AccessMaxOfflineMsgs = gen_mod:get_opt(access_max_user_messages, Opts, max_user_offline_messages),
+ AccessMaxOfflineMsgs = gen_mod:get_opt(access_max_user_messages, Opts, fun(A) -> A end, max_user_offline_messages),
register(gen_mod:get_module_proc(Host, ?PROCNAME),
spawn(?MODULE, loop, [Host, AccessMaxOfflineMsgs])).
@@ -107,78 +115,68 @@ loop(Host, AccessMaxOfflineMsgs) ->
loop(Host, AccessMaxOfflineMsgs)
end.
-store_offline_msg(_Host, US, Msgs, Len, MaxOfflineMsgs, mnesia) ->
- F = fun() ->
- %% Only count messages if needed:
- Count = if MaxOfflineMsgs =/= infinity ->
- Len + p1_mnesia:count_records(
- offline_msg,
- #offline_msg{us=US, _='_'});
- true ->
- 0
- end,
- if
- Count > MaxOfflineMsgs ->
- discard_warn_sender(Msgs);
- true ->
- if
- Len >= ?OFFLINE_TABLE_LOCK_THRESHOLD ->
- mnesia:write_lock_table(offline_msg);
- true ->
- ok
- end,
- lists:foreach(fun(M) ->
- mnesia:write(M)
- end, Msgs)
- end
- end,
+store_offline_msg(_Host, US, Msgs, Len, MaxOfflineMsgs,
+ mnesia) ->
+ F = fun () ->
+ Count = if MaxOfflineMsgs =/= infinity ->
+ Len +
+ p1_mnesia:count_records(offline_msg,
+ #offline_msg{us = US,
+ _ = '_'});
+ true -> 0
+ end,
+ if Count > MaxOfflineMsgs -> discard_warn_sender(Msgs);
+ true ->
+ if Len >= (?OFFLINE_TABLE_LOCK_THRESHOLD) ->
+ mnesia:write_lock_table(offline_msg);
+ true -> ok
+ end,
+ lists:foreach(fun (M) -> mnesia:write(M) end, Msgs)
+ end
+ end,
mnesia:transaction(F);
store_offline_msg(Host, {User, _Server}, Msgs, Len, MaxOfflineMsgs, odbc) ->
Count = if MaxOfflineMsgs =/= infinity ->
- Len + count_offline_messages(User, Host);
- true -> 0
- end,
- if
- Count > MaxOfflineMsgs ->
- discard_warn_sender(Msgs);
- true ->
- Query = lists:map(
- fun(M) ->
- Username =
- ejabberd_odbc:escape(
- (M#offline_msg.to)#jid.luser),
- From = M#offline_msg.from,
- To = M#offline_msg.to,
- {xmlelement, Name, Attrs, Els} =
- M#offline_msg.packet,
- Attrs2 = jlib:replace_from_to_attrs(
- jlib:jid_to_string(From),
- jlib:jid_to_string(To),
- Attrs),
- Packet = {xmlelement, Name, Attrs2,
- Els ++
- [jlib:timestamp_to_xml(
- calendar:now_to_universal_time(
- M#offline_msg.timestamp),
- utc,
- jlib:make_jid("", Host, ""),
- "Offline Storage"),
- %% TODO: Delete the next three lines once XEP-0091 is Obsolete
- jlib:timestamp_to_xml(
- calendar:now_to_universal_time(
- M#offline_msg.timestamp))]},
- XML =
- ejabberd_odbc:escape(
- xml:element_to_binary(Packet)),
- odbc_queries:add_spool_sql(Username, XML)
- end, Msgs),
- odbc_queries:add_spool(Host, Query)
+ Len + count_offline_messages(User, Host);
+ true -> 0
+ end,
+ if Count > MaxOfflineMsgs -> discard_warn_sender(Msgs);
+ true ->
+ Query = lists:map(fun (M) ->
+ Username =
+ ejabberd_odbc:escape((M#offline_msg.to)#jid.luser),
+ From = M#offline_msg.from,
+ To = M#offline_msg.to,
+ #xmlel{name = Name, attrs = Attrs,
+ children = Els} =
+ M#offline_msg.packet,
+ Attrs2 =
+ jlib:replace_from_to_attrs(jlib:jid_to_string(From),
+ jlib:jid_to_string(To),
+ Attrs),
+ Packet = #xmlel{name = Name,
+ attrs = Attrs2,
+ children =
+ Els ++
+ [jlib:timestamp_to_xml(calendar:now_to_universal_time(M#offline_msg.timestamp),
+ utc,
+ jlib:make_jid(<<"">>,
+ Host,
+ <<"">>),
+ <<"Offline Storage">>),
+ jlib:timestamp_to_xml(calendar:now_to_universal_time(M#offline_msg.timestamp))]},
+ XML =
+ ejabberd_odbc:escape(xml:element_to_binary(Packet)),
+ odbc_queries:add_spool_sql(Username, XML)
+ end,
+ Msgs),
+ odbc_queries:add_spool(Host, Query)
end.
%% Function copied from ejabberd_sm.erl:
get_max_user_messages(AccessRule, {User, Server}, Host) ->
case acl:match_rule(
- Host, AccessRule, jlib:make_jid(User, Server, "")) of
+ Host, AccessRule, jlib:make_jid(User, Server, <<"">>)) of
Max when is_integer(Max) -> Max;
infinity -> infinity;
_ -> ?MAX_USER_MESSAGES
@@ -186,32 +184,22 @@ get_max_user_messages(AccessRule, {User, Server}, Host) ->
receive_all(US, Msgs, DBType) ->
receive
- #offline_msg{us=US} = Msg ->
- receive_all(US, [Msg | Msgs], DBType)
- after 0 ->
- %% FIXME: the diff between mnesia and odbc version:
- %%
- %% after 0 ->
- %% - Msgs
- %% + lists:reverse(Msgs)
- %% end.
- %%
- %% Is it a bug in mnesia version?
- case DBType of
- mnesia ->
- Msgs;
- odbc ->
- lists:reverse(Msgs)
- end
+ #offline_msg{us = US} = Msg ->
+ receive_all(US, [Msg | Msgs], DBType)
+ after 0 ->
+ case DBType of
+ mnesia -> Msgs;
+ odbc -> lists:reverse(Msgs)
+ end
end.
stop(Host) ->
ejabberd_hooks:delete(offline_message_hook, Host,
?MODULE, store_packet, 50),
- ejabberd_hooks:delete(resend_offline_messages_hook, Host,
- ?MODULE, pop_offline_messages, 50),
- ejabberd_hooks:delete(remove_user, Host,
- ?MODULE, remove_user, 50),
+ ejabberd_hooks:delete(resend_offline_messages_hook,
+ Host, ?MODULE, pop_offline_messages, 50),
+ ejabberd_hooks:delete(remove_user, Host, ?MODULE,
+ remove_user, 50),
ejabberd_hooks:delete(anonymous_purge_hook, Host,
?MODULE, remove_user, 50),
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, get_sm_features, 50),
@@ -221,7 +209,7 @@ stop(Host) ->
ejabberd_hooks:delete(webadmin_user, Host,
?MODULE, webadmin_user, 50),
ejabberd_hooks:delete(webadmin_user_parse_query, Host,
- ?MODULE, webadmin_user_parse_query, 50),
+ ?MODULE, webadmin_user_parse_query, 50),
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
exit(whereis(Proc), stop),
{wait, Proc}.
@@ -242,256 +230,206 @@ get_sm_features(Acc, _From, _To, _Node, _Lang) ->
store_packet(From, To, Packet) ->
- Type = xml:get_tag_attr_s("type", Packet),
- if
- (Type /= "error") and (Type /= "groupchat") and
- (Type /= "headline") ->
- case check_event_chatstates(From, To, Packet) of
- true ->
- #jid{luser = LUser, lserver = LServer} = To,
- TimeStamp = now(),
- {xmlelement, _Name, _Attrs, Els} = Packet,
- Expire = find_x_expire(TimeStamp, Els),
- gen_mod:get_module_proc(To#jid.lserver, ?PROCNAME) !
- #offline_msg{us = {LUser, LServer},
- timestamp = TimeStamp,
- expire = Expire,
- from = From,
- to = To,
- packet = Packet},
- stop;
- _ ->
- ok
- end;
- true ->
- ok
+ Type = xml:get_tag_attr_s(<<"type">>, Packet),
+ if (Type /= <<"error">>) and (Type /= <<"groupchat">>)
+ and (Type /= <<"headline">>) ->
+ case check_event(From, To, Packet) of
+ true ->
+ #jid{luser = LUser, lserver = LServer} = To,
+ TimeStamp = now(),
+ #xmlel{children = Els} = Packet,
+ Expire = find_x_expire(TimeStamp, Els),
+ gen_mod:get_module_proc(To#jid.lserver, ?PROCNAME) !
+ #offline_msg{us = {LUser, LServer},
+ timestamp = TimeStamp, expire = Expire,
+ from = From, to = To, packet = Packet},
+ stop;
+ _ -> ok
+ end;
+ true -> ok
end.
%% Check if the packet has any content about XEP-0022 or XEP-0085
-check_event_chatstates(From, To, Packet) ->
- {xmlelement, Name, Attrs, Els} = Packet,
- case find_x_event_chatstates(Els, {false, false, false}) of
- %% There wasn't any x:event or chatstates subelements
- {false, false, _} ->
- true;
- %% There a chatstates subelement and other stuff, but no x:event
- {false, CEl, true} when CEl /= false ->
- true;
- %% There was only a subelement: a chatstates
- {false, CEl, false} when CEl /= false ->
- %% Don't allow offline storage
- false;
- %% There was an x:event element, and maybe also other stuff
- {El, _, _} when El /= false ->
- case xml:get_subtag(El, "id") of
- false ->
- case xml:get_subtag(El, "offline") of
- false ->
- true;
- _ ->
- ID = case xml:get_tag_attr_s("id", Packet) of
- "" ->
- {xmlelement, "id", [], []};
- S ->
- {xmlelement, "id", [],
- [{xmlcdata, S}]}
- end,
- ejabberd_router:route(
- To, From, {xmlelement, Name, Attrs,
- [{xmlelement, "x",
- [{"xmlns", ?NS_EVENT}],
- [ID,
- {xmlelement, "offline", [], []}]}]
- }),
- true
- end;
- _ ->
- false
- end
+check_event(From, To, Packet) ->
+ #xmlel{name = Name, attrs = Attrs, children = Els} =
+ Packet,
+ case find_x_event(Els) of
+ false -> true;
+ El ->
+ case xml:get_subtag(El, <<"id">>) of
+ false ->
+ case xml:get_subtag(El, <<"offline">>) of
+ false -> true;
+ _ ->
+ ID = case xml:get_tag_attr_s(<<"id">>, Packet) of
+ <<"">> ->
+ #xmlel{name = <<"id">>, attrs = [],
+ children = []};
+ S ->
+ #xmlel{name = <<"id">>, attrs = [],
+ children = [{xmlcdata, S}]}
+ end,
+ ejabberd_router:route(To, From,
+ #xmlel{name = Name, attrs = Attrs,
+ children =
+ [#xmlel{name = <<"x">>,
+ attrs =
+ [{<<"xmlns">>,
+ ?NS_EVENT}],
+ children =
+ [ID,
+ #xmlel{name
+ =
+ <<"offline">>,
+ attrs
+ =
+ [],
+ children
+ =
+ []}]}]}),
+ true
+ end;
+ _ -> false
+ end
end.
%% Check if the packet has subelements about XEP-0022, XEP-0085 or other
-find_x_event_chatstates([], Res) ->
- Res;
-find_x_event_chatstates([{xmlcdata, _} | Els], Res) ->
- find_x_event_chatstates(Els, Res);
-find_x_event_chatstates([El | Els], {A, B, C}) ->
- case xml:get_tag_attr_s("xmlns", El) of
- ?NS_EVENT ->
- find_x_event_chatstates(Els, {El, B, C});
- ?NS_CHATSTATES ->
- find_x_event_chatstates(Els, {A, El, C});
- _ ->
- find_x_event_chatstates(Els, {A, B, true})
+find_x_event([]) -> false;
+find_x_event([{xmlcdata, _} | Els]) ->
+ find_x_event(Els);
+find_x_event([El | Els]) ->
+ case xml:get_tag_attr_s(<<"xmlns">>, El) of
+ ?NS_EVENT -> El;
+ _ -> find_x_event(Els)
end.
-find_x_expire(_, []) ->
- never;
+find_x_expire(_, []) -> never;
find_x_expire(TimeStamp, [{xmlcdata, _} | Els]) ->
find_x_expire(TimeStamp, Els);
find_x_expire(TimeStamp, [El | Els]) ->
- case xml:get_tag_attr_s("xmlns", El) of
- ?NS_EXPIRE ->
- Val = xml:get_tag_attr_s("seconds", El),
- case catch list_to_integer(Val) of
- {'EXIT', _} ->
- never;
- Int when Int > 0 ->
- {MegaSecs, Secs, MicroSecs} = TimeStamp,
- S = MegaSecs * 1000000 + Secs + Int,
- MegaSecs1 = S div 1000000,
- Secs1 = S rem 1000000,
- {MegaSecs1, Secs1, MicroSecs};
- _ ->
- never
- end;
- _ ->
- find_x_expire(TimeStamp, Els)
+ case xml:get_tag_attr_s(<<"xmlns">>, El) of
+ ?NS_EXPIRE ->
+ Val = xml:get_tag_attr_s(<<"seconds">>, El),
+ case catch jlib:binary_to_integer(Val) of
+ {'EXIT', _} -> never;
+ Int when Int > 0 ->
+ {MegaSecs, Secs, MicroSecs} = TimeStamp,
+ S = MegaSecs * 1000000 + Secs + Int,
+ MegaSecs1 = S div 1000000,
+ Secs1 = S rem 1000000,
+ {MegaSecs1, Secs1, MicroSecs};
+ _ -> never
+ end;
+ _ -> find_x_expire(TimeStamp, Els)
end.
-
resend_offline_messages(User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
- F = fun() ->
+ F = fun () ->
Rs = mnesia:wread({offline_msg, US}),
mnesia:delete({offline_msg, US}),
Rs
end,
case mnesia:transaction(F) of
- {atomic, Rs} ->
- lists:foreach(
- fun(R) ->
- {xmlelement, Name, Attrs, Els} = R#offline_msg.packet,
- ejabberd_sm !
- {route,
- R#offline_msg.from,
- R#offline_msg.to,
- {xmlelement, Name, Attrs,
- Els ++
- [jlib:timestamp_to_xml(
- calendar:now_to_universal_time(
- R#offline_msg.timestamp),
- utc,
- jlib:make_jid("", Server, ""),
- "Offline Storage"),
- %% TODO: Delete the next three lines once XEP-0091 is Obsolete
- jlib:timestamp_to_xml(
- calendar:now_to_universal_time(
- R#offline_msg.timestamp))]}}
- end,
- lists:keysort(#offline_msg.timestamp, Rs));
- _ ->
- ok
+ {atomic, Rs} ->
+ lists:foreach(fun (R) ->
+ #xmlel{name = Name, attrs = Attrs,
+ children = Els} =
+ R#offline_msg.packet,
+ ejabberd_sm !
+ {route, R#offline_msg.from, R#offline_msg.to,
+ #xmlel{name = Name, attrs = Attrs,
+ children =
+ Els ++
+ [jlib:timestamp_to_xml(calendar:now_to_universal_time(R#offline_msg.timestamp))]}}
+ end,
+ lists:keysort(#offline_msg.timestamp, Rs));
+ _ -> ok
end.
pop_offline_messages(Ls, User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
pop_offline_messages(Ls, LUser, LServer,
- gen_mod:db_type(LServer, ?MODULE)).
+ gen_mod:db_type(LServer, ?MODULE)).
pop_offline_messages(Ls, LUser, LServer, mnesia) ->
US = {LUser, LServer},
- F = fun() ->
+ F = fun () ->
Rs = mnesia:wread({offline_msg, US}),
mnesia:delete({offline_msg, US}),
Rs
end,
case mnesia:transaction(F) of
- {atomic, Rs} ->
- TS = now(),
- Ls ++ lists:map(
- fun(R) ->
- {xmlelement, Name, Attrs, Els} = R#offline_msg.packet,
- {route,
- R#offline_msg.from,
- R#offline_msg.to,
- {xmlelement, Name, Attrs,
- Els ++
- [jlib:timestamp_to_xml(
- calendar:now_to_universal_time(
- R#offline_msg.timestamp),
- utc,
- jlib:make_jid("", LServer, ""),
- "Offline Storage"),
- %% TODO: Delete the next three lines once XEP-0091 is Obsolete
- jlib:timestamp_to_xml(
- calendar:now_to_universal_time(
- R#offline_msg.timestamp))]}}
- end,
- lists:filter(
- fun(R) ->
- case R#offline_msg.expire of
- never ->
- true;
- TimeStamp ->
- TS < TimeStamp
- end
+ {atomic, Rs} ->
+ TS = now(),
+ Ls ++
+ lists:map(fun (R) ->
+ offline_msg_to_route(LServer, R)
end,
- lists:keysort(#offline_msg.timestamp, Rs)));
- _ ->
- Ls
+ lists:filter(fun (R) ->
+ case R#offline_msg.expire of
+ never -> true;
+ TimeStamp -> TS < TimeStamp
+ end
+ end,
+ lists:keysort(#offline_msg.timestamp, Rs)));
+ _ -> Ls
end;
pop_offline_messages(Ls, LUser, LServer, odbc) ->
EUser = ejabberd_odbc:escape(LUser),
- case odbc_queries:get_and_del_spool_msg_t(LServer, EUser) of
- {atomic, {selected, ["username","xml"], Rs}} ->
- Ls ++ lists:flatmap(
- fun({_, XML}) ->
- case xml_stream:parse_element(XML) of
- {error, _Reason} ->
- [];
- El ->
- To = jlib:string_to_jid(
- xml:get_tag_attr_s("to", El)),
- From = jlib:string_to_jid(
- xml:get_tag_attr_s("from", El)),
- if
- (To /= error) and
- (From /= error) ->
- [{route, From, To, El}];
- true ->
- []
- end
- end
- end, Rs);
- _ ->
- Ls
+ case odbc_queries:get_and_del_spool_msg_t(LServer,
+ EUser)
+ of
+ {atomic, {selected, [<<"username">>, <<"xml">>], Rs}} ->
+ Ls ++
+ lists:flatmap(fun ([_, XML]) ->
+ case xml_stream:parse_element(XML) of
+ {error, _Reason} ->
+ [];
+ El ->
+ case offline_msg_to_route(LServer, El) of
+ error ->
+ [];
+ RouteMsg ->
+ [RouteMsg]
+ end
+ end
+ end,
+ Rs);
+ _ -> Ls
end.
remove_expired_messages(Server) ->
LServer = jlib:nameprep(Server),
- remove_expired_messages(LServer, gen_mod:db_type(LServer, ?MODULE)).
+ remove_expired_messages(LServer,
+ gen_mod:db_type(LServer, ?MODULE)).
remove_expired_messages(_LServer, mnesia) ->
TimeStamp = now(),
- F = fun() ->
+ F = fun () ->
mnesia:write_lock_table(offline_msg),
- mnesia:foldl(
- fun(Rec, _Acc) ->
- case Rec#offline_msg.expire of
- never ->
- ok;
- TS ->
- if
- TS < TimeStamp ->
- mnesia:delete_object(Rec);
- true ->
- ok
- end
- end
- end, ok, offline_msg)
+ mnesia:foldl(fun (Rec, _Acc) ->
+ case Rec#offline_msg.expire of
+ never -> ok;
+ TS ->
+ if TS < TimeStamp ->
+ mnesia:delete_object(Rec);
+ true -> ok
+ end
+ end
+ end,
+ ok, offline_msg)
end,
mnesia:transaction(F);
-remove_expired_messages(_LServer, odbc) ->
- %% TODO
- {atomic, ok}.
+remove_expired_messages(_LServer, odbc) -> {atomic, ok}.
remove_old_messages(Days, Server) ->
LServer = jlib:nameprep(Server),
- remove_old_messages(Days, LServer, gen_mod:db_type(LServer, ?MODULE)).
+ remove_old_messages(Days, LServer,
+ gen_mod:db_type(LServer, ?MODULE)).
remove_old_messages(Days, _LServer, mnesia) ->
{MegaSecs, Secs, _MicroSecs} = now(),
@@ -499,401 +437,393 @@ remove_old_messages(Days, _LServer, mnesia) ->
MegaSecs1 = S div 1000000,
Secs1 = S rem 1000000,
TimeStamp = {MegaSecs1, Secs1, 0},
- F = fun() ->
+ F = fun () ->
mnesia:write_lock_table(offline_msg),
- mnesia:foldl(
- fun(#offline_msg{timestamp = TS} = Rec, _Acc)
- when TS < TimeStamp ->
- mnesia:delete_object(Rec);
- (_Rec, _Acc) -> ok
- end, ok, offline_msg)
+ mnesia:foldl(fun (#offline_msg{timestamp = TS} = Rec,
+ _Acc)
+ when TS < TimeStamp ->
+ mnesia:delete_object(Rec);
+ (_Rec, _Acc) -> ok
+ end,
+ ok, offline_msg)
end,
mnesia:transaction(F);
remove_old_messages(_Days, _LServer, odbc) ->
- %% TODO
{atomic, ok}.
remove_user(User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
- remove_user(LUser, LServer, gen_mod:db_type(LServer, ?MODULE)).
+ remove_user(LUser, LServer,
+ gen_mod:db_type(LServer, ?MODULE)).
remove_user(LUser, LServer, mnesia) ->
US = {LUser, LServer},
- F = fun() ->
- mnesia:delete({offline_msg, US})
- end,
+ F = fun () -> mnesia:delete({offline_msg, US}) end,
mnesia:transaction(F);
remove_user(LUser, LServer, odbc) ->
Username = ejabberd_odbc:escape(LUser),
odbc_queries:del_spool_msg(LServer, Username).
+jid_to_binary(#jid{user = U, server = S, resource = R,
+ luser = LU, lserver = LS, lresource = LR}) ->
+ #jid{user = iolist_to_binary(U),
+ server = iolist_to_binary(S),
+ resource = iolist_to_binary(R),
+ luser = iolist_to_binary(LU),
+ lserver = iolist_to_binary(LS),
+ lresource = iolist_to_binary(LR)}.
+
update_table() ->
Fields = record_info(fields, offline_msg),
case mnesia:table_info(offline_msg, attributes) of
- Fields ->
- ok;
- [user, timestamp, expire, from, to, packet] ->
- ?INFO_MSG("Converting offline_msg table from "
- "{user, timestamp, expire, from, to, packet} format", []),
- Host = ?MYNAME,
- {atomic, ok} = mnesia:create_table(
- mod_offline_tmp_table,
- [{disc_only_copies, [node()]},
- {type, bag},
- {local_content, true},
- {record_name, offline_msg},
- {attributes, record_info(fields, offline_msg)}]),
- mnesia:transform_table(offline_msg, ignore, Fields),
- F1 = fun() ->
- mnesia:write_lock_table(mod_offline_tmp_table),
- mnesia:foldl(
- fun(#offline_msg{us = U} = R, _) ->
- mnesia:dirty_write(
- mod_offline_tmp_table,
- R#offline_msg{us = {U, Host}})
- end, ok, offline_msg)
- end,
- mnesia:transaction(F1),
- mnesia:clear_table(offline_msg),
- F2 = fun() ->
- mnesia:write_lock_table(offline_msg),
- mnesia:foldl(
- fun(R, _) ->
- mnesia:dirty_write(R)
- end, ok, mod_offline_tmp_table)
- end,
- mnesia:transaction(F2),
- mnesia:delete_table(mod_offline_tmp_table);
- [user, timestamp, from, to, packet] ->
- ?INFO_MSG("Converting offline_msg table from "
- "{user, timestamp, from, to, packet} format", []),
- Host = ?MYNAME,
- {atomic, ok} = mnesia:create_table(
- mod_offline_tmp_table,
- [{disc_only_copies, [node()]},
- {type, bag},
- {local_content, true},
- {record_name, offline_msg},
- {attributes, record_info(fields, offline_msg)}]),
- mnesia:transform_table(
- offline_msg,
- fun({_, U, TS, F, T, P}) ->
- {xmlelement, _Name, _Attrs, Els} = P,
- Expire = find_x_expire(TS, Els),
- #offline_msg{us = U,
- timestamp = TS,
- expire = Expire,
- from = F,
- to = T,
- packet = P}
- end, Fields),
- F1 = fun() ->
- mnesia:write_lock_table(mod_offline_tmp_table),
- mnesia:foldl(
- fun(#offline_msg{us = U} = R, _) ->
- mnesia:dirty_write(
- mod_offline_tmp_table,
- R#offline_msg{us = {U, Host}})
- end, ok, offline_msg)
- end,
- mnesia:transaction(F1),
- mnesia:clear_table(offline_msg),
- F2 = fun() ->
- mnesia:write_lock_table(offline_msg),
- mnesia:foldl(
- fun(R, _) ->
- mnesia:dirty_write(R)
- end, ok, mod_offline_tmp_table)
- end,
- mnesia:transaction(F2),
- mnesia:delete_table(mod_offline_tmp_table);
- _ ->
- ?INFO_MSG("Recreating offline_msg table", []),
- mnesia:transform_table(offline_msg, ignore, Fields)
+ Fields ->
+ ejabberd_config:convert_table_to_binary(
+ offline_msg, Fields, bag,
+ fun(#offline_msg{us = {U, _}}) -> U end,
+ fun(#offline_msg{us = {U, S},
+ from = From,
+ to = To,
+ packet = El} = R) ->
+ R#offline_msg{us = {iolist_to_binary(U),
+ iolist_to_binary(S)},
+ from = jid_to_binary(From),
+ to = jid_to_binary(To),
+ packet = xml:to_xmlel(El)}
+ end);
+ _ ->
+ ?INFO_MSG("Recreating offline_msg table", []),
+ mnesia:transform_table(offline_msg, ignore, Fields)
end.
-
%% Helper functions:
%% Warn senders that their messages have been discarded:
discard_warn_sender(Msgs) ->
- lists:foreach(
- fun(#offline_msg{from=From, to=To, packet=Packet}) ->
- ErrText = "Your contact offline message queue is full. The message has been discarded.",
- Lang = xml:get_tag_attr_s("xml:lang", Packet),
- Err = jlib:make_error_reply(
- Packet, ?ERRT_RESOURCE_CONSTRAINT(Lang, ErrText)),
- ejabberd_router:route(
- To,
- From, Err)
- end, Msgs).
-
+ lists:foreach(fun (#offline_msg{from = From, to = To,
+ packet = Packet}) ->
+ ErrText = <<"Your contact offline message queue is "
+ "full. The message has been discarded.">>,
+ Lang = xml:get_tag_attr_s(<<"xml:lang">>, Packet),
+ Err = jlib:make_error_reply(Packet,
+ ?ERRT_RESOURCE_CONSTRAINT(Lang,
+ ErrText)),
+ ejabberd_router:route(To, From, Err)
+ end,
+ Msgs).
webadmin_page(_, Host,
- #request{us = _US,
- path = ["user", U, "queue"],
- q = Query,
- lang = Lang} = _Request) ->
- Res = user_queue(U, Host, Query, Lang),
- {stop, Res};
-
+ #request{us = _US, path = [<<"user">>, U, <<"queue">>],
+ q = Query, lang = Lang} =
+ _Request) ->
+ Res = user_queue(U, Host, Query, Lang), {stop, Res};
webadmin_page(Acc, _, _) -> Acc.
+offline_msg_to_route(LServer, #offline_msg{} = R) ->
+ El = #xmlel{children = Els} = R#offline_msg.packet,
+ {route, R#offline_msg.from, R#offline_msg.to,
+ El#xmlel{children =
+ Els ++
+ [jlib:timestamp_to_xml(
+ calendar:now_to_universal_time(
+ R#offline_msg.timestamp),
+ utc,
+ jlib:make_jid(<<"">>, LServer, <<"">>),
+ <<"Offline Storage">>),
+ jlib:timestamp_to_xml(
+ calendar:now_to_universal_time(
+ R#offline_msg.timestamp))]}};
+offline_msg_to_route(_LServer, #xmlel{} = El) ->
+ To = jlib:string_to_jid(xml:get_tag_attr_s(<<"to">>, El)),
+ From = jlib:string_to_jid(xml:get_tag_attr_s(<<"from">>, El)),
+ if (To /= error) and (From /= error) ->
+ {route, From, To, El};
+ true ->
+ error
+ end.
+
read_all_msgs(LUser, LServer, mnesia) ->
US = {LUser, LServer},
lists:keysort(#offline_msg.timestamp,
- mnesia:dirty_read({offline_msg, US}));
+ mnesia:dirty_read({offline_msg, US}));
read_all_msgs(LUser, LServer, odbc) ->
Username = ejabberd_odbc:escape(LUser),
- case catch ejabberd_odbc:sql_query(
- LServer,
- ["select xml from spool"
- " where username='", Username, "'"
- " order by seq;"]) of
- {selected, ["username", "xml"], Rs} ->
- lists:flatmap(
- fun({XML}) ->
- case xml_stream:parse_element(XML) of
- {error, _Reason} ->
- [];
- El ->
- [El]
- end
- end, Rs);
- _ ->
- []
+ case catch ejabberd_odbc:sql_query(LServer,
+ [<<"select xml from spool where username='">>,
+ Username, <<"' order by seq;">>])
+ of
+ {selected, [<<"xml">>], Rs} ->
+ lists:flatmap(fun ([XML]) ->
+ case xml_stream:parse_element(XML) of
+ {error, _Reason} -> [];
+ El -> [El]
+ end
+ end,
+ Rs);
+ _ -> []
end.
format_user_queue(Msgs, mnesia) ->
- lists:map(
- fun(#offline_msg{timestamp = TimeStamp, from = From, to = To,
- packet = {xmlelement, Name, Attrs, Els}} = Msg) ->
- ID = jlib:encode_base64(binary_to_list(term_to_binary(Msg))),
- {{Year, Month, Day}, {Hour, Minute, Second}} =
- calendar:now_to_local_time(TimeStamp),
- Time = lists:flatten(
- io_lib:format(
- "~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
- [Year, Month, Day, Hour, Minute, Second])),
- SFrom = jlib:jid_to_string(From),
- STo = jlib:jid_to_string(To),
- Attrs2 = jlib:replace_from_to_attrs(SFrom, STo, Attrs),
- Packet = {xmlelement, Name, Attrs2, Els},
- FPacket = ejabberd_web_admin:pretty_print_xml(Packet),
- ?XE("tr",
- [?XAE("td", [{"class", "valign"}], [?INPUT("checkbox", "selected", ID)]),
- ?XAC("td", [{"class", "valign"}], Time),
- ?XAC("td", [{"class", "valign"}], SFrom),
- ?XAC("td", [{"class", "valign"}], STo),
- ?XAE("td", [{"class", "valign"}], [?XC("pre", FPacket)])]
- )
- end, Msgs);
+ lists:map(fun (#offline_msg{timestamp = TimeStamp,
+ from = From, to = To,
+ packet =
+ #xmlel{name = Name, attrs = Attrs,
+ children = Els}} =
+ Msg) ->
+ ID = jlib:encode_base64((term_to_binary(Msg))),
+ {{Year, Month, Day}, {Hour, Minute, Second}} =
+ calendar:now_to_local_time(TimeStamp),
+ Time =
+ iolist_to_binary(io_lib:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
+ [Year, Month, Day,
+ Hour, Minute,
+ Second])),
+ SFrom = jlib:jid_to_string(From),
+ STo = jlib:jid_to_string(To),
+ Attrs2 = jlib:replace_from_to_attrs(SFrom, STo, Attrs),
+ Packet = #xmlel{name = Name, attrs = Attrs2,
+ children = Els},
+ FPacket = ejabberd_web_admin:pretty_print_xml(Packet),
+ ?XE(<<"tr">>,
+ [?XAE(<<"td">>, [{<<"class">>, <<"valign">>}],
+ [?INPUT(<<"checkbox">>, <<"selected">>, ID)]),
+ ?XAC(<<"td">>, [{<<"class">>, <<"valign">>}], Time),
+ ?XAC(<<"td">>, [{<<"class">>, <<"valign">>}], SFrom),
+ ?XAC(<<"td">>, [{<<"class">>, <<"valign">>}], STo),
+ ?XAE(<<"td">>, [{<<"class">>, <<"valign">>}],
+ [?XC(<<"pre">>, FPacket)])])
+ end,
+ Msgs);
format_user_queue(Msgs, odbc) ->
- lists:map(
- fun({xmlelement, _Name, _Attrs, _Els} = Msg) ->
- ID = jlib:encode_base64(binary_to_list(term_to_binary(Msg))),
- Packet = Msg,
- FPacket = ejabberd_web_admin:pretty_print_xml(Packet),
- ?XE("tr",
- [?XAE("td", [{"class", "valign"}], [?INPUT("checkbox", "selected", ID)]),
- ?XAE("td", [{"class", "valign"}], [?XC("pre", FPacket)])]
- )
- end, Msgs).
+ lists:map(fun (#xmlel{} = Msg) ->
+ ID = jlib:encode_base64((term_to_binary(Msg))),
+ Packet = Msg,
+ FPacket = ejabberd_web_admin:pretty_print_xml(Packet),
+ ?XE(<<"tr">>,
+ [?XAE(<<"td">>, [{<<"class">>, <<"valign">>}],
+ [?INPUT(<<"checkbox">>, <<"selected">>, ID)]),
+ ?XAE(<<"td">>, [{<<"class">>, <<"valign">>}],
+ [?XC(<<"pre">>, FPacket)])])
+ end,
+ Msgs).
user_queue(User, Server, Query, Lang) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
DBType = gen_mod:db_type(LServer, ?MODULE),
- Res = user_queue_parse_query(LUser, LServer, Query, DBType),
+ Res = user_queue_parse_query(LUser, LServer, Query,
+ DBType),
MsgsAll = read_all_msgs(LUser, LServer, DBType),
- Msgs = get_messages_subset(User, Server, MsgsAll, DBType),
+ Msgs = get_messages_subset(US, Server, MsgsAll,
+ DBType),
FMsgs = format_user_queue(Msgs, DBType),
- [?XC("h1", io_lib:format(?T("~s's Offline Messages Queue"),
- [us_to_list(US)]))] ++
- case Res of
- ok -> [?XREST("Submitted")];
- nothing -> []
- end ++
- [?XAE("form", [{"action", ""}, {"method", "post"}],
- [?XE("table",
- [?XE("thead",
- [?XE("tr",
- [?X("td"),
- ?XCT("td", "Time"),
- ?XCT("td", "From"),
- ?XCT("td", "To"),
- ?XCT("td", "Packet")
- ])]),
- ?XE("tbody",
- if
- FMsgs == [] ->
- [?XE("tr",
- [?XAC("td", [{"colspan", "4"}], " ")]
- )];
- true ->
- FMsgs
- end
- )]),
+ [?XC(<<"h1">>,
+ list_to_binary(io_lib:format(?T(<<"~s's Offline Messages Queue">>),
+ [us_to_list(US)])))]
+ ++
+ case Res of
+ ok -> [?XREST(<<"Submitted">>)];
+ nothing -> []
+ end
+ ++
+ [?XAE(<<"form">>,
+ [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
+ [?XE(<<"table">>,
+ [?XE(<<"thead">>,
+ [?XE(<<"tr">>,
+ [?X(<<"td">>), ?XCT(<<"td">>, <<"Time">>),
+ ?XCT(<<"td">>, <<"From">>),
+ ?XCT(<<"td">>, <<"To">>),
+ ?XCT(<<"td">>, <<"Packet">>)])]),
+ ?XE(<<"tbody">>,
+ if FMsgs == [] ->
+ [?XE(<<"tr">>,
+ [?XAC(<<"td">>, [{<<"colspan">>, <<"4">>}],
+ <<" ">>)])];
+ true -> FMsgs
+ end)]),
?BR,
- ?INPUTT("submit", "delete", "Delete Selected")
- ])].
+ ?INPUTT(<<"submit">>, <<"delete">>,
+ <<"Delete Selected">>)])].
user_queue_parse_query(LUser, LServer, Query, mnesia) ->
US = {LUser, LServer},
- case lists:keysearch("delete", 1, Query) of
- {value, _} ->
- Msgs = lists:keysort(#offline_msg.timestamp,
- mnesia:dirty_read({offline_msg, US})),
- F = fun() ->
- lists:foreach(
- fun(Msg) ->
- ID = jlib:encode_base64(
- binary_to_list(term_to_binary(Msg))),
- case lists:member({"selected", ID}, Query) of
- true ->
- mnesia:delete_object(Msg);
- false ->
- ok
- end
- end, Msgs)
- end,
- mnesia:transaction(F),
- ok;
- false ->
- nothing
+ case lists:keysearch(<<"delete">>, 1, Query) of
+ {value, _} ->
+ Msgs = lists:keysort(#offline_msg.timestamp,
+ mnesia:dirty_read({offline_msg, US})),
+ F = fun () ->
+ lists:foreach(fun (Msg) ->
+ ID =
+ jlib:encode_base64((term_to_binary(Msg))),
+ case lists:member({<<"selected">>,
+ ID},
+ Query)
+ of
+ true -> mnesia:delete_object(Msg);
+ false -> ok
+ end
+ end,
+ Msgs)
+ end,
+ mnesia:transaction(F),
+ ok;
+ false -> nothing
end;
user_queue_parse_query(LUser, LServer, Query, odbc) ->
Username = ejabberd_odbc:escape(LUser),
- case lists:keysearch("delete", 1, Query) of
- {value, _} ->
- Msgs = case catch ejabberd_odbc:sql_query(
- LServer,
- ["select xml, seq from spool"
- " where username='", Username, "'"
- " order by seq;"]) of
- {selected, ["xml", "seq"], Rs} ->
- lists:flatmap(
- fun({XML, Seq}) ->
- case xml_stream:parse_element(XML) of
- {error, _Reason} ->
- [];
- El ->
- [{El, Seq}]
- end
- end, Rs);
- _ ->
- []
- end,
- F = fun() ->
- lists:foreach(
- fun({Msg, Seq}) ->
- ID = jlib:encode_base64(
- binary_to_list(term_to_binary(Msg))),
- case lists:member({"selected", ID}, Query) of
- true ->
- SSeq = ejabberd_odbc:escape(Seq),
- catch ejabberd_odbc:sql_query(
- LServer,
- ["delete from spool"
- " where username='", Username, "'"
- " and seq='", SSeq, "';"]);
- false ->
- ok
- end
- end, Msgs)
- end,
- mnesia:transaction(F),
- ok;
- false ->
- nothing
+ case lists:keysearch(<<"delete">>, 1, Query) of
+ {value, _} ->
+ Msgs = case catch ejabberd_odbc:sql_query(LServer,
+ [<<"select xml, seq from spool where username='">>,
+ Username,
+ <<"' order by seq;">>])
+ of
+ {selected, [<<"xml">>, <<"seq">>], Rs} ->
+ lists:flatmap(fun ([XML, Seq]) ->
+ case xml_stream:parse_element(XML)
+ of
+ {error, _Reason} -> [];
+ El -> [{El, Seq}]
+ end
+ end,
+ Rs);
+ _ -> []
+ end,
+ F = fun () ->
+ lists:foreach(fun ({Msg, Seq}) ->
+ ID =
+ jlib:encode_base64((term_to_binary(Msg))),
+ case lists:member({<<"selected">>,
+ ID},
+ Query)
+ of
+ true ->
+ SSeq =
+ ejabberd_odbc:escape(Seq),
+ catch
+ ejabberd_odbc:sql_query(LServer,
+ [<<"delete from spool where username='">>,
+ Username,
+ <<"' and seq='">>,
+ SSeq,
+ <<"';">>]);
+ false -> ok
+ end
+ end,
+ Msgs)
+ end,
+ mnesia:transaction(F),
+ ok;
+ false -> nothing
end.
us_to_list({User, Server}) ->
- jlib:jid_to_string({User, Server, ""}).
+ jlib:jid_to_string({User, Server, <<"">>}).
get_queue_length(LUser, LServer) ->
- get_queue_length(LUser, LServer, gen_mod:db_type(LServer, ?MODULE)).
+ get_queue_length(LUser, LServer,
+ gen_mod:db_type(LServer, ?MODULE)).
get_queue_length(LUser, LServer, mnesia) ->
- length(mnesia:dirty_read({offline_msg, {LUser, LServer}}));
+ length(mnesia:dirty_read({offline_msg,
+ {LUser, LServer}}));
get_queue_length(LUser, LServer, odbc) ->
Username = ejabberd_odbc:escape(LUser),
- case catch ejabberd_odbc:sql_query(
- LServer,
- ["select count(*) from spool"
- " where username='", Username, "';"]) of
- {selected, [_], [{SCount}]} ->
- list_to_integer(SCount);
- _ ->
- 0
+ case catch ejabberd_odbc:sql_query(LServer,
+ [<<"select count(*) from spool where username='">>,
+ Username, <<"';">>])
+ of
+ {selected, [_], [[SCount]]} ->
+ jlib:binary_to_integer(SCount);
+ _ -> 0
end.
get_messages_subset(User, Host, MsgsAll, DBType) ->
Access = gen_mod:get_module_opt(Host, ?MODULE, access_max_user_messages,
+ fun(A) when is_atom(A) -> A end,
max_user_offline_messages),
- MaxOfflineMsgs = case get_max_user_messages(Access, {User, Host}, Host) of
- Number when is_integer(Number) -> Number;
- _ -> 100
+ MaxOfflineMsgs = case get_max_user_messages(Access,
+ User, Host)
+ of
+ Number when is_integer(Number) -> Number;
+ _ -> 100
end,
Length = length(MsgsAll),
- get_messages_subset2(MaxOfflineMsgs, Length, MsgsAll, DBType).
+ get_messages_subset2(MaxOfflineMsgs, Length, MsgsAll,
+ DBType).
-get_messages_subset2(Max, Length, MsgsAll, _DBType) when Length =< Max*2 ->
+get_messages_subset2(Max, Length, MsgsAll, _DBType)
+ when Length =< Max * 2 ->
MsgsAll;
get_messages_subset2(Max, Length, MsgsAll, mnesia) ->
FirstN = Max,
{MsgsFirstN, Msgs2} = lists:split(FirstN, MsgsAll),
- MsgsLastN = lists:nthtail(Length - FirstN - FirstN, Msgs2),
- NoJID = jlib:make_jid("...", "...", ""),
- IntermediateMsg = #offline_msg{timestamp = now(), from = NoJID, to = NoJID,
- packet = {xmlelement, "...", [], []}},
+ MsgsLastN = lists:nthtail(Length - FirstN - FirstN,
+ Msgs2),
+ NoJID = jlib:make_jid(<<"...">>, <<"...">>, <<"">>),
+ IntermediateMsg = #offline_msg{timestamp = now(),
+ from = NoJID, to = NoJID,
+ packet =
+ #xmlel{name = <<"...">>, attrs = [],
+ children = []}},
MsgsFirstN ++ [IntermediateMsg] ++ MsgsLastN;
get_messages_subset2(Max, Length, MsgsAll, odbc) ->
FirstN = Max,
{MsgsFirstN, Msgs2} = lists:split(FirstN, MsgsAll),
- MsgsLastN = lists:nthtail(Length - FirstN - FirstN, Msgs2),
- IntermediateMsg = {xmlelement, "...", [], []},
+ MsgsLastN = lists:nthtail(Length - FirstN - FirstN,
+ Msgs2),
+ IntermediateMsg = #xmlel{name = <<"...">>, attrs = [],
+ children = []},
MsgsFirstN ++ [IntermediateMsg] ++ MsgsLastN.
webadmin_user(Acc, User, Server, Lang) ->
- QueueLen = get_queue_length(jlib:nodeprep(User), jlib:nameprep(Server)),
- FQueueLen = [?AC("queue/",
- integer_to_list(QueueLen))],
- Acc ++ [?XCT("h3", "Offline Messages:")] ++ FQueueLen ++ [?C(" "), ?INPUTT("submit", "removealloffline", "Remove All Offline Messages")].
+ QueueLen = get_queue_length(jlib:nodeprep(User),
+ jlib:nameprep(Server)),
+ FQueueLen = [?AC(<<"queue/">>,
+ (iolist_to_binary(integer_to_list(QueueLen))))],
+ Acc ++
+ [?XCT(<<"h3">>, <<"Offline Messages:">>)] ++
+ FQueueLen ++
+ [?C(<<" ">>),
+ ?INPUTT(<<"submit">>, <<"removealloffline">>,
+ <<"Remove All Offline Messages">>)].
delete_all_msgs(User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
- delete_all_msgs(LUser, LServer, gen_mod:db_type(LServer, ?MODULE)).
+ delete_all_msgs(LUser, LServer,
+ gen_mod:db_type(LServer, ?MODULE)).
delete_all_msgs(LUser, LServer, mnesia) ->
US = {LUser, LServer},
- F = fun() ->
- mnesia:write_lock_table(offline_msg),
- lists:foreach(
- fun(Msg) ->
- mnesia:delete_object(Msg)
- end, mnesia:dirty_read({offline_msg, US}))
- end,
+ F = fun () ->
+ mnesia:write_lock_table(offline_msg),
+ lists:foreach(fun (Msg) -> mnesia:delete_object(Msg)
+ end,
+ mnesia:dirty_read({offline_msg, US}))
+ end,
mnesia:transaction(F);
delete_all_msgs(LUser, LServer, odbc) ->
Username = ejabberd_odbc:escape(LUser),
odbc_queries:del_spool_msg(LServer, Username),
- %% TODO: process the output
{atomic, ok}.
-webadmin_user_parse_query(_, "removealloffline", User, Server, _Query) ->
+webadmin_user_parse_query(_, <<"removealloffline">>,
+ User, Server, _Query) ->
case delete_all_msgs(User, Server) of
- {aborted, Reason} ->
- ?ERROR_MSG("Failed to remove offline messages: ~p", [Reason]),
- {stop, error};
- {atomic, ok} ->
- ?INFO_MSG("Removed all offline messages for ~s@~s", [User, Server]),
- {stop, ok}
+ {aborted, Reason} ->
+ ?ERROR_MSG("Failed to remove offline messages: ~p",
+ [Reason]),
+ {stop, error};
+ {atomic, ok} ->
+ ?INFO_MSG("Removed all offline messages for ~s@~s",
+ [User, Server]),
+ {stop, ok}
end;
-webadmin_user_parse_query(Acc, _Action, _User, _Server, _Query) ->
+webadmin_user_parse_query(Acc, _Action, _User, _Server,
+ _Query) ->
Acc.
%% Returns as integer the number of offline messages for a given user
diff --git a/src/mod_ping.erl b/src/mod_ping.erl
index 184d19632..c3bfd7b32 100644
--- a/src/mod_ping.erl
+++ b/src/mod_ping.erl
@@ -25,18 +25,24 @@
%%%----------------------------------------------------------------------
-module(mod_ping).
+
-author('bjc@kublai.com').
-behavior(gen_mod).
+
-behavior(gen_server).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
-define(SUPERVISOR, ejabberd_sup).
--define(NS_PING, "urn:xmpp:ping").
--define(DEFAULT_SEND_PINGS, false). % bool()
--define(DEFAULT_PING_INTERVAL, 60). % seconds
+
+-define(NS_PING, <<"urn:xmpp:ping">>).
+
+-define(DEFAULT_SEND_PINGS, false).
+
+-define(DEFAULT_PING_INTERVAL, 60).
-define(DICT, dict).
@@ -47,24 +53,27 @@
-export([start/2, stop/1]).
%% gen_server callbacks
--export([init/1, terminate/2, handle_call/3, handle_cast/2,
- handle_info/2, code_change/3]).
+-export([init/1, terminate/2, handle_call/3,
+ handle_cast/2, handle_info/2, code_change/3]).
%% Hook callbacks
--export([iq_ping/3, user_online/3, user_offline/3, user_send/3]).
+-export([iq_ping/3, user_online/3, user_offline/3,
+ user_send/3]).
--record(state, {host = "",
- send_pings = ?DEFAULT_SEND_PINGS,
- ping_interval = ?DEFAULT_PING_INTERVAL,
- timeout_action = none,
- timers = ?DICT:new()}).
+-record(state,
+ {host = <<"">>,
+ send_pings = ?DEFAULT_SEND_PINGS :: boolean(),
+ ping_interval = ?DEFAULT_PING_INTERVAL :: non_neg_integer(),
+ timeout_action = none :: none | kill,
+ timers = (?DICT):new() :: dict()}).
%%====================================================================
%% API
%%====================================================================
start_link(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?MODULE),
- gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
+ gen_server:start_link({local, Proc}, ?MODULE,
+ [Host, Opts], []).
start_ping(Host, JID) ->
Proc = gen_mod:get_module_proc(Host, ?MODULE),
@@ -80,7 +89,7 @@ stop_ping(Host, JID) ->
start(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?MODULE),
PingSpec = {Proc, {?MODULE, start_link, [Host, Opts]},
- transient, 2000, worker, [?MODULE]},
+ transient, 2000, worker, [?MODULE]},
supervisor:start_child(?SUPERVISOR, PingSpec).
stop(Host) ->
@@ -92,43 +101,50 @@ stop(Host) ->
%% gen_server callbacks
%%====================================================================
init([Host, Opts]) ->
- SendPings = gen_mod:get_opt(send_pings, Opts, ?DEFAULT_SEND_PINGS),
- PingInterval = gen_mod:get_opt(ping_interval, Opts, ?DEFAULT_PING_INTERVAL),
- TimeoutAction = gen_mod:get_opt(timeout_action, Opts, none),
- IQDisc = gen_mod:get_opt(iqdisc, Opts, no_queue),
+ SendPings = gen_mod:get_opt(send_pings, Opts,
+ fun(B) when is_boolean(B) -> B end,
+ ?DEFAULT_SEND_PINGS),
+ PingInterval = gen_mod:get_opt(ping_interval, Opts,
+ fun(I) when is_integer(I), I>0 -> I end,
+ ?DEFAULT_PING_INTERVAL),
+ TimeoutAction = gen_mod:get_opt(timeout_action, Opts,
+ fun(none) -> none;
+ (kill) -> kill
+ end, none),
+ IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
+ no_queue),
mod_disco:register_feature(Host, ?NS_PING),
- gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_PING,
- ?MODULE, iq_ping, IQDisc),
- gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_PING,
- ?MODULE, iq_ping, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
+ ?NS_PING, ?MODULE, iq_ping, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_local, Host,
+ ?NS_PING, ?MODULE, iq_ping, IQDisc),
case SendPings of
- true ->
- %% Ping requests are sent to all entities, whether they
- %% announce 'urn:xmpp:ping' in their caps or not
- ejabberd_hooks:add(sm_register_connection_hook, Host,
- ?MODULE, user_online, 100),
- ejabberd_hooks:add(sm_remove_connection_hook, Host,
- ?MODULE, user_offline, 100),
- ejabberd_hooks:add(user_send_packet, Host,
- ?MODULE, user_send, 100);
- _ ->
- ok
+ true ->
+ ejabberd_hooks:add(sm_register_connection_hook, Host,
+ ?MODULE, user_online, 100),
+ ejabberd_hooks:add(sm_remove_connection_hook, Host,
+ ?MODULE, user_offline, 100),
+ ejabberd_hooks:add(user_send_packet, Host, ?MODULE,
+ user_send, 100);
+ _ -> ok
end,
- {ok, #state{host = Host,
- send_pings = SendPings,
- ping_interval = PingInterval,
- timeout_action = TimeoutAction,
- timers = ?DICT:new()}}.
+ {ok,
+ #state{host = Host, send_pings = SendPings,
+ ping_interval = PingInterval,
+ timeout_action = TimeoutAction,
+ timers = (?DICT):new()}}.
terminate(_Reason, #state{host = Host}) ->
ejabberd_hooks:delete(sm_remove_connection_hook, Host,
?MODULE, user_offline, 100),
ejabberd_hooks:delete(sm_register_connection_hook, Host,
?MODULE, user_online, 100),
- ejabberd_hooks:delete(user_send_packet, Host,
- ?MODULE, user_send, 100),
- gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_PING),
- gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_PING),
+ ejabberd_hooks:delete(user_send_packet, Host, ?MODULE,
+ user_send, 100),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, Host,
+ ?NS_PING),
+ gen_iq_handler:remove_iq_handler(ejabberd_sm, Host,
+ ?NS_PING),
mod_disco:unregister_feature(Host, ?NS_PING).
handle_call(stop, _From, State) ->
@@ -137,56 +153,60 @@ handle_call(_Req, _From, State) ->
{reply, {error, badarg}, State}.
handle_cast({start_ping, JID}, State) ->
- Timers = add_timer(JID, State#state.ping_interval, State#state.timers),
+ Timers = add_timer(JID, State#state.ping_interval,
+ State#state.timers),
{noreply, State#state{timers = Timers}};
handle_cast({stop_ping, JID}, State) ->
Timers = del_timer(JID, State#state.timers),
{noreply, State#state{timers = Timers}};
handle_cast({iq_pong, JID, timeout}, State) ->
Timers = del_timer(JID, State#state.timers),
- ejabberd_hooks:run(user_ping_timeout, State#state.host, [JID]),
+ ejabberd_hooks:run(user_ping_timeout, State#state.host,
+ [JID]),
case State#state.timeout_action of
- kill ->
- #jid{user = User, server = Server, resource = Resource} = JID,
- case ejabberd_sm:get_session_pid(User, Server, Resource) of
- Pid when is_pid(Pid) ->
- ejabberd_c2s:stop(Pid);
- _ ->
- ok
- end;
- _ ->
- ok
+ kill ->
+ #jid{user = User, server = Server,
+ resource = Resource} =
+ JID,
+ case ejabberd_sm:get_session_pid(User, Server, Resource)
+ of
+ Pid when is_pid(Pid) -> ejabberd_c2s:stop(Pid);
+ _ -> ok
+ end;
+ _ -> ok
end,
{noreply, State#state{timers = Timers}};
-handle_cast(_Msg, State) ->
- {noreply, State}.
+handle_cast(_Msg, State) -> {noreply, State}.
handle_info({timeout, _TRef, {ping, JID}}, State) ->
IQ = #iq{type = get,
- sub_el = [{xmlelement, "ping", [{"xmlns", ?NS_PING}], []}]},
+ sub_el =
+ [#xmlel{name = <<"ping">>,
+ attrs = [{<<"xmlns">>, ?NS_PING}], children = []}]},
Pid = self(),
- F = fun(Response) ->
+ F = fun (Response) ->
gen_server:cast(Pid, {iq_pong, JID, Response})
end,
- From = jlib:make_jid("", State#state.host, ""),
+ From = jlib:make_jid(<<"">>, State#state.host, <<"">>),
ejabberd_local:route_iq(From, JID, IQ, F),
- Timers = add_timer(JID, State#state.ping_interval, State#state.timers),
+ Timers = add_timer(JID, State#state.ping_interval,
+ State#state.timers),
{noreply, State#state{timers = Timers}};
-handle_info(_Info, State) ->
- {noreply, State}.
+handle_info(_Info, State) -> {noreply, State}.
-code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
+code_change(_OldVsn, State, _Extra) -> {ok, State}.
%%====================================================================
%% Hook callbacks
%%====================================================================
-iq_ping(_From, _To, #iq{type = Type, sub_el = SubEl} = IQ) ->
+iq_ping(_From, _To,
+ #iq{type = Type, sub_el = SubEl} = IQ) ->
case {Type, SubEl} of
- {get, {xmlelement, "ping", _, _}} ->
- IQ#iq{type = result, sub_el = []};
- _ ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_FEATURE_NOT_IMPLEMENTED]}
+ {get, #xmlel{name = <<"ping">>}} ->
+ IQ#iq{type = result, sub_el = []};
+ _ ->
+ IQ#iq{type = error,
+ sub_el = [SubEl, ?ERR_FEATURE_NOT_IMPLEMENTED]}
end.
user_online(_SID, JID, _Info) ->
@@ -203,35 +223,26 @@ user_send(JID, _From, _Packet) ->
%%====================================================================
add_timer(JID, Interval, Timers) ->
LJID = jlib:jid_tolower(JID),
- NewTimers = case ?DICT:find(LJID, Timers) of
- {ok, OldTRef} ->
- cancel_timer(OldTRef),
- ?DICT:erase(LJID, Timers);
- _ ->
- Timers
+ NewTimers = case (?DICT):find(LJID, Timers) of
+ {ok, OldTRef} ->
+ cancel_timer(OldTRef), (?DICT):erase(LJID, Timers);
+ _ -> Timers
end,
- TRef = erlang:start_timer(Interval * 1000, self(), {ping, JID}),
- ?DICT:store(LJID, TRef, NewTimers).
+ TRef = erlang:start_timer(Interval * 1000, self(),
+ {ping, JID}),
+ (?DICT):store(LJID, TRef, NewTimers).
del_timer(JID, Timers) ->
LJID = jlib:jid_tolower(JID),
- case ?DICT:find(LJID, Timers) of
- {ok, TRef} ->
- cancel_timer(TRef),
- ?DICT:erase(LJID, Timers);
- _ ->
- Timers
+ case (?DICT):find(LJID, Timers) of
+ {ok, TRef} ->
+ cancel_timer(TRef), (?DICT):erase(LJID, Timers);
+ _ -> Timers
end.
cancel_timer(TRef) ->
case erlang:cancel_timer(TRef) of
- false ->
- receive
- {timeout, TRef, _} ->
- ok
- after 0 ->
- ok
- end;
- _ ->
- ok
+ false ->
+ receive {timeout, TRef, _} -> ok after 0 -> ok end;
+ _ -> ok
end.
diff --git a/src/mod_pres_counter.erl b/src/mod_pres_counter.erl
index 314f05fbb..9246f8d55 100644
--- a/src/mod_pres_counter.erl
+++ b/src/mod_pres_counter.erl
@@ -28,18 +28,18 @@
-behavior(gen_mod).
--export([start/2,
- stop/1,
- check_packet/6]).
+-export([start/2, stop/1, check_packet/6]).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
--record(pres_counter, {dir, start, count, logged = false}).
+-record(pres_counter,
+ {dir, start, count, logged = false}).
start(Host, _Opts) ->
- ejabberd_hooks:add(privacy_check_packet, Host,
- ?MODULE, check_packet, 25),
+ ejabberd_hooks:add(privacy_check_packet, Host, ?MODULE,
+ check_packet, 25),
ok.
stop(Host) ->
@@ -47,88 +47,75 @@ stop(Host) ->
?MODULE, check_packet, 25),
ok.
-check_packet(_, _User, Server,
- _PrivacyList,
- {From, To, {xmlelement, Name, Attrs, _}},
- Dir) ->
+check_packet(_, _User, Server, _PrivacyList,
+ {From, To, #xmlel{name = Name, attrs = Attrs}}, Dir) ->
case Name of
- "presence" ->
- IsSubscription =
- case xml:get_attr_s("type", Attrs) of
- "subscribe" -> true;
- "subscribed" -> true;
- "unsubscribe" -> true;
- "unsubscribed" -> true;
- _ -> false
- end,
- if
- IsSubscription ->
- JID = case Dir of
- in -> To;
- out -> From
- end,
- update(Server, JID, Dir);
- true ->
- allow
- end;
- _ ->
- allow
+ <<"presence">> ->
+ IsSubscription = case xml:get_attr_s(<<"type">>, Attrs)
+ of
+ <<"subscribe">> -> true;
+ <<"subscribed">> -> true;
+ <<"unsubscribe">> -> true;
+ <<"unsubscribed">> -> true;
+ _ -> false
+ end,
+ if IsSubscription ->
+ JID = case Dir of
+ in -> To;
+ out -> From
+ end,
+ update(Server, JID, Dir);
+ true -> allow
+ end;
+ _ -> allow
end.
update(Server, JID, Dir) ->
- %% get options
- StormCount = gen_mod:get_module_opt(Server, ?MODULE, count, 5),
- TimeInterval = gen_mod:get_module_opt(Server, ?MODULE, interval, 60),
+ StormCount = gen_mod:get_module_opt(Server, ?MODULE, count,
+ fun(I) when is_integer(I), I>0 -> I end,
+ 5),
+ TimeInterval = gen_mod:get_module_opt(Server, ?MODULE, interval,
+ fun(I) when is_integer(I), I>0 -> I end,
+ 60),
{MegaSecs, Secs, _MicroSecs} = now(),
TimeStamp = MegaSecs * 1000000 + Secs,
case read(Dir) of
- undefined ->
- write(Dir, #pres_counter{dir = Dir,
- start = TimeStamp,
- count = 1}),
- allow;
- #pres_counter{start = TimeStart, count = Count, logged = Logged} = R ->
- %% record for this key exists, check if we're
- %% within TimeInterval seconds, and whether the StormCount is
- %% high enough. or else just increment the count.
- if
- TimeStamp - TimeStart > TimeInterval ->
- write(Dir, R#pres_counter{
- start = TimeStamp,
- count = 1}),
- allow;
- (Count =:= StormCount) and Logged ->
- {stop, deny};
- Count =:= StormCount ->
- write(Dir, R#pres_counter{logged = true}),
- case Dir of
- in ->
- ?WARNING_MSG(
- "User ~s is being flooded, "
- "ignoring received presence subscriptions",
- [jlib:jid_to_string(JID)]);
- out ->
- IP = ejabberd_sm:get_user_ip(
- JID#jid.luser,
- JID#jid.lserver,
- JID#jid.lresource),
- ?WARNING_MSG(
- "Flooder detected: ~s, on IP: ~s "
- "ignoring sent presence subscriptions~n",
- [jlib:jid_to_string(JID),
- jlib:ip_to_list(IP)])
- end,
- {stop, deny};
- true ->
- write(Dir, R#pres_counter{
- start = TimeStamp,
- count = Count + 1}),
- allow
- end
+ undefined ->
+ write(Dir,
+ #pres_counter{dir = Dir, start = TimeStamp, count = 1}),
+ allow;
+ #pres_counter{start = TimeStart, count = Count,
+ logged = Logged} =
+ R ->
+ if TimeStamp - TimeStart > TimeInterval ->
+ write(Dir,
+ R#pres_counter{start = TimeStamp, count = 1}),
+ allow;
+ (Count =:= StormCount) and Logged -> {stop, deny};
+ Count =:= StormCount ->
+ write(Dir, R#pres_counter{logged = true}),
+ case Dir of
+ in ->
+ ?WARNING_MSG("User ~s is being flooded, ignoring received "
+ "presence subscriptions",
+ [jlib:jid_to_string(JID)]);
+ out ->
+ IP = ejabberd_sm:get_user_ip(JID#jid.luser,
+ JID#jid.lserver,
+ JID#jid.lresource),
+ ?WARNING_MSG("Flooder detected: ~s, on IP: ~s ignoring "
+ "sent presence subscriptions~n",
+ [jlib:jid_to_string(JID),
+ jlib:ip_to_list(IP)])
+ end,
+ {stop, deny};
+ true ->
+ write(Dir,
+ R#pres_counter{start = TimeStamp, count = Count + 1}),
+ allow
+ end
end.
-read(K)->
- get({pres_counter, K}).
+read(K) -> get({pres_counter, K}).
-write(K, V)->
- put({pres_counter, K}, V).
+write(K, V) -> put({pres_counter, K}, V).
diff --git a/src/mod_privacy.erl b/src/mod_privacy.erl
index 4163551be..17b9299c5 100644
--- a/src/mod_privacy.erl
+++ b/src/mod_privacy.erl
@@ -25,963 +25,911 @@
%%%----------------------------------------------------------------------
-module(mod_privacy).
+
-author('alexey@process-one.net').
-behaviour(gen_mod).
--export([start/2, stop/1,
- process_iq/3,
- process_iq_set/4,
- process_iq_get/5,
- get_user_list/3,
- check_packet/6,
- remove_user/2,
- item_to_raw/1,
- raw_to_item/1,
- is_list_needdb/1,
- updated_list/3]).
+-export([start/2, stop/1, process_iq/3, export/1,
+ process_iq_set/4, process_iq_get/5, get_user_list/3,
+ check_packet/6, remove_user/2, item_to_raw/1,
+ raw_to_item/1, is_list_needdb/1, updated_list/3,
+ item_to_xml/1, get_user_lists/2]).
%% For mod_blocking
-export([sql_add_privacy_list/2,
- sql_get_default_privacy_list/2,
- sql_get_default_privacy_list_t/1,
- sql_get_privacy_list_data/3,
- sql_get_privacy_list_data_by_id_t/1,
- sql_get_privacy_list_id_t/2,
- sql_set_default_privacy_list/2,
- sql_set_privacy_list/2]).
+ sql_get_default_privacy_list/2,
+ sql_get_default_privacy_list_t/1,
+ sql_get_privacy_list_data/3,
+ sql_get_privacy_list_data_by_id_t/1,
+ sql_get_privacy_list_id_t/2,
+ sql_set_default_privacy_list/2,
+ sql_set_privacy_list/2]).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
--include("mod_privacy.hrl").
+-include("mod_privacy.hrl").
start(Host, Opts) ->
- IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
+ IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
+ one_queue),
case gen_mod:db_type(Opts) of
- mnesia ->
- mnesia:create_table(privacy,
- [{disc_copies, [node()]},
- {attributes, record_info(fields, privacy)}]),
- update_table();
- _ ->
- ok
+ mnesia ->
+ mnesia:create_table(privacy,
+ [{disc_copies, [node()]},
+ {attributes, record_info(fields, privacy)}]),
+ update_table();
+ _ -> ok
end,
- ejabberd_hooks:add(privacy_iq_get, Host,
- ?MODULE, process_iq_get, 50),
- ejabberd_hooks:add(privacy_iq_set, Host,
- ?MODULE, process_iq_set, 50),
- ejabberd_hooks:add(privacy_get_user_list, Host,
- ?MODULE, get_user_list, 50),
- ejabberd_hooks:add(privacy_check_packet, Host,
- ?MODULE, check_packet, 50),
- ejabberd_hooks:add(privacy_updated_list, Host,
- ?MODULE, updated_list, 50),
- ejabberd_hooks:add(remove_user, Host,
- ?MODULE, remove_user, 50),
- gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_PRIVACY,
- ?MODULE, process_iq, IQDisc).
+ ejabberd_hooks:add(privacy_iq_get, Host, ?MODULE,
+ process_iq_get, 50),
+ ejabberd_hooks:add(privacy_iq_set, Host, ?MODULE,
+ process_iq_set, 50),
+ ejabberd_hooks:add(privacy_get_user_list, Host, ?MODULE,
+ get_user_list, 50),
+ ejabberd_hooks:add(privacy_check_packet, Host, ?MODULE,
+ check_packet, 50),
+ ejabberd_hooks:add(privacy_updated_list, Host, ?MODULE,
+ updated_list, 50),
+ ejabberd_hooks:add(remove_user, Host, ?MODULE,
+ remove_user, 50),
+ gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
+ ?NS_PRIVACY, ?MODULE, process_iq, IQDisc).
stop(Host) ->
- ejabberd_hooks:delete(privacy_iq_get, Host,
- ?MODULE, process_iq_get, 50),
- ejabberd_hooks:delete(privacy_iq_set, Host,
- ?MODULE, process_iq_set, 50),
+ ejabberd_hooks:delete(privacy_iq_get, Host, ?MODULE,
+ process_iq_get, 50),
+ ejabberd_hooks:delete(privacy_iq_set, Host, ?MODULE,
+ process_iq_set, 50),
ejabberd_hooks:delete(privacy_get_user_list, Host,
?MODULE, get_user_list, 50),
ejabberd_hooks:delete(privacy_check_packet, Host,
?MODULE, check_packet, 50),
ejabberd_hooks:delete(privacy_updated_list, Host,
?MODULE, updated_list, 50),
- ejabberd_hooks:delete(remove_user, Host,
- ?MODULE, remove_user, 50),
- gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_PRIVACY).
+ ejabberd_hooks:delete(remove_user, Host, ?MODULE,
+ remove_user, 50),
+ gen_iq_handler:remove_iq_handler(ejabberd_sm, Host,
+ ?NS_PRIVACY).
process_iq(_From, _To, IQ) ->
SubEl = IQ#iq.sub_el,
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}.
-
process_iq_get(_, From, _To, #iq{sub_el = SubEl},
#userlist{name = Active}) ->
#jid{luser = LUser, lserver = LServer} = From,
- {xmlelement, _, _, Els} = SubEl,
+ #xmlel{children = Els} = SubEl,
case xml:remove_cdata(Els) of
- [] ->
- process_lists_get(LUser, LServer, Active);
- [{xmlelement, Name, Attrs, _SubEls}] ->
- case Name of
- "list" ->
- ListName = xml:get_attr("name", Attrs),
- process_list_get(LUser, LServer, ListName);
- _ ->
- {error, ?ERR_BAD_REQUEST}
- end;
- _ ->
- {error, ?ERR_BAD_REQUEST}
+ [] -> process_lists_get(LUser, LServer, Active);
+ [#xmlel{name = Name, attrs = Attrs}] ->
+ case Name of
+ <<"list">> ->
+ ListName = xml:get_attr(<<"name">>, Attrs),
+ process_list_get(LUser, LServer, ListName);
+ _ -> {error, ?ERR_BAD_REQUEST}
+ end;
+ _ -> {error, ?ERR_BAD_REQUEST}
end.
process_lists_get(LUser, LServer, Active) ->
case process_lists_get(LUser, LServer, Active,
- gen_mod:db_type(LServer, ?MODULE)) of
- error ->
- {error, ?ERR_INTERNAL_SERVER_ERROR};
- {_Default, []} ->
- {result, [{xmlelement, "query", [{"xmlns", ?NS_PRIVACY}], []}]};
- {Default, LItems} ->
- DItems =
- case Default of
- none ->
- LItems;
- _ ->
- [{xmlelement, "default",
- [{"name", Default}], []} | LItems]
- end,
- ADItems =
- case Active of
- none ->
- DItems;
- _ ->
- [{xmlelement, "active",
- [{"name", Active}], []} | DItems]
- end,
- {result,
- [{xmlelement, "query", [{"xmlns", ?NS_PRIVACY}],
- ADItems}]}
+ gen_mod:db_type(LServer, ?MODULE))
+ of
+ error -> {error, ?ERR_INTERNAL_SERVER_ERROR};
+ {_Default, []} ->
+ {result,
+ [#xmlel{name = <<"query">>,
+ attrs = [{<<"xmlns">>, ?NS_PRIVACY}], children = []}]};
+ {Default, LItems} ->
+ DItems = case Default of
+ none -> LItems;
+ _ ->
+ [#xmlel{name = <<"default">>,
+ attrs = [{<<"name">>, Default}], children = []}
+ | LItems]
+ end,
+ ADItems = case Active of
+ none -> DItems;
+ _ ->
+ [#xmlel{name = <<"active">>,
+ attrs = [{<<"name">>, Active}], children = []}
+ | DItems]
+ end,
+ {result,
+ [#xmlel{name = <<"query">>,
+ attrs = [{<<"xmlns">>, ?NS_PRIVACY}],
+ children = ADItems}]}
end.
process_lists_get(LUser, LServer, _Active, mnesia) ->
- case catch mnesia:dirty_read(privacy, {LUser, LServer}) of
- {'EXIT', _Reason} ->
- error;
- [] ->
- {none, []};
- [#privacy{default = Default, lists = Lists}] ->
- LItems = lists:map(
- fun({N, _}) ->
- {xmlelement, "list", [{"name", N}], []}
- end, Lists),
- {Default, LItems}
+ case catch mnesia:dirty_read(privacy, {LUser, LServer})
+ of
+ {'EXIT', _Reason} -> error;
+ [] -> {none, []};
+ [#privacy{default = Default, lists = Lists}] ->
+ LItems = lists:map(fun ({N, _}) ->
+ #xmlel{name = <<"list">>,
+ attrs = [{<<"name">>, N}],
+ children = []}
+ end,
+ Lists),
+ {Default, LItems}
end;
process_lists_get(LUser, LServer, _Active, odbc) ->
- Default = case catch sql_get_default_privacy_list(LUser, LServer) of
- {selected, ["name"], []} ->
- none;
- {selected, ["name"], [{DefName}]} ->
- DefName;
- _ ->
- none
+ Default = case catch sql_get_default_privacy_list(LUser,
+ LServer)
+ of
+ {selected, [<<"name">>], []} -> none;
+ {selected, [<<"name">>], [[DefName]]} -> DefName;
+ _ -> none
end,
case catch sql_get_privacy_list_names(LUser, LServer) of
- {selected, ["name"], Names} ->
- LItems = lists:map(
- fun({N}) ->
- {xmlelement, "list", [{"name", N}], []}
- end, Names),
- {Default, LItems};
- _ ->
- error
+ {selected, [<<"name">>], Names} ->
+ LItems = lists:map(fun ([N]) ->
+ #xmlel{name = <<"list">>,
+ attrs = [{<<"name">>, N}],
+ children = []}
+ end,
+ Names),
+ {Default, LItems};
+ _ -> error
end.
process_list_get(LUser, LServer, {value, Name}) ->
case process_list_get(LUser, LServer, Name,
- gen_mod:db_type(LServer, ?MODULE)) of
- error ->
- {error, ?ERR_INTERNAL_SERVER_ERROR};
- not_found ->
- {error, ?ERR_ITEM_NOT_FOUND};
- Items ->
- LItems = lists:map(fun item_to_xml/1, Items),
- {result,
- [{xmlelement, "query", [{"xmlns", ?NS_PRIVACY}],
- [{xmlelement, "list",
- [{"name", Name}], LItems}]}]}
+ gen_mod:db_type(LServer, ?MODULE))
+ of
+ error -> {error, ?ERR_INTERNAL_SERVER_ERROR};
+ not_found -> {error, ?ERR_ITEM_NOT_FOUND};
+ Items ->
+ LItems = lists:map(fun item_to_xml/1, Items),
+ {result,
+ [#xmlel{name = <<"query">>,
+ attrs = [{<<"xmlns">>, ?NS_PRIVACY}],
+ children =
+ [#xmlel{name = <<"list">>, attrs = [{<<"name">>, Name}],
+ children = LItems}]}]}
end;
process_list_get(_LUser, _LServer, false) ->
{error, ?ERR_BAD_REQUEST}.
process_list_get(LUser, LServer, Name, mnesia) ->
- case catch mnesia:dirty_read(privacy, {LUser, LServer}) of
- {'EXIT', _Reason} ->
- error;
- [] ->
- not_found;
- [#privacy{lists = Lists}] ->
- case lists:keysearch(Name, 1, Lists) of
- {value, {_, List}} ->
- List;
- _ ->
- not_found
- end
+ case catch mnesia:dirty_read(privacy, {LUser, LServer})
+ of
+ {'EXIT', _Reason} -> error;
+ [] -> not_found;
+ [#privacy{lists = Lists}] ->
+ case lists:keysearch(Name, 1, Lists) of
+ {value, {_, List}} -> List;
+ _ -> not_found
+ end
end;
process_list_get(LUser, LServer, Name, odbc) ->
- case catch sql_get_privacy_list_id(LUser, LServer, Name) of
- {selected, ["id"], []} ->
- not_found;
- {selected, ["id"], [{ID}]} ->
- case catch sql_get_privacy_list_data_by_id(ID, LServer) of
- {selected, ["t", "value", "action", "ord", "match_all",
- "match_iq", "match_message",
- "match_presence_in", "match_presence_out"],
- RItems} ->
- lists:map(fun raw_to_item/1, RItems);
- _ ->
- error
- end;
- _ ->
- error
+ case catch sql_get_privacy_list_id(LUser, LServer, Name)
+ of
+ {selected, [<<"id">>], []} -> not_found;
+ {selected, [<<"id">>], [[ID]]} ->
+ case catch sql_get_privacy_list_data_by_id(ID, LServer)
+ of
+ {selected,
+ [<<"t">>, <<"value">>, <<"action">>, <<"ord">>,
+ <<"match_all">>, <<"match_iq">>, <<"match_message">>,
+ <<"match_presence_in">>, <<"match_presence_out">>],
+ RItems} ->
+ lists:map(fun raw_to_item/1, RItems);
+ _ -> error
+ end;
+ _ -> error
end.
item_to_xml(Item) ->
- Attrs1 = [{"action", action_to_list(Item#listitem.action)},
- {"order", order_to_list(Item#listitem.order)}],
+ Attrs1 = [{<<"action">>,
+ action_to_list(Item#listitem.action)},
+ {<<"order">>, order_to_list(Item#listitem.order)}],
Attrs2 = case Item#listitem.type of
- none ->
- Attrs1;
- Type ->
- [{"type", type_to_list(Item#listitem.type)},
- {"value", value_to_list(Type, Item#listitem.value)} |
- Attrs1]
+ none -> Attrs1;
+ Type ->
+ [{<<"type">>, type_to_list(Item#listitem.type)},
+ {<<"value">>, value_to_list(Type, Item#listitem.value)}
+ | Attrs1]
end,
SubEls = case Item#listitem.match_all of
- true ->
- [];
- false ->
- SE1 = case Item#listitem.match_iq of
- true ->
- [{xmlelement, "iq", [], []}];
- false ->
- []
- end,
- SE2 = case Item#listitem.match_message of
- true ->
- [{xmlelement, "message", [], []} | SE1];
- false ->
- SE1
- end,
- SE3 = case Item#listitem.match_presence_in of
- true ->
- [{xmlelement, "presence-in", [], []} | SE2];
- false ->
- SE2
- end,
- SE4 = case Item#listitem.match_presence_out of
- true ->
- [{xmlelement, "presence-out", [], []} | SE3];
- false ->
- SE3
- end,
- SE4
+ true -> [];
+ false ->
+ SE1 = case Item#listitem.match_iq of
+ true ->
+ [#xmlel{name = <<"iq">>, attrs = [],
+ children = []}];
+ false -> []
+ end,
+ SE2 = case Item#listitem.match_message of
+ true ->
+ [#xmlel{name = <<"message">>, attrs = [],
+ children = []}
+ | SE1];
+ false -> SE1
+ end,
+ SE3 = case Item#listitem.match_presence_in of
+ true ->
+ [#xmlel{name = <<"presence-in">>, attrs = [],
+ children = []}
+ | SE2];
+ false -> SE2
+ end,
+ SE4 = case Item#listitem.match_presence_out of
+ true ->
+ [#xmlel{name = <<"presence-out">>, attrs = [],
+ children = []}
+ | SE3];
+ false -> SE3
+ end,
+ SE4
end,
- {xmlelement, "item", Attrs2, SubEls}.
-
+ #xmlel{name = <<"item">>, attrs = Attrs2,
+ children = SubEls}.
action_to_list(Action) ->
case Action of
- allow -> "allow";
- deny -> "deny"
+ allow -> <<"allow">>;
+ deny -> <<"deny">>
end.
order_to_list(Order) ->
- integer_to_list(Order).
+ iolist_to_binary(integer_to_list(Order)).
type_to_list(Type) ->
case Type of
- jid -> "jid";
- group -> "group";
- subscription -> "subscription"
+ jid -> <<"jid">>;
+ group -> <<"group">>;
+ subscription -> <<"subscription">>
end.
value_to_list(Type, Val) ->
case Type of
- jid -> jlib:jid_to_string(Val);
- group -> Val;
- subscription ->
- case Val of
- both -> "both";
- to -> "to";
- from -> "from";
- none -> "none"
- end
+ jid -> jlib:jid_to_string(Val);
+ group -> Val;
+ subscription ->
+ case Val of
+ both -> <<"both">>;
+ to -> <<"to">>;
+ from -> <<"from">>;
+ none -> <<"none">>
+ end
end.
-
-
list_to_action(S) ->
case S of
- "allow" -> allow;
- "deny" -> deny
+ <<"allow">> -> allow;
+ <<"deny">> -> deny
end.
-
-
process_iq_set(_, From, _To, #iq{sub_el = SubEl}) ->
#jid{luser = LUser, lserver = LServer} = From,
- {xmlelement, _, _, Els} = SubEl,
+ #xmlel{children = Els} = SubEl,
case xml:remove_cdata(Els) of
- [{xmlelement, Name, Attrs, SubEls}] ->
- ListName = xml:get_attr("name", Attrs),
- case Name of
- "list" ->
- process_list_set(LUser, LServer, ListName,
- xml:remove_cdata(SubEls));
- "active" ->
- process_active_set(LUser, LServer, ListName);
- "default" ->
- process_default_set(LUser, LServer, ListName);
- _ ->
- {error, ?ERR_BAD_REQUEST}
- end;
- _ ->
- {error, ?ERR_BAD_REQUEST}
+ [#xmlel{name = Name, attrs = Attrs,
+ children = SubEls}] ->
+ ListName = xml:get_attr(<<"name">>, Attrs),
+ case Name of
+ <<"list">> ->
+ process_list_set(LUser, LServer, ListName,
+ xml:remove_cdata(SubEls));
+ <<"active">> ->
+ process_active_set(LUser, LServer, ListName);
+ <<"default">> ->
+ process_default_set(LUser, LServer, ListName);
+ _ -> {error, ?ERR_BAD_REQUEST}
+ end;
+ _ -> {error, ?ERR_BAD_REQUEST}
end.
process_default_set(LUser, LServer, Value) ->
case process_default_set(LUser, LServer, Value,
- gen_mod:db_type(LServer, ?MODULE)) of
- {atomic, error} ->
- {error, ?ERR_INTERNAL_SERVER_ERROR};
- {atomic, not_found} ->
- {error, ?ERR_ITEM_NOT_FOUND};
- {atomic, ok} ->
- {result, []};
- _ ->
- {error, ?ERR_INTERNAL_SERVER_ERROR}
+ gen_mod:db_type(LServer, ?MODULE))
+ of
+ {atomic, error} -> {error, ?ERR_INTERNAL_SERVER_ERROR};
+ {atomic, not_found} -> {error, ?ERR_ITEM_NOT_FOUND};
+ {atomic, ok} -> {result, []};
+ _ -> {error, ?ERR_INTERNAL_SERVER_ERROR}
end.
-process_default_set(LUser, LServer, {value, Name}, mnesia) ->
- F = fun() ->
+process_default_set(LUser, LServer, {value, Name},
+ mnesia) ->
+ F = fun () ->
case mnesia:read({privacy, {LUser, LServer}}) of
- [] ->
- not_found;
- [#privacy{lists = Lists} = P] ->
- case lists:keymember(Name, 1, Lists) of
- true ->
- mnesia:write(P#privacy{default = Name,
- lists = Lists}),
- ok;
- false ->
- not_found
- end
+ [] -> not_found;
+ [#privacy{lists = Lists} = P] ->
+ case lists:keymember(Name, 1, Lists) of
+ true ->
+ mnesia:write(P#privacy{default = Name,
+ lists = Lists}),
+ ok;
+ false -> not_found
+ end
end
end,
mnesia:transaction(F);
-process_default_set(LUser, LServer, {value, Name}, odbc) ->
- F = fun() ->
+process_default_set(LUser, LServer, {value, Name},
+ odbc) ->
+ F = fun () ->
case sql_get_privacy_list_names_t(LUser) of
- {selected, ["name"], []} ->
- not_found;
- {selected, ["name"], Names} ->
- case lists:member({Name}, Names) of
- true ->
- sql_set_default_privacy_list(LUser, Name),
- ok;
- false ->
- not_found
- end
+ {selected, [<<"name">>], []} -> not_found;
+ {selected, [<<"name">>], Names} ->
+ case lists:member([Name], Names) of
+ true -> sql_set_default_privacy_list(LUser, Name), ok;
+ false -> not_found
+ end
end
end,
odbc_queries:sql_transaction(LServer, F);
process_default_set(LUser, LServer, false, mnesia) ->
- F = fun() ->
+ F = fun () ->
case mnesia:read({privacy, {LUser, LServer}}) of
- [] ->
- ok;
- [R] ->
- mnesia:write(R#privacy{default = none})
+ [] -> ok;
+ [R] -> mnesia:write(R#privacy{default = none})
end
end,
mnesia:transaction(F);
process_default_set(LUser, LServer, false, odbc) ->
- case catch sql_unset_default_privacy_list(LUser, LServer) of
- {'EXIT', _Reason} ->
- {atomic, error};
- {error, _Reason} ->
- {atomic, error};
- _ ->
- {atomic, ok}
+ case catch sql_unset_default_privacy_list(LUser,
+ LServer)
+ of
+ {'EXIT', _Reason} -> {atomic, error};
+ {error, _Reason} -> {atomic, error};
+ _ -> {atomic, ok}
end.
process_active_set(LUser, LServer, {value, Name}) ->
case process_active_set(LUser, LServer, Name,
- gen_mod:db_type(LServer, ?MODULE)) of
- error ->
- {error, ?ERR_ITEM_NOT_FOUND};
- Items ->
- NeedDb = is_list_needdb(Items),
- {result, [], #userlist{name = Name, list = Items, needdb = NeedDb}}
+ gen_mod:db_type(LServer, ?MODULE))
+ of
+ error -> {error, ?ERR_ITEM_NOT_FOUND};
+ Items ->
+ NeedDb = is_list_needdb(Items),
+ {result, [],
+ #userlist{name = Name, list = Items, needdb = NeedDb}}
end;
process_active_set(_LUser, _LServer, false) ->
{result, [], #userlist{}}.
process_active_set(LUser, LServer, Name, mnesia) ->
- case catch mnesia:dirty_read(privacy, {LUser, LServer}) of
- [] ->
- error;
- [#privacy{lists = Lists}] ->
- case lists:keysearch(Name, 1, Lists) of
- {value, {_, List}} ->
- List;
- false ->
- error
- end
+ case catch mnesia:dirty_read(privacy, {LUser, LServer})
+ of
+ [] -> error;
+ [#privacy{lists = Lists}] ->
+ case lists:keysearch(Name, 1, Lists) of
+ {value, {_, List}} -> List;
+ false -> error
+ end
end;
process_active_set(LUser, LServer, Name, odbc) ->
- case catch sql_get_privacy_list_id(LUser, LServer, Name) of
- {selected, ["id"], []} ->
- error;
- {selected, ["id"], [{ID}]} ->
- case catch sql_get_privacy_list_data_by_id(ID, LServer) of
- {selected, ["t", "value", "action", "ord", "match_all",
- "match_iq", "match_message",
- "match_presence_in", "match_presence_out"],
- RItems} ->
- lists:map(fun raw_to_item/1, RItems);
- _ ->
- error
- end;
- _ ->
- error
+ case catch sql_get_privacy_list_id(LUser, LServer, Name)
+ of
+ {selected, [<<"id">>], []} -> error;
+ {selected, [<<"id">>], [[ID]]} ->
+ case catch sql_get_privacy_list_data_by_id(ID, LServer)
+ of
+ {selected,
+ [<<"t">>, <<"value">>, <<"action">>, <<"ord">>,
+ <<"match_all">>, <<"match_iq">>, <<"match_message">>,
+ <<"match_presence_in">>, <<"match_presence_out">>],
+ RItems} ->
+ lists:map(fun raw_to_item/1, RItems);
+ _ -> error
+ end;
+ _ -> error
end.
remove_privacy_list(LUser, LServer, Name, mnesia) ->
- F = fun() ->
- case mnesia:read({privacy, {LUser, LServer}}) of
- [] ->
- ok;
- [#privacy{default = Default, lists = Lists} = P] ->
- %% TODO: check active
- if
- Name == Default ->
- conflict;
- true ->
- NewLists =
- lists:keydelete(Name, 1, Lists),
- mnesia:write(
- P#privacy{lists = NewLists})
- end
- end
- end,
+ F = fun () ->
+ case mnesia:read({privacy, {LUser, LServer}}) of
+ [] -> ok;
+ [#privacy{default = Default, lists = Lists} = P] ->
+ if Name == Default -> conflict;
+ true ->
+ NewLists = lists:keydelete(Name, 1, Lists),
+ mnesia:write(P#privacy{lists = NewLists})
+ end
+ end
+ end,
mnesia:transaction(F);
remove_privacy_list(LUser, LServer, Name, odbc) ->
- F = fun() ->
- case sql_get_default_privacy_list_t(LUser) of
- {selected, ["name"], []} ->
- sql_remove_privacy_list(LUser, Name),
- ok;
- {selected, ["name"], [{Default}]} ->
- %% TODO: check active
- if
- Name == Default ->
- conflict;
- true ->
- sql_remove_privacy_list(LUser, Name),
- ok
- end
- end
- end,
+ F = fun () ->
+ case sql_get_default_privacy_list_t(LUser) of
+ {selected, [<<"name">>], []} ->
+ sql_remove_privacy_list(LUser, Name), ok;
+ {selected, [<<"name">>], [[Default]]} ->
+ if Name == Default -> conflict;
+ true -> sql_remove_privacy_list(LUser, Name), ok
+ end
+ end
+ end,
odbc_queries:sql_transaction(LServer, F).
set_privacy_list(LUser, LServer, Name, List, mnesia) ->
- F = fun() ->
- case mnesia:wread({privacy, {LUser, LServer}}) of
- [] ->
- NewLists = [{Name, List}],
- mnesia:write(#privacy{us = {LUser, LServer},
- lists = NewLists});
- [#privacy{lists = Lists} = P] ->
- NewLists1 = lists:keydelete(Name, 1, Lists),
- NewLists = [{Name, List} | NewLists1],
- mnesia:write(P#privacy{lists = NewLists})
+ F = fun () ->
+ case mnesia:wread({privacy, {LUser, LServer}}) of
+ [] ->
+ NewLists = [{Name, List}],
+ mnesia:write(#privacy{us = {LUser, LServer},
+ lists = NewLists});
+ [#privacy{lists = Lists} = P] ->
+ NewLists1 = lists:keydelete(Name, 1, Lists),
+ NewLists = [{Name, List} | NewLists1],
+ mnesia:write(P#privacy{lists = NewLists})
end
- end,
+ end,
mnesia:transaction(F);
set_privacy_list(LUser, LServer, Name, List, odbc) ->
RItems = lists:map(fun item_to_raw/1, List),
- F = fun() ->
- ID =
- case sql_get_privacy_list_id_t(LUser, Name) of
- {selected, ["id"], []} ->
- sql_add_privacy_list(LUser, Name),
- {selected, ["id"], [{I}]} =
- sql_get_privacy_list_id_t(LUser, Name),
- I;
- {selected, ["id"], [{I}]} ->
- I
- end,
- sql_set_privacy_list(ID, RItems),
- ok
- end,
+ F = fun () ->
+ ID = case sql_get_privacy_list_id_t(LUser, Name) of
+ {selected, [<<"id">>], []} ->
+ sql_add_privacy_list(LUser, Name),
+ {selected, [<<"id">>], [[I]]} =
+ sql_get_privacy_list_id_t(LUser, Name),
+ I;
+ {selected, [<<"id">>], [[I]]} -> I
+ end,
+ sql_set_privacy_list(ID, RItems),
+ ok
+ end,
odbc_queries:sql_transaction(LServer, F).
process_list_set(LUser, LServer, {value, Name}, Els) ->
case parse_items(Els) of
- false ->
- {error, ?ERR_BAD_REQUEST};
- remove ->
- case remove_privacy_list(LUser, LServer, Name,
- gen_mod:db_type(LServer, ?MODULE)) of
- {atomic, conflict} ->
- {error, ?ERR_CONFLICT};
- {atomic, ok} ->
- ejabberd_router:route(
- jlib:make_jid(LUser, LServer, ""),
- jlib:make_jid(LUser, LServer, ""),
- {xmlelement, "broadcast", [],
- [{privacy_list,
- #userlist{name = Name, list = []},
- Name}]}),
- {result, []};
- _ ->
- {error, ?ERR_INTERNAL_SERVER_ERROR}
- end;
- List ->
- case set_privacy_list(LUser, LServer, Name, List,
- gen_mod:db_type(LServer, ?MODULE)) of
- {atomic, ok} ->
- NeedDb = is_list_needdb(List),
- ejabberd_router:route(
- jlib:make_jid(LUser, LServer, ""),
- jlib:make_jid(LUser, LServer, ""),
- {xmlelement, "broadcast", [],
- [{privacy_list,
- #userlist{name = Name, list = List, needdb = NeedDb},
- Name}]}),
- {result, []};
- _ ->
- {error, ?ERR_INTERNAL_SERVER_ERROR}
- end
+ false -> {error, ?ERR_BAD_REQUEST};
+ remove ->
+ case remove_privacy_list(LUser, LServer, Name,
+ gen_mod:db_type(LServer, ?MODULE))
+ of
+ {atomic, conflict} -> {error, ?ERR_CONFLICT};
+ {atomic, ok} ->
+ ejabberd_sm:route(jlib:make_jid(LUser, LServer,
+ <<"">>),
+ jlib:make_jid(LUser, LServer, <<"">>),
+ {broadcast, {privacy_list,
+ #userlist{name = Name,
+ list = []},
+ Name}}),
+ {result, []};
+ _ -> {error, ?ERR_INTERNAL_SERVER_ERROR}
+ end;
+ List ->
+ case set_privacy_list(LUser, LServer, Name, List,
+ gen_mod:db_type(LServer, ?MODULE))
+ of
+ {atomic, ok} ->
+ NeedDb = is_list_needdb(List),
+ ejabberd_sm:route(jlib:make_jid(LUser, LServer,
+ <<"">>),
+ jlib:make_jid(LUser, LServer, <<"">>),
+ {broadcast, {privacy_list,
+ #userlist{name = Name,
+ list = List,
+ needdb = NeedDb},
+ Name}}),
+ {result, []};
+ _ -> {error, ?ERR_INTERNAL_SERVER_ERROR}
+ end
end;
-
process_list_set(_LUser, _LServer, false, _Els) ->
{error, ?ERR_BAD_REQUEST}.
-
-parse_items([]) ->
- remove;
-parse_items(Els) ->
- parse_items(Els, []).
+parse_items([]) -> remove;
+parse_items(Els) -> parse_items(Els, []).
parse_items([], Res) ->
- %% Sort the items by their 'order' attribute
lists:keysort(#listitem.order, Res);
-parse_items([{xmlelement, "item", Attrs, SubEls} | Els], Res) ->
- Type = xml:get_attr("type", Attrs),
- Value = xml:get_attr("value", Attrs),
- SAction = xml:get_attr("action", Attrs),
- SOrder = xml:get_attr("order", Attrs),
- Action = case catch list_to_action(element(2, SAction)) of
- {'EXIT', _} -> false;
- Val -> Val
+parse_items([#xmlel{name = <<"item">>, attrs = Attrs,
+ children = SubEls}
+ | Els],
+ Res) ->
+ Type = xml:get_attr(<<"type">>, Attrs),
+ Value = xml:get_attr(<<"value">>, Attrs),
+ SAction = xml:get_attr(<<"action">>, Attrs),
+ SOrder = xml:get_attr(<<"order">>, Attrs),
+ Action = case catch list_to_action(element(2, SAction))
+ of
+ {'EXIT', _} -> false;
+ Val -> Val
end,
- Order = case catch list_to_integer(element(2, SOrder)) of
- {'EXIT', _} ->
- false;
- IntVal ->
- if
- IntVal >= 0 ->
- IntVal;
- true ->
- false
- end
+ Order = case catch jlib:binary_to_integer(element(2,
+ SOrder))
+ of
+ {'EXIT', _} -> false;
+ IntVal ->
+ if IntVal >= 0 -> IntVal;
+ true -> false
+ end
end,
- if
- (Action /= false) and (Order /= false) ->
- I1 = #listitem{action = Action, order = Order},
- I2 = case {Type, Value} of
- {{value, T}, {value, V}} ->
- case T of
- "jid" ->
- case jlib:string_to_jid(V) of
- error ->
- false;
- JID ->
- I1#listitem{
- type = jid,
- value = jlib:jid_tolower(JID)}
- end;
- "group" ->
- I1#listitem{type = group,
- value = V};
- "subscription" ->
- case V of
- "none" ->
- I1#listitem{type = subscription,
- value = none};
- "both" ->
- I1#listitem{type = subscription,
- value = both};
- "from" ->
- I1#listitem{type = subscription,
- value = from};
- "to" ->
- I1#listitem{type = subscription,
- value = to};
- _ ->
- false
- end
- end;
- {{value, _}, false} ->
- false;
- _ ->
- I1
- end,
- case I2 of
- false ->
- false;
- _ ->
- case parse_matches(I2, xml:remove_cdata(SubEls)) of
- false ->
- false;
- I3 ->
- parse_items(Els, [I3 | Res])
- end
- end;
- true ->
- false
+ if (Action /= false) and (Order /= false) ->
+ I1 = #listitem{action = Action, order = Order},
+ I2 = case {Type, Value} of
+ {{value, T}, {value, V}} ->
+ case T of
+ <<"jid">> ->
+ case jlib:string_to_jid(V) of
+ error -> false;
+ JID ->
+ I1#listitem{type = jid,
+ value = jlib:jid_tolower(JID)}
+ end;
+ <<"group">> -> I1#listitem{type = group, value = V};
+ <<"subscription">> ->
+ case V of
+ <<"none">> ->
+ I1#listitem{type = subscription,
+ value = none};
+ <<"both">> ->
+ I1#listitem{type = subscription,
+ value = both};
+ <<"from">> ->
+ I1#listitem{type = subscription,
+ value = from};
+ <<"to">> ->
+ I1#listitem{type = subscription, value = to};
+ _ -> false
+ end
+ end;
+ {{value, _}, false} -> false;
+ _ -> I1
+ end,
+ case I2 of
+ false -> false;
+ _ ->
+ case parse_matches(I2, xml:remove_cdata(SubEls)) of
+ false -> false;
+ I3 -> parse_items(Els, [I3 | Res])
+ end
+ end;
+ true -> false
end;
-
-parse_items(_, _Res) ->
- false.
-
+parse_items(_, _Res) -> false.
parse_matches(Item, []) ->
Item#listitem{match_all = true};
-parse_matches(Item, Els) ->
- parse_matches1(Item, Els).
-
-parse_matches1(Item, []) ->
- Item;
-parse_matches1(Item, [{xmlelement, "message", _, _} | Els]) ->
- parse_matches1(Item#listitem{match_message = true}, Els);
-parse_matches1(Item, [{xmlelement, "iq", _, _} | Els]) ->
+parse_matches(Item, Els) -> parse_matches1(Item, Els).
+
+parse_matches1(Item, []) -> Item;
+parse_matches1(Item,
+ [#xmlel{name = <<"message">>} | Els]) ->
+ parse_matches1(Item#listitem{match_message = true},
+ Els);
+parse_matches1(Item, [#xmlel{name = <<"iq">>} | Els]) ->
parse_matches1(Item#listitem{match_iq = true}, Els);
-parse_matches1(Item, [{xmlelement, "presence-in", _, _} | Els]) ->
- parse_matches1(Item#listitem{match_presence_in = true}, Els);
-parse_matches1(Item, [{xmlelement, "presence-out", _, _} | Els]) ->
- parse_matches1(Item#listitem{match_presence_out = true}, Els);
-parse_matches1(_Item, [{xmlelement, _, _, _} | _Els]) ->
- false.
-
-
-
-
-
-
+parse_matches1(Item,
+ [#xmlel{name = <<"presence-in">>} | Els]) ->
+ parse_matches1(Item#listitem{match_presence_in = true},
+ Els);
+parse_matches1(Item,
+ [#xmlel{name = <<"presence-out">>} | Els]) ->
+ parse_matches1(Item#listitem{match_presence_out = true},
+ Els);
+parse_matches1(_Item, [#xmlel{} | _Els]) -> false.
is_list_needdb(Items) ->
- lists:any(
- fun(X) ->
- case X#listitem.type of
- subscription -> true;
- group -> true;
- _ -> false
- end
- end, Items).
+ lists:any(fun (X) ->
+ case X#listitem.type of
+ subscription -> true;
+ group -> true;
+ _ -> false
+ end
+ end,
+ Items).
get_user_list(Acc, User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
{Default, Items} = get_user_list(Acc, LUser, LServer,
- gen_mod:db_type(LServer, ?MODULE)),
+ gen_mod:db_type(LServer, ?MODULE)),
NeedDb = is_list_needdb(Items),
- #userlist{name = Default, list = Items, needdb = NeedDb}.
+ #userlist{name = Default, list = Items,
+ needdb = NeedDb}.
get_user_list(_, LUser, LServer, mnesia) ->
- case catch mnesia:dirty_read(privacy, {LUser, LServer}) of
- [] ->
- {none, []};
- [#privacy{default = Default, lists = Lists}] ->
- case Default of
- none ->
- {none, []};
- _ ->
- case lists:keysearch(Default, 1, Lists) of
- {value, {_, List}} ->
- {Default, List};
- _ ->
- {none, []}
- end
- end;
- _ ->
- {none, []}
+ case catch mnesia:dirty_read(privacy, {LUser, LServer})
+ of
+ [] -> {none, []};
+ [#privacy{default = Default, lists = Lists}] ->
+ case Default of
+ none -> {none, []};
+ _ ->
+ case lists:keysearch(Default, 1, Lists) of
+ {value, {_, List}} -> {Default, List};
+ _ -> {none, []}
+ end
+ end;
+ _ -> {none, []}
end;
get_user_list(_, LUser, LServer, odbc) ->
- case catch sql_get_default_privacy_list(LUser, LServer) of
- {selected, ["name"], []} ->
- {none, []};
- {selected, ["name"], [{Default}]} ->
- case catch sql_get_privacy_list_data(LUser, LServer, Default) of
- {selected, ["t", "value", "action", "ord", "match_all",
- "match_iq", "match_message",
- "match_presence_in", "match_presence_out"],
- RItems} ->
- {Default, lists:map(fun raw_to_item/1, RItems)};
- _ ->
- {none, []}
- end;
- _ ->
- {none, []}
+ case catch sql_get_default_privacy_list(LUser, LServer)
+ of
+ {selected, [<<"name">>], []} -> {none, []};
+ {selected, [<<"name">>], [[Default]]} ->
+ case catch sql_get_privacy_list_data(LUser, LServer,
+ Default)
+ of
+ {selected,
+ [<<"t">>, <<"value">>, <<"action">>, <<"ord">>,
+ <<"match_all">>, <<"match_iq">>, <<"match_message">>,
+ <<"match_presence_in">>, <<"match_presence_out">>],
+ RItems} ->
+ {Default, lists:map(fun raw_to_item/1, RItems)};
+ _ -> {none, []}
+ end;
+ _ -> {none, []}
+ end.
+
+get_user_lists(User, Server) ->
+ LUser = jlib:nodeprep(User),
+ LServer = jlib:nameprep(Server),
+ get_user_lists(LUser, LServer, gen_mod:db_type(LServer, ?MODULE)).
+
+get_user_lists(LUser, LServer, mnesia) ->
+ case catch mnesia:dirty_read(privacy, {LUser, LServer}) of
+ [#privacy{} = P] ->
+ {ok, P};
+ _ ->
+ error
+ end;
+get_user_lists(LUser, LServer, odbc) ->
+ Default = case catch sql_get_default_privacy_list(LUser, LServer) of
+ {selected, [<<"name">>], []} ->
+ none;
+ {selected, [<<"name">>], [[DefName]]} ->
+ DefName;
+ _ ->
+ none
+ end,
+ case catch sql_get_privacy_list_names(LUser, LServer) of
+ {selected, [<<"name">>], Names} ->
+ Lists =
+ lists:flatmap(
+ fun([Name]) ->
+ case catch sql_get_privacy_list_data(
+ LUser, LServer, Name) of
+ {selected,
+ [<<"t">>, <<"value">>, <<"action">>,
+ <<"ord">>, <<"match_all">>, <<"match_iq">>,
+ <<"match_message">>, <<"match_presence_in">>,
+ <<"match_presence_out">>],
+ RItems} ->
+ [{Name, lists:map(fun raw_to_item/1, RItems)}];
+ _ ->
+ []
+ end
+ end, Names),
+ {ok, #privacy{default = Default,
+ us = {LUser, LServer},
+ lists = Lists}};
+ _ ->
+ error
end.
%% From is the sender, To is the destination.
%% If Dir = out, User@Server is the sender account (From).
%% If Dir = in, User@Server is the destination account (To).
-check_packet(_, _User, _Server,
- _UserList,
- {#jid{luser = "", lserver = Server} = _From,
- #jid{lserver = Server} = _To,
- _},
+check_packet(_, _User, _Server, _UserList,
+ {#jid{luser = <<"">>, lserver = Server} = _From,
+ #jid{lserver = Server} = _To, _},
in) ->
allow;
-check_packet(_, _User, _Server,
- _UserList,
+check_packet(_, _User, _Server, _UserList,
{#jid{lserver = Server} = _From,
- #jid{luser = "", lserver = Server} = _To,
- _},
+ #jid{luser = <<"">>, lserver = Server} = _To, _},
out) ->
allow;
-check_packet(_, _User, _Server,
- _UserList,
+check_packet(_, _User, _Server, _UserList,
{#jid{luser = User, lserver = Server} = _From,
- #jid{luser = User, lserver = Server} = _To,
- _},
+ #jid{luser = User, lserver = Server} = _To, _},
_Dir) ->
allow;
check_packet(_, User, Server,
#userlist{list = List, needdb = NeedDb},
- {From, To, {xmlelement, PName, Attrs, _}},
- Dir) ->
+ {From, To, #xmlel{name = PName, attrs = Attrs}}, Dir) ->
case List of
- [] ->
- allow;
- _ ->
- PType = case PName of
- "message" -> message;
- "iq" -> iq;
- "presence" ->
- case xml:get_attr_s("type", Attrs) of
- %% notification
- "" -> presence;
- "unavailable" -> presence;
- %% subscribe, subscribed, unsubscribe,
- %% unsubscribed, error, probe, or other
- _ -> other
- end
- end,
- PType2 = case {PType, Dir} of
- {message, in} -> message;
- {iq, in} -> iq;
- {presence, in} -> presence_in;
- {presence, out} -> presence_out;
- {_, _} -> other
- end,
- LJID = case Dir of
- in -> jlib:jid_tolower(From);
- out -> jlib:jid_tolower(To)
+ [] -> allow;
+ _ ->
+ PType = case PName of
+ <<"message">> -> message;
+ <<"iq">> -> iq;
+ <<"presence">> ->
+ case xml:get_attr_s(<<"type">>, Attrs) of
+ %% notification
+ <<"">> -> presence;
+ <<"unavailable">> -> presence;
+ %% subscribe, subscribed, unsubscribe,
+ %% unsubscribed, error, probe, or other
+ _ -> other
+ end
+ end,
+ PType2 = case {PType, Dir} of
+ {message, in} -> message;
+ {iq, in} -> iq;
+ {presence, in} -> presence_in;
+ {presence, out} -> presence_out;
+ {_, _} -> other
end,
- {Subscription, Groups} =
- case NeedDb of
- true -> ejabberd_hooks:run_fold(roster_get_jid_info,
- jlib:nameprep(Server),
- {none, []},
- [User, Server, LJID]);
- false -> {[], []}
- end,
- check_packet_aux(List, PType2, LJID, Subscription, Groups)
+ LJID = case Dir of
+ in -> jlib:jid_tolower(From);
+ out -> jlib:jid_tolower(To)
+ end,
+ {Subscription, Groups} = case NeedDb of
+ true ->
+ ejabberd_hooks:run_fold(roster_get_jid_info,
+ jlib:nameprep(Server),
+ {none, []},
+ [User, Server,
+ LJID]);
+ false -> {[], []}
+ end,
+ check_packet_aux(List, PType2, LJID, Subscription,
+ Groups)
end.
%% Ptype = mesage | iq | presence_in | presence_out | other
-check_packet_aux([], _PType, _JID, _Subscription, _Groups) ->
+check_packet_aux([], _PType, _JID, _Subscription,
+ _Groups) ->
allow;
-check_packet_aux([Item | List], PType, JID, Subscription, Groups) ->
- #listitem{type = Type, value = Value, action = Action} = Item,
+check_packet_aux([Item | List], PType, JID,
+ Subscription, Groups) ->
+ #listitem{type = Type, value = Value, action = Action} =
+ Item,
case is_ptype_match(Item, PType) of
- true ->
- case Type of
- none ->
- Action;
- _ ->
- case is_type_match(Type, Value,
- JID, Subscription, Groups) of
- true ->
- Action;
- false ->
- check_packet_aux(List, PType,
- JID, Subscription, Groups)
- end
- end;
- false ->
- check_packet_aux(List, PType, JID, Subscription, Groups)
+ true ->
+ case Type of
+ none -> Action;
+ _ ->
+ case is_type_match(Type, Value, JID, Subscription,
+ Groups)
+ of
+ true -> Action;
+ false ->
+ check_packet_aux(List, PType, JID, Subscription, Groups)
+ end
+ end;
+ false ->
+ check_packet_aux(List, PType, JID, Subscription, Groups)
end.
-
is_ptype_match(Item, PType) ->
case Item#listitem.match_all of
- true ->
- true;
- false ->
- case PType of
- message ->
- Item#listitem.match_message;
- iq ->
- Item#listitem.match_iq;
- presence_in ->
- Item#listitem.match_presence_in;
- presence_out ->
- Item#listitem.match_presence_out;
- other ->
- false
- end
+ true -> true;
+ false ->
+ case PType of
+ message -> Item#listitem.match_message;
+ iq -> Item#listitem.match_iq;
+ presence_in -> Item#listitem.match_presence_in;
+ presence_out -> Item#listitem.match_presence_out;
+ other -> false
+ end
end.
-
is_type_match(Type, Value, JID, Subscription, Groups) ->
case Type of
- jid ->
- case Value of
- {"", Server, ""} ->
- case JID of
- {_, Server, _} ->
- true;
- _ ->
- false
- end;
- {User, Server, ""} ->
- case JID of
- {User, Server, _} ->
- true;
- _ ->
- false
- end;
- _ ->
- Value == JID
- end;
- subscription ->
- Value == Subscription;
- group ->
- lists:member(Value, Groups)
+ jid ->
+ case Value of
+ {<<"">>, Server, <<"">>} ->
+ case JID of
+ {_, Server, _} -> true;
+ _ -> false
+ end;
+ {User, Server, <<"">>} ->
+ case JID of
+ {User, Server, _} -> true;
+ _ -> false
+ end;
+ _ -> Value == JID
+ end;
+ subscription -> Value == Subscription;
+ group -> lists:member(Value, Groups)
end.
-
remove_user(User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
- remove_user(LUser, LServer, gen_mod:db_type(LServer, ?MODULE)).
+ remove_user(LUser, LServer,
+ gen_mod:db_type(LServer, ?MODULE)).
remove_user(LUser, LServer, mnesia) ->
- F = fun() ->
- mnesia:delete({privacy,
- {LUser, LServer}})
- end,
+ F = fun () -> mnesia:delete({privacy, {LUser, LServer}})
+ end,
mnesia:transaction(F);
remove_user(LUser, LServer, odbc) ->
sql_del_privacy_lists(LUser, LServer).
-updated_list(_,
- #userlist{name = OldName} = Old,
+updated_list(_, #userlist{name = OldName} = Old,
#userlist{name = NewName} = New) ->
- if
- OldName == NewName ->
- New;
- true ->
- Old
+ if OldName == NewName -> New;
+ true -> Old
end.
-raw_to_item({SType, SValue, SAction, SOrder, SMatchAll, SMatchIQ,
- SMatchMessage, SMatchPresenceIn, SMatchPresenceOut}) ->
- {Type, Value} =
- case SType of
- "n" ->
- {none, none};
- "j" ->
- case jlib:string_to_jid(SValue) of
- #jid{} = JID ->
- {jid, jlib:jid_tolower(JID)}
- end;
- "g" ->
- {group, SValue};
- "s" ->
- case SValue of
- "none" ->
- {subscription, none};
- "both" ->
- {subscription, both};
- "from" ->
- {subscription, from};
- "to" ->
- {subscription, to}
- end
- end,
- Action =
- case SAction of
- "a" -> allow;
- "d" -> deny
- end,
- Order = list_to_integer(SOrder),
+raw_to_item([SType, SValue, SAction, SOrder, SMatchAll,
+ SMatchIQ, SMatchMessage, SMatchPresenceIn,
+ SMatchPresenceOut]) ->
+ {Type, Value} = case SType of
+ <<"n">> -> {none, none};
+ <<"j">> ->
+ case jlib:string_to_jid(SValue) of
+ #jid{} = JID -> {jid, jlib:jid_tolower(JID)}
+ end;
+ <<"g">> -> {group, SValue};
+ <<"s">> ->
+ case SValue of
+ <<"none">> -> {subscription, none};
+ <<"both">> -> {subscription, both};
+ <<"from">> -> {subscription, from};
+ <<"to">> -> {subscription, to}
+ end
+ end,
+ Action = case SAction of
+ <<"a">> -> allow;
+ <<"d">> -> deny
+ end,
+ Order = jlib:binary_to_integer(SOrder),
MatchAll = ejabberd_odbc:to_bool(SMatchAll),
MatchIQ = ejabberd_odbc:to_bool(SMatchIQ),
MatchMessage = ejabberd_odbc:to_bool(SMatchMessage),
- MatchPresenceIn = ejabberd_odbc:to_bool(SMatchPresenceIn),
- MatchPresenceOut = ejabberd_odbc:to_bool(SMatchPresenceOut),
- #listitem{type = Type,
- value = Value,
- action = Action,
- order = Order,
- match_all = MatchAll,
- match_iq = MatchIQ,
+ MatchPresenceIn =
+ ejabberd_odbc:to_bool(SMatchPresenceIn),
+ MatchPresenceOut =
+ ejabberd_odbc:to_bool(SMatchPresenceOut),
+ #listitem{type = Type, value = Value, action = Action,
+ order = Order, match_all = MatchAll, match_iq = MatchIQ,
match_message = MatchMessage,
match_presence_in = MatchPresenceIn,
- match_presence_out = MatchPresenceOut
- }.
-
-item_to_raw(#listitem{type = Type,
- value = Value,
- action = Action,
- order = Order,
- match_all = MatchAll,
- match_iq = MatchIQ,
- match_message = MatchMessage,
+ match_presence_out = MatchPresenceOut}.
+
+item_to_raw(#listitem{type = Type, value = Value,
+ action = Action, order = Order, match_all = MatchAll,
+ match_iq = MatchIQ, match_message = MatchMessage,
match_presence_in = MatchPresenceIn,
- match_presence_out = MatchPresenceOut
- }) ->
- {SType, SValue} =
- case Type of
- none ->
- {"n", ""};
- jid ->
- {"j", ejabberd_odbc:escape(jlib:jid_to_string(Value))};
- group ->
- {"g", ejabberd_odbc:escape(Value)};
- subscription ->
- case Value of
- none ->
- {"s", "none"};
- both ->
- {"s", "both"};
- from ->
- {"s", "from"};
- to ->
- {"s", "to"}
- end
- end,
- SAction =
- case Action of
- allow -> "a";
- deny -> "d"
- end,
- SOrder = integer_to_list(Order),
- SMatchAll = if MatchAll -> "1"; true -> "0" end,
- SMatchIQ = if MatchIQ -> "1"; true -> "0" end,
- SMatchMessage = if MatchMessage -> "1"; true -> "0" end,
- SMatchPresenceIn = if MatchPresenceIn -> "1"; true -> "0" end,
- SMatchPresenceOut = if MatchPresenceOut -> "1"; true -> "0" end,
+ match_presence_out = MatchPresenceOut}) ->
+ {SType, SValue} = case Type of
+ none -> {<<"n">>, <<"">>};
+ jid ->
+ {<<"j">>,
+ ejabberd_odbc:escape(jlib:jid_to_string(Value))};
+ group -> {<<"g">>, ejabberd_odbc:escape(Value)};
+ subscription ->
+ case Value of
+ none -> {<<"s">>, <<"none">>};
+ both -> {<<"s">>, <<"both">>};
+ from -> {<<"s">>, <<"from">>};
+ to -> {<<"s">>, <<"to">>}
+ end
+ end,
+ SAction = case Action of
+ allow -> <<"a">>;
+ deny -> <<"d">>
+ end,
+ SOrder = iolist_to_binary(integer_to_list(Order)),
+ SMatchAll = if MatchAll -> <<"1">>;
+ true -> <<"0">>
+ end,
+ SMatchIQ = if MatchIQ -> <<"1">>;
+ true -> <<"0">>
+ end,
+ SMatchMessage = if MatchMessage -> <<"1">>;
+ true -> <<"0">>
+ end,
+ SMatchPresenceIn = if MatchPresenceIn -> <<"1">>;
+ true -> <<"0">>
+ end,
+ SMatchPresenceOut = if MatchPresenceOut -> <<"1">>;
+ true -> <<"0">>
+ end,
[SType, SValue, SAction, SOrder, SMatchAll, SMatchIQ,
SMatchMessage, SMatchPresenceIn, SMatchPresenceOut].
sql_get_default_privacy_list(LUser, LServer) ->
Username = ejabberd_odbc:escape(LUser),
- odbc_queries:get_default_privacy_list(LServer, Username).
+ odbc_queries:get_default_privacy_list(LServer,
+ Username).
sql_get_default_privacy_list_t(LUser) ->
Username = ejabberd_odbc:escape(LUser),
@@ -998,7 +946,8 @@ sql_get_privacy_list_names_t(LUser) ->
sql_get_privacy_list_id(LUser, LServer, Name) ->
Username = ejabberd_odbc:escape(LUser),
SName = ejabberd_odbc:escape(Name),
- odbc_queries:get_privacy_list_id(LServer, Username, SName).
+ odbc_queries:get_privacy_list_id(LServer, Username,
+ SName).
sql_get_privacy_list_id_t(LUser, Name) ->
Username = ejabberd_odbc:escape(LUser),
@@ -1008,7 +957,8 @@ sql_get_privacy_list_id_t(LUser, Name) ->
sql_get_privacy_list_data(LUser, LServer, Name) ->
Username = ejabberd_odbc:escape(LUser),
SName = ejabberd_odbc:escape(Name),
- odbc_queries:get_privacy_list_data(LServer, Username, SName).
+ odbc_queries:get_privacy_list_data(LServer, Username,
+ SName).
sql_get_privacy_list_data_by_id(ID, LServer) ->
odbc_queries:get_privacy_list_data_by_id(LServer, ID).
@@ -1023,7 +973,8 @@ sql_set_default_privacy_list(LUser, Name) ->
sql_unset_default_privacy_list(LUser, LServer) ->
Username = ejabberd_odbc:escape(LUser),
- odbc_queries:unset_default_privacy_list(LServer, Username).
+ odbc_queries:unset_default_privacy_list(LServer,
+ Username).
sql_remove_privacy_list(LUser, Name) ->
Username = ejabberd_odbc:escape(LUser),
@@ -1041,48 +992,104 @@ sql_set_privacy_list(ID, RItems) ->
sql_del_privacy_lists(LUser, LServer) ->
Username = ejabberd_odbc:escape(LUser),
Server = ejabberd_odbc:escape(LServer),
- odbc_queries:del_privacy_lists(LServer, Server, Username).
+ odbc_queries:del_privacy_lists(LServer, Server,
+ Username).
update_table() ->
Fields = record_info(fields, privacy),
case mnesia:table_info(privacy, attributes) of
- Fields ->
- ok;
- [user, default, lists] ->
- ?INFO_MSG("Converting privacy table from "
- "{user, default, lists} format", []),
- Host = ?MYNAME,
- {atomic, ok} = mnesia:create_table(
- mod_privacy_tmp_table,
- [{disc_only_copies, [node()]},
- {type, bag},
- {local_content, true},
- {record_name, privacy},
- {attributes, record_info(fields, privacy)}]),
- mnesia:transform_table(privacy, ignore, Fields),
- F1 = fun() ->
- mnesia:write_lock_table(mod_privacy_tmp_table),
- mnesia:foldl(
- fun(#privacy{us = U} = R, _) ->
- mnesia:dirty_write(
- mod_privacy_tmp_table,
- R#privacy{us = {U, Host}})
- end, ok, privacy)
- end,
- mnesia:transaction(F1),
- mnesia:clear_table(privacy),
- F2 = fun() ->
- mnesia:write_lock_table(privacy),
- mnesia:foldl(
- fun(R, _) ->
- mnesia:dirty_write(R)
- end, ok, mod_privacy_tmp_table)
- end,
- mnesia:transaction(F2),
- mnesia:delete_table(mod_privacy_tmp_table);
- _ ->
- ?INFO_MSG("Recreating privacy table", []),
- mnesia:transform_table(privacy, ignore, Fields)
+ Fields ->
+ ejabberd_config:convert_table_to_binary(
+ privacy, Fields, set,
+ fun(#privacy{us = {U, _}}) -> U end,
+ fun(#privacy{us = {U, S}, default = Def, lists = Lists} = R) ->
+ NewLists =
+ lists:map(
+ fun({Name, Ls}) ->
+ NewLs =
+ lists:map(
+ fun(#listitem{value = Val} = L) ->
+ NewVal =
+ case Val of
+ {LU, LS, LR} ->
+ {iolist_to_binary(LU),
+ iolist_to_binary(LS),
+ iolist_to_binary(LR)};
+ none -> none;
+ both -> both;
+ from -> from;
+ to -> to;
+ _ -> iolist_to_binary(Val)
+ end,
+ L#listitem{value = NewVal}
+ end, Ls),
+ {iolist_to_binary(Name), NewLs}
+ end, Lists),
+ NewDef = case Def of
+ none -> none;
+ _ -> iolist_to_binary(Def)
+ end,
+ NewUS = {iolist_to_binary(U), iolist_to_binary(S)},
+ R#privacy{us = NewUS, default = NewDef,
+ lists = NewLists}
+ end);
+ _ ->
+ ?INFO_MSG("Recreating privacy table", []),
+ mnesia:transform_table(privacy, ignore, Fields)
end.
-
+export(Server) ->
+ case ejabberd_odbc:sql_query(jlib:nameprep(Server),
+ [<<"select id from privacy_list order by "
+ "id desc limit 1;">>]) of
+ {selected, [<<"id">>], [[I]]} ->
+ put(id, jlib:binary_to_integer(I));
+ _ ->
+ put(id, 0)
+ end,
+ [{privacy,
+ fun(Host, #privacy{us = {LUser, LServer}, lists = Lists,
+ default = Default})
+ when LServer == Host ->
+ Username = ejabberd_odbc:escape(LUser),
+ if Default /= none ->
+ SDefault = ejabberd_odbc:escape(Default),
+ [[<<"delete from privacy_default_list where ">>,
+ <<"username='">>, Username, <<"';">>],
+ [<<"insert into privacy_default_list(username, "
+ "name) ">>,
+ <<"values ('">>, Username, <<"', '">>,
+ SDefault, <<"');">>]];
+ true ->
+ []
+ end ++
+ lists:flatmap(
+ fun({Name, List}) ->
+ SName = ejabberd_odbc:escape(Name),
+ RItems = lists:map(fun item_to_raw/1, List),
+ ID = jlib:integer_to_binary(get_id()),
+ [[<<"delete from privacy_list where username='">>,
+ Username, <<"' and name='">>,
+ SName, <<"';">>],
+ [<<"insert into privacy_list(username, "
+ "name, id) values ('">>,
+ Username, <<"', '">>, SName,
+ <<"', '">>, ID, <<"');">>],
+ [<<"delete from privacy_list_data where "
+ "id='">>, ID, <<"';">>]] ++
+ [[<<"insert into privacy_list_data(id, t, "
+ "value, action, ord, match_all, match_iq, "
+ "match_message, match_presence_in, "
+ "match_presence_out) values ('">>,
+ ID, <<"', '">>, str:join(Items, <<"', '">>),
+ <<"');">>] || Items <- RItems]
+ end,
+ Lists);
+ (_Host, _R) ->
+ []
+ end}].
+
+get_id() ->
+ ID = get(id),
+ put(id, ID + 1),
+ ID + 1.
diff --git a/src/mod_privacy.hrl b/src/mod_privacy.hrl
index 63abc6925..f70982b0f 100644
--- a/src/mod_privacy.hrl
+++ b/src/mod_privacy.hrl
@@ -19,19 +19,26 @@
%%%
%%%----------------------------------------------------------------------
--record(privacy, {us,
- default = none,
- lists = []}).
+-record(privacy, {us = {<<"">>, <<"">>} :: {binary(), binary()},
+ default = none :: none | binary(),
+ lists = [] :: [{binary(), [listitem()]}]}).
--record(listitem, {type = none,
- value = none,
- action,
- order,
- match_all = false,
- match_iq = false,
- match_message = false,
- match_presence_in = false,
- match_presence_out = false
- }).
+-record(listitem, {type = none :: none | jid | group | subscription,
+ value = none :: none | both | from | to | ljid() | binary(),
+ action = allow :: allow | deny,
+ order = 0 :: integer(),
+ match_all = false :: boolean(),
+ match_iq = false :: boolean(),
+ match_message = false :: boolean(),
+ match_presence_in = false :: boolean(),
+ match_presence_out = false :: boolean()}).
--record(userlist, {name = none, list = [], needdb = false }).
+-type listitem() :: #listitem{}.
+
+-record(userlist, {name = none :: none | binary(),
+ list = [] :: [listitem()],
+ needdb = false :: boolean()}).
+
+-type userlist() :: #userlist{}.
+
+-export_type([userlist/0]).
diff --git a/src/mod_private.erl b/src/mod_private.erl
index dbe72c450..45af660a2 100644
--- a/src/mod_private.erl
+++ b/src/mod_private.erl
@@ -25,233 +25,254 @@
%%%----------------------------------------------------------------------
-module(mod_private).
+
-author('alexey@process-one.net').
-behaviour(gen_mod).
--export([start/2,
- stop/1,
- process_sm_iq/3,
- remove_user/2]).
+-export([start/2, stop/1, process_sm_iq/3,
+ remove_user/2, get_data/2, export/1]).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
--record(private_storage, {usns, xml}).
+-record(private_storage,
+ {usns = {<<"">>, <<"">>, <<"">>} :: {binary(), binary(), binary() |
+ '$1' | '_'},
+ xml = #xmlel{} :: xmlel() | '_' | '$1'}).
-define(Xmlel_Query(Attrs, Children),
-(
- {xmlelement, "query", Attrs, Children}
-)).
+ #xmlel{name = <<"query">>, attrs = Attrs,
+ children = Children}).
start(Host, Opts) ->
- IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
+ IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
+ one_queue),
case gen_mod:db_type(Opts) of
- mnesia ->
- mnesia:create_table(private_storage,
- [{disc_only_copies, [node()]},
- {attributes,
- record_info(fields, private_storage)}]),
- update_table();
- _ ->
- ok
+ mnesia ->
+ mnesia:create_table(private_storage,
+ [{disc_only_copies, [node()]},
+ {attributes,
+ record_info(fields, private_storage)}]),
+ update_table();
+ _ -> ok
end,
- ejabberd_hooks:add(remove_user, Host,
- ?MODULE, remove_user, 50),
- gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_PRIVATE,
- ?MODULE, process_sm_iq, IQDisc).
+ ejabberd_hooks:add(remove_user, Host, ?MODULE,
+ remove_user, 50),
+ gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
+ ?NS_PRIVATE, ?MODULE, process_sm_iq, IQDisc).
stop(Host) ->
- ejabberd_hooks:delete(remove_user, Host,
- ?MODULE, remove_user, 50),
- gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_PRIVATE).
-
+ ejabberd_hooks:delete(remove_user, Host, ?MODULE,
+ remove_user, 50),
+ gen_iq_handler:remove_iq_handler(ejabberd_sm, Host,
+ ?NS_PRIVATE).
process_sm_iq(#jid{luser = LUser, lserver = LServer},
- #jid{luser = LUser, lserver = LServer}, IQ)
- when IQ#iq.type == 'set' ->
+ #jid{luser = LUser, lserver = LServer}, IQ)
+ when IQ#iq.type == set ->
case IQ#iq.sub_el of
- {xmlelement, "query", _, Xmlels} ->
- case filter_xmlels(Xmlels) of
- [] ->
- IQ#iq{
- type = error,
- sub_el = [IQ#iq.sub_el, ?ERR_NOT_ACCEPTABLE]
- };
- Data ->
- DBType = gen_mod:db_type(LServer, ?MODULE),
- F = fun() ->
- lists:foreach(
- fun
- (Datum) ->
- set_data(LUser, LServer,
- Datum, DBType)
- end, Data)
- end,
- case DBType of
- odbc ->
- ejabberd_odbc:sql_transaction(LServer, F);
- mnesia ->
- mnesia:transaction(F)
- end,
- IQ#iq{type = result, sub_el = []}
- end;
- _ ->
- IQ#iq{type = error, sub_el = [IQ#iq.sub_el, ?ERR_NOT_ACCEPTABLE]}
+ #xmlel{name = <<"query">>, children = Xmlels} ->
+ case filter_xmlels(Xmlels) of
+ [] ->
+ IQ#iq{type = error,
+ sub_el = [IQ#iq.sub_el, ?ERR_NOT_ACCEPTABLE]};
+ Data ->
+ DBType = gen_mod:db_type(LServer, ?MODULE),
+ F = fun () ->
+ lists:foreach(fun (Datum) ->
+ set_data(LUser, LServer,
+ Datum, DBType)
+ end,
+ Data)
+ end,
+ case DBType of
+ odbc -> ejabberd_odbc:sql_transaction(LServer, F);
+ mnesia -> mnesia:transaction(F)
+ end,
+ IQ#iq{type = result, sub_el = []}
+ end;
+ _ ->
+ IQ#iq{type = error,
+ sub_el = [IQ#iq.sub_el, ?ERR_NOT_ACCEPTABLE]}
end;
%%
process_sm_iq(#jid{luser = LUser, lserver = LServer},
- #jid{luser = LUser, lserver = LServer}, IQ)
- when IQ#iq.type == 'get' ->
+ #jid{luser = LUser, lserver = LServer}, IQ)
+ when IQ#iq.type == get ->
case IQ#iq.sub_el of
- {xmlelement, "query", Attrs, Xmlels} ->
- case filter_xmlels(Xmlels) of
- [] ->
- IQ#iq{
- type = error,
- sub_el = [IQ#iq.sub_el, ?ERR_BAD_FORMAT]
- };
- Data ->
- case catch get_data(LUser, LServer, Data) of
- {'EXIT', _Reason} ->
- IQ#iq{
- type = error,
- sub_el = [IQ#iq.sub_el, ?ERR_INTERNAL_SERVER_ERROR]
- };
- Storage_Xmlels ->
- IQ#iq{
- type = result,
- sub_el = [?Xmlel_Query(Attrs, Storage_Xmlels)]
- }
- end
- end;
- _ ->
- IQ#iq{type = error, sub_el = [IQ#iq.sub_el, ?ERR_BAD_FORMAT]}
+ #xmlel{name = <<"query">>, attrs = Attrs,
+ children = Xmlels} ->
+ case filter_xmlels(Xmlels) of
+ [] ->
+ IQ#iq{type = error,
+ sub_el = [IQ#iq.sub_el, ?ERR_BAD_FORMAT]};
+ Data ->
+ case catch get_data(LUser, LServer, Data) of
+ {'EXIT', _Reason} ->
+ IQ#iq{type = error,
+ sub_el =
+ [IQ#iq.sub_el, ?ERR_INTERNAL_SERVER_ERROR]};
+ Storage_Xmlels ->
+ IQ#iq{type = result,
+ sub_el = [?Xmlel_Query(Attrs, Storage_Xmlels)]}
+ end
+ end;
+ _ ->
+ IQ#iq{type = error,
+ sub_el = [IQ#iq.sub_el, ?ERR_BAD_FORMAT]}
end;
%%
process_sm_iq(_From, _To, IQ) ->
- IQ#iq{type = error, sub_el = [IQ#iq.sub_el, ?ERR_FORBIDDEN]}.
+ IQ#iq{type = error,
+ sub_el = [IQ#iq.sub_el, ?ERR_FORBIDDEN]}.
+filter_xmlels(Xmlels) -> filter_xmlels(Xmlels, []).
-filter_xmlels(Xmlels) ->
- filter_xmlels(Xmlels, []).
-
-filter_xmlels([], Data) ->
- lists:reverse(Data);
-filter_xmlels([{xmlelement, _, Attrs, _} = Xmlel | Xmlels], Data) ->
- case xml:get_attr_s("xmlns", Attrs) of
- "" -> [];
- XmlNS -> filter_xmlels(Xmlels, [{XmlNS, Xmlel} | Data])
+filter_xmlels([], Data) -> lists:reverse(Data);
+filter_xmlels([#xmlel{attrs = Attrs} = Xmlel | Xmlels],
+ Data) ->
+ case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ <<"">> -> [];
+ XmlNS -> filter_xmlels(Xmlels, [{XmlNS, Xmlel} | Data])
end;
filter_xmlels([_ | Xmlels], Data) ->
filter_xmlels(Xmlels, Data).
-
set_data(LUser, LServer, {XmlNS, Xmlel}, mnesia) ->
- mnesia:write(#private_storage{
- usns = {LUser, LServer, XmlNS},
- xml = Xmlel});
+ mnesia:write(#private_storage{usns =
+ {LUser, LServer, XmlNS},
+ xml = Xmlel});
set_data(LUser, LServer, {XMLNS, El}, odbc) ->
Username = ejabberd_odbc:escape(LUser),
LXMLNS = ejabberd_odbc:escape(XMLNS),
- SData = ejabberd_odbc:escape(
- xml:element_to_binary(El)),
- odbc_queries:set_private_data(LServer, Username, LXMLNS, SData).
+ SData = ejabberd_odbc:escape(xml:element_to_binary(El)),
+ odbc_queries:set_private_data(LServer, Username, LXMLNS,
+ SData).
get_data(LUser, LServer, Data) ->
- get_data(LUser, LServer, gen_mod:db_type(LServer, ?MODULE), Data, []).
+ get_data(LUser, LServer,
+ gen_mod:db_type(LServer, ?MODULE), Data, []).
-get_data(_LUser, _LServer, _DBType, [], Storage_Xmlels) ->
+get_data(_LUser, _LServer, _DBType, [],
+ Storage_Xmlels) ->
lists:reverse(Storage_Xmlels);
-get_data(LUser, LServer, mnesia, [{XmlNS, Xmlel} | Data], Storage_Xmlels) ->
- case mnesia:dirty_read(private_storage, {LUser, LServer, XmlNS}) of
- [#private_storage{xml = Storage_Xmlel}] ->
- get_data(LUser, LServer, mnesia, Data,
- [Storage_Xmlel | Storage_Xmlels]);
- _ ->
- get_data(LUser, LServer, mnesia, Data,
- [Xmlel | Storage_Xmlels])
+get_data(LUser, LServer, mnesia,
+ [{XmlNS, Xmlel} | Data], Storage_Xmlels) ->
+ case mnesia:dirty_read(private_storage,
+ {LUser, LServer, XmlNS})
+ of
+ [#private_storage{xml = Storage_Xmlel}] ->
+ get_data(LUser, LServer, mnesia, Data,
+ [Storage_Xmlel | Storage_Xmlels]);
+ _ ->
+ get_data(LUser, LServer, mnesia, Data,
+ [Xmlel | Storage_Xmlels])
end;
-get_data(LUser, LServer, odbc, [{XMLNS, El} | Els], Res) ->
+get_data(LUser, LServer, odbc, [{XMLNS, El} | Els],
+ Res) ->
Username = ejabberd_odbc:escape(LUser),
LXMLNS = ejabberd_odbc:escape(XMLNS),
- case catch odbc_queries:get_private_data(LServer, Username, LXMLNS) of
- {selected, ["data"], [{SData}]} ->
- case xml_stream:parse_element(SData) of
- Data when element(1, Data) == xmlelement ->
- get_data(LUser, LServer, odbc, Els, [Data | Res])
- end;
- %% MREMOND: I wonder when the query could return a vcard ?
- {selected, ["vcard"], []} ->
- get_data(LUser, LServer, odbc, Els, [El | Res]);
- _ ->
- get_data(LUser, LServer, odbc, Els, [El | Res])
+ case catch odbc_queries:get_private_data(LServer,
+ Username, LXMLNS)
+ of
+ {selected, [<<"data">>], [[SData]]} ->
+ case xml_stream:parse_element(SData) of
+ Data when is_record(Data, xmlel) ->
+ get_data(LUser, LServer, odbc, Els, [Data | Res])
+ end;
+ %% MREMOND: I wonder when the query could return a vcard ?
+ {selected, [<<"vcard">>], []} ->
+ get_data(LUser, LServer, odbc, Els, [El | Res]);
+ _ -> get_data(LUser, LServer, odbc, Els, [El | Res])
+ end.
+
+
+get_data(LUser, LServer) ->
+ get_all_data(LUser, LServer,
+ gen_mod:db_type(LServer, ?MODULE)).
+
+get_all_data(LUser, LServer, mnesia) ->
+ lists:flatten(
+ mnesia:dirty_select(private_storage,
+ [{#private_storage{usns = {LUser, LServer, '_'},
+ xml = '$1'},
+ [], ['$1']}]));
+get_all_data(LUser, LServer, odbc) ->
+ Username = ejabberd_odbc:escape(LUser),
+ case catch odbc_queries:get_private_data(LServer, Username) of
+ {selected, [<<"namespace">>, <<"data">>], Res} ->
+ lists:flatmap(
+ fun([_, SData]) ->
+ case xml_stream:parse_element(SData) of
+ #xmlel{} = El ->
+ [El];
+ _ ->
+ []
+ end
+ end, Res);
+ _ ->
+ []
end.
remove_user(User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
- remove_user(LUser, LServer, gen_mod:db_type(Server, ?MODULE)).
+ remove_user(LUser, LServer,
+ gen_mod:db_type(Server, ?MODULE)).
remove_user(LUser, LServer, mnesia) ->
- F = fun() ->
- Namespaces = mnesia:select(
- private_storage,
- [{#private_storage{usns={LUser, LServer, '$1'},
- _ = '_'},
- [],
- ['$$']}]),
- lists:foreach(
- fun([Namespace]) ->
- mnesia:delete({private_storage,
- {LUser, LServer, Namespace}})
- end, Namespaces)
- end,
+ F = fun () ->
+ Namespaces = mnesia:select(private_storage,
+ [{#private_storage{usns =
+ {LUser,
+ LServer,
+ '$1'},
+ _ = '_'},
+ [], ['$$']}]),
+ lists:foreach(fun ([Namespace]) ->
+ mnesia:delete({private_storage,
+ {LUser, LServer,
+ Namespace}})
+ end,
+ Namespaces)
+ end,
mnesia:transaction(F);
remove_user(LUser, LServer, odbc) ->
Username = ejabberd_odbc:escape(LUser),
- odbc_queries:del_user_private_storage(LServer, Username).
+ odbc_queries:del_user_private_storage(LServer,
+ Username).
update_table() ->
Fields = record_info(fields, private_storage),
case mnesia:table_info(private_storage, attributes) of
- Fields ->
- ok;
- [userns, xml] ->
- ?INFO_MSG("Converting private_storage table from "
- "{user, default, lists} format", []),
- Host = ?MYNAME,
- {atomic, ok} = mnesia:create_table(
- mod_private_tmp_table,
- [{disc_only_copies, [node()]},
- {type, bag},
- {local_content, true},
- {record_name, private_storage},
- {attributes, record_info(fields, private_storage)}]),
- mnesia:transform_table(private_storage, ignore, Fields),
- F1 = fun() ->
- mnesia:write_lock_table(mod_private_tmp_table),
- mnesia:foldl(
- fun(#private_storage{usns = {U, NS}} = R, _) ->
- mnesia:dirty_write(
- mod_private_tmp_table,
- R#private_storage{usns = {U, Host, NS}})
- end, ok, private_storage)
- end,
- mnesia:transaction(F1),
- mnesia:clear_table(private_storage),
- F2 = fun() ->
- mnesia:write_lock_table(private_storage),
- mnesia:foldl(
- fun(R, _) ->
- mnesia:dirty_write(R)
- end, ok, mod_private_tmp_table)
- end,
- mnesia:transaction(F2),
- mnesia:delete_table(mod_private_tmp_table);
- _ ->
- ?INFO_MSG("Recreating private_storage table", []),
- mnesia:transform_table(private_storage, ignore, Fields)
+ Fields ->
+ ejabberd_config:convert_table_to_binary(
+ private_storage, Fields, set,
+ fun(#private_storage{usns = {U, _, _}}) -> U end,
+ fun(#private_storage{usns = {U, S, NS}, xml = El} = R) ->
+ R#private_storage{usns = {iolist_to_binary(U),
+ iolist_to_binary(S),
+ iolist_to_binary(NS)},
+ xml = xml:to_xmlel(El)}
+ end);
+ _ ->
+ ?INFO_MSG("Recreating private_storage table", []),
+ mnesia:transform_table(private_storage, ignore, Fields)
end.
-
+export(_Server) ->
+ [{private_storage,
+ fun(Host, #private_storage{usns = {LUser, LServer, XMLNS},
+ xml = Data})
+ when LServer == Host ->
+ Username = ejabberd_odbc:escape(LUser),
+ LXMLNS = ejabberd_odbc:escape(XMLNS),
+ SData =
+ ejabberd_odbc:escape(xml:element_to_binary(Data)),
+ odbc_queries:set_private_data_sql(Username, LXMLNS,
+ SData);
+ (_Host, _R) ->
+ []
+ end}].
diff --git a/src/mod_proxy65/Makefile.in b/src/mod_proxy65/Makefile.in
index 3a9806b22..3fc94c662 100644
--- a/src/mod_proxy65/Makefile.in
+++ b/src/mod_proxy65/Makefile.in
@@ -14,7 +14,7 @@ EFLAGS += -pz ..
# make debug=true to compile Erlang module with debug informations.
ifdef debug
- EFLAGS+=+debug_info +export_all
+ EFLAGS+=+debug_info
endif
OUTDIR = ..
diff --git a/src/mod_proxy65/mod_proxy65.erl b/src/mod_proxy65/mod_proxy65.erl
index f335037d5..3e8354caf 100644
--- a/src/mod_proxy65/mod_proxy65.erl
+++ b/src/mod_proxy65/mod_proxy65.erl
@@ -25,9 +25,11 @@
%%%----------------------------------------------------------------------
-module(mod_proxy65).
+
-author('xram@jabber.ru').
-behaviour(gen_mod).
+
-behaviour(supervisor).
%% gen_mod callbacks.
@@ -43,15 +45,12 @@
start(Host, Opts) ->
case mod_proxy65_service:add_listener(Host, Opts) of
- {error, _} = Err ->
- erlang:error(Err);
- _ ->
- Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
- ChildSpec = {
- Proc, {?MODULE, start_link, [Host, Opts]},
- transient, infinity, supervisor, [?MODULE]
- },
- supervisor:start_child(ejabberd_sup, ChildSpec)
+ {error, _} = Err -> erlang:error(Err);
+ _ ->
+ Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
+ ChildSpec = {Proc, {?MODULE, start_link, [Host, Opts]},
+ transient, infinity, supervisor, [?MODULE]},
+ supervisor:start_child(ejabberd_sup, ChildSpec)
end.
stop(Host) ->
@@ -62,20 +61,22 @@ stop(Host) ->
start_link(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
- supervisor:start_link({local, Proc}, ?MODULE, [Host, Opts]).
+ supervisor:start_link({local, Proc}, ?MODULE,
+ [Host, Opts]).
init([Host, Opts]) ->
- Service =
- {mod_proxy65_service, {mod_proxy65_service, start_link, [Host, Opts]},
- transient, 5000, worker, [mod_proxy65_service]},
- StreamSupervisor =
- {ejabberd_mod_proxy65_sup,
- {ejabberd_tmp_sup, start_link,
- [gen_mod:get_module_proc(Host, ejabberd_mod_proxy65_sup),
- mod_proxy65_stream]},
- transient, infinity, supervisor, [ejabberd_tmp_sup]},
- StreamManager =
- {mod_proxy65_sm, {mod_proxy65_sm, start_link, [Host, Opts]},
- transient, 5000, worker, [mod_proxy65_sm]},
- {ok, {{one_for_one, 10, 1},
- [StreamManager, StreamSupervisor, Service]}}.
+ Service = {mod_proxy65_service,
+ {mod_proxy65_service, start_link, [Host, Opts]},
+ transient, 5000, worker, [mod_proxy65_service]},
+ StreamSupervisor = {ejabberd_mod_proxy65_sup,
+ {ejabberd_tmp_sup, start_link,
+ [gen_mod:get_module_proc(Host,
+ ejabberd_mod_proxy65_sup),
+ mod_proxy65_stream]},
+ transient, infinity, supervisor, [ejabberd_tmp_sup]},
+ StreamManager = {mod_proxy65_sm,
+ {mod_proxy65_sm, start_link, [Host, Opts]}, transient,
+ 5000, worker, [mod_proxy65_sm]},
+ {ok,
+ {{one_for_one, 10, 1},
+ [StreamManager, StreamSupervisor, Service]}}.
diff --git a/src/mod_proxy65/mod_proxy65.hrl b/src/mod_proxy65/mod_proxy65.hrl
index 27e2f2e61..d0779af14 100644
--- a/src/mod_proxy65/mod_proxy65.hrl
+++ b/src/mod_proxy65/mod_proxy65.hrl
@@ -26,36 +26,49 @@
%% Authentication methods
-define(AUTH_ANONYMOUS, 0).
+
-define(AUTH_GSSAPI, 1).
+
-define(AUTH_PLAIN, 2).
--define(AUTH_NO_METHODS, 16#FF).
%% Address Type
+-define(AUTH_NO_METHODS, 255).
+
-define(ATYP_IPV4, 1).
+
-define(ATYP_DOMAINNAME, 3).
+
-define(ATYP_IPV6, 4).
%% Commands
-define(CMD_CONNECT, 1).
+
-define(CMD_BIND, 2).
+
-define(CMD_UDP, 3).
%% RFC 1928 replies
-define(SUCCESS, 0).
+
-define(ERR_GENERAL_FAILURE, 1).
+
-define(ERR_NOT_ALLOWED, 2).
+
-define(ERR_NETWORK_UNREACHABLE, 3).
+
-define(ERR_HOST_UNREACHABLE, 4).
+
-define(ERR_CONNECTION_REFUSED, 5).
+
-define(ERR_TTL_EXPIRED, 6).
+
-define(ERR_COMMAND_NOT_SUPPORTED, 7).
+
-define(ERR_ADDRESS_TYPE_NOT_SUPPORTED, 8).
%% RFC 1928 defined timeout.
-define(SOCKS5_REPLY_TIMEOUT, 10000).
--record(s5_request, {
- rsv = 0,
- cmd,
- sha1
- }).
+-record(s5_request, {rsv = 0 :: integer(),
+ cmd = connect :: connect | udp,
+ sha1 = <<"">> :: binary()}).
diff --git a/src/mod_proxy65/mod_proxy65_lib.erl b/src/mod_proxy65/mod_proxy65_lib.erl
index 19ad49c05..388811436 100644
--- a/src/mod_proxy65/mod_proxy65_lib.erl
+++ b/src/mod_proxy65/mod_proxy65_lib.erl
@@ -25,59 +25,49 @@
%%%----------------------------------------------------------------------
-module(mod_proxy65_lib).
+
-author('xram@jabber.ru').
-include("mod_proxy65.hrl").
--export([
- unpack_init_message/1,
- unpack_auth_request/1,
- unpack_request/1,
- make_init_reply/1,
- make_auth_reply/1,
- make_reply/1,
- make_error_reply/1,
- make_error_reply/2
- ]).
+-export([unpack_init_message/1, unpack_auth_request/1,
+ unpack_request/1, make_init_reply/1, make_auth_reply/1,
+ make_reply/1, make_error_reply/1, make_error_reply/2]).
-unpack_init_message(<<?VERSION_5, N, AuthMethodList:N/binary>>)
- when N > 0, N < 256 ->
+unpack_init_message(<<(?VERSION_5), N,
+ AuthMethodList:N/binary>>)
+ when N > 0, N < 256 ->
{ok, binary_to_list(AuthMethodList)};
+unpack_init_message(_) -> error.
-unpack_init_message(_) ->
- error.
-
-unpack_auth_request(<<1, ULen, User:ULen/binary,
- PLen, Pass:PLen/binary>>) when ULen < 256, PLen < 256 ->
- {binary_to_list(User), binary_to_list(Pass)};
+unpack_auth_request(<<1, ULen, User:ULen/binary, PLen,
+ Pass:PLen/binary>>)
+ when ULen < 256, PLen < 256 ->
+ {(User), (Pass)};
+unpack_auth_request(_) -> error.
-unpack_auth_request(_) ->
- error.
-
-unpack_request(<<?VERSION_5, CMD, RSV,
- ?ATYP_DOMAINNAME, 40,
- SHA1:40/binary, 0, 0>>) when CMD == ?CMD_CONNECT;
- CMD == ?CMD_UDP ->
- Command = if
- CMD == ?CMD_CONNECT -> connect;
- CMD == ?CMD_UDP -> udp
+unpack_request(<<(?VERSION_5), CMD, RSV,
+ (?ATYP_DOMAINNAME), 40, SHA1:40/binary, 0, 0>>)
+ when CMD == (?CMD_CONNECT); CMD == (?CMD_UDP) ->
+ Command = if CMD == (?CMD_CONNECT) -> connect;
+ CMD == (?CMD_UDP) -> udp
end,
- #s5_request{cmd = Command, rsv = RSV, sha1 = binary_to_list(SHA1)};
-
-unpack_request(_) ->
- error.
+ #s5_request{cmd = Command, rsv = RSV, sha1 = (SHA1)};
+unpack_request(_) -> error.
-make_init_reply(Method) ->
- [?VERSION_5, Method].
+make_init_reply(Method) -> [?VERSION_5, Method].
make_auth_reply(true) -> [1, ?SUCCESS];
make_auth_reply(false) -> [1, ?ERR_NOT_ALLOWED].
make_reply(#s5_request{rsv = RSV, sha1 = SHA1}) ->
- [?VERSION_5, ?SUCCESS, RSV, ?ATYP_DOMAINNAME, length(SHA1), SHA1, 0,0].
+ [?VERSION_5, ?SUCCESS, RSV, ?ATYP_DOMAINNAME,
+ byte_size(SHA1), SHA1, 0, 0].
make_error_reply(Request) ->
make_error_reply(Request, ?ERR_NOT_ALLOWED).
-make_error_reply(#s5_request{rsv = RSV, sha1 = SHA1}, Reason) ->
- [?VERSION_5, Reason, RSV, ?ATYP_DOMAINNAME, length(SHA1), SHA1, 0,0].
+make_error_reply(#s5_request{rsv = RSV, sha1 = SHA1},
+ Reason) ->
+ [?VERSION_5, Reason, RSV, ?ATYP_DOMAINNAME,
+ byte_size(SHA1), SHA1, 0, 0].
diff --git a/src/mod_proxy65/mod_proxy65_service.erl b/src/mod_proxy65/mod_proxy65_service.erl
index d3dda4332..22ac6cde6 100644
--- a/src/mod_proxy65/mod_proxy65_service.erl
+++ b/src/mod_proxy65/mod_proxy65_service.erl
@@ -25,37 +25,33 @@
%%%----------------------------------------------------------------------
-module(mod_proxy65_service).
+
-author('xram@jabber.ru').
-behaviour(gen_server).
%% gen_server callbacks.
--export([init/1,
- handle_info/2,
- handle_call/3,
- handle_cast/2,
- terminate/2,
- code_change/3
- ]).
+-export([init/1, handle_info/2, handle_call/3,
+ handle_cast/2, terminate/2, code_change/3]).
%% API.
--export([start_link/2, add_listener/2, delete_listener/1]).
+-export([start_link/2, add_listener/2,
+ delete_listener/1]).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
-define(PROCNAME, ejabberd_mod_proxy65_service).
--record(state, {
- myhost,
- serverhost,
- name,
- stream_addr,
- port,
- ip,
- acl
- }).
-
+-record(state,
+ {myhost = <<"">> :: binary(),
+ serverhost = <<"">> :: binary(),
+ name = <<"">> :: binary(),
+ stream_addr = [] :: [attr()],
+ port = 0 :: inet:port_number(),
+ ip = {127,0,0,1} :: inet:ip_address(),
+ acl = none :: atom()}).
%%%------------------------
%%% gen_server callbacks
@@ -63,43 +59,44 @@
start_link(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
- gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
+ gen_server:start_link({local, Proc}, ?MODULE,
+ [Host, Opts], []).
init([Host, Opts]) ->
State = parse_options(Host, Opts),
ejabberd_router:register_route(State#state.myhost),
{ok, State}.
-terminate(_Reason, #state{myhost=MyHost}) ->
- ejabberd_router:unregister_route(MyHost),
- ok.
+terminate(_Reason, #state{myhost = MyHost}) ->
+ ejabberd_router:unregister_route(MyHost), ok.
-handle_info({route, From, To, {xmlelement, "iq", _, _} = Packet}, State) ->
+handle_info({route, From, To,
+ #xmlel{name = <<"iq">>} = Packet},
+ State) ->
IQ = jlib:iq_query_info(Packet),
case catch process_iq(From, IQ, State) of
- Result when is_record(Result, iq) ->
- ejabberd_router:route(To, From, jlib:iq_to_xml(Result));
- {'EXIT', Reason} ->
- ?ERROR_MSG("Error when processing IQ stanza: ~p", [Reason]),
- Err = jlib:make_error_reply(Packet, ?ERR_INTERNAL_SERVER_ERROR),
- ejabberd_router:route(To, From, Err);
- _ ->
- ok
+ Result when is_record(Result, iq) ->
+ ejabberd_router:route(To, From, jlib:iq_to_xml(Result));
+ {'EXIT', Reason} ->
+ ?ERROR_MSG("Error when processing IQ stanza: ~p",
+ [Reason]),
+ Err = jlib:make_error_reply(Packet,
+ ?ERR_INTERNAL_SERVER_ERROR),
+ ejabberd_router:route(To, From, Err);
+ _ -> ok
end,
{noreply, State};
-handle_info(_Info, State) ->
- {noreply, State}.
+handle_info(_Info, State) -> {noreply, State}.
handle_call(get_port_ip, _From, State) ->
- {reply, {port_ip, State#state.port, State#state.ip}, State};
+ {reply, {port_ip, State#state.port, State#state.ip},
+ State};
handle_call(_Request, _From, State) ->
{reply, ok, State}.
-handle_cast(_Request, State) ->
- {noreply, State}.
+handle_cast(_Request, State) -> {noreply, State}.
-code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
+code_change(_OldVsn, State, _Extra) -> {ok, State}.
%%%------------------------
%%% Listener management
@@ -108,141 +105,181 @@ code_change(_OldVsn, State, _Extra) ->
add_listener(Host, Opts) ->
State = parse_options(Host, Opts),
NewOpts = [Host | Opts],
- ejabberd_listener:add_listener({State#state.port, State#state.ip}, mod_proxy65_stream, NewOpts).
+ ejabberd_listener:add_listener({State#state.port,
+ State#state.ip},
+ mod_proxy65_stream, NewOpts).
delete_listener(Host) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
- {port_ip, Port, IP} = gen_server:call(Proc, get_port_ip),
- catch ejabberd_listener:delete_listener({Port, IP}, mod_proxy65_stream).
+ {port_ip, Port, IP} = gen_server:call(Proc,
+ get_port_ip),
+ catch ejabberd_listener:delete_listener({Port, IP},
+ mod_proxy65_stream).
%%%------------------------
%%% IQ Processing
%%%------------------------
%% disco#info request
-process_iq(_, #iq{type = get, xmlns = ?NS_DISCO_INFO, lang = Lang} = IQ,
- #state{name=Name, serverhost=ServerHost}) ->
- Info = ejabberd_hooks:run_fold(
- disco_info, ServerHost, [], [ServerHost, ?MODULE, "", ""]),
- IQ#iq{type = result, sub_el =
- [{xmlelement, "query", [{"xmlns", ?NS_DISCO_INFO}],
- iq_disco_info(Lang, Name) ++ Info}]};
-
+process_iq(_,
+ #iq{type = get, xmlns = ?NS_DISCO_INFO, lang = Lang} =
+ IQ,
+ #state{name = Name, serverhost = ServerHost}) ->
+ Info = ejabberd_hooks:run_fold(disco_info, ServerHost,
+ [], [ServerHost, ?MODULE, <<"">>, <<"">>]),
+ IQ#iq{type = result,
+ sub_el =
+ [#xmlel{name = <<"query">>,
+ attrs = [{<<"xmlns">>, ?NS_DISCO_INFO}],
+ children = iq_disco_info(Lang, Name) ++ Info}]};
%% disco#items request
-process_iq(_, #iq{type = get, xmlns = ?NS_DISCO_ITEMS} = IQ, _) ->
- IQ#iq{type = result, sub_el =
- [{xmlelement, "query", [{"xmlns", ?NS_DISCO_ITEMS}], []}]};
-
+process_iq(_,
+ #iq{type = get, xmlns = ?NS_DISCO_ITEMS} = IQ, _) ->
+ IQ#iq{type = result,
+ sub_el =
+ [#xmlel{name = <<"query">>,
+ attrs = [{<<"xmlns">>, ?NS_DISCO_ITEMS}],
+ children = []}]};
%% vCard request
-process_iq(_, #iq{type = get, xmlns = ?NS_VCARD, lang = Lang} = IQ, _) ->
- IQ#iq{type = result, sub_el =
- [{xmlelement, "vCard", [{"xmlns", ?NS_VCARD}], iq_vcard(Lang)}]};
-
+process_iq(_,
+ #iq{type = get, xmlns = ?NS_VCARD, lang = Lang} = IQ,
+ _) ->
+ IQ#iq{type = result,
+ sub_el =
+ [#xmlel{name = <<"vCard">>,
+ attrs = [{<<"xmlns">>, ?NS_VCARD}],
+ children = iq_vcard(Lang)}]};
%% bytestreams info request
-process_iq(JID, #iq{type = get, sub_el = SubEl, xmlns = ?NS_BYTESTREAMS} = IQ,
- #state{acl = ACL, stream_addr = StreamAddr, serverhost = ServerHost}) ->
+process_iq(JID,
+ #iq{type = get, sub_el = SubEl,
+ xmlns = ?NS_BYTESTREAMS} =
+ IQ,
+ #state{acl = ACL, stream_addr = StreamAddr,
+ serverhost = ServerHost}) ->
case acl:match_rule(ServerHost, ACL, JID) of
- allow ->
- StreamHostEl = [{xmlelement, "streamhost", StreamAddr, []}],
- IQ#iq{type = result, sub_el =
- [{xmlelement, "query", [{"xmlns", ?NS_BYTESTREAMS}], StreamHostEl}]};
- deny ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]}
+ allow ->
+ StreamHostEl = [#xmlel{name = <<"streamhost">>,
+ attrs = StreamAddr, children = []}],
+ IQ#iq{type = result,
+ sub_el =
+ [#xmlel{name = <<"query">>,
+ attrs = [{<<"xmlns">>, ?NS_BYTESTREAMS}],
+ children = StreamHostEl}]};
+ deny ->
+ IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]}
end;
-
%% bytestream activation request
-process_iq(InitiatorJID, #iq{type = set, sub_el = SubEl, xmlns = ?NS_BYTESTREAMS} = IQ,
+process_iq(InitiatorJID,
+ #iq{type = set, sub_el = SubEl,
+ xmlns = ?NS_BYTESTREAMS} =
+ IQ,
#state{acl = ACL, serverhost = ServerHost}) ->
case acl:match_rule(ServerHost, ACL, InitiatorJID) of
- allow ->
- ActivateEl = xml:get_path_s(SubEl, [{elem, "activate"}]),
- SID = xml:get_tag_attr_s("sid", SubEl),
- case catch jlib:string_to_jid(xml:get_tag_cdata(ActivateEl)) of
- TargetJID when is_record(TargetJID, jid), SID /= "",
- length(SID) =< 128, TargetJID /= InitiatorJID ->
- Target = jlib:jid_to_string(jlib:jid_tolower(TargetJID)),
- Initiator = jlib:jid_to_string(jlib:jid_tolower(InitiatorJID)),
- SHA1 = sha:sha(SID ++ Initiator ++ Target),
- case mod_proxy65_sm:activate_stream(SHA1, InitiatorJID, TargetJID, ServerHost) of
- ok ->
- IQ#iq{type = result, sub_el = []};
- false ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_ITEM_NOT_FOUND]};
- limit ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_RESOURCE_CONSTRAINT]};
- conflict ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_CONFLICT]};
- _ ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]}
- end;
- _ ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]}
- end;
- deny ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]}
+ allow ->
+ ActivateEl = xml:get_path_s(SubEl,
+ [{elem, <<"activate">>}]),
+ SID = xml:get_tag_attr_s(<<"sid">>, SubEl),
+ case catch
+ jlib:string_to_jid(xml:get_tag_cdata(ActivateEl))
+ of
+ TargetJID
+ when is_record(TargetJID, jid), SID /= <<"">>,
+ byte_size(SID) =< 128, TargetJID /= InitiatorJID ->
+ Target =
+ jlib:jid_to_string(jlib:jid_tolower(TargetJID)),
+ Initiator =
+ jlib:jid_to_string(jlib:jid_tolower(InitiatorJID)),
+ SHA1 = sha:sha(<<SID/binary, Initiator/binary, Target/binary>>),
+ case mod_proxy65_sm:activate_stream(SHA1, InitiatorJID,
+ TargetJID, ServerHost)
+ of
+ ok -> IQ#iq{type = result, sub_el = []};
+ false ->
+ IQ#iq{type = error,
+ sub_el = [SubEl, ?ERR_ITEM_NOT_FOUND]};
+ limit ->
+ IQ#iq{type = error,
+ sub_el = [SubEl, ?ERR_RESOURCE_CONSTRAINT]};
+ conflict ->
+ IQ#iq{type = error, sub_el = [SubEl, ?ERR_CONFLICT]};
+ _ ->
+ IQ#iq{type = error,
+ sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]}
+ end;
+ _ ->
+ IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]}
+ end;
+ deny ->
+ IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]}
end;
-
%% Unknown "set" or "get" request
-process_iq(_, #iq{type=Type, sub_el=SubEl} = IQ, _) when Type==get; Type==set ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_SERVICE_UNAVAILABLE]};
-
+process_iq(_, #iq{type = Type, sub_el = SubEl} = IQ, _)
+ when Type == get; Type == set ->
+ IQ#iq{type = error,
+ sub_el = [SubEl, ?ERR_SERVICE_UNAVAILABLE]};
%% IQ "result" or "error".
-process_iq(_, _, _) ->
- ok.
+process_iq(_, _, _) -> ok.
%%%-------------------------
%%% Auxiliary functions.
%%%-------------------------
--define(FEATURE(Feat), {xmlelement,"feature",[{"var", Feat}],[]}).
+-define(FEATURE(Feat),
+ #xmlel{name = <<"feature">>,
+ attrs = [{<<"var">>, Feat}], children = []}).
iq_disco_info(Lang, Name) ->
- [{xmlelement, "identity",
- [{"category", "proxy"},
- {"type", "bytestreams"},
- {"name", translate:translate(Lang, Name)}], []},
- ?FEATURE(?NS_DISCO_INFO),
- ?FEATURE(?NS_VCARD),
- ?FEATURE(?NS_BYTESTREAMS)].
+ [#xmlel{name = <<"identity">>,
+ attrs =
+ [{<<"category">>, <<"proxy">>},
+ {<<"type">>, <<"bytestreams">>},
+ {<<"name">>, translate:translate(Lang, Name)}],
+ children = []},
+ ?FEATURE((?NS_DISCO_INFO)), ?FEATURE((?NS_VCARD)),
+ ?FEATURE((?NS_BYTESTREAMS))].
iq_vcard(Lang) ->
- [{xmlelement, "FN", [],
- [{xmlcdata, "ejabberd/mod_proxy65"}]},
- {xmlelement, "URL", [],
- [{xmlcdata, ?EJABBERD_URI}]},
- {xmlelement, "DESC", [],
- [{xmlcdata, translate:translate(Lang, "ejabberd SOCKS5 Bytestreams module") ++
- "\nCopyright (c) 2003-2013 ProcessOne"}]}].
+ [#xmlel{name = <<"FN">>, attrs = [],
+ children = [{xmlcdata, <<"ejabberd/mod_proxy65">>}]},
+ #xmlel{name = <<"URL">>, attrs = [],
+ children = [{xmlcdata, ?EJABBERD_URI}]},
+ #xmlel{name = <<"DESC">>, attrs = [],
+ children =
+ [{xmlcdata,
+ <<(translate:translate(Lang,
+ <<"ejabberd SOCKS5 Bytestreams module">>))/binary,
+ "\nCopyright (c) 2003-2013 ProcessOne">>}]}].
parse_options(ServerHost, Opts) ->
- MyHost = gen_mod:get_opt_host(ServerHost, Opts, "proxy.@HOST@"),
- Port = gen_mod:get_opt(port, Opts, 7777),
- ACL = gen_mod:get_opt(access, Opts, all),
- Name = gen_mod:get_opt(name, Opts, "SOCKS5 Bytestreams"),
- IP = case gen_mod:get_opt(ip, Opts, none) of
- none -> get_my_ip();
- Addr -> Addr
- end,
- HostName = case gen_mod:get_opt(hostname, Opts, none) of
- none ->
- inet_parse:ntoa(IP);
- HostAddr when is_tuple(HostAddr) ->
- inet_parse:ntoa(HostAddr);
- HostNameStr ->
- HostNameStr
- end,
- StreamAddr = [{"jid", MyHost}, {"host", HostName},
- {"port", integer_to_list(Port)}],
- #state{myhost = MyHost,
- serverhost = ServerHost,
- name = Name,
- port = Port,
- ip = IP,
- stream_addr = StreamAddr,
- acl = ACL}.
+ MyHost = gen_mod:get_opt_host(ServerHost, Opts,
+ <<"proxy.@HOST@">>),
+ Port = gen_mod:get_opt(port, Opts,
+ fun(P) when is_integer(P), P>0, P<65536 -> P end,
+ 7777),
+ ACL = gen_mod:get_opt(access, Opts, fun(A) when is_atom(A) -> A end,
+ all),
+ Name = gen_mod:get_opt(name, Opts, fun iolist_to_binary/1,
+ <<"SOCKS5 Bytestreams">>),
+ IP = gen_mod:get_opt(ip, Opts,
+ fun(Addr) ->
+ jlib:ip_to_list(Addr),
+ Addr
+ end, get_my_ip()),
+ HostName = gen_mod:get_opt(hostname, Opts,
+ fun(Addr) when is_tuple(Addr) ->
+ jlib:ip_to_list(Addr);
+ (S) ->
+ iolist_to_binary(S)
+ end, jlib:ip_to_list(IP)),
+ StreamAddr = [{<<"jid">>, MyHost},
+ {<<"host">>, HostName},
+ {<<"port">>, jlib:integer_to_binary(Port)}],
+ #state{myhost = MyHost, serverhost = ServerHost,
+ name = Name, port = Port, ip = IP,
+ stream_addr = StreamAddr, acl = ACL}.
get_my_ip() ->
{ok, MyHostName} = inet:gethostname(),
case inet:getaddr(MyHostName, inet) of
- {ok, Addr} -> Addr;
- {error, _} -> {127,0,0,1}
+ {ok, Addr} -> Addr;
+ {error, _} -> {127, 0, 0, 1}
end.
diff --git a/src/mod_proxy65/mod_proxy65_sm.erl b/src/mod_proxy65/mod_proxy65_sm.erl
index b5af45abc..fa9d257ef 100644
--- a/src/mod_proxy65/mod_proxy65_sm.erl
+++ b/src/mod_proxy65/mod_proxy65_sm.erl
@@ -25,96 +25,91 @@
%%%----------------------------------------------------------------------
-module(mod_proxy65_sm).
+
-author('xram@jabber.ru').
-behaviour(gen_server).
%% gen_server callbacks.
--export([init/1,
- handle_info/2,
- handle_call/3,
- handle_cast/2,
- terminate/2,
- code_change/3
- ]).
+-export([init/1, handle_info/2, handle_call/3,
+ handle_cast/2, terminate/2, code_change/3]).
%% API.
--export([
- start_link/2,
- register_stream/1,
- unregister_stream/1,
- activate_stream/4
- ]).
-
--record(state, {max_connections}).
--record(bytestream, {
- sha1, %% SHA1 key
- target, %% Target Pid
- initiator, %% Initiator Pid
- active = false, %% Activity flag
- jid_i %% Initiator's JID
- }).
+-export([start_link/2, register_stream/1,
+ unregister_stream/1, activate_stream/4]).
+
+-record(state, {max_connections = infinity :: non_neg_integer() | infinity}).
+
+-include("jlib.hrl").
+
+-record(bytestream,
+ {sha1 = <<"">> :: binary() | '$1',
+ target :: pid() | '_',
+ initiator :: pid() | '_',
+ active = false :: boolean() | '_',
+ jid_i = {<<"">>, <<"">>, <<"">>} :: ljid() | '_'}).
-define(PROCNAME, ejabberd_mod_proxy65_sm).
%% Unused callbacks.
-handle_cast(_Request, State) ->
- {noreply, State}.
-code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
-handle_info(_Info, State) ->
- {noreply, State}.
+handle_cast(_Request, State) -> {noreply, State}.
+
+code_change(_OldVsn, State, _Extra) -> {ok, State}.
+
+handle_info(_Info, State) -> {noreply, State}.
+
%%----------------
start_link(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
- gen_server:start_link({local, Proc}, ?MODULE, [Opts], []).
+ gen_server:start_link({local, Proc}, ?MODULE, [Opts],
+ []).
init([Opts]) ->
mnesia:create_table(bytestream, [{ram_copies, [node()]},
{attributes, record_info(fields, bytestream)}]),
mnesia:add_table_copy(bytestream, node(), ram_copies),
- MaxConnections = gen_mod:get_opt(max_connections, Opts, infinity),
- {ok, #state{max_connections=MaxConnections}}.
+ MaxConnections = gen_mod:get_opt(max_connections, Opts,
+ fun(I) when is_integer(I), I>0 ->
+ I;
+ (infinity) ->
+ infinity
+ end, infinity),
+ {ok, #state{max_connections = MaxConnections}}.
-terminate(_Reason, _State) ->
- ok.
+terminate(_Reason, _State) -> ok.
handle_call({activate, SHA1, IJid}, _From, State) ->
MaxConns = State#state.max_connections,
- F = fun() ->
+ F = fun () ->
case mnesia:read(bytestream, SHA1, write) of
- [#bytestream{target = TPid, initiator = IPid} = ByteStream]
- when is_pid(TPid), is_pid(IPid) ->
- ActiveFlag = ByteStream#bytestream.active,
- if
- ActiveFlag == false ->
- ConnsPerJID =
- mnesia:select(bytestream,
- [{#bytestream{sha1 = '$1',
- jid_i = IJid,
- _='_'},
- [],
- ['$1']}]),
- if
- length(ConnsPerJID) < MaxConns ->
- mnesia:write(
- ByteStream#bytestream{active = true,
- jid_i = IJid}),
- {ok, IPid, TPid};
- true ->
- {limit, IPid, TPid}
- end;
- true ->
- conflict
- end;
- _ ->
- false
+ [#bytestream{target = TPid, initiator = IPid} =
+ ByteStream]
+ when is_pid(TPid), is_pid(IPid) ->
+ ActiveFlag = ByteStream#bytestream.active,
+ if ActiveFlag == false ->
+ ConnsPerJID = mnesia:select(bytestream,
+ [{#bytestream{sha1 =
+ '$1',
+ jid_i =
+ IJid,
+ _ = '_'},
+ [], ['$1']}]),
+ if length(ConnsPerJID) < MaxConns ->
+ mnesia:write(ByteStream#bytestream{active =
+ true,
+ jid_i =
+ IJid}),
+ {ok, IPid, TPid};
+ true -> {limit, IPid, TPid}
+ end;
+ true -> conflict
+ end;
+ _ -> false
end
end,
Reply = mnesia:transaction(F),
{reply, Reply, State};
-
handle_call(_Request, _From, State) ->
{reply, ok, State}.
@@ -127,20 +122,19 @@ handle_call(_Request, _From, State) ->
%%% transaction abort
%%% SHA1 = string()
%%%---------------------------------------------------
-register_stream(SHA1) when is_list(SHA1) ->
+register_stream(SHA1) when is_binary(SHA1) ->
StreamPid = self(),
- F = fun() ->
+ F = fun () ->
case mnesia:read(bytestream, SHA1, write) of
- [] ->
- mnesia:write(#bytestream{sha1 = SHA1,
- target = StreamPid});
- [#bytestream{target = Pid,
- initiator = undefined} = ByteStream]
- when is_pid(Pid), Pid /= StreamPid ->
- mnesia:write(
- ByteStream#bytestream{initiator = StreamPid});
- _ ->
- error
+ [] ->
+ mnesia:write(#bytestream{sha1 = SHA1,
+ target = StreamPid});
+ [#bytestream{target = Pid, initiator = undefined} =
+ ByteStream]
+ when is_pid(Pid), Pid /= StreamPid ->
+ mnesia:write(ByteStream#bytestream{initiator =
+ StreamPid});
+ _ -> error
end
end,
mnesia:transaction(F).
@@ -149,8 +143,8 @@ register_stream(SHA1) when is_list(SHA1) ->
%%% unregister_stream(SHA1) -> ok | transaction abort
%%% SHA1 = string()
%%%----------------------------------------------------
-unregister_stream(SHA1) when is_list(SHA1) ->
- F = fun() -> mnesia:delete({bytestream, SHA1}) end,
+unregister_stream(SHA1) when is_binary(SHA1) ->
+ F = fun () -> mnesia:delete({bytestream, SHA1}) end,
mnesia:transaction(F).
%%%--------------------------------------------------------
@@ -163,19 +157,18 @@ unregister_stream(SHA1) when is_list(SHA1) ->
%%% IJid = TJid = jid()
%%% Host = string()
%%%--------------------------------------------------------
-activate_stream(SHA1, IJid, TJid, Host) when is_list(SHA1) ->
+activate_stream(SHA1, IJid, TJid, Host)
+ when is_binary(SHA1) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
- case catch gen_server:call(Proc, {activate, SHA1, IJid}) of
- {atomic, {ok, IPid, TPid}} ->
- mod_proxy65_stream:activate({IPid, IJid}, {TPid, TJid});
- {atomic, {limit, IPid, TPid}} ->
- mod_proxy65_stream:stop(IPid),
- mod_proxy65_stream:stop(TPid),
- limit;
- {atomic, conflict} ->
- conflict;
- {atomic, false} ->
- false;
- _ ->
- error
+ case catch gen_server:call(Proc, {activate, SHA1, IJid})
+ of
+ {atomic, {ok, IPid, TPid}} ->
+ mod_proxy65_stream:activate({IPid, IJid}, {TPid, TJid});
+ {atomic, {limit, IPid, TPid}} ->
+ mod_proxy65_stream:stop(IPid),
+ mod_proxy65_stream:stop(TPid),
+ limit;
+ {atomic, conflict} -> conflict;
+ {atomic, false} -> false;
+ _ -> error
end.
diff --git a/src/mod_proxy65/mod_proxy65_stream.erl b/src/mod_proxy65/mod_proxy65_stream.erl
index 7bc2d58a8..1eca99b39 100644
--- a/src/mod_proxy65/mod_proxy65_stream.erl
+++ b/src/mod_proxy65/mod_proxy65_stream.erl
@@ -24,168 +24,169 @@
%%%----------------------------------------------------------------------
-module(mod_proxy65_stream).
+
-author('xram@jabber.ru').
-behaviour(gen_fsm).
%% gen_fsm callbacks.
--export([
- init/1,
- handle_event/3,
- handle_sync_event/4,
- code_change/4,
- handle_info/3,
- terminate/3
- ]).
+-export([init/1, handle_event/3, handle_sync_event/4,
+ code_change/4, handle_info/3, terminate/3]).
%% gen_fsm states.
--export([
- wait_for_init/2,
- wait_for_auth/2,
- wait_for_request/2,
- wait_for_activation/2,
- stream_established/2
- ]).
+-export([wait_for_init/2, wait_for_auth/2,
+ wait_for_request/2, wait_for_activation/2,
+ stream_established/2]).
%% API.
--export([
- start/2,
- stop/1,
- start_link/3,
- activate/2,
- relay/3,
- socket_type/0
- ]).
+-export([start/2, stop/1, start_link/3, activate/2,
+ relay/3, socket_type/0]).
-include("mod_proxy65.hrl").
+
-include("ejabberd.hrl").
--define(WAIT_TIMEOUT, 60000). %% 1 minute (is it enough?)
+-define(WAIT_TIMEOUT, 60000).
--record(state, {
- socket, %% TCP socket
- timer, %% timer reference
- sha1, %% SHA1 key
- host, %% virtual host
- auth_type, %% authentication type: anonymous or plain
- shaper %% Shaper name
- }).
+-record(state,
+ {socket :: inet:socket(),
+ timer = make_ref() :: reference(),
+ sha1 = <<"">> :: binary(),
+ host = <<"">> :: binary(),
+ auth_type = anonymous :: plain | anonymous,
+ shaper = none :: shaper:shaper()}).
%% Unused callbacks
handle_event(_Event, StateName, StateData) ->
{next_state, StateName, StateData}.
+
code_change(_OldVsn, StateName, StateData, _Extra) ->
{ok, StateName, StateData}.
+
%%-------------------------------
start({gen_tcp, Socket}, Opts1) ->
- {[Host], Opts} = lists:partition(fun(O) -> is_list(O) end, Opts1),
- Supervisor = gen_mod:get_module_proc(Host, ejabberd_mod_proxy65_sup),
- supervisor:start_child(Supervisor, [Socket, Host, Opts]).
+ {[Host], Opts} = lists:partition(fun (O) -> is_binary(O)
+ end,
+ Opts1),
+ Supervisor = gen_mod:get_module_proc(Host,
+ ejabberd_mod_proxy65_sup),
+ supervisor:start_child(Supervisor,
+ [Socket, Host, Opts]).
start_link(Socket, Host, Opts) ->
gen_fsm:start_link(?MODULE, [Socket, Host, Opts], []).
init([Socket, Host, Opts]) ->
process_flag(trap_exit, true),
- AuthType = gen_mod:get_opt(auth_type, Opts, anonymous),
- Shaper = gen_mod:get_opt(shaper, Opts, none),
- RecvBuf = gen_mod:get_opt(recbuf, Opts, 8192),
- SendBuf = gen_mod:get_opt(sndbuf, Opts, 8192),
+ AuthType = gen_mod:get_opt(auth_type, Opts,
+ fun(plain) -> plain;
+ (anonymous) -> anonymous
+ end, anonymous),
+ Shaper = gen_mod:get_opt(shaper, Opts,
+ fun(A) when is_atom(A) -> A end,
+ none),
+ RecvBuf = gen_mod:get_opt(recbuf, Opts,
+ fun(I) when is_integer(I), I>0 -> I end,
+ 8192),
+ SendBuf = gen_mod:get_opt(sndbuf, Opts,
+ fun(I) when is_integer(I), I>0 -> I end,
+ 8192),
TRef = erlang:send_after(?WAIT_TIMEOUT, self(), stop),
- inet:setopts(Socket, [{active, true}, {recbuf, RecvBuf}, {sndbuf, SendBuf}]),
- {ok, wait_for_init, #state{host = Host,
- auth_type = AuthType,
- socket = Socket,
- shaper = Shaper,
- timer = TRef}}.
+ inet:setopts(Socket,
+ [{active, true}, {recbuf, RecvBuf}, {sndbuf, SendBuf}]),
+ {ok, wait_for_init,
+ #state{host = Host, auth_type = AuthType,
+ socket = Socket, shaper = Shaper, timer = TRef}}.
-terminate(_Reason, StateName, #state{sha1=SHA1}) ->
+terminate(_Reason, StateName, #state{sha1 = SHA1}) ->
catch mod_proxy65_sm:unregister_stream(SHA1),
if StateName == stream_established ->
- ?INFO_MSG("Bytestream terminated", []);
- true ->
- ok
+ ?INFO_MSG("Bytestream terminated", []);
+ true -> ok
end.
%%%------------------------------
%%% API.
%%%------------------------------
-socket_type() ->
- raw.
+socket_type() -> raw.
-stop(StreamPid) ->
- StreamPid ! stop.
+stop(StreamPid) -> StreamPid ! stop.
activate({P1, J1}, {P2, J2}) ->
- case catch {gen_fsm:sync_send_all_state_event(P1, get_socket),
- gen_fsm:sync_send_all_state_event(P2, get_socket)} of
- {S1, S2} when is_port(S1), is_port(S2) ->
- P1 ! {activate, P2, S2, J1, J2},
- P2 ! {activate, P1, S1, J1, J2},
- JID1 = jlib:jid_to_string(J1),
- JID2 = jlib:jid_to_string(J2),
- ?INFO_MSG("(~w:~w) Activated bytestream for ~s -> ~s", [P1, P2, JID1, JID2]),
- ok;
- _ ->
- error
+ case catch {gen_fsm:sync_send_all_state_event(P1,
+ get_socket),
+ gen_fsm:sync_send_all_state_event(P2, get_socket)}
+ of
+ {S1, S2} when is_port(S1), is_port(S2) ->
+ P1 ! {activate, P2, S2, J1, J2},
+ P2 ! {activate, P1, S1, J1, J2},
+ JID1 = jlib:jid_to_string(J1),
+ JID2 = jlib:jid_to_string(J2),
+ ?INFO_MSG("(~w:~w) Activated bytestream for ~s "
+ "-> ~s",
+ [P1, P2, JID1, JID2]),
+ ok;
+ _ -> error
end.
%%%-----------------------
%%% States
%%%-----------------------
-wait_for_init(Packet, #state{socket=Socket, auth_type=AuthType} = StateData) ->
+wait_for_init(Packet,
+ #state{socket = Socket, auth_type = AuthType} =
+ StateData) ->
case mod_proxy65_lib:unpack_init_message(Packet) of
- {ok, AuthMethods} ->
- Method = select_auth_method(AuthType, AuthMethods),
- gen_tcp:send(Socket, mod_proxy65_lib:make_init_reply(Method)),
- case Method of
- ?AUTH_ANONYMOUS ->
- {next_state, wait_for_request, StateData};
- ?AUTH_PLAIN ->
- {next_state, wait_for_auth, StateData};
- ?AUTH_NO_METHODS ->
- {stop, normal, StateData}
- end;
- error ->
- {stop, normal, StateData}
+ {ok, AuthMethods} ->
+ Method = select_auth_method(AuthType, AuthMethods),
+ gen_tcp:send(Socket,
+ mod_proxy65_lib:make_init_reply(Method)),
+ case Method of
+ ?AUTH_ANONYMOUS ->
+ {next_state, wait_for_request, StateData};
+ ?AUTH_PLAIN -> {next_state, wait_for_auth, StateData};
+ ?AUTH_NO_METHODS -> {stop, normal, StateData}
+ end;
+ error -> {stop, normal, StateData}
end.
-wait_for_auth(Packet, #state{socket=Socket, host=Host} = StateData) ->
+wait_for_auth(Packet,
+ #state{socket = Socket, host = Host} = StateData) ->
case mod_proxy65_lib:unpack_auth_request(Packet) of
- {User, Pass} ->
- Result = ejabberd_auth:check_password(User, Host, Pass),
- gen_tcp:send(Socket, mod_proxy65_lib:make_auth_reply(Result)),
- case Result of
- true ->
- {next_state, wait_for_request, StateData};
- false ->
- {stop, normal, StateData}
- end;
- _ ->
- {stop, normal, StateData}
+ {User, Pass} ->
+ Result = ejabberd_auth:check_password(User, Host, Pass),
+ gen_tcp:send(Socket,
+ mod_proxy65_lib:make_auth_reply(Result)),
+ case Result of
+ true -> {next_state, wait_for_request, StateData};
+ false -> {stop, normal, StateData}
+ end;
+ _ -> {stop, normal, StateData}
end.
-wait_for_request(Packet, #state{socket=Socket} = StateData) ->
+wait_for_request(Packet,
+ #state{socket = Socket} = StateData) ->
Request = mod_proxy65_lib:unpack_request(Packet),
case Request of
- #s5_request{sha1=SHA1, cmd=connect} ->
- case catch mod_proxy65_sm:register_stream(SHA1) of
- {atomic, ok} ->
- inet:setopts(Socket, [{active, false}]),
- gen_tcp:send(Socket, mod_proxy65_lib:make_reply(Request)),
- {next_state, wait_for_activation, StateData#state{sha1=SHA1}};
- _ ->
- Err = mod_proxy65_lib:make_error_reply(Request),
- gen_tcp:send(Socket, Err),
- {stop, normal, StateData}
- end;
- #s5_request{cmd=udp} ->
- Err = mod_proxy65_lib:make_error_reply(Request, ?ERR_COMMAND_NOT_SUPPORTED),
- gen_tcp:send(Socket, Err),
- {stop, normal, StateData};
- _ ->
- {stop, normal, StateData}
+ #s5_request{sha1 = SHA1, cmd = connect} ->
+ case catch mod_proxy65_sm:register_stream(SHA1) of
+ {atomic, ok} ->
+ inet:setopts(Socket, [{active, false}]),
+ gen_tcp:send(Socket,
+ mod_proxy65_lib:make_reply(Request)),
+ {next_state, wait_for_activation,
+ StateData#state{sha1 = SHA1}};
+ _ ->
+ Err = mod_proxy65_lib:make_error_reply(Request),
+ gen_tcp:send(Socket, Err),
+ {stop, normal, StateData}
+ end;
+ #s5_request{cmd = udp} ->
+ Err = mod_proxy65_lib:make_error_reply(Request,
+ ?ERR_COMMAND_NOT_SUPPORTED),
+ gen_tcp:send(Socket, Err),
+ {stop, normal, StateData};
+ _ -> {stop, normal, StateData}
end.
wait_for_activation(_Data, StateData) ->
@@ -200,12 +201,11 @@ stream_established(_Data, StateData) ->
%% SOCKS5 packets.
handle_info({tcp, _S, Data}, StateName, StateData)
- when StateName /= wait_for_activation ->
+ when StateName /= wait_for_activation ->
erlang:cancel_timer(StateData#state.timer),
TRef = erlang:send_after(?WAIT_TIMEOUT, self(), stop),
gen_fsm:send_event(self(), Data),
- {next_state, StateName, StateData#state{timer=TRef}};
-
+ {next_state, StateName, StateData#state{timer = TRef}};
%% Activation message.
handle_info({activate, PeerPid, PeerSocket, IJid, TJid},
wait_for_activation, StateData) ->
@@ -215,35 +215,36 @@ handle_info({activate, PeerPid, PeerSocket, IJid, TJid},
Shaper = StateData#state.shaper,
Host = StateData#state.host,
MaxRate = find_maxrate(Shaper, IJid, TJid, Host),
- spawn_link(?MODULE, relay, [MySocket, PeerSocket, MaxRate]),
+ spawn_link(?MODULE, relay,
+ [MySocket, PeerSocket, MaxRate]),
{next_state, stream_established, StateData};
-
%% Socket closed
-handle_info({tcp_closed, _Socket}, _StateName, StateData) ->
+handle_info({tcp_closed, _Socket}, _StateName,
+ StateData) ->
{stop, normal, StateData};
-handle_info({tcp_error, _Socket, _Reason}, _StateName, StateData) ->
+handle_info({tcp_error, _Socket, _Reason}, _StateName,
+ StateData) ->
{stop, normal, StateData};
-
%% Got stop message.
handle_info(stop, _StateName, StateData) ->
{stop, normal, StateData};
-
%% Either linked process or peer process died.
-handle_info({'EXIT',_,_}, _StateName, StateData) ->
+handle_info({'EXIT', _, _}, _StateName, StateData) ->
{stop, normal, StateData};
-handle_info({'DOWN',_,_,_,_}, _StateName, StateData) ->
+handle_info({'DOWN', _, _, _, _}, _StateName,
+ StateData) ->
{stop, normal, StateData};
-
%% Packets of no interest
handle_info(_Info, StateName, StateData) ->
{next_state, StateName, StateData}.
%% Socket request.
-handle_sync_event(get_socket, _From, wait_for_activation, StateData) ->
+handle_sync_event(get_socket, _From,
+ wait_for_activation, StateData) ->
Socket = StateData#state.socket,
{reply, Socket, wait_for_activation, StateData};
-
-handle_sync_event(_Event, _From, StateName, StateData) ->
+handle_sync_event(_Event, _From, StateName,
+ StateData) ->
{reply, error, StateName, StateData}.
%%%-------------------------------------------------
@@ -251,16 +252,14 @@ handle_sync_event(_Event, _From, StateName, StateData) ->
%%%-------------------------------------------------
relay(MySocket, PeerSocket, Shaper) ->
case gen_tcp:recv(MySocket, 0) of
- {ok, Data} ->
- gen_tcp:send(PeerSocket, Data),
- {NewShaper, Pause} = shaper:update(Shaper, size(Data)),
- if
- Pause > 0 -> timer:sleep(Pause);
- true -> pass
- end,
- relay(MySocket, PeerSocket, NewShaper);
- _ ->
- stopped
+ {ok, Data} ->
+ gen_tcp:send(PeerSocket, Data),
+ {NewShaper, Pause} = shaper:update(Shaper, byte_size(Data)),
+ if Pause > 0 -> timer:sleep(Pause);
+ true -> pass
+ end,
+ relay(MySocket, PeerSocket, NewShaper);
+ _ -> stopped
end.
%%%------------------------
@@ -268,23 +267,21 @@ relay(MySocket, PeerSocket, Shaper) ->
%%%------------------------
select_auth_method(plain, AuthMethods) ->
case lists:member(?AUTH_PLAIN, AuthMethods) of
- true -> ?AUTH_PLAIN;
- false -> ?AUTH_NO_METHODS
+ true -> ?AUTH_PLAIN;
+ false -> ?AUTH_NO_METHODS
end;
-
select_auth_method(anonymous, AuthMethods) ->
case lists:member(?AUTH_ANONYMOUS, AuthMethods) of
- true -> ?AUTH_ANONYMOUS;
- false -> ?AUTH_NO_METHODS
+ true -> ?AUTH_ANONYMOUS;
+ false -> ?AUTH_NO_METHODS
end.
%% Obviously, we must use shaper with maximum rate.
find_maxrate(Shaper, JID1, JID2, Host) ->
- MaxRate1 = shaper:new(acl:match_rule(Host, Shaper, JID1)),
- MaxRate2 = shaper:new(acl:match_rule(Host, Shaper, JID2)),
- if
- MaxRate1 == none; MaxRate2 == none ->
- none;
- true ->
- lists:max([MaxRate1, MaxRate2])
+ MaxRate1 = shaper:new(acl:match_rule(Host, Shaper,
+ JID1)),
+ MaxRate2 = shaper:new(acl:match_rule(Host, Shaper,
+ JID2)),
+ if MaxRate1 == none; MaxRate2 == none -> none;
+ true -> lists:max([MaxRate1, MaxRate2])
end.
diff --git a/src/mod_pubsub/Makefile.in b/src/mod_pubsub/Makefile.in
index 1ea6a1625..88bf2ba0c 100644
--- a/src/mod_pubsub/Makefile.in
+++ b/src/mod_pubsub/Makefile.in
@@ -14,7 +14,7 @@ EFLAGS += -pz ..
# make debug=true to compile Erlang module with debug informations.
ifdef debug
- EFLAGS+=+debug_info +export_all
+ EFLAGS+=+debug_info
endif
OUTDIR = ..
diff --git a/src/mod_pubsub/gen_pubsub_node.erl b/src/mod_pubsub/gen_pubsub_node.erl
index 57ebb57a6..0cf1fd2ff 100644
--- a/src/mod_pubsub/gen_pubsub_node.erl
+++ b/src/mod_pubsub/gen_pubsub_node.erl
@@ -4,12 +4,12 @@
%%% compliance with the License. You should have received a copy of the
%%% Erlang Public License along with this software. If not, it can be
%%% retrieved via the world wide web at http://www.erlang.org/.
-%%%
+%%%
%%% Software distributed under the License is distributed on an "AS IS"
%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%%% the License for the specific language governing rights and limitations
%%% under the License.
-%%%
+%%%
%%% The Initial Developer of the Original Code is ProcessOne.
%%% Portions created by ProcessOne are Copyright 2006-2013, ProcessOne
%%% All Rights Reserved.''
@@ -30,47 +30,238 @@
-module(gen_pubsub_node).
--export([behaviour_info/1]).
-
-%% @spec (Query::atom()) -> Callbacks | atom()
-%% Callbacks = [{Function,Arity}]
-%% Function = atom()
-%% Arity = integer()
-%% @doc Behaviour definition
-behaviour_info(callbacks) ->
- [{init, 3},
- {terminate, 2},
- {options, 0},
- {features, 0},
- {create_node_permission, 6},
- {create_node, 2},
- {delete_node, 1},
- {purge_node, 2},
- {subscribe_node, 8},
- {unsubscribe_node, 4},
- {publish_item, 6},
- {delete_item, 4},
- {remove_extra_items, 3},
- {get_node_affiliations, 1},
- {get_entity_affiliations, 2},
- {get_affiliation, 2},
- {set_affiliation, 3},
- {get_node_subscriptions, 1},
- {get_entity_subscriptions, 2},
- {get_subscriptions, 2},
- {set_subscriptions, 4},
- {get_pending_nodes, 2},
- {get_states, 1},
- {get_state, 2},
- {set_state, 1},
- {get_items, 6},
- {get_items, 2},
- {get_item, 7},
- {get_item, 2},
- {set_item, 1},
- {get_item_name, 3},
- {node_to_path, 1},
- {path_to_node, 1}
- ];
-behaviour_info(_Other) ->
- undefined.
+-include("jlib.hrl").
+
+-type(host() :: mod_pubsub:host()
+ | mod_pubsub_odbc:host()
+).
+
+-type(nodeId() :: mod_pubsub:nodeId()
+ | mod_pubsub_odbc:nodeId()
+).
+
+-type(nodeIdx() :: mod_pubsub:nodeIdx()
+ | mod_pubsub_odbc:nodeIdx()
+).
+
+-type(itemId() :: mod_pubsub:itemId()
+ | mod_pubsub_odbc:itemId()
+).
+
+-type(pubsubNode() :: mod_pubsub:pubsubNode()
+ | mod_pubsub_odbc:pubsubNode()
+).
+
+-type(pubsubState() :: mod_pubsub:pubsubState()
+ | mod_pubsub_odbc:pubsubState()
+).
+
+-type(pubsubItem() :: mod_pubsub:pubsubItem()
+ | mod_pubsub_odbc:pubsubItem()
+).
+
+-type(nodeOptions() :: mod_pubsub:nodeOptions()
+ | mod_pubsub_odbc:nodeOptions()
+).
+
+-type(subOptions() :: mod_pubsub:subOptions()
+ | mod_pubsub_odbc:subOptions()
+).
+
+-type(affiliation() :: mod_pubsub:affiliation()
+ | mod_pubsub_odbc:affiliation()
+).
+
+-type(subscription() :: mod_pubsub:subscription()
+ | mod_pubsub_odbc:subscription()
+).
+
+-type(subId() :: mod_pubsub:subId()
+ | mod_pubsub_odbc:subId()
+).
+
+-type(accessModel() :: mod_pubsub:accessModel()
+ | mod_pubsub_odbc:accessModel()
+).
+
+-type(publishModel() :: mod_pubsub:publishModel()
+ | mod_pubsub_odbc:publishModel()
+).
+
+-type(payload() :: mod_pubsub:payload()
+ | mod_pubsub_odbc:payload()
+).
+
+-callback init(Host :: binary(),
+ ServerHost :: binary(),
+ Opts :: [any()]) -> atom().
+
+-callback terminate(Host :: host(),
+ ServerHost :: binary()) -> atom().
+
+-callback options() -> [{atom(), any()}].
+
+-callback features() -> [binary()].
+
+-callback create_node_permission(Host :: host(),
+ ServerHost :: binary(),
+ Node :: nodeId(),
+ ParentNode :: nodeId(),
+ Owner :: jid(), Access :: atom()) ->
+ {result, boolean()}.
+
+-callback create_node(NodeIdx :: nodeIdx(),
+ Owner :: jid()) ->
+ {result, {default, broadcast}}.
+
+-callback delete_node(Nodes :: [pubsubNode(),...]) ->
+ {result,
+ {default, broadcast,
+ [{pubsubNode(),
+ [{ljid(), [{subscription(), subId()}]},...]},...]
+ }
+ }
+ |
+ {result,
+ {[],
+ [{pubsubNode(),
+ [{ljid(), [{subscription(), subId()}]},...]},...]
+ }
+ }.
+
+-callback purge_node(NodeIdx :: nodeIdx(),
+ Owner :: jid()) ->
+ {result, {default, broadcast}} |
+ {error, xmlel()}.
+
+-callback subscribe_node(NodeIdx :: nodeIdx(),
+ Sender :: jid(),
+ Subscriber :: ljid(),
+ AccessModel :: accessModel(),
+ SendLast :: 'never' | 'on_sub' | 'on_sub_and_presence',
+ PresenceSubscription :: boolean(),
+ RosterGroup :: boolean(),
+ Options :: subOptions()) ->
+ {result, {default, subscribed, subId()}} |
+ {result, {default, subscribed, subId(), send_last}} |
+ {result, {default, pending, subId()}} |
+ {error, xmlel()}.
+
+-callback unsubscribe_node(NodeIdx :: nodeIdx(),
+ Sender :: jid(),
+ Subscriber :: ljid(),
+ SubId :: subId()) ->
+ {result, default} |
+ {error, xmlel()}.
+
+-callback publish_item(NodeId :: nodeIdx(),
+ Publisher :: jid(),
+ PublishModel :: publishModel(),
+ Max_Items :: non_neg_integer(),
+ ItemId :: <<>> | itemId(),
+ Payload :: payload()) ->
+ {result, {default, broadcast, [itemId()]}} |
+ {error, xmlel()}.
+
+-callback delete_item(NodeIdx :: nodeIdx(),
+ Publisher :: jid(),
+ PublishModel :: publishModel(),
+ ItemId :: <<>> | itemId()) ->
+ {result, {default, broadcast}} |
+ {error, xmlel()}.
+
+-callback remove_extra_items(NodeIdx :: nodeIdx(),
+ Max_Items :: unlimited | non_neg_integer(),
+ ItemIds :: [itemId()]) ->
+ {result, {[itemId()], [itemId()]}
+ }.
+
+-callback get_node_affiliations(NodeIdx :: nodeIdx()) ->
+ {result, [{ljid(), affiliation()}]}.
+
+-callback get_entity_affiliations(Host :: host(),
+ Owner :: jid()) ->
+ {result, [{pubsubNode(), affiliation()}]}.
+
+-callback get_affiliation(NodeIdx :: nodeIdx(),
+ Owner :: jid()) ->
+ {result, affiliation()}.
+
+-callback set_affiliation(NodeIdx :: nodeIdx(),
+ Owner :: ljid(),
+ Affiliation :: affiliation()) ->
+ ok |
+ {error, xmlel()}.
+
+-callback get_node_subscriptions(NodeIdx :: nodeIdx()) ->
+ {result,
+ [{ljid(), subscription(), subId()}] |
+ [{ljid(), none},...]
+ }.
+
+-callback get_entity_subscriptions(Host :: host(),
+ Owner :: jid()) ->
+ {result, [{pubsubNode(), subscription(), subId(), ljid()}]
+ }.
+
+-callback get_subscriptions(NodeIdx :: nodeIdx(),
+ Owner :: ljid()) ->
+ {result, [{subscription(), subId()}]}.
+
+-callback get_pending_nodes(Host :: host(),
+ Owner :: jid()) ->
+ {result, [nodeId()]}.
+
+-callback get_states(NodeIdx::nodeIdx()) ->
+ {result, [pubsubState()]}.
+
+-callback get_state(NodeIdx :: nodeIdx(),
+ JID :: ljid()) ->
+ pubsubState().
+
+-callback set_state(State::pubsubState()) ->
+ ok |
+ {error, xmlel()}.
+
+-callback get_items(NodeIdx :: nodeIdx(),
+ JID :: jid(),
+ AccessModel :: accessModel(),
+ Presence_Subscription :: boolean(),
+ RosterGroup :: boolean(),
+ SubId :: subId()) ->
+ {result, [pubsubItem()]} |
+ {error, xmlel()}.
+
+-callback get_items(NodeIdx :: nodeIdx(),
+ From :: jid()) ->
+ {result, [pubsubItem()]}.
+
+-callback get_item(NodeIdx :: nodeIdx(),
+ ItemId :: itemId(),
+ JID :: jid(),
+ AccessModel :: accessModel(),
+ PresenceSubscription :: boolean(),
+ RosterGroup :: boolean(),
+ SubId :: subId()) ->
+ {result, pubsubItem()} |
+ {error, xmlel()}.
+
+-callback get_item(NodeIdx :: nodeIdx(),
+ ItemId :: itemId()) ->
+ {result, pubsubItem()} |
+ {error, xmlel()}.
+
+-callback set_item(Item :: pubsubItem()) ->
+ ok.
+% | {error, _}.
+
+-callback get_item_name(Host :: host(),
+ ServerHost :: binary(),
+ Node :: nodeId()) ->
+ itemId().
+
+-callback node_to_path(Node :: nodeId()) ->
+ [nodeId()].
+
+-callback path_to_node(Node :: [nodeId()]) ->
+ nodeId().
diff --git a/src/mod_pubsub/gen_pubsub_nodetree.erl b/src/mod_pubsub/gen_pubsub_nodetree.erl
index 81daeb08e..8acba659c 100644
--- a/src/mod_pubsub/gen_pubsub_nodetree.erl
+++ b/src/mod_pubsub/gen_pubsub_nodetree.erl
@@ -4,12 +4,12 @@
%%% compliance with the License. You should have received a copy of the
%%% Erlang Public License along with this software. If not, it can be
%%% retrieved via the world wide web at http://www.erlang.org/.
-%%%
+%%%
%%% Software distributed under the License is distributed on an "AS IS"
%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%%% the License for the specific language governing rights and limitations
%%% under the License.
-%%%
+%%%
%%% The Initial Developer of the Original Code is ProcessOne.
%%% Portions created by ProcessOne are Copyright 2006-2013, ProcessOne
%%% All Rights Reserved.''
@@ -25,34 +25,100 @@
%%% @private
%%% @doc <p>The module <strong>{@module}</strong> defines the PubSub node
-%%% tree plugin behaviour. This behaviour is used to check that a PubSub
+%%% tree plugin behaviour. This behaviour is used to check that a PubSub
%%% node tree plugin respects the current ejabberd PubSub plugin API.</p>
-module(gen_pubsub_nodetree).
--export([behaviour_info/1]).
-
-%% @spec (Query::atom()) -> Callbacks | atom()
-%% Callbacks = [{Function,Arity}]
-%% Function = atom()
-%% Arity = integer()
-%% @doc Behaviour definition
-behaviour_info(callbacks) ->
- [{init, 3},
- {terminate, 2},
- {options, 0},
- {set_node, 1},
- {get_node, 3},
- {get_node, 2},
- {get_node, 1},
- {get_nodes, 2},
- {get_nodes, 1},
- {get_parentnodes, 3},
- {get_parentnodes_tree, 3},
- {get_subnodes, 3},
- {get_subnodes_tree, 3},
- {create_node, 6},
- {delete_node, 2}
- ];
-behaviour_info(_Other) ->
- undefined.
+-include("jlib.hrl").
+
+-type(host() :: mod_pubsub:host()
+ | mod_pubsub_odbc:host()
+).
+
+-type(nodeId() :: mod_pubsub:nodeId()
+ | mod_pubsub_odbc:nodeId()
+).
+
+-type(nodeIdx() :: mod_pubsub:nodeIdx()
+ | mod_pubsub_odbc:nodeIdx()
+).
+
+-type(itemId() :: mod_pubsub:itemId()
+ | mod_pubsub_odbc:itemId()
+).
+
+-type(pubsubNode() :: mod_pubsub:pubsubNode()
+ | mod_pubsub_odbc:pubsubNode()
+).
+
+-type(nodeOptions() :: mod_pubsub:nodeOptions()
+ | mod_pubsub_odbc:nodeOptions()
+).
+
+-callback init(Host :: host(),
+ ServerHost :: binary(),
+ Opts :: [any()]) -> atom().
+
+-callback terminate(Host :: host(), ServerHost :: binary()) -> atom().
+
+-callback options() -> nodeOptions().
+
+-callback set_node(PubsubNode :: pubsubNode()) ->
+ ok | {result, NodeIdx::mod_pubsub_odbc:nodeIdx()} | {error, xmlel()}.
+
+-callback get_node(Host :: host(),
+ NodeId :: nodeId(),
+ From :: jid()) ->
+ pubsubNode() |
+ {error, xmlel()}.
+
+-callback get_node(Host :: host(),
+ NodeId :: nodeId()) ->
+ pubsubNode() |
+ {error, xmlel()}.
+
+-callback get_node(NodeIdx :: nodeIdx()) ->
+ pubsubNode() |
+ {error, xmlel()}.
+
+-callback get_nodes(Host :: host(),
+ From :: jid())->
+ [pubsubNode()].
+
+-callback get_nodes(Host :: host())->
+ [pubsubNode()].
+
+-callback get_parentnodes(Host :: host(),
+ NodeId :: nodeId(),
+ From :: jid()) ->
+ [pubsubNode()] |
+ {error, xmlel()}.
+
+-callback get_parentnodes_tree(Host :: host(),
+ NodeId :: nodeId(),
+ From :: jid()) ->
+ [{0, [pubsubNode(),...]}].
+
+-callback get_subnodes(Host :: host(),
+ NodeId :: nodeId(),
+ From :: ljid()) ->
+ [pubsubNode()].
+
+-callback get_subnodes_tree(Host :: host(),
+ NodeId :: nodeId(),
+ From :: ljid()) ->
+ [pubsubNode()].
+
+-callback create_node(Host :: host(),
+ NodeId :: nodeId(),
+ Type :: binary(),
+ Owner :: jid(),
+ Options :: nodeOptions(),
+ Parents :: [nodeId()]) ->
+ {ok, NodeIdx::nodeIdx()} |
+ {error, xmlel()}.
+
+-callback delete_node(Host :: host(),
+ NodeId :: nodeId()) ->
+ [pubsubNode()].
diff --git a/src/mod_pubsub/mod_pubsub.erl b/src/mod_pubsub/mod_pubsub.erl
index 0cda9de11..5f1d38e7d 100644
--- a/src/mod_pubsub/mod_pubsub.erl
+++ b/src/mod_pubsub/mod_pubsub.erl
@@ -5,11 +5,13 @@
%%% Erlang Public License along with this software. If not, it can be
%%% retrieved via the world wide web at http://www.erlang.org/.
%%%
+%%%
%%% Software distributed under the License is distributed on an "AS IS"
%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%%% the License for the specific language governing rights and limitations
%%% under the License.
%%%
+%%%
%%% The Initial Developer of the Original Code is ProcessOne.
%%% Portions created by ProcessOne are Copyright 2006-2013, ProcessOne
%%% All Rights Reserved.''
@@ -22,7 +24,6 @@
%%% @end
%%% ====================================================================
-
%%% @doc The module <strong>{@module}</strong> is the core of the PubSub
%%% extension. It relies on PubSub plugins for a large part of its functions.
%%%
@@ -43,38 +44,39 @@
%%% XEP-0060 section 12.18.
-module(mod_pubsub).
+
-author('christophe.romain@process-one.net').
+
-version('1.13-0').
-behaviour(gen_server).
+
-behaviour(gen_mod).
-include("ejabberd.hrl").
+
-include("adhoc.hrl").
+
-include("jlib.hrl").
+
-include("pubsub.hrl").
--define(STDTREE, "tree").
--define(STDNODE, "flat").
--define(PEPNODE, "pep").
+-define(STDTREE, <<"tree">>).
+
+-define(STDNODE, <<"flat">>).
+
+-define(PEPNODE, <<"pep">>).
%% exports for hooks
--export([presence_probe/3,
- caps_update/3,
- in_subscription/6,
- out_subscription/4,
- on_user_offline/3,
- remove_user/2,
- disco_local_identity/5,
- disco_local_features/5,
- disco_local_items/5,
- disco_sm_identity/5,
- disco_sm_features/5,
- disco_sm_items/5
- ]).
+-export([presence_probe/3, caps_update/3,
+ in_subscription/6, out_subscription/4,
+ on_user_offline/3, remove_user/2,
+ disco_local_identity/5, disco_local_features/5,
+ disco_local_items/5, disco_sm_identity/5,
+ disco_sm_features/5, disco_sm_items/5]).
+
%% exported iq handlers
--export([iq_sm/3
- ]).
+-export([iq_sm/3]).
%% exports for console debug manual use
-export([create_node/5,
@@ -95,47 +97,22 @@
]).
%% general helpers for plugins
--export([node_to_string/1,
- string_to_node/1,
- subscription_to_string/1,
- affiliation_to_string/1,
- string_to_subscription/1,
- string_to_affiliation/1,
- extended_error/2,
- extended_error/3,
- rename_default_nodeplugin/0
- ]).
+-export([subscription_to_string/1, affiliation_to_string/1,
+ string_to_subscription/1, string_to_affiliation/1,
+ extended_error/2, extended_error/3,
+ rename_default_nodeplugin/0]).
%% API and gen_server callbacks
--export([start_link/2,
- start/2,
- stop/1,
- init/1,
- handle_call/3,
- handle_cast/2,
- handle_info/2,
- terminate/2,
- code_change/3
- ]).
+-export([start_link/2, start/2, stop/1, init/1,
+ handle_call/3, handle_cast/2, handle_info/2,
+ terminate/2, code_change/3]).
%% calls for parallel sending of last items
--export([send_loop/1
- ]).
+-export([send_loop/1]).
-define(PROCNAME, ejabberd_mod_pubsub).
+
-define(LOOPNAME, ejabberd_mod_pubsub_loop).
--define(PLUGIN_PREFIX, "node_").
--define(TREE_PREFIX, "nodetree_").
-
--record(state, {server_host,
- host,
- access,
- pep_mapping = [],
- ignore_pep_from_offline = true,
- last_item_cache = false,
- max_items_node = ?MAXITEMS,
- nodetree = ?STDTREE,
- plugins = [?STDNODE]}).
%%====================================================================
%% API
@@ -144,14 +121,127 @@
%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
%% Description: Starts the server
%%--------------------------------------------------------------------
+-define(PLUGIN_PREFIX, <<"node_">>).
+
+-define(TREE_PREFIX, <<"nodetree_">>).
+
+%
+-export_type([
+ host/0,
+ hostPubsub/0,
+ hostPEP/0,
+ %%
+ nodeIdx/0,
+ nodeId/0,
+ itemId/0,
+ subId/0,
+ payload/0,
+ %%
+ nodeOption/0,
+ nodeOptions/0,
+ subOption/0,
+ subOptions/0,
+ %%
+ affiliation/0,
+ subscription/0,
+ accessModel/0,
+ publishModel/0
+]).
+
+%% -type payload() defined here because the -type xmlel() is not accessible
+%% from pubsub.hrl
+-type(payload() :: [] | [xmlel(),...]).
+
+-export_type([
+ pubsubNode/0,
+ pubsubState/0,
+ pubsubItem/0,
+ pubsubSubscription/0,
+ pubsubLastItem/0
+]).
+
+-type(pubsubNode() ::
+ #pubsub_node{
+ nodeid :: {Host::mod_pubsub:host(), NodeId::mod_pubsub:nodeId()},
+ id :: mod_pubsub:nodeIdx(),
+ parents :: [Parent_NodeId::mod_pubsub:nodeId()],
+ type :: binary(),
+ owners :: [Owner::ljid(),...],
+ options :: mod_pubsub:nodeOptions()
+ }
+).
+
+-type(pubsubState() ::
+ #pubsub_state{
+ stateid :: {Entity::ljid(), NodeIdx::mod_pubsub:nodeIdx()},
+ items :: [ItemId::mod_pubsub:itemId()],
+ affiliation :: mod_pubsub:affiliation(),
+ subscriptions :: [{mod_pubsub:subscription(), mod_pubsub:subId()}]
+ }
+).
+
+-type(pubsubItem() ::
+ #pubsub_item{
+ itemid :: {mod_pubsub:itemId(), mod_pubsub:nodeIdx()},
+ creation :: {erlang:timestamp(), ljid()},
+ modification :: {erlang:timestamp(), ljid()},
+ payload :: mod_pubsub:payload()
+ }
+).
+
+-type(pubsubSubscription() ::
+ #pubsub_subscription{
+ subid :: mod_pubsub:subId(),
+ options :: [] | mod_pubsub:subOptions()
+ }
+).
+
+-type(pubsubLastItem() ::
+ #pubsub_last_item{
+ nodeid :: mod_pubsub:nodeIdx(),
+ itemid :: mod_pubsub:itemId(),
+ creation :: {erlang:timestamp(), ljid()},
+ payload :: mod_pubsub:payload()
+ }
+).
+
+-record(state,
+{
+ server_host,
+ host,
+ access,
+ pep_mapping = [],
+ ignore_pep_from_offline = true,
+ last_item_cache = false,
+ max_items_node = ?MAXITEMS,
+ nodetree = ?STDTREE,
+ plugins = [?STDNODE]
+}).
+
+-type(state() ::
+ #state{
+ server_host :: binary(),
+ host :: mod_pubsub:hostPubsub(),
+ access :: atom(),
+ pep_mapping :: [{binary(), binary()}],
+ ignore_pep_from_offline :: boolean(),
+ last_item_cache :: boolean(),
+ max_items_node :: non_neg_integer(),
+ nodetree :: binary(),
+ plugins :: [binary(),...]
+ }
+
+).
+
+
start_link(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
- gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
+ gen_server:start_link({local, Proc}, ?MODULE,
+ [Host, Opts], []).
start(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
- ChildSpec = {Proc,
- {?MODULE, start_link, [Host, Opts]},
+ ChildSpec = {Proc, {?MODULE, start_link, [Host, Opts]},
transient, 1000, worker, [?MODULE]},
supervisor:start_child(ejabberd_sup, ChildSpec).
@@ -171,65 +261,104 @@ stop(Host) ->
%% {stop, Reason}
%% Description: Initiates the server
%%--------------------------------------------------------------------
+-spec(init/1 ::
+(
+ _:: _)
+ -> {ok, state()}
+).
+
init([ServerHost, Opts]) ->
- ?DEBUG("pubsub init ~p ~p",[ServerHost,Opts]),
- Host = gen_mod:get_opt_host(ServerHost, Opts, "pubsub.@HOST@"),
- Access = gen_mod:get_opt(access_createnode, Opts, all),
- PepOffline = gen_mod:get_opt(ignore_pep_from_offline, Opts, true),
- IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
- LastItemCache = gen_mod:get_opt(last_item_cache, Opts, false),
- MaxItemsNode = gen_mod:get_opt(max_items_node, Opts, ?MAXITEMS),
+ ?DEBUG("pubsub init ~p ~p", [ServerHost, Opts]),
+ Host = gen_mod:get_opt_host(ServerHost, Opts, <<"pubsub.@HOST@">>),
+ Access = gen_mod:get_opt(access_createnode, Opts,
+ fun(A) when is_atom(A) -> A end, all),
+ PepOffline = gen_mod:get_opt(ignore_pep_from_offline, Opts,
+ fun(A) when is_boolean(A) -> A end, true),
+ IQDisc = gen_mod:get_opt(iqdisc, Opts,
+ fun(A) when is_atom(A) -> A end, one_queue),
+ LastItemCache = gen_mod:get_opt(last_item_cache, Opts,
+ fun(A) when is_boolean(A) -> A end, false),
+ MaxItemsNode = gen_mod:get_opt(max_items_node, Opts,
+ fun(A) when is_integer(A) andalso A >= 0 -> A end, ?MAXITEMS),
pubsub_index:init(Host, ServerHost, Opts),
- ets:new(gen_mod:get_module_proc(Host, config), [set, named_table]),
- ets:new(gen_mod:get_module_proc(ServerHost, config), [set, named_table]),
- {Plugins, NodeTree, PepMapping} = init_plugins(Host, ServerHost, Opts),
- mnesia:create_table(pubsub_last_item, [{ram_copies, [node()]}, {attributes, record_info(fields, pubsub_last_item)}]),
+ ets:new(gen_mod:get_module_proc(Host, config),
+ [set, named_table]),
+ ets:new(gen_mod:get_module_proc(ServerHost, config),
+ [set, named_table]),
+ {Plugins, NodeTree, PepMapping} = init_plugins(Host,
+ ServerHost, Opts),
+ mnesia:create_table(pubsub_last_item,
+ [{ram_copies, [node()]},
+ {attributes, record_info(fields, pubsub_last_item)}]),
mod_disco:register_feature(ServerHost, ?NS_PUBSUB),
- ets:insert(gen_mod:get_module_proc(Host, config), {nodetree, NodeTree}),
- ets:insert(gen_mod:get_module_proc(Host, config), {plugins, Plugins}),
- ets:insert(gen_mod:get_module_proc(Host, config), {last_item_cache, LastItemCache}),
- ets:insert(gen_mod:get_module_proc(Host, config), {max_items_node, MaxItemsNode}),
- ets:insert(gen_mod:get_module_proc(ServerHost, config), {nodetree, NodeTree}),
- ets:insert(gen_mod:get_module_proc(ServerHost, config), {plugins, Plugins}),
- ets:insert(gen_mod:get_module_proc(ServerHost, config), {last_item_cache, LastItemCache}),
- ets:insert(gen_mod:get_module_proc(ServerHost, config), {max_items_node, MaxItemsNode}),
- ets:insert(gen_mod:get_module_proc(ServerHost, config), {pep_mapping, PepMapping}),
- ets:insert(gen_mod:get_module_proc(ServerHost, config), {ignore_pep_from_offline, PepOffline}),
- ets:insert(gen_mod:get_module_proc(ServerHost, config), {host, Host}),
- ejabberd_hooks:add(sm_remove_connection_hook, ServerHost, ?MODULE, on_user_offline, 75),
- ejabberd_hooks:add(disco_local_identity, ServerHost, ?MODULE, disco_local_identity, 75),
- ejabberd_hooks:add(disco_local_features, ServerHost, ?MODULE, disco_local_features, 75),
- ejabberd_hooks:add(disco_local_items, ServerHost, ?MODULE, disco_local_items, 75),
- ejabberd_hooks:add(presence_probe_hook, ServerHost, ?MODULE, presence_probe, 80),
- ejabberd_hooks:add(roster_in_subscription, ServerHost, ?MODULE, in_subscription, 50),
- ejabberd_hooks:add(roster_out_subscription, ServerHost, ?MODULE, out_subscription, 50),
- ejabberd_hooks:add(remove_user, ServerHost, ?MODULE, remove_user, 50),
- ejabberd_hooks:add(anonymous_purge_hook, ServerHost, ?MODULE, remove_user, 50),
+ ets:insert(gen_mod:get_module_proc(Host, config),
+ {nodetree, NodeTree}),
+ ets:insert(gen_mod:get_module_proc(Host, config),
+ {plugins, Plugins}),
+ ets:insert(gen_mod:get_module_proc(Host, config),
+ {last_item_cache, LastItemCache}),
+ ets:insert(gen_mod:get_module_proc(Host, config),
+ {max_items_node, MaxItemsNode}),
+ ets:insert(gen_mod:get_module_proc(ServerHost, config),
+ {nodetree, NodeTree}),
+ ets:insert(gen_mod:get_module_proc(ServerHost, config),
+ {plugins, Plugins}),
+ ets:insert(gen_mod:get_module_proc(ServerHost, config),
+ {last_item_cache, LastItemCache}),
+ ets:insert(gen_mod:get_module_proc(ServerHost, config),
+ {max_items_node, MaxItemsNode}),
+ ets:insert(gen_mod:get_module_proc(ServerHost, config),
+ {pep_mapping, PepMapping}),
+ ets:insert(gen_mod:get_module_proc(ServerHost, config),
+ {ignore_pep_from_offline, PepOffline}),
+ ets:insert(gen_mod:get_module_proc(ServerHost, config),
+ {host, Host}),
+ ejabberd_hooks:add(sm_remove_connection_hook,
+ ServerHost, ?MODULE, on_user_offline, 75),
+ ejabberd_hooks:add(disco_local_identity, ServerHost,
+ ?MODULE, disco_local_identity, 75),
+ ejabberd_hooks:add(disco_local_features, ServerHost,
+ ?MODULE, disco_local_features, 75),
+ ejabberd_hooks:add(disco_local_items, ServerHost,
+ ?MODULE, disco_local_items, 75),
+ ejabberd_hooks:add(presence_probe_hook, ServerHost,
+ ?MODULE, presence_probe, 80),
+ ejabberd_hooks:add(roster_in_subscription, ServerHost,
+ ?MODULE, in_subscription, 50),
+ ejabberd_hooks:add(roster_out_subscription, ServerHost,
+ ?MODULE, out_subscription, 50),
+ ejabberd_hooks:add(remove_user, ServerHost, ?MODULE,
+ remove_user, 50),
+ ejabberd_hooks:add(anonymous_purge_hook, ServerHost,
+ ?MODULE, remove_user, 50),
case lists:member(?PEPNODE, Plugins) of
- true ->
- ejabberd_hooks:add(caps_update, ServerHost, ?MODULE, caps_update, 80),
- ejabberd_hooks:add(disco_sm_identity, ServerHost, ?MODULE, disco_sm_identity, 75),
- ejabberd_hooks:add(disco_sm_features, ServerHost, ?MODULE, disco_sm_features, 75),
- ejabberd_hooks:add(disco_sm_items, ServerHost, ?MODULE, disco_sm_items, 75),
- gen_iq_handler:add_iq_handler(ejabberd_sm, ServerHost, ?NS_PUBSUB, ?MODULE, iq_sm, IQDisc),
- gen_iq_handler:add_iq_handler(ejabberd_sm, ServerHost, ?NS_PUBSUB_OWNER, ?MODULE, iq_sm, IQDisc);
- false ->
- ok
+ true ->
+ ejabberd_hooks:add(caps_update, ServerHost, ?MODULE,
+ caps_update, 80),
+ ejabberd_hooks:add(disco_sm_identity, ServerHost,
+ ?MODULE, disco_sm_identity, 75),
+ ejabberd_hooks:add(disco_sm_features, ServerHost,
+ ?MODULE, disco_sm_features, 75),
+ ejabberd_hooks:add(disco_sm_items, ServerHost, ?MODULE,
+ disco_sm_items, 75),
+ gen_iq_handler:add_iq_handler(ejabberd_sm, ServerHost,
+ ?NS_PUBSUB, ?MODULE, iq_sm, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_sm, ServerHost,
+ ?NS_PUBSUB_OWNER, ?MODULE, iq_sm,
+ IQDisc);
+ false -> ok
end,
ejabberd_router:register_route(Host),
update_node_database(Host, ServerHost),
update_state_database(Host, ServerHost),
- put(server_host, ServerHost), % not clean, but needed to plug hooks at any location
+ put(server_host, ServerHost),
init_nodes(Host, ServerHost, NodeTree, Plugins),
- State = #state{host = Host,
- server_host = ServerHost,
- access = Access,
- pep_mapping = PepMapping,
- ignore_pep_from_offline = PepOffline,
- last_item_cache = LastItemCache,
- max_items_node = MaxItemsNode,
- nodetree = NodeTree,
- plugins = Plugins},
+ State = #state{host = Host, server_host = ServerHost,
+ access = Access, pep_mapping = PepMapping,
+ ignore_pep_from_offline = PepOffline,
+ last_item_cache = LastItemCache,
+ max_items_node = MaxItemsNode, nodetree = NodeTree,
+ plugins = Plugins},
init_send_loop(ServerHost, State),
{ok, State}.
@@ -253,197 +382,360 @@ init_send_loop(ServerHost, State) ->
%% and sorted to ensure that each module is initialized only once.</p>
%% <p>See {@link node_hometree:init/1} for an example implementation.</p>
init_plugins(Host, ServerHost, Opts) ->
- TreePlugin = list_to_atom(?TREE_PREFIX ++
- gen_mod:get_opt(nodetree, Opts, ?STDTREE)),
- ?DEBUG("** tree plugin is ~p",[TreePlugin]),
+ TreePlugin =
+ jlib:binary_to_atom(<<(?TREE_PREFIX)/binary,
+ (gen_mod:get_opt(nodetree, Opts, fun(A) when is_list(A) -> A end,
+ ?STDTREE))/binary>>),
+ ?DEBUG("** tree plugin is ~p", [TreePlugin]),
TreePlugin:init(Host, ServerHost, Opts),
- Plugins = gen_mod:get_opt(plugins, Opts, [?STDNODE]),
- PepMapping = gen_mod:get_opt(pep_mapping, Opts, []),
- ?DEBUG("** PEP Mapping : ~p~n",[PepMapping]),
- PluginsOK = lists:foldl(fun(Name, Acc) ->
- Plugin = list_to_atom(?PLUGIN_PREFIX ++ Name),
- case catch apply(Plugin, init, [Host, ServerHost, Opts]) of
- {'EXIT', _Error} ->
- Acc;
- _ ->
- ?DEBUG("** init ~s plugin",[Name]),
- [Name | Acc]
- end
- end, [], Plugins),
+ Plugins = gen_mod:get_opt(plugins, Opts,
+ fun(A) when is_list(A) -> A end, [?STDNODE]),
+ PepMapping = gen_mod:get_opt(pep_mapping, Opts,
+ fun(A) when is_list(A) -> A end, []),
+ ?DEBUG("** PEP Mapping : ~p~n", [PepMapping]),
+ PluginsOK = lists:foldl(fun (Name, Acc) ->
+ Plugin =
+ jlib:binary_to_atom(<<(?PLUGIN_PREFIX)/binary,
+ Name/binary>>),
+ case catch apply(Plugin, init,
+ [Host, ServerHost, Opts])
+ of
+ {'EXIT', _Error} -> Acc;
+ _ ->
+ ?DEBUG("** init ~s plugin", [Name]),
+ [Name | Acc]
+ end
+ end,
+ [], Plugins),
{lists:reverse(PluginsOK), TreePlugin, PepMapping}.
-terminate_plugins(Host, ServerHost, Plugins, TreePlugin) ->
- lists:foreach(fun(Name) ->
- ?DEBUG("** terminate ~s plugin",[Name]),
- Plugin = list_to_atom(?PLUGIN_PREFIX++Name),
+terminate_plugins(Host, ServerHost, Plugins,
+ TreePlugin) ->
+ lists:foreach(fun (Name) ->
+ ?DEBUG("** terminate ~s plugin", [Name]),
+ Plugin =
+ jlib:binary_to_atom(<<(?PLUGIN_PREFIX)/binary,
+ Name/binary>>),
Plugin:terminate(Host, ServerHost)
- end, Plugins),
+ end,
+ Plugins),
TreePlugin:terminate(Host, ServerHost),
ok.
init_nodes(Host, ServerHost, _NodeTree, Plugins) ->
- %% TODO, this call should be done plugin side
- case lists:member("hometree", Plugins) of
- true ->
- create_node(Host, ServerHost, string_to_node("/home"), service_jid(Host), "hometree"),
- create_node(Host, ServerHost, string_to_node("/home/"++ServerHost), service_jid(Host), "hometree");
- false ->
- ok
+ case lists:member(<<"hometree">>, Plugins) of
+ true ->
+ create_node(Host, ServerHost, <<"/home">>, service_jid(Host), <<"hometree">>),
+ create_node(Host, ServerHost, <<"/home/", ServerHost/binary>>, service_jid(Host),
+ <<"hometree">>);
+ false -> ok
end.
update_node_database(Host, ServerHost) ->
mnesia:del_table_index(pubsub_node, type),
mnesia:del_table_index(pubsub_node, parentid),
case catch mnesia:table_info(pubsub_node, attributes) of
- [host_node, host_parent, info] ->
- ?INFO_MSG("upgrade node pubsub tables",[]),
- F = fun() ->
- {Result, LastIdx} = lists:foldl(
- fun({pubsub_node, NodeId, ParentId, {nodeinfo, Items, Options, Entities}}, {RecList, NodeIdx}) ->
- ItemsList =
- lists:foldl(
- fun({item, IID, Publisher, Payload}, Acc) ->
- C = {unknown, Publisher},
- M = {now(), Publisher},
- mnesia:write(
- #pubsub_item{itemid = {IID, NodeIdx},
- creation = C,
- modification = M,
- payload = Payload}),
- [{Publisher, IID} | Acc]
- end, [], Items),
- Owners =
- dict:fold(
- fun(JID, {entity, Aff, Sub}, Acc) ->
- UsrItems =
- lists:foldl(
- fun({P, I}, IAcc) ->
- case P of
- JID -> [I | IAcc];
- _ -> IAcc
- end
- end, [], ItemsList),
- mnesia:write({pubsub_state,
- {JID, NodeIdx},
- UsrItems,
- Aff,
- Sub}),
- case Aff of
- owner -> [JID | Acc];
- _ -> Acc
- end
- end, [], Entities),
- mnesia:delete({pubsub_node, NodeId}),
- {[#pubsub_node{nodeid = NodeId,
- id = NodeIdx,
- parents = [element(2, ParentId)],
- owners = Owners,
- options = Options} |
- RecList], NodeIdx + 1}
- end, {[], 1},
- mnesia:match_object(
- {pubsub_node, {Host, '_'}, '_', '_'})),
- mnesia:write(#pubsub_index{index = node, last = LastIdx, free = []}),
- Result
- end,
- {atomic, NewRecords} = mnesia:transaction(F),
- {atomic, ok} = mnesia:delete_table(pubsub_node),
- {atomic, ok} = mnesia:create_table(pubsub_node,
- [{disc_copies, [node()]},
- {attributes, record_info(fields, pubsub_node)}]),
- FNew = fun() -> lists:foreach(fun(Record) ->
- mnesia:write(Record)
- end, NewRecords)
- end,
- case mnesia:transaction(FNew) of
- {atomic, Result} ->
- ?INFO_MSG("Pubsub node tables updated correctly: ~p", [Result]);
- {aborted, Reason} ->
- ?ERROR_MSG("Problem updating Pubsub node tables:~n~p", [Reason])
- end;
- [nodeid, parentid, type, owners, options] ->
- F = fun({pubsub_node, NodeId, {_, Parent}, Type, Owners, Options}) ->
- #pubsub_node{
- nodeid = NodeId,
- id = 0,
- parents = [Parent],
- type = Type,
- owners = Owners,
- options = Options}
- end,
- mnesia:transform_table(pubsub_node, F, [nodeid, id, parents, type, owners, options]),
- FNew = fun() ->
- LastIdx = lists:foldl(fun(#pubsub_node{nodeid = NodeId} = PubsubNode, NodeIdx) ->
- mnesia:write(PubsubNode#pubsub_node{id = NodeIdx}),
- lists:foreach(fun(#pubsub_state{stateid = StateId} = State) ->
- {JID, _} = StateId,
- mnesia:delete({pubsub_state, StateId}),
- mnesia:write(State#pubsub_state{stateid = {JID, NodeIdx}})
- end, mnesia:match_object(#pubsub_state{stateid = {'_', NodeId}, _ = '_'})),
- lists:foreach(fun(#pubsub_item{itemid = ItemId} = Item) ->
- {IID, _} = ItemId,
- {M1, M2} = Item#pubsub_item.modification,
- {C1, C2} = Item#pubsub_item.creation,
- mnesia:delete({pubsub_item, ItemId}),
- mnesia:write(Item#pubsub_item{itemid = {IID, NodeIdx},
- modification = {M2, M1},
- creation = {C2, C1}})
- end, mnesia:match_object(#pubsub_item{itemid = {'_', NodeId}, _ = '_'})),
- NodeIdx + 1
- end, 1, mnesia:match_object(
- {pubsub_node, {Host, '_'}, '_', '_', '_', '_', '_'})
- ++ mnesia:match_object(
- {pubsub_node, {{'_', ServerHost, '_'}, '_'}, '_', '_', '_', '_', '_'})),
- mnesia:write(#pubsub_index{index = node, last = LastIdx, free = []})
- end,
- case mnesia:transaction(FNew) of
- {atomic, Result} ->
- rename_default_nodeplugin(),
- ?INFO_MSG("Pubsub node tables updated correctly: ~p", [Result]);
- {aborted, Reason} ->
- ?ERROR_MSG("Problem updating Pubsub node tables:~n~p", [Reason])
- end;
- [nodeid, id, parent, type, owners, options] ->
- F = fun({pubsub_node, NodeId, Id, Parent, Type, Owners, Options}) ->
- #pubsub_node{
- nodeid = NodeId,
- id = Id,
- parents = [Parent],
- type = Type,
- owners = Owners,
- options = Options}
- end,
- mnesia:transform_table(pubsub_node, F, [nodeid, id, parents, type, owners, options]),
- rename_default_nodeplugin();
- _ ->
- ok
+ [host_node, host_parent, info] ->
+ ?INFO_MSG("upgrade node pubsub tables", []),
+ F = fun () ->
+ {Result, LastIdx} = lists:foldl(fun ({pubsub_node,
+ NodeId, ParentId,
+ {nodeinfo, Items,
+ Options,
+ Entities}},
+ {RecList,
+ NodeIdx}) ->
+ ItemsList =
+ lists:foldl(fun
+ ({item,
+ IID,
+ Publisher,
+ Payload},
+ Acc) ->
+ C =
+ {unknown,
+ Publisher},
+ M =
+ {now(),
+ Publisher},
+ mnesia:write(#pubsub_item{itemid
+ =
+ {IID,
+ NodeIdx},
+ creation
+ =
+ C,
+ modification
+ =
+ M,
+ payload
+ =
+ Payload}),
+ [{Publisher,
+ IID}
+ | Acc]
+ end,
+ [],
+ Items),
+ Owners =
+ dict:fold(fun
+ (JID,
+ {entity,
+ Aff,
+ Sub},
+ Acc) ->
+ UsrItems =
+ lists:foldl(fun
+ ({P,
+ I},
+ IAcc) ->
+ case
+ P
+ of
+ JID ->
+ [I
+ | IAcc];
+ _ ->
+ IAcc
+ end
+ end,
+ [],
+ ItemsList),
+ mnesia:write({pubsub_state,
+ {JID,
+ NodeIdx},
+ UsrItems,
+ Aff,
+ Sub}),
+ case
+ Aff
+ of
+ owner ->
+ [JID
+ | Acc];
+ _ ->
+ Acc
+ end
+ end,
+ [],
+ Entities),
+ mnesia:delete({pubsub_node,
+ NodeId}),
+ {[#pubsub_node{nodeid
+ =
+ NodeId,
+ id
+ =
+ NodeIdx,
+ parents
+ =
+ [element(2,
+ ParentId)],
+ owners
+ =
+ Owners,
+ options
+ =
+ Options}
+ | RecList],
+ NodeIdx + 1}
+ end,
+ {[], 1},
+ mnesia:match_object({pubsub_node,
+ {Host,
+ '_'},
+ '_',
+ '_'})),
+ mnesia:write(#pubsub_index{index = node, last = LastIdx,
+ free = []}),
+ Result
+ end,
+ {atomic, NewRecords} = mnesia:transaction(F),
+ {atomic, ok} = mnesia:delete_table(pubsub_node),
+ {atomic, ok} = mnesia:create_table(pubsub_node,
+ [{disc_copies, [node()]},
+ {attributes,
+ record_info(fields,
+ pubsub_node)}]),
+ FNew = fun () ->
+ lists:foreach(fun (Record) -> mnesia:write(Record) end,
+ NewRecords)
+ end,
+ case mnesia:transaction(FNew) of
+ {atomic, Result} ->
+ ?INFO_MSG("Pubsub node tables updated correctly: ~p",
+ [Result]);
+ {aborted, Reason} ->
+ ?ERROR_MSG("Problem updating Pubsub node tables:~n~p",
+ [Reason])
+ end;
+ [nodeid, parentid, type, owners, options] ->
+ F = fun ({pubsub_node, NodeId, {_, Parent}, Type,
+ Owners, Options}) ->
+ #pubsub_node{nodeid = NodeId, id = 0,
+ parents = [Parent], type = Type,
+ owners = Owners, options = Options}
+ end,
+ mnesia:transform_table(pubsub_node, F,
+ [nodeid, id, parents, type, owners, options]),
+ FNew = fun () ->
+ LastIdx = lists:foldl(fun (#pubsub_node{nodeid =
+ NodeId} =
+ PubsubNode,
+ NodeIdx) ->
+ mnesia:write(PubsubNode#pubsub_node{id
+ =
+ NodeIdx}),
+ lists:foreach(fun
+ (#pubsub_state{stateid
+ =
+ StateId} =
+ State) ->
+ {JID,
+ _} =
+ StateId,
+ mnesia:delete({pubsub_state,
+ StateId}),
+ mnesia:write(State#pubsub_state{stateid
+ =
+ {JID,
+ NodeIdx}})
+ end,
+ mnesia:match_object(#pubsub_state{stateid
+ =
+ {'_',
+ NodeId},
+ _
+ =
+ '_'})),
+ lists:foreach(fun
+ (#pubsub_item{itemid
+ =
+ ItemId} =
+ Item) ->
+ {IID,
+ _} =
+ ItemId,
+ {M1,
+ M2} =
+ Item#pubsub_item.modification,
+ {C1,
+ C2} =
+ Item#pubsub_item.creation,
+ mnesia:delete({pubsub_item,
+ ItemId}),
+ mnesia:write(Item#pubsub_item{itemid
+ =
+ {IID,
+ NodeIdx},
+ modification
+ =
+ {M2,
+ M1},
+ creation
+ =
+ {C2,
+ C1}})
+ end,
+ mnesia:match_object(#pubsub_item{itemid
+ =
+ {'_',
+ NodeId},
+ _
+ =
+ '_'})),
+ NodeIdx + 1
+ end,
+ 1,
+ mnesia:match_object({pubsub_node,
+ {Host, '_'},
+ '_', '_',
+ '_', '_',
+ '_'})
+ ++
+ mnesia:match_object({pubsub_node,
+ {{'_',
+ ServerHost,
+ '_'},
+ '_'},
+ '_', '_',
+ '_', '_',
+ '_'})),
+ mnesia:write(#pubsub_index{index = node,
+ last = LastIdx, free = []})
+ end,
+ case mnesia:transaction(FNew) of
+ {atomic, Result} ->
+ rename_default_nodeplugin(),
+ ?INFO_MSG("Pubsub node tables updated correctly: ~p",
+ [Result]);
+ {aborted, Reason} ->
+ ?ERROR_MSG("Problem updating Pubsub node tables:~n~p",
+ [Reason])
+ end;
+ [nodeid, id, parent, type, owners, options] ->
+ F = fun ({pubsub_node, NodeId, Id, Parent, Type, Owners,
+ Options}) ->
+ #pubsub_node{nodeid = NodeId, id = Id,
+ parents = [Parent], type = Type,
+ owners = Owners, options = Options}
+ end,
+ mnesia:transform_table(pubsub_node, F,
+ [nodeid, id, parents, type, owners, options]),
+ rename_default_nodeplugin();
+ _ -> ok
end,
- mnesia:transaction(fun() ->
- case catch mnesia:first(pubsub_node) of
- {_, L} when is_list(L) ->
- lists:foreach(
- fun({H, N}) when is_list(N) ->
- [Node] = mnesia:read({pubsub_node, {H, N}}),
- Type = Node#pubsub_node.type,
- BN = element(2, node_call(Type, path_to_node, [N])),
- BP = case [element(2, node_call(Type, path_to_node, [P])) || P <- Node#pubsub_node.parents] of
- [<<>>] -> [];
- Parents -> Parents
- end,
- mnesia:write(Node#pubsub_node{nodeid={H, BN}, parents=BP}),
- mnesia:delete({pubsub_node, {H, N}});
- (_) ->
- ok
- end, mnesia:all_keys(pubsub_node));
- _ ->
- ok
- end
- end).
+ mnesia:transaction(fun () ->
+ case catch mnesia:first(pubsub_node) of
+ {_, L} when is_binary(L) ->
+ lists:foreach(fun ({H, N})
+ when is_binary(N) ->
+ [Node] =
+ mnesia:read({pubsub_node,
+ {H,
+ N}}),
+ Type =
+ Node#pubsub_node.type,
+ BN = element(2,
+ node_call(Type,
+ path_to_node,
+ [N])),
+ BP = case [element(2,
+ node_call(Type,
+ path_to_node,
+ [P]))
+ || P
+ <- Node#pubsub_node.parents]
+ of
+ [<<>>] -> [];
+ Parents ->
+ Parents
+ end,
+ mnesia:write(Node#pubsub_node{nodeid
+ =
+ {H,
+ BN},
+ parents
+ =
+ BP}),
+ mnesia:delete({pubsub_node,
+ {H,
+ N}});
+ (_) -> ok
+ end,
+ mnesia:all_keys(pubsub_node));
+ _ -> ok
+ end
+ end).
rename_default_nodeplugin() ->
- lists:foreach(fun(Node) ->
- mnesia:dirty_write(Node#pubsub_node{type = "hometree"})
- end, mnesia:dirty_match_object(#pubsub_node{type = "default", _ = '_'})).
+ lists:foreach(fun (Node) ->
+ mnesia:dirty_write(Node#pubsub_node{type =
+ <<"hometree">>})
+ end,
+ mnesia:dirty_match_object(#pubsub_node{type =
+ <<"default">>,
+ _ = '_'})).
update_state_database(_Host, _ServerHost) ->
case catch mnesia:table_info(pubsub_state, attributes) of
@@ -486,243 +778,385 @@ update_state_database(_Host, _ServerHost) ->
send_loop(State) ->
receive
- {presence, JID, Pid} ->
- Host = State#state.host,
- ServerHost = State#state.server_host,
- LJID = jlib:jid_tolower(JID),
- BJID = jlib:jid_remove_resource(LJID),
- %% for each node From is subscribed to
- %% and if the node is so configured, send the last published item to From
- lists:foreach(fun(PType) ->
- {result, Subscriptions} = node_action(Host, PType, get_entity_subscriptions, [Host, JID]),
- lists:foreach(
- fun({Node, subscribed, _, SubJID}) ->
- if (SubJID == LJID) or (SubJID == BJID) ->
- #pubsub_node{nodeid = {H, N}, type = Type, id = NodeId, options = Options} = Node,
- case get_option(Options, send_last_published_item) of
- on_sub_and_presence ->
- send_items(H, N, NodeId, Type, LJID, last);
- _ ->
- ok
- end;
- true ->
- % resource not concerned about that subscription
- ok
- end;
- (_) ->
- ok
- end, Subscriptions)
- end, State#state.plugins),
- %% and force send the last PEP events published by its offline and local contacts
- %% only if pubsub is explicitely configured for that.
- %% this is a hack in a sense that PEP should only be based on presence
- %% and is not able to "store" events of remote users (via s2s)
- %% this makes that hack only work for local domain by now
- if not State#state.ignore_pep_from_offline ->
- {User, Server, Resource} = jlib:jid_tolower(JID),
- case catch ejabberd_c2s:get_subscribed(Pid) of
- Contacts when is_list(Contacts) ->
- lists:foreach(
- fun({U, S, R}) ->
- case S of
- ServerHost -> %% local contacts
- case user_resources(U, S) of
- [] -> %% offline
- PeerJID = jlib:make_jid(U, S, R),
- self() ! {presence, User, Server, [Resource], PeerJID};
- _ -> %% online
- % this is already handled by presence probe
- ok
- end;
- _ -> %% remote contacts
- % we can not do anything in any cases
- ok
- end
- end, Contacts);
- _ ->
- ok
- end;
- true ->
- ok
- end,
- send_loop(State);
- {presence, User, Server, Resources, JID} ->
- %% get resources caps and check if processing is needed
- spawn(fun() ->
- Host = State#state.host,
- Owner = jlib:jid_remove_resource(jlib:jid_tolower(JID)),
- lists:foreach(fun(#pubsub_node{nodeid = {_, Node}, type = Type, id = NodeId, options = Options}) ->
- case get_option(Options, send_last_published_item) of
- on_sub_and_presence ->
- lists:foreach(
- fun(Resource) ->
- LJID = {User, Server, Resource},
- Subscribed = case get_option(Options, access_model) of
- open -> true;
- presence -> true;
- whitelist -> false; % subscribers are added manually
- authorize -> false; % likewise
- roster ->
- Grps = get_option(Options, roster_groups_allowed, []),
- {OU, OS, _} = Owner,
- element(2, get_roster_info(OU, OS, LJID, Grps))
- end,
- if Subscribed ->
- send_items(Owner, Node, NodeId, Type, LJID, last);
- true ->
- ok
- end
- end, Resources);
- _ ->
- ok
- end
- end, tree_action(Host, get_nodes, [Owner, JID]))
+ {presence, JID, Pid} ->
+ Host = State#state.host,
+ ServerHost = State#state.server_host,
+ LJID = jlib:jid_tolower(JID),
+ BJID = jlib:jid_remove_resource(LJID),
+ lists:foreach(fun (PType) ->
+ {result, Subscriptions} = node_action(Host,
+ PType,
+ get_entity_subscriptions,
+ [Host,
+ JID]),
+ lists:foreach(fun ({Node, subscribed, _,
+ SubJID}) ->
+ if (SubJID == LJID) or
+ (SubJID == BJID) ->
+ #pubsub_node{nodeid
+ =
+ {H,
+ N},
+ type =
+ Type,
+ id =
+ NodeId,
+ options
+ =
+ Options} =
+ Node,
+ case
+ get_option(Options,
+ send_last_published_item)
+ of
+ on_sub_and_presence ->
+ send_items(H,
+ N,
+ NodeId,
+ Type,
+ LJID,
+ last);
+ _ -> ok
+ end;
+ true ->
+ % resource not concerned about that subscription
+ ok
+ end;
+ (_) -> ok
+ end,
+ Subscriptions)
+ end,
+ State#state.plugins),
+ if not State#state.ignore_pep_from_offline ->
+ {User, Server, Resource} = jlib:jid_tolower(JID),
+ case catch ejabberd_c2s:get_subscribed(Pid) of
+ Contacts when is_list(Contacts) ->
+ lists:foreach(fun ({U, S, R}) ->
+ case S of
+ ServerHost -> %% local contacts
+ case user_resources(U, S) of
+ [] -> %% offline
+ PeerJID =
+ jlib:make_jid(U, S,
+ R),
+ self() !
+ {presence, User,
+ Server, [Resource],
+ PeerJID};
+ _ -> %% online
+ % this is already handled by presence probe
+ ok
+ end;
+ _ -> %% remote contacts
+ % we can not do anything in any cases
+ ok
+ end
+ end,
+ Contacts);
+ _ -> ok
+ end;
+ true -> ok
+ end,
+ send_loop(State);
+ {presence, User, Server, Resources, JID} ->
+ spawn(fun () ->
+ Host = State#state.host,
+ Owner = jlib:jid_remove_resource(jlib:jid_tolower(JID)),
+ lists:foreach(fun (#pubsub_node{nodeid = {_, Node},
+ type = Type,
+ id = NodeId,
+ options = Options}) ->
+ case get_option(Options,
+ send_last_published_item)
+ of
+ on_sub_and_presence ->
+ lists:foreach(fun
+ (Resource) ->
+ LJID =
+ {User,
+ Server,
+ Resource},
+ Subscribed =
+ case
+ get_option(Options,
+ access_model)
+ of
+ open ->
+ true;
+ presence ->
+ true;
+ whitelist ->
+ false; % subscribers are added manually
+ authorize ->
+ false; % likewise
+ roster ->
+ Grps =
+ get_option(Options,
+ roster_groups_allowed,
+ []),
+ {OU,
+ OS,
+ _} =
+ Owner,
+ element(2,
+ get_roster_info(OU,
+ OS,
+ LJID,
+ Grps))
+ end,
+ if
+ Subscribed ->
+ send_items(Owner,
+ Node,
+ NodeId,
+ Type,
+ LJID,
+ last);
+ true ->
+ ok
+ end
+ end,
+ Resources);
+ _ -> ok
+ end
+ end,
+ tree_action(Host, get_nodes,
+ [Owner, JID]))
end),
- send_loop(State);
- stop ->
- ok
+ send_loop(State);
+ stop -> ok
end.
%% -------
%% disco hooks handling functions
%%
-disco_local_identity(Acc, _From, To, [], _Lang) ->
+-spec(disco_local_identity/5 ::
+(
+ Acc :: [xmlel()],
+ _From :: jid(),
+ To :: jid(),
+ NodeId :: <<>> | mod_pubsub:nodeId(),
+ Lang :: binary())
+ -> [xmlel()]
+).
+disco_local_identity(Acc, _From, To, <<>>, _Lang) ->
case lists:member(?PEPNODE, plugins(To#jid.lserver)) of
- true ->
- [{xmlelement, "identity", [{"category", "pubsub"}, {"type", "pep"}], []} | Acc];
- false -> Acc
+ true ->
+ [#xmlel{name = <<"identity">>,
+ attrs =
+ [{<<"category">>, <<"pubsub">>},
+ {<<"type">>, <<"pep">>}],
+ children = []}
+ | Acc];
+ false -> Acc
end;
disco_local_identity(Acc, _From, _To, _Node, _Lang) ->
Acc.
-disco_local_features(Acc, _From, To, [], _Lang) ->
+-spec(disco_local_features/5 ::
+(
+ Acc :: [xmlel()],
+ _From :: jid(),
+ To :: jid(),
+ NodeId :: <<>> | mod_pubsub:nodeId(),
+ Lang :: binary())
+ -> [binary(),...]
+).
+disco_local_features(Acc, _From, To, <<>>, _Lang) ->
Host = To#jid.lserver,
Feats = case Acc of
- {result, I} -> I;
- _ -> []
- end,
- {result, Feats ++ lists:map(fun(Feature) ->
- ?NS_PUBSUB++"#"++Feature
- end, features(Host, <<>>))};
+ {result, I} -> I;
+ _ -> []
+ end,
+ {result,
+ Feats ++
+ lists:map(fun (Feature) ->
+ <<(?NS_PUBSUB)/binary, "#", Feature/binary>>
+ end,
+ features(Host, <<>>))};
disco_local_features(Acc, _From, _To, _Node, _Lang) ->
Acc.
-disco_local_items(Acc, _From, _To, [], _Lang) ->
- Acc;
-disco_local_items(Acc, _From, _To, _Node, _Lang) ->
- Acc.
-
-disco_sm_identity(Acc, From, To, Node, Lang) when is_list(Node) ->
- disco_sm_identity(Acc, From, To, list_to_binary(Node), Lang);
+disco_local_items(Acc, _From, _To, <<>>, _Lang) -> Acc;
+disco_local_items(Acc, _From, _To, _Node, _Lang) -> Acc.
+
+%disco_sm_identity(Acc, From, To, Node, Lang)
+% when is_binary(Node) ->
+% disco_sm_identity(Acc, From, To, iolist_to_binary(Node),
+% Lang);
+-spec(disco_sm_identity/5 ::
+(
+ Acc :: empty | [xmlel()],
+ From :: jid(),
+ To :: jid(),
+ Node :: mod_pubsub:nodeId(),
+ Lang :: binary())
+ -> [xmlel()]
+).
disco_sm_identity(empty, From, To, Node, Lang) ->
disco_sm_identity([], From, To, Node, Lang);
disco_sm_identity(Acc, From, To, Node, _Lang) ->
- disco_identity(jlib:jid_tolower(jlib:jid_remove_resource(To)), Node, From) ++ Acc.
+ disco_identity(jlib:jid_tolower(jlib:jid_remove_resource(To)), Node, From)
+ ++ Acc.
disco_identity(_Host, <<>>, _From) ->
- [{xmlelement, "identity", [{"category", "pubsub"}, {"type", "pep"}], []}];
+ [#xmlel{name = <<"identity">>,
+ attrs =
+ [{<<"category">>, <<"pubsub">>},
+ {<<"type">>, <<"pep">>}],
+ children = []}];
disco_identity(Host, Node, From) ->
- Action = fun(#pubsub_node{id = Idx, type = Type, options = Options, owners = Owners}) ->
- case get_allowed_items_call(Host, Idx, From, Type, Options, Owners) of
- {result, _} ->
- {result, [{xmlelement, "identity", [{"category", "pubsub"}, {"type", "pep"}], []},
- {xmlelement, "identity",
- [{"category", "pubsub"},
- {"type", "leaf"}
- | case get_option(Options, title) of
- false -> [];
- [Title] -> [{"name", Title}]
- end],
- []}]};
- _ -> {result, []}
- end
- end,
+ Action = fun (#pubsub_node{id = Idx, type = Type,
+ options = Options, owners = Owners}) ->
+ case get_allowed_items_call(Host, Idx, From, Type, Options, Owners) of
+ {result, _} ->
+ {result,
+ [#xmlel{name = <<"identity">>,
+ attrs =
+ [{<<"category">>, <<"pubsub">>},
+ {<<"type">>, <<"pep">>}],
+ children = []},
+ #xmlel{name = <<"identity">>,
+ attrs =
+ [{<<"category">>, <<"pubsub">>},
+ {<<"type">>, <<"leaf">>}
+ | case get_option(Options, title) of
+ false -> [];
+ [Title] -> [{<<"name">>, Title}]
+ end],
+ children = []}]};
+ _ -> {result, []}
+ end
+ end,
case transaction(Host, Node, Action, sync_dirty) of
- {result, {_, Result}} -> Result;
- _ -> []
+ {result, {_, Result}} -> Result;
+ _ -> []
end.
-disco_sm_features(Acc, From, To, Node, Lang) when is_list(Node) ->
- disco_sm_features(Acc, From, To, list_to_binary(Node), Lang);
+-spec(disco_sm_features/5 ::
+(
+ Acc :: empty | {result, Features::[Feature::binary()]},
+ From :: jid(),
+ To :: jid(),
+ Node :: mod_pubsub:nodeId(),
+ Lang :: binary())
+ -> {result, Features::[Feature::binary()]}
+).
+%disco_sm_features(Acc, From, To, Node, Lang)
+% when is_binary(Node) ->
+% disco_sm_features(Acc, From, To, iolist_to_binary(Node),
+% Lang);
disco_sm_features(empty, From, To, Node, Lang) ->
disco_sm_features({result, []}, From, To, Node, Lang);
disco_sm_features({result, OtherFeatures} = _Acc, From, To, Node, _Lang) ->
{result,
OtherFeatures ++
disco_features(jlib:jid_tolower(jlib:jid_remove_resource(To)), Node, From)};
-disco_sm_features(Acc, _From, _To, _Node, _Lang) ->
- Acc.
+disco_sm_features(Acc, _From, _To, _Node, _Lang) -> Acc.
disco_features(_Host, <<>>, _From) ->
- [?NS_PUBSUB
- | [?NS_PUBSUB++"#"++Feature || Feature <- features("pep")]];
+ [?NS_PUBSUB | [<<(?NS_PUBSUB)/binary, "#", Feature/binary>>
+ || Feature <- features(<<"pep">>)]];
disco_features(Host, Node, From) ->
- Action = fun(#pubsub_node{id = Idx, type = Type, options = Options, owners = Owners}) ->
- case get_allowed_items_call(Host, Idx, From, Type, Options, Owners) of
- {result, _} ->
- {result, [?NS_PUBSUB
- | [?NS_PUBSUB ++ "#" ++ Feature || Feature <- features("pep")]]};
- _ -> {result, []}
- end
- end,
+ Action = fun (#pubsub_node{id = Idx, type = Type,
+ options = Options, owners = Owners}) ->
+ case get_allowed_items_call(Host, Idx, From, Type, Options, Owners) of
+ {result, _} ->
+ {result,
+ [?NS_PUBSUB | [<<(?NS_PUBSUB)/binary, "#",
+ Feature/binary>>
+ || Feature <- features(<<"pep">>)]]};
+ _ -> {result, []}
+ end
+ end,
case transaction(Host, Node, Action, sync_dirty) of
- {result, {_, Result}} -> Result;
- _ -> []
+ {result, {_, Result}} -> Result;
+ _ -> []
end.
-disco_sm_items(Acc, From, To, Node, Lang) when is_list(Node) ->
- disco_sm_items(Acc, From, To, list_to_binary(Node), Lang);
+-spec(disco_sm_items/5 ::
+(
+ Acc :: empty | {result, [xmlel()]},
+ From :: jid(),
+ To :: jid(),
+ Node :: mod_pubsub:nodeId(),
+ Lang :: binary())
+ -> {result, [xmlel()]}
+).
+%disco_sm_items(Acc, From, To, Node, Lang)
+% when is_binary(Node) ->
+% disco_sm_items(Acc, From, To, iolist_to_binary(Node),
+% Lang);
disco_sm_items(empty, From, To, Node, Lang) ->
disco_sm_items({result, []}, From, To, Node, Lang);
disco_sm_items({result, OtherItems}, From, To, Node, _Lang) ->
{result,
lists:usort(OtherItems ++
- disco_items(jlib:jid_tolower(jlib:jid_remove_resource(To)), Node, From))};
-disco_sm_items(Acc, _From, _To, _Node, _Lang) ->
- Acc.
-
+ disco_items(jlib:jid_tolower(jlib:jid_remove_resource(To)), Node, From))};
+disco_sm_items(Acc, _From, _To, _Node, _Lang) -> Acc.
+
+-spec(disco_items/3 ::
+(
+ Host :: mod_pubsub:host(),
+ Node :: mod_pubsub:nodeId(),
+ From :: jid())
+ -> [xmlel()]
+).
disco_items(Host, <<>>, From) ->
- Action = fun(#pubsub_node{nodeid ={_, NodeID}, options = Options, type = Type, id = Idx, owners = Owners}, Acc) ->
- case get_allowed_items_call(Host, Idx, From, Type, Options, Owners) of
- {result, _} ->
- [{xmlelement, "item",
- [{"node", binary_to_list(NodeID)},
- {"jid", case Host of
- {_,_,_} -> jlib:jid_to_string(Host);
- _Host -> Host
- end}
- | case get_option(Options, title) of
- false -> [];
- [Title] -> [{"name", Title}]
- end],
- []}
+ Action = fun (#pubsub_node{nodeid = {_, NodeID},
+ options = Options, type = Type, id = Idx,
+ owners = Owners},
+ Acc) ->
+ case get_allowed_items_call(Host, Idx, From, Type, Options, Owners) of
+ {result, _} ->
+ [#xmlel{name = <<"item">>,
+ attrs =
+ [{<<"node">>, (NodeID)},
+ {<<"jid">>,
+ case Host of
+ {_, _, _} ->
+ jlib:jid_to_string(Host);
+ _Host -> Host
+ end}
+ | case get_option(Options, title) of
+ false -> [];
+ [Title] -> [{<<"name">>, Title}]
+ end],
+ children = []}
| Acc];
- _ -> Acc
- end
- end,
+ _ -> Acc
+ end
+ end,
case transaction(Host, Action, sync_dirty) of
- {result, Items} -> Items;
- _ -> []
+ {result, Items} -> Items;
+ _ -> []
end;
-
disco_items(Host, Node, From) ->
- Action = fun(#pubsub_node{id = Idx, type = Type, options = Options, owners = Owners}) ->
- case get_allowed_items_call(Host, Idx, From, Type, Options, Owners) of
- {result, Items} ->
- {result, [{xmlelement, "item",
- [{"jid", case Host of
- {_,_,_} -> jlib:jid_to_string(Host);
- _Host -> Host
- end},
- {"name", ItemID}], []}
- || #pubsub_item{itemid = {ItemID,_}} <- Items]};
- _ -> {result, []}
- end
- end,
+ Action = fun (#pubsub_node{id = Idx, type = Type,
+ options = Options, owners = Owners}) ->
+ case get_allowed_items_call(Host, Idx, From, Type,
+ Options, Owners)
+ of
+ {result, Items} ->
+ {result,
+ [#xmlel{name = <<"item">>,
+ attrs =
+ [{<<"jid">>,
+ case Host of
+ {_, _, _} ->
+ jlib:jid_to_string(Host);
+ _Host -> Host
+ end},
+ {<<"name">>, ItemID}],
+ children = []}
+ || #pubsub_item{itemid = {ItemID, _}} <- Items]};
+ _ -> {result, []}
+ end
+ end,
case transaction(Host, Node, Action, sync_dirty) of
- {result, {_, Result}} -> Result;
- _ -> []
+ {result, {_, Result}} -> Result;
+ _ -> []
end.
%% -------
@@ -733,36 +1167,40 @@ caps_update(#jid{luser = U, lserver = S, lresource = R} = From, To, _Features) -
Pid = ejabberd_sm:get_session_pid(U, S, R),
presence_probe(From, To, Pid).
-presence_probe(#jid{luser = User, lserver = Server, lresource = Resource} = JID, JID, Pid) ->
- %%?DEBUG("presence probe self ~s@~s/~s ~s@~s/~s",[User,Server,Resource,element(2,JID),element(3,JID),element(4,JID)]),
+presence_probe(#jid{luser = User, lserver = Server, lresource = Resource} = JID,
+ JID, Pid) ->
presence(Server, {presence, JID, Pid}),
presence(Server, {presence, User, Server, [Resource], JID});
-presence_probe(#jid{luser = User, lserver = Server}, #jid{luser = User, lserver = Server}, _Pid) ->
+presence_probe(#jid{luser = User, lserver = Server},
+ #jid{luser = User, lserver = Server}, _Pid) ->
%% ignore presence_probe from other ressources for the current user
%% this way, we do not send duplicated last items if user already connected with other clients
ok;
-presence_probe(#jid{luser = User, lserver = Server, lresource = Resource}, #jid{lserver = Host} = JID, _Pid) ->
- %%?DEBUG("presence probe peer ~s@~s/~s ~s@~s/~s",[User,Server,Resource,element(2,JID),element(3,JID),element(4,JID)]),
+presence_probe(#jid{luser = User, lserver = Server, lresource = Resource},
+ #jid{lserver = Host} = JID, _Pid) ->
presence(Host, {presence, User, Server, [Resource], JID}).
presence(ServerHost, Presence) ->
- SendLoop = case whereis(gen_mod:get_module_proc(ServerHost, ?LOOPNAME)) of
- undefined ->
- % in case send_loop process died, we rebuild a minimal State record and respawn it
- Host = host(ServerHost),
- Plugins = plugins(Host),
- PepOffline = case catch ets:lookup(gen_mod:get_module_proc(ServerHost, config), ignore_pep_from_offline) of
- [{ignore_pep_from_offline, PO}] -> PO;
- _ -> true
- end,
- State = #state{host = Host,
- server_host = ServerHost,
- ignore_pep_from_offline = PepOffline,
- plugins = Plugins},
- init_send_loop(ServerHost, State);
- Pid ->
- Pid
- end,
+ SendLoop = case
+ whereis(gen_mod:get_module_proc(ServerHost, ?LOOPNAME))
+ of
+ undefined ->
+ Host = host(ServerHost),
+ Plugins = plugins(Host),
+ PepOffline = case catch
+ ets:lookup(gen_mod:get_module_proc(ServerHost,
+ config),
+ ignore_pep_from_offline)
+ of
+ [{ignore_pep_from_offline, PO}] -> PO;
+ _ -> true
+ end,
+ State = #state{host = Host, server_host = ServerHost,
+ ignore_pep_from_offline = PepOffline,
+ plugins = Plugins},
+ init_send_loop(ServerHost, State);
+ Pid -> Pid
+ end,
SendLoop ! Presence.
%% -------
@@ -770,46 +1208,73 @@ presence(ServerHost, Presence) ->
%%
out_subscription(User, Server, JID, subscribed) ->
- Owner = jlib:make_jid(User, Server, ""),
+ Owner = jlib:make_jid(User, Server, <<"">>),
{PUser, PServer, PResource} = jlib:jid_tolower(JID),
PResources = case PResource of
- [] -> user_resources(PUser, PServer);
- _ -> [PResource]
- end,
- presence(Server, {presence, PUser, PServer, PResources, Owner}),
+ <<>> -> user_resources(PUser, PServer);
+ _ -> [PResource]
+ end,
+ presence(Server,
+ {presence, PUser, PServer, PResources, Owner}),
true;
-out_subscription(_,_,_,_) ->
- true.
-in_subscription(_, User, Server, Owner, unsubscribed, _) ->
- unsubscribe_user(jlib:make_jid(User, Server, ""), Owner),
+out_subscription(_, _, _, _) -> true.
+
+in_subscription(_, User, Server, Owner, unsubscribed,
+ _) ->
+ unsubscribe_user(jlib:make_jid(User, Server, <<"">>),
+ Owner),
true;
-in_subscription(_, _, _, _, _, _) ->
- true.
+in_subscription(_, _, _, _, _, _) -> true.
unsubscribe_user(Entity, Owner) ->
BJID = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
Host = host(element(2, BJID)),
- spawn(fun() ->
- lists:foreach(fun(PType) ->
- {result, Subscriptions} = node_action(Host, PType, get_entity_subscriptions, [Host, Entity]),
- lists:foreach(fun
- ({#pubsub_node{options = Options, owners = Owners, id = NodeId}, subscribed, _, JID}) ->
- case get_option(Options, access_model) of
- presence ->
- case lists:member(BJID, Owners) of
- true ->
- node_action(Host, PType, unsubscribe_node, [NodeId, Entity, JID, all]);
- false ->
- {result, ok}
- end;
- _ ->
- {result, ok}
- end;
- (_) ->
- ok
- end, Subscriptions)
- end, plugins(Host))
- end).
+ spawn(fun () ->
+ lists:foreach(fun (PType) ->
+ {result, Subscriptions} =
+ node_action(Host, PType,
+ get_entity_subscriptions,
+ [Host, Entity]),
+ lists:foreach(fun ({#pubsub_node{options
+ =
+ Options,
+ owners
+ =
+ Owners,
+ id =
+ NodeId},
+ subscribed, _,
+ JID}) ->
+ case
+ get_option(Options,
+ access_model)
+ of
+ presence ->
+ case
+ lists:member(BJID,
+ Owners)
+ of
+ true ->
+ node_action(Host,
+ PType,
+ unsubscribe_node,
+ [NodeId,
+ Entity,
+ JID,
+ all]);
+ false ->
+ {result,
+ ok}
+ end;
+ _ ->
+ {result, ok}
+ end;
+ (_) -> ok
+ end,
+ Subscriptions)
+ end,
+ plugins(Host))
+ end).
%% -------
%% user remove hook handling function
@@ -818,25 +1283,8 @@ unsubscribe_user(Entity, Owner) ->
remove_user(User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
- Entity = jlib:make_jid(LUser, LServer, ""),
+ Entity = jlib:make_jid(LUser, LServer, <<"">>),
Host = host(LServer),
- HomeTreeBase = string_to_node("/home/"++LServer++"/"++LUser),
- spawn(fun() ->
- lists:foreach(fun(PType) ->
- {result, Subscriptions} = node_action(Host, PType, get_entity_subscriptions, [Host, Entity]),
- lists:foreach(fun
- ({#pubsub_node{id = NodeId}, _, _, JID}) -> node_action(Host, PType, unsubscribe_node, [NodeId, Entity, JID, all])
- end, Subscriptions),
- {result, Affiliations} = node_action(Host, PType, get_entity_affiliations, [Host, Entity]),
- lists:foreach(fun
- ({#pubsub_node{nodeid = {H, N}, parents = []}, owner}) -> delete_node(H, N, Entity);
- ({#pubsub_node{nodeid = {H, N}, type = "hometree"}, owner}) when N == HomeTreeBase -> delete_node(H, N, Entity);
- ({#pubsub_node{id = NodeId}, publisher}) -> node_action(Host, PType, set_affiliation, [NodeId, Entity, none]);
- (_) -> ok
- end, Affiliations)
- end, plugins(Host))
- end).
-
%%--------------------------------------------------------------------
%% Function:
%% handle_call(Request, From, State) -> {reply, Reply, State} |
@@ -848,6 +1296,66 @@ remove_user(User, Server) ->
%% Description: Handling call messages
%%--------------------------------------------------------------------
%% @private
+ HomeTreeBase = <<"/home/", LServer/binary, "/", LUser/binary>>,
+ spawn(fun () ->
+ lists:foreach(fun (PType) ->
+ {result, Subscriptions} =
+ node_action(Host, PType,
+ get_entity_subscriptions,
+ [Host, Entity]),
+ lists:foreach(fun ({#pubsub_node{id =
+ NodeId},
+ _, _, JID}) ->
+ node_action(Host,
+ PType,
+ unsubscribe_node,
+ [NodeId,
+ Entity,
+ JID,
+ all])
+ end,
+ Subscriptions),
+ {result, Affiliations} =
+ node_action(Host, PType,
+ get_entity_affiliations,
+ [Host, Entity]),
+ lists:foreach(fun ({#pubsub_node{nodeid
+ =
+ {H,
+ N},
+ parents
+ =
+ []},
+ owner}) ->
+ delete_node(H, N,
+ Entity);
+ ({#pubsub_node{nodeid
+ =
+ {H,
+ N},
+ type =
+ <<"hometree">>},
+ owner})
+ when N ==
+ HomeTreeBase ->
+ delete_node(H, N,
+ Entity);
+ ({#pubsub_node{id =
+ NodeId},
+ publisher}) ->
+ node_action(Host,
+ PType,
+ set_affiliation,
+ [NodeId,
+ Entity,
+ none]);
+ (_) -> ok
+ end,
+ Affiliations)
+ end,
+ plugins(Host))
+ end).
+
handle_call(server_host, _From, State) ->
{reply, State#state.server_host, State};
handle_call(plugins, _From, State) ->
@@ -866,9 +1374,6 @@ handle_call(stop, _From, State) ->
%% Description: Handling cast messages
%%--------------------------------------------------------------------
%% @private
-handle_cast(_Msg, State) ->
- {noreply, State}.
-
%%--------------------------------------------------------------------
%% Function: handle_info(Info, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
@@ -876,18 +1381,26 @@ handle_cast(_Msg, State) ->
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------
%% @private
+handle_cast(_Msg, State) -> {noreply, State}.
+
+-spec(handle_info/2 ::
+(
+ _ :: {route, From::jid(), To::jid(), Packet::xmlel()},
+ State :: state())
+ -> {noreply, state()}
+).
+
handle_info({route, From, To, Packet},
- #state{server_host = ServerHost,
- access = Access,
- plugins = Plugins} = State) ->
- case catch do_route(ServerHost, Access, Plugins, To#jid.lserver, From, To, Packet) of
- {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]);
- _ -> ok
+ #state{server_host = ServerHost, access = Access,
+ plugins = Plugins} =
+ State) ->
+ case catch do_route(ServerHost, Access, Plugins,
+ To#jid.lserver, From, To, Packet)
+ of
+ {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]);
+ _ -> ok
end,
{noreply, State};
-handle_info(_Info, State) ->
- {noreply, State}.
-
%%--------------------------------------------------------------------
%% Function: terminate(Reason, State) -> void()
%% Description: This function is called by a gen_server when it is about to
@@ -896,522 +1409,748 @@ handle_info(_Info, State) ->
%% The return value is ignored.
%%--------------------------------------------------------------------
%% @private
-terminate(_Reason, #state{host = Host,
- server_host = ServerHost,
- nodetree = TreePlugin,
- plugins = Plugins}) ->
+handle_info(_Info, State) -> {noreply, State}.
+
+terminate(_Reason,
+ #state{host = Host, server_host = ServerHost,
+ nodetree = TreePlugin, plugins = Plugins}) ->
ejabberd_router:unregister_route(Host),
case lists:member(?PEPNODE, Plugins) of
- true ->
- ejabberd_hooks:delete(caps_update, ServerHost, ?MODULE, caps_update, 80),
- ejabberd_hooks:delete(disco_sm_identity, ServerHost, ?MODULE, disco_sm_identity, 75),
- ejabberd_hooks:delete(disco_sm_features, ServerHost, ?MODULE, disco_sm_features, 75),
- ejabberd_hooks:delete(disco_sm_items, ServerHost, ?MODULE, disco_sm_items, 75),
- gen_iq_handler:remove_iq_handler(ejabberd_sm, ServerHost, ?NS_PUBSUB),
- gen_iq_handler:remove_iq_handler(ejabberd_sm, ServerHost, ?NS_PUBSUB_OWNER);
- false ->
- ok
+ true ->
+ ejabberd_hooks:delete(caps_update, ServerHost, ?MODULE,
+ caps_update, 80),
+ ejabberd_hooks:delete(disco_sm_identity, ServerHost,
+ ?MODULE, disco_sm_identity, 75),
+ ejabberd_hooks:delete(disco_sm_features, ServerHost,
+ ?MODULE, disco_sm_features, 75),
+ ejabberd_hooks:delete(disco_sm_items, ServerHost,
+ ?MODULE, disco_sm_items, 75),
+ gen_iq_handler:remove_iq_handler(ejabberd_sm,
+ ServerHost, ?NS_PUBSUB),
+ gen_iq_handler:remove_iq_handler(ejabberd_sm,
+ ServerHost, ?NS_PUBSUB_OWNER);
+ false -> ok
end,
- ejabberd_hooks:delete(sm_remove_connection_hook, ServerHost, ?MODULE, on_user_offline, 75),
- ejabberd_hooks:delete(disco_local_identity, ServerHost, ?MODULE, disco_local_identity, 75),
- ejabberd_hooks:delete(disco_local_features, ServerHost, ?MODULE, disco_local_features, 75),
- ejabberd_hooks:delete(disco_local_items, ServerHost, ?MODULE, disco_local_items, 75),
- ejabberd_hooks:delete(presence_probe_hook, ServerHost, ?MODULE, presence_probe, 80),
- ejabberd_hooks:delete(roster_in_subscription, ServerHost, ?MODULE, in_subscription, 50),
- ejabberd_hooks:delete(roster_out_subscription, ServerHost, ?MODULE, out_subscription, 50),
- ejabberd_hooks:delete(remove_user, ServerHost, ?MODULE, remove_user, 50),
- ejabberd_hooks:delete(anonymous_purge_hook, ServerHost, ?MODULE, remove_user, 50),
+ ejabberd_hooks:delete(sm_remove_connection_hook,
+ ServerHost, ?MODULE, on_user_offline, 75),
+ ejabberd_hooks:delete(disco_local_identity, ServerHost,
+ ?MODULE, disco_local_identity, 75),
+ ejabberd_hooks:delete(disco_local_features, ServerHost,
+ ?MODULE, disco_local_features, 75),
+ ejabberd_hooks:delete(disco_local_items, ServerHost,
+ ?MODULE, disco_local_items, 75),
+ ejabberd_hooks:delete(presence_probe_hook, ServerHost,
+ ?MODULE, presence_probe, 80),
+ ejabberd_hooks:delete(roster_in_subscription,
+ ServerHost, ?MODULE, in_subscription, 50),
+ ejabberd_hooks:delete(roster_out_subscription,
+ ServerHost, ?MODULE, out_subscription, 50),
+ ejabberd_hooks:delete(remove_user, ServerHost, ?MODULE,
+ remove_user, 50),
+ ejabberd_hooks:delete(anonymous_purge_hook, ServerHost,
+ ?MODULE, remove_user, 50),
mod_disco:unregister_feature(ServerHost, ?NS_PUBSUB),
gen_mod:get_module_proc(ServerHost, ?LOOPNAME) ! stop,
- terminate_plugins(Host, ServerHost, Plugins, TreePlugin).
-
%%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
%% Description: Convert process state when code is changed
%%--------------------------------------------------------------------
%% @private
-code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
+ terminate_plugins(Host, ServerHost, Plugins,
+ TreePlugin).
+
+code_change(_OldVsn, State, _Extra) -> {ok, State}.
+
+-spec(do_route/7 ::
+(
+ ServerHost :: binary(),
+ Access :: atom(),
+ Plugins :: [binary(),...],
+ Host :: mod_pubsub:hostPubsub(),
+ From :: jid(),
+ To :: jid(),
+ Packet :: xmlel())
+ -> ok
+).
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
do_route(ServerHost, Access, Plugins, Host, From, To, Packet) ->
- {xmlelement, Name, Attrs, _Els} = Packet,
+ #xmlel{name = Name, attrs = Attrs} = Packet,
case To of
- #jid{luser = "", lresource = ""} ->
- case Name of
- "iq" ->
- case jlib:iq_query_info(Packet) of
- #iq{type = get, xmlns = ?NS_DISCO_INFO,
- sub_el = SubEl, lang = Lang} = IQ ->
- {xmlelement, _, QAttrs, _} = SubEl,
- Node = xml:get_attr_s("node", QAttrs),
- Info = ejabberd_hooks:run_fold(
- disco_info, ServerHost, [],
- [ServerHost, ?MODULE, "", ""]),
- Res = case iq_disco_info(Host, Node, From, Lang) of
- {result, IQRes} ->
- jlib:iq_to_xml(
- IQ#iq{type = result,
- sub_el = [{xmlelement, "query",
- QAttrs, IQRes++Info}]});
- {error, Error} ->
- jlib:make_error_reply(Packet, Error)
- end,
- ejabberd_router:route(To, From, Res);
- #iq{type = get, xmlns = ?NS_DISCO_ITEMS,
- sub_el = SubEl} = IQ ->
- {xmlelement, _, QAttrs, _} = SubEl,
- Node = xml:get_attr_s("node", QAttrs),
- Res = case iq_disco_items(Host, Node, From) of
- {result, IQRes} ->
- jlib:iq_to_xml(
- IQ#iq{type = result,
- sub_el = [{xmlelement, "query",
- QAttrs, IQRes}]});
- {error, Error} ->
- jlib:make_error_reply(Packet, Error)
- end,
- ejabberd_router:route(To, From, Res);
- #iq{type = IQType, xmlns = ?NS_PUBSUB,
- lang = Lang, sub_el = SubEl} = IQ ->
- Res =
- case iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang, Access, Plugins) of
- {result, IQRes} ->
- jlib:iq_to_xml(
- IQ#iq{type = result,
- sub_el = IQRes});
- {error, Error} ->
- jlib:make_error_reply(Packet, Error)
- end,
- ejabberd_router:route(To, From, Res);
- #iq{type = IQType, xmlns = ?NS_PUBSUB_OWNER,
- lang = Lang, sub_el = SubEl} = IQ ->
- Res =
- case iq_pubsub_owner(Host, ServerHost, From, IQType, SubEl, Lang) of
- {result, IQRes} ->
- jlib:iq_to_xml(
- IQ#iq{type = result,
- sub_el = IQRes});
- {error, Error} ->
- jlib:make_error_reply(Packet, Error)
- end,
- ejabberd_router:route(To, From, Res);
- #iq{type = get, xmlns = ?NS_VCARD = XMLNS,
- lang = Lang, sub_el = _SubEl} = IQ ->
- Res = IQ#iq{type = result,
- sub_el = [{xmlelement, "vCard", [{"xmlns", XMLNS}],
- iq_get_vcard(Lang)}]},
- ejabberd_router:route(To, From, jlib:iq_to_xml(Res));
- #iq{type = set, xmlns = ?NS_COMMANDS} = IQ ->
- Res = case iq_command(Host, ServerHost, From, IQ, Access, Plugins) of
- {error, Error} ->
- jlib:make_error_reply(Packet, Error);
- {result, IQRes} ->
- jlib:iq_to_xml(IQ#iq{type = result,
- sub_el = IQRes})
- end,
- ejabberd_router:route(To, From, Res);
- #iq{} ->
- Err = jlib:make_error_reply(
- Packet,
- ?ERR_FEATURE_NOT_IMPLEMENTED),
- ejabberd_router:route(To, From, Err);
- _ ->
- ok
- end;
- "message" ->
- case xml:get_attr_s("type", Attrs) of
- "error" ->
- ok;
- _ ->
- case find_authorization_response(Packet) of
- none ->
- ok;
- invalid ->
- ejabberd_router:route(To, From,
- jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST));
- XFields ->
- handle_authorization_response(Host, From, To, Packet, XFields)
- end
- end;
- _ ->
- ok
- end;
- _ ->
- case xml:get_attr_s("type", Attrs) of
- "error" ->
- ok;
- "result" ->
- ok;
- _ ->
- Err = jlib:make_error_reply(Packet, ?ERR_ITEM_NOT_FOUND),
- ejabberd_router:route(To, From, Err)
- end
+ #jid{luser = <<"">>, lresource = <<"">>} ->
+ case Name of
+ <<"iq">> ->
+ case jlib:iq_query_info(Packet) of
+ #iq{type = get, xmlns = ?NS_DISCO_INFO, sub_el = SubEl,
+ lang = Lang} =
+ IQ ->
+ #xmlel{attrs = QAttrs} = SubEl,
+ Node = xml:get_attr_s(<<"node">>, QAttrs),
+ Info = ejabberd_hooks:run_fold(disco_info, ServerHost,
+ [],
+ [ServerHost, ?MODULE,
+ <<"">>, <<"">>]),
+ Res = case iq_disco_info(Host, Node, From, Lang) of
+ {result, IQRes} ->
+ jlib:iq_to_xml(IQ#iq{type = result,
+ sub_el =
+ [#xmlel{name =
+ <<"query">>,
+ attrs =
+ QAttrs,
+ children =
+ IQRes ++
+ Info}]});
+ {error, Error} ->
+ jlib:make_error_reply(Packet, Error)
+ end,
+ ejabberd_router:route(To, From, Res);
+ #iq{type = get, xmlns = ?NS_DISCO_ITEMS,
+ sub_el = SubEl} =
+ IQ ->
+ #xmlel{attrs = QAttrs} = SubEl,
+ Node = xml:get_attr_s(<<"node">>, QAttrs),
+ Res = case iq_disco_items(Host, Node, From) of
+ {result, IQRes} ->
+ jlib:iq_to_xml(IQ#iq{type = result,
+ sub_el =
+ [#xmlel{name =
+ <<"query">>,
+ attrs =
+ QAttrs,
+ children =
+ IQRes}]})
+% {error, Error} ->
+% jlib:make_error_reply(Packet, Error)
+ end,
+ ejabberd_router:route(To, From, Res);
+ #iq{type = IQType, xmlns = ?NS_PUBSUB, lang = Lang,
+ sub_el = SubEl} =
+ IQ ->
+ Res = case iq_pubsub(Host, ServerHost, From, IQType,
+ SubEl, Lang, Access, Plugins)
+ of
+ {result, IQRes} ->
+ jlib:iq_to_xml(IQ#iq{type = result,
+ sub_el = IQRes});
+ {error, Error} ->
+ jlib:make_error_reply(Packet, Error)
+ end,
+ ejabberd_router:route(To, From, Res);
+ #iq{type = IQType, xmlns = ?NS_PUBSUB_OWNER,
+ lang = Lang, sub_el = SubEl} =
+ IQ ->
+ Res = case iq_pubsub_owner(Host, ServerHost, From,
+ IQType, SubEl, Lang)
+ of
+ {result, IQRes} ->
+ jlib:iq_to_xml(IQ#iq{type = result,
+ sub_el = IQRes});
+ {error, Error} ->
+ jlib:make_error_reply(Packet, Error)
+ end,
+ ejabberd_router:route(To, From, Res);
+ #iq{type = get, xmlns = (?NS_VCARD) = XMLNS,
+ lang = Lang, sub_el = _SubEl} =
+ IQ ->
+ Res = IQ#iq{type = result,
+ sub_el =
+ [#xmlel{name = <<"vCard">>,
+ attrs = [{<<"xmlns">>, XMLNS}],
+ children = iq_get_vcard(Lang)}]},
+ ejabberd_router:route(To, From, jlib:iq_to_xml(Res));
+ #iq{type = set, xmlns = ?NS_COMMANDS} = IQ ->
+ Res = case iq_command(Host, ServerHost, From, IQ,
+ Access, Plugins)
+ of
+ {error, Error} ->
+ jlib:make_error_reply(Packet, Error);
+ {result, IQRes} ->
+ jlib:iq_to_xml(IQ#iq{type = result,
+ sub_el = IQRes})
+ end,
+ ejabberd_router:route(To, From, Res);
+ #iq{} ->
+ Err = jlib:make_error_reply(Packet,
+ ?ERR_FEATURE_NOT_IMPLEMENTED),
+ ejabberd_router:route(To, From, Err);
+ _ -> ok
+ end;
+ <<"message">> ->
+ case xml:get_attr_s(<<"type">>, Attrs) of
+ <<"error">> -> ok;
+ _ ->
+ case find_authorization_response(Packet) of
+ none -> ok;
+ invalid ->
+ ejabberd_router:route(To, From,
+ jlib:make_error_reply(Packet,
+ ?ERR_BAD_REQUEST));
+ XFields ->
+ handle_authorization_response(Host, From, To,
+ Packet, XFields)
+ end
+ end;
+ _ -> ok
+ end;
+ _ ->
+ case xml:get_attr_s(<<"type">>, Attrs) of
+ <<"error">> -> ok;
+ <<"result">> -> ok;
+ _ ->
+ Err = jlib:make_error_reply(Packet,
+ ?ERR_ITEM_NOT_FOUND),
+ ejabberd_router:route(To, From, Err)
+ end
end.
-command_disco_info(_Host, <<?NS_COMMANDS>>, _From) ->
- IdentityEl = {xmlelement, "identity", [{"category", "automation"}, {"type", "command-list"}], []},
+command_disco_info(_Host, ?NS_COMMANDS, _From) ->
+ IdentityEl = #xmlel{name = <<"identity">>,
+ attrs =
+ [{<<"category">>, <<"automation">>},
+ {<<"type">>, <<"command-list">>}],
+ children = []},
{result, [IdentityEl]};
-command_disco_info(_Host, <<?NS_PUBSUB_GET_PENDING>>, _From) ->
- IdentityEl = {xmlelement, "identity", [{"category", "automation"}, {"type", "command-node"}], []},
- FeaturesEl = {xmlelement, "feature", [{"var", ?NS_COMMANDS}], []},
+command_disco_info(_Host, ?NS_PUBSUB_GET_PENDING,
+ _From) ->
+ IdentityEl = #xmlel{name = <<"identity">>,
+ attrs =
+ [{<<"category">>, <<"automation">>},
+ {<<"type">>, <<"command-node">>}],
+ children = []},
+ FeaturesEl = #xmlel{name = <<"feature">>,
+ attrs = [{<<"var">>, ?NS_COMMANDS}], children = []},
{result, [IdentityEl, FeaturesEl]}.
node_disco_info(Host, Node, From) ->
node_disco_info(Host, Node, From, true, true).
-%node_disco_identity(Host, Node, From) ->
-% node_disco_info(Host, Node, From, true, false).
-%node_disco_features(Host, Node, From) ->
-% node_disco_info(Host, Node, From, false, true).
+
node_disco_info(Host, Node, From, Identity, Features) ->
- Action =
- fun(#pubsub_node{type = Type, id = NodeId}) ->
- I = case Identity of
- false ->
- [];
- true ->
- Types =
- case tree_call(Host, get_subnodes, [Host, Node, From]) of
- [] ->
- ["leaf"]; %% No sub-nodes: it's a leaf node
- _ ->
- case node_call(Type, get_items, [NodeId, From]) of
- {result, []} -> ["collection"];
- {result, _} -> ["leaf", "collection"];
- _ -> []
- end
- end,
- lists:map(fun(T) ->
- {xmlelement, "identity", [{"category", "pubsub"},
- {"type", T}], []}
- end, Types)
- end,
- F = case Features of
- false ->
- [];
- true ->
- [{xmlelement, "feature", [{"var", ?NS_PUBSUB}], []} |
- lists:map(fun(T) ->
- {xmlelement, "feature", [{"var", ?NS_PUBSUB++"#"++T}], []}
- end, features(Type))]
- end,
- %% TODO: add meta-data info (spec section 5.4)
- {result, I ++ F}
- end,
+% Action =
+% fun(#pubsub_node{type = Type, id = NodeId}) ->
+% I = case Identity of
+% false ->
+% [];
+% true ->
+% Types =
+% case tree_call(Host, get_subnodes, [Host, Node, From]) of
+% [] ->
+% [<<"leaf">>]; %% No sub-nodes: it's a leaf node
+% _ ->
+% case node_call(Type, get_items, [NodeId, From]) of
+% {result, []} -> [<<"collection">>];
+% {result, _} -> [<<"leaf">>, <<"collection">>];
+% _ -> []
+% end
+% end,
+% lists:map(fun(T) ->
+% #xmlel{name = <<"identity">>,
+% attrs =
+% [{<<"category">>,
+% <<"pubsub">>},
+% {<<"type">>, T}],
+% children = []}
+% end, Types)
+% end,
+% F = case Features of
+% false ->
+% [];
+% true ->
+% [#xmlel{name = <<"feature">>,
+% attrs = [{<<"var">>, ?NS_PUBSUB}],
+% children = []}
+% | lists:map(fun (T) ->
+% #xmlel{name = <<"feature">>,
+% attrs =
+% [{<<"var">>,
+% <<(?NS_PUBSUB)/binary,
+% "#",
+% T/binary>>}],
+% children = []}
+% end,
+% features(Type))]
+% end,
+% %% TODO: add meta-data info (spec section 5.4)
+% {result, I ++ F}
+% end,
+% case transaction(Host, Node, Action, sync_dirty) of
+% {result, {_, Result}} -> {result, Result};
+% Other -> Other
+% end.
+ Action = fun (#pubsub_node{type = Type, id = NodeId}) ->
+ I = Types = case tree_call(Host, get_subnodes,
+ [Host, Node, From])
+ of
+ [] -> [<<"leaf">>];
+ _ ->
+ case node_call(Type, get_items,
+ [NodeId, From])
+ of
+ {result, []} ->
+ [<<"collection">>];
+ {result, _} ->
+ [<<"leaf">>,
+ <<"collection">>];
+ _ -> []
+ end
+ end,
+ lists:map(fun (T) ->
+ #xmlel{name = <<"identity">>,
+ attrs =
+ [{<<"category">>,
+ <<"pubsub">>},
+ {<<"type">>, T}],
+ children = []}
+ end,
+ Types),
+ F = [#xmlel{name = <<"feature">>,
+ attrs = [{<<"var">>, ?NS_PUBSUB}],
+ children = []}
+ | lists:map(fun (T) ->
+ #xmlel{name = <<"feature">>,
+ attrs =
+ [{<<"var">>,
+ <<(?NS_PUBSUB)/binary,
+ "#",
+ T/binary>>}],
+ children = []}
+ end,
+ features(Type))],
+ {result, I ++ F}
+ end,
case transaction(Host, Node, Action, sync_dirty) of
- {result, {_, Result}} -> {result, Result};
- Other -> Other
+ {result, {_, Result}} -> {result, Result};
+ Other -> Other
end.
iq_disco_info(Host, SNode, From, Lang) ->
- [RealSNode|_] = case SNode of
- [] -> [[]];
- _ -> string:tokens(SNode, "!")
- end,
- Node = string_to_node(RealSNode),
+ [Node | _] = case SNode of
+ <<>> -> [<<>>];
+ _ -> str:tokens(SNode, <<"!">>)
+ end,
+ % Node = string_to_node(RealSNode),
case Node of
- <<>> ->
- {result,
- [{xmlelement, "identity",
- [{"category", "pubsub"},
- {"type", "service"},
- {"name", translate:translate(Lang, "Publish-Subscribe")}], []},
- {xmlelement, "feature", [{"var", ?NS_DISCO_INFO}], []},
- {xmlelement, "feature", [{"var", ?NS_DISCO_ITEMS}], []},
- {xmlelement, "feature", [{"var", ?NS_PUBSUB}], []},
- {xmlelement, "feature", [{"var", ?NS_COMMANDS}], []},
- {xmlelement, "feature", [{"var", ?NS_VCARD}], []}] ++
- lists:map(fun(Feature) ->
- {xmlelement, "feature", [{"var", ?NS_PUBSUB++"#"++Feature}], []}
- end, features(Host, Node))};
- <<?NS_COMMANDS>> ->
- command_disco_info(Host, Node, From);
- <<?NS_PUBSUB_GET_PENDING>> ->
- command_disco_info(Host, Node, From);
- _ ->
- node_disco_info(Host, Node, From)
- end.
-
-iq_disco_items(Host, [], From) ->
- case tree_action(Host, get_subnodes, [Host, <<>>, From]) of
- Nodes when is_list(Nodes) ->
- {result, lists:map(
- fun(#pubsub_node{nodeid = {_, SubNode}, options = Options}) ->
- Attrs =
- case get_option(Options, title) of
- false ->
- [{"jid", Host} |nodeAttr(SubNode)];
- Title ->
- [{"jid", Host}, {"name", Title}|nodeAttr(SubNode)]
- end,
- {xmlelement, "item", Attrs, []}
- end, Nodes)};
- Other ->
- Other
- end;
+ <<>> ->
+ {result,
+ [#xmlel{name = <<"identity">>,
+ attrs =
+ [{<<"category">>, <<"pubsub">>},
+ {<<"type">>, <<"service">>},
+ {<<"name">>,
+ translate:translate(Lang, <<"Publish-Subscribe">>)}],
+ children = []},
+ #xmlel{name = <<"feature">>,
+ attrs = [{<<"var">>, ?NS_DISCO_INFO}], children = []},
+ #xmlel{name = <<"feature">>,
+ attrs = [{<<"var">>, ?NS_DISCO_ITEMS}], children = []},
+ #xmlel{name = <<"feature">>,
+ attrs = [{<<"var">>, ?NS_PUBSUB}], children = []},
+ #xmlel{name = <<"feature">>,
+ attrs = [{<<"var">>, ?NS_COMMANDS}], children = []},
+ #xmlel{name = <<"feature">>,
+ attrs = [{<<"var">>, ?NS_VCARD}], children = []}]
+ ++
+ lists:map(fun (Feature) ->
+ #xmlel{name = <<"feature">>,
+ attrs =
+ [{<<"var">>, <<(?NS_PUBSUB)/binary, "#", Feature/binary>>}],
+ children = []}
+ end,
+ features(Host, Node))};
+ ?NS_COMMANDS -> command_disco_info(Host, Node, From);
+ ?NS_PUBSUB_GET_PENDING ->
+ command_disco_info(Host, Node, From);
+ _ -> node_disco_info(Host, Node, From)
+ end.
+
+-spec(iq_disco_items/3 ::
+(
+ Host :: mod_pubsub:host(),
+ NodeId :: <<>> | mod_pubsub:nodeId(),
+ From :: jid())
+ -> {result, [xmlel()]}
+).
+iq_disco_items(Host, <<>>, From) ->
+ {result,
+ lists:map(fun (#pubsub_node{nodeid = {_, SubNode},
+ options = Options}) ->
+ Attrs = case get_option(Options, title) of
+ false ->
+ [{<<"jid">>, Host}
+ | nodeAttr(SubNode)];
+ Title ->
+ [{<<"jid">>, Host},
+ {<<"name">>, Title}
+ | nodeAttr(SubNode)]
+ end,
+ #xmlel{name = <<"item">>, attrs = Attrs,
+ children = []}
+ end,
+ tree_action(Host, get_subnodes, [Host, <<>>, From]))};
+% case tree_action(Host, get_subnodes, [Host, <<>>, From]) of
+% Nodes when is_list(Nodes) ->
+% {result,
+% lists:map(fun (#pubsub_node{nodeid = {_, SubNode},
+% options = Options}) ->
+% Attrs = case get_option(Options, title) of
+% false ->
+% [{<<"jid">>, Host}
+% | nodeAttr(SubNode)];
+% Title ->
+% [{<<"jid">>, Host},
+% {<<"name">>, Title}
+% | nodeAttr(SubNode)]
+% end,
+% #xmlel{name = <<"item">>, attrs = Attrs,
+% children = []}
+% end,
+% Nodes)};
+% Other -> Other
+% end;
iq_disco_items(Host, ?NS_COMMANDS, _From) ->
- %% TODO: support localization of this string
- CommandItems = [{xmlelement, "item", [{"jid", Host}, {"node", ?NS_PUBSUB_GET_PENDING}, {"name", "Get Pending"}], []}],
+ CommandItems = [#xmlel{name = <<"item">>,
+ attrs =
+ [{<<"jid">>, Host},
+ {<<"node">>, ?NS_PUBSUB_GET_PENDING},
+ {<<"name">>, <<"Get Pending">>}],
+ children = []}],
{result, CommandItems};
iq_disco_items(_Host, ?NS_PUBSUB_GET_PENDING, _From) ->
- CommandItems = [],
- {result, CommandItems};
+ CommandItems = [], {result, CommandItems};
iq_disco_items(Host, Item, From) ->
- case string:tokens(Item, "!") of
- [_SNode, _ItemID] ->
- {result, []};
- [SNode] ->
- Node = string_to_node(SNode),
- Action = fun(#pubsub_node{id = Idx, type = Type, options = Options, owners = Owners}) ->
- NodeItems = case get_allowed_items_call(Host, Idx, From, Type, Options, Owners) of
- {result, R} -> R;
- _ -> []
- end,
- Nodes = lists:map(
- fun(#pubsub_node{nodeid = {_, SubNode}, options = SubOptions}) ->
- Attrs =
- case get_option(SubOptions, title) of
- false ->
- [{"jid", Host} |nodeAttr(SubNode)];
- Title ->
- [{"jid", Host}, {"name", Title}|nodeAttr(SubNode)]
- end,
- {xmlelement, "item", Attrs, []}
- end, tree_call(Host, get_subnodes, [Host, Node, From])),
- Items = lists:map(
- fun(#pubsub_item{itemid = {RN, _}}) ->
- {result, Name} = node_call(Type, get_item_name, [Host, Node, RN]),
- {xmlelement, "item", [{"jid", Host}, {"name", Name}], []}
- end, NodeItems),
- {result, Nodes ++ Items}
- end,
- case transaction(Host, Node, Action, sync_dirty) of
- {result, {_, Result}} -> {result, Result};
- Other -> Other
- end
+ case str:tokens(Item, <<"!">>) of
+ [_Node, _ItemID] -> {result, []};
+ [Node] ->
+% Node = string_to_node(SNode),
+ Action = fun (#pubsub_node{id = Idx, type = Type,
+ options = Options, owners = Owners}) ->
+ NodeItems = case get_allowed_items_call(Host, Idx,
+ From, Type,
+ Options,
+ Owners)
+ of
+ {result, R} -> R;
+ _ -> []
+ end,
+ Nodes = lists:map(fun (#pubsub_node{nodeid =
+ {_, SubNode},
+ options =
+ SubOptions}) ->
+ Attrs = case
+ get_option(SubOptions,
+ title)
+ of
+ false ->
+ [{<<"jid">>,
+ Host}
+ | nodeAttr(SubNode)];
+ Title ->
+ [{<<"jid">>,
+ Host},
+ {<<"name">>,
+ Title}
+ | nodeAttr(SubNode)]
+ end,
+ #xmlel{name = <<"item">>,
+ attrs = Attrs,
+ children = []}
+ end,
+ tree_call(Host, get_subnodes,
+ [Host, Node, From])),
+ Items = lists:map(fun (#pubsub_item{itemid =
+ {RN, _}}) ->
+ {result, Name} =
+ node_call(Type,
+ get_item_name,
+ [Host, Node,
+ RN]),
+ #xmlel{name = <<"item">>,
+ attrs =
+ [{<<"jid">>,
+ Host},
+ {<<"name">>,
+ Name}],
+ children = []}
+ end,
+ NodeItems),
+ {result, Nodes ++ Items}
+ end,
+ case transaction(Host, Node, Action, sync_dirty) of
+ {result, {_, Result}} -> {result, Result};
+ Other -> Other
+ end
end.
+-spec(iq_sm/3 ::
+(
+ From :: jid(),
+ To :: jid(),
+ IQ :: iq_request())
+ -> iq_result() | iq_error()
+).
iq_sm(From, To, #iq{type = Type, sub_el = SubEl, xmlns = XMLNS, lang = Lang} = IQ) ->
ServerHost = To#jid.lserver,
LOwner = jlib:jid_tolower(jlib:jid_remove_resource(To)),
Res = case XMLNS of
- ?NS_PUBSUB -> iq_pubsub(LOwner, ServerHost, From, Type, SubEl, Lang);
- ?NS_PUBSUB_OWNER -> iq_pubsub_owner(LOwner, ServerHost, From, Type, SubEl, Lang)
+ ?NS_PUBSUB ->
+ iq_pubsub(LOwner, ServerHost, From, Type, SubEl, Lang);
+ ?NS_PUBSUB_OWNER ->
+ iq_pubsub_owner(LOwner, ServerHost, From, Type, SubEl,
+ Lang)
end,
case Res of
- {result, IQRes} -> IQ#iq{type = result, sub_el = IQRes};
- {error, Error} -> IQ#iq{type = error, sub_el = [Error, SubEl]}
+ {result, IQRes} -> IQ#iq{type = result, sub_el = IQRes};
+ {error, Error} ->
+ IQ#iq{type = error, sub_el = [Error, SubEl]}
end.
iq_get_vcard(Lang) ->
- [{xmlelement, "FN", [], [{xmlcdata, "ejabberd/mod_pubsub"}]},
- {xmlelement, "URL", [], [{xmlcdata, ?EJABBERD_URI}]},
- {xmlelement, "DESC", [],
- [{xmlcdata,
- translate:translate(Lang,
- "ejabberd Publish-Subscribe module") ++
- "\nCopyright (c) 2004-2013 ProcessOne"}]}].
+ [#xmlel{name = <<"FN">>, attrs = [],
+ children = [{xmlcdata, <<"ejabberd/mod_pubsub">>}]},
+ #xmlel{name = <<"URL">>, attrs = [],
+ children = [{xmlcdata, ?EJABBERD_URI}]},
+ #xmlel{name = <<"DESC">>, attrs = [],
+ children =
+ [{xmlcdata,
+ <<(translate:translate(Lang,
+ <<"ejabberd Publish-Subscribe module">>))/binary,
+ "\nCopyright (c) 2004-2013 ProcessOne">>}]}].
+
+-spec(iq_pubsub/6 ::
+(
+ Host :: mod_pubsub:host(),
+ ServerHost :: binary(),
+ From :: jid(),
+ IQType :: 'get' | 'set',
+ SubEl :: xmlel(),
+ Lang :: binary())
+ -> {result, [xmlel()]}
+ %%%
+ | {error, xmlel()}
+).
iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang) ->
iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang, all, plugins(ServerHost)).
+-spec(iq_pubsub/8 ::
+(
+ Host :: mod_pubsub:host(),
+ ServerHost :: binary(),
+ From :: jid(),
+ IQType :: 'get' | 'set',
+ SubEl :: xmlel(),
+ Lang :: binary(),
+ Access :: atom(),
+ Plugins :: [binary(),...])
+ -> {result, [xmlel()]}
+ %%%
+ | {error, xmlel()}
+).
+
iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang, Access, Plugins) ->
- {xmlelement, _, _, SubEls} = SubEl,
+ #xmlel{children = SubEls} = SubEl,
case xml:remove_cdata(SubEls) of
- [{xmlelement, Name, Attrs, Els} | Rest] ->
- Node = string_to_node(xml:get_attr_s("node", Attrs)),
- case {IQType, Name} of
- {set, "create"} ->
- Config = case Rest of
- [{xmlelement, "configure", _, C}] -> C;
- _ -> []
- end,
- %% Get the type of the node
- Type = case xml:get_attr_s("type", Attrs) of
- [] -> hd(Plugins);
- T -> T
- end,
- %% we use Plugins list matching because we do not want to allocate
- %% atoms for non existing type, this prevent atom allocation overflow
- case lists:member(Type, Plugins) of
- false ->
- {error, extended_error(
- ?ERR_FEATURE_NOT_IMPLEMENTED,
- unsupported, "create-nodes")};
- true ->
- create_node(Host, ServerHost, Node, From,
- Type, Access, Config)
- end;
- {set, "publish"} ->
- case xml:remove_cdata(Els) of
- [{xmlelement, "item", ItemAttrs, Payload}] ->
- ItemId = xml:get_attr_s("id", ItemAttrs),
- publish_item(Host, ServerHost, Node, From, ItemId, Payload);
- [] ->
- %% Publisher attempts to publish to persistent node with no item
- {error, extended_error(?ERR_BAD_REQUEST,
- "item-required")};
- _ ->
- %% Entity attempts to publish item with multiple payload elements or namespace does not match
- {error, extended_error(?ERR_BAD_REQUEST,
- "invalid-payload")}
- end;
- {set, "retract"} ->
- ForceNotify = case xml:get_attr_s("notify", Attrs) of
- "1" -> true;
- "true" -> true;
- _ -> false
- end,
- case xml:remove_cdata(Els) of
- [{xmlelement, "item", ItemAttrs, _}] ->
- ItemId = xml:get_attr_s("id", ItemAttrs),
- delete_item(Host, Node, From, ItemId, ForceNotify);
- _ ->
- %% Request does not specify an item
- {error, extended_error(?ERR_BAD_REQUEST,
- "item-required")}
- end;
- {set, "subscribe"} ->
- Config = case Rest of
- [{xmlelement, "options", _, C}] -> C;
- _ -> []
- end,
- JID = xml:get_attr_s("jid", Attrs),
- subscribe_node(Host, Node, From, JID, Config);
- {set, "unsubscribe"} ->
- JID = xml:get_attr_s("jid", Attrs),
- SubId = xml:get_attr_s("subid", Attrs),
- unsubscribe_node(Host, Node, From, JID, SubId);
- {get, "items"} ->
- MaxItems = xml:get_attr_s("max_items", Attrs),
- SubId = xml:get_attr_s("subid", Attrs),
- ItemIDs = lists:foldl(fun
- ({xmlelement, "item", ItemAttrs, _}, Acc) ->
- case xml:get_attr_s("id", ItemAttrs) of
- "" -> Acc;
- ItemID -> [ItemID|Acc]
- end;
- (_, Acc) ->
- Acc
- end, [], xml:remove_cdata(Els)),
- get_items(Host, Node, From, SubId, MaxItems, ItemIDs);
- {get, "subscriptions"} ->
- get_subscriptions(Host, Node, From, Plugins);
- {get, "affiliations"} ->
- get_affiliations(Host, Node, From, Plugins);
- {get, "options"} ->
- SubID = xml:get_attr_s("subid", Attrs),
- JID = xml:get_attr_s("jid", Attrs),
- get_options(Host, Node, JID, SubID, Lang);
- {set, "options"} ->
- SubID = xml:get_attr_s("subid", Attrs),
- JID = xml:get_attr_s("jid", Attrs),
- set_options(Host, Node, JID, SubID, Els);
- _ ->
- {error, ?ERR_FEATURE_NOT_IMPLEMENTED}
- end;
- Other ->
- ?INFO_MSG("Too many actions: ~p", [Other]),
- {error, ?ERR_BAD_REQUEST}
+ [#xmlel{name = Name, attrs = Attrs, children = Els} | Rest] ->
+ Node = xml:get_attr_s(<<"node">>, Attrs),
+ case {IQType, Name} of
+ {set, <<"create">>} ->
+ Config = case Rest of
+ [#xmlel{name = <<"configure">>, children = C}] -> C;
+ _ -> []
+ end,
+ Type = case xml:get_attr_s(<<"type">>, Attrs) of
+ <<>> -> hd(Plugins);
+ T -> T
+ end,
+ case lists:member(Type, Plugins) of
+ false ->
+ {error,
+ extended_error(?ERR_FEATURE_NOT_IMPLEMENTED,
+ unsupported, <<"create-nodes">>)};
+ true ->
+ create_node(Host, ServerHost, Node, From, Type, Access, Config)
+ end;
+ {set, <<"publish">>} ->
+ case xml:remove_cdata(Els) of
+ [#xmlel{name = <<"item">>, attrs = ItemAttrs,
+ children = Payload}] ->
+ ItemId = xml:get_attr_s(<<"id">>, ItemAttrs),
+ publish_item(Host, ServerHost, Node, From, ItemId, Payload);
+ [] ->
+ {error,
+ extended_error(?ERR_BAD_REQUEST, <<"item-required">>)};
+ _ ->
+ {error,
+ extended_error(?ERR_BAD_REQUEST, <<"invalid-payload">>)}
+ end;
+ {set, <<"retract">>} ->
+ ForceNotify = case xml:get_attr_s(<<"notify">>, Attrs)
+ of
+ <<"1">> -> true;
+ <<"true">> -> true;
+ _ -> false
+ end,
+ case xml:remove_cdata(Els) of
+ [#xmlel{name = <<"item">>, attrs = ItemAttrs}] ->
+ ItemId = xml:get_attr_s(<<"id">>, ItemAttrs),
+ delete_item(Host, Node, From, ItemId, ForceNotify);
+ _ ->
+ {error,
+ extended_error(?ERR_BAD_REQUEST, <<"item-required">>)}
+ end;
+ {set, <<"subscribe">>} ->
+ Config = case Rest of
+ [#xmlel{name = <<"options">>, children = C}] -> C;
+ _ -> []
+ end,
+ JID = xml:get_attr_s(<<"jid">>, Attrs),
+ subscribe_node(Host, Node, From, JID, Config);
+ {set, <<"unsubscribe">>} ->
+ JID = xml:get_attr_s(<<"jid">>, Attrs),
+ SubId = xml:get_attr_s(<<"subid">>, Attrs),
+ unsubscribe_node(Host, Node, From, JID, SubId);
+ {get, <<"items">>} ->
+ MaxItems = xml:get_attr_s(<<"max_items">>, Attrs),
+ SubId = xml:get_attr_s(<<"subid">>, Attrs),
+ ItemIDs = lists:foldl(fun (#xmlel{name = <<"item">>,
+ attrs = ItemAttrs},
+ Acc) ->
+ case xml:get_attr_s(<<"id">>,
+ ItemAttrs)
+ of
+ <<"">> -> Acc;
+ ItemID -> [ItemID | Acc]
+ end;
+ (_, Acc) -> Acc
+ end,
+ [], xml:remove_cdata(Els)),
+ get_items(Host, Node, From, SubId, MaxItems, ItemIDs);
+ {get, <<"subscriptions">>} ->
+ get_subscriptions(Host, Node, From, Plugins);
+ {get, <<"affiliations">>} ->
+ get_affiliations(Host, Node, From, Plugins);
+ {get, <<"options">>} ->
+ SubID = xml:get_attr_s(<<"subid">>, Attrs),
+ JID = xml:get_attr_s(<<"jid">>, Attrs),
+ get_options(Host, Node, JID, SubID, Lang);
+ {set, <<"options">>} ->
+ SubID = xml:get_attr_s(<<"subid">>, Attrs),
+ JID = xml:get_attr_s(<<"jid">>, Attrs),
+ set_options(Host, Node, JID, SubID, Els);
+ _ -> {error, ?ERR_FEATURE_NOT_IMPLEMENTED}
+ end;
+ Other ->
+ ?INFO_MSG("Too many actions: ~p", [Other]),
+ {error, ?ERR_BAD_REQUEST}
end.
+
+-spec(iq_pubsub_owner/6 ::
+(
+ Host :: mod_pubsub:host(),
+ ServerHost :: binary(),
+ From :: jid(),
+ IQType :: 'get' | 'set',
+ SubEl :: xmlel(),
+ Lang :: binary())
+ -> {result, [xmlel()]}
+ %%%
+ | {error, xmlel()}
+).
iq_pubsub_owner(Host, ServerHost, From, IQType, SubEl, Lang) ->
- {xmlelement, _, _, SubEls} = SubEl,
+ #xmlel{children = SubEls} = SubEl,
Action = xml:remove_cdata(SubEls),
case Action of
- [{xmlelement, Name, Attrs, Els}] ->
- Node = string_to_node(xml:get_attr_s("node", Attrs)),
- case {IQType, Name} of
- {get, "configure"} ->
- get_configure(Host, ServerHost, Node, From, Lang);
- {set, "configure"} ->
- set_configure(Host, Node, From, Els, Lang);
- {get, "default"} ->
- get_default(Host, Node, From, Lang);
- {set, "delete"} ->
- delete_node(Host, Node, From);
- {set, "purge"} ->
- purge_node(Host, Node, From);
- {get, "subscriptions"} ->
- get_subscriptions(Host, Node, From);
- {set, "subscriptions"} ->
- set_subscriptions(Host, Node, From, xml:remove_cdata(Els));
- {get, "affiliations"} ->
- get_affiliations(Host, Node, From);
- {set, "affiliations"} ->
- set_affiliations(Host, Node, From, xml:remove_cdata(Els));
- _ ->
- {error, ?ERR_FEATURE_NOT_IMPLEMENTED}
- end;
- _ ->
- ?INFO_MSG("Too many actions: ~p", [Action]),
- {error, ?ERR_BAD_REQUEST}
+ [#xmlel{name = Name, attrs = Attrs, children = Els}] ->
+ Node = xml:get_attr_s(<<"node">>, Attrs),
+ case {IQType, Name} of
+ {get, <<"configure">>} ->
+ get_configure(Host, ServerHost, Node, From, Lang);
+ {set, <<"configure">>} ->
+ set_configure(Host, Node, From, Els, Lang);
+ {get, <<"default">>} ->
+ get_default(Host, Node, From, Lang);
+ {set, <<"delete">>} -> delete_node(Host, Node, From);
+ {set, <<"purge">>} -> purge_node(Host, Node, From);
+ {get, <<"subscriptions">>} ->
+ get_subscriptions(Host, Node, From);
+ {set, <<"subscriptions">>} ->
+ set_subscriptions(Host, Node, From,
+ xml:remove_cdata(Els));
+ {get, <<"affiliations">>} ->
+ get_affiliations(Host, Node, From);
+ {set, <<"affiliations">>} ->
+ set_affiliations(Host, Node, From, xml:remove_cdata(Els));
+ _ -> {error, ?ERR_FEATURE_NOT_IMPLEMENTED}
+ end;
+ _ ->
+ ?INFO_MSG("Too many actions: ~p", [Action]),
+ {error, ?ERR_BAD_REQUEST}
end.
iq_command(Host, ServerHost, From, IQ, Access, Plugins) ->
case adhoc:parse_request(IQ) of
- Req when is_record(Req, adhoc_request) ->
- case adhoc_request(Host, ServerHost, From, Req, Access, Plugins) of
- Resp when is_record(Resp, adhoc_response) ->
- {result, [adhoc:produce_response(Req, Resp)]};
- Error ->
- Error
- end;
- Err ->
- Err
+ Req when is_record(Req, adhoc_request) ->
+ case adhoc_request(Host, ServerHost, From, Req, Access,
+ Plugins)
+ of
+ Resp when is_record(Resp, adhoc_response) ->
+ {result, [adhoc:produce_response(Req, Resp)]};
+ Error -> Error
+ end;
+ Err -> Err
end.
%% @doc <p>Processes an Ad Hoc Command.</p>
adhoc_request(Host, _ServerHost, Owner,
- #adhoc_request{node = ?NS_PUBSUB_GET_PENDING,
- lang = Lang,
- action = "execute",
- xdata = false},
- _Access, Plugins) ->
+ #adhoc_request{node = ?NS_PUBSUB_GET_PENDING,
+ lang = Lang, action = <<"execute">>,
+ xdata = false},
+ _Access, Plugins) ->
send_pending_node_form(Host, Owner, Lang, Plugins);
adhoc_request(Host, _ServerHost, Owner,
- #adhoc_request{node = ?NS_PUBSUB_GET_PENDING,
- action = "execute",
- xdata = XData},
- _Access, _Plugins) ->
+ #adhoc_request{node = ?NS_PUBSUB_GET_PENDING,
+ action = <<"execute">>, xdata = XData},
+ _Access, _Plugins) ->
ParseOptions = case XData of
- {xmlelement, "x", _Attrs, _SubEls} = XEl ->
- case jlib:parse_xdata_submit(XEl) of
- invalid ->
- {error, ?ERR_BAD_REQUEST};
- XData2 ->
- case set_xoption(Host, XData2, []) of
- NewOpts when is_list(NewOpts) ->
- {result, NewOpts};
- Err ->
- Err
- end
- end;
- _ ->
- ?INFO_MSG("Bad XForm: ~p", [XData]),
- {error, ?ERR_BAD_REQUEST}
+ #xmlel{name = <<"x">>} = XEl ->
+ case jlib:parse_xdata_submit(XEl) of
+ invalid -> {error, ?ERR_BAD_REQUEST};
+ XData2 ->
+ case set_xoption(Host, XData2, []) of
+ NewOpts when is_list(NewOpts) ->
+ {result, NewOpts};
+ Err -> Err
+ end
+ end;
+ _ ->
+ ?INFO_MSG("Bad XForm: ~p", [XData]),
+ {error, ?ERR_BAD_REQUEST}
end,
case ParseOptions of
- {result, XForm} ->
- case lists:keysearch(node, 1, XForm) of
- {value, {_, Node}} ->
- send_pending_auth_events(Host, Node, Owner);
- false ->
- {error, extended_error(?ERR_BAD_REQUEST, "bad-payload")}
- end;
- Error ->
- Error
+ {result, XForm} ->
+ case lists:keysearch(node, 1, XForm) of
+ {value, {_, Node}} ->
+ send_pending_auth_events(Host, Node, Owner);
+ false ->
+ {error,
+ extended_error(?ERR_BAD_REQUEST, <<"bad-payload">>)}
+ end;
+ Error -> Error
end;
-adhoc_request(_Host, _ServerHost, _Owner, #adhoc_request{action = "cancel"},
- _Access, _Plugins) ->
+adhoc_request(_Host, _ServerHost, _Owner,
+ #adhoc_request{action = <<"cancel">>}, _Access,
+ _Plugins) ->
#adhoc_response{status = canceled};
-adhoc_request(Host, ServerHost, Owner, #adhoc_request{action = []} = R,
- Access, Plugins) ->
- adhoc_request(Host, ServerHost, Owner, R#adhoc_request{action = "execute"},
- Access, Plugins);
-adhoc_request(_Host, _ServerHost, _Owner, Other, _Access, _Plugins) ->
+adhoc_request(Host, ServerHost, Owner,
+ #adhoc_request{action = <<>>} = R, Access, Plugins) ->
+ adhoc_request(Host, ServerHost, Owner,
+ R#adhoc_request{action = <<"execute">>}, Access,
+ Plugins);
+adhoc_request(_Host, _ServerHost, _Owner, Other,
+ _Access, _Plugins) ->
?DEBUG("Couldn't process ad hoc command:~n~p", [Other]),
{error, ?ERR_ITEM_NOT_FOUND}.
@@ -1419,269 +2158,339 @@ adhoc_request(_Host, _ServerHost, _Owner, Other, _Access, _Plugins) ->
%% @doc <p>Sends the process pending subscriptions XForm for Host to
%% Owner.</p>
send_pending_node_form(Host, Owner, _Lang, Plugins) ->
- Filter =
- fun (Plugin) ->
- lists:member("get-pending", features(Plugin))
- end,
+ Filter = fun (Plugin) ->
+ lists:member(<<"get-pending">>, features(Plugin))
+ end,
case lists:filter(Filter, Plugins) of
- [] ->
- {error, ?ERR_FEATURE_NOT_IMPLEMENTED};
- Ps ->
- XOpts = lists:map(fun (Node) ->
- {xmlelement, "option", [],
- [{xmlelement, "value", [],
- [{xmlcdata, node_to_string(Node)}]}]}
- end, get_pending_nodes(Host, Owner, Ps)),
- XForm = {xmlelement, "x", [{"xmlns", ?NS_XDATA}, {"type", "form"}],
- [{xmlelement, "field",
- [{"type", "list-single"}, {"var", "pubsub#node"}],
- lists:usort(XOpts)}]},
- #adhoc_response{status = executing,
- defaultaction = "execute",
- elements = [XForm]}
+ [] -> {error, ?ERR_FEATURE_NOT_IMPLEMENTED};
+ Ps ->
+ XOpts = lists:map(fun (Node) ->
+ #xmlel{name = <<"option">>, attrs = [],
+ children =
+ [#xmlel{name = <<"value">>,
+ attrs = [],
+ children =
+ [{xmlcdata, Node}]}]}
+ end,
+ get_pending_nodes(Host, Owner, Ps)),
+ XForm = #xmlel{name = <<"x">>,
+ attrs =
+ [{<<"xmlns">>, ?NS_XDATA},
+ {<<"type">>, <<"form">>}],
+ children =
+ [#xmlel{name = <<"field">>,
+ attrs =
+ [{<<"type">>, <<"list-single">>},
+ {<<"var">>, <<"pubsub#node">>}],
+ children = lists:usort(XOpts)}]},
+ #adhoc_response{status = executing,
+ defaultaction = <<"execute">>, elements = [XForm]}
end.
get_pending_nodes(Host, Owner, Plugins) ->
- Tr =
- fun (Type) ->
- case node_call(Type, get_pending_nodes, [Host, Owner]) of
- {result, Nodes} -> Nodes;
- _ -> []
- end
- end,
- case transaction(fun () -> {result, lists:flatmap(Tr, Plugins)} end,
- sync_dirty) of
- {result, Res} -> Res;
- Err -> Err
+ Tr = fun (Type) ->
+ case node_call(Type, get_pending_nodes, [Host, Owner])
+ of
+ {result, Nodes} -> Nodes;
+ _ -> []
+ end
+ end,
+ case transaction(fun () ->
+ {result, lists:flatmap(Tr, Plugins)}
+ end,
+ sync_dirty)
+ of
+ {result, Res} -> Res;
+ Err -> Err
end.
%% @spec (Host, Node, Owner) -> iqRes()
%% @doc <p>Send a subscription approval form to Owner for all pending
%% subscriptions on Host and Node.</p>
send_pending_auth_events(Host, Node, Owner) ->
- ?DEBUG("Sending pending auth events for ~s on ~s:~s",
- [jlib:jid_to_string(Owner), Host, node_to_string(Node)]),
- Action =
- fun (#pubsub_node{id = NodeID, type = Type}) ->
- case lists:member("get-pending", features(Type)) of
- true ->
- case node_call(Type, get_affiliation, [NodeID, Owner]) of
- {result, owner} ->
- node_call(Type, get_node_subscriptions, [NodeID]);
- _ ->
- {error, ?ERR_FORBIDDEN}
- end;
- false ->
- {error, ?ERR_FEATURE_NOT_IMPLEMENTED}
- end
- end,
+ ?DEBUG("Sending pending auth events for ~s on "
+ "~s:~s",
+ [jlib:jid_to_string(Owner), Host, Node]),
+ Action = fun (#pubsub_node{id = NodeID, type = Type}) ->
+ case lists:member(<<"get-pending">>, features(Type)) of
+ true ->
+ case node_call(Type, get_affiliation,
+ [NodeID, Owner])
+ of
+ {result, owner} ->
+ node_call(Type, get_node_subscriptions,
+ [NodeID]);
+ _ -> {error, ?ERR_FORBIDDEN}
+ end;
+ false -> {error, ?ERR_FEATURE_NOT_IMPLEMENTED}
+ end
+ end,
case transaction(Host, Node, Action, sync_dirty) of
- {result, {N, Subscriptions}} ->
- lists:foreach(fun({J, pending, _SubID}) -> send_authorization_request(N, jlib:make_jid(J));
- ({J, pending}) -> send_authorization_request(N, jlib:make_jid(J));
- (_) -> ok
- end, Subscriptions),
- #adhoc_response{};
- Err ->
- Err
+ {result, {N, Subscriptions}} ->
+ lists:foreach(fun ({J, pending, _SubID}) ->
+ send_authorization_request(N, jlib:make_jid(J));
+ ({J, pending}) ->
+ send_authorization_request(N, jlib:make_jid(J));
+ (_) -> ok
+ end,
+ Subscriptions),
+ #adhoc_response{};
+ Err -> Err
end.
%%% authorization handling
-send_authorization_request(#pubsub_node{owners = Owners, nodeid = {Host, Node}}, Subscriber) ->
- Lang = "en", %% TODO fix
- Stanza = {xmlelement, "message",
- [],
- [{xmlelement, "x", [{"xmlns", ?NS_XDATA}, {"type", "form"}],
- [{xmlelement, "title", [],
- [{xmlcdata, translate:translate(Lang, "PubSub subscriber request")}]},
- {xmlelement, "instructions", [],
- [{xmlcdata, translate:translate(Lang, "Choose whether to approve this entity's subscription.")}]},
- {xmlelement, "field",
- [{"var", "FORM_TYPE"}, {"type", "hidden"}],
- [{xmlelement, "value", [], [{xmlcdata, ?NS_PUBSUB_SUB_AUTH}]}]},
- {xmlelement, "field",
- [{"var", "pubsub#node"}, {"type", "text-single"},
- {"label", translate:translate(Lang, "Node ID")}],
- [{xmlelement, "value", [],
- [{xmlcdata, node_to_string(Node)}]}]},
- {xmlelement, "field", [{"var", "pubsub#subscriber_jid"},
- {"type", "jid-single"},
- {"label", translate:translate(Lang, "Subscriber Address")}],
- [{xmlelement, "value", [],
- [{xmlcdata, jlib:jid_to_string(Subscriber)}]}]},
- {xmlelement, "field",
- [{"var", "pubsub#allow"},
- {"type", "boolean"},
- {"label", translate:translate(Lang, "Allow this Jabber ID to subscribe to this pubsub node?")}],
- [{xmlelement, "value", [], [{xmlcdata, "false"}]}]}]}]},
- lists:foreach(fun(Owner) ->
- ejabberd_router:route(service_jid(Host), jlib:make_jid(Owner), Stanza)
- end, Owners).
+send_authorization_request(#pubsub_node{owners = Owners, nodeid = {Host, Node}},
+ Subscriber) ->
+ Lang = <<"en">>,
+ Stanza = #xmlel{name = <<"message">>, attrs = [],
+ children =
+ [#xmlel{name = <<"x">>,
+ attrs =
+ [{<<"xmlns">>, ?NS_XDATA},
+ {<<"type">>, <<"form">>}],
+ children =
+ [#xmlel{name = <<"title">>, attrs = [],
+ children =
+ [{xmlcdata,
+ translate:translate(Lang,
+ <<"PubSub subscriber request">>)}]},
+ #xmlel{name = <<"instructions">>,
+ attrs = [],
+ children =
+ [{xmlcdata,
+ translate:translate(Lang,
+ <<"Choose whether to approve this entity's "
+ "subscription.">>)}]},
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"var">>, <<"FORM_TYPE">>},
+ {<<"type">>, <<"hidden">>}],
+ children =
+ [#xmlel{name = <<"value">>,
+ attrs = [],
+ children =
+ [{xmlcdata,
+ ?NS_PUBSUB_SUB_AUTH}]}]},
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"var">>, <<"pubsub#node">>},
+ {<<"type">>,
+ <<"text-single">>},
+ {<<"label">>,
+ translate:translate(Lang,
+ <<"Node ID">>)}],
+ children =
+ [#xmlel{name = <<"value">>,
+ attrs = [],
+ children =
+ [{xmlcdata, Node}]}]},
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"var">>,
+ <<"pubsub#subscriber_jid">>},
+ {<<"type">>, <<"jid-single">>},
+ {<<"label">>,
+ translate:translate(Lang,
+ <<"Subscriber Address">>)}],
+ children =
+ [#xmlel{name = <<"value">>,
+ attrs = [],
+ children =
+ [{xmlcdata,
+ jlib:jid_to_string(Subscriber)}]}]},
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"var">>,
+ <<"pubsub#allow">>},
+ {<<"type">>, <<"boolean">>},
+ {<<"label">>,
+ translate:translate(Lang,
+ <<"Allow this Jabber ID to subscribe to "
+ "this pubsub node?">>)}],
+ children =
+ [#xmlel{name = <<"value">>,
+ attrs = [],
+ children =
+ [{xmlcdata,
+ <<"false">>}]}]}]}]},
+ lists:foreach(fun (Owner) ->
+ ejabberd_router:route(service_jid(Host),
+ jlib:make_jid(Owner), Stanza)
+ end,
+ Owners).
find_authorization_response(Packet) ->
- {xmlelement, _Name, _Attrs, Els} = Packet,
- XData1 = lists:map(fun({xmlelement, "x", XAttrs, _} = XEl) ->
- case xml:get_attr_s("xmlns", XAttrs) of
- ?NS_XDATA ->
- case xml:get_attr_s("type", XAttrs) of
- "cancel" ->
- none;
- _ ->
- jlib:parse_xdata_submit(XEl)
- end;
- _ ->
- none
+ #xmlel{children = Els} = Packet,
+ XData1 = lists:map(fun (#xmlel{name = <<"x">>,
+ attrs = XAttrs} =
+ XEl) ->
+ case xml:get_attr_s(<<"xmlns">>, XAttrs) of
+ ?NS_XDATA ->
+ case xml:get_attr_s(<<"type">>, XAttrs) of
+ <<"cancel">> -> none;
+ _ -> jlib:parse_xdata_submit(XEl)
+ end;
+ _ -> none
end;
- (_) ->
- none
- end, xml:remove_cdata(Els)),
- XData = lists:filter(fun(E) -> E /= none end, XData1),
+ (_) -> none
+ end,
+ xml:remove_cdata(Els)),
+ XData = lists:filter(fun (E) -> E /= none end, XData1),
case XData of
- [invalid] -> invalid;
- [] -> none;
- [XFields] when is_list(XFields) ->
- ?DEBUG("XFields: ~p", [XFields]),
- case lists:keysearch("FORM_TYPE", 1, XFields) of
- {value, {_, [?NS_PUBSUB_SUB_AUTH]}} ->
- XFields;
- _ ->
- invalid
- end
+ [invalid] -> invalid;
+ [] -> none;
+ [XFields] when is_list(XFields) ->
+ ?DEBUG("XFields: ~p", [XFields]),
+ case lists:keysearch(<<"FORM_TYPE">>, 1, XFields) of
+ {value, {_, [?NS_PUBSUB_SUB_AUTH]}} -> XFields;
+ _ -> invalid
+ end
end.
-
%% @spec (Host, JID, Node, Subscription) -> void
%% Host = mod_pubsub:host()
%% JID = jlib:jid()
%% SNode = string()
%% Subscription = atom() | {atom(), mod_pubsub:subid()}
%% @doc Send a message to JID with the supplied Subscription
+%% TODO : ask Christophe's opinion
send_authorization_approval(Host, JID, SNode, Subscription) ->
SubAttrs = case Subscription of
- {S, SID} -> [{"subscription", subscription_to_string(S)},
- {"subid", SID}];
- S -> [{"subscription", subscription_to_string(S)}]
+% {S, SID} ->
+% [{<<"subscription">>, subscription_to_string(S)},
+% {<<"subid">>, SID}];
+ S -> [{<<"subscription">>, subscription_to_string(S)}]
end,
- Stanza = event_stanza(
- [{xmlelement, "subscription",
- [{"jid", jlib:jid_to_string(JID)}|nodeAttr(SNode)] ++ SubAttrs,
- []}]),
+ Stanza = event_stanza([#xmlel{name = <<"subscription">>,
+ attrs =
+ [{<<"jid">>, jlib:jid_to_string(JID)}
+ | nodeAttr(SNode)]
+ ++ SubAttrs,
+ children = []}]),
ejabberd_router:route(service_jid(Host), JID, Stanza).
handle_authorization_response(Host, From, To, Packet, XFields) ->
- case {lists:keysearch("pubsub#node", 1, XFields),
- lists:keysearch("pubsub#subscriber_jid", 1, XFields),
- lists:keysearch("pubsub#allow", 1, XFields)} of
- {{value, {_, [SNode]}}, {value, {_, [SSubscriber]}},
- {value, {_, [SAllow]}}} ->
- Node = string_to_node(SNode),
- Subscriber = jlib:string_to_jid(SSubscriber),
- Allow = case SAllow of
- "1" -> true;
- "true" -> true;
- _ -> false
- end,
- Action = fun(#pubsub_node{type = Type, owners = Owners, id = NodeId}) ->
- IsApprover = lists:member(jlib:jid_tolower(jlib:jid_remove_resource(From)), Owners),
- {result, Subscriptions} = node_call(Type, get_subscriptions, [NodeId, Subscriber]),
- if
- not IsApprover ->
- {error, ?ERR_FORBIDDEN};
- true ->
- update_auth(Host, SNode, Type, NodeId,
- Subscriber, Allow,
- Subscriptions)
- end
- end,
- case transaction(Host, Node, Action, sync_dirty) of
- {error, Error} ->
- ejabberd_router:route(
- To, From,
- jlib:make_error_reply(Packet, Error));
- {result, {_, _NewSubscription}} ->
- %% XXX: notify about subscription state change, section 12.11
- ok;
- _ ->
- ejabberd_router:route(
- To, From,
- jlib:make_error_reply(Packet, ?ERR_INTERNAL_SERVER_ERROR))
- end;
- _ ->
- ejabberd_router:route(
- To, From,
- jlib:make_error_reply(Packet, ?ERR_NOT_ACCEPTABLE))
+ case {lists:keysearch(<<"pubsub#node">>, 1, XFields),
+ lists:keysearch(<<"pubsub#subscriber_jid">>, 1, XFields),
+ lists:keysearch(<<"pubsub#allow">>, 1, XFields)}
+ of
+ {{value, {_, [Node]}}, {value, {_, [SSubscriber]}},
+ {value, {_, [SAllow]}}} ->
+% Node = string_to_node(SNode),
+ Subscriber = jlib:string_to_jid(SSubscriber),
+ Allow = case SAllow of
+ <<"1">> -> true;
+ <<"true">> -> true;
+ _ -> false
+ end,
+ Action = fun (#pubsub_node{type = Type, owners = Owners,
+ id = NodeId}) ->
+ IsApprover =
+ lists:member(jlib:jid_tolower(jlib:jid_remove_resource(From)),
+ Owners),
+ {result, Subscriptions} = node_call(Type,
+ get_subscriptions,
+ [NodeId,
+ Subscriber]),
+ if not IsApprover -> {error, ?ERR_FORBIDDEN};
+ true ->
+ update_auth(Host, Node, Type, NodeId,
+ Subscriber, Allow, Subscriptions)
+ end
+ end,
+ case transaction(Host, Node, Action, sync_dirty) of
+ {error, Error} ->
+ ejabberd_router:route(To, From,
+ jlib:make_error_reply(Packet, Error));
+ {result, {_, _NewSubscription}} ->
+ %% XXX: notify about subscription state change, section 12.11
+ ok;
+ _ ->
+ ejabberd_router:route(To, From,
+ jlib:make_error_reply(Packet,
+ ?ERR_INTERNAL_SERVER_ERROR))
+ end;
+ _ ->
+ ejabberd_router:route(To, From,
+ jlib:make_error_reply(Packet,
+ ?ERR_NOT_ACCEPTABLE))
end.
-update_auth(Host, Node, Type, NodeId, Subscriber,
- Allow, Subscriptions) ->
- Subscription = lists:filter(fun({pending, _}) -> true;
- (_) -> false
- end, Subscriptions),
+update_auth(Host, Node, Type, NodeId, Subscriber, Allow,
+ Subscriptions) ->
+ Subscription = lists:filter(fun ({pending, _}) -> true;
+ (_) -> false
+ end,
+ Subscriptions),
case Subscription of
- [{pending, SubID}] -> %% TODO does not work if several pending
- NewSubscription = case Allow of
- true -> subscribed;
- false -> none
- end,
- node_call(Type, set_subscriptions,
- [NodeId, Subscriber, NewSubscription, SubID]),
- send_authorization_approval(Host, Subscriber, Node,
- NewSubscription),
- {result, ok};
- _ ->
- {error, ?ERR_UNEXPECTED_REQUEST}
+ [{pending, SubID}] ->
+ NewSubscription = case Allow of
+ true -> subscribed;
+ false -> none
+ end,
+ node_call(Type, set_subscriptions,
+ [NodeId, Subscriber, NewSubscription, SubID]),
+ send_authorization_approval(Host, Subscriber, Node,
+ NewSubscription),
+ {result, ok};
+ _ -> {error, ?ERR_UNEXPECTED_REQUEST}
end.
-define(XFIELD(Type, Label, Var, Val),
- {xmlelement, "field", [{"type", Type},
- {"label", translate:translate(Lang, Label)},
- {"var", Var}],
- [{xmlelement, "value", [], [{xmlcdata, Val}]}]}).
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"type">>, Type},
+ {<<"label">>, translate:translate(Lang, Label)},
+ {<<"var">>, Var}],
+ children =
+ [#xmlel{name = <<"value">>, attrs = [],
+ children = [{xmlcdata, Val}]}]}).
-define(BOOLXFIELD(Label, Var, Val),
- ?XFIELD("boolean", Label, Var,
+ ?XFIELD(<<"boolean">>, Label, Var,
case Val of
- true -> "1";
- _ -> "0"
+ true -> <<"1">>;
+ _ -> <<"0">>
end)).
-define(STRINGXFIELD(Label, Var, Val),
- ?XFIELD("text-single", Label, Var, Val)).
+ ?XFIELD(<<"text-single">>, Label, Var, Val)).
-define(STRINGMXFIELD(Label, Var, Vals),
- {xmlelement, "field", [{"type", "text-multi"},
- {"label", translate:translate(Lang, Label)},
- {"var", Var}],
- [{xmlelement, "value", [], [{xmlcdata, V}]} || V <- Vals]}).
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"type">>, <<"text-multi">>},
+ {<<"label">>, translate:translate(Lang, Label)},
+ {<<"var">>, Var}],
+ children =
+ [#xmlel{name = <<"value">>, attrs = [],
+ children = [{xmlcdata, V}]}
+ || V <- Vals]}).
-define(XFIELDOPT(Type, Label, Var, Val, Opts),
- {xmlelement, "field", [{"type", Type},
- {"label", translate:translate(Lang, Label)},
- {"var", Var}],
- lists:map(fun(Opt) ->
- {xmlelement, "option", [],
- [{xmlelement, "value", [],
- [{xmlcdata, Opt}]}]}
- end, Opts) ++
- [{xmlelement, "value", [], [{xmlcdata, Val}]}]}).
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"type">>, Type},
+ {<<"label">>, translate:translate(Lang, Label)},
+ {<<"var">>, Var}],
+ children =
+ lists:map(fun (Opt) ->
+ #xmlel{name = <<"option">>, attrs = [],
+ children =
+ [#xmlel{name = <<"value">>,
+ attrs = [],
+ children =
+ [{xmlcdata, Opt}]}]}
+ end,
+ Opts)
+ ++
+ [#xmlel{name = <<"value">>, attrs = [],
+ children = [{xmlcdata, Val}]}]}).
-define(LISTXFIELD(Label, Var, Val, Opts),
- ?XFIELDOPT("list-single", Label, Var, Val, Opts)).
+ ?XFIELDOPT(<<"list-single">>, Label, Var, Val, Opts)).
-define(LISTMXFIELD(Label, Var, Vals, Opts),
- {xmlelement, "field", [{"type", "list-multi"},
- {"label", translate:translate(Lang, Label)},
- {"var", Var}],
- lists:map(fun(Opt) ->
- {xmlelement, "option", [],
- [{xmlelement, "value", [],
- [{xmlcdata, Opt}]}]}
- end, Opts) ++
- lists:map(fun(Val) ->
- {xmlelement, "value", [],
- [{xmlcdata, Val}]}
- end, Vals)}).
-
%% @spec (Host::host(), ServerHost::host(), Node::pubsubNode(), Owner::jid(), NodeType::nodeType()) ->
%% {error, Reason::stanzaError()} |
%% {result, []}
@@ -1702,54 +2511,104 @@ update_auth(Host, Node, Type, NodeId, Subscriber,
%%<li>nodetree create_node checks if nodeid already exists</li>
%%<li>node plugin create_node just sets default affiliation/subscription</li>
%%</ul>
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"type">>, <<"list-multi">>},
+ {<<"label">>, translate:translate(Lang, Label)},
+ {<<"var">>, Var}],
+ children =
+ lists:map(fun (Opt) ->
+ #xmlel{name = <<"option">>, attrs = [],
+ children =
+ [#xmlel{name = <<"value">>,
+ attrs = [],
+ children =
+ [{xmlcdata, Opt}]}]}
+ end,
+ Opts)
+ ++
+ lists:map(fun (Val) ->
+ #xmlel{name = <<"value">>, attrs = [],
+ children = [{xmlcdata, Val}]}
+ end,
+ Vals)}).
+
+-spec(create_node/5 ::
+(
+ Host :: mod_pubsub:host(),
+ ServerHost :: binary(),
+ Node :: <<>> | mod_pubsub:nodeId(),
+ Owner :: jid(),
+ Type :: binary())
+ -> {result, [xmlel(),...]}
+ %%%
+ | {error, xmlel()}
+).
+
create_node(Host, ServerHost, Node, Owner, Type) ->
create_node(Host, ServerHost, Node, Owner, Type, all, []).
+
+-spec(create_node/7 ::
+(
+ Host :: mod_pubsub:host(),
+ ServerHost :: binary(),
+ Node :: <<>> | mod_pubsub:nodeId(),
+ Owner :: jid(),
+ Type :: binary(),
+ Access :: atom(),
+ Configuration :: [xmlel()])
+ -> {result, [xmlel(),...]}
+ %%%
+ | {error, xmlel()}
+).
create_node(Host, ServerHost, <<>>, Owner, Type, Access, Configuration) ->
- case lists:member("instant-nodes", features(Type)) of
- true ->
- NewNode = string_to_node(randoms:get_string()),
- case create_node(Host, ServerHost,
- NewNode, Owner, Type, Access, Configuration) of
- {result, []} ->
- {result,
- [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}],
- [{xmlelement, "create", nodeAttr(NewNode), []}]}]};
- Error ->
- Error
- end;
- false ->
- %% Service does not support instant nodes
- {error, extended_error(?ERR_NOT_ACCEPTABLE, "nodeid-required")}
+ case lists:member(<<"instant-nodes">>, features(Type)) of
+ true ->
+ NewNode = randoms:get_string(),
+ case create_node(Host, ServerHost, NewNode, Owner, Type,
+ Access, Configuration)
+ of
+ {result, _} ->
+ {result,
+ [#xmlel{name = <<"pubsub">>,
+ attrs = [{<<"xmlns">>, ?NS_PUBSUB}],
+ children =
+ [#xmlel{name = <<"create">>,
+ attrs = nodeAttr(NewNode),
+ children = []}]}]};
+ Error -> Error
+ end;
+ false ->
+ {error,
+ extended_error(?ERR_NOT_ACCEPTABLE,
+ <<"nodeid-required">>)}
end;
create_node(Host, ServerHost, Node, Owner, GivenType, Access, Configuration) ->
Type = select_type(ServerHost, Host, Node, GivenType),
- %% TODO, check/set node_type = Type
ParseOptions = case xml:remove_cdata(Configuration) of
- [] ->
- {result, node_options(Type)};
- [{xmlelement, "x", _Attrs, _SubEls} = XEl] ->
- case jlib:parse_xdata_submit(XEl) of
- invalid ->
- {error, ?ERR_BAD_REQUEST};
- XData ->
- case set_xoption(Host, XData, node_options(Type)) of
- NewOpts when is_list(NewOpts) ->
- {result, NewOpts};
- Err ->
- Err
- end
- end;
- _ ->
- ?INFO_MSG("Node ~p; bad configuration: ~p", [Node, Configuration]),
- {error, ?ERR_BAD_REQUEST}
+ [] -> {result, node_options(Type)};
+ [#xmlel{name = <<"x">>} = XEl] ->
+ case jlib:parse_xdata_submit(XEl) of
+ invalid -> {error, ?ERR_BAD_REQUEST};
+ XData ->
+ case set_xoption(Host, XData, node_options(Type))
+ of
+ NewOpts when is_list(NewOpts) ->
+ {result, NewOpts};
+ Err -> Err
+ end
+ end;
+ _ ->
+ ?INFO_MSG("Node ~p; bad configuration: ~p",
+ [Node, Configuration]),
+ {error, ?ERR_BAD_REQUEST}
end,
case ParseOptions of
{result, NodeOptions} ->
CreateNode =
fun() ->
- SNode = node_to_string(Node),
Parent = case node_call(Type, node_to_path, [Node]) of
- {result, [SNode]} -> <<>>;
+ {result, [Node]} -> <<>>;
{result, Path} -> element(2, node_call(Type, path_to_node, [lists:sublist(Path, length(Path)-1)]))
end,
Parents = case Parent of
@@ -1778,9 +2637,11 @@ create_node(Host, ServerHost, Node, Owner, GivenType, Access, Configuration) ->
{error, ?ERR_FORBIDDEN}
end
end,
- Reply = [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}],
- [{xmlelement, "create", nodeAttr(Node),
- []}]}],
+ Reply = [#xmlel{name = <<"pubsub">>,
+ attrs = [{<<"xmlns">>, ?NS_PUBSUB}],
+ children = [#xmlel{name = <<"create">>,
+ attrs = nodeAttr(Node),
+ children = []}]}],
case transaction(CreateNode, transaction) of
{result, {NodeId, SubsByDepth, {Result, broadcast}}} ->
broadcast_created_node(Host, Node, NodeId, Type, NodeOptions, SubsByDepth),
@@ -1811,6 +2672,15 @@ create_node(Host, ServerHost, Node, Owner, GivenType, Access, Configuration) ->
%% Node = pubsubNode()
%% Owner = jid()
%% Reason = stanzaError()
+-spec(delete_node/3 ::
+(
+ Host :: mod_pubsub:host(),
+ Node :: mod_pubsub:nodeId(),
+ Owner :: jid())
+ -> {result, [xmlel(),...]}
+ %%%
+ | {error, xmlel()}
+).
%% @doc <p>Delete specified node and all childs.</p>
%%<p>There are several reasons why the node deletion request might fail:</p>
%%<ul>
@@ -1819,7 +2689,6 @@ create_node(Host, ServerHost, Node, Owner, GivenType, Access, Configuration) ->
%%<li>The specified node does not exist.</li>
%%</ul>
delete_node(_Host, <<>>, _Owner) ->
- %% Node is the root
{error, ?ERR_NOT_ALLOWED};
delete_node(Host, Node, Owner) ->
Action = fun(#pubsub_node{type = Type, id = NodeId}) ->
@@ -1836,9 +2705,9 @@ delete_node(Host, Node, Owner) ->
%% Entity is not an owner
{error, ?ERR_FORBIDDEN}
end
- end,
+ end,
Reply = [],
- ServerHost = get(server_host), % not clean, but prevent many API changes
+ ServerHost = get(server_host),
case transaction(Host, Node, Action, transaction) of
{result, {_TNode, {SubsByDepth, {Result, broadcast, Removed}}}} ->
lists:foreach(fun({RNode, _RSubscriptions}) ->
@@ -1882,6 +2751,17 @@ delete_node(Host, Node, Owner) ->
%% Node = pubsubNode()
%% From = jid()
%% JID = jid()
+-spec(subscribe_node/5 ::
+(
+ Host :: mod_pubsub:host(),
+ Node :: mod_pubsub:nodeId(),
+ From :: jid(),
+ JID :: binary(),
+ Configuration :: [xmlel()])
+ -> {result, [xmlel(),...]}
+ %%%
+ | {error, xmlel()}
+).
%% @see node_hometree:subscribe_node/5
%% @doc <p>Accepts or rejects subcription requests on a PubSub node.</p>
%%<p>There are several reasons why the subscription request might fail:</p>
@@ -1898,85 +2778,98 @@ delete_node(Host, Node, Owner) ->
%%<li>The node does not exist.</li>
%%</ul>
subscribe_node(Host, Node, From, JID, Configuration) ->
- SubOpts = case pubsub_subscription:parse_options_xform(Configuration) of
- {result, GoodSubOpts} -> GoodSubOpts;
- _ -> invalid
- end,
+ SubOpts = case
+ pubsub_subscription:parse_options_xform(Configuration)
+ of
+ {result, GoodSubOpts} -> GoodSubOpts;
+ _ -> invalid
+ end,
Subscriber = case jlib:string_to_jid(JID) of
- error -> {"", "", ""};
- J -> jlib:jid_tolower(J)
+ error -> {<<"">>, <<"">>, <<"">>};
+ J ->
+ case jlib:jid_tolower(J) of
+ error -> {<<"">>, <<"">>, <<"">>};
+ J1 -> J1
+ end
end,
- Action = fun(#pubsub_node{options = Options, owners = Owners, type = Type, id = NodeId}) ->
- Features = features(Type),
- SubscribeFeature = lists:member("subscribe", Features),
- OptionsFeature = lists:member("subscription-options", Features),
- HasOptions = not (SubOpts == []),
- SubscribeConfig = get_option(Options, subscribe),
- AccessModel = get_option(Options, access_model),
- SendLast = get_option(Options, send_last_published_item),
- AllowedGroups = get_option(Options, roster_groups_allowed, []),
- {PresenceSubscription, RosterGroup} = get_presence_and_roster_permissions(Host, Subscriber, Owners, AccessModel, AllowedGroups),
- if
- not SubscribeFeature ->
- %% Node does not support subscriptions
- {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "subscribe")};
+ Action = fun (#pubsub_node{options = Options,
+ owners = Owners, type = Type, id = NodeId}) ->
+ Features = features(Type),
+ SubscribeFeature = lists:member(<<"subscribe">>, Features),
+ OptionsFeature = lists:member(<<"subscription-options">>, Features),
+ HasOptions = not (SubOpts == []),
+ SubscribeConfig = get_option(Options, subscribe),
+ AccessModel = get_option(Options, access_model),
+ SendLast = get_option(Options, send_last_published_item),
+ AllowedGroups = get_option(Options, roster_groups_allowed, []),
+ {PresenceSubscription, RosterGroup} =
+ get_presence_and_roster_permissions(Host, Subscriber,
+ Owners, AccessModel, AllowedGroups),
+ if not SubscribeFeature ->
+ {error,
+ extended_error(?ERR_FEATURE_NOT_IMPLEMENTED,
+ unsupported, <<"subscribe">>)};
not SubscribeConfig ->
- %% Node does not support subscriptions
- {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "subscribe")};
+ {error,
+ extended_error(?ERR_FEATURE_NOT_IMPLEMENTED,
+ unsupported, <<"subscribe">>)};
HasOptions andalso not OptionsFeature ->
- %% Node does not support subscription options
- {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "subscription-options")};
+ {error,
+ extended_error(?ERR_FEATURE_NOT_IMPLEMENTED,
+ unsupported,
+ <<"subscription-options">>)};
SubOpts == invalid ->
- %% Passed invalit options submit form
- {error, extended_error(?ERR_BAD_REQUEST, "invalid-options")};
+ {error,
+ extended_error(?ERR_BAD_REQUEST,
+ <<"invalid-options">>)};
true ->
node_call(Type, subscribe_node,
- [NodeId, From, Subscriber,
- AccessModel, SendLast,
- PresenceSubscription, RosterGroup,
- SubOpts])
- end
- end,
- Reply = fun(Subscription) ->
- %% TODO, this is subscription-notification, should depends on node features
+ [NodeId, From, Subscriber, AccessModel,
+ SendLast, PresenceSubscription,
+ RosterGroup, SubOpts])
+ end
+ end,
+ Reply = fun (Subscription) ->
SubAttrs = case Subscription of
- {subscribed, SubId} ->
- [{"subscription", subscription_to_string(subscribed)},
- {"subid", SubId},
- {"node",Node}];
- Other ->
- [{"subscription", subscription_to_string(Other)},
- {"node", Node}]
+ {subscribed, SubId} ->
+ [{<<"subscription">>,
+ subscription_to_string(subscribed)},
+ {<<"subid">>, SubId}, {<<"node">>, Node}];
+ Other ->
+ [{<<"subscription">>,
+ subscription_to_string(Other)},
+ {<<"node">>, Node}]
end,
- Fields =
- [{"jid", jlib:jid_to_string(Subscriber)} | SubAttrs],
- [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}],
- [{xmlelement, "subscription", Fields, []}]}]
+ Fields = [{<<"jid">>, jlib:jid_to_string(Subscriber)}
+ | SubAttrs],
+ [#xmlel{name = <<"pubsub">>,
+ attrs = [{<<"xmlns">>, ?NS_PUBSUB}],
+ children =
+ [#xmlel{name = <<"subscription">>,
+ attrs = Fields, children = []}]}]
end,
case transaction(Host, Node, Action, sync_dirty) of
- {result, {TNode, {Result, subscribed, SubId, send_last}}} ->
- NodeId = TNode#pubsub_node.id,
- Type = TNode#pubsub_node.type,
- send_items(Host, Node, NodeId, Type, Subscriber, last),
- case Result of
- default -> {result, Reply({subscribed, SubId})};
- _ -> {result, Result}
- end;
- {result, {_TNode, {default, subscribed, SubId}}} ->
- {result, Reply({subscribed, SubId})};
- {result, {_TNode, {Result, subscribed, _SubId}}} ->
- {result, Result};
- {result, {TNode, {default, pending, _SubId}}} ->
- send_authorization_request(TNode, Subscriber),
- {result, Reply(pending)};
- {result, {TNode, {Result, pending}}} ->
- send_authorization_request(TNode, Subscriber),
- {result, Result};
- {result, {_, Result}} ->
- %% this case should never occure anyway
- {result, Result};
- Error ->
- Error
+ {result,
+ {TNode, {Result, subscribed, SubId, send_last}}} ->
+ NodeId = TNode#pubsub_node.id,
+ Type = TNode#pubsub_node.type,
+ send_items(Host, Node, NodeId, Type, Subscriber, last),
+ case Result of
+ default -> {result, Reply({subscribed, SubId})};
+ _ -> {result, Result}
+ end;
+ {result, {_TNode, {default, subscribed, SubId}}} ->
+ {result, Reply({subscribed, SubId})};
+ {result, {_TNode, {Result, subscribed, _SubId}}} ->
+ {result, Result};
+ {result, {TNode, {default, pending, _SubId}}} ->
+ send_authorization_request(TNode, Subscriber),
+ {result, Reply(pending)};
+ {result, {TNode, {Result, pending}}} ->
+ send_authorization_request(TNode, Subscriber),
+ {result, Result};
+ {result, {_, Result}} -> {result, Result};
+ Error -> Error
end.
%% @spec (Host, Noce, From, JID, SubId) -> {error, Reason} | {result, []}
@@ -1995,23 +2888,37 @@ subscribe_node(Host, Node, From, JID, Configuration) ->
%%<li>The node does not exist.</li>
%%<li>The request specifies a subscription ID that is not valid or current.</li>
%%</ul>
-unsubscribe_node(Host, Node, From, JID, SubId) when is_list(JID) ->
+-spec(unsubscribe_node/5 ::
+(
+ Host :: mod_pubsub:host(),
+ Node :: mod_pubsub:nodeId(),
+ From :: jid(),
+ JID :: binary() | ljid(),
+ SubId :: mod_pubsub:subId())
+ -> {result, []}
+ %%%
+ | {error, xmlel()}
+).
+unsubscribe_node(Host, Node, From, JID, SubId)
+ when is_binary(JID) ->
Subscriber = case jlib:string_to_jid(JID) of
- error -> {"", "", ""};
- J -> jlib:jid_tolower(J)
+ error -> {<<"">>, <<"">>, <<"">>};
+ J ->
+ case jlib:jid_tolower(J) of
+ error -> {<<"">>, <<"">>, <<"">>};
+ J1 -> J1
+ end
end,
unsubscribe_node(Host, Node, From, Subscriber, SubId);
unsubscribe_node(Host, Node, From, Subscriber, SubId) ->
- Action = fun(#pubsub_node{type = Type, id = NodeId}) ->
- node_call(Type, unsubscribe_node, [NodeId, From, Subscriber, SubId])
- end,
+ Action = fun (#pubsub_node{type = Type, id = NodeId}) ->
+ node_call(Type, unsubscribe_node,
+ [NodeId, From, Subscriber, SubId])
+ end,
case transaction(Host, Node, Action, sync_dirty) of
- {result, {_, default}} ->
- {result, []};
- {result, {_, Result}} ->
- {result, Result};
- Error ->
- Error
+ {result, {_, default}} -> {result, []};
+% {result, {_, Result}} -> {result, Result};
+ Error -> Error
end.
%% @spec (Host::host(), ServerHost::host(), JID::jid(), Node::pubsubNode(), ItemId::string(), Payload::term()) ->
@@ -2028,52 +2935,68 @@ unsubscribe_node(Host, Node, From, Subscriber, SubId) ->
%%<li>The item contains more than one payload element or the namespace of the root payload element does not match the configured namespace for the node.</li>
%%<li>The request does not match the node configuration.</li>
%%</ul>
-publish_item(Host, ServerHost, Node, Publisher, "", Payload) ->
- %% if publisher does not specify an ItemId, the service MUST generate the ItemId
+-spec(publish_item/6 ::
+(
+ Host :: mod_pubsub:host(),
+ ServerHost :: binary(),
+ Node :: mod_pubsub:nodeId(),
+ Publisher :: jid(),
+ ItemId :: <<>> | mod_pubsub:itemId(),
+ Payload :: mod_pubsub:payload())
+ -> {result, [xmlel(),...]}
+ %%%
+ | {error, xmlel()}
+).
+publish_item(Host, ServerHost, Node, Publisher, <<>>, Payload) ->
publish_item(Host, ServerHost, Node, Publisher, uniqid(), Payload);
publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload) ->
- Action = fun(#pubsub_node{options = Options, type = Type, id = NodeId}) ->
- Features = features(Type),
- PublishFeature = lists:member("publish", Features),
- PublishModel = get_option(Options, publish_model),
- DeliverPayloads = get_option(Options, deliver_payloads),
- PersistItems = get_option(Options, persist_items),
- MaxItems = case PersistItems of
- false -> 0;
- true -> max_items(Host, Options)
- end,
- PayloadCount = payload_xmlelements(Payload),
- PayloadSize = size(term_to_binary(Payload))-2, % size(term_to_binary([])) == 2
- PayloadMaxSize = get_option(Options, max_payload_size),
- % pubsub#deliver_payloads true
- % pubsub#persist_items true -> 1 item; false -> 0 item
- if
- not PublishFeature ->
- %% Node does not support item publication
- {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "publish")};
+ Action = fun (#pubsub_node{options = Options, type = Type, id = NodeId}) ->
+ Features = features(Type),
+ PublishFeature = lists:member(<<"publish">>, Features),
+ PublishModel = get_option(Options, publish_model),
+ DeliverPayloads = get_option(Options, deliver_payloads),
+ PersistItems = get_option(Options, persist_items),
+ MaxItems = case PersistItems of
+ false -> 0;
+ true -> max_items(Host, Options)
+ end,
+ PayloadCount = payload_xmlelements(Payload),
+ PayloadSize = byte_size(term_to_binary(Payload)) - 2,
+ PayloadMaxSize = get_option(Options, max_payload_size),
+ if not PublishFeature ->
+ {error,
+ extended_error(?ERR_FEATURE_NOT_IMPLEMENTED,
+ unsupported, <<"publish">>)};
PayloadSize > PayloadMaxSize ->
- %% Entity attempts to publish very large payload
- {error, extended_error(?ERR_NOT_ACCEPTABLE, "payload-too-big")};
+ {error,
+ extended_error(?ERR_NOT_ACCEPTABLE, <<"payload-too-big">>)};
(PayloadCount == 0) and (Payload == []) ->
- %% Publisher attempts to publish to payload node with no payload
- {error, extended_error(?ERR_BAD_REQUEST, "payload-required")};
+ {error,
+ extended_error(?ERR_BAD_REQUEST, <<"payload-required">>)};
(PayloadCount > 1) or (PayloadCount == 0) ->
- %% Entity attempts to publish item with multiple payload elements
- {error, extended_error(?ERR_BAD_REQUEST, "invalid-payload")};
- (DeliverPayloads == 0) and (PersistItems == 0) and (PayloadSize > 0) ->
- %% Publisher attempts to publish to transient notification node with item
- {error, extended_error(?ERR_BAD_REQUEST, "item-forbidden")};
- ((DeliverPayloads == 1) or (PersistItems == 1)) and (PayloadSize == 0) ->
- %% Publisher attempts to publish to persistent node with no item
- {error, extended_error(?ERR_BAD_REQUEST, "item-required")};
+ {error,
+ extended_error(?ERR_BAD_REQUEST, <<"invalid-payload">>)};
+ (DeliverPayloads == false) and (PersistItems == false) and
+ (PayloadSize > 0) ->
+ {error,
+ extended_error(?ERR_BAD_REQUEST, <<"item-forbidden">>)};
+ ((DeliverPayloads == true) or (PersistItems == true)) and
+ (PayloadSize == 0) ->
+ {error,
+ extended_error(?ERR_BAD_REQUEST, <<"item-required">>)};
true ->
node_call(Type, publish_item, [NodeId, Publisher, PublishModel, MaxItems, ItemId, Payload])
end
end,
ejabberd_hooks:run(pubsub_publish_item, ServerHost, [ServerHost, Node, Publisher, service_jid(Host), ItemId, Payload]),
- Reply = [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}],
- [{xmlelement, "publish", nodeAttr(Node),
- [{xmlelement, "item", itemAttr(ItemId), []}]}]}],
+ Reply = [#xmlel{name = <<"pubsub">>,
+ attrs = [{<<"xmlns">>, ?NS_PUBSUB}],
+ children =
+ [#xmlel{name = <<"publish">>, attrs = nodeAttr(Node),
+ children =
+ [#xmlel{name = <<"item">>,
+ attrs = itemAttr(ItemId),
+ children = []}]}]}],
case transaction(Host, Node, Action, sync_dirty) of
{result, {TNode, {Result, Broadcast, Removed}}} ->
NodeId = TNode#pubsub_node.id,
@@ -2123,8 +3046,12 @@ publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload) ->
case lists:member("auto-create", features(Type)) of
true ->
case create_node(Host, ServerHost, Node, Publisher, Type) of
- {result, [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}],
- [{xmlelement, "create", [{"node", NewNode}], []}]}]} ->
+ {result, [#xmlel{name = <<"pubsub">>,
+ attrs = [{<<"xmlns">>, ?NS_PUBSUB}],
+ children =
+ [#xmlel{name = <<"create">>,
+ attrs = [{<<"node">>, NewNode}],
+ children = []}]}]} ->
publish_item(Host, ServerHost, list_to_binary(NewNode),
Publisher, ItemId, Payload);
_ ->
@@ -2140,6 +3067,16 @@ publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload) ->
%% @spec (Host::host(), JID::jid(), Node::pubsubNode(), ItemId::string()) ->
%% {error, Reason::stanzaError()} |
%% {result, []}
+-spec(delete_item/4 ::
+(
+ Host :: mod_pubsub:host(),
+ Node :: mod_pubsub:nodeId(),
+ Publisher :: jid(),
+ ItemId :: mod_pubsub:itemId())
+ -> {result, []}
+ %%%
+ | {error, xmlel()}
+).
%% @doc <p>Delete item from a PubSub node.</p>
%% <p>The permission to delete an item must be verified by the plugin implementation.</p>
%%<p>There are several reasons why the item retraction request might fail:</p>
@@ -2153,50 +3090,54 @@ publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload) ->
%%</ul>
delete_item(Host, Node, Publisher, ItemId) ->
delete_item(Host, Node, Publisher, ItemId, false).
-delete_item(_, "", _, _, _) ->
- %% Request does not specify a node
- {error, extended_error(?ERR_BAD_REQUEST, "node-required")};
+
+
+delete_item(_, <<"">>, _, _, _) ->
+ {error,
+ extended_error(?ERR_BAD_REQUEST, <<"node-required">>)};
delete_item(Host, Node, Publisher, ItemId, ForceNotify) ->
- Action = fun(#pubsub_node{options = Options, type = Type, id = NodeId}) ->
- Features = features(Type),
- PersistentFeature = lists:member("persistent-items", Features),
- DeleteFeature = lists:member("delete-items", Features),
- PublishModel = get_option(Options, publish_model),
- if
- %%-> iq_pubsub just does that matchs
+ Action = fun (#pubsub_node{options = Options, type = Type, id = NodeId}) ->
+ Features = features(Type),
+ PersistentFeature = lists:member(<<"persistent-items">>, Features),
+ DeleteFeature = lists:member(<<"delete-items">>, Features),
+ PublishModel = get_option(Options, publish_model),
+ if %%-> iq_pubsub just does that matchs
%% %% Request does not specify an item
%% {error, extended_error(?ERR_BAD_REQUEST, "item-required")};
not PersistentFeature ->
- %% Node does not support persistent items
- {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "persistent-items")};
+ {error,
+ extended_error(?ERR_FEATURE_NOT_IMPLEMENTED,
+ unsupported,
+ <<"persistent-items">>)};
not DeleteFeature ->
- %% Service does not support item deletion
- {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "delete-items")};
+ {error,
+ extended_error(?ERR_FEATURE_NOT_IMPLEMENTED,
+ unsupported, <<"delete-items">>)};
true ->
- node_call(Type, delete_item, [NodeId, Publisher, PublishModel, ItemId])
- end
+ node_call(Type, delete_item,
+ [NodeId, Publisher, PublishModel, ItemId])
+ end
end,
Reply = [],
case transaction(Host, Node, Action, sync_dirty) of
- {result, {TNode, {Result, broadcast}}} ->
- NodeId = TNode#pubsub_node.id,
- Type = TNode#pubsub_node.type,
- Options = TNode#pubsub_node.options,
- broadcast_retract_items(Host, Node, NodeId, Type, Options, [ItemId], ForceNotify),
- case get_cached_item(Host, NodeId) of
- #pubsub_item{itemid = {ItemId, NodeId}, _ = '_'} -> unset_cached_item(Host, NodeId);
+ {result, {TNode, {Result, broadcast}}} ->
+ NodeId = TNode#pubsub_node.id,
+ Type = TNode#pubsub_node.type,
+ Options = TNode#pubsub_node.options,
+ broadcast_retract_items(Host, Node, NodeId, Type,
+ Options, [ItemId], ForceNotify),
+ case get_cached_item(Host, NodeId) of
+ #pubsub_item{itemid = {ItemId, NodeId}} ->
+ unset_cached_item(Host, NodeId);
_ -> ok
- end,
- case Result of
- default -> {result, Reply};
- _ -> {result, Result}
- end;
- {result, {_, default}} ->
- {result, Reply};
- {result, {_, Result}} ->
- {result, Result};
- Error ->
- Error
+ end,
+ case Result of
+ default -> {result, Reply};
+ _ -> {result, Result}
+ end;
+ {result, {_, default}} -> {result, Reply};
+ {result, {_, Result}} -> {result, Result};
+ Error -> Error
end.
%% @spec (Host, JID, Node) ->
@@ -2213,44 +3154,53 @@ delete_item(Host, Node, Publisher, ItemId, ForceNotify) ->
%%<li>The node is not configured to persist items.</li>
%%<li>The specified node does not exist.</li>
%%</ul>
+-spec(purge_node/3 ::
+(
+ Host :: mod_pubsub:host(),
+ Node :: mod_pubsub:nodeId(),
+ Owner :: jid())
+ -> {result, []}
+ %%%
+ | {error, xmlel()}
+).
purge_node(Host, Node, Owner) ->
- Action = fun(#pubsub_node{options = Options, type = Type, id = NodeId}) ->
+ Action = fun (#pubsub_node{options = Options, type = Type, id = NodeId}) ->
Features = features(Type),
- PurgeFeature = lists:member("purge-nodes", Features),
- PersistentFeature = lists:member("persistent-items", Features),
+ PurgeFeature = lists:member(<<"purge-nodes">>, Features),
+ PersistentFeature = lists:member(<<"persistent-items">>, Features),
PersistentConfig = get_option(Options, persist_items),
- if
- not PurgeFeature ->
- %% Service does not support node purging
- {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "purge-nodes")};
- not PersistentFeature ->
- %% Node does not support persistent items
- {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "persistent-items")};
- not PersistentConfig ->
- %% Node is not configured for persistent items
- {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "persistent-items")};
- true ->
- node_call(Type, purge_node, [NodeId, Owner])
+ if not PurgeFeature ->
+ {error,
+ extended_error(?ERR_FEATURE_NOT_IMPLEMENTED,
+ unsupported, <<"purge-nodes">>)};
+ not PersistentFeature ->
+ {error,
+ extended_error(?ERR_FEATURE_NOT_IMPLEMENTED,
+ unsupported,
+ <<"persistent-items">>)};
+ not PersistentConfig ->
+ {error,
+ extended_error(?ERR_FEATURE_NOT_IMPLEMENTED,
+ unsupported,
+ <<"persistent-items">>)};
+ true -> node_call(Type, purge_node, [NodeId, Owner])
end
end,
Reply = [],
case transaction(Host, Node, Action, sync_dirty) of
- {result, {TNode, {Result, broadcast}}} ->
- NodeId = TNode#pubsub_node.id,
- Type = TNode#pubsub_node.type,
- Options = TNode#pubsub_node.options,
- broadcast_purge_node(Host, Node, NodeId, Type, Options),
- unset_cached_item(Host, NodeId),
- case Result of
- default -> {result, Reply};
- _ -> {result, Result}
- end;
- {result, {_, default}} ->
- {result, Reply};
- {result, {_, Result}} ->
- {result, Result};
- Error ->
- Error
+ {result, {TNode, {Result, broadcast}}} ->
+ NodeId = TNode#pubsub_node.id,
+ Type = TNode#pubsub_node.type,
+ Options = TNode#pubsub_node.options,
+ broadcast_purge_node(Host, Node, NodeId, Type, Options),
+ unset_cached_item(Host, NodeId),
+ case Result of
+ default -> {result, Reply};
+ _ -> {result, Result}
+ end;
+ {result, {_, default}} -> {result, Reply};
+ {result, {_, Result}} -> {result, Result};
+ Error -> Error
end.
%% @doc <p>Return the items of a given node.</p>
@@ -2258,82 +3208,108 @@ purge_node(Host, Node, Owner) ->
%% <p>The permission are not checked in this function.</p>
%% @todo We probably need to check that the user doing the query has the right
%% to read the items.
+-spec(get_items/6 ::
+(
+ Host :: mod_pubsub:host(),
+ Node :: mod_pubsub:nodeId(),
+ From :: jid(),
+ SubId :: mod_pubsub:subId(),
+ SMaxItems :: binary(),
+ ItemIDs :: [mod_pubsub:itemId()])
+ -> {result, [xmlel(),...]}
+ %%%
+ | {error, xmlel()}
+).
get_items(Host, Node, From, SubId, SMaxItems, ItemIDs) ->
- MaxItems =
- if
- SMaxItems == "" -> get_max_items_node(Host);
- true ->
- case catch list_to_integer(SMaxItems) of
- {'EXIT', _} -> {error, ?ERR_BAD_REQUEST};
- Val -> Val
- end
- end,
+ MaxItems = if SMaxItems == <<"">> ->
+ get_max_items_node(Host);
+ true ->
+ case catch jlib:binary_to_integer(SMaxItems) of
+ {'EXIT', _} -> {error, ?ERR_BAD_REQUEST};
+ Val -> Val
+ end
+ end,
case MaxItems of
- {error, Error} ->
- {error, Error};
- _ ->
- Action = fun(#pubsub_node{options = Options, type = Type, id = NodeId, owners = Owners}) ->
- Features = features(Type),
- RetreiveFeature = lists:member("retrieve-items", Features),
- PersistentFeature = lists:member("persistent-items", Features),
- AccessModel = get_option(Options, access_model),
- AllowedGroups = get_option(Options, roster_groups_allowed, []),
- {PresenceSubscription, RosterGroup} = get_presence_and_roster_permissions(Host, From, Owners, AccessModel, AllowedGroups),
- if
- not RetreiveFeature ->
- %% Item Retrieval Not Supported
- {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "retrieve-items")};
- not PersistentFeature ->
- %% Persistent Items Not Supported
- {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "persistent-items")};
- true ->
- node_call(Type, get_items,
- [NodeId, From,
- AccessModel, PresenceSubscription, RosterGroup,
- SubId])
- end
- end,
- case transaction(Host, Node, Action, sync_dirty) of
- {result, {_, Items}} ->
- SendItems = case ItemIDs of
- [] ->
- Items;
- _ ->
- lists:filter(fun(#pubsub_item{itemid = {ItemId, _}}) ->
- lists:member(ItemId, ItemIDs)
- end, Items)
- end,
- %% Generate the XML response (Item list), limiting the
- %% number of items sent to MaxItems:
- {result, [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}],
- [{xmlelement, "items", nodeAttr(Node),
- itemsEls(lists:sublist(SendItems, MaxItems))}]}]};
- Error ->
- Error
- end
+ {error, Error} -> {error, Error};
+ _ ->
+ Action = fun (#pubsub_node{options = Options, type = Type, id = NodeId,
+ owners = Owners}) ->
+ Features = features(Type),
+ RetreiveFeature = lists:member(<<"retrieve-items">>, Features),
+ PersistentFeature = lists:member(<<"persistent-items">>, Features),
+ AccessModel = get_option(Options, access_model),
+ AllowedGroups = get_option(Options, roster_groups_allowed, []),
+ {PresenceSubscription, RosterGroup} =
+ get_presence_and_roster_permissions(Host, From, Owners,
+ AccessModel, AllowedGroups),
+ if not RetreiveFeature ->
+ {error,
+ extended_error(?ERR_FEATURE_NOT_IMPLEMENTED,
+ unsupported,
+ <<"retrieve-items">>)};
+ not PersistentFeature ->
+ {error,
+ extended_error(?ERR_FEATURE_NOT_IMPLEMENTED,
+ unsupported,
+ <<"persistent-items">>)};
+ true ->
+ node_call(Type, get_items,
+ [NodeId, From, AccessModel,
+ PresenceSubscription, RosterGroup,
+ SubId])
+ end
+ end,
+ case transaction(Host, Node, Action, sync_dirty) of
+ {result, {_, Items}} ->
+ SendItems = case ItemIDs of
+ [] -> Items;
+ _ ->
+ lists:filter(fun (#pubsub_item{itemid =
+ {ItemId,
+ _}}) ->
+ lists:member(ItemId,
+ ItemIDs)
+ end,
+ Items)
+ end,
+ {result,
+ [#xmlel{name = <<"pubsub">>,
+ attrs = [{<<"xmlns">>, ?NS_PUBSUB}],
+ children =
+ [#xmlel{name = <<"items">>, attrs = nodeAttr(Node),
+ children =
+ itemsEls(lists:sublist(SendItems,
+ MaxItems))}]}]};
+ Error -> Error
+ end
end.
+
get_items(Host, Node) ->
- Action = fun(#pubsub_node{type = Type, id = NodeId}) ->
- node_call(Type, get_items, [NodeId, service_jid(Host)])
- end,
+ Action = fun (#pubsub_node{type = Type, id = NodeId}) ->
+ node_call(Type, get_items, [NodeId, service_jid(Host)])
+ end,
case transaction(Host, Node, Action, sync_dirty) of
- {result, {_, Items}} -> Items;
- Error -> Error
+ {result, {_, Items}} -> Items;
+ Error -> Error
end.
+
get_item(Host, Node, ItemId) ->
- Action = fun(#pubsub_node{type = Type, id = NodeId}) ->
- node_call(Type, get_item, [NodeId, ItemId])
- end,
+ Action = fun (#pubsub_node{type = Type, id = NodeId}) ->
+ node_call(Type, get_item, [NodeId, ItemId])
+ end,
case transaction(Host, Node, Action, sync_dirty) of
- {result, {_, Items}} -> Items;
- Error -> Error
+ {result, {_, Items}} -> Items;
+ Error -> Error
end.
+
get_allowed_items_call(Host, NodeIdx, From, Type, Options, Owners) ->
AccessModel = get_option(Options, access_model),
AllowedGroups = get_option(Options, roster_groups_allowed, []),
- {PresenceSubscription, RosterGroup} = get_presence_and_roster_permissions(Host, From, Owners, AccessModel, AllowedGroups),
- node_call(Type, get_items, [NodeIdx, From, AccessModel, PresenceSubscription, RosterGroup, undefined]).
-
+ {PresenceSubscription, RosterGroup} =
+ get_presence_and_roster_permissions(Host, From, Owners, AccessModel,
+ AllowedGroups),
+ node_call(Type, get_items,
+ [NodeIdx, From, AccessModel, PresenceSubscription, RosterGroup, undefined]).
%% @spec (Host, Node, NodeId, Type, LJID, Number) -> any()
%% Host = pubsubHost()
@@ -2344,68 +3320,63 @@ get_allowed_items_call(Host, NodeIdx, From, Type, Options, Owners) ->
%% Number = last | integer()
%% @doc <p>Resend the items of a node to the user.</p>
%% @todo use cache-last-item feature
-send_items(Host, Node, NodeId, Type, {U,S,R} = LJID, last) ->
+send_items(Host, Node, NodeId, Type, {U, S, R} = LJID, last) ->
case get_cached_item(Host, NodeId) of
- undefined ->
- send_items(Host, Node, NodeId, Type, LJID, 1);
- LastItem ->
- {ModifNow, ModifUSR} = LastItem#pubsub_item.modification,
- Stanza = event_stanza_with_delay(
- [{xmlelement, "items", nodeAttr(Node),
- itemsEls([LastItem])}], ModifNow, ModifUSR),
- case is_tuple(Host) of
- false ->
- ejabberd_router:route(service_jid(Host), jlib:make_jid(LJID), Stanza);
- true ->
- case ejabberd_sm:get_session_pid(U,S,R) of
- C2SPid when is_pid(C2SPid) ->
- ejabberd_c2s:broadcast(C2SPid,
- {pep_message, binary_to_list(Node)++"+notify"},
- _Sender = service_jid(Host),
- Stanza);
- _ ->
- ok
- end
- end
+ undefined ->
+ send_items(Host, Node, NodeId, Type, LJID, 1);
+ LastItem ->
+ {ModifNow, ModifUSR} =
+ LastItem#pubsub_item.modification,
+ Stanza = event_stanza_with_delay([#xmlel{name =
+ <<"items">>,
+ attrs = nodeAttr(Node),
+ children =
+ itemsEls([LastItem])}],
+ ModifNow, ModifUSR),
+ case is_tuple(Host) of
+ false ->
+ ejabberd_router:route(service_jid(Host),
+ jlib:make_jid(LJID), Stanza);
+ true ->
+ case ejabberd_sm:get_session_pid(U, S, R) of
+ C2SPid when is_pid(C2SPid) ->
+ ejabberd_c2s:broadcast(C2SPid,
+ {pep_message,
+ <<((Node))/binary, "+notify">>},
+ _Sender = service_jid(Host),
+ Stanza);
+ _ -> ok
+ end
+ end
end;
-send_items(Host, Node, NodeId, Type, {U,S,R} = LJID, Number) ->
- ToSend = case node_action(Host, Type, get_items, [NodeId, LJID]) of
- {result, []} ->
- [];
- {result, Items} ->
- case Number of
- N when N > 0 -> lists:sublist(Items, N);
- _ -> Items
- end;
- _ ->
- []
- end,
+send_items(Host, Node, NodeId, Type, {U, S, R} = LJID,
+ Number) ->
+ ToSend = case node_action(Host, Type, get_items,
+ [NodeId, LJID])
+ of
+ {result, []} -> [];
+ {result, Items} ->
+ case Number of
+ N when N > 0 -> lists:sublist(Items, N);
+ _ -> Items
+ end;
+ _ -> []
+ end,
Stanza = case ToSend of
- [LastItem] ->
- {ModifNow, ModifUSR} = LastItem#pubsub_item.modification,
- event_stanza_with_delay(
- [{xmlelement, "items", nodeAttr(Node),
- itemsEls(ToSend)}], ModifNow, ModifUSR);
- _ ->
- event_stanza(
- [{xmlelement, "items", nodeAttr(Node),
- itemsEls(ToSend)}])
- end,
+ [LastItem] ->
+ {ModifNow, ModifUSR} =
+ LastItem#pubsub_item.modification,
+ event_stanza_with_delay([#xmlel{name = <<"items">>,
+ attrs = nodeAttr(Node),
+ children =
+ itemsEls(ToSend)}],
+ ModifNow, ModifUSR);
+ _ ->
+ event_stanza([#xmlel{name = <<"items">>,
+ attrs = nodeAttr(Node),
+ children = itemsEls(ToSend)}])
+ end,
case is_tuple(Host) of
- false ->
- ejabberd_router:route(service_jid(Host), jlib:make_jid(LJID), Stanza);
- true ->
- case ejabberd_sm:get_session_pid(U,S,R) of
- C2SPid when is_pid(C2SPid) ->
- ejabberd_c2s:broadcast(C2SPid,
- {pep_message, binary_to_list(Node)++"+notify"},
- _Sender = service_jid(Host),
- Stanza);
- _ ->
- ok
- end
- end.
-
%% @spec (Host, JID, Plugins) -> {error, Reason} | {result, Response}
%% Host = host()
%% JID = jid()
@@ -2413,275 +3384,388 @@ send_items(Host, Node, NodeId, Type, {U,S,R} = LJID, Number) ->
%% Reason = stanzaError()
%% Response = [pubsubIQResponse()]
%% @doc <p>Return the list of affiliations as an XMPP response.</p>
-get_affiliations(Host, <<>>, JID, Plugins) when is_list(Plugins) ->
- Result = lists:foldl(
- fun(Type, {Status, Acc}) ->
- Features = features(Type),
- RetrieveFeature = lists:member("retrieve-affiliations", Features),
- if
- not RetrieveFeature ->
- %% Service does not support retreive affiliatons
- {{error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "retrieve-affiliations")}, Acc};
- true ->
- {result, Affiliations} = node_action(Host, Type, get_entity_affiliations, [Host, JID]),
- {Status, [Affiliations|Acc]}
- end
- end, {ok, []}, Plugins),
+ false ->
+ ejabberd_router:route(service_jid(Host),
+ jlib:make_jid(LJID), Stanza);
+ true ->
+ case ejabberd_sm:get_session_pid(U, S, R) of
+ C2SPid when is_pid(C2SPid) ->
+ ejabberd_c2s:broadcast(C2SPid,
+ {pep_message,
+ <<((Node))/binary, "+notify">>},
+ _Sender = service_jid(Host), Stanza);
+ _ -> ok
+ end
+ end.
+
+-spec(get_affiliations/4 ::
+(
+ Host :: mod_pubsub:host(),
+ Node :: mod_pubsub:nodeId(),
+ JID :: jid(),
+ Plugins :: [binary()])
+ -> {result, [xmlel(),...]}
+ %%%
+ | {error, xmlel()}
+).
+get_affiliations(Host, <<>>, JID, Plugins)
+ when is_list(Plugins) ->
+ Result = lists:foldl(fun (Type, {Status, Acc}) ->
+ Features = features(Type),
+ RetrieveFeature =
+ lists:member(<<"retrieve-affiliations">>, Features),
+ if not RetrieveFeature ->
+ {{error,
+ extended_error(?ERR_FEATURE_NOT_IMPLEMENTED,
+ unsupported,
+ <<"retrieve-affiliations">>)},
+ Acc};
+ true ->
+ {result, Affiliations} =
+ node_action(Host, Type,
+ get_entity_affiliations,
+ [Host, JID]),
+ {Status, [Affiliations | Acc]}
+ end
+ end,
+ {ok, []}, Plugins),
case Result of
- {ok, Affiliations} ->
- Entities = lists:flatmap(
- fun({_, none}) -> [];
- ({#pubsub_node{nodeid = {_, Node}}, Affiliation}) ->
- [{xmlelement, "affiliation",
- [{"affiliation", affiliation_to_string(Affiliation)}|nodeAttr(Node)],
- []}]
- end, lists:usort(lists:flatten(Affiliations))),
- {result, [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}],
- [{xmlelement, "affiliations", [],
- Entities}]}]};
- {Error, _} ->
- Error
+ {ok, Affiliations} ->
+ Entities = lists:flatmap(fun ({_, none}) -> [];
+ ({#pubsub_node{nodeid = {_, Node}},
+ Affiliation}) ->
+ [#xmlel{name = <<"affiliation">>,
+ attrs =
+ [{<<"affiliation">>,
+ affiliation_to_string(Affiliation)}
+ | nodeAttr(Node)],
+ children = []}]
+ end,
+ lists:usort(lists:flatten(Affiliations))),
+ {result,
+ [#xmlel{name = <<"pubsub">>,
+ attrs = [{<<"xmlns">>, ?NS_PUBSUB}],
+ children =
+ [#xmlel{name = <<"affiliations">>, attrs = [],
+ children = Entities}]}]};
+ {Error, _} -> Error
end;
-get_affiliations(Host, NodeId, JID, Plugins) when is_list(Plugins) ->
- Result = lists:foldl(
- fun(Type, {Status, Acc}) ->
- Features = features(Type),
- RetrieveFeature = lists:member("retrieve-affiliations", Features),
- if
- not RetrieveFeature ->
- %% Service does not support retreive affiliatons
- {{error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "retrieve-affiliations")}, Acc};
- true ->
- {result, Affiliations} = node_action(Host, Type, get_entity_affiliations, [Host, JID]),
- {Status, [Affiliations|Acc]}
- end
- end, {ok, []}, Plugins),
+get_affiliations(Host, NodeId, JID, Plugins)
+ when is_list(Plugins) ->
+ Result = lists:foldl(fun (Type, {Status, Acc}) ->
+ Features = features(Type),
+ RetrieveFeature =
+ lists:member(<<"retrieve-affiliations">>,
+ Features),
+ if not RetrieveFeature ->
+ {{error,
+ extended_error(?ERR_FEATURE_NOT_IMPLEMENTED,
+ unsupported,
+ <<"retrieve-affiliations">>)},
+ Acc};
+ true ->
+ {result, Affiliations} =
+ node_action(Host, Type,
+ get_entity_affiliations,
+ [Host, JID]),
+ {Status, [Affiliations | Acc]}
+ end
+ end,
+ {ok, []}, Plugins),
case Result of
- {ok, Affiliations} ->
- Entities = lists:flatmap(
- fun({_, none}) -> [];
- ({#pubsub_node{nodeid = {_, Node}}, Affiliation})
- when NodeId == Node ->
- [{xmlelement, "affiliation",
- [{"affiliation", affiliation_to_string(Affiliation)}|nodeAttr(Node)],
- []}];
- (_) ->
- []
- end, lists:usort(lists:flatten(Affiliations))),
- {result, [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}],
- [{xmlelement, "affiliations", [],
- Entities}]}]};
- {Error, _} ->
- Error
+ {ok, Affiliations} ->
+ Entities = lists:flatmap(fun ({_, none}) -> [];
+ ({#pubsub_node{nodeid = {_, Node}},
+ Affiliation})
+ when NodeId == Node ->
+ [#xmlel{name = <<"affiliation">>,
+ attrs =
+ [{<<"affiliation">>,
+ affiliation_to_string(Affiliation)}
+ | nodeAttr(Node)],
+ children = []}];
+ (_) -> []
+ end,
+ lists:usort(lists:flatten(Affiliations))),
+ {result,
+ [#xmlel{name = <<"pubsub">>,
+ attrs = [{<<"xmlns">>, ?NS_PUBSUB}],
+ children =
+ [#xmlel{name = <<"affiliations">>, attrs = [],
+ children = Entities}]}]};
+ {Error, _} -> Error
end.
-
+-spec(get_affiliations/3 ::
+(
+ Host :: mod_pubsub:host(),
+ Node :: mod_pubsub:nodeId(),
+ JID :: jid())
+ -> {result, [xmlel(),...]}
+ %%%
+ | {error, xmlel()}
+).
get_affiliations(Host, Node, JID) ->
- Action = fun(#pubsub_node{type = Type, id = NodeId}) ->
+ Action = fun (#pubsub_node{type = Type, id = NodeId}) ->
Features = features(Type),
- RetrieveFeature = lists:member("modify-affiliations", Features),
- {result, Affiliation} = node_call(Type, get_affiliation, [NodeId, JID]),
- if
- not RetrieveFeature ->
- %% Service does not support modify affiliations
- {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "modify-affiliations")};
- Affiliation /= owner ->
- %% Entity is not an owner
- {error, ?ERR_FORBIDDEN};
- true ->
- node_call(Type, get_node_affiliations, [NodeId])
+ RetrieveFeature =
+ lists:member(<<"modify-affiliations">>, Features),
+ {result, Affiliation} = node_call(Type, get_affiliation,
+ [NodeId, JID]),
+ if not RetrieveFeature ->
+ {error,
+ extended_error(?ERR_FEATURE_NOT_IMPLEMENTED,
+ unsupported,
+ <<"modify-affiliations">>)};
+ Affiliation /= owner -> {error, ?ERR_FORBIDDEN};
+ true -> node_call(Type, get_node_affiliations, [NodeId])
end
end,
case transaction(Host, Node, Action, sync_dirty) of
- {result, {_, []}} ->
- {error, ?ERR_ITEM_NOT_FOUND};
- {result, {_, Affiliations}} ->
- Entities = lists:flatmap(
- fun({_, none}) -> [];
- ({AJID, Affiliation}) ->
- [{xmlelement, "affiliation",
- [{"jid", jlib:jid_to_string(AJID)},
- {"affiliation", affiliation_to_string(Affiliation)}],
- []}]
- end, Affiliations),
- {result, [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB_OWNER}],
- [{xmlelement, "affiliations", nodeAttr(Node),
- Entities}]}]};
- Error ->
- Error
+ {result, {_, []}} -> {error, ?ERR_ITEM_NOT_FOUND};
+ {result, {_, Affiliations}} ->
+ Entities = lists:flatmap(fun ({_, none}) -> [];
+ ({AJID, Affiliation}) ->
+ [#xmlel{name = <<"affiliation">>,
+ attrs =
+ [{<<"jid">>,
+ jlib:jid_to_string(AJID)},
+ {<<"affiliation">>,
+ affiliation_to_string(Affiliation)}],
+ children = []}]
+ end,
+ Affiliations),
+ {result,
+ [#xmlel{name = <<"pubsub">>,
+ attrs = [{<<"xmlns">>, ?NS_PUBSUB_OWNER}],
+ children =
+ [#xmlel{name = <<"affiliations">>,
+ attrs = nodeAttr(Node), children = Entities}]}]};
+ Error -> Error
end.
+-spec(set_affiliations/4 ::
+(
+ Host :: mod_pubsub:host(),
+ Node :: mod_pubsub:nodeId(),
+ From :: jid(),
+ EntitiesEls :: [xmlel()])
+ -> {result, []}
+ %%%
+ | {error, xmlel()}
+).
set_affiliations(Host, Node, From, EntitiesEls) ->
Owner = jlib:jid_tolower(jlib:jid_remove_resource(From)),
- Entities =
- lists:foldl(
- fun(El, Acc) ->
- case Acc of
- error ->
- error;
- _ ->
- case El of
- {xmlelement, "affiliation", Attrs, _} ->
- JID = jlib:string_to_jid(
- xml:get_attr_s("jid", Attrs)),
- Affiliation = string_to_affiliation(
- xml:get_attr_s("affiliation", Attrs)),
- if
- (JID == error) or
- (Affiliation == false) ->
- error;
- true ->
- [{jlib:jid_tolower(JID), Affiliation} | Acc]
- end
- end
- end
- end, [], EntitiesEls),
+ Entities = lists:foldl(fun (El, Acc) ->
+ case Acc of
+ error -> error;
+ _ ->
+ case El of
+ #xmlel{name = <<"affiliation">>,
+ attrs = Attrs} ->
+ JID =
+ jlib:string_to_jid(xml:get_attr_s(<<"jid">>,
+ Attrs)),
+ Affiliation =
+ string_to_affiliation(xml:get_attr_s(<<"affiliation">>,
+ Attrs)),
+ if (JID == error) or
+ (Affiliation == false) ->
+ error;
+ true ->
+ [{jlib:jid_tolower(JID),
+ Affiliation}
+ | Acc]
+ end
+ end
+ end
+ end,
+ [], EntitiesEls),
case Entities of
- error ->
- {error, ?ERR_BAD_REQUEST};
- _ ->
- Action = fun(#pubsub_node{owners = Owners, type = Type, id = NodeId}=N) ->
- case lists:member(Owner, Owners) of
- true ->
- OwnerJID = jlib:make_jid(Owner),
- FilteredEntities = case Owners of
- [Owner] -> [E || E <- Entities, element(1, E) =/= OwnerJID];
- _ -> Entities
- end,
- lists:foreach(
- fun({JID, Affiliation}) ->
- node_call(Type, set_affiliation, [NodeId, JID, Affiliation]),
- case Affiliation of
- owner ->
- NewOwner = jlib:jid_tolower(jlib:jid_remove_resource(JID)),
- NewOwners = [NewOwner|Owners],
- tree_call(Host, set_node, [N#pubsub_node{owners = NewOwners}]);
- none ->
- OldOwner = jlib:jid_tolower(jlib:jid_remove_resource(JID)),
- case lists:member(OldOwner, Owners) of
- true ->
- NewOwners = Owners--[OldOwner],
- tree_call(Host, set_node, [N#pubsub_node{owners = NewOwners}]);
- _ ->
- ok
- end;
- _ ->
- ok
- end
- end, FilteredEntities),
- {result, []};
- _ ->
- {error, ?ERR_FORBIDDEN}
- end
- end,
- case transaction(Host, Node, Action, sync_dirty) of
- {result, {_, Result}} -> {result, Result};
- Other -> Other
- end
+ error -> {error, ?ERR_BAD_REQUEST};
+ _ ->
+ Action = fun (#pubsub_node{owners = Owners, type = Type,
+ id = NodeId} =
+ N) ->
+ case lists:member(Owner, Owners) of
+ true ->
+ OwnerJID = jlib:make_jid(Owner),
+ FilteredEntities = case Owners of
+ [Owner] ->
+ [E
+ || E <- Entities,
+ element(1, E) =/=
+ OwnerJID];
+ _ -> Entities
+ end,
+ lists:foreach(fun ({JID, Affiliation}) ->
+ node_call(Type,
+ set_affiliation,
+ [NodeId, JID,
+ Affiliation]),
+ case Affiliation of
+ owner ->
+ NewOwner =
+ jlib:jid_tolower(jlib:jid_remove_resource(JID)),
+ NewOwners =
+ [NewOwner
+ | Owners],
+ tree_call(Host,
+ set_node,
+ [N#pubsub_node{owners
+ =
+ NewOwners}]);
+ none ->
+ OldOwner =
+ jlib:jid_tolower(jlib:jid_remove_resource(JID)),
+ case
+ lists:member(OldOwner,
+ Owners)
+ of
+ true ->
+ NewOwners =
+ Owners --
+ [OldOwner],
+ tree_call(Host,
+ set_node,
+ [N#pubsub_node{owners
+ =
+ NewOwners}]);
+ _ -> ok
+ end;
+ _ -> ok
+ end
+ end,
+ FilteredEntities),
+ {result, []};
+ _ -> {error, ?ERR_FORBIDDEN}
+ end
+ end,
+ case transaction(Host, Node, Action, sync_dirty) of
+ {result, {_, Result}} -> {result, Result};
+ Other -> Other
+ end
end.
get_options(Host, Node, JID, SubID, Lang) ->
- Action = fun(#pubsub_node{type = Type, id = NodeID}) ->
- case lists:member("subscription-options", features(Type)) of
- true ->
- get_options_helper(JID, Lang, Node, NodeID, SubID, Type);
- false ->
- {error, extended_error(
- ?ERR_FEATURE_NOT_IMPLEMENTED,
- unsupported, "subscription-options")}
+ Action = fun (#pubsub_node{type = Type, id = NodeID}) ->
+ case lists:member(<<"subscription-options">>, features(Type)) of
+ true ->
+ get_options_helper(JID, Lang, Node, NodeID, SubID, Type);
+ false ->
+ {error,
+ extended_error(?ERR_FEATURE_NOT_IMPLEMENTED,
+ unsupported,
+ <<"subscription-options">>)}
end
end,
case transaction(Host, Node, Action, sync_dirty) of
- {result, {_Node, XForm}} -> {result, [XForm]};
- Error -> Error
+ {result, {_Node, XForm}} -> {result, [XForm]};
+ Error -> Error
end.
get_options_helper(JID, Lang, Node, NodeID, SubID, Type) ->
Subscriber = case jlib:string_to_jid(JID) of
- error -> {"", "", ""};
- J -> jlib:jid_tolower(J)
+ error -> {<<"">>, <<"">>, <<"">>};
+ J -> case jlib:jid_tolower(J) of
+ error -> {<<"">>, <<"">>, <<"">>};
+ J1 -> J1
+ end
end,
{result, Subs} = node_call(Type, get_subscriptions,
[NodeID, Subscriber]),
- SubIDs = lists:foldl(fun({subscribed, SID}, Acc) ->
+ SubIDs = lists:foldl(fun ({subscribed, SID}, Acc) ->
[SID | Acc];
- (_, Acc) ->
- Acc
- end, [], Subs),
+ (_, Acc) -> Acc
+ end,
+ [], Subs),
case {SubID, SubIDs} of
- {_, []} ->
- {error, extended_error(?ERR_NOT_ACCEPTABLE, "not-subscribed")};
- {[], [SID]} ->
- read_sub(Subscriber, Node, NodeID, SID, Lang);
- {[], _} ->
- {error, extended_error(?ERR_NOT_ACCEPTABLE, "subid-required")};
- {_, _} ->
- read_sub(Subscriber, Node, NodeID, SubID, Lang)
+ {_, []} ->
+ {error,
+ extended_error(?ERR_NOT_ACCEPTABLE, <<"not-subscribed">>)};
+ {<<>>, [SID]} ->
+ read_sub(Subscriber, Node, NodeID, SID, Lang);
+ {<<>>, _} ->
+ {error,
+ extended_error(?ERR_NOT_ACCEPTABLE, <<"subid-required">>)};
+ {_, _} ->
+ read_sub(Subscriber, Node, NodeID, SubID, Lang)
end.
read_sub(Subscriber, Node, NodeID, SubID, Lang) ->
case pubsub_subscription:get_subscription(Subscriber, NodeID, SubID) of
{error, notfound} ->
- {error, extended_error(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
+ {error, extended_error(?ERR_NOT_ACCEPTABLE, <<"invalid-subid">>)};
{result, #pubsub_subscription{options = Options}} ->
{result, XdataEl} = pubsub_subscription:get_options_xform(Lang, Options),
- OptionsEl = {xmlelement, "options", [{"jid", jlib:jid_to_string(Subscriber)},
- {"subid", SubID}|nodeAttr(Node)],
- [XdataEl]},
- PubsubEl = {xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}], [OptionsEl]},
+ OptionsEl = #xmlel{name = <<"options">>,
+ attrs =
+ [{<<"jid">>, jlib:jid_to_string(Subscriber)},
+ {<<"subid">>, SubID}
+ | nodeAttr(Node)],
+ children = [XdataEl]},
+ PubsubEl = #xmlel{name = <<"pubsub">>,
+ attrs = [{<<"xmlns">>, ?NS_PUBSUB}],
+ children = [OptionsEl]},
{result, PubsubEl}
end.
set_options(Host, Node, JID, SubID, Configuration) ->
- Action = fun(#pubsub_node{type = Type, id = NodeID}) ->
- case lists:member("subscription-options", features(Type)) of
- true ->
- set_options_helper(Configuration, JID, NodeID,
- SubID, Type);
- false ->
- {error, extended_error(
- ?ERR_FEATURE_NOT_IMPLEMENTED,
- unsupported, "subscription-options")}
+ Action = fun (#pubsub_node{type = Type, id = NodeID}) ->
+ case lists:member(<<"subscription-options">>,
+ features(Type))
+ of
+ true ->
+ set_options_helper(Configuration, JID, NodeID, SubID,
+ Type);
+ false ->
+ {error,
+ extended_error(?ERR_FEATURE_NOT_IMPLEMENTED,
+ unsupported,
+ <<"subscription-options">>)}
end
end,
case transaction(Host, Node, Action, sync_dirty) of
- {result, {_Node, Result}} -> {result, Result};
- Error -> Error
+ {result, {_Node, Result}} -> {result, Result};
+ Error -> Error
end.
set_options_helper(Configuration, JID, NodeID, SubID, Type) ->
SubOpts = case pubsub_subscription:parse_options_xform(Configuration) of
- {result, GoodSubOpts} -> GoodSubOpts;
- _ -> invalid
- end,
+ {result, GoodSubOpts} -> GoodSubOpts;
+ _ -> invalid
+ end,
Subscriber = case jlib:string_to_jid(JID) of
- error -> {"", "", ""};
- J -> jlib:jid_tolower(J)
+ error -> {<<"">>, <<"">>, <<"">>};
+ J -> jlib:jid_tolower(J)
end,
{result, Subs} = node_call(Type, get_subscriptions,
[NodeID, Subscriber]),
- SubIDs = lists:foldl(fun({subscribed, SID}, Acc) ->
+ SubIDs = lists:foldl(fun ({subscribed, SID}, Acc) ->
[SID | Acc];
- (_, Acc) ->
- Acc
- end, [], Subs),
+ (_, Acc) -> Acc
+ end,
+ [], Subs),
case {SubID, SubIDs} of
- {_, []} ->
- {error, extended_error(?ERR_NOT_ACCEPTABLE, "not-subscribed")};
- {[], [SID]} ->
- write_sub(Subscriber, NodeID, SID, SubOpts);
- {[], _} ->
- {error, extended_error(?ERR_NOT_ACCEPTABLE, "subid-required")};
- {_, _} ->
- write_sub(Subscriber, NodeID, SubID, SubOpts)
+ {_, []} ->
+ {error,
+ extended_error(?ERR_NOT_ACCEPTABLE,
+ <<"not-subscribed">>)};
+ {<<>>, [SID]} ->
+ write_sub(Subscriber, NodeID, SID, SubOpts);
+ {<<>>, _} ->
+ {error,
+ extended_error(?ERR_NOT_ACCEPTABLE,
+ <<"subid-required">>)};
+ {_, _} -> write_sub(Subscriber, NodeID, SubID, SubOpts)
end.
write_sub(_Subscriber, _NodeID, _SubID, invalid) ->
- {error, extended_error(?ERR_BAD_REQUEST, "invalid-options")};
+ {error, extended_error(?ERR_BAD_REQUEST, <<"invalid-options">>)};
write_sub(Subscriber, NodeID, SubID, Options) ->
case pubsub_subscription:set_subscription(Subscriber, NodeID, SubID, Options) of
{error, notfound} ->
- {error, extended_error(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
+ {error, extended_error(?ERR_NOT_ACCEPTABLE, <<"invalid-subid">>)};
{result, _} ->
{result, []}
end.
@@ -2698,11 +3782,11 @@ get_subscriptions(Host, Node, JID, Plugins) when is_list(Plugins) ->
Result = lists:foldl(
fun(Type, {Status, Acc}) ->
Features = features(Type),
- RetrieveFeature = lists:member("retrieve-subscriptions", Features),
+ RetrieveFeature = lists:member(<<"retrieve-subscriptions">>, Features),
if
not RetrieveFeature ->
%% Service does not support retreive subscriptions
- {{error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "retrieve-subscriptions")}, Acc};
+ {{error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, <<"retrieve-subscriptions">>)}, Acc};
true ->
Subscriber = jlib:jid_remove_resource(JID),
{result, Subscriptions} = node_action(Host, Type, get_entity_subscriptions, [Host, Subscriber]),
@@ -2710,256 +3794,337 @@ get_subscriptions(Host, Node, JID, Plugins) when is_list(Plugins) ->
end
end, {ok, []}, Plugins),
case Result of
- {ok, Subscriptions} ->
- Entities = lists:flatmap(
- fun({_, none}) ->
- [];
- ({#pubsub_node{nodeid = {_, SubsNode}}, Subscription}) ->
- case Node of
- <<>> ->
- [{xmlelement, "subscription",
- [{"subscription", subscription_to_string(Subscription)}|nodeAttr(SubsNode)],
- []}];
- SubsNode ->
- [{xmlelement, "subscription",
- [{"subscription", subscription_to_string(Subscription)}],
- []}];
- _ ->
- []
- end;
- ({_, none, _}) ->
- [];
- ({#pubsub_node{nodeid = {_, SubsNode}}, Subscription, SubID, SubJID}) ->
- case Node of
- <<>> ->
- [{xmlelement, "subscription",
- [{"jid", jlib:jid_to_string(SubJID)},
- {"subid", SubID},
- {"subscription", subscription_to_string(Subscription)}|nodeAttr(SubsNode)],
- []}];
- SubsNode ->
- [{xmlelement, "subscription",
- [{"jid", jlib:jid_to_string(SubJID)},
- {"subid", SubID},
- {"subscription", subscription_to_string(Subscription)}],
- []}];
- _ ->
- []
- end;
- ({#pubsub_node{nodeid = {_, SubsNode}}, Subscription, SubJID}) ->
- case Node of
- <<>> ->
- [{xmlelement, "subscription",
- [{"jid", jlib:jid_to_string(SubJID)},
- {"subscription", subscription_to_string(Subscription)}|nodeAttr(SubsNode)],
- []}];
- SubsNode ->
- [{xmlelement, "subscription",
- [{"jid", jlib:jid_to_string(SubJID)},
- {"subscription", subscription_to_string(Subscription)}],
- []}];
- _ ->
- []
- end
- end, lists:usort(lists:flatten(Subscriptions))),
- {result, [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}],
- [{xmlelement, "subscriptions", [],
- Entities}]}]};
- {Error, _} ->
- Error
+ {ok, Subscriptions} ->
+ Entities = lists:flatmap(fun ({_, none}) -> [];
+ ({#pubsub_node{nodeid = {_, SubsNode}},
+ Subscription}) ->
+ case Node of
+ <<>> ->
+ [#xmlel{name =
+ <<"subscription">>,
+ attrs =
+ [{<<"subscription">>,
+ subscription_to_string(Subscription)}
+ | nodeAttr(SubsNode)],
+ children = []}];
+ SubsNode ->
+ [#xmlel{name =
+ <<"subscription">>,
+ attrs =
+ [{<<"subscription">>,
+ subscription_to_string(Subscription)}],
+ children = []}];
+ _ -> []
+ end;
+ ({_, none, _}) -> [];
+ ({#pubsub_node{nodeid = {_, SubsNode}},
+ Subscription, SubID, SubJID}) ->
+ case Node of
+ <<>> ->
+ [#xmlel{name =
+ <<"subscription">>,
+ attrs =
+ [{<<"jid">>,
+ jlib:jid_to_string(SubJID)},
+ {<<"subid">>,
+ SubID},
+ {<<"subscription">>,
+ subscription_to_string(Subscription)}
+ | nodeAttr(SubsNode)],
+ children = []}];
+ SubsNode ->
+ [#xmlel{name =
+ <<"subscription">>,
+ attrs =
+ [{<<"jid">>,
+ jlib:jid_to_string(SubJID)},
+ {<<"subid">>,
+ SubID},
+ {<<"subscription">>,
+ subscription_to_string(Subscription)}],
+ children = []}];
+ _ -> []
+ end;
+ ({#pubsub_node{nodeid = {_, SubsNode}},
+ Subscription, SubJID}) ->
+ case Node of
+ <<>> ->
+ [#xmlel{name =
+ <<"subscription">>,
+ attrs =
+ [{<<"jid">>,
+ jlib:jid_to_string(SubJID)},
+ {<<"subscription">>,
+ subscription_to_string(Subscription)}
+ | nodeAttr(SubsNode)],
+ children = []}];
+ SubsNode ->
+ [#xmlel{name =
+ <<"subscription">>,
+ attrs =
+ [{<<"jid">>,
+ jlib:jid_to_string(SubJID)},
+ {<<"subscription">>,
+ subscription_to_string(Subscription)}],
+ children = []}];
+ _ -> []
+ end
+ end,
+ lists:usort(lists:flatten(Subscriptions))),
+ {result,
+ [#xmlel{name = <<"pubsub">>,
+ attrs = [{<<"xmlns">>, ?NS_PUBSUB}],
+ children =
+ [#xmlel{name = <<"subscriptions">>, attrs = [],
+ children = Entities}]}]};
+ {Error, _} -> Error
end.
+
get_subscriptions(Host, Node, JID) ->
- Action = fun(#pubsub_node{type = Type, id = NodeId}) ->
+ Action = fun (#pubsub_node{type = Type, id = NodeId}) ->
Features = features(Type),
- RetrieveFeature = lists:member("manage-subscriptions", Features),
- {result, Affiliation} = node_call(Type, get_affiliation, [NodeId, JID]),
- if
- not RetrieveFeature ->
- %% Service does not support manage subscriptions
- {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "manage-subscriptions")};
- Affiliation /= owner ->
- %% Entity is not an owner
- {error, ?ERR_FORBIDDEN};
- true ->
- node_call(Type, get_node_subscriptions, [NodeId])
+ RetrieveFeature =
+ lists:member(<<"manage-subscriptions">>, Features),
+ {result, Affiliation} = node_call(Type, get_affiliation,
+ [NodeId, JID]),
+ if not RetrieveFeature ->
+ {error,
+ extended_error(?ERR_FEATURE_NOT_IMPLEMENTED,
+ unsupported,
+ <<"manage-subscriptions">>)};
+ Affiliation /= owner -> {error, ?ERR_FORBIDDEN};
+ true ->
+ node_call(Type, get_node_subscriptions, [NodeId])
end
end,
case transaction(Host, Node, Action, sync_dirty) of
- {result, {_, Subscriptions}} ->
- Entities = lists:flatmap(
- fun({_, none}) -> [];
- ({_, pending, _}) -> [];
- ({AJID, Subscription}) ->
- [{xmlelement, "subscription",
- [{"jid", jlib:jid_to_string(AJID)},
- {"subscription", subscription_to_string(Subscription)}],
- []}];
- ({AJID, Subscription, SubId}) ->
- [{xmlelement, "subscription",
- [{"jid", jlib:jid_to_string(AJID)},
- {"subscription", subscription_to_string(Subscription)},
- {"subid", SubId}],
- []}]
- end, Subscriptions),
- {result, [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB_OWNER}],
- [{xmlelement, "subscriptions", nodeAttr(Node),
- Entities}]}]};
- Error ->
- Error
+ {result, {_, Subscriptions}} ->
+ Entities = lists:flatmap(fun ({_, none}) -> [];
+ ({_, pending, _}) -> [];
+ ({AJID, Subscription}) ->
+ [#xmlel{name = <<"subscription">>,
+ attrs =
+ [{<<"jid">>,
+ jlib:jid_to_string(AJID)},
+ {<<"subscription">>,
+ subscription_to_string(Subscription)}],
+ children = []}];
+ ({AJID, Subscription, SubId}) ->
+ [#xmlel{name = <<"subscription">>,
+ attrs =
+ [{<<"jid">>,
+ jlib:jid_to_string(AJID)},
+ {<<"subscription">>,
+ subscription_to_string(Subscription)},
+ {<<"subid">>, SubId}],
+ children = []}]
+ end,
+ Subscriptions),
+ {result,
+ [#xmlel{name = <<"pubsub">>,
+ attrs = [{<<"xmlns">>, ?NS_PUBSUB_OWNER}],
+ children =
+ [#xmlel{name = <<"subscriptions">>,
+ attrs = nodeAttr(Node), children = Entities}]}]};
+ Error -> Error
end.
set_subscriptions(Host, Node, From, EntitiesEls) ->
- Owner = jlib:jid_tolower(jlib:jid_remove_resource(From)),
- Entities =
- lists:foldl(
- fun(El, Acc) ->
- case Acc of
- error ->
- error;
- _ ->
- case El of
- {xmlelement, "subscription", Attrs, _} ->
- JID = jlib:string_to_jid(
- xml:get_attr_s("jid", Attrs)),
- Subscription = string_to_subscription(
- xml:get_attr_s("subscription", Attrs)),
- SubId = xml:get_attr_s("subid", Attrs),
- if
- (JID == error) or
- (Subscription == false) ->
- error;
- true ->
- [{jlib:jid_tolower(JID), Subscription, SubId} | Acc]
- end
- end
- end
- end, [], EntitiesEls),
+ Owner =
+ jlib:jid_tolower(jlib:jid_remove_resource(From)),
+ Entities = lists:foldl(fun (El, Acc) ->
+ case Acc of
+ error -> error;
+ _ ->
+ case El of
+ #xmlel{name = <<"subscription">>,
+ attrs = Attrs} ->
+ JID =
+ jlib:string_to_jid(xml:get_attr_s(<<"jid">>,
+ Attrs)),
+ Subscription =
+ string_to_subscription(xml:get_attr_s(<<"subscription">>,
+ Attrs)),
+ SubId =
+ xml:get_attr_s(<<"subid">>,
+ Attrs),
+ if (JID == error) or
+ (Subscription == false) ->
+ error;
+ true ->
+ [{jlib:jid_tolower(JID),
+ Subscription, SubId}
+ | Acc]
+ end
+ end
+ end
+ end,
+ [], EntitiesEls),
case Entities of
- error ->
- {error, ?ERR_BAD_REQUEST};
- _ ->
- Notify = fun(JID, Sub, _SubId) ->
- Stanza = {xmlelement, "message", [],
- [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}],
- [{xmlelement, "subscription",
- [{"jid", jlib:jid_to_string(JID)},
- %{"subid", SubId},
- {"subscription", subscription_to_string(Sub)} | nodeAttr(Node)], []}]}]},
- ejabberd_router:route(service_jid(Host), jlib:make_jid(JID), Stanza)
- end,
- Action = fun(#pubsub_node{owners = Owners, type = Type, id = NodeId}) ->
- case lists:member(Owner, Owners) of
- true ->
- Result = lists:foldl(fun({JID, Subscription, SubId}, Acc) ->
-
- case node_call(Type, set_subscriptions, [NodeId, JID, Subscription, SubId]) of
- {error, Err} -> [{error, Err} | Acc];
- _ -> Notify(JID, Subscription, SubId), Acc
- end
- end, [], Entities),
- case Result of
- [] -> {result, []};
- _ -> {error, ?ERR_NOT_ACCEPTABLE}
- end;
- _ ->
- {error, ?ERR_FORBIDDEN}
- end
- end,
- case transaction(Host, Node, Action, sync_dirty) of
- {result, {_, Result}} -> {result, Result};
- Other -> Other
- end
+ error -> {error, ?ERR_BAD_REQUEST};
+ _ ->
+ Notify = fun (JID, Sub, _SubId) ->
+ Stanza = #xmlel{name = <<"message">>, attrs = [],
+ children =
+ [#xmlel{name = <<"pubsub">>,
+ attrs =
+ [{<<"xmlns">>,
+ ?NS_PUBSUB}],
+ children =
+ [#xmlel{name =
+ <<"subscription">>,
+ attrs =
+ [{<<"jid">>,
+ jlib:jid_to_string(JID)},
+ {<<"subscription">>,
+ subscription_to_string(Sub)}
+ | nodeAttr(Node)],
+ children =
+ []}]}]},
+ ejabberd_router:route(service_jid(Host),
+ jlib:make_jid(JID), Stanza)
+ end,
+ Action = fun (#pubsub_node{owners = Owners, type = Type,
+ id = NodeId}) ->
+ case lists:member(Owner, Owners) of
+ true ->
+ Result = lists:foldl(fun ({JID, Subscription,
+ SubId},
+ Acc) ->
+ case
+ node_call(Type,
+ set_subscriptions,
+ [NodeId,
+ JID,
+ Subscription,
+ SubId])
+ of
+ {error, Err} ->
+ [{error,
+ Err}
+ | Acc];
+ _ ->
+ Notify(JID,
+ Subscription,
+ SubId),
+ Acc
+ end
+ end,
+ [], Entities),
+ case Result of
+ [] -> {result, []};
+ _ -> {error, ?ERR_NOT_ACCEPTABLE}
+ end;
+ _ -> {error, ?ERR_FORBIDDEN}
+ end
+ end,
+ case transaction(Host, Node, Action, sync_dirty) of
+ {result, {_, Result}} -> {result, Result};
+ Other -> Other
+ end
end.
+-spec(get_presence_and_roster_permissions/5 ::
+(
+ Host :: mod_pubsub:host(),
+ From :: ljid(),
+ Owners :: [ljid(),...],
+ AccessModel :: mod_pubsub:accessModel(),
+ AllowedGroups :: [binary()])
+ -> {PresenceSubscription::boolean(), RosterGroup::boolean()}
+).
get_presence_and_roster_permissions(Host, From, Owners, AccessModel, AllowedGroups) ->
if (AccessModel == presence) or (AccessModel == roster) ->
- case Host of
- {User, Server, _} ->
- get_roster_info(User, Server, From, AllowedGroups);
- _ ->
- [{OUser, OServer, _}|_] = Owners,
- get_roster_info(OUser, OServer, From, AllowedGroups)
- end;
- true ->
- {true, true}
+ case Host of
+ {User, Server, _} ->
+ get_roster_info(User, Server, From, AllowedGroups);
+ _ ->
+ [{OUser, OServer, _} | _] = Owners,
+ get_roster_info(OUser, OServer, From, AllowedGroups)
+ end;
+ true -> {true, true}
end.
%% @spec (OwnerUser, OwnerServer, {SubscriberUser, SubscriberServer, SubscriberResource}, AllowedGroups)
%% -> {PresenceSubscription, RosterGroup}
-get_roster_info(_, _, {"", "", _}, _) ->
+get_roster_info(_, _, {<<"">>, <<"">>, _}, _) ->
{false, false};
-get_roster_info(OwnerUser, OwnerServer, {SubscriberUser, SubscriberServer, _}, AllowedGroups) ->
+get_roster_info(OwnerUser, OwnerServer,
+ {SubscriberUser, SubscriberServer, _}, AllowedGroups) ->
{Subscription, Groups} =
- ejabberd_hooks:run_fold(
- roster_get_jid_info, OwnerServer,
- {none, []},
- [OwnerUser, OwnerServer, {SubscriberUser, SubscriberServer, ""}]),
- PresenceSubscription = (Subscription == both) orelse (Subscription == from)
- orelse ({OwnerUser, OwnerServer} == {SubscriberUser, SubscriberServer}),
- RosterGroup = lists:any(fun(Group) ->
+ ejabberd_hooks:run_fold(roster_get_jid_info,
+ OwnerServer, {none, []},
+ [OwnerUser, OwnerServer,
+ {SubscriberUser, SubscriberServer, <<"">>}]),
+ PresenceSubscription = Subscription == both orelse
+ Subscription == from orelse
+ {OwnerUser, OwnerServer} ==
+ {SubscriberUser, SubscriberServer},
+ RosterGroup = lists:any(fun (Group) ->
lists:member(Group, AllowedGroups)
- end, Groups),
+ end,
+ Groups),
{PresenceSubscription, RosterGroup};
-get_roster_info(OwnerUser, OwnerServer, JID, AllowedGroups) ->
- get_roster_info(OwnerUser, OwnerServer, jlib:jid_tolower(JID), AllowedGroups).
-
%% @spec (AffiliationStr) -> Affiliation
%% AffiliationStr = string()
%% Affiliation = atom()
%% @doc <p>Convert an affiliation type from string to atom.</p>
-string_to_affiliation("owner") -> owner;
-string_to_affiliation("publisher") -> publisher;
-string_to_affiliation("member") -> member;
-string_to_affiliation("outcast") -> outcast;
-string_to_affiliation("none") -> none;
+get_roster_info(OwnerUser, OwnerServer, JID,
+ AllowedGroups) ->
+ get_roster_info(OwnerUser, OwnerServer,
+ jlib:jid_tolower(JID), AllowedGroups).
+
+string_to_affiliation(<<"owner">>) -> owner;
+string_to_affiliation(<<"publisher">>) -> publisher;
+string_to_affiliation(<<"member">>) -> member;
+string_to_affiliation(<<"outcast">>) -> outcast;
+string_to_affiliation(<<"none">>) -> none;
string_to_affiliation(_) -> false.
%% @spec (SubscriptionStr) -> Subscription
%% SubscriptionStr = string()
%% Subscription = atom()
%% @doc <p>Convert a subscription type from string to atom.</p>
-string_to_subscription("subscribed") -> subscribed;
-string_to_subscription("pending") -> pending;
-string_to_subscription("unconfigured") -> unconfigured;
-string_to_subscription("none") -> none;
+string_to_subscription(<<"subscribed">>) -> subscribed;
+string_to_subscription(<<"pending">>) -> pending;
+string_to_subscription(<<"unconfigured">>) ->
+ unconfigured;
+string_to_subscription(<<"none">>) -> none;
string_to_subscription(_) -> false.
%% @spec (Affiliation) -> AffiliationStr
%% Affiliation = atom()
%% AffiliationStr = string()
%% @doc <p>Convert an affiliation type from atom to string.</p>
-affiliation_to_string(owner) -> "owner";
-affiliation_to_string(publisher) -> "publisher";
-affiliation_to_string(member) -> "member";
-affiliation_to_string(outcast) -> "outcast";
-affiliation_to_string(_) -> "none".
-
%% @spec (Subscription) -> SubscriptionStr
%% Subscription = atom()
%% SubscriptionStr = string()
%% @doc <p>Convert a subscription type from atom to string.</p>
-subscription_to_string(subscribed) -> "subscribed";
-subscription_to_string(pending) -> "pending";
-subscription_to_string(unconfigured) -> "unconfigured";
-subscription_to_string(_) -> "none".
-
%% @spec (Node) -> NodeStr
%% Node = pubsubNode()
%% NodeStr = string()
%% @doc <p>Convert a node type from pubsubNode to string.</p>
-node_to_string(Node) -> binary_to_list(Node).
-string_to_node(SNode) -> list_to_binary(SNode).
-
%% @spec (Host) -> jid()
%% Host = host()
%% @doc <p>Generate pubsub service JID.</p>
+affiliation_to_string(owner) -> <<"owner">>;
+affiliation_to_string(publisher) -> <<"publisher">>;
+affiliation_to_string(member) -> <<"member">>;
+affiliation_to_string(outcast) -> <<"outcast">>;
+affiliation_to_string(_) -> <<"none">>.
+
+subscription_to_string(subscribed) -> <<"subscribed">>;
+subscription_to_string(pending) -> <<"pending">>;
+subscription_to_string(unconfigured) -> <<"unconfigured">>;
+subscription_to_string(_) -> <<"none">>.
+
+-spec(service_jid/1 ::
+(
+ Host :: mod_pubsub:host())
+ -> jid()
+).
service_jid(Host) ->
- case Host of
- {U,S,_} -> {jid, U, S, "", U, S, ""};
- _ -> {jid, "", Host, "", "", Host, ""}
- end.
-
%% @spec (LJID, NotifyType, Depth, NodeOptions, SubOptions) -> boolean()
%% LJID = jid()
%% NotifyType = items | nodes
@@ -2968,14 +4133,21 @@ service_jid(Host) ->
%% SubOptions = [{atom(), term()}]
%% @doc <p>Check if a notification must be delivered or not based on
%% node and subscription options.</p>
-is_to_deliver(LJID, NotifyType, Depth, NodeOptions, SubOptions) ->
+ case Host of
+ {U, S, _} -> {jid, U, S, <<"">>, U, S, <<"">>};
+ _ -> {jid, <<"">>, Host, <<"">>, <<"">>, Host, <<"">>}
+ end.
+
+is_to_deliver(LJID, NotifyType, Depth, NodeOptions,
+ SubOptions) ->
sub_to_deliver(LJID, NotifyType, Depth, SubOptions)
- andalso node_to_deliver(LJID, NodeOptions).
+ andalso node_to_deliver(LJID, NodeOptions).
sub_to_deliver(_LJID, NotifyType, Depth, SubOptions) ->
lists:all(fun (Option) ->
sub_option_can_deliver(NotifyType, Depth, Option)
- end, SubOptions).
+ end,
+ SubOptions).
sub_option_can_deliver(items, _, {subscription_type, nodes}) -> false;
sub_option_can_deliver(nodes, _, {subscription_type, items}) -> false;
@@ -2989,6 +4161,12 @@ node_to_deliver(LJID, NodeOptions) ->
PresenceDelivery = get_option(NodeOptions, presence_based_delivery),
presence_can_deliver(LJID, PresenceDelivery).
+-spec(presence_can_deliver/2 ::
+(
+ Entity :: ljid(),
+ _ :: boolean())
+ -> boolean()
+).
presence_can_deliver(_, false) -> true;
presence_can_deliver({User, Server, Resource}, true) ->
case mnesia:dirty_match_object({session, '_', '_', {User, Server}, '_', '_'}) of
@@ -3005,6 +4183,12 @@ presence_can_deliver({User, Server, Resource}, true) ->
end, false, Ss)
end.
+-spec(state_can_deliver/2 ::
+(
+ Entity::ljid(),
+ SubOptions :: mod_pubsub:subOptions() | [])
+ -> [ljid()]
+).
state_can_deliver({U, S, R}, []) -> [{U, S, R}];
state_can_deliver({U, S, R}, SubOptions) ->
%% Check SubOptions for 'show_values'
@@ -3016,7 +4200,7 @@ state_can_deliver({U, S, R}, SubOptions) ->
%% Get subscriber resources
Resources = case R of
%% If the subscriber JID is a bare one, get all its resources
- [] -> user_resources(U, S);
+ <<>> -> user_resources(U, S);
%% If the subscriber JID is a full one, use its resource
R -> [R]
end,
@@ -3028,8 +4212,14 @@ state_can_deliver({U, S, R}, SubOptions) ->
end, [], Resources)
end.
+-spec(get_resource_state/3 ::
+(
+ Entity :: ljid(),
+ ShowValues :: [binary()],
+ JIDs :: [ljid()])
+ -> [ljid()]
+).
get_resource_state({U, S, R}, ShowValues, JIDs) ->
- %% Get user session PID
case ejabberd_sm:get_session_pid(U, S, R) of
%% If no PID, item can be delivered
none -> lists:append([{U, S, R}], JIDs);
@@ -3038,8 +4228,8 @@ get_resource_state({U, S, R}, ShowValues, JIDs) ->
%% Get user resource state
%% TODO : add a catch clause
Show = case ejabberd_c2s:get_presence(Pid) of
- {_, _, "available", _} -> "online";
- {_, _, State, _} -> State
+ {_, _, <<"available">>, _} -> <<"online">>;
+ {_, _, State, _} -> State
end,
%% Is current resource state listed in 'show-values' suboption ?
case lists:member(Show, ShowValues) of %andalso Show =/= "online" of
@@ -3052,26 +4242,37 @@ get_resource_state({U, S, R}, ShowValues, JIDs) ->
%% @spec (Payload) -> int()
%% Payload = term()
+-spec(payload_xmlelements/1 ::
+(
+ Payload :: mod_pubsub:payload())
+ -> Count :: non_neg_integer()
+).
%% @doc <p>Count occurence of XML elements in payload.</p>
payload_xmlelements(Payload) -> payload_xmlelements(Payload, 0).
payload_xmlelements([], Count) -> Count;
-payload_xmlelements([{xmlelement, _, _, _}|Tail], Count) -> payload_xmlelements(Tail, Count+1);
-payload_xmlelements([_|Tail], Count) -> payload_xmlelements(Tail, Count).
+payload_xmlelements([#xmlel{} | Tail], Count) ->
+ payload_xmlelements(Tail, Count + 1);
+payload_xmlelements([_ | Tail], Count) ->
+ payload_xmlelements(Tail, Count).
%% @spec (Els) -> stanza()
%% Els = [xmlelement()]
%% @doc <p>Build pubsub event stanza</p>
-event_stanza(Els) ->
- event_stanza_withmoreels(Els, []).
+event_stanza(Els) -> event_stanza_withmoreels(Els, []).
event_stanza_with_delay(Els, ModifNow, ModifUSR) ->
DateTime = calendar:now_to_datetime(ModifNow),
- MoreEls = [jlib:timestamp_to_xml(DateTime, utc, ModifUSR, "")],
+ MoreEls = [jlib:timestamp_to_xml(DateTime, utc,
+ ModifUSR, <<"">>)],
event_stanza_withmoreels(Els, MoreEls).
event_stanza_withmoreels(Els, MoreEls) ->
- {xmlelement, "message", [],
- [{xmlelement, "event", [{"xmlns", ?NS_PUBSUB_EVENT}], Els} | MoreEls]}.
+ #xmlel{name = <<"message">>, attrs = [],
+ children =
+ [#xmlel{name = <<"event">>,
+ attrs = [{<<"xmlns">>, ?NS_PUBSUB_EVENT}],
+ children = Els}
+ | MoreEls]}.
%%%%%% broadcast functions
@@ -3083,8 +4284,9 @@ broadcast_publish_item(Host, Node, NodeId, Type, NodeOptions, Removed, ItemId, F
false -> []
end,
Stanza = event_stanza(
- [{xmlelement, "items", nodeAttr(Node),
- [{xmlelement, "item", itemAttr(ItemId), Content}]}]),
+ [#xmlel{name = <<"items">>, attrs = nodeAttr(Node),
+ children = [#xmlel{name = <<"item">>, attrs = itemAttr(ItemId),
+ children = Content}]}]),
broadcast_stanza(Host, From, Node, NodeId, Type,
NodeOptions, SubsByDepth, items, Stanza, true),
case Removed of
@@ -3094,8 +4296,8 @@ broadcast_publish_item(Host, Node, NodeId, Type, NodeOptions, Removed, ItemId, F
case get_option(NodeOptions, notify_retract) of
true ->
RetractStanza = event_stanza(
- [{xmlelement, "items", nodeAttr(Node),
- [{xmlelement, "retract", itemAttr(RId), []} || RId <- Removed]}]),
+ [#xmlel{name = <<"items">>, attrs = nodeAttr(Node),
+ children = [#xmlel{name = <<"retract">>, attrs = itemAttr(RId)} || RId <- Removed]}]),
broadcast_stanza(Host, Node, NodeId, Type,
NodeOptions, SubsByDepth,
items, RetractStanza, true);
@@ -3118,8 +4320,8 @@ broadcast_retract_items(Host, Node, NodeId, Type, NodeOptions, ItemIds, ForceNot
case get_collection_subscriptions(Host, Node) of
SubsByDepth when is_list(SubsByDepth) ->
Stanza = event_stanza(
- [{xmlelement, "items", nodeAttr(Node),
- [{xmlelement, "retract", itemAttr(ItemId), []} || ItemId <- ItemIds]}]),
+ [#xmlel{name = <<"items">>, attrs = nodeAttr(Node),
+ children = [#xmlel{name = <<"retract">>, attrs = itemAttr(ItemId)} || ItemId <- ItemIds]}]),
broadcast_stanza(Host, Node, NodeId, Type,
NodeOptions, SubsByDepth, items, Stanza, true),
{result, true};
@@ -3136,8 +4338,7 @@ broadcast_purge_node(Host, Node, NodeId, Type, NodeOptions) ->
case get_collection_subscriptions(Host, Node) of
SubsByDepth when is_list(SubsByDepth) ->
Stanza = event_stanza(
- [{xmlelement, "purge", nodeAttr(Node),
- []}]),
+ [#xmlel{name = <<"purge">>, attrs = nodeAttr(Node)}]),
broadcast_stanza(Host, Node, NodeId, Type,
NodeOptions, SubsByDepth, nodes, Stanza, false),
{result, true};
@@ -3156,8 +4357,7 @@ broadcast_removed_node(Host, Node, NodeId, Type, NodeOptions, SubsByDepth) ->
{result, false};
_ ->
Stanza = event_stanza(
- [{xmlelement, "delete", nodeAttr(Node),
- []}]),
+ [#xmlel{name = <<"delete">>, attrs = nodeAttr(Node)}]),
broadcast_stanza(Host, Node, NodeId, Type,
NodeOptions, SubsByDepth, nodes, Stanza, false),
{result, true}
@@ -3169,7 +4369,7 @@ broadcast_removed_node(Host, Node, NodeId, Type, NodeOptions, SubsByDepth) ->
broadcast_created_node(_, _, _, _, _, []) ->
{result, false};
broadcast_created_node(Host, Node, NodeId, Type, NodeOptions, SubsByDepth) ->
- Stanza = event_stanza([{xmlelement, "create", nodeAttr(Node), []}]),
+ Stanza = event_stanza([#xmlel{name = <<"create">>, attrs = nodeAttr(Node)}]),
broadcast_stanza(Host, Node, NodeId, Type, NodeOptions, SubsByDepth, nodes, Stanza, true),
{result, true}.
@@ -3180,13 +4380,13 @@ broadcast_config_notification(Host, Node, NodeId, Type, NodeOptions, Lang) ->
SubsByDepth when is_list(SubsByDepth) ->
Content = case get_option(NodeOptions, deliver_payloads) of
true ->
- [{xmlelement, "x", [{"xmlns", ?NS_XDATA}, {"type", "result"}],
- get_configure_xfields(Type, NodeOptions, Lang, [])}];
+ [#xmlel{name = <<"x">>, attrs = [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"result">>}],
+ children = get_configure_xfields(Type, NodeOptions, Lang, [])}];
false ->
[]
end,
Stanza = event_stanza(
- [{xmlelement, "configuration", nodeAttr(Node), Content}]),
+ [#xmlel{name = <<"configuration">>, attrs = nodeAttr(Node), children = Content}]),
broadcast_stanza(Host, Node, NodeId, Type,
NodeOptions, SubsByDepth, nodes, Stanza, false),
{result, true};
@@ -3355,57 +4555,80 @@ user_resources(User, Server) ->
%%<li>The service does not support retrieval of default node configuration.</li>
%%</ul>
get_configure(Host, ServerHost, Node, From, Lang) ->
- Action =
- fun(#pubsub_node{options = Options, type = Type, id = NodeId}) ->
- case node_call(Type, get_affiliation, [NodeId, From]) of
- {result, owner} ->
- Groups = ejabberd_hooks:run_fold(roster_groups, ServerHost, [], [ServerHost]),
- {result,
- [{xmlelement, "pubsub",
- [{"xmlns", ?NS_PUBSUB_OWNER}],
- [{xmlelement, "configure", nodeAttr(Node),
- [{xmlelement, "x",
- [{"xmlns", ?NS_XDATA}, {"type", "form"}],
- get_configure_xfields(Type, Options, Lang, Groups)
- }]}]}]};
- _ ->
- {error, ?ERR_FORBIDDEN}
- end
- end,
+ Action = fun (#pubsub_node{options = Options,
+ type = Type, id = NodeId}) ->
+ case node_call(Type, get_affiliation, [NodeId, From]) of
+ {result, owner} ->
+ Groups = ejabberd_hooks:run_fold(roster_groups,
+ ServerHost, [],
+ [ServerHost]),
+ {result,
+ [#xmlel{name = <<"pubsub">>,
+ attrs = [{<<"xmlns">>, ?NS_PUBSUB_OWNER}],
+ children =
+ [#xmlel{name = <<"configure">>,
+ attrs = nodeAttr(Node),
+ children =
+ [#xmlel{name = <<"x">>,
+ attrs =
+ [{<<"xmlns">>,
+ ?NS_XDATA},
+ {<<"type">>,
+ <<"form">>}],
+ children =
+ get_configure_xfields(Type,
+ Options,
+ Lang,
+ Groups)}]}]}]};
+ _ -> {error, ?ERR_FORBIDDEN}
+ end
+ end,
case transaction(Host, Node, Action, sync_dirty) of
- {result, {_, Result}} -> {result, Result};
- Other -> Other
+ {result, {_, Result}} -> {result, Result};
+ Other -> Other
end.
get_default(Host, Node, _From, Lang) ->
Type = select_type(Host, Host, Node),
Options = node_options(Type),
- {result, [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB_OWNER}],
- [{xmlelement, "default", [],
- [{xmlelement, "x", [{"xmlns", ?NS_XDATA}, {"type", "form"}],
- get_configure_xfields(Type, Options, Lang, [])
- }]}]}]}.
-
%% Get node option
%% The result depend of the node type plugin system.
+ {result,
+ [#xmlel{name = <<"pubsub">>,
+ attrs = [{<<"xmlns">>, ?NS_PUBSUB_OWNER}],
+ children =
+ [#xmlel{name = <<"default">>, attrs = [],
+ children =
+ [#xmlel{name = <<"x">>,
+ attrs =
+ [{<<"xmlns">>, ?NS_XDATA},
+ {<<"type">>, <<"form">>}],
+ children =
+ get_configure_xfields(Type, Options,
+ Lang, [])}]}]}]}.
+
get_option([], _) -> false;
get_option(Options, Var) ->
get_option(Options, Var, false).
+
get_option(Options, Var, Def) ->
case lists:keysearch(Var, 1, Options) of
- {value, {_Val, Ret}} -> Ret;
- _ -> Def
+ {value, {_Val, Ret}} -> Ret;
+ _ -> Def
end.
%% Get default options from the module plugin.
node_options(Type) ->
- Module = list_to_atom(?PLUGIN_PREFIX ++ Type),
+ Module =
+ jlib:binary_to_atom(<<(?PLUGIN_PREFIX)/binary,
+ Type/binary>>),
case catch Module:options() of
- {'EXIT',{undef,_}} ->
- DefaultModule = list_to_atom(?PLUGIN_PREFIX++?STDNODE),
- DefaultModule:options();
- Result ->
- Result
+ {'EXIT', {undef, _}} ->
+ DefaultModule =
+ jlib:binary_to_atom(<<(?PLUGIN_PREFIX)/binary,
+ (?STDNODE)/binary>>),
+ DefaultModule:options();
+ Result -> Result
end.
%% @spec (Host, Options) -> MaxItems
@@ -3421,80 +4644,116 @@ node_options(Type) ->
%% version.
max_items(Host, Options) ->
case get_option(Options, persist_items) of
- true ->
- case get_option(Options, max_items) of
- false -> unlimited;
- Result when (Result < 0) -> 0;
- Result -> Result
- end;
- false ->
- case get_option(Options, send_last_published_item) of
- never ->
- 0;
- _ ->
- case is_last_item_cache_enabled(Host) of
- true -> 0;
- false -> 1
- end
- end
+ true ->
+ case get_option(Options, max_items) of
+ false -> unlimited;
+ Result when Result < 0 -> 0;
+ Result -> Result
+ end;
+ false ->
+ case get_option(Options, send_last_published_item) of
+ never -> 0;
+ _ ->
+ case is_last_item_cache_enabled(Host) of
+ true -> 0;
+ false -> 1
+ end
+ end
end.
-define(BOOL_CONFIG_FIELD(Label, Var),
- ?BOOLXFIELD(Label, "pubsub#" ++ atom_to_list(Var),
- get_option(Options, Var))).
+ ?BOOLXFIELD(Label,
+ <<"pubsub#",
+ (iolist_to_binary(atom_to_list(Var)))/binary>>,
+ (get_option(Options, Var)))).
-define(STRING_CONFIG_FIELD(Label, Var),
- ?STRINGXFIELD(Label, "pubsub#" ++ atom_to_list(Var),
- get_option(Options, Var, ""))).
+ ?STRINGXFIELD(Label,
+ <<"pubsub#",
+ (iolist_to_binary(atom_to_list(Var)))/binary>>,
+ (get_option(Options, Var, <<"">>)))).
-define(INTEGER_CONFIG_FIELD(Label, Var),
- ?STRINGXFIELD(Label, "pubsub#" ++ atom_to_list(Var),
- integer_to_list(get_option(Options, Var)))).
+ ?STRINGXFIELD(Label,
+ <<"pubsub#",
+ (iolist_to_binary(atom_to_list(Var)))/binary>>,
+ (iolist_to_binary(integer_to_list(get_option(Options,
+ Var)))))).
-define(JLIST_CONFIG_FIELD(Label, Var, Opts),
- ?LISTXFIELD(Label, "pubsub#" ++ atom_to_list(Var),
- jlib:jid_to_string(get_option(Options, Var)),
+ ?LISTXFIELD(Label,
+ <<"pubsub#",
+ (iolist_to_binary(atom_to_list(Var)))/binary>>,
+ (jlib:jid_to_string(get_option(Options, Var))),
[jlib:jid_to_string(O) || O <- Opts])).
-define(ALIST_CONFIG_FIELD(Label, Var, Opts),
- ?LISTXFIELD(Label, "pubsub#" ++ atom_to_list(Var),
- atom_to_list(get_option(Options, Var)),
- [atom_to_list(O) || O <- Opts])).
+ ?LISTXFIELD(Label,
+ <<"pubsub#",
+ (iolist_to_binary(atom_to_list(Var)))/binary>>,
+ (iolist_to_binary(atom_to_list(get_option(Options,
+ Var)))),
+ [iolist_to_binary(atom_to_list(O)) || O <- Opts])).
-define(LISTM_CONFIG_FIELD(Label, Var, Opts),
- ?LISTMXFIELD(Label, "pubsub#" ++ atom_to_list(Var),
- get_option(Options, Var), Opts)).
+ ?LISTMXFIELD(Label,
+ <<"pubsub#",
+ (iolist_to_binary(atom_to_list(Var)))/binary>>,
+ (get_option(Options, Var)), Opts)).
-define(NLIST_CONFIG_FIELD(Label, Var),
- ?STRINGMXFIELD(Label, "pubsub#" ++ atom_to_list(Var),
- [node_to_string(N) || N <- get_option(Options, Var, [])])).
+ ?STRINGMXFIELD(Label,
+ <<"pubsub#",
+ (iolist_to_binary(atom_to_list(Var)))/binary>>,
+ get_option(Options, Var, []))).
get_configure_xfields(_Type, Options, Lang, Groups) ->
- [?XFIELD("hidden", "", "FORM_TYPE", ?NS_PUBSUB_NODE_CONFIG),
- ?BOOL_CONFIG_FIELD("Deliver payloads with event notifications", deliver_payloads),
- ?BOOL_CONFIG_FIELD("Deliver event notifications", deliver_notifications),
- ?BOOL_CONFIG_FIELD("Notify subscribers when the node configuration changes", notify_config),
- ?BOOL_CONFIG_FIELD("Notify subscribers when the node is deleted", notify_delete),
- ?BOOL_CONFIG_FIELD("Notify subscribers when items are removed from the node", notify_retract),
- ?BOOL_CONFIG_FIELD("Persist items to storage", persist_items),
- ?STRING_CONFIG_FIELD("A friendly name for the node", title),
- ?INTEGER_CONFIG_FIELD("Max # of items to persist", max_items),
- ?BOOL_CONFIG_FIELD("Whether to allow subscriptions", subscribe),
- ?ALIST_CONFIG_FIELD("Specify the access model", access_model,
+ [?XFIELD(<<"hidden">>, <<"">>, <<"FORM_TYPE">>,
+ (?NS_PUBSUB_NODE_CONFIG)),
+ ?BOOL_CONFIG_FIELD(<<"Deliver payloads with event notifications">>,
+ deliver_payloads),
+ ?BOOL_CONFIG_FIELD(<<"Deliver event notifications">>,
+ deliver_notifications),
+ ?BOOL_CONFIG_FIELD(<<"Notify subscribers when the node configuratio"
+ "n changes">>,
+ notify_config),
+ ?BOOL_CONFIG_FIELD(<<"Notify subscribers when the node is "
+ "deleted">>,
+ notify_delete),
+ ?BOOL_CONFIG_FIELD(<<"Notify subscribers when items are removed "
+ "from the node">>,
+ notify_retract),
+ ?BOOL_CONFIG_FIELD(<<"Persist items to storage">>,
+ persist_items),
+ ?STRING_CONFIG_FIELD(<<"A friendly name for the node">>,
+ title),
+ ?INTEGER_CONFIG_FIELD(<<"Max # of items to persist">>,
+ max_items),
+ ?BOOL_CONFIG_FIELD(<<"Whether to allow subscriptions">>,
+ subscribe),
+ ?ALIST_CONFIG_FIELD(<<"Specify the access model">>,
+ access_model,
[open, authorize, presence, roster, whitelist]),
- %% XXX: change to list-multi, include current roster groups as options
- ?LISTM_CONFIG_FIELD("Roster groups allowed to subscribe", roster_groups_allowed, Groups),
- ?ALIST_CONFIG_FIELD("Specify the publisher model", publish_model,
- [publishers, subscribers, open]),
- ?BOOL_CONFIG_FIELD("Purge all items when the relevant publisher goes offline", purge_offline),
- ?ALIST_CONFIG_FIELD("Specify the event message type", notification_type,
- [headline, normal]),
- ?INTEGER_CONFIG_FIELD("Max payload size in bytes", max_payload_size),
- ?ALIST_CONFIG_FIELD("When to send the last published item", send_last_published_item,
+ ?LISTM_CONFIG_FIELD(<<"Roster groups allowed to subscribe">>,
+ roster_groups_allowed, Groups),
+ ?ALIST_CONFIG_FIELD(<<"Specify the publisher model">>,
+ publish_model, [publishers, subscribers, open]),
+ ?BOOL_CONFIG_FIELD(<<"Purge all items when the relevant publisher "
+ "goes offline">>,
+ purge_offline),
+ ?ALIST_CONFIG_FIELD(<<"Specify the event message type">>,
+ notification_type, [headline, normal]),
+ ?INTEGER_CONFIG_FIELD(<<"Max payload size in bytes">>,
+ max_payload_size),
+ ?ALIST_CONFIG_FIELD(<<"When to send the last published item">>,
+ send_last_published_item,
[never, on_sub, on_sub_and_presence]),
- ?BOOL_CONFIG_FIELD("Only deliver notifications to available users", presence_based_delivery),
- ?NLIST_CONFIG_FIELD("The collections with which a node is affiliated", collection)
- ].
+ ?BOOL_CONFIG_FIELD(<<"Only deliver notifications to available "
+ "users">>,
+ presence_based_delivery),
+ ?NLIST_CONFIG_FIELD(<<"The collections with which a node is "
+ "affiliated">>,
+ collection)].
%%<p>There are several reasons why the node configuration request might fail:</p>
%%<ul>
@@ -3506,52 +4765,60 @@ get_configure_xfields(_Type, Options, Lang, Groups) ->
%%</ul>
set_configure(Host, Node, From, Els, Lang) ->
case xml:remove_cdata(Els) of
- [{xmlelement, "x", _Attrs1, _Els1} = XEl] ->
- case {xml:get_tag_attr_s("xmlns", XEl), xml:get_tag_attr_s("type", XEl)} of
- {?NS_XDATA, "cancel"} ->
- {result, []};
- {?NS_XDATA, "submit"} ->
- Action =
- fun(#pubsub_node{options = Options, type = Type, id = NodeId} = N) ->
- case node_call(Type, get_affiliation, [NodeId, From]) of
- {result, owner} ->
- case jlib:parse_xdata_submit(XEl) of
- invalid ->
- {error, ?ERR_BAD_REQUEST};
- XData ->
- OldOpts = case Options of
- [] -> node_options(Type);
- _ -> Options
- end,
- case set_xoption(Host, XData, OldOpts) of
- NewOpts when is_list(NewOpts) ->
- case tree_call(Host, set_node, [N#pubsub_node{options = NewOpts}]) of
- ok -> {result, ok};
- Err -> Err
- end;
- Err ->
- Err
- end
- end;
- _ ->
- {error, ?ERR_FORBIDDEN}
- end
- end,
- case transaction(Host, Node, Action, transaction) of
- {result, {TNode, ok}} ->
- NodeId = TNode#pubsub_node.id,
- Type = TNode#pubsub_node.type,
- Options = TNode#pubsub_node.options,
- broadcast_config_notification(Host, Node, NodeId, Type, Options, Lang),
- {result, []};
- Other ->
- Other
- end;
- _ ->
- {error, ?ERR_BAD_REQUEST}
- end;
- _ ->
- {error, ?ERR_BAD_REQUEST}
+ [#xmlel{name = <<"x">>} = XEl] ->
+ case {xml:get_tag_attr_s(<<"xmlns">>, XEl),
+ xml:get_tag_attr_s(<<"type">>, XEl)}
+ of
+ {?NS_XDATA, <<"cancel">>} -> {result, []};
+ {?NS_XDATA, <<"submit">>} ->
+ Action = fun (#pubsub_node{options = Options,
+ type = Type, id = NodeId} =
+ N) ->
+ case node_call(Type, get_affiliation,
+ [NodeId, From])
+ of
+ {result, owner} ->
+ case jlib:parse_xdata_submit(XEl) of
+ invalid -> {error, ?ERR_BAD_REQUEST};
+ XData ->
+ OldOpts = case Options of
+ [] ->
+ node_options(Type);
+ _ -> Options
+ end,
+ case set_xoption(Host, XData,
+ OldOpts)
+ of
+ NewOpts
+ when is_list(NewOpts) ->
+ case tree_call(Host,
+ set_node,
+ [N#pubsub_node{options
+ =
+ NewOpts}])
+ of
+ ok -> {result, ok};
+ Err -> Err
+ end;
+ Err -> Err
+ end
+ end;
+ _ -> {error, ?ERR_FORBIDDEN}
+ end
+ end,
+ case transaction(Host, Node, Action, transaction) of
+ {result, {TNode, ok}} ->
+ NodeId = TNode#pubsub_node.id,
+ Type = TNode#pubsub_node.type,
+ Options = TNode#pubsub_node.options,
+ broadcast_config_notification(Host, Node, NodeId, Type,
+ Options, Lang),
+ {result, []};
+ Other -> Other
+ end;
+ _ -> {error, ?ERR_BAD_REQUEST}
+ end;
+ _ -> {error, ?ERR_BAD_REQUEST}
end.
add_opt(Key, Value, Opts) ->
@@ -3560,100 +4827,144 @@ add_opt(Key, Value, Opts) ->
-define(SET_BOOL_XOPT(Opt, Val),
BoolVal = case Val of
- "0" -> false;
- "1" -> true;
- "false" -> false;
- "true" -> true;
- _ -> error
+ <<"0">> -> false;
+ <<"1">> -> true;
+ <<"false">> -> false;
+ <<"true">> -> true;
+ _ -> error
end,
case BoolVal of
- error -> {error, ?ERR_NOT_ACCEPTABLE};
- _ -> set_xoption(Host, Opts, add_opt(Opt, BoolVal, NewOpts))
+ error -> {error, ?ERR_NOT_ACCEPTABLE};
+ _ ->
+ set_xoption(Host, Opts, add_opt(Opt, BoolVal, NewOpts))
end).
-define(SET_STRING_XOPT(Opt, Val),
set_xoption(Host, Opts, add_opt(Opt, Val, NewOpts))).
-define(SET_INTEGER_XOPT(Opt, Val, Min, Max),
- case catch list_to_integer(Val) of
- IVal when is_integer(IVal),
- IVal >= Min,
- IVal =< Max ->
- set_xoption(Host, Opts, add_opt(Opt, IVal, NewOpts));
- _ ->
- {error, ?ERR_NOT_ACCEPTABLE}
+ case catch jlib:binary_to_integer(Val) of
+ IVal when is_integer(IVal), IVal >= Min, IVal =< Max ->
+ set_xoption(Host, Opts, add_opt(Opt, IVal, NewOpts));
+ _ -> {error, ?ERR_NOT_ACCEPTABLE}
end).
-define(SET_ALIST_XOPT(Opt, Val, Vals),
- case lists:member(Val, [atom_to_list(V) || V <- Vals]) of
- true -> set_xoption(Host, Opts, add_opt(Opt, list_to_atom(Val), NewOpts));
- false -> {error, ?ERR_NOT_ACCEPTABLE}
+ case lists:member(Val,
+ [iolist_to_binary(atom_to_list(V)) || V <- Vals])
+ of
+ true ->
+ set_xoption(Host, Opts,
+ add_opt(Opt, jlib:binary_to_atom(Val), NewOpts));
+ false -> {error, ?ERR_NOT_ACCEPTABLE}
end).
-define(SET_LIST_XOPT(Opt, Val),
set_xoption(Host, Opts, add_opt(Opt, Val, NewOpts))).
-set_xoption(_Host, [], NewOpts) ->
- NewOpts;
-set_xoption(Host, [{"FORM_TYPE", _} | Opts], NewOpts) ->
+set_xoption(_Host, [], NewOpts) -> NewOpts;
+set_xoption(Host, [{<<"FORM_TYPE">>, _} | Opts],
+ NewOpts) ->
set_xoption(Host, Opts, NewOpts);
-set_xoption(Host, [{"pubsub#roster_groups_allowed", Value} | Opts], NewOpts) ->
+set_xoption(Host,
+ [{<<"pubsub#roster_groups_allowed">>, Value} | Opts],
+ NewOpts) ->
?SET_LIST_XOPT(roster_groups_allowed, Value);
-set_xoption(Host, [{"pubsub#deliver_payloads", [Val]} | Opts], NewOpts) ->
+set_xoption(Host,
+ [{<<"pubsub#deliver_payloads">>, [Val]} | Opts],
+ NewOpts) ->
?SET_BOOL_XOPT(deliver_payloads, Val);
-set_xoption(Host, [{"pubsub#deliver_notifications", [Val]} | Opts], NewOpts) ->
+set_xoption(Host,
+ [{<<"pubsub#deliver_notifications">>, [Val]} | Opts],
+ NewOpts) ->
?SET_BOOL_XOPT(deliver_notifications, Val);
-set_xoption(Host, [{"pubsub#notify_config", [Val]} | Opts], NewOpts) ->
+set_xoption(Host,
+ [{<<"pubsub#notify_config">>, [Val]} | Opts],
+ NewOpts) ->
?SET_BOOL_XOPT(notify_config, Val);
-set_xoption(Host, [{"pubsub#notify_delete", [Val]} | Opts], NewOpts) ->
+set_xoption(Host,
+ [{<<"pubsub#notify_delete">>, [Val]} | Opts],
+ NewOpts) ->
?SET_BOOL_XOPT(notify_delete, Val);
-set_xoption(Host, [{"pubsub#notify_retract", [Val]} | Opts], NewOpts) ->
+set_xoption(Host,
+ [{<<"pubsub#notify_retract">>, [Val]} | Opts],
+ NewOpts) ->
?SET_BOOL_XOPT(notify_retract, Val);
-set_xoption(Host, [{"pubsub#persist_items", [Val]} | Opts], NewOpts) ->
+set_xoption(Host,
+ [{<<"pubsub#persist_items">>, [Val]} | Opts],
+ NewOpts) ->
?SET_BOOL_XOPT(persist_items, Val);
-set_xoption(Host, [{"pubsub#max_items", [Val]} | Opts], NewOpts) ->
+set_xoption(Host,
+ [{<<"pubsub#max_items">>, [Val]} | Opts], NewOpts) ->
MaxItems = get_max_items_node(Host),
?SET_INTEGER_XOPT(max_items, Val, 0, MaxItems);
-set_xoption(Host, [{"pubsub#subscribe", [Val]} | Opts], NewOpts) ->
+set_xoption(Host,
+ [{<<"pubsub#subscribe">>, [Val]} | Opts], NewOpts) ->
?SET_BOOL_XOPT(subscribe, Val);
-set_xoption(Host, [{"pubsub#access_model", [Val]} | Opts], NewOpts) ->
- ?SET_ALIST_XOPT(access_model, Val, [open, authorize, presence, roster, whitelist]);
-set_xoption(Host, [{"pubsub#publish_model", [Val]} | Opts], NewOpts) ->
- ?SET_ALIST_XOPT(publish_model, Val, [publishers, subscribers, open]);
-set_xoption(Host, [{"pubsub#notification_type", [Val]} | Opts], NewOpts) ->
- ?SET_ALIST_XOPT(notification_type, Val, [headline, normal]);
-set_xoption(Host, [{"pubsub#node_type", [Val]} | Opts], NewOpts) ->
+set_xoption(Host,
+ [{<<"pubsub#access_model">>, [Val]} | Opts], NewOpts) ->
+ ?SET_ALIST_XOPT(access_model, Val,
+ [open, authorize, presence, roster, whitelist]);
+set_xoption(Host,
+ [{<<"pubsub#publish_model">>, [Val]} | Opts],
+ NewOpts) ->
+ ?SET_ALIST_XOPT(publish_model, Val,
+ [publishers, subscribers, open]);
+set_xoption(Host,
+ [{<<"pubsub#notification_type">>, [Val]} | Opts],
+ NewOpts) ->
+ ?SET_ALIST_XOPT(notification_type, Val,
+ [headline, normal]);
+set_xoption(Host,
+ [{<<"pubsub#node_type">>, [Val]} | Opts], NewOpts) ->
?SET_ALIST_XOPT(node_type, Val, [leaf, collection]);
-set_xoption(Host, [{"pubsub#max_payload_size", [Val]} | Opts], NewOpts) ->
- ?SET_INTEGER_XOPT(max_payload_size, Val, 0, ?MAX_PAYLOAD_SIZE);
-set_xoption(Host, [{"pubsub#send_last_published_item", [Val]} | Opts], NewOpts) ->
- ?SET_ALIST_XOPT(send_last_published_item, Val, [never, on_sub, on_sub_and_presence]);
-set_xoption(Host, [{"pubsub#presence_based_delivery", [Val]} | Opts], NewOpts) ->
+set_xoption(Host,
+ [{<<"pubsub#max_payload_size">>, [Val]} | Opts],
+ NewOpts) ->
+ ?SET_INTEGER_XOPT(max_payload_size, Val, 0,
+ (?MAX_PAYLOAD_SIZE));
+set_xoption(Host,
+ [{<<"pubsub#send_last_published_item">>, [Val]} | Opts],
+ NewOpts) ->
+ ?SET_ALIST_XOPT(send_last_published_item, Val,
+ [never, on_sub, on_sub_and_presence]);
+set_xoption(Host,
+ [{<<"pubsub#presence_based_delivery">>, [Val]} | Opts],
+ NewOpts) ->
?SET_BOOL_XOPT(presence_based_delivery, Val);
-set_xoption(Host, [{"pubsub#purge_offline", [Val]} | Opts], NewOpts) ->
+set_xoption(Host,
+ [{<<"pubsub#purge_offline">>, [Val]} | Opts],
+ NewOpts) ->
?SET_BOOL_XOPT(purge_offline, Val);
-set_xoption(Host, [{"pubsub#title", Value} | Opts], NewOpts) ->
+set_xoption(Host, [{<<"pubsub#title">>, Value} | Opts],
+ NewOpts) ->
?SET_STRING_XOPT(title, Value);
-set_xoption(Host, [{"pubsub#type", Value} | Opts], NewOpts) ->
+set_xoption(Host, [{<<"pubsub#type">>, Value} | Opts],
+ NewOpts) ->
?SET_STRING_XOPT(type, Value);
-set_xoption(Host, [{"pubsub#body_xslt", Value} | Opts], NewOpts) ->
+set_xoption(Host,
+ [{<<"pubsub#body_xslt">>, Value} | Opts], NewOpts) ->
?SET_STRING_XOPT(body_xslt, Value);
-set_xoption(Host, [{"pubsub#collection", Value} | Opts], NewOpts) ->
- NewValue = [string_to_node(V) || V <- Value],
- ?SET_LIST_XOPT(collection, NewValue);
-set_xoption(Host, [{"pubsub#node", [Value]} | Opts], NewOpts) ->
- NewValue = string_to_node(Value),
- ?SET_LIST_XOPT(node, NewValue);
+set_xoption(Host,
+ [{<<"pubsub#collection">>, Value} | Opts], NewOpts) ->
+% NewValue = [string_to_node(V) || V <- Value],
+ ?SET_LIST_XOPT(collection, Value);
+set_xoption(Host, [{<<"pubsub#node">>, [Value]} | Opts],
+ NewOpts) ->
+% NewValue = string_to_node(Value),
+ ?SET_LIST_XOPT(node, Value);
set_xoption(Host, [_ | Opts], NewOpts) ->
- % skip unknown field
set_xoption(Host, Opts, NewOpts).
get_max_items_node({_, ServerHost, _}) ->
get_max_items_node(ServerHost);
get_max_items_node(Host) ->
- case catch ets:lookup(gen_mod:get_module_proc(Host, config), max_items_node) of
- [{max_items_node, Integer}] -> Integer;
- _ -> ?MAXITEMS
+ case catch ets:lookup(gen_mod:get_module_proc(Host,
+ config),
+ max_items_node)
+ of
+ [{max_items_node, Integer}] -> Integer;
+ _ -> ?MAXITEMS
end.
%%%% last item cache handling
@@ -3661,207 +4972,243 @@ get_max_items_node(Host) ->
is_last_item_cache_enabled({_, ServerHost, _}) ->
is_last_item_cache_enabled(ServerHost);
is_last_item_cache_enabled(Host) ->
- case catch ets:lookup(gen_mod:get_module_proc(Host, config), last_item_cache) of
- [{last_item_cache, true}] -> true;
- _ -> false
+ case catch ets:lookup(gen_mod:get_module_proc(Host,
+ config),
+ last_item_cache)
+ of
+ [{last_item_cache, true}] -> true;
+ _ -> false
end.
-set_cached_item({_, ServerHost, _}, NodeId, ItemId, Publisher, Payload) ->
- set_cached_item(ServerHost, NodeId, ItemId, Publisher, Payload);
-set_cached_item(Host, NodeId, ItemId, Publisher, Payload) ->
+set_cached_item({_, ServerHost, _}, NodeId, ItemId,
+ Publisher, Payload) ->
+ set_cached_item(ServerHost, NodeId, ItemId, Publisher,
+ Payload);
+set_cached_item(Host, NodeId, ItemId, Publisher,
+ Payload) ->
case is_last_item_cache_enabled(Host) of
- true -> mnesia:dirty_write({pubsub_last_item, NodeId, ItemId, {now(), jlib:jid_tolower(jlib:jid_remove_resource(Publisher))}, Payload});
- _ -> ok
+ true ->
+ mnesia:dirty_write({pubsub_last_item, NodeId, ItemId,
+ {now(),
+ jlib:jid_tolower(jlib:jid_remove_resource(Publisher))},
+ Payload});
+ _ -> ok
end.
+
unset_cached_item({_, ServerHost, _}, NodeId) ->
unset_cached_item(ServerHost, NodeId);
unset_cached_item(Host, NodeId) ->
case is_last_item_cache_enabled(Host) of
- true -> mnesia:dirty_delete({pubsub_last_item, NodeId});
- _ -> ok
+ true -> mnesia:dirty_delete({pubsub_last_item, NodeId});
+ _ -> ok
end.
+
+-spec(get_cached_item/2 ::
+(
+ Host :: mod_pubsub:host(),
+ NodeIdx :: mod_pubsub:nodeIdx())
+ -> undefined | mod_pubsub:pubsubItem()
+).
get_cached_item({_, ServerHost, _}, NodeId) ->
get_cached_item(ServerHost, NodeId);
-get_cached_item(Host, NodeId) ->
+get_cached_item(Host, NodeIdx) ->
case is_last_item_cache_enabled(Host) of
- true ->
- case mnesia:dirty_read({pubsub_last_item, NodeId}) of
- [{pubsub_last_item, NodeId, ItemId, Creation, Payload}] ->
- #pubsub_item{itemid = {ItemId, NodeId}, payload = Payload,
- creation = Creation, modification = Creation};
- _ ->
- undefined
- end;
- _ ->
- undefined
+ true ->
+ case mnesia:dirty_read({pubsub_last_item, NodeIdx}) of
+ [#pubsub_last_item{itemid = ItemId, creation = Creation, payload = Payload}] ->
+% [{pubsub_last_item, NodeId, ItemId, Creation,
+% Payload}] ->
+ #pubsub_item{itemid = {ItemId, NodeIdx},
+ payload = Payload, creation = Creation,
+ modification = Creation};
+ _ -> undefined
+ end;
+ _ -> undefined
end.
%%%% plugin handling
host(ServerHost) ->
- case catch ets:lookup(gen_mod:get_module_proc(ServerHost, config), host) of
- [{host, Host}] -> Host;
- _ -> "pubsub."++ServerHost
+ case catch
+ ets:lookup(gen_mod:get_module_proc(ServerHost, config),
+ host)
+ of
+ [{host, Host}] -> Host;
+ _ -> <<"pubsub.", ServerHost/binary>>
end.
plugins(Host) ->
- case catch ets:lookup(gen_mod:get_module_proc(Host, config), plugins) of
- [{plugins, []}] -> [?STDNODE];
- [{plugins, PL}] -> PL;
- _ -> [?STDNODE]
+ case catch ets:lookup(gen_mod:get_module_proc(Host,
+ config),
+ plugins)
+ of
+ [{plugins, []}] -> [?STDNODE];
+ [{plugins, PL}] -> PL;
+ _ -> [?STDNODE]
end.
-select_type(ServerHost, Host, Node, Type)->
+
+select_type(ServerHost, Host, Node, Type) ->
SelectedType = case Host of
- {_User, _Server, _Resource} ->
- case catch ets:lookup(gen_mod:get_module_proc(ServerHost, config), pep_mapping) of
- [{pep_mapping, PM}] -> proplists:get_value(node_to_string(Node), PM, ?PEPNODE);
- _ -> ?PEPNODE
- end;
- _ ->
- Type
- end,
+ {_User, _Server, _Resource} ->
+ case catch
+ ets:lookup(gen_mod:get_module_proc(ServerHost,
+ config),
+ pep_mapping)
+ of
+ [{pep_mapping, PM}] ->
+ proplists:get_value(Node, PM, ?PEPNODE);
+ _ -> ?PEPNODE
+ end;
+ _ -> Type
+ end,
ConfiguredTypes = plugins(ServerHost),
case lists:member(SelectedType, ConfiguredTypes) of
- true -> SelectedType;
- false -> hd(ConfiguredTypes)
+ true -> SelectedType;
+ false -> hd(ConfiguredTypes)
end.
-select_type(ServerHost, Host, Node) ->
- select_type(ServerHost, Host, Node, hd(plugins(ServerHost))).
+
+select_type(ServerHost, Host, Node) ->
+ select_type(ServerHost, Host, Node,
+ hd(plugins(ServerHost))).
features() ->
- [
- % see plugin "access-authorize", % OPTIONAL
- "access-open", % OPTIONAL this relates to access_model option in node_hometree
- "access-presence", % OPTIONAL this relates to access_model option in node_pep
- %TODO "access-roster", % OPTIONAL
- "access-whitelist", % OPTIONAL
- % see plugin "auto-create", % OPTIONAL
- % see plugin "auto-subscribe", % RECOMMENDED
- "collections", % RECOMMENDED
- "config-node", % RECOMMENDED
- "create-and-configure", % RECOMMENDED
- % see plugin "create-nodes", % RECOMMENDED
- % see plugin "delete-items", % RECOMMENDED
- % see plugin "delete-nodes", % RECOMMENDED
- % see plugin "filtered-notifications", % RECOMMENDED
- % see plugin "get-pending", % OPTIONAL
- % see plugin "instant-nodes", % RECOMMENDED
- "item-ids", % RECOMMENDED
- "last-published", % RECOMMENDED
- %TODO "cache-last-item",
- %TODO "leased-subscription", % OPTIONAL
- % see plugin "manage-subscriptions", % OPTIONAL
- "member-affiliation", % RECOMMENDED
- %TODO "meta-data", % RECOMMENDED
- % see plugin "modify-affiliations", % OPTIONAL
- % see plugin "multi-collection", % OPTIONAL
- % see plugin "multi-subscribe", % OPTIONAL
- % see plugin "outcast-affiliation", % RECOMMENDED
- % see plugin "persistent-items", % RECOMMENDED
- "presence-notifications", % OPTIONAL
- "presence-subscribe", % RECOMMENDED
- % see plugin "publish", % REQUIRED
- %TODO "publish-options", % OPTIONAL
- "publisher-affiliation", % RECOMMENDED
- % see plugin "purge-nodes", % OPTIONAL
- % see plugin "retract-items", % OPTIONAL
- % see plugin "retrieve-affiliations", % RECOMMENDED
- "retrieve-default" % RECOMMENDED
- % see plugin "retrieve-items", % RECOMMENDED
+ [% see plugin "access-authorize", % OPTIONAL
+ <<"access-open">>, % OPTIONAL this relates to access_model option in node_hometree
+ <<"access-presence">>, % OPTIONAL this relates to access_model option in node_pep
+ <<"access-whitelist">>, % OPTIONAL
+ <<"collections">>, % RECOMMENDED
+ <<"config-node">>, % RECOMMENDED
+ <<"create-and-configure">>, % RECOMMENDED
+ <<"item-ids">>, % RECOMMENDED
+ <<"last-published">>, % RECOMMENDED
+ <<"member-affiliation">>, % RECOMMENDED
+ <<"presence-notifications">>, % OPTIONAL
+ <<"presence-subscribe">>, % RECOMMENDED
+ <<"publisher-affiliation">>, % RECOMMENDED
+ <<"retrieve-default">>].
+
+ % see plugin "retrieve-items", % RECOMMENDED
% see plugin "retrieve-subscriptions", % RECOMMENDED
%TODO "shim", % OPTIONAL
% see plugin "subscribe", % REQUIRED
% see plugin "subscription-options", % OPTIONAL
% see plugin "subscription-notifications" % OPTIONAL
- ].
+
features(Type) ->
- Module = list_to_atom(?PLUGIN_PREFIX++Type),
- features() ++ case catch Module:features() of
- {'EXIT', {undef, _}} -> [];
- Result -> Result
- end.
+ Module =
+ jlib:binary_to_atom(<<(?PLUGIN_PREFIX)/binary,
+ Type/binary>>),
+ features() ++
+ case catch Module:features() of
+ {'EXIT', {undef, _}} -> [];
+ Result -> Result
+ end.
+
features(Host, <<>>) ->
- lists:usort(lists:foldl(fun(Plugin, Acc) ->
- Acc ++ features(Plugin)
- end, [], plugins(Host)));
+ lists:usort(lists:foldl(fun (Plugin, Acc) ->
+ Acc ++ features(Plugin)
+ end,
+ [], plugins(Host)));
features(Host, Node) ->
- Action = fun(#pubsub_node{type = Type}) -> {result, features(Type)} end,
+ Action = fun (#pubsub_node{type = Type}) ->
+ {result, features(Type)}
+ end,
case transaction(Host, Node, Action, sync_dirty) of
- {result, Features} -> lists:usort(features() ++ Features);
- _ -> features()
+ {result, Features} ->
+ lists:usort(features() ++ Features);
+ _ -> features()
end.
%% @doc <p>node tree plugin call.</p>
tree_call({_User, Server, _Resource}, Function, Args) ->
tree_call(Server, Function, Args);
tree_call(Host, Function, Args) ->
- ?DEBUG("tree_call ~p ~p ~p",[Host, Function, Args]),
- Module = case catch ets:lookup(gen_mod:get_module_proc(Host, config), nodetree) of
- [{nodetree, N}] -> N;
- _ -> list_to_atom(?TREE_PREFIX ++ ?STDTREE)
- end,
+ ?DEBUG("tree_call ~p ~p ~p", [Host, Function, Args]),
+ Module = case catch
+ ets:lookup(gen_mod:get_module_proc(Host, config),
+ nodetree)
+ of
+ [{nodetree, N}] -> N;
+ _ ->
+ jlib:binary_to_atom(<<(?TREE_PREFIX)/binary,
+ (?STDTREE)/binary>>)
+ end,
catch apply(Module, Function, Args).
+
tree_action(Host, Function, Args) ->
- ?DEBUG("tree_action ~p ~p ~p",[Host,Function,Args]),
- Fun = fun() -> tree_call(Host, Function, Args) end,
+ ?DEBUG("tree_action ~p ~p ~p", [Host, Function, Args]),
+ Fun = fun () -> tree_call(Host, Function, Args) end,
catch mnesia:sync_dirty(Fun).
%% @doc <p>node plugin call.</p>
node_call(Type, Function, Args) ->
- ?DEBUG("node_call ~p ~p ~p",[Type, Function, Args]),
- Module = list_to_atom(?PLUGIN_PREFIX++Type),
+ ?DEBUG("node_call ~p ~p ~p", [Type, Function, Args]),
+ Module =
+ jlib:binary_to_atom(<<(?PLUGIN_PREFIX)/binary,
+ Type/binary>>),
case apply(Module, Function, Args) of
- {result, Result} -> {result, Result};
- {error, Error} -> {error, Error};
- {'EXIT', {undef, Undefined}} ->
- case Type of
- ?STDNODE -> {error, {undef, Undefined}};
- _ -> node_call(?STDNODE, Function, Args)
- end;
- {'EXIT', Reason} -> {error, Reason};
- Result -> {result, Result} %% any other return value is forced as result
+ {result, Result} -> {result, Result};
+ {error, Error} -> {error, Error};
+ {'EXIT', {undef, Undefined}} ->
+ case Type of
+ ?STDNODE -> {error, {undef, Undefined}};
+ _ -> node_call(?STDNODE, Function, Args)
+ end;
+ {'EXIT', Reason} -> {error, Reason};
+ Result ->
+ {result,
+ Result} %% any other return value is forced as result
end.
node_action(Host, Type, Function, Args) ->
- ?DEBUG("node_action ~p ~p ~p ~p",[Host,Type,Function,Args]),
- transaction(fun() ->
- node_call(Type, Function, Args)
- end, sync_dirty).
+ ?DEBUG("node_action ~p ~p ~p ~p",
+ [Host, Type, Function, Args]),
+ transaction(fun () -> node_call(Type, Function, Args)
+ end,
+ sync_dirty).
%% @doc <p>plugin transaction handling.</p>
transaction(Host, Node, Action, Trans) ->
- transaction(fun() ->
+ transaction(fun () ->
case tree_call(Host, get_node, [Host, Node]) of
- N when is_record(N, pubsub_node) ->
- case Action(N) of
- {result, Result} -> {result, {N, Result}};
- {atomic, {result, Result}} -> {result, {N, Result}};
- Other -> Other
- end;
- Error ->
- Error
+ N when is_record(N, pubsub_node) ->
+ case Action(N) of
+ {result, Result} -> {result, {N, Result}};
+ {atomic, {result, Result}} ->
+ {result, {N, Result}};
+ Other -> Other
+ end;
+ Error -> Error
end
- end, Trans).
+ end,
+ Trans).
+
transaction(Host, Action, Trans) ->
- transaction(fun() ->
- {result, lists:foldl(Action, [], tree_call(Host, get_nodes, [Host]))}
- end, Trans).
+ transaction(fun () ->
+ {result,
+ lists:foldl(Action, [],
+ tree_call(Host, get_nodes, [Host]))}
+ end,
+ Trans).
transaction(Fun, Trans) ->
case catch mnesia:Trans(Fun) of
- {result, Result} -> {result, Result};
- {error, Error} -> {error, Error};
- {atomic, {result, Result}} -> {result, Result};
- {atomic, {error, Error}} -> {error, Error};
- {aborted, Reason} ->
- ?ERROR_MSG("transaction return internal error: ~p~n", [{aborted, Reason}]),
- {error, ?ERR_INTERNAL_SERVER_ERROR};
- {'EXIT', Reason} ->
- ?ERROR_MSG("transaction return internal error: ~p~n", [{'EXIT', Reason}]),
- {error, ?ERR_INTERNAL_SERVER_ERROR};
- Other ->
- ?ERROR_MSG("transaction return internal error: ~p~n", [Other]),
- {error, ?ERR_INTERNAL_SERVER_ERROR}
+ {result, Result} -> {result, Result};
+ {error, Error} -> {error, Error};
+ {atomic, {result, Result}} -> {result, Result};
+ {atomic, {error, Error}} -> {error, Error};
+ {aborted, Reason} ->
+ ?ERROR_MSG("transaction return internal error: ~p~n",
+ [{aborted, Reason}]),
+ {error, ?ERR_INTERNAL_SERVER_ERROR};
+ {'EXIT', Reason} ->
+ ?ERROR_MSG("transaction return internal error: ~p~n",
+ [{'EXIT', Reason}]),
+ {error, ?ERR_INTERNAL_SERVER_ERROR};
+ Other ->
+ ?ERROR_MSG("transaction return internal error: ~p~n",
+ [Other]),
+ {error, ?ERR_INTERNAL_SERVER_ERROR}
end.
%%%% helpers
@@ -3869,40 +5216,42 @@ transaction(Fun, Trans) ->
%% Add pubsub-specific error element
extended_error(Error, Ext) ->
extended_error(Error, Ext,
- [{"xmlns", ?NS_PUBSUB_ERRORS}]).
-extended_error(Error, unsupported, Feature) ->
- extended_error(Error, "unsupported",
- [{"xmlns", ?NS_PUBSUB_ERRORS},
- {"feature", Feature}]);
-extended_error({xmlelement, Error, Attrs, SubEls}, Ext, ExtAttrs) ->
- {xmlelement, Error, Attrs,
- lists:reverse([{xmlelement, Ext, ExtAttrs, []} | SubEls])}.
+ [{<<"xmlns">>, ?NS_PUBSUB_ERRORS}]).
+extended_error(Error, unsupported, Feature) ->
%% Give a uniq identifier
+ extended_error(Error, <<"unsupported">>,
+ [{<<"xmlns">>, ?NS_PUBSUB_ERRORS},
+ {<<"feature">>, Feature}]);
+extended_error(#xmlel{name = Error, attrs = Attrs,
+ children = SubEls},
+ Ext, ExtAttrs) ->
+ #xmlel{name = Error, attrs = Attrs,
+ children =
+ lists:reverse([#xmlel{name = Ext, attrs = ExtAttrs,
+ children = []}
+ | SubEls])}.
+
+-spec(uniqid/0 :: () -> mod_pubsub:itemId()).
uniqid() ->
{T1, T2, T3} = now(),
- lists:flatten(io_lib:fwrite("~.16B~.16B~.16B", [T1, T2, T3])).
+ iolist_to_binary(io_lib:fwrite("~.16B~.16B~.16B", [T1, T2, T3])).
-% node attributes
-nodeAttr(Node) when is_list(Node) ->
- [{"node", Node}];
-nodeAttr(Node) ->
- [{"node", node_to_string(Node)}].
+nodeAttr(Node) -> [{<<"node">>, Node}].
-% item attributes
itemAttr([]) -> [];
-itemAttr(ItemId) -> [{"id", ItemId}].
+itemAttr(ItemId) -> [{<<"id">>, ItemId}].
-% build item elements from item list
itemsEls(Items) ->
- lists:map(fun(#pubsub_item{itemid = {ItemId, _}, payload = Payload}) ->
- {xmlelement, "item", itemAttr(ItemId), Payload}
- end, Items).
+ lists:map(fun (#pubsub_item{itemid = {ItemId, _}, payload = Payload}) ->
+ #xmlel{name = <<"item">>, attrs = itemAttr(ItemId), children = Payload}
+ end, Items).
-add_message_type({xmlelement, "message", Attrs, Els}, Type) ->
- {xmlelement, "message", [{"type", Type}|Attrs], Els};
-add_message_type(XmlEl, _Type) ->
- XmlEl.
+add_message_type(#xmlel{name = <<"message">>, attrs = Attrs, children = Els},
+ Type) ->
+ #xmlel{name = <<"message">>,
+ attrs = [{<<"type">>, Type} | Attrs], children = Els};
+add_message_type(XmlEl, _Type) -> XmlEl.
%% Place of <headers/> changed at the bottom of the stanza
%% cf. http://xmpp.org/extensions/xep-0060.html#publisher-publish-success-subid
@@ -3911,14 +5260,19 @@ add_message_type(XmlEl, _Type) ->
%% (i.e., as the last child of the <message/> stanza)".
add_shim_headers(Stanza, HeaderEls) ->
- add_headers(Stanza, "headers", ?NS_SHIM, HeaderEls).
+ add_headers(Stanza, <<"headers">>, ?NS_SHIM, HeaderEls).
add_extended_headers(Stanza, HeaderEls) ->
- add_headers(Stanza, "addresses", ?NS_ADDRESS, HeaderEls).
+ add_headers(Stanza, <<"addresses">>, ?NS_ADDRESS,
+ HeaderEls).
-add_headers({xmlelement, Name, Attrs, Els}, HeaderName, HeaderNS, HeaderEls) ->
- HeaderEl = {xmlelement, HeaderName, [{"xmlns", HeaderNS}], HeaderEls},
- {xmlelement, Name, Attrs, lists:append(Els, [HeaderEl])}.
+add_headers(#xmlel{name = Name, attrs = Attrs, children = Els},
+ HeaderName, HeaderNS, HeaderEls) ->
+ HeaderEl = #xmlel{name = HeaderName,
+ attrs = [{<<"xmlns">>, HeaderNS}],
+ children = HeaderEls},
+ #xmlel{name = Name, attrs = Attrs,
+ children = lists:append(Els, [HeaderEl])}.
%% Removed multiple <header name=Collection>Foo</header/> elements
%% Didn't seem compliant, but not sure. Confirmation required.
@@ -3934,18 +5288,24 @@ add_headers({xmlelement, Name, Attrs, Els}, HeaderName, HeaderNS, HeaderEls) ->
%% identifier of the collection".
collection_shim(Node) ->
- [{xmlelement, "header", [{"name", "Collection"}],
- [{xmlcdata, node_to_string(Node)}]}].
+ [#xmlel{name = <<"header">>,
+ attrs = [{<<"name">>, <<"Collection">>}],
+ children = [{xmlcdata, Node}]}].
subid_shim(SubIDs) ->
- [{xmlelement, "header", [{"name", "SubID"}],
- [{xmlcdata, SubID}]} || SubID <- SubIDs].
+ [#xmlel{name = <<"header">>,
+ attrs = [{<<"name">>, <<"SubID">>}],
+ children = [{xmlcdata, SubID}]}
+ || SubID <- SubIDs].
%% The argument is a list of Jids because this function could be used
%% with the 'pubsub#replyto' (type=jid-multi) node configuration.
extended_headers(Jids) ->
- [{xmlelement, "address", [{"type", "replyto"}, {"jid", Jid}], []} || Jid <- Jids].
+ [#xmlel{name = <<"address">>,
+ attrs = [{<<"type">>, <<"replyto">>}, {<<"jid">>, Jid}],
+ children = []}
+ || Jid <- Jids].
on_user_offline(_, JID, _) ->
{User, Server, Resource} = jlib:jid_tolower(JID),
@@ -3957,57 +5317,84 @@ on_user_offline(_, JID, _) ->
purge_offline({User, Server, _} = LJID) ->
Host = host(element(2, LJID)),
Plugins = plugins(Host),
- Result = lists:foldl(
- fun(Type, {Status, Acc}) ->
- case lists:member("retrieve-affiliations", features(Type)) of
- false ->
- {{error, extended_error('feature-not-implemented', unsupported, "retrieve-affiliations")}, Acc};
- true ->
- {result, Affiliations} = node_action(Host, Type, get_entity_affiliations, [Host, LJID]),
- {Status, [Affiliations|Acc]}
- end
- end, {ok, []}, Plugins),
+ Result = lists:foldl(fun (Type, {Status, Acc}) ->
+ case lists:member(<<"retrieve-affiliations">>,
+ features(Type))
+ of
+ false ->
+ {{error,
+ extended_error(?ERR_FEATURE_NOT_IMPLEMENTED,
+ unsupported,
+ <<"retrieve-affiliations">>)},
+ Acc};
+ true ->
+ {result, Affiliations} =
+ node_action(Host, Type,
+ get_entity_affiliations,
+ [Host, LJID]),
+ {Status, [Affiliations | Acc]}
+ end
+ end,
+ {ok, []}, Plugins),
case Result of
- {ok, Affiliations} ->
- lists:foreach(
- fun({#pubsub_node{nodeid = {_, NodeId}, options = Options, type = Type}, Affiliation})
- when Affiliation == 'owner' orelse Affiliation == 'publisher' ->
- Action = fun(#pubsub_node{type = NType, id = NodeIdx}) ->
- node_call(NType, get_items, [NodeIdx, service_jid(Host)])
- end,
- case transaction(Host, NodeId, Action, sync_dirty) of
- {result, {_, []}} ->
- true;
- {result, {_, Items}} ->
- Features = features(Type),
- case
- {lists:member("retract-items", Features),
- lists:member("persistent-items", Features),
- get_option(Options, persist_items),
- get_option(Options, purge_offline)}
- of
- {true, true, true, true} ->
- ForceNotify = get_option(Options, notify_retract),
- lists:foreach(
- fun(#pubsub_item{itemid = {ItemId, _}, modification = {_, Modification}}) ->
- case Modification of
- {User, Server, _} ->
- delete_item(Host, NodeId, LJID, ItemId, ForceNotify);
- _ ->
- true
- end;
- (_) ->
- true
- end, Items);
- _ ->
- true
+ {ok, Affiliations} ->
+ lists:foreach(fun ({#pubsub_node{nodeid = {_, NodeId},
+ options = Options, type = Type},
+ Affiliation})
+ when Affiliation == owner orelse
+ Affiliation == publisher ->
+ Action = fun (#pubsub_node{type = NType,
+ id = NodeIdx}) ->
+ node_call(NType, get_items,
+ [NodeIdx,
+ service_jid(Host)])
+ end,
+ case transaction(Host, NodeId, Action,
+ sync_dirty)
+ of
+ {result, {_, []}} -> true;
+ {result, {_, Items}} ->
+ Features = features(Type),
+ case {lists:member(<<"retract-items">>,
+ Features),
+ lists:member(<<"persistent-items">>,
+ Features),
+ get_option(Options, persist_items),
+ get_option(Options, purge_offline)}
+ of
+ {true, true, true, true} ->
+ ForceNotify = get_option(Options,
+ notify_retract),
+ lists:foreach(fun
+ (#pubsub_item{itemid
+ =
+ {ItemId,
+ _},
+ modification
+ =
+ {_,
+ Modification}}) ->
+ case
+ Modification
+ of
+ {User, Server,
+ _} ->
+ delete_item(Host,
+ NodeId,
+ LJID,
+ ItemId,
+ ForceNotify);
+ _ -> true
+ end;
+ (_) -> true
+ end,
+ Items);
+ _ -> true
+ end;
+ Error -> Error
end;
- Error ->
- Error
- end;
- (_) ->
- true
- end, lists:usort(lists:flatten(Affiliations)));
- {Error, _} ->
- ?DEBUG("on_user_offline ~p", [Error])
+ (_) -> true
+ end,
+ lists:usort(lists:flatten(Affiliations)));
+ {Error, _} -> ?DEBUG("on_user_offline ~p", [Error])
end.
diff --git a/src/mod_pubsub/mod_pubsub_odbc.erl b/src/mod_pubsub/mod_pubsub_odbc.erl
index 0be08b51f..3396570a4 100644
--- a/src/mod_pubsub/mod_pubsub_odbc.erl
+++ b/src/mod_pubsub/mod_pubsub_odbc.erl
@@ -5,11 +5,13 @@
%%% Erlang Public License along with this software. If not, it can be
%%% retrieved via the world wide web at http://www.erlang.org/.
%%%
+%%%
%%% Software distributed under the License is distributed on an "AS IS"
%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%%% the License for the specific language governing rights and limitations
%%% under the License.
%%%
+%%%
%%% The Initial Developer of the Original Code is ProcessOne.
%%% Portions created by ProcessOne are Copyright 2006-2013, ProcessOne
%%% All Rights Reserved.''
@@ -22,7 +24,6 @@
%%% @end
%%% ====================================================================
-
%%% @doc The module <strong>{@module}</strong> is the core of the PubSub
%%% extension. It relies on PubSub plugins for a large part of its functions.
%%%
@@ -43,38 +44,39 @@
%%% XEP-0060 section 12.18.
-module(mod_pubsub_odbc).
+
-author('christophe.romain@process-one.net').
+
-version('1.13-0').
-behaviour(gen_server).
+
-behaviour(gen_mod).
-include("ejabberd.hrl").
+
-include("adhoc.hrl").
+
-include("jlib.hrl").
+
-include("pubsub.hrl").
--define(STDTREE, "tree_odbc").
--define(STDNODE, "flat_odbc").
--define(PEPNODE, "pep_odbc").
+-define(STDTREE, <<"tree_odbc">>).
+
+-define(STDNODE, <<"flat_odbc">>).
+
+-define(PEPNODE, <<"pep_odbc">>).
%% exports for hooks
--export([presence_probe/3,
- caps_update/3,
- in_subscription/6,
- out_subscription/4,
- on_user_offline/3,
- remove_user/2,
- disco_local_identity/5,
- disco_local_features/5,
- disco_local_items/5,
- disco_sm_identity/5,
- disco_sm_features/5,
- disco_sm_items/5
- ]).
+-export([presence_probe/3, caps_update/3,
+ in_subscription/6, out_subscription/4,
+ on_user_offline/3, remove_user/2,
+ disco_local_identity/5, disco_local_features/5,
+ disco_local_items/5, disco_sm_identity/5,
+ disco_sm_features/5, disco_sm_items/5]).
+
%% exported iq handlers
--export([iq_sm/3
- ]).
+-export([iq_sm/3]).
%% exports for console debug manual use
-export([create_node/5,
@@ -95,47 +97,22 @@
]).
%% general helpers for plugins
--export([node_to_string/1,
- string_to_node/1,
- subscription_to_string/1,
- affiliation_to_string/1,
- string_to_subscription/1,
- string_to_affiliation/1,
- extended_error/2,
- extended_error/3,
- escape/1
- ]).
+-export([subscription_to_string/1, affiliation_to_string/1,
+ string_to_subscription/1, string_to_affiliation/1,
+ extended_error/2, extended_error/3,
+ escape/1]).
%% API and gen_server callbacks
--export([start_link/2,
- start/2,
- stop/1,
- init/1,
- handle_call/3,
- handle_cast/2,
- handle_info/2,
- terminate/2,
- code_change/3
- ]).
+-export([start_link/2, start/2, stop/1, init/1,
+ handle_call/3, handle_cast/2, handle_info/2,
+ terminate/2, code_change/3]).
%% calls for parallel sending of last items
--export([send_loop/1
- ]).
+-export([send_loop/1]).
-define(PROCNAME, ejabberd_mod_pubsub_odbc).
+
-define(LOOPNAME, ejabberd_mod_pubsub_loop).
--define(PLUGIN_PREFIX, "node_").
--define(TREE_PREFIX, "nodetree_").
-
--record(state, {server_host,
- host,
- access,
- pep_mapping = [],
- ignore_pep_from_offline = true,
- last_item_cache = false,
- max_items_node = ?MAXITEMS,
- nodetree = ?STDTREE,
- plugins = [?STDNODE]}).
%%====================================================================
%% API
@@ -144,14 +121,127 @@
%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
%% Description: Starts the server
%%--------------------------------------------------------------------
+-define(PLUGIN_PREFIX, <<"node_">>).
+
+-define(TREE_PREFIX, <<"nodetree_">>).
+
+%
+-export_type([
+ host/0,
+ hostPubsub/0,
+ hostPEP/0,
+ %%
+ nodeIdx/0,
+ nodeId/0,
+ itemId/0,
+ subId/0,
+ payload/0,
+ %%
+ nodeOption/0,
+ nodeOptions/0,
+ subOption/0,
+ subOptions/0,
+ %%
+ affiliation/0,
+ subscription/0,
+ accessModel/0,
+ publishModel/0
+]).
+
+%% -type payload() defined here because the -type xmlel() is not accessible
+%% from pubsub.hrl
+-type(payload() :: [] | [xmlel(),...]).
+
+-export_type([
+ pubsubNode/0,
+ pubsubState/0,
+ pubsubItem/0,
+ pubsubSubscription/0,
+ pubsubLastItem/0
+]).
+
+-type(pubsubNode() ::
+ #pubsub_node{
+ nodeid :: {Host::mod_pubsub:host(), NodeId::mod_pubsub:nodeId()},
+ id :: mod_pubsub:nodeIdx(),
+ parents :: [Parent_NodeId::mod_pubsub:nodeId()],
+ type :: binary(),
+ owners :: [Owner::ljid(),...],
+ options :: mod_pubsub:nodeOptions()
+ }
+).
+
+-type(pubsubState() ::
+ #pubsub_state{
+ stateid :: {Entity::ljid(), NodeIdx::mod_pubsub:nodeIdx()},
+ items :: [ItemId::mod_pubsub:itemId()],
+ affiliation :: mod_pubsub:affiliation(),
+ subscriptions :: [{mod_pubsub:subscription(), mod_pubsub:subId()}]
+ }
+).
+
+-type(pubsubItem() ::
+ #pubsub_item{
+ itemid :: {mod_pubsub:itemId(), mod_pubsub:nodeIdx()},
+ creation :: {erlang:timestamp(), ljid()},
+ modification :: {erlang:timestamp(), ljid()},
+ payload :: mod_pubsub:payload()
+ }
+).
+
+-type(pubsubSubscription() ::
+ #pubsub_subscription{
+ subid :: mod_pubsub:subId(),
+ options :: [] | mod_pubsub:subOptions()
+ }
+).
+
+-type(pubsubLastItem() ::
+ #pubsub_last_item{
+ nodeid :: mod_pubsub:nodeIdx(),
+ itemid :: mod_pubsub:itemId(),
+ creation :: {erlang:timestamp(), ljid()},
+ payload :: mod_pubsub:payload()
+ }
+).
+
+-record(state,
+{
+ server_host,
+ host,
+ access,
+ pep_mapping = [],
+ ignore_pep_from_offline = true,
+ last_item_cache = false,
+ max_items_node = ?MAXITEMS,
+ nodetree = ?STDTREE,
+ plugins = [?STDNODE]
+}).
+
+-type(state() ::
+ #state{
+ server_host :: binary(),
+ host :: mod_pubsub:hostPubsub(),
+ access :: atom(),
+ pep_mapping :: [{binary(), binary()}],
+ ignore_pep_from_offline :: boolean(),
+ last_item_cache :: boolean(),
+ max_items_node :: non_neg_integer(),
+ nodetree :: binary(),
+ plugins :: [binary(),...]
+ }
+
+).
+
+
start_link(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
- gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
+ gen_server:start_link({local, Proc}, ?MODULE,
+ [Host, Opts], []).
start(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
- ChildSpec = {Proc,
- {?MODULE, start_link, [Host, Opts]},
+ ChildSpec = {Proc, {?MODULE, start_link, [Host, Opts]},
transient, 1000, worker, [?MODULE]},
supervisor:start_child(ejabberd_sup, ChildSpec).
@@ -171,63 +261,102 @@ stop(Host) ->
%% {stop, Reason}
%% Description: Initiates the server
%%--------------------------------------------------------------------
+-spec(init/1 ::
+(
+ _:: _)
+ -> {ok, state()}
+).
+
init([ServerHost, Opts]) ->
- ?DEBUG("pubsub init ~p ~p",[ServerHost,Opts]),
- Host = gen_mod:get_opt_host(ServerHost, Opts, "pubsub.@HOST@"),
- Access = gen_mod:get_opt(access_createnode, Opts, all),
- PepOffline = gen_mod:get_opt(ignore_pep_from_offline, Opts, true),
- IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
- LastItemCache = gen_mod:get_opt(last_item_cache, Opts, false),
- MaxItemsNode = gen_mod:get_opt(max_items_node, Opts, ?MAXITEMS),
+ ?DEBUG("pubsub init ~p ~p", [ServerHost, Opts]),
+ Host = gen_mod:get_opt_host(ServerHost, Opts, <<"pubsub.@HOST@">>),
+ Access = gen_mod:get_opt(access_createnode, Opts,
+ fun(A) when is_atom(A) -> A end, all),
+ PepOffline = gen_mod:get_opt(ignore_pep_from_offline, Opts,
+ fun(A) when is_boolean(A) -> A end, true),
+ IQDisc = gen_mod:get_opt(iqdisc, Opts,
+ fun(A) when is_atom(A) -> A end, one_queue),
+ LastItemCache = gen_mod:get_opt(last_item_cache, Opts,
+ fun(A) when is_boolean(A) -> A end, false),
+ MaxItemsNode = gen_mod:get_opt(max_items_node, Opts,
+ fun(A) when is_integer(A) andalso A >= 0 -> A end, ?MAXITEMS),
pubsub_index:init(Host, ServerHost, Opts),
- ets:new(gen_mod:get_module_proc(Host, config), [set, named_table]),
- ets:new(gen_mod:get_module_proc(ServerHost, config), [set, named_table]),
- {Plugins, NodeTree, PepMapping} = init_plugins(Host, ServerHost, Opts),
- mnesia:create_table(pubsub_last_item, [{ram_copies, [node()]}, {attributes, record_info(fields, pubsub_last_item)}]),
+ ets:new(gen_mod:get_module_proc(Host, config),
+ [set, named_table]),
+ ets:new(gen_mod:get_module_proc(ServerHost, config),
+ [set, named_table]),
+ {Plugins, NodeTree, PepMapping} = init_plugins(Host,
+ ServerHost, Opts),
+ mnesia:create_table(pubsub_last_item,
+ [{ram_copies, [node()]},
+ {attributes, record_info(fields, pubsub_last_item)}]),
mod_disco:register_feature(ServerHost, ?NS_PUBSUB),
- ets:insert(gen_mod:get_module_proc(Host, config), {nodetree, NodeTree}),
- ets:insert(gen_mod:get_module_proc(Host, config), {plugins, Plugins}),
- ets:insert(gen_mod:get_module_proc(Host, config), {last_item_cache, LastItemCache}),
- ets:insert(gen_mod:get_module_proc(Host, config), {max_items_node, MaxItemsNode}),
- ets:insert(gen_mod:get_module_proc(ServerHost, config), {nodetree, NodeTree}),
- ets:insert(gen_mod:get_module_proc(ServerHost, config), {plugins, Plugins}),
- ets:insert(gen_mod:get_module_proc(ServerHost, config), {last_item_cache, LastItemCache}),
- ets:insert(gen_mod:get_module_proc(ServerHost, config), {max_items_node, MaxItemsNode}),
- ets:insert(gen_mod:get_module_proc(ServerHost, config), {pep_mapping, PepMapping}),
- ets:insert(gen_mod:get_module_proc(ServerHost, config), {ignore_pep_from_offline, PepOffline}),
- ets:insert(gen_mod:get_module_proc(ServerHost, config), {host, Host}),
- ejabberd_hooks:add(sm_remove_connection_hook, ServerHost, ?MODULE, on_user_offline, 75),
- ejabberd_hooks:add(disco_local_identity, ServerHost, ?MODULE, disco_local_identity, 75),
- ejabberd_hooks:add(disco_local_features, ServerHost, ?MODULE, disco_local_features, 75),
- ejabberd_hooks:add(disco_local_items, ServerHost, ?MODULE, disco_local_items, 75),
- ejabberd_hooks:add(presence_probe_hook, ServerHost, ?MODULE, presence_probe, 80),
- ejabberd_hooks:add(roster_in_subscription, ServerHost, ?MODULE, in_subscription, 50),
- ejabberd_hooks:add(roster_out_subscription, ServerHost, ?MODULE, out_subscription, 50),
- ejabberd_hooks:add(remove_user, ServerHost, ?MODULE, remove_user, 50),
- ejabberd_hooks:add(anonymous_purge_hook, ServerHost, ?MODULE, remove_user, 50),
+ ets:insert(gen_mod:get_module_proc(Host, config),
+ {nodetree, NodeTree}),
+ ets:insert(gen_mod:get_module_proc(Host, config),
+ {plugins, Plugins}),
+ ets:insert(gen_mod:get_module_proc(Host, config),
+ {last_item_cache, LastItemCache}),
+ ets:insert(gen_mod:get_module_proc(Host, config),
+ {max_items_node, MaxItemsNode}),
+ ets:insert(gen_mod:get_module_proc(ServerHost, config),
+ {nodetree, NodeTree}),
+ ets:insert(gen_mod:get_module_proc(ServerHost, config),
+ {plugins, Plugins}),
+ ets:insert(gen_mod:get_module_proc(ServerHost, config),
+ {last_item_cache, LastItemCache}),
+ ets:insert(gen_mod:get_module_proc(ServerHost, config),
+ {max_items_node, MaxItemsNode}),
+ ets:insert(gen_mod:get_module_proc(ServerHost, config),
+ {pep_mapping, PepMapping}),
+ ets:insert(gen_mod:get_module_proc(ServerHost, config),
+ {ignore_pep_from_offline, PepOffline}),
+ ets:insert(gen_mod:get_module_proc(ServerHost, config),
+ {host, Host}),
+ ejabberd_hooks:add(sm_remove_connection_hook,
+ ServerHost, ?MODULE, on_user_offline, 75),
+ ejabberd_hooks:add(disco_local_identity, ServerHost,
+ ?MODULE, disco_local_identity, 75),
+ ejabberd_hooks:add(disco_local_features, ServerHost,
+ ?MODULE, disco_local_features, 75),
+ ejabberd_hooks:add(disco_local_items, ServerHost,
+ ?MODULE, disco_local_items, 75),
+ ejabberd_hooks:add(presence_probe_hook, ServerHost,
+ ?MODULE, presence_probe, 80),
+ ejabberd_hooks:add(roster_in_subscription, ServerHost,
+ ?MODULE, in_subscription, 50),
+ ejabberd_hooks:add(roster_out_subscription, ServerHost,
+ ?MODULE, out_subscription, 50),
+ ejabberd_hooks:add(remove_user, ServerHost, ?MODULE,
+ remove_user, 50),
+ ejabberd_hooks:add(anonymous_purge_hook, ServerHost,
+ ?MODULE, remove_user, 50),
case lists:member(?PEPNODE, Plugins) of
- true ->
- ejabberd_hooks:add(caps_update, ServerHost, ?MODULE, caps_update, 80),
- ejabberd_hooks:add(disco_sm_identity, ServerHost, ?MODULE, disco_sm_identity, 75),
- ejabberd_hooks:add(disco_sm_features, ServerHost, ?MODULE, disco_sm_features, 75),
- ejabberd_hooks:add(disco_sm_items, ServerHost, ?MODULE, disco_sm_items, 75),
- gen_iq_handler:add_iq_handler(ejabberd_sm, ServerHost, ?NS_PUBSUB, ?MODULE, iq_sm, IQDisc),
- gen_iq_handler:add_iq_handler(ejabberd_sm, ServerHost, ?NS_PUBSUB_OWNER, ?MODULE, iq_sm, IQDisc);
- false ->
- ok
+ true ->
+ ejabberd_hooks:add(caps_update, ServerHost, ?MODULE,
+ caps_update, 80),
+ ejabberd_hooks:add(disco_sm_identity, ServerHost,
+ ?MODULE, disco_sm_identity, 75),
+ ejabberd_hooks:add(disco_sm_features, ServerHost,
+ ?MODULE, disco_sm_features, 75),
+ ejabberd_hooks:add(disco_sm_items, ServerHost, ?MODULE,
+ disco_sm_items, 75),
+ gen_iq_handler:add_iq_handler(ejabberd_sm, ServerHost,
+ ?NS_PUBSUB, ?MODULE, iq_sm, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_sm, ServerHost,
+ ?NS_PUBSUB_OWNER, ?MODULE, iq_sm,
+ IQDisc);
+ false -> ok
end,
ejabberd_router:register_route(Host),
- put(server_host, ServerHost), % not clean, but needed to plug hooks at any location
+ put(server_host, ServerHost),
init_nodes(Host, ServerHost, NodeTree, Plugins),
- State = #state{host = Host,
- server_host = ServerHost,
- access = Access,
- pep_mapping = PepMapping,
- ignore_pep_from_offline = PepOffline,
- last_item_cache = LastItemCache,
- max_items_node = MaxItemsNode,
- nodetree = NodeTree,
- plugins = Plugins},
+ State = #state{host = Host, server_host = ServerHost,
+ access = Access, pep_mapping = PepMapping,
+ ignore_pep_from_offline = PepOffline,
+ last_item_cache = LastItemCache,
+ max_items_node = MaxItemsNode, nodetree = NodeTree,
+ plugins = Plugins},
init_send_loop(ServerHost, State),
{ok, State}.
@@ -251,285 +380,431 @@ init_send_loop(ServerHost, State) ->
%% and sorted to ensure that each module is initialized only once.</p>
%% <p>See {@link node_hometree:init/1} for an example implementation.</p>
init_plugins(Host, ServerHost, Opts) ->
- TreePlugin = list_to_atom(?TREE_PREFIX ++
- gen_mod:get_opt(nodetree, Opts, ?STDTREE)),
- ?DEBUG("** tree plugin is ~p",[TreePlugin]),
+ TreePlugin =
+ jlib:binary_to_atom(<<(?TREE_PREFIX)/binary,
+ (gen_mod:get_opt(nodetree, Opts, fun(A) when is_list(A) -> A end,
+ ?STDTREE))/binary>>),
+ ?DEBUG("** tree plugin is ~p", [TreePlugin]),
TreePlugin:init(Host, ServerHost, Opts),
- Plugins = gen_mod:get_opt(plugins, Opts, [?STDNODE]),
- PepMapping = gen_mod:get_opt(pep_mapping, Opts, []),
- ?DEBUG("** PEP Mapping : ~p~n",[PepMapping]),
- PluginsOK = lists:foldl(fun(Name, Acc) ->
- Plugin = list_to_atom(?PLUGIN_PREFIX ++ Name),
- case catch apply(Plugin, init, [Host, ServerHost, Opts]) of
- {'EXIT', _Error} ->
- Acc;
- _ ->
- ?DEBUG("** init ~s plugin",[Name]),
- [Name | Acc]
- end
- end, [], Plugins),
+ Plugins = gen_mod:get_opt(plugins, Opts,
+ fun(A) when is_list(A) -> A end, [?STDNODE]),
+ PepMapping = gen_mod:get_opt(pep_mapping, Opts,
+ fun(A) when is_list(A) -> A end, []),
+ ?DEBUG("** PEP Mapping : ~p~n", [PepMapping]),
+ PluginsOK = lists:foldl(fun (Name, Acc) ->
+ Plugin =
+ jlib:binary_to_atom(<<(?PLUGIN_PREFIX)/binary,
+ Name/binary>>),
+ case catch apply(Plugin, init,
+ [Host, ServerHost, Opts])
+ of
+ {'EXIT', _Error} -> Acc;
+ _ ->
+ ?DEBUG("** init ~s plugin", [Name]),
+ [Name | Acc]
+ end
+ end,
+ [], Plugins),
{lists:reverse(PluginsOK), TreePlugin, PepMapping}.
-terminate_plugins(Host, ServerHost, Plugins, TreePlugin) ->
- lists:foreach(fun(Name) ->
- ?DEBUG("** terminate ~s plugin",[Name]),
- Plugin = list_to_atom(?PLUGIN_PREFIX++Name),
+terminate_plugins(Host, ServerHost, Plugins,
+ TreePlugin) ->
+ lists:foreach(fun (Name) ->
+ ?DEBUG("** terminate ~s plugin", [Name]),
+ Plugin =
+ jlib:binary_to_atom(<<(?PLUGIN_PREFIX)/binary,
+ Name/binary>>),
Plugin:terminate(Host, ServerHost)
- end, Plugins),
+ end,
+ Plugins),
TreePlugin:terminate(Host, ServerHost),
ok.
init_nodes(Host, ServerHost, _NodeTree, Plugins) ->
- %% TODO, this call should be done plugin side
- case lists:member("hometree_odbc", Plugins) of
- true ->
- create_node(Host, ServerHost, string_to_node("/home"), service_jid(Host), "hometree_odbc"),
- create_node(Host, ServerHost, string_to_node("/home/"++ServerHost), service_jid(Host), "hometree_odbc");
- false ->
- ok
+ case lists:member(<<"hometree_odbc">>, Plugins) of
+ true ->
+ create_node(Host, ServerHost, <<"/home">>, service_jid(Host), <<"hometree_odbc">>),
+ create_node(Host, ServerHost, <<"/home/", ServerHost/binary>>, service_jid(Host),
+ <<"hometree_odbc">>);
+ false -> ok
end.
send_loop(State) ->
receive
- {presence, JID, Pid} ->
- Host = State#state.host,
- ServerHost = State#state.server_host,
- LJID = jlib:jid_tolower(JID),
- BJID = jlib:jid_remove_resource(LJID),
- %% for each node From is subscribed to
- %% and if the node is so configured, send the last published item to From
- lists:foreach(fun(PType) ->
- Subscriptions = case catch node_action(Host, PType, get_entity_subscriptions_for_send_last, [Host, JID]) of
- {result, S} -> S;
- _ -> []
- end,
- lists:foreach(
- fun({Node, subscribed, _, SubJID}) ->
- if (SubJID == LJID) or (SubJID == BJID) ->
- #pubsub_node{nodeid = {H, N}, type = Type, id = NodeId} = Node,
- send_items(H, N, NodeId, Type, LJID, last);
- true ->
- % resource not concerned about that subscription
- ok
- end;
- (_) ->
- ok
- end, Subscriptions)
- end, State#state.plugins),
- %% and force send the last PEP events published by its offline and local contacts
- %% only if pubsub is explicitely configured for that.
- %% this is a hack in a sense that PEP should only be based on presence
- %% and is not able to "store" events of remote users (via s2s)
- %% this makes that hack only work for local domain by now
- if not State#state.ignore_pep_from_offline ->
- {User, Server, Resource} = jlib:jid_tolower(JID),
- case catch ejabberd_c2s:get_subscribed(Pid) of
- Contacts when is_list(Contacts) ->
- lists:foreach(
- fun({U, S, R}) ->
- case S of
- ServerHost -> %% local contacts
- case user_resources(U, S) of
- [] -> %% offline
- PeerJID = jlib:make_jid(U, S, R),
- self() ! {presence, User, Server, [Resource], PeerJID};
- _ -> %% online
- % this is already handled by presence probe
- ok
- end;
- _ -> %% remote contacts
- % we can not do anything in any cases
- ok
- end
- end, Contacts);
- _ ->
- ok
- end;
- true ->
- ok
- end,
- send_loop(State);
- {presence, User, Server, Resources, JID} ->
- %% get resources caps and check if processing is needed
- spawn(fun() ->
- Host = State#state.host,
- Owner = jlib:jid_remove_resource(jlib:jid_tolower(JID)),
- lists:foreach(fun(#pubsub_node{nodeid = {_, Node}, type = Type, id = NodeId, options = Options}) ->
- case get_option(Options, send_last_published_item) of
- on_sub_and_presence ->
- lists:foreach(
- fun(Resource) ->
- LJID = {User, Server, Resource},
- Subscribed = case get_option(Options, access_model) of
- open -> true;
- presence -> true;
- whitelist -> false; % subscribers are added manually
- authorize -> false; % likewise
- roster ->
- Grps = get_option(Options, roster_groups_allowed, []),
- {OU, OS, _} = Owner,
- element(2, get_roster_info(OU, OS, LJID, Grps))
- end,
- if Subscribed ->
- send_items(Owner, Node, NodeId, Type, LJID, last);
- true ->
- ok
- end
- end, Resources);
- _ ->
- ok
- end
- end, tree_action(Host, get_nodes, [Owner, JID]))
+ {presence, JID, Pid} ->
+ Host = State#state.host,
+ ServerHost = State#state.server_host,
+ LJID = jlib:jid_tolower(JID),
+ BJID = jlib:jid_remove_resource(LJID),
+ lists:foreach(fun (PType) ->
+ {result, Subscriptions} = case catch node_action(Host,
+ PType,
+ get_entity_subscriptions_for_send_last,
+ [Host, JID]) of
+ {result, S} -> S;
+ _ -> []
+ end,
+ lists:foreach(fun ({Node, subscribed, _,
+ SubJID}) ->
+ if (SubJID == LJID) or
+ (SubJID == BJID) ->
+ #pubsub_node{nodeid
+ =
+ {H,
+ N},
+ type =
+ Type,
+ id =
+ NodeId} =
+ Node,
+ send_items(H,
+ N,
+ NodeId,
+ Type,
+ LJID,
+ last);
+ true ->
+ % resource not concerned about that subscription
+ ok
+ end;
+ (_) -> ok
+ end,
+ Subscriptions)
+ end,
+ State#state.plugins),
+ if not State#state.ignore_pep_from_offline ->
+ {User, Server, Resource} = jlib:jid_tolower(JID),
+ case catch ejabberd_c2s:get_subscribed(Pid) of
+ Contacts when is_list(Contacts) ->
+ lists:foreach(fun ({U, S, R}) ->
+ case S of
+ ServerHost -> %% local contacts
+ case user_resources(U, S) of
+ [] -> %% offline
+ PeerJID =
+ jlib:make_jid(U, S,
+ R),
+ self() !
+ {presence, User,
+ Server, [Resource],
+ PeerJID};
+ _ -> %% online
+ % this is already handled by presence probe
+ ok
+ end;
+ _ -> %% remote contacts
+ % we can not do anything in any cases
+ ok
+ end
+ end,
+ Contacts);
+ _ -> ok
+ end;
+ true -> ok
+ end,
+ send_loop(State);
+ {presence, User, Server, Resources, JID} ->
+ spawn(fun () ->
+ Host = State#state.host,
+ Owner = jlib:jid_remove_resource(jlib:jid_tolower(JID)),
+ lists:foreach(fun (#pubsub_node{nodeid = {_, Node},
+ type = Type,
+ id = NodeId,
+ options = Options}) ->
+ case get_option(Options,
+ send_last_published_item)
+ of
+ on_sub_and_presence ->
+ lists:foreach(fun
+ (Resource) ->
+ LJID =
+ {User,
+ Server,
+ Resource},
+ Subscribed =
+ case
+ get_option(Options,
+ access_model)
+ of
+ open ->
+ true;
+ presence ->
+ true;
+ whitelist ->
+ false; % subscribers are added manually
+ authorize ->
+ false; % likewise
+ roster ->
+ Grps =
+ get_option(Options,
+ roster_groups_allowed,
+ []),
+ {OU,
+ OS,
+ _} =
+ Owner,
+ element(2,
+ get_roster_info(OU,
+ OS,
+ LJID,
+ Grps))
+ end,
+ if
+ Subscribed ->
+ send_items(Owner,
+ Node,
+ NodeId,
+ Type,
+ LJID,
+ last);
+ true ->
+ ok
+ end
+ end,
+ Resources);
+ _ -> ok
+ end
+ end,
+ tree_action(Host, get_nodes,
+ [Owner, JID]))
end),
- send_loop(State);
- stop ->
- ok
+ send_loop(State);
+ stop -> ok
end.
%% -------
%% disco hooks handling functions
%%
-disco_local_identity(Acc, _From, To, [], _Lang) ->
+-spec(disco_local_identity/5 ::
+(
+ Acc :: [xmlel()],
+ _From :: jid(),
+ To :: jid(),
+ NodeId :: <<>> | mod_pubsub:nodeId(),
+ Lang :: binary())
+ -> [xmlel()]
+).
+disco_local_identity(Acc, _From, To, <<>>, _Lang) ->
case lists:member(?PEPNODE, plugins(To#jid.lserver)) of
- true ->
- [{xmlelement, "identity", [{"category", "pubsub"}, {"type", "pep"}], []} | Acc];
- false -> Acc
+ true ->
+ [#xmlel{name = <<"identity">>,
+ attrs =
+ [{<<"category">>, <<"pubsub">>},
+ {<<"type">>, <<"pep">>}],
+ children = []}
+ | Acc];
+ false -> Acc
end;
disco_local_identity(Acc, _From, _To, _Node, _Lang) ->
Acc.
-disco_local_features(Acc, _From, To, [], _Lang) ->
+-spec(disco_local_features/5 ::
+(
+ Acc :: [xmlel()],
+ _From :: jid(),
+ To :: jid(),
+ NodeId :: <<>> | mod_pubsub:nodeId(),
+ Lang :: binary())
+ -> [binary(),...]
+).
+disco_local_features(Acc, _From, To, <<>>, _Lang) ->
Host = To#jid.lserver,
Feats = case Acc of
- {result, I} -> I;
- _ -> []
- end,
- {result, Feats ++ lists:map(fun(Feature) ->
- ?NS_PUBSUB++"#"++Feature
- end, features(Host, <<>>))};
+ {result, I} -> I;
+ _ -> []
+ end,
+ {result,
+ Feats ++
+ lists:map(fun (Feature) ->
+ <<(?NS_PUBSUB)/binary, "#", Feature/binary>>
+ end,
+ features(Host, <<>>))};
disco_local_features(Acc, _From, _To, _Node, _Lang) ->
Acc.
-disco_local_items(Acc, _From, _To, [], _Lang) ->
- Acc;
-disco_local_items(Acc, _From, _To, _Node, _Lang) ->
- Acc.
-
-disco_sm_identity(Acc, From, To, Node, Lang) when is_list(Node) ->
- disco_sm_identity(Acc, From, To, list_to_binary(Node), Lang);
+disco_local_items(Acc, _From, _To, <<>>, _Lang) -> Acc;
+disco_local_items(Acc, _From, _To, _Node, _Lang) -> Acc.
+
+%disco_sm_identity(Acc, From, To, Node, Lang)
+% when is_binary(Node) ->
+% disco_sm_identity(Acc, From, To, iolist_to_binary(Node),
+% Lang);
+-spec(disco_sm_identity/5 ::
+(
+ Acc :: empty | [xmlel()],
+ From :: jid(),
+ To :: jid(),
+ Node :: mod_pubsub:nodeId(),
+ Lang :: binary())
+ -> [xmlel()]
+).
disco_sm_identity(empty, From, To, Node, Lang) ->
disco_sm_identity([], From, To, Node, Lang);
disco_sm_identity(Acc, From, To, Node, _Lang) ->
- disco_identity(jlib:jid_tolower(jlib:jid_remove_resource(To)), Node, From) ++ Acc.
+ disco_identity(jlib:jid_tolower(jlib:jid_remove_resource(To)), Node, From)
+ ++ Acc.
disco_identity(_Host, <<>>, _From) ->
- [{xmlelement, "identity", [{"category", "pubsub"}, {"type", "pep"}], []}];
+ [#xmlel{name = <<"identity">>,
+ attrs =
+ [{<<"category">>, <<"pubsub">>},
+ {<<"type">>, <<"pep">>}],
+ children = []}];
disco_identity(Host, Node, From) ->
- Action = fun(#pubsub_node{id = Idx, type = Type, options = Options}) ->
- Owners = node_owners_call(Type, Idx),
- case get_allowed_items_call(Host, Idx, From, Type, Options, Owners) of
- {result, _} ->
- {result, [{xmlelement, "identity", [{"category", "pubsub"}, {"type", "pep"}], []},
- {xmlelement, "identity",
- [{"category", "pubsub"},
- {"type", "leaf"}
- | case get_option(Options, title) of
- false -> [];
- [Title] -> [{"name", Title}]
- end],
- []}]};
- _ -> {result, []}
- end
- end,
+ Action = fun (#pubsub_node{id = Idx, type = Type,
+ options = Options}) ->
+ Owners = node_owners_call(Type, Idx),
+ case get_allowed_items_call(Host, Idx, From, Type, Options, Owners) of
+ {result, _} ->
+ {result,
+ [#xmlel{name = <<"identity">>,
+ attrs =
+ [{<<"category">>, <<"pubsub">>},
+ {<<"type">>, <<"pep">>}],
+ children = []},
+ #xmlel{name = <<"identity">>,
+ attrs =
+ [{<<"category">>, <<"pubsub">>},
+ {<<"type">>, <<"leaf">>}
+ | case get_option(Options, title) of
+ false -> [];
+ [Title] -> [{<<"name">>, Title}]
+ end],
+ children = []}]};
+ _ -> {result, []}
+ end
+ end,
case transaction(Host, Node, Action, sync_dirty) of
- {result, {_, Result}} -> Result;
- _ -> []
+ {result, {_, Result}} -> Result;
+ _ -> []
end.
-disco_sm_features(Acc, From, To, Node, Lang) when is_list(Node) ->
- disco_sm_features(Acc, From, To, list_to_binary(Node), Lang);
+-spec(disco_sm_features/5 ::
+(
+ Acc :: empty | {result, Features::[Feature::binary()]},
+ From :: jid(),
+ To :: jid(),
+ Node :: mod_pubsub:nodeId(),
+ Lang :: binary())
+ -> {result, Features::[Feature::binary()]}
+).
+%disco_sm_features(Acc, From, To, Node, Lang)
+% when is_binary(Node) ->
+% disco_sm_features(Acc, From, To, iolist_to_binary(Node),
+% Lang);
disco_sm_features(empty, From, To, Node, Lang) ->
disco_sm_features({result, []}, From, To, Node, Lang);
disco_sm_features({result, OtherFeatures} = _Acc, From, To, Node, _Lang) ->
{result,
OtherFeatures ++
disco_features(jlib:jid_tolower(jlib:jid_remove_resource(To)), Node, From)};
-disco_sm_features(Acc, _From, _To, _Node, _Lang) ->
- Acc.
+disco_sm_features(Acc, _From, _To, _Node, _Lang) -> Acc.
disco_features(_Host, <<>>, _From) ->
- [?NS_PUBSUB
- | [?NS_PUBSUB++"#"++Feature || Feature <- features("pep")]];
+ [?NS_PUBSUB | [<<(?NS_PUBSUB)/binary, "#", Feature/binary>>
+ || Feature <- features(<<"pep">>)]];
disco_features(Host, Node, From) ->
- Action = fun(#pubsub_node{id = Idx, type = Type, options = Options}) ->
- Owners = node_owners_call(Type, Idx),
- case get_allowed_items_call(Host, Idx, From, Type, Options, Owners) of
- {result, _} ->
- {result, [?NS_PUBSUB
- | [?NS_PUBSUB ++ "#" ++ Feature || Feature <- features("pep")]]};
- _ -> {result, []}
- end
- end,
+ Action = fun (#pubsub_node{id = Idx, type = Type,
+ options = Options}) ->
+ Owners = node_owners_call(Type, Idx),
+ case get_allowed_items_call(Host, Idx, From, Type, Options, Owners) of
+ {result, _} ->
+ {result,
+ [?NS_PUBSUB | [<<(?NS_PUBSUB)/binary, "#",
+ Feature/binary>>
+ || Feature <- features(<<"pep">>)]]};
+ _ -> {result, []}
+ end
+ end,
case transaction(Host, Node, Action, sync_dirty) of
- {result, {_, Result}} -> Result;
- _ -> []
+ {result, {_, Result}} -> Result;
+ _ -> []
end.
-disco_sm_items(Acc, From, To, Node, Lang) when is_list(Node) ->
- disco_sm_items(Acc, From, To, list_to_binary(Node), Lang);
+-spec(disco_sm_items/5 ::
+(
+ Acc :: empty | {result, [xmlel()]},
+ From :: jid(),
+ To :: jid(),
+ Node :: mod_pubsub:nodeId(),
+ Lang :: binary())
+ -> {result, [xmlel()]}
+).
+%disco_sm_items(Acc, From, To, Node, Lang)
+% when is_binary(Node) ->
+% disco_sm_items(Acc, From, To, iolist_to_binary(Node),
+% Lang);
disco_sm_items(empty, From, To, Node, Lang) ->
disco_sm_items({result, []}, From, To, Node, Lang);
disco_sm_items({result, OtherItems}, From, To, Node, _Lang) ->
{result,
lists:usort(OtherItems ++
- disco_items(jlib:jid_tolower(jlib:jid_remove_resource(To)), Node, From))};
-disco_sm_items(Acc, _From, _To, _Node, _Lang) ->
- Acc.
-
+ disco_items(jlib:jid_tolower(jlib:jid_remove_resource(To)), Node, From))};
+disco_sm_items(Acc, _From, _To, _Node, _Lang) -> Acc.
+
+-spec(disco_items/3 ::
+(
+ Host :: mod_pubsub:host(),
+ Node :: mod_pubsub:nodeId(),
+ From :: jid())
+ -> [xmlel()]
+).
disco_items(Host, <<>>, From) ->
- Action = fun(#pubsub_node{nodeid ={_, NodeID}, options = Options, type = Type, id = Idx}, Acc) ->
- Owners = node_owners_call(Type, Idx),
- case get_allowed_items_call(Host, Idx, From, Type, Options, Owners) of
- {result, _} ->
- [{xmlelement, "item",
- [{"node", binary_to_list(NodeID)},
- {"jid", case Host of
- {_,_,_} -> jlib:jid_to_string(Host);
- _Host -> Host
- end}
- | case get_option(Options, title) of
- false -> [];
- [Title] -> [{"name", Title}]
- end],
- []}
+ Action = fun (#pubsub_node{nodeid = {_, NodeID},
+ options = Options, type = Type, id = Idx},
+ Acc) ->
+ Owners = node_owners_call(Type, Idx),
+ case get_allowed_items_call(Host, Idx, From, Type, Options, Owners) of
+ {result, _} ->
+ [#xmlel{name = <<"item">>,
+ attrs =
+ [{<<"node">>, (NodeID)},
+ {<<"jid">>,
+ case Host of
+ {_, _, _} ->
+ jlib:jid_to_string(Host);
+ _Host -> Host
+ end}
+ | case get_option(Options, title) of
+ false -> [];
+ [Title] -> [{<<"name">>, Title}]
+ end],
+ children = []}
| Acc];
- _ -> Acc
- end
- end,
+ _ -> Acc
+ end
+ end,
case transaction_on_nodes(Host, Action, sync_dirty) of
- {result, Items} -> Items;
- _ -> []
+ {result, Items} -> Items;
+ _ -> []
end;
-
disco_items(Host, Node, From) ->
- Action = fun(#pubsub_node{id = Idx, type = Type, options = Options}) ->
- Owners = node_owners_call(Type, Idx),
- case get_allowed_items_call(Host, Idx, From, Type, Options, Owners) of
- {result, Items} ->
- {result, [{xmlelement, "item",
- [{"jid", case Host of
- {_,_,_} -> jlib:jid_to_string(Host);
- _Host -> Host
- end},
- {"name", ItemID}], []}
- || #pubsub_item{itemid = {ItemID,_}} <- Items]};
- _ -> {result, []}
- end
- end,
+ Action = fun (#pubsub_node{id = Idx, type = Type,
+ options = Options}) ->
+ Owners = node_owners_call(Type, Idx),
+ case get_allowed_items_call(Host, Idx, From, Type,
+ Options, Owners)
+ of
+ {result, Items} ->
+ {result,
+ [#xmlel{name = <<"item">>,
+ attrs =
+ [{<<"jid">>,
+ case Host of
+ {_, _, _} ->
+ jlib:jid_to_string(Host);
+ _Host -> Host
+ end},
+ {<<"name">>, ItemID}],
+ children = []}
+ || #pubsub_item{itemid = {ItemID, _}} <- Items]};
+ _ -> {result, []}
+ end
+ end,
case transaction(Host, Node, Action, sync_dirty) of
- {result, {_, Result}} -> Result;
- _ -> []
+ {result, {_, Result}} -> Result;
+ _ -> []
end.
%% -------
@@ -540,36 +815,40 @@ caps_update(#jid{luser = U, lserver = S, lresource = R} = From, To, _Features) -
Pid = ejabberd_sm:get_session_pid(U, S, R),
presence_probe(From, To, Pid).
-presence_probe(#jid{luser = User, lserver = Server, lresource = Resource} = JID, JID, Pid) ->
- %%?DEBUG("presence probe self ~s@~s/~s ~s@~s/~s",[User,Server,Resource,element(2,JID),element(3,JID),element(4,JID)]),
+presence_probe(#jid{luser = User, lserver = Server, lresource = Resource} = JID,
+ JID, Pid) ->
presence(Server, {presence, JID, Pid}),
presence(Server, {presence, User, Server, [Resource], JID});
-presence_probe(#jid{luser = User, lserver = Server}, #jid{luser = User, lserver = Server}, _Pid) ->
+presence_probe(#jid{luser = User, lserver = Server},
+ #jid{luser = User, lserver = Server}, _Pid) ->
%% ignore presence_probe from other ressources for the current user
%% this way, we do not send duplicated last items if user already connected with other clients
ok;
-presence_probe(#jid{luser = User, lserver = Server, lresource = Resource}, #jid{lserver = Host} = JID, _Pid) ->
- %%?DEBUG("presence probe peer ~s@~s/~s ~s@~s/~s",[User,Server,Resource,element(2,JID),element(3,JID),element(4,JID)]),
+presence_probe(#jid{luser = User, lserver = Server, lresource = Resource},
+ #jid{lserver = Host} = JID, _Pid) ->
presence(Host, {presence, User, Server, [Resource], JID}).
presence(ServerHost, Presence) ->
- SendLoop = case whereis(gen_mod:get_module_proc(ServerHost, ?LOOPNAME)) of
- undefined ->
- % in case send_loop process died, we rebuild a minimal State record and respawn it
- Host = host(ServerHost),
- Plugins = plugins(Host),
- PepOffline = case catch ets:lookup(gen_mod:get_module_proc(ServerHost, config), ignore_pep_from_offline) of
- [{ignore_pep_from_offline, PO}] -> PO;
- _ -> true
- end,
- State = #state{host = Host,
- server_host = ServerHost,
- ignore_pep_from_offline = PepOffline,
- plugins = Plugins},
- init_send_loop(ServerHost, State);
- Pid ->
- Pid
- end,
+ SendLoop = case
+ whereis(gen_mod:get_module_proc(ServerHost, ?LOOPNAME))
+ of
+ undefined ->
+ Host = host(ServerHost),
+ Plugins = plugins(Host),
+ PepOffline = case catch
+ ets:lookup(gen_mod:get_module_proc(ServerHost,
+ config),
+ ignore_pep_from_offline)
+ of
+ [{ignore_pep_from_offline, PO}] -> PO;
+ _ -> true
+ end,
+ State = #state{host = Host, server_host = ServerHost,
+ ignore_pep_from_offline = PepOffline,
+ plugins = Plugins},
+ init_send_loop(ServerHost, State);
+ Pid -> Pid
+ end,
SendLoop ! Presence.
%% -------
@@ -577,46 +856,70 @@ presence(ServerHost, Presence) ->
%%
out_subscription(User, Server, JID, subscribed) ->
- Owner = jlib:make_jid(User, Server, ""),
+ Owner = jlib:make_jid(User, Server, <<"">>),
{PUser, PServer, PResource} = jlib:jid_tolower(JID),
PResources = case PResource of
- [] -> user_resources(PUser, PServer);
- _ -> [PResource]
- end,
- presence(Server, {presence, PUser, PServer, PResources, Owner}),
+ <<>> -> user_resources(PUser, PServer);
+ _ -> [PResource]
+ end,
+ presence(Server,
+ {presence, PUser, PServer, PResources, Owner}),
true;
-out_subscription(_,_,_,_) ->
- true.
-in_subscription(_, User, Server, Owner, unsubscribed, _) ->
- unsubscribe_user(jlib:make_jid(User, Server, ""), Owner),
+out_subscription(_, _, _, _) -> true.
+
+in_subscription(_, User, Server, Owner, unsubscribed,
+ _) ->
+ unsubscribe_user(jlib:make_jid(User, Server, <<"">>),
+ Owner),
true;
-in_subscription(_, _, _, _, _, _) ->
- true.
+in_subscription(_, _, _, _, _, _) -> true.
unsubscribe_user(Entity, Owner) ->
BJID = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
Host = host(element(2, BJID)),
- spawn(fun() ->
- lists:foreach(fun(PType) ->
- {result, Subscriptions} = node_action(Host, PType, get_entity_subscriptions, [Host, Entity]),
- lists:foreach(fun
- ({#pubsub_node{options = Options, id = NodeId}, subscribed, _, JID}) ->
- case get_option(Options, access_model) of
- presence ->
- case lists:member(BJID, node_owners(Host, PType, NodeId)) of
- true ->
- node_action(Host, PType, unsubscribe_node, [NodeId, Entity, JID, all]);
- false ->
- {result, ok}
- end;
- _ ->
- {result, ok}
- end;
- (_) ->
- ok
- end, Subscriptions)
- end, plugins(Host))
- end).
+ spawn(fun () ->
+ lists:foreach(fun (PType) ->
+ {result, Subscriptions} =
+ node_action(Host, PType,
+ get_entity_subscriptions,
+ [Host, Entity]),
+ lists:foreach(fun ({#pubsub_node{options
+ =
+ Options,
+ id =
+ NodeId},
+ subscribed, _,
+ JID}) ->
+ case
+ get_option(Options,
+ access_model)
+ of
+ presence ->
+ case
+ lists:member(BJID,
+ node_owners(Host, PType, NodeId))
+ of
+ true ->
+ node_action(Host,
+ PType,
+ unsubscribe_node,
+ [NodeId,
+ Entity,
+ JID,
+ all]);
+ false ->
+ {result,
+ ok}
+ end;
+ _ ->
+ {result, ok}
+ end;
+ (_) -> ok
+ end,
+ Subscriptions)
+ end,
+ plugins(Host))
+ end).
%% -------
%% user remove hook handling function
@@ -625,25 +928,8 @@ unsubscribe_user(Entity, Owner) ->
remove_user(User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
- Entity = jlib:make_jid(LUser, LServer, ""),
+ Entity = jlib:make_jid(LUser, LServer, <<"">>),
Host = host(LServer),
- HomeTreeBase = string_to_node("/home/"++LServer++"/"++LUser),
- spawn(fun() ->
- lists:foreach(fun(PType) ->
- {result, Subscriptions} = node_action(Host, PType, get_entity_subscriptions, [Host, Entity]),
- lists:foreach(fun
- ({#pubsub_node{id = NodeId}, _, _, JID}) -> node_action(Host, PType, unsubscribe_node, [NodeId, Entity, JID, all])
- end, Subscriptions),
- {result, Affiliations} = node_action(Host, PType, get_entity_affiliations, [Host, Entity]),
- lists:foreach(fun
- ({#pubsub_node{nodeid = {H, N}, parents = []}, owner}) -> delete_node(H, N, Entity);
- ({#pubsub_node{nodeid = {H, N}, type = "hometree"}, owner}) when N == HomeTreeBase -> delete_node(H, N, Entity);
- ({#pubsub_node{id = NodeId}, publisher}) -> node_action(Host, PType, set_affiliation, [NodeId, Entity, none]);
- (_) -> ok
- end, Affiliations)
- end, plugins(Host))
- end).
-
%%--------------------------------------------------------------------
%% Function:
%% handle_call(Request, From, State) -> {reply, Reply, State} |
@@ -655,6 +941,66 @@ remove_user(User, Server) ->
%% Description: Handling call messages
%%--------------------------------------------------------------------
%% @private
+ HomeTreeBase = <<"/home/", LServer/binary, "/", LUser/binary>>,
+ spawn(fun () ->
+ lists:foreach(fun (PType) ->
+ {result, Subscriptions} =
+ node_action(Host, PType,
+ get_entity_subscriptions,
+ [Host, Entity]),
+ lists:foreach(fun ({#pubsub_node{id =
+ NodeId},
+ _, _, JID}) ->
+ node_action(Host,
+ PType,
+ unsubscribe_node,
+ [NodeId,
+ Entity,
+ JID,
+ all])
+ end,
+ Subscriptions),
+ {result, Affiliations} =
+ node_action(Host, PType,
+ get_entity_affiliations,
+ [Host, Entity]),
+ lists:foreach(fun ({#pubsub_node{nodeid
+ =
+ {H,
+ N},
+ parents
+ =
+ []},
+ owner}) ->
+ delete_node(H, N,
+ Entity);
+ ({#pubsub_node{nodeid
+ =
+ {H,
+ N},
+ type =
+ <<"hometree">>},
+ owner})
+ when N ==
+ HomeTreeBase ->
+ delete_node(H, N,
+ Entity);
+ ({#pubsub_node{id =
+ NodeId},
+ publisher}) ->
+ node_action(Host,
+ PType,
+ set_affiliation,
+ [NodeId,
+ Entity,
+ none]);
+ (_) -> ok
+ end,
+ Affiliations)
+ end,
+ plugins(Host))
+ end).
+
handle_call(server_host, _From, State) ->
{reply, State#state.server_host, State};
handle_call(plugins, _From, State) ->
@@ -673,9 +1019,6 @@ handle_call(stop, _From, State) ->
%% Description: Handling cast messages
%%--------------------------------------------------------------------
%% @private
-handle_cast(_Msg, State) ->
- {noreply, State}.
-
%%--------------------------------------------------------------------
%% Function: handle_info(Info, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
@@ -683,18 +1026,26 @@ handle_cast(_Msg, State) ->
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------
%% @private
+handle_cast(_Msg, State) -> {noreply, State}.
+
+-spec(handle_info/2 ::
+(
+ _ :: {route, From::jid(), To::jid(), Packet::xmlel()},
+ State :: state())
+ -> {noreply, state()}
+).
+
handle_info({route, From, To, Packet},
- #state{server_host = ServerHost,
- access = Access,
- plugins = Plugins} = State) ->
- case catch do_route(ServerHost, Access, Plugins, To#jid.lserver, From, To, Packet) of
- {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]);
- _ -> ok
+ #state{server_host = ServerHost, access = Access,
+ plugins = Plugins} =
+ State) ->
+ case catch do_route(ServerHost, Access, Plugins,
+ To#jid.lserver, From, To, Packet)
+ of
+ {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]);
+ _ -> ok
end,
{noreply, State};
-handle_info(_Info, State) ->
- {noreply, State}.
-
%%--------------------------------------------------------------------
%% Function: terminate(Reason, State) -> void()
%% Description: This function is called by a gen_server when it is about to
@@ -703,529 +1054,762 @@ handle_info(_Info, State) ->
%% The return value is ignored.
%%--------------------------------------------------------------------
%% @private
-terminate(_Reason, #state{host = Host,
- server_host = ServerHost,
- nodetree = TreePlugin,
- plugins = Plugins}) ->
+handle_info(_Info, State) -> {noreply, State}.
+
+terminate(_Reason,
+ #state{host = Host, server_host = ServerHost,
+ nodetree = TreePlugin, plugins = Plugins}) ->
ejabberd_router:unregister_route(Host),
case lists:member(?PEPNODE, Plugins) of
- true ->
- ejabberd_hooks:delete(caps_update, ServerHost, ?MODULE, caps_update, 80),
- ejabberd_hooks:delete(disco_sm_identity, ServerHost, ?MODULE, disco_sm_identity, 75),
- ejabberd_hooks:delete(disco_sm_features, ServerHost, ?MODULE, disco_sm_features, 75),
- ejabberd_hooks:delete(disco_sm_items, ServerHost, ?MODULE, disco_sm_items, 75),
- gen_iq_handler:remove_iq_handler(ejabberd_sm, ServerHost, ?NS_PUBSUB),
- gen_iq_handler:remove_iq_handler(ejabberd_sm, ServerHost, ?NS_PUBSUB_OWNER);
- false ->
- ok
+ true ->
+ ejabberd_hooks:delete(caps_update, ServerHost, ?MODULE,
+ caps_update, 80),
+ ejabberd_hooks:delete(disco_sm_identity, ServerHost,
+ ?MODULE, disco_sm_identity, 75),
+ ejabberd_hooks:delete(disco_sm_features, ServerHost,
+ ?MODULE, disco_sm_features, 75),
+ ejabberd_hooks:delete(disco_sm_items, ServerHost,
+ ?MODULE, disco_sm_items, 75),
+ gen_iq_handler:remove_iq_handler(ejabberd_sm,
+ ServerHost, ?NS_PUBSUB),
+ gen_iq_handler:remove_iq_handler(ejabberd_sm,
+ ServerHost, ?NS_PUBSUB_OWNER);
+ false -> ok
end,
- ejabberd_hooks:delete(sm_remove_connection_hook, ServerHost, ?MODULE, on_user_offline, 75),
- ejabberd_hooks:delete(disco_local_identity, ServerHost, ?MODULE, disco_local_identity, 75),
- ejabberd_hooks:delete(disco_local_features, ServerHost, ?MODULE, disco_local_features, 75),
- ejabberd_hooks:delete(disco_local_items, ServerHost, ?MODULE, disco_local_items, 75),
- ejabberd_hooks:delete(presence_probe_hook, ServerHost, ?MODULE, presence_probe, 80),
- ejabberd_hooks:delete(roster_in_subscription, ServerHost, ?MODULE, in_subscription, 50),
- ejabberd_hooks:delete(roster_out_subscription, ServerHost, ?MODULE, out_subscription, 50),
- ejabberd_hooks:delete(remove_user, ServerHost, ?MODULE, remove_user, 50),
- ejabberd_hooks:delete(anonymous_purge_hook, ServerHost, ?MODULE, remove_user, 50),
+ ejabberd_hooks:delete(sm_remove_connection_hook,
+ ServerHost, ?MODULE, on_user_offline, 75),
+ ejabberd_hooks:delete(disco_local_identity, ServerHost,
+ ?MODULE, disco_local_identity, 75),
+ ejabberd_hooks:delete(disco_local_features, ServerHost,
+ ?MODULE, disco_local_features, 75),
+ ejabberd_hooks:delete(disco_local_items, ServerHost,
+ ?MODULE, disco_local_items, 75),
+ ejabberd_hooks:delete(presence_probe_hook, ServerHost,
+ ?MODULE, presence_probe, 80),
+ ejabberd_hooks:delete(roster_in_subscription,
+ ServerHost, ?MODULE, in_subscription, 50),
+ ejabberd_hooks:delete(roster_out_subscription,
+ ServerHost, ?MODULE, out_subscription, 50),
+ ejabberd_hooks:delete(remove_user, ServerHost, ?MODULE,
+ remove_user, 50),
+ ejabberd_hooks:delete(anonymous_purge_hook, ServerHost,
+ ?MODULE, remove_user, 50),
mod_disco:unregister_feature(ServerHost, ?NS_PUBSUB),
gen_mod:get_module_proc(ServerHost, ?LOOPNAME) ! stop,
- terminate_plugins(Host, ServerHost, Plugins, TreePlugin).
-
%%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
%% Description: Convert process state when code is changed
%%--------------------------------------------------------------------
%% @private
-code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
+ terminate_plugins(Host, ServerHost, Plugins,
+ TreePlugin).
+
+code_change(_OldVsn, State, _Extra) -> {ok, State}.
+
+-spec(do_route/7 ::
+(
+ ServerHost :: binary(),
+ Access :: atom(),
+ Plugins :: [binary(),...],
+ Host :: mod_pubsub:hostPubsub(),
+ From :: jid(),
+ To :: jid(),
+ Packet :: xmlel())
+ -> ok
+).
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
do_route(ServerHost, Access, Plugins, Host, From, To, Packet) ->
- {xmlelement, Name, Attrs, _Els} = Packet,
+ #xmlel{name = Name, attrs = Attrs} = Packet,
case To of
- #jid{luser = "", lresource = ""} ->
- case Name of
- "iq" ->
- case jlib:iq_query_info(Packet) of
- #iq{type = get, xmlns = ?NS_DISCO_INFO,
- sub_el = SubEl, lang = Lang} = IQ ->
- {xmlelement, _, QAttrs, _} = SubEl,
- Node = xml:get_attr_s("node", QAttrs),
- Info = ejabberd_hooks:run_fold(
- disco_info, ServerHost, [],
- [ServerHost, ?MODULE, "", ""]),
- Res = case iq_disco_info(Host, Node, From, Lang) of
- {result, IQRes} ->
- jlib:iq_to_xml(
- IQ#iq{type = result,
- sub_el = [{xmlelement, "query",
- QAttrs, IQRes++Info}]});
- {error, Error} ->
- jlib:make_error_reply(Packet, Error)
- end,
- ejabberd_router:route(To, From, Res);
- #iq{type = get, xmlns = ?NS_DISCO_ITEMS,
- sub_el = SubEl} = IQ ->
- {xmlelement, _, QAttrs, _} = SubEl,
- Node = xml:get_attr_s("node", QAttrs),
- Rsm = jlib:rsm_decode(IQ),
- Res = case iq_disco_items(Host, Node, From, Rsm) of
- {result, IQRes} ->
- jlib:iq_to_xml(
- IQ#iq{type = result,
- sub_el = [{xmlelement, "query",
- QAttrs, IQRes}]});
- {error, Error} ->
- jlib:make_error_reply(Packet, Error)
- end,
- ejabberd_router:route(To, From, Res);
- #iq{type = IQType, xmlns = ?NS_PUBSUB,
- lang = Lang, sub_el = SubEl} = IQ ->
- Res =
- case iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang, Access, Plugins) of
- {result, IQRes} ->
- jlib:iq_to_xml(
- IQ#iq{type = result,
- sub_el = IQRes});
- {error, Error} ->
- jlib:make_error_reply(Packet, Error)
- end,
- ejabberd_router:route(To, From, Res);
- #iq{type = IQType, xmlns = ?NS_PUBSUB_OWNER,
- lang = Lang, sub_el = SubEl} = IQ ->
- Res =
- case iq_pubsub_owner(Host, ServerHost, From, IQType, SubEl, Lang) of
- {result, IQRes} ->
- jlib:iq_to_xml(
- IQ#iq{type = result,
- sub_el = IQRes});
- {error, Error} ->
- jlib:make_error_reply(Packet, Error)
- end,
- ejabberd_router:route(To, From, Res);
- #iq{type = get, xmlns = ?NS_VCARD = XMLNS,
- lang = Lang, sub_el = _SubEl} = IQ ->
- Res = IQ#iq{type = result,
- sub_el = [{xmlelement, "vCard", [{"xmlns", XMLNS}],
- iq_get_vcard(Lang)}]},
- ejabberd_router:route(To, From, jlib:iq_to_xml(Res));
- #iq{type = set, xmlns = ?NS_COMMANDS} = IQ ->
- Res = case iq_command(Host, ServerHost, From, IQ, Access, Plugins) of
- {error, Error} ->
- jlib:make_error_reply(Packet, Error);
- {result, IQRes} ->
- jlib:iq_to_xml(IQ#iq{type = result,
- sub_el = IQRes})
- end,
- ejabberd_router:route(To, From, Res);
- #iq{} ->
- Err = jlib:make_error_reply(
- Packet,
- ?ERR_FEATURE_NOT_IMPLEMENTED),
- ejabberd_router:route(To, From, Err);
- _ ->
- ok
- end;
- "message" ->
- case xml:get_attr_s("type", Attrs) of
- "error" ->
- ok;
- _ ->
- case find_authorization_response(Packet) of
- none ->
- ok;
- invalid ->
- ejabberd_router:route(To, From,
- jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST));
- XFields ->
- handle_authorization_response(Host, From, To, Packet, XFields)
- end
- end;
- _ ->
- ok
- end;
- _ ->
- case xml:get_attr_s("type", Attrs) of
- "error" ->
- ok;
- "result" ->
- ok;
- _ ->
- Err = jlib:make_error_reply(Packet, ?ERR_ITEM_NOT_FOUND),
- ejabberd_router:route(To, From, Err)
- end
+ #jid{luser = <<"">>, lresource = <<"">>} ->
+ case Name of
+ <<"iq">> ->
+ case jlib:iq_query_info(Packet) of
+ #iq{type = get, xmlns = ?NS_DISCO_INFO, sub_el = SubEl,
+ lang = Lang} =
+ IQ ->
+ #xmlel{attrs = QAttrs} = SubEl,
+ Node = xml:get_attr_s(<<"node">>, QAttrs),
+ Info = ejabberd_hooks:run_fold(disco_info, ServerHost,
+ [],
+ [ServerHost, ?MODULE,
+ <<"">>, <<"">>]),
+ Res = case iq_disco_info(Host, Node, From, Lang) of
+ {result, IQRes} ->
+ jlib:iq_to_xml(IQ#iq{type = result,
+ sub_el =
+ [#xmlel{name =
+ <<"query">>,
+ attrs =
+ QAttrs,
+ children =
+ IQRes ++
+ Info}]});
+ {error, Error} ->
+ jlib:make_error_reply(Packet, Error)
+ end,
+ ejabberd_router:route(To, From, Res);
+ #iq{type = get, xmlns = ?NS_DISCO_ITEMS,
+ sub_el = SubEl} =
+ IQ ->
+ #xmlel{attrs = QAttrs} = SubEl,
+ Node = xml:get_attr_s(<<"node">>, QAttrs),
+ Rsm = jlib:rsm_decode(IQ),
+ Res = case iq_disco_items(Host, Node, From, Rsm) of
+ {result, IQRes} ->
+ jlib:iq_to_xml(IQ#iq{type = result,
+ sub_el =
+ [#xmlel{name =
+ <<"query">>,
+ attrs =
+ QAttrs,
+ children =
+ IQRes}]})
+% {error, Error} ->
+% jlib:make_error_reply(Packet, Error)
+ end,
+ ejabberd_router:route(To, From, Res);
+ #iq{type = IQType, xmlns = ?NS_PUBSUB, lang = Lang,
+ sub_el = SubEl} =
+ IQ ->
+ Res = case iq_pubsub(Host, ServerHost, From, IQType,
+ SubEl, Lang, Access, Plugins)
+ of
+ {result, IQRes} ->
+ jlib:iq_to_xml(IQ#iq{type = result,
+ sub_el = IQRes});
+ {error, Error} ->
+ jlib:make_error_reply(Packet, Error)
+ end,
+ ejabberd_router:route(To, From, Res);
+ #iq{type = IQType, xmlns = ?NS_PUBSUB_OWNER,
+ lang = Lang, sub_el = SubEl} =
+ IQ ->
+ Res = case iq_pubsub_owner(Host, ServerHost, From,
+ IQType, SubEl, Lang)
+ of
+ {result, IQRes} ->
+ jlib:iq_to_xml(IQ#iq{type = result,
+ sub_el = IQRes});
+ {error, Error} ->
+ jlib:make_error_reply(Packet, Error)
+ end,
+ ejabberd_router:route(To, From, Res);
+ #iq{type = get, xmlns = (?NS_VCARD) = XMLNS,
+ lang = Lang, sub_el = _SubEl} =
+ IQ ->
+ Res = IQ#iq{type = result,
+ sub_el =
+ [#xmlel{name = <<"vCard">>,
+ attrs = [{<<"xmlns">>, XMLNS}],
+ children = iq_get_vcard(Lang)}]},
+ ejabberd_router:route(To, From, jlib:iq_to_xml(Res));
+ #iq{type = set, xmlns = ?NS_COMMANDS} = IQ ->
+ Res = case iq_command(Host, ServerHost, From, IQ,
+ Access, Plugins)
+ of
+ {error, Error} ->
+ jlib:make_error_reply(Packet, Error);
+ {result, IQRes} ->
+ jlib:iq_to_xml(IQ#iq{type = result,
+ sub_el = IQRes})
+ end,
+ ejabberd_router:route(To, From, Res);
+ #iq{} ->
+ Err = jlib:make_error_reply(Packet,
+ ?ERR_FEATURE_NOT_IMPLEMENTED),
+ ejabberd_router:route(To, From, Err);
+ _ -> ok
+ end;
+ <<"message">> ->
+ case xml:get_attr_s(<<"type">>, Attrs) of
+ <<"error">> -> ok;
+ _ ->
+ case find_authorization_response(Packet) of
+ none -> ok;
+ invalid ->
+ ejabberd_router:route(To, From,
+ jlib:make_error_reply(Packet,
+ ?ERR_BAD_REQUEST));
+ XFields ->
+ handle_authorization_response(Host, From, To,
+ Packet, XFields)
+ end
+ end;
+ _ -> ok
+ end;
+ _ ->
+ case xml:get_attr_s(<<"type">>, Attrs) of
+ <<"error">> -> ok;
+ <<"result">> -> ok;
+ _ ->
+ Err = jlib:make_error_reply(Packet,
+ ?ERR_ITEM_NOT_FOUND),
+ ejabberd_router:route(To, From, Err)
+ end
end.
-command_disco_info(_Host, <<?NS_COMMANDS>>, _From) ->
- IdentityEl = {xmlelement, "identity", [{"category", "automation"}, {"type", "command-list"}], []},
+command_disco_info(_Host, ?NS_COMMANDS, _From) ->
+ IdentityEl = #xmlel{name = <<"identity">>,
+ attrs =
+ [{<<"category">>, <<"automation">>},
+ {<<"type">>, <<"command-list">>}],
+ children = []},
{result, [IdentityEl]};
-command_disco_info(_Host, <<?NS_PUBSUB_GET_PENDING>>, _From) ->
- IdentityEl = {xmlelement, "identity", [{"category", "automation"}, {"type", "command-node"}], []},
- FeaturesEl = {xmlelement, "feature", [{"var", ?NS_COMMANDS}], []},
+command_disco_info(_Host, ?NS_PUBSUB_GET_PENDING,
+ _From) ->
+ IdentityEl = #xmlel{name = <<"identity">>,
+ attrs =
+ [{<<"category">>, <<"automation">>},
+ {<<"type">>, <<"command-node">>}],
+ children = []},
+ FeaturesEl = #xmlel{name = <<"feature">>,
+ attrs = [{<<"var">>, ?NS_COMMANDS}], children = []},
{result, [IdentityEl, FeaturesEl]}.
node_disco_info(Host, Node, From) ->
node_disco_info(Host, Node, From, true, true).
-%node_disco_identity(Host, Node, From) ->
-% node_disco_info(Host, Node, From, true, false).
-%node_disco_features(Host, Node, From) ->
-% node_disco_info(Host, Node, From, false, true).
+
node_disco_info(Host, Node, From, Identity, Features) ->
- Action =
- fun(#pubsub_node{type = Type, id = NodeId}) ->
- I = case Identity of
- false ->
- [];
- true ->
- Types =
- case tree_call(Host, get_subnodes, [Host, Node, From]) of
- [] ->
- ["leaf"]; %% No sub-nodes: it's a leaf node
- _ ->
- case node_call(Type, get_items, [NodeId, From, none]) of
- {result, []} -> ["collection"];
- {result, _} -> ["leaf", "collection"];
- _ -> []
- end
- end,
- lists:map(fun(T) ->
- {xmlelement, "identity", [{"category", "pubsub"},
- {"type", T}], []}
- end, Types)
- end,
- F = case Features of
- false ->
- [];
- true ->
- [{xmlelement, "feature", [{"var", ?NS_PUBSUB}], []} |
- lists:map(fun
- ("rsm")-> {xmlelement, "feature", [{"var", ?NS_RSM}], []};
- (T) -> {xmlelement, "feature", [{"var", ?NS_PUBSUB++"#"++T}], []}
- end, features(Type))]
- end,
- %% TODO: add meta-data info (spec section 5.4)
- {result, I ++ F}
- end,
+% Action =
+% fun(#pubsub_node{type = Type, id = NodeId}) ->
+% I = case Identity of
+% false ->
+% [];
+% true ->
+% Types =
+% case tree_call(Host, get_subnodes, [Host, Node, From]) of
+% [] ->
+% [<<"leaf">>]; %% No sub-nodes: it's a leaf node
+% _ ->
+% case node_call(Type, get_items, [NodeId, From, none]) of
+% {result, []} -> [<<"collection">>];
+% {result, _} -> [<<"leaf">>, <<"collection">>];
+% _ -> []
+% end
+% end,
+% lists:map(fun(T) ->
+% #xmlel{name = <<"identity">>,
+% attrs =
+% [{<<"category">>,
+% <<"pubsub">>},
+% {<<"type">>, T}],
+% children = []}
+% end, Types)
+% end,
+% F = case Features of
+% false ->
+% [];
+% true ->
+% [#xmlel{name = <<"feature">>,
+% attrs = [{<<"var">>, ?NS_PUBSUB}],
+% children = []}
+% | lists:map(fun
+% (<<"rsm">>)->
+% #xmlel{name = <<"feature">>,
+% attrs = [{<<"var">>, ?NS_RSM}]};
+% (T) ->
+% #xmlel{name = <<"feature">>,
+% attrs =
+% [{<<"var">>,
+% <<(?NS_PUBSUB)/binary,
+% "#",
+% T/binary>>}],
+% children = []}
+% end,
+% features(Type))]
+% end,
+% %% TODO: add meta-data info (spec section 5.4)
+% {result, I ++ F}
+% end,
+% case transaction(Host, Node, Action, sync_dirty) of
+% {result, {_, Result}} -> {result, Result};
+% Other -> Other
+% end.
+ Action = fun (#pubsub_node{type = Type, id = NodeId}) ->
+ I = Types = case tree_call(Host, get_subnodes,
+ [Host, Node, From])
+ of
+ [] -> [<<"leaf">>];
+ _ ->
+ case node_call(Type, get_items,
+ [NodeId, From, none])
+ of
+ {result, []} ->
+ [<<"collection">>];
+ {result, _} ->
+ [<<"leaf">>,
+ <<"collection">>];
+ _ -> []
+ end
+ end,
+ lists:map(fun (T) ->
+ #xmlel{name = <<"identity">>,
+ attrs =
+ [{<<"category">>,
+ <<"pubsub">>},
+ {<<"type">>, T}],
+ children = []}
+ end,
+ Types),
+ F = [#xmlel{name = <<"feature">>,
+ attrs = [{<<"var">>, ?NS_PUBSUB}],
+ children = []}
+ | lists:map(fun
+ (<<"rsm">>)->
+ #xmlel{name = <<"feature">>,
+ attrs = [{<<"var">>, ?NS_RSM}]};
+ (T) ->
+ #xmlel{name = <<"feature">>,
+ attrs =
+ [{<<"var">>,
+ <<(?NS_PUBSUB)/binary,
+ "#",
+ T/binary>>}],
+ children = []}
+ end,
+ features(Type))],
+ {result, I ++ F}
+ end,
case transaction(Host, Node, Action, sync_dirty) of
- {result, {_, Result}} -> {result, Result};
- Other -> Other
+ {result, {_, Result}} -> {result, Result};
+ Other -> Other
end.
iq_disco_info(Host, SNode, From, Lang) ->
- [RealSNode|_] = case SNode of
- [] -> [[]];
- _ -> string:tokens(SNode, "!")
- end,
- Node = string_to_node(RealSNode),
+ [Node | _] = case SNode of
+ <<>> -> [<<>>];
+ _ -> str:tokens(SNode, <<"!">>)
+ end,
+ % Node = string_to_node(RealSNode),
case Node of
- <<>> ->
- {result,
- [{xmlelement, "identity",
- [{"category", "pubsub"},
- {"type", "service"},
- {"name", translate:translate(Lang, "Publish-Subscribe")}], []},
- {xmlelement, "feature", [{"var", ?NS_DISCO_INFO}], []},
- {xmlelement, "feature", [{"var", ?NS_DISCO_ITEMS}], []},
- {xmlelement, "feature", [{"var", ?NS_PUBSUB}], []},
- {xmlelement, "feature", [{"var", ?NS_COMMANDS}], []},
- {xmlelement, "feature", [{"var", ?NS_VCARD}], []}] ++
+ <<>> ->
+ {result,
+ [#xmlel{name = <<"identity">>,
+ attrs =
+ [{<<"category">>, <<"pubsub">>},
+ {<<"type">>, <<"service">>},
+ {<<"name">>,
+ translate:translate(Lang, <<"Publish-Subscribe">>)}],
+ children = []},
+ #xmlel{name = <<"feature">>,
+ attrs = [{<<"var">>, ?NS_DISCO_INFO}], children = []},
+ #xmlel{name = <<"feature">>,
+ attrs = [{<<"var">>, ?NS_DISCO_ITEMS}], children = []},
+ #xmlel{name = <<"feature">>,
+ attrs = [{<<"var">>, ?NS_PUBSUB}], children = []},
+ #xmlel{name = <<"feature">>,
+ attrs = [{<<"var">>, ?NS_COMMANDS}], children = []},
+ #xmlel{name = <<"feature">>,
+ attrs = [{<<"var">>, ?NS_VCARD}], children = []}]
+ ++
lists:map(fun
- ("rsm")-> {xmlelement, "feature", [{"var", ?NS_RSM}], []};
- (T) -> {xmlelement, "feature", [{"var", ?NS_PUBSUB++"#"++T}], []}
- end, features(Host, Node))};
- <<?NS_COMMANDS>> ->
- command_disco_info(Host, Node, From);
- <<?NS_PUBSUB_GET_PENDING>> ->
- command_disco_info(Host, Node, From);
- _ ->
- node_disco_info(Host, Node, From)
- end.
-
-iq_disco_items(Host, [], From, _RSM) ->
- case tree_action(Host, get_subnodes, [Host, <<>>, From]) of
- Nodes when is_list(Nodes) ->
- {result, lists:map(
- fun(#pubsub_node{nodeid = {_, SubNode}, options = Options}) ->
- Attrs =
- case get_option(Options, title) of
- false ->
- [{"jid", Host} |nodeAttr(SubNode)];
- Title ->
- [{"jid", Host}, {"name", Title}|nodeAttr(SubNode)]
- end,
- {xmlelement, "item", Attrs, []}
- end, Nodes)};
- Other ->
- Other
- end;
+ (<<"rsm">>)->
+ #xmlel{name = <<"feature">>,
+ attrs = [{<<"var">>, ?NS_RSM}]};
+ (Feature) ->
+ #xmlel{name = <<"feature">>,
+ attrs =
+ [{<<"var">>, <<(?NS_PUBSUB)/binary, "#", Feature/binary>>}],
+ children = []}
+ end,
+ features(Host, Node))};
+ ?NS_COMMANDS -> command_disco_info(Host, Node, From);
+ ?NS_PUBSUB_GET_PENDING ->
+ command_disco_info(Host, Node, From);
+ _ -> node_disco_info(Host, Node, From)
+ end.
+
+-spec(iq_disco_items/4 ::
+(
+ Host :: mod_pubsub:host(),
+ NodeId :: <<>> | mod_pubsub:nodeId(),
+ From :: jid(),
+ Rsm :: any())
+ -> {result, [xmlel()]}
+).
+iq_disco_items(Host, <<>>, From, _RSM) ->
+ {result,
+ lists:map(fun (#pubsub_node{nodeid = {_, SubNode},
+ options = Options}) ->
+ Attrs = case get_option(Options, title) of
+ false ->
+ [{<<"jid">>, Host}
+ | nodeAttr(SubNode)];
+ Title ->
+ [{<<"jid">>, Host},
+ {<<"name">>, Title}
+ | nodeAttr(SubNode)]
+ end,
+ #xmlel{name = <<"item">>, attrs = Attrs,
+ children = []}
+ end,
+ tree_action(Host, get_subnodes, [Host, <<>>, From]))};
+% case tree_action(Host, get_subnodes, [Host, <<>>, From]) of
+% Nodes when is_list(Nodes) ->
+% {result,
+% lists:map(fun (#pubsub_node{nodeid = {_, SubNode},
+% options = Options}) ->
+% Attrs = case get_option(Options, title) of
+% false ->
+% [{<<"jid">>, Host}
+% | nodeAttr(SubNode)];
+% Title ->
+% [{<<"jid">>, Host},
+% {<<"name">>, Title}
+% | nodeAttr(SubNode)]
+% end,
+% #xmlel{name = <<"item">>, attrs = Attrs,
+% children = []}
+% end,
+% Nodes)};
+% Other -> Other
+% end;
iq_disco_items(Host, ?NS_COMMANDS, _From, _RSM) ->
- %% TODO: support localization of this string
- CommandItems = [{xmlelement, "item", [{"jid", Host}, {"node", ?NS_PUBSUB_GET_PENDING}, {"name", "Get Pending"}], []}],
+ CommandItems = [#xmlel{name = <<"item">>,
+ attrs =
+ [{<<"jid">>, Host},
+ {<<"node">>, ?NS_PUBSUB_GET_PENDING},
+ {<<"name">>, <<"Get Pending">>}],
+ children = []}],
{result, CommandItems};
iq_disco_items(_Host, ?NS_PUBSUB_GET_PENDING, _From, _RSM) ->
- CommandItems = [],
- {result, CommandItems};
+ CommandItems = [], {result, CommandItems};
iq_disco_items(Host, Item, From, RSM) ->
- case string:tokens(Item, "!") of
- [_SNode, _ItemID] ->
- {result, []};
- [SNode] ->
- Node = string_to_node(SNode),
- Action = fun(#pubsub_node{id = Idx, type = Type, options = Options}) ->
- Owners = node_owners_call(Type, Idx),
- {NodeItems, RsmOut} = case get_allowed_items_call(Host, Idx, From, Type, Options, Owners, RSM) of
- {result, R} -> R;
- _ -> {[], none}
- end,
- Nodes = lists:map(
- fun(#pubsub_node{nodeid = {_, SubNode}, options = SubOptions}) ->
- Attrs =
- case get_option(SubOptions, title) of
- false ->
- [{"jid", Host} |nodeAttr(SubNode)];
- Title ->
- [{"jid", Host}, {"name", Title}|nodeAttr(SubNode)]
- end,
- {xmlelement, "item", Attrs, []}
- end, tree_call(Host, get_subnodes, [Host, Node, From])),
- Items = lists:map(
- fun(#pubsub_item{itemid = {RN, _}}) ->
- {result, Name} = node_call(Type, get_item_name, [Host, Node, RN]),
- {xmlelement, "item", [{"jid", Host}, {"name", Name}], []}
- end, NodeItems),
- {result, Nodes ++ Items ++ jlib:rsm_encode(RsmOut)}
- end,
- case transaction(Host, Node, Action, sync_dirty) of
- {result, {_, Result}} -> {result, Result};
- Other -> Other
- end
+ case str:tokens(Item, <<"!">>) of
+ [_Node, _ItemID] -> {result, []};
+ [Node] ->
+% Node = string_to_node(SNode),
+ Action = fun (#pubsub_node{id = Idx, type = Type,
+ options = Options}) ->
+ Owners = node_owners_call(Type, Idx),
+ {NodeItems, RsmOut} = case get_allowed_items_call(Host, Idx, From, Type, Options, Owners, RSM) of
+ {result, R} -> R;
+ _ -> {[], none}
+ end,
+ Nodes = lists:map(fun (#pubsub_node{nodeid =
+ {_, SubNode},
+ options =
+ SubOptions}) ->
+ Attrs = case
+ get_option(SubOptions,
+ title)
+ of
+ false ->
+ [{<<"jid">>,
+ Host}
+ | nodeAttr(SubNode)];
+ Title ->
+ [{<<"jid">>,
+ Host},
+ {<<"name">>,
+ Title}
+ | nodeAttr(SubNode)]
+ end,
+ #xmlel{name = <<"item">>,
+ attrs = Attrs,
+ children = []}
+ end,
+ tree_call(Host, get_subnodes,
+ [Host, Node, From])),
+ Items = lists:map(fun (#pubsub_item{itemid =
+ {RN, _}}) ->
+ {result, Name} =
+ node_call(Type,
+ get_item_name,
+ [Host, Node,
+ RN]),
+ #xmlel{name = <<"item">>,
+ attrs =
+ [{<<"jid">>,
+ Host},
+ {<<"name">>,
+ Name}],
+ children = []}
+ end,
+ NodeItems),
+ {result, Nodes ++ Items ++ jlib:rsm_encode(RsmOut)}
+ end,
+ case transaction(Host, Node, Action, sync_dirty) of
+ {result, {_, Result}} -> {result, Result};
+ Other -> Other
+ end
end.
+-spec(iq_sm/3 ::
+(
+ From :: jid(),
+ To :: jid(),
+ IQ :: iq_request())
+ -> iq_result() | iq_error()
+).
iq_sm(From, To, #iq{type = Type, sub_el = SubEl, xmlns = XMLNS, lang = Lang} = IQ) ->
ServerHost = To#jid.lserver,
LOwner = jlib:jid_tolower(jlib:jid_remove_resource(To)),
Res = case XMLNS of
- ?NS_PUBSUB -> iq_pubsub(LOwner, ServerHost, From, Type, SubEl, Lang);
- ?NS_PUBSUB_OWNER -> iq_pubsub_owner(LOwner, ServerHost, From, Type, SubEl, Lang)
+ ?NS_PUBSUB ->
+ iq_pubsub(LOwner, ServerHost, From, Type, SubEl, Lang);
+ ?NS_PUBSUB_OWNER ->
+ iq_pubsub_owner(LOwner, ServerHost, From, Type, SubEl,
+ Lang)
end,
case Res of
- {result, IQRes} -> IQ#iq{type = result, sub_el = IQRes};
- {error, Error} -> IQ#iq{type = error, sub_el = [Error, SubEl]}
+ {result, IQRes} -> IQ#iq{type = result, sub_el = IQRes};
+ {error, Error} ->
+ IQ#iq{type = error, sub_el = [Error, SubEl]}
end.
iq_get_vcard(Lang) ->
- [{xmlelement, "FN", [], [{xmlcdata, "ejabberd/mod_pubsub"}]},
- {xmlelement, "URL", [], [{xmlcdata, ?EJABBERD_URI}]},
- {xmlelement, "DESC", [],
- [{xmlcdata,
- translate:translate(Lang,
- "ejabberd Publish-Subscribe module") ++
- "\nCopyright (c) 2004-2013 ProcessOne"}]}].
+ [#xmlel{name = <<"FN">>, attrs = [],
+ children = [{xmlcdata, <<"ejabberd/mod_pubsub">>}]},
+ #xmlel{name = <<"URL">>, attrs = [],
+ children = [{xmlcdata, ?EJABBERD_URI}]},
+ #xmlel{name = <<"DESC">>, attrs = [],
+ children =
+ [{xmlcdata,
+ <<(translate:translate(Lang,
+ <<"ejabberd Publish-Subscribe module">>))/binary,
+ "\nCopyright (c) 2004-2013 ProcessOne">>}]}].
+
+-spec(iq_pubsub/6 ::
+(
+ Host :: mod_pubsub:host(),
+ ServerHost :: binary(),
+ From :: jid(),
+ IQType :: 'get' | 'set',
+ SubEl :: xmlel(),
+ Lang :: binary())
+ -> {result, [xmlel()]}
+ %%%
+ | {error, xmlel()}
+).
iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang) ->
iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang, all, plugins(ServerHost)).
+-spec(iq_pubsub/8 ::
+(
+ Host :: mod_pubsub:host(),
+ ServerHost :: binary(),
+ From :: jid(),
+ IQType :: 'get' | 'set',
+ SubEl :: xmlel(),
+ Lang :: binary(),
+ Access :: atom(),
+ Plugins :: [binary(),...])
+ -> {result, [xmlel()]}
+ %%%
+ | {error, xmlel()}
+).
+
iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang, Access, Plugins) ->
- {xmlelement, _, _, SubEls} = SubEl,
+ #xmlel{children = SubEls} = SubEl,
case xml:remove_cdata(SubEls) of
- [{xmlelement, Name, Attrs, Els} | Rest] ->
- Node = string_to_node(xml:get_attr_s("node", Attrs)),
- case {IQType, Name} of
- {set, "create"} ->
- Config = case Rest of
- [{xmlelement, "configure", _, C}] -> C;
- _ -> []
- end,
- %% Get the type of the node
- Type = case xml:get_attr_s("type", Attrs) of
- [] -> hd(Plugins);
- T -> T
- end,
- %% we use Plugins list matching because we do not want to allocate
- %% atoms for non existing type, this prevent atom allocation overflow
- case lists:member(Type, Plugins) of
- false ->
- {error, extended_error(
- ?ERR_FEATURE_NOT_IMPLEMENTED,
- unsupported, "create-nodes")};
- true ->
- create_node(Host, ServerHost, Node, From,
- Type, Access, Config)
- end;
- {set, "publish"} ->
- case xml:remove_cdata(Els) of
- [{xmlelement, "item", ItemAttrs, Payload}] ->
- ItemId = xml:get_attr_s("id", ItemAttrs),
- publish_item(Host, ServerHost, Node, From, ItemId, Payload);
- [] ->
- %% Publisher attempts to publish to persistent node with no item
- {error, extended_error(?ERR_BAD_REQUEST,
- "item-required")};
- _ ->
- %% Entity attempts to publish item with multiple payload elements or namespace does not match
- {error, extended_error(?ERR_BAD_REQUEST,
- "invalid-payload")}
- end;
- {set, "retract"} ->
- ForceNotify = case xml:get_attr_s("notify", Attrs) of
- "1" -> true;
- "true" -> true;
- _ -> false
- end,
- case xml:remove_cdata(Els) of
- [{xmlelement, "item", ItemAttrs, _}] ->
- ItemId = xml:get_attr_s("id", ItemAttrs),
- delete_item(Host, Node, From, ItemId, ForceNotify);
- _ ->
- %% Request does not specify an item
- {error, extended_error(?ERR_BAD_REQUEST,
- "item-required")}
- end;
- {set, "subscribe"} ->
- Config = case Rest of
- [{xmlelement, "options", _, C}] -> C;
- _ -> []
- end,
- JID = xml:get_attr_s("jid", Attrs),
- subscribe_node(Host, Node, From, JID, Config);
- {set, "unsubscribe"} ->
- JID = xml:get_attr_s("jid", Attrs),
- SubId = xml:get_attr_s("subid", Attrs),
- unsubscribe_node(Host, Node, From, JID, SubId);
- {get, "items"} ->
- MaxItems = xml:get_attr_s("max_items", Attrs),
- SubId = xml:get_attr_s("subid", Attrs),
- ItemIDs = lists:foldl(fun
- ({xmlelement, "item", ItemAttrs, _}, Acc) ->
- case xml:get_attr_s("id", ItemAttrs) of
- "" -> Acc;
- ItemID -> [ItemID|Acc]
- end;
- (_, Acc) ->
- Acc
- end, [], xml:remove_cdata(Els)),
- RSM = jlib:rsm_decode(SubEl),
- get_items(Host, Node, From, SubId, MaxItems, ItemIDs, RSM);
- {get, "subscriptions"} ->
- get_subscriptions(Host, Node, From, Plugins);
- {get, "affiliations"} ->
- get_affiliations(Host, Node, From, Plugins);
- {get, "options"} ->
- SubID = xml:get_attr_s("subid", Attrs),
- JID = xml:get_attr_s("jid", Attrs),
- get_options(Host, Node, JID, SubID, Lang);
- {set, "options"} ->
- SubID = xml:get_attr_s("subid", Attrs),
- JID = xml:get_attr_s("jid", Attrs),
- set_options(Host, Node, JID, SubID, Els);
- _ ->
- {error, ?ERR_FEATURE_NOT_IMPLEMENTED}
- end;
- Other ->
- ?INFO_MSG("Too many actions: ~p", [Other]),
- {error, ?ERR_BAD_REQUEST}
+ [#xmlel{name = Name, attrs = Attrs, children = Els} | Rest] ->
+ Node = xml:get_attr_s(<<"node">>, Attrs),
+ case {IQType, Name} of
+ {set, <<"create">>} ->
+ Config = case Rest of
+ [#xmlel{name = <<"configure">>, children = C}] -> C;
+ _ -> []
+ end,
+ Type = case xml:get_attr_s(<<"type">>, Attrs) of
+ <<>> -> hd(Plugins);
+ T -> T
+ end,
+ case lists:member(Type, Plugins) of
+ false ->
+ {error,
+ extended_error(?ERR_FEATURE_NOT_IMPLEMENTED,
+ unsupported, <<"create-nodes">>)};
+ true ->
+ create_node(Host, ServerHost, Node, From, Type, Access, Config)
+ end;
+ {set, <<"publish">>} ->
+ case xml:remove_cdata(Els) of
+ [#xmlel{name = <<"item">>, attrs = ItemAttrs,
+ children = Payload}] ->
+ ItemId = xml:get_attr_s(<<"id">>, ItemAttrs),
+ publish_item(Host, ServerHost, Node, From, ItemId, Payload);
+ [] ->
+ {error,
+ extended_error(?ERR_BAD_REQUEST, <<"item-required">>)};
+ _ ->
+ {error,
+ extended_error(?ERR_BAD_REQUEST, <<"invalid-payload">>)}
+ end;
+ {set, <<"retract">>} ->
+ ForceNotify = case xml:get_attr_s(<<"notify">>, Attrs)
+ of
+ <<"1">> -> true;
+ <<"true">> -> true;
+ _ -> false
+ end,
+ case xml:remove_cdata(Els) of
+ [#xmlel{name = <<"item">>, attrs = ItemAttrs}] ->
+ ItemId = xml:get_attr_s(<<"id">>, ItemAttrs),
+ delete_item(Host, Node, From, ItemId, ForceNotify);
+ _ ->
+ {error,
+ extended_error(?ERR_BAD_REQUEST, <<"item-required">>)}
+ end;
+ {set, <<"subscribe">>} ->
+ Config = case Rest of
+ [#xmlel{name = <<"options">>, children = C}] -> C;
+ _ -> []
+ end,
+ JID = xml:get_attr_s(<<"jid">>, Attrs),
+ subscribe_node(Host, Node, From, JID, Config);
+ {set, <<"unsubscribe">>} ->
+ JID = xml:get_attr_s(<<"jid">>, Attrs),
+ SubId = xml:get_attr_s(<<"subid">>, Attrs),
+ unsubscribe_node(Host, Node, From, JID, SubId);
+ {get, <<"items">>} ->
+ MaxItems = xml:get_attr_s(<<"max_items">>, Attrs),
+ SubId = xml:get_attr_s(<<"subid">>, Attrs),
+ ItemIDs = lists:foldl(fun (#xmlel{name = <<"item">>,
+ attrs = ItemAttrs},
+ Acc) ->
+ case xml:get_attr_s(<<"id">>,
+ ItemAttrs)
+ of
+ <<"">> -> Acc;
+ ItemID -> [ItemID | Acc]
+ end;
+ (_, Acc) -> Acc
+ end,
+ [], xml:remove_cdata(Els)),
+ RSM = jlib:rsm_decode(SubEl),
+ get_items(Host, Node, From, SubId, MaxItems, ItemIDs, RSM);
+ {get, <<"subscriptions">>} ->
+ get_subscriptions(Host, Node, From, Plugins);
+ {get, <<"affiliations">>} ->
+ get_affiliations(Host, Node, From, Plugins);
+ {get, <<"options">>} ->
+ SubID = xml:get_attr_s(<<"subid">>, Attrs),
+ JID = xml:get_attr_s(<<"jid">>, Attrs),
+ get_options(Host, Node, JID, SubID, Lang);
+ {set, <<"options">>} ->
+ SubID = xml:get_attr_s(<<"subid">>, Attrs),
+ JID = xml:get_attr_s(<<"jid">>, Attrs),
+ set_options(Host, Node, JID, SubID, Els);
+ _ -> {error, ?ERR_FEATURE_NOT_IMPLEMENTED}
+ end;
+ Other ->
+ ?INFO_MSG("Too many actions: ~p", [Other]),
+ {error, ?ERR_BAD_REQUEST}
end.
+
+-spec(iq_pubsub_owner/6 ::
+(
+ Host :: mod_pubsub:host(),
+ ServerHost :: binary(),
+ From :: jid(),
+ IQType :: 'get' | 'set',
+ SubEl :: xmlel(),
+ Lang :: binary())
+ -> {result, [xmlel()]}
+ %%%
+ | {error, xmlel()}
+).
iq_pubsub_owner(Host, ServerHost, From, IQType, SubEl, Lang) ->
- {xmlelement, _, _, SubEls} = SubEl,
- Action = lists:filter(fun({xmlelement, "set", _, _}) -> false;
- (_) -> true
- end, xml:remove_cdata(SubEls)),
+ #xmlel{children = SubEls} = SubEl,
+ Action = lists:filter(fun(#xmlel{name = <<"set">>, _ = '_'}) -> false;
+ (_) -> true
+ end, xml:remove_cdata(SubEls)),
case Action of
- [{xmlelement, Name, Attrs, Els}] ->
- Node = string_to_node(xml:get_attr_s("node", Attrs)),
- case {IQType, Name} of
- {get, "configure"} ->
- get_configure(Host, ServerHost, Node, From, Lang);
- {set, "configure"} ->
- set_configure(Host, Node, From, Els, Lang);
- {get, "default"} ->
- get_default(Host, Node, From, Lang);
- {set, "delete"} ->
- delete_node(Host, Node, From);
- {set, "purge"} ->
- purge_node(Host, Node, From);
- {get, "subscriptions"} ->
- get_subscriptions(Host, Node, From);
- {set, "subscriptions"} ->
- set_subscriptions(Host, Node, From, xml:remove_cdata(Els));
- {get, "affiliations"} ->
- get_affiliations(Host, Node, From);
- {set, "affiliations"} ->
- set_affiliations(Host, Node, From, xml:remove_cdata(Els));
- _ ->
- {error, ?ERR_FEATURE_NOT_IMPLEMENTED}
- end;
- _ ->
- ?INFO_MSG("Too many actions: ~p", [Action]),
- {error, ?ERR_BAD_REQUEST}
+ [#xmlel{name = Name, attrs = Attrs, children = Els}] ->
+ Node = xml:get_attr_s(<<"node">>, Attrs),
+ case {IQType, Name} of
+ {get, <<"configure">>} ->
+ get_configure(Host, ServerHost, Node, From, Lang);
+ {set, <<"configure">>} ->
+ set_configure(Host, Node, From, Els, Lang);
+ {get, <<"default">>} ->
+ get_default(Host, Node, From, Lang);
+ {set, <<"delete">>} -> delete_node(Host, Node, From);
+ {set, <<"purge">>} -> purge_node(Host, Node, From);
+ {get, <<"subscriptions">>} ->
+ get_subscriptions(Host, Node, From);
+ {set, <<"subscriptions">>} ->
+ set_subscriptions(Host, Node, From,
+ xml:remove_cdata(Els));
+ {get, <<"affiliations">>} ->
+ get_affiliations(Host, Node, From);
+ {set, <<"affiliations">>} ->
+ set_affiliations(Host, Node, From, xml:remove_cdata(Els));
+ _ -> {error, ?ERR_FEATURE_NOT_IMPLEMENTED}
+ end;
+ _ ->
+ ?INFO_MSG("Too many actions: ~p", [Action]),
+ {error, ?ERR_BAD_REQUEST}
end.
iq_command(Host, ServerHost, From, IQ, Access, Plugins) ->
case adhoc:parse_request(IQ) of
- Req when is_record(Req, adhoc_request) ->
- case adhoc_request(Host, ServerHost, From, Req, Access, Plugins) of
- Resp when is_record(Resp, adhoc_response) ->
- {result, [adhoc:produce_response(Req, Resp)]};
- Error ->
- Error
- end;
- Err ->
- Err
+ Req when is_record(Req, adhoc_request) ->
+ case adhoc_request(Host, ServerHost, From, Req, Access,
+ Plugins)
+ of
+ Resp when is_record(Resp, adhoc_response) ->
+ {result, [adhoc:produce_response(Req, Resp)]};
+ Error -> Error
+ end;
+ Err -> Err
end.
%% @doc <p>Processes an Ad Hoc Command.</p>
adhoc_request(Host, _ServerHost, Owner,
- #adhoc_request{node = ?NS_PUBSUB_GET_PENDING,
- lang = Lang,
- action = "execute",
- xdata = false},
- _Access, Plugins) ->
+ #adhoc_request{node = ?NS_PUBSUB_GET_PENDING,
+ lang = Lang, action = <<"execute">>,
+ xdata = false},
+ _Access, Plugins) ->
send_pending_node_form(Host, Owner, Lang, Plugins);
adhoc_request(Host, _ServerHost, Owner,
- #adhoc_request{node = ?NS_PUBSUB_GET_PENDING,
- action = "execute",
- xdata = XData},
- _Access, _Plugins) ->
+ #adhoc_request{node = ?NS_PUBSUB_GET_PENDING,
+ action = <<"execute">>, xdata = XData},
+ _Access, _Plugins) ->
ParseOptions = case XData of
- {xmlelement, "x", _Attrs, _SubEls} = XEl ->
- case jlib:parse_xdata_submit(XEl) of
- invalid ->
- {error, ?ERR_BAD_REQUEST};
- XData2 ->
- case set_xoption(Host, XData2, []) of
- NewOpts when is_list(NewOpts) ->
- {result, NewOpts};
- Err ->
- Err
- end
- end;
- _ ->
- ?INFO_MSG("Bad XForm: ~p", [XData]),
- {error, ?ERR_BAD_REQUEST}
+ #xmlel{name = <<"x">>} = XEl ->
+ case jlib:parse_xdata_submit(XEl) of
+ invalid -> {error, ?ERR_BAD_REQUEST};
+ XData2 ->
+ case set_xoption(Host, XData2, []) of
+ NewOpts when is_list(NewOpts) ->
+ {result, NewOpts};
+ Err -> Err
+ end
+ end;
+ _ ->
+ ?INFO_MSG("Bad XForm: ~p", [XData]),
+ {error, ?ERR_BAD_REQUEST}
end,
case ParseOptions of
- {result, XForm} ->
- case lists:keysearch(node, 1, XForm) of
- {value, {_, Node}} ->
- send_pending_auth_events(Host, Node, Owner);
- false ->
- {error, extended_error(?ERR_BAD_REQUEST, "bad-payload")}
- end;
- Error ->
- Error
+ {result, XForm} ->
+ case lists:keysearch(node, 1, XForm) of
+ {value, {_, Node}} ->
+ send_pending_auth_events(Host, Node, Owner);
+ false ->
+ {error,
+ extended_error(?ERR_BAD_REQUEST, <<"bad-payload">>)}
+ end;
+ Error -> Error
end;
-adhoc_request(_Host, _ServerHost, _Owner, #adhoc_request{action = "cancel"},
- _Access, _Plugins) ->
+adhoc_request(_Host, _ServerHost, _Owner,
+ #adhoc_request{action = <<"cancel">>}, _Access,
+ _Plugins) ->
#adhoc_response{status = canceled};
-adhoc_request(Host, ServerHost, Owner, #adhoc_request{action = []} = R,
- Access, Plugins) ->
- adhoc_request(Host, ServerHost, Owner, R#adhoc_request{action = "execute"},
- Access, Plugins);
-adhoc_request(_Host, _ServerHost, _Owner, Other, _Access, _Plugins) ->
+adhoc_request(Host, ServerHost, Owner,
+ #adhoc_request{action = <<>>} = R, Access, Plugins) ->
+ adhoc_request(Host, ServerHost, Owner,
+ R#adhoc_request{action = <<"execute">>}, Access,
+ Plugins);
+adhoc_request(_Host, _ServerHost, _Owner, Other,
+ _Access, _Plugins) ->
?DEBUG("Couldn't process ad hoc command:~n~p", [Other]),
{error, ?ERR_ITEM_NOT_FOUND}.
@@ -1233,270 +1817,341 @@ adhoc_request(_Host, _ServerHost, _Owner, Other, _Access, _Plugins) ->
%% @doc <p>Sends the process pending subscriptions XForm for Host to
%% Owner.</p>
send_pending_node_form(Host, Owner, _Lang, Plugins) ->
- Filter =
- fun (Plugin) ->
- lists:member("get-pending", features(Plugin))
- end,
+ Filter = fun (Plugin) ->
+ lists:member(<<"get-pending">>, features(Plugin))
+ end,
case lists:filter(Filter, Plugins) of
- [] ->
- {error, ?ERR_FEATURE_NOT_IMPLEMENTED};
- Ps ->
- XOpts = lists:map(fun (Node) ->
- {xmlelement, "option", [],
- [{xmlelement, "value", [],
- [{xmlcdata, node_to_string(Node)}]}]}
- end, get_pending_nodes(Host, Owner, Ps)),
- XForm = {xmlelement, "x", [{"xmlns", ?NS_XDATA}, {"type", "form"}],
- [{xmlelement, "field",
- [{"type", "list-single"}, {"var", "pubsub#node"}],
- lists:usort(XOpts)}]},
- #adhoc_response{status = executing,
- defaultaction = "execute",
- elements = [XForm]}
+ [] -> {error, ?ERR_FEATURE_NOT_IMPLEMENTED};
+ Ps ->
+ XOpts = lists:map(fun (Node) ->
+ #xmlel{name = <<"option">>, attrs = [],
+ children =
+ [#xmlel{name = <<"value">>,
+ attrs = [],
+ children =
+ [{xmlcdata, Node}]}]}
+ end,
+ get_pending_nodes(Host, Owner, Ps)),
+ XForm = #xmlel{name = <<"x">>,
+ attrs =
+ [{<<"xmlns">>, ?NS_XDATA},
+ {<<"type">>, <<"form">>}],
+ children =
+ [#xmlel{name = <<"field">>,
+ attrs =
+ [{<<"type">>, <<"list-single">>},
+ {<<"var">>, <<"pubsub#node">>}],
+ children = lists:usort(XOpts)}]},
+ #adhoc_response{status = executing,
+ defaultaction = <<"execute">>, elements = [XForm]}
end.
get_pending_nodes(Host, Owner, Plugins) ->
- Tr =
- fun (Type) ->
- case node_call(Type, get_pending_nodes, [Host, Owner]) of
- {result, Nodes} -> Nodes;
- _ -> []
- end
- end,
+ Tr = fun (Type) ->
+ case node_call(Type, get_pending_nodes, [Host, Owner])
+ of
+ {result, Nodes} -> Nodes;
+ _ -> []
+ end
+ end,
case transaction(Host,
- fun () -> {result, lists:flatmap(Tr, Plugins)} end,
- sync_dirty) of
- {result, Res} -> Res;
- Err -> Err
+ fun () ->
+ {result, lists:flatmap(Tr, Plugins)}
+ end,
+ sync_dirty)
+ of
+ {result, Res} -> Res;
+ Err -> Err
end.
%% @spec (Host, Node, Owner) -> iqRes()
%% @doc <p>Send a subscription approval form to Owner for all pending
%% subscriptions on Host and Node.</p>
send_pending_auth_events(Host, Node, Owner) ->
- ?DEBUG("Sending pending auth events for ~s on ~s:~s",
- [jlib:jid_to_string(Owner), Host, node_to_string(Node)]),
- Action =
- fun (#pubsub_node{id = NodeID, type = Type}) ->
- case lists:member("get-pending", features(Type)) of
- true ->
- case node_call(Type, get_affiliation, [NodeID, Owner]) of
- {result, owner} ->
- node_call(Type, get_node_subscriptions, [NodeID]);
- _ ->
- {error, ?ERR_FORBIDDEN}
- end;
- false ->
- {error, ?ERR_FEATURE_NOT_IMPLEMENTED}
- end
- end,
+ ?DEBUG("Sending pending auth events for ~s on "
+ "~s:~s",
+ [jlib:jid_to_string(Owner), Host, Node]),
+ Action = fun (#pubsub_node{id = NodeID, type = Type}) ->
+ case lists:member(<<"get-pending">>, features(Type)) of
+ true ->
+ case node_call(Type, get_affiliation,
+ [NodeID, Owner])
+ of
+ {result, owner} ->
+ node_call(Type, get_node_subscriptions,
+ [NodeID]);
+ _ -> {error, ?ERR_FORBIDDEN}
+ end;
+ false -> {error, ?ERR_FEATURE_NOT_IMPLEMENTED}
+ end
+ end,
case transaction(Host, Node, Action, sync_dirty) of
- {result, {N, Subscriptions}} ->
- lists:foreach(fun({J, pending, _SubID}) -> send_authorization_request(N, jlib:make_jid(J));
- ({J, pending}) -> send_authorization_request(N, jlib:make_jid(J));
- (_) -> ok
- end, Subscriptions),
- #adhoc_response{};
- Err ->
- Err
+ {result, {N, Subscriptions}} ->
+ lists:foreach(fun ({J, pending, _SubID}) ->
+ send_authorization_request(N, jlib:make_jid(J));
+ ({J, pending}) ->
+ send_authorization_request(N, jlib:make_jid(J));
+ (_) -> ok
+ end,
+ Subscriptions),
+ #adhoc_response{};
+ Err -> Err
end.
%%% authorization handling
-send_authorization_request(#pubsub_node{nodeid = {Host, Node}, type = Type, id = NodeId}, Subscriber) ->
- Lang = "en", %% TODO fix
- Stanza = {xmlelement, "message",
- [],
- [{xmlelement, "x", [{"xmlns", ?NS_XDATA}, {"type", "form"}],
- [{xmlelement, "title", [],
- [{xmlcdata, translate:translate(Lang, "PubSub subscriber request")}]},
- {xmlelement, "instructions", [],
- [{xmlcdata, translate:translate(Lang, "Choose whether to approve this entity's subscription.")}]},
- {xmlelement, "field",
- [{"var", "FORM_TYPE"}, {"type", "hidden"}],
- [{xmlelement, "value", [], [{xmlcdata, ?NS_PUBSUB_SUB_AUTH}]}]},
- {xmlelement, "field",
- [{"var", "pubsub#node"}, {"type", "text-single"},
- {"label", translate:translate(Lang, "Node ID")}],
- [{xmlelement, "value", [],
- [{xmlcdata, node_to_string(Node)}]}]},
- {xmlelement, "field", [{"var", "pubsub#subscriber_jid"},
- {"type", "jid-single"},
- {"label", translate:translate(Lang, "Subscriber Address")}],
- [{xmlelement, "value", [],
- [{xmlcdata, jlib:jid_to_string(Subscriber)}]}]},
- {xmlelement, "field",
- [{"var", "pubsub#allow"},
- {"type", "boolean"},
- {"label", translate:translate(Lang, "Allow this Jabber ID to subscribe to this pubsub node?")}],
- [{xmlelement, "value", [], [{xmlcdata, "false"}]}]}]}]},
- lists:foreach(fun(Owner) ->
- ejabberd_router:route(service_jid(Host), jlib:make_jid(Owner), Stanza)
- end, node_owners(Host, Type, NodeId)).
+send_authorization_request(#pubsub_node{nodeid = {Host, Node},
+ type = Type, id = NodeId},
+ Subscriber) ->
+ Lang = <<"en">>,
+ Stanza = #xmlel{name = <<"message">>, attrs = [],
+ children =
+ [#xmlel{name = <<"x">>,
+ attrs =
+ [{<<"xmlns">>, ?NS_XDATA},
+ {<<"type">>, <<"form">>}],
+ children =
+ [#xmlel{name = <<"title">>, attrs = [],
+ children =
+ [{xmlcdata,
+ translate:translate(Lang,
+ <<"PubSub subscriber request">>)}]},
+ #xmlel{name = <<"instructions">>,
+ attrs = [],
+ children =
+ [{xmlcdata,
+ translate:translate(Lang,
+ <<"Choose whether to approve this entity's "
+ "subscription.">>)}]},
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"var">>, <<"FORM_TYPE">>},
+ {<<"type">>, <<"hidden">>}],
+ children =
+ [#xmlel{name = <<"value">>,
+ attrs = [],
+ children =
+ [{xmlcdata,
+ ?NS_PUBSUB_SUB_AUTH}]}]},
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"var">>, <<"pubsub#node">>},
+ {<<"type">>,
+ <<"text-single">>},
+ {<<"label">>,
+ translate:translate(Lang,
+ <<"Node ID">>)}],
+ children =
+ [#xmlel{name = <<"value">>,
+ attrs = [],
+ children =
+ [{xmlcdata, Node}]}]},
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"var">>,
+ <<"pubsub#subscriber_jid">>},
+ {<<"type">>, <<"jid-single">>},
+ {<<"label">>,
+ translate:translate(Lang,
+ <<"Subscriber Address">>)}],
+ children =
+ [#xmlel{name = <<"value">>,
+ attrs = [],
+ children =
+ [{xmlcdata,
+ jlib:jid_to_string(Subscriber)}]}]},
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"var">>,
+ <<"pubsub#allow">>},
+ {<<"type">>, <<"boolean">>},
+ {<<"label">>,
+ translate:translate(Lang,
+ <<"Allow this Jabber ID to subscribe to "
+ "this pubsub node?">>)}],
+ children =
+ [#xmlel{name = <<"value">>,
+ attrs = [],
+ children =
+ [{xmlcdata,
+ <<"false">>}]}]}]}]},
+ lists:foreach(fun (Owner) ->
+ ejabberd_router:route(service_jid(Host),
+ jlib:make_jid(Owner), Stanza)
+ end,
+ node_owners(Host, Type, NodeId)).
find_authorization_response(Packet) ->
- {xmlelement, _Name, _Attrs, Els} = Packet,
- XData1 = lists:map(fun({xmlelement, "x", XAttrs, _} = XEl) ->
- case xml:get_attr_s("xmlns", XAttrs) of
- ?NS_XDATA ->
- case xml:get_attr_s("type", XAttrs) of
- "cancel" ->
- none;
- _ ->
- jlib:parse_xdata_submit(XEl)
- end;
- _ ->
- none
+ #xmlel{children = Els} = Packet,
+ XData1 = lists:map(fun (#xmlel{name = <<"x">>,
+ attrs = XAttrs} =
+ XEl) ->
+ case xml:get_attr_s(<<"xmlns">>, XAttrs) of
+ ?NS_XDATA ->
+ case xml:get_attr_s(<<"type">>, XAttrs) of
+ <<"cancel">> -> none;
+ _ -> jlib:parse_xdata_submit(XEl)
+ end;
+ _ -> none
end;
- (_) ->
- none
- end, xml:remove_cdata(Els)),
- XData = lists:filter(fun(E) -> E /= none end, XData1),
+ (_) -> none
+ end,
+ xml:remove_cdata(Els)),
+ XData = lists:filter(fun (E) -> E /= none end, XData1),
case XData of
- [invalid] -> invalid;
- [] -> none;
- [XFields] when is_list(XFields) ->
- ?DEBUG("XFields: ~p", [XFields]),
- case lists:keysearch("FORM_TYPE", 1, XFields) of
- {value, {_, [?NS_PUBSUB_SUB_AUTH]}} ->
- XFields;
- _ ->
- invalid
- end
+ [invalid] -> invalid;
+ [] -> none;
+ [XFields] when is_list(XFields) ->
+ ?DEBUG("XFields: ~p", [XFields]),
+ case lists:keysearch(<<"FORM_TYPE">>, 1, XFields) of
+ {value, {_, [?NS_PUBSUB_SUB_AUTH]}} -> XFields;
+ _ -> invalid
+ end
end.
-
%% @spec (Host, JID, Node, Subscription) -> void
%% Host = mod_pubsub:host()
%% JID = jlib:jid()
%% SNode = string()
%% Subscription = atom() | {atom(), mod_pubsub:subid()}
%% @doc Send a message to JID with the supplied Subscription
+%% TODO : ask Christophe's opinion
send_authorization_approval(Host, JID, SNode, Subscription) ->
SubAttrs = case Subscription of
- {S, SID} -> [{"subscription", subscription_to_string(S)},
- {"subid", SID}];
- S -> [{"subscription", subscription_to_string(S)}]
+% {S, SID} ->
+% [{<<"subscription">>, subscription_to_string(S)},
+% {<<"subid">>, SID}];
+ S -> [{<<"subscription">>, subscription_to_string(S)}]
end,
- Stanza = event_stanza(
- [{xmlelement, "subscription",
- [{"jid", jlib:jid_to_string(JID)}|nodeAttr(SNode)] ++ SubAttrs,
- []}]),
+ Stanza = event_stanza([#xmlel{name = <<"subscription">>,
+ attrs =
+ [{<<"jid">>, jlib:jid_to_string(JID)}
+ | nodeAttr(SNode)]
+ ++ SubAttrs,
+ children = []}]),
ejabberd_router:route(service_jid(Host), JID, Stanza).
handle_authorization_response(Host, From, To, Packet, XFields) ->
- case {lists:keysearch("pubsub#node", 1, XFields),
- lists:keysearch("pubsub#subscriber_jid", 1, XFields),
- lists:keysearch("pubsub#allow", 1, XFields)} of
- {{value, {_, [SNode]}}, {value, {_, [SSubscriber]}},
- {value, {_, [SAllow]}}} ->
- Node = string_to_node(SNode),
- Subscriber = jlib:string_to_jid(SSubscriber),
- Allow = case SAllow of
- "1" -> true;
- "true" -> true;
- _ -> false
- end,
- Action = fun(#pubsub_node{type = Type, id = NodeId}) ->
- IsApprover = lists:member(jlib:jid_tolower(jlib:jid_remove_resource(From)), node_owners_call(Type, NodeId)),
- {result, Subscriptions} = node_call(Type, get_subscriptions, [NodeId, Subscriber]),
- if
- not IsApprover ->
- {error, ?ERR_FORBIDDEN};
- true ->
- update_auth(Host, SNode, Type, NodeId,
- Subscriber, Allow,
- Subscriptions)
- end
- end,
- case transaction(Host, Node, Action, sync_dirty) of
- {error, Error} ->
- ejabberd_router:route(
- To, From,
- jlib:make_error_reply(Packet, Error));
- {result, {_, _NewSubscription}} ->
- %% XXX: notify about subscription state change, section 12.11
- ok;
- _ ->
- ejabberd_router:route(
- To, From,
- jlib:make_error_reply(Packet, ?ERR_INTERNAL_SERVER_ERROR))
- end;
- _ ->
- ejabberd_router:route(
- To, From,
- jlib:make_error_reply(Packet, ?ERR_NOT_ACCEPTABLE))
+ case {lists:keysearch(<<"pubsub#node">>, 1, XFields),
+ lists:keysearch(<<"pubsub#subscriber_jid">>, 1, XFields),
+ lists:keysearch(<<"pubsub#allow">>, 1, XFields)}
+ of
+ {{value, {_, [Node]}}, {value, {_, [SSubscriber]}},
+ {value, {_, [SAllow]}}} ->
+% Node = string_to_node(SNode),
+ Subscriber = jlib:string_to_jid(SSubscriber),
+ Allow = case SAllow of
+ <<"1">> -> true;
+ <<"true">> -> true;
+ _ -> false
+ end,
+ Action = fun (#pubsub_node{type = Type,
+ id = NodeId}) ->
+ IsApprover =
+ lists:member(jlib:jid_tolower(jlib:jid_remove_resource(From)),
+ node_owners_call(Type, NodeId)),
+ {result, Subscriptions} = node_call(Type,
+ get_subscriptions,
+ [NodeId,
+ Subscriber]),
+ if not IsApprover -> {error, ?ERR_FORBIDDEN};
+ true ->
+ update_auth(Host, Node, Type, NodeId,
+ Subscriber, Allow, Subscriptions)
+ end
+ end,
+ case transaction(Host, Node, Action, sync_dirty) of
+ {error, Error} ->
+ ejabberd_router:route(To, From,
+ jlib:make_error_reply(Packet, Error));
+ {result, {_, _NewSubscription}} ->
+ %% XXX: notify about subscription state change, section 12.11
+ ok;
+ _ ->
+ ejabberd_router:route(To, From,
+ jlib:make_error_reply(Packet,
+ ?ERR_INTERNAL_SERVER_ERROR))
+ end;
+ _ ->
+ ejabberd_router:route(To, From,
+ jlib:make_error_reply(Packet,
+ ?ERR_NOT_ACCEPTABLE))
end.
-update_auth(Host, Node, Type, NodeId, Subscriber,
- Allow, Subscriptions) ->
- Subscription = lists:filter(fun({pending, _}) -> true;
- (_) -> false
- end, Subscriptions),
+update_auth(Host, Node, Type, NodeId, Subscriber, Allow,
+ Subscriptions) ->
+ Subscription = lists:filter(fun ({pending, _}) -> true;
+ (_) -> false
+ end,
+ Subscriptions),
case Subscription of
- [{pending, SubID}] -> %% TODO does not work if several pending
- NewSubscription = case Allow of
- true -> subscribed;
- false -> none
- end,
- node_call(Type, set_subscriptions,
- [NodeId, Subscriber, NewSubscription, SubID]),
- send_authorization_approval(Host, Subscriber, Node,
- NewSubscription),
- {result, ok};
- _ ->
- {error, ?ERR_UNEXPECTED_REQUEST}
+ [{pending, SubID}] ->
+ NewSubscription = case Allow of
+ true -> subscribed;
+ false -> none
+ end,
+ node_call(Type, set_subscriptions,
+ [NodeId, Subscriber, NewSubscription, SubID]),
+ send_authorization_approval(Host, Subscriber, Node,
+ NewSubscription),
+ {result, ok};
+ _ -> {error, ?ERR_UNEXPECTED_REQUEST}
end.
-define(XFIELD(Type, Label, Var, Val),
- {xmlelement, "field", [{"type", Type},
- {"label", translate:translate(Lang, Label)},
- {"var", Var}],
- [{xmlelement, "value", [], [{xmlcdata, Val}]}]}).
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"type">>, Type},
+ {<<"label">>, translate:translate(Lang, Label)},
+ {<<"var">>, Var}],
+ children =
+ [#xmlel{name = <<"value">>, attrs = [],
+ children = [{xmlcdata, Val}]}]}).
-define(BOOLXFIELD(Label, Var, Val),
- ?XFIELD("boolean", Label, Var,
+ ?XFIELD(<<"boolean">>, Label, Var,
case Val of
- true -> "1";
- _ -> "0"
+ true -> <<"1">>;
+ _ -> <<"0">>
end)).
-define(STRINGXFIELD(Label, Var, Val),
- ?XFIELD("text-single", Label, Var, Val)).
+ ?XFIELD(<<"text-single">>, Label, Var, Val)).
-define(STRINGMXFIELD(Label, Var, Vals),
- {xmlelement, "field", [{"type", "text-multi"},
- {"label", translate:translate(Lang, Label)},
- {"var", Var}],
- [{xmlelement, "value", [], [{xmlcdata, V}]} || V <- Vals]}).
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"type">>, <<"text-multi">>},
+ {<<"label">>, translate:translate(Lang, Label)},
+ {<<"var">>, Var}],
+ children =
+ [#xmlel{name = <<"value">>, attrs = [],
+ children = [{xmlcdata, V}]}
+ || V <- Vals]}).
-define(XFIELDOPT(Type, Label, Var, Val, Opts),
- {xmlelement, "field", [{"type", Type},
- {"label", translate:translate(Lang, Label)},
- {"var", Var}],
- lists:map(fun(Opt) ->
- {xmlelement, "option", [],
- [{xmlelement, "value", [],
- [{xmlcdata, Opt}]}]}
- end, Opts) ++
- [{xmlelement, "value", [], [{xmlcdata, Val}]}]}).
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"type">>, Type},
+ {<<"label">>, translate:translate(Lang, Label)},
+ {<<"var">>, Var}],
+ children =
+ lists:map(fun (Opt) ->
+ #xmlel{name = <<"option">>, attrs = [],
+ children =
+ [#xmlel{name = <<"value">>,
+ attrs = [],
+ children =
+ [{xmlcdata, Opt}]}]}
+ end,
+ Opts)
+ ++
+ [#xmlel{name = <<"value">>, attrs = [],
+ children = [{xmlcdata, Val}]}]}).
-define(LISTXFIELD(Label, Var, Val, Opts),
- ?XFIELDOPT("list-single", Label, Var, Val, Opts)).
+ ?XFIELDOPT(<<"list-single">>, Label, Var, Val, Opts)).
-define(LISTMXFIELD(Label, Var, Vals, Opts),
- {xmlelement, "field", [{"type", "list-multi"},
- {"label", translate:translate(Lang, Label)},
- {"var", Var}],
- lists:map(fun(Opt) ->
- {xmlelement, "option", [],
- [{xmlelement, "value", [],
- [{xmlcdata, Opt}]}]}
- end, Opts) ++
- lists:map(fun(Val) ->
- {xmlelement, "value", [],
- [{xmlcdata, Val}]}
- end, Vals)}).
-
%% @spec (Host::host(), ServerHost::host(), Node::pubsubNode(), Owner::jid(), NodeType::nodeType()) ->
%% {error, Reason::stanzaError()} |
%% {result, []}
@@ -1517,54 +2172,104 @@ update_auth(Host, Node, Type, NodeId, Subscriber,
%%<li>nodetree create_node checks if nodeid already exists</li>
%%<li>node plugin create_node just sets default affiliation/subscription</li>
%%</ul>
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"type">>, <<"list-multi">>},
+ {<<"label">>, translate:translate(Lang, Label)},
+ {<<"var">>, Var}],
+ children =
+ lists:map(fun (Opt) ->
+ #xmlel{name = <<"option">>, attrs = [],
+ children =
+ [#xmlel{name = <<"value">>,
+ attrs = [],
+ children =
+ [{xmlcdata, Opt}]}]}
+ end,
+ Opts)
+ ++
+ lists:map(fun (Val) ->
+ #xmlel{name = <<"value">>, attrs = [],
+ children = [{xmlcdata, Val}]}
+ end,
+ Vals)}).
+
+-spec(create_node/5 ::
+(
+ Host :: mod_pubsub:host(),
+ ServerHost :: binary(),
+ Node :: <<>> | mod_pubsub:nodeId(),
+ Owner :: jid(),
+ Type :: binary())
+ -> {result, [xmlel(),...]}
+ %%%
+ | {error, xmlel()}
+).
+
create_node(Host, ServerHost, Node, Owner, Type) ->
create_node(Host, ServerHost, Node, Owner, Type, all, []).
+
+-spec(create_node/7 ::
+(
+ Host :: mod_pubsub:host(),
+ ServerHost :: binary(),
+ Node :: <<>> | mod_pubsub:nodeId(),
+ Owner :: jid(),
+ Type :: binary(),
+ Access :: atom(),
+ Configuration :: [xmlel()])
+ -> {result, [xmlel(),...]}
+ %%%
+ | {error, xmlel()}
+).
create_node(Host, ServerHost, <<>>, Owner, Type, Access, Configuration) ->
- case lists:member("instant-nodes", features(Type)) of
- true ->
- NewNode = string_to_node(randoms:get_string()),
- case create_node(Host, ServerHost,
- NewNode, Owner, Type, Access, Configuration) of
- {result, []} ->
- {result,
- [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}],
- [{xmlelement, "create", nodeAttr(NewNode), []}]}]};
- Error ->
- Error
- end;
- false ->
- %% Service does not support instant nodes
- {error, extended_error(?ERR_NOT_ACCEPTABLE, "nodeid-required")}
+ case lists:member(<<"instant-nodes">>, features(Type)) of
+ true ->
+ NewNode = randoms:get_string(),
+ case create_node(Host, ServerHost, NewNode, Owner, Type,
+ Access, Configuration)
+ of
+ {result, _} ->
+ {result,
+ [#xmlel{name = <<"pubsub">>,
+ attrs = [{<<"xmlns">>, ?NS_PUBSUB}],
+ children =
+ [#xmlel{name = <<"create">>,
+ attrs = nodeAttr(NewNode),
+ children = []}]}]};
+ Error -> Error
+ end;
+ false ->
+ {error,
+ extended_error(?ERR_NOT_ACCEPTABLE,
+ <<"nodeid-required">>)}
end;
create_node(Host, ServerHost, Node, Owner, GivenType, Access, Configuration) ->
Type = select_type(ServerHost, Host, Node, GivenType),
- %% TODO, check/set node_type = Type
ParseOptions = case xml:remove_cdata(Configuration) of
- [] ->
- {result, node_options(Type)};
- [{xmlelement, "x", _Attrs, _SubEls} = XEl] ->
- case jlib:parse_xdata_submit(XEl) of
- invalid ->
- {error, ?ERR_BAD_REQUEST};
- XData ->
- case set_xoption(Host, XData, node_options(Type)) of
- NewOpts when is_list(NewOpts) ->
- {result, NewOpts};
- Err ->
- Err
- end
- end;
- _ ->
- ?INFO_MSG("Node ~p; bad configuration: ~p", [Node, Configuration]),
- {error, ?ERR_BAD_REQUEST}
+ [] -> {result, node_options(Type)};
+ [#xmlel{name = <<"x">>} = XEl] ->
+ case jlib:parse_xdata_submit(XEl) of
+ invalid -> {error, ?ERR_BAD_REQUEST};
+ XData ->
+ case set_xoption(Host, XData, node_options(Type))
+ of
+ NewOpts when is_list(NewOpts) ->
+ {result, NewOpts};
+ Err -> Err
+ end
+ end;
+ _ ->
+ ?INFO_MSG("Node ~p; bad configuration: ~p",
+ [Node, Configuration]),
+ {error, ?ERR_BAD_REQUEST}
end,
case ParseOptions of
{result, NodeOptions} ->
CreateNode =
fun() ->
- SNode = node_to_string(Node),
Parent = case node_call(Type, node_to_path, [Node]) of
- {result, [SNode]} -> <<>>;
+ {result, [Node]} -> <<>>;
{result, Path} -> element(2, node_call(Type, path_to_node, [lists:sublist(Path, length(Path)-1)]))
end,
Parents = case Parent of
@@ -1593,9 +2298,11 @@ create_node(Host, ServerHost, Node, Owner, GivenType, Access, Configuration) ->
{error, ?ERR_FORBIDDEN}
end
end,
- Reply = [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}],
- [{xmlelement, "create", nodeAttr(Node),
- []}]}],
+ Reply = [#xmlel{name = <<"pubsub">>,
+ attrs = [{<<"xmlns">>, ?NS_PUBSUB}],
+ children = [#xmlel{name = <<"create">>,
+ attrs = nodeAttr(Node),
+ children = []}]}],
case transaction(Host, CreateNode, transaction) of
{result, {NodeId, SubsByDepth, {Result, broadcast}}} ->
broadcast_created_node(Host, Node, NodeId, Type, NodeOptions, SubsByDepth),
@@ -1626,6 +2333,15 @@ create_node(Host, ServerHost, Node, Owner, GivenType, Access, Configuration) ->
%% Node = pubsubNode()
%% Owner = jid()
%% Reason = stanzaError()
+-spec(delete_node/3 ::
+(
+ Host :: mod_pubsub:host(),
+ Node :: mod_pubsub:nodeId(),
+ Owner :: jid())
+ -> {result, [xmlel(),...]}
+ %%%
+ | {error, xmlel()}
+).
%% @doc <p>Delete specified node and all childs.</p>
%%<p>There are several reasons why the node deletion request might fail:</p>
%%<ul>
@@ -1634,7 +2350,6 @@ create_node(Host, ServerHost, Node, Owner, GivenType, Access, Configuration) ->
%%<li>The specified node does not exist.</li>
%%</ul>
delete_node(_Host, <<>>, _Owner) ->
- %% Node is the root
{error, ?ERR_NOT_ALLOWED};
delete_node(Host, Node, Owner) ->
Action = fun(#pubsub_node{type = Type, id = NodeId}) ->
@@ -1651,9 +2366,9 @@ delete_node(Host, Node, Owner) ->
%% Entity is not an owner
{error, ?ERR_FORBIDDEN}
end
- end,
+ end,
Reply = [],
- ServerHost = get(server_host), % not clean, but prevent many API changes
+ ServerHost = get(server_host),
case transaction(Host, Node, Action, transaction) of
{result, {_TNode, {SubsByDepth, {Result, broadcast, Removed}}}} ->
lists:foreach(fun({RNode, _RSubscriptions}) ->
@@ -1697,6 +2412,17 @@ delete_node(Host, Node, Owner) ->
%% Node = pubsubNode()
%% From = jid()
%% JID = jid()
+-spec(subscribe_node/5 ::
+(
+ Host :: mod_pubsub:host(),
+ Node :: mod_pubsub:nodeId(),
+ From :: jid(),
+ JID :: binary(),
+ Configuration :: [xmlel()])
+ -> {result, [xmlel(),...]}
+ %%%
+ | {error, xmlel()}
+).
%% @see node_hometree:subscribe_node/5
%% @doc <p>Accepts or rejects subcription requests on a PubSub node.</p>
%%<p>There are several reasons why the subscription request might fail:</p>
@@ -1713,86 +2439,99 @@ delete_node(Host, Node, Owner) ->
%%<li>The node does not exist.</li>
%%</ul>
subscribe_node(Host, Node, From, JID, Configuration) ->
- SubOpts = case pubsub_subscription_odbc:parse_options_xform(Configuration) of
- {result, GoodSubOpts} -> GoodSubOpts;
- _ -> invalid
- end,
+ SubOpts = case
+ pubsub_subscription_odbc:parse_options_xform(Configuration)
+ of
+ {result, GoodSubOpts} -> GoodSubOpts;
+ _ -> invalid
+ end,
Subscriber = case jlib:string_to_jid(JID) of
- error -> {"", "", ""};
- J -> jlib:jid_tolower(J)
+ error -> {<<"">>, <<"">>, <<"">>};
+ J ->
+ case jlib:jid_tolower(J) of
+ error -> {<<"">>, <<"">>, <<"">>};
+ J1 -> J1
+ end
end,
- Action = fun(#pubsub_node{options = Options, type = Type, id = NodeId}) ->
- Features = features(Type),
- SubscribeFeature = lists:member("subscribe", Features),
- OptionsFeature = lists:member("subscription-options", Features),
- HasOptions = not (SubOpts == []),
- SubscribeConfig = get_option(Options, subscribe),
- AccessModel = get_option(Options, access_model),
- SendLast = get_option(Options, send_last_published_item),
- AllowedGroups = get_option(Options, roster_groups_allowed, []),
- Owners = node_owners_call(Type, NodeId),
- {PresenceSubscription, RosterGroup} = get_presence_and_roster_permissions(Host, Subscriber, Owners, AccessModel, AllowedGroups),
- if
- not SubscribeFeature ->
- %% Node does not support subscriptions
- {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "subscribe")};
+ Action = fun (#pubsub_node{options = Options,
+ type = Type, id = NodeId}) ->
+ Features = features(Type),
+ SubscribeFeature = lists:member(<<"subscribe">>, Features),
+ OptionsFeature = lists:member(<<"subscription-options">>, Features),
+ HasOptions = not (SubOpts == []),
+ SubscribeConfig = get_option(Options, subscribe),
+ AccessModel = get_option(Options, access_model),
+ SendLast = get_option(Options, send_last_published_item),
+ AllowedGroups = get_option(Options, roster_groups_allowed, []),
+ Owners = node_owners_call(Type, NodeId),
+ {PresenceSubscription, RosterGroup} =
+ get_presence_and_roster_permissions(Host, Subscriber,
+ Owners, AccessModel, AllowedGroups),
+ if not SubscribeFeature ->
+ {error,
+ extended_error(?ERR_FEATURE_NOT_IMPLEMENTED,
+ unsupported, <<"subscribe">>)};
not SubscribeConfig ->
- %% Node does not support subscriptions
- {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "subscribe")};
+ {error,
+ extended_error(?ERR_FEATURE_NOT_IMPLEMENTED,
+ unsupported, <<"subscribe">>)};
HasOptions andalso not OptionsFeature ->
- %% Node does not support subscription options
- {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "subscription-options")};
+ {error,
+ extended_error(?ERR_FEATURE_NOT_IMPLEMENTED,
+ unsupported,
+ <<"subscription-options">>)};
SubOpts == invalid ->
- %% Passed invalit options submit form
- {error, extended_error(?ERR_BAD_REQUEST, "invalid-options")};
+ {error,
+ extended_error(?ERR_BAD_REQUEST,
+ <<"invalid-options">>)};
true ->
node_call(Type, subscribe_node,
- [NodeId, From, Subscriber,
- AccessModel, SendLast,
- PresenceSubscription, RosterGroup,
- SubOpts])
- end
- end,
- Reply = fun(Subscription) ->
- %% TODO, this is subscription-notification, should depends on node features
+ [NodeId, From, Subscriber, AccessModel,
+ SendLast, PresenceSubscription,
+ RosterGroup, SubOpts])
+ end
+ end,
+ Reply = fun (Subscription) ->
SubAttrs = case Subscription of
- {subscribed, SubId} ->
- [{"subscription", subscription_to_string(subscribed)},
- {"subid", SubId},
- {"node",Node}];
- Other ->
- [{"subscription", subscription_to_string(Other)},
- {"node", Node}]
+ {subscribed, SubId} ->
+ [{<<"subscription">>,
+ subscription_to_string(subscribed)},
+ {<<"subid">>, SubId}, {<<"node">>, Node}];
+ Other ->
+ [{<<"subscription">>,
+ subscription_to_string(Other)},
+ {<<"node">>, Node}]
end,
- Fields =
- [{"jid", jlib:jid_to_string(Subscriber)} | SubAttrs],
- [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}],
- [{xmlelement, "subscription", Fields, []}]}]
+ Fields = [{<<"jid">>, jlib:jid_to_string(Subscriber)}
+ | SubAttrs],
+ [#xmlel{name = <<"pubsub">>,
+ attrs = [{<<"xmlns">>, ?NS_PUBSUB}],
+ children =
+ [#xmlel{name = <<"subscription">>,
+ attrs = Fields, children = []}]}]
end,
case transaction(Host, Node, Action, sync_dirty) of
- {result, {TNode, {Result, subscribed, SubId, send_last}}} ->
- NodeId = TNode#pubsub_node.id,
- Type = TNode#pubsub_node.type,
- send_items(Host, Node, NodeId, Type, Subscriber, last),
- case Result of
- default -> {result, Reply({subscribed, SubId})};
- _ -> {result, Result}
- end;
- {result, {_TNode, {default, subscribed, SubId}}} ->
- {result, Reply({subscribed, SubId})};
- {result, {_TNode, {Result, subscribed, _SubId}}} ->
- {result, Result};
- {result, {TNode, {default, pending, _SubId}}} ->
- send_authorization_request(TNode, Subscriber),
- {result, Reply(pending)};
- {result, {TNode, {Result, pending}}} ->
- send_authorization_request(TNode, Subscriber),
- {result, Result};
- {result, {_, Result}} ->
- %% this case should never occure anyway
- {result, Result};
- Error ->
- Error
+ {result,
+ {TNode, {Result, subscribed, SubId, send_last}}} ->
+ NodeId = TNode#pubsub_node.id,
+ Type = TNode#pubsub_node.type,
+ send_items(Host, Node, NodeId, Type, Subscriber, last),
+ case Result of
+ default -> {result, Reply({subscribed, SubId})};
+ _ -> {result, Result}
+ end;
+ {result, {_TNode, {default, subscribed, SubId}}} ->
+ {result, Reply({subscribed, SubId})};
+ {result, {_TNode, {Result, subscribed, _SubId}}} ->
+ {result, Result};
+ {result, {TNode, {default, pending, _SubId}}} ->
+ send_authorization_request(TNode, Subscriber),
+ {result, Reply(pending)};
+ {result, {TNode, {Result, pending}}} ->
+ send_authorization_request(TNode, Subscriber),
+ {result, Result};
+ {result, {_, Result}} -> {result, Result};
+ Error -> Error
end.
%% @spec (Host, Noce, From, JID, SubId) -> {error, Reason} | {result, []}
@@ -1811,23 +2550,37 @@ subscribe_node(Host, Node, From, JID, Configuration) ->
%%<li>The node does not exist.</li>
%%<li>The request specifies a subscription ID that is not valid or current.</li>
%%</ul>
-unsubscribe_node(Host, Node, From, JID, SubId) when is_list(JID) ->
+-spec(unsubscribe_node/5 ::
+(
+ Host :: mod_pubsub:host(),
+ Node :: mod_pubsub:nodeId(),
+ From :: jid(),
+ JID :: binary() | ljid(),
+ SubId :: mod_pubsub:subId())
+ -> {result, []}
+ %%%
+ | {error, xmlel()}
+).
+unsubscribe_node(Host, Node, From, JID, SubId)
+ when is_binary(JID) ->
Subscriber = case jlib:string_to_jid(JID) of
- error -> {"", "", ""};
- J -> jlib:jid_tolower(J)
+ error -> {<<"">>, <<"">>, <<"">>};
+ J ->
+ case jlib:jid_tolower(J) of
+ error -> {<<"">>, <<"">>, <<"">>};
+ J1 -> J1
+ end
end,
unsubscribe_node(Host, Node, From, Subscriber, SubId);
unsubscribe_node(Host, Node, From, Subscriber, SubId) ->
- Action = fun(#pubsub_node{type = Type, id = NodeId}) ->
- node_call(Type, unsubscribe_node, [NodeId, From, Subscriber, SubId])
- end,
+ Action = fun (#pubsub_node{type = Type, id = NodeId}) ->
+ node_call(Type, unsubscribe_node,
+ [NodeId, From, Subscriber, SubId])
+ end,
case transaction(Host, Node, Action, sync_dirty) of
- {result, {_, default}} ->
- {result, []};
- {result, {_, Result}} ->
- {result, Result};
- Error ->
- Error
+ {result, {_, default}} -> {result, []};
+% {result, {_, Result}} -> {result, Result};
+ Error -> Error
end.
%% @spec (Host::host(), ServerHost::host(), JID::jid(), Node::pubsubNode(), ItemId::string(), Payload::term()) ->
@@ -1844,49 +2597,65 @@ unsubscribe_node(Host, Node, From, Subscriber, SubId) ->
%%<li>The item contains more than one payload element or the namespace of the root payload element does not match the configured namespace for the node.</li>
%%<li>The request does not match the node configuration.</li>
%%</ul>
-publish_item(Host, ServerHost, Node, Publisher, "", Payload) ->
- %% if publisher does not specify an ItemId, the service MUST generate the ItemId
+-spec(publish_item/6 ::
+(
+ Host :: mod_pubsub:host(),
+ ServerHost :: binary(),
+ Node :: mod_pubsub:nodeId(),
+ Publisher :: jid(),
+ ItemId :: <<>> | mod_pubsub:itemId(),
+ Payload :: mod_pubsub:payload())
+ -> {result, [xmlel(),...]}
+ %%%
+ | {error, xmlel()}
+).
+publish_item(Host, ServerHost, Node, Publisher, <<>>, Payload) ->
publish_item(Host, ServerHost, Node, Publisher, uniqid(), Payload);
publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload) ->
- Action = fun(#pubsub_node{options = Options, type = Type, id = NodeId}) ->
- Features = features(Type),
- PublishFeature = lists:member("publish", Features),
- PublishModel = get_option(Options, publish_model),
- MaxItems = max_items(Host, Options),
- DeliverPayloads = get_option(Options, deliver_payloads),
- PersistItems = get_option(Options, persist_items),
- PayloadCount = payload_xmlelements(Payload),
- PayloadSize = size(term_to_binary(Payload))-2, % size(term_to_binary([])) == 2
- PayloadMaxSize = get_option(Options, max_payload_size),
- % pubsub#deliver_payloads true
- % pubsub#persist_items true -> 1 item; false -> 0 item
- if
- not PublishFeature ->
- %% Node does not support item publication
- {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "publish")};
+ Action = fun (#pubsub_node{options = Options, type = Type, id = NodeId}) ->
+ Features = features(Type),
+ PublishFeature = lists:member(<<"publish">>, Features),
+ PublishModel = get_option(Options, publish_model),
+ MaxItems = max_items(Host, Options),
+ DeliverPayloads = get_option(Options, deliver_payloads),
+ PersistItems = get_option(Options, persist_items),
+ PayloadCount = payload_xmlelements(Payload),
+ PayloadSize = byte_size(term_to_binary(Payload)) - 2,
+ PayloadMaxSize = get_option(Options, max_payload_size),
+ if not PublishFeature ->
+ {error,
+ extended_error(?ERR_FEATURE_NOT_IMPLEMENTED,
+ unsupported, <<"publish">>)};
PayloadSize > PayloadMaxSize ->
- %% Entity attempts to publish very large payload
- {error, extended_error(?ERR_NOT_ACCEPTABLE, "payload-too-big")};
+ {error,
+ extended_error(?ERR_NOT_ACCEPTABLE, <<"payload-too-big">>)};
(PayloadCount == 0) and (Payload == []) ->
- %% Publisher attempts to publish to payload node with no payload
- {error, extended_error(?ERR_BAD_REQUEST, "payload-required")};
+ {error,
+ extended_error(?ERR_BAD_REQUEST, <<"payload-required">>)};
(PayloadCount > 1) or (PayloadCount == 0) ->
- %% Entity attempts to publish item with multiple payload elements
- {error, extended_error(?ERR_BAD_REQUEST, "invalid-payload")};
- (DeliverPayloads == 0) and (PersistItems == 0) and (PayloadSize > 0) ->
- %% Publisher attempts to publish to transient notification node with item
- {error, extended_error(?ERR_BAD_REQUEST, "item-forbidden")};
- ((DeliverPayloads == 1) or (PersistItems == 1)) and (PayloadSize == 0) ->
- %% Publisher attempts to publish to persistent node with no item
- {error, extended_error(?ERR_BAD_REQUEST, "item-required")};
+ {error,
+ extended_error(?ERR_BAD_REQUEST, <<"invalid-payload">>)};
+ (DeliverPayloads == false) and (PersistItems == false) and
+ (PayloadSize > 0) ->
+ {error,
+ extended_error(?ERR_BAD_REQUEST, <<"item-forbidden">>)};
+ ((DeliverPayloads == true) or (PersistItems == true)) and
+ (PayloadSize == 0) ->
+ {error,
+ extended_error(?ERR_BAD_REQUEST, <<"item-required">>)};
true ->
node_call(Type, publish_item, [NodeId, Publisher, PublishModel, MaxItems, ItemId, Payload])
end
end,
ejabberd_hooks:run(pubsub_publish_item, ServerHost, [ServerHost, Node, Publisher, service_jid(Host), ItemId, Payload]),
- Reply = [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}],
- [{xmlelement, "publish", nodeAttr(Node),
- [{xmlelement, "item", itemAttr(ItemId), []}]}]}],
+ Reply = [#xmlel{name = <<"pubsub">>,
+ attrs = [{<<"xmlns">>, ?NS_PUBSUB}],
+ children =
+ [#xmlel{name = <<"publish">>, attrs = nodeAttr(Node),
+ children =
+ [#xmlel{name = <<"item">>,
+ attrs = itemAttr(ItemId),
+ children = []}]}]}],
case transaction(Host, Node, Action, sync_dirty) of
{result, {TNode, {Result, Broadcast, Removed}}} ->
NodeId = TNode#pubsub_node.id,
@@ -1936,8 +2705,12 @@ publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload) ->
case lists:member("auto-create", features(Type)) of
true ->
case create_node(Host, ServerHost, Node, Publisher, Type) of
- {result, [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}],
- [{xmlelement, "create", [{"node", NewNode}], []}]}]} ->
+ {result, [#xmlel{name = <<"pubsub">>,
+ attrs = [{<<"xmlns">>, ?NS_PUBSUB}],
+ children =
+ [#xmlel{name = <<"create">>,
+ attrs = [{<<"node">>, NewNode}],
+ children = []}]}]} ->
publish_item(Host, ServerHost, list_to_binary(NewNode),
Publisher, ItemId, Payload);
_ ->
@@ -1953,6 +2726,16 @@ publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload) ->
%% @spec (Host::host(), JID::jid(), Node::pubsubNode(), ItemId::string()) ->
%% {error, Reason::stanzaError()} |
%% {result, []}
+-spec(delete_item/4 ::
+(
+ Host :: mod_pubsub:host(),
+ Node :: mod_pubsub:nodeId(),
+ Publisher :: jid(),
+ ItemId :: mod_pubsub:itemId())
+ -> {result, []}
+ %%%
+ | {error, xmlel()}
+).
%% @doc <p>Delete item from a PubSub node.</p>
%% <p>The permission to delete an item must be verified by the plugin implementation.</p>
%%<p>There are several reasons why the item retraction request might fail:</p>
@@ -1966,50 +2749,54 @@ publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload) ->
%%</ul>
delete_item(Host, Node, Publisher, ItemId) ->
delete_item(Host, Node, Publisher, ItemId, false).
-delete_item(_, "", _, _, _) ->
- %% Request does not specify a node
- {error, extended_error(?ERR_BAD_REQUEST, "node-required")};
+
+
+delete_item(_, <<"">>, _, _, _) ->
+ {error,
+ extended_error(?ERR_BAD_REQUEST, <<"node-required">>)};
delete_item(Host, Node, Publisher, ItemId, ForceNotify) ->
- Action = fun(#pubsub_node{options = Options, type = Type, id = NodeId}) ->
- Features = features(Type),
- PersistentFeature = lists:member("persistent-items", Features),
- DeleteFeature = lists:member("delete-items", Features),
- PublishModel = get_option(Options, publish_model),
- if
- %%-> iq_pubsub just does that matchs
+ Action = fun (#pubsub_node{options = Options, type = Type, id = NodeId}) ->
+ Features = features(Type),
+ PersistentFeature = lists:member(<<"persistent-items">>, Features),
+ DeleteFeature = lists:member(<<"delete-items">>, Features),
+ PublishModel = get_option(Options, publish_model),
+ if %%-> iq_pubsub just does that matchs
%% %% Request does not specify an item
%% {error, extended_error(?ERR_BAD_REQUEST, "item-required")};
not PersistentFeature ->
- %% Node does not support persistent items
- {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "persistent-items")};
+ {error,
+ extended_error(?ERR_FEATURE_NOT_IMPLEMENTED,
+ unsupported,
+ <<"persistent-items">>)};
not DeleteFeature ->
- %% Service does not support item deletion
- {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "delete-items")};
+ {error,
+ extended_error(?ERR_FEATURE_NOT_IMPLEMENTED,
+ unsupported, <<"delete-items">>)};
true ->
- node_call(Type, delete_item, [NodeId, Publisher, PublishModel, ItemId])
- end
+ node_call(Type, delete_item,
+ [NodeId, Publisher, PublishModel, ItemId])
+ end
end,
Reply = [],
case transaction(Host, Node, Action, sync_dirty) of
- {result, {TNode, {Result, broadcast}}} ->
- NodeId = TNode#pubsub_node.id,
- Type = TNode#pubsub_node.type,
- Options = TNode#pubsub_node.options,
- broadcast_retract_items(Host, Node, NodeId, Type, Options, [ItemId], ForceNotify),
- case get_cached_item(Host, NodeId) of
- #pubsub_item{itemid = {ItemId, NodeId}, _ = '_'} -> unset_cached_item(Host, NodeId);
+ {result, {TNode, {Result, broadcast}}} ->
+ NodeId = TNode#pubsub_node.id,
+ Type = TNode#pubsub_node.type,
+ Options = TNode#pubsub_node.options,
+ broadcast_retract_items(Host, Node, NodeId, Type,
+ Options, [ItemId], ForceNotify),
+ case get_cached_item(Host, NodeId) of
+ #pubsub_item{itemid = {ItemId, NodeId}} ->
+ unset_cached_item(Host, NodeId);
_ -> ok
- end,
- case Result of
- default -> {result, Reply};
- _ -> {result, Result}
- end;
- {result, {_, default}} ->
- {result, Reply};
- {result, {_, Result}} ->
- {result, Result};
- Error ->
- Error
+ end,
+ case Result of
+ default -> {result, Reply};
+ _ -> {result, Result}
+ end;
+ {result, {_, default}} -> {result, Reply};
+ {result, {_, Result}} -> {result, Result};
+ Error -> Error
end.
%% @spec (Host, JID, Node) ->
@@ -2026,44 +2813,53 @@ delete_item(Host, Node, Publisher, ItemId, ForceNotify) ->
%%<li>The node is not configured to persist items.</li>
%%<li>The specified node does not exist.</li>
%%</ul>
+-spec(purge_node/3 ::
+(
+ Host :: mod_pubsub:host(),
+ Node :: mod_pubsub:nodeId(),
+ Owner :: jid())
+ -> {result, []}
+ %%%
+ | {error, xmlel()}
+).
purge_node(Host, Node, Owner) ->
- Action = fun(#pubsub_node{options = Options, type = Type, id = NodeId}) ->
+ Action = fun (#pubsub_node{options = Options, type = Type, id = NodeId}) ->
Features = features(Type),
- PurgeFeature = lists:member("purge-nodes", Features),
- PersistentFeature = lists:member("persistent-items", Features),
+ PurgeFeature = lists:member(<<"purge-nodes">>, Features),
+ PersistentFeature = lists:member(<<"persistent-items">>, Features),
PersistentConfig = get_option(Options, persist_items),
- if
- not PurgeFeature ->
- %% Service does not support node purging
- {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "purge-nodes")};
- not PersistentFeature ->
- %% Node does not support persistent items
- {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "persistent-items")};
- not PersistentConfig ->
- %% Node is not configured for persistent items
- {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "persistent-items")};
- true ->
- node_call(Type, purge_node, [NodeId, Owner])
+ if not PurgeFeature ->
+ {error,
+ extended_error(?ERR_FEATURE_NOT_IMPLEMENTED,
+ unsupported, <<"purge-nodes">>)};
+ not PersistentFeature ->
+ {error,
+ extended_error(?ERR_FEATURE_NOT_IMPLEMENTED,
+ unsupported,
+ <<"persistent-items">>)};
+ not PersistentConfig ->
+ {error,
+ extended_error(?ERR_FEATURE_NOT_IMPLEMENTED,
+ unsupported,
+ <<"persistent-items">>)};
+ true -> node_call(Type, purge_node, [NodeId, Owner])
end
end,
Reply = [],
case transaction(Host, Node, Action, sync_dirty) of
- {result, {TNode, {Result, broadcast}}} ->
- NodeId = TNode#pubsub_node.id,
- Type = TNode#pubsub_node.type,
- Options = TNode#pubsub_node.options,
- broadcast_purge_node(Host, Node, NodeId, Type, Options),
- unset_cached_item(Host, NodeId),
- case Result of
- default -> {result, Reply};
- _ -> {result, Result}
- end;
- {result, {_, default}} ->
- {result, Reply};
- {result, {_, Result}} ->
- {result, Result};
- Error ->
- Error
+ {result, {TNode, {Result, broadcast}}} ->
+ NodeId = TNode#pubsub_node.id,
+ Type = TNode#pubsub_node.type,
+ Options = TNode#pubsub_node.options,
+ broadcast_purge_node(Host, Node, NodeId, Type, Options),
+ unset_cached_item(Host, NodeId),
+ case Result of
+ default -> {result, Reply};
+ _ -> {result, Result}
+ end;
+ {result, {_, default}} -> {result, Reply};
+ {result, {_, Result}} -> {result, Result};
+ Error -> Error
end.
%% @doc <p>Return the items of a given node.</p>
@@ -2071,78 +2867,101 @@ purge_node(Host, Node, Owner) ->
%% <p>The permission are not checked in this function.</p>
%% @todo We probably need to check that the user doing the query has the right
%% to read the items.
+-spec(get_items/7 ::
+(
+ Host :: mod_pubsub:host(),
+ Node :: mod_pubsub:nodeId(),
+ From :: jid(),
+ SubId :: mod_pubsub:subId(),
+ SMaxItems :: binary(),
+ ItemIDs :: [mod_pubsub:itemId()],
+ Rsm :: any())
+ -> {result, [xmlel(),...]}
+ %%%
+ | {error, xmlel()}
+).
get_items(Host, Node, From, SubId, SMaxItems, ItemIDs, RSM) ->
- MaxItems =
- if
- SMaxItems == "" -> get_max_items_node(Host);
- true ->
- case catch list_to_integer(SMaxItems) of
- {'EXIT', _} -> {error, ?ERR_BAD_REQUEST};
- Val -> Val
- end
- end,
+ MaxItems = if SMaxItems == <<"">> ->
+ get_max_items_node(Host);
+ true ->
+ case catch jlib:binary_to_integer(SMaxItems) of
+ {'EXIT', _} -> {error, ?ERR_BAD_REQUEST};
+ Val -> Val
+ end
+ end,
case MaxItems of
- {error, Error} ->
- {error, Error};
- _ ->
- Action = fun(#pubsub_node{options = Options, type = Type, id = NodeId}) ->
- Features = features(Type),
- RetreiveFeature = lists:member("retrieve-items", Features),
- PersistentFeature = lists:member("persistent-items", Features),
- AccessModel = get_option(Options, access_model),
- AllowedGroups = get_option(Options, roster_groups_allowed, []),
- Owners = node_owners_call(Type, NodeId),
- {PresenceSubscription, RosterGroup} = get_presence_and_roster_permissions(Host, From, Owners, AccessModel, AllowedGroups),
- if
- not RetreiveFeature ->
- %% Item Retrieval Not Supported
- {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "retrieve-items")};
- not PersistentFeature ->
- %% Persistent Items Not Supported
- {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "persistent-items")};
- true ->
- node_call(Type, get_items,
- [NodeId, From,
- AccessModel, PresenceSubscription, RosterGroup,
- SubId, RSM])
- end
- end,
- case transaction(Host, Node, Action, sync_dirty) of
- {result, {_, {Items, RSMOut}}} ->
- SendItems = case ItemIDs of
- [] ->
- Items;
- _ ->
- lists:filter(fun(#pubsub_item{itemid = {ItemId, _}}) ->
- lists:member(ItemId, ItemIDs)
- end, Items)
- end,
- %% Generate the XML response (Item list), limiting the
- %% number of items sent to MaxItems:
- {result, [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}],
- [{xmlelement, "items", nodeAttr(Node),
- itemsEls(lists:sublist(SendItems, MaxItems))}
- | jlib:rsm_encode(RSMOut)]}]};
- Error ->
- Error
- end
+ {error, Error} -> {error, Error};
+ _ ->
+ Action = fun (#pubsub_node{options = Options, type = Type, id = NodeId}) ->
+ Features = features(Type),
+ RetreiveFeature = lists:member(<<"retrieve-items">>, Features),
+ PersistentFeature = lists:member(<<"persistent-items">>, Features),
+ AccessModel = get_option(Options, access_model),
+ AllowedGroups = get_option(Options, roster_groups_allowed, []),
+ Owners = node_owners_call(Type, NodeId),
+ {PresenceSubscription, RosterGroup} =
+ get_presence_and_roster_permissions(Host, From, Owners,
+ AccessModel, AllowedGroups),
+ if not RetreiveFeature ->
+ {error,
+ extended_error(?ERR_FEATURE_NOT_IMPLEMENTED,
+ unsupported,
+ <<"retrieve-items">>)};
+ not PersistentFeature ->
+ {error,
+ extended_error(?ERR_FEATURE_NOT_IMPLEMENTED,
+ unsupported,
+ <<"persistent-items">>)};
+ true ->
+ node_call(Type, get_items,
+ [NodeId, From, AccessModel,
+ PresenceSubscription, RosterGroup,
+ SubId, RSM])
+ end
+ end,
+ case transaction(Host, Node, Action, sync_dirty) of
+ {result, {_, {Items, RSMOut}}} ->
+ SendItems = case ItemIDs of
+ [] -> Items;
+ _ ->
+ lists:filter(fun (#pubsub_item{itemid =
+ {ItemId,
+ _}}) ->
+ lists:member(ItemId,
+ ItemIDs)
+ end,
+ Items)
+ end,
+ {result,
+ [#xmlel{name = <<"pubsub">>,
+ attrs = [{<<"xmlns">>, ?NS_PUBSUB}],
+ children =
+ [#xmlel{name = <<"items">>, attrs = nodeAttr(Node),
+ children =
+ itemsEls(lists:sublist(SendItems, MaxItems))}
+ | jlib:rsm_encode(RSMOut)]}]};
+ Error -> Error
+ end
end.
+
get_items(Host, Node) ->
- Action = fun(#pubsub_node{type = Type, id = NodeId}) ->
- node_call(Type, get_items, [NodeId, service_jid(Host)])
- end,
+ Action = fun (#pubsub_node{type = Type, id = NodeId}) ->
+ node_call(Type, get_items, [NodeId, service_jid(Host)])
+ end,
case transaction(Host, Node, Action, sync_dirty) of
- {result, {_, Items}} -> Items;
- Error -> Error
+ {result, {_, Items}} -> Items;
+ Error -> Error
end.
+
get_item(Host, Node, ItemId) ->
- Action = fun(#pubsub_node{type = Type, id = NodeId}) ->
- node_call(Type, get_item, [NodeId, ItemId])
- end,
+ Action = fun (#pubsub_node{type = Type, id = NodeId}) ->
+ node_call(Type, get_item, [NodeId, ItemId])
+ end,
case transaction(Host, Node, Action, sync_dirty) of
- {result, {_, Items}} -> Items;
- Error -> Error
+ {result, {_, Items}} -> Items;
+ Error -> Error
end.
+
get_allowed_items_call(Host, NodeIdx, From, Type, Options, Owners) ->
case get_allowed_items_call(Host, NodeIdx, From, Type, Options, Owners, none) of
{result, {I, _}} -> {result, I};
@@ -2151,9 +2970,11 @@ get_allowed_items_call(Host, NodeIdx, From, Type, Options, Owners) ->
get_allowed_items_call(Host, NodeIdx, From, Type, Options, Owners, RSM) ->
AccessModel = get_option(Options, access_model),
AllowedGroups = get_option(Options, roster_groups_allowed, []),
- {PresenceSubscription, RosterGroup} = get_presence_and_roster_permissions(Host, From, Owners, AccessModel, AllowedGroups),
- node_call(Type, get_items, [NodeIdx, From, AccessModel, PresenceSubscription, RosterGroup, undefined, RSM]).
-
+ {PresenceSubscription, RosterGroup} =
+ get_presence_and_roster_permissions(Host, From, Owners, AccessModel,
+ AllowedGroups),
+ node_call(Type, get_items,
+ [NodeIdx, From, AccessModel, PresenceSubscription, RosterGroup, undefined, RSM]).
%% @spec (Host, Node, NodeId, Type, LJID, Number) -> any()
%% Host = pubsubHost()
@@ -2172,43 +2993,51 @@ send_items(Host, Node, NodeId, Type, LJID, last) ->
{result, [LastItem]} ->
{ModifNow, ModifUSR} = LastItem#pubsub_item.modification,
event_stanza_with_delay(
- [{xmlelement, "items", nodeAttr(Node),
- itemsEls([LastItem])}], ModifNow, ModifUSR);
+ [#xmlel{name = <<"items">>, attrs = nodeAttr(Node),
+ children = itemsEls([LastItem])}], ModifNow, ModifUSR);
_ ->
event_stanza(
- [{xmlelement, "items", nodeAttr(Node),
- itemsEls([])}])
+ [#xmlel{name = <<"items">>, attrs = nodeAttr(Node),
+ children = itemsEls([])}])
end;
- LastItem ->
- {ModifNow, ModifUSR} = LastItem#pubsub_item.modification,
- event_stanza_with_delay(
- [{xmlelement, "items", nodeAttr(Node),
- itemsEls([LastItem])}], ModifNow, ModifUSR)
+ LastItem ->
+ {ModifNow, ModifUSR} =
+ LastItem#pubsub_item.modification,
+ event_stanza_with_delay([#xmlel{name =
+ <<"items">>,
+ attrs = nodeAttr(Node),
+ children =
+ itemsEls([LastItem])}],
+ ModifNow, ModifUSR)
end,
ejabberd_router:route(service_jid(Host), jlib:make_jid(LJID), Stanza);
-send_items(Host, Node, NodeId, Type, LJID, Number) ->
- ToSend = case node_action(Host, Type, get_items, [NodeId, LJID]) of
- {result, []} ->
- [];
- {result, Items} ->
- case Number of
- N when N > 0 -> lists:sublist(Items, N);
- _ -> Items
- end;
- _ ->
- []
- end,
+send_items(Host, Node, NodeId, Type, {U, S, R} = LJID,
+ Number) ->
+ ToSend = case node_action(Host, Type, get_items,
+ [NodeId, LJID])
+ of
+ {result, []} -> [];
+ {result, Items} ->
+ case Number of
+ N when N > 0 -> lists:sublist(Items, N);
+ _ -> Items
+ end;
+ _ -> []
+ end,
Stanza = case ToSend of
- [LastItem] ->
- {ModifNow, ModifUSR} = LastItem#pubsub_item.modification,
- event_stanza_with_delay(
- [{xmlelement, "items", nodeAttr(Node),
- itemsEls(ToSend)}], ModifNow, ModifUSR);
- _ ->
- event_stanza(
- [{xmlelement, "items", nodeAttr(Node),
- itemsEls(ToSend)}])
- end,
+ [LastItem] ->
+ {ModifNow, ModifUSR} =
+ LastItem#pubsub_item.modification,
+ event_stanza_with_delay([#xmlel{name = <<"items">>,
+ attrs = nodeAttr(Node),
+ children =
+ itemsEls(ToSend)}],
+ ModifNow, ModifUSR);
+ _ ->
+ event_stanza([#xmlel{name = <<"items">>,
+ attrs = nodeAttr(Node),
+ children = itemsEls(ToSend)}])
+ end,
ejabberd_router:route(service_jid(Host), jlib:make_jid(LJID), Stanza).
%% @spec (Host, JID, Plugins) -> {error, Reason} | {result, Response}
@@ -2218,259 +3047,340 @@ send_items(Host, Node, NodeId, Type, LJID, Number) ->
%% Reason = stanzaError()
%% Response = [pubsubIQResponse()]
%% @doc <p>Return the list of affiliations as an XMPP response.</p>
-get_affiliations(Host, <<>>, JID, Plugins) when is_list(Plugins) ->
- Result = lists:foldl(
- fun(Type, {Status, Acc}) ->
- Features = features(Type),
- RetrieveFeature = lists:member("retrieve-affiliations", Features),
- if
- not RetrieveFeature ->
- %% Service does not support retreive affiliatons
- {{error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "retrieve-affiliations")}, Acc};
- true ->
- {result, Affiliations} = node_action(Host, Type, get_entity_affiliations, [Host, JID]),
- {Status, [Affiliations|Acc]}
- end
- end, {ok, []}, Plugins),
+-spec(get_affiliations/4 ::
+(
+ Host :: mod_pubsub:host(),
+ Node :: mod_pubsub:nodeId(),
+ JID :: jid(),
+ Plugins :: [binary()])
+ -> {result, [xmlel(),...]}
+ %%%
+ | {error, xmlel()}
+).
+get_affiliations(Host, <<>>, JID, Plugins)
+ when is_list(Plugins) ->
+ Result = lists:foldl(fun (Type, {Status, Acc}) ->
+ Features = features(Type),
+ RetrieveFeature =
+ lists:member(<<"retrieve-affiliations">>, Features),
+ if not RetrieveFeature ->
+ {{error,
+ extended_error(?ERR_FEATURE_NOT_IMPLEMENTED,
+ unsupported,
+ <<"retrieve-affiliations">>)},
+ Acc};
+ true ->
+ {result, Affiliations} =
+ node_action(Host, Type,
+ get_entity_affiliations,
+ [Host, JID]),
+ {Status, [Affiliations | Acc]}
+ end
+ end,
+ {ok, []}, Plugins),
case Result of
- {ok, Affiliations} ->
- Entities = lists:flatmap(
- fun({_, none}) -> [];
- ({#pubsub_node{nodeid = {_, Node}}, Affiliation}) ->
- [{xmlelement, "affiliation",
- [{"affiliation", affiliation_to_string(Affiliation)}|nodeAttr(Node)],
- []}]
- end, lists:usort(lists:flatten(Affiliations))),
- {result, [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}],
- [{xmlelement, "affiliations", [],
- Entities}]}]};
- {Error, _} ->
- Error
+ {ok, Affiliations} ->
+ Entities = lists:flatmap(fun ({_, none}) -> [];
+ ({#pubsub_node{nodeid = {_, Node}},
+ Affiliation}) ->
+ [#xmlel{name = <<"affiliation">>,
+ attrs =
+ [{<<"affiliation">>,
+ affiliation_to_string(Affiliation)}
+ | nodeAttr(Node)],
+ children = []}]
+ end,
+ lists:usort(lists:flatten(Affiliations))),
+ {result,
+ [#xmlel{name = <<"pubsub">>,
+ attrs = [{<<"xmlns">>, ?NS_PUBSUB}],
+ children =
+ [#xmlel{name = <<"affiliations">>, attrs = [],
+ children = Entities}]}]};
+ {Error, _} -> Error
end;
-get_affiliations(Host, NodeId, JID, Plugins) when is_list(Plugins) ->
- Result = lists:foldl(
- fun(Type, {Status, Acc}) ->
- Features = features(Type),
- RetrieveFeature = lists:member("retrieve-affiliations", Features),
- if
- not RetrieveFeature ->
- %% Service does not support retreive affiliatons
- {{error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "retrieve-affiliations")}, Acc};
- true ->
- {result, Affiliations} = node_action(Host, Type, get_entity_affiliations, [Host, JID]),
- {Status, [Affiliations|Acc]}
- end
- end, {ok, []}, Plugins),
+get_affiliations(Host, NodeId, JID, Plugins)
+ when is_list(Plugins) ->
+ Result = lists:foldl(fun (Type, {Status, Acc}) ->
+ Features = features(Type),
+ RetrieveFeature =
+ lists:member(<<"retrieve-affiliations">>,
+ Features),
+ if not RetrieveFeature ->
+ {{error,
+ extended_error(?ERR_FEATURE_NOT_IMPLEMENTED,
+ unsupported,
+ <<"retrieve-affiliations">>)},
+ Acc};
+ true ->
+ {result, Affiliations} =
+ node_action(Host, Type,
+ get_entity_affiliations,
+ [Host, JID]),
+ {Status, [Affiliations | Acc]}
+ end
+ end,
+ {ok, []}, Plugins),
case Result of
- {ok, Affiliations} ->
- Entities = lists:flatmap(
- fun({_, none}) -> [];
- ({#pubsub_node{nodeid = {_, Node}}, Affiliation})
- when NodeId == Node ->
- [{xmlelement, "affiliation",
- [{"affiliation", affiliation_to_string(Affiliation)}|nodeAttr(Node)],
- []}];
- (_) ->
- []
- end, lists:usort(lists:flatten(Affiliations))),
- {result, [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}],
- [{xmlelement, "affiliations", [],
- Entities}]}]};
- {Error, _} ->
- Error
+ {ok, Affiliations} ->
+ Entities = lists:flatmap(fun ({_, none}) -> [];
+ ({#pubsub_node{nodeid = {_, Node}},
+ Affiliation})
+ when NodeId == Node ->
+ [#xmlel{name = <<"affiliation">>,
+ attrs =
+ [{<<"affiliation">>,
+ affiliation_to_string(Affiliation)}
+ | nodeAttr(Node)],
+ children = []}];
+ (_) -> []
+ end,
+ lists:usort(lists:flatten(Affiliations))),
+ {result,
+ [#xmlel{name = <<"pubsub">>,
+ attrs = [{<<"xmlns">>, ?NS_PUBSUB}],
+ children =
+ [#xmlel{name = <<"affiliations">>, attrs = [],
+ children = Entities}]}]};
+ {Error, _} -> Error
end.
-
+-spec(get_affiliations/3 ::
+(
+ Host :: mod_pubsub:host(),
+ Node :: mod_pubsub:nodeId(),
+ JID :: jid())
+ -> {result, [xmlel(),...]}
+ %%%
+ | {error, xmlel()}
+).
get_affiliations(Host, Node, JID) ->
- Action = fun(#pubsub_node{type = Type, id = NodeId}) ->
+ Action = fun (#pubsub_node{type = Type, id = NodeId}) ->
Features = features(Type),
- RetrieveFeature = lists:member("modify-affiliations", Features),
- {result, Affiliation} = node_call(Type, get_affiliation, [NodeId, JID]),
- if
- not RetrieveFeature ->
- %% Service does not support modify affiliations
- {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "modify-affiliations")};
- Affiliation /= owner ->
- %% Entity is not an owner
- {error, ?ERR_FORBIDDEN};
- true ->
- node_call(Type, get_node_affiliations, [NodeId])
+ RetrieveFeature =
+ lists:member(<<"modify-affiliations">>, Features),
+ {result, Affiliation} = node_call(Type, get_affiliation,
+ [NodeId, JID]),
+ if not RetrieveFeature ->
+ {error,
+ extended_error(?ERR_FEATURE_NOT_IMPLEMENTED,
+ unsupported,
+ <<"modify-affiliations">>)};
+ Affiliation /= owner -> {error, ?ERR_FORBIDDEN};
+ true -> node_call(Type, get_node_affiliations, [NodeId])
end
end,
case transaction(Host, Node, Action, sync_dirty) of
- {result, {_, []}} ->
- {error, ?ERR_ITEM_NOT_FOUND};
- {result, {_, Affiliations}} ->
- Entities = lists:flatmap(
- fun({_, none}) -> [];
- ({AJID, Affiliation}) ->
- [{xmlelement, "affiliation",
- [{"jid", jlib:jid_to_string(AJID)},
- {"affiliation", affiliation_to_string(Affiliation)}],
- []}]
- end, Affiliations),
- {result, [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB_OWNER}],
- [{xmlelement, "affiliations", nodeAttr(Node),
- Entities}]}]};
- Error ->
- Error
+ {result, {_, []}} -> {error, ?ERR_ITEM_NOT_FOUND};
+ {result, {_, Affiliations}} ->
+ Entities = lists:flatmap(fun ({_, none}) -> [];
+ ({AJID, Affiliation}) ->
+ [#xmlel{name = <<"affiliation">>,
+ attrs =
+ [{<<"jid">>,
+ jlib:jid_to_string(AJID)},
+ {<<"affiliation">>,
+ affiliation_to_string(Affiliation)}],
+ children = []}]
+ end,
+ Affiliations),
+ {result,
+ [#xmlel{name = <<"pubsub">>,
+ attrs = [{<<"xmlns">>, ?NS_PUBSUB_OWNER}],
+ children =
+ [#xmlel{name = <<"affiliations">>,
+ attrs = nodeAttr(Node), children = Entities}]}]};
+ Error -> Error
end.
+-spec(set_affiliations/4 ::
+(
+ Host :: mod_pubsub:host(),
+ Node :: mod_pubsub:nodeId(),
+ From :: jid(),
+ EntitiesEls :: [xmlel()])
+ -> {result, []}
+ %%%
+ | {error, xmlel()}
+).
set_affiliations(Host, Node, From, EntitiesEls) ->
Owner = jlib:jid_tolower(jlib:jid_remove_resource(From)),
- Entities =
- lists:foldl(
- fun(El, Acc) ->
- case Acc of
- error ->
- error;
- _ ->
- case El of
- {xmlelement, "affiliation", Attrs, _} ->
- JID = jlib:string_to_jid(
- xml:get_attr_s("jid", Attrs)),
- Affiliation = string_to_affiliation(
- xml:get_attr_s("affiliation", Attrs)),
- if
- (JID == error) or
- (Affiliation == false) ->
- error;
- true ->
- [{jlib:jid_tolower(JID), Affiliation} | Acc]
- end
- end
- end
- end, [], EntitiesEls),
+ Entities = lists:foldl(fun (El, Acc) ->
+ case Acc of
+ error -> error;
+ _ ->
+ case El of
+ #xmlel{name = <<"affiliation">>,
+ attrs = Attrs} ->
+ JID =
+ jlib:string_to_jid(xml:get_attr_s(<<"jid">>,
+ Attrs)),
+ Affiliation =
+ string_to_affiliation(xml:get_attr_s(<<"affiliation">>,
+ Attrs)),
+ if (JID == error) or
+ (Affiliation == false) ->
+ error;
+ true ->
+ [{jlib:jid_tolower(JID),
+ Affiliation}
+ | Acc]
+ end
+ end
+ end
+ end,
+ [], EntitiesEls),
case Entities of
- error ->
- {error, ?ERR_BAD_REQUEST};
- _ ->
- Action = fun(#pubsub_node{type = Type, id = NodeId}) ->
- Owners = node_owners_call(Type, NodeId),
- case lists:member(Owner, Owners) of
- true ->
- OwnerJID = jlib:make_jid(Owner),
- FilteredEntities = case Owners of
- [Owner] -> [E || E <- Entities, element(1, E) =/= OwnerJID];
- _ -> Entities
- end,
- lists:foreach(
- fun({JID, Affiliation}) ->
- node_call(Type, set_affiliation, [NodeId, JID, Affiliation])
- end, FilteredEntities),
- {result, []};
- _ ->
- {error, ?ERR_FORBIDDEN}
- end
- end,
- case transaction(Host, Node, Action, sync_dirty) of
- {result, {_, Result}} -> {result, Result};
- Other -> Other
- end
+ error -> {error, ?ERR_BAD_REQUEST};
+ _ ->
+ Action = fun (#pubsub_node{type = Type,
+ id = NodeId} =
+ N) ->
+ Owners = node_owners_call(Type, NodeId),
+ case lists:member(Owner, Owners) of
+ true ->
+ OwnerJID = jlib:make_jid(Owner),
+ FilteredEntities = case Owners of
+ [Owner] ->
+ [E
+ || E <- Entities,
+ element(1, E) =/=
+ OwnerJID];
+ _ -> Entities
+ end,
+ lists:foreach(fun ({JID, Affiliation}) ->
+ node_call(Type, set_affiliation, [NodeId, JID, Affiliation])
+ end,
+ FilteredEntities),
+ {result, []};
+ _ -> {error, ?ERR_FORBIDDEN}
+ end
+ end,
+ case transaction(Host, Node, Action, sync_dirty) of
+ {result, {_, Result}} -> {result, Result};
+ Other -> Other
+ end
end.
get_options(Host, Node, JID, SubID, Lang) ->
- Action = fun(#pubsub_node{type = Type, id = NodeID}) ->
- case lists:member("subscription-options", features(Type)) of
- true ->
- get_options_helper(JID, Lang, Node, NodeID, SubID, Type);
- false ->
- {error, extended_error(
- ?ERR_FEATURE_NOT_IMPLEMENTED,
- unsupported, "subscription-options")}
+ Action = fun (#pubsub_node{type = Type, id = NodeID}) ->
+ case lists:member(<<"subscription-options">>, features(Type)) of
+ true ->
+ get_options_helper(JID, Lang, Node, NodeID, SubID, Type);
+ false ->
+ {error,
+ extended_error(?ERR_FEATURE_NOT_IMPLEMENTED,
+ unsupported,
+ <<"subscription-options">>)}
end
end,
case transaction(Host, Node, Action, sync_dirty) of
- {result, {_Node, XForm}} -> {result, [XForm]};
- Error -> Error
+ {result, {_Node, XForm}} -> {result, [XForm]};
+ Error -> Error
end.
get_options_helper(JID, Lang, Node, NodeID, SubID, Type) ->
Subscriber = case jlib:string_to_jid(JID) of
- error -> {"", "", ""};
- J -> jlib:jid_tolower(J)
+ error -> {<<"">>, <<"">>, <<"">>};
+ J -> case jlib:jid_tolower(J) of
+ error -> {<<"">>, <<"">>, <<"">>};
+ J1 -> J1
+ end
end,
{result, Subs} = node_call(Type, get_subscriptions,
[NodeID, Subscriber]),
- SubIDs = lists:foldl(fun({subscribed, SID}, Acc) ->
+ SubIDs = lists:foldl(fun ({subscribed, SID}, Acc) ->
[SID | Acc];
- (_, Acc) ->
- Acc
- end, [], Subs),
+ (_, Acc) -> Acc
+ end,
+ [], Subs),
case {SubID, SubIDs} of
- {_, []} ->
- {error, extended_error(?ERR_NOT_ACCEPTABLE, "not-subscribed")};
- {[], [SID]} ->
- read_sub(Subscriber, Node, NodeID, SID, Lang);
- {[], _} ->
- {error, extended_error(?ERR_NOT_ACCEPTABLE, "subid-required")};
- {_, _} ->
- read_sub(Subscriber, Node, NodeID, SubID, Lang)
+ {_, []} ->
+ {error,
+ extended_error(?ERR_NOT_ACCEPTABLE, <<"not-subscribed">>)};
+ {<<>>, [SID]} ->
+ read_sub(Subscriber, Node, NodeID, SID, Lang);
+ {<<>>, _} ->
+ {error,
+ extended_error(?ERR_NOT_ACCEPTABLE, <<"subid-required">>)};
+ {_, _} ->
+ read_sub(Subscriber, Node, NodeID, SubID, Lang)
end.
read_sub(Subscriber, Node, NodeID, SubID, Lang) ->
case pubsub_subscription_odbc:get_subscription(Subscriber, NodeID, SubID) of
{error, notfound} ->
- {error, extended_error(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
+ {error, extended_error(?ERR_NOT_ACCEPTABLE, <<"invalid-subid">>)};
{result, #pubsub_subscription{options = Options}} ->
{result, XdataEl} = pubsub_subscription_odbc:get_options_xform(Lang, Options),
- OptionsEl = {xmlelement, "options", [{"jid", jlib:jid_to_string(Subscriber)},
- {"subid", SubID}|nodeAttr(Node)],
- [XdataEl]},
- PubsubEl = {xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}], [OptionsEl]},
+ OptionsEl = #xmlel{name = <<"options">>,
+ attrs =
+ [{<<"jid">>, jlib:jid_to_string(Subscriber)},
+ {<<"subid">>, SubID}
+ | nodeAttr(Node)],
+ children = [XdataEl]},
+ PubsubEl = #xmlel{name = <<"pubsub">>,
+ attrs = [{<<"xmlns">>, ?NS_PUBSUB}],
+ children = [OptionsEl]},
{result, PubsubEl}
end.
set_options(Host, Node, JID, SubID, Configuration) ->
- Action = fun(#pubsub_node{type = Type, id = NodeID}) ->
- case lists:member("subscription-options", features(Type)) of
- true ->
- set_options_helper(Configuration, JID, NodeID,
- SubID, Type);
- false ->
- {error, extended_error(
- ?ERR_FEATURE_NOT_IMPLEMENTED,
- unsupported, "subscription-options")}
+ Action = fun (#pubsub_node{type = Type, id = NodeID}) ->
+ case lists:member(<<"subscription-options">>,
+ features(Type))
+ of
+ true ->
+ set_options_helper(Configuration, JID, NodeID, SubID,
+ Type);
+ false ->
+ {error,
+ extended_error(?ERR_FEATURE_NOT_IMPLEMENTED,
+ unsupported,
+ <<"subscription-options">>)}
end
end,
case transaction(Host, Node, Action, sync_dirty) of
- {result, {_Node, Result}} -> {result, Result};
- Error -> Error
+ {result, {_Node, Result}} -> {result, Result};
+ Error -> Error
end.
set_options_helper(Configuration, JID, NodeID, SubID, Type) ->
SubOpts = case pubsub_subscription_odbc:parse_options_xform(Configuration) of
- {result, GoodSubOpts} -> GoodSubOpts;
- _ -> invalid
- end,
+ {result, GoodSubOpts} -> GoodSubOpts;
+ _ -> invalid
+ end,
Subscriber = case jlib:string_to_jid(JID) of
- error -> {"", "", ""};
- J -> jlib:jid_tolower(J)
+ error -> {<<"">>, <<"">>, <<"">>};
+ J -> jlib:jid_tolower(J)
end,
{result, Subs} = node_call(Type, get_subscriptions,
[NodeID, Subscriber]),
- SubIDs = lists:foldl(fun({subscribed, SID}, Acc) ->
+ SubIDs = lists:foldl(fun ({subscribed, SID}, Acc) ->
[SID | Acc];
- (_, Acc) ->
- Acc
- end, [], Subs),
+ (_, Acc) -> Acc
+ end,
+ [], Subs),
case {SubID, SubIDs} of
- {_, []} ->
- {error, extended_error(?ERR_NOT_ACCEPTABLE, "not-subscribed")};
- {[], [SID]} ->
- write_sub(Subscriber, NodeID, SID, SubOpts);
- {[], _} ->
- {error, extended_error(?ERR_NOT_ACCEPTABLE, "subid-required")};
- {_, _} ->
- write_sub(Subscriber, NodeID, SubID, SubOpts)
+ {_, []} ->
+ {error,
+ extended_error(?ERR_NOT_ACCEPTABLE,
+ <<"not-subscribed">>)};
+ {<<>>, [SID]} ->
+ write_sub(Subscriber, NodeID, SID, SubOpts);
+ {<<>>, _} ->
+ {error,
+ extended_error(?ERR_NOT_ACCEPTABLE,
+ <<"subid-required">>)};
+ {_, _} -> write_sub(Subscriber, NodeID, SubID, SubOpts)
end.
write_sub(_Subscriber, _NodeID, _SubID, invalid) ->
- {error, extended_error(?ERR_BAD_REQUEST, "invalid-options")};
+ {error, extended_error(?ERR_BAD_REQUEST, <<"invalid-options">>)};
write_sub(Subscriber, NodeID, SubID, Options) ->
case pubsub_subscription_odbc:set_subscription(Subscriber, NodeID, SubID, Options) of
{error, notfound} ->
- {error, extended_error(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
+ {error, extended_error(?ERR_NOT_ACCEPTABLE, <<"invalid-subid">>)};
{result, _} ->
{result, []}
end.
@@ -2487,11 +3397,11 @@ get_subscriptions(Host, Node, JID, Plugins) when is_list(Plugins) ->
Result = lists:foldl(
fun(Type, {Status, Acc}) ->
Features = features(Type),
- RetrieveFeature = lists:member("retrieve-subscriptions", Features),
+ RetrieveFeature = lists:member(<<"retrieve-subscriptions">>, Features),
if
not RetrieveFeature ->
%% Service does not support retreive subscriptions
- {{error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "retrieve-subscriptions")}, Acc};
+ {{error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, <<"retrieve-subscriptions">>)}, Acc};
true ->
Subscriber = jlib:jid_remove_resource(JID),
{result, Subscriptions} = node_action(Host, Type, get_entity_subscriptions, [Host, Subscriber]),
@@ -2499,256 +3409,337 @@ get_subscriptions(Host, Node, JID, Plugins) when is_list(Plugins) ->
end
end, {ok, []}, Plugins),
case Result of
- {ok, Subscriptions} ->
- Entities = lists:flatmap(
- fun({_, none}) ->
- [];
- ({#pubsub_node{nodeid = {_, SubsNode}}, Subscription}) ->
- case Node of
- <<>> ->
- [{xmlelement, "subscription",
- [{"subscription", subscription_to_string(Subscription)}|nodeAttr(SubsNode)],
- []}];
- SubsNode ->
- [{xmlelement, "subscription",
- [{"subscription", subscription_to_string(Subscription)}],
- []}];
- _ ->
- []
- end;
- ({_, none, _}) ->
- [];
- ({#pubsub_node{nodeid = {_, SubsNode}}, Subscription, SubID, SubJID}) ->
- case Node of
- <<>> ->
- [{xmlelement, "subscription",
- [{"jid", jlib:jid_to_string(SubJID)},
- {"subid", SubID},
- {"subscription", subscription_to_string(Subscription)}|nodeAttr(SubsNode)],
- []}];
- SubsNode ->
- [{xmlelement, "subscription",
- [{"jid", jlib:jid_to_string(SubJID)},
- {"subid", SubID},
- {"subscription", subscription_to_string(Subscription)}],
- []}];
- _ ->
- []
- end;
- ({#pubsub_node{nodeid = {_, SubsNode}}, Subscription, SubJID}) ->
- case Node of
- <<>> ->
- [{xmlelement, "subscription",
- [{"jid", jlib:jid_to_string(SubJID)},
- {"subscription", subscription_to_string(Subscription)}|nodeAttr(SubsNode)],
- []}];
- SubsNode ->
- [{xmlelement, "subscription",
- [{"jid", jlib:jid_to_string(SubJID)},
- {"subscription", subscription_to_string(Subscription)}],
- []}];
- _ ->
- []
- end
- end, lists:usort(lists:flatten(Subscriptions))),
- {result, [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}],
- [{xmlelement, "subscriptions", [],
- Entities}]}]};
- {Error, _} ->
- Error
+ {ok, Subscriptions} ->
+ Entities = lists:flatmap(fun ({_, none}) -> [];
+ ({#pubsub_node{nodeid = {_, SubsNode}},
+ Subscription}) ->
+ case Node of
+ <<>> ->
+ [#xmlel{name =
+ <<"subscription">>,
+ attrs =
+ [{<<"subscription">>,
+ subscription_to_string(Subscription)}
+ | nodeAttr(SubsNode)],
+ children = []}];
+ SubsNode ->
+ [#xmlel{name =
+ <<"subscription">>,
+ attrs =
+ [{<<"subscription">>,
+ subscription_to_string(Subscription)}],
+ children = []}];
+ _ -> []
+ end;
+ ({_, none, _}) -> [];
+ ({#pubsub_node{nodeid = {_, SubsNode}},
+ Subscription, SubID, SubJID}) ->
+ case Node of
+ <<>> ->
+ [#xmlel{name =
+ <<"subscription">>,
+ attrs =
+ [{<<"jid">>,
+ jlib:jid_to_string(SubJID)},
+ {<<"subid">>,
+ SubID},
+ {<<"subscription">>,
+ subscription_to_string(Subscription)}
+ | nodeAttr(SubsNode)],
+ children = []}];
+ SubsNode ->
+ [#xmlel{name =
+ <<"subscription">>,
+ attrs =
+ [{<<"jid">>,
+ jlib:jid_to_string(SubJID)},
+ {<<"subid">>,
+ SubID},
+ {<<"subscription">>,
+ subscription_to_string(Subscription)}],
+ children = []}];
+ _ -> []
+ end;
+ ({#pubsub_node{nodeid = {_, SubsNode}},
+ Subscription, SubJID}) ->
+ case Node of
+ <<>> ->
+ [#xmlel{name =
+ <<"subscription">>,
+ attrs =
+ [{<<"jid">>,
+ jlib:jid_to_string(SubJID)},
+ {<<"subscription">>,
+ subscription_to_string(Subscription)}
+ | nodeAttr(SubsNode)],
+ children = []}];
+ SubsNode ->
+ [#xmlel{name =
+ <<"subscription">>,
+ attrs =
+ [{<<"jid">>,
+ jlib:jid_to_string(SubJID)},
+ {<<"subscription">>,
+ subscription_to_string(Subscription)}],
+ children = []}];
+ _ -> []
+ end
+ end,
+ lists:usort(lists:flatten(Subscriptions))),
+ {result,
+ [#xmlel{name = <<"pubsub">>,
+ attrs = [{<<"xmlns">>, ?NS_PUBSUB}],
+ children =
+ [#xmlel{name = <<"subscriptions">>, attrs = [],
+ children = Entities}]}]};
+ {Error, _} -> Error
end.
+
get_subscriptions(Host, Node, JID) ->
- Action = fun(#pubsub_node{type = Type, id = NodeId}) ->
+ Action = fun (#pubsub_node{type = Type, id = NodeId}) ->
Features = features(Type),
- RetrieveFeature = lists:member("manage-subscriptions", Features),
- {result, Affiliation} = node_call(Type, get_affiliation, [NodeId, JID]),
- if
- not RetrieveFeature ->
- %% Service does not support manage subscriptions
- {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "manage-subscriptions")};
- Affiliation /= owner ->
- %% Entity is not an owner
- {error, ?ERR_FORBIDDEN};
- true ->
- node_call(Type, get_node_subscriptions, [NodeId])
+ RetrieveFeature =
+ lists:member(<<"manage-subscriptions">>, Features),
+ {result, Affiliation} = node_call(Type, get_affiliation,
+ [NodeId, JID]),
+ if not RetrieveFeature ->
+ {error,
+ extended_error(?ERR_FEATURE_NOT_IMPLEMENTED,
+ unsupported,
+ <<"manage-subscriptions">>)};
+ Affiliation /= owner -> {error, ?ERR_FORBIDDEN};
+ true ->
+ node_call(Type, get_node_subscriptions, [NodeId])
end
end,
case transaction(Host, Node, Action, sync_dirty) of
- {result, {_, Subscriptions}} ->
- Entities = lists:flatmap(
- fun({_, none}) -> [];
- ({_, pending, _}) -> [];
- ({AJID, Subscription}) ->
- [{xmlelement, "subscription",
- [{"jid", jlib:jid_to_string(AJID)},
- {"subscription", subscription_to_string(Subscription)}],
- []}];
- ({AJID, Subscription, SubId}) ->
- [{xmlelement, "subscription",
- [{"jid", jlib:jid_to_string(AJID)},
- {"subscription", subscription_to_string(Subscription)},
- {"subid", SubId}],
- []}]
- end, Subscriptions),
- {result, [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB_OWNER}],
- [{xmlelement, "subscriptions", nodeAttr(Node),
- Entities}]}]};
- Error ->
- Error
+ {result, {_, Subscriptions}} ->
+ Entities = lists:flatmap(fun ({_, none}) -> [];
+ ({_, pending, _}) -> [];
+ ({AJID, Subscription}) ->
+ [#xmlel{name = <<"subscription">>,
+ attrs =
+ [{<<"jid">>,
+ jlib:jid_to_string(AJID)},
+ {<<"subscription">>,
+ subscription_to_string(Subscription)}],
+ children = []}];
+ ({AJID, Subscription, SubId}) ->
+ [#xmlel{name = <<"subscription">>,
+ attrs =
+ [{<<"jid">>,
+ jlib:jid_to_string(AJID)},
+ {<<"subscription">>,
+ subscription_to_string(Subscription)},
+ {<<"subid">>, SubId}],
+ children = []}]
+ end,
+ Subscriptions),
+ {result,
+ [#xmlel{name = <<"pubsub">>,
+ attrs = [{<<"xmlns">>, ?NS_PUBSUB_OWNER}],
+ children =
+ [#xmlel{name = <<"subscriptions">>,
+ attrs = nodeAttr(Node), children = Entities}]}]};
+ Error -> Error
end.
set_subscriptions(Host, Node, From, EntitiesEls) ->
- Owner = jlib:jid_tolower(jlib:jid_remove_resource(From)),
- Entities =
- lists:foldl(
- fun(El, Acc) ->
- case Acc of
- error ->
- error;
- _ ->
- case El of
- {xmlelement, "subscription", Attrs, _} ->
- JID = jlib:string_to_jid(
- xml:get_attr_s("jid", Attrs)),
- Subscription = string_to_subscription(
- xml:get_attr_s("subscription", Attrs)),
- SubId = xml:get_attr_s("subid", Attrs),
- if
- (JID == error) or
- (Subscription == false) ->
- error;
- true ->
- [{jlib:jid_tolower(JID), Subscription, SubId} | Acc]
- end
- end
- end
- end, [], EntitiesEls),
+ Owner =
+ jlib:jid_tolower(jlib:jid_remove_resource(From)),
+ Entities = lists:foldl(fun (El, Acc) ->
+ case Acc of
+ error -> error;
+ _ ->
+ case El of
+ #xmlel{name = <<"subscription">>,
+ attrs = Attrs} ->
+ JID =
+ jlib:string_to_jid(xml:get_attr_s(<<"jid">>,
+ Attrs)),
+ Subscription =
+ string_to_subscription(xml:get_attr_s(<<"subscription">>,
+ Attrs)),
+ SubId =
+ xml:get_attr_s(<<"subid">>,
+ Attrs),
+ if (JID == error) or
+ (Subscription == false) ->
+ error;
+ true ->
+ [{jlib:jid_tolower(JID),
+ Subscription, SubId}
+ | Acc]
+ end
+ end
+ end
+ end,
+ [], EntitiesEls),
case Entities of
- error ->
- {error, ?ERR_BAD_REQUEST};
- _ ->
- Notify = fun(JID, Sub, _SubId) ->
- Stanza = {xmlelement, "message", [],
- [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}],
- [{xmlelement, "subscription",
- [{"jid", jlib:jid_to_string(JID)},
- %{"subid", SubId},
- {"subscription", subscription_to_string(Sub)} | nodeAttr(Node)], []}]}]},
- ejabberd_router:route(service_jid(Host), jlib:make_jid(JID), Stanza)
- end,
- Action = fun(#pubsub_node{type = Type, id = NodeId}) ->
- case lists:member(Owner, node_owners_call(Type, NodeId)) of
- true ->
- Result = lists:foldl(fun({JID, Subscription, SubId}, Acc) ->
-
- case node_call(Type, set_subscriptions, [NodeId, JID, Subscription, SubId]) of
- {error, Err} -> [{error, Err} | Acc];
- _ -> Notify(JID, Subscription, SubId), Acc
- end
- end, [], Entities),
- case Result of
- [] -> {result, []};
- _ -> {error, ?ERR_NOT_ACCEPTABLE}
- end;
- _ ->
- {error, ?ERR_FORBIDDEN}
- end
- end,
- case transaction(Host, Node, Action, sync_dirty) of
- {result, {_, Result}} -> {result, Result};
- Other -> Other
- end
+ error -> {error, ?ERR_BAD_REQUEST};
+ _ ->
+ Notify = fun (JID, Sub, _SubId) ->
+ Stanza = #xmlel{name = <<"message">>, attrs = [],
+ children =
+ [#xmlel{name = <<"pubsub">>,
+ attrs =
+ [{<<"xmlns">>,
+ ?NS_PUBSUB}],
+ children =
+ [#xmlel{name =
+ <<"subscription">>,
+ attrs =
+ [{<<"jid">>,
+ jlib:jid_to_string(JID)},
+ {<<"subscription">>,
+ subscription_to_string(Sub)}
+ | nodeAttr(Node)],
+ children =
+ []}]}]},
+ ejabberd_router:route(service_jid(Host),
+ jlib:make_jid(JID), Stanza)
+ end,
+ Action = fun (#pubsub_node{type = Type,
+ id = NodeId}) ->
+ case lists:member(Owner, node_owners_call(Type, NodeId)) of
+ true ->
+ Result = lists:foldl(fun ({JID, Subscription,
+ SubId},
+ Acc) ->
+ case
+ node_call(Type,
+ set_subscriptions,
+ [NodeId,
+ JID,
+ Subscription,
+ SubId])
+ of
+ {error, Err} ->
+ [{error,
+ Err}
+ | Acc];
+ _ ->
+ Notify(JID,
+ Subscription,
+ SubId),
+ Acc
+ end
+ end,
+ [], Entities),
+ case Result of
+ [] -> {result, []};
+ _ -> {error, ?ERR_NOT_ACCEPTABLE}
+ end;
+ _ -> {error, ?ERR_FORBIDDEN}
+ end
+ end,
+ case transaction(Host, Node, Action, sync_dirty) of
+ {result, {_, Result}} -> {result, Result};
+ Other -> Other
+ end
end.
+-spec(get_presence_and_roster_permissions/5 ::
+(
+ Host :: mod_pubsub:host(),
+ From :: ljid(),
+ Owners :: [ljid(),...],
+ AccessModel :: mod_pubsub:accessModel(),
+ AllowedGroups :: [binary()])
+ -> {PresenceSubscription::boolean(), RosterGroup::boolean()}
+).
get_presence_and_roster_permissions(Host, From, Owners, AccessModel, AllowedGroups) ->
if (AccessModel == presence) or (AccessModel == roster) ->
- case Host of
- {User, Server, _} ->
- get_roster_info(User, Server, From, AllowedGroups);
- _ ->
- [{OUser, OServer, _}|_] = Owners,
- get_roster_info(OUser, OServer, From, AllowedGroups)
- end;
- true ->
- {true, true}
+ case Host of
+ {User, Server, _} ->
+ get_roster_info(User, Server, From, AllowedGroups);
+ _ ->
+ [{OUser, OServer, _} | _] = Owners,
+ get_roster_info(OUser, OServer, From, AllowedGroups)
+ end;
+ true -> {true, true}
end.
%% @spec (OwnerUser, OwnerServer, {SubscriberUser, SubscriberServer, SubscriberResource}, AllowedGroups)
%% -> {PresenceSubscription, RosterGroup}
-get_roster_info(_, _, {"", "", _}, _) ->
+get_roster_info(_, _, {<<"">>, <<"">>, _}, _) ->
{false, false};
-get_roster_info(OwnerUser, OwnerServer, {SubscriberUser, SubscriberServer, _}, AllowedGroups) ->
+get_roster_info(OwnerUser, OwnerServer,
+ {SubscriberUser, SubscriberServer, _}, AllowedGroups) ->
{Subscription, Groups} =
- ejabberd_hooks:run_fold(
- roster_get_jid_info, OwnerServer,
- {none, []},
- [OwnerUser, OwnerServer, {SubscriberUser, SubscriberServer, ""}]),
- PresenceSubscription = (Subscription == both) orelse (Subscription == from)
- orelse ({OwnerUser, OwnerServer} == {SubscriberUser, SubscriberServer}),
- RosterGroup = lists:any(fun(Group) ->
+ ejabberd_hooks:run_fold(roster_get_jid_info,
+ OwnerServer, {none, []},
+ [OwnerUser, OwnerServer,
+ {SubscriberUser, SubscriberServer, <<"">>}]),
+ PresenceSubscription = Subscription == both orelse
+ Subscription == from orelse
+ {OwnerUser, OwnerServer} ==
+ {SubscriberUser, SubscriberServer},
+ RosterGroup = lists:any(fun (Group) ->
lists:member(Group, AllowedGroups)
- end, Groups),
+ end,
+ Groups),
{PresenceSubscription, RosterGroup};
-get_roster_info(OwnerUser, OwnerServer, JID, AllowedGroups) ->
- get_roster_info(OwnerUser, OwnerServer, jlib:jid_tolower(JID), AllowedGroups).
-
%% @spec (AffiliationStr) -> Affiliation
%% AffiliationStr = string()
%% Affiliation = atom()
%% @doc <p>Convert an affiliation type from string to atom.</p>
-string_to_affiliation("owner") -> owner;
-string_to_affiliation("publisher") -> publisher;
-string_to_affiliation("member") -> member;
-string_to_affiliation("outcast") -> outcast;
-string_to_affiliation("none") -> none;
+get_roster_info(OwnerUser, OwnerServer, JID,
+ AllowedGroups) ->
+ get_roster_info(OwnerUser, OwnerServer,
+ jlib:jid_tolower(JID), AllowedGroups).
+
+string_to_affiliation(<<"owner">>) -> owner;
+string_to_affiliation(<<"publisher">>) -> publisher;
+string_to_affiliation(<<"member">>) -> member;
+string_to_affiliation(<<"outcast">>) -> outcast;
+string_to_affiliation(<<"none">>) -> none;
string_to_affiliation(_) -> false.
%% @spec (SubscriptionStr) -> Subscription
%% SubscriptionStr = string()
%% Subscription = atom()
%% @doc <p>Convert a subscription type from string to atom.</p>
-string_to_subscription("subscribed") -> subscribed;
-string_to_subscription("pending") -> pending;
-string_to_subscription("unconfigured") -> unconfigured;
-string_to_subscription("none") -> none;
+string_to_subscription(<<"subscribed">>) -> subscribed;
+string_to_subscription(<<"pending">>) -> pending;
+string_to_subscription(<<"unconfigured">>) ->
+ unconfigured;
+string_to_subscription(<<"none">>) -> none;
string_to_subscription(_) -> false.
%% @spec (Affiliation) -> AffiliationStr
%% Affiliation = atom()
%% AffiliationStr = string()
%% @doc <p>Convert an affiliation type from atom to string.</p>
-affiliation_to_string(owner) -> "owner";
-affiliation_to_string(publisher) -> "publisher";
-affiliation_to_string(member) -> "member";
-affiliation_to_string(outcast) -> "outcast";
-affiliation_to_string(_) -> "none".
-
%% @spec (Subscription) -> SubscriptionStr
%% Subscription = atom()
%% SubscriptionStr = string()
%% @doc <p>Convert a subscription type from atom to string.</p>
-subscription_to_string(subscribed) -> "subscribed";
-subscription_to_string(pending) -> "pending";
-subscription_to_string(unconfigured) -> "unconfigured";
-subscription_to_string(_) -> "none".
-
%% @spec (Node) -> NodeStr
%% Node = pubsubNode()
%% NodeStr = string()
%% @doc <p>Convert a node type from pubsubNode to string.</p>
-node_to_string(Node) -> binary_to_list(Node).
-string_to_node(SNode) -> list_to_binary(SNode).
-
%% @spec (Host) -> jid()
%% Host = host()
%% @doc <p>Generate pubsub service JID.</p>
+affiliation_to_string(owner) -> <<"owner">>;
+affiliation_to_string(publisher) -> <<"publisher">>;
+affiliation_to_string(member) -> <<"member">>;
+affiliation_to_string(outcast) -> <<"outcast">>;
+affiliation_to_string(_) -> <<"none">>.
+
+subscription_to_string(subscribed) -> <<"subscribed">>;
+subscription_to_string(pending) -> <<"pending">>;
+subscription_to_string(unconfigured) -> <<"unconfigured">>;
+subscription_to_string(_) -> <<"none">>.
+
+-spec(service_jid/1 ::
+(
+ Host :: mod_pubsub:host())
+ -> jid()
+).
service_jid(Host) ->
- case Host of
- {U,S,_} -> {jid, U, S, "", U, S, ""};
- _ -> {jid, "", Host, "", "", Host, ""}
- end.
-
%% @spec (LJID, NotifyType, Depth, NodeOptions, SubOptions) -> boolean()
%% LJID = jid()
%% NotifyType = items | nodes
@@ -2757,14 +3748,21 @@ service_jid(Host) ->
%% SubOptions = [{atom(), term()}]
%% @doc <p>Check if a notification must be delivered or not based on
%% node and subscription options.</p>
-is_to_deliver(LJID, NotifyType, Depth, NodeOptions, SubOptions) ->
+ case Host of
+ {U, S, _} -> {jid, U, S, <<"">>, U, S, <<"">>};
+ _ -> {jid, <<"">>, Host, <<"">>, <<"">>, Host, <<"">>}
+ end.
+
+is_to_deliver(LJID, NotifyType, Depth, NodeOptions,
+ SubOptions) ->
sub_to_deliver(LJID, NotifyType, Depth, SubOptions)
- andalso node_to_deliver(LJID, NodeOptions).
+ andalso node_to_deliver(LJID, NodeOptions).
sub_to_deliver(_LJID, NotifyType, Depth, SubOptions) ->
lists:all(fun (Option) ->
sub_option_can_deliver(NotifyType, Depth, Option)
- end, SubOptions).
+ end,
+ SubOptions).
sub_option_can_deliver(items, _, {subscription_type, nodes}) -> false;
sub_option_can_deliver(nodes, _, {subscription_type, items}) -> false;
@@ -2778,6 +3776,12 @@ node_to_deliver(LJID, NodeOptions) ->
PresenceDelivery = get_option(NodeOptions, presence_based_delivery),
presence_can_deliver(LJID, PresenceDelivery).
+-spec(presence_can_deliver/2 ::
+(
+ Entity :: ljid(),
+ _ :: boolean())
+ -> boolean()
+).
presence_can_deliver(_, false) -> true;
presence_can_deliver({User, Server, Resource}, true) ->
case mnesia:dirty_match_object({session, '_', '_', {User, Server}, '_', '_'}) of
@@ -2794,6 +3798,12 @@ presence_can_deliver({User, Server, Resource}, true) ->
end, false, Ss)
end.
+-spec(state_can_deliver/2 ::
+(
+ Entity::ljid(),
+ SubOptions :: mod_pubsub:subOptions() | [])
+ -> [ljid()]
+).
state_can_deliver({U, S, R}, []) -> [{U, S, R}];
state_can_deliver({U, S, R}, SubOptions) ->
%% Check SubOptions for 'show_values'
@@ -2805,7 +3815,7 @@ state_can_deliver({U, S, R}, SubOptions) ->
%% Get subscriber resources
Resources = case R of
%% If the subscriber JID is a bare one, get all its resources
- [] -> user_resources(U, S);
+ <<>> -> user_resources(U, S);
%% If the subscriber JID is a full one, use its resource
R -> [R]
end,
@@ -2817,8 +3827,14 @@ state_can_deliver({U, S, R}, SubOptions) ->
end, [], Resources)
end.
+-spec(get_resource_state/3 ::
+(
+ Entity :: ljid(),
+ ShowValues :: [binary()],
+ JIDs :: [ljid()])
+ -> [ljid()]
+).
get_resource_state({U, S, R}, ShowValues, JIDs) ->
- %% Get user session PID
case ejabberd_sm:get_session_pid(U, S, R) of
%% If no PID, item can be delivered
none -> lists:append([{U, S, R}], JIDs);
@@ -2827,8 +3843,8 @@ get_resource_state({U, S, R}, ShowValues, JIDs) ->
%% Get user resource state
%% TODO : add a catch clause
Show = case ejabberd_c2s:get_presence(Pid) of
- {_, _, "available", _} -> "online";
- {_, _, State, _} -> State
+ {_, _, <<"available">>, _} -> <<"online">>;
+ {_, _, State, _} -> State
end,
%% Is current resource state listed in 'show-values' suboption ?
case lists:member(Show, ShowValues) of %andalso Show =/= "online" of
@@ -2841,26 +3857,37 @@ get_resource_state({U, S, R}, ShowValues, JIDs) ->
%% @spec (Payload) -> int()
%% Payload = term()
+-spec(payload_xmlelements/1 ::
+(
+ Payload :: mod_pubsub:payload())
+ -> Count :: non_neg_integer()
+).
%% @doc <p>Count occurence of XML elements in payload.</p>
payload_xmlelements(Payload) -> payload_xmlelements(Payload, 0).
payload_xmlelements([], Count) -> Count;
-payload_xmlelements([{xmlelement, _, _, _}|Tail], Count) -> payload_xmlelements(Tail, Count+1);
-payload_xmlelements([_|Tail], Count) -> payload_xmlelements(Tail, Count).
+payload_xmlelements([#xmlel{} | Tail], Count) ->
+ payload_xmlelements(Tail, Count + 1);
+payload_xmlelements([_ | Tail], Count) ->
+ payload_xmlelements(Tail, Count).
%% @spec (Els) -> stanza()
%% Els = [xmlelement()]
%% @doc <p>Build pubsub event stanza</p>
-event_stanza(Els) ->
- event_stanza_withmoreels(Els, []).
+event_stanza(Els) -> event_stanza_withmoreels(Els, []).
event_stanza_with_delay(Els, ModifNow, ModifUSR) ->
DateTime = calendar:now_to_datetime(ModifNow),
- MoreEls = [jlib:timestamp_to_xml(DateTime, utc, ModifUSR, "")],
+ MoreEls = [jlib:timestamp_to_xml(DateTime, utc,
+ ModifUSR, <<"">>)],
event_stanza_withmoreels(Els, MoreEls).
event_stanza_withmoreels(Els, MoreEls) ->
- {xmlelement, "message", [],
- [{xmlelement, "event", [{"xmlns", ?NS_PUBSUB_EVENT}], Els} | MoreEls]}.
+ #xmlel{name = <<"message">>, attrs = [],
+ children =
+ [#xmlel{name = <<"event">>,
+ attrs = [{<<"xmlns">>, ?NS_PUBSUB_EVENT}],
+ children = Els}
+ | MoreEls]}.
%%%%%% broadcast functions
@@ -2872,8 +3899,9 @@ broadcast_publish_item(Host, Node, NodeId, Type, NodeOptions, Removed, ItemId, F
false -> []
end,
Stanza = event_stanza(
- [{xmlelement, "items", nodeAttr(Node),
- [{xmlelement, "item", itemAttr(ItemId), Content}]}]),
+ [#xmlel{name = <<"items">>, attrs = nodeAttr(Node),
+ children = [#xmlel{name = <<"item">>, attrs = itemAttr(ItemId),
+ children = Content}]}]),
broadcast_stanza(Host, From, Node, NodeId, Type,
NodeOptions, SubsByDepth, items, Stanza, true),
case Removed of
@@ -2883,8 +3911,8 @@ broadcast_publish_item(Host, Node, NodeId, Type, NodeOptions, Removed, ItemId, F
case get_option(NodeOptions, notify_retract) of
true ->
RetractStanza = event_stanza(
- [{xmlelement, "items", nodeAttr(Node),
- [{xmlelement, "retract", itemAttr(RId), []} || RId <- Removed]}]),
+ [#xmlel{name = <<"items">>, attrs = nodeAttr(Node),
+ children = [#xmlel{name = <<"retract">>, attrs = itemAttr(RId)} || RId <- Removed]}]),
broadcast_stanza(Host, Node, NodeId, Type,
NodeOptions, SubsByDepth,
items, RetractStanza, true);
@@ -2907,8 +3935,8 @@ broadcast_retract_items(Host, Node, NodeId, Type, NodeOptions, ItemIds, ForceNot
case get_collection_subscriptions(Host, Node) of
SubsByDepth when is_list(SubsByDepth) ->
Stanza = event_stanza(
- [{xmlelement, "items", nodeAttr(Node),
- [{xmlelement, "retract", itemAttr(ItemId), []} || ItemId <- ItemIds]}]),
+ [#xmlel{name = <<"items">>, attrs = nodeAttr(Node),
+ children = [#xmlel{name = <<"retract">>, attrs = itemAttr(ItemId)} || ItemId <- ItemIds]}]),
broadcast_stanza(Host, Node, NodeId, Type,
NodeOptions, SubsByDepth, items, Stanza, true),
{result, true};
@@ -2925,8 +3953,7 @@ broadcast_purge_node(Host, Node, NodeId, Type, NodeOptions) ->
case get_collection_subscriptions(Host, Node) of
SubsByDepth when is_list(SubsByDepth) ->
Stanza = event_stanza(
- [{xmlelement, "purge", nodeAttr(Node),
- []}]),
+ [#xmlel{name = <<"purge">>, attrs = nodeAttr(Node)}]),
broadcast_stanza(Host, Node, NodeId, Type,
NodeOptions, SubsByDepth, nodes, Stanza, false),
{result, true};
@@ -2945,8 +3972,7 @@ broadcast_removed_node(Host, Node, NodeId, Type, NodeOptions, SubsByDepth) ->
{result, false};
_ ->
Stanza = event_stanza(
- [{xmlelement, "delete", nodeAttr(Node),
- []}]),
+ [#xmlel{name = <<"delete">>, attrs = nodeAttr(Node)}]),
broadcast_stanza(Host, Node, NodeId, Type,
NodeOptions, SubsByDepth, nodes, Stanza, false),
{result, true}
@@ -2958,7 +3984,7 @@ broadcast_removed_node(Host, Node, NodeId, Type, NodeOptions, SubsByDepth) ->
broadcast_created_node(_, _, _, _, _, []) ->
{result, false};
broadcast_created_node(Host, Node, NodeId, Type, NodeOptions, SubsByDepth) ->
- Stanza = event_stanza([{xmlelement, "create", nodeAttr(Node), []}]),
+ Stanza = event_stanza([#xmlel{name = <<"create">>, attrs = nodeAttr(Node)}]),
broadcast_stanza(Host, Node, NodeId, Type, NodeOptions, SubsByDepth, nodes, Stanza, true),
{result, true}.
@@ -2969,13 +3995,13 @@ broadcast_config_notification(Host, Node, NodeId, Type, NodeOptions, Lang) ->
SubsByDepth when is_list(SubsByDepth) ->
Content = case get_option(NodeOptions, deliver_payloads) of
true ->
- [{xmlelement, "x", [{"xmlns", ?NS_XDATA}, {"type", "result"}],
- get_configure_xfields(Type, NodeOptions, Lang, [])}];
+ [#xmlel{name = <<"x">>, attrs = [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"result">>}],
+ children = get_configure_xfields(Type, NodeOptions, Lang, [])}];
false ->
[]
end,
Stanza = event_stanza(
- [{xmlelement, "configuration", nodeAttr(Node), Content}]),
+ [#xmlel{name = <<"configuration">>, attrs = nodeAttr(Node), children = Content}]),
broadcast_stanza(Host, Node, NodeId, Type,
NodeOptions, SubsByDepth, nodes, Stanza, false),
{result, true};
@@ -3144,81 +4170,83 @@ user_resources(User, Server) ->
%%<li>The service does not support retrieval of default node configuration.</li>
%%</ul>
get_configure(Host, ServerHost, Node, From, Lang) ->
- Action =
- fun(#pubsub_node{options = Options, type = Type, id = NodeId}) ->
- case node_call(Type, get_affiliation, [NodeId, From]) of
- {result, owner} ->
- Groups = ejabberd_hooks:run_fold(roster_groups, ServerHost, [], [ServerHost]),
- {result,
- [{xmlelement, "pubsub",
- [{"xmlns", ?NS_PUBSUB_OWNER}],
- [{xmlelement, "configure", nodeAttr(Node),
- [{xmlelement, "x",
- [{"xmlns", ?NS_XDATA}, {"type", "form"}],
- get_configure_xfields(Type, Options, Lang, Groups)
- }]}]}]};
- _ ->
- {error, ?ERR_FORBIDDEN}
- end
- end,
+ Action = fun (#pubsub_node{options = Options,
+ type = Type, id = NodeId}) ->
+ case node_call(Type, get_affiliation, [NodeId, From]) of
+ {result, owner} ->
+ Groups = ejabberd_hooks:run_fold(roster_groups,
+ ServerHost, [],
+ [ServerHost]),
+ {result,
+ [#xmlel{name = <<"pubsub">>,
+ attrs = [{<<"xmlns">>, ?NS_PUBSUB_OWNER}],
+ children =
+ [#xmlel{name = <<"configure">>,
+ attrs = nodeAttr(Node),
+ children =
+ [#xmlel{name = <<"x">>,
+ attrs =
+ [{<<"xmlns">>,
+ ?NS_XDATA},
+ {<<"type">>,
+ <<"form">>}],
+ children =
+ get_configure_xfields(Type,
+ Options,
+ Lang,
+ Groups)}]}]}]};
+ _ -> {error, ?ERR_FORBIDDEN}
+ end
+ end,
case transaction(Host, Node, Action, sync_dirty) of
- {result, {_, Result}} -> {result, Result};
- Other -> Other
+ {result, {_, Result}} -> {result, Result};
+ Other -> Other
end.
get_default(Host, Node, _From, Lang) ->
Type = select_type(Host, Host, Node),
Options = node_options(Type),
- {result, [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB_OWNER}],
- [{xmlelement, "default", [],
- [{xmlelement, "x", [{"xmlns", ?NS_XDATA}, {"type", "form"}],
- get_configure_xfields(Type, Options, Lang, [])
- }]}]}]}.
-
%% Get node option
%% The result depend of the node type plugin system.
+ {result,
+ [#xmlel{name = <<"pubsub">>,
+ attrs = [{<<"xmlns">>, ?NS_PUBSUB_OWNER}],
+ children =
+ [#xmlel{name = <<"default">>, attrs = [],
+ children =
+ [#xmlel{name = <<"x">>,
+ attrs =
+ [{<<"xmlns">>, ?NS_XDATA},
+ {<<"type">>, <<"form">>}],
+ children =
+ get_configure_xfields(Type, Options,
+ Lang, [])}]}]}]}.
+
get_option([], _) -> false;
get_option(Options, Var) ->
get_option(Options, Var, false).
+
get_option(Options, Var, Def) ->
case lists:keysearch(Var, 1, Options) of
- {value, {_Val, Ret}} -> Ret;
- _ -> Def
+ {value, {_Val, Ret}} -> Ret;
+ _ -> Def
end.
%% Get default options from the module plugin.
node_options(Type) ->
- Module = list_to_atom(?PLUGIN_PREFIX ++ Type),
+ Module =
+ jlib:binary_to_atom(<<(?PLUGIN_PREFIX)/binary,
+ Type/binary>>),
case catch Module:options() of
- {'EXIT',{undef,_}} ->
- DefaultModule = list_to_atom(?PLUGIN_PREFIX++?STDNODE),
- DefaultModule:options();
- Result ->
- Result
- end.
-
%% @spec (Host, Type, NodeId) -> [ljid()]
%% NodeId = pubsubNodeId()
%% @doc <p>Return list of node owners.</p>
-node_owners(Host, Type, NodeId) ->
- case node_action(Host, Type, get_node_affiliations, [NodeId]) of
- {result, Affiliations} ->
- lists:foldl(
- fun({LJID, owner}, Acc) -> [LJID|Acc];
- (_, Acc) -> Acc
- end, [], Affiliations);
- _ ->
- []
- end.
-node_owners_call(Type, NodeId) ->
- case node_call(Type, get_node_affiliations, [NodeId]) of
- {result, Affiliations} ->
- lists:foldl(
- fun({LJID, owner}, Acc) -> [LJID|Acc];
- (_, Acc) -> Acc
- end, [], Affiliations);
- _ ->
- []
+ {'EXIT', {undef, _}} ->
+ DefaultModule =
+ jlib:binary_to_atom(<<(?PLUGIN_PREFIX)/binary,
+ (?STDNODE)/binary>>),
+ DefaultModule:options();
+ Result -> Result
end.
%% @spec (Host, Options) -> MaxItems
@@ -3234,80 +4262,116 @@ node_owners_call(Type, NodeId) ->
%% version.
max_items(Host, Options) ->
case get_option(Options, persist_items) of
- true ->
- case get_option(Options, max_items) of
- false -> unlimited;
- Result when (Result < 0) -> 0;
- Result -> Result
- end;
- false ->
- case get_option(Options, send_last_published_item) of
- never ->
- 0;
- _ ->
- case is_last_item_cache_enabled(Host) of
- true -> 0;
- false -> 1
- end
- end
+ true ->
+ case get_option(Options, max_items) of
+ false -> unlimited;
+ Result when Result < 0 -> 0;
+ Result -> Result
+ end;
+ false ->
+ case get_option(Options, send_last_published_item) of
+ never -> 0;
+ _ ->
+ case is_last_item_cache_enabled(Host) of
+ true -> 0;
+ false -> 1
+ end
+ end
end.
-define(BOOL_CONFIG_FIELD(Label, Var),
- ?BOOLXFIELD(Label, "pubsub#" ++ atom_to_list(Var),
- get_option(Options, Var))).
+ ?BOOLXFIELD(Label,
+ <<"pubsub#",
+ (iolist_to_binary(atom_to_list(Var)))/binary>>,
+ (get_option(Options, Var)))).
-define(STRING_CONFIG_FIELD(Label, Var),
- ?STRINGXFIELD(Label, "pubsub#" ++ atom_to_list(Var),
- get_option(Options, Var, ""))).
+ ?STRINGXFIELD(Label,
+ <<"pubsub#",
+ (iolist_to_binary(atom_to_list(Var)))/binary>>,
+ (get_option(Options, Var, <<"">>)))).
-define(INTEGER_CONFIG_FIELD(Label, Var),
- ?STRINGXFIELD(Label, "pubsub#" ++ atom_to_list(Var),
- integer_to_list(get_option(Options, Var)))).
+ ?STRINGXFIELD(Label,
+ <<"pubsub#",
+ (iolist_to_binary(atom_to_list(Var)))/binary>>,
+ (iolist_to_binary(integer_to_list(get_option(Options,
+ Var)))))).
-define(JLIST_CONFIG_FIELD(Label, Var, Opts),
- ?LISTXFIELD(Label, "pubsub#" ++ atom_to_list(Var),
- jlib:jid_to_string(get_option(Options, Var)),
+ ?LISTXFIELD(Label,
+ <<"pubsub#",
+ (iolist_to_binary(atom_to_list(Var)))/binary>>,
+ (jlib:jid_to_string(get_option(Options, Var))),
[jlib:jid_to_string(O) || O <- Opts])).
-define(ALIST_CONFIG_FIELD(Label, Var, Opts),
- ?LISTXFIELD(Label, "pubsub#" ++ atom_to_list(Var),
- atom_to_list(get_option(Options, Var)),
- [atom_to_list(O) || O <- Opts])).
+ ?LISTXFIELD(Label,
+ <<"pubsub#",
+ (iolist_to_binary(atom_to_list(Var)))/binary>>,
+ (iolist_to_binary(atom_to_list(get_option(Options,
+ Var)))),
+ [iolist_to_binary(atom_to_list(O)) || O <- Opts])).
-define(LISTM_CONFIG_FIELD(Label, Var, Opts),
- ?LISTMXFIELD(Label, "pubsub#" ++ atom_to_list(Var),
- get_option(Options, Var), Opts)).
+ ?LISTMXFIELD(Label,
+ <<"pubsub#",
+ (iolist_to_binary(atom_to_list(Var)))/binary>>,
+ (get_option(Options, Var)), Opts)).
-define(NLIST_CONFIG_FIELD(Label, Var),
- ?STRINGMXFIELD(Label, "pubsub#" ++ atom_to_list(Var),
- [node_to_string(N) || N <- get_option(Options, Var, [])])).
+ ?STRINGMXFIELD(Label,
+ <<"pubsub#",
+ (iolist_to_binary(atom_to_list(Var)))/binary>>,
+ get_option(Options, Var, []))).
get_configure_xfields(_Type, Options, Lang, Groups) ->
- [?XFIELD("hidden", "", "FORM_TYPE", ?NS_PUBSUB_NODE_CONFIG),
- ?BOOL_CONFIG_FIELD("Deliver payloads with event notifications", deliver_payloads),
- ?BOOL_CONFIG_FIELD("Deliver event notifications", deliver_notifications),
- ?BOOL_CONFIG_FIELD("Notify subscribers when the node configuration changes", notify_config),
- ?BOOL_CONFIG_FIELD("Notify subscribers when the node is deleted", notify_delete),
- ?BOOL_CONFIG_FIELD("Notify subscribers when items are removed from the node", notify_retract),
- ?BOOL_CONFIG_FIELD("Persist items to storage", persist_items),
- ?STRING_CONFIG_FIELD("A friendly name for the node", title),
- ?INTEGER_CONFIG_FIELD("Max # of items to persist", max_items),
- ?BOOL_CONFIG_FIELD("Whether to allow subscriptions", subscribe),
- ?ALIST_CONFIG_FIELD("Specify the access model", access_model,
+ [?XFIELD(<<"hidden">>, <<"">>, <<"FORM_TYPE">>,
+ (?NS_PUBSUB_NODE_CONFIG)),
+ ?BOOL_CONFIG_FIELD(<<"Deliver payloads with event notifications">>,
+ deliver_payloads),
+ ?BOOL_CONFIG_FIELD(<<"Deliver event notifications">>,
+ deliver_notifications),
+ ?BOOL_CONFIG_FIELD(<<"Notify subscribers when the node configuratio"
+ "n changes">>,
+ notify_config),
+ ?BOOL_CONFIG_FIELD(<<"Notify subscribers when the node is "
+ "deleted">>,
+ notify_delete),
+ ?BOOL_CONFIG_FIELD(<<"Notify subscribers when items are removed "
+ "from the node">>,
+ notify_retract),
+ ?BOOL_CONFIG_FIELD(<<"Persist items to storage">>,
+ persist_items),
+ ?STRING_CONFIG_FIELD(<<"A friendly name for the node">>,
+ title),
+ ?INTEGER_CONFIG_FIELD(<<"Max # of items to persist">>,
+ max_items),
+ ?BOOL_CONFIG_FIELD(<<"Whether to allow subscriptions">>,
+ subscribe),
+ ?ALIST_CONFIG_FIELD(<<"Specify the access model">>,
+ access_model,
[open, authorize, presence, roster, whitelist]),
- %% XXX: change to list-multi, include current roster groups as options
- ?LISTM_CONFIG_FIELD("Roster groups allowed to subscribe", roster_groups_allowed, Groups),
- ?ALIST_CONFIG_FIELD("Specify the publisher model", publish_model,
- [publishers, subscribers, open]),
- ?BOOL_CONFIG_FIELD("Purge all items when the relevant publisher goes offline", purge_offline),
- ?ALIST_CONFIG_FIELD("Specify the event message type", notification_type,
- [headline, normal]),
- ?INTEGER_CONFIG_FIELD("Max payload size in bytes", max_payload_size),
- ?ALIST_CONFIG_FIELD("When to send the last published item", send_last_published_item,
+ ?LISTM_CONFIG_FIELD(<<"Roster groups allowed to subscribe">>,
+ roster_groups_allowed, Groups),
+ ?ALIST_CONFIG_FIELD(<<"Specify the publisher model">>,
+ publish_model, [publishers, subscribers, open]),
+ ?BOOL_CONFIG_FIELD(<<"Purge all items when the relevant publisher "
+ "goes offline">>,
+ purge_offline),
+ ?ALIST_CONFIG_FIELD(<<"Specify the event message type">>,
+ notification_type, [headline, normal]),
+ ?INTEGER_CONFIG_FIELD(<<"Max payload size in bytes">>,
+ max_payload_size),
+ ?ALIST_CONFIG_FIELD(<<"When to send the last published item">>,
+ send_last_published_item,
[never, on_sub, on_sub_and_presence]),
- ?BOOL_CONFIG_FIELD("Only deliver notifications to available users", presence_based_delivery),
- ?NLIST_CONFIG_FIELD("The collections with which a node is affiliated", collection)
- ].
+ ?BOOL_CONFIG_FIELD(<<"Only deliver notifications to available "
+ "users">>,
+ presence_based_delivery),
+ ?NLIST_CONFIG_FIELD(<<"The collections with which a node is "
+ "affiliated">>,
+ collection)].
%%<p>There are several reasons why the node configuration request might fail:</p>
%%<ul>
@@ -3319,52 +4383,60 @@ get_configure_xfields(_Type, Options, Lang, Groups) ->
%%</ul>
set_configure(Host, Node, From, Els, Lang) ->
case xml:remove_cdata(Els) of
- [{xmlelement, "x", _Attrs1, _Els1} = XEl] ->
- case {xml:get_tag_attr_s("xmlns", XEl), xml:get_tag_attr_s("type", XEl)} of
- {?NS_XDATA, "cancel"} ->
- {result, []};
- {?NS_XDATA, "submit"} ->
- Action =
- fun(#pubsub_node{options = Options, type = Type, id = NodeId} = N) ->
- case node_call(Type, get_affiliation, [NodeId, From]) of
- {result, owner} ->
- case jlib:parse_xdata_submit(XEl) of
- invalid ->
- {error, ?ERR_BAD_REQUEST};
- XData ->
- OldOpts = case Options of
- [] -> node_options(Type);
- _ -> Options
- end,
- case set_xoption(Host, XData, OldOpts) of
- NewOpts when is_list(NewOpts) ->
- case tree_call(Host, set_node, [N#pubsub_node{options = NewOpts}]) of
- ok -> {result, ok};
- Err -> Err
- end;
- Err ->
- Err
- end
- end;
- _ ->
- {error, ?ERR_FORBIDDEN}
- end
- end,
- case transaction(Host, Node, Action, transaction) of
- {result, {TNode, ok}} ->
- NodeId = TNode#pubsub_node.id,
- Type = TNode#pubsub_node.type,
- Options = TNode#pubsub_node.options,
- broadcast_config_notification(Host, Node, NodeId, Type, Options, Lang),
- {result, []};
- Other ->
- Other
- end;
- _ ->
- {error, ?ERR_BAD_REQUEST}
- end;
- _ ->
- {error, ?ERR_BAD_REQUEST}
+ [#xmlel{name = <<"x">>} = XEl] ->
+ case {xml:get_tag_attr_s(<<"xmlns">>, XEl),
+ xml:get_tag_attr_s(<<"type">>, XEl)}
+ of
+ {?NS_XDATA, <<"cancel">>} -> {result, []};
+ {?NS_XDATA, <<"submit">>} ->
+ Action = fun (#pubsub_node{options = Options,
+ type = Type, id = NodeId} =
+ N) ->
+ case node_call(Type, get_affiliation,
+ [NodeId, From])
+ of
+ {result, owner} ->
+ case jlib:parse_xdata_submit(XEl) of
+ invalid -> {error, ?ERR_BAD_REQUEST};
+ XData ->
+ OldOpts = case Options of
+ [] ->
+ node_options(Type);
+ _ -> Options
+ end,
+ case set_xoption(Host, XData,
+ OldOpts)
+ of
+ NewOpts
+ when is_list(NewOpts) ->
+ case tree_call(Host,
+ set_node,
+ [N#pubsub_node{options
+ =
+ NewOpts}])
+ of
+ ok -> {result, ok};
+ Err -> Err
+ end;
+ Err -> Err
+ end
+ end;
+ _ -> {error, ?ERR_FORBIDDEN}
+ end
+ end,
+ case transaction(Host, Node, Action, transaction) of
+ {result, {TNode, ok}} ->
+ NodeId = TNode#pubsub_node.id,
+ Type = TNode#pubsub_node.type,
+ Options = TNode#pubsub_node.options,
+ broadcast_config_notification(Host, Node, NodeId, Type,
+ Options, Lang),
+ {result, []};
+ Other -> Other
+ end;
+ _ -> {error, ?ERR_BAD_REQUEST}
+ end;
+ _ -> {error, ?ERR_BAD_REQUEST}
end.
add_opt(Key, Value, Opts) ->
@@ -3373,100 +4445,144 @@ add_opt(Key, Value, Opts) ->
-define(SET_BOOL_XOPT(Opt, Val),
BoolVal = case Val of
- "0" -> false;
- "1" -> true;
- "false" -> false;
- "true" -> true;
- _ -> error
+ <<"0">> -> false;
+ <<"1">> -> true;
+ <<"false">> -> false;
+ <<"true">> -> true;
+ _ -> error
end,
case BoolVal of
- error -> {error, ?ERR_NOT_ACCEPTABLE};
- _ -> set_xoption(Host, Opts, add_opt(Opt, BoolVal, NewOpts))
+ error -> {error, ?ERR_NOT_ACCEPTABLE};
+ _ ->
+ set_xoption(Host, Opts, add_opt(Opt, BoolVal, NewOpts))
end).
-define(SET_STRING_XOPT(Opt, Val),
set_xoption(Host, Opts, add_opt(Opt, Val, NewOpts))).
-define(SET_INTEGER_XOPT(Opt, Val, Min, Max),
- case catch list_to_integer(Val) of
- IVal when is_integer(IVal),
- IVal >= Min,
- IVal =< Max ->
- set_xoption(Host, Opts, add_opt(Opt, IVal, NewOpts));
- _ ->
- {error, ?ERR_NOT_ACCEPTABLE}
+ case catch jlib:binary_to_integer(Val) of
+ IVal when is_integer(IVal), IVal >= Min, IVal =< Max ->
+ set_xoption(Host, Opts, add_opt(Opt, IVal, NewOpts));
+ _ -> {error, ?ERR_NOT_ACCEPTABLE}
end).
-define(SET_ALIST_XOPT(Opt, Val, Vals),
- case lists:member(Val, [atom_to_list(V) || V <- Vals]) of
- true -> set_xoption(Host, Opts, add_opt(Opt, list_to_atom(Val), NewOpts));
- false -> {error, ?ERR_NOT_ACCEPTABLE}
+ case lists:member(Val,
+ [iolist_to_binary(atom_to_list(V)) || V <- Vals])
+ of
+ true ->
+ set_xoption(Host, Opts,
+ add_opt(Opt, jlib:binary_to_atom(Val), NewOpts));
+ false -> {error, ?ERR_NOT_ACCEPTABLE}
end).
-define(SET_LIST_XOPT(Opt, Val),
set_xoption(Host, Opts, add_opt(Opt, Val, NewOpts))).
-set_xoption(_Host, [], NewOpts) ->
- NewOpts;
-set_xoption(Host, [{"FORM_TYPE", _} | Opts], NewOpts) ->
+set_xoption(_Host, [], NewOpts) -> NewOpts;
+set_xoption(Host, [{<<"FORM_TYPE">>, _} | Opts],
+ NewOpts) ->
set_xoption(Host, Opts, NewOpts);
-set_xoption(Host, [{"pubsub#roster_groups_allowed", Value} | Opts], NewOpts) ->
+set_xoption(Host,
+ [{<<"pubsub#roster_groups_allowed">>, Value} | Opts],
+ NewOpts) ->
?SET_LIST_XOPT(roster_groups_allowed, Value);
-set_xoption(Host, [{"pubsub#deliver_payloads", [Val]} | Opts], NewOpts) ->
+set_xoption(Host,
+ [{<<"pubsub#deliver_payloads">>, [Val]} | Opts],
+ NewOpts) ->
?SET_BOOL_XOPT(deliver_payloads, Val);
-set_xoption(Host, [{"pubsub#deliver_notifications", [Val]} | Opts], NewOpts) ->
+set_xoption(Host,
+ [{<<"pubsub#deliver_notifications">>, [Val]} | Opts],
+ NewOpts) ->
?SET_BOOL_XOPT(deliver_notifications, Val);
-set_xoption(Host, [{"pubsub#notify_config", [Val]} | Opts], NewOpts) ->
+set_xoption(Host,
+ [{<<"pubsub#notify_config">>, [Val]} | Opts],
+ NewOpts) ->
?SET_BOOL_XOPT(notify_config, Val);
-set_xoption(Host, [{"pubsub#notify_delete", [Val]} | Opts], NewOpts) ->
+set_xoption(Host,
+ [{<<"pubsub#notify_delete">>, [Val]} | Opts],
+ NewOpts) ->
?SET_BOOL_XOPT(notify_delete, Val);
-set_xoption(Host, [{"pubsub#notify_retract", [Val]} | Opts], NewOpts) ->
+set_xoption(Host,
+ [{<<"pubsub#notify_retract">>, [Val]} | Opts],
+ NewOpts) ->
?SET_BOOL_XOPT(notify_retract, Val);
-set_xoption(Host, [{"pubsub#persist_items", [Val]} | Opts], NewOpts) ->
+set_xoption(Host,
+ [{<<"pubsub#persist_items">>, [Val]} | Opts],
+ NewOpts) ->
?SET_BOOL_XOPT(persist_items, Val);
-set_xoption(Host, [{"pubsub#max_items", [Val]} | Opts], NewOpts) ->
+set_xoption(Host,
+ [{<<"pubsub#max_items">>, [Val]} | Opts], NewOpts) ->
MaxItems = get_max_items_node(Host),
?SET_INTEGER_XOPT(max_items, Val, 0, MaxItems);
-set_xoption(Host, [{"pubsub#subscribe", [Val]} | Opts], NewOpts) ->
+set_xoption(Host,
+ [{<<"pubsub#subscribe">>, [Val]} | Opts], NewOpts) ->
?SET_BOOL_XOPT(subscribe, Val);
-set_xoption(Host, [{"pubsub#access_model", [Val]} | Opts], NewOpts) ->
- ?SET_ALIST_XOPT(access_model, Val, [open, authorize, presence, roster, whitelist]);
-set_xoption(Host, [{"pubsub#publish_model", [Val]} | Opts], NewOpts) ->
- ?SET_ALIST_XOPT(publish_model, Val, [publishers, subscribers, open]);
-set_xoption(Host, [{"pubsub#notification_type", [Val]} | Opts], NewOpts) ->
- ?SET_ALIST_XOPT(notification_type, Val, [headline, normal]);
-set_xoption(Host, [{"pubsub#node_type", [Val]} | Opts], NewOpts) ->
+set_xoption(Host,
+ [{<<"pubsub#access_model">>, [Val]} | Opts], NewOpts) ->
+ ?SET_ALIST_XOPT(access_model, Val,
+ [open, authorize, presence, roster, whitelist]);
+set_xoption(Host,
+ [{<<"pubsub#publish_model">>, [Val]} | Opts],
+ NewOpts) ->
+ ?SET_ALIST_XOPT(publish_model, Val,
+ [publishers, subscribers, open]);
+set_xoption(Host,
+ [{<<"pubsub#notification_type">>, [Val]} | Opts],
+ NewOpts) ->
+ ?SET_ALIST_XOPT(notification_type, Val,
+ [headline, normal]);
+set_xoption(Host,
+ [{<<"pubsub#node_type">>, [Val]} | Opts], NewOpts) ->
?SET_ALIST_XOPT(node_type, Val, [leaf, collection]);
-set_xoption(Host, [{"pubsub#max_payload_size", [Val]} | Opts], NewOpts) ->
- ?SET_INTEGER_XOPT(max_payload_size, Val, 0, ?MAX_PAYLOAD_SIZE);
-set_xoption(Host, [{"pubsub#send_last_published_item", [Val]} | Opts], NewOpts) ->
- ?SET_ALIST_XOPT(send_last_published_item, Val, [never, on_sub, on_sub_and_presence]);
-set_xoption(Host, [{"pubsub#presence_based_delivery", [Val]} | Opts], NewOpts) ->
+set_xoption(Host,
+ [{<<"pubsub#max_payload_size">>, [Val]} | Opts],
+ NewOpts) ->
+ ?SET_INTEGER_XOPT(max_payload_size, Val, 0,
+ (?MAX_PAYLOAD_SIZE));
+set_xoption(Host,
+ [{<<"pubsub#send_last_published_item">>, [Val]} | Opts],
+ NewOpts) ->
+ ?SET_ALIST_XOPT(send_last_published_item, Val,
+ [never, on_sub, on_sub_and_presence]);
+set_xoption(Host,
+ [{<<"pubsub#presence_based_delivery">>, [Val]} | Opts],
+ NewOpts) ->
?SET_BOOL_XOPT(presence_based_delivery, Val);
-set_xoption(Host, [{"pubsub#purge_offline", [Val]} | Opts], NewOpts) ->
+set_xoption(Host,
+ [{<<"pubsub#purge_offline">>, [Val]} | Opts],
+ NewOpts) ->
?SET_BOOL_XOPT(purge_offline, Val);
-set_xoption(Host, [{"pubsub#title", Value} | Opts], NewOpts) ->
+set_xoption(Host, [{<<"pubsub#title">>, Value} | Opts],
+ NewOpts) ->
?SET_STRING_XOPT(title, Value);
-set_xoption(Host, [{"pubsub#type", Value} | Opts], NewOpts) ->
+set_xoption(Host, [{<<"pubsub#type">>, Value} | Opts],
+ NewOpts) ->
?SET_STRING_XOPT(type, Value);
-set_xoption(Host, [{"pubsub#body_xslt", Value} | Opts], NewOpts) ->
+set_xoption(Host,
+ [{<<"pubsub#body_xslt">>, Value} | Opts], NewOpts) ->
?SET_STRING_XOPT(body_xslt, Value);
-set_xoption(Host, [{"pubsub#collection", Value} | Opts], NewOpts) ->
- NewValue = [string_to_node(V) || V <- Value],
- ?SET_LIST_XOPT(collection, NewValue);
-set_xoption(Host, [{"pubsub#node", [Value]} | Opts], NewOpts) ->
- NewValue = string_to_node(Value),
- ?SET_LIST_XOPT(node, NewValue);
+set_xoption(Host,
+ [{<<"pubsub#collection">>, Value} | Opts], NewOpts) ->
+% NewValue = [string_to_node(V) || V <- Value],
+ ?SET_LIST_XOPT(collection, Value);
+set_xoption(Host, [{<<"pubsub#node">>, [Value]} | Opts],
+ NewOpts) ->
+% NewValue = string_to_node(Value),
+ ?SET_LIST_XOPT(node, Value);
set_xoption(Host, [_ | Opts], NewOpts) ->
- % skip unknown field
set_xoption(Host, Opts, NewOpts).
get_max_items_node({_, ServerHost, _}) ->
get_max_items_node(ServerHost);
get_max_items_node(Host) ->
- case catch ets:lookup(gen_mod:get_module_proc(Host, config), max_items_node) of
- [{max_items_node, Integer}] -> Integer;
- _ -> ?MAXITEMS
+ case catch ets:lookup(gen_mod:get_module_proc(Host,
+ config),
+ max_items_node)
+ of
+ [{max_items_node, Integer}] -> Integer;
+ _ -> ?MAXITEMS
end.
%%%% last item cache handling
@@ -3474,149 +4590,196 @@ get_max_items_node(Host) ->
is_last_item_cache_enabled({_, ServerHost, _}) ->
is_last_item_cache_enabled(ServerHost);
is_last_item_cache_enabled(Host) ->
- case catch ets:lookup(gen_mod:get_module_proc(Host, config), last_item_cache) of
- [{last_item_cache, true}] -> true;
- _ -> false
+ case catch ets:lookup(gen_mod:get_module_proc(Host,
+ config),
+ last_item_cache)
+ of
+ [{last_item_cache, true}] -> true;
+ _ -> false
end.
-set_cached_item({_, ServerHost, _}, NodeId, ItemId, Publisher, Payload) ->
- set_cached_item(ServerHost, NodeId, ItemId, Publisher, Payload);
-set_cached_item(Host, NodeId, ItemId, Publisher, Payload) ->
+set_cached_item({_, ServerHost, _}, NodeId, ItemId,
+ Publisher, Payload) ->
+ set_cached_item(ServerHost, NodeId, ItemId, Publisher,
+ Payload);
+set_cached_item(Host, NodeId, ItemId, Publisher,
+ Payload) ->
case is_last_item_cache_enabled(Host) of
- true -> mnesia:dirty_write({pubsub_last_item, NodeId, ItemId, {now(), jlib:jid_tolower(jlib:jid_remove_resource(Publisher))}, Payload});
- _ -> ok
+ true ->
+ mnesia:dirty_write({pubsub_last_item, NodeId, ItemId,
+ {now(),
+ jlib:jid_tolower(jlib:jid_remove_resource(Publisher))},
+ Payload});
+ _ -> ok
end.
+
unset_cached_item({_, ServerHost, _}, NodeId) ->
unset_cached_item(ServerHost, NodeId);
unset_cached_item(Host, NodeId) ->
case is_last_item_cache_enabled(Host) of
- true -> mnesia:dirty_delete({pubsub_last_item, NodeId});
- _ -> ok
+ true -> mnesia:dirty_delete({pubsub_last_item, NodeId});
+ _ -> ok
end.
+
+-spec(get_cached_item/2 ::
+(
+ Host :: mod_pubsub:host(),
+ NodeIdx :: mod_pubsub:nodeIdx())
+ -> undefined | mod_pubsub:pubsubItem()
+).
get_cached_item({_, ServerHost, _}, NodeId) ->
get_cached_item(ServerHost, NodeId);
-get_cached_item(Host, NodeId) ->
+get_cached_item(Host, NodeIdx) ->
case is_last_item_cache_enabled(Host) of
- true ->
- case mnesia:dirty_read({pubsub_last_item, NodeId}) of
- [{pubsub_last_item, NodeId, ItemId, Creation, Payload}] ->
- #pubsub_item{itemid = {ItemId, NodeId}, payload = Payload,
- creation = Creation, modification = Creation};
- _ ->
- undefined
- end;
- _ ->
- undefined
+ true ->
+ case mnesia:dirty_read({pubsub_last_item, NodeIdx}) of
+ [#pubsub_last_item{itemid = ItemId, creation = Creation, payload = Payload}] ->
+% [{pubsub_last_item, NodeId, ItemId, Creation,
+% Payload}] ->
+ #pubsub_item{itemid = {ItemId, NodeIdx},
+ payload = Payload, creation = Creation,
+ modification = Creation};
+ _ -> undefined
+ end;
+ _ -> undefined
end.
%%%% plugin handling
host(ServerHost) ->
- case catch ets:lookup(gen_mod:get_module_proc(ServerHost, config), host) of
- [{host, Host}] -> Host;
- _ -> "pubsub."++ServerHost
+ case catch
+ ets:lookup(gen_mod:get_module_proc(ServerHost, config),
+ host)
+ of
+ [{host, Host}] -> Host;
+ _ -> <<"pubsub.", ServerHost/binary>>
end.
plugins(Host) ->
- case catch ets:lookup(gen_mod:get_module_proc(Host, config), plugins) of
- [{plugins, []}] -> [?STDNODE];
- [{plugins, PL}] -> PL;
- _ -> [?STDNODE]
+ case catch ets:lookup(gen_mod:get_module_proc(Host,
+ config),
+ plugins)
+ of
+ [{plugins, []}] -> [?STDNODE];
+ [{plugins, PL}] -> PL;
+ _ -> [?STDNODE]
end.
-select_type(ServerHost, Host, Node, Type)->
+
+select_type(ServerHost, Host, Node, Type) ->
SelectedType = case Host of
- {_User, _Server, _Resource} ->
- case catch ets:lookup(gen_mod:get_module_proc(ServerHost, config), pep_mapping) of
- [{pep_mapping, PM}] -> proplists:get_value(node_to_string(Node), PM, ?PEPNODE);
- _ -> ?PEPNODE
- end;
- _ ->
- Type
- end,
+ {_User, _Server, _Resource} ->
+ case catch
+ ets:lookup(gen_mod:get_module_proc(ServerHost,
+ config),
+ pep_mapping)
+ of
+ [{pep_mapping, PM}] ->
+ proplists:get_value(Node, PM, ?PEPNODE);
+ _ -> ?PEPNODE
+ end;
+ _ -> Type
+ end,
ConfiguredTypes = plugins(ServerHost),
case lists:member(SelectedType, ConfiguredTypes) of
- true -> SelectedType;
- false -> hd(ConfiguredTypes)
+ true -> SelectedType;
+ false -> hd(ConfiguredTypes)
end.
-select_type(ServerHost, Host, Node) ->
- select_type(ServerHost, Host, Node, hd(plugins(ServerHost))).
+
+select_type(ServerHost, Host, Node) ->
+ select_type(ServerHost, Host, Node,
+ hd(plugins(ServerHost))).
features() ->
- [
- % see plugin "access-authorize", % OPTIONAL
- "access-open", % OPTIONAL this relates to access_model option in node_hometree
- "access-presence", % OPTIONAL this relates to access_model option in node_pep
- %TODO "access-roster", % OPTIONAL
- "access-whitelist", % OPTIONAL
- % see plugin "auto-create", % OPTIONAL
- % see plugin "auto-subscribe", % RECOMMENDED
- "collections", % RECOMMENDED
- "config-node", % RECOMMENDED
- "create-and-configure", % RECOMMENDED
- % see plugin "create-nodes", % RECOMMENDED
- % see plugin "delete-items", % RECOMMENDED
- % see plugin "delete-nodes", % RECOMMENDED
- % see plugin "filtered-notifications", % RECOMMENDED
- % see plugin "get-pending", % OPTIONAL
- % see plugin "instant-nodes", % RECOMMENDED
- "item-ids", % RECOMMENDED
- "last-published", % RECOMMENDED
- %TODO "cache-last-item",
- %TODO "leased-subscription", % OPTIONAL
- % see plugin "manage-subscriptions", % OPTIONAL
- "member-affiliation", % RECOMMENDED
- %TODO "meta-data", % RECOMMENDED
- % see plugin "modify-affiliations", % OPTIONAL
- % see plugin "multi-collection", % OPTIONAL
- % see plugin "multi-subscribe", % OPTIONAL
- % see plugin "outcast-affiliation", % RECOMMENDED
- % see plugin "persistent-items", % RECOMMENDED
- "presence-notifications", % OPTIONAL
- "presence-subscribe", % RECOMMENDED
- % see plugin "publish", % REQUIRED
- %TODO "publish-options", % OPTIONAL
- "publisher-affiliation", % RECOMMENDED
- % see plugin "purge-nodes", % OPTIONAL
- % see plugin "retract-items", % OPTIONAL
- % see plugin "retrieve-affiliations", % RECOMMENDED
- "retrieve-default" % RECOMMENDED
- % see plugin "retrieve-items", % RECOMMENDED
+ [% see plugin "access-authorize", % OPTIONAL
+ <<"access-open">>, % OPTIONAL this relates to access_model option in node_hometree
+ <<"access-presence">>, % OPTIONAL this relates to access_model option in node_pep
+ <<"access-whitelist">>, % OPTIONAL
+ <<"collections">>, % RECOMMENDED
+ <<"config-node">>, % RECOMMENDED
+ <<"create-and-configure">>, % RECOMMENDED
+ <<"item-ids">>, % RECOMMENDED
+ <<"last-published">>, % RECOMMENDED
+ <<"member-affiliation">>, % RECOMMENDED
+ <<"presence-notifications">>, % OPTIONAL
+ <<"presence-subscribe">>, % RECOMMENDED
+ <<"publisher-affiliation">>, % RECOMMENDED
+ <<"retrieve-default">>].
+
+ % see plugin "retrieve-items", % RECOMMENDED
% see plugin "retrieve-subscriptions", % RECOMMENDED
%TODO "shim", % OPTIONAL
% see plugin "subscribe", % REQUIRED
% see plugin "subscription-options", % OPTIONAL
% see plugin "subscription-notifications" % OPTIONAL
- ].
+
features(Type) ->
- Module = list_to_atom(?PLUGIN_PREFIX++Type),
- features() ++ case catch Module:features() of
- {'EXIT', {undef, _}} -> [];
- Result -> Result
- end.
+ Module =
+ jlib:binary_to_atom(<<(?PLUGIN_PREFIX)/binary,
+ Type/binary>>),
+ features() ++
+ case catch Module:features() of
+ {'EXIT', {undef, _}} -> [];
+ Result -> Result
+ end.
+
features(Host, <<>>) ->
- lists:usort(lists:foldl(fun(Plugin, Acc) ->
- Acc ++ features(Plugin)
- end, [], plugins(Host)));
+ lists:usort(lists:foldl(fun (Plugin, Acc) ->
+ Acc ++ features(Plugin)
+ end,
+ [], plugins(Host)));
features(Host, Node) ->
- Action = fun(#pubsub_node{type = Type}) -> {result, features(Type)} end,
+ Action = fun (#pubsub_node{type = Type}) ->
+ {result, features(Type)}
+ end,
case transaction(Host, Node, Action, sync_dirty) of
- {result, Features} -> lists:usort(features() ++ Features);
- _ -> features()
+ {result, Features} ->
+ lists:usort(features() ++ Features);
+ _ -> features()
+ end.
+
+%% @spec (Host, Type, NodeId) -> [ljid()]
+%% NodeId = pubsubNodeId()
+%% @doc <p>Return list of node owners.</p>
+node_owners(Host, Type, NodeId) ->
+ case node_action(Host, Type, get_node_affiliations, [NodeId]) of
+ {result, Affiliations} ->
+ lists:foldl(
+ fun({LJID, owner}, Acc) -> [LJID|Acc];
+ (_, Acc) -> Acc
+ end, [], Affiliations);
+ _ ->
+ []
+ end.
+node_owners_call(Type, NodeId) ->
+ case node_call(Type, get_node_affiliations, [NodeId]) of
+ {result, Affiliations} ->
+ lists:foldl(
+ fun({LJID, owner}, Acc) -> [LJID|Acc];
+ (_, Acc) -> Acc
+ end, [], Affiliations);
+ _ ->
+ []
end.
%% @doc <p>node tree plugin call.</p>
tree_call({_User, Server, _Resource}, Function, Args) ->
tree_call(Server, Function, Args);
tree_call(Host, Function, Args) ->
- ?DEBUG("tree_call ~p ~p ~p",[Host, Function, Args]),
- Module = case catch ets:lookup(gen_mod:get_module_proc(Host, config), nodetree) of
- [{nodetree, N}] -> N;
- _ -> list_to_atom(?TREE_PREFIX ++ ?STDTREE)
- end,
+ ?DEBUG("tree_call ~p ~p ~p", [Host, Function, Args]),
+ Module = case catch
+ ets:lookup(gen_mod:get_module_proc(Host, config),
+ nodetree)
+ of
+ [{nodetree, N}] -> N;
+ _ ->
+ jlib:binary_to_atom(<<(?TREE_PREFIX)/binary,
+ (?STDTREE)/binary>>)
+ end,
catch apply(Module, Function, Args).
+
tree_action(Host, Function, Args) ->
- ?DEBUG("tree_action ~p ~p ~p",[Host,Function,Args]),
- Fun = fun() -> tree_call(Host, Function, Args) end,
+ ?DEBUG("tree_action ~p ~p ~p", [Host, Function, Args]),
+ Fun = fun () -> tree_call(Host, Function, Args) end,
case catch ejabberd_odbc:sql_bloc(odbc_conn(Host), Fun) of
{atomic, Result} ->
Result;
@@ -3627,44 +4790,53 @@ tree_action(Host, Function, Args) ->
%% @doc <p>node plugin call.</p>
node_call(Type, Function, Args) ->
- ?DEBUG("node_call ~p ~p ~p",[Type, Function, Args]),
- Module = list_to_atom(?PLUGIN_PREFIX++Type),
+ ?DEBUG("node_call ~p ~p ~p", [Type, Function, Args]),
+ Module =
+ jlib:binary_to_atom(<<(?PLUGIN_PREFIX)/binary,
+ Type/binary>>),
case apply(Module, Function, Args) of
- {result, Result} -> {result, Result};
- {error, Error} -> {error, Error};
- {'EXIT', {undef, Undefined}} ->
- case Type of
- ?STDNODE -> {error, {undef, Undefined}};
- _ -> node_call(?STDNODE, Function, Args)
- end;
- {'EXIT', Reason} -> {error, Reason};
- Result -> {result, Result} %% any other return value is forced as result
+ {result, Result} -> {result, Result};
+ {error, Error} -> {error, Error};
+ {'EXIT', {undef, Undefined}} ->
+ case Type of
+ ?STDNODE -> {error, {undef, Undefined}};
+ _ -> node_call(?STDNODE, Function, Args)
+ end;
+ {'EXIT', Reason} -> {error, Reason};
+ Result ->
+ {result,
+ Result} %% any other return value is forced as result
end.
node_action(Host, Type, Function, Args) ->
- ?DEBUG("node_action ~p ~p ~p ~p",[Host,Type,Function,Args]),
- transaction(Host, fun() ->
- node_call(Type, Function, Args)
- end, sync_dirty).
+ ?DEBUG("node_action ~p ~p ~p ~p",
+ [Host, Type, Function, Args]),
+ transaction(Host, fun () -> node_call(Type, Function, Args) end,
+ sync_dirty).
%% @doc <p>plugin transaction handling.</p>
transaction(Host, Node, Action, Trans) ->
- transaction(Host, fun() ->
+ transaction(Host, fun () ->
case tree_call(Host, get_node, [Host, Node]) of
- N when is_record(N, pubsub_node) ->
- case Action(N) of
- {result, Result} -> {result, {N, Result}};
- {atomic, {result, Result}} -> {result, {N, Result}};
- Other -> Other
- end;
- Error ->
- Error
+ N when is_record(N, pubsub_node) ->
+ case Action(N) of
+ {result, Result} -> {result, {N, Result}};
+ {atomic, {result, Result}} ->
+ {result, {N, Result}};
+ Other -> Other
+ end;
+ Error -> Error
end
- end, Trans).
+ end,
+ Trans).
+
transaction_on_nodes(Host, Action, Trans) ->
- transaction(Host, fun() ->
- {result, lists:foldl(Action, [], tree_call(Host, get_nodes, [Host]))}
- end, Trans).
+ transaction(Host, fun () ->
+ {result,
+ lists:foldl(Action, [],
+ tree_call(Host, get_nodes, [Host]))}
+ end,
+ Trans).
transaction(Host, Fun, Trans) ->
transaction_retry(Host, Fun, Trans, 2).
@@ -3674,14 +4846,15 @@ transaction_retry(Host, Fun, Trans, Count) ->
_ -> sql_bloc
end,
case catch ejabberd_odbc:SqlFun(odbc_conn(Host), Fun) of
- {result, Result} -> {result, Result};
- {error, Error} -> {error, Error};
- {atomic, {result, Result}} -> {result, Result};
- {atomic, {error, Error}} -> {error, Error};
- {aborted, Reason} ->
- ?ERROR_MSG("transaction return internal error: ~p~n", [{aborted, Reason}]),
- {error, ?ERR_INTERNAL_SERVER_ERROR};
- {'EXIT', {timeout, _} = Reason} ->
+ {result, Result} -> {result, Result};
+ {error, Error} -> {error, Error};
+ {atomic, {result, Result}} -> {result, Result};
+ {atomic, {error, Error}} -> {error, Error};
+ {aborted, Reason} ->
+ ?ERROR_MSG("transaction return internal error: ~p~n",
+ [{aborted, Reason}]),
+ {error, ?ERR_INTERNAL_SERVER_ERROR};
+ {'EXIT', {timeout, _} = Reason} ->
case Count of
0 ->
?ERROR_MSG("transaction return internal error: ~p~n", [{'EXIT', Reason}]),
@@ -3690,12 +4863,14 @@ transaction_retry(Host, Fun, Trans, Count) ->
erlang:yield(),
transaction_retry(Host, Fun, Trans, N-1)
end;
- {'EXIT', Reason} ->
- ?ERROR_MSG("transaction return internal error: ~p~n", [{'EXIT', Reason}]),
- {error, ?ERR_INTERNAL_SERVER_ERROR};
- Other ->
- ?ERROR_MSG("transaction return internal error: ~p~n", [Other]),
- {error, ?ERR_INTERNAL_SERVER_ERROR}
+ {'EXIT', Reason} ->
+ ?ERROR_MSG("transaction return internal error: ~p~n",
+ [{'EXIT', Reason}]),
+ {error, ?ERR_INTERNAL_SERVER_ERROR};
+ Other ->
+ ?ERROR_MSG("transaction return internal error: ~p~n",
+ [Other]),
+ {error, ?ERR_INTERNAL_SERVER_ERROR}
end.
odbc_conn({_U, Host, _R})->
@@ -3714,40 +4889,42 @@ escape(Value)->
%% Add pubsub-specific error element
extended_error(Error, Ext) ->
extended_error(Error, Ext,
- [{"xmlns", ?NS_PUBSUB_ERRORS}]).
-extended_error(Error, unsupported, Feature) ->
- extended_error(Error, "unsupported",
- [{"xmlns", ?NS_PUBSUB_ERRORS},
- {"feature", Feature}]);
-extended_error({xmlelement, Error, Attrs, SubEls}, Ext, ExtAttrs) ->
- {xmlelement, Error, Attrs,
- lists:reverse([{xmlelement, Ext, ExtAttrs, []} | SubEls])}.
+ [{<<"xmlns">>, ?NS_PUBSUB_ERRORS}]).
+extended_error(Error, unsupported, Feature) ->
%% Give a uniq identifier
+ extended_error(Error, <<"unsupported">>,
+ [{<<"xmlns">>, ?NS_PUBSUB_ERRORS},
+ {<<"feature">>, Feature}]);
+extended_error(#xmlel{name = Error, attrs = Attrs,
+ children = SubEls},
+ Ext, ExtAttrs) ->
+ #xmlel{name = Error, attrs = Attrs,
+ children =
+ lists:reverse([#xmlel{name = Ext, attrs = ExtAttrs,
+ children = []}
+ | SubEls])}.
+
+-spec(uniqid/0 :: () -> mod_pubsub:itemId()).
uniqid() ->
{T1, T2, T3} = now(),
- lists:flatten(io_lib:fwrite("~.16B~.16B~.16B", [T1, T2, T3])).
+ iolist_to_binary(io_lib:fwrite("~.16B~.16B~.16B", [T1, T2, T3])).
-% node attributes
-nodeAttr(Node) when is_list(Node) ->
- [{"node", Node}];
-nodeAttr(Node) ->
- [{"node", node_to_string(Node)}].
+nodeAttr(Node) -> [{<<"node">>, Node}].
-% item attributes
itemAttr([]) -> [];
-itemAttr(ItemId) -> [{"id", ItemId}].
+itemAttr(ItemId) -> [{<<"id">>, ItemId}].
-% build item elements from item list
itemsEls(Items) ->
- lists:map(fun(#pubsub_item{itemid = {ItemId, _}, payload = Payload}) ->
- {xmlelement, "item", itemAttr(ItemId), Payload}
- end, Items).
+ lists:map(fun (#pubsub_item{itemid = {ItemId, _}, payload = Payload}) ->
+ #xmlel{name = <<"item">>, attrs = itemAttr(ItemId), children = Payload}
+ end, Items).
-add_message_type({xmlelement, "message", Attrs, Els}, Type) ->
- {xmlelement, "message", [{"type", Type}|Attrs], Els};
-add_message_type(XmlEl, _Type) ->
- XmlEl.
+add_message_type(#xmlel{name = <<"message">>, attrs = Attrs, children = Els},
+ Type) ->
+ #xmlel{name = <<"message">>,
+ attrs = [{<<"type">>, Type} | Attrs], children = Els};
+add_message_type(XmlEl, _Type) -> XmlEl.
%% Place of <headers/> changed at the bottom of the stanza
%% cf. http://xmpp.org/extensions/xep-0060.html#publisher-publish-success-subid
@@ -3756,14 +4933,19 @@ add_message_type(XmlEl, _Type) ->
%% (i.e., as the last child of the <message/> stanza)".
add_shim_headers(Stanza, HeaderEls) ->
- add_headers(Stanza, "headers", ?NS_SHIM, HeaderEls).
+ add_headers(Stanza, <<"headers">>, ?NS_SHIM, HeaderEls).
add_extended_headers(Stanza, HeaderEls) ->
- add_headers(Stanza, "addresses", ?NS_ADDRESS, HeaderEls).
+ add_headers(Stanza, <<"addresses">>, ?NS_ADDRESS,
+ HeaderEls).
-add_headers({xmlelement, Name, Attrs, Els}, HeaderName, HeaderNS, HeaderEls) ->
- HeaderEl = {xmlelement, HeaderName, [{"xmlns", HeaderNS}], HeaderEls},
- {xmlelement, Name, Attrs, lists:append(Els, [HeaderEl])}.
+add_headers(#xmlel{name = Name, attrs = Attrs, children = Els},
+ HeaderName, HeaderNS, HeaderEls) ->
+ HeaderEl = #xmlel{name = HeaderName,
+ attrs = [{<<"xmlns">>, HeaderNS}],
+ children = HeaderEls},
+ #xmlel{name = Name, attrs = Attrs,
+ children = lists:append(Els, [HeaderEl])}.
%% Removed multiple <header name=Collection>Foo</header/> elements
%% Didn't seem compliant, but not sure. Confirmation required.
@@ -3779,18 +4961,24 @@ add_headers({xmlelement, Name, Attrs, Els}, HeaderName, HeaderNS, HeaderEls) ->
%% identifier of the collection".
collection_shim(Node) ->
- [{xmlelement, "header", [{"name", "Collection"}],
- [{xmlcdata, node_to_string(Node)}]}].
+ [#xmlel{name = <<"header">>,
+ attrs = [{<<"name">>, <<"Collection">>}],
+ children = [{xmlcdata, Node}]}].
subid_shim(SubIDs) ->
- [{xmlelement, "header", [{"name", "SubID"}],
- [{xmlcdata, SubID}]} || SubID <- SubIDs].
+ [#xmlel{name = <<"header">>,
+ attrs = [{<<"name">>, <<"SubID">>}],
+ children = [{xmlcdata, SubID}]}
+ || SubID <- SubIDs].
%% The argument is a list of Jids because this function could be used
%% with the 'pubsub#replyto' (type=jid-multi) node configuration.
extended_headers(Jids) ->
- [{xmlelement, "address", [{"type", "replyto"}, {"jid", Jid}], []} || Jid <- Jids].
+ [#xmlel{name = <<"address">>,
+ attrs = [{<<"type">>, <<"replyto">>}, {<<"jid">>, Jid}],
+ children = []}
+ || Jid <- Jids].
on_user_offline(_, JID, _) ->
{User, Server, Resource} = jlib:jid_tolower(JID),
@@ -3802,57 +4990,84 @@ on_user_offline(_, JID, _) ->
purge_offline({User, Server, _} = LJID) ->
Host = host(element(2, LJID)),
Plugins = plugins(Host),
- Result = lists:foldl(
- fun(Type, {Status, Acc}) ->
- case lists:member("retrieve-affiliations", features(Type)) of
- false ->
- {{error, extended_error('feature-not-implemented', unsupported, "retrieve-affiliations")}, Acc};
- true ->
- {result, Affiliations} = node_action(Host, Type, get_entity_affiliations, [Host, LJID]),
- {Status, [Affiliations|Acc]}
- end
- end, {ok, []}, Plugins),
+ Result = lists:foldl(fun (Type, {Status, Acc}) ->
+ case lists:member(<<"retrieve-affiliations">>,
+ features(Type))
+ of
+ false ->
+ {{error,
+ extended_error(?ERR_FEATURE_NOT_IMPLEMENTED,
+ unsupported,
+ <<"retrieve-affiliations">>)},
+ Acc};
+ true ->
+ {result, Affiliations} =
+ node_action(Host, Type,
+ get_entity_affiliations,
+ [Host, LJID]),
+ {Status, [Affiliations | Acc]}
+ end
+ end,
+ {ok, []}, Plugins),
case Result of
- {ok, Affiliations} ->
- lists:foreach(
- fun({#pubsub_node{nodeid = {_, NodeId}, options = Options, type = Type}, Affiliation})
- when Affiliation == 'owner' orelse Affiliation == 'publisher' ->
- Action = fun(#pubsub_node{type = NType, id = NodeIdx}) ->
- node_call(NType, get_items, [NodeIdx, service_jid(Host)])
- end,
- case transaction(Host, NodeId, Action, sync_dirty) of
- {result, {_, []}} ->
- true;
- {result, {_, Items}} ->
- Features = features(Type),
- case
- {lists:member("retract-items", Features),
- lists:member("persistent-items", Features),
- get_option(Options, persist_items),
- get_option(Options, purge_offline)}
- of
- {true, true, true, true} ->
- ForceNotify = get_option(Options, notify_retract),
- lists:foreach(
- fun(#pubsub_item{itemid = {ItemId, _}, modification = {_, Modification}}) ->
- case Modification of
- {User, Server, _} ->
- delete_item(Host, NodeId, LJID, ItemId, ForceNotify);
- _ ->
- true
- end;
- (_) ->
- true
- end, Items);
- _ ->
- true
+ {ok, Affiliations} ->
+ lists:foreach(fun ({#pubsub_node{nodeid = {_, NodeId},
+ options = Options, type = Type},
+ Affiliation})
+ when Affiliation == owner orelse
+ Affiliation == publisher ->
+ Action = fun (#pubsub_node{type = NType,
+ id = NodeIdx}) ->
+ node_call(NType, get_items,
+ [NodeIdx,
+ service_jid(Host)])
+ end,
+ case transaction(Host, NodeId, Action,
+ sync_dirty)
+ of
+ {result, {_, []}} -> true;
+ {result, {_, Items}} ->
+ Features = features(Type),
+ case {lists:member(<<"retract-items">>,
+ Features),
+ lists:member(<<"persistent-items">>,
+ Features),
+ get_option(Options, persist_items),
+ get_option(Options, purge_offline)}
+ of
+ {true, true, true, true} ->
+ ForceNotify = get_option(Options,
+ notify_retract),
+ lists:foreach(fun
+ (#pubsub_item{itemid
+ =
+ {ItemId,
+ _},
+ modification
+ =
+ {_,
+ Modification}}) ->
+ case
+ Modification
+ of
+ {User, Server,
+ _} ->
+ delete_item(Host,
+ NodeId,
+ LJID,
+ ItemId,
+ ForceNotify);
+ _ -> true
+ end;
+ (_) -> true
+ end,
+ Items);
+ _ -> true
+ end;
+ Error -> Error
end;
- Error ->
- Error
- end;
- (_) ->
- true
- end, lists:usort(lists:flatten(Affiliations)));
- {Error, _} ->
- ?DEBUG("on_user_offline ~p", [Error])
+ (_) -> true
+ end,
+ lists:usort(lists:flatten(Affiliations)));
+ {Error, _} -> ?DEBUG("on_user_offline ~p", [Error])
end.
diff --git a/src/mod_pubsub/node_buddy.erl b/src/mod_pubsub/node_buddy.erl
index d3abc6386..23269b1eb 100644
--- a/src/mod_pubsub/node_buddy.erl
+++ b/src/mod_pubsub/node_buddy.erl
@@ -5,11 +5,13 @@
%%% Erlang Public License along with this software. If not, it can be
%%% retrieved via the world wide web at http://www.erlang.org/.
%%%
+%%%
%%% Software distributed under the License is distributed on an "AS IS"
%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%%% the License for the specific language governing rights and limitations
%%% under the License.
%%%
+%%%
%%% The Initial Developer of the Original Code is ProcessOne.
%%% Portions created by ProcessOne are Copyright 2006-2013, ProcessOne
%%% All Rights Reserved.''
@@ -24,9 +26,11 @@
%%% ====================================================================
-module(node_buddy).
+
-author('christophe.romain@process-one.net').
-include("pubsub.hrl").
+
-include("jlib.hrl").
-behaviour(gen_pubsub_node).
@@ -40,39 +44,18 @@
%% (this makes code cleaner, but execution a little bit longer)
%% API definition
--export([init/3, terminate/2,
- options/0, features/0,
- create_node_permission/6,
- create_node/2,
- delete_node/1,
- purge_node/2,
- subscribe_node/8,
- unsubscribe_node/4,
- publish_item/6,
- delete_item/4,
- remove_extra_items/3,
- get_entity_affiliations/2,
- get_node_affiliations/1,
- get_affiliation/2,
- set_affiliation/3,
- get_entity_subscriptions/2,
- get_node_subscriptions/1,
- get_subscriptions/2,
- set_subscriptions/4,
- get_pending_nodes/2,
- get_states/1,
- get_state/2,
- set_state/1,
- get_items/6,
- get_items/2,
- get_item/7,
- get_item/2,
- set_item/1,
- get_item_name/3,
- node_to_path/1,
- path_to_node/1
- ]).
-
+-export([init/3, terminate/2, options/0, features/0,
+ create_node_permission/6, create_node/2, delete_node/1,
+ purge_node/2, subscribe_node/8, unsubscribe_node/4,
+ publish_item/6, delete_item/4, remove_extra_items/3,
+ get_entity_affiliations/2, get_node_affiliations/1,
+ get_affiliation/2, set_affiliation/3,
+ get_entity_subscriptions/2, get_node_subscriptions/1,
+ get_subscriptions/2, set_subscriptions/4,
+ get_pending_nodes/2, get_states/1, get_state/2,
+ set_state/1, get_items/6, get_items/2, get_item/7,
+ get_item/2, set_item/1, get_item_name/3, node_to_path/1,
+ path_to_node/1]).
init(Host, ServerHost, Opts) ->
node_hometree:init(Host, ServerHost, Opts).
@@ -81,16 +64,11 @@ terminate(Host, ServerHost) ->
node_hometree:terminate(Host, ServerHost).
options() ->
- [{deliver_payloads, true},
- {notify_config, false},
- {notify_delete, false},
- {notify_retract, true},
- {purge_offline, false},
- {persist_items, true},
- {max_items, ?MAXITEMS},
- {subscribe, true},
- {access_model, presence},
- {roster_groups_allowed, []},
+ [{deliver_payloads, true}, {notify_config, false},
+ {notify_delete, false}, {notify_retract, true},
+ {purge_offline, false}, {persist_items, true},
+ {max_items, ?MAXITEMS}, {subscribe, true},
+ {access_model, presence}, {roster_groups_allowed, []},
{publish_model, publishers},
{notification_type, headline},
{max_payload_size, ?MAX_PAYLOAD_SIZE},
@@ -99,25 +77,18 @@ options() ->
{presence_based_delivery, false}].
features() ->
- ["create-nodes",
- "delete-nodes",
- "delete-items",
- "instant-nodes",
- "item-ids",
- "outcast-affiliation",
- "persistent-items",
- "publish",
- "purge-nodes",
- "retract-items",
- "retrieve-affiliations",
- "retrieve-items",
- "retrieve-subscriptions",
- "subscribe",
- "subscription-notifications"
- ].
-
-create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) ->
- node_hometree:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access).
+ [<<"create-nodes">>, <<"delete-nodes">>,
+ <<"delete-items">>, <<"instant-nodes">>, <<"item-ids">>,
+ <<"outcast-affiliation">>, <<"persistent-items">>,
+ <<"publish">>, <<"purge-nodes">>, <<"retract-items">>,
+ <<"retrieve-affiliations">>, <<"retrieve-items">>,
+ <<"retrieve-subscriptions">>, <<"subscribe">>,
+ <<"subscription-notifications">>].
+
+create_node_permission(Host, ServerHost, Node,
+ ParentNode, Owner, Access) ->
+ node_hometree:create_node_permission(Host, ServerHost,
+ Node, ParentNode, Owner, Access).
create_node(NodeId, Owner) ->
node_hometree:create_node(NodeId, Owner).
@@ -125,20 +96,28 @@ create_node(NodeId, Owner) ->
delete_node(Removed) ->
node_hometree:delete_node(Removed).
-subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options) ->
- node_hometree:subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options).
+subscribe_node(NodeId, Sender, Subscriber, AccessModel,
+ SendLast, PresenceSubscription, RosterGroup, Options) ->
+ node_hometree:subscribe_node(NodeId, Sender, Subscriber,
+ AccessModel, SendLast, PresenceSubscription,
+ RosterGroup, Options).
unsubscribe_node(NodeId, Sender, Subscriber, SubID) ->
- node_hometree:unsubscribe_node(NodeId, Sender, Subscriber, SubID).
+ node_hometree:unsubscribe_node(NodeId, Sender,
+ Subscriber, SubID).
-publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload) ->
- node_hometree:publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload).
+publish_item(NodeId, Publisher, Model, MaxItems, ItemId,
+ Payload) ->
+ node_hometree:publish_item(NodeId, Publisher, Model,
+ MaxItems, ItemId, Payload).
remove_extra_items(NodeId, MaxItems, ItemIds) ->
- node_hometree:remove_extra_items(NodeId, MaxItems, ItemIds).
+ node_hometree:remove_extra_items(NodeId, MaxItems,
+ ItemIds).
delete_item(NodeId, Publisher, PublishModel, ItemId) ->
- node_hometree:delete_item(NodeId, Publisher, PublishModel, ItemId).
+ node_hometree:delete_item(NodeId, Publisher,
+ PublishModel, ItemId).
purge_node(NodeId, Owner) ->
node_hometree:purge_node(NodeId, Owner).
@@ -153,7 +132,8 @@ get_affiliation(NodeId, Owner) ->
node_hometree:get_affiliation(NodeId, Owner).
set_affiliation(NodeId, Owner, Affiliation) ->
- node_hometree:set_affiliation(NodeId, Owner, Affiliation).
+ node_hometree:set_affiliation(NodeId, Owner,
+ Affiliation).
get_entity_subscriptions(Host, Owner) ->
node_hometree:get_entity_subscriptions(Host, Owner).
@@ -165,41 +145,40 @@ get_subscriptions(NodeId, Owner) ->
node_hometree:get_subscriptions(NodeId, Owner).
set_subscriptions(NodeId, Owner, Subscription, SubId) ->
- node_hometree:set_subscriptions(NodeId, Owner, Subscription, SubId).
+ node_hometree:set_subscriptions(NodeId, Owner,
+ Subscription, SubId).
get_pending_nodes(Host, Owner) ->
node_hometree:get_pending_nodes(Host, Owner).
-get_states(NodeId) ->
- node_hometree:get_states(NodeId).
+get_states(NodeId) -> node_hometree:get_states(NodeId).
get_state(NodeId, JID) ->
node_hometree:get_state(NodeId, JID).
-set_state(State) ->
- node_hometree:set_state(State).
+set_state(State) -> node_hometree:set_state(State).
get_items(NodeId, From) ->
node_hometree:get_items(NodeId, From).
-get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
- node_hometree:get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId).
+get_items(NodeId, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId) ->
+ node_hometree:get_items(NodeId, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId).
get_item(NodeId, ItemId) ->
node_hometree:get_item(NodeId, ItemId).
-get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
- node_hometree:get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId).
+get_item(NodeId, ItemId, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId) ->
+ node_hometree:get_item(NodeId, ItemId, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId).
-set_item(Item) ->
- node_hometree:set_item(Item).
+set_item(Item) -> node_hometree:set_item(Item).
get_item_name(Host, Node, Id) ->
node_hometree:get_item_name(Host, Node, Id).
-node_to_path(Node) ->
- node_flat:node_to_path(Node).
-
-path_to_node(Path) ->
- node_flat:path_to_node(Path).
+node_to_path(Node) -> node_flat:node_to_path(Node).
+path_to_node(Path) -> node_flat:path_to_node(Path).
diff --git a/src/mod_pubsub/node_club.erl b/src/mod_pubsub/node_club.erl
index 9ef26c611..10849b36d 100644
--- a/src/mod_pubsub/node_club.erl
+++ b/src/mod_pubsub/node_club.erl
@@ -5,11 +5,13 @@
%%% Erlang Public License along with this software. If not, it can be
%%% retrieved via the world wide web at http://www.erlang.org/.
%%%
+%%%
%%% Software distributed under the License is distributed on an "AS IS"
%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%%% the License for the specific language governing rights and limitations
%%% under the License.
%%%
+%%%
%%% The Initial Developer of the Original Code is ProcessOne.
%%% Portions created by ProcessOne are Copyright 2006-2013, ProcessOne
%%% All Rights Reserved.''
@@ -24,9 +26,11 @@
%%% ====================================================================
-module(node_club).
+
-author('christophe.romain@process-one.net').
-include("pubsub.hrl").
+
-include("jlib.hrl").
-behaviour(gen_pubsub_node).
@@ -40,39 +44,18 @@
%% (this makes code cleaner, but execution a little bit longer)
%% API definition
--export([init/3, terminate/2,
- options/0, features/0,
- create_node_permission/6,
- create_node/2,
- delete_node/1,
- purge_node/2,
- subscribe_node/8,
- unsubscribe_node/4,
- publish_item/6,
- delete_item/4,
- remove_extra_items/3,
- get_entity_affiliations/2,
- get_node_affiliations/1,
- get_affiliation/2,
- set_affiliation/3,
- get_entity_subscriptions/2,
- get_node_subscriptions/1,
- get_subscriptions/2,
- set_subscriptions/4,
- get_pending_nodes/2,
- get_states/1,
- get_state/2,
- set_state/1,
- get_items/6,
- get_items/2,
- get_item/7,
- get_item/2,
- set_item/1,
- get_item_name/3,
- node_to_path/1,
- path_to_node/1
- ]).
-
+-export([init/3, terminate/2, options/0, features/0,
+ create_node_permission/6, create_node/2, delete_node/1,
+ purge_node/2, subscribe_node/8, unsubscribe_node/4,
+ publish_item/6, delete_item/4, remove_extra_items/3,
+ get_entity_affiliations/2, get_node_affiliations/1,
+ get_affiliation/2, set_affiliation/3,
+ get_entity_subscriptions/2, get_node_subscriptions/1,
+ get_subscriptions/2, set_subscriptions/4,
+ get_pending_nodes/2, get_states/1, get_state/2,
+ set_state/1, get_items/6, get_items/2, get_item/7,
+ get_item/2, set_item/1, get_item_name/3, node_to_path/1,
+ path_to_node/1]).
init(Host, ServerHost, Opts) ->
node_hometree:init(Host, ServerHost, Opts).
@@ -81,16 +64,11 @@ terminate(Host, ServerHost) ->
node_hometree:terminate(Host, ServerHost).
options() ->
- [{deliver_payloads, true},
- {notify_config, false},
- {notify_delete, false},
- {notify_retract, true},
- {purge_offline, false},
- {persist_items, true},
- {max_items, ?MAXITEMS},
- {subscribe, true},
- {access_model, authorize},
- {roster_groups_allowed, []},
+ [{deliver_payloads, true}, {notify_config, false},
+ {notify_delete, false}, {notify_retract, true},
+ {purge_offline, false}, {persist_items, true},
+ {max_items, ?MAXITEMS}, {subscribe, true},
+ {access_model, authorize}, {roster_groups_allowed, []},
{publish_model, publishers},
{notification_type, headline},
{max_payload_size, ?MAX_PAYLOAD_SIZE},
@@ -99,24 +77,18 @@ options() ->
{presence_based_delivery, false}].
features() ->
- ["create-nodes",
- "delete-nodes",
- "delete-items",
- "instant-nodes",
- "outcast-affiliation",
- "persistent-items",
- "publish",
- "purge-nodes",
- "retract-items",
- "retrieve-affiliations",
- "retrieve-items",
- "retrieve-subscriptions",
- "subscribe",
- "subscription-notifications"
- ].
-
-create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) ->
- node_hometree:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access).
+ [<<"create-nodes">>, <<"delete-nodes">>,
+ <<"delete-items">>, <<"instant-nodes">>,
+ <<"outcast-affiliation">>, <<"persistent-items">>,
+ <<"publish">>, <<"purge-nodes">>, <<"retract-items">>,
+ <<"retrieve-affiliations">>, <<"retrieve-items">>,
+ <<"retrieve-subscriptions">>, <<"subscribe">>,
+ <<"subscription-notifications">>].
+
+create_node_permission(Host, ServerHost, Node,
+ ParentNode, Owner, Access) ->
+ node_hometree:create_node_permission(Host, ServerHost,
+ Node, ParentNode, Owner, Access).
create_node(NodeId, Owner) ->
node_hometree:create_node(NodeId, Owner).
@@ -124,20 +96,28 @@ create_node(NodeId, Owner) ->
delete_node(Removed) ->
node_hometree:delete_node(Removed).
-subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options) ->
- node_hometree:subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options).
+subscribe_node(NodeId, Sender, Subscriber, AccessModel,
+ SendLast, PresenceSubscription, RosterGroup, Options) ->
+ node_hometree:subscribe_node(NodeId, Sender, Subscriber,
+ AccessModel, SendLast, PresenceSubscription,
+ RosterGroup, Options).
unsubscribe_node(NodeId, Sender, Subscriber, SubID) ->
- node_hometree:unsubscribe_node(NodeId, Sender, Subscriber, SubID).
+ node_hometree:unsubscribe_node(NodeId, Sender,
+ Subscriber, SubID).
-publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload) ->
- node_hometree:publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload).
+publish_item(NodeId, Publisher, Model, MaxItems, ItemId,
+ Payload) ->
+ node_hometree:publish_item(NodeId, Publisher, Model,
+ MaxItems, ItemId, Payload).
remove_extra_items(NodeId, MaxItems, ItemIds) ->
- node_hometree:remove_extra_items(NodeId, MaxItems, ItemIds).
+ node_hometree:remove_extra_items(NodeId, MaxItems,
+ ItemIds).
delete_item(NodeId, Publisher, PublishModel, ItemId) ->
- node_hometree:delete_item(NodeId, Publisher, PublishModel, ItemId).
+ node_hometree:delete_item(NodeId, Publisher,
+ PublishModel, ItemId).
purge_node(NodeId, Owner) ->
node_hometree:purge_node(NodeId, Owner).
@@ -152,7 +132,8 @@ get_affiliation(NodeId, Owner) ->
node_hometree:get_affiliation(NodeId, Owner).
set_affiliation(NodeId, Owner, Affiliation) ->
- node_hometree:set_affiliation(NodeId, Owner, Affiliation).
+ node_hometree:set_affiliation(NodeId, Owner,
+ Affiliation).
get_entity_subscriptions(Host, Owner) ->
node_hometree:get_entity_subscriptions(Host, Owner).
@@ -164,41 +145,40 @@ get_subscriptions(NodeId, Owner) ->
node_hometree:get_subscriptions(NodeId, Owner).
set_subscriptions(NodeId, Owner, Subscription, SubId) ->
- node_hometree:set_subscriptions(NodeId, Owner, Subscription, SubId).
+ node_hometree:set_subscriptions(NodeId, Owner,
+ Subscription, SubId).
get_pending_nodes(Host, Owner) ->
node_hometree:get_pending_nodes(Host, Owner).
-get_states(NodeId) ->
- node_hometree:get_states(NodeId).
+get_states(NodeId) -> node_hometree:get_states(NodeId).
get_state(NodeId, JID) ->
node_hometree:get_state(NodeId, JID).
-set_state(State) ->
- node_hometree:set_state(State).
+set_state(State) -> node_hometree:set_state(State).
get_items(NodeId, From) ->
node_hometree:get_items(NodeId, From).
-get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
- node_hometree:get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId).
+get_items(NodeId, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId) ->
+ node_hometree:get_items(NodeId, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId).
get_item(NodeId, ItemId) ->
node_hometree:get_item(NodeId, ItemId).
-get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
- node_hometree:get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId).
+get_item(NodeId, ItemId, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId) ->
+ node_hometree:get_item(NodeId, ItemId, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId).
-set_item(Item) ->
- node_hometree:set_item(Item).
+set_item(Item) -> node_hometree:set_item(Item).
get_item_name(Host, Node, Id) ->
node_hometree:get_item_name(Host, Node, Id).
-node_to_path(Node) ->
- node_flat:node_to_path(Node).
-
-path_to_node(Path) ->
- node_flat:path_to_node(Path).
+node_to_path(Node) -> node_flat:node_to_path(Node).
+path_to_node(Path) -> node_flat:path_to_node(Path).
diff --git a/src/mod_pubsub/node_dag.erl b/src/mod_pubsub/node_dag.erl
index b70169460..9a36a4c4a 100644
--- a/src/mod_pubsub/node_dag.erl
+++ b/src/mod_pubsub/node_dag.erl
@@ -16,47 +16,29 @@
%%% ====================================================================
-module(node_dag).
+
-author('bjc@kublai.com').
-include("pubsub.hrl").
+
-include("jlib.hrl").
-behaviour(gen_pubsub_node).
%% API definition
--export([init/3, terminate/2,
- options/0, features/0,
- create_node_permission/6,
- create_node/2,
- delete_node/1,
- purge_node/2,
- subscribe_node/8,
- unsubscribe_node/4,
- publish_item/6,
- delete_item/4,
- remove_extra_items/3,
- get_entity_affiliations/2,
- get_node_affiliations/1,
- get_affiliation/2,
- set_affiliation/3,
- get_entity_subscriptions/2,
- get_node_subscriptions/1,
- get_subscriptions/2,
- set_subscriptions/4,
- get_pending_nodes/2,
- get_states/1,
- get_state/2,
- set_state/1,
- get_items/6,
- get_items/2,
- get_item/7,
- get_item/2,
- set_item/1,
- get_item_name/3,
- node_to_path/1,
+-export([init/3, terminate/2, options/0, features/0,
+ create_node_permission/6, create_node/2, delete_node/1,
+ purge_node/2, subscribe_node/8, unsubscribe_node/4,
+ publish_item/6, delete_item/4, remove_extra_items/3,
+ get_entity_affiliations/2, get_node_affiliations/1,
+ get_affiliation/2, set_affiliation/3,
+ get_entity_subscriptions/2, get_node_subscriptions/1,
+ get_subscriptions/2, set_subscriptions/4,
+ get_pending_nodes/2, get_states/1, get_state/2,
+ set_state/1, get_items/6, get_items/2, get_item/7,
+ get_item/2, set_item/1, get_item_name/3, node_to_path/1,
path_to_node/1]).
-
init(Host, ServerHost, Opts) ->
node_hometree:init(Host, ServerHost, Opts).
@@ -67,10 +49,10 @@ options() ->
[{node_type, leaf} | node_hometree:options()].
features() ->
- ["multi-collection" | node_hometree:features()].
+ [<<"multi-collection">> | node_hometree:features()].
-create_node_permission(_Host, _ServerHost, _Node, _ParentNode,
- _Owner, _Access) ->
+create_node_permission(_Host, _ServerHost, _Node,
+ _ParentNode, _Owner, _Access) ->
{result, true}.
create_node(NodeID, Owner) ->
@@ -81,39 +63,40 @@ delete_node(Removed) ->
subscribe_node(NodeID, Sender, Subscriber, AccessModel,
SendLast, PresenceSubscription, RosterGroup, Options) ->
- node_hometree:subscribe_node(NodeID, Sender, Subscriber, AccessModel,
- SendLast, PresenceSubscription, RosterGroup,
- Options).
+ node_hometree:subscribe_node(NodeID, Sender, Subscriber,
+ AccessModel, SendLast, PresenceSubscription,
+ RosterGroup, Options).
unsubscribe_node(NodeID, Sender, Subscriber, SubID) ->
- node_hometree:unsubscribe_node(NodeID, Sender, Subscriber, SubID).
+ node_hometree:unsubscribe_node(NodeID, Sender,
+ Subscriber, SubID).
-publish_item(NodeID, Publisher, Model, MaxItems, ItemID, Payload) ->
- %% TODO: should look up the NodeTree plugin here. There's no
- %% access to the Host of the request at this level, so for now we
- %% just use nodetree_dag.
+publish_item(NodeID, Publisher, Model, MaxItems, ItemID,
+ Payload) ->
case nodetree_dag:get_node(NodeID) of
- #pubsub_node{options = Options} ->
- case find_opt(node_type, Options) of
- collection ->
- {error, ?ERR_EXTENDED(?ERR_NOT_ALLOWED, "publish")};
- _ ->
- node_hometree:publish_item(NodeID, Publisher, Model,
- MaxItems, ItemID, Payload)
- end;
- Err ->
- Err
+ #pubsub_node{options = Options} ->
+ case find_opt(node_type, Options) of
+ collection ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"publish">>)};
+ _ ->
+ node_hometree:publish_item(NodeID, Publisher, Model,
+ MaxItems, ItemID, Payload)
+ end;
+ Err -> Err
end.
-find_opt(_, []) -> false;
+find_opt(_, []) -> false;
find_opt(Option, [{Option, Value} | _]) -> Value;
-find_opt(Option, [_ | T]) -> find_opt(Option, T).
+find_opt(Option, [_ | T]) -> find_opt(Option, T).
remove_extra_items(NodeID, MaxItems, ItemIDs) ->
- node_hometree:remove_extra_items(NodeID, MaxItems, ItemIDs).
+ node_hometree:remove_extra_items(NodeID, MaxItems,
+ ItemIDs).
delete_item(NodeID, Publisher, PublishModel, ItemID) ->
- node_hometree:delete_item(NodeID, Publisher, PublishModel, ItemID).
+ node_hometree:delete_item(NodeID, Publisher,
+ PublishModel, ItemID).
purge_node(NodeID, Owner) ->
node_hometree:purge_node(NodeID, Owner).
@@ -128,7 +111,8 @@ get_affiliation(NodeID, Owner) ->
node_hometree:get_affiliation(NodeID, Owner).
set_affiliation(NodeID, Owner, Affiliation) ->
- node_hometree:set_affiliation(NodeID, Owner, Affiliation).
+ node_hometree:set_affiliation(NodeID, Owner,
+ Affiliation).
get_entity_subscriptions(Host, Owner) ->
node_hometree:get_entity_subscriptions(Host, Owner).
@@ -140,45 +124,40 @@ get_subscriptions(NodeID, Owner) ->
node_hometree:get_subscriptions(NodeID, Owner).
set_subscriptions(NodeID, Owner, Subscription, SubID) ->
- node_hometree:set_subscriptions(NodeID, Owner, Subscription, SubID).
+ node_hometree:set_subscriptions(NodeID, Owner,
+ Subscription, SubID).
get_pending_nodes(Host, Owner) ->
node_hometree:get_pending_nodes(Host, Owner).
-get_states(NodeID) ->
- node_hometree:get_states(NodeID).
+get_states(NodeID) -> node_hometree:get_states(NodeID).
get_state(NodeID, JID) ->
node_hometree:get_state(NodeID, JID).
-set_state(State) ->
- node_hometree:set_state(State).
+set_state(State) -> node_hometree:set_state(State).
get_items(NodeID, From) ->
node_hometree:get_items(NodeID, From).
-get_items(NodeID, JID, AccessModel, PresenceSubscription,
- RosterGroup, SubID) ->
- node_hometree:get_items(NodeID, JID, AccessModel, PresenceSubscription,
- RosterGroup, SubID).
+get_items(NodeID, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubID) ->
+ node_hometree:get_items(NodeID, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubID).
get_item(NodeID, ItemID) ->
node_hometree:get_item(NodeID, ItemID).
-get_item(NodeID, ItemID, JID, AccessModel, PresenceSubscription,
- RosterGroup, SubID) ->
+get_item(NodeID, ItemID, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubID) ->
node_hometree:get_item(NodeID, ItemID, JID, AccessModel,
- PresenceSubscription, RosterGroup, SubID).
+ PresenceSubscription, RosterGroup, SubID).
-set_item(Item) ->
- node_hometree:set_item(Item).
+set_item(Item) -> node_hometree:set_item(Item).
get_item_name(Host, Node, ID) ->
node_hometree:get_item_name(Host, Node, ID).
-node_to_path(Node) ->
- node_hometree:node_to_path(Node).
-
-path_to_node(Path) ->
- node_hometree:path_to_node(Path).
+node_to_path(Node) -> node_hometree:node_to_path(Node).
+path_to_node(Path) -> node_hometree:path_to_node(Path).
diff --git a/src/mod_pubsub/node_dispatch.erl b/src/mod_pubsub/node_dispatch.erl
index c03ebda2d..9b72af7e7 100644
--- a/src/mod_pubsub/node_dispatch.erl
+++ b/src/mod_pubsub/node_dispatch.erl
@@ -5,11 +5,13 @@
%%% Erlang Public License along with this software. If not, it can be
%%% retrieved via the world wide web at http://www.erlang.org/.
%%%
+%%%
%%% Software distributed under the License is distributed on an "AS IS"
%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%%% the License for the specific language governing rights and limitations
%%% under the License.
%%%
+%%%
%%% The Initial Developer of the Original Code is ProcessOne.
%%% Portions created by ProcessOne are Copyright 2006-2013, ProcessOne
%%% All Rights Reserved.''
@@ -24,9 +26,11 @@
%%% ====================================================================
-module(node_dispatch).
+
-author('christophe.romain@process-one.net').
-include("pubsub.hrl").
+
-include("jlib.hrl").
-behaviour(gen_pubsub_node).
@@ -38,39 +42,18 @@
%%% This module can not work with virtual nodetree
%% API definition
--export([init/3, terminate/2,
- options/0, features/0,
- create_node_permission/6,
- create_node/2,
- delete_node/1,
- purge_node/2,
- subscribe_node/8,
- unsubscribe_node/4,
- publish_item/6,
- delete_item/4,
- remove_extra_items/3,
- get_entity_affiliations/2,
- get_node_affiliations/1,
- get_affiliation/2,
- set_affiliation/3,
- get_entity_subscriptions/2,
- get_node_subscriptions/1,
- get_subscriptions/2,
- set_subscriptions/4,
- get_pending_nodes/2,
- get_states/1,
- get_state/2,
- set_state/1,
- get_items/6,
- get_items/2,
- get_item/7,
- get_item/2,
- set_item/1,
- get_item_name/3,
- node_to_path/1,
- path_to_node/1
- ]).
-
+-export([init/3, terminate/2, options/0, features/0,
+ create_node_permission/6, create_node/2, delete_node/1,
+ purge_node/2, subscribe_node/8, unsubscribe_node/4,
+ publish_item/6, delete_item/4, remove_extra_items/3,
+ get_entity_affiliations/2, get_node_affiliations/1,
+ get_affiliation/2, set_affiliation/3,
+ get_entity_subscriptions/2, get_node_subscriptions/1,
+ get_subscriptions/2, set_subscriptions/4,
+ get_pending_nodes/2, get_states/1, get_state/2,
+ set_state/1, get_items/6, get_items/2, get_item/7,
+ get_item/2, set_item/1, get_item_name/3, node_to_path/1,
+ path_to_node/1]).
init(Host, ServerHost, Opts) ->
node_hometree:init(Host, ServerHost, Opts).
@@ -79,16 +62,11 @@ terminate(Host, ServerHost) ->
node_hometree:terminate(Host, ServerHost).
options() ->
- [{deliver_payloads, true},
- {notify_config, false},
- {notify_delete, false},
- {notify_retract, true},
- {purge_offline, false},
- {persist_items, true},
- {max_items, ?MAXITEMS},
- {subscribe, true},
- {access_model, open},
- {roster_groups_allowed, []},
+ [{deliver_payloads, true}, {notify_config, false},
+ {notify_delete, false}, {notify_retract, true},
+ {purge_offline, false}, {persist_items, true},
+ {max_items, ?MAXITEMS}, {subscribe, true},
+ {access_model, open}, {roster_groups_allowed, []},
{publish_model, publishers},
{notification_type, headline},
{max_payload_size, ?MAX_PAYLOAD_SIZE},
@@ -97,23 +75,15 @@ options() ->
{presence_based_delivery, false}].
features() ->
- ["create-nodes",
- "delete-nodes",
- "instant-nodes",
- "outcast-affiliation",
- "persistent-items",
- "publish",
- %%"purge-nodes",
- %%"retract-items",
- %%"retrieve-affiliations",
- "retrieve-items"
- %%"retrieve-subscriptions",
- %%"subscribe",
- %%"subscription-notifications",
- ].
-
-create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) ->
- node_hometree:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access).
+ [<<"create-nodes">>, <<"delete-nodes">>,
+ <<"instant-nodes">>, <<"outcast-affiliation">>,
+ <<"persistent-items">>, <<"publish">>,
+ <<"retrieve-items">>].
+
+create_node_permission(Host, ServerHost, Node,
+ ParentNode, Owner, Access) ->
+ node_hometree:create_node_permission(Host, ServerHost,
+ Node, ParentNode, Owner, Access).
create_node(NodeId, Owner) ->
node_hometree:create_node(NodeId, Owner).
@@ -121,88 +91,86 @@ create_node(NodeId, Owner) ->
delete_node(Removed) ->
node_hometree:delete_node(Removed).
-subscribe_node(_NodeId, _Sender, _Subscriber, _AccessModel,
- _SendLast, _PresenceSubscription, _RosterGroup, _Options) ->
+subscribe_node(_NodeId, _Sender, _Subscriber,
+ _AccessModel, _SendLast, _PresenceSubscription,
+ _RosterGroup, _Options) ->
{error, ?ERR_FORBIDDEN}.
-unsubscribe_node(_NodeId, _Sender, _Subscriber, _SubID) ->
+unsubscribe_node(_NodeId, _Sender, _Subscriber,
+ _SubID) ->
{error, ?ERR_FORBIDDEN}.
-publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload) ->
- lists:foreach(fun(SubNode) ->
- node_hometree:publish_item(
- SubNode#pubsub_node.id, Publisher, Model,
- MaxItems, ItemId, Payload)
- end, nodetree_tree:get_subnodes(NodeId, Publisher, Publisher)).
+publish_item(NodeId, Publisher, Model, MaxItems, ItemId,
+ Payload) ->
+ lists:foreach(fun (SubNode) ->
+ node_hometree:publish_item(SubNode#pubsub_node.id,
+ Publisher, Model, MaxItems,
+ ItemId, Payload)
+ end,
+ nodetree_tree:get_subnodes(NodeId, Publisher,
+ Publisher)).
remove_extra_items(_NodeId, _MaxItems, ItemIds) ->
{result, {ItemIds, []}}.
-delete_item(_NodeId, _Publisher, _PublishModel, _ItemId) ->
+delete_item(_NodeId, _Publisher, _PublishModel,
+ _ItemId) ->
{error, ?ERR_ITEM_NOT_FOUND}.
-purge_node(_NodeId, _Owner) ->
- {error, ?ERR_FORBIDDEN}.
+purge_node(_NodeId, _Owner) -> {error, ?ERR_FORBIDDEN}.
-get_entity_affiliations(_Host, _Owner) ->
- {result, []}.
+get_entity_affiliations(_Host, _Owner) -> {result, []}.
-get_node_affiliations(_NodeId) ->
- {result, []}.
+get_node_affiliations(_NodeId) -> {result, []}.
-get_affiliation(_NodeId, _Owner) ->
- {result, []}.
+get_affiliation(_NodeId, _Owner) -> {result, []}.
set_affiliation(NodeId, Owner, Affiliation) ->
- node_hometree:set_affiliation(NodeId, Owner, Affiliation).
+ node_hometree:set_affiliation(NodeId, Owner,
+ Affiliation).
-get_entity_subscriptions(_Host, _Owner) ->
- {result, []}.
+get_entity_subscriptions(_Host, _Owner) -> {result, []}.
get_node_subscriptions(NodeId) ->
- %% note: get_node_subscriptions is used for broadcasting
- %% DO NOT REMOVE
node_hometree:get_node_subscriptions(NodeId).
-get_subscriptions(_NodeId, _Owner) ->
- {result, []}.
+get_subscriptions(_NodeId, _Owner) -> {result, []}.
set_subscriptions(NodeId, Owner, Subscription, SubId) ->
- node_hometree:set_subscriptions(NodeId, Owner, Subscription, SubId).
+ node_hometree:set_subscriptions(NodeId, Owner,
+ Subscription, SubId).
get_pending_nodes(Host, Owner) ->
node_hometree:get_pending_nodes(Host, Owner).
-get_states(NodeId) ->
- node_hometree:get_states(NodeId).
+get_states(NodeId) -> node_hometree:get_states(NodeId).
get_state(NodeId, JID) ->
node_hometree:get_state(NodeId, JID).
-set_state(State) ->
- node_hometree:set_state(State).
+set_state(State) -> node_hometree:set_state(State).
get_items(NodeId, From) ->
node_hometree:get_items(NodeId, From).
-get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
- node_hometree:get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId).
+get_items(NodeId, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId) ->
+ node_hometree:get_items(NodeId, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId).
get_item(NodeId, ItemId) ->
node_hometree:get_item(NodeId, ItemId).
-get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
- node_hometree:get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId).
+get_item(NodeId, ItemId, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId) ->
+ node_hometree:get_item(NodeId, ItemId, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId).
-set_item(Item) ->
- node_hometree:set_item(Item).
+set_item(Item) -> node_hometree:set_item(Item).
get_item_name(Host, Node, Id) ->
node_hometree:get_item_name(Host, Node, Id).
-node_to_path(Node) ->
- node_flat:node_to_path(Node).
-
-path_to_node(Path) ->
- node_flat:path_to_node(Path).
+node_to_path(Node) -> node_flat:node_to_path(Node).
+path_to_node(Path) -> node_flat:path_to_node(Path).
diff --git a/src/mod_pubsub/node_flat.erl b/src/mod_pubsub/node_flat.erl
index fc361e8a0..836858520 100644
--- a/src/mod_pubsub/node_flat.erl
+++ b/src/mod_pubsub/node_flat.erl
@@ -5,11 +5,13 @@
%%% Erlang Public License along with this software. If not, it can be
%%% retrieved via the world wide web at http://www.erlang.org/.
%%%
+%%%
%%% Software distributed under the License is distributed on an "AS IS"
%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%%% the License for the specific language governing rights and limitations
%%% under the License.
%%%
+%%%
%%% The Initial Developer of the Original Code is ProcessOne.
%%% Portions created by ProcessOne are Copyright 2006-2013, ProcessOne
%%% All Rights Reserved.''
@@ -23,47 +25,28 @@
%%% ====================================================================
-module(node_flat).
+
-author('christophe.romain@process-one.net').
-include("pubsub.hrl").
+
-include("jlib.hrl").
-behaviour(gen_pubsub_node).
%% API definition
--export([init/3, terminate/2,
- options/0, features/0,
- create_node_permission/6,
- create_node/2,
- delete_node/1,
- purge_node/2,
- subscribe_node/8,
- unsubscribe_node/4,
- publish_item/6,
- delete_item/4,
- remove_extra_items/3,
- get_entity_affiliations/2,
- get_node_affiliations/1,
- get_affiliation/2,
- set_affiliation/3,
- get_entity_subscriptions/2,
- get_node_subscriptions/1,
- get_subscriptions/2,
- set_subscriptions/4,
- get_pending_nodes/2,
- get_states/1,
- get_state/2,
- set_state/1,
- get_items/6,
- get_items/2,
- get_item/7,
- get_item/2,
- set_item/1,
- get_item_name/3,
- node_to_path/1,
- path_to_node/1
- ]).
-
+-export([init/3, terminate/2, options/0, features/0,
+ create_node_permission/6, create_node/2, delete_node/1,
+ purge_node/2, subscribe_node/8, unsubscribe_node/4,
+ publish_item/6, delete_item/4, remove_extra_items/3,
+ get_entity_affiliations/2, get_node_affiliations/1,
+ get_affiliation/2, set_affiliation/3,
+ get_entity_subscriptions/2, get_node_subscriptions/1,
+ get_subscriptions/2, set_subscriptions/4,
+ get_pending_nodes/2, get_states/1, get_state/2,
+ set_state/1, get_items/6, get_items/2, get_item/7,
+ get_item/2, set_item/1, get_item_name/3, node_to_path/1,
+ path_to_node/1]).
init(Host, ServerHost, Opts) ->
node_hometree:init(Host, ServerHost, Opts).
@@ -72,16 +55,11 @@ terminate(Host, ServerHost) ->
node_hometree:terminate(Host, ServerHost).
options() ->
- [{deliver_payloads, true},
- {notify_config, false},
- {notify_delete, false},
- {notify_retract, true},
- {purge_offline, false},
- {persist_items, true},
- {max_items, ?MAXITEMS},
- {subscribe, true},
- {access_model, open},
- {roster_groups_allowed, []},
+ [{deliver_payloads, true}, {notify_config, false},
+ {notify_delete, false}, {notify_retract, true},
+ {purge_offline, false}, {persist_items, true},
+ {max_items, ?MAXITEMS}, {subscribe, true},
+ {access_model, open}, {roster_groups_allowed, []},
{publish_model, publishers},
{notification_type, headline},
{max_payload_size, ?MAX_PAYLOAD_SIZE},
@@ -89,20 +67,20 @@ options() ->
{deliver_notifications, true},
{presence_based_delivery, false}].
-features() ->
- node_hometree:features().
+features() -> node_hometree:features().
%% use same code as node_hometree, but do not limite node to
%% the home/localhost/user/... hierarchy
%% any node is allowed
-create_node_permission(Host, ServerHost, _Node, _ParentNode, Owner, Access) ->
+create_node_permission(Host, ServerHost, _Node,
+ _ParentNode, Owner, Access) ->
LOwner = jlib:jid_tolower(Owner),
Allowed = case LOwner of
- {"", Host, ""} ->
- true; % pubsub service always allowed
- _ ->
- acl:match_rule(ServerHost, Access, LOwner) =:= allow
- end,
+ {<<"">>, Host, <<"">>} ->
+ true; % pubsub service always allowed
+ _ ->
+ acl:match_rule(ServerHost, Access, LOwner) =:= allow
+ end,
{result, Allowed}.
create_node(NodeId, Owner) ->
@@ -111,20 +89,28 @@ create_node(NodeId, Owner) ->
delete_node(Removed) ->
node_hometree:delete_node(Removed).
-subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options) ->
- node_hometree:subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options).
+subscribe_node(NodeId, Sender, Subscriber, AccessModel,
+ SendLast, PresenceSubscription, RosterGroup, Options) ->
+ node_hometree:subscribe_node(NodeId, Sender, Subscriber,
+ AccessModel, SendLast, PresenceSubscription,
+ RosterGroup, Options).
unsubscribe_node(NodeId, Sender, Subscriber, SubID) ->
- node_hometree:unsubscribe_node(NodeId, Sender, Subscriber, SubID).
+ node_hometree:unsubscribe_node(NodeId, Sender,
+ Subscriber, SubID).
-publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload) ->
- node_hometree:publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload).
+publish_item(NodeId, Publisher, Model, MaxItems, ItemId,
+ Payload) ->
+ node_hometree:publish_item(NodeId, Publisher, Model,
+ MaxItems, ItemId, Payload).
remove_extra_items(NodeId, MaxItems, ItemIds) ->
- node_hometree:remove_extra_items(NodeId, MaxItems, ItemIds).
+ node_hometree:remove_extra_items(NodeId, MaxItems,
+ ItemIds).
delete_item(NodeId, Publisher, PublishModel, ItemId) ->
- node_hometree:delete_item(NodeId, Publisher, PublishModel, ItemId).
+ node_hometree:delete_item(NodeId, Publisher,
+ PublishModel, ItemId).
purge_node(NodeId, Owner) ->
node_hometree:purge_node(NodeId, Owner).
@@ -139,7 +125,8 @@ get_affiliation(NodeId, Owner) ->
node_hometree:get_affiliation(NodeId, Owner).
set_affiliation(NodeId, Owner, Affiliation) ->
- node_hometree:set_affiliation(NodeId, Owner, Affiliation).
+ node_hometree:set_affiliation(NodeId, Owner,
+ Affiliation).
get_entity_subscriptions(Host, Owner) ->
node_hometree:get_entity_subscriptions(Host, Owner).
@@ -151,47 +138,49 @@ get_subscriptions(NodeId, Owner) ->
node_hometree:get_subscriptions(NodeId, Owner).
set_subscriptions(NodeId, Owner, Subscription, SubId) ->
- node_hometree:set_subscriptions(NodeId, Owner, Subscription, SubId).
+ node_hometree:set_subscriptions(NodeId, Owner,
+ Subscription, SubId).
get_pending_nodes(Host, Owner) ->
node_hometree:get_pending_nodes(Host, Owner).
-get_states(NodeId) ->
- node_hometree:get_states(NodeId).
+get_states(NodeId) -> node_hometree:get_states(NodeId).
get_state(NodeId, JID) ->
node_hometree:get_state(NodeId, JID).
-set_state(State) ->
- node_hometree:set_state(State).
+set_state(State) -> node_hometree:set_state(State).
get_items(NodeId, From) ->
node_hometree:get_items(NodeId, From).
-get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
- node_hometree:get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId).
+get_items(NodeId, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId) ->
+ node_hometree:get_items(NodeId, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId).
get_item(NodeId, ItemId) ->
node_hometree:get_item(NodeId, ItemId).
-get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
- node_hometree:get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId).
+get_item(NodeId, ItemId, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId) ->
+ node_hometree:get_item(NodeId, ItemId, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId).
-set_item(Item) ->
- node_hometree:set_item(Item).
+set_item(Item) -> node_hometree:set_item(Item).
get_item_name(Host, Node, Id) ->
node_hometree:get_item_name(Host, Node, Id).
-node_to_path(Node) ->
- [binary_to_list(Node)].
+node_to_path(Node) -> [(Node)].
path_to_node(Path) ->
case Path of
- % default slot
- [Node] -> list_to_binary(Node);
- % handle old possible entries, used when migrating database content to new format
- [Node|_] when is_list(Node) -> list_to_binary(string:join([""|Path], "/"));
- % default case (used by PEP for example)
- _ -> list_to_binary(Path)
+ % default slot
+ [Node] -> iolist_to_binary(Node);
+ % handle old possible entries, used when migrating database content to new format
+ [Node | _] when is_binary(Node) ->
+ iolist_to_binary(str:join([<<"">> | Path], <<"/">>));
+ % default case (used by PEP for example)
+ _ -> iolist_to_binary(Path)
end.
diff --git a/src/mod_pubsub/node_flat_odbc.erl b/src/mod_pubsub/node_flat_odbc.erl
index 56cc867b7..4b686f65e 100644
--- a/src/mod_pubsub/node_flat_odbc.erl
+++ b/src/mod_pubsub/node_flat_odbc.erl
@@ -5,11 +5,13 @@
%%% Erlang Public License along with this software. If not, it can be
%%% retrieved via the world wide web at http://www.erlang.org/.
%%%
+%%%
%%% Software distributed under the License is distributed on an "AS IS"
%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%%% the License for the specific language governing rights and limitations
%%% under the License.
%%%
+%%%
%%% The Initial Developer of the Original Code is ProcessOne.
%%% Portions created by ProcessOne are Copyright 2006-2013, ProcessOne
%%% All Rights Reserved.''
@@ -23,51 +25,30 @@
%%% ====================================================================
-module(node_flat_odbc).
+
-author('christophe.romain@process-one.net').
-include("pubsub.hrl").
+
-include("jlib.hrl").
-behaviour(gen_pubsub_node).
%% API definition
--export([init/3, terminate/2,
- options/0, features/0,
- create_node_permission/6,
- create_node/2,
- delete_node/1,
- purge_node/2,
- subscribe_node/8,
- unsubscribe_node/4,
- publish_item/6,
- delete_item/4,
- remove_extra_items/3,
- get_entity_affiliations/2,
- get_node_affiliations/1,
- get_affiliation/2,
- set_affiliation/3,
+-export([init/3, terminate/2, options/0, features/0,
+ create_node_permission/6, create_node/2, delete_node/1,
+ purge_node/2, subscribe_node/8, unsubscribe_node/4,
+ publish_item/6, delete_item/4, remove_extra_items/3,
+ get_entity_affiliations/2, get_node_affiliations/1,
+ get_affiliation/2, set_affiliation/3,
get_entity_subscriptions/2,
get_entity_subscriptions_for_send_last/2,
- get_node_subscriptions/1,
- get_subscriptions/2,
- set_subscriptions/4,
- get_pending_nodes/2,
- get_states/1,
- get_state/2,
- set_state/1,
- get_items/7,
- get_items/6,
- get_items/3,
- get_items/2,
- get_item/7,
- get_item/2,
- set_item/1,
- get_item_name/3,
- get_last_items/3,
- node_to_path/1,
- path_to_node/1
- ]).
-
+ get_node_subscriptions/1, get_subscriptions/2,
+ set_subscriptions/4, get_pending_nodes/2, get_states/1,
+ get_state/2, set_state/1, get_items/7, get_items/6,
+ get_items/3, get_items/2, get_item/7, get_item/2,
+ set_item/1, get_item_name/3, get_last_items/3,
+ node_to_path/1, path_to_node/1]).
init(Host, ServerHost, Opts) ->
node_hometree_odbc:init(Host, ServerHost, Opts).
@@ -92,24 +73,23 @@ options() ->
{max_payload_size, ?MAX_PAYLOAD_SIZE},
{send_last_published_item, on_sub_and_presence},
{deliver_notifications, true},
- {presence_based_delivery, false},
- {odbc, true},
+ {presence_based_delivery, false}, {odbc, true},
{rsm, true}].
-features() ->
- node_hometree_odbc:features().
+features() -> node_hometree_odbc:features().
%% use same code as node_hometree_odbc, but do not limite node to
%% the home/localhost/user/... hierarchy
%% any node is allowed
-create_node_permission(Host, ServerHost, _Node, _ParentNode, Owner, Access) ->
+create_node_permission(Host, ServerHost, _Node,
+ _ParentNode, Owner, Access) ->
LOwner = jlib:jid_tolower(Owner),
Allowed = case LOwner of
- {"", Host, ""} ->
- true; % pubsub service always allowed
- _ ->
- acl:match_rule(ServerHost, Access, LOwner) =:= allow
- end,
+ {<<"">>, Host, <<"">>} ->
+ true; % pubsub service always allowed
+ _ ->
+ acl:match_rule(ServerHost, Access, LOwner) =:= allow
+ end,
{result, Allowed}.
create_node(NodeId, Owner) ->
@@ -118,20 +98,29 @@ create_node(NodeId, Owner) ->
delete_node(Removed) ->
node_hometree_odbc:delete_node(Removed).
-subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options) ->
- node_hometree_odbc:subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options).
+subscribe_node(NodeId, Sender, Subscriber, AccessModel,
+ SendLast, PresenceSubscription, RosterGroup, Options) ->
+ node_hometree_odbc:subscribe_node(NodeId, Sender,
+ Subscriber, AccessModel, SendLast,
+ PresenceSubscription, RosterGroup,
+ Options).
unsubscribe_node(NodeId, Sender, Subscriber, SubID) ->
- node_hometree_odbc:unsubscribe_node(NodeId, Sender, Subscriber, SubID).
+ node_hometree_odbc:unsubscribe_node(NodeId, Sender,
+ Subscriber, SubID).
-publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload) ->
- node_hometree_odbc:publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload).
+publish_item(NodeId, Publisher, Model, MaxItems, ItemId,
+ Payload) ->
+ node_hometree_odbc:publish_item(NodeId, Publisher,
+ Model, MaxItems, ItemId, Payload).
remove_extra_items(NodeId, MaxItems, ItemIds) ->
- node_hometree_odbc:remove_extra_items(NodeId, MaxItems, ItemIds).
+ node_hometree_odbc:remove_extra_items(NodeId, MaxItems,
+ ItemIds).
delete_item(NodeId, Publisher, PublishModel, ItemId) ->
- node_hometree_odbc:delete_item(NodeId, Publisher, PublishModel, ItemId).
+ node_hometree_odbc:delete_item(NodeId, Publisher,
+ PublishModel, ItemId).
purge_node(NodeId, Owner) ->
node_hometree_odbc:purge_node(NodeId, Owner).
@@ -146,13 +135,16 @@ get_affiliation(NodeId, Owner) ->
node_hometree_odbc:get_affiliation(NodeId, Owner).
set_affiliation(NodeId, Owner, Affiliation) ->
- node_hometree_odbc:set_affiliation(NodeId, Owner, Affiliation).
+ node_hometree_odbc:set_affiliation(NodeId, Owner,
+ Affiliation).
get_entity_subscriptions(Host, Owner) ->
- node_hometree_odbc:get_entity_subscriptions(Host, Owner).
+ node_hometree_odbc:get_entity_subscriptions(Host,
+ Owner).
get_entity_subscriptions_for_send_last(Host, Owner) ->
- node_hometree_odbc:get_entity_subscriptions_for_send_last(Host, Owner).
+ node_hometree_odbc:get_entity_subscriptions_for_send_last(Host,
+ Owner).
get_node_subscriptions(NodeId) ->
node_hometree_odbc:get_node_subscriptions(NodeId).
@@ -161,7 +153,8 @@ get_subscriptions(NodeId, Owner) ->
node_hometree_odbc:get_subscriptions(NodeId, Owner).
set_subscriptions(NodeId, Owner, Subscription, SubId) ->
- node_hometree_odbc:set_subscriptions(NodeId, Owner, Subscription, SubId).
+ node_hometree_odbc:set_subscriptions(NodeId, Owner,
+ Subscription, SubId).
get_pending_nodes(Host, Owner) ->
node_hometree_odbc:get_pending_nodes(Host, Owner).
@@ -172,26 +165,34 @@ get_states(NodeId) ->
get_state(NodeId, JID) ->
node_hometree_odbc:get_state(NodeId, JID).
-set_state(State) ->
- node_hometree_odbc:set_state(State).
+set_state(State) -> node_hometree_odbc:set_state(State).
get_items(NodeId, From) ->
node_hometree_odbc:get_items(NodeId, From).
+
get_items(NodeId, From, RSM) ->
node_hometree_odbc:get_items(NodeId, From, RSM).
-get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
- get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, none).
-get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) ->
- node_hometree_odbc:get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM).
+
+get_items(NodeId, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId) ->
+ get_items(NodeId, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId, none).
+
+get_items(NodeId, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId, RSM) ->
+ node_hometree_odbc:get_items(NodeId, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId, RSM).
get_item(NodeId, ItemId) ->
node_hometree_odbc:get_item(NodeId, ItemId).
-get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
- node_hometree_odbc:get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId).
+get_item(NodeId, ItemId, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId) ->
+ node_hometree_odbc:get_item(NodeId, ItemId, JID,
+ AccessModel, PresenceSubscription, RosterGroup,
+ SubId).
-set_item(Item) ->
- node_hometree_odbc:set_item(Item).
+set_item(Item) -> node_hometree_odbc:set_item(Item).
get_item_name(Host, Node, Id) ->
node_hometree_odbc:get_item_name(Host, Node, Id).
@@ -199,16 +200,15 @@ get_item_name(Host, Node, Id) ->
get_last_items(NodeId, From, Count) ->
node_hometree_odbc:get_last_items(NodeId, From, Count).
-node_to_path(Node) ->
- [binary_to_list(Node)].
+node_to_path(Node) -> [(Node)].
path_to_node(Path) ->
- case Path of
- % default slot
- [Node] -> list_to_binary(Node);
- % handle old possible entries, used when migrating database content to new format
- [Node|_] when is_list(Node) -> list_to_binary(string:join([""|Path], "/"));
- % default case (used by PEP for example)
- _ -> list_to_binary(Path)
+ case Path of
+ % default slot
+ [Node] -> iolist_to_binary(Node);
+ % handle old possible entries, used when migrating database content to new format
+ [Node | _] when is_binary(Node) ->
+ iolist_to_binary(str:join([<<"">> | Path], <<"/">>));
+ % default case (used by PEP for example)
+ _ -> iolist_to_binary(Path)
end.
-
diff --git a/src/mod_pubsub/node_hometree.erl b/src/mod_pubsub/node_hometree.erl
index 522677a15..57507e67b 100644
--- a/src/mod_pubsub/node_hometree.erl
+++ b/src/mod_pubsub/node_hometree.erl
@@ -5,11 +5,13 @@
%%% Erlang Public License along with this software. If not, it can be
%%% retrieved via the world wide web at http://www.erlang.org/.
%%%
+%%%
%%% Software distributed under the License is distributed on an "AS IS"
%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%%% the License for the specific language governing rights and limitations
%%% under the License.
%%%
+%%%
%%% The Initial Developer of the Original Code is ProcessOne.
%%% Portions created by ProcessOne are Copyright 2006-2013, ProcessOne
%%% All Rights Reserved.''
@@ -39,46 +41,28 @@
%%% improvements.</p>
-module(node_hometree).
+
-author('christophe.romain@process-one.net').
-include("pubsub.hrl").
+
-include("jlib.hrl").
-behaviour(gen_pubsub_node).
%% API definition
--export([init/3, terminate/2,
- options/0, features/0,
- create_node_permission/6,
- create_node/2,
- delete_node/1,
- purge_node/2,
- subscribe_node/8,
- unsubscribe_node/4,
- publish_item/6,
- delete_item/4,
- remove_extra_items/3,
- get_entity_affiliations/2,
- get_node_affiliations/1,
- get_affiliation/2,
- set_affiliation/3,
- get_entity_subscriptions/2,
- get_node_subscriptions/1,
- get_subscriptions/2,
- set_subscriptions/4,
- get_pending_nodes/2,
- get_states/1,
- get_state/2,
- set_state/1,
- get_items/6,
- get_items/2,
- get_item/7,
- get_item/2,
- set_item/1,
- get_item_name/3,
- node_to_path/1,
- path_to_node/1
- ]).
+-export([init/3, terminate/2, options/0, features/0,
+ create_node_permission/6, create_node/2, delete_node/1,
+ purge_node/2, subscribe_node/8, unsubscribe_node/4,
+ publish_item/6, delete_item/4, remove_extra_items/3,
+ get_entity_affiliations/2, get_node_affiliations/1,
+ get_affiliation/2, set_affiliation/3,
+ get_entity_subscriptions/2, get_node_subscriptions/1,
+ get_subscriptions/2, set_subscriptions/4,
+ get_pending_nodes/2, get_states/1, get_state/2,
+ set_state/1, get_items/6, get_items/2, get_item/7,
+ get_item/2, set_item/1, get_item_name/3, node_to_path/1,
+ path_to_node/1]).
%% ================
%% API definition
@@ -103,9 +87,9 @@ init(_Host, _ServerHost, _Options) ->
{attributes, record_info(fields, pubsub_item)}]),
ItemsFields = record_info(fields, pubsub_item),
case mnesia:table_info(pubsub_item, attributes) of
- ItemsFields -> ok;
- _ ->
- mnesia:transform_table(pubsub_item, ignore, ItemsFields)
+ ItemsFields -> ok;
+ _ ->
+ mnesia:transform_table(pubsub_item, ignore, ItemsFields)
end,
ok.
@@ -114,8 +98,9 @@ init(_Host, _ServerHost, _Options) ->
%% ServerHost = string()
%% @doc <p>Called during pubsub modules termination. Any pubsub plugin must
%% implement this function. It can return anything.</p>
-terminate(_Host, _ServerHost) ->
- ok.
+terminate(_Host, _ServerHost) -> ok.
+
+-spec(options/0 :: () -> NodeOptions::mod_pubsub:nodeOptions()).
%% @spec () -> Options
%% Options = [mod_pubsub:nodeOption()]
@@ -135,16 +120,11 @@ terminate(_Host, _ServerHost) ->
%% {send_last_published_item, never},
%% {presence_based_delivery, false}]'''
options() ->
- [{deliver_payloads, true},
- {notify_config, false},
- {notify_delete, false},
- {notify_retract, true},
- {purge_offline, false},
- {persist_items, true},
- {max_items, ?MAXITEMS},
- {subscribe, true},
- {access_model, open},
- {roster_groups_allowed, []},
+ [{deliver_payloads, true}, {notify_config, false},
+ {notify_delete, false}, {notify_retract, true},
+ {purge_offline, false}, {persist_items, true},
+ {max_items, ?MAXITEMS}, {subscribe, true},
+ {access_model, open}, {roster_groups_allowed, []},
{publish_model, publishers},
{notification_type, headline},
{max_payload_size, ?MAX_PAYLOAD_SIZE},
@@ -155,30 +135,8 @@ options() ->
%% @spec () -> Features
%% Features = [string()]
%% @doc Returns the node features
+-spec(features/0 :: () -> Features::[binary(),...]).
features() ->
- ["create-nodes",
- "auto-create",
- "access-authorize",
- "delete-nodes",
- "delete-items",
- "get-pending",
- "instant-nodes",
- "manage-subscriptions",
- "modify-affiliations",
- "multi-subscribe",
- "outcast-affiliation",
- "persistent-items",
- "publish",
- "purge-nodes",
- "retract-items",
- "retrieve-affiliations",
- "retrieve-items",
- "retrieve-subscriptions",
- "subscribe",
- "subscription-notifications",
- "subscription-options"
- ].
-
%% @spec (Host, ServerHost, NodeId, ParentNodeId, Owner, Access) -> {result, Allowed}
%% Host = mod_pubsub:hostPubsub()
%% ServerHost = string()
@@ -199,29 +157,58 @@ features() ->
%% module by implementing this function like this:
%% ```check_create_user_permission(Host, ServerHost, NodeId, ParentNodeId, Owner, Access) ->
%% node_default:check_create_user_permission(Host, ServerHost, NodeId, ParentNodeId, Owner, Access).'''</p>
+ [<<"create-nodes">>, <<"auto-create">>,
+ <<"access-authorize">>, <<"delete-nodes">>,
+ <<"delete-items">>, <<"get-pending">>,
+ <<"instant-nodes">>, <<"manage-subscriptions">>,
+ <<"modify-affiliations">>, <<"multi-subscribe">>,
+ <<"outcast-affiliation">>, <<"persistent-items">>,
+ <<"publish">>, <<"purge-nodes">>, <<"retract-items">>,
+ <<"retrieve-affiliations">>, <<"retrieve-items">>,
+ <<"retrieve-subscriptions">>, <<"subscribe">>,
+ <<"subscription-notifications">>,
+ <<"subscription-options">>].
+
+-spec(create_node_permission/6 ::
+(
+ Host :: mod_pubsub:host(),
+ ServerHost :: binary(),
+ NodeId :: mod_pubsub:nodeId(),
+ _ParentNodeId :: mod_pubsub:nodeId(),
+ Owner :: jid(),
+ Access :: atom())
+ -> {result, boolean()}
+).
+
create_node_permission(Host, ServerHost, NodeId, _ParentNodeId, Owner, Access) ->
LOwner = jlib:jid_tolower(Owner),
{User, Server, _Resource} = LOwner,
Allowed = case LOwner of
- {"", Host, ""} ->
- true; % pubsub service always allowed
- _ ->
- case acl:match_rule(ServerHost, Access, LOwner) of
- allow ->
- case node_to_path(NodeId) of
- ["home", Server, User | _] -> true;
- _ -> false
- end;
+ {<<"">>, Host, <<"">>} ->
+ true; % pubsub service always allowed
_ ->
- false
- end
- end,
+ case acl:match_rule(ServerHost, Access, LOwner) of
+ allow ->
+ case node_to_path(NodeId) of
+ [<<"home">>, Server, User | _] -> true;
+ _ -> false
+ end;
+ _ -> false
+ end
+ end,
{result, Allowed}.
%% @spec (NodeIdx, Owner) -> {result, {default, broadcast}}
%% NodeIdx = mod_pubsub:nodeIdx()
%% Owner = mod_pubsub:jid()
%% @doc <p></p>
+-spec(create_node/2 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ Owner :: jid())
+ -> {result, {default, broadcast}}
+).
+
create_node(NodeIdx, Owner) ->
OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
set_state(#pubsub_state{stateid = {OwnerKey, NodeIdx}, affiliation = owner}),
@@ -232,21 +219,27 @@ create_node(NodeIdx, Owner) ->
%% Reply = [{mod_pubsub:pubsubNode(),
%% [{mod_pubsub:ljid(), [{mod_pubsub:subscription(), mod_pubsub:subId()}]}]}]
%% @doc <p>purge items of deleted nodes after effective deletion.</p>
+-spec(delete_node/1 ::
+(
+ Nodes :: [mod_pubsub:pubsubNode(),...])
+ -> {result,
+ {default, broadcast,
+ [{mod_pubsub:pubsubNode(),
+ [{ljid(), [{mod_pubsub:subscription(), mod_pubsub:subId()}]},...]},...]
+ }
+ }
+).
delete_node(Nodes) ->
- Tr = fun(#pubsub_state{stateid = {J, _}, subscriptions = Ss}) ->
- lists:map(fun(S) ->
- {J, S}
- end, Ss)
- end,
- Reply = lists:map(
- fun(#pubsub_node{id = NodeId} = PubsubNode) ->
- {result, States} = get_states(NodeId),
- lists:foreach(
- fun(#pubsub_state{stateid = {LJID, _}, items = Items}) ->
- del_items(NodeId, Items),
- del_state(NodeId, LJID)
- end, States),
- {PubsubNode, lists:flatmap(Tr, States)}
+ Tr = fun (#pubsub_state{stateid = {J, _}, subscriptions = Ss}) ->
+ lists:map(fun (S) -> {J, S} end, Ss)
+ end,
+ Reply = lists:map(fun (#pubsub_node{id = NodeIdx} = PubsubNode) ->
+ {result, States} = get_states(NodeIdx),
+ lists:foreach(fun (#pubsub_state{stateid = {LJID, _}, items = Items}) ->
+ del_items(NodeIdx, Items),
+ del_state(NodeIdx, LJID)
+ end, States),
+ {PubsubNode, lists:flatmap(Tr, States)}
end, Nodes),
{result, {default, broadcast, Reply}}.
@@ -295,66 +288,84 @@ delete_node(Nodes) ->
%% to completly disable persistance.</li></ul>
%% </p>
%% <p>In the default plugin module, the record is unchanged.</p>
+-spec(subscribe_node/8 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ Sender :: jid(),
+ Subscriber :: ljid(),
+ AccessModel :: mod_pubsub:accessModel(),
+ SendLast :: 'never' | 'on_sub' | 'on_sub_and_presence',
+ PresenceSubscription :: boolean(),
+ RosterGroup :: boolean(),
+ Options :: mod_pubsub:subOptions())
+ -> {result, {default, subscribed, mod_pubsub:subId()}}
+ | {result, {default, subscribed, mod_pubsub:subId(), send_last}}
+ | {result, {default, pending, mod_pubsub:subId()}}
+ %%%
+ | {error, xmlel()}
+).
subscribe_node(NodeIdx, Sender, Subscriber, AccessModel,
SendLast, PresenceSubscription, RosterGroup, Options) ->
SubKey = jlib:jid_tolower(Subscriber),
GenKey = jlib:jid_remove_resource(SubKey),
- Authorized = (jlib:jid_tolower(jlib:jid_remove_resource(Sender)) == GenKey),
+ Authorized =
+ jlib:jid_tolower(jlib:jid_remove_resource(Sender)) ==
+ GenKey,
GenState = get_state(NodeIdx, GenKey),
SubState = case SubKey of
- GenKey -> GenState;
- _ -> get_state(NodeIdx, SubKey)
- end,
+ GenKey -> GenState;
+ _ -> get_state(NodeIdx, SubKey)
+ end,
Affiliation = GenState#pubsub_state.affiliation,
Subscriptions = SubState#pubsub_state.subscriptions,
- Whitelisted = lists:member(Affiliation, [member, publisher, owner]),
- PendingSubscription = lists:any(fun({pending, _}) -> true;
- (_) -> false
- end, Subscriptions),
- if
- not Authorized ->
- %% JIDs do not match
- {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "invalid-jid")};
- Affiliation == outcast ->
- %% Requesting entity is blocked
- {error, ?ERR_FORBIDDEN};
- PendingSubscription ->
- %% Requesting entity has pending subscription
- {error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "pending-subscription")};
- (AccessModel == presence) and (not PresenceSubscription) ->
- %% Entity is not authorized to create a subscription (presence subscription required)
- {error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "presence-subscription-required")};
- (AccessModel == roster) and (not RosterGroup) ->
- %% Entity is not authorized to create a subscription (not in roster group)
- {error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "not-in-roster-group")};
- (AccessModel == whitelist) and (not Whitelisted) ->
- %% Node has whitelist access model and entity lacks required affiliation
- {error, ?ERR_EXTENDED(?ERR_NOT_ALLOWED, "closed-node")};
- %%MustPay ->
- %% % Payment is required for a subscription
- %% {error, ?ERR_PAYMENT_REQUIRED};
- %%ForbiddenAnonymous ->
- %% % Requesting entity is anonymous
- %% {error, ?ERR_FORBIDDEN};
- true ->
- case pubsub_subscription:add_subscription(Subscriber, NodeIdx, Options) of
- SubId when is_list(SubId) ->
- NewSub = case AccessModel of
- authorize -> pending;
- _ -> subscribed
- end,
- set_state(SubState#pubsub_state{subscriptions = [{NewSub, SubId} | Subscriptions]}),
+ Whitelisted = lists:member(Affiliation,
+ [member, publisher, owner]),
+ PendingSubscription = lists:any(fun ({pending, _}) ->
+ true;
+ (_) -> false
+ end,
+ Subscriptions),
+ if not Authorized ->
+ {error,
+ ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"invalid-jid">>)};
+ Affiliation == outcast -> {error, ?ERR_FORBIDDEN};
+ PendingSubscription ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED),
+ <<"pending-subscription">>)};
+ (AccessModel == presence) and
+ not PresenceSubscription ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED),
+ <<"presence-subscription-required">>)};
+ (AccessModel == roster) and not RosterGroup ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED),
+ <<"not-in-roster-group">>)};
+ (AccessModel == whitelist) and not Whitelisted ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)};
+ %%MustPay ->
+ %% % Payment is required for a subscription
+ %% {error, ?ERR_PAYMENT_REQUIRED};
+ %%ForbiddenAnonymous ->
+ %% % Requesting entity is anonymous
+ %% {error, ?ERR_FORBIDDEN};
+ true ->
+ SubId = pubsub_subscription:add_subscription(Subscriber, NodeIdx, Options),
+ NewSub = case AccessModel of
+ authorize -> pending;
+ _ -> subscribed
+ end,
+ set_state(SubState#pubsub_state{subscriptions =
+ [{NewSub, SubId} | Subscriptions]}),
case {NewSub, SendLast} of
- {subscribed, never} ->
- {result, {default, subscribed, SubId}};
- {subscribed, _} ->
- {result, {default, subscribed, SubId, send_last}};
- {_, _} ->
- {result, {default, pending, SubId}}
- end;
- _ ->
- {error, ?ERR_INTERNAL_SERVER_ERROR}
- end
+ {subscribed, never} ->
+ {result, {default, subscribed, SubId}};
+ {subscribed, _} ->
+ {result, {default, subscribed, SubId, send_last}};
+ {_, _} -> {result, {default, pending, SubId}}
+ end
end.
%% @spec (NodeIdx, Sender, Subscriber, SubId) -> {error, Reason} | {result, default}
@@ -364,76 +375,100 @@ subscribe_node(NodeIdx, Sender, Subscriber, AccessModel,
%% SubId = mod_pubsub:subId()
%% Reason = mod_pubsub:stanzaError()
%% @doc <p>Unsubscribe the <tt>Subscriber</tt> from the <tt>Node</tt>.</p>
+-spec(unsubscribe_node/4 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ Sender :: jid(),
+ Subscriber :: ljid(),
+ SubId :: subId())
+ -> {result, default}
+ %
+ | {error, xmlel()}
+).
+
unsubscribe_node(NodeIdx, Sender, Subscriber, SubId) ->
SubKey = jlib:jid_tolower(Subscriber),
GenKey = jlib:jid_remove_resource(SubKey),
- Authorized = (jlib:jid_tolower(jlib:jid_remove_resource(Sender)) == GenKey),
+ Authorized =
+ jlib:jid_tolower(jlib:jid_remove_resource(Sender)) ==
+ GenKey,
GenState = get_state(NodeIdx, GenKey),
SubState = case SubKey of
- GenKey -> GenState;
- _ -> get_state(NodeIdx, SubKey)
- end,
- Subscriptions = lists:filter(fun({_Sub, _SubId}) -> true;
- (_SubId) -> false
- end, SubState#pubsub_state.subscriptions),
+ GenKey -> GenState;
+ _ -> get_state(NodeIdx, SubKey)
+ end,
+ Subscriptions = lists:filter(fun ({_Sub, _SubId}) ->
+ true;
+ (_SubId) -> false
+ end,
+ SubState#pubsub_state.subscriptions),
SubIdExists = case SubId of
- [] -> false;
- List when is_list(List) -> true;
- _ -> false
+ <<>> -> false;
+ Binary when is_binary(Binary) -> true;
+ _ -> false
end,
if
- %% Requesting entity is prohibited from unsubscribing entity
- not Authorized ->
- {error, ?ERR_FORBIDDEN};
- %% Entity did not specify SubId
- %%SubId == "", ?? ->
- %% {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
- %% Invalid subscription identifier
- %%InvalidSubId ->
- %% {error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
- %% Requesting entity is not a subscriber
- Subscriptions == [] ->
- {error, ?ERR_EXTENDED(?ERR_UNEXPECTED_REQUEST_CANCEL, "not-subscribed")};
- %% Subid supplied, so use that.
- SubIdExists ->
- Sub = first_in_list(fun(S) ->
- case S of
- {_Sub, SubId} -> true;
- _ -> false
- end
- end, SubState#pubsub_state.subscriptions),
- case Sub of
- {value, S} ->
- delete_subscriptions(SubKey, NodeIdx, [S], SubState),
- {result, default};
- false ->
- {error, ?ERR_EXTENDED(?ERR_UNEXPECTED_REQUEST_CANCEL, "not-subscribed")}
- end;
- %% Asking to remove all subscriptions to the given node
- SubId == all ->
- delete_subscriptions(SubKey, NodeIdx, Subscriptions, SubState),
- {result, default};
- %% No subid supplied, but there's only one matching subscription
- length(Subscriptions) == 1 ->
- delete_subscriptions(SubKey, NodeIdx, Subscriptions, SubState),
- {result, default};
- %% No subid and more than one possible subscription match.
- true ->
- {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")}
+ %% Requesting entity is prohibited from unsubscribing entity
+ not Authorized -> {error, ?ERR_FORBIDDEN};
+ %% Entity did not specify SubId
+ %%SubId == "", ?? ->
+ %% {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
+ %% Invalid subscription identifier
+ %%InvalidSubId ->
+ %% {error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
+ %% Requesting entity is not a subscriber
+ Subscriptions == [] ->
+ {error,
+ ?ERR_EXTENDED((?ERR_UNEXPECTED_REQUEST_CANCEL),
+ <<"not-subscribed">>)};
+ %% Subid supplied, so use that.
+ SubIdExists ->
+ Sub = first_in_list(fun (S) ->
+ case S of
+ {_Sub, SubId} -> true;
+ _ -> false
+ end
+ end,
+ SubState#pubsub_state.subscriptions),
+ case Sub of
+ {value, S} ->
+ delete_subscriptions(SubKey, NodeIdx, [S], SubState),
+ {result, default};
+ false ->
+ {error,
+ ?ERR_EXTENDED((?ERR_UNEXPECTED_REQUEST_CANCEL),
+ <<"not-subscribed">>)}
+ end;
+ %% Asking to remove all subscriptions to the given node
+ SubId == all ->
+ delete_subscriptions(SubKey, NodeIdx, Subscriptions, SubState),
+ {result, default};
+ %% No subid supplied, but there's only one matching subscription
+ length(Subscriptions) == 1 ->
+ delete_subscriptions(SubKey, NodeIdx, Subscriptions, SubState),
+ {result, default};
+ %% No subid and more than one possible subscription match.
+ true ->
+ {error,
+ ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"subid-required">>)}
end.
+-spec(delete_subscriptions/4 ::
+(
+ SubKey :: ljid(),
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ Subscriptions :: [{mod_pubsub:subscription(), mod_pubsub:subId()}],
+ SubState :: mod_pubsub:pubsubState())
+ -> ok
+).
delete_subscriptions(SubKey, NodeIdx, Subscriptions, SubState) ->
- NewSubs = lists:foldl(fun({Subscription, SubId}, Acc) ->
- pubsub_subscription:delete_subscription(SubKey, NodeIdx, SubId),
- Acc -- [{Subscription, SubId}]
+ NewSubs = lists:foldl(fun ({Subscription, SubId}, Acc) ->
+ pubsub_subscription:delete_subscription(SubKey, NodeIdx, SubId),
+ Acc -- [{Subscription, SubId}]
end, SubState#pubsub_state.subscriptions, Subscriptions),
case {SubState#pubsub_state.affiliation, NewSubs} of
- {none, []} ->
- % Just a regular subscriber, and this is final item, so
- % delete the state.
- del_state(NodeIdx, SubKey);
- _ ->
- set_state(SubState#pubsub_state{subscriptions = NewSubs})
+ {none, []} -> del_state(NodeIdx, SubKey);
+ _ -> set_state(SubState#pubsub_state{subscriptions = NewSubs})
end.
%% @spec (NodeIdx, Publisher, PublishModel, MaxItems, ItemId, Payload) ->
@@ -476,49 +511,62 @@ delete_subscriptions(SubKey, NodeIdx, Subscriptions, SubState) ->
%% to completly disable persistance.</li></ul>
%% </p>
%% <p>In the default plugin module, the record is unchanged.</p>
+-spec(publish_item/6 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ Publisher :: jid(),
+ PublishModel :: mod_pubsub:publishModel(),
+ Max_Items :: non_neg_integer(),
+ ItemId :: <<>> | mod_pubsub:itemId(),
+ Payload :: mod_pubsub:payload())
+ -> {result, {default, broadcast, [mod_pubsub:itemId()]}}
+ %%%
+ | {error, xmlel()}
+).
+
publish_item(NodeIdx, Publisher, PublishModel, MaxItems, ItemId, Payload) ->
SubKey = jlib:jid_tolower(Publisher),
GenKey = jlib:jid_remove_resource(SubKey),
GenState = get_state(NodeIdx, GenKey),
SubState = case SubKey of
- GenKey -> GenState;
- _ -> get_state(NodeIdx, SubKey)
- end,
+ GenKey -> GenState;
+ _ -> get_state(NodeIdx, SubKey)
+ end,
Affiliation = GenState#pubsub_state.affiliation,
Subscribed = case PublishModel of
- subscribers -> is_subscribed(SubState#pubsub_state.subscriptions);
- _ -> undefined
- end,
- if
- not ((PublishModel == open)
- or ((PublishModel == publishers)
- and ((Affiliation == owner) or (Affiliation == publisher)))
- or (Subscribed == true)) ->
- %% Entity does not have sufficient privileges to publish to node
- {error, ?ERR_FORBIDDEN};
- true ->
- %% TODO: check creation, presence, roster
- if MaxItems > 0 ->
- Now = now(),
- PubId = {Now, SubKey},
- Item = case get_item(NodeIdx, ItemId) of
- {result, OldItem} ->
- OldItem#pubsub_item{modification = PubId,
- payload = Payload};
- _ ->
- #pubsub_item{itemid = {ItemId, NodeIdx},
- creation = {Now, GenKey},
- modification = PubId,
- payload = Payload}
- end,
- Items = [ItemId | GenState#pubsub_state.items--[ItemId]],
- {result, {NI, OI}} = remove_extra_items(NodeIdx, MaxItems, Items),
- set_item(Item),
- set_state(GenState#pubsub_state{items = NI}),
- {result, {default, broadcast, OI}};
- true ->
- {result, {default, broadcast, []}}
- end
+ subscribers ->
+ is_subscribed(SubState#pubsub_state.subscriptions);
+ _ -> undefined
+ end,
+ if not
+ ((PublishModel == open) or
+ (PublishModel == publishers) and
+ ((Affiliation == owner) or (Affiliation == publisher))
+ or (Subscribed == true)) ->
+ {error, ?ERR_FORBIDDEN};
+ true ->
+ if MaxItems > 0 ->
+ Now = now(),
+ PubId = {Now, SubKey},
+ Item = case get_item(NodeIdx, ItemId) of
+ {result, OldItem} ->
+ OldItem#pubsub_item{modification = PubId,
+ payload = Payload};
+ _ ->
+ #pubsub_item{itemid = {ItemId, NodeIdx},
+ creation = {Now, GenKey},
+ modification = PubId,
+ payload = Payload}
+ end,
+ Items = [ItemId | GenState#pubsub_state.items --
+ [ItemId]],
+ {result, {NI, OI}} = remove_extra_items(NodeIdx,
+ MaxItems, Items),
+ set_item(Item),
+ set_state(GenState#pubsub_state{items = NI}),
+ {result, {default, broadcast, OI}};
+ true -> {result, {default, broadcast, []}}
+ end
end.
%% @spec (NodeIdx, MaxItems, ItemIds) -> {result, {NewItemIds,OldItemIds}}
@@ -537,14 +585,22 @@ publish_item(NodeIdx, Publisher, PublishModel, MaxItems, ItemId, Payload) ->
%% plugin is using the default pubsub storage), it can implements this function like this:
%% ```remove_extra_items(NodeIdx, MaxItems, ItemIds) ->
%% node_default:remove_extra_items(NodeIdx, MaxItems, ItemIds).'''</p>
+-spec(remove_extra_items/3 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ Max_Items :: unlimited | non_neg_integer(),
+ ItemIds :: [mod_pubsub:itemId()])
+ -> {result,
+ {NewItems::[mod_pubsub:itemId()],
+ OldItems::[mod_pubsub:itemId()]}
+ }
+).
remove_extra_items(_NodeIdx, unlimited, ItemIds) ->
{result, {ItemIds, []}};
remove_extra_items(NodeIdx, MaxItems, ItemIds) ->
NewItems = lists:sublist(ItemIds, MaxItems),
OldItems = lists:nthtail(length(NewItems), ItemIds),
- %% Remove extra items:
del_items(NodeIdx, OldItems),
- %% Return the new items list:
{result, {NewItems, OldItems}}.
%% @spec (NodeIdx, Publisher, PublishModel, ItemId) ->
@@ -557,74 +613,83 @@ remove_extra_items(NodeIdx, MaxItems, ItemIds) ->
%% @doc <p>Triggers item deletion.</p>
%% <p>Default plugin: The user performing the deletion must be the node owner
%% or a publisher, or PublishModel being open.</p>
+-spec(delete_item/4 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ Publisher :: jid(),
+ PublishModel :: mod_pubsub:publishModel(),
+ ItemId :: <<>> | mod_pubsub:itemId())
+ -> {result, {default, broadcast}}
+ %%%
+ | {error, xmlel()}
+).
delete_item(NodeIdx, Publisher, PublishModel, ItemId) ->
SubKey = jlib:jid_tolower(Publisher),
GenKey = jlib:jid_remove_resource(SubKey),
GenState = get_state(NodeIdx, GenKey),
#pubsub_state{affiliation = Affiliation, items = Items} = GenState,
- Allowed = (Affiliation == publisher) orelse (Affiliation == owner)
- orelse (PublishModel == open)
- orelse case get_item(NodeIdx, ItemId) of
- {result, #pubsub_item{creation = {_, GenKey}}} -> true;
- _ -> false
- end,
- if
- not Allowed ->
- %% Requesting entity does not have sufficient privileges
- {error, ?ERR_FORBIDDEN};
- true ->
- case lists:member(ItemId, Items) of
- true ->
- del_item(NodeIdx, ItemId),
- set_state(GenState#pubsub_state{items = lists:delete(ItemId, Items)}),
- {result, {default, broadcast}};
- false ->
- case Affiliation of
- owner ->
- %% Owner can delete any items from its own node
- {result, States} = get_states(NodeIdx),
- lists:foldl(
- fun(#pubsub_state{items = PI} = S, Res) ->
- case lists:member(ItemId, PI) of
- true ->
- del_item(NodeIdx, ItemId),
- set_state(S#pubsub_state{items = lists:delete(ItemId, PI)}),
- {result, {default, broadcast}};
- false ->
- Res
- end;
- (_, Res) ->
- Res
- end, {error, ?ERR_ITEM_NOT_FOUND}, States);
- _ ->
- %% Non-existent node or item
- {error, ?ERR_ITEM_NOT_FOUND}
- end
- end
+ Allowed = Affiliation == publisher orelse
+ Affiliation == owner orelse
+ PublishModel == open orelse
+ case get_item(NodeIdx, ItemId) of
+ {result, #pubsub_item{creation = {_, GenKey}}} -> true;
+ _ -> false
+ end,
+ if not Allowed -> {error, ?ERR_FORBIDDEN};
+ true ->
+ case lists:member(ItemId, Items) of
+ true ->
+ del_item(NodeIdx, ItemId),
+ set_state(GenState#pubsub_state{items = lists:delete(ItemId, Items)}),
+ {result, {default, broadcast}};
+ false ->
+ case Affiliation of
+ owner ->
+ {result, States} = get_states(NodeIdx),
+ lists:foldl(fun (#pubsub_state{items = PI} = S, Res) ->
+ case lists:member(ItemId, PI) of
+ true ->
+ del_item(NodeIdx, ItemId),
+ set_state(S#pubsub_state{items
+ = lists:delete(ItemId, PI)}),
+ {result, {default, broadcast}};
+ false -> Res
+ end;
+ (_, Res) -> Res
+ end,
+ {error, ?ERR_ITEM_NOT_FOUND}, States);
+ _ -> {error, ?ERR_ITEM_NOT_FOUND}
+ end
+ end
end.
%% @spec (NodeIdx, Owner) -> {error, Reason} | {result, {default, broadcast}}
%% NodeIdx = mod_pubsub:nodeIdx()
%% Owner = mod_pubsub:jid()
%% Reason = mod_pubsub:stanzaError()
+-spec(purge_node/2 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ Owner :: jid())
+ -> {result, {default, broadcast}}
+ | {error, xmlel()}
+).
+
purge_node(NodeIdx, Owner) ->
SubKey = jlib:jid_tolower(Owner),
GenKey = jlib:jid_remove_resource(SubKey),
GenState = get_state(NodeIdx, GenKey),
case GenState of
- #pubsub_state{affiliation = owner} ->
- {result, States} = get_states(NodeIdx),
- lists:foreach(
- fun(#pubsub_state{items = []}) ->
- ok;
- (#pubsub_state{items = Items} = S) ->
- del_items(NodeIdx, Items),
- set_state(S#pubsub_state{items = []})
- end, States),
- {result, {default, broadcast}};
- _ ->
- %% Entity is not owner
- {error, ?ERR_FORBIDDEN}
+ #pubsub_state{affiliation = owner} ->
+ {result, States} = get_states(NodeIdx),
+ lists:foreach(fun (#pubsub_state{items = []}) -> ok;
+ (#pubsub_state{items = Items} = S) ->
+ del_items(NodeIdx, Items),
+ set_state(S#pubsub_state{items = []})
+ end,
+ States),
+ {result, {default, broadcast}};
+ _ -> {error, ?ERR_FORBIDDEN}
end.
%% @spec (Host, Owner) -> {result, Reply}
@@ -638,44 +703,76 @@ purge_node(NodeIdx, Owner) ->
%% the default PubSub module. Otherwise, it should return its own affiliation,
%% that will be added to the affiliation stored in the main
%% <tt>pubsub_state</tt> table.</p>
+-spec(get_entity_affiliations/2 ::
+(
+ Host :: mod_pubsub:host(),
+ Owner :: jid())
+ -> {result, [{mod_pubsub:pubsubNode(), mod_pubsub:affiliation()}]}
+).
+
get_entity_affiliations(Host, Owner) ->
SubKey = jlib:jid_tolower(Owner),
GenKey = jlib:jid_remove_resource(SubKey),
States = mnesia:match_object(#pubsub_state{stateid = {GenKey, '_'}, _ = '_'}),
- NodeTree = case catch ets:lookup(gen_mod:get_module_proc(Host, config), nodetree) of
- [{nodetree, N}] -> N;
- _ -> nodetree_tree
- end,
- Reply = lists:foldl(fun(#pubsub_state{stateid = {_, N}, affiliation = A}, Acc) ->
- case NodeTree:get_node(N) of
- #pubsub_node{nodeid = {Host, _}} = Node -> [{Node, A}|Acc];
- _ -> Acc
- end
- end, [], States),
+ NodeTree = case catch
+ ets:lookup(gen_mod:get_module_proc(Host, config),
+ nodetree)
+ of
+ [{nodetree, N}] -> N;
+ _ -> nodetree_tree
+ end,
+ Reply = lists:foldl(fun (#pubsub_state{stateid = {_, N}, affiliation = A},
+ Acc) ->
+ case NodeTree:get_node(N) of
+ #pubsub_node{nodeid = {Host, _}} = Node ->
+ [{Node, A} | Acc];
+ _ -> Acc
+ end
+ end,
+ [], States),
{result, Reply}.
-get_node_affiliations(NodeId) ->
- {result, States} = get_states(NodeId),
- Tr = fun(#pubsub_state{stateid = {J, _}, affiliation = A}) ->
+-spec(get_node_affiliations/1 ::
+(
+ NodeIdx::mod_pubsub:nodeIdx())
+ -> {result, [{ljid(), mod_pubsub:affiliation()}]}
+).
+
+get_node_affiliations(NodeIdx) ->
+ {result, States} = get_states(NodeIdx),
+ Tr = fun (#pubsub_state{stateid = {J, _},
+ affiliation = A}) ->
{J, A}
end,
{result, lists:map(Tr, States)}.
-get_affiliation(NodeId, Owner) ->
+-spec(get_affiliation/2 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ Owner :: jid())
+ -> {result, mod_pubsub:affiliation()}
+).
+
+get_affiliation(NodeIdx, Owner) ->
SubKey = jlib:jid_tolower(Owner),
GenKey = jlib:jid_remove_resource(SubKey),
- GenState = get_state(NodeId, GenKey),
- {result, GenState#pubsub_state.affiliation}.
+ #pubsub_state{affiliation = Affiliation} = get_state(NodeIdx, GenKey),
+ {result, Affiliation}.
-set_affiliation(NodeId, Owner, Affiliation) ->
+-spec(set_affiliation/3 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ Owner :: ljid(),
+ Affiliation :: mod_pubsub:affiliation())
+ -> ok
+).
+set_affiliation(NodeIdx, Owner, Affiliation) ->
SubKey = jlib:jid_tolower(Owner),
GenKey = jlib:jid_remove_resource(SubKey),
- GenState = get_state(NodeId, GenKey),
+ GenState = get_state(NodeIdx, GenKey),
case {Affiliation, GenState#pubsub_state.subscriptions} of
- {none, none} ->
- del_state(NodeId, GenKey);
- _ ->
- set_state(GenState#pubsub_state{affiliation = Affiliation})
+ {none, []} -> del_state(NodeIdx, GenKey);
+ _ -> set_state(GenState#pubsub_state{affiliation = Affiliation})
end.
%% @spec (Host, Owner) ->
@@ -695,107 +792,165 @@ set_affiliation(NodeId, Owner, Affiliation) ->
%% the default PubSub module. Otherwise, it should return its own affiliation,
%% that will be added to the affiliation stored in the main
%% <tt>pubsub_state</tt> table.</p>
+-spec(get_entity_subscriptions/2 ::
+(
+ Host :: mod_pubsub:host(),
+ Owner :: jid())
+ -> {result,
+ [{mod_pubsub:pubsubNode(),
+ mod_pubsub:subscription(),
+ mod_pubsub:subId(),
+ ljid()}]
+ }
+).
+
get_entity_subscriptions(Host, Owner) ->
{U, D, _} = SubKey = jlib:jid_tolower(Owner),
GenKey = jlib:jid_remove_resource(SubKey),
States = case SubKey of
- GenKey -> mnesia:match_object(
- #pubsub_state{stateid = {{U, D, '_'}, '_'}, _ = '_'});
- _ -> mnesia:match_object(
- #pubsub_state{stateid = {GenKey, '_'}, _ = '_'})
- ++ mnesia:match_object(
- #pubsub_state{stateid = {SubKey, '_'}, _ = '_'})
- end,
- NodeTree = case catch ets:lookup(gen_mod:get_module_proc(Host, config), nodetree) of
- [{nodetree, N}] -> N;
- _ -> nodetree_tree
- end,
- Reply = lists:foldl(fun(#pubsub_state{stateid = {J, N}, subscriptions = Ss}, Acc) ->
- case NodeTree:get_node(N) of
- #pubsub_node{nodeid = {Host, _}} = Node ->
- lists:foldl(fun({Sub, SubId}, Acc2) ->
- [{Node, Sub, SubId, J} | Acc2];
- (S, Acc2) ->
- [{Node, S, J} | Acc2]
- end, Acc, Ss);
- _ -> Acc
- end
- end, [], States),
+ GenKey ->
+ mnesia:match_object(#pubsub_state{stateid =
+ {{U, D, '_'}, '_'},
+ _ = '_'});
+ _ ->
+ mnesia:match_object(#pubsub_state{stateid =
+ {GenKey, '_'},
+ _ = '_'})
+ ++
+ mnesia:match_object(#pubsub_state{stateid =
+ {SubKey, '_'},
+ _ = '_'})
+ end,
+ NodeTree = case catch
+ ets:lookup(gen_mod:get_module_proc(Host, config),
+ nodetree)
+ of
+ [{nodetree, N}] -> N;
+ _ -> nodetree_tree
+ end,
+ Reply = lists:foldl(fun (#pubsub_state{stateid = {J, N}, subscriptions = Ss},
+ Acc) ->
+ case NodeTree:get_node(N) of
+ #pubsub_node{nodeid = {Host, _}} = Node ->
+ lists:foldl(fun ({Sub, SubId}, Acc2) ->
+ [{Node, Sub, SubId, J} | Acc2]
+ end,
+ Acc, Ss);
+ _ -> Acc
+ end
+ end,
+ [], States),
{result, Reply}.
-get_node_subscriptions(NodeId) ->
- {result, States} = get_states(NodeId),
- Tr = fun(#pubsub_state{stateid = {J, _}, subscriptions = Subscriptions}) ->
- %% TODO: get rid of cases to handle non-list subscriptions
+-spec(get_node_subscriptions/1 ::
+(
+ NodeIdx::mod_pubsub:nodeIdx())
+ -> {result,
+ [{ljid(), mod_pubsub:subscription(), mod_pubsub:subId()}] |
+ [{ljid(), none},...]
+ }
+).
+get_node_subscriptions(NodeIdx) ->
+ {result, States} = get_states(NodeIdx),
+ Tr = fun (#pubsub_state{stateid = {J, _},
+ subscriptions = Subscriptions}) ->
case Subscriptions of
- [_|_] ->
- lists:foldl(fun({S, SubId}, Acc) ->
- [{J, S, SubId} | Acc];
- (S, Acc) ->
- [{J, S} | Acc]
- end, [], Subscriptions);
- [] ->
- [];
- _ ->
- [{J, none}]
+ [_ | _] ->
+ lists:foldl(fun ({S, SubId}, Acc) ->
+ [{J, S, SubId} | Acc]
+ end,
+ [], Subscriptions);
+ [] -> [];
+ _ -> [{J, none}]
end
end,
{result, lists:flatmap(Tr, States)}.
-get_subscriptions(NodeId, Owner) ->
+-spec(get_subscriptions/2 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ Owner :: ljid())
+ -> {result, [{mod_pubsub:subscription(), mod_pubsub:subId()}]}
+).
+get_subscriptions(NodeIdx, Owner) ->
SubKey = jlib:jid_tolower(Owner),
- SubState = get_state(NodeId, SubKey),
+ SubState = get_state(NodeIdx, SubKey),
{result, SubState#pubsub_state.subscriptions}.
-set_subscriptions(NodeId, Owner, Subscription, SubId) ->
+-spec(set_subscriptions/4 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ Owner :: jid(),
+ Subscription :: mod_pubsub:subscription(),
+ SubId :: mod_pubsub:subId())
+ -> ok
+ %%%
+ | {error, xmlel()}
+).
+
+set_subscriptions(NodeIdx, Owner, Subscription, SubId) ->
SubKey = jlib:jid_tolower(Owner),
- SubState = get_state(NodeId, SubKey),
+ SubState = get_state(NodeIdx, SubKey),
case {SubId, SubState#pubsub_state.subscriptions} of
- {_, []} ->
- case Subscription of
- none -> {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "not-subscribed")};
- _ -> new_subscription(NodeId, Owner, Subscription, SubState)
- end;
- {"", [{_, SID}]} ->
- case Subscription of
- none -> unsub_with_subid(NodeId, SID, SubState);
- _ -> replace_subscription({Subscription, SID}, SubState)
- end;
- {"", [_|_]} ->
- {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
- _ ->
- case Subscription of
- none -> unsub_with_subid(NodeId, SubId, SubState);
- _ -> replace_subscription({Subscription, SubId}, SubState)
- end
+ {_, []} ->
+ case Subscription of
+ none ->
+ {error,
+ ?ERR_EXTENDED((?ERR_BAD_REQUEST),
+ <<"not-subscribed">>)};
+ _ ->
+ new_subscription(NodeIdx, Owner, Subscription, SubState)
+ end;
+ {<<>>, [{_, SID}]} ->
+ case Subscription of
+ none -> unsub_with_subid(NodeIdx, SID, SubState);
+ _ -> replace_subscription({Subscription, SID}, SubState)
+ end;
+ {<<>>, [_ | _]} ->
+ {error,
+ ?ERR_EXTENDED((?ERR_BAD_REQUEST),
+ <<"subid-required">>)};
+ _ ->
+ case Subscription of
+ none -> unsub_with_subid(NodeIdx, SubId, SubState);
+ _ ->
+ replace_subscription({Subscription, SubId}, SubState)
+ end
end.
replace_subscription(NewSub, SubState) ->
- NewSubs = replace_subscription(NewSub,
- SubState#pubsub_state.subscriptions, []),
+ NewSubs = replace_subscription(NewSub, SubState#pubsub_state.subscriptions, []),
set_state(SubState#pubsub_state{subscriptions = NewSubs}).
-replace_subscription(_, [], Acc) ->
- Acc;
+replace_subscription(_, [], Acc) -> Acc;
replace_subscription({Sub, SubId}, [{_, SubID} | T], Acc) ->
replace_subscription({Sub, SubId}, T, [{Sub, SubID} | Acc]).
new_subscription(NodeId, Owner, Subscription, SubState) ->
SubId = pubsub_subscription:add_subscription(Owner, NodeId, []),
Subscriptions = SubState#pubsub_state.subscriptions,
- set_state(SubState#pubsub_state{subscriptions = [{Subscription, SubId} | Subscriptions]}),
+ set_state(SubState#pubsub_state{subscriptions =
+ [{Subscription, SubId} | Subscriptions]}),
{Subscription, SubId}.
-unsub_with_subid(NodeId, SubId, SubState) ->
+-spec(unsub_with_subid/3 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ SubId :: mod_pubsub:subId(),
+ SubState :: mod_pubsub:pubsubState())
+ -> ok
+).
+unsub_with_subid(NodeIdx, SubId, #pubsub_state{stateid = {Entity, _}} = SubState) ->
pubsub_subscription:delete_subscription(SubState#pubsub_state.stateid,
- NodeId, SubId),
- NewSubs = lists:filter(fun ({_, SID}) -> SubId =/= SID end,
+ NodeIdx, SubId),
+ NewSubs = lists:filter(fun ({_, SID}) -> SubId =/= SID
+ end,
SubState#pubsub_state.subscriptions),
case {NewSubs, SubState#pubsub_state.affiliation} of
- {[], none} ->
- del_state(NodeId, element(1, SubState#pubsub_state.stateid));
- _ ->
- set_state(SubState#pubsub_state{subscriptions = NewSubs})
+ {[], none} ->
+ del_state(NodeIdx, Entity);
+ _ ->
+ set_state(SubState#pubsub_state{subscriptions = NewSubs})
end.
%% TODO : doc
@@ -805,45 +960,61 @@ unsub_with_subid(NodeId, SubId, SubState) ->
%% Reply = [] | [mod_pubsub:nodeId()]
%% @doc <p>Returns a list of Owner's nodes on Host with pending
%% subscriptions.</p>
+-spec(get_pending_nodes/2 ::
+(
+ Host :: mod_pubsub:host(),
+ Owner :: jid())
+ -> {result, [mod_pubsub:nodeId()]}
+).
+
get_pending_nodes(Host, Owner) ->
GenKey = jlib:jid_remove_resource(jlib:jid_tolower(Owner)),
- States = mnesia:match_object(#pubsub_state{stateid = {GenKey, '_'},
- affiliation = owner,
- _ = '_'}),
- NodeIDs = [ID || #pubsub_state{stateid = {_, ID}} <- States],
- NodeTree = case catch ets:lookup(gen_mod:get_module_proc(Host, config), nodetree) of
- [{nodetree, N}] -> N;
- _ -> nodetree_tree
+ States = mnesia:match_object(#pubsub_state{stateid =
+ {GenKey, '_'},
+ affiliation = owner, _ = '_'}),
+ NodeIDs = [ID
+ || #pubsub_state{stateid = {_, ID}} <- States],
+ NodeTree = case catch
+ ets:lookup(gen_mod:get_module_proc(Host, config),
+ nodetree)
+ of
+ [{nodetree, N}] -> N;
+ _ -> nodetree_tree
end,
- Reply = mnesia:foldl(fun(#pubsub_state{stateid = {_, NID}} = S, Acc) ->
- case lists:member(NID, NodeIDs) of
- true ->
- case get_nodes_helper(NodeTree, S) of
- {value, Node} -> [Node | Acc];
- false -> Acc
- end;
- false ->
- Acc
- end
- end, [], pubsub_state),
+ Reply = mnesia:foldl(fun (#pubsub_state{stateid = {_, NID}} = S,
+ Acc) ->
+ case lists:member(NID, NodeIDs) of
+ true ->
+ case get_nodes_helper(NodeTree, S) of
+ {value, Node} -> [Node | Acc];
+ false -> Acc
+ end;
+ false -> Acc
+ end
+ end,
+ [], pubsub_state),
{result, Reply}.
-get_nodes_helper(NodeTree,
- #pubsub_state{stateid = {_, N}, subscriptions = Subs}) ->
+-spec(get_nodes_helper/2 ::
+(
+ NodeTree :: module(),
+ Pubsub_State :: mod_pubsub:pubsubState())
+ -> {value, NodeId::mod_pubsub:nodeId()}
+ | false
+
+).
+get_nodes_helper(NodeTree, #pubsub_state{stateid = {_, N}, subscriptions = Subs}) ->
HasPending = fun ({pending, _}) -> true;
- (pending) -> true;
- (_) -> false
+ (pending) -> true;
+ (_) -> false
end,
case lists:any(HasPending, Subs) of
- true ->
- case NodeTree:get_node(N) of
- #pubsub_node{nodeid = {_, Node}} ->
- {value, Node};
- _ ->
- false
- end;
- false ->
- false
+ true ->
+ case NodeTree:get_node(N) of
+ #pubsub_node{nodeid = {_, Node}} -> {value, Node};
+ _ -> false
+ end;
+ false -> false
end.
%% @spec (NodeIdx) -> {result, States}
@@ -859,6 +1030,12 @@ get_nodes_helper(NodeTree,
%% they can implement this function like this:
%% ```get_states(NodeIdx) ->
%% node_default:get_states(NodeIdx).'''</p>
+-spec(get_states/1 ::
+(
+ NodeIdx::mod_pubsub:nodeIdx())
+ -> {result, [mod_pubsub:pubsubState()]}
+).
+
get_states(NodeIdx) ->
States = case catch mnesia:match_object(
#pubsub_state{stateid = {'_', NodeIdx}, _ = '_'}) of
@@ -872,6 +1049,13 @@ get_states(NodeIdx) ->
%% JID = mod_pubsub:jid()
%% State = mod_pubsub:pubsubState()
%% @doc <p>Returns a state (one state list), given its reference.</p>
+-spec(get_state/2 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ JID :: ljid())
+ -> mod_pubsub:pubsubState()
+).
+
get_state(NodeIdx, JID) ->
StateId = {JID, NodeIdx},
case catch mnesia:read({pubsub_state, StateId}) of
@@ -883,16 +1067,26 @@ get_state(NodeIdx, JID) ->
%% State = mod_pubsub:pubsubState()
%% Reason = mod_pubsub:stanzaError()
%% @doc <p>Write a state into database.</p>
+-spec(set_state/1 ::
+(
+ State::mod_pubsub:pubsubState())
+ -> ok
+).
set_state(State) when is_record(State, pubsub_state) ->
- mnesia:write(State);
-set_state(_) ->
- {error, ?ERR_INTERNAL_SERVER_ERROR}.
-
%% @spec (NodeIdx, JID) -> ok | {error, Reason}
%% NodeIdx = mod_pubsub:nodeIdx()
%% JID = mod_pubsub:jid()
%% Reason = mod_pubsub:stanzaError()
%% @doc <p>Delete a state from database.</p>
+ mnesia:write(State).
+%set_state(_) -> {error, ?ERR_INTERNAL_SERVER_ERROR}.
+
+-spec(del_state/2 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ JID :: ljid())
+ -> ok
+).
del_state(NodeIdx, JID) ->
mnesia:delete({pubsub_state, {JID, NodeIdx}}).
@@ -910,10 +1104,30 @@ del_state(NodeIdx, JID) ->
%% they can implement this function like this:
%% ```get_items(NodeIdx, From) ->
%% node_default:get_items(NodeIdx, From).'''</p>
+-spec(get_items/2 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ _From :: jid())
+ -> {result, [mod_pubsub:pubsubItem()]}
+).
+
get_items(NodeIdx, _From) ->
Items = mnesia:match_object(#pubsub_item{itemid = {'_', NodeIdx}, _ = '_'}),
{result, lists:reverse(lists:keysort(#pubsub_item.modification, Items))}.
+-spec(get_items/6 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ JID :: jid(),
+ AccessModel :: mod_pubsub:accessModel(),
+ Presence_Subscription :: boolean(),
+ RosterGroup :: boolean(),
+ _SubId :: mod_pubsub:subId())
+ -> {result, [mod_pubsub:pubsubItem()]}
+ %%%
+ | {error, xmlel()}
+).
+
get_items(NodeIdx, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId) ->
SubKey = jlib:jid_tolower(JID),
GenKey = jlib:jid_remove_resource(SubKey),
@@ -922,33 +1136,32 @@ get_items(NodeIdx, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId)
Affiliation = GenState#pubsub_state.affiliation,
Subscriptions = SubState#pubsub_state.subscriptions,
Whitelisted = can_fetch_item(Affiliation, Subscriptions),
- if
- %%SubId == "", ?? ->
- %% Entity has multiple subscriptions to the node but does not specify a subscription ID
- %{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
- %%InvalidSubId ->
- %% Entity is subscribed but specifies an invalid subscription ID
- %{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
- GenState#pubsub_state.affiliation == outcast ->
- %% Requesting entity is blocked
- {error, ?ERR_FORBIDDEN};
- (AccessModel == presence) and (not PresenceSubscription) ->
- %% Entity is not authorized to create a subscription (presence subscription required)
- {error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "presence-subscription-required")};
- (AccessModel == roster) and (not RosterGroup) ->
- %% Entity is not authorized to create a subscription (not in roster group)
- {error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "not-in-roster-group")};
- (AccessModel == whitelist) and (not Whitelisted) ->
- %% Node has whitelist access model and entity lacks required affiliation
- {error, ?ERR_EXTENDED(?ERR_NOT_ALLOWED, "closed-node")};
- (AccessModel == authorize) and (not Whitelisted) ->
- %% Node has authorize access model
- {error, ?ERR_FORBIDDEN};
- %%MustPay ->
- %% % Payment is required for a subscription
- %% {error, ?ERR_PAYMENT_REQUIRED};
- true ->
- get_items(NodeIdx, JID)
+ if %%SubId == "", ?? ->
+ %% Entity has multiple subscriptions to the node but does not specify a subscription ID
+ %{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
+ %%InvalidSubId ->
+ %% Entity is subscribed but specifies an invalid subscription ID
+ %{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
+ GenState#pubsub_state.affiliation == outcast ->
+ {error, ?ERR_FORBIDDEN};
+ (AccessModel == presence) and
+ not PresenceSubscription ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED),
+ <<"presence-subscription-required">>)};
+ (AccessModel == roster) and not RosterGroup ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED),
+ <<"not-in-roster-group">>)};
+ (AccessModel == whitelist) and not Whitelisted ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)};
+ (AccessModel == authorize) and not Whitelisted ->
+ {error, ?ERR_FORBIDDEN};
+ %%MustPay ->
+ %% % Payment is required for a subscription
+ %% {error, ?ERR_PAYMENT_REQUIRED};
+ true -> get_items(NodeIdx, JID)
end.
%% @spec (NodeIdx, ItemId) -> {result, Item} | {error, 'item-not-found'}
@@ -956,12 +1169,18 @@ get_items(NodeIdx, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId)
%% ItemId = mod_pubsub:itemId()
%% Item = mod_pubsub:pubsubItem()
%% @doc <p>Returns an item (one item list), given its reference.</p>
+-spec(get_item/2 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ ItemId :: mod_pubsub:itemId())
+ -> {result, mod_pubsub:pubsubItem()}
+ | {error, xmlel()}
+).
+
get_item(NodeIdx, ItemId) ->
case mnesia:read({pubsub_item, {ItemId, NodeIdx}}) of
- [Item] when is_record(Item, pubsub_item) ->
- {result, Item};
- _ ->
- {error, ?ERR_ITEM_NOT_FOUND}
+ [Item] when is_record(Item, pubsub_item) -> {result, Item};
+ _ -> {error, ?ERR_ITEM_NOT_FOUND}
end.
%% @spec (NodeIdx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> {result, Item} | {error, Reason}
@@ -974,101 +1193,137 @@ get_item(NodeIdx, ItemId) ->
%% SubId = mod_pubsub:subId()
%% Item = mod_pubsub:pubsubItem()
%% Reason = mod_pubsub:stanzaError() | 'item-not-found'
+-spec(get_item/7 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ ItemId :: mod_pubsub:itemId(),
+ JID :: jid(),
+ AccessModel :: mod_pubsub:accessModel(),
+ PresenceSubscription :: boolean(),
+ RosterGroup :: boolean(),
+ SubId :: mod_pubsub:subId())
+ -> {result, mod_pubsub:pubsubItem()}
+ | {error, xmlel()}
+).
-get_item(NodeIdx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId) ->
+get_item(NodeIdx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup,
+ _SubId) ->
SubKey = jlib:jid_tolower(JID),
GenKey = jlib:jid_remove_resource(SubKey),
GenState = get_state(NodeIdx, GenKey),
Affiliation = GenState#pubsub_state.affiliation,
Subscriptions = GenState#pubsub_state.subscriptions,
Whitelisted = can_fetch_item(Affiliation, Subscriptions),
- if
- %%SubId == "", ?? ->
- %% Entity has multiple subscriptions to the node but does not specify a subscription ID
- %{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
- %%InvalidSubId ->
- %% Entity is subscribed but specifies an invalid subscription ID
- %{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
- GenState#pubsub_state.affiliation == outcast ->
- %% Requesting entity is blocked
- {error, ?ERR_FORBIDDEN};
- (AccessModel == presence) and (not PresenceSubscription) ->
- %% Entity is not authorized to create a subscription (presence subscription required)
- {error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "presence-subscription-required")};
- (AccessModel == roster) and (not RosterGroup) ->
- %% Entity is not authorized to create a subscription (not in roster group)
- {error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "not-in-roster-group")};
- (AccessModel == whitelist) and (not Whitelisted) ->
- %% Node has whitelist access model and entity lacks required affiliation
- {error, ?ERR_EXTENDED(?ERR_NOT_ALLOWED, "closed-node")};
- (AccessModel == authorize) and (not Whitelisted) ->
- %% Node has authorize access model
- {error, ?ERR_FORBIDDEN};
- %%MustPay ->
- %% % Payment is required for a subscription
- %% {error, ?ERR_PAYMENT_REQUIRED};
- true ->
- get_item(NodeIdx, ItemId)
+ if %%SubId == "", ?? ->
+ %% Entity has multiple subscriptions to the node but does not specify a subscription ID
+ %{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
+ %%InvalidSubId ->
+ %% Entity is subscribed but specifies an invalid subscription ID
+ %{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
+ GenState#pubsub_state.affiliation == outcast ->
+ {error, ?ERR_FORBIDDEN};
+ (AccessModel == presence) and
+ not PresenceSubscription ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED),
+ <<"presence-subscription-required">>)};
+ (AccessModel == roster) and not RosterGroup ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"not-in-roster-group">>)};
+ (AccessModel == whitelist) and not Whitelisted ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)};
+ (AccessModel == authorize) and not Whitelisted ->
+ {error, ?ERR_FORBIDDEN};
+ %%MustPay ->
+ %% % Payment is required for a subscription
+ %% {error, ?ERR_PAYMENT_REQUIRED};
+ true -> get_item(NodeIdx, ItemId)
end.
%% @spec (Item) -> ok | {error, Reason}
%% Item = mod_pubsub:pubsubItem()
%% Reason = mod_pubsub:stanzaError()
%% @doc <p>Write an item into database.</p>
+-spec(set_item/1 ::
+(
+ Item::mod_pubsub:pubsubItem())
+ -> ok
+).
set_item(Item) when is_record(Item, pubsub_item) ->
- mnesia:write(Item);
-set_item(_) ->
- {error, ?ERR_INTERNAL_SERVER_ERROR}.
-
%% @spec (NodeIdx, ItemId) -> ok | {error, Reason}
%% NodeIdx = mod_pubsub:nodeIdx()
%% ItemId = mod_pubsub:itemId()
%% Reason = mod_pubsub:stanzaError()
%% @doc <p>Delete an item from database.</p>
+ mnesia:write(Item).
+%set_item(_) -> {error, ?ERR_INTERNAL_SERVER_ERROR}.
+
+-spec(del_item/2 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ ItemId :: mod_pubsub:itemId())
+ -> ok
+).
del_item(NodeIdx, ItemId) ->
mnesia:delete({pubsub_item, {ItemId, NodeIdx}}).
+-spec(del_items/2 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ ItemIds :: [mod_pubsub:pubsubItem(),...])
+ -> ok
+).
+
del_items(NodeIdx, ItemIds) ->
- lists:foreach(fun(ItemId) ->
- del_item(NodeIdx, ItemId)
- end, ItemIds).
+ lists:foreach(fun (ItemId) -> del_item(NodeIdx, ItemId)
+ end,
+ ItemIds).
+
+get_item_name(_Host, _Node, Id) -> Id.
%% @doc <p>Return the name of the node if known: Default is to return
%% node id.</p>
-get_item_name(_Host, _Node, Id) ->
- Id.
+-spec(node_to_path/1 ::
+(
+ Node::binary())
+ -> [binary()]
+).
+node_to_path(Node) -> str:tokens((Node), <<"/">>).
-node_to_path(Node) ->
- string:tokens(binary_to_list(Node), "/").
+-spec(path_to_node/1 ::
+(
+ Path :: [binary()])
+ -> binary()
+).
-path_to_node([]) ->
- <<>>;
+path_to_node([]) -> <<>>;
path_to_node(Path) ->
- list_to_binary(string:join([""|Path], "/")).
-
%% @spec (Affiliation, Subscription) -> true | false
%% Affiliation = owner | member | publisher | outcast | none
%% Subscription = subscribed | none
%% @doc Determines if the combination of Affiliation and Subscribed
%% are allowed to get items from a node.
-can_fetch_item(owner, _) -> true;
-can_fetch_item(member, _) -> true;
-can_fetch_item(publisher, _) -> true;
-can_fetch_item(outcast, _) -> false;
-can_fetch_item(none, Subscriptions) -> is_subscribed(Subscriptions);
-can_fetch_item(_Affiliation, _Subscription) -> false.
+ iolist_to_binary(str:join([<<"">> | Path], <<"/">>)).
+
+can_fetch_item(owner, _) -> true;
+can_fetch_item(member, _) -> true;
+can_fetch_item(publisher, _) -> true;
+can_fetch_item(outcast, _) -> false;
+can_fetch_item(none, Subscriptions) ->
+ is_subscribed(Subscriptions).
+%can_fetch_item(_Affiliation, _Subscription) -> false.
is_subscribed(Subscriptions) ->
lists:any(fun ({subscribed, _SubId}) -> true;
- (_) -> false
- end, Subscriptions).
+ (_) -> false
+ end,
+ Subscriptions).
%% Returns the first item where Pred() is true in List
-first_in_list(_Pred, []) ->
- false;
+first_in_list(_Pred, []) -> false;
first_in_list(Pred, [H | T]) ->
case Pred(H) of
- true -> {value, H};
- _ -> first_in_list(Pred, T)
+ true -> {value, H};
+ _ -> first_in_list(Pred, T)
end.
-
diff --git a/src/mod_pubsub/node_hometree_odbc.erl b/src/mod_pubsub/node_hometree_odbc.erl
index 0deb9d1a0..a5f8668ec 100644
--- a/src/mod_pubsub/node_hometree_odbc.erl
+++ b/src/mod_pubsub/node_hometree_odbc.erl
@@ -5,11 +5,13 @@
%%% Erlang Public License along with this software. If not, it can be
%%% retrieved via the world wide web at http://www.erlang.org/.
%%%
+%%%
%%% Software distributed under the License is distributed on an "AS IS"
%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%%% the License for the specific language governing rights and limitations
%%% under the License.
%%%
+%%%
%%% The Initial Developer of the Original Code is ProcessOne.
%%% Portions created by ProcessOne are Copyright 2006-2013, ProcessOne
%%% All Rights Reserved.''
@@ -39,9 +41,11 @@
%%% improvements.</p>
-module(node_hometree_odbc).
+
-author('christophe.romain@process-one.net').
-include("pubsub.hrl").
+
-include("jlib.hrl").
-define(PUBSUB, mod_pubsub_odbc).
@@ -49,52 +53,25 @@
-behaviour(gen_pubsub_node).
%% API definition
--export([init/3, terminate/2,
- options/0, features/0,
- create_node_permission/6,
- create_node/2,
- delete_node/1,
- purge_node/2,
- subscribe_node/8,
- unsubscribe_node/4,
- publish_item/6,
- delete_item/4,
- remove_extra_items/3,
- get_entity_affiliations/2,
- get_node_affiliations/1,
- get_affiliation/2,
- set_affiliation/3,
+-export([init/3, terminate/2, options/0, features/0,
+ create_node_permission/6, create_node/2, delete_node/1,
+ purge_node/2, subscribe_node/8, unsubscribe_node/4,
+ publish_item/6, delete_item/4, remove_extra_items/3,
+ get_entity_affiliations/2, get_node_affiliations/1,
+ get_affiliation/2, set_affiliation/3,
get_entity_subscriptions/2,
get_entity_subscriptions_for_send_last/2,
- get_node_subscriptions/1,
- get_subscriptions/2,
- set_subscriptions/4,
- get_pending_nodes/2,
- get_states/1,
- get_state/2,
- set_state/1,
- get_items/7,
- get_items/6,
- get_items/3,
- get_items/2,
- get_item/7,
- get_item/2,
- set_item/1,
- get_item_name/3,
- get_last_items/3,
- path_to_node/1,
- node_to_path/1
- ]).
-
--export([
- decode_jid/1,
- decode_node/1,
- decode_affiliation/1,
- decode_subscriptions/1,
- encode_jid/1,
- encode_affiliation/1,
- encode_subscriptions/1
- ]).
+ get_node_subscriptions/1, get_subscriptions/2,
+ set_subscriptions/4, get_pending_nodes/2, get_states/1,
+ get_state/2, set_state/1, get_items/7, get_items/6,
+ get_items/3, get_items/2, get_item/7, get_item/2,
+ set_item/1, get_item_name/3, get_last_items/3,
+ path_to_node/1, node_to_path/1]).
+
+-export([decode_jid/1, decode_node/1,
+ decode_affiliation/1, decode_subscriptions/1,
+ encode_jid/1, encode_affiliation/1,
+ encode_subscriptions/1]).
%% ================
%% API definition
@@ -110,16 +87,14 @@
%% plugin. It can be used for example by the developer to create the specific
%% module database schema if it does not exists yet.</p>
init(_Host, _ServerHost, _Opts) ->
- pubsub_subscription_odbc:init(),
- ok.
+ pubsub_subscription_odbc:init(), ok.
%% @spec (Host, ServerHost) -> any()
%% Host = mod_pubsub:host()
%% ServerHost = host()
%% @doc <p>Called during pubsub modules termination. Any pubsub plugin must
%% implement this function. It can return anything.</p>
-terminate(_Host, _ServerHost) ->
- ok.
+terminate(_Host, _ServerHost) -> ok.
%% @spec () -> [Option]
%% Option = mod_pubsub:nodeOption()
@@ -138,53 +113,25 @@ terminate(_Host, _ServerHost) ->
%% {max_payload_size, 100000},
%% {send_last_published_item, never},
%% {presence_based_delivery, false}]'''
+-spec(options/0 :: () -> NodeOptions::mod_pubsub:nodeOptions()).
options() ->
- [{deliver_payloads, true},
- {notify_config, false},
- {notify_delete, false},
- {notify_retract, true},
- {purge_offline, false},
- {persist_items, true},
- {max_items, ?MAXITEMS},
- {subscribe, true},
- {access_model, open},
- {roster_groups_allowed, []},
+ [{deliver_payloads, true}, {notify_config, false},
+ {notify_delete, false}, {notify_retract, true},
+ {purge_offline, false}, {persist_items, true},
+ {max_items, ?MAXITEMS}, {subscribe, true},
+ {access_model, open}, {roster_groups_allowed, []},
{publish_model, publishers},
{notification_type, headline},
{max_payload_size, ?MAX_PAYLOAD_SIZE},
{send_last_published_item, on_sub_and_presence},
{deliver_notifications, true},
- {presence_based_delivery, false},
- {odbc, true},
+ {presence_based_delivery, false}, {odbc, true},
{rsm, true}].
%% @spec () -> []
%% @doc Returns the node features
+-spec(features/0 :: () -> Features::[Feature::binary(),...]).
features() ->
- ["create-nodes",
- "auto-create",
- "access-authorize",
- "delete-nodes",
- "delete-items",
- "get-pending",
- "instant-nodes",
- "manage-subscriptions",
- "modify-affiliations",
- "multi-subscribe",
- "outcast-affiliation",
- "persistent-items",
- "publish",
- "purge-nodes",
- "retract-items",
- "retrieve-affiliations",
- "retrieve-items",
- "retrieve-subscriptions",
- "subscribe",
- "subscription-notifications",
- "subscription-options",
- "rsm"
- ].
-
%% @spec (Host, ServerHost, Node, ParentNode, Owner, Access) -> bool()
%% Host = mod_pubsub:host()
%% ServerHost = mod_pubsub:host()
@@ -204,23 +151,44 @@ features() ->
%% module by implementing this function like this:
%% ```check_create_user_permission(Host, ServerHost, Node, ParentNode, Owner, Access) ->
%% node_default:check_create_user_permission(Host, ServerHost, Node, ParentNode, Owner, Access).'''</p>
+ [<<"create-nodes">>, <<"auto-create">>,
+ <<"access-authorize">>, <<"delete-nodes">>,
+ <<"delete-items">>, <<"get-pending">>,
+ <<"instant-nodes">>, <<"manage-subscriptions">>,
+ <<"modify-affiliations">>, <<"multi-subscribe">>,
+ <<"outcast-affiliation">>, <<"persistent-items">>,
+ <<"publish">>, <<"purge-nodes">>, <<"retract-items">>,
+ <<"retrieve-affiliations">>, <<"retrieve-items">>,
+ <<"retrieve-subscriptions">>, <<"subscribe">>,
+ <<"subscription-notifications">>,
+ <<"subscription-options">>, <<"rsm">>].
+
+-spec(create_node_permission/6 ::
+(
+ Host :: mod_pubsub:host(),
+ ServerHost :: binary(),
+ Node :: mod_pubsub:nodeId(),
+ _ParentNode :: _,
+ Owner :: jid(),
+ Access :: atom())
+ -> {result, boolean()}
+).
create_node_permission(Host, ServerHost, Node, _ParentNode, Owner, Access) ->
LOwner = jlib:jid_tolower(Owner),
{User, Server, _Resource} = LOwner,
Allowed = case LOwner of
- {"", Host, ""} ->
- true; % pubsub service always allowed
- _ ->
- case acl:match_rule(ServerHost, Access, LOwner) of
- allow ->
- case node_to_path(Node) of
- ["home", Server, User | _] -> true;
- _ -> false
- end;
+ {<<"">>, Host, <<"">>} ->
+ true; % pubsub service always allowed
_ ->
- false
- end
- end,
+ case acl:match_rule(ServerHost, Access, LOwner) of
+ allow ->
+ case node_to_path(Node) of
+ [<<"home">>, Server, User | _] -> true;
+ _ -> false
+ end;
+ _ -> false
+ end
+ end,
{result, Allowed}.
%% @spec (NodeId, Owner) ->
@@ -228,36 +196,53 @@ create_node_permission(Host, ServerHost, Node, _ParentNode, Owner, Access) ->
%% NodeId = mod_pubsub:pubsubNodeId()
%% Owner = mod_pubsub:jid()
%% @doc <p></p>
-create_node(NodeId, Owner) ->
+-spec(create_node/2 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ Owner :: jid())
+ -> {result, {default, broadcast}}
+).
+create_node(NodeIdx, Owner) ->
OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
- State = #pubsub_state{stateid = {OwnerKey, NodeId}, affiliation = owner},
- catch ejabberd_odbc:sql_query_t(
- ["insert into pubsub_state(nodeid, jid, affiliation, subscriptions) "
- "values(", state_to_raw(NodeId, State), ");"]),
+ State = #pubsub_state{stateid = {OwnerKey, NodeIdx}, affiliation = owner},
+ catch
+ ejabberd_odbc:sql_query_t([<<"insert into pubsub_state(nodeid, jid, "
+ "affiliation, subscriptions) values(">>,
+ state_to_raw(NodeIdx, State), <<");">>]),
{result, {default, broadcast}}.
%% @spec (Removed) -> ok
%% Removed = [mod_pubsub:pubsubNode()]
%% @doc <p>purge items of deleted nodes after effective deletion.</p>
+-spec(delete_node/1 ::
+(
+ Removed :: [mod_pubsub:pubsubNode(),...])
+ -> {result, {default, broadcast, _}}
+).
delete_node(Removed) ->
- Reply = lists:map(
- fun(#pubsub_node{id = NodeId} = PubsubNode) ->
- Subscriptions = case catch ejabberd_odbc:sql_query_t(
- ["select jid, subscriptions "
- "from pubsub_state "
- "where nodeid='", NodeId, "';"]) of
- {selected, ["jid", "subscriptions"], RItems} ->
- lists:map(fun({SJID, Subscriptions}) ->
- {decode_jid(SJID), decode_subscriptions(Subscriptions)}
- end, RItems);
- _ ->
- []
- end,
- %% state and item remove already done thanks to DELETE CASCADE
- %% but here we get nothing in States, making notify_retract unavailable !
- %% TODO, remove DELETE CASCADE from schema
- {PubsubNode, Subscriptions}
- end, Removed),
+ Reply = lists:map(fun (#pubsub_node{id = NodeId} =
+ PubsubNode) ->
+ Subscriptions = case catch
+ ejabberd_odbc:sql_query_t([<<"select jid, subscriptions from pubsub_state "
+ "where nodeid='">>,
+ NodeId,
+ <<"';">>])
+ of
+ {selected,
+ [<<"jid">>,
+ <<"subscriptions">>],
+ RItems} ->
+ lists:map(fun ({SJID,
+ Subscriptions}) ->
+ {decode_jid(SJID),
+ decode_subscriptions(Subscriptions)}
+ end,
+ RItems);
+ _ -> []
+ end,
+ {PubsubNode, Subscriptions}
+ end,
+ Removed),
{result, {default, broadcast, Reply}}.
%% @spec (NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options) ->
@@ -293,60 +278,81 @@ delete_node(Removed) ->
%% to completly disable persistance.</li></ul>
%% </p>
%% <p>In the default plugin module, the record is unchanged.</p>
+-spec(subscribe_node/8 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ Sender :: jid(),
+ Subscriber :: ljid(),
+ AccessModel :: mod_pubsub:accessModel(),
+ SendLast :: 'never' | 'on_sub' | 'on_sub_and_presence',
+ PresenceSubscription :: boolean(),
+ RosterGroup :: boolean(),
+ Options :: mod_pubsub:subOptions())
+ -> {result, {default, subscribed, mod_pubsub:subId()}}
+ | {result, {default, subscribed, mod_pubsub:subId(), send_last}}
+ | {result, {default, pending, mod_pubsub:subId()}}
+ %%%
+ | {error, _}
+ | {error, _, binary()}
+).
subscribe_node(NodeId, Sender, Subscriber, AccessModel,
SendLast, PresenceSubscription, RosterGroup, Options) ->
SubKey = jlib:jid_tolower(Subscriber),
GenKey = jlib:jid_remove_resource(SubKey),
- Authorized = (jlib:jid_tolower(jlib:jid_remove_resource(Sender)) == GenKey),
- {Affiliation, Subscriptions} = select_affiliation_subscriptions(NodeId, GenKey, SubKey),
- Whitelisted = lists:member(Affiliation, [member, publisher, owner]),
- PendingSubscription = lists:any(fun({pending, _}) -> true;
- (_) -> false
- end, Subscriptions),
- if
- not Authorized ->
- %% JIDs do not match
- {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "invalid-jid")};
- Affiliation == outcast ->
- %% Requesting entity is blocked
- {error, ?ERR_FORBIDDEN};
- PendingSubscription ->
- %% Requesting entity has pending subscription
- {error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "pending-subscription")};
- (AccessModel == presence) and (not PresenceSubscription) ->
- %% Entity is not authorized to create a subscription (presence subscription required)
- {error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "presence-subscription-required")};
- (AccessModel == roster) and (not RosterGroup) ->
- %% Entity is not authorized to create a subscription (not in roster group)
- {error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "not-in-roster-group")};
- (AccessModel == whitelist) and (not Whitelisted) ->
- %% Node has whitelist access model and entity lacks required affiliation
- {error, ?ERR_EXTENDED(?ERR_NOT_ALLOWED, "closed-node")};
- %%MustPay ->
- %% % Payment is required for a subscription
- %% {error, ?ERR_PAYMENT_REQUIRED};
- %%ForbiddenAnonymous ->
- %% % Requesting entity is anonymous
- %% {error, ?ERR_FORBIDDEN};
- true ->
- case pubsub_subscription_odbc:subscribe_node(Subscriber, NodeId, Options) of
- {result, SubId} ->
- NewSub = case AccessModel of
- authorize -> pending;
- _ -> subscribed
- end,
- update_subscription(NodeId, SubKey, [{NewSub, SubId} | Subscriptions]),
- case {NewSub, SendLast} of
- {subscribed, never} ->
- {result, {default, subscribed, SubId}};
- {subscribed, _} ->
- {result, {default, subscribed, SubId, send_last}};
- {_, _} ->
- {result, {default, pending, SubId}}
- end;
- _ ->
- {error, ?ERR_INTERNAL_SERVER_ERROR}
- end
+ Authorized =
+ jlib:jid_tolower(jlib:jid_remove_resource(Sender)) ==
+ GenKey,
+ {Affiliation, Subscriptions} =
+ select_affiliation_subscriptions(NodeId, GenKey,
+ SubKey),
+ Whitelisted = lists:member(Affiliation,
+ [member, publisher, owner]),
+ PendingSubscription = lists:any(fun ({pending, _}) ->
+ true;
+ (_) -> false
+ end,
+ Subscriptions),
+ if not Authorized ->
+ {error,
+ ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"invalid-jid">>)};
+ Affiliation == outcast -> {error, ?ERR_FORBIDDEN};
+ PendingSubscription ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED),
+ <<"pending-subscription">>)};
+ (AccessModel == presence) and
+ not PresenceSubscription ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED),
+ <<"presence-subscription-required">>)};
+ (AccessModel == roster) and not RosterGroup ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED),
+ <<"not-in-roster-group">>)};
+ (AccessModel == whitelist) and not Whitelisted ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)};
+ %%MustPay ->
+ %% % Payment is required for a subscription
+ %% {error, ?ERR_PAYMENT_REQUIRED};
+ %%ForbiddenAnonymous ->
+ %% % Requesting entity is anonymous
+ %% {error, ?ERR_FORBIDDEN};
+ true ->
+ {result, SubId} = pubsub_subscription_odbc:subscribe_node(Subscriber, NodeId, Options),
+ NewSub = case AccessModel of
+ authorize -> pending;
+ _ -> subscribed
+ end,
+ update_subscription(NodeId, SubKey,
+ [{NewSub, SubId} | Subscriptions]),
+ case {NewSub, SendLast} of
+ {subscribed, never} ->
+ {result, {default, subscribed, SubId}};
+ {subscribed, _} ->
+ {result, {default, subscribed, SubId, send_last}};
+ {_, _} -> {result, {default, pending, SubId}}
+ end
end.
%% @spec (NodeId, Sender, Subscriber, SubId) ->
@@ -357,68 +363,97 @@ subscribe_node(NodeId, Sender, Subscriber, AccessModel,
%% SubId = mod_pubsub:subid()
%% Reason = mod_pubsub:stanzaError()
%% @doc <p>Unsubscribe the <tt>Subscriber</tt> from the <tt>Node</tt>.</p>
+-spec(unsubscribe_node/4 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ Sender :: jid(),
+ Subscriber :: jid(),
+ SubId :: subId())
+ -> {result, default}
+ %
+ | {error, _}
+ | {error, _, binary()}
+).
unsubscribe_node(NodeId, Sender, Subscriber, SubId) ->
SubKey = jlib:jid_tolower(Subscriber),
GenKey = jlib:jid_remove_resource(SubKey),
- Authorized = (jlib:jid_tolower(jlib:jid_remove_resource(Sender)) == GenKey),
- {Affiliation, Subscriptions} = select_affiliation_subscriptions(NodeId, SubKey),
+ Authorized =
+ jlib:jid_tolower(jlib:jid_remove_resource(Sender)) ==
+ GenKey,
+ {Affiliation, Subscriptions} =
+ select_affiliation_subscriptions(NodeId, SubKey),
SubIdExists = case SubId of
- [] -> false;
- List when is_list(List) -> true;
- _ -> false
+ [] -> false;
+ List when is_binary(List) -> true;
+ _ -> false
end,
if
- %% Requesting entity is prohibited from unsubscribing entity
- not Authorized ->
- {error, ?ERR_FORBIDDEN};
- %% Entity did not specify SubId
- %%SubId == "", ?? ->
- %% {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
- %% Invalid subscription identifier
- %%InvalidSubId ->
- %% {error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
- %% Requesting entity is not a subscriber
- Subscriptions == [] ->
- {error, ?ERR_EXTENDED(?ERR_UNEXPECTED_REQUEST_CANCEL, "not-subscribed")};
- %% Subid supplied, so use that.
- SubIdExists ->
- Sub = first_in_list(fun(S) ->
- case S of
- {_Sub, SubId} -> true;
- _ -> false
- end
- end, Subscriptions),
- case Sub of
- {value, S} ->
- delete_subscription(SubKey, NodeId, S, Affiliation, Subscriptions),
- {result, default};
- false ->
- {error, ?ERR_EXTENDED(?ERR_UNEXPECTED_REQUEST_CANCEL, "not-subscribed")}
- end;
- %% Asking to remove all subscriptions to the given node
- SubId == all ->
- [delete_subscription(SubKey, NodeId, S, Affiliation, Subscriptions) || S <- Subscriptions],
- {result, default};
- %% No subid supplied, but there's only one matching
- %% subscription, so use that.
- length(Subscriptions) == 1 ->
- delete_subscription(SubKey, NodeId, hd(Subscriptions), Affiliation, Subscriptions),
- {result, default};
- %% No subid and more than one possible subscription match.
- true ->
- {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")}
+ %% Requesting entity is prohibited from unsubscribing entity
+ not Authorized -> {error, ?ERR_FORBIDDEN};
+ %% Entity did not specify SubId
+ %%SubId == "", ?? ->
+ %% {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
+ %% Invalid subscription identifier
+ %%InvalidSubId ->
+ %% {error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
+ %% Requesting entity is not a subscriber
+ Subscriptions == [] ->
+ {error,
+ ?ERR_EXTENDED((?ERR_UNEXPECTED_REQUEST_CANCEL),
+ <<"not-subscribed">>)};
+ %% Subid supplied, so use that.
+ SubIdExists ->
+ Sub = first_in_list(fun (S) ->
+ case S of
+ {_Sub, SubId} -> true;
+ _ -> false
+ end
+ end,
+ Subscriptions),
+ case Sub of
+ {value, S} ->
+ delete_subscription(SubKey, NodeId, S, Affiliation,
+ Subscriptions),
+ {result, default};
+ false ->
+ {error,
+ ?ERR_EXTENDED((?ERR_UNEXPECTED_REQUEST_CANCEL),
+ <<"not-subscribed">>)}
+ end;
+ %% Asking to remove all subscriptions to the given node
+ SubId == all ->
+ [delete_subscription(SubKey, NodeId, S, Affiliation,
+ Subscriptions)
+ || S <- Subscriptions],
+ {result, default};
+ %% No subid supplied, but there's only one matching
+ %% subscription, so use that.
+ length(Subscriptions) == 1 ->
+ delete_subscription(SubKey, NodeId, hd(Subscriptions),
+ Affiliation, Subscriptions),
+ {result, default};
+ %% No subid and more than one possible subscription match.
+ true ->
+ {error,
+ ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"subid-required">>)}
end.
-delete_subscription(SubKey, NodeId, {Subscription, SubId}, Affiliation, Subscriptions) ->
+%-spec(delete_subscriptions/5 ::
+%(
+% SubKey :: ljid(),
+% NodeIdx :: mod_pubsub:nodeIdx(),
+% _ :: {mod_pubsub:subscription(), mod_pubsub:subId()},
+% SubState :: mod_pubsub:pubsubState(),
+% Subscriptions :: [{mod_pubsub:subscription(), mod_pubsub:subId()}])
+% -> ok
+%).
+delete_subscription(SubKey, NodeIdx,
+ {Subscription, SubId}, Affiliation, Subscriptions) ->
NewSubs = Subscriptions -- [{Subscription, SubId}],
- pubsub_subscription_odbc:unsubscribe_node(SubKey, NodeId, SubId),
+ pubsub_subscription_odbc:unsubscribe_node(SubKey, NodeIdx, SubId),
case {Affiliation, NewSubs} of
- {none, []} ->
- % Just a regular subscriber, and this is final item, so
- % delete the state.
- del_state(NodeId, SubKey);
- _ ->
- update_subscription(NodeId, SubKey, NewSubs)
+ {none, []} -> del_state(NodeIdx, SubKey);
+ _ -> update_subscription(NodeIdx, SubKey, NewSubs)
end.
%% @spec (NodeId, Publisher, PublishModel, MaxItems, ItemId, Payload) ->
@@ -459,38 +494,46 @@ delete_subscription(SubKey, NodeId, {Subscription, SubId}, Affiliation, Subscrip
%% to completly disable persistance.</li></ul>
%% </p>
%% <p>In the default plugin module, the record is unchanged.</p>
-publish_item(NodeId, Publisher, PublishModel, MaxItems, ItemId, Payload) ->
+
+-spec(publish_item/6 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ Publisher :: jid(),
+ PublishModel :: mod_pubsub:publishModel(),
+ Max_Items :: non_neg_integer(),
+ ItemId :: <<>> | mod_pubsub:itemId(),
+ Payload :: mod_pubsub:payload())
+ -> {result, {default, broadcast, [mod_pubsub:itemId()]}}
+ %%%
+ | {error, _}
+).
+publish_item(NodeIdx, Publisher, PublishModel, MaxItems, ItemId, Payload) ->
SubKey = jlib:jid_tolower(Publisher),
GenKey = jlib:jid_remove_resource(SubKey),
- {Affiliation, Subscriptions} = select_affiliation_subscriptions(NodeId, GenKey, SubKey),
+ {Affiliation, Subscriptions} =
+ select_affiliation_subscriptions(NodeIdx, GenKey, SubKey),
Subscribed = case PublishModel of
- subscribers -> is_subscribed(Subscriptions);
- _ -> undefined
- end,
- if
- not ((PublishModel == open)
- or ((PublishModel == publishers)
- and ((Affiliation == owner) or (Affiliation == publisher)))
- or (Subscribed == true)) ->
- %% Entity does not have sufficient privileges to publish to node
- {error, ?ERR_FORBIDDEN};
- true ->
- %% TODO: check creation, presence, roster
- if MaxItems > 0 ->
- %% Note: this works cause set_item tries an update before
- %% the insert, and the update just ignore creation field.
- PubId = {now(), SubKey},
- set_item(#pubsub_item{itemid = {ItemId, NodeId},
+ subscribers -> is_subscribed(Subscriptions);
+ _ -> undefined
+ end,
+ if not
+ ((PublishModel == open) or
+ (PublishModel == publishers) and
+ ((Affiliation == owner) or (Affiliation == publisher))
+ or (Subscribed == true)) ->
+ {error, ?ERR_FORBIDDEN};
+ true ->
+ if MaxItems > 0 ->
+ PubId = {now(), SubKey},
+ set_item(#pubsub_item{itemid = {ItemId, NodeIdx},
creation = {now(), GenKey},
modification = PubId,
payload = Payload}),
- Items = [ItemId | itemids(NodeId, GenKey)--[ItemId]],
- {result, {_, OI}} = remove_extra_items(NodeId, MaxItems, Items),
- %% set new item list use useless
- {result, {default, broadcast, OI}};
- true ->
- {result, {default, broadcast, []}}
- end
+ Items = [ItemId | itemids(NodeIdx, GenKey) -- [ItemId]],
+ {result, {_, OI}} = remove_extra_items(NodeIdx, MaxItems, Items),
+ {result, {default, broadcast, OI}};
+ true -> {result, {default, broadcast, []}}
+ end
end.
%% @spec (NodeId, MaxItems, ItemIds) -> {NewItemIds,OldItemIds}
@@ -513,9 +556,7 @@ remove_extra_items(_NodeId, unlimited, ItemIds) ->
remove_extra_items(NodeId, MaxItems, ItemIds) ->
NewItems = lists:sublist(ItemIds, MaxItems),
OldItems = lists:nthtail(length(NewItems), ItemIds),
- %% Remove extra items:
del_items(NodeId, OldItems),
- %% Return the new items list:
{result, {NewItems, OldItems}}.
%% @spec (NodeId, Publisher, PublishModel, ItemId) ->
@@ -528,29 +569,33 @@ remove_extra_items(NodeId, MaxItems, ItemIds) ->
%% @doc <p>Triggers item deletion.</p>
%% <p>Default plugin: The user performing the deletion must be the node owner
%% or a publisher.</p>
-delete_item(NodeId, Publisher, PublishModel, ItemId) ->
+-spec(delete_item/4 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ Publisher :: jid(),
+ PublishModel :: mod_pubsub:publishModel(),
+ ItemId :: <<>> | mod_pubsub:itemId())
+ -> {result, {default, broadcast}}
+ %%%
+ | {error, _}
+).
+delete_item(NodeIdx, Publisher, PublishModel, ItemId) ->
SubKey = jlib:jid_tolower(Publisher),
GenKey = jlib:jid_remove_resource(SubKey),
- {result, Affiliation} = get_affiliation(NodeId, GenKey),
- Allowed = (Affiliation == publisher) orelse (Affiliation == owner)
- orelse (PublishModel == open)
- orelse case get_item(NodeId, ItemId) of
- {result, #pubsub_item{creation = {_, GenKey}}} -> true;
- _ -> false
- end,
- if
- not Allowed ->
- %% Requesting entity does not have sufficient privileges
- {error, ?ERR_FORBIDDEN};
- true ->
- case del_item(NodeId, ItemId) of
- {updated, 1} ->
- %% set new item list use useless
- {result, {default, broadcast}};
- _ ->
- %% Non-existent node or item
- {error, ?ERR_ITEM_NOT_FOUND}
- end
+ {result, Affiliation} = get_affiliation(NodeIdx, GenKey),
+ Allowed = Affiliation == publisher orelse
+ Affiliation == owner orelse
+ PublishModel == open orelse
+ case get_item(NodeIdx, ItemId) of
+ {result, #pubsub_item{creation = {_, GenKey}}} -> true;
+ _ -> false
+ end,
+ if not Allowed -> {error, ?ERR_FORBIDDEN};
+ true ->
+ case del_item(NodeIdx, ItemId) of
+ {updated, 1} -> {result, {default, broadcast}};
+ _ -> {error, ?ERR_ITEM_NOT_FOUND}
+ end
end.
%% @spec (NodeId, Owner) ->
@@ -558,21 +603,27 @@ delete_item(NodeId, Publisher, PublishModel, ItemId) ->
%% {result, {default, broadcast}}
%% NodeId = mod_pubsub:pubsubNodeId()
%% Owner = mod_pubsub:jid()
-purge_node(NodeId, Owner) ->
+-spec(purge_node/2 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ Owner :: jid())
+ -> {result, {default, broadcast}}
+ | {error, _}
+).
+purge_node(NodeIdx, Owner) ->
SubKey = jlib:jid_tolower(Owner),
GenKey = jlib:jid_remove_resource(SubKey),
- GenState = get_state(NodeId, GenKey),
+ GenState = get_state(NodeIdx, GenKey),
case GenState of
- #pubsub_state{affiliation = owner} ->
- {result, States} = get_states(NodeId),
- lists:foreach(
- fun(#pubsub_state{items = []}) -> ok;
- (#pubsub_state{items = Items}) -> del_items(NodeId, Items)
- end, States),
- {result, {default, broadcast}};
- _ ->
- %% Entity is not owner
- {error, ?ERR_FORBIDDEN}
+ #pubsub_state{affiliation = owner} ->
+ {result, States} = get_states(NodeIdx),
+ lists:foreach(fun (#pubsub_state{items = []}) -> ok;
+ (#pubsub_state{items = Items}) ->
+ del_items(NodeIdx, Items)
+ end,
+ States),
+ {result, {default, broadcast}};
+ _ -> {error, ?ERR_FORBIDDEN}
end.
%% @spec (Host, JID) -> [{Node,Affiliation}]
@@ -585,60 +636,98 @@ purge_node(NodeId, Owner) ->
%% the default PubSub module. Otherwise, it should return its own affiliation,
%% that will be added to the affiliation stored in the main
%% <tt>pubsub_state</tt> table.</p>
+-spec(get_entity_affiliations/2 ::
+(
+ Host :: mod_pubsub:hostPubsub(),
+ Owner :: jid())
+ -> {result, [{mod_pubsub:pubsubNode(), mod_pubsub:affiliation()}]}
+).
get_entity_affiliations(Host, Owner) ->
SubKey = jlib:jid_tolower(Owner),
GenKey = jlib:jid_remove_resource(SubKey),
- H = ?PUBSUB:escape(Host),
+ H = (?PUBSUB):escape(Host),
J = encode_jid(GenKey),
- Reply = case catch ejabberd_odbc:sql_query_t(
- ["select node, type, i.nodeid, affiliation "
- "from pubsub_state i, pubsub_node n "
- "where i.nodeid = n.nodeid "
- "and jid='", J, "' "
- "and host='", H, "';"]) of
- {selected, ["node", "type", "nodeid", "affiliation"], RItems} ->
- lists:map(fun({N, T, I, A}) ->
- Node = nodetree_tree_odbc:raw_to_node(Host, {N, "", T, I}),
- {Node, decode_affiliation(A)}
- end, RItems);
- _ ->
- []
- end,
+ Reply = case catch
+ ejabberd_odbc:sql_query_t([<<"select node, type, i.nodeid, affiliation "
+ "from pubsub_state i, pubsub_node n where "
+ "i.nodeid = n.nodeid and jid='">>,
+ J, <<"' and host='">>, H,
+ <<"';">>])
+ of
+ {selected,
+ [<<"node">>, <<"type">>, <<"nodeid">>,
+ <<"affiliation">>],
+ RItems} ->
+ lists:map(fun ({N, T, I, A}) ->
+ Node = nodetree_tree_odbc:raw_to_node(Host,
+ {N,
+ <<"">>,
+ T,
+ I}),
+ {Node, decode_affiliation(A)}
+ end,
+ RItems);
+ _ -> []
+ end,
{result, Reply}.
-get_node_affiliations(NodeId) ->
- Reply = case catch ejabberd_odbc:sql_query_t(
- ["select jid, affiliation "
- "from pubsub_state "
- "where nodeid='", NodeId, "';"]) of
- {selected, ["jid", "affiliation"], RItems} ->
- lists:map(fun({J, A}) -> {decode_jid(J), decode_affiliation(A)} end, RItems);
- _ ->
- []
- end,
+-spec(get_node_affiliations/1 ::
+(
+ NodeIdx::mod_pubsub:nodeIdx())
+ -> {result, [{ljid(), mod_pubsub:affiliation()}]}
+).
+get_node_affiliations(NodeIdx) ->
+ Reply = case catch
+ ejabberd_odbc:sql_query_t([<<"select jid, affiliation from pubsub_state "
+ "where nodeid='">>,
+ NodeIdx, <<"';">>])
+ of
+ {selected, [<<"jid">>, <<"affiliation">>], RItems} ->
+ lists:map(fun ({J, A}) ->
+ {decode_jid(J), decode_affiliation(A)}
+ end,
+ RItems);
+ _ -> []
+ end,
{result, Reply}.
-get_affiliation(NodeId, Owner) ->
+-spec(get_affiliation/2 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ Owner :: ljid())
+ -> {result, mod_pubsub:affiliation()}
+).
+
+get_affiliation(NodeIdx, Owner) ->
SubKey = jlib:jid_tolower(Owner),
GenKey = jlib:jid_remove_resource(SubKey),
J = encode_jid(GenKey),
- Reply = case catch ejabberd_odbc:sql_query_t(
- ["select affiliation from pubsub_state "
- "where nodeid='", NodeId, "' and jid='", J, "';"]) of
- {selected, ["affiliation"], [{A}]} -> decode_affiliation(A);
- _ -> none
- end,
+ Reply = case catch
+ ejabberd_odbc:sql_query_t([<<"select affiliation from pubsub_state "
+ "where nodeid='">>,
+ NodeIdx, <<"' and jid='">>, J,
+ <<"';">>])
+ of
+ {selected, [<<"affiliation">>], [{A}]} ->
+ decode_affiliation(A);
+ _ -> none
+ end,
{result, Reply}.
-set_affiliation(NodeId, Owner, Affiliation) ->
+-spec(set_affiliation/3 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ Owner :: ljid(),
+ Affiliation :: mod_pubsub:affiliation())
+ -> ok
+).
+set_affiliation(NodeIdx, Owner, Affiliation) ->
SubKey = jlib:jid_tolower(Owner),
GenKey = jlib:jid_remove_resource(SubKey),
- {_, Subscriptions} = select_affiliation_subscriptions(NodeId, GenKey),
+ {_, Subscriptions} = select_affiliation_subscriptions(NodeIdx, GenKey),
case {Affiliation, Subscriptions} of
- {none, none} ->
- del_state(NodeId, GenKey);
- _ ->
- update_affiliation(NodeId, GenKey, Affiliation)
+ {none, none} -> del_state(NodeIdx, GenKey);
+ _ -> update_affiliation(NodeIdx, GenKey, Affiliation)
end.
%% @spec (Host, Owner) -> [{Node,Subscription}]
@@ -651,223 +740,341 @@ set_affiliation(NodeId, Owner, Affiliation) ->
%% the default PubSub module. Otherwise, it should return its own affiliation,
%% that will be added to the affiliation stored in the main
%% <tt>pubsub_state</tt> table.</p>
+
+-spec(get_entity_subscriptions/2 ::
+(
+ Host :: mod_pubsub:host(),
+ Owner :: jid())
+ -> {result,
+ [{mod_pubsub:pubsubNode(),
+ mod_pubsub:subscription(),
+ mod_pubsub:subId(),
+ ljid()}]
+ }
+).
get_entity_subscriptions(Host, Owner) ->
SubKey = jlib:jid_tolower(Owner),
GenKey = jlib:jid_remove_resource(SubKey),
- H = ?PUBSUB:escape(Host),
+ H = (?PUBSUB):escape(Host),
SJ = encode_jid(SubKey),
GJ = encode_jid(GenKey),
Query = case SubKey of
- GenKey ->
- ["select node, type, i.nodeid, jid, subscriptions "
- "from pubsub_state i, pubsub_node n "
- "where i.nodeid = n.nodeid "
- "and jid like '", GJ, "%' "
- "and host='", H, "';"];
- _ ->
- ["select node, type, i.nodeid, jid, subscriptions "
- "from pubsub_state i, pubsub_node n "
- "where i.nodeid = n.nodeid "
- "and jid in ('", SJ, "', '", GJ, "') "
- "and host='", H, "';"]
- end,
+ GenKey ->
+ [<<"select node, type, i.nodeid, jid, subscriptio"
+ "ns from pubsub_state i, pubsub_node "
+ "n where i.nodeid = n.nodeid and jid "
+ "like '">>,
+ GJ, <<"%' and host='">>, H, <<"';">>];
+ _ ->
+ [<<"select node, type, i.nodeid, jid, subscriptio"
+ "ns from pubsub_state i, pubsub_node "
+ "n where i.nodeid = n.nodeid and jid "
+ "in ('">>,
+ SJ, <<"', '">>, GJ, <<"') and host='">>, H, <<"';">>]
+ end,
Reply = case catch ejabberd_odbc:sql_query_t(Query) of
- {selected, ["node", "type", "nodeid", "jid", "subscriptions"], RItems} ->
- lists:foldl(fun({N, T, I, J, S}, Acc) ->
- Node = nodetree_tree_odbc:raw_to_node(Host, {N, "", T, I}),
- Jid = decode_jid(J),
- case decode_subscriptions(S) of
- [] ->
- [{Node, none, Jid}|Acc];
- Subs ->
- lists:foldl(fun({Sub, SubId}, Acc2) -> [{Node, Sub, SubId, Jid}|Acc2];
- (Sub, Acc2) -> [{Node, Sub, Jid}|Acc2]
- end, Acc, Subs)
- end
- end, [], RItems);
- _ ->
- []
- end,
+ {selected,
+ [<<"node">>, <<"type">>, <<"nodeid">>, <<"jid">>,
+ <<"subscriptions">>],
+ RItems} ->
+ lists:foldl(fun ({N, T, I, J, S}, Acc) ->
+ Node =
+ nodetree_tree_odbc:raw_to_node(Host,
+ {N,
+ <<"">>,
+ T,
+ I}),
+ Jid = decode_jid(J),
+ case decode_subscriptions(S) of
+ [] -> [{Node, none, Jid} | Acc];
+ Subs ->
+ lists:foldl(fun ({Sub, SubId},
+ Acc2) ->
+ [{Node, Sub,
+ SubId, Jid}
+ | Acc2];
+ (Sub, Acc2) ->
+ [{Node, Sub,
+ Jid}
+ | Acc2]
+ end,
+ Acc, Subs)
+ end
+ end,
+ [], RItems);
+ _ -> []
+ end,
{result, Reply}.
%% do the same as get_entity_subscriptions but filter result only to
%% nodes having send_last_published_item=on_sub_and_presence
%% as this call avoid seeking node, it must return node and type as well
+-spec(get_entity_subscriptions_for_send_last/2 ::
+(
+ Host :: mod_pubsub:hostPubsub(),
+ Owner :: jid())
+ -> {result,
+ [{mod_pubsub:pubsubNode(),
+ mod_pubsub:subscription(),
+ mod_pubsub:subId(),
+ ljid()}]
+ }
+).
+
get_entity_subscriptions_for_send_last(Host, Owner) ->
SubKey = jlib:jid_tolower(Owner),
GenKey = jlib:jid_remove_resource(SubKey),
- H = ?PUBSUB:escape(Host),
+ H = (?PUBSUB):escape(Host),
SJ = encode_jid(SubKey),
GJ = encode_jid(GenKey),
Query = case SubKey of
- GenKey ->
- ["select node, type, i.nodeid, jid, subscriptions "
- "from pubsub_state i, pubsub_node n, pubsub_node_option o "
- "where i.nodeid = n.nodeid and n.nodeid = o.nodeid "
- "and name='send_last_published_item' and val='on_sub_and_presence' "
- "and jid like '", GJ, "%' "
- "and host='", H, "';"];
- _ ->
- ["select node, type, i.nodeid, jid, subscriptions "
- "from pubsub_state i, pubsub_node n, pubsub_node_option o "
- "where i.nodeid = n.nodeid and n.nodeid = o.nodeid "
- "and name='send_last_published_item' and val='on_sub_and_presence' "
- "and jid in ('", SJ, "', '", GJ, "') "
- "and host='", H, "';"]
- end,
+ GenKey ->
+ [<<"select node, type, i.nodeid, jid, subscriptio"
+ "ns from pubsub_state i, pubsub_node "
+ "n, pubsub_node_option o where i.nodeid "
+ "= n.nodeid and n.nodeid = o.nodeid and "
+ "name='send_last_published_item' and "
+ "val='on_sub_and_presence' and jid like "
+ "'">>,
+ GJ, <<"%' and host='">>, H, <<"';">>];
+ _ ->
+ [<<"select node, type, i.nodeid, jid, subscriptio"
+ "ns from pubsub_state i, pubsub_node "
+ "n, pubsub_node_option o where i.nodeid "
+ "= n.nodeid and n.nodeid = o.nodeid and "
+ "name='send_last_published_item' and "
+ "val='on_sub_and_presence' and jid in "
+ "('">>,
+ SJ, <<"', '">>, GJ, <<"') and host='">>, H, <<"';">>]
+ end,
Reply = case catch ejabberd_odbc:sql_query_t(Query) of
- {selected, ["node", "type", "nodeid", "jid", "subscriptions"], RItems} ->
- lists:foldl(fun({N, T, I, J, S}, Acc) ->
- Node = nodetree_tree_odbc:raw_to_node(Host, {N, "", T, I}),
- Jid = decode_jid(J),
- case decode_subscriptions(S) of
- [] ->
- [{Node, none, Jid}|Acc];
- Subs ->
- lists:foldl(fun({Sub, SubId}, Acc2) -> [{Node, Sub, SubId, Jid}|Acc2];
- (Sub, Acc2) -> [{Node, Sub, Jid}|Acc2]
- end, Acc, Subs)
- end
- end, [], RItems);
- _ ->
- []
- end,
+ {selected,
+ [<<"node">>, <<"type">>, <<"nodeid">>, <<"jid">>,
+ <<"subscriptions">>],
+ RItems} ->
+ lists:foldl(fun ({N, T, I, J, S}, Acc) ->
+ Node =
+ nodetree_tree_odbc:raw_to_node(Host,
+ {N,
+ <<"">>,
+ T,
+ I}),
+ Jid = decode_jid(J),
+ case decode_subscriptions(S) of
+ [] -> [{Node, none, Jid} | Acc];
+ Subs ->
+ lists:foldl(fun ({Sub, SubId}, Acc2) ->
+ [{Node, Sub, SubId, Jid}| Acc2]
+ end,
+ Acc, Subs)
+ end
+ end,
+ [], RItems);
+ _ -> []
+ end,
{result, Reply}.
-get_node_subscriptions(NodeId) ->
- Reply = case catch ejabberd_odbc:sql_query_t(
- ["select jid, subscriptions "
- "from pubsub_state "
- "where nodeid='", NodeId, "';"]) of
- {selected, ["jid", "subscriptions"], RItems} ->
- lists:foldl(fun({J, S}, Acc) ->
- Jid = decode_jid(J),
- case decode_subscriptions(S) of
- [] ->
- [{Jid, none}|Acc];
- Subs ->
- lists:foldl(fun({Sub, SubId}, Acc2) -> [{Jid, Sub, SubId}|Acc2];
- (Sub, Acc2) -> [{Jid, Sub}|Acc2]
- end, Acc, Subs)
- end
- end, [], RItems);
- _ ->
- []
- end,
+-spec(get_node_subscriptions/1 ::
+(
+ NodeIdx::mod_pubsub:nodeIdx())
+ -> {result, [{ljid(), mod_pubsub:subscription(), mod_pubsub:subId()}]}
+).
+get_node_subscriptions(NodeIdx) ->
+ Reply = case catch
+ ejabberd_odbc:sql_query_t([<<"select jid, subscriptions from pubsub_state "
+ "where nodeid='">>,
+ NodeIdx, <<"';">>])
+ of
+ {selected, [<<"jid">>, <<"subscriptions">>], RItems} ->
+ lists:foldl(fun ({J, S}, Acc) ->
+ Jid = decode_jid(J),
+ case decode_subscriptions(S) of
+ [] -> [{Jid, none} | Acc];
+ Subs ->
+ lists:foldl(fun ({Sub, SubId}, Acc2) ->
+ [{Jid, Sub, SubId} | Acc2]
+ end,
+ Acc, Subs)
+ end
+ end,
+ [], RItems);
+ _ -> []
+ end,
{result, Reply}.
-get_subscriptions(NodeId, Owner) ->
+-spec(get_subscriptions/2 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ Owner :: ljid())
+ -> {result, [{mod_pubsub:subscription(), mod_pubsub:subId()}]}
+).
+get_subscriptions(NodeIdx, Owner) ->
SubKey = jlib:jid_tolower(Owner),
J = encode_jid(SubKey),
- Reply = case catch ejabberd_odbc:sql_query_t(
- ["select subscriptions from pubsub_state "
- "where nodeid='", NodeId, "' and jid='", J, "';"]) of
- {selected, ["subscriptions"], [{S}]} -> decode_subscriptions(S);
- _ -> []
- end,
+ Reply = case catch
+ ejabberd_odbc:sql_query_t([<<"select subscriptions from pubsub_state "
+ "where nodeid='">>,
+ NodeIdx, <<"' and jid='">>, J,
+ <<"';">>])
+ of
+ {selected, [<<"subscriptions">>], [{S}]} ->
+ decode_subscriptions(S);
+ _ -> []
+ end,
{result, Reply}.
-set_subscriptions(NodeId, Owner, Subscription, SubId) ->
+-spec(set_subscriptions/4 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ Owner :: jid(),
+ Subscription :: mod_pubsub:subscription(),
+ SubId :: mod_pubsub:subId())
+ -> _
+ %%%
+ | {error, xmlel()}
+).
+set_subscriptions(NodeIdx, Owner, Subscription, SubId) ->
SubKey = jlib:jid_tolower(Owner),
- SubState = get_state_without_itemids(NodeId, SubKey),
+ SubState = get_state_without_itemids(NodeIdx, SubKey),
case {SubId, SubState#pubsub_state.subscriptions} of
- {_, []} ->
- case Subscription of
- none -> {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "not-subscribed")};
- _ -> new_subscription(NodeId, Owner, Subscription, SubState)
- end;
- {"", [{_, SID}]} ->
- case Subscription of
- none -> unsub_with_subid(NodeId, SID, SubState);
- _ -> replace_subscription({Subscription, SID}, SubState)
- end;
- {"", [_|_]} ->
- {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
- _ ->
- case Subscription of
- none -> unsub_with_subid(NodeId, SubId, SubState);
- _ -> replace_subscription({Subscription, SubId}, SubState)
- end
+ {_, []} ->
+ case Subscription of
+ none ->
+ {error,
+ ?ERR_EXTENDED((?ERR_BAD_REQUEST),
+ <<"not-subscribed">>)};
+ _ ->
+ new_subscription(NodeIdx, Owner, Subscription, SubState)
+ end;
+ {<<"">>, [{_, SID}]} ->
+ case Subscription of
+ none -> unsub_with_subid(NodeIdx, SID, SubState);
+ _ -> replace_subscription({Subscription, SID}, SubState)
+ end;
+ {<<"">>, [_ | _]} ->
+ {error,
+ ?ERR_EXTENDED((?ERR_BAD_REQUEST),
+ <<"subid-required">>)};
+ _ ->
+ case Subscription of
+ none -> unsub_with_subid(NodeIdx, SubId, SubState);
+ _ ->
+ replace_subscription({Subscription, SubId}, SubState)
+ end
end.
+-spec(replace_subscription/2 ::
+(
+ NewSub :: {mod_pubsub:subscription(), mod_pubsub:subId()},
+ SubState :: mod_pubsub:pubsubState())
+ -> {result, []}
+).
replace_subscription(NewSub, SubState) ->
- NewSubs = replace_subscription(NewSub,
- SubState#pubsub_state.subscriptions, []),
+ NewSubs = replace_subscription(NewSub, SubState#pubsub_state.subscriptions, []),
set_state(SubState#pubsub_state{subscriptions = NewSubs}).
-replace_subscription(_, [], Acc) ->
- Acc;
+replace_subscription(_, [], Acc) -> Acc;
replace_subscription({Sub, SubId}, [{_, SubID} | T], Acc) ->
replace_subscription({Sub, SubId}, T, [{Sub, SubID} | Acc]).
-new_subscription(NodeId, Owner, Subscription, SubState) ->
- case pubsub_subscription_odbc:subscribe_node(Owner, NodeId, []) of
- {result, SubId} ->
- Subscriptions = SubState#pubsub_state.subscriptions,
- set_state(SubState#pubsub_state{subscriptions = [{Subscription, SubId} | Subscriptions]}),
- {Subscription, SubId};
- _ ->
- {error, ?ERR_INTERNAL_SERVER_ERROR}
+-spec(new_subscription/4 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ Owner :: jid(),
+ Subscription :: mod_pubsub:subscription(),
+ SubState :: mod_pubsub:pubsubState())
+ -> {mod_pubsub:subscription(), mod_pubsub:subId()}
+ %%%
+ | {error, xmlel()}
+).
+
+new_subscription(NodeIdx, Owner, Subscription, SubState) ->
+ case pubsub_subscription_odbc:subscribe_node(Owner, NodeIdx, []) of
+ {result, SubId} ->
+ Subscriptions = SubState#pubsub_state.subscriptions,
+ set_state(SubState#pubsub_state{subscriptions =
+ [{Subscription, SubId} | Subscriptions]}),
+ {Subscription, SubId};
+ _ -> {error, ?ERR_INTERNAL_SERVER_ERROR}
end.
-unsub_with_subid(NodeId, SubId, SubState) ->
+-spec(unsub_with_subid/3 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ SubId :: mod_pubsub:subId(),
+ SubState :: mod_pubsub:pubsubState())
+ -> ok
+).
+unsub_with_subid(NodeIdx, SubId, SubState) ->
pubsub_subscription_odbc:unsubscribe_node(SubState#pubsub_state.stateid,
- NodeId, SubId),
- NewSubs = lists:filter(fun ({_, SID}) -> SubId =/= SID end,
+ NodeIdx, SubId),
+ NewSubs = lists:filter(fun ({_, SID}) -> SubId =/= SID
+ end,
SubState#pubsub_state.subscriptions),
case {NewSubs, SubState#pubsub_state.affiliation} of
- {[], none} ->
- del_state(NodeId, element(1, SubState#pubsub_state.stateid));
- _ ->
- set_state(SubState#pubsub_state{subscriptions = NewSubs})
+ {[], none} ->
+ del_state(NodeIdx,
+ element(1, SubState#pubsub_state.stateid));
+ _ ->
+ set_state(SubState#pubsub_state{subscriptions = NewSubs})
end.
-
%% @spec (Host, Owner) -> {result, [Node]} | {error, Reason}
%% Host = host()
%% Owner = jid()
%% Node = pubsubNode()
%% @doc <p>Returns a list of Owner's nodes on Host with pending
%% subscriptions.</p>
+-spec(get_pending_nodes/2 ::
+(
+ Host :: mod_pubsub:hostPubsub(),
+ Owner :: jid())
+ -> {result, [mod_pubsub:nodeId()]}
+).
get_pending_nodes(Host, Owner) ->
GenKey = jlib:jid_remove_resource(jlib:jid_tolower(Owner)),
- States = mnesia:match_object(#pubsub_state{stateid = {GenKey, '_'},
- affiliation = owner,
- _ = '_'}),
- NodeIDs = [ID || #pubsub_state{stateid = {_, ID}} <- States],
- NodeTree = case catch ets:lookup(gen_mod:get_module_proc(Host, config), nodetree) of
- [{nodetree, N}] -> N;
- _ -> nodetree_tree_odbc
+ States = mnesia:match_object(#pubsub_state{stateid =
+ {GenKey, '_'},
+ affiliation = owner, _ = '_'}),
+ NodeIDs = [ID
+ || #pubsub_state{stateid = {_, ID}} <- States],
+ NodeTree = case catch
+ ets:lookup(gen_mod:get_module_proc(Host, config),
+ nodetree)
+ of
+ [{nodetree, N}] -> N;
+ _ -> nodetree_tree_odbc
end,
- Reply = mnesia:foldl(fun(#pubsub_state{stateid = {_, NID}} = S, Acc) ->
- case lists:member(NID, NodeIDs) of
- true ->
- case get_nodes_helper(NodeTree, S) of
- {value, Node} -> [Node | Acc];
- false -> Acc
- end;
- false ->
- Acc
- end
- end, [], pubsub_state),
+ Reply = mnesia:foldl(fun (#pubsub_state{stateid =
+ {_, NID}} =
+ S,
+ Acc) ->
+ case lists:member(NID, NodeIDs) of
+ true ->
+ case get_nodes_helper(NodeTree, S) of
+ {value, Node} -> [Node | Acc];
+ false -> Acc
+ end;
+ false -> Acc
+ end
+ end,
+ [], pubsub_state),
{result, Reply}.
get_nodes_helper(NodeTree,
- #pubsub_state{stateid = {_, N}, subscriptions = Subs}) ->
+ #pubsub_state{stateid = {_, N},
+ subscriptions = Subs}) ->
HasPending = fun ({pending, _}) -> true;
- (pending) -> true;
- (_) -> false
+ (pending) -> true;
+ (_) -> false
end,
case lists:any(HasPending, Subs) of
- true ->
- case NodeTree:get_node(N) of
- #pubsub_node{nodeid = {_, Node}} ->
- {value, Node};
- _ ->
- false
- end;
- false ->
- false
+ true ->
+ case NodeTree:get_node(N) of
+ #pubsub_node{nodeid = {_, Node}} -> {value, Node};
+ _ -> false
+ end;
+ false -> false
end.
%% @spec (NodeId) -> [States] | []
@@ -882,20 +1089,29 @@ get_nodes_helper(NodeTree,
%% they can implement this function like this:
%% ```get_states(NodeId) ->
%% node_default:get_states(NodeId).'''</p>
-get_states(NodeId) ->
- case catch ejabberd_odbc:sql_query_t(
- ["select jid, affiliation, subscriptions "
- "from pubsub_state "
- "where nodeid='", NodeId, "';"]) of
- {selected, ["jid", "affiliation", "subscriptions"], RItems} ->
- {result, lists:map(fun({SJID, Affiliation, Subscriptions}) ->
- #pubsub_state{stateid = {decode_jid(SJID), NodeId},
- items = itemids(NodeId, SJID),
- affiliation = decode_affiliation(Affiliation),
- subscriptions = decode_subscriptions(Subscriptions)}
- end, RItems)};
- _ ->
- {result, []}
+-spec(get_states/1 ::
+(
+ NodeIdx::mod_pubsub:nodeIdx())
+ -> {result, [mod_pubsub:pubsubState()]}
+).
+get_states(NodeIdx) ->
+ case catch
+ ejabberd_odbc:sql_query_t([<<"select jid, affiliation, subscriptions "
+ "from pubsub_state where nodeid='">>,
+ NodeIdx, <<"';">>])
+ of
+ {selected,
+ [<<"jid">>, <<"affiliation">>, <<"subscriptions">>],
+ RItems} ->
+ {result,
+ lists:map(fun ({SJID, Affiliation, Subscriptions}) ->
+ #pubsub_state{stateid = {decode_jid(SJID), NodeIdx},
+ items = itemids(NodeIdx, SJID),
+ affiliation = decode_affiliation(Affiliation),
+ subscriptions = decode_subscriptions(Subscriptions)}
+ end,
+ RItems)};
+ _ -> {result, []}
end.
%% @spec (NodeId, JID) -> [State] | []
@@ -903,50 +1119,78 @@ get_states(NodeId) ->
%% JID = mod_pubsub:jid()
%% State = mod_pubsub:pubsubItems()
%% @doc <p>Returns a state (one state list), given its reference.</p>
-get_state(NodeId, JID) ->
- State = get_state_without_itemids(NodeId, JID),
+
+-spec(get_state/2 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ JID :: ljid())
+ -> mod_pubsub:pubsubState()
+).
+get_state(NodeIdx, JID) ->
+ State = get_state_without_itemids(NodeIdx, JID),
{SJID, _} = State#pubsub_state.stateid,
- State#pubsub_state{items = itemids(NodeId, SJID)}.
-get_state_without_itemids(NodeId, JID) ->
+ State#pubsub_state{items = itemids(NodeIdx, SJID)}.
+
+-spec(get_state_without_itemids/2 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ JID :: jid())
+ -> mod_pubsub:pubsubState()
+).
+get_state_without_itemids(NodeIdx, JID) ->
J = encode_jid(JID),
- case catch ejabberd_odbc:sql_query_t(
- ["select jid, affiliation, subscriptions "
- "from pubsub_state "
- "where jid='", J, "' "
- "and nodeid='", NodeId, "';"]) of
- {selected, ["jid", "affiliation", "subscriptions"], [{SJID, Affiliation, Subscriptions}]} ->
- #pubsub_state{stateid = {decode_jid(SJID), NodeId},
- affiliation = decode_affiliation(Affiliation),
- subscriptions = decode_subscriptions(Subscriptions)};
- _ ->
- #pubsub_state{stateid={JID, NodeId}}
+ case catch
+ ejabberd_odbc:sql_query_t([<<"select jid, affiliation, subscriptions "
+ "from pubsub_state where jid='">>,
+ J, <<"' and nodeid='">>, NodeIdx,
+ <<"';">>])
+ of
+ {selected,
+ [<<"jid">>, <<"affiliation">>, <<"subscriptions">>],
+ [{SJID, Affiliation, Subscriptions}]} ->
+ #pubsub_state{stateid = {decode_jid(SJID), NodeIdx},
+ affiliation = decode_affiliation(Affiliation),
+ subscriptions = decode_subscriptions(Subscriptions)};
+ _ -> #pubsub_state{stateid = {JID, NodeIdx}}
end.
%% @spec (State) -> ok | {error, Reason::stanzaError()}
%% State = mod_pubsub:pubsubStates()
%% @doc <p>Write a state into database.</p>
+
+-spec(set_state/1 ::
+(
+ State :: mod_pubsub:pubsubState())
+ -> {result, []}
+).
set_state(State) ->
- {_, NodeId} = State#pubsub_state.stateid,
- set_state(NodeId, State).
-set_state(NodeId, State) ->
- %% NOTE: in odbc version, as we do not handle item list,
- %% we just need to update affiliation and subscription
- %% cause {JID,NodeId} is the key. if it does not exists, then we insert it.
- %% MySQL can be optimized using INSERT ... ON DUPLICATE KEY as well
+ {_, NodeIdx} = State#pubsub_state.stateid,
+ set_state(NodeIdx, State).
+
+-spec(set_state/2 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ State :: mod_pubsub:pubsubState())
+ -> {result, []}
+).
+set_state(NodeIdx, State) ->
{JID, _} = State#pubsub_state.stateid,
J = encode_jid(JID),
S = encode_subscriptions(State#pubsub_state.subscriptions),
A = encode_affiliation(State#pubsub_state.affiliation),
- case catch ejabberd_odbc:sql_query_t(
- ["update pubsub_state "
- "set subscriptions='", S, "', affiliation='", A, "' "
- "where nodeid='", NodeId, "' and jid='", J, "';"]) of
- {updated, 1} ->
- ok;
- _ ->
- catch ejabberd_odbc:sql_query_t(
- ["insert into pubsub_state(nodeid, jid, affiliation, subscriptions) "
- "values('", NodeId, "', '", J, "', '", A, "', '", S, "');"])
+ case catch
+ ejabberd_odbc:sql_query_t([<<"update pubsub_state set subscriptions='">>,
+ S, <<"', affiliation='">>, A,
+ <<"' where nodeid='">>, NodeIdx,
+ <<"' and jid='">>, J, <<"';">>])
+ of
+ {updated, 1} -> ok;
+ _ ->
+ catch
+ ejabberd_odbc:sql_query_t([<<"insert into pubsub_state(nodeid, jid, "
+ "affiliation, subscriptions) values('">>,
+ NodeIdx, <<"', '">>, J, <<"', '">>, A,
+ <<"', '">>, S, <<"');">>])
end,
{result, []}.
@@ -956,10 +1200,9 @@ set_state(NodeId, State) ->
%% @doc <p>Delete a state from database.</p>
del_state(NodeId, JID) ->
J = encode_jid(JID),
- catch ejabberd_odbc:sql_query_t(
- ["delete from pubsub_state "
- "where jid='", J, "' "
- "and nodeid='", NodeId, "';"]),
+ catch
+ ejabberd_odbc:sql_query_t([<<"delete from pubsub_state where jid='">>,
+ J, <<"' and nodeid='">>, NodeId, <<"';">>]),
ok.
%% @spec (NodeId, From) -> {[Items],RsmOut} | []
@@ -976,139 +1219,179 @@ del_state(NodeId, JID) ->
%% ```get_items(NodeId, From) ->
%% node_default:get_items(NodeId, From).'''</p>
get_items(NodeId, _From) ->
- case catch ejabberd_odbc:sql_query_t(
- ["select itemid, publisher, creation, modification, payload "
- "from pubsub_item "
- "where nodeid='", NodeId, "' "
- "order by modification desc;"]) of
- {selected, ["itemid", "publisher", "creation", "modification", "payload"], RItems} ->
- {result, lists:map(fun(RItem) -> raw_to_item(NodeId, RItem) end, RItems)};
- _ ->
- {result, []}
+ case catch
+ ejabberd_odbc:sql_query_t([<<"select itemid, publisher, creation, "
+ "modification, payload from pubsub_item "
+ "where nodeid='">>,
+ NodeId,
+ <<"' order by modification desc;">>])
+ of
+ {selected,
+ [<<"itemid">>, <<"publisher">>, <<"creation">>,
+ <<"modification">>, <<"payload">>],
+ RItems} ->
+ {result,
+ lists:map(fun (RItem) -> raw_to_item(NodeId, RItem) end,
+ RItems)};
+ _ -> {result, []}
end.
-get_items(NodeId, From, none) ->
- MaxItems = case catch ejabberd_odbc:sql_query_t(
- ["select val from pubsub_node_option "
- "where nodeid='", NodeId, "' "
- "and name='max_items';"]) of
- {selected, ["val"], [{Value}]} ->
- Tokens = element(2, erl_scan:string(Value++".")),
- element(2, erl_parse:parse_term(Tokens));
- _ ->
- ?MAXITEMS
- end,
- get_items(NodeId, From, #rsm_in{max=MaxItems});
-get_items(NodeId, _From, #rsm_in{max=M, direction=Direction, id=I, index=IncIndex})->
- Max = ?PUBSUB:escape(i2l(M)),
-
- {Way, Order} = case Direction of
- aft -> {"<", "desc"};
- before when I == [] -> {"is not", "asc"};
- before -> {">", "asc"};
- _ when IncIndex =/= undefined -> {"<", "desc"}; % using index
- _ -> {"is not", "desc"}% Can be better
- end,
- [AttrName, Id] = case I of
- undefined when IncIndex =/= undefined ->
- case catch ejabberd_odbc:sql_query_t(
- ["select modification from pubsub_item pi "
- "where exists ( "
- "select count(*) as count1 "
- "from pubsub_item "
- "where nodeid='", NodeId, "' "
- "and modification > pi.modification "
- "having count1 = ",?PUBSUB:escape(i2l(IncIndex))," );"]) of
- {selected, [_], [{O}]} -> ["modification", "'"++O++"'"];
- _ -> ["modification", "null"]
- end;
- undefined -> ["modification", "null"];
- [] -> ["modification", "null"];
- I -> [A, B] = string:tokens(?PUBSUB:escape(i2l(I)), "@"),
- [A, "'"++B++"'"]
- end,
- Count= case catch ejabberd_odbc:sql_query_t(
- ["select count(*) "
- "from pubsub_item "
- "where nodeid='", NodeId, "';"]) of
- {selected, [_], [{C}]} -> C;
- _ -> "0"
- end,
- case catch ejabberd_odbc:sql_query_t(
- ["select itemid, publisher, creation, modification, payload "
- "from pubsub_item "
- "where nodeid='", NodeId, "' "
- "and ", AttrName," ", Way, " ", Id, " "
- "order by ", AttrName," ", Order," limit ", i2l(Max)," ;"]) of
- {selected, ["itemid", "publisher", "creation", "modification", "payload"], RItems} ->
- case length(RItems) of
- 0 -> {result, {[], #rsm_out{count=Count}}};
- _ ->
- {_, _, _, F, _} = hd(RItems),
- Index = case catch ejabberd_odbc:sql_query_t(
- ["select count(*) "
- "from pubsub_item "
- "where nodeid='", NodeId, "' "
- "and ", AttrName," > '", F, "';"]) of
- %{selected, [_], [{C}, {In}]} -> [string:strip(C, both, $"), string:strip(In, both, $")];
- {selected, [_], [{In}]} -> In;
- _ -> "0"
- end,
- %{F, _} = string:to_integer(FStr),
- {_, _, _, L, _} = lists:last(RItems),
- RsmOut = #rsm_out{count=Count, index=Index, first="modification@"++F, last="modification@"++i2l(L)},
- {result, {lists:map(fun(RItem) -> raw_to_item(NodeId, RItem) end, RItems), RsmOut}}
- end;
- _ ->
- {result, {[], none}}
+get_items(NodeId, From, none) ->
+ MaxItems = case catch
+ ejabberd_odbc:sql_query_t([<<"select val from pubsub_node_option where "
+ "nodeid='">>,
+ NodeId,
+ <<"' and name='max_items';">>])
+ of
+ {selected, [<<"val">>], [{Value}]} ->
+ Tokens = element(2,
+ erl_scan:string(<<Value/binary, ".">>)),
+ element(2, erl_parse:parse_term(Tokens));
+ _ -> ?MAXITEMS
+ end,
+ get_items(NodeId, From, #rsm_in{max = MaxItems});
+get_items(NodeId, _From,
+ #rsm_in{max = M, direction = Direction, id = I,
+ index = IncIndex}) ->
+ Max = (?PUBSUB):escape(i2l(M)),
+ {Way, Order} = case Direction of
+ aft -> {<<"<">>, <<"desc">>};
+ before when I == [] -> {<<"is not">>, <<"asc">>};
+ before -> {<<">">>, <<"asc">>};
+ _ when IncIndex =/= undefined ->
+ {<<"<">>, <<"desc">>}; % using index
+ _ -> {<<"is not">>, <<"desc">>}% Can be better
+ end,
+ [AttrName, Id] = case I of
+ undefined when IncIndex =/= undefined ->
+ case catch
+ ejabberd_odbc:sql_query_t([<<"select modification from pubsub_item "
+ "pi where exists ( select count(*) as "
+ "count1 from pubsub_item where nodeid='">>,
+ NodeId,
+ <<"' and modification > pi.modification "
+ "having count1 = ">>,
+ (?PUBSUB):escape(i2l(IncIndex)),
+ <<" );">>])
+ of
+ {selected, [_], [{O}]} ->
+ [<<"modification">>, <<"'", O/binary, "'">>];
+ _ -> [<<"modification">>, <<"null">>]
+ end;
+ undefined -> [<<"modification">>, <<"null">>];
+ [] -> [<<"modification">>, <<"null">>];
+ I ->
+ [A, B] = str:tokens((?PUBSUB):escape(i2l(I)),
+ <<"@">>),
+ [A, <<"'", B/binary, "'">>]
+ end,
+ Count = case catch
+ ejabberd_odbc:sql_query_t([<<"select count(*) from pubsub_item where "
+ "nodeid='">>,
+ NodeId, <<"';">>])
+ of
+ {selected, [_], [{C}]} -> C;
+ _ -> <<"0">>
+ end,
+ case catch
+ ejabberd_odbc:sql_query_t([<<"select itemid, publisher, creation, "
+ "modification, payload from pubsub_item "
+ "where nodeid='">>,
+ NodeId, <<"' and ">>, AttrName, <<" ">>,
+ Way, <<" ">>, Id, <<" order by ">>,
+ AttrName, <<" ">>, Order, <<" limit ">>,
+ i2l(Max), <<" ;">>])
+ of
+ {selected,
+ [<<"itemid">>, <<"publisher">>, <<"creation">>,
+ <<"modification">>, <<"payload">>],
+ RItems} ->
+ case str:len(RItems) of
+ 0 -> {result, {[], #rsm_out{count = Count}}};
+ _ ->
+ {_, _, _, F, _} = hd(RItems),
+ Index = case catch
+ ejabberd_odbc:sql_query_t([<<"select count(*) from pubsub_item where "
+ "nodeid='">>,
+ NodeId, <<"' and ">>,
+ AttrName, <<" > '">>,
+ F, <<"';">>])
+ of
+ %{selected, [_], [{C}, {In}]} -> [string:strip(C, both, $"), string:strip(In, both, $")];
+ {selected, [_], [{In}]} -> In;
+ _ -> <<"0">>
+ end,
+ {_, _, _, L, _} = lists:last(RItems),
+ RsmOut = #rsm_out{count = Count, index = Index,
+ first = <<"modification@", F/binary>>,
+ last = <<"modification@", (i2l(L))/binary>>},
+ {result,
+ {lists:map(fun (RItem) -> raw_to_item(NodeId, RItem)
+ end,
+ RItems),
+ RsmOut}}
+ end;
+ _ -> {result, {[], none}}
end.
-get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
- get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, none).
-get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId, RSM) ->
+get_items(NodeId, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId) ->
+ get_items(NodeId, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId, none).
+
+get_items(NodeId, JID, AccessModel,
+ PresenceSubscription, RosterGroup, _SubId, RSM) ->
SubKey = jlib:jid_tolower(JID),
GenKey = jlib:jid_remove_resource(SubKey),
- {Affiliation, Subscriptions} = select_affiliation_subscriptions(NodeId, GenKey, SubKey),
- Whitelisted = can_fetch_item(Affiliation, Subscriptions),
- if
- %%SubId == "", ?? ->
- %% Entity has multiple subscriptions to the node but does not specify a subscription ID
- %{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
- %%InvalidSubId ->
- %% Entity is subscribed but specifies an invalid subscription ID
- %{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
- Affiliation == outcast ->
- %% Requesting entity is blocked
- {error, ?ERR_FORBIDDEN};
- (AccessModel == presence) and (not PresenceSubscription) ->
- %% Entity is not authorized to create a subscription (presence subscription required)
- {error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "presence-subscription-required")};
- (AccessModel == roster) and (not RosterGroup) ->
- %% Entity is not authorized to create a subscription (not in roster group)
- {error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "not-in-roster-group")};
- (AccessModel == whitelist) and (not Whitelisted) ->
- %% Node has whitelist access model and entity lacks required affiliation
- {error, ?ERR_EXTENDED(?ERR_NOT_ALLOWED, "closed-node")};
- (AccessModel == authorize) and (not Whitelisted) ->
- %% Node has authorize access model
- {error, ?ERR_FORBIDDEN};
- %%MustPay ->
- %% % Payment is required for a subscription
- %% {error, ?ERR_PAYMENT_REQUIRED};
- true ->
- get_items(NodeId, JID, RSM)
+ {Affiliation, Subscriptions} =
+ select_affiliation_subscriptions(NodeId, GenKey,
+ SubKey),
+ Whitelisted = can_fetch_item(Affiliation,
+ Subscriptions),
+ if %%SubId == "", ?? ->
+ %% Entity has multiple subscriptions to the node but does not specify a subscription ID
+ %{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
+ %%InvalidSubId ->
+ %% Entity is subscribed but specifies an invalid subscription ID
+ %{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
+ Affiliation == outcast -> {error, ?ERR_FORBIDDEN};
+ (AccessModel == presence) and
+ not PresenceSubscription ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED),
+ <<"presence-subscription-required">>)};
+ (AccessModel == roster) and not RosterGroup ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED),
+ <<"not-in-roster-group">>)};
+ (AccessModel == whitelist) and not Whitelisted ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)};
+ (AccessModel == authorize) and not Whitelisted ->
+ {error, ?ERR_FORBIDDEN};
+ %%MustPay ->
+ %% % Payment is required for a subscription
+ %% {error, ?ERR_PAYMENT_REQUIRED};
+ true -> get_items(NodeId, JID, RSM)
end.
get_last_items(NodeId, _From, Count) ->
- case catch ejabberd_odbc:sql_query_t(
- ["select itemid, publisher, creation, modification, payload "
- "from pubsub_item "
- "where nodeid='", NodeId, "' "
- "order by modification desc limit ", i2l(Count), ";"]) of
- {selected, ["itemid", "publisher", "creation", "modification", "payload"], RItems} ->
- {result, lists:map(fun(RItem) -> raw_to_item(NodeId, RItem) end, RItems)};
- _ ->
- {result, []}
+ case catch
+ ejabberd_odbc:sql_query_t([<<"select itemid, publisher, creation, "
+ "modification, payload from pubsub_item "
+ "where nodeid='">>,
+ NodeId,
+ <<"' order by modification desc limit ">>,
+ i2l(Count), <<";">>])
+ of
+ {selected,
+ [<<"itemid">>, <<"publisher">>, <<"creation">>,
+ <<"modification">>, <<"payload">>],
+ RItems} ->
+ {result,
+ lists:map(fun (RItem) -> raw_to_item(NodeId, RItem) end,
+ RItems)};
+ _ -> {result, []}
end.
%% @spec (NodeId, ItemId) -> [Item] | []
@@ -1117,49 +1400,56 @@ get_last_items(NodeId, _From, Count) ->
%% Item = mod_pubsub:pubsubItems()
%% @doc <p>Returns an item (one item list), given its reference.</p>
get_item(NodeId, ItemId) ->
- I = ?PUBSUB:escape(ItemId),
- case catch ejabberd_odbc:sql_query_t(
- ["select itemid, publisher, creation, modification, payload "
- "from pubsub_item "
- "where nodeid='", NodeId, "' "
- "and itemid='", I,"';"]) of
- {selected, ["itemid", "publisher", "creation", "modification", "payload"], [RItem]} ->
- {result, raw_to_item(NodeId, RItem)};
- _ ->
- {error, ?ERR_ITEM_NOT_FOUND}
+ I = (?PUBSUB):escape(ItemId),
+ case catch
+ ejabberd_odbc:sql_query_t([<<"select itemid, publisher, creation, "
+ "modification, payload from pubsub_item "
+ "where nodeid='">>,
+ NodeId, <<"' and itemid='">>, I,
+ <<"';">>])
+ of
+ {selected,
+ [<<"itemid">>, <<"publisher">>, <<"creation">>,
+ <<"modification">>, <<"payload">>],
+ [RItem]} ->
+ {result, raw_to_item(NodeId, RItem)};
+ _ -> {error, ?ERR_ITEM_NOT_FOUND}
end.
-get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId) ->
+
+get_item(NodeId, ItemId, JID, AccessModel,
+ PresenceSubscription, RosterGroup, _SubId) ->
SubKey = jlib:jid_tolower(JID),
GenKey = jlib:jid_remove_resource(SubKey),
- {Affiliation, Subscriptions} = select_affiliation_subscriptions(NodeId, GenKey, SubKey),
- Whitelisted = can_fetch_item(Affiliation, Subscriptions),
- if
- %%SubId == "", ?? ->
- %% Entity has multiple subscriptions to the node but does not specify a subscription ID
- %{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
- %%InvalidSubId ->
- %% Entity is subscribed but specifies an invalid subscription ID
- %{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
- Affiliation == outcast ->
- %% Requesting entity is blocked
- {error, ?ERR_FORBIDDEN};
- (AccessModel == presence) and (not PresenceSubscription) ->
- %% Entity is not authorized to create a subscription (presence subscription required)
- {error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "presence-subscription-required")};
- (AccessModel == roster) and (not RosterGroup) ->
- %% Entity is not authorized to create a subscription (not in roster group)
- {error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "not-in-roster-group")};
- (AccessModel == whitelist) and (not Whitelisted) ->
- %% Node has whitelist access model and entity lacks required affiliation
- {error, ?ERR_EXTENDED(?ERR_NOT_ALLOWED, "closed-node")};
- (AccessModel == authorize) and (not Whitelisted) ->
- %% Node has authorize access model
- {error, ?ERR_FORBIDDEN};
- %%MustPay ->
- %% % Payment is required for a subscription
- %% {error, ?ERR_PAYMENT_REQUIRED};
- true ->
- get_item(NodeId, ItemId)
+ {Affiliation, Subscriptions} =
+ select_affiliation_subscriptions(NodeId, GenKey,
+ SubKey),
+ Whitelisted = can_fetch_item(Affiliation,
+ Subscriptions),
+ if %%SubId == "", ?? ->
+ %% Entity has multiple subscriptions to the node but does not specify a subscription ID
+ %{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
+ %%InvalidSubId ->
+ %% Entity is subscribed but specifies an invalid subscription ID
+ %{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
+ Affiliation == outcast -> {error, ?ERR_FORBIDDEN};
+ (AccessModel == presence) and
+ not PresenceSubscription ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED),
+ <<"presence-subscription-required">>)};
+ (AccessModel == roster) and not RosterGroup ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED),
+ <<"not-in-roster-group">>)};
+ (AccessModel == whitelist) and not Whitelisted ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)};
+ (AccessModel == authorize) and not Whitelisted ->
+ {error, ?ERR_FORBIDDEN};
+ %%MustPay ->
+ %% % Payment is required for a subscription
+ %% {error, ?ERR_PAYMENT_REQUIRED};
+ true -> get_item(NodeId, ItemId)
end.
%% @spec (Item) -> ok | {error, Reason::stanzaError()}
@@ -1167,26 +1457,36 @@ get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, _S
%% @doc <p>Write an item into database.</p>
set_item(Item) ->
{ItemId, NodeId} = Item#pubsub_item.itemid,
- I = ?PUBSUB:escape(ItemId),
+ I = (?PUBSUB):escape(ItemId),
{C, _} = Item#pubsub_item.creation,
{M, JID} = Item#pubsub_item.modification,
P = encode_jid(JID),
Payload = Item#pubsub_item.payload,
- XML = ?PUBSUB:escape(lists:flatten(lists:map(fun(X) -> xml:element_to_string(X) end, Payload))),
- S = fun({T1, T2, T3}) ->
- lists:flatten([i2l(T1, 6), ":", i2l(T2, 6), ":", i2l(T3, 6)])
+ XML = (?PUBSUB):escape(lists:flatten(lists:map(fun
+ (X) ->
+ xml:element_to_binary(X)
+ end,
+ Payload))),
+ S = fun ({T1, T2, T3}) ->
+ lists:flatten([i2l(T1, 6), <<":">>, i2l(T2, 6), <<":">>,
+ i2l(T3, 6)])
end,
- case catch ejabberd_odbc:sql_query_t(
- ["update pubsub_item "
- "set publisher='", P, "', modification='", S(M), "', payload='", XML, "' "
- "where nodeid='", NodeId, "' and itemid='", I, "';"]) of
- {updated, 1} ->
- ok;
- _ ->
- catch ejabberd_odbc:sql_query_t(
- ["insert into pubsub_item "
- "(nodeid, itemid, publisher, creation, modification, payload) "
- "values('", NodeId, "', '", I, "', '", P, "', '", S(C), "', '", S(M), "', '", XML, "');"])
+ case catch
+ ejabberd_odbc:sql_query_t([<<"update pubsub_item set publisher='">>,
+ P, <<"', modification='">>, S(M),
+ <<"', payload='">>, XML,
+ <<"' where nodeid='">>, NodeId,
+ <<"' and itemid='">>, I, <<"';">>])
+ of
+ {updated, 1} -> ok;
+ _ ->
+ catch
+ ejabberd_odbc:sql_query_t([<<"insert into pubsub_item (nodeid, itemid, "
+ "publisher, creation, modification, payload) "
+ "values('">>,
+ NodeId, <<"', '">>, I, <<"', '">>, P,
+ <<"', '">>, S(C), <<"', '">>, S(M),
+ <<"', '">>, XML, <<"');">>])
end,
{result, []}.
@@ -1195,161 +1495,181 @@ set_item(Item) ->
%% ItemId = string()
%% @doc <p>Delete an item from database.</p>
del_item(NodeId, ItemId) ->
- I = ?PUBSUB:escape(ItemId),
- catch ejabberd_odbc:sql_query_t(
- ["delete from pubsub_item "
- "where itemid='", I, "' "
- "and nodeid='", NodeId, "';"]).
-del_items(_, []) ->
- ok;
-del_items(NodeId, [ItemId]) ->
- del_item(NodeId, ItemId);
-del_items(NodeId, ItemIds) ->
- I = string:join([["'", ?PUBSUB:escape(X), "'"] || X <- ItemIds], ","),
- catch ejabberd_odbc:sql_query_t(
- ["delete from pubsub_item "
- "where itemid in (", I, ") "
- "and nodeid='", NodeId, "';"]).
+ I = (?PUBSUB):escape(ItemId),
+ catch
+ ejabberd_odbc:sql_query_t([<<"delete from pubsub_item where itemid='">>,
+ I, <<"' and nodeid='">>, NodeId, <<"';">>]).
+del_items(_, []) -> ok;
+del_items(NodeId, [ItemId]) -> del_item(NodeId, ItemId);
+del_items(NodeId, ItemIds) ->
%% @doc <p>Return the name of the node if known: Default is to return
%% node id.</p>
-get_item_name(_Host, _Node, Id) ->
- Id.
+ I = str:join([[<<"'">>, (?PUBSUB):escape(X), <<"'">>]
+ || X <- ItemIds],
+ <<",">>),
+ catch
+ ejabberd_odbc:sql_query_t([<<"delete from pubsub_item where itemid "
+ "in (">>,
+ I, <<") and nodeid='">>, NodeId, <<"';">>]).
-node_to_path(Node) ->
- string:tokens(binary_to_list(Node), "/").
+get_item_name(_Host, _Node, Id) -> Id.
-path_to_node([]) ->
- <<>>;
-path_to_node(Path) ->
- list_to_binary(string:join([""|Path], "/")).
+node_to_path(Node) -> str:tokens((Node), <<"/">>).
+path_to_node([]) -> <<>>;
+path_to_node(Path) ->
%% @spec (Affiliation, Subscription) -> true | false
%% Affiliation = owner | member | publisher | outcast | none
%% Subscription = subscribed | none
%% @doc Determines if the combination of Affiliation and Subscribed
%% are allowed to get items from a node.
-can_fetch_item(owner, _) -> true;
-can_fetch_item(member, _) -> true;
-can_fetch_item(publisher, _) -> true;
-can_fetch_item(outcast, _) -> false;
-can_fetch_item(none, Subscriptions) -> is_subscribed(Subscriptions);
+ iolist_to_binary(str:join([<<"">> | Path], <<"/">>)).
+
+can_fetch_item(owner, _) -> true;
+can_fetch_item(member, _) -> true;
+can_fetch_item(publisher, _) -> true;
+can_fetch_item(outcast, _) -> false;
+can_fetch_item(none, Subscriptions) ->
+ is_subscribed(Subscriptions);
can_fetch_item(_Affiliation, _Subscription) -> false.
is_subscribed(Subscriptions) ->
lists:any(fun ({subscribed, _SubId}) -> true;
- (_) -> false
- end, Subscriptions).
+ (_) -> false
+ end,
+ Subscriptions).
%% Returns the first item where Pred() is true in List
-first_in_list(_Pred, []) ->
- false;
+first_in_list(_Pred, []) -> false;
first_in_list(Pred, [H | T]) ->
case Pred(H) of
- true -> {value, H};
- _ -> first_in_list(Pred, T)
+ true -> {value, H};
+ _ -> first_in_list(Pred, T)
end.
itemids(NodeId, {U, S, R}) ->
itemids(NodeId, encode_jid({U, S, R}));
itemids(NodeId, SJID) ->
- case catch ejabberd_odbc:sql_query_t(
- ["select itemid "
- "from pubsub_item "
- "where nodeid='", NodeId, "' "
- "and publisher like '", SJID, "%' "
- "order by modification desc;"]) of
- {selected, ["itemid"], RItems} ->
- lists:map(fun({ItemId}) -> ItemId end, RItems);
- _ ->
- []
+ case catch
+ ejabberd_odbc:sql_query_t([<<"select itemid from pubsub_item where "
+ "nodeid='">>,
+ NodeId, <<"' and publisher like '">>,
+ SJID,
+ <<"%' order by modification desc;">>])
+ of
+ {selected, [<<"itemid">>], RItems} ->
+ lists:map(fun ({ItemId}) -> ItemId end, RItems);
+ _ -> []
end.
select_affiliation_subscriptions(NodeId, JID) ->
J = encode_jid(JID),
- case catch ejabberd_odbc:sql_query_t(
- ["select affiliation,subscriptions from pubsub_state "
- "where nodeid='", NodeId, "' and jid='", J, "';"]) of
- {selected, ["affiliation", "subscriptions"], [{A, S}]} ->
- {decode_affiliation(A), decode_subscriptions(S)};
- _ ->
- {none, []}
+ case catch
+ ejabberd_odbc:sql_query_t([<<"select affiliation,subscriptions from "
+ "pubsub_state where nodeid='">>,
+ NodeId, <<"' and jid='">>, J, <<"';">>])
+ of
+ {selected, [<<"affiliation">>, <<"subscriptions">>],
+ [{A, S}]} ->
+ {decode_affiliation(A), decode_subscriptions(S)};
+ _ -> {none, []}
end.
+
select_affiliation_subscriptions(NodeId, JID, JID) ->
select_affiliation_subscriptions(NodeId, JID);
-select_affiliation_subscriptions(NodeId, GenKey, SubKey) ->
+select_affiliation_subscriptions(NodeId, GenKey,
+ SubKey) ->
{result, Affiliation} = get_affiliation(NodeId, GenKey),
- {result, Subscriptions} = get_subscriptions(NodeId, SubKey),
+ {result, Subscriptions} = get_subscriptions(NodeId,
+ SubKey),
{Affiliation, Subscriptions}.
update_affiliation(NodeId, JID, Affiliation) ->
J = encode_jid(JID),
A = encode_affiliation(Affiliation),
- case catch ejabberd_odbc:sql_query_t(
- ["update pubsub_state "
- "set affiliation='", A, "' "
- "where nodeid='", NodeId, "' and jid='", J, "';"]) of
- {updated, 1} ->
- ok;
- _ ->
- catch ejabberd_odbc:sql_query_t(
- ["insert into pubsub_state(nodeid, jid, affiliation, subscriptions) "
- "values('", NodeId, "', '", J, "', '", A, "', '');"])
+ case catch
+ ejabberd_odbc:sql_query_t([<<"update pubsub_state set affiliation='">>,
+ A, <<"' where nodeid='">>, NodeId,
+ <<"' and jid='">>, J, <<"';">>])
+ of
+ {updated, 1} -> ok;
+ _ ->
+ catch
+ ejabberd_odbc:sql_query_t([<<"insert into pubsub_state(nodeid, jid, "
+ "affiliation, subscriptions) values('">>,
+ NodeId, <<"', '">>, J, <<"', '">>, A,
+ <<"', '');">>])
end.
update_subscription(NodeId, JID, Subscription) ->
J = encode_jid(JID),
S = encode_subscriptions(Subscription),
- case catch ejabberd_odbc:sql_query_t(
- ["update pubsub_state "
- "set subscriptions='", S, "' "
- "where nodeid='", NodeId, "' and jid='", J, "';"]) of
- {updated, 1} ->
- ok;
- _ ->
- catch ejabberd_odbc:sql_query_t(
- ["insert into pubsub_state(nodeid, jid, affiliation, subscriptions) "
- "values('", NodeId, "', '", J, "', 'n', '", S, "');"])
+ case catch
+ ejabberd_odbc:sql_query_t([<<"update pubsub_state set subscriptions='">>,
+ S, <<"' where nodeid='">>, NodeId,
+ <<"' and jid='">>, J, <<"';">>])
+ of
+ {updated, 1} -> ok;
+ _ ->
+ catch
+ ejabberd_odbc:sql_query_t([<<"insert into pubsub_state(nodeid, jid, "
+ "affiliation, subscriptions) values('">>,
+ NodeId, <<"', '">>, J, <<"', 'n', '">>,
+ S, <<"');">>])
end.
-decode_jid(SJID) -> jlib:jid_tolower(jlib:string_to_jid(SJID)).
+decode_jid(SJID) ->
+ jlib:jid_tolower(jlib:string_to_jid(SJID)).
-decode_node(N) -> ?PUBSUB:string_to_node(N).
+decode_node(N) -> (?PUBSUB):string_to_node(N).
-decode_affiliation("o") -> owner;
-decode_affiliation("p") -> publisher;
-decode_affiliation("m") -> member;
-decode_affiliation("c") -> outcast;
+decode_affiliation(<<"o">>) -> owner;
+decode_affiliation(<<"p">>) -> publisher;
+decode_affiliation(<<"m">>) -> member;
+decode_affiliation(<<"c">>) -> outcast;
decode_affiliation(_) -> none.
-decode_subscription("s") -> subscribed;
-decode_subscription("p") -> pending;
-decode_subscription("u") -> unconfigured;
+decode_subscription(<<"s">>) -> subscribed;
+decode_subscription(<<"p">>) -> pending;
+decode_subscription(<<"u">>) -> unconfigured;
decode_subscription(_) -> none.
+
decode_subscriptions(Subscriptions) ->
- lists:foldl(fun(Subscription, Acc) ->
- case string:tokens(Subscription, ":") of
- [S, SubId] -> [{decode_subscription(S), SubId}|Acc];
- _ -> Acc
- end
- end, [], string:tokens(Subscriptions, ",")).
-
-encode_jid(JID) -> ?PUBSUB:escape(jlib:jid_to_string(JID)).
-
-encode_affiliation(owner) -> "o";
-encode_affiliation(publisher) -> "p";
-encode_affiliation(member) -> "m";
-encode_affiliation(outcast) -> "c";
-encode_affiliation(_) -> "n".
-
-encode_subscription(subscribed) -> "s";
-encode_subscription(pending) -> "p";
-encode_subscription(unconfigured) -> "u";
-encode_subscription(_) -> "n".
+ lists:foldl(fun (Subscription, Acc) ->
+ case str:tokens(Subscription, <<":">>) of
+ [S, SubId] -> [{decode_subscription(S), SubId} | Acc];
+ _ -> Acc
+ end
+ end,
+ [], str:tokens(Subscriptions, <<",">>)).
+
+%-spec(encode_jid/1 ::
+%(
+% JID :: jid() | jid())
+% -> binary()
+%).
+encode_jid(JID) ->
+ (?PUBSUB):escape(jlib:jid_to_string(JID)).
+
+encode_affiliation(owner) -> <<"o">>;
+encode_affiliation(publisher) -> <<"p">>;
+encode_affiliation(member) -> <<"m">>;
+encode_affiliation(outcast) -> <<"c">>;
+encode_affiliation(_) -> <<"n">>.
+
+encode_subscription(subscribed) -> <<"s">>;
+encode_subscription(pending) -> <<"p">>;
+encode_subscription(unconfigured) -> <<"u">>;
+encode_subscription(_) -> <<"n">>.
+
encode_subscriptions(Subscriptions) ->
- string:join(lists:map(fun({S, SubId}) ->
- encode_subscription(S)++":"++SubId
- end, Subscriptions), ",").
+ str:join(lists:map(fun ({S, SubId}) ->
+ <<(encode_subscription(S))/binary, ":",
+ SubId/binary>>
+ end,
+ Subscriptions),
+ <<",">>).
%%% record getter/setter
@@ -1357,32 +1677,38 @@ state_to_raw(NodeId, State) ->
{JID, _} = State#pubsub_state.stateid,
J = encode_jid(JID),
A = encode_affiliation(State#pubsub_state.affiliation),
- S = encode_subscriptions(State#pubsub_state.subscriptions),
- ["'", NodeId, "', '", J, "', '", A, "', '", S, "'"].
+ S =
+ encode_subscriptions(State#pubsub_state.subscriptions),
+ [<<"'">>, NodeId, <<"', '">>, J, <<"', '">>, A,
+ <<"', '">>, S, <<"'">>].
-raw_to_item(NodeId, {ItemId, SJID, Creation, Modification, XML}) ->
+raw_to_item(NodeId,
+ {ItemId, SJID, Creation, Modification, XML}) ->
JID = decode_jid(SJID),
- ToTime = fun(Str) ->
- [T1,T2,T3] = string:tokens(Str, ":"),
+ ToTime = fun (Str) ->
+ [T1, T2, T3] = str:tokens(Str, <<":">>),
{l2i(T1), l2i(T2), l2i(T3)}
end,
Payload = case xml_stream:parse_element(XML) of
- {error, _Reason} -> [];
- El -> [El]
+ {error, _Reason} -> [];
+ El -> [El]
end,
#pubsub_item{itemid = {ItemId, NodeId},
- creation={ToTime(Creation), JID},
- modification={ToTime(Modification), JID},
+ creation = {ToTime(Creation), JID},
+ modification = {ToTime(Modification), JID},
payload = Payload}.
-l2i(L) when is_list(L) -> list_to_integer(L);
+l2i(L) when is_binary(L) -> jlib:binary_to_integer(L);
l2i(I) when is_integer(I) -> I.
-i2l(I) when is_integer(I) -> integer_to_list(I);
-i2l(L) when is_list(L) -> L.
+
+i2l(I) when is_integer(I) ->
+ iolist_to_binary(integer_to_list(I));
+i2l(L) when is_binary(L) -> L.
+
i2l(I, N) when is_integer(I) -> i2l(i2l(I), N);
-i2l(L, N) when is_list(L) ->
- case length(L) of
- N -> L;
- C when C > N -> L;
- _ -> i2l([$0|L], N)
+i2l(L, N) when is_binary(L) ->
+ case str:len(L) of
+ N -> L;
+ C when C > N -> L;
+ _ -> i2l(<<$0, L/binary>>, N)
end.
diff --git a/src/mod_pubsub/node_mb.erl b/src/mod_pubsub/node_mb.erl
index c83b445e2..b93a57eb5 100644
--- a/src/mod_pubsub/node_mb.erl
+++ b/src/mod_pubsub/node_mb.erl
@@ -5,11 +5,13 @@
%%% Erlang Public License along with this software. If not, it can be
%%% retrieved via the world wide web at http://www.erlang.org/.
%%%
+%%%
%%% Software distributed under the License is distributed on an "AS IS"
%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%%% the License for the specific language governing rights and limitations
%%% under the License.
%%%
+%%%
%%% The Initial Developer of the Original Code is ProcessOne.
%%% Portions created by ProcessOne are Copyright 2006-2013, ProcessOne
%%% All Rights Reserved.''
@@ -22,7 +24,6 @@
%%% @end
%%% ====================================================================
-
%%% @doc The module <strong>{@module}</strong> is the pep microblog PubSub plugin.
%%% <p> To be used, mod_pubsub must be configured :
%%% {mod_pubsub, [ % requires mod_caps
@@ -34,66 +35,43 @@
%%% <p>PubSub plugin nodes are using the {@link gen_pubsub_node} behaviour.</p>
-module(node_mb).
+
-author('eric@ohmforce.com').
-include("ejabberd.hrl").
+
-include("pubsub.hrl").
+
-include("jlib.hrl").
-behaviour(gen_pubsub_node).
%% API definition
--export([init/3, terminate/2,
- options/0, features/0,
- create_node_permission/6,
- create_node/2,
- delete_node/1,
- purge_node/2,
- subscribe_node/8,
- unsubscribe_node/4,
- publish_item/6,
- delete_item/4,
- remove_extra_items/3,
- get_entity_affiliations/2,
- get_node_affiliations/1,
- get_affiliation/2,
- set_affiliation/3,
- get_entity_subscriptions/2,
- get_node_subscriptions/1,
- get_subscriptions/2,
- set_subscriptions/4,
- get_pending_nodes/2,
- get_states/1,
- get_state/2,
- set_state/1,
- get_items/6,
- get_items/2,
- get_item/7,
- get_item/2,
- set_item/1,
- get_item_name/3,
- node_to_path/1,
- path_to_node/1
- ]).
+-export([init/3, terminate/2, options/0, features/0,
+ create_node_permission/6, create_node/2, delete_node/1,
+ purge_node/2, subscribe_node/8, unsubscribe_node/4,
+ publish_item/6, delete_item/4, remove_extra_items/3,
+ get_entity_affiliations/2, get_node_affiliations/1,
+ get_affiliation/2, set_affiliation/3,
+ get_entity_subscriptions/2, get_node_subscriptions/1,
+ get_subscriptions/2, set_subscriptions/4,
+ get_pending_nodes/2, get_states/1, get_state/2,
+ set_state/1, get_items/6, get_items/2, get_item/7,
+ get_item/2, set_item/1, get_item_name/3, node_to_path/1,
+ path_to_node/1]).
init(Host, ServerHost, Opts) ->
node_pep:init(Host, ServerHost, Opts).
terminate(Host, ServerHost) ->
- node_pep:terminate(Host, ServerHost),
- ok.
+ node_pep:terminate(Host, ServerHost), ok.
options() ->
- [{deliver_payloads, true},
- {notify_config, false},
- {notify_delete, false},
- {notify_retract, false},
- {purge_offline, false},
- {persist_items, true},
- {max_items, ?MAXITEMS},
- {subscribe, true},
- {access_model, presence},
- {roster_groups_allowed, []},
+ [{deliver_payloads, true}, {notify_config, false},
+ {notify_delete, false}, {notify_retract, false},
+ {purge_offline, false}, {persist_items, true},
+ {max_items, ?MAXITEMS}, {subscribe, true},
+ {access_model, presence}, {roster_groups_allowed, []},
{publish_model, publishers},
{notification_type, headline},
{max_payload_size, ?MAX_PAYLOAD_SIZE},
@@ -102,50 +80,51 @@ options() ->
{presence_based_delivery, true}].
features() ->
- ["create-nodes", %*
- "auto-create", %*
- "auto-subscribe", %*
- "delete-nodes", %*
- "delete-items", %*
- "filtered-notifications", %*
- "modify-affiliations",
- "outcast-affiliation",
- "persistent-items",
- "publish", %*
- "purge-nodes",
- "retract-items",
- "retrieve-affiliations",
- "retrieve-items", %*
- "retrieve-subscriptions",
- "subscribe" %*
- ].
-
-create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) ->
- node_pep:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access).
+ [<<"create-nodes">>, %*
+ <<"auto-create">>, %*
+ <<"auto-subscribe">>, %*
+ <<"delete-nodes">>, %*
+ <<"delete-items">>, %*
+ <<"filtered-notifications">>, %*
+ <<"modify-affiliations">>, <<"outcast-affiliation">>,
+ <<"persistent-items">>,
+ <<"publish">>, %*
+ <<"purge-nodes">>, <<"retract-items">>,
+ <<"retrieve-affiliations">>,
+ <<"retrieve-items">>, %*
+ <<"retrieve-subscriptions">>, <<"subscribe">>].
+
+create_node_permission(Host, ServerHost, Node,
+ ParentNode, Owner, Access) ->
+ node_pep:create_node_permission(Host, ServerHost, Node,
+ ParentNode, Owner, Access).
create_node(NodeId, Owner) ->
node_pep:create_node(NodeId, Owner).
-delete_node(Removed) ->
- node_pep:delete_node(Removed).
+delete_node(Removed) -> node_pep:delete_node(Removed).
subscribe_node(NodeId, Sender, Subscriber, AccessModel,
SendLast, PresenceSubscription, RosterGroup, Options) ->
- node_pep:subscribe_node(
- NodeId, Sender, Subscriber, AccessModel, SendLast,
- PresenceSubscription, RosterGroup, Options).
+ node_pep:subscribe_node(NodeId, Sender, Subscriber,
+ AccessModel, SendLast, PresenceSubscription,
+ RosterGroup, Options).
unsubscribe_node(NodeId, Sender, Subscriber, SubID) ->
- node_pep:unsubscribe_node(NodeId, Sender, Subscriber, SubID).
+ node_pep:unsubscribe_node(NodeId, Sender, Subscriber,
+ SubID).
-publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload) ->
- node_pep:publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload).
+publish_item(NodeId, Publisher, Model, MaxItems, ItemId,
+ Payload) ->
+ node_pep:publish_item(NodeId, Publisher, Model,
+ MaxItems, ItemId, Payload).
remove_extra_items(NodeId, MaxItems, ItemIds) ->
node_pep:remove_extra_items(NodeId, MaxItems, ItemIds).
delete_item(NodeId, Publisher, PublishModel, ItemId) ->
- node_pep:delete_item(NodeId, Publisher, PublishModel, ItemId).
+ node_pep:delete_item(NodeId, Publisher, PublishModel,
+ ItemId).
purge_node(NodeId, Owner) ->
node_pep:purge_node(NodeId, Owner).
@@ -172,41 +151,40 @@ get_subscriptions(NodeId, Owner) ->
node_pep:get_subscriptions(NodeId, Owner).
set_subscriptions(NodeId, Owner, Subscription, SubId) ->
- node_pep:set_subscriptions(NodeId, Owner, Subscription, SubId).
+ node_pep:set_subscriptions(NodeId, Owner, Subscription,
+ SubId).
get_pending_nodes(Host, Owner) ->
node_hometree:get_pending_nodes(Host, Owner).
-get_states(NodeId) ->
- node_pep:get_states(NodeId).
+get_states(NodeId) -> node_pep:get_states(NodeId).
get_state(NodeId, JID) ->
node_pep:get_state(NodeId, JID).
-set_state(State) ->
- node_pep:set_state(State).
+set_state(State) -> node_pep:set_state(State).
get_items(NodeId, From) ->
node_pep:get_items(NodeId, From).
-get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
- node_pep:get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId).
+get_items(NodeId, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId) ->
+ node_pep:get_items(NodeId, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId).
get_item(NodeId, ItemId) ->
node_pep:get_item(NodeId, ItemId).
-get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
- node_pep:get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId).
+get_item(NodeId, ItemId, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId) ->
+ node_pep:get_item(NodeId, ItemId, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId).
-set_item(Item) ->
- node_pep:set_item(Item).
+set_item(Item) -> node_pep:set_item(Item).
get_item_name(Host, Node, Id) ->
node_pep:get_item_name(Host, Node, Id).
-node_to_path(Node) ->
- node_pep:node_to_path(Node).
-
-path_to_node(Path) ->
- node_pep:path_to_node(Path).
+node_to_path(Node) -> node_pep:node_to_path(Node).
+path_to_node(Path) -> node_pep:path_to_node(Path).
diff --git a/src/mod_pubsub/node_pep.erl b/src/mod_pubsub/node_pep.erl
index f4c3f76b3..38a9bcec7 100644
--- a/src/mod_pubsub/node_pep.erl
+++ b/src/mod_pubsub/node_pep.erl
@@ -5,11 +5,13 @@
%%% Erlang Public License along with this software. If not, it can be
%%% retrieved via the world wide web at http://www.erlang.org/.
%%%
+%%%
%%% Software distributed under the License is distributed on an "AS IS"
%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%%% the License for the specific language governing rights and limitations
%%% under the License.
%%%
+%%%
%%% The Initial Developer of the Original Code is ProcessOne.
%%% Portions created by ProcessOne are Copyright 2006-2013, ProcessOne
%%% All Rights Reserved.''
@@ -27,47 +29,30 @@
%%% <p>PubSub plugin nodes are using the {@link gen_pubsub_node} behaviour.</p>
-module(node_pep).
+
-author('christophe.romain@process-one.net').
-include("ejabberd.hrl").
+
-include("pubsub.hrl").
+
-include("jlib.hrl").
-behaviour(gen_pubsub_node).
%% API definition
--export([init/3, terminate/2,
- options/0, features/0,
- create_node_permission/6,
- create_node/2,
- delete_node/1,
- purge_node/2,
- subscribe_node/8,
- unsubscribe_node/4,
- publish_item/6,
- delete_item/4,
- remove_extra_items/3,
- get_entity_affiliations/2,
- get_node_affiliations/1,
- get_affiliation/2,
- set_affiliation/3,
- get_entity_subscriptions/2,
- get_node_subscriptions/1,
- get_subscriptions/2,
- set_subscriptions/4,
- get_pending_nodes/2,
- get_states/1,
- get_state/2,
- set_state/1,
- get_items/6,
- get_items/2,
- get_item/7,
- get_item/2,
- set_item/1,
- get_item_name/3,
- node_to_path/1,
- path_to_node/1
- ]).
+-export([init/3, terminate/2, options/0, features/0,
+ create_node_permission/6, create_node/2, delete_node/1,
+ purge_node/2, subscribe_node/8, unsubscribe_node/4,
+ publish_item/6, delete_item/4, remove_extra_items/3,
+ get_entity_affiliations/2, get_node_affiliations/1,
+ get_affiliation/2, set_affiliation/3,
+ get_entity_subscriptions/2, get_node_subscriptions/1,
+ get_subscriptions/2, set_subscriptions/4,
+ get_pending_nodes/2, get_states/1, get_state/2,
+ set_state/1, get_items/6, get_items/2, get_item/7,
+ get_item/2, set_item/1, get_item_name/3, node_to_path/1,
+ path_to_node/1]).
init(Host, ServerHost, Opts) ->
node_hometree:init(Host, ServerHost, Opts),
@@ -75,20 +60,15 @@ init(Host, ServerHost, Opts) ->
ok.
terminate(Host, ServerHost) ->
- node_hometree:terminate(Host, ServerHost),
- ok.
+ node_hometree:terminate(Host, ServerHost), ok.
+-spec(options/0 :: () -> NodeOptions::mod_pubsub:nodeOptions()).
options() ->
- [{deliver_payloads, true},
- {notify_config, false},
- {notify_delete, false},
- {notify_retract, false},
- {purge_offline, false},
- {persist_items, false},
- {max_items, ?MAXITEMS},
- {subscribe, true},
- {access_model, presence},
- {roster_groups_allowed, []},
+ [{deliver_payloads, true}, {notify_config, false},
+ {notify_delete, false}, {notify_retract, false},
+ {purge_offline, false}, {persist_items, false},
+ {max_items, ?MAXITEMS}, {subscribe, true},
+ {access_model, presence}, {roster_groups_allowed, []},
{publish_model, publishers},
{notification_type, headline},
{max_payload_size, ?MAX_PAYLOAD_SIZE},
@@ -96,184 +76,411 @@ options() ->
{deliver_notifications, true},
{presence_based_delivery, true}].
+-spec(features/0 :: () -> Features::[binary(),...]).
features() ->
- ["create-nodes", %*
- "auto-create", %*
- "auto-subscribe", %*
- "delete-nodes", %*
- "delete-items", %*
- "filtered-notifications", %*
- "modify-affiliations",
- "outcast-affiliation",
- "persistent-items",
- "publish", %*
- "purge-nodes",
- "retract-items",
- "retrieve-affiliations",
- "retrieve-items", %*
- "retrieve-subscriptions",
- "subscribe" %*
- ].
+ [<<"create-nodes">>, %*
+ <<"auto-create">>, %*
+ <<"auto-subscribe">>, %*
+ <<"delete-nodes">>, %*
+ <<"delete-items">>, %*
+ <<"filtered-notifications">>, %*
+ <<"modify-affiliations">>, <<"outcast-affiliation">>,
+ <<"persistent-items">>,
+ <<"publish">>, %*
+ <<"purge-nodes">>, <<"retract-items">>,
+ <<"retrieve-affiliations">>,
+ <<"retrieve-items">>, %*
+ <<"retrieve-subscriptions">>, <<"subscribe">>].
+
+
+-spec(create_node_permission/6 ::
+(
+ Host :: mod_pubsub:hostPEP(),
+ ServerHost :: binary(),
+ NodeId :: mod_pubsub:nodeId(),
+ _ParentNodeId :: mod_pubsub:nodeId(),
+ Owner :: jid(),
+ Access :: atom())
+ -> {result, boolean()}
+).
create_node_permission(Host, ServerHost, _Node, _ParentNode, Owner, Access) ->
LOwner = jlib:jid_tolower(Owner),
{User, Server, _Resource} = LOwner,
Allowed = case LOwner of
- {"", Host, ""} ->
- true; % pubsub service always allowed
- _ ->
- case acl:match_rule(ServerHost, Access, LOwner) of
- allow ->
- case Host of
- {User, Server, _} -> true;
- _ -> false
- end;
- E ->
- ?DEBUG("Create not allowed : ~p~n", [E]),
- false
- end
- end,
+ {<<"">>, Host, <<"">>} ->
+ true; % pubsub service always allowed
+ _ ->
+ case acl:match_rule(ServerHost, Access, LOwner) of
+ allow ->
+ case Host of
+ {User, Server, _} -> true;
+ _ -> false
+ end;
+ E -> ?DEBUG("Create not allowed : ~p~n", [E]), false
+ end
+ end,
{result, Allowed}.
-create_node(NodeId, Owner) ->
- node_hometree:create_node(NodeId, Owner).
+-spec(create_node/2 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ Owner :: jid())
+ -> {result, {default, broadcast}}
+).
+create_node(NodeIdx, Owner) ->
+ node_hometree:create_node(NodeIdx, Owner).
+
+-spec(delete_node/1 ::
+(
+ Nodes :: [mod_pubsub:pubsubNode(),...])
+ -> {result,
+ {[],
+ [{mod_pubsub:pubsubNode(),
+ [{ljid(), [{mod_pubsub:subscription(), mod_pubsub:subId()}]},...]},...]
+ }
+ }
+).
delete_node(Removed) ->
- case node_hometree:delete_node(Removed) of
- {result, {_, _, Removed}} -> {result, {[], Removed}};
- Error -> Error
+ {result, {_, _, Removed}} = node_hometree:delete_node(Removed),
+ {result, {[], Removed}}.
+% case node_hometree:delete_node(Removed) of
+% {result, {_, _, Removed}} -> {result, {[], Removed}};
+% Error -> Error
+% end.
+
+-spec(subscribe_node/8 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ Sender :: jid(),
+ Subscriber :: ljid(),
+ AccessModel :: mod_pubsub:accessModel(),
+ SendLast :: 'never' | 'on_sub' | 'on_sub_and_presence',
+ PresenceSubscription :: boolean(),
+ RosterGroup :: boolean(),
+ Options :: mod_pubsub:subOptions())
+ -> {result, {default, subscribed, mod_pubsub:subId()}}
+ | {result, {default, subscribed, mod_pubsub:subId(), send_last}}
+ | {result, {default, pending, mod_pubsub:subId()}}
+ %%%
+ | {error, xmlel()}
+).
+subscribe_node(NodeIdx, Sender, Subscriber, AccessModel, SendLast,
+ PresenceSubscription, RosterGroup, Options) ->
+ node_hometree:subscribe_node(NodeIdx, Sender, Subscriber, AccessModel,
+ SendLast, PresenceSubscription, RosterGroup, Options).
+
+-spec(unsubscribe_node/4 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ Sender :: jid(),
+ Subscriber :: ljid(),
+ SubId :: subId())
+ -> {result, default}
+ %
+ | {error, xmlel()}
+).
+unsubscribe_node(NodeIdx, Sender, Subscriber, SubID) ->
+ case node_hometree:unsubscribe_node(NodeIdx, Sender, Subscriber, SubID) of
+ {error, Error} -> {error, Error};
+ {result, _} -> {result, []}
end.
-subscribe_node(NodeId, Sender, Subscriber, AccessModel,
- SendLast, PresenceSubscription, RosterGroup, Options) ->
- node_hometree:subscribe_node(
- NodeId, Sender, Subscriber, AccessModel, SendLast,
- PresenceSubscription, RosterGroup, Options).
-
-unsubscribe_node(NodeId, Sender, Subscriber, SubID) ->
- case node_hometree:unsubscribe_node(NodeId, Sender, Subscriber, SubID) of
- {error, Error} -> {error, Error};
- {result, _} -> {result, []}
- end.
-
-publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload) ->
- node_hometree:publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload).
-
+-spec(publish_item/6 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ Publisher :: jid(),
+ PublishModel :: mod_pubsub:publishModel(),
+ Max_Items :: non_neg_integer(),
+ ItemId :: <<>> | mod_pubsub:itemId(),
+ Payload :: mod_pubsub:payload())
+ -> {result, {default, broadcast, [mod_pubsub:itemId()]}}
+ %%%
+ | {error, xmlel()}
+).
+publish_item(NodeIdx, Publisher, Model, MaxItems, ItemId, Payload) ->
+ node_hometree:publish_item(NodeIdx, Publisher, Model, MaxItems, ItemId, Payload).
+
+-spec(remove_extra_items/3 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ Max_Items :: unlimited | non_neg_integer(),
+ ItemIds :: [mod_pubsub:itemId()])
+ -> {result,
+ {NewItems::[mod_pubsub:itemId()],
+ OldItems::[mod_pubsub:itemId()]}
+ }
+).
remove_extra_items(NodeId, MaxItems, ItemIds) ->
node_hometree:remove_extra_items(NodeId, MaxItems, ItemIds).
-delete_item(NodeId, Publisher, PublishModel, ItemId) ->
- node_hometree:delete_item(NodeId, Publisher, PublishModel, ItemId).
-
-purge_node(NodeId, Owner) ->
- node_hometree:purge_node(NodeId, Owner).
+-spec(delete_item/4 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ Publisher :: jid(),
+ PublishModel :: mod_pubsub:publishModel(),
+ ItemId :: <<>> | mod_pubsub:itemId())
+ -> {result, {default, broadcast}}
+ %%%
+ | {error, xmlel()}
+).
+delete_item(NodeIdx, Publisher, PublishModel, ItemId) ->
+ node_hometree:delete_item(NodeIdx, Publisher, PublishModel, ItemId).
+
+-spec(purge_node/2 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ Owner :: jid())
+ -> {result, {default, broadcast}}
+ | {error, xmlel()}
+).
+purge_node(NodeIdx, Owner) ->
+ node_hometree:purge_node(NodeIdx, Owner).
+
+-spec(get_entity_affiliations/2 ::
+(
+ Host :: mod_pubsub:hostPEP(),
+ Owner :: jid())
+ -> {result, [{mod_pubsub:pubsubNode(), mod_pubsub:affiliation()}]}
+).
get_entity_affiliations(_Host, Owner) ->
{_, D, _} = SubKey = jlib:jid_tolower(Owner),
SubKey = jlib:jid_tolower(Owner),
GenKey = jlib:jid_remove_resource(SubKey),
- States = mnesia:match_object(#pubsub_state{stateid = {GenKey, '_'}, _ = '_'}),
- NodeTree = case catch ets:lookup(gen_mod:get_module_proc(D, config), nodetree) of
- [{nodetree, N}] -> N;
- _ -> nodetree_tree
- end,
- Reply = lists:foldl(fun(#pubsub_state{stateid = {_, N}, affiliation = A}, Acc) ->
- case NodeTree:get_node(N) of
- #pubsub_node{nodeid = {{_, D, _}, _}} = Node -> [{Node, A}|Acc];
- _ -> Acc
- end
- end, [], States),
+ States = mnesia:match_object(#pubsub_state{stateid =
+ {GenKey, '_'},
+ _ = '_'}),
+ NodeTree = case catch
+ ets:lookup(gen_mod:get_module_proc(D, config), nodetree)
+ of
+ [{nodetree, N}] -> N;
+ _ -> nodetree_tree
+ end,
+ Reply = lists:foldl(fun (#pubsub_state{stateid = {_, N},
+ affiliation = A},
+ Acc) ->
+ case NodeTree:get_node(N) of
+ #pubsub_node{nodeid = {{_, D, _}, _}} =
+ Node ->
+ [{Node, A} | Acc];
+ _ -> Acc
+ end
+ end,
+ [], States),
{result, Reply}.
-get_node_affiliations(NodeId) ->
- node_hometree:get_node_affiliations(NodeId).
-
-get_affiliation(NodeId, Owner) ->
- node_hometree:get_affiliation(NodeId, Owner).
-
-set_affiliation(NodeId, Owner, Affiliation) ->
- node_hometree:set_affiliation(NodeId, Owner, Affiliation).
+-spec(get_node_affiliations/1 ::
+(
+ NodeIdx::mod_pubsub:nodeIdx())
+ -> {result, [{ljid(), mod_pubsub:affiliation()}]}
+).
+get_node_affiliations(NodeIdx) ->
+ node_hometree:get_node_affiliations(NodeIdx).
+
+-spec(get_affiliation/2 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ Owner :: jid())
+ -> {result, mod_pubsub:affiliation()}
+).
+get_affiliation(NodeIdx, Owner) ->
+ node_hometree:get_affiliation(NodeIdx, Owner).
+
+-spec(set_affiliation/3 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ Owner :: ljid(),
+ Affiliation :: mod_pubsub:affiliation())
+ -> ok
+).
+set_affiliation(NodeIdx, Owner, Affiliation) ->
+ node_hometree:set_affiliation(NodeIdx, Owner, Affiliation).
+
+-spec(get_entity_subscriptions/2 ::
+(
+ Host :: mod_pubsub:hostPEP(),
+ Owner :: jid())
+ -> {result,
+ [{mod_pubsub:pubsubNode(),
+ mod_pubsub:subscription(),
+ mod_pubsub:subId(),
+ ljid()}]
+ }
+).
get_entity_subscriptions(_Host, Owner) ->
{U, D, _} = SubKey = jlib:jid_tolower(Owner),
GenKey = jlib:jid_remove_resource(SubKey),
States = case SubKey of
- GenKey -> mnesia:match_object(
- #pubsub_state{stateid = {{U, D, '_'}, '_'}, _ = '_'});
- _ -> mnesia:match_object(
- #pubsub_state{stateid = {GenKey, '_'}, _ = '_'})
- ++ mnesia:match_object(
- #pubsub_state{stateid = {SubKey, '_'}, _ = '_'})
- end,
- NodeTree = case catch ets:lookup(gen_mod:get_module_proc(D, config), nodetree) of
- [{nodetree, N}] -> N;
- _ -> nodetree_tree
- end,
- Reply = lists:foldl(fun(#pubsub_state{stateid = {J, N}, subscriptions = Ss}, Acc) ->
- case NodeTree:get_node(N) of
- #pubsub_node{nodeid = {{_, D, _}, _}} = Node ->
- lists:foldl(fun({subscribed, SubID}, Acc2) ->
- [{Node, subscribed, SubID, J} | Acc2];
- ({pending, _SubID}, Acc2) ->
- [{Node, pending, J} | Acc2];
- (S, Acc2) ->
- [{Node, S, J} | Acc2]
- end, Acc, Ss);
- _ -> Acc
- end
- end, [], States),
+ GenKey ->
+ mnesia:match_object(#pubsub_state{stateid =
+ {{U, D, '_'}, '_'},
+ _ = '_'});
+ _ ->
+ mnesia:match_object(#pubsub_state{stateid =
+ {GenKey, '_'},
+ _ = '_'})
+ ++
+ mnesia:match_object(#pubsub_state{stateid =
+ {SubKey, '_'},
+ _ = '_'})
+ end,
+ NodeTree = case catch
+ ets:lookup(gen_mod:get_module_proc(D, config), nodetree)
+ of
+ [{nodetree, N}] -> N;
+ _ -> nodetree_tree
+ end,
+ Reply = lists:foldl(fun (#pubsub_state{stateid = {J, N},
+ subscriptions = Ss},
+ Acc) ->
+ case NodeTree:get_node(N) of
+ #pubsub_node{nodeid = {{_, D, _}, _}} = Node ->
+ lists:foldl(fun
+ ({subscribed, SubID}, Acc2) ->
+ [{Node, subscribed, SubID, J} | Acc2];
+ ({pending, _SubID}, Acc2) ->
+ [{Node, pending, J} | Acc2];
+ (S, Acc2) ->
+ [{Node, S, J} | Acc2]
+ end, Acc, Ss);
+ _ -> Acc
+ end
+ end,
+ [], States),
{result, Reply}.
-get_node_subscriptions(NodeId) ->
- %% note: get_node_subscriptions is used for broadcasting
- %% there should not have any subscriptions
- %% but that call returns also all subscription to none
- %% and this is required for broadcast to occurs
- %% DO NOT REMOVE
- node_hometree:get_node_subscriptions(NodeId).
-
-get_subscriptions(NodeId, Owner) ->
- node_hometree:get_subscriptions(NodeId, Owner).
-
-set_subscriptions(NodeId, Owner, Subscription, SubId) ->
- node_hometree:set_subscriptions(NodeId, Owner, Subscription, SubId).
-
+-spec(get_node_subscriptions/1 ::
+(
+ NodeIdx::mod_pubsub:nodeIdx())
+ -> {result,
+ [{ljid(), mod_pubsub:subscription(), mod_pubsub:subId()}] |
+ [{ljid(), none},...]
+ }
+).
+get_node_subscriptions(NodeIdx) ->
+ node_hometree:get_node_subscriptions(NodeIdx).
+
+-spec(get_subscriptions/2 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ Owner :: ljid())
+ -> {result, [{mod_pubsub:subscription(), mod_pubsub:subId()}]}
+).
+get_subscriptions(NodeIdx, Owner) ->
+ node_hometree:get_subscriptions(NodeIdx, Owner).
+
+-spec(set_subscriptions/4 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ Owner :: jid(),
+ Subscription :: mod_pubsub:subscription(),
+ SubId :: mod_pubsub:subId())
+ -> ok
+ %%%
+ | {error, xmlel()}
+).
+set_subscriptions(NodeIdx, Owner, Subscription, SubId) ->
+ node_hometree:set_subscriptions(NodeIdx, Owner, Subscription, SubId).
+
+-spec(get_pending_nodes/2 ::
+(
+ Host :: mod_pubsub:hostPubsub(),
+ Owner :: jid())
+ -> {result, [mod_pubsub:nodeId()]}
+).
get_pending_nodes(Host, Owner) ->
node_hometree:get_pending_nodes(Host, Owner).
-get_states(NodeId) ->
- node_hometree:get_states(NodeId).
-
-get_state(NodeId, JID) ->
- node_hometree:get_state(NodeId, JID).
-
-set_state(State) ->
- node_hometree:set_state(State).
-
-get_items(NodeId, From) ->
- node_hometree:get_items(NodeId, From).
-
-get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
- node_hometree:get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId).
-
-get_item(NodeId, ItemId) ->
- node_hometree:get_item(NodeId, ItemId).
-
-get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
- node_hometree:get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId).
-
-set_item(Item) ->
- node_hometree:set_item(Item).
+-spec(get_states/1 ::
+(
+ NodeIdx::mod_pubsub:nodeIdx())
+ -> {result, [mod_pubsub:pubsubState()]}
+).
+get_states(NodeIdx) -> node_hometree:get_states(NodeIdx).
+
+-spec(get_state/2 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ JID :: ljid())
+ -> mod_pubsub:pubsubState()
+).
+get_state(NodeIdx, JID) ->
+ node_hometree:get_state(NodeIdx, JID).
+
+-spec(set_state/1 ::
+(
+ State::mod_pubsub:pubsubState())
+ -> ok
+).
+set_state(State) -> node_hometree:set_state(State).
+
+-spec(get_items/2 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ _From :: jid())
+ -> {result, [mod_pubsub:pubsubItem()]}
+).
+get_items(NodeIdx, From) ->
+ node_hometree:get_items(NodeIdx, From).
+
+-spec(get_items/6 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ JID :: jid(),
+ AccessModel :: mod_pubsub:accessModel(),
+ Presence_Subscription :: boolean(),
+ RosterGroup :: boolean(),
+ _SubId :: mod_pubsub:subId())
+ -> {result, [mod_pubsub:pubsubItem()]}
+ %%%
+ | {error, xmlel()}
+).
+get_items(NodeIdx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
+ node_hometree:get_items(NodeIdx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId).
+
+-spec(get_item/2 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ ItemId :: mod_pubsub:itemId())
+ -> {result, mod_pubsub:pubsubItem()}
+ | {error, xmlel()}
+).
+get_item(NodeIdx, ItemId) ->
+ node_hometree:get_item(NodeIdx, ItemId).
+
+-spec(get_item/7 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ ItemId :: mod_pubsub:itemId(),
+ JID :: jid(),
+ AccessModel :: mod_pubsub:accessModel(),
+ PresenceSubscription :: boolean(),
+ RosterGroup :: boolean(),
+ SubId :: mod_pubsub:subId())
+ -> {result, mod_pubsub:pubsubItem()}
+ | {error, xmlel()}
+).
+get_item(NodeIdx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup,
+ SubId) ->
+ node_hometree:get_item(NodeIdx, ItemId, JID, AccessModel, PresenceSubscription,
+ RosterGroup, SubId).
+
+-spec(set_item/1 ::
+(
+ Item::mod_pubsub:pubsubItem())
+ -> ok
+).
+set_item(Item) -> node_hometree:set_item(Item).
get_item_name(Host, Node, Id) ->
node_hometree:get_item_name(Host, Node, Id).
-node_to_path(Node) ->
- node_flat:node_to_path(Node).
-
-path_to_node(Path) ->
- node_flat:path_to_node(Path).
+node_to_path(Node) -> node_flat:node_to_path(Node).
+path_to_node(Path) -> node_flat:path_to_node(Path).
%%%
%%% Internal
@@ -284,13 +491,13 @@ path_to_node(Path) ->
%% Check that the mod_caps module is enabled in that Jabber Host
%% If not, show a warning message in the ejabberd log file.
complain_if_modcaps_disabled(ServerHost) ->
- Modules = ejabberd_config:get_local_option({modules, ServerHost}),
+ Modules = ejabberd_config:get_local_option({modules, ServerHost}, fun(Ms) when is_list(Ms) -> Ms end),
ModCaps = [mod_caps_enabled || {mod_caps, _Opts} <- Modules],
case ModCaps of
- [] ->
- ?WARNING_MSG("The PEP plugin is enabled in mod_pubsub of host ~p. "
- "This plugin requires mod_caps to be enabled, "
- "but it isn't.", [ServerHost]);
- _ ->
- ok
+ [] ->
+ ?WARNING_MSG("The PEP plugin is enabled in mod_pubsub "
+ "of host ~p. This plugin requires mod_caps "
+ "to be enabled, but it isn't.",
+ [ServerHost]);
+ _ -> ok
end.
diff --git a/src/mod_pubsub/node_pep_odbc.erl b/src/mod_pubsub/node_pep_odbc.erl
index 9057e870c..8f4f36a45 100644
--- a/src/mod_pubsub/node_pep_odbc.erl
+++ b/src/mod_pubsub/node_pep_odbc.erl
@@ -5,11 +5,13 @@
%%% Erlang Public License along with this software. If not, it can be
%%% retrieved via the world wide web at http://www.erlang.org/.
%%%
+%%%
%%% Software distributed under the License is distributed on an "AS IS"
%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%%% the License for the specific language governing rights and limitations
%%% under the License.
%%%
+%%%
%%% The Initial Developer of the Original Code is ProcessOne.
%%% Portions created by ProcessOne are Copyright 2006-2013, ProcessOne
%%% All Rights Reserved.''
@@ -27,10 +29,13 @@
%%% <p>PubSub plugin nodes are using the {@link gen_pubsub_node} behaviour.</p>
-module(node_pep_odbc).
+
-author('christophe.romain@process-one.net').
-include("ejabberd.hrl").
+
-include("pubsub.hrl").
+
-include("jlib.hrl").
-define(PUBSUB, mod_pubsub_odbc).
@@ -38,42 +43,20 @@
-behaviour(gen_pubsub_node).
%% API definition
--export([init/3, terminate/2,
- options/0, features/0,
- create_node_permission/6,
- create_node/2,
- delete_node/1,
- purge_node/2,
- subscribe_node/8,
- unsubscribe_node/4,
- publish_item/6,
- delete_item/4,
- remove_extra_items/3,
- get_entity_affiliations/2,
- get_node_affiliations/1,
- get_affiliation/2,
- set_affiliation/3,
+-export([init/3, terminate/2, options/0, features/0,
+ create_node_permission/6, create_node/2, delete_node/1,
+ purge_node/2, subscribe_node/8, unsubscribe_node/4,
+ publish_item/6, delete_item/4, remove_extra_items/3,
+ get_entity_affiliations/2, get_node_affiliations/1,
+ get_affiliation/2, set_affiliation/3,
get_entity_subscriptions/2,
get_entity_subscriptions_for_send_last/2,
- get_node_subscriptions/1,
- get_subscriptions/2,
- set_subscriptions/4,
- get_pending_nodes/2,
- get_states/1,
- get_state/2,
- set_state/1,
- get_items/7,
- get_items/6,
- get_items/3,
- get_items/2,
- get_item/7,
- get_item/2,
- set_item/1,
- get_item_name/3,
- get_last_items/3,
- node_to_path/1,
- path_to_node/1
- ]).
+ get_node_subscriptions/1, get_subscriptions/2,
+ set_subscriptions/4, get_pending_nodes/2, get_states/1,
+ get_state/2, set_state/1, get_items/7, get_items/6,
+ get_items/3, get_items/2, get_item/7, get_item/2,
+ set_item/1, get_item_name/3, get_last_items/3,
+ node_to_path/1, path_to_node/1]).
init(Host, ServerHost, Opts) ->
node_hometree_odbc:init(Host, ServerHost, Opts),
@@ -81,9 +64,9 @@ init(Host, ServerHost, Opts) ->
ok.
terminate(Host, ServerHost) ->
- node_hometree_odbc:terminate(Host, ServerHost),
- ok.
+ node_hometree_odbc:terminate(Host, ServerHost), ok.
+-spec(options/0 :: () -> NodeOptions::mod_pubsub:nodeOptions()).
options() ->
[{odbc, true},
{node_type, pep},
@@ -104,90 +87,191 @@ options() ->
{deliver_notifications, true},
{presence_based_delivery, true}].
+-spec(features/0 :: () -> Features::[binary(),...]).
features() ->
- ["create-nodes", %*
- "auto-create", %*
- "auto-subscribe", %*
- "delete-nodes", %*
- "delete-items", %*
- "filtered-notifications", %*
- "modify-affiliations",
- "outcast-affiliation",
- "persistent-items",
- "publish", %*
- "purge-nodes",
- "retract-items",
- "retrieve-affiliations",
- "retrieve-items", %*
- "retrieve-subscriptions",
- "subscribe" %*
- ].
-
-create_node_permission(Host, ServerHost, _Node, _ParentNode, Owner, Access) ->
+ [<<"create-nodes">>, %*
+ <<"auto-create">>, %*
+ <<"auto-subscribe">>, %*
+ <<"delete-nodes">>, %*
+ <<"delete-items">>, %*
+ <<"filtered-notifications">>, %*
+ <<"modify-affiliations">>, <<"outcast-affiliation">>,
+ <<"persistent-items">>,
+ <<"publish">>, %*
+ <<"purge-nodes">>, <<"retract-items">>,
+ <<"retrieve-affiliations">>,
+ <<"retrieve-items">>, %*
+ <<"retrieve-subscriptions">>, <<"subscribe">>].
+
+-spec(create_node_permission/6 ::
+(
+ Host :: mod_pubsub:hostPEP(),
+ ServerHost :: binary(),
+ NodeId :: mod_pubsub:nodeId(),
+ _ParentNodeId :: mod_pubsub:nodeId(),
+ Owner :: jid(),
+ Access :: atom())
+ -> {result, boolean()}
+).
+
+create_node_permission(Host, ServerHost, _NodeId, _ParentNode, Owner, Access) ->
LOwner = jlib:jid_tolower(Owner),
{User, Server, _Resource} = LOwner,
Allowed = case LOwner of
- {"", Host, ""} ->
- true; % pubsub service always allowed
- _ ->
- case acl:match_rule(ServerHost, Access, LOwner) of
- allow ->
- case Host of
- {User, Server, _} -> true;
- _ -> false
- end;
- E ->
- ?DEBUG("Create not allowed : ~p~n", [E]),
- false
- end
- end,
+ {<<"">>, Host, <<"">>} ->
+ true; % pubsub service always allowed
+ _ ->
+ case acl:match_rule(ServerHost, Access, LOwner) of
+ allow ->
+ case Host of
+ {User, Server, _} -> true;
+ _ -> false
+ end;
+ E -> ?DEBUG("Create not allowed : ~p~n", [E]), false
+ end
+ end,
{result, Allowed}.
-create_node(NodeId, Owner) ->
- case node_hometree_odbc:create_node(NodeId, Owner) of
- {result, _} -> {result, []};
- Error -> Error
+-spec(create_node/2 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ Owner :: jid())
+ -> {result, []}
+).
+create_node(NodeIdx, Owner) ->
+ case node_hometree_odbc:create_node(NodeIdx, Owner) of
+ {result, _} -> {result, []};
+ Error -> Error
end.
+-spec(delete_node/1 ::
+(
+ Removed :: [mod_pubsub:pubsubNode(),...])
+ -> {result,
+ {[],
+ [{mod_pubsub:pubsubNode(),
+ [{ljid(), [{mod_pubsub:subscription(), mod_pubsub:subId()}]}]}]
+ }
+ }
+).
delete_node(Removed) ->
case node_hometree_odbc:delete_node(Removed) of
- {result, {_, _, Removed}} -> {result, {[], Removed}};
- Error -> Error
+ {result, {_, _, Removed}} -> {result, {[], Removed}};
+ Error -> Error
end.
+-spec(subscribe_node/8 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ Sender :: jid(),
+ Subscriber :: ljid(),
+ AccessModel :: mod_pubsub:accessModel(),
+ SendLast :: 'never' | 'on_sub' | 'on_sub_and_presence',
+ PresenceSubscription :: boolean(),
+ RosterGroup :: boolean(),
+ Options :: mod_pubsub:subOptions())
+ -> {result, {default, subscribed, mod_pubsub:subId()}}
+ | {result, {default, subscribed, mod_pubsub:subId(), send_last}}
+ | {result, {default, pending, mod_pubsub:subId()}}
+ %%%
+ | {error, _}
+ | {error, _, binary()}
+).
subscribe_node(NodeId, Sender, Subscriber, AccessModel,
SendLast, PresenceSubscription, RosterGroup, Options) ->
- node_hometree_odbc:subscribe_node(
- NodeId, Sender, Subscriber, AccessModel, SendLast,
- PresenceSubscription, RosterGroup, Options).
-
-unsubscribe_node(NodeId, Sender, Subscriber, SubID) ->
- case node_hometree_odbc:unsubscribe_node(NodeId, Sender, Subscriber, SubID) of
- {error, Error} -> {error, Error};
- {result, _} -> {result, []}
+ node_hometree_odbc:subscribe_node(NodeId, Sender,
+ Subscriber, AccessModel, SendLast,
+ PresenceSubscription, RosterGroup,
+ Options).
+
+
+-spec(unsubscribe_node/4 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ Sender :: jid(),
+ Subscriber :: jid(),
+ SubId :: subId())
+ -> {result, []}
+ %
+ | {error, _}
+ | {error, _, binary()}
+).
+unsubscribe_node(NodeIdx, Sender, Subscriber, SubID) ->
+ case node_hometree_odbc:unsubscribe_node(NodeIdx, Sender, Subscriber, SubID) of
+ {error, Error} -> {error, Error};
+ {result, _} -> {result, []}
end.
-publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload) ->
- node_hometree_odbc:publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload).
+-spec(publish_item/6 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ Publisher :: jid(),
+ PublishModel :: mod_pubsub:publishModel(),
+ Max_Items :: non_neg_integer(),
+ ItemId :: <<>> | mod_pubsub:itemId(),
+ Payload :: mod_pubsub:payload())
+ -> {result, {default, broadcast, [mod_pubsub:itemId()]}}
+ %%%
+ | {error, _}
+).
+publish_item(NodeIdx, Publisher, Model, MaxItems, ItemId, Payload) ->
+ node_hometree_odbc:publish_item(NodeIdx, Publisher,
+ Model, MaxItems, ItemId, Payload).
remove_extra_items(NodeId, MaxItems, ItemIds) ->
- node_hometree_odbc:remove_extra_items(NodeId, MaxItems, ItemIds).
-
+ node_hometree_odbc:remove_extra_items(NodeId, MaxItems,
+ ItemIds).
+
+-spec(delete_item/4 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ Publisher :: jid(),
+ PublishModel :: mod_pubsub:publishModel(),
+ ItemId :: <<>> | mod_pubsub:itemId())
+ -> {result, {default, broadcast}}
+ %%%
+ | {error, _}
+).
delete_item(NodeId, Publisher, PublishModel, ItemId) ->
- node_hometree_odbc:delete_item(NodeId, Publisher, PublishModel, ItemId).
-
-purge_node(NodeId, Owner) ->
- node_hometree_odbc:purge_node(NodeId, Owner).
-
+ node_hometree_odbc:delete_item(NodeId, Publisher,
+ PublishModel, ItemId).
+
+-spec(purge_node/2 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ Owner :: jid())
+ -> {result, {default, broadcast}}
+ | {error, _}
+).
+purge_node(NodeIdx, Owner) ->
+ node_hometree_odbc:purge_node(NodeIdx, Owner).
+
+-spec(get_entity_affiliations/2 ::
+(
+ Host :: mod_pubsub:hostPubsub(),
+ Owner :: jid())
+ -> {result, [{mod_pubsub:pubsubNode(), mod_pubsub:affiliation()}]}
+).
get_entity_affiliations(_Host, Owner) ->
OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
node_hometree_odbc:get_entity_affiliations(OwnerKey, Owner).
-get_node_affiliations(NodeId) ->
- node_hometree_odbc:get_node_affiliations(NodeId).
-
-get_affiliation(NodeId, Owner) ->
- node_hometree_odbc:get_affiliation(NodeId, Owner).
+-spec(get_node_affiliations/1 ::
+(
+ NodeIdx::mod_pubsub:nodeIdx())
+ -> {result, [{ljid(), mod_pubsub:affiliation()}]}
+).
+get_node_affiliations(NodeIdx) ->
+ node_hometree_odbc:get_node_affiliations(NodeIdx).
+
+-spec(get_affiliation/2 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ Owner :: ljid())
+ -> {result, mod_pubsub:affiliation()}
+).
+get_affiliation(NodeIdx, Owner) ->
+ node_hometree_odbc:get_affiliation(NodeIdx, Owner).
set_affiliation(NodeId, Owner, Affiliation) ->
node_hometree_odbc:set_affiliation(NodeId, Owner, Affiliation).
@@ -195,82 +279,102 @@ set_affiliation(NodeId, Owner, Affiliation) ->
get_entity_subscriptions(_Host, Owner) ->
SubKey = jlib:jid_tolower(Owner),
GenKey = jlib:jid_remove_resource(SubKey),
- Host = ?PUBSUB:escape(element(2, SubKey)),
+ Host = (?PUBSUB):escape(element(2, SubKey)),
SJ = node_hometree_odbc:encode_jid(SubKey),
GJ = node_hometree_odbc:encode_jid(GenKey),
Query = case SubKey of
- GenKey ->
- ["select host, node, type, i.nodeid, jid, subscriptions "
- "from pubsub_state i, pubsub_node n "
- "where i.nodeid = n.nodeid "
- "and jid like '", GJ, "%' "
- "and host like '%@", Host, "';"];
- _ ->
- ["select host, node, type, i.nodeid, jid, subscriptions "
- "from pubsub_state i, pubsub_node n "
- "where i.nodeid = n.nodeid "
- "and jid in ('", SJ, "', '", GJ, "') "
- "and host like '%@", Host, "';"]
- end,
+ GenKey ->
+ [<<"select host, node, type, i.nodeid, jid, "
+ "subscriptions from pubsub_state i, pubsub_nod"
+ "e n where i.nodeid = n.nodeid and jid "
+ "like '">>,
+ GJ, <<"%' and host like '%@">>, Host, <<"';">>];
+ _ ->
+ [<<"select host, node, type, i.nodeid, jid, "
+ "subscriptions from pubsub_state i, pubsub_nod"
+ "e n where i.nodeid = n.nodeid and jid "
+ "in ('">>,
+ SJ, <<"', '">>, GJ, <<"') and host like '%@">>, Host,
+ <<"';">>]
+ end,
Reply = case catch ejabberd_odbc:sql_query_t(Query) of
- {selected, ["host", "node", "type", "nodeid", "jid", "subscriptions"], RItems} ->
- lists:map(fun({H, N, T, I, J, S}) ->
- O = node_hometree_odbc:decode_jid(H),
- Node = nodetree_tree_odbc:raw_to_node(O, {N, "", T, I}),
- {Node, node_hometree_odbc:decode_subscriptions(S), node_hometree_odbc:decode_jid(J)}
- end, RItems);
- _ ->
- []
- end,
+ {selected,
+ [<<"host">>, <<"node">>, <<"type">>, <<"nodeid">>,
+ <<"jid">>, <<"subscriptions">>],
+ RItems} ->
+ lists:map(fun ({H, N, T, I, J, S}) ->
+ O = node_hometree_odbc:decode_jid(H),
+ Node = nodetree_tree_odbc:raw_to_node(O,
+ {N,
+ <<"">>,
+ T,
+ I}),
+ {Node,
+ node_hometree_odbc:decode_subscriptions(S),
+ node_hometree_odbc:decode_jid(J)}
+ end,
+ RItems);
+ _ -> []
+ end,
{result, Reply}.
get_entity_subscriptions_for_send_last(_Host, Owner) ->
SubKey = jlib:jid_tolower(Owner),
GenKey = jlib:jid_remove_resource(SubKey),
- Host = ?PUBSUB:escape(element(2, SubKey)),
+ Host = (?PUBSUB):escape(element(2, SubKey)),
SJ = node_hometree_odbc:encode_jid(SubKey),
GJ = node_hometree_odbc:encode_jid(GenKey),
Query = case SubKey of
- GenKey ->
- ["select host, node, type, i.nodeid, jid, subscriptions "
- "from pubsub_state i, pubsub_node n, pubsub_node_option o "
- "where i.nodeid = n.nodeid and n.nodeid = o.nodeid "
- "and name='send_last_published_item' and val='on_sub_and_presence' "
- "and jid like '", GJ, "%' "
- "and host like '%@", Host, "';"];
- _ ->
- ["select host, node, type, i.nodeid, jid, subscriptions "
- "from pubsub_state i, pubsub_node n, pubsub_node_option o "
- "where i.nodeid = n.nodeid and n.nodeid = o.nodeid "
- "and name='send_last_published_item' and val='on_sub_and_presence' "
- "and jid in ('", SJ, "', '", GJ, "') "
- "and host like '%@", Host, "';"]
- end,
+ GenKey ->
+ [<<"select host, node, type, i.nodeid, jid, "
+ "subscriptions from pubsub_state i, pubsub_nod"
+ "e n, pubsub_node_option o where i.nodeid "
+ "= n.nodeid and n.nodeid = o.nodeid and "
+ "name='send_last_published_item' and "
+ "val='on_sub_and_presence' and jid like "
+ "'">>,
+ GJ, <<"%' and host like '%@">>, Host, <<"';">>];
+ _ ->
+ [<<"select host, node, type, i.nodeid, jid, "
+ "subscriptions from pubsub_state i, pubsub_nod"
+ "e n, pubsub_node_option o where i.nodeid "
+ "= n.nodeid and n.nodeid = o.nodeid and "
+ "name='send_last_published_item' and "
+ "val='on_sub_and_presence' and jid in "
+ "('">>,
+ SJ, <<"', '">>, GJ, <<"') and host like '%@">>, Host,
+ <<"';">>]
+ end,
Reply = case catch ejabberd_odbc:sql_query_t(Query) of
- {selected, ["host", "node", "type", "nodeid", "jid", "subscriptions"], RItems} ->
- lists:map(fun({H, N, T, I, J, S}) ->
- O = node_hometree_odbc:decode_jid(H),
- Node = nodetree_tree_odbc:raw_to_node(O, {N, "", T, I}),
- {Node, node_hometree_odbc:decode_subscriptions(S), node_hometree_odbc:decode_jid(J)}
- end, RItems);
- _ ->
- []
- end,
+ {selected,
+ [<<"host">>, <<"node">>, <<"type">>, <<"nodeid">>,
+ <<"jid">>, <<"subscriptions">>],
+ RItems} ->
+ lists:map(fun ({H, N, T, I, J, S}) ->
+ O = node_hometree_odbc:decode_jid(H),
+ Node = nodetree_tree_odbc:raw_to_node(O,
+ {N,
+ <<"">>,
+ T,
+ I}),
+ {Node,
+ node_hometree_odbc:decode_subscriptions(S),
+ node_hometree_odbc:decode_jid(J)}
+ end,
+ RItems);
+ _ -> []
+ end,
{result, Reply}.
get_node_subscriptions(NodeId) ->
- %% note: get_node_subscriptions is used for broadcasting
- %% there should not have any subscriptions
- %% but that call returns also all subscription to none
- %% and this is required for broadcast to occurs
- %% DO NOT REMOVE
node_hometree_odbc:get_node_subscriptions(NodeId).
get_subscriptions(NodeId, Owner) ->
node_hometree_odbc:get_subscriptions(NodeId, Owner).
set_subscriptions(NodeId, Owner, Subscription, SubId) ->
- node_hometree_odbc:set_subscriptions(NodeId, Owner, Subscription, SubId).
+ node_hometree_odbc:set_subscriptions(NodeId, Owner,
+ Subscription, SubId).
get_pending_nodes(Host, Owner) ->
node_hometree_odbc:get_pending_nodes(Host, Owner).
@@ -281,17 +385,23 @@ get_states(NodeId) ->
get_state(NodeId, JID) ->
node_hometree_odbc:get_state(NodeId, JID).
-set_state(State) ->
- node_hometree_odbc:set_state(State).
+set_state(State) -> node_hometree_odbc:set_state(State).
get_items(NodeId, From) ->
node_hometree_odbc:get_items(NodeId, From).
+
get_items(NodeId, From, RSM) ->
node_hometree_odbc:get_items(NodeId, From, RSM).
-get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
- get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, none).
-get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) ->
- node_hometree_odbc:get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM).
+
+get_items(NodeId, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId) ->
+ get_items(NodeId, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId, none).
+
+get_items(NodeId, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId, RSM) ->
+ node_hometree_odbc:get_items(NodeId, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId, RSM).
get_last_items(NodeId, JID, Count) ->
node_hometree_odbc:get_last_items(NodeId, JID, Count).
@@ -299,20 +409,20 @@ get_last_items(NodeId, JID, Count) ->
get_item(NodeId, ItemId) ->
node_hometree_odbc:get_item(NodeId, ItemId).
-get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
- node_hometree_odbc:get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId).
+get_item(NodeId, ItemId, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId) ->
+ node_hometree_odbc:get_item(NodeId, ItemId, JID,
+ AccessModel, PresenceSubscription, RosterGroup,
+ SubId).
-set_item(Item) ->
- node_hometree_odbc:set_item(Item).
+set_item(Item) -> node_hometree_odbc:set_item(Item).
get_item_name(Host, Node, Id) ->
node_hometree_odbc:get_item_name(Host, Node, Id).
-node_to_path(Node) ->
- node_flat_odbc:node_to_path(Node).
+node_to_path(Node) -> node_flat_odbc:node_to_path(Node).
-path_to_node(Path) ->
- node_flat_odbc:path_to_node(Path).
+path_to_node(Path) -> node_flat_odbc:path_to_node(Path).
%%%
%%% Internal
@@ -323,14 +433,16 @@ path_to_node(Path) ->
%% Check that the mod_caps module is enabled in that Jabber Host
%% If not, show a warning message in the ejabberd log file.
complain_if_modcaps_disabled(ServerHost) ->
- Modules = ejabberd_config:get_local_option({modules, ServerHost}),
- ModCaps = [mod_caps_enabled || {mod_caps, _Opts} <- Modules],
+ Modules = ejabberd_config:get_local_option({modules,
+ ServerHost},
+ fun(Ms) when is_list(Ms) -> Ms end),
+ ModCaps = [mod_caps_enabled
+ || {mod_caps, _Opts} <- Modules],
case ModCaps of
- [] ->
- ?WARNING_MSG("The PEP plugin is enabled in mod_pubsub of host ~p. "
- "This plugin requires mod_caps to be enabled, "
- "but it isn't.", [ServerHost]);
- _ ->
- ok
+ [] ->
+ ?WARNING_MSG("The PEP plugin is enabled in mod_pubsub "
+ "of host ~p. This plugin requires mod_caps "
+ "to be enabled, but it isn't.",
+ [ServerHost]);
+ _ -> ok
end.
-
diff --git a/src/mod_pubsub/node_private.erl b/src/mod_pubsub/node_private.erl
index 8a1cd3e45..27b7158a1 100644
--- a/src/mod_pubsub/node_private.erl
+++ b/src/mod_pubsub/node_private.erl
@@ -5,11 +5,13 @@
%%% Erlang Public License along with this software. If not, it can be
%%% retrieved via the world wide web at http://www.erlang.org/.
%%%
+%%%
%%% Software distributed under the License is distributed on an "AS IS"
%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%%% the License for the specific language governing rights and limitations
%%% under the License.
%%%
+%%%
%%% The Initial Developer of the Original Code is ProcessOne.
%%% Portions created by ProcessOne are Copyright 2006-2013, ProcessOne
%%% All Rights Reserved.''
@@ -24,9 +26,11 @@
%%% ====================================================================
-module(node_private).
+
-author('christophe.romain@process-one.net').
-include("pubsub.hrl").
+
-include("jlib.hrl").
-behaviour(gen_pubsub_node).
@@ -40,39 +44,18 @@
%% (this makes code cleaner, but execution a little bit longer)
%% API definition
--export([init/3, terminate/2,
- options/0, features/0,
- create_node_permission/6,
- create_node/2,
- delete_node/1,
- purge_node/2,
- subscribe_node/8,
- unsubscribe_node/4,
- publish_item/6,
- delete_item/4,
- remove_extra_items/3,
- get_entity_affiliations/2,
- get_node_affiliations/1,
- get_affiliation/2,
- set_affiliation/3,
- get_entity_subscriptions/2,
- get_node_subscriptions/1,
- get_subscriptions/2,
- set_subscriptions/4,
- get_pending_nodes/2,
- get_states/1,
- get_state/2,
- set_state/1,
- get_items/6,
- get_items/2,
- get_item/7,
- get_item/2,
- set_item/1,
- get_item_name/3,
- node_to_path/1,
- path_to_node/1
- ]).
-
+-export([init/3, terminate/2, options/0, features/0,
+ create_node_permission/6, create_node/2, delete_node/1,
+ purge_node/2, subscribe_node/8, unsubscribe_node/4,
+ publish_item/6, delete_item/4, remove_extra_items/3,
+ get_entity_affiliations/2, get_node_affiliations/1,
+ get_affiliation/2, set_affiliation/3,
+ get_entity_subscriptions/2, get_node_subscriptions/1,
+ get_subscriptions/2, set_subscriptions/4,
+ get_pending_nodes/2, get_states/1, get_state/2,
+ set_state/1, get_items/6, get_items/2, get_item/7,
+ get_item/2, set_item/1, get_item_name/3, node_to_path/1,
+ path_to_node/1]).
init(Host, ServerHost, Opts) ->
node_hometree:init(Host, ServerHost, Opts).
@@ -81,16 +64,11 @@ terminate(Host, ServerHost) ->
node_hometree:terminate(Host, ServerHost).
options() ->
- [{deliver_payloads, true},
- {notify_config, false},
- {notify_delete, false},
- {notify_retract, true},
- {purge_offline, false},
- {persist_items, true},
- {max_items, ?MAXITEMS},
- {subscribe, true},
- {access_model, whitelist},
- {roster_groups_allowed, []},
+ [{deliver_payloads, true}, {notify_config, false},
+ {notify_delete, false}, {notify_retract, true},
+ {purge_offline, false}, {persist_items, true},
+ {max_items, ?MAXITEMS}, {subscribe, true},
+ {access_model, whitelist}, {roster_groups_allowed, []},
{publish_model, publishers},
{notification_type, headline},
{max_payload_size, ?MAX_PAYLOAD_SIZE},
@@ -99,25 +77,18 @@ options() ->
{presence_based_delivery, false}].
features() ->
- ["create-nodes",
- "delete-nodes",
- "delete-items",
- "instant-nodes",
- "outcast-affiliation",
- "persistent-items",
- "publish",
- "purge-nodes",
- "retract-items",
- "retrieve-affiliations",
- "retrieve-items",
- "retrieve-subscriptions",
- "subscribe",
- "subscription-notifications"
- ].
-
-create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) ->
- node_hometree:create_node_permission(Host, ServerHost, Node, ParentNode,
- Owner, Access).
+ [<<"create-nodes">>, <<"delete-nodes">>,
+ <<"delete-items">>, <<"instant-nodes">>,
+ <<"outcast-affiliation">>, <<"persistent-items">>,
+ <<"publish">>, <<"purge-nodes">>, <<"retract-items">>,
+ <<"retrieve-affiliations">>, <<"retrieve-items">>,
+ <<"retrieve-subscriptions">>, <<"subscribe">>,
+ <<"subscription-notifications">>].
+
+create_node_permission(Host, ServerHost, Node,
+ ParentNode, Owner, Access) ->
+ node_hometree:create_node_permission(Host, ServerHost,
+ Node, ParentNode, Owner, Access).
create_node(NodeId, Owner) ->
node_hometree:create_node(NodeId, Owner).
@@ -125,23 +96,28 @@ create_node(NodeId, Owner) ->
delete_node(Removed) ->
node_hometree:delete_node(Removed).
-subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast,
- PresenceSubscription, RosterGroup, Options) ->
- node_hometree:subscribe_node(NodeId, Sender, Subscriber, AccessModel,
- SendLast, PresenceSubscription, RosterGroup,
- Options).
+subscribe_node(NodeId, Sender, Subscriber, AccessModel,
+ SendLast, PresenceSubscription, RosterGroup, Options) ->
+ node_hometree:subscribe_node(NodeId, Sender, Subscriber,
+ AccessModel, SendLast, PresenceSubscription,
+ RosterGroup, Options).
unsubscribe_node(NodeId, Sender, Subscriber, SubID) ->
- node_hometree:unsubscribe_node(NodeId, Sender, Subscriber, SubID).
+ node_hometree:unsubscribe_node(NodeId, Sender,
+ Subscriber, SubID).
-publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload) ->
- node_hometree:publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload).
+publish_item(NodeId, Publisher, Model, MaxItems, ItemId,
+ Payload) ->
+ node_hometree:publish_item(NodeId, Publisher, Model,
+ MaxItems, ItemId, Payload).
remove_extra_items(NodeId, MaxItems, ItemIds) ->
- node_hometree:remove_extra_items(NodeId, MaxItems, ItemIds).
+ node_hometree:remove_extra_items(NodeId, MaxItems,
+ ItemIds).
delete_item(NodeId, Publisher, PublishModel, ItemId) ->
- node_hometree:delete_item(NodeId, Publisher, PublishModel, ItemId).
+ node_hometree:delete_item(NodeId, Publisher,
+ PublishModel, ItemId).
purge_node(NodeId, Owner) ->
node_hometree:purge_node(NodeId, Owner).
@@ -156,7 +132,8 @@ get_affiliation(NodeId, Owner) ->
node_hometree:get_affiliation(NodeId, Owner).
set_affiliation(NodeId, Owner, Affiliation) ->
- node_hometree:set_affiliation(NodeId, Owner, Affiliation).
+ node_hometree:set_affiliation(NodeId, Owner,
+ Affiliation).
get_entity_subscriptions(Host, Owner) ->
node_hometree:get_entity_subscriptions(Host, Owner).
@@ -168,41 +145,40 @@ get_subscriptions(NodeId, Owner) ->
node_hometree:get_subscriptions(NodeId, Owner).
set_subscriptions(NodeId, Owner, Subscription, SubId) ->
- node_hometree:set_subscriptions(NodeId, Owner, Subscription, SubId).
+ node_hometree:set_subscriptions(NodeId, Owner,
+ Subscription, SubId).
get_pending_nodes(Host, Owner) ->
node_hometree:get_pending_nodes(Host, Owner).
-get_states(NodeId) ->
- node_hometree:get_states(NodeId).
+get_states(NodeId) -> node_hometree:get_states(NodeId).
get_state(NodeId, JID) ->
node_hometree:get_state(NodeId, JID).
-set_state(State) ->
- node_hometree:set_state(State).
+set_state(State) -> node_hometree:set_state(State).
get_items(NodeId, From) ->
node_hometree:get_items(NodeId, From).
-get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
- node_hometree:get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId).
+get_items(NodeId, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId) ->
+ node_hometree:get_items(NodeId, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId).
get_item(NodeId, ItemId) ->
node_hometree:get_item(NodeId, ItemId).
-get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
- node_hometree:get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId).
+get_item(NodeId, ItemId, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId) ->
+ node_hometree:get_item(NodeId, ItemId, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId).
-set_item(Item) ->
- node_hometree:set_item(Item).
+set_item(Item) -> node_hometree:set_item(Item).
get_item_name(Host, Node, Id) ->
node_hometree:get_item_name(Host, Node, Id).
-node_to_path(Node) ->
- node_flat:node_to_path(Node).
-
-path_to_node(Path) ->
- node_flat:path_to_node(Path).
+node_to_path(Node) -> node_flat:node_to_path(Node).
+path_to_node(Path) -> node_flat:path_to_node(Path).
diff --git a/src/mod_pubsub/node_public.erl b/src/mod_pubsub/node_public.erl
index 8004696f0..3c391cfe8 100644
--- a/src/mod_pubsub/node_public.erl
+++ b/src/mod_pubsub/node_public.erl
@@ -5,11 +5,13 @@
%%% Erlang Public License along with this software. If not, it can be
%%% retrieved via the world wide web at http://www.erlang.org/.
%%%
+%%%
%%% Software distributed under the License is distributed on an "AS IS"
%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%%% the License for the specific language governing rights and limitations
%%% under the License.
%%%
+%%%
%%% The Initial Developer of the Original Code is ProcessOne.
%%% Portions created by ProcessOne are Copyright 2006-2013, ProcessOne
%%% All Rights Reserved.''
@@ -24,9 +26,11 @@
%%% ====================================================================
-module(node_public).
+
-author('christophe.romain@process-one.net').
-include("pubsub.hrl").
+
-include("jlib.hrl").
-behaviour(gen_pubsub_node).
@@ -40,39 +44,18 @@
%% (this makes code cleaner, but execution a little bit longer)
%% API definition
--export([init/3, terminate/2,
- options/0, features/0,
- create_node_permission/6,
- create_node/2,
- delete_node/1,
- purge_node/2,
- subscribe_node/8,
- unsubscribe_node/4,
- publish_item/6,
- delete_item/4,
- remove_extra_items/3,
- get_entity_affiliations/2,
- get_node_affiliations/1,
- get_affiliation/2,
- set_affiliation/3,
- get_entity_subscriptions/2,
- get_node_subscriptions/1,
- get_subscriptions/2,
- set_subscriptions/4,
- get_pending_nodes/2,
- get_states/1,
- get_state/2,
- set_state/1,
- get_items/6,
- get_items/2,
- get_item/7,
- get_item/2,
- set_item/1,
- get_item_name/3,
- node_to_path/1,
- path_to_node/1
- ]).
-
+-export([init/3, terminate/2, options/0, features/0,
+ create_node_permission/6, create_node/2, delete_node/1,
+ purge_node/2, subscribe_node/8, unsubscribe_node/4,
+ publish_item/6, delete_item/4, remove_extra_items/3,
+ get_entity_affiliations/2, get_node_affiliations/1,
+ get_affiliation/2, set_affiliation/3,
+ get_entity_subscriptions/2, get_node_subscriptions/1,
+ get_subscriptions/2, set_subscriptions/4,
+ get_pending_nodes/2, get_states/1, get_state/2,
+ set_state/1, get_items/6, get_items/2, get_item/7,
+ get_item/2, set_item/1, get_item_name/3, node_to_path/1,
+ path_to_node/1]).
init(Host, ServerHost, Opts) ->
node_hometree:init(Host, ServerHost, Opts).
@@ -81,16 +64,11 @@ terminate(Host, ServerHost) ->
node_hometree:terminate(Host, ServerHost).
options() ->
- [{deliver_payloads, true},
- {notify_config, false},
- {notify_delete, false},
- {notify_retract, true},
- {purge_offline, false},
- {persist_items, true},
- {max_items, ?MAXITEMS},
- {subscribe, true},
- {access_model, open},
- {roster_groups_allowed, []},
+ [{deliver_payloads, true}, {notify_config, false},
+ {notify_delete, false}, {notify_retract, true},
+ {purge_offline, false}, {persist_items, true},
+ {max_items, ?MAXITEMS}, {subscribe, true},
+ {access_model, open}, {roster_groups_allowed, []},
{publish_model, publishers},
{notification_type, headline},
{max_payload_size, ?MAX_PAYLOAD_SIZE},
@@ -99,24 +77,18 @@ options() ->
{presence_based_delivery, false}].
features() ->
- ["create-nodes",
- "delete-nodes",
- "delete-items",
- "instant-nodes",
- "outcast-affiliation",
- "persistent-items",
- "publish",
- "purge-nodes",
- "retract-items",
- "retrieve-affiliations",
- "retrieve-items",
- "retrieve-subscriptions",
- "subscribe",
- "subscription-notifications"
- ].
-
-create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) ->
- node_hometree:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access).
+ [<<"create-nodes">>, <<"delete-nodes">>,
+ <<"delete-items">>, <<"instant-nodes">>,
+ <<"outcast-affiliation">>, <<"persistent-items">>,
+ <<"publish">>, <<"purge-nodes">>, <<"retract-items">>,
+ <<"retrieve-affiliations">>, <<"retrieve-items">>,
+ <<"retrieve-subscriptions">>, <<"subscribe">>,
+ <<"subscription-notifications">>].
+
+create_node_permission(Host, ServerHost, Node,
+ ParentNode, Owner, Access) ->
+ node_hometree:create_node_permission(Host, ServerHost,
+ Node, ParentNode, Owner, Access).
create_node(NodeId, Owner) ->
node_hometree:create_node(NodeId, Owner).
@@ -124,20 +96,28 @@ create_node(NodeId, Owner) ->
delete_node(Removed) ->
node_hometree:delete_node(Removed).
-subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options) ->
- node_hometree:subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options).
+subscribe_node(NodeId, Sender, Subscriber, AccessModel,
+ SendLast, PresenceSubscription, RosterGroup, Options) ->
+ node_hometree:subscribe_node(NodeId, Sender, Subscriber,
+ AccessModel, SendLast, PresenceSubscription,
+ RosterGroup, Options).
unsubscribe_node(NodeId, Sender, Subscriber, SubID) ->
- node_hometree:unsubscribe_node(NodeId, Sender, Subscriber, SubID).
+ node_hometree:unsubscribe_node(NodeId, Sender,
+ Subscriber, SubID).
-publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload) ->
- node_hometree:publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload).
+publish_item(NodeId, Publisher, Model, MaxItems, ItemId,
+ Payload) ->
+ node_hometree:publish_item(NodeId, Publisher, Model,
+ MaxItems, ItemId, Payload).
remove_extra_items(NodeId, MaxItems, ItemIds) ->
- node_hometree:remove_extra_items(NodeId, MaxItems, ItemIds).
+ node_hometree:remove_extra_items(NodeId, MaxItems,
+ ItemIds).
delete_item(NodeId, Publisher, PublishModel, ItemId) ->
- node_hometree:delete_item(NodeId, Publisher, PublishModel, ItemId).
+ node_hometree:delete_item(NodeId, Publisher,
+ PublishModel, ItemId).
purge_node(NodeId, Owner) ->
node_hometree:purge_node(NodeId, Owner).
@@ -152,7 +132,8 @@ get_affiliation(NodeId, Owner) ->
node_hometree:get_affiliation(NodeId, Owner).
set_affiliation(NodeId, Owner, Affiliation) ->
- node_hometree:set_affiliation(NodeId, Owner, Affiliation).
+ node_hometree:set_affiliation(NodeId, Owner,
+ Affiliation).
get_entity_subscriptions(Host, Owner) ->
node_hometree:get_entity_subscriptions(Host, Owner).
@@ -164,43 +145,42 @@ get_subscriptions(NodeId, Owner) ->
node_hometree:get_subscriptions(NodeId, Owner).
set_subscriptions(NodeId, Owner, Subscription, SubId) ->
- node_hometree:set_subscriptions(NodeId, Owner, Subscription, SubId).
+ node_hometree:set_subscriptions(NodeId, Owner,
+ Subscription, SubId).
get_pending_nodes(Host, Owner) ->
node_hometree:get_pending_nodes(Host, Owner).
-get_states(NodeId) ->
- node_hometree:get_states(NodeId).
+get_states(NodeId) -> node_hometree:get_states(NodeId).
get_state(NodeId, JID) ->
node_hometree:get_state(NodeId, JID).
-set_state(State) ->
- node_hometree:set_state(State).
+set_state(State) -> node_hometree:set_state(State).
get_items(NodeId, From) ->
node_hometree:get_items(NodeId, From).
-get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
- node_hometree:get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId).
+get_items(NodeId, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId) ->
+ node_hometree:get_items(NodeId, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId).
get_item(NodeId, ItemId) ->
node_hometree:get_item(NodeId, ItemId).
-get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
- node_hometree:get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId).
+get_item(NodeId, ItemId, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId) ->
+ node_hometree:get_item(NodeId, ItemId, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId).
-set_item(Item) ->
- node_hometree:set_item(Item).
+set_item(Item) -> node_hometree:set_item(Item).
%% @doc <p>Return the name of the node if known: Default is to return
%% node id.</p>
get_item_name(Host, Node, Id) ->
node_hometree:get_item_name(Host, Node, Id).
-node_to_path(Node) ->
- node_flat:node_to_path(Node).
-
-path_to_node(Path) ->
- node_flat:path_to_node(Path).
+node_to_path(Node) -> node_flat:node_to_path(Node).
+path_to_node(Path) -> node_flat:path_to_node(Path).
diff --git a/src/mod_pubsub/nodetree_dag.erl b/src/mod_pubsub/nodetree_dag.erl
index 7074cabc4..2be3e3522 100644
--- a/src/mod_pubsub/nodetree_dag.erl
+++ b/src/mod_pubsub/nodetree_dag.erl
@@ -16,35 +16,30 @@
%%% ====================================================================
-module(nodetree_dag).
+
-author('bjc@kublai.com').
%% API
--export([init/3,
- terminate/2,
- options/0,
- set_node/1,
- get_node/3,
- get_node/2,
- get_node/1,
- get_nodes/2,
- get_nodes/1,
- get_parentnodes/3,
- get_parentnodes_tree/3,
- get_subnodes/3,
- get_subnodes_tree/3,
- create_node/6,
+-export([init/3, terminate/2, options/0, set_node/1,
+ get_node/3, get_node/2, get_node/1, get_nodes/2,
+ get_nodes/1, get_parentnodes/3, get_parentnodes_tree/3,
+ get_subnodes/3, get_subnodes_tree/3, create_node/6,
delete_node/2]).
-include_lib("stdlib/include/qlc.hrl").
-include("ejabberd.hrl").
+
-include("pubsub.hrl").
+
-include("jlib.hrl").
-behaviour(gen_pubsub_nodetree).
-define(DEFAULT_NODETYPE, leaf).
+
-define(DEFAULT_PARENTS, []).
+
-define(DEFAULT_CHILDREN, []).
-compile(export_all).
@@ -58,115 +53,172 @@ init(Host, ServerHost, Opts) ->
terminate(Host, ServerHost) ->
nodetree_tree:terminate(Host, ServerHost).
+-spec(create_node/6 ::
+(
+ Key :: mod_pubsub:hostPubsub(),
+ NodeID :: mod_pubsub:nodeId(),
+ Type :: binary(),
+ Owner :: jid(),
+ Options :: mod_pubsub:nodeOptions(),
+ Parents :: [mod_pubsub:nodeId()])
+ -> {ok, NodeIdx::mod_pubsub:nodeIdx()}
+ | {error, xmlel()}
+).
create_node(Key, NodeID, Type, Owner, Options, Parents) ->
OwnerJID = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
case find_node(Key, NodeID) of
- false ->
- ID = pubsub_index:new(node),
- N = #pubsub_node{nodeid = oid(Key, NodeID),
- id = ID,
- type = Type,
- parents = Parents,
- owners = [OwnerJID],
- options = Options},
- case set_node(N) of
- ok -> {ok, ID};
- Other -> Other
- end;
- _ ->
- {error, ?ERR_CONFLICT}
+ false ->
+ NodeIdx = pubsub_index:new(node),
+ N = #pubsub_node{nodeid = oid(Key, NodeID), id = NodeIdx,
+ type = Type, parents = Parents, owners = [OwnerJID],
+ options = Options},
+ case set_node(N) of
+ ok -> {ok, NodeIdx};
+ Other -> Other
+ end;
+ _ -> {error, ?ERR_CONFLICT}
end.
-set_node(#pubsub_node{nodeid = {Key, _},
- owners = Owners,
- options = Options} = Node) ->
- Parents = find_opt(collection, ?DEFAULT_PARENTS, Options),
+-spec(set_node/1 ::
+(
+ PubsubNode::mod_pubsub:pubsubNode())
+ -> ok
+ %%%
+ | {error, xmlel()}
+).
+set_node(#pubsub_node{nodeid = {Key, _}, owners = Owners, options = Options} =
+ Node) ->
+ Parents = find_opt(collection, ?DEFAULT_PARENTS, Options),
case validate_parentage(Key, Owners, Parents) of
- true ->
- %% Update parents whenever the config changes.
- mnesia:write(Node#pubsub_node{parents = Parents});
- Other ->
- Other
+ true ->
+ mnesia:write(Node#pubsub_node{parents = Parents});
+ Other -> Other
end.
+-spec(delete_node/2 ::
+(
+ Key :: mod_pubsub:hostPubsub(),
+ NodeID :: mod_pubsub:nodeId())
+ -> [mod_pubsub:pubsubNode(),...]
+ %%%
+ | {error, xmlel()}
+).
delete_node(Key, NodeID) ->
case find_node(Key, NodeID) of
- false ->
- {error, ?ERR_ITEM_NOT_FOUND};
- Node ->
- %% Find all of N's children, update their configs to
- %% remove N from the collection setting.
- lists:foreach(fun (#pubsub_node{options = Opts} = Child) ->
- NewOpts = remove_config_parent(NodeID, Opts),
- Parents = find_opt(collection, ?DEFAULT_PARENTS, NewOpts),
- ok = mnesia:write(pubsub_node,
- Child#pubsub_node{
- parents = Parents,
- options = NewOpts},
- write)
- end, get_subnodes(Key, NodeID)),
-
- %% Remove and return the requested node.
- pubsub_index:free(node, Node#pubsub_node.id),
- mnesia:delete_object(pubsub_node, Node, write),
- [Node]
+ false -> {error, ?ERR_ITEM_NOT_FOUND};
+ Node ->
+ lists:foreach(fun (#pubsub_node{options = Opts} =
+ Child) ->
+ NewOpts = remove_config_parent(NodeID, Opts),
+ Parents = find_opt(collection, ?DEFAULT_PARENTS,
+ NewOpts),
+ ok = mnesia:write(pubsub_node,
+ Child#pubsub_node{parents =
+ Parents,
+ options =
+ NewOpts},
+ write)
+ end,
+ get_subnodes(Key, NodeID)),
+ pubsub_index:free(node, Node#pubsub_node.id),
+ mnesia:delete_object(pubsub_node, Node, write),
+ [Node]
end.
-options() ->
- nodetree_tree:options().
+options() -> nodetree_tree:options().
-get_node(Host, NodeID, _From) ->
- get_node(Host, NodeID).
+get_node(Host, NodeID, _From) -> get_node(Host, NodeID).
+-spec(get_node/2 ::
+(
+ Host :: mod_pubsub:hostPubsub(),
+ NodeID :: mod_pubsub:nodeId())
+ -> mod_pubsub:pubsubNode()
+ %%%
+ | {error, xmlel}
+).
get_node(Host, NodeID) ->
case find_node(Host, NodeID) of
- false -> {error, ?ERR_ITEM_NOT_FOUND};
- Node -> Node
+ false -> {error, ?ERR_ITEM_NOT_FOUND};
+ Node -> Node
end.
-get_node(NodeId) ->
- nodetree_tree:get_node(NodeId).
+-spec(get_node/1 ::
+(
+ NodeIdx::mod_pubsub:nodeIdx())
+ -> mod_pubsub:pubsubNode()
+ | {error, xmlel()}
+).
+get_node(NodeId) -> nodetree_tree:get_node(NodeId).
get_nodes(Key, From) ->
nodetree_tree:get_nodes(Key, From).
-get_nodes(Key) ->
- nodetree_tree:get_nodes(Key).
-
+-spec(get_nodes/1 ::
+(
+ Host::mod_pubsub:host())
+ -> [mod_pubsub:pubsubNode()]
+).
+get_nodes(Key) -> nodetree_tree:get_nodes(Key).
+
+-spec(get_parentnodes/3 ::
+(
+ Host :: mod_pubsub:hostPubsub(),
+ NodeID :: mod_pubsub:nodeId(),
+ _From :: _)
+ -> [mod_pubsub:pubsubNode()]
+ %%%
+ | {error, xmlel()}
+).
get_parentnodes(Host, NodeID, _From) ->
case find_node(Host, NodeID) of
- false -> {error, ?ERR_ITEM_NOT_FOUND};
- #pubsub_node{parents = Parents} ->
- Q = qlc:q([N || #pubsub_node{nodeid = {NHost, NNode}} = N <- mnesia:table(pubsub_node),
- Parent <- Parents,
- Host == NHost,
- Parent == NNode]),
- qlc:e(Q)
+ false -> {error, ?ERR_ITEM_NOT_FOUND};
+ #pubsub_node{parents = Parents} ->
+ Q = qlc:q([N
+ || #pubsub_node{nodeid = {NHost, NNode}} = N
+ <- mnesia:table(pubsub_node),
+ Parent <- Parents, Host == NHost, Parent == NNode]),
+ qlc:e(Q)
end.
get_parentnodes_tree(Host, NodeID, _From) ->
Pred = fun (NID, #pubsub_node{nodeid = {_, NNodeID}}) ->
NID == NNodeID
end,
- Tr = fun (#pubsub_node{parents = Parents}) -> Parents end,
+ Tr = fun (#pubsub_node{parents = Parents}) -> Parents
+ end,
traversal_helper(Pred, Tr, Host, [NodeID]).
get_subnodes(Host, NodeID, _From) ->
get_subnodes(Host, NodeID).
+-spec(get_subnodes/2 ::
+(
+ Host :: mod_pubsub:hostPubsub(),
+ NodeId :: mod_pubsub:nodeId())
+ -> [mod_pubsub:pubsubNode()]
+).
get_subnodes(Host, <<>>) ->
get_subnodes_helper(Host, <<>>);
get_subnodes(Host, NodeID) ->
case find_node(Host, NodeID) of
- false -> {error, ?ERR_ITEM_NOT_FOUND};
- _ -> get_subnodes_helper(Host, NodeID)
+ false -> {error, ?ERR_ITEM_NOT_FOUND};
+ _ -> get_subnodes_helper(Host, NodeID)
end.
+-spec(get_subnodes_helper/2 ::
+(
+ Host :: mod_pubsub:hostPubsub(),
+ NodeID :: mod_pubsub:nodeId())
+ -> [mod_pubsub:pubsubNode()]
+).
get_subnodes_helper(Host, NodeID) ->
- Q = qlc:q([Node || #pubsub_node{nodeid = {NHost, _},
- parents = Parents} = Node <- mnesia:table(pubsub_node),
- Host == NHost,
- lists:member(NodeID, Parents)]),
+ Q = qlc:q([Node
+ || #pubsub_node{nodeid = {NHost, _},
+ parents = Parents} =
+ Node
+ <- mnesia:table(pubsub_node),
+ Host == NHost, lists:member(NodeID, Parents)]),
qlc:e(Q).
get_subnodes_tree(Host, NodeID, From) ->
@@ -175,7 +227,7 @@ get_subnodes_tree(Host, NodeID, From) ->
end,
Tr = fun (#pubsub_node{nodeid = {_, N}}) -> [N] end,
traversal_helper(Pred, Tr, 1, Host, [NodeID],
- [{0, [get_node(Host, NodeID, From)]}]).
+ [{0, [get_node(Host, NodeID, From)]}]).
%%====================================================================
%% Internal functions
@@ -184,10 +236,16 @@ oid(Key, Name) -> {Key, Name}.
%% Key = jlib:jid() | host()
%% NodeID = string()
+-spec(find_node/2 ::
+(
+ Key :: mod_pubsub:hostPubsub(),
+ NodeID :: mod_pubsub:nodeId())
+ -> mod_pubsub:pubsubNode() | false
+).
find_node(Key, NodeID) ->
case mnesia:read(pubsub_node, oid(Key, NodeID), read) of
- [] -> false;
- [Node] -> Node
+ [] -> false;
+ [Node] -> Node
end.
%% Key = jlib:jid() | host()
@@ -195,23 +253,33 @@ find_node(Key, NodeID) ->
%% Options = [{Key = atom(), Value = term()}]
find_opt(Key, Default, Options) ->
case lists:keysearch(Key, 1, Options) of
- {value, {Key, Val}} -> Val;
- _ -> Default
+ {value, {Key, Val}} -> Val;
+ _ -> Default
end.
+-spec(traversal_helper/4 ::
+(
+ Pred :: fun(),
+ Tr :: fun(),
+ Host :: mod_pubsub:hostPubsub(),
+ NodeId :: [mod_pubsub:pubsubNode(),...])
+ -> [{Depth::non_neg_integer(), Nodes::[mod_pubsub:pubsubNode(),...]}]
+).
+
traversal_helper(Pred, Tr, Host, NodeIDs) ->
traversal_helper(Pred, Tr, 0, Host, NodeIDs, []).
traversal_helper(_Pred, _Tr, _Depth, _Host, [], Acc) ->
Acc;
traversal_helper(Pred, Tr, Depth, Host, NodeIDs, Acc) ->
- Q = qlc:q([Node || #pubsub_node{nodeid = {NHost, _}} = Node <- mnesia:table(pubsub_node),
- NodeID <- NodeIDs,
- Host == NHost,
- Pred(NodeID, Node)]),
+ Q = qlc:q([Node
+ || #pubsub_node{nodeid = {NHost, _}} = Node
+ <- mnesia:table(pubsub_node),
+ NodeID <- NodeIDs, Host == NHost, Pred(NodeID, Node)]),
Nodes = qlc:e(Q),
IDs = lists:flatmap(Tr, Nodes),
- traversal_helper(Pred, Tr, Depth + 1, Host, IDs, [{Depth, Nodes} | Acc]).
+ traversal_helper(Pred, Tr, Depth + 1, Host, IDs,
+ [{Depth, Nodes} | Acc]).
remove_config_parent(NodeID, Options) ->
remove_config_parent(NodeID, Options, []).
@@ -220,28 +288,35 @@ remove_config_parent(_NodeID, [], Acc) ->
lists:reverse(Acc);
remove_config_parent(NodeID, [{collection, Parents} | T], Acc) ->
remove_config_parent(NodeID, T,
- [{collection, lists:delete(NodeID, Parents)} | Acc]);
+ [{collection, lists:delete(NodeID, Parents)} | Acc]);
remove_config_parent(NodeID, [H | T], Acc) ->
remove_config_parent(NodeID, T, [H | Acc]).
-validate_parentage(_Key, _Owners, []) ->
- true;
+-spec(validate_parentage/3 ::
+(
+ Key :: mod_pubsub:hostPubsub(),
+ Owners :: [ljid(),...],
+ Parent_NodeIds :: [mod_pubsub:nodeId()])
+ -> true
+ %%%
+ | {error, xmlel()}
+).
+validate_parentage(_Key, _Owners, []) -> true;
validate_parentage(Key, Owners, [[] | T]) ->
validate_parentage(Key, Owners, T);
validate_parentage(Key, Owners, [<<>> | T]) ->
validate_parentage(Key, Owners, T);
validate_parentage(Key, Owners, [ParentID | T]) ->
case find_node(Key, ParentID) of
- false -> {error, ?ERR_ITEM_NOT_FOUND};
- #pubsub_node{owners = POwners, options = POptions} ->
- NodeType = find_opt(node_type, ?DEFAULT_NODETYPE, POptions),
- MutualOwners = [O || O <- Owners, PO <- POwners,
- O == PO],
- case {MutualOwners, NodeType} of
- {[], _} -> {error, ?ERR_FORBIDDEN};
- {_, collection} -> validate_parentage(Key, Owners, T);
- {_, _} -> {error, ?ERR_NOT_ALLOWED}
- end
+ false -> {error, ?ERR_ITEM_NOT_FOUND};
+ #pubsub_node{owners = POwners, options = POptions} ->
+ NodeType = find_opt(node_type, ?DEFAULT_NODETYPE, POptions),
+ MutualOwners = [O || O <- Owners, PO <- POwners, O == PO],
+ case {MutualOwners, NodeType} of
+ {[], _} -> {error, ?ERR_FORBIDDEN};
+ {_, collection} -> validate_parentage(Key, Owners, T);
+ {_, _} -> {error, ?ERR_NOT_ALLOWED}
+ end
end.
%% @spec (Host) -> jid()
@@ -249,6 +324,6 @@ validate_parentage(Key, Owners, [ParentID | T]) ->
%% @doc <p>Generate pubsub service JID.</p>
service_jid(Host) ->
case Host of
- {U,S,_} -> {jid, U, S, "", U, S, ""};
- _ -> {jid, "", Host, "", "", Host, ""}
+ {U, S, _} -> jlib:make_jid(U, S, <<>>);
+ _ -> jlib:make_jid(<<>>, Host, <<>>)
end.
diff --git a/src/mod_pubsub/nodetree_tree.erl b/src/mod_pubsub/nodetree_tree.erl
index 7a30039bd..23159d7a2 100644
--- a/src/mod_pubsub/nodetree_tree.erl
+++ b/src/mod_pubsub/nodetree_tree.erl
@@ -5,11 +5,13 @@
%%% Erlang Public License along with this software. If not, it can be
%%% retrieved via the world wide web at http://www.erlang.org/.
%%%
+%%%
%%% Software distributed under the License is distributed on an "AS IS"
%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%%% the License for the specific language governing rights and limitations
%%% under the License.
%%%
+%%%
%%% The Initial Developer of the Original Code is ProcessOne.
%%% Portions created by ProcessOne are Copyright 2006-2013, ProcessOne
%%% All Rights Reserved.''
@@ -34,32 +36,22 @@
%%% improvements.</p>
-module(nodetree_tree).
+
-author('christophe.romain@process-one.net').
-include_lib("stdlib/include/qlc.hrl").
-include("pubsub.hrl").
+
-include("jlib.hrl").
-behaviour(gen_pubsub_nodetree).
--export([init/3,
- terminate/2,
- options/0,
- set_node/1,
- get_node/3,
- get_node/2,
- get_node/1,
- get_nodes/2,
- get_nodes/1,
- get_parentnodes/3,
- get_parentnodes_tree/3,
- get_subnodes/3,
- get_subnodes_tree/3,
- create_node/6,
- delete_node/2
- ]).
-
+-export([init/3, terminate/2, options/0, set_node/1,
+ get_node/3, get_node/2, get_node/1, get_nodes/2,
+ get_nodes/1, get_parentnodes/3, get_parentnodes_tree/3,
+ get_subnodes/3, get_subnodes_tree/3, create_node/6,
+ delete_node/2]).
%% ================
%% API definition
@@ -81,61 +73,79 @@ init(_Host, _ServerHost, _Options) ->
mnesia:add_table_index(pubsub_node, id),
NodesFields = record_info(fields, pubsub_node),
case mnesia:table_info(pubsub_node, attributes) of
- NodesFields -> ok;
- _ ->
- ok
- %% mnesia:transform_table(pubsub_state, ignore, StatesFields)
+ NodesFields -> ok;
+ _ -> ok
end,
+ %% mnesia:transform_table(pubsub_state, ignore, StatesFields)
ok.
%% @spec (Host, ServerHost) -> ok
%% Host = string()
%% ServerHost = string()
-terminate(_Host, _ServerHost) ->
- ok.
%% @spec () -> Options
%% Options = [mod_pubsub:nodeOption()]
%% @doc Returns the default pubsub node tree options.
-options() ->
- [{virtual_tree, false}].
+terminate(_Host, _ServerHost) -> ok.
+
+options() -> [{virtual_tree, false}].
%% @spec (Node) -> ok | {error, Reason}
%% Node = mod_pubsub:pubsubNode()
%% Reason = mod_pubsub:stanzaError()
+-spec(set_node/1 ::
+(
+ Node::mod_pubsub:pubsubNode())
+ -> ok
+).
set_node(Node) when is_record(Node, pubsub_node) ->
- mnesia:write(Node);
-set_node(_) ->
- {error, ?ERR_INTERNAL_SERVER_ERROR}.
+ mnesia:write(Node).
+%set_node(_) -> {error, ?ERR_INTERNAL_SERVER_ERROR}.
-get_node(Host, Node, _From) ->
- get_node(Host, Node).
+get_node(Host, Node, _From) -> get_node(Host, Node).
%% @spec (Host, NodeId) -> Node | {error, Reason}
%% Host = mod_pubsub:host()
%% NodeId = mod_pubsub:nodeId()
%% Node = mod_pubsub:pubsubNode()
%% Reason = mod_pubsub:stanzaError()
+-spec(get_node/2 ::
+(
+ Host :: mod_pubsub:host(),
+ NodeId :: mod_pubsub:nodeId())
+ -> mod_pubsub:pubsubNode()
+ | {error, xmlel()}
+).
get_node(Host, NodeId) ->
case catch mnesia:read({pubsub_node, {Host, NodeId}}) of
- [Record] when is_record(Record, pubsub_node) -> Record;
- [] -> {error, ?ERR_ITEM_NOT_FOUND};
- Error -> Error
+ [Record] when is_record(Record, pubsub_node) -> Record;
+ [] -> {error, ?ERR_ITEM_NOT_FOUND}
+% Error -> Error
end.
-get_node(NodeId) ->
- case catch mnesia:index_read(pubsub_node, NodeId, #pubsub_node.id) of
- [Record] when is_record(Record, pubsub_node) -> Record;
- [] -> {error, ?ERR_ITEM_NOT_FOUND};
- Error -> Error
+-spec(get_node/1 ::
+(
+ NodeIdx::mod_pubsub:nodeIdx())
+ -> mod_pubsub:pubsubNode()
+ | {error, xmlel()}
+).
+get_node(NodeIdx) ->
+ case catch mnesia:index_read(pubsub_node, NodeIdx, #pubsub_node.id) of
+ [Record] when is_record(Record, pubsub_node) -> Record;
+ [] -> {error, ?ERR_ITEM_NOT_FOUND}
+% Error -> Error
end.
-get_nodes(Host, _From) ->
- get_nodes(Host).
+get_nodes(Host, _From) -> get_nodes(Host).
%% @spec (Host) -> Nodes | {error, Reason}
%% Host = mod_pubsub:host()
%% Nodes = [mod_pubsub:pubsubNode()]
%% Reason = {aborted, atom()}
+-spec(get_nodes/1 ::
+(
+ Host::mod_pubsub:host())
+ -> [mod_pubsub:pubsubNode()]
+).
get_nodes(Host) ->
mnesia:match_object(#pubsub_node{nodeid = {Host, '_'}, _ = '_'}).
@@ -144,8 +154,7 @@ get_nodes(Host) ->
%% NodeId = mod_pubsub:nodeId()
%% From = mod_pubsub:jid()
%% @doc <p>Default node tree does not handle parents, return empty list.</p>
-get_parentnodes(_Host, _NodeId, _From) ->
- [].
+get_parentnodes(_Host, _NodeId, _From) -> [].
%% @spec (Host, NodeId, From) -> [{Depth, Node}] | []
%% Host = mod_pubsub:host()
@@ -155,10 +164,17 @@ get_parentnodes(_Host, _NodeId, _From) ->
%% Node = mod_pubsub:pubsubNode()
%% @doc <p>Default node tree does not handle parents, return a list
%% containing just this node.</p>
+-spec(get_parentnodes_tree/3 ::
+(
+ Host :: mod_pubsub:host(),
+ NodeId :: mod_pubsub:nodeId(),
+ From :: jid())
+ -> [{0, [mod_pubsub:pubsubNode(),...]}]
+).
get_parentnodes_tree(Host, NodeId, From) ->
case get_node(Host, NodeId, From) of
- Node when is_record(Node, pubsub_node) -> [{0, [Node]}];
- _Error -> []
+ Node when is_record(Node, pubsub_node) -> [{0, [Node]}];
+ _Error -> []
end.
%% @spec (Host, NodeId, From) -> Nodes
@@ -169,17 +185,27 @@ get_parentnodes_tree(Host, NodeId, From) ->
get_subnodes(Host, NodeId, _From) ->
get_subnodes(Host, NodeId).
+-spec(get_subnodes/2 ::
+(
+ Host :: mod_pubsub:host(),
+ NodeId :: mod_pubsub:nodeId())
+ -> [mod_pubsub:pubsubNode()]
+).
get_subnodes(Host, <<>>) ->
- Q = qlc:q([N || #pubsub_node{nodeid = {NHost, _},
- parents = Parents} = N <- mnesia:table(pubsub_node),
- Host == NHost,
- Parents == []]),
+ Q = qlc:q([N
+ || #pubsub_node{nodeid = {NHost, _},
+ parents = Parents} =
+ N
+ <- mnesia:table(pubsub_node),
+ Host == NHost, Parents == []]),
qlc:e(Q);
get_subnodes(Host, Node) ->
- Q = qlc:q([N || #pubsub_node{nodeid = {NHost, _},
- parents = Parents} = N <- mnesia:table(pubsub_node),
- Host == NHost,
- lists:member(Node, Parents)]),
+ Q = qlc:q([N
+ || #pubsub_node{nodeid = {NHost, _},
+ parents = Parents} =
+ N
+ <- mnesia:table(pubsub_node),
+ Host == NHost, lists:member(Node, Parents)]),
qlc:e(Q).
get_subnodes_tree(Host, Node, _From) ->
@@ -189,21 +215,30 @@ get_subnodes_tree(Host, Node, _From) ->
%% Host = mod_pubsub:host()
%% NodeId = mod_pubsub:nodeId()
%% Nodes = [] | [mod_pubsub:pubsubNode()]
+-spec(get_subnodes_tree/2 ::
+(
+ Host :: mod_pubsub:host(),
+ NodeId :: mod_pubsub:nodeId())
+ -> [mod_pubsub:pubsubNode()]
+).
get_subnodes_tree(Host, NodeId) ->
case get_node(Host, NodeId) of
- {error, _} ->
- [];
- Rec ->
- BasePlugin = list_to_atom("node_"++Rec#pubsub_node.type),
- BasePath = BasePlugin:node_to_path(NodeId),
- mnesia:foldl(fun(#pubsub_node{nodeid = {H, N}} = R, Acc) ->
- Plugin = list_to_atom("node_"++R#pubsub_node.type),
- Path = Plugin:node_to_path(N),
- case lists:prefix(BasePath, Path) and (H == Host) of
- true -> [R | Acc];
- false -> Acc
- end
- end, [], pubsub_node)
+ {error, _} -> [];
+ Rec ->
+ BasePlugin = jlib:binary_to_atom(<<"node_",
+ (Rec#pubsub_node.type)/binary>>),
+ BasePath = BasePlugin:node_to_path(NodeId),
+ mnesia:foldl(fun (#pubsub_node{nodeid = {H, N}} = R,
+ Acc) ->
+ Plugin = jlib:binary_to_atom(<<"node_",
+ (R#pubsub_node.type)/binary>>),
+ Path = Plugin:node_to_path(N),
+ case lists:prefix(BasePath, Path) and (H == Host) of
+ true -> [R | Acc];
+ false -> Acc
+ end
+ end,
+ [], pubsub_node)
end.
%% @spec (Host, NodeId, Type, Owner, Options, Parents) ->
@@ -216,56 +251,72 @@ get_subnodes_tree(Host, NodeId) ->
%% Parents = [] | [mod_pubsub:nodeId()]
%% NodeIdx = mod_pubsub:nodeIdx()
%% Reason = mod_pubsub:stanzaError()
+-spec(create_node/6 ::
+(
+ Host :: mod_pubsub:host(),
+ NodeId :: mod_pubsub:nodeId(),
+ Type :: binary(),
+ Owner :: jid(),
+ Options :: mod_pubsub:nodeOptions(),
+ Parents :: [mod_pubsub:nodeId()])
+ -> {ok, NodeIdx::mod_pubsub:nodeIdx()}
+ %%%
+ | {error, xmlel()}
+).
create_node(Host, NodeId, Type, Owner, Options, Parents) ->
BJID = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
case catch mnesia:read({pubsub_node, {Host, NodeId}}) of
- [] ->
- ParentExists =
- case Host of
- {_U, _S, _R} ->
- %% This is special case for PEP handling
- %% PEP does not uses hierarchy
- true;
- _ ->
- case Parents of
- [] -> true;
- [Parent|_] ->
- case catch mnesia:read({pubsub_node, {Host, Parent}}) of
- [#pubsub_node{owners = [{[], Host, []}]}] -> true;
- [#pubsub_node{owners = Owners}] -> lists:member(BJID, Owners);
- _ -> false
- end;
- _ ->
- false
- end
- end,
- case ParentExists of
- true ->
- NodeIdx = pubsub_index:new(node),
- mnesia:write(#pubsub_node{nodeid = {Host, NodeId},
- id = NodeIdx,
- parents = Parents,
- type = Type,
- owners = [BJID],
- options = Options}),
- {ok, NodeIdx};
- false ->
- %% Requesting entity is prohibited from creating nodes
- {error, ?ERR_FORBIDDEN}
- end;
- _ ->
- %% NodeID already exists
- {error, ?ERR_CONFLICT}
+ [] ->
+ ParentExists = case Host of
+ {_U, _S, _R} ->
+ %% This is special case for PEP handling
+ %% PEP does not uses hierarchy
+ true;
+ _ ->
+ case Parents of
+ [] -> true;
+ [Parent | _] ->
+ case catch mnesia:read({pubsub_node,
+ {Host, Parent}})
+ of
+ [#pubsub_node{owners =
+ [{[], Host, []}]}] ->
+ true;
+ [#pubsub_node{owners = Owners}] ->
+ lists:member(BJID, Owners);
+ _ -> false
+ end;
+ _ -> false
+ end
+ end,
+ case ParentExists of
+ true ->
+ NodeIdx = pubsub_index:new(node),
+ mnesia:write(#pubsub_node{nodeid = {Host, NodeId},
+ id = NodeIdx, parents = Parents,
+ type = Type, owners = [BJID],
+ options = Options}),
+ {ok, NodeIdx};
+ false -> {error, ?ERR_FORBIDDEN}
+ end;
+ _ -> {error, ?ERR_CONFLICT}
end.
%% @spec (Host, NodeId) -> Removed
%% Host = mod_pubsub:host()
%% NodeId = mod_pubsub:nodeId()
%% Removed = [mod_pubsub:pubsubNode()]
+-spec(delete_node/2 ::
+(
+ Host :: mod_pubsub:host(),
+ NodeId :: mod_pubsub:nodeId())
+ -> [mod_pubsub:pubsubNode(),...]
+).
delete_node(Host, NodeId) ->
Removed = get_subnodes_tree(Host, NodeId),
- lists:foreach(fun(#pubsub_node{nodeid = {_, SubNodeId}, id = SubNodeIdx}) ->
- pubsub_index:free(node, SubNodeIdx),
- mnesia:delete({pubsub_node, {Host, SubNodeId}})
- end, Removed),
+ lists:foreach(fun (#pubsub_node{nodeid = {_, SubNodeId}, id = SubNodeIdx}) ->
+ pubsub_index:free(node, SubNodeIdx),
+ mnesia:delete({pubsub_node, {Host, SubNodeId}})
+ end,
+ Removed),
Removed.
diff --git a/src/mod_pubsub/nodetree_tree_odbc.erl b/src/mod_pubsub/nodetree_tree_odbc.erl
index f4b9737c9..9756b897b 100644
--- a/src/mod_pubsub/nodetree_tree_odbc.erl
+++ b/src/mod_pubsub/nodetree_tree_odbc.erl
@@ -5,11 +5,13 @@
%%% Erlang Public License along with this software. If not, it can be
%%% retrieved via the world wide web at http://www.erlang.org/.
%%%
+%%%
%%% Software distributed under the License is distributed on an "AS IS"
%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%%% the License for the specific language governing rights and limitations
%%% under the License.
%%%
+%%%
%%% The Initial Developer of the Original Code is ProcessOne.
%%% Portions created by ProcessOne are Copyright 2006-2013, ProcessOne
%%% All Rights Reserved.''
@@ -34,32 +36,24 @@
%%% improvements.</p>
-module(nodetree_tree_odbc).
+
-author('christophe.romain@process-one.net').
-include("pubsub.hrl").
+
-include("jlib.hrl").
-define(PUBSUB, mod_pubsub_odbc).
--define(PLUGIN_PREFIX, "node_").
+
+-define(PLUGIN_PREFIX, <<"node_">>).
-behaviour(gen_pubsub_nodetree).
--export([init/3,
- terminate/2,
- options/0,
- set_node/1,
- get_node/3,
- get_node/2,
- get_node/1,
- get_nodes/2,
- get_nodes/1,
- get_parentnodes/3,
- get_parentnodes_tree/3,
- get_subnodes/3,
- get_subnodes_tree/3,
- create_node/6,
- delete_node/2
- ]).
+-export([init/3, terminate/2, options/0, set_node/1,
+ get_node/3, get_node/2, get_node/1, get_nodes/2,
+ get_nodes/1, get_parentnodes/3, get_parentnodes_tree/3,
+ get_subnodes/3, get_subnodes_tree/3, create_node/6,
+ delete_node/2]).
-export([raw_to_node/2]).
@@ -76,67 +70,87 @@
%% <p>This function is mainly used to trigger the setup task necessary for the
%% plugin. It can be used for example by the developer to create the specific
%% module database schema if it does not exists yet.</p>
-init(_Host, _ServerHost, _Opts) ->
- ok.
-terminate(_Host, _ServerHost) ->
- ok.
-
%% @spec () -> [Option]
%% Option = mod_pubsub:nodetreeOption()
%% @doc Returns the default pubsub node tree options.
-options() ->
- [{virtual_tree, false},
- {odbc, true}].
-
%% @spec (Host, Node, From) -> pubsubNode() | {error, Reason}
%% Host = mod_pubsub:host()
%% Node = mod_pubsub:pubsubNode()
-get_node(Host, Node, _From) ->
- get_node(Host, Node).
+init(_Host, _ServerHost, _Opts) -> ok.
+
+terminate(_Host, _ServerHost) -> ok.
+
+options() -> [{virtual_tree, false}, {odbc, true}].
+
+get_node(Host, Node, _From) -> get_node(Host, Node).
+
+-spec(get_node/2 ::
+(
+ Host :: mod_pubsub:host(),
+ NodeId :: mod_pubsub:nodeId())
+ -> mod_pubsub:pubsubNode()
+ | {error, _}
+).
get_node(Host, Node) ->
- H = ?PUBSUB:escape(Host),
- N = ?PUBSUB:escape(?PUBSUB:node_to_string(Node)),
- case catch ejabberd_odbc:sql_query_t(
- ["select node, parent, type, nodeid "
- "from pubsub_node "
- "where host='", H, "' and node='", N, "';"])
+ H = (?PUBSUB):escape(Host),
+ N = (?PUBSUB):escape(Node),
+ case catch
+ ejabberd_odbc:sql_query_t([<<"select node, parent, type, nodeid from "
+ "pubsub_node where host='">>,
+ H, <<"' and node='">>, N, <<"';">>])
of
- {selected, ["node", "parent", "type", "nodeid"], [RItem]} ->
- raw_to_node(Host, RItem);
- {'EXIT', _Reason} ->
- {error, ?ERR_INTERNAL_SERVER_ERROR};
- _ ->
- {error, ?ERR_ITEM_NOT_FOUND}
+ {selected,
+ [<<"node">>, <<"parent">>, <<"type">>, <<"nodeid">>],
+ [RItem]} ->
+ raw_to_node(Host, RItem);
+ {'EXIT', _Reason} ->
+ {error, ?ERR_INTERNAL_SERVER_ERROR};
+ _ -> {error, ?ERR_ITEM_NOT_FOUND}
end.
-get_node(NodeId) ->
- case catch ejabberd_odbc:sql_query_t(
- ["select host, node, parent, type "
- "from pubsub_node "
- "where nodeid='", NodeId, "';"])
+
+-spec(get_node/1 ::
+(
+ NodeIdx::mod_pubsub:nodeIdx())
+ -> mod_pubsub:pubsubNode()
+ | {error, _}
+).
+get_node(NodeIdx) ->
+ case catch
+ ejabberd_odbc:sql_query_t([<<"select host, node, parent, type from "
+ "pubsub_node where nodeid='">>,
+ NodeIdx, <<"';">>])
of
- {selected, ["host", "node", "parent", "type"], [{Host, Node, Parent, Type}]} ->
- raw_to_node(Host, {Node, Parent, Type, NodeId});
- {'EXIT', _Reason} ->
- {error, ?ERR_INTERNAL_SERVER_ERROR};
- _ ->
- {error, ?ERR_ITEM_NOT_FOUND}
+ {selected,
+ [<<"host">>, <<"node">>, <<"parent">>, <<"type">>],
+ [{Host, Node, Parent, Type}]} ->
+ raw_to_node(Host, {Node, Parent, Type, NodeIdx});
+ {'EXIT', _Reason} ->
+ {error, ?ERR_INTERNAL_SERVER_ERROR};
+ _ -> {error, ?ERR_ITEM_NOT_FOUND}
end.
%% @spec (Host, From) -> [pubsubNode()] | {error, Reason}
%% Host = mod_pubsub:host() | mod_pubsub:jid()
-get_nodes(Host, _From) ->
- get_nodes(Host).
+get_nodes(Host, _From) -> get_nodes(Host).
+
+-spec(get_nodes/1 ::
+(
+ Host::mod_pubsub:host())
+ -> [mod_pubsub:pubsubNode()]
+).
get_nodes(Host) ->
- H = ?PUBSUB:escape(Host),
- case catch ejabberd_odbc:sql_query_t(
- ["select node, parent, type, nodeid "
- "from pubsub_node "
- "where host='", H, "';"])
+ H = (?PUBSUB):escape(Host),
+ case catch
+ ejabberd_odbc:sql_query_t([<<"select node, parent, type, nodeid from "
+ "pubsub_node where host='">>,
+ H, <<"';">>])
of
- {selected, ["node", "parent", "type", "nodeid"], RItems} ->
- lists:map(fun(Item) -> raw_to_node(Host, Item) end, RItems);
- _ ->
- []
+ {selected,
+ [<<"node">>, <<"parent">>, <<"type">>, <<"nodeid">>],
+ RItems} ->
+ lists:map(fun (Item) -> raw_to_node(Host, Item) end,
+ RItems);
+ _ -> []
end.
%% @spec (Host, Node, From) -> [{Depth, Record}] | {error, Reason}
@@ -146,9 +160,6 @@ get_nodes(Host) ->
%% Depth = integer()
%% Record = pubsubNode()
%% @doc <p>Default node tree does not handle parents, return empty list.</p>
-get_parentnodes(_Host, _Node, _From) ->
- [].
-
%% @spec (Host, Node, From) -> [{Depth, Record}] | {error, Reason}
%% Host = mod_pubsub:host() | mod_pubsub:jid()
%% Node = mod_pubsub:pubsubNode()
@@ -157,10 +168,20 @@ get_parentnodes(_Host, _Node, _From) ->
%% Record = pubsubNode()
%% @doc <p>Default node tree does not handle parents, return a list
%% containing just this node.</p>
+get_parentnodes(_Host, _Node, _From) -> [].
+
+-spec(get_parentnodes_tree/3 ::
+(
+ Host :: mod_pubsub:host(),
+ NodeId :: mod_pubsub:nodeId(),
+ From :: jid())
+ -> [{0, [mod_pubsub:pubsubNode(),...]}]
+).
+
get_parentnodes_tree(Host, Node, From) ->
case get_node(Host, Node, From) of
- N when is_record(N, pubsub_node) -> [{0, [N]}];
- _Error -> []
+ N when is_record(N, pubsub_node) -> [{0, [N]}];
+ _Error -> []
end.
get_subnodes(Host, Node, _From) ->
@@ -169,18 +190,26 @@ get_subnodes(Host, Node, _From) ->
%% @spec (Host, Index) -> [pubsubNode()] | {error, Reason}
%% Host = mod_pubsub:host()
%% Node = mod_pubsub:pubsubNode()
+-spec(get_subnodes/2 ::
+(
+ Host :: mod_pubsub:host(),
+ NodeId :: mod_pubsub:nodeId())
+ -> [mod_pubsub:pubsubNode()]
+).
get_subnodes(Host, Node) ->
- H = ?PUBSUB:escape(Host),
- N = ?PUBSUB:escape(?PUBSUB:node_to_string(Node)),
- case catch ejabberd_odbc:sql_query_t(
- ["select node, parent, type, nodeid "
- "from pubsub_node "
- "where host='", H, "' and parent='", N, "';"])
+ H = (?PUBSUB):escape(Host),
+ N = (?PUBSUB):escape(Node),
+ case catch
+ ejabberd_odbc:sql_query_t([<<"select node, parent, type, nodeid from "
+ "pubsub_node where host='">>,
+ H, <<"' and parent='">>, N, <<"';">>])
of
- {selected, ["node", "parent", "type", "nodeid"], RItems} ->
- lists:map(fun(Item) -> raw_to_node(Host, Item) end, RItems);
- _ ->
- []
+ {selected,
+ [<<"node">>, <<"parent">>, <<"type">>, <<"nodeid">>],
+ RItems} ->
+ lists:map(fun (Item) -> raw_to_node(Host, Item) end,
+ RItems);
+ _ -> []
end.
get_subnodes_tree(Host, Node, _From) ->
@@ -189,18 +218,27 @@ get_subnodes_tree(Host, Node, _From) ->
%% @spec (Host, Index) -> [pubsubNode()] | {error, Reason}
%% Host = mod_pubsub:host()
%% Node = mod_pubsub:pubsubNode()
+-spec(get_subnodes_tree/2 ::
+(
+ Host :: mod_pubsub:host(),
+ NodeId :: mod_pubsub:nodeId())
+ -> [mod_pubsub:pubsubNode()]
+).
+
get_subnodes_tree(Host, Node) ->
- H = ?PUBSUB:escape(Host),
- N = ?PUBSUB:escape(?PUBSUB:node_to_string(Node)),
- case catch ejabberd_odbc:sql_query_t(
- ["select node, parent, type, nodeid "
- "from pubsub_node "
- "where host='", H, "' and node like '", N, "%';"])
+ H = (?PUBSUB):escape(Host),
+ N = (?PUBSUB):escape(Node),
+ case catch
+ ejabberd_odbc:sql_query_t([<<"select node, parent, type, nodeid from "
+ "pubsub_node where host='">>,
+ H, <<"' and node like '">>, N, <<"%';">>])
of
- {selected, ["node", "parent", "type", "nodeid"], RItems} ->
- lists:map(fun(Item) -> raw_to_node(Host, Item) end, RItems);
- _ ->
- []
+ {selected,
+ [<<"node">>, <<"parent">>, <<"type">>, <<"nodeid">>],
+ RItems} ->
+ lists:map(fun (Item) -> raw_to_node(Host, Item) end,
+ RItems);
+ _ -> []
end.
%% @spec (Host, Node, Type, Owner, Options, Parents) -> ok | {error, Reason}
@@ -210,162 +248,219 @@ get_subnodes_tree(Host, Node) ->
%% Owner = mod_pubsub:jid()
%% Options = list()
%% Parents = list()
+
+-spec(create_node/6 ::
+(
+ Host :: mod_pubsub:host(),
+ NodeId :: mod_pubsub:nodeId(),
+ Type :: binary(),
+ Owner :: jid(),
+ Options :: mod_pubsub:nodeOptions(),
+ Parents :: [mod_pubsub:nodeId()])
+ -> {ok, NodeIdx::mod_pubsub:nodeIdx()}
+ %%%
+ | {error, _}
+).
+
create_node(Host, Node, Type, Owner, Options, Parents) ->
BJID = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
case nodeid(Host, Node) of
- {error, ?ERR_ITEM_NOT_FOUND} ->
- ParentExists =
- case Host of
- {_U, _S, _R} ->
- %% This is special case for PEP handling
- %% PEP does not uses hierarchy
- true;
- _ ->
- case Parents of
- [] -> true;
- [Parent|_] ->
- case nodeid(Host, Parent) of
- {result, PNodeId} ->
- case nodeowners(PNodeId) of
- [{[], Host, []}] -> true;
- Owners -> lists:member(BJID, Owners)
- end;
- _ ->
- false
- end;
- _ ->
- false
- end
- end,
- case ParentExists of
- true ->
- case set_node(#pubsub_node{
- nodeid={Host, Node},
- parents=Parents,
- type=Type,
- options=Options}) of
- {result, NodeId} -> {ok, NodeId};
- Other -> Other
- end;
- false ->
- %% Requesting entity is prohibited from creating nodes
- {error, ?ERR_FORBIDDEN}
- end;
- {result, _} ->
- %% NodeID already exists
- {error, ?ERR_CONFLICT};
- Error ->
- Error
+ {error, ?ERR_ITEM_NOT_FOUND} ->
+ ParentExists = case Host of
+ {_U, _S, _R} ->
+ %% This is special case for PEP handling
+ %% PEP does not uses hierarchy
+ true;
+ _ ->
+ case Parents of
+ [] -> true;
+ [Parent | _] ->
+ case nodeid(Host, Parent) of
+ {result, PNodeId} ->
+ case nodeowners(PNodeId) of
+ [{<<>>, Host, <<>>}] -> true;
+ Owners ->
+ lists:member(BJID, Owners)
+ end;
+ _ -> false
+ end;
+ _ -> false
+ end
+ end,
+ case ParentExists of
+ true ->
+ case set_node(#pubsub_node{nodeid = {Host, Node},
+ parents = Parents, type = Type,
+ options = Options})
+ of
+ {result, NodeId} -> {ok, NodeId};
+ Other -> Other
+ end;
+ false -> {error, ?ERR_FORBIDDEN}
+ end;
+ {result, _} -> {error, ?ERR_CONFLICT};
+ Error -> Error
end.
%% @spec (Host, Node) -> [mod_pubsub:node()]
%% Host = mod_pubsub:host() | mod_pubsub:jid()
%% Node = mod_pubsub:pubsubNode()
+-spec(delete_node/2 ::
+(
+ Host :: mod_pubsub:host(),
+ NodeId :: mod_pubsub:nodeId())
+ -> [mod_pubsub:pubsubNode()]
+).
+
delete_node(Host, Node) ->
- H = ?PUBSUB:escape(Host),
- N = ?PUBSUB:escape(?PUBSUB:node_to_string(Node)),
+ H = (?PUBSUB):escape(Host),
+ N = (?PUBSUB):escape(Node),
Removed = get_subnodes_tree(Host, Node),
- catch ejabberd_odbc:sql_query_t(
- ["delete from pubsub_node "
- "where host='", H, "' and node like '", N, "%';"]),
+ catch
+ ejabberd_odbc:sql_query_t([<<"delete from pubsub_node where host='">>,
+ H, <<"' and node like '">>, N, <<"%';">>]),
Removed.
%% helpers
-
-raw_to_node(Host, {Node, Parent, Type, NodeId}) ->
- Options = case catch ejabberd_odbc:sql_query_t(
- ["select name,val "
- "from pubsub_node_option "
- "where nodeid='", NodeId, "';"])
+-spec(raw_to_node/2 ::
+(
+ Host :: mod_pubus:host(),
+ _ :: {NodeId::mod_pubsub:nodeId(),
+ Parent::mod_pubsub:nodeId(),
+ Type::binary(),
+ NodeIdx::mod_pubsub:nodeIdx()})
+ -> mod_pubsub:pubsubNode()
+).
+raw_to_node(Host, {Node, Parent, Type, NodeIdx}) ->
+ Options = case catch
+ ejabberd_odbc:sql_query_t([<<"select name,val from pubsub_node_option "
+ "where nodeid='">>,
+ NodeIdx, <<"';">>])
of
- {selected, ["name", "val"], ROptions} ->
- DbOpts = lists:map(fun({Key, Value}) ->
- RKey = list_to_atom(Key),
- Tokens = element(2, erl_scan:string(Value++".")),
- RValue = element(2, erl_parse:parse_term(Tokens)),
- {RKey, RValue}
- end, ROptions),
- Module = list_to_atom(?PLUGIN_PREFIX++Type),
- StdOpts = Module:options(),
- lists:foldl(fun({Key, Value}, Acc)->
- lists:keyreplace(Key, 1, Acc, {Key, Value})
- end, StdOpts, DbOpts);
- _ ->
- []
+ {selected, [<<"name">>, <<"val">>], ROptions} ->
+ DbOpts = lists:map(fun ({Key, Value}) ->
+ RKey =
+ jlib:binary_to_atom(Key),
+ Tokens = element(2,
+ erl_scan:string(<<Value/binary,
+ ".">>)),
+ RValue = element(2,
+ erl_parse:parse_term(Tokens)),
+ {RKey, RValue}
+ end,
+ ROptions),
+ Module =
+ jlib:binary_to_atom(<<(?PLUGIN_PREFIX)/binary,
+ Type/binary>>),
+ StdOpts = Module:options(),
+ lists:foldl(fun ({Key, Value}, Acc) ->
+ lists:keyreplace(Key, 1, Acc,
+ {Key, Value})
+ end,
+ StdOpts, DbOpts);
+ _ -> []
end,
- #pubsub_node{
- nodeid = {Host, ?PUBSUB:string_to_node(Node)},
- parents = [?PUBSUB:string_to_node(Parent)],
- id = NodeId,
- type = Type,
- options = Options}.
-
%% @spec (NodeRecord) -> ok | {error, Reason}
%% Record = mod_pubsub:pubsub_node()
+ #pubsub_node{nodeid =
+ {Host, Node},
+ parents = [Parent],
+ id = NodeIdx, type = Type, options = Options}.
+
+-spec(set_node/1 ::
+(
+ Record::mod_pubsub:pubsubNode())
+ -> {result, NodeIdx::mod_pubsub:nodeIdx()}
+ %%%
+ | {error, _}
+).
set_node(Record) ->
{Host, Node} = Record#pubsub_node.nodeid,
Parent = case Record#pubsub_node.parents of
- [] -> <<>>;
- [First|_] -> First
- end,
+ [] -> <<>>;
+ [First | _] -> First
+ end,
Type = Record#pubsub_node.type,
- H = ?PUBSUB:escape(Host),
- N = ?PUBSUB:escape(?PUBSUB:node_to_string(Node)),
- P = ?PUBSUB:escape(?PUBSUB:node_to_string(Parent)),
- NodeId = case nodeid(Host, Node) of
- {result, OldNodeId} ->
- catch ejabberd_odbc:sql_query_t(
- ["delete from pubsub_node_option "
- "where nodeid='", OldNodeId, "';"]),
- catch ejabberd_odbc:sql_query_t(
- ["update pubsub_node "
- "set host='", H, "' "
- "node='", N, "' "
- "parent='", P, "' "
- "type='", Type, "' "
- "where nodeid='", OldNodeId, "';"]),
- OldNodeId;
- _ ->
- catch ejabberd_odbc:sql_query_t(
- ["insert into pubsub_node(host, node, parent, type) "
- "values('", H, "', '", N, "', '", P, "', '", Type, "');"]),
- case nodeid(Host, Node) of
- {result, NewNodeId} -> NewNodeId;
- _ -> none % this should not happen
- end
+ H = (?PUBSUB):escape(Host),
+ N = (?PUBSUB):escape(Node),
+ P = (?PUBSUB):escape(Parent),
+ NodeIdx = case nodeid(Host, Node) of
+ {result, OldNodeIdx} ->
+ catch
+ ejabberd_odbc:sql_query_t([<<"delete from pubsub_node_option where "
+ "nodeid='">>,
+ OldNodeIdx, <<"';">>]),
+ catch
+ ejabberd_odbc:sql_query_t([<<"update pubsub_node set host='">>,
+ H, <<"' node='">>, N,
+ <<"' parent='">>, P,
+ <<"' type='">>, Type,
+ <<"' where nodeid='">>,
+ OldNodeIdx, <<"';">>]),
+ OldNodeIdx;
+ _ ->
+ catch
+ ejabberd_odbc:sql_query_t([<<"insert into pubsub_node(host, node, "
+ "parent, type) values('">>,
+ H, <<"', '">>, N, <<"', '">>, P,
+ <<"', '">>, Type, <<"');">>]),
+ case nodeid(Host, Node) of
+ {result, NewNodeIdx} -> NewNodeIdx;
+ _ -> none % this should not happen
+ end
end,
- case NodeId of
- none ->
- {error, ?ERR_INTERNAL_SERVER_ERROR};
- _ ->
- lists:foreach(fun({Key, Value}) ->
- SKey = atom_to_list(Key),
- SValue = ?PUBSUB:escape(lists:flatten(io_lib:fwrite("~p",[Value]))),
- catch ejabberd_odbc:sql_query_t(
- ["insert into pubsub_node_option(nodeid, name, val) "
- "values('", NodeId, "', '", SKey, "', '", SValue, "');"])
- end, Record#pubsub_node.options),
- {result, NodeId}
+ case NodeIdx of
+ none -> {error, ?ERR_INTERNAL_SERVER_ERROR};
+ _ ->
+ lists:foreach(fun ({Key, Value}) ->
+ SKey = iolist_to_binary(atom_to_list(Key)),
+ SValue =
+ (?PUBSUB):escape(lists:flatten(io_lib:fwrite("~p",
+ [Value]))),
+ catch
+ ejabberd_odbc:sql_query_t([<<"insert into pubsub_node_option(nodeid, "
+ "name, val) values('">>,
+ NodeIdx, <<"', '">>,
+ SKey, <<"', '">>,
+ SValue, <<"');">>])
+ end,
+ Record#pubsub_node.options),
+ {result, NodeIdx}
end.
-nodeid(Host, Node) ->
- H = ?PUBSUB:escape(Host),
- N = ?PUBSUB:escape(?PUBSUB:node_to_string(Node)),
- case catch ejabberd_odbc:sql_query_t(
- ["select nodeid "
- "from pubsub_node "
- "where host='", H, "' and node='", N, "';"])
+-spec(nodeid/2 ::
+(
+ Host :: mod_pubsub:host(),
+ NodeId :: mod_pubsub:nodeId())
+ -> {result, NodeIdx::mod_pubsub:nodeIdx()}
+ %%%
+ | {error, _}
+).
+
+nodeid(Host, NodeId) ->
+ H = (?PUBSUB):escape(Host),
+ N = (?PUBSUB):escape(NodeId),
+ case catch
+ ejabberd_odbc:sql_query_t([<<"select nodeid from pubsub_node where "
+ "host='">>,
+ H, <<"' and node='">>, N, <<"';">>])
of
- {selected, ["nodeid"], [{NodeId}]} ->
- {result, NodeId};
- {'EXIT', _Reason} ->
- {error, ?ERR_INTERNAL_SERVER_ERROR};
- _ ->
- {error, ?ERR_ITEM_NOT_FOUND}
+ {selected, [<<"nodeid">>], [{NodeIdx}]} ->
+ {result, NodeIdx};
+ {'EXIT', _Reason} ->
+ {error, ?ERR_INTERNAL_SERVER_ERROR};
+ _ -> {error, ?ERR_ITEM_NOT_FOUND}
end.
-nodeowners(NodeId) ->
- {result, Res} = node_hometree_odbc:get_node_affiliations(NodeId),
- lists:foldl(fun({LJID, owner}, Acc) -> [LJID|Acc];
- (_, Acc) -> Acc
- end, [], Res).
+-spec(nodeowners/1 ::
+(
+ NodeIdx::mod_pubsub:nodeIdx())
+ -> Node_Owners::[ljid()]
+).
+
+nodeowners(NodeIdx) ->
+ {result, Res} = node_hometree_odbc:get_node_affiliations(NodeIdx),
+ lists:foldl(fun ({LJID, owner}, Acc) -> [LJID | Acc];
+ (_, Acc) -> Acc
+ end,
+ [], Res).
diff --git a/src/mod_pubsub/nodetree_virtual.erl b/src/mod_pubsub/nodetree_virtual.erl
index ff216c30d..2aa5d7405 100644
--- a/src/mod_pubsub/nodetree_virtual.erl
+++ b/src/mod_pubsub/nodetree_virtual.erl
@@ -5,11 +5,13 @@
%%% Erlang Public License along with this software. If not, it can be
%%% retrieved via the world wide web at http://www.erlang.org/.
%%%
+%%%
%%% Software distributed under the License is distributed on an "AS IS"
%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%%% the License for the specific language governing rights and limitations
%%% under the License.
%%%
+%%%
%%% The Initial Developer of the Original Code is ProcessOne.
%%% Portions created by ProcessOne are Copyright 2006-2013, ProcessOne
%%% All Rights Reserved.''
@@ -31,29 +33,20 @@
%%% Please, send us comments, feedback and improvements.</p>
-module(nodetree_virtual).
+
-author('christophe.romain@process-one.net').
-include("pubsub.hrl").
+
-include("jlib.hrl").
-behaviour(gen_pubsub_nodetree).
--export([init/3,
- terminate/2,
- options/0,
- set_node/1,
- get_node/3,
- get_node/2,
- get_node/1,
- get_nodes/2,
- get_nodes/1,
- get_parentnodes/3,
- get_parentnodes_tree/3,
- get_subnodes/3,
- get_subnodes_tree/3,
- create_node/6,
- delete_node/2
- ]).
+-export([init/3, terminate/2, options/0, set_node/1,
+ get_node/3, get_node/2, get_node/1, get_nodes/2,
+ get_nodes/1, get_parentnodes/3, get_parentnodes_tree/3,
+ get_subnodes/3, get_subnodes_tree/3, create_node/6,
+ delete_node/2]).
%% ================
%% API definition
@@ -68,38 +61,36 @@
%% <p>This function is mainly used to trigger the setup task necessary for the
%% plugin. It can be used for example by the developer to create the specific
%% module database schema if it does not exists yet.</p>
-init(_Host, _ServerHost, _Opts) ->
- ok.
-terminate(_Host, _ServerHost) ->
- ok.
-
%% @spec () -> [Option]
%% Option = mod_pubsub:nodetreeOption()
%% @doc <p>Returns the default pubsub node tree options.</p>
-options() ->
- [{virtual_tree, true}].
-
%% @spec (NodeRecord) -> ok | {error, Reason}
%% NodeRecord = mod_pubsub:pubsub_node()
%% @doc <p>No node record is stored on database. Just do nothing.</p>
-set_node(_NodeRecord) ->
- ok.
-
%% @spec (Host, Node, From) -> pubsubNode()
%% Host = mod_pubsub:host()
%% Node = mod_pubsub:pubsubNode()
%% From = mod_pubsub:jid()
%% @doc <p>Virtual node tree does not handle a node database. Any node is considered
%% as existing. Node record contains default values.</p>
-get_node(Host, Node, _From) ->
- get_node(Host, Node).
-get_node(Host, Node) ->
- get_node({Host, Node}).
+init(_Host, _ServerHost, _Opts) -> ok.
+
+terminate(_Host, _ServerHost) -> ok.
+
+options() -> [{virtual_tree, true}].
+
+set_node(_NodeRecord) -> ok.
+
+get_node(Host, Node, _From) -> get_node(Host, Node).
+
+get_node(Host, Node) -> get_node({Host, Node}).
+
get_node({Host, _} = NodeId) ->
Record = #pubsub_node{nodeid = NodeId, id = NodeId},
- Module = list_to_atom("node_" ++ Record#pubsub_node.type),
+ Module = jlib:binary_to_atom(<<"node_",
+ (Record#pubsub_node.type)/binary>>),
Options = Module:options(),
- Owners = [{"", Host, ""}],
+ Owners = [{<<"">>, Host, <<"">>}],
Record#pubsub_node{owners = Owners, options = Options}.
%% @spec (Host, From) -> [pubsubNode()]
@@ -107,47 +98,41 @@ get_node({Host, _} = NodeId) ->
%% From = mod_pubsub:jid()
%% @doc <p>Virtual node tree does not handle a node database. Any node is considered
%% as existing. Nodes list can not be determined.</p>
-get_nodes(Host, _From) ->
- get_nodes(Host).
-get_nodes(_Host) ->
- [].
-
%% @spec (Host, Node, From) -> [pubsubNode()]
%% Host = mod_pubsub:host()
%% Node = mod_pubsub:pubsubNode()
%% From = mod_pubsub:jid()
%% @doc <p>Virtual node tree does not handle parent/child. Child list is empty.</p>
-get_parentnodes(_Host, _Node, _From) ->
- [].
-
%% @spec (Host, Node, From) -> [pubsubNode()]
%% Host = mod_pubsub:host()
%% Node = mod_pubsub:pubsubNode()
%% From = mod_pubsub:jid()
%% @doc <p>Virtual node tree does not handle parent/child. Child list is empty.</p>
-get_parentnodes_tree(_Host, _Node, _From) ->
- [].
-
%% @spec (Host, Node, From) -> [pubsubNode()]
%% Host = mod_pubsub:host()
%% Node = mod_pubsub:pubsubNode()
%% From = mod_pubsub:jid()
%% @doc <p>Virtual node tree does not handle parent/child. Child list is empty.</p>
+get_nodes(Host, _From) -> get_nodes(Host).
+
+get_nodes(_Host) -> [].
+
+get_parentnodes(_Host, _Node, _From) -> [].
+
+get_parentnodes_tree(_Host, _Node, _From) -> [].
+
get_subnodes(Host, Node, _From) ->
get_subnodes(Host, Node).
-get_subnodes(_Host, _Node) ->
- [].
-
%% @spec (Host, Node, From) -> [pubsubNode()]
%% Host = mod_pubsub:host()
%% Node = mod_pubsub:pubsubNode()
%% From = mod_pubsub:jid()
%% @doc <p>Virtual node tree does not handle parent/child. Child list is empty.</p>
+
+get_subnodes(_Host, _Node) -> [].
+
get_subnodes_tree(Host, Node, _From) ->
get_subnodes_tree(Host, Node).
-get_subnodes_tree(_Host, _Node) ->
- [].
-
%% @spec (Host, Node, Type, Owner, Options, Parents) -> ok
%% Host = mod_pubsub:host()
%% Node = mod_pubsub:pubsubNode()
@@ -157,13 +142,16 @@ get_subnodes_tree(_Host, _Node) ->
%% @doc <p>No node record is stored on database. Any valid node
%% is considered as already created.</p>
%% <p>default allowed nodes: /home/host/user/any/node/name</p>
-create_node(Host, Node, _Type, _Owner, _Options, _Parents) ->
- {error, {virtual, {Host, Node}}}.
-
%% @spec (Host, Node) -> [mod_pubsub:node()]
%% Host = mod_pubsub:host()
%% Node = mod_pubsub:pubsubNode()
%% @doc <p>Virtual node tree does not handle parent/child.
%% node deletion just affects the corresponding node.</p>
-delete_node(Host, Node) ->
- [get_node(Host, Node)].
+
+get_subnodes_tree(_Host, _Node) -> [].
+
+create_node(Host, Node, _Type, _Owner, _Options,
+ _Parents) ->
+ {error, {virtual, {Host, Node}}}.
+
+delete_node(Host, Node) -> [get_node(Host, Node)].
diff --git a/src/mod_pubsub/pubsub.hrl b/src/mod_pubsub/pubsub.hrl
index 5a943ee44..4f2cc9f38 100644
--- a/src/mod_pubsub/pubsub.hrl
+++ b/src/mod_pubsub/pubsub.hrl
@@ -5,11 +5,13 @@
%%% Erlang Public License along with this software. If not, it can be
%%% retrieved via the world wide web at http://www.erlang.org/.
%%%
+%%%
%%% Software distributed under the License is distributed on an "AS IS"
%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%%% the License for the specific language governing rights and limitations
%%% under the License.
%%%
+%%%
%%% The Initial Developer of the Original Code is ProcessOne.
%%% Portions created by ProcessOne are Copyright 2006-2013, ProcessOne
%%% All Rights Reserved.''
@@ -23,7 +25,8 @@
%% -------------------------------
%% Pubsub constants
--define(ERR_EXTENDED(E,C), mod_pubsub:extended_error(E,C)).
+-define(ERR_EXTENDED(E, C),
+ mod_pubsub:extended_error(E, C)).
%% The actual limit can be configured with mod_pubsub's option max_items_node
-define(MAXITEMS, 10).
@@ -36,9 +39,11 @@
%% Pubsub types
%% @type hostPubsub() = string().
+-type(hostPubsub() :: binary()).
%% <p><tt>hostPubsub</tt> is the name of the PubSub service. For example, it can be
%% <tt>"pubsub.localhost"</tt>.</p>
+-type(hostPEP() :: {binary(), binary(), <<>>}).
%% @type hostPEP() = {User, Server, Resource}
%% User = string()
%% Server = string()
@@ -46,23 +51,31 @@
%% <p>For example, it can be :
%% ```{"bob", "example.org", []}'''.</p>
+-type(host() :: hostPubsub() | hostPEP()).
%% @type host() = hostPubsub() | hostPEP().
+-type(nodeId() :: binary()).
%% @type nodeId() = binary().
%% <p>A node is defined by a list of its ancestors. The last element is the name
%% of the current node. For example:
+%% of the current node. For example:
%% ```<<"/home/localhost/user">>'''</p>
+-type(nodeIdx() :: pos_integer()).
%% @type nodeIdx() = integer().
+-type(itemId() :: binary()).
%% @type itemId() = string().
+-type(subId() :: binary()).
%% @type subId() = string().
+
%% @type payload() = [#xmlelement{} | #xmlcdata{}].
%% @type stanzaError() = #xmlelement{}.
%% Example:
+%% Example:
%% ```{xmlelement, "error",
%% [{"code", Code}, {"type", Type}],
%% [{xmlelement, Condition, [{"xmlns", ?NS_STANZAS}], []}]}'''
@@ -73,12 +86,26 @@
%% [{xmlelement, "affiliations", [],
%% []}]}'''
+-type(nodeOption() ::
+ {Option::atom(),
+ Value::binary() | [binary()] | boolean() | non_neg_integer()
+}).
+
+-type(nodeOptions() :: [NodeOption::mod_pubsub:nodeOption(),...]).
+
%% @type nodeOption() = {Option, Value}
%% Option = atom()
%% Value = term().
%% Example:
%% ```{deliver_payloads, true}'''
+-type(subOption() ::
+ {Option::atom(),
+ Value::binary() | [binary()] | boolean()
+}).
+
+-type(subOptions() :: [SubOption::mod_pubsub:subOption(),...]).
+
%% @type nodeType() = string().
%% <p>The <tt>nodeType</tt> is a string containing the name of the PubSub
%% plugin to use to manage a given node. For example, it can be
@@ -92,15 +119,34 @@
%% LServer = string()
%% LResource = string().
+%-type(ljid() :: {binary(), binary(), binary()}).
%% @type ljid() = {User, Server, Resource}
%% User = string()
%% Server = string()
%% Resource = string().
+-type(affiliation() :: 'none'
+ | 'owner'
+ | 'publisher'
+ %| 'publish-only'
+ | 'member'
+ | 'outcast'
+).
%% @type affiliation() = 'none' | 'owner' | 'publisher' | 'publish-only' | 'member' | 'outcast'.
+-type(subscription() :: 'none'
+ | 'pending'
+ | 'unconfigured'
+ | 'subscribed'
+).
%% @type subscription() = 'none' | 'pending' | 'unconfigured' | 'subscribed'.
+-type(accessModel() :: 'open'
+ | 'presence'
+ | 'roster'
+ | 'authorize'
+ | 'whitelist'
+).
%% @type accessModel() = 'open' | 'presence' | 'roster' | 'authorize' | 'whitelist'.
%% @type pubsubIndex() = {pubsub_index, Index, Last, Free}
@@ -108,11 +154,17 @@
%% Last = integer()
%% Free = [integer()].
%% internal pubsub index table
+-type(publishModel() :: 'publishers'
+ | 'subscribers'
+ | 'open'
+).
+
+
-record(pubsub_index,
{
- index,
- last,
- free
+ index :: atom(),
+ last :: mod_pubsub:nodeIdx(),
+ free :: [mod_pubsub:nodeIdx()]
}).
%% @type pubsubNode() = {pubsub_node, NodeId, Id, Parents, Type, Owners, Options}
@@ -128,12 +180,12 @@
%% <tt>id</tt> can be anything you want.
-record(pubsub_node,
{
- nodeid,
- id,
- parents = [],
- type = "flat",
- owners = [],
- options = []
+ nodeid ,%:: {Host::mod_pubsub:host(), NodeId::mod_pubsub:nodeId()},
+ id ,%:: mod_pubsub:nodeIdx(),
+ parents = [] ,%:: [Parent_NodeId::mod_pubsub:nodeId()],
+ type = <<"flat">> ,%:: binary(),
+ owners = [] ,%:: [Owner::ljid(),...],
+ options = [] %:: mod_pubsub:nodeOptions()
}).
%% @type pubsubState() = {pubsub_state, StateId, Items, Affiliation, Subscriptions}
@@ -143,12 +195,16 @@
%% Subscriptions = [{subscription(), subId()}].
%% <p>This is the format of the <tt>affiliations</tt> table. The type of the
%% table is: <tt>set</tt>,<tt>ram/disc</tt>.</p>
+
+%-record(pubsub_state,
+% {stateid, items = [], affiliation = none,
+% subscriptions = []}).
-record(pubsub_state,
{
- stateid,
- items = [],
- affiliation = 'none',
- subscriptions = []
+ stateid ,%:: {Entity::ljid(), NodeIdx::mod_pubsub:nodeIdx()},
+ items = [] ,%:: [ItemId::mod_pubsub:itemId()],
+ affiliation = 'none' ,%:: mod_pubsub:affiliation(),
+ subscriptions = [] %:: [{mod_pubsub:subscription(), mod_pubsub:subId()}]
}).
%% @type pubsubItem() = {pubsub_item, ItemId, Creation, Modification, Payload}
@@ -158,12 +214,16 @@
%% Payload = payload().
%% <p>This is the format of the <tt>published items</tt> table. The type of the
%% table is: <tt>set</tt>,<tt>disc</tt>,<tt>fragmented</tt>.</p>
+%-record(pubsub_item,
+% {itemid, creation = {unknown, unknown},
+% modification = {unknown, unknown}, payload = []}).
+
-record(pubsub_item,
{
- itemid,
- creation = {'unknown','unknown'},
- modification = {'unknown','unknown'},
- payload = []
+ itemid ,%:: {mod_pubsub:itemId(), mod_pubsub:nodeIdx()},
+ creation = {unknown, unknown} ,%:: {erlang:timestamp(), ljid()},
+ modification = {unknown, unknown} ,%:: {erlang:timestamp(), ljid()},
+ payload = [] %:: mod_pubsub:payload()
}).
%% @type pubsubSubscription() = {pubsub_subscription, SubId, Options}
@@ -171,10 +231,11 @@
%% Options = [nodeOption()].
%% <p>This is the format of the <tt>subscriptions</tt> table. The type of the
%% table is: <tt>set</tt>,<tt>ram/disc</tt>.</p>
+%-record(pubsub_subscription, {subid, options}).
-record(pubsub_subscription,
{
- subid,
- options
+ subid ,%:: mod_pubsub:subId(),
+ options %:: [] | mod_pubsub:subOptions()
}).
%% @type pubsubLastItem() = {pubsub_last_item, NodeId, ItemId, Creation, Payload}
@@ -184,10 +245,13 @@
%% Payload = payload().
%% <p>This is the format of the <tt>last items</tt> table. it stores last item payload
%% for every node</p>
+%-record(pubsub_last_item,
+% {nodeid, itemid, creation, payload}).
+
-record(pubsub_last_item,
{
- nodeid,
- itemid,
- creation,
- payload
+ nodeid ,%:: mod_pubsub:nodeIdx(),
+ itemid ,%:: mod_pubsub:itemId(),
+ creation ,%:: {erlang:timestamp(), ljid()},
+ payload %:: mod_pubsub:payload()
}).
diff --git a/src/mod_pubsub/pubsub_db_odbc.erl b/src/mod_pubsub/pubsub_db_odbc.erl
index 2cafc97c7..ca1318865 100644
--- a/src/mod_pubsub/pubsub_db_odbc.erl
+++ b/src/mod_pubsub/pubsub_db_odbc.erl
@@ -20,119 +20,132 @@
%%% @end
%%% ====================================================================
-module(pubsub_db_odbc).
+
-author("pablo.polvorin@process-one.net").
-include("pubsub.hrl").
--export([add_subscription/1,
- read_subscription/1,
- delete_subscription/1,
- update_subscription/1]).
-
+-export([add_subscription/1, read_subscription/1,
+ delete_subscription/1, update_subscription/1]).
%% TODO: Those -spec lines produce errors in old Erlang versions.
%% They can be enabled again in ejabberd 3.0 because it uses R12B or higher.
%% -spec read_subscription(SubID :: string()) -> {ok, #pubsub_subscription{}} | notfound.
read_subscription(SubID) ->
- case ejabberd_odbc:sql_query_t(
- ["select opt_name, opt_value "
- "from pubsub_subscription_opt "
- "where subid = '", ejabberd_odbc:escape(SubID), "'"]) of
- {selected, ["opt_name", "opt_value"], []} ->
- notfound;
-
- {selected, ["opt_name", "opt_value"], Options} ->
-
- {ok, #pubsub_subscription{subid = SubID,
- options = lists:map(fun subscription_opt_from_odbc/1, Options)}}
- end.
-
-
+ case
+ ejabberd_odbc:sql_query_t([<<"select opt_name, opt_value from pubsub_subscr"
+ "iption_opt where subid = '">>,
+ ejabberd_odbc:escape(SubID), <<"'">>])
+ of
+ {selected, [<<"opt_name">>, <<"opt_value">>], []} ->
+ notfound;
+ {selected, [<<"opt_name">>, <<"opt_value">>],
+ Options} ->
+ {ok,
+ #pubsub_subscription{subid = SubID,
+ options =
+ lists:map(fun subscription_opt_from_odbc/1,
+ Options)}}
+ end.
%% -spec delete_subscription(SubID :: string()) -> ok.
delete_subscription(SubID) ->
- ejabberd_odbc:sql_query_t(["delete from pubsub_subscription_opt "
- "where subid = '", ejabberd_odbc:escape(SubID), "'"]),
- ok.
-
-
%% -spec update_subscription(#pubsub_subscription{}) -> ok .
-update_subscription(#pubsub_subscription{subid = SubId} = Sub) ->
- delete_subscription(SubId),
- add_subscription(Sub).
-
%% -spec add_subscription(#pubsub_subscription{}) -> ok.
-add_subscription(#pubsub_subscription{subid = SubId, options = Opts}) ->
- EscapedSubId = ejabberd_odbc:escape(SubId),
- lists:foreach(fun(Opt) ->
- {OdbcOptName, OdbcOptValue} = subscription_opt_to_odbc(Opt),
- ejabberd_odbc:sql_query_t(
- ["insert into pubsub_subscription_opt(subid, opt_name, opt_value)"
- "values ('", EscapedSubId, "','", OdbcOptName, "','", OdbcOptValue, "')"])
- end, Opts),
- ok.
-
-
-
%% -------------- Internal utilities -----------------------
-subscription_opt_from_odbc({"DELIVER", Value}) ->
- {deliver, odbc_to_boolean(Value)};
-subscription_opt_from_odbc({"DIGEST", Value}) ->
- {digest, odbc_to_boolean(Value)};
-subscription_opt_from_odbc({"DIGEST_FREQUENCY", Value}) ->
- {digest_frequency, odbc_to_integer(Value)};
-subscription_opt_from_odbc({"EXPIRE", Value}) ->
- {expire, odbc_to_timestamp(Value)};
-subscription_opt_from_odbc({"INCLUDE_BODY", Value}) ->
- {include_body, odbc_to_boolean(Value)};
-
+ ejabberd_odbc:sql_query_t([<<"delete from pubsub_subscription_opt "
+ "where subid = '">>,
+ ejabberd_odbc:escape(SubID), <<"'">>]),
+ ok.
+
+update_subscription(#pubsub_subscription{subid =
+ SubId} =
+ Sub) ->
+ delete_subscription(SubId), add_subscription(Sub).
+
+add_subscription(#pubsub_subscription{subid = SubId,
+ options = Opts}) ->
+ EscapedSubId = ejabberd_odbc:escape(SubId),
+ lists:foreach(fun (Opt) ->
+ {OdbcOptName, OdbcOptValue} =
+ subscription_opt_to_odbc(Opt),
+ ejabberd_odbc:sql_query_t([<<"insert into pubsub_subscription_opt(subid, "
+ "opt_name, opt_value)values ('">>,
+ EscapedSubId, <<"','">>,
+ OdbcOptName, <<"','">>,
+ OdbcOptValue, <<"')">>])
+ end,
+ Opts),
+ ok.
+
+subscription_opt_from_odbc({<<"DELIVER">>, Value}) ->
+ {deliver, odbc_to_boolean(Value)};
+subscription_opt_from_odbc({<<"DIGEST">>, Value}) ->
+ {digest, odbc_to_boolean(Value)};
+subscription_opt_from_odbc({<<"DIGEST_FREQUENCY">>,
+ Value}) ->
+ {digest_frequency, odbc_to_integer(Value)};
+subscription_opt_from_odbc({<<"EXPIRE">>, Value}) ->
+ {expire, odbc_to_timestamp(Value)};
+subscription_opt_from_odbc({<<"INCLUDE_BODY">>,
+ Value}) ->
+ {include_body, odbc_to_boolean(Value)};
%%TODO: might be > than 1 show_values value??.
%% need to use compact all in only 1 opt.
-subscription_opt_from_odbc({"SHOW_VALUES", Value}) ->
- {show_values, Value};
-subscription_opt_from_odbc({"SUBSCRIPTION_TYPE", Value}) ->
- {subscription_type, case Value of
- "items" -> items;
- "nodes" -> nodes
- end};
-
-subscription_opt_from_odbc({"SUBSCRIPTION_DEPTH", Value}) ->
- {subscription_depth, case Value of
- "all" -> all;
- N -> odbc_to_integer(N)
- end}.
+subscription_opt_from_odbc({<<"SHOW_VALUES">>,
+ Value}) ->
+ {show_values, Value};
+subscription_opt_from_odbc({<<"SUBSCRIPTION_TYPE">>,
+ Value}) ->
+ {subscription_type,
+ case Value of
+ <<"items">> -> items;
+ <<"nodes">> -> nodes
+ end};
+subscription_opt_from_odbc({<<"SUBSCRIPTION_DEPTH">>,
+ Value}) ->
+ {subscription_depth,
+ case Value of
+ <<"all">> -> all;
+ N -> odbc_to_integer(N)
+ end}.
subscription_opt_to_odbc({deliver, Bool}) ->
- {"DELIVER", boolean_to_odbc(Bool)};
+ {<<"DELIVER">>, boolean_to_odbc(Bool)};
subscription_opt_to_odbc({digest, Bool}) ->
- {"DIGEST", boolean_to_odbc(Bool)};
+ {<<"DIGEST">>, boolean_to_odbc(Bool)};
subscription_opt_to_odbc({digest_frequency, Int}) ->
- {"DIGEST_FREQUENCY", integer_to_odbc(Int)};
+ {<<"DIGEST_FREQUENCY">>, integer_to_odbc(Int)};
subscription_opt_to_odbc({expire, Timestamp}) ->
- {"EXPIRE", timestamp_to_odbc(Timestamp)};
+ {<<"EXPIRE">>, timestamp_to_odbc(Timestamp)};
subscription_opt_to_odbc({include_body, Bool}) ->
- {"INCLUDE_BODY", boolean_to_odbc(Bool)};
+ {<<"INCLUDE_BODY">>, boolean_to_odbc(Bool)};
subscription_opt_to_odbc({show_values, Values}) ->
- {"SHOW_VALUES", Values};
+ {<<"SHOW_VALUES">>, Values};
subscription_opt_to_odbc({subscription_type, Type}) ->
- {"SUBSCRIPTION_TYPE", case Type of
- items -> "items";
- nodes -> "nodes"
- end};
+ {<<"SUBSCRIPTION_TYPE">>,
+ case Type of
+ items -> <<"items">>;
+ nodes -> <<"nodes">>
+ end};
subscription_opt_to_odbc({subscription_depth, Depth}) ->
- {"SUBSCRIPTION_DEPTH", case Depth of
- all -> "all";
- N -> integer_to_odbc(N)
- end}.
+ {<<"SUBSCRIPTION_DEPTH">>,
+ case Depth of
+ all -> <<"all">>;
+ N -> integer_to_odbc(N)
+ end}.
integer_to_odbc(N) ->
- integer_to_list(N).
+ iolist_to_binary(integer_to_list(N)).
+
+boolean_to_odbc(true) -> <<"1">>;
+boolean_to_odbc(false) -> <<"0">>.
-boolean_to_odbc(true) -> "1";
-boolean_to_odbc(false) -> "0".
timestamp_to_odbc(T) -> jlib:now_to_utc_string(T).
+odbc_to_integer(N) -> jlib:binary_to_integer(N).
+
+odbc_to_boolean(B) -> B == <<"1">>.
-odbc_to_integer(N) -> list_to_integer(N).
-odbc_to_boolean(B) -> B == "1".
-odbc_to_timestamp(T) -> jlib:datetime_string_to_timestamp(T).
+odbc_to_timestamp(T) ->
+ jlib:datetime_string_to_timestamp(T).
diff --git a/src/mod_pubsub/pubsub_index.erl b/src/mod_pubsub/pubsub_index.erl
index 5041c28f7..1ff5a1e45 100644
--- a/src/mod_pubsub/pubsub_index.erl
+++ b/src/mod_pubsub/pubsub_index.erl
@@ -5,11 +5,13 @@
%%% Erlang Public License along with this software. If not, it can be
%%% retrieved via the world wide web at http://www.erlang.org/.
%%%
+%%%
%%% Software distributed under the License is distributed on an "AS IS"
%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%%% the License for the specific language governing rights and limitations
%%% under the License.
%%%
+%%%
%%% The Initial Developer of the Original Code is ProcessOne.
%%% Portions created by ProcessOne are Copyright 2006-2013, ProcessOne
%%% All Rights Reserved.''
@@ -27,6 +29,7 @@
%% new/1 and free/2 MUST be called inside a transaction bloc
-module(pubsub_index).
+
-author('christophe.romain@process-one.net').
-include("pubsub.hrl").
@@ -40,26 +43,25 @@ init(_Host, _ServerHost, _Opts) ->
new(Index) ->
case mnesia:read({pubsub_index, Index}) of
- [I] ->
- case I#pubsub_index.free of
- [] ->
- Id = I#pubsub_index.last + 1,
- mnesia:write(I#pubsub_index{last = Id}),
- Id;
- [Id|Free] ->
- mnesia:write(I#pubsub_index{free = Free}),
- Id
- end;
- _ ->
- mnesia:write(#pubsub_index{index = Index, last = 1, free = []}),
- 1
+ [I] ->
+ case I#pubsub_index.free of
+ [] ->
+ Id = I#pubsub_index.last + 1,
+ mnesia:write(I#pubsub_index{last = Id}),
+ Id;
+ [Id | Free] ->
+ mnesia:write(I#pubsub_index{free = Free}), Id
+ end;
+ _ ->
+ mnesia:write(#pubsub_index{index = Index, last = 1,
+ free = []}),
+ 1
end.
free(Index, Id) ->
case mnesia:read({pubsub_index, Index}) of
- [I] ->
- Free = I#pubsub_index.free,
- mnesia:write(I#pubsub_index{free = [Id|Free]});
- _ ->
- ok
+ [I] ->
+ Free = I#pubsub_index.free,
+ mnesia:write(I#pubsub_index{free = [Id | Free]});
+ _ -> ok
end.
diff --git a/src/mod_pubsub/pubsub_odbc.patch b/src/mod_pubsub/pubsub_odbc.patch
index b7c18bacc..986c6fe00 100644
--- a/src/mod_pubsub/pubsub_odbc.patch
+++ b/src/mod_pubsub/pubsub_odbc.patch
@@ -1,222 +1,376 @@
---- mod_pubsub.erl 2012-04-11 16:47:33.620900390 +0200
-+++ mod_pubsub_odbc.erl 2012-04-11 16:47:53.390899087 +0200
-@@ -42,7 +42,7 @@
+--- mod_pubsub.erl 2013-03-03 23:32:53.669953265 +0100
++++ mod_pubsub_odbc.erl 2013-03-03 23:37:10.128953065 +0100
+@@ -41,7 +41,7 @@
%%% 6.2.3.1, 6.2.3.5, and 6.3. For information on subscription leases see
%%% XEP-0060 section 12.18.
--module(mod_pubsub).
+-module(mod_pubsub_odbc).
+
-author('christophe.romain@process-one.net').
- -version('1.13-0').
-@@ -54,9 +54,9 @@
- -include("jlib.hrl").
+@@ -59,11 +59,11 @@
+
-include("pubsub.hrl").
---define(STDTREE, "tree").
---define(STDNODE, "flat").
---define(PEPNODE, "pep").
-+-define(STDTREE, "tree_odbc").
-+-define(STDNODE, "flat_odbc").
-+-define(PEPNODE, "pep_odbc").
+--define(STDTREE, <<"tree">>).
++-define(STDTREE, <<"tree_odbc">>).
+
+--define(STDNODE, <<"flat">>).
++-define(STDNODE, <<"flat_odbc">>).
+
+--define(PEPNODE, <<"pep">>).
++-define(PEPNODE, <<"pep_odbc">>).
%% exports for hooks
- -export([presence_probe/3,
-@@ -103,7 +103,7 @@
- string_to_affiliation/1,
- extended_error/2,
- extended_error/3,
-- rename_default_nodeplugin/0
-+ escape/1
- ]).
+ -export([presence_probe/3, caps_update/3,
+@@ -98,7 +98,7 @@
+ -export([subscription_to_string/1, affiliation_to_string/1,
+ string_to_subscription/1, string_to_affiliation/1,
+ extended_error/2, extended_error/3,
+- rename_default_nodeplugin/0]).
++ escape/1]).
%% API and gen_server callbacks
-@@ -122,7 +122,7 @@
- -export([send_loop/1
- ]).
+ -export([start_link/2, start/2, stop/1, init/1,
+@@ -108,7 +108,7 @@
+ %% calls for parallel sending of last items
+ -export([send_loop/1]).
--define(PROCNAME, ejabberd_mod_pubsub).
+-define(PROCNAME, ejabberd_mod_pubsub_odbc).
+
-define(LOOPNAME, ejabberd_mod_pubsub_loop).
- -define(PLUGIN_PREFIX, "node_").
- -define(TREE_PREFIX, "nodetree_").
-@@ -217,8 +217,6 @@
- ok
+
+@@ -333,8 +333,6 @@
+ false -> ok
end,
ejabberd_router:register_route(Host),
- update_node_database(Host, ServerHost),
- update_state_database(Host, ServerHost),
- put(server_host, ServerHost), % not clean, but needed to plug hooks at any location
+ put(server_host, ServerHost),
init_nodes(Host, ServerHost, NodeTree, Plugins),
- State = #state{host = Host,
-@@ -283,207 +281,14 @@
+ State = #state{host = Host, server_host = ServerHost,
+@@ -394,359 +392,14 @@
+ ok.
init_nodes(Host, ServerHost, _NodeTree, Plugins) ->
- %% TODO, this call should be done plugin side
-- case lists:member("hometree", Plugins) of
-+ case lists:member("hometree_odbc", Plugins) of
- true ->
-- create_node(Host, ServerHost, string_to_node("/home"), service_jid(Host), "hometree"),
-- create_node(Host, ServerHost, string_to_node("/home/"++ServerHost), service_jid(Host), "hometree");
-+ create_node(Host, ServerHost, string_to_node("/home"), service_jid(Host), "hometree_odbc"),
-+ create_node(Host, ServerHost, string_to_node("/home/"++ServerHost), service_jid(Host), "hometree_odbc");
- false ->
- ok
+- case lists:member(<<"hometree">>, Plugins) of
++ case lists:member(<<"hometree_odbc">>, Plugins) of
+ true ->
+- create_node(Host, ServerHost, <<"/home">>, service_jid(Host), <<"hometree">>),
++ create_node(Host, ServerHost, <<"/home">>, service_jid(Host), <<"hometree_odbc">>),
+ create_node(Host, ServerHost, <<"/home/", ServerHost/binary>>, service_jid(Host),
+- <<"hometree">>);
++ <<"hometree_odbc">>);
+ false -> ok
end.
-update_node_database(Host, ServerHost) ->
- mnesia:del_table_index(pubsub_node, type),
- mnesia:del_table_index(pubsub_node, parentid),
- case catch mnesia:table_info(pubsub_node, attributes) of
-- [host_node, host_parent, info] ->
-- ?INFO_MSG("upgrade node pubsub tables",[]),
-- F = fun() ->
-- {Result, LastIdx} = lists:foldl(
-- fun({pubsub_node, NodeId, ParentId, {nodeinfo, Items, Options, Entities}}, {RecList, NodeIdx}) ->
-- ItemsList =
-- lists:foldl(
-- fun({item, IID, Publisher, Payload}, Acc) ->
-- C = {unknown, Publisher},
-- M = {now(), Publisher},
-- mnesia:write(
-- #pubsub_item{itemid = {IID, NodeIdx},
-- creation = C,
-- modification = M,
-- payload = Payload}),
-- [{Publisher, IID} | Acc]
-- end, [], Items),
-- Owners =
-- dict:fold(
-- fun(JID, {entity, Aff, Sub}, Acc) ->
-- UsrItems =
-- lists:foldl(
-- fun({P, I}, IAcc) ->
-- case P of
-- JID -> [I | IAcc];
-- _ -> IAcc
-- end
-- end, [], ItemsList),
-- mnesia:write({pubsub_state,
-- {JID, NodeIdx},
-- UsrItems,
-- Aff,
-- Sub}),
-- case Aff of
-- owner -> [JID | Acc];
-- _ -> Acc
-- end
-- end, [], Entities),
-- mnesia:delete({pubsub_node, NodeId}),
-- {[#pubsub_node{nodeid = NodeId,
-- id = NodeIdx,
-- parents = [element(2, ParentId)],
-- owners = Owners,
-- options = Options} |
-- RecList], NodeIdx + 1}
-- end, {[], 1},
-- mnesia:match_object(
-- {pubsub_node, {Host, '_'}, '_', '_'})),
-- mnesia:write(#pubsub_index{index = node, last = LastIdx, free = []}),
-- Result
-- end,
-- {atomic, NewRecords} = mnesia:transaction(F),
-- {atomic, ok} = mnesia:delete_table(pubsub_node),
-- {atomic, ok} = mnesia:create_table(pubsub_node,
-- [{disc_copies, [node()]},
-- {attributes, record_info(fields, pubsub_node)}]),
-- FNew = fun() -> lists:foreach(fun(Record) ->
-- mnesia:write(Record)
-- end, NewRecords)
-- end,
-- case mnesia:transaction(FNew) of
-- {atomic, Result} ->
-- ?INFO_MSG("Pubsub node tables updated correctly: ~p", [Result]);
-- {aborted, Reason} ->
-- ?ERROR_MSG("Problem updating Pubsub node tables:~n~p", [Reason])
-- end;
-- [nodeid, parentid, type, owners, options] ->
-- F = fun({pubsub_node, NodeId, {_, Parent}, Type, Owners, Options}) ->
-- #pubsub_node{
-- nodeid = NodeId,
-- id = 0,
-- parents = [Parent],
-- type = Type,
-- owners = Owners,
-- options = Options}
-- end,
-- mnesia:transform_table(pubsub_node, F, [nodeid, id, parents, type, owners, options]),
-- FNew = fun() ->
-- LastIdx = lists:foldl(fun(#pubsub_node{nodeid = NodeId} = PubsubNode, NodeIdx) ->
-- mnesia:write(PubsubNode#pubsub_node{id = NodeIdx}),
-- lists:foreach(fun(#pubsub_state{stateid = StateId} = State) ->
-- {JID, _} = StateId,
-- mnesia:delete({pubsub_state, StateId}),
-- mnesia:write(State#pubsub_state{stateid = {JID, NodeIdx}})
-- end, mnesia:match_object(#pubsub_state{stateid = {'_', NodeId}, _ = '_'})),
-- lists:foreach(fun(#pubsub_item{itemid = ItemId} = Item) ->
-- {IID, _} = ItemId,
-- {M1, M2} = Item#pubsub_item.modification,
-- {C1, C2} = Item#pubsub_item.creation,
-- mnesia:delete({pubsub_item, ItemId}),
-- mnesia:write(Item#pubsub_item{itemid = {IID, NodeIdx},
-- modification = {M2, M1},
-- creation = {C2, C1}})
-- end, mnesia:match_object(#pubsub_item{itemid = {'_', NodeId}, _ = '_'})),
-- NodeIdx + 1
-- end, 1, mnesia:match_object(
-- {pubsub_node, {Host, '_'}, '_', '_', '_', '_', '_'})
-- ++ mnesia:match_object(
-- {pubsub_node, {{'_', ServerHost, '_'}, '_'}, '_', '_', '_', '_', '_'})),
-- mnesia:write(#pubsub_index{index = node, last = LastIdx, free = []})
-- end,
-- case mnesia:transaction(FNew) of
-- {atomic, Result} ->
-- rename_default_nodeplugin(),
-- ?INFO_MSG("Pubsub node tables updated correctly: ~p", [Result]);
-- {aborted, Reason} ->
-- ?ERROR_MSG("Problem updating Pubsub node tables:~n~p", [Reason])
-- end;
-- [nodeid, id, parent, type, owners, options] ->
-- F = fun({pubsub_node, NodeId, Id, Parent, Type, Owners, Options}) ->
-- #pubsub_node{
-- nodeid = NodeId,
-- id = Id,
-- parents = [Parent],
-- type = Type,
-- owners = Owners,
-- options = Options}
-- end,
-- mnesia:transform_table(pubsub_node, F, [nodeid, id, parents, type, owners, options]),
-- rename_default_nodeplugin();
-- _ ->
-- ok
+- [host_node, host_parent, info] ->
+- ?INFO_MSG("upgrade node pubsub tables", []),
+- F = fun () ->
+- {Result, LastIdx} = lists:foldl(fun ({pubsub_node,
+- NodeId, ParentId,
+- {nodeinfo, Items,
+- Options,
+- Entities}},
+- {RecList,
+- NodeIdx}) ->
+- ItemsList =
+- lists:foldl(fun
+- ({item,
+- IID,
+- Publisher,
+- Payload},
+- Acc) ->
+- C =
+- {unknown,
+- Publisher},
+- M =
+- {now(),
+- Publisher},
+- mnesia:write(#pubsub_item{itemid
+- =
+- {IID,
+- NodeIdx},
+- creation
+- =
+- C,
+- modification
+- =
+- M,
+- payload
+- =
+- Payload}),
+- [{Publisher,
+- IID}
+- | Acc]
+- end,
+- [],
+- Items),
+- Owners =
+- dict:fold(fun
+- (JID,
+- {entity,
+- Aff,
+- Sub},
+- Acc) ->
+- UsrItems =
+- lists:foldl(fun
+- ({P,
+- I},
+- IAcc) ->
+- case
+- P
+- of
+- JID ->
+- [I
+- | IAcc];
+- _ ->
+- IAcc
+- end
+- end,
+- [],
+- ItemsList),
+- mnesia:write({pubsub_state,
+- {JID,
+- NodeIdx},
+- UsrItems,
+- Aff,
+- Sub}),
+- case
+- Aff
+- of
+- owner ->
+- [JID
+- | Acc];
+- _ ->
+- Acc
+- end
+- end,
+- [],
+- Entities),
+- mnesia:delete({pubsub_node,
+- NodeId}),
+- {[#pubsub_node{nodeid
+- =
+- NodeId,
+- id
+- =
+- NodeIdx,
+- parents
+- =
+- [element(2,
+- ParentId)],
+- owners
+- =
+- Owners,
+- options
+- =
+- Options}
+- | RecList],
+- NodeIdx + 1}
+- end,
+- {[], 1},
+- mnesia:match_object({pubsub_node,
+- {Host,
+- '_'},
+- '_',
+- '_'})),
+- mnesia:write(#pubsub_index{index = node, last = LastIdx,
+- free = []}),
+- Result
+- end,
+- {atomic, NewRecords} = mnesia:transaction(F),
+- {atomic, ok} = mnesia:delete_table(pubsub_node),
+- {atomic, ok} = mnesia:create_table(pubsub_node,
+- [{disc_copies, [node()]},
+- {attributes,
+- record_info(fields,
+- pubsub_node)}]),
+- FNew = fun () ->
+- lists:foreach(fun (Record) -> mnesia:write(Record) end,
+- NewRecords)
+- end,
+- case mnesia:transaction(FNew) of
+- {atomic, Result} ->
+- ?INFO_MSG("Pubsub node tables updated correctly: ~p",
+- [Result]);
+- {aborted, Reason} ->
+- ?ERROR_MSG("Problem updating Pubsub node tables:~n~p",
+- [Reason])
+- end;
+- [nodeid, parentid, type, owners, options] ->
+- F = fun ({pubsub_node, NodeId, {_, Parent}, Type,
+- Owners, Options}) ->
+- #pubsub_node{nodeid = NodeId, id = 0,
+- parents = [Parent], type = Type,
+- owners = Owners, options = Options}
+- end,
+- mnesia:transform_table(pubsub_node, F,
+- [nodeid, id, parents, type, owners, options]),
+- FNew = fun () ->
+- LastIdx = lists:foldl(fun (#pubsub_node{nodeid =
+- NodeId} =
+- PubsubNode,
+- NodeIdx) ->
+- mnesia:write(PubsubNode#pubsub_node{id
+- =
+- NodeIdx}),
+- lists:foreach(fun
+- (#pubsub_state{stateid
+- =
+- StateId} =
+- State) ->
+- {JID,
+- _} =
+- StateId,
+- mnesia:delete({pubsub_state,
+- StateId}),
+- mnesia:write(State#pubsub_state{stateid
+- =
+- {JID,
+- NodeIdx}})
+- end,
+- mnesia:match_object(#pubsub_state{stateid
+- =
+- {'_',
+- NodeId},
+- _
+- =
+- '_'})),
+- lists:foreach(fun
+- (#pubsub_item{itemid
+- =
+- ItemId} =
+- Item) ->
+- {IID,
+- _} =
+- ItemId,
+- {M1,
+- M2} =
+- Item#pubsub_item.modification,
+- {C1,
+- C2} =
+- Item#pubsub_item.creation,
+- mnesia:delete({pubsub_item,
+- ItemId}),
+- mnesia:write(Item#pubsub_item{itemid
+- =
+- {IID,
+- NodeIdx},
+- modification
+- =
+- {M2,
+- M1},
+- creation
+- =
+- {C2,
+- C1}})
+- end,
+- mnesia:match_object(#pubsub_item{itemid
+- =
+- {'_',
+- NodeId},
+- _
+- =
+- '_'})),
+- NodeIdx + 1
+- end,
+- 1,
+- mnesia:match_object({pubsub_node,
+- {Host, '_'},
+- '_', '_',
+- '_', '_',
+- '_'})
+- ++
+- mnesia:match_object({pubsub_node,
+- {{'_',
+- ServerHost,
+- '_'},
+- '_'},
+- '_', '_',
+- '_', '_',
+- '_'})),
+- mnesia:write(#pubsub_index{index = node,
+- last = LastIdx, free = []})
+- end,
+- case mnesia:transaction(FNew) of
+- {atomic, Result} ->
+- rename_default_nodeplugin(),
+- ?INFO_MSG("Pubsub node tables updated correctly: ~p",
+- [Result]);
+- {aborted, Reason} ->
+- ?ERROR_MSG("Problem updating Pubsub node tables:~n~p",
+- [Reason])
+- end;
+- [nodeid, id, parent, type, owners, options] ->
+- F = fun ({pubsub_node, NodeId, Id, Parent, Type, Owners,
+- Options}) ->
+- #pubsub_node{nodeid = NodeId, id = Id,
+- parents = [Parent], type = Type,
+- owners = Owners, options = Options}
+- end,
+- mnesia:transform_table(pubsub_node, F,
+- [nodeid, id, parents, type, owners, options]),
+- rename_default_nodeplugin();
+- _ -> ok
- end,
-- mnesia:transaction(fun() ->
-- case catch mnesia:first(pubsub_node) of
-- {_, L} when is_list(L) ->
-- lists:foreach(
-- fun({H, N}) when is_list(N) ->
-- [Node] = mnesia:read({pubsub_node, {H, N}}),
-- Type = Node#pubsub_node.type,
-- BN = element(2, node_call(Type, path_to_node, [N])),
-- BP = case [element(2, node_call(Type, path_to_node, [P])) || P <- Node#pubsub_node.parents] of
-- [<<>>] -> [];
-- Parents -> Parents
-- end,
-- mnesia:write(Node#pubsub_node{nodeid={H, BN}, parents=BP}),
-- mnesia:delete({pubsub_node, {H, N}});
-- (_) ->
-- ok
-- end, mnesia:all_keys(pubsub_node));
-- _ ->
-- ok
-- end
-- end).
+- mnesia:transaction(fun () ->
+- case catch mnesia:first(pubsub_node) of
+- {_, L} when is_binary(L) ->
+- lists:foreach(fun ({H, N})
+- when is_binary(N) ->
+- [Node] =
+- mnesia:read({pubsub_node,
+- {H,
+- N}}),
+- Type =
+- Node#pubsub_node.type,
+- BN = element(2,
+- node_call(Type,
+- path_to_node,
+- [N])),
+- BP = case [element(2,
+- node_call(Type,
+- path_to_node,
+- [P]))
+- || P
+- <- Node#pubsub_node.parents]
+- of
+- [<<>>] -> [];
+- Parents ->
+- Parents
+- end,
+- mnesia:write(Node#pubsub_node{nodeid
+- =
+- {H,
+- BN},
+- parents
+- =
+- BP}),
+- mnesia:delete({pubsub_node,
+- {H,
+- N}});
+- (_) -> ok
+- end,
+- mnesia:all_keys(pubsub_node));
+- _ -> ok
+- end
+- end).
-
-rename_default_nodeplugin() ->
-- lists:foreach(fun(Node) ->
-- mnesia:dirty_write(Node#pubsub_node{type = "hometree"})
-- end, mnesia:dirty_match_object(#pubsub_node{type = "default", _ = '_'})).
+- lists:foreach(fun (Node) ->
+- mnesia:dirty_write(Node#pubsub_node{type =
+- <<"hometree">>})
+- end,
+- mnesia:dirty_match_object(#pubsub_node{type =
+- <<"default">>,
+- _ = '_'})).
-
-update_state_database(_Host, _ServerHost) ->
- case catch mnesia:table_info(pubsub_state, attributes) of
@@ -259,293 +413,367 @@
-
send_loop(State) ->
receive
- {presence, JID, Pid} ->
-@@ -494,17 +299,15 @@
- %% for each node From is subscribed to
- %% and if the node is so configured, send the last published item to From
- lists:foreach(fun(PType) ->
-- {result, Subscriptions} = node_action(Host, PType, get_entity_subscriptions, [Host, JID]),
-+ Subscriptions = case catch node_action(Host, PType, get_entity_subscriptions_for_send_last, [Host, JID]) of
-+ {result, S} -> S;
-+ _ -> []
-+ end,
- lists:foreach(
- fun({Node, subscribed, _, SubJID}) ->
- if (SubJID == LJID) or (SubJID == BJID) ->
-- #pubsub_node{nodeid = {H, N}, type = Type, id = NodeId, options = Options} = Node,
-- case get_option(Options, send_last_published_item) of
-- on_sub_and_presence ->
-- send_items(H, N, NodeId, Type, LJID, last);
-- _ ->
-- ok
-- end;
-+ #pubsub_node{nodeid = {H, N}, type = Type, id = NodeId} = Node,
-+ send_items(H, N, NodeId, Type, LJID, last);
- true ->
- % resource not concerned about that subscription
- ok
-@@ -623,7 +426,8 @@
- disco_identity(_Host, <<>>, _From) ->
- [{xmlelement, "identity", [{"category", "pubsub"}, {"type", "pep"}], []}];
+ {presence, JID, Pid} ->
+@@ -755,11 +408,13 @@
+ LJID = jlib:jid_tolower(JID),
+ BJID = jlib:jid_remove_resource(LJID),
+ lists:foreach(fun (PType) ->
+- {result, Subscriptions} = node_action(Host,
++ {result, Subscriptions} = case catch node_action(Host,
+ PType,
+- get_entity_subscriptions,
+- [Host,
+- JID]),
++ get_entity_subscriptions_for_send_last,
++ [Host, JID]) of
++ {result, S} -> S;
++ _ -> []
++ end,
+ lists:foreach(fun ({Node, subscribed, _,
+ SubJID}) ->
+ if (SubJID == LJID) or
+@@ -771,24 +426,14 @@
+ type =
+ Type,
+ id =
+- NodeId,
+- options
+- =
+- Options} =
++ NodeId} =
+ Node,
+- case
+- get_option(Options,
+- send_last_published_item)
+- of
+- on_sub_and_presence ->
+- send_items(H,
++ send_items(H,
+ N,
+ NodeId,
+ Type,
+ LJID,
+ last);
+- _ -> ok
+- end;
+ true ->
+ % resource not concerned about that subscription
+ ok
+@@ -979,7 +624,8 @@
+ children = []}];
disco_identity(Host, Node, From) ->
-- Action = fun(#pubsub_node{id = Idx, type = Type, options = Options, owners = Owners}) ->
-+ Action = fun(#pubsub_node{id = Idx, type = Type, options = Options}) ->
-+ Owners = node_owners_call(Type, Idx),
- case get_allowed_items_call(Host, Idx, From, Type, Options, Owners) of
- {result, _} ->
- {result, [{xmlelement, "identity", [{"category", "pubsub"}, {"type", "pep"}], []},
-@@ -658,7 +462,8 @@
- [?NS_PUBSUB
- | [?NS_PUBSUB++"#"++Feature || Feature <- features("pep")]];
+ Action = fun (#pubsub_node{id = Idx, type = Type,
+- options = Options, owners = Owners}) ->
++ options = Options}) ->
++ Owners = node_owners_call(Type, Idx),
+ case get_allowed_items_call(Host, Idx, From, Type, Options, Owners) of
+ {result, _} ->
+ {result,
+@@ -1031,7 +677,8 @@
+ || Feature <- features(<<"pep">>)]];
disco_features(Host, Node, From) ->
-- Action = fun(#pubsub_node{id = Idx, type = Type, options = Options, owners = Owners}) ->
-+ Action = fun(#pubsub_node{id = Idx, type = Type, options = Options}) ->
-+ Owners = node_owners_call(Type, Idx),
- case get_allowed_items_call(Host, Idx, From, Type, Options, Owners) of
- {result, _} ->
- {result, [?NS_PUBSUB
-@@ -683,7 +488,8 @@
- Acc.
-
+ Action = fun (#pubsub_node{id = Idx, type = Type,
+- options = Options, owners = Owners}) ->
++ options = Options}) ->
++ Owners = node_owners_call(Type, Idx),
+ case get_allowed_items_call(Host, Idx, From, Type, Options, Owners) of
+ {result, _} ->
+ {result,
+@@ -1076,9 +723,9 @@
+ ).
disco_items(Host, <<>>, From) ->
-- Action = fun(#pubsub_node{nodeid ={_, NodeID}, options = Options, type = Type, id = Idx, owners = Owners}, Acc) ->
-+ Action = fun(#pubsub_node{nodeid ={_, NodeID}, options = Options, type = Type, id = Idx}, Acc) ->
-+ Owners = node_owners_call(Type, Idx),
- case get_allowed_items_call(Host, Idx, From, Type, Options, Owners) of
- {result, _} ->
- [{xmlelement, "item",
-@@ -701,13 +507,14 @@
- _ -> Acc
- end
- end,
+ Action = fun (#pubsub_node{nodeid = {_, NodeID},
+- options = Options, type = Type, id = Idx,
+- owners = Owners},
++ options = Options, type = Type, id = Idx},
+ Acc) ->
++ Owners = node_owners_call(Type, Idx),
+ case get_allowed_items_call(Host, Idx, From, Type, Options, Owners) of
+ {result, _} ->
+ [#xmlel{name = <<"item">>,
+@@ -1099,13 +746,14 @@
+ _ -> Acc
+ end
+ end,
- case transaction(Host, Action, sync_dirty) of
+ case transaction_on_nodes(Host, Action, sync_dirty) of
- {result, Items} -> Items;
- _ -> []
+ {result, Items} -> Items;
+ _ -> []
end;
-
disco_items(Host, Node, From) ->
-- Action = fun(#pubsub_node{id = Idx, type = Type, options = Options, owners = Owners}) ->
-+ Action = fun(#pubsub_node{id = Idx, type = Type, options = Options}) ->
-+ Owners = node_owners_call(Type, Idx),
- case get_allowed_items_call(Host, Idx, From, Type, Options, Owners) of
- {result, Items} ->
- {result, [{xmlelement, "item",
-@@ -793,10 +600,10 @@
- lists:foreach(fun(PType) ->
- {result, Subscriptions} = node_action(Host, PType, get_entity_subscriptions, [Host, Entity]),
- lists:foreach(fun
-- ({#pubsub_node{options = Options, owners = Owners, id = NodeId}, subscribed, _, JID}) ->
-+ ({#pubsub_node{options = Options, id = NodeId}, subscribed, _, JID}) ->
- case get_option(Options, access_model) of
- presence ->
-- case lists:member(BJID, Owners) of
-+ case lists:member(BJID, node_owners(Host, PType, NodeId)) of
- true ->
- node_action(Host, PType, unsubscribe_node, [NodeId, Entity, JID, all]);
- false ->
-@@ -964,7 +771,8 @@
- sub_el = SubEl} = IQ ->
- {xmlelement, _, QAttrs, _} = SubEl,
- Node = xml:get_attr_s("node", QAttrs),
-- Res = case iq_disco_items(Host, Node, From) of
-+ Rsm = jlib:rsm_decode(IQ),
-+ Res = case iq_disco_items(Host, Node, From, Rsm) of
- {result, IQRes} ->
- jlib:iq_to_xml(
- IQ#iq{type = result,
-@@ -1077,7 +885,7 @@
- [] ->
- ["leaf"]; %% No sub-nodes: it's a leaf node
- _ ->
-- case node_call(Type, get_items, [NodeId, From]) of
-+ case node_call(Type, get_items, [NodeId, From, none]) of
- {result, []} -> ["collection"];
- {result, _} -> ["leaf", "collection"];
- _ -> []
-@@ -1093,8 +901,9 @@
- [];
- true ->
- [{xmlelement, "feature", [{"var", ?NS_PUBSUB}], []} |
-- lists:map(fun(T) ->
-- {xmlelement, "feature", [{"var", ?NS_PUBSUB++"#"++T}], []}
-+ lists:map(fun
-+ ("rsm")-> {xmlelement, "feature", [{"var", ?NS_RSM}], []};
-+ (T) -> {xmlelement, "feature", [{"var", ?NS_PUBSUB++"#"++T}], []}
- end, features(Type))]
- end,
- %% TODO: add meta-data info (spec section 5.4)
-@@ -1123,8 +932,9 @@
- {xmlelement, "feature", [{"var", ?NS_PUBSUB}], []},
- {xmlelement, "feature", [{"var", ?NS_COMMANDS}], []},
- {xmlelement, "feature", [{"var", ?NS_VCARD}], []}] ++
-- lists:map(fun(Feature) ->
-- {xmlelement, "feature", [{"var", ?NS_PUBSUB++"#"++Feature}], []}
+ Action = fun (#pubsub_node{id = Idx, type = Type,
+- options = Options, owners = Owners}) ->
++ options = Options}) ->
++ Owners = node_owners_call(Type, Idx),
+ case get_allowed_items_call(Host, Idx, From, Type,
+ Options, Owners)
+ of
+@@ -1209,9 +857,6 @@
+ lists:foreach(fun ({#pubsub_node{options
+ =
+ Options,
+- owners
+- =
+- Owners,
+ id =
+ NodeId},
+ subscribed, _,
+@@ -1223,7 +868,7 @@
+ presence ->
+ case
+ lists:member(BJID,
+- Owners)
++ node_owners(Host, PType, NodeId))
+ of
+ true ->
+ node_action(Host,
+@@ -1442,7 +1087,8 @@
+ IQ ->
+ #xmlel{attrs = QAttrs} = SubEl,
+ Node = xml:get_attr_s(<<"node">>, QAttrs),
+- Res = case iq_disco_items(Host, Node, From) of
++ Rsm = jlib:rsm_decode(IQ),
++ Res = case iq_disco_items(Host, Node, From, Rsm) of
+ {result, IQRes} ->
+ jlib:iq_to_xml(IQ#iq{type = result,
+ sub_el =
+@@ -1569,7 +1215,7 @@
+ % [] ->
+ % [<<"leaf">>]; %% No sub-nodes: it's a leaf node
+ % _ ->
+-% case node_call(Type, get_items, [NodeId, From]) of
++% case node_call(Type, get_items, [NodeId, From, none]) of
+ % {result, []} -> [<<"collection">>];
+ % {result, _} -> [<<"leaf">>, <<"collection">>];
+ % _ -> []
+@@ -1591,7 +1237,11 @@
+ % [#xmlel{name = <<"feature">>,
+ % attrs = [{<<"var">>, ?NS_PUBSUB}],
+ % children = []}
+-% | lists:map(fun (T) ->
++% | lists:map(fun
++% (<<"rsm">>)->
++% #xmlel{name = <<"feature">>,
++% attrs = [{<<"var">>, ?NS_RSM}]};
++% (T) ->
+ % #xmlel{name = <<"feature">>,
+ % attrs =
+ % [{<<"var">>,
+@@ -1616,7 +1266,7 @@
+ [] -> [<<"leaf">>];
+ _ ->
+ case node_call(Type, get_items,
+- [NodeId, From])
++ [NodeId, From, none])
+ of
+ {result, []} ->
+ [<<"collection">>];
+@@ -1638,7 +1288,11 @@
+ F = [#xmlel{name = <<"feature">>,
+ attrs = [{<<"var">>, ?NS_PUBSUB}],
+ children = []}
+- | lists:map(fun (T) ->
++ | lists:map(fun
++ (<<"rsm">>)->
++ #xmlel{name = <<"feature">>,
++ attrs = [{<<"var">>, ?NS_RSM}]};
++ (T) ->
+ #xmlel{name = <<"feature">>,
+ attrs =
+ [{<<"var">>,
+@@ -1682,7 +1336,11 @@
+ #xmlel{name = <<"feature">>,
+ attrs = [{<<"var">>, ?NS_VCARD}], children = []}]
+ ++
+- lists:map(fun (Feature) ->
+ lists:map(fun
-+ ("rsm")-> {xmlelement, "feature", [{"var", ?NS_RSM}], []};
-+ (T) -> {xmlelement, "feature", [{"var", ?NS_PUBSUB++"#"++T}], []}
- end, features(Host, Node))};
- <<?NS_COMMANDS>> ->
- command_disco_info(Host, Node, From);
-@@ -1134,7 +944,7 @@
- node_disco_info(Host, Node, From)
++ (<<"rsm">>)->
++ #xmlel{name = <<"feature">>,
++ attrs = [{<<"var">>, ?NS_RSM}]};
++ (Feature) ->
+ #xmlel{name = <<"feature">>,
+ attrs =
+ [{<<"var">>, <<(?NS_PUBSUB)/binary, "#", Feature/binary>>}],
+@@ -1695,14 +1353,15 @@
+ _ -> node_disco_info(Host, Node, From)
end.
--iq_disco_items(Host, [], From) ->
-+iq_disco_items(Host, [], From, _RSM) ->
- case tree_action(Host, get_subnodes, [Host, <<>>, From]) of
- Nodes when is_list(Nodes) ->
- {result, lists:map(
-@@ -1151,23 +961,24 @@
- Other ->
- Other
- end;
+--spec(iq_disco_items/3 ::
++-spec(iq_disco_items/4 ::
+ (
+ Host :: mod_pubsub:host(),
+ NodeId :: <<>> | mod_pubsub:nodeId(),
+- From :: jid())
++ From :: jid(),
++ Rsm :: any())
+ -> {result, [xmlel()]}
+ ).
+-iq_disco_items(Host, <<>>, From) ->
++iq_disco_items(Host, <<>>, From, _RSM) ->
+ {result,
+ lists:map(fun (#pubsub_node{nodeid = {_, SubNode},
+ options = Options}) ->
+@@ -1739,7 +1398,7 @@
+ % Nodes)};
+ % Other -> Other
+ % end;
-iq_disco_items(Host, ?NS_COMMANDS, _From) ->
+iq_disco_items(Host, ?NS_COMMANDS, _From, _RSM) ->
- %% TODO: support localization of this string
- CommandItems = [{xmlelement, "item", [{"jid", Host}, {"node", ?NS_PUBSUB_GET_PENDING}, {"name", "Get Pending"}], []}],
+ CommandItems = [#xmlel{name = <<"item">>,
+ attrs =
+ [{<<"jid">>, Host},
+@@ -1747,22 +1406,19 @@
+ {<<"name">>, <<"Get Pending">>}],
+ children = []}],
{result, CommandItems};
-iq_disco_items(_Host, ?NS_PUBSUB_GET_PENDING, _From) ->
+iq_disco_items(_Host, ?NS_PUBSUB_GET_PENDING, _From, _RSM) ->
- CommandItems = [],
- {result, CommandItems};
+ CommandItems = [], {result, CommandItems};
-iq_disco_items(Host, Item, From) ->
+iq_disco_items(Host, Item, From, RSM) ->
- case string:tokens(Item, "!") of
- [_SNode, _ItemID] ->
- {result, []};
- [SNode] ->
- Node = string_to_node(SNode),
-- Action = fun(#pubsub_node{id = Idx, type = Type, options = Options, owners = Owners}) ->
-- NodeItems = case get_allowed_items_call(Host, Idx, From, Type, Options, Owners) of
-+ Action = fun(#pubsub_node{id = Idx, type = Type, options = Options}) ->
-+ Owners = node_owners_call(Type, Idx),
-+ {NodeItems, RsmOut} = case get_allowed_items_call(Host, Idx, From, Type, Options, Owners, RSM) of
- {result, R} -> R;
-- _ -> []
-+ _ -> {[], none}
- end,
- Nodes = lists:map(
- fun(#pubsub_node{nodeid = {_, SubNode}, options = SubOptions}) ->
-@@ -1185,7 +996,7 @@
- {result, Name} = node_call(Type, get_item_name, [Host, Node, RN]),
- {xmlelement, "item", [{"jid", Host}, {"name", Name}], []}
- end, NodeItems),
-- {result, Nodes ++ Items}
-+ {result, Nodes ++ Items ++ jlib:rsm_encode(RsmOut)}
- end,
- case transaction(Host, Node, Action, sync_dirty) of
- {result, {_, Result}} -> {result, Result};
-@@ -1296,7 +1107,8 @@
- (_, Acc) ->
- Acc
- end, [], xml:remove_cdata(Els)),
-- get_items(Host, Node, From, SubId, MaxItems, ItemIDs);
-+ RSM = jlib:rsm_decode(SubEl),
-+ get_items(Host, Node, From, SubId, MaxItems, ItemIDs, RSM);
- {get, "subscriptions"} ->
- get_subscriptions(Host, Node, From, Plugins);
- {get, "affiliations"} ->
-@@ -1319,7 +1131,9 @@
-
+ case str:tokens(Item, <<"!">>) of
+ [_Node, _ItemID] -> {result, []};
+ [Node] ->
+ % Node = string_to_node(SNode),
+ Action = fun (#pubsub_node{id = Idx, type = Type,
+- options = Options, owners = Owners}) ->
+- NodeItems = case get_allowed_items_call(Host, Idx,
+- From, Type,
+- Options,
+- Owners)
+- of
++ options = Options}) ->
++ Owners = node_owners_call(Type, Idx),
++ {NodeItems, RsmOut} = case get_allowed_items_call(Host, Idx, From, Type, Options, Owners, RSM) of
+ {result, R} -> R;
+- _ -> []
++ _ -> {[], none}
+ end,
+ Nodes = lists:map(fun (#pubsub_node{nodeid =
+ {_, SubNode},
+@@ -1805,7 +1461,7 @@
+ children = []}
+ end,
+ NodeItems),
+- {result, Nodes ++ Items}
++ {result, Nodes ++ Items ++ jlib:rsm_encode(RsmOut)}
+ end,
+ case transaction(Host, Node, Action, sync_dirty) of
+ {result, {_, Result}} -> {result, Result};
+@@ -1956,7 +1612,8 @@
+ (_, Acc) -> Acc
+ end,
+ [], xml:remove_cdata(Els)),
+- get_items(Host, Node, From, SubId, MaxItems, ItemIDs);
++ RSM = jlib:rsm_decode(SubEl),
++ get_items(Host, Node, From, SubId, MaxItems, ItemIDs, RSM);
+ {get, <<"subscriptions">>} ->
+ get_subscriptions(Host, Node, From, Plugins);
+ {get, <<"affiliations">>} ->
+@@ -1991,7 +1648,9 @@
+ ).
iq_pubsub_owner(Host, ServerHost, From, IQType, SubEl, Lang) ->
- {xmlelement, _, _, SubEls} = SubEl,
+ #xmlel{children = SubEls} = SubEl,
- Action = xml:remove_cdata(SubEls),
-+ Action = lists:filter(fun({xmlelement, "set", _, _}) -> false;
-+ (_) -> true
-+ end, xml:remove_cdata(SubEls)),
++ Action = lists:filter(fun(#xmlel{name = <<"set">>, _ = '_'}) -> false;
++ (_) -> true
++ end, xml:remove_cdata(SubEls)),
case Action of
- [{xmlelement, Name, Attrs, Els}] ->
- Node = string_to_node(xml:get_attr_s("node", Attrs)),
-@@ -1449,7 +1263,8 @@
- _ -> []
- end
- end,
-- case transaction(fun () -> {result, lists:flatmap(Tr, Plugins)} end,
+ [#xmlel{name = Name, attrs = Attrs, children = Els}] ->
+ Node = xml:get_attr_s(<<"node">>, Attrs),
+@@ -2121,7 +1780,8 @@
+ _ -> []
+ end
+ end,
+- case transaction(fun () ->
+ case transaction(Host,
-+ fun () -> {result, lists:flatmap(Tr, Plugins)} end,
- sync_dirty) of
- {result, Res} -> Res;
- Err -> Err
-@@ -1488,7 +1303,7 @@
++ fun () ->
+ {result, lists:flatmap(Tr, Plugins)}
+ end,
+ sync_dirty)
+@@ -2163,7 +1823,8 @@
%%% authorization handling
--send_authorization_request(#pubsub_node{owners = Owners, nodeid = {Host, Node}}, Subscriber) ->
-+send_authorization_request(#pubsub_node{nodeid = {Host, Node}, type = Type, id = NodeId}, Subscriber) ->
- Lang = "en", %% TODO fix
- Stanza = {xmlelement, "message",
- [],
-@@ -1517,7 +1332,7 @@
- [{xmlelement, "value", [], [{xmlcdata, "false"}]}]}]}]},
- lists:foreach(fun(Owner) ->
- ejabberd_router:route(service_jid(Host), jlib:make_jid(Owner), Stanza)
-- end, Owners).
-+ end, node_owners(Host, Type, NodeId)).
+-send_authorization_request(#pubsub_node{owners = Owners, nodeid = {Host, Node}},
++send_authorization_request(#pubsub_node{nodeid = {Host, Node},
++ type = Type, id = NodeId},
+ Subscriber) ->
+ Lang = <<"en">>,
+ Stanza = #xmlel{name = <<"message">>, attrs = [],
+@@ -2241,7 +1902,7 @@
+ ejabberd_router:route(service_jid(Host),
+ jlib:make_jid(Owner), Stanza)
+ end,
+- Owners).
++ node_owners(Host, Type, NodeId)).
find_authorization_response(Packet) ->
- {xmlelement, _Name, _Attrs, Els} = Packet,
-@@ -1581,8 +1396,8 @@
- "true" -> true;
- _ -> false
- end,
-- Action = fun(#pubsub_node{type = Type, owners = Owners, id = NodeId}) ->
-- IsApprover = lists:member(jlib:jid_tolower(jlib:jid_remove_resource(From)), Owners),
-+ Action = fun(#pubsub_node{type = Type, id = NodeId}) ->
-+ IsApprover = lists:member(jlib:jid_tolower(jlib:jid_remove_resource(From)), node_owners_call(Type, NodeId)),
- {result, Subscriptions} = node_call(Type, get_subscriptions, [NodeId, Subscriber]),
- if
- not IsApprover ->
-@@ -1781,7 +1596,7 @@
- Reply = [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}],
- [{xmlelement, "create", nodeAttr(Node),
- []}]}],
+ #xmlel{children = Els} = Packet,
+@@ -2300,11 +1961,11 @@
+ <<"true">> -> true;
+ _ -> false
+ end,
+- Action = fun (#pubsub_node{type = Type, owners = Owners,
++ Action = fun (#pubsub_node{type = Type,
+ id = NodeId}) ->
+ IsApprover =
+ lists:member(jlib:jid_tolower(jlib:jid_remove_resource(From)),
+- Owners),
++ node_owners_call(Type, NodeId)),
+ {result, Subscriptions} = node_call(Type,
+ get_subscriptions,
+ [NodeId,
+@@ -2539,7 +2200,7 @@
+ children = [#xmlel{name = <<"create">>,
+ attrs = nodeAttr(Node),
+ children = []}]}],
- case transaction(CreateNode, transaction) of
+ case transaction(Host, CreateNode, transaction) of
{result, {NodeId, SubsByDepth, {Result, broadcast}}} ->
broadcast_created_node(Host, Node, NodeId, Type, NodeOptions, SubsByDepth),
ejabberd_hooks:run(pubsub_create_node, ServerHost, [ServerHost, Host, Node, NodeId, NodeOptions]),
-@@ -1898,7 +1713,7 @@
- %%<li>The node does not exist.</li>
+@@ -2663,7 +2324,7 @@
%%</ul>
subscribe_node(Host, Node, From, JID, Configuration) ->
-- SubOpts = case pubsub_subscription:parse_options_xform(Configuration) of
-+ SubOpts = case pubsub_subscription_odbc:parse_options_xform(Configuration) of
- {result, GoodSubOpts} -> GoodSubOpts;
- _ -> invalid
- end,
-@@ -1906,7 +1721,7 @@
- error -> {"", "", ""};
- J -> jlib:jid_tolower(J)
+ SubOpts = case
+- pubsub_subscription:parse_options_xform(Configuration)
++ pubsub_subscription_odbc:parse_options_xform(Configuration)
+ of
+ {result, GoodSubOpts} -> GoodSubOpts;
+ _ -> invalid
+@@ -2677,7 +2338,7 @@
+ end
end,
-- Action = fun(#pubsub_node{options = Options, owners = Owners, type = Type, id = NodeId}) ->
-+ Action = fun(#pubsub_node{options = Options, type = Type, id = NodeId}) ->
- Features = features(Type),
- SubscribeFeature = lists:member("subscribe", Features),
- OptionsFeature = lists:member("subscription-options", Features),
-@@ -1915,6 +1730,7 @@
- AccessModel = get_option(Options, access_model),
- SendLast = get_option(Options, send_last_published_item),
- AllowedGroups = get_option(Options, roster_groups_allowed, []),
-+ Owners = node_owners_call(Type, NodeId),
- {PresenceSubscription, RosterGroup} = get_presence_and_roster_permissions(Host, Subscriber, Owners, AccessModel, AllowedGroups),
- if
- not SubscribeFeature ->
-@@ -2036,12 +1852,9 @@
- Features = features(Type),
- PublishFeature = lists:member("publish", Features),
- PublishModel = get_option(Options, publish_model),
-+ MaxItems = max_items(Host, Options),
- DeliverPayloads = get_option(Options, deliver_payloads),
- PersistItems = get_option(Options, persist_items),
-- MaxItems = case PersistItems of
-- false -> 0;
-- true -> max_items(Host, Options)
-- end,
- PayloadCount = payload_xmlelements(Payload),
- PayloadSize = size(term_to_binary(Payload))-2, % size(term_to_binary([])) == 2
- PayloadMaxSize = get_option(Options, max_payload_size),
-@@ -2092,7 +1905,7 @@
+ Action = fun (#pubsub_node{options = Options,
+- owners = Owners, type = Type, id = NodeId}) ->
++ type = Type, id = NodeId}) ->
+ Features = features(Type),
+ SubscribeFeature = lists:member(<<"subscribe">>, Features),
+ OptionsFeature = lists:member(<<"subscription-options">>, Features),
+@@ -2686,6 +2347,7 @@
+ AccessModel = get_option(Options, access_model),
+ SendLast = get_option(Options, send_last_published_item),
+ AllowedGroups = get_option(Options, roster_groups_allowed, []),
++ Owners = node_owners_call(Type, NodeId),
+ {PresenceSubscription, RosterGroup} =
+ get_presence_and_roster_permissions(Host, Subscriber,
+ Owners, AccessModel, AllowedGroups),
+@@ -2808,12 +2470,9 @@
+ Features = features(Type),
+ PublishFeature = lists:member(<<"publish">>, Features),
+ PublishModel = get_option(Options, publish_model),
++ MaxItems = max_items(Host, Options),
+ DeliverPayloads = get_option(Options, deliver_payloads),
+ PersistItems = get_option(Options, persist_items),
+- MaxItems = case PersistItems of
+- false -> 0;
+- true -> max_items(Host, Options)
+- end,
+ PayloadCount = payload_xmlelements(Payload),
+ PayloadSize = byte_size(term_to_binary(Payload)) - 2,
+ PayloadMaxSize = get_option(Options, max_payload_size),
+@@ -2869,7 +2528,7 @@
false ->
ok
end,
@@ -554,57 +782,74 @@
case Result of
default -> {result, Reply};
_ -> {result, Result}
-@@ -2258,7 +2071,7 @@
- %% <p>The permission are not checked in this function.</p>
- %% @todo We probably need to check that the user doing the query has the right
- %% to read the items.
+@@ -3040,19 +2699,20 @@
+ Error -> Error
+ end.
+
+--spec(get_items/6 ::
++-spec(get_items/7 ::
+ (
+ Host :: mod_pubsub:host(),
+ Node :: mod_pubsub:nodeId(),
+ From :: jid(),
+ SubId :: mod_pubsub:subId(),
+ SMaxItems :: binary(),
+- ItemIDs :: [mod_pubsub:itemId()])
++ ItemIDs :: [mod_pubsub:itemId()],
++ Rsm :: any())
+ -> {result, [xmlel(),...]}
+ %%%
+ | {error, xmlel()}
+ ).
-get_items(Host, Node, From, SubId, SMaxItems, ItemIDs) ->
+get_items(Host, Node, From, SubId, SMaxItems, ItemIDs, RSM) ->
- MaxItems =
- if
- SMaxItems == "" -> get_max_items_node(Host);
-@@ -2272,12 +2085,13 @@
- {error, Error} ->
- {error, Error};
- _ ->
-- Action = fun(#pubsub_node{options = Options, type = Type, id = NodeId, owners = Owners}) ->
-+ Action = fun(#pubsub_node{options = Options, type = Type, id = NodeId}) ->
- Features = features(Type),
- RetreiveFeature = lists:member("retrieve-items", Features),
- PersistentFeature = lists:member("persistent-items", Features),
- AccessModel = get_option(Options, access_model),
- AllowedGroups = get_option(Options, roster_groups_allowed, []),
-+ Owners = node_owners_call(Type, NodeId),
- {PresenceSubscription, RosterGroup} = get_presence_and_roster_permissions(Host, From, Owners, AccessModel, AllowedGroups),
- if
- not RetreiveFeature ->
-@@ -2290,11 +2104,11 @@
- node_call(Type, get_items,
- [NodeId, From,
- AccessModel, PresenceSubscription, RosterGroup,
-- SubId])
-+ SubId, RSM])
- end
- end,
- case transaction(Host, Node, Action, sync_dirty) of
-- {result, {_, Items}} ->
-+ {result, {_, {Items, RSMOut}}} ->
- SendItems = case ItemIDs of
- [] ->
- Items;
-@@ -2307,7 +2121,8 @@
- %% number of items sent to MaxItems:
- {result, [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}],
- [{xmlelement, "items", nodeAttr(Node),
-- itemsEls(lists:sublist(SendItems, MaxItems))}]}]};
-+ itemsEls(lists:sublist(SendItems, MaxItems))}
-+ | jlib:rsm_encode(RSMOut)]}]};
- Error ->
- Error
- end
-@@ -2329,10 +2144,15 @@
- Error -> Error
+ MaxItems = if SMaxItems == <<"">> ->
+ get_max_items_node(Host);
+ true ->
+@@ -3064,13 +2724,13 @@
+ case MaxItems of
+ {error, Error} -> {error, Error};
+ _ ->
+- Action = fun (#pubsub_node{options = Options, type = Type, id = NodeId,
+- owners = Owners}) ->
++ Action = fun (#pubsub_node{options = Options, type = Type, id = NodeId}) ->
+ Features = features(Type),
+ RetreiveFeature = lists:member(<<"retrieve-items">>, Features),
+ PersistentFeature = lists:member(<<"persistent-items">>, Features),
+ AccessModel = get_option(Options, access_model),
+ AllowedGroups = get_option(Options, roster_groups_allowed, []),
++ Owners = node_owners_call(Type, NodeId),
+ {PresenceSubscription, RosterGroup} =
+ get_presence_and_roster_permissions(Host, From, Owners,
+ AccessModel, AllowedGroups),
+@@ -3088,11 +2748,11 @@
+ node_call(Type, get_items,
+ [NodeId, From, AccessModel,
+ PresenceSubscription, RosterGroup,
+- SubId])
++ SubId, RSM])
+ end
+ end,
+ case transaction(Host, Node, Action, sync_dirty) of
+- {result, {_, Items}} ->
++ {result, {_, {Items, RSMOut}}} ->
+ SendItems = case ItemIDs of
+ [] -> Items;
+ _ ->
+@@ -3110,8 +2770,8 @@
+ children =
+ [#xmlel{name = <<"items">>, attrs = nodeAttr(Node),
+ children =
+- itemsEls(lists:sublist(SendItems,
+- MaxItems))}]}]};
++ itemsEls(lists:sublist(SendItems, MaxItems))}
++ | jlib:rsm_encode(RSMOut)]}]};
+ Error -> Error
+ end
end.
+@@ -3135,43 +2795,45 @@
+ end.
+
get_allowed_items_call(Host, NodeIdx, From, Type, Options, Owners) ->
+ case get_allowed_items_call(Host, NodeIdx, From, Type, Options, Owners, none) of
+ {result, {I, _}} -> {result, I};
@@ -613,165 +858,187 @@
+get_allowed_items_call(Host, NodeIdx, From, Type, Options, Owners, RSM) ->
AccessModel = get_option(Options, access_model),
AllowedGroups = get_option(Options, roster_groups_allowed, []),
- {PresenceSubscription, RosterGroup} = get_presence_and_roster_permissions(Host, From, Owners, AccessModel, AllowedGroups),
-- node_call(Type, get_items, [NodeIdx, From, AccessModel, PresenceSubscription, RosterGroup, undefined]).
-+ node_call(Type, get_items, [NodeIdx, From, AccessModel, PresenceSubscription, RosterGroup, undefined, RSM]).
+ {PresenceSubscription, RosterGroup} =
+ get_presence_and_roster_permissions(Host, From, Owners, AccessModel,
+ AllowedGroups),
+ node_call(Type, get_items,
+- [NodeIdx, From, AccessModel, PresenceSubscription, RosterGroup, undefined]).
++ [NodeIdx, From, AccessModel, PresenceSubscription, RosterGroup, undefined, RSM]).
-
- %% @spec (Host, Node, NodeId, Type, LJID, Number) -> any()
-@@ -2344,31 +2164,29 @@
- %% Number = last | integer()
- %% @doc <p>Resend the items of a node to the user.</p>
- %% @todo use cache-last-item feature
--send_items(Host, Node, NodeId, Type, {U,S,R} = LJID, last) ->
+-send_items(Host, Node, NodeId, Type, {U, S, R} = LJID, last) ->
- case get_cached_item(Host, NodeId) of
+- undefined ->
+- send_items(Host, Node, NodeId, Type, LJID, 1);
+send_items(Host, Node, NodeId, Type, LJID, last) ->
+ Stanza = case get_cached_item(Host, NodeId) of
- undefined ->
-- send_items(Host, Node, NodeId, Type, LJID, 1);
++ undefined ->
+ % special ODBC optimization, works only with node_hometree_odbc, node_flat_odbc and node_pep_odbc
+ case node_action(Host, Type, get_last_items, [NodeId, LJID, 1]) of
+ {result, [LastItem]} ->
+ {ModifNow, ModifUSR} = LastItem#pubsub_item.modification,
+ event_stanza_with_delay(
-+ [{xmlelement, "items", nodeAttr(Node),
-+ itemsEls([LastItem])}], ModifNow, ModifUSR);
++ [#xmlel{name = <<"items">>, attrs = nodeAttr(Node),
++ children = itemsEls([LastItem])}], ModifNow, ModifUSR);
+ _ ->
+ event_stanza(
-+ [{xmlelement, "items", nodeAttr(Node),
-+ itemsEls([])}])
++ [#xmlel{name = <<"items">>, attrs = nodeAttr(Node),
++ children = itemsEls([])}])
+ end;
- LastItem ->
- {ModifNow, ModifUSR} = LastItem#pubsub_item.modification,
-- Stanza = event_stanza_with_delay(
-+ event_stanza_with_delay(
- [{xmlelement, "items", nodeAttr(Node),
-- itemsEls([LastItem])}], ModifNow, ModifUSR),
-- case is_tuple(Host) of
-- false ->
-- ejabberd_router:route(service_jid(Host), jlib:make_jid(LJID), Stanza);
-- true ->
-- case ejabberd_sm:get_session_pid(U,S,R) of
-- C2SPid when is_pid(C2SPid) ->
-- ejabberd_c2s:broadcast(C2SPid,
-- {pep_message, binary_to_list(Node)++"+notify"},
-- _Sender = service_jid(Host),
-- Stanza);
-- _ ->
-- ok
-- end
-- end
+ LastItem ->
+ {ModifNow, ModifUSR} =
+ LastItem#pubsub_item.modification,
+- Stanza = event_stanza_with_delay([#xmlel{name =
++ event_stanza_with_delay([#xmlel{name =
+ <<"items">>,
+ attrs = nodeAttr(Node),
+ children =
+ itemsEls([LastItem])}],
+- ModifNow, ModifUSR),
+- case is_tuple(Host) of
+- false ->
+- ejabberd_router:route(service_jid(Host),
+- jlib:make_jid(LJID), Stanza);
+- true ->
+- case ejabberd_sm:get_session_pid(U, S, R) of
+- C2SPid when is_pid(C2SPid) ->
+- ejabberd_c2s:broadcast(C2SPid,
+- {pep_message,
+- <<((Node))/binary, "+notify">>},
+- _Sender = service_jid(Host),
+- Stanza);
+- _ -> ok
+- end
+- end
- end;
--send_items(Host, Node, NodeId, Type, {U,S,R} = LJID, Number) ->
-+ itemsEls([LastItem])}], ModifNow, ModifUSR)
++ ModifNow, ModifUSR)
+ end,
+ ejabberd_router:route(service_jid(Host), jlib:make_jid(LJID), Stanza);
-+send_items(Host, Node, NodeId, Type, LJID, Number) ->
- ToSend = case node_action(Host, Type, get_items, [NodeId, LJID]) of
- {result, []} ->
- [];
-@@ -2391,20 +2209,7 @@
- [{xmlelement, "items", nodeAttr(Node),
- itemsEls(ToSend)}])
- end,
+ send_items(Host, Node, NodeId, Type, {U, S, R} = LJID,
+ Number) ->
+ ToSend = case node_action(Host, Type, get_items,
+@@ -3199,20 +2861,7 @@
+ attrs = nodeAttr(Node),
+ children = itemsEls(ToSend)}])
+ end,
- case is_tuple(Host) of
-- false ->
-- ejabberd_router:route(service_jid(Host), jlib:make_jid(LJID), Stanza);
-- true ->
-- case ejabberd_sm:get_session_pid(U,S,R) of
-- C2SPid when is_pid(C2SPid) ->
-- ejabberd_c2s:broadcast(C2SPid,
-- {pep_message, binary_to_list(Node)++"+notify"},
-- _Sender = service_jid(Host),
-- Stanza);
-- _ ->
-- ok
-- end
+- false ->
+- ejabberd_router:route(service_jid(Host),
+- jlib:make_jid(LJID), Stanza);
+- true ->
+- case ejabberd_sm:get_session_pid(U, S, R) of
+- C2SPid when is_pid(C2SPid) ->
+- ejabberd_c2s:broadcast(C2SPid,
+- {pep_message,
+- <<((Node))/binary, "+notify">>},
+- _Sender = service_jid(Host), Stanza);
+- _ -> ok
+- end
- end.
+ ejabberd_router:route(service_jid(Host), jlib:make_jid(LJID), Stanza).
- %% @spec (Host, JID, Plugins) -> {error, Reason} | {result, Response}
- %% Host = host()
-@@ -2540,7 +2345,8 @@
- error ->
- {error, ?ERR_BAD_REQUEST};
- _ ->
-- Action = fun(#pubsub_node{owners = Owners, type = Type, id = NodeId}=N) ->
-+ Action = fun(#pubsub_node{type = Type, id = NodeId}) ->
-+ Owners = node_owners_call(Type, NodeId),
- case lists:member(Owner, Owners) of
- true ->
- OwnerJID = jlib:make_jid(Owner),
-@@ -2550,24 +2356,7 @@
- end,
- lists:foreach(
- fun({JID, Affiliation}) ->
-- node_call(Type, set_affiliation, [NodeId, JID, Affiliation]),
-- case Affiliation of
-- owner ->
-- NewOwner = jlib:jid_tolower(jlib:jid_remove_resource(JID)),
-- NewOwners = [NewOwner|Owners],
-- tree_call(Host, set_node, [N#pubsub_node{owners = NewOwners}]);
-- none ->
-- OldOwner = jlib:jid_tolower(jlib:jid_remove_resource(JID)),
-- case lists:member(OldOwner, Owners) of
-- true ->
-- NewOwners = Owners--[OldOwner],
-- tree_call(Host, set_node, [N#pubsub_node{owners = NewOwners}]);
-- _ ->
-- ok
-- end;
-- _ ->
-- ok
-- end
-+ node_call(Type, set_affiliation, [NodeId, JID, Affiliation])
- end, FilteredEntities),
- {result, []};
- _ ->
-@@ -2620,11 +2409,11 @@
+ -spec(get_affiliations/4 ::
+ (
+@@ -3400,9 +3049,10 @@
+ case Entities of
+ error -> {error, ?ERR_BAD_REQUEST};
+ _ ->
+- Action = fun (#pubsub_node{owners = Owners, type = Type,
++ Action = fun (#pubsub_node{type = Type,
+ id = NodeId} =
+ N) ->
++ Owners = node_owners_call(Type, NodeId),
+ case lists:member(Owner, Owners) of
+ true ->
+ OwnerJID = jlib:make_jid(Owner),
+@@ -3415,42 +3065,7 @@
+ _ -> Entities
+ end,
+ lists:foreach(fun ({JID, Affiliation}) ->
+- node_call(Type,
+- set_affiliation,
+- [NodeId, JID,
+- Affiliation]),
+- case Affiliation of
+- owner ->
+- NewOwner =
+- jlib:jid_tolower(jlib:jid_remove_resource(JID)),
+- NewOwners =
+- [NewOwner
+- | Owners],
+- tree_call(Host,
+- set_node,
+- [N#pubsub_node{owners
+- =
+- NewOwners}]);
+- none ->
+- OldOwner =
+- jlib:jid_tolower(jlib:jid_remove_resource(JID)),
+- case
+- lists:member(OldOwner,
+- Owners)
+- of
+- true ->
+- NewOwners =
+- Owners --
+- [OldOwner],
+- tree_call(Host,
+- set_node,
+- [N#pubsub_node{owners
+- =
+- NewOwners}]);
+- _ -> ok
+- end;
+- _ -> ok
+- end
++ node_call(Type, set_affiliation, [NodeId, JID, Affiliation])
+ end,
+ FilteredEntities),
+ {result, []};
+@@ -3509,11 +3124,11 @@
end.
read_sub(Subscriber, Node, NodeID, SubID, Lang) ->
- case pubsub_subscription:get_subscription(Subscriber, NodeID, SubID) of
+ case pubsub_subscription_odbc:get_subscription(Subscriber, NodeID, SubID) of
{error, notfound} ->
- {error, extended_error(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
+ {error, extended_error(?ERR_NOT_ACCEPTABLE, <<"invalid-subid">>)};
{result, #pubsub_subscription{options = Options}} ->
- {result, XdataEl} = pubsub_subscription:get_options_xform(Lang, Options),
+ {result, XdataEl} = pubsub_subscription_odbc:get_options_xform(Lang, Options),
- OptionsEl = {xmlelement, "options", [{"jid", jlib:jid_to_string(Subscriber)},
- {"subid", SubID}|nodeAttr(Node)],
- [XdataEl]},
-@@ -2650,7 +2439,7 @@
+ OptionsEl = #xmlel{name = <<"options">>,
+ attrs =
+ [{<<"jid">>, jlib:jid_to_string(Subscriber)},
+@@ -3547,7 +3162,7 @@
end.
set_options_helper(Configuration, JID, NodeID, SubID, Type) ->
- SubOpts = case pubsub_subscription:parse_options_xform(Configuration) of
+ SubOpts = case pubsub_subscription_odbc:parse_options_xform(Configuration) of
- {result, GoodSubOpts} -> GoodSubOpts;
- _ -> invalid
- end,
-@@ -2679,7 +2468,7 @@
+ {result, GoodSubOpts} -> GoodSubOpts;
+ _ -> invalid
+ end,
+@@ -3579,7 +3194,7 @@
write_sub(_Subscriber, _NodeID, _SubID, invalid) ->
- {error, extended_error(?ERR_BAD_REQUEST, "invalid-options")};
+ {error, extended_error(?ERR_BAD_REQUEST, <<"invalid-options">>)};
write_sub(Subscriber, NodeID, SubID, Options) ->
- case pubsub_subscription:set_subscription(Subscriber, NodeID, SubID, Options) of
+ case pubsub_subscription_odbc:set_subscription(Subscriber, NodeID, SubID, Options) of
{error, notfound} ->
- {error, extended_error(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
+ {error, extended_error(?ERR_NOT_ACCEPTABLE, <<"invalid-subid">>)};
{result, _} ->
-@@ -2847,8 +2636,8 @@
- {"subscription", subscription_to_string(Sub)} | nodeAttr(Node)], []}]}]},
- ejabberd_router:route(service_jid(Host), jlib:make_jid(JID), Stanza)
- end,
-- Action = fun(#pubsub_node{owners = Owners, type = Type, id = NodeId}) ->
-- case lists:member(Owner, Owners) of
-+ Action = fun(#pubsub_node{type = Type, id = NodeId}) ->
-+ case lists:member(Owner, node_owners_call(Type, NodeId)) of
- true ->
- Result = lists:foldl(fun({JID, Subscription, SubId}, Acc) ->
-
-@@ -3203,7 +2992,7 @@
+@@ -3800,9 +3415,9 @@
+ ejabberd_router:route(service_jid(Host),
+ jlib:make_jid(JID), Stanza)
+ end,
+- Action = fun (#pubsub_node{owners = Owners, type = Type,
++ Action = fun (#pubsub_node{type = Type,
+ id = NodeId}) ->
+- case lists:member(Owner, Owners) of
++ case lists:member(Owner, node_owners_call(Type, NodeId)) of
+ true ->
+ Result = lists:foldl(fun ({JID, Subscription,
+ SubId},
+@@ -4181,7 +3796,7 @@
{Depth, [{N, get_node_subs(N)} || N <- Nodes]}
end, tree_call(Host, get_parentnodes_tree, [Host, Node, service_jid(Host)]))}
end,
@@ -780,7 +1047,7 @@
{result, CollSubs} -> CollSubs;
_ -> []
end.
-@@ -3217,9 +3006,9 @@
+@@ -4195,9 +3810,9 @@
get_options_for_subs(NodeID, Subs) ->
lists:foldl(fun({JID, subscribed, SubID}, Acc) ->
@@ -792,8 +1059,8 @@
_ -> Acc
end;
(_, Acc) ->
-@@ -3408,6 +3197,30 @@
- Result
+@@ -4870,6 +4485,30 @@
+ _ -> features()
end.
+%% @spec (Host, Type, NodeId) -> [ljid()]
@@ -820,13 +1087,13 @@
+ []
+ end.
+
- %% @spec (Host, Options) -> MaxItems
- %% Host = host()
- %% Options = [Option]
-@@ -3804,7 +3617,13 @@
+ tree_call({_User, Server, _Resource}, Function, Args) ->
+ tree_call(Server, Function, Args);
+ tree_call(Host, Function, Args) ->
+@@ -4888,7 +4527,13 @@
tree_action(Host, Function, Args) ->
- ?DEBUG("tree_action ~p ~p ~p",[Host,Function,Args]),
- Fun = fun() -> tree_call(Host, Function, Args) end,
+ ?DEBUG("tree_action ~p ~p ~p", [Host, Function, Args]),
+ Fun = fun () -> tree_call(Host, Function, Args) end,
- catch mnesia:sync_dirty(Fun).
+ case catch ejabberd_odbc:sql_bloc(odbc_conn(Host), Fun) of
+ {atomic, Result} ->
@@ -836,34 +1103,36 @@
+ {error, ?ERR_INTERNAL_SERVER_ERROR}
+ end.
- %% @doc <p>node plugin call.</p>
node_call(Type, Function, Args) ->
-@@ -3824,13 +3643,13 @@
-
+ ?DEBUG("node_call ~p ~p ~p", [Type, Function, Args]),
+@@ -4912,12 +4557,11 @@
node_action(Host, Type, Function, Args) ->
- ?DEBUG("node_action ~p ~p ~p ~p",[Host,Type,Function,Args]),
-- transaction(fun() ->
-+ transaction(Host, fun() ->
- node_call(Type, Function, Args)
- end, sync_dirty).
+ ?DEBUG("node_action ~p ~p ~p ~p",
+ [Host, Type, Function, Args]),
+- transaction(fun () -> node_call(Type, Function, Args)
+- end,
++ transaction(Host, fun () -> node_call(Type, Function, Args) end,
+ sync_dirty).
- %% @doc <p>plugin transaction handling.</p>
transaction(Host, Node, Action, Trans) ->
-- transaction(fun() ->
-+ transaction(Host, fun() ->
+- transaction(fun () ->
++ transaction(Host, fun () ->
case tree_call(Host, get_node, [Host, Node]) of
- N when is_record(N, pubsub_node) ->
- case Action(N) of
-@@ -3842,13 +3661,19 @@
- Error
- end
- end, Trans).
+ N when is_record(N, pubsub_node) ->
+ case Action(N) of
+@@ -4931,16 +4575,22 @@
+ end,
+ Trans).
+
-transaction(Host, Action, Trans) ->
-- transaction(fun() ->
+- transaction(fun () ->
+transaction_on_nodes(Host, Action, Trans) ->
-+ transaction(Host, fun() ->
- {result, lists:foldl(Action, [], tree_call(Host, get_nodes, [Host]))}
- end, Trans).
++ transaction(Host, fun () ->
+ {result,
+ lists:foldl(Action, [],
+ tree_call(Host, get_nodes, [Host]))}
+ end,
+ Trans).
-transaction(Fun, Trans) ->
- case catch mnesia:Trans(Fun) of
@@ -875,14 +1144,14 @@
+ _ -> sql_bloc
+ end,
+ case catch ejabberd_odbc:SqlFun(odbc_conn(Host), Fun) of
- {result, Result} -> {result, Result};
- {error, Error} -> {error, Error};
- {atomic, {result, Result}} -> {result, Result};
-@@ -3856,6 +3681,15 @@
- {aborted, Reason} ->
- ?ERROR_MSG("transaction return internal error: ~p~n", [{aborted, Reason}]),
- {error, ?ERR_INTERNAL_SERVER_ERROR};
-+ {'EXIT', {timeout, _} = Reason} ->
+ {result, Result} -> {result, Result};
+ {error, Error} -> {error, Error};
+ {atomic, {result, Result}} -> {result, Result};
+@@ -4949,6 +4599,15 @@
+ ?ERROR_MSG("transaction return internal error: ~p~n",
+ [{aborted, Reason}]),
+ {error, ?ERR_INTERNAL_SERVER_ERROR};
++ {'EXIT', {timeout, _} = Reason} ->
+ case Count of
+ 0 ->
+ ?ERROR_MSG("transaction return internal error: ~p~n", [{'EXIT', Reason}]),
@@ -891,11 +1160,11 @@
+ erlang:yield(),
+ transaction_retry(Host, Fun, Trans, N-1)
+ end;
- {'EXIT', Reason} ->
- ?ERROR_MSG("transaction return internal error: ~p~n", [{'EXIT', Reason}]),
- {error, ?ERR_INTERNAL_SERVER_ERROR};
-@@ -3864,6 +3698,17 @@
- {error, ?ERR_INTERNAL_SERVER_ERROR}
+ {'EXIT', Reason} ->
+ ?ERROR_MSG("transaction return internal error: ~p~n",
+ [{'EXIT', Reason}]),
+@@ -4959,6 +4618,17 @@
+ {error, ?ERR_INTERNAL_SERVER_ERROR}
end.
+odbc_conn({_U, Host, _R})->
@@ -911,4 +1180,4 @@
+
%%%% helpers
- %% Add pubsub-specific error element
+ extended_error(Error, Ext) ->
diff --git a/src/mod_pubsub/pubsub_subscription.erl b/src/mod_pubsub/pubsub_subscription.erl
index 600f1e119..bb09cdd60 100644
--- a/src/mod_pubsub/pubsub_subscription.erl
+++ b/src/mod_pubsub/pubsub_subscription.erl
@@ -21,130 +21,174 @@
%%% ====================================================================
-module(pubsub_subscription).
+
-author("bjc@kublai.com").
%% API
--export([init/0,
- subscribe_node/3,
- unsubscribe_node/3,
- get_subscription/3,
- set_subscription/4,
- get_options_xform/2,
- parse_options_xform/1]).
+-export([init/0, subscribe_node/3, unsubscribe_node/3,
+ get_subscription/3, set_subscription/4,
+ get_options_xform/2, parse_options_xform/1]).
% Internal function also exported for use in transactional bloc from pubsub plugins
--export([add_subscription/3,
- delete_subscription/3,
- read_subscription/3,
- write_subscription/4]).
+-export([add_subscription/3, delete_subscription/3,
+ read_subscription/3, write_subscription/4]).
-include("pubsub.hrl").
+
-include("jlib.hrl").
--define(PUBSUB_DELIVER, "pubsub#deliver").
--define(PUBSUB_DIGEST, "pubsub#digest").
--define(PUBSUB_DIGEST_FREQUENCY, "pubsub#digest_frequency").
--define(PUBSUB_EXPIRE, "pubsub#expire").
--define(PUBSUB_INCLUDE_BODY, "pubsub#include_body").
--define(PUBSUB_SHOW_VALUES, "pubsub#show-values").
--define(PUBSUB_SUBSCRIPTION_TYPE, "pubsub#subscription_type").
--define(PUBSUB_SUBSCRIPTION_DEPTH, "pubsub#subscription_depth").
+-define(PUBSUB_DELIVER, <<"pubsub#deliver">>).
+
+-define(PUBSUB_DIGEST, <<"pubsub#digest">>).
+
+-define(PUBSUB_DIGEST_FREQUENCY,
+ <<"pubsub#digest_frequency">>).
+
+-define(PUBSUB_EXPIRE, <<"pubsub#expire">>).
+
+-define(PUBSUB_INCLUDE_BODY, <<"pubsub#include_body">>).
+
+-define(PUBSUB_SHOW_VALUES, <<"pubsub#show-values">>).
+
+-define(PUBSUB_SUBSCRIPTION_TYPE,
+ <<"pubsub#subscription_type">>).
+
+-define(PUBSUB_SUBSCRIPTION_DEPTH,
+ <<"pubsub#subscription_depth">>).
-define(DELIVER_LABEL,
- "Whether an entity wants to receive or disable notifications").
+ <<"Whether an entity wants to receive or "
+ "disable notifications">>).
+
-define(DIGEST_LABEL,
- "Whether an entity wants to receive digests (aggregations) of notifications or all notifications individually").
+ <<"Whether an entity wants to receive digests "
+ "(aggregations) of notifications or all "
+ "notifications individually">>).
+
-define(DIGEST_FREQUENCY_LABEL,
- "The minimum number of milliseconds between sending any two notification digests").
+ <<"The minimum number of milliseconds between "
+ "sending any two notification digests">>).
+
-define(EXPIRE_LABEL,
- "The DateTime at which a leased subscription will end or has ended").
+ <<"The DateTime at which a leased subscription "
+ "will end or has ended">>).
+
-define(INCLUDE_BODY_LABEL,
- "Whether an entity wants to receive an XMPP message body in addition to the payload format").
+ <<"Whether an entity wants to receive an "
+ "XMPP message body in addition to the "
+ "payload format">>).
+
-define(SHOW_VALUES_LABEL,
- "The presence states for which an entity wants to receive notifications").
+ <<"The presence states for which an entity "
+ "wants to receive notifications">>).
+
-define(SUBSCRIPTION_TYPE_LABEL,
- "Type of notification to receive").
+ <<"Type of notification to receive">>).
+
-define(SUBSCRIPTION_DEPTH_LABEL,
- "Depth from subscription for which to receive notifications").
+ <<"Depth from subscription for which to "
+ "receive notifications">>).
+
+-define(SHOW_VALUE_AWAY_LABEL,
+ <<"XMPP Show Value of Away">>).
--define(SHOW_VALUE_AWAY_LABEL, "XMPP Show Value of Away").
--define(SHOW_VALUE_CHAT_LABEL, "XMPP Show Value of Chat").
--define(SHOW_VALUE_DND_LABEL, "XMPP Show Value of DND (Do Not Disturb)").
--define(SHOW_VALUE_ONLINE_LABEL, "Mere Availability in XMPP (No Show Value)").
--define(SHOW_VALUE_XA_LABEL, "XMPP Show Value of XA (Extended Away)").
+-define(SHOW_VALUE_CHAT_LABEL,
+ <<"XMPP Show Value of Chat">>).
+
+-define(SHOW_VALUE_DND_LABEL,
+ <<"XMPP Show Value of DND (Do Not Disturb)">>).
+
+-define(SHOW_VALUE_ONLINE_LABEL,
+ <<"Mere Availability in XMPP (No Show Value)">>).
+
+-define(SHOW_VALUE_XA_LABEL,
+ <<"XMPP Show Value of XA (Extended Away)">>).
-define(SUBSCRIPTION_TYPE_VALUE_ITEMS_LABEL,
- "Receive notification of new items only").
+ <<"Receive notification of new items only">>).
+
-define(SUBSCRIPTION_TYPE_VALUE_NODES_LABEL,
- "Receive notification of new nodes only").
+ <<"Receive notification of new nodes only">>).
-define(SUBSCRIPTION_DEPTH_VALUE_ONE_LABEL,
- "Receive notification from direct child nodes only").
+ <<"Receive notification from direct child "
+ "nodes only">>).
+
-define(SUBSCRIPTION_DEPTH_VALUE_ALL_LABEL,
- "Receive notification from all descendent nodes").
+ <<"Receive notification from all descendent "
+ "nodes">>).
%%====================================================================
%% API
%%====================================================================
-init() ->
- ok = create_table().
+init() -> ok = create_table().
subscribe_node(JID, NodeID, Options) ->
case catch mnesia:sync_dirty(fun add_subscription/3,
- [JID, NodeID, Options]) of
- {'EXIT', {aborted, Error}} -> Error;
- {error, Error} -> {error, Error};
- Result -> {result, Result}
+ [JID, NodeID, Options])
+ of
+ {'EXIT', {aborted, Error}} -> Error;
+ {error, Error} -> {error, Error};
+ Result -> {result, Result}
end.
unsubscribe_node(JID, NodeID, SubID) ->
case catch mnesia:sync_dirty(fun delete_subscription/3,
- [JID, NodeID, SubID]) of
- {'EXIT', {aborted, Error}} -> Error;
- {error, Error} -> {error, Error};
- Result -> {result, Result}
+ [JID, NodeID, SubID])
+ of
+ {'EXIT', {aborted, Error}} -> Error;
+ {error, Error} -> {error, Error};
+ Result -> {result, Result}
end.
get_subscription(JID, NodeID, SubID) ->
case catch mnesia:sync_dirty(fun read_subscription/3,
- [JID, NodeID, SubID]) of
- {'EXIT', {aborted, Error}} -> Error;
- {error, Error} -> {error, Error};
- Result -> {result, Result}
+ [JID, NodeID, SubID])
+ of
+ {'EXIT', {aborted, Error}} -> Error;
+ {error, Error} -> {error, Error};
+ Result -> {result, Result}
end.
set_subscription(JID, NodeID, SubID, Options) ->
case catch mnesia:sync_dirty(fun write_subscription/4,
- [JID, NodeID, SubID, Options]) of
- {'EXIT', {aborted, Error}} -> Error;
- {error, Error} -> {error, Error};
- Result -> {result, Result}
+ [JID, NodeID, SubID, Options])
+ of
+ {'EXIT', {aborted, Error}} -> Error;
+ {error, Error} -> {error, Error};
+ Result -> {result, Result}
end.
-get_options_xform(Lang, Options) ->
- Keys = [deliver, show_values, subscription_type, subscription_depth],
- XFields = [get_option_xfield(Lang, Key, Options) || Key <- Keys],
- {result, {xmlelement, "x", [{"xmlns", ?NS_XDATA}],
- [{xmlelement, "field", [{"var", "FORM_TYPE"}, {"type", "hidden"}],
- [{xmlelement, "value", [],
- [{xmlcdata, ?NS_PUBSUB_SUB_OPTIONS}]}]}] ++ XFields}}.
+get_options_xform(Lang, Options) ->
+ Keys = [deliver, show_values, subscription_type,
+ subscription_depth],
+ XFields = [get_option_xfield(Lang, Key, Options)
+ || Key <- Keys],
+ {result,
+ #xmlel{name = <<"x">>,
+ attrs = [{<<"xmlns">>, ?NS_XDATA}],
+ children =
+ [#xmlel{name = <<"field">>,
+ attrs =
+ [{<<"var">>, <<"FORM_TYPE">>},
+ {<<"type">>, <<"hidden">>}],
+ children =
+ [#xmlel{name = <<"value">>, attrs = [],
+ children =
+ [{xmlcdata, ?NS_PUBSUB_SUB_OPTIONS}]}]}]
+ ++ XFields}}.
parse_options_xform(XFields) ->
case xml:remove_cdata(XFields) of
- [] -> {result, []};
- [{xmlelement, "x", _Attrs, _Els} = XEl] ->
- case jlib:parse_xdata_submit(XEl) of
- XData when is_list(XData) ->
- case set_xoption(XData, []) of
- Opts when is_list(Opts) -> {result, Opts};
- Other -> Other
- end;
- Other ->
- Other
- end;
- Other ->
- Other
+ [#xmlel{name = <<"x">>} = XEl] ->
+ case jlib:parse_xdata_submit(XEl) of
+ XData when is_list(XData) ->
+ Opts = set_xoption(XData, []),
+ {result, Opts};
+ Other -> Other
+ end;
+ _ -> {result, []}
end.
%%====================================================================
@@ -153,36 +197,73 @@ parse_options_xform(XFields) ->
create_table() ->
case mnesia:create_table(pubsub_subscription,
[{disc_copies, [node()]},
- {attributes, record_info(fields, pubsub_subscription)},
- {type, set}]) of
- {atomic, ok} -> ok;
- {aborted, {already_exists, _}} -> ok;
- Other -> Other
+ {attributes,
+ record_info(fields, pubsub_subscription)},
+ {type, set}])
+ of
+ {atomic, ok} -> ok;
+ {aborted, {already_exists, _}} -> ok;
+ Other -> Other
end.
+-spec(add_subscription/3 ::
+(
+ _JID :: ljid(),
+ _NodeID :: mod_pubsub:nodeIdx(),
+ Options :: [] | mod_pubsub:subOptions())
+ -> SubId :: mod_pubsub:subId()
+).
+
+add_subscription(_JID, _NodeID, []) -> make_subid();
add_subscription(_JID, _NodeID, Options) ->
SubID = make_subid(),
- mnesia:write(#pubsub_subscription{subid = SubID, options = Options}),
+ mnesia:write(#pubsub_subscription{subid = SubID,
+ options = Options}),
SubID.
+-spec(delete_subscription/3 ::
+(
+ _JID :: _,
+ _NodeID :: _,
+ SubId :: mod_pubsub:subId())
+ -> ok
+).
+
delete_subscription(_JID, _NodeID, SubID) ->
mnesia:delete({pubsub_subscription, SubID}).
+-spec(read_subscription/3 ::
+(
+ _JID :: ljid(),
+ _NodeID :: _,
+ SubID :: mod_pubsub:subId())
+ -> mod_pubsub:pubsubSubscription()
+ | {error, notfound}
+).
+
read_subscription(_JID, _NodeID, SubID) ->
case mnesia:read({pubsub_subscription, SubID}) of
- [Sub] -> Sub;
- _ -> {error, notfound}
+ [Sub] -> Sub;
+ _ -> {error, notfound}
end.
-write_subscription(JID, NodeID, SubID, Options) ->
- case read_subscription(JID, NodeID, SubID) of
- {error, notfound} -> {error, notfound};
- Sub -> mnesia:write(Sub#pubsub_subscription{options = Options})
- end.
+-spec(write_subscription/4 ::
+(
+ _JID :: ljid(),
+ _NodeID :: _,
+ SubID :: mod_pubsub:subId(),
+ Options :: mod_pubsub:subOptions())
+ -> ok
+).
+write_subscription(_JID, _NodeID, SubID, Options) ->
+ mnesia:write(#pubsub_subscription{subid = SubID,
+ options = Options}).
+
+-spec(make_subid/0 :: () -> SubId::mod_pubsub:subId()).
make_subid() ->
{T1, T2, T3} = now(),
- lists:flatten(io_lib:fwrite("~.16B~.16B~.16B", [T1, T2, T3])).
+ iolist_to_binary(io_lib:fwrite("~.16B~.16B~.16B", [T1, T2, T3])).
%%
%% Subscription XForm processing.
@@ -190,133 +271,193 @@ make_subid() ->
%% Return processed options, with types converted and so forth, using
%% Opts as defaults.
-set_xoption([], Opts) ->
- Opts;
+set_xoption([], Opts) -> Opts;
set_xoption([{Var, Value} | T], Opts) ->
NewOpts = case var_xfield(Var) of
- {error, _} ->
- Opts;
- Key ->
- Val = val_xfield(Key, Value),
- lists:keystore(Key, 1, Opts, {Key, Val})
+ {error, _} -> Opts;
+ Key ->
+ Val = val_xfield(Key, Value),
+ lists:keystore(Key, 1, Opts, {Key, Val})
end,
set_xoption(T, NewOpts).
%% Return the options list's key for an XForm var.
-var_xfield(?PUBSUB_DELIVER) -> deliver;
-var_xfield(?PUBSUB_DIGEST) -> digest;
-var_xfield(?PUBSUB_DIGEST_FREQUENCY) -> digest_frequency;
-var_xfield(?PUBSUB_EXPIRE) -> expire;
-var_xfield(?PUBSUB_INCLUDE_BODY) -> include_body;
-var_xfield(?PUBSUB_SHOW_VALUES) -> show_values;
-var_xfield(?PUBSUB_SUBSCRIPTION_TYPE) -> subscription_type;
-var_xfield(?PUBSUB_SUBSCRIPTION_DEPTH) -> subscription_depth;
-var_xfield(_) -> {error, badarg}.
-
%% Convert Values for option list's Key.
-val_xfield(deliver, [Val]) -> xopt_to_bool(Val);
-val_xfield(digest, [Val]) -> xopt_to_bool(Val);
-val_xfield(digest_frequency, [Val]) -> list_to_integer(Val);
-val_xfield(expire, [Val]) -> jlib:datetime_string_to_timestamp(Val);
-val_xfield(include_body, [Val]) -> xopt_to_bool(Val);
-val_xfield(show_values, Vals) -> Vals;
-val_xfield(subscription_type, ["items"]) -> items;
-val_xfield(subscription_type, ["nodes"]) -> nodes;
-val_xfield(subscription_depth, ["all"]) -> all;
-val_xfield(subscription_depth, [Depth]) ->
- case catch list_to_integer(Depth) of
- N when is_integer(N) -> N;
- _ -> {error, ?ERR_NOT_ACCEPTABLE}
+var_xfield(?PUBSUB_DELIVER) -> deliver;
+var_xfield(?PUBSUB_DIGEST) -> digest;
+var_xfield(?PUBSUB_DIGEST_FREQUENCY) ->
+ digest_frequency;
+var_xfield(?PUBSUB_EXPIRE) -> expire;
+var_xfield(?PUBSUB_INCLUDE_BODY) -> include_body;
+var_xfield(?PUBSUB_SHOW_VALUES) -> show_values;
+var_xfield(?PUBSUB_SUBSCRIPTION_TYPE) ->
+ subscription_type;
+var_xfield(?PUBSUB_SUBSCRIPTION_DEPTH) ->
+ subscription_depth;
+var_xfield(_) -> {error, badarg}.
+
+val_xfield(deliver, [Val]) -> xopt_to_bool(Val);
+%val_xfield(digest, [Val]) -> xopt_to_bool(Val);
+%val_xfield(digest_frequency, [Val]) ->
+% jlib:binary_to_integer(Val);
+%val_xfield(expire, [Val]) ->
+% jlib:datetime_string_to_timestamp(Val);
+%val_xfield(include_body, [Val]) -> xopt_to_bool(Val);
+val_xfield(show_values, Vals) -> Vals;
+val_xfield(subscription_type, [<<"items">>]) -> items;
+val_xfield(subscription_type, [<<"nodes">>]) -> nodes;
+val_xfield(subscription_depth, [<<"all">>]) -> all;
+val_xfield(subscription_depth, [Depth]) ->
+ case catch jlib:binary_to_integer(Depth) of
+ N when is_integer(N) -> N;
+ _ -> {error, ?ERR_NOT_ACCEPTABLE}
end.
%% Convert XForm booleans to Erlang booleans.
-xopt_to_bool("0") -> false;
-xopt_to_bool("1") -> true;
-xopt_to_bool("false") -> false;
-xopt_to_bool("true") -> true;
-xopt_to_bool(_) -> {error, ?ERR_NOT_ACCEPTABLE}.
+xopt_to_bool(<<"0">>) -> false;
+xopt_to_bool(<<"1">>) -> true;
+xopt_to_bool(<<"false">>) -> false;
+xopt_to_bool(<<"true">>) -> true;
+xopt_to_bool(_) -> {error, ?ERR_NOT_ACCEPTABLE}.
+
+-spec(get_option_xfield/3 ::
+(
+ Lang :: binary(),
+ Key :: atom(),
+ Options :: mod_pubsub:subOptions())
+ -> xmlel()
+).
%% Return a field for an XForm for Key, with data filled in, if
%% applicable, from Options.
get_option_xfield(Lang, Key, Options) ->
Var = xfield_var(Key),
Label = xfield_label(Key),
- {Type, OptEls} = type_and_options(xfield_type(Key), Lang),
+ {Type, OptEls} = type_and_options(xfield_type(Key),
+ Lang),
Vals = case lists:keysearch(Key, 1, Options) of
- {value, {_, Val}} ->
- [tr_xfield_values(Vals) || Vals <- xfield_val(Key, Val)];
- false ->
- []
+ {value, {_, Val}} ->
+ [tr_xfield_values(Vals)
+ || Vals <- xfield_val(Key, Val)];
+ false -> []
end,
- {xmlelement, "field",
- [{"var", Var}, {"type", Type},
- {"label", translate:translate(Lang, Label)}],
- OptEls ++ Vals}.
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"var">>, Var}, {<<"type">>, Type},
+ {<<"label">>, translate:translate(Lang, Label)}],
+ children = OptEls ++ Vals}.
type_and_options({Type, Options}, Lang) ->
{Type, [tr_xfield_options(O, Lang) || O <- Options]};
-type_and_options(Type, _Lang) ->
- {Type, []}.
+type_and_options(Type, _Lang) -> {Type, []}.
tr_xfield_options({Value, Label}, Lang) ->
- {xmlelement, "option",
- [{"label", translate:translate(Lang, Label)}], [{xmlelement, "value", [],
- [{xmlcdata, Value}]}]}.
+ #xmlel{name = <<"option">>,
+ attrs =
+ [{<<"label">>, translate:translate(Lang, Label)}],
+ children =
+ [#xmlel{name = <<"value">>, attrs = [],
+ children = [{xmlcdata, Value}]}]}.
tr_xfield_values(Value) ->
- {xmlelement, "value", [], [{xmlcdata, Value}]}.
-
%% Return the XForm variable name for a subscription option key.
-xfield_var(deliver) -> ?PUBSUB_DELIVER;
-xfield_var(digest) -> ?PUBSUB_DIGEST;
-xfield_var(digest_frequency) -> ?PUBSUB_DIGEST_FREQUENCY;
-xfield_var(expire) -> ?PUBSUB_EXPIRE;
-xfield_var(include_body) -> ?PUBSUB_INCLUDE_BODY;
-xfield_var(show_values) -> ?PUBSUB_SHOW_VALUES;
-xfield_var(subscription_type) -> ?PUBSUB_SUBSCRIPTION_TYPE;
-xfield_var(subscription_depth) -> ?PUBSUB_SUBSCRIPTION_DEPTH.
-
%% Return the XForm variable type for a subscription option key.
-xfield_type(deliver) -> "boolean";
-xfield_type(digest) -> "boolean";
-xfield_type(digest_frequency) -> "text-single";
-xfield_type(expire) -> "text-single";
-xfield_type(include_body) -> "boolean";
-xfield_type(show_values) ->
- {"list-multi", [{"away", ?SHOW_VALUE_AWAY_LABEL},
- {"chat", ?SHOW_VALUE_CHAT_LABEL},
- {"dnd", ?SHOW_VALUE_DND_LABEL},
- {"online", ?SHOW_VALUE_ONLINE_LABEL},
- {"xa", ?SHOW_VALUE_XA_LABEL}]};
-xfield_type(subscription_type) ->
- {"list-single", [{"items", ?SUBSCRIPTION_TYPE_VALUE_ITEMS_LABEL},
- {"nodes", ?SUBSCRIPTION_TYPE_VALUE_NODES_LABEL}]};
+ #xmlel{name = <<"value">>, attrs = [],
+ children = [{xmlcdata, Value}]}.
+
+-spec(xfield_var/1 ::
+(
+ Var :: 'deliver'
+% | 'digest'
+% | 'digest_frequency'
+% | 'expire'
+% | 'include_body'
+ | 'show_values'
+ | 'subscription_type'
+ | 'subscription_depth')
+ -> binary()
+).
+
+xfield_var(deliver) -> ?PUBSUB_DELIVER;
+%xfield_var(digest) -> ?PUBSUB_DIGEST;
+%xfield_var(digest_frequency) ->
+% ?PUBSUB_DIGEST_FREQUENCY;
+%xfield_var(expire) -> ?PUBSUB_EXPIRE;
+%xfield_var(include_body) -> ?PUBSUB_INCLUDE_BODY;
+xfield_var(show_values) -> ?PUBSUB_SHOW_VALUES;
+xfield_var(subscription_type) ->
+ ?PUBSUB_SUBSCRIPTION_TYPE;
+xfield_var(subscription_depth) ->
+ ?PUBSUB_SUBSCRIPTION_DEPTH.
+
+xfield_type(deliver) -> <<"boolean">>;
+%xfield_type(digest) -> <<"boolean">>;
+%xfield_type(digest_frequency) -> <<"text-single">>;
+%xfield_type(expire) -> <<"text-single">>;
+%xfield_type(include_body) -> <<"boolean">>;
+xfield_type(show_values) ->
+ {<<"list-multi">>,
+ [{<<"away">>, ?SHOW_VALUE_AWAY_LABEL},
+ {<<"chat">>, ?SHOW_VALUE_CHAT_LABEL},
+ {<<"dnd">>, ?SHOW_VALUE_DND_LABEL},
+ {<<"online">>, ?SHOW_VALUE_ONLINE_LABEL},
+ {<<"xa">>, ?SHOW_VALUE_XA_LABEL}]};
+xfield_type(subscription_type) ->
+ {<<"list-single">>,
+ [{<<"items">>, ?SUBSCRIPTION_TYPE_VALUE_ITEMS_LABEL},
+ {<<"nodes">>, ?SUBSCRIPTION_TYPE_VALUE_NODES_LABEL}]};
xfield_type(subscription_depth) ->
- {"list-single", [{"1", ?SUBSCRIPTION_DEPTH_VALUE_ONE_LABEL},
- {"all", ?SUBSCRIPTION_DEPTH_VALUE_ALL_LABEL}]}.
+ {<<"list-single">>,
+ [{<<"1">>, ?SUBSCRIPTION_DEPTH_VALUE_ONE_LABEL},
+ {<<"all">>, ?SUBSCRIPTION_DEPTH_VALUE_ALL_LABEL}]}.
%% Return the XForm variable label for a subscription option key.
xfield_label(deliver) -> ?DELIVER_LABEL;
-xfield_label(digest) -> ?DIGEST_LABEL;
-xfield_label(digest_frequency) -> ?DIGEST_FREQUENCY_LABEL;
-xfield_label(expire) -> ?EXPIRE_LABEL;
-xfield_label(include_body) -> ?INCLUDE_BODY_LABEL;
+%xfield_label(digest) -> ?DIGEST_LABEL;
+%xfield_label(digest_frequency) ->
+% ?DIGEST_FREQUENCY_LABEL;
+%xfield_label(expire) -> ?EXPIRE_LABEL;
+%xfield_label(include_body) -> ?INCLUDE_BODY_LABEL;
xfield_label(show_values) -> ?SHOW_VALUES_LABEL;
-xfield_label(subscription_type) -> ?SUBSCRIPTION_TYPE_LABEL;
-xfield_label(subscription_depth) -> ?SUBSCRIPTION_DEPTH_LABEL.
-
%% Return the XForm value for a subscription option key.
-xfield_val(deliver, Val) -> [bool_to_xopt(Val)];
-xfield_val(digest, Val) -> [bool_to_xopt(Val)];
-xfield_val(digest_frequency, Val) -> [integer_to_list(Val)];
-xfield_val(expire, Val) -> [jlib:now_to_utc_string(Val)];
-xfield_val(include_body, Val) -> [bool_to_xopt(Val)];
-xfield_val(show_values, Val) -> Val;
-xfield_val(subscription_type, items) -> ["items"];
-xfield_val(subscription_type, nodes) -> ["nodes"];
-xfield_val(subscription_depth, all) -> ["all"];
-xfield_val(subscription_depth, N) -> [integer_to_list(N)].
-
%% Convert erlang booleans to XForms.
-bool_to_xopt(false) -> "false";
-bool_to_xopt(true) -> "true".
+xfield_label(subscription_type) ->
+ ?SUBSCRIPTION_TYPE_LABEL;
+xfield_label(subscription_depth) ->
+ ?SUBSCRIPTION_DEPTH_LABEL.
+
+-spec(xfield_val/2 ::
+(
+ Field :: 'deliver'
+% | 'digest'
+% | 'digest_frequency'
+% | 'expire'
+% | 'include_body'
+ | 'show_values'
+ | 'subscription_type'
+ | 'subscription_depth',
+ Val :: boolean()
+ | binary()
+ | integer()
+ | [binary()])
+% | erlang:timestamp())
+ -> [binary()]
+).
+
+xfield_val(deliver, Val) -> [bool_to_xopt(Val)];
+%xfield_val(digest, Val) -> [bool_to_xopt(Val)];
+%xfield_val(digest_frequency, Val) ->
+% [iolist_to_binary(integer_to_list(Val))];
+%xfield_val(expire, Val) ->
+% [jlib:now_to_utc_string(Val)];
+%%xfield_val(include_body, Val) -> [bool_to_xopt(Val)];
+xfield_val(show_values, Val) -> Val;
+xfield_val(subscription_type, items) -> [<<"items">>];
+xfield_val(subscription_type, nodes) -> [<<"nodes">>];
+xfield_val(subscription_depth, all) -> [<<"all">>];
+xfield_val(subscription_depth, N) ->
+ [iolist_to_binary(integer_to_list(N))].
+
+
+bool_to_xopt(true) -> <<"true">>;
+bool_to_xopt(false) -> <<"false">>.
diff --git a/src/mod_pubsub/pubsub_subscription_odbc.erl b/src/mod_pubsub/pubsub_subscription_odbc.erl
index ea26f1ddd..55b337c80 100644
--- a/src/mod_pubsub/pubsub_subscription_odbc.erl
+++ b/src/mod_pubsub/pubsub_subscription_odbc.erl
@@ -22,139 +22,211 @@
%%% ====================================================================
-module(pubsub_subscription_odbc).
+
-author("pablo.polvorin@process-one.net").
%% API
--export([init/0,
- subscribe_node/3,
- unsubscribe_node/3,
- get_subscription/3,
- set_subscription/4,
- get_options_xform/2,
- parse_options_xform/1]).
+-export([init/0, subscribe_node/3, unsubscribe_node/3,
+ get_subscription/3, set_subscription/4,
+ get_options_xform/2, parse_options_xform/1]).
-include("pubsub.hrl").
+
-include("jlib.hrl").
--define(PUBSUB_DELIVER, "pubsub#deliver").
--define(PUBSUB_DIGEST, "pubsub#digest").
--define(PUBSUB_DIGEST_FREQUENCY, "pubsub#digest_frequency").
--define(PUBSUB_EXPIRE, "pubsub#expire").
--define(PUBSUB_INCLUDE_BODY, "pubsub#include_body").
--define(PUBSUB_SHOW_VALUES, "pubsub#show-values").
--define(PUBSUB_SUBSCRIPTION_TYPE, "pubsub#subscription_type").
--define(PUBSUB_SUBSCRIPTION_DEPTH, "pubsub#subscription_depth").
+-define(PUBSUB_DELIVER, <<"pubsub#deliver">>).
+
+-define(PUBSUB_DIGEST, <<"pubsub#digest">>).
+
+-define(PUBSUB_DIGEST_FREQUENCY,
+ <<"pubsub#digest_frequency">>).
+
+-define(PUBSUB_EXPIRE, <<"pubsub#expire">>).
+
+-define(PUBSUB_INCLUDE_BODY, <<"pubsub#include_body">>).
+
+-define(PUBSUB_SHOW_VALUES, <<"pubsub#show-values">>).
+
+-define(PUBSUB_SUBSCRIPTION_TYPE,
+ <<"pubsub#subscription_type">>).
+
+-define(PUBSUB_SUBSCRIPTION_DEPTH,
+ <<"pubsub#subscription_depth">>).
-define(DELIVER_LABEL,
- "Whether an entity wants to receive or disable notifications").
+ <<"Whether an entity wants to receive or "
+ "disable notifications">>).
+
-define(DIGEST_LABEL,
- "Whether an entity wants to receive digests (aggregations) of notifications or all notifications individually").
+ <<"Whether an entity wants to receive digests "
+ "(aggregations) of notifications or all "
+ "notifications individually">>).
+
-define(DIGEST_FREQUENCY_LABEL,
- "The minimum number of milliseconds between sending any two notification digests").
+ <<"The minimum number of milliseconds between "
+ "sending any two notification digests">>).
+
-define(EXPIRE_LABEL,
- "The DateTime at which a leased subscription will end or has ended").
+ <<"The DateTime at which a leased subscription "
+ "will end or has ended">>).
+
-define(INCLUDE_BODY_LABEL,
- "Whether an entity wants to receive an XMPP message body in addition to the payload format").
+ <<"Whether an entity wants to receive an "
+ "XMPP message body in addition to the "
+ "payload format">>).
+
-define(SHOW_VALUES_LABEL,
- "The presence states for which an entity wants to receive notifications").
+ <<"The presence states for which an entity "
+ "wants to receive notifications">>).
+
-define(SUBSCRIPTION_TYPE_LABEL,
- "Type of notification to receive").
+ <<"Type of notification to receive">>).
+
-define(SUBSCRIPTION_DEPTH_LABEL,
- "Depth from subscription for which to receive notifications").
+ <<"Depth from subscription for which to "
+ "receive notifications">>).
+
+-define(SHOW_VALUE_AWAY_LABEL,
+ <<"XMPP Show Value of Away">>).
--define(SHOW_VALUE_AWAY_LABEL, "XMPP Show Value of Away").
--define(SHOW_VALUE_CHAT_LABEL, "XMPP Show Value of Chat").
--define(SHOW_VALUE_DND_LABEL, "XMPP Show Value of DND (Do Not Disturb)").
--define(SHOW_VALUE_ONLINE_LABEL, "Mere Availability in XMPP (No Show Value)").
--define(SHOW_VALUE_XA_LABEL, "XMPP Show Value of XA (Extended Away)").
+-define(SHOW_VALUE_CHAT_LABEL,
+ <<"XMPP Show Value of Chat">>).
+
+-define(SHOW_VALUE_DND_LABEL,
+ <<"XMPP Show Value of DND (Do Not Disturb)">>).
+
+-define(SHOW_VALUE_ONLINE_LABEL,
+ <<"Mere Availability in XMPP (No Show Value)">>).
+
+-define(SHOW_VALUE_XA_LABEL,
+ <<"XMPP Show Value of XA (Extended Away)">>).
-define(SUBSCRIPTION_TYPE_VALUE_ITEMS_LABEL,
- "Receive notification of new items only").
+ <<"Receive notification of new items only">>).
+
-define(SUBSCRIPTION_TYPE_VALUE_NODES_LABEL,
- "Receive notification of new nodes only").
+ <<"Receive notification of new nodes only">>).
-define(SUBSCRIPTION_DEPTH_VALUE_ONE_LABEL,
- "Receive notification from direct child nodes only").
--define(SUBSCRIPTION_DEPTH_VALUE_ALL_LABEL,
- "Receive notification from all descendent nodes").
+ <<"Receive notification from direct child "
+ "nodes only">>).
+-define(SUBSCRIPTION_DEPTH_VALUE_ALL_LABEL,
+ <<"Receive notification from all descendent "
+ "nodes">>).
-define(DB_MOD, pubsub_db_odbc).
%%====================================================================
%% API
%%====================================================================
-init() ->
- ok = create_table().
+init() -> ok = create_table().
+
+-spec(subscribe_node/3 ::
+(
+ _JID :: _,
+ _NodeID :: _,
+ Options :: mod_pubsub:subOptions())
+ -> {result, mod_pubsub:subId()}
+).
subscribe_node(_JID, _NodeID, Options) ->
SubID = make_subid(),
- ?DB_MOD:add_subscription(#pubsub_subscription{subid = SubID, options = Options}),
+ (?DB_MOD):add_subscription(#pubsub_subscription{subid =
+ SubID,
+ options = Options}),
{result, SubID}.
-
+-spec(unsubscribe_node/3 ::
+(
+ _JID :: _,
+ _NodeID :: _,
+ SubID :: mod_pubsub:subId())
+ -> {result, mod_pubsub:subscription()}
+ | {error, notfound}
+).
unsubscribe_node(_JID, _NodeID, SubID) ->
- case ?DB_MOD:read_subscription(SubID) of
- {ok, Sub} ->
- ?DB_MOD:delete_subscription(SubID),
- {result, Sub};
- notfound ->
- {error, notfound}
+ case (?DB_MOD):read_subscription(SubID) of
+ {ok, Sub} ->
+ (?DB_MOD):delete_subscription(SubID), {result, Sub};
+ notfound -> {error, notfound}
end.
+-spec(get_subscription/3 ::
+(
+ _JID :: _,
+ _NodeID :: _,
+ SubId :: mod_pubsub:subId())
+ -> {result, mod_pubsub:subscription()}
+ | {error, notfound}
+).
get_subscription(_JID, _NodeID, SubID) ->
- case ?DB_MOD:read_subscription(SubID) of
- {ok, Sub} -> {result, Sub};
- notfound -> {error, notfound}
+ case (?DB_MOD):read_subscription(SubID) of
+ {ok, Sub} -> {result, Sub};
+ notfound -> {error, notfound}
end.
-
+-spec(set_subscription/4 ::
+(
+ _JID :: _,
+ _NodeID :: _,
+ SubId :: mod_pubsub:subId(),
+ Options :: mod_pubsub:subOptions())
+ -> {result, ok}
+).
set_subscription(_JID, _NodeID, SubID, Options) ->
- case ?DB_MOD:read_subscription(SubID) of
- {ok, _} ->
- ?DB_MOD:update_subscription(#pubsub_subscription{subid = SubID, options = Options}),
- {result, ok};
- notfound ->
- ?DB_MOD:add_subscription(#pubsub_subscription{subid = SubID, options = Options}),
- {result, ok}
+ case (?DB_MOD):read_subscription(SubID) of
+ {ok, _} ->
+ (?DB_MOD):update_subscription(#pubsub_subscription{subid
+ = SubID,
+ options =
+ Options}),
+ {result, ok};
+ notfound ->
+ (?DB_MOD):add_subscription(#pubsub_subscription{subid =
+ SubID,
+ options = Options}),
+ {result, ok}
end.
-
get_options_xform(Lang, Options) ->
Keys = [deliver, show_values, subscription_type, subscription_depth],
- XFields = [get_option_xfield(Lang, Key, Options) || Key <- Keys],
-
- {result, {xmlelement, "x", [{"xmlns", ?NS_XDATA}],
- [{xmlelement, "field", [{"var", "FORM_TYPE"}, {"type", "hidden"}],
- [{xmlelement, "value", [],
- [{xmlcdata, ?NS_PUBSUB_SUB_OPTIONS}]}]}] ++ XFields}}.
+ XFields = [get_option_xfield(Lang, Key, Options)
+ || Key <- Keys],
+ {result,
+ #xmlel{name = <<"x">>,
+ attrs = [{<<"xmlns">>, ?NS_XDATA}],
+ children =
+ [#xmlel{name = <<"field">>,
+ attrs =
+ [{<<"var">>, <<"FORM_TYPE">>},
+ {<<"type">>, <<"hidden">>}],
+ children =
+ [#xmlel{name = <<"value">>, attrs = [],
+ children =
+ [{xmlcdata, ?NS_PUBSUB_SUB_OPTIONS}]}]}]
+ ++ XFields}}.
parse_options_xform(XFields) ->
case xml:remove_cdata(XFields) of
- [] -> {result, []};
- [{xmlelement, "x", _Attrs, _Els} = XEl] ->
- case jlib:parse_xdata_submit(XEl) of
- XData when is_list(XData) ->
- case set_xoption(XData, []) of
- Opts when is_list(Opts) -> {result, Opts};
- Other -> Other
- end;
- Other ->
- Other
- end;
- Other ->
- Other
+ [#xmlel{name = <<"x">>} = XEl] ->
+ case jlib:parse_xdata_submit(XEl) of
+ XData when is_list(XData) ->
+ Opts = set_xoption(XData, []),
+ {result, Opts};
+ Other -> Other
+ end;
+ _ -> {result, []}
end.
%%====================================================================
%% Internal functions
%%====================================================================
-create_table() ->
- ok.
-
+create_table() -> ok.
+-spec(make_subid/0 :: () -> mod_pubsub:subId()).
make_subid() ->
{T1, T2, T3} = now(),
- lists:flatten(io_lib:fwrite("~.16B~.16B~.16B", [T1, T2, T3])).
+ iolist_to_binary(io_lib:fwrite("~.16B~.16B~.16B", [T1, T2, T3])).
%%
%% Subscription XForm processing.
@@ -162,133 +234,153 @@ make_subid() ->
%% Return processed options, with types converted and so forth, using
%% Opts as defaults.
-set_xoption([], Opts) ->
- Opts;
+set_xoption([], Opts) -> Opts;
set_xoption([{Var, Value} | T], Opts) ->
NewOpts = case var_xfield(Var) of
- {error, _} ->
- Opts;
- Key ->
- Val = val_xfield(Key, Value),
- lists:keystore(Key, 1, Opts, {Key, Val})
+ {error, _} -> Opts;
+ Key ->
+ Val = val_xfield(Key, Value),
+ lists:keystore(Key, 1, Opts, {Key, Val})
end,
set_xoption(T, NewOpts).
%% Return the options list's key for an XForm var.
-var_xfield(?PUBSUB_DELIVER) -> deliver;
-var_xfield(?PUBSUB_DIGEST) -> digest;
-var_xfield(?PUBSUB_DIGEST_FREQUENCY) -> digest_frequency;
-var_xfield(?PUBSUB_EXPIRE) -> expire;
-var_xfield(?PUBSUB_INCLUDE_BODY) -> include_body;
-var_xfield(?PUBSUB_SHOW_VALUES) -> show_values;
-var_xfield(?PUBSUB_SUBSCRIPTION_TYPE) -> subscription_type;
-var_xfield(?PUBSUB_SUBSCRIPTION_DEPTH) -> subscription_depth;
-var_xfield(_) -> {error, badarg}.
-
%% Convert Values for option list's Key.
-val_xfield(deliver, [Val]) -> xopt_to_bool(Val);
-val_xfield(digest, [Val]) -> xopt_to_bool(Val);
-val_xfield(digest_frequency, [Val]) -> list_to_integer(Val);
-val_xfield(expire, [Val]) -> jlib:datetime_string_to_timestamp(Val);
-val_xfield(include_body, [Val]) -> xopt_to_bool(Val);
-val_xfield(show_values, Vals) -> Vals;
-val_xfield(subscription_type, ["items"]) -> items;
-val_xfield(subscription_type, ["nodes"]) -> nodes;
-val_xfield(subscription_depth, ["all"]) -> all;
-val_xfield(subscription_depth, [Depth]) ->
- case catch list_to_integer(Depth) of
- N when is_integer(N) -> N;
- _ -> {error, ?ERR_NOT_ACCEPTABLE}
+var_xfield(?PUBSUB_DELIVER) -> deliver;
+%var_xfield(?PUBSUB_DIGEST) -> digest;
+%var_xfield(?PUBSUB_DIGEST_FREQUENCY) ->
+% digest_frequency;
+%var_xfield(?PUBSUB_EXPIRE) -> expire;
+%var_xfield(?PUBSUB_INCLUDE_BODY) -> include_body;
+var_xfield(?PUBSUB_SHOW_VALUES) -> show_values;
+var_xfield(?PUBSUB_SUBSCRIPTION_TYPE) ->
+ subscription_type;
+var_xfield(?PUBSUB_SUBSCRIPTION_DEPTH) ->
+ subscription_depth;
+var_xfield(_) -> {error, badarg}.
+
+val_xfield(deliver, [Val]) -> xopt_to_bool(Val);
+%val_xfield(digest, [Val]) -> xopt_to_bool(Val);
+%val_xfield(digest_frequency, [Val]) ->
+% jlib:binary_to_integer(Val);
+%val_xfield(expire, [Val]) ->
+% jlib:datetime_string_to_timestamp(Val);
+%val_xfield(include_body, [Val]) -> xopt_to_bool(Val);
+val_xfield(show_values, Vals) -> Vals;
+val_xfield(subscription_type, [<<"items">>]) -> items;
+val_xfield(subscription_type, [<<"nodes">>]) -> nodes;
+val_xfield(subscription_depth, [<<"all">>]) -> all;
+val_xfield(subscription_depth, [Depth]) ->
+ case catch jlib:binary_to_integer(Depth) of
+ N when is_integer(N) -> N;
+ _ -> {error, ?ERR_NOT_ACCEPTABLE}
end.
%% Convert XForm booleans to Erlang booleans.
-xopt_to_bool("0") -> false;
-xopt_to_bool("1") -> true;
-xopt_to_bool("false") -> false;
-xopt_to_bool("true") -> true;
-xopt_to_bool(_) -> {error, ?ERR_NOT_ACCEPTABLE}.
+xopt_to_bool(<<"0">>) -> false;
+xopt_to_bool(<<"1">>) -> true;
+xopt_to_bool(<<"false">>) -> false;
+xopt_to_bool(<<"true">>) -> true;
+xopt_to_bool(_) -> {error, ?ERR_NOT_ACCEPTABLE}.
%% Return a field for an XForm for Key, with data filled in, if
%% applicable, from Options.
get_option_xfield(Lang, Key, Options) ->
Var = xfield_var(Key),
Label = xfield_label(Key),
- {Type, OptEls} = type_and_options(xfield_type(Key), Lang),
+ {Type, OptEls} = type_and_options(xfield_type(Key),
+ Lang),
Vals = case lists:keysearch(Key, 1, Options) of
- {value, {_, Val}} ->
- [tr_xfield_values(Vals) || Vals <- xfield_val(Key, Val)];
- false ->
- []
+ {value, {_, Val}} ->
+ [tr_xfield_values(Vals)
+ || Vals <- xfield_val(Key, Val)];
+ false -> []
end,
- {xmlelement, "field",
- [{"var", Var}, {"type", Type},
- {"label", translate:translate(Lang, Label)}],
- OptEls ++ Vals}.
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"var">>, Var}, {<<"type">>, Type},
+ {<<"label">>, translate:translate(Lang, Label)}],
+ children = OptEls ++ Vals}.
type_and_options({Type, Options}, Lang) ->
{Type, [tr_xfield_options(O, Lang) || O <- Options]};
-type_and_options(Type, _Lang) ->
- {Type, []}.
+type_and_options(Type, _Lang) -> {Type, []}.
tr_xfield_options({Value, Label}, Lang) ->
- {xmlelement, "option",
- [{"label", translate:translate(Lang, Label)}], [{xmlelement, "value", [],
- [{xmlcdata, Value}]}]}.
+ #xmlel{name = <<"option">>,
+ attrs =
+ [{<<"label">>, translate:translate(Lang, Label)}],
+ children =
+ [#xmlel{name = <<"value">>, attrs = [],
+ children = [{xmlcdata, Value}]}]}.
tr_xfield_values(Value) ->
- {xmlelement, "value", [], [{xmlcdata, Value}]}.
-
%% Return the XForm variable name for a subscription option key.
-xfield_var(deliver) -> ?PUBSUB_DELIVER;
-xfield_var(digest) -> ?PUBSUB_DIGEST;
-xfield_var(digest_frequency) -> ?PUBSUB_DIGEST_FREQUENCY;
-xfield_var(expire) -> ?PUBSUB_EXPIRE;
-xfield_var(include_body) -> ?PUBSUB_INCLUDE_BODY;
-xfield_var(show_values) -> ?PUBSUB_SHOW_VALUES;
-xfield_var(subscription_type) -> ?PUBSUB_SUBSCRIPTION_TYPE;
-xfield_var(subscription_depth) -> ?PUBSUB_SUBSCRIPTION_DEPTH.
-
%% Return the XForm variable type for a subscription option key.
-xfield_type(deliver) -> "boolean";
-xfield_type(digest) -> "boolean";
-xfield_type(digest_frequency) -> "text-single";
-xfield_type(expire) -> "text-single";
-xfield_type(include_body) -> "boolean";
-xfield_type(show_values) ->
- {"list-multi", [{"away", ?SHOW_VALUE_AWAY_LABEL},
- {"chat", ?SHOW_VALUE_CHAT_LABEL},
- {"dnd", ?SHOW_VALUE_DND_LABEL},
- {"online", ?SHOW_VALUE_ONLINE_LABEL},
- {"xa", ?SHOW_VALUE_XA_LABEL}]};
-xfield_type(subscription_type) ->
- {"list-single", [{"items", ?SUBSCRIPTION_TYPE_VALUE_ITEMS_LABEL},
- {"nodes", ?SUBSCRIPTION_TYPE_VALUE_NODES_LABEL}]};
+ #xmlel{name = <<"value">>, attrs = [],
+ children = [{xmlcdata, Value}]}.
+
+xfield_var(deliver) -> ?PUBSUB_DELIVER;
+%xfield_var(digest) -> ?PUBSUB_DIGEST;
+%xfield_var(digest_frequency) ->
+% ?PUBSUB_DIGEST_FREQUENCY;
+%xfield_var(expire) -> ?PUBSUB_EXPIRE;
+%xfield_var(include_body) -> ?PUBSUB_INCLUDE_BODY;
+xfield_var(show_values) -> ?PUBSUB_SHOW_VALUES;
+xfield_var(subscription_type) ->
+ ?PUBSUB_SUBSCRIPTION_TYPE;
+xfield_var(subscription_depth) ->
+ ?PUBSUB_SUBSCRIPTION_DEPTH.
+
+xfield_type(deliver) -> <<"boolean">>;
+%xfield_type(digest) -> <<"boolean">>;
+%xfield_type(digest_frequency) -> <<"text-single">>;
+%xfield_type(expire) -> <<"text-single">>;
+%xfield_type(include_body) -> <<"boolean">>;
+xfield_type(show_values) ->
+ {<<"list-multi">>,
+ [{<<"away">>, ?SHOW_VALUE_AWAY_LABEL},
+ {<<"chat">>, ?SHOW_VALUE_CHAT_LABEL},
+ {<<"dnd">>, ?SHOW_VALUE_DND_LABEL},
+ {<<"online">>, ?SHOW_VALUE_ONLINE_LABEL},
+ {<<"xa">>, ?SHOW_VALUE_XA_LABEL}]};
+xfield_type(subscription_type) ->
+ {<<"list-single">>,
+ [{<<"items">>, ?SUBSCRIPTION_TYPE_VALUE_ITEMS_LABEL},
+ {<<"nodes">>, ?SUBSCRIPTION_TYPE_VALUE_NODES_LABEL}]};
xfield_type(subscription_depth) ->
- {"list-single", [{"1", ?SUBSCRIPTION_DEPTH_VALUE_ONE_LABEL},
- {"all", ?SUBSCRIPTION_DEPTH_VALUE_ALL_LABEL}]}.
+ {<<"list-single">>,
+ [{<<"1">>, ?SUBSCRIPTION_DEPTH_VALUE_ONE_LABEL},
+ {<<"all">>, ?SUBSCRIPTION_DEPTH_VALUE_ALL_LABEL}]}.
%% Return the XForm variable label for a subscription option key.
xfield_label(deliver) -> ?DELIVER_LABEL;
-xfield_label(digest) -> ?DIGEST_LABEL;
-xfield_label(digest_frequency) -> ?DIGEST_FREQUENCY_LABEL;
-xfield_label(expire) -> ?EXPIRE_LABEL;
-xfield_label(include_body) -> ?INCLUDE_BODY_LABEL;
+%xfield_label(digest) -> ?DIGEST_LABEL;
+%xfield_label(digest_frequency) ->
+% ?DIGEST_FREQUENCY_LABEL;
+%xfield_label(expire) -> ?EXPIRE_LABEL;
+%xfield_label(include_body) -> ?INCLUDE_BODY_LABEL;
xfield_label(show_values) -> ?SHOW_VALUES_LABEL;
-xfield_label(subscription_type) -> ?SUBSCRIPTION_TYPE_LABEL;
-xfield_label(subscription_depth) -> ?SUBSCRIPTION_DEPTH_LABEL.
-
%% Return the XForm value for a subscription option key.
-xfield_val(deliver, Val) -> [bool_to_xopt(Val)];
-xfield_val(digest, Val) -> [bool_to_xopt(Val)];
-xfield_val(digest_frequency, Val) -> [integer_to_list(Val)];
-xfield_val(expire, Val) -> [jlib:now_to_utc_string(Val)];
-xfield_val(include_body, Val) -> [bool_to_xopt(Val)];
-xfield_val(show_values, Val) -> Val;
-xfield_val(subscription_type, items) -> ["items"];
-xfield_val(subscription_type, nodes) -> ["nodes"];
-xfield_val(subscription_depth, all) -> ["all"];
-xfield_val(subscription_depth, N) -> [integer_to_list(N)].
-
%% Convert erlang booleans to XForms.
-bool_to_xopt(false) -> "false";
-bool_to_xopt(true) -> "true".
+xfield_label(subscription_type) ->
+ ?SUBSCRIPTION_TYPE_LABEL;
+xfield_label(subscription_depth) ->
+ ?SUBSCRIPTION_DEPTH_LABEL.
+
+xfield_val(deliver, Val) -> [bool_to_xopt(Val)];
+%xfield_val(digest, Val) -> [bool_to_xopt(Val)];
+%xfield_val(digest_frequency, Val) ->
+% [iolist_to_binary(integer_to_list(Val))];
+%xfield_val(expire, Val) ->
+% [jlib:now_to_utc_string(Val)];
+%xfield_val(include_body, Val) -> [bool_to_xopt(Val)];
+xfield_val(show_values, Val) -> Val;
+xfield_val(subscription_type, items) -> [<<"items">>];
+xfield_val(subscription_type, nodes) -> [<<"nodes">>];
+xfield_val(subscription_depth, all) -> [<<"all">>];
+xfield_val(subscription_depth, N) ->
+ [iolist_to_binary(integer_to_list(N))].
+
+bool_to_xopt(false) -> <<"false">>;
+bool_to_xopt(true) -> <<"true">>.
diff --git a/src/mod_register.erl b/src/mod_register.erl
index 891254051..9acc71a22 100644
--- a/src/mod_register.erl
+++ b/src/mod_register.erl
@@ -25,65 +25,67 @@
%%%----------------------------------------------------------------------
-module(mod_register).
+
-author('alexey@process-one.net').
-behaviour(gen_mod).
--export([start/2,
- stop/1,
- stream_feature_register/2,
- unauthenticated_iq_register/4,
- try_register/5,
- process_iq/3]).
+-export([start/2, stop/1, stream_feature_register/2,
+ unauthenticated_iq_register/4, try_register/5,
+ process_iq/3, send_registration_notifications/3]).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
start(Host, Opts) ->
- IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
- gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_REGISTER,
- ?MODULE, process_iq, IQDisc),
- gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_REGISTER,
- ?MODULE, process_iq, IQDisc),
- ejabberd_hooks:add(c2s_stream_features, Host,
- ?MODULE, stream_feature_register, 50),
+ IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
+ one_queue),
+ gen_iq_handler:add_iq_handler(ejabberd_local, Host,
+ ?NS_REGISTER, ?MODULE, process_iq, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
+ ?NS_REGISTER, ?MODULE, process_iq, IQDisc),
+ ejabberd_hooks:add(c2s_stream_features, Host, ?MODULE,
+ stream_feature_register, 50),
ejabberd_hooks:add(c2s_unauthenticated_iq, Host,
- ?MODULE, unauthenticated_iq_register, 50),
+ ?MODULE, unauthenticated_iq_register, 50),
mnesia:create_table(mod_register_ip,
- [{ram_copies, [node()]},
- {local_content, true},
+ [{ram_copies, [node()]}, {local_content, true},
{attributes, [key, value]}]),
- mnesia:add_table_copy(mod_register_ip, node(), ram_copies),
+ mnesia:add_table_copy(mod_register_ip, node(),
+ ram_copies),
ok.
stop(Host) ->
ejabberd_hooks:delete(c2s_stream_features, Host,
- ?MODULE, stream_feature_register, 50),
+ ?MODULE, stream_feature_register, 50),
ejabberd_hooks:delete(c2s_unauthenticated_iq, Host,
?MODULE, unauthenticated_iq_register, 50),
- gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_REGISTER),
- gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_REGISTER).
-
+ gen_iq_handler:remove_iq_handler(ejabberd_local, Host,
+ ?NS_REGISTER),
+ gen_iq_handler:remove_iq_handler(ejabberd_sm, Host,
+ ?NS_REGISTER).
stream_feature_register(Acc, _Host) ->
- [{xmlelement, "register",
- [{"xmlns", ?NS_FEATURE_IQREGISTER}], []} | Acc].
+ [#xmlel{name = <<"register">>,
+ attrs = [{<<"xmlns">>, ?NS_FEATURE_IQREGISTER}],
+ children = []}
+ | Acc].
-unauthenticated_iq_register(_Acc,
- Server, #iq{xmlns = ?NS_REGISTER} = IQ, IP) ->
+unauthenticated_iq_register(_Acc, Server,
+ #iq{xmlns = ?NS_REGISTER} = IQ, IP) ->
Address = case IP of
- {A, _Port} -> A;
- _ -> undefined
+ {A, _Port} -> A;
+ _ -> undefined
end,
- ResIQ = process_iq(jlib:make_jid("", "", ""),
- jlib:make_jid("", Server, ""),
- IQ,
- Address),
- Res1 = jlib:replace_from_to(jlib:make_jid("", Server, ""),
- jlib:make_jid("", "", ""),
- jlib:iq_to_xml(ResIQ)),
- jlib:remove_attr("to", Res1);
-
+ ResIQ = process_iq(jlib:make_jid(<<"">>, <<"">>,
+ <<"">>),
+ jlib:make_jid(<<"">>, Server, <<"">>), IQ, Address),
+ Res1 = jlib:replace_from_to(jlib:make_jid(<<"">>,
+ Server, <<"">>),
+ jlib:make_jid(<<"">>, <<"">>, <<"">>),
+ jlib:iq_to_xml(ResIQ)),
+ jlib:remove_attr(<<"to">>, Res1);
unauthenticated_iq_register(Acc, _Server, _IQ, _IP) ->
Acc.
@@ -91,462 +93,514 @@ process_iq(From, To, IQ) ->
process_iq(From, To, IQ, jlib:jid_tolower(From)).
process_iq(From, To,
- #iq{type = Type, lang = Lang, sub_el = SubEl, id = ID} = IQ,
+ #iq{type = Type, lang = Lang, sub_el = SubEl, id = ID} =
+ IQ,
Source) ->
- IsCaptchaEnabled = case gen_mod:get_module_opt(
- To#jid.lserver, ?MODULE, captcha_protected, false) of
- true ->
- true;
- _ ->
- false
+ IsCaptchaEnabled = case
+ gen_mod:get_module_opt(To#jid.lserver, ?MODULE,
+ captcha_protected,
+ fun(B) when is_boolean(B) -> B end,
+ false)
+ of
+ true -> true;
+ _ -> false
end,
case Type of
- set ->
- UTag = xml:get_subtag(SubEl, "username"),
- PTag = xml:get_subtag(SubEl, "password"),
- RTag = xml:get_subtag(SubEl, "remove"),
- Server = To#jid.lserver,
- Access = gen_mod:get_module_opt(Server, ?MODULE, access, all),
- AllowRemove = (allow == acl:match_rule(Server, Access, From)),
- if
- (UTag /= false) and (RTag /= false) and AllowRemove ->
- User = xml:get_tag_cdata(UTag),
- case From of
- #jid{user = User, lserver = Server} ->
- ejabberd_auth:remove_user(User, Server),
- IQ#iq{type = result, sub_el = [SubEl]};
- _ ->
- if
- PTag /= false ->
- Password = xml:get_tag_cdata(PTag),
- case ejabberd_auth:remove_user(User,
- Server,
- Password) of
- ok ->
- IQ#iq{type = result,
- sub_el = [SubEl]};
- %% TODO FIXME: This piece of
- %% code does not work since
- %% the code have been changed
- %% to allow several auth
- %% modules. lists:foreach can
- %% only return ok:
- not_allowed ->
- IQ#iq{type = error,
- sub_el =
- [SubEl, ?ERR_NOT_ALLOWED]};
- not_exists ->
- IQ#iq{type = error,
- sub_el =
- [SubEl, ?ERR_ITEM_NOT_FOUND]};
- _ ->
- IQ#iq{type = error,
- sub_el =
- [SubEl,
- ?ERR_INTERNAL_SERVER_ERROR]}
- end;
- true ->
+ set ->
+ UTag = xml:get_subtag(SubEl, <<"username">>),
+ PTag = xml:get_subtag(SubEl, <<"password">>),
+ RTag = xml:get_subtag(SubEl, <<"remove">>),
+ Server = To#jid.lserver,
+ Access = gen_mod:get_module_opt(Server, ?MODULE, access,
+ fun(A) when is_atom(A) -> A end,
+ all),
+ AllowRemove = allow ==
+ acl:match_rule(Server, Access, From),
+ if (UTag /= false) and (RTag /= false) and
+ AllowRemove ->
+ User = xml:get_tag_cdata(UTag),
+ case From of
+ #jid{user = User, lserver = Server} ->
+ ejabberd_auth:remove_user(User, Server),
+ IQ#iq{type = result, sub_el = [SubEl]};
+ _ ->
+ if PTag /= false ->
+ Password = xml:get_tag_cdata(PTag),
+ case ejabberd_auth:remove_user(User, Server,
+ Password)
+ of
+ ok -> IQ#iq{type = result, sub_el = [SubEl]};
+ %% TODO FIXME: This piece of
+ %% code does not work since
+ %% the code have been changed
+ %% to allow several auth
+ %% modules. lists:foreach can
+ %% only return ok:
+ not_allowed ->
IQ#iq{type = error,
- sub_el = [SubEl, ?ERR_BAD_REQUEST]}
- end
- end;
- (UTag == false) and (RTag /= false) and AllowRemove ->
- case From of
- #jid{user = User,
- lserver = Server,
- resource = Resource} ->
- ResIQ = #iq{type = result, xmlns = ?NS_REGISTER,
- id = ID,
- sub_el = [SubEl]},
- ejabberd_router:route(
- jlib:make_jid(User, Server, Resource),
- jlib:make_jid(User, Server, Resource),
- jlib:iq_to_xml(ResIQ)),
- ejabberd_auth:remove_user(User, Server),
- ignore;
- _ ->
- IQ#iq{type = error,
- sub_el = [SubEl, ?ERR_NOT_ALLOWED]}
- end;
- (UTag /= false) and (PTag /= false) ->
- User = xml:get_tag_cdata(UTag),
- Password = xml:get_tag_cdata(PTag),
- try_register_or_set_password(
- User, Server, Password, From,
- IQ, SubEl, Source, Lang, not IsCaptchaEnabled);
- IsCaptchaEnabled ->
- case ejabberd_captcha:process_reply(SubEl) of
- ok ->
- case process_xdata_submit(SubEl) of
- {ok, User, Password} ->
- try_register_or_set_password(
- User, Server, Password, From,
- IQ, SubEl, Source, Lang, true);
+ sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+ not_exists ->
+ IQ#iq{type = error,
+ sub_el =
+ [SubEl, ?ERR_ITEM_NOT_FOUND]};
_ ->
IQ#iq{type = error,
- sub_el = [SubEl, ?ERR_BAD_REQUEST]}
- end;
- {error, malformed} ->
- IQ#iq{type = error,
- sub_el = [SubEl, ?ERR_BAD_REQUEST]};
- _ ->
- ErrText = "The CAPTCHA verification has failed",
- IQ#iq{type = error,
- sub_el = [SubEl,
- ?ERRT_NOT_ALLOWED(Lang, ErrText)]}
- end;
- true ->
- IQ#iq{type = error,
- sub_el = [SubEl, ?ERR_BAD_REQUEST]}
- end;
- get ->
- {IsRegistered, UsernameSubels, QuerySubels} =
- case From of
- #jid{user = User, lserver = Server} ->
- case ejabberd_auth:is_user_exists(User,Server) of
- true ->
- {true, [{xmlcdata, User}],
- [{xmlelement, "registered", [], []}]};
- false ->
- {false, [{xmlcdata, User}], []}
- end;
- _ ->
- {false, [], []}
- end,
- if IsCaptchaEnabled and not IsRegistered ->
- TopInstrEl = {xmlelement, "instructions", [],
- [{xmlcdata,
- translate:translate(
- Lang, "You need a client that supports x:data "
- "and CAPTCHA to register")}]},
- InstrEl = {xmlelement, "instructions", [],
- [{xmlcdata,
- translate:translate(
- Lang,
- "Choose a username and password "
- "to register with this server")}]},
- UField = {xmlelement, "field",
- [{"type", "text-single"},
- {"label", translate:translate(Lang, "User")},
- {"var", "username"}],
- [{xmlelement, "required", [], []}]},
- PField = {xmlelement, "field",
- [{"type", "text-private"},
- {"label", translate:translate(Lang, "Password")},
- {"var", "password"}],
- [{xmlelement, "required", [], []}]},
- case ejabberd_captcha:create_captcha_x(
- ID, To, Lang, Source, [InstrEl, UField, PField]) of
- {ok, CaptchaEls} ->
- IQ#iq{type = result,
- sub_el = [{xmlelement, "query",
- [{"xmlns", "jabber:iq:register"}],
+ sub_el =
+ [SubEl,
+ ?ERR_INTERNAL_SERVER_ERROR]}
+ end;
+ true ->
+ IQ#iq{type = error,
+ sub_el = [SubEl, ?ERR_BAD_REQUEST]}
+ end
+ end;
+ (UTag == false) and (RTag /= false) and AllowRemove ->
+ case From of
+ #jid{user = User, lserver = Server,
+ resource = Resource} ->
+ ResIQ = #iq{type = result, xmlns = ?NS_REGISTER,
+ id = ID, sub_el = [SubEl]},
+ ejabberd_router:route(jlib:make_jid(User, Server,
+ Resource),
+ jlib:make_jid(User, Server,
+ Resource),
+ jlib:iq_to_xml(ResIQ)),
+ ejabberd_auth:remove_user(User, Server),
+ ignore;
+ _ ->
+ IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}
+ end;
+ (UTag /= false) and (PTag /= false) ->
+ User = xml:get_tag_cdata(UTag),
+ Password = xml:get_tag_cdata(PTag),
+ try_register_or_set_password(User, Server, Password,
+ From, IQ, SubEl, Source, Lang,
+ not IsCaptchaEnabled);
+ IsCaptchaEnabled ->
+ case ejabberd_captcha:process_reply(SubEl) of
+ ok ->
+ case process_xdata_submit(SubEl) of
+ {ok, User, Password} ->
+ try_register_or_set_password(User, Server,
+ Password, From, IQ,
+ SubEl, Source, Lang,
+ true);
+ _ ->
+ IQ#iq{type = error,
+ sub_el = [SubEl, ?ERR_BAD_REQUEST]}
+ end;
+ {error, malformed} ->
+ IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]};
+ _ ->
+ ErrText = <<"The CAPTCHA verification has failed">>,
+ IQ#iq{type = error,
+ sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, ErrText)]}
+ end;
+ true ->
+ IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]}
+ end;
+ get ->
+ {IsRegistered, UsernameSubels, QuerySubels} = case From
+ of
+ #jid{user = User,
+ lserver =
+ Server} ->
+ case
+ ejabberd_auth:is_user_exists(User,
+ Server)
+ of
+ true ->
+ {true,
+ [{xmlcdata,
+ User}],
+ [#xmlel{name
+ =
+ <<"registered">>,
+ attrs
+ =
+ [],
+ children
+ =
+ []}]};
+ false ->
+ {false,
+ [{xmlcdata,
+ User}],
+ []}
+ end;
+ _ -> {false, [], []}
+ end,
+ if IsCaptchaEnabled and not IsRegistered ->
+ TopInstrEl = #xmlel{name = <<"instructions">>,
+ attrs = [],
+ children =
+ [{xmlcdata,
+ translate:translate(Lang,
+ <<"You need a client that supports x:data "
+ "and CAPTCHA to register">>)}]},
+ InstrEl = #xmlel{name = <<"instructions">>, attrs = [],
+ children =
+ [{xmlcdata,
+ translate:translate(Lang,
+ <<"Choose a username and password to register "
+ "with this server">>)}]},
+ UField = #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"type">>, <<"text-single">>},
+ {<<"label">>,
+ translate:translate(Lang, <<"User">>)},
+ {<<"var">>, <<"username">>}],
+ children =
+ [#xmlel{name = <<"required">>, attrs = [],
+ children = []}]},
+ PField = #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"type">>, <<"text-private">>},
+ {<<"label">>,
+ translate:translate(Lang,
+ <<"Password">>)},
+ {<<"var">>, <<"password">>}],
+ children =
+ [#xmlel{name = <<"required">>, attrs = [],
+ children = []}]},
+ case ejabberd_captcha:create_captcha_x(ID, To, Lang,
+ Source,
+ [InstrEl, UField,
+ PField])
+ of
+ {ok, CaptchaEls} ->
+ IQ#iq{type = result,
+ sub_el =
+ [#xmlel{name = <<"query">>,
+ attrs =
+ [{<<"xmlns">>,
+ <<"jabber:iq:register">>}],
+ children =
[TopInstrEl | CaptchaEls]}]};
- {error, limit} ->
- ErrText = "Too many CAPTCHA requests",
- IQ#iq{type = error,
- sub_el = [SubEl, ?ERRT_RESOURCE_CONSTRAINT(
- Lang, ErrText)]};
- _Err ->
- ErrText = "Unable to generate a CAPTCHA",
- IQ#iq{type = error,
- sub_el = [SubEl, ?ERRT_INTERNAL_SERVER_ERROR(
- Lang, ErrText)]}
- end;
- true ->
- IQ#iq{type = result,
- sub_el = [{xmlelement,
- "query",
- [{"xmlns", "jabber:iq:register"}],
- [{xmlelement, "instructions", [],
- [{xmlcdata,
- translate:translate(
- Lang,
- "Choose a username and password "
- "to register with this server")}]},
- {xmlelement, "username", [], UsernameSubels},
- {xmlelement, "password", [], []}
- | QuerySubels]}]}
- end
+ {error, limit} ->
+ ErrText = <<"Too many CAPTCHA requests">>,
+ IQ#iq{type = error,
+ sub_el =
+ [SubEl,
+ ?ERRT_RESOURCE_CONSTRAINT(Lang, ErrText)]};
+ _Err ->
+ ErrText = <<"Unable to generate a CAPTCHA">>,
+ IQ#iq{type = error,
+ sub_el =
+ [SubEl,
+ ?ERRT_INTERNAL_SERVER_ERROR(Lang, ErrText)]}
+ end;
+ true ->
+ IQ#iq{type = result,
+ sub_el =
+ [#xmlel{name = <<"query">>,
+ attrs =
+ [{<<"xmlns">>,
+ <<"jabber:iq:register">>}],
+ children =
+ [#xmlel{name = <<"instructions">>,
+ attrs = [],
+ children =
+ [{xmlcdata,
+ translate:translate(Lang,
+ <<"Choose a username and password to register "
+ "with this server">>)}]},
+ #xmlel{name = <<"username">>,
+ attrs = [],
+ children = UsernameSubels},
+ #xmlel{name = <<"password">>,
+ attrs = [], children = []}
+ | QuerySubels]}]}
+ end
end.
-try_register_or_set_password(User, Server, Password, From, IQ,
- SubEl, Source, Lang, CaptchaSucceed) ->
+try_register_or_set_password(User, Server, Password,
+ From, IQ, SubEl, Source, Lang, CaptchaSucceed) ->
case From of
- #jid{user = User, lserver = Server} ->
- try_set_password(User, Server, Password, IQ, SubEl, Lang);
- _ when CaptchaSucceed ->
- case check_from(From, Server) of
- allow ->
- case try_register(User, Server, Password,
- Source, Lang) of
- ok ->
- IQ#iq{type = result,
- sub_el = [SubEl]};
- {error, Error} ->
- IQ#iq{type = error,
- sub_el = [SubEl, Error]}
- end;
- deny ->
- IQ#iq{type = error,
- sub_el = [SubEl, ?ERR_FORBIDDEN]}
- end;
- _ ->
- IQ#iq{type = error,
- sub_el = [SubEl, ?ERR_NOT_ALLOWED]}
+ #jid{user = User, lserver = Server} ->
+ try_set_password(User, Server, Password, IQ, SubEl,
+ Lang);
+ _ when CaptchaSucceed ->
+ case check_from(From, Server) of
+ allow ->
+ case try_register(User, Server, Password, Source, Lang)
+ of
+ ok -> IQ#iq{type = result, sub_el = [SubEl]};
+ {error, Error} ->
+ IQ#iq{type = error, sub_el = [SubEl, Error]}
+ end;
+ deny ->
+ IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]}
+ end;
+ _ ->
+ IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}
end.
%% @doc Try to change password and return IQ response
-try_set_password(User, Server, Password, IQ, SubEl, Lang) ->
+try_set_password(User, Server, Password, IQ, SubEl,
+ Lang) ->
case is_strong_password(Server, Password) of
- true ->
- case ejabberd_auth:set_password(User, Server, Password) of
- ok ->
- IQ#iq{type = result, sub_el = [SubEl]};
- {error, empty_password} ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]};
- {error, not_allowed} ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
- {error, invalid_jid} ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_ITEM_NOT_FOUND]};
- _ ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]}
- end;
- false ->
- ErrText = "The password is too weak",
- IQ#iq{type = error,
- sub_el = [SubEl, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)]}
+ true ->
+ case ejabberd_auth:set_password(User, Server, Password)
+ of
+ ok -> IQ#iq{type = result, sub_el = [SubEl]};
+ {error, empty_password} ->
+ IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]};
+ {error, not_allowed} ->
+ IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+ {error, invalid_jid} ->
+ IQ#iq{type = error,
+ sub_el = [SubEl, ?ERR_ITEM_NOT_FOUND]};
+ _ ->
+ IQ#iq{type = error,
+ sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]}
+ end;
+ false ->
+ ErrText = <<"The password is too weak">>,
+ IQ#iq{type = error,
+ sub_el = [SubEl, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)]}
end.
try_register(User, Server, Password, SourceRaw, Lang) ->
case jlib:is_nodename(User) of
- false ->
- {error, ?ERR_BAD_REQUEST};
- _ ->
- JID = jlib:make_jid(User, Server, ""),
- Access = gen_mod:get_module_opt(Server, ?MODULE, access, all),
- IPAccess = get_ip_access(Server),
- case {acl:match_rule(Server, Access, JID),
- check_ip_access(SourceRaw, IPAccess)} of
- {deny, _} ->
- {error, ?ERR_FORBIDDEN};
- {_, deny} ->
- {error, ?ERR_FORBIDDEN};
- {allow, allow} ->
- Source = may_remove_resource(SourceRaw),
- case check_timeout(Source) of
+ false -> {error, ?ERR_BAD_REQUEST};
+ _ ->
+ JID = jlib:make_jid(User, Server, <<"">>),
+ Access = gen_mod:get_module_opt(Server, ?MODULE, access,
+ fun(A) when is_atom(A) -> A end,
+ all),
+ IPAccess = get_ip_access(Server),
+ case {acl:match_rule(Server, Access, JID),
+ check_ip_access(SourceRaw, IPAccess)}
+ of
+ {deny, _} -> {error, ?ERR_FORBIDDEN};
+ {_, deny} -> {error, ?ERR_FORBIDDEN};
+ {allow, allow} ->
+ Source = may_remove_resource(SourceRaw),
+ case check_timeout(Source) of
+ true ->
+ case is_strong_password(Server, Password) of
true ->
- case is_strong_password(Server, Password) of
- true ->
- case ejabberd_auth:try_register(
- User, Server, Password) of
- {atomic, ok} ->
- send_welcome_message(JID),
- send_registration_notifications(JID, Source),
- ok;
- Error ->
- remove_timeout(Source),
- case Error of
- {atomic, exists} ->
- {error, ?ERR_CONFLICT};
- {error, invalid_jid} ->
- {error, ?ERR_JID_MALFORMED};
- {error, not_allowed} ->
- {error, ?ERR_NOT_ALLOWED};
- {error, _Reason} ->
- {error, ?ERR_INTERNAL_SERVER_ERROR}
- end
- end;
- false ->
- ErrText = "The password is too weak",
- {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)}
+ case ejabberd_auth:try_register(User, Server,
+ Password)
+ of
+ {atomic, ok} ->
+ send_welcome_message(JID),
+ send_registration_notifications(
+ ?MODULE, JID, Source),
+ ok;
+ Error ->
+ remove_timeout(Source),
+ case Error of
+ {atomic, exists} -> {error, ?ERR_CONFLICT};
+ {error, invalid_jid} ->
+ {error, ?ERR_JID_MALFORMED};
+ {error, not_allowed} ->
+ {error, ?ERR_NOT_ALLOWED};
+ {error, _Reason} ->
+ {error, ?ERR_INTERNAL_SERVER_ERROR}
+ end
end;
false ->
- ErrText = "Users are not allowed to register "
- "accounts so quickly",
- {error, ?ERRT_RESOURCE_CONSTRAINT(Lang, ErrText)}
- end
- end
+ ErrText = <<"The password is too weak">>,
+ {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)}
+ end;
+ false ->
+ ErrText =
+ <<"Users are not allowed to register accounts "
+ "so quickly">>,
+ {error, ?ERRT_RESOURCE_CONSTRAINT(Lang, ErrText)}
+ end
+ end
end.
-
send_welcome_message(JID) ->
Host = JID#jid.lserver,
- case gen_mod:get_module_opt(Host, ?MODULE, welcome_message, {"", ""}) of
- {"", ""} ->
- ok;
- {Subj, Body} ->
- ejabberd_router:route(
- jlib:make_jid("", Host, ""),
- JID,
- {xmlelement, "message", [{"type", "normal"}],
- [{xmlelement, "subject", [], [{xmlcdata, Subj}]},
- {xmlelement, "body", [], [{xmlcdata, Body}]}]});
- _ ->
- ok
+ case gen_mod:get_module_opt(Host, ?MODULE, welcome_message,
+ fun({S, B}) ->
+ {iolist_to_binary(S),
+ iolist_to_binary(B)}
+ end, {<<"">>, <<"">>})
+ of
+ {<<"">>, <<"">>} -> ok;
+ {Subj, Body} ->
+ ejabberd_router:route(jlib:make_jid(<<"">>, Host,
+ <<"">>),
+ JID,
+ #xmlel{name = <<"message">>,
+ attrs = [{<<"type">>, <<"normal">>}],
+ children =
+ [#xmlel{name = <<"subject">>,
+ attrs = [],
+ children =
+ [{xmlcdata, Subj}]},
+ #xmlel{name = <<"body">>,
+ attrs = [],
+ children =
+ [{xmlcdata, Body}]}]});
+ _ -> ok
end.
-send_registration_notifications(UJID, Source) ->
+send_registration_notifications(Mod, UJID, Source) ->
Host = UJID#jid.lserver,
- case gen_mod:get_module_opt(Host, ?MODULE, registration_watchers, []) of
- [] -> ok;
- JIDs when is_list(JIDs) ->
- Body = lists:flatten(
- io_lib:format(
- "[~s] The account ~s was registered from IP address ~s "
- "on node ~w using ~p.",
- [get_time_string(), jlib:jid_to_string(UJID),
- ip_to_string(Source), node(), ?MODULE])),
- lists:foreach(
- fun(S) ->
- case jlib:string_to_jid(S) of
- error -> ok;
- JID ->
- ejabberd_router:route(
- jlib:make_jid("", Host, ""),
- JID,
- {xmlelement, "message", [{"type", "chat"}],
- [{xmlelement, "body", [],
- [{xmlcdata, Body}]}]})
- end
- end, JIDs);
- _ ->
- ok
+ case gen_mod:get_module_opt(
+ Host, Mod, registration_watchers,
+ fun(Ss) ->
+ [#jid{} = jlib:string_to_jid(iolist_to_binary(S))
+ || S <- Ss]
+ end, []) of
+ [] -> ok;
+ JIDs when is_list(JIDs) ->
+ Body =
+ iolist_to_binary(io_lib:format("[~s] The account ~s was registered from "
+ "IP address ~s on node ~w using ~p.",
+ [get_time_string(),
+ jlib:jid_to_string(UJID),
+ ip_to_string(Source), node(),
+ Mod])),
+ lists:foreach(
+ fun(JID) ->
+ ejabberd_router:route(
+ jlib:make_jid(<<"">>, Host, <<"">>),
+ JID,
+ #xmlel{name = <<"message">>,
+ attrs = [{<<"type">>, <<"chat">>}],
+ children = [#xmlel{name = <<"body">>,
+ attrs = [],
+ children = [{xmlcdata,Body}]}]})
+ end, JIDs)
end.
-check_from(#jid{user = "", server = ""}, _Server) ->
+check_from(#jid{user = <<"">>, server = <<"">>},
+ _Server) ->
allow;
check_from(JID, Server) ->
- Access = gen_mod:get_module_opt(Server, ?MODULE, access_from, none),
+ Access = gen_mod:get_module_opt(Server, ?MODULE, access_from,
+ fun(A) when is_atom(A) -> A end,
+ none),
acl:match_rule(Server, Access, JID).
-check_timeout(undefined) ->
- true;
+check_timeout(undefined) -> true;
check_timeout(Source) ->
- Timeout = case ejabberd_config:get_local_option(registration_timeout) of
- undefined -> 600;
- TO -> TO
- end,
- if
- is_integer(Timeout) ->
- {MSec, Sec, _USec} = now(),
- Priority = -(MSec * 1000000 + Sec),
- CleanPriority = Priority + Timeout,
- F = fun() ->
- Treap = case mnesia:read(mod_register_ip, treap,
- write) of
- [] ->
- treap:empty();
- [{mod_register_ip, treap, T}] -> T
- end,
- Treap1 = clean_treap(Treap, CleanPriority),
- case treap:lookup(Source, Treap1) of
- error ->
- Treap2 = treap:insert(Source, Priority, [],
- Treap1),
- mnesia:write({mod_register_ip, treap, Treap2}),
- true;
- {ok, _, _} ->
- mnesia:write({mod_register_ip, treap, Treap1}),
- false
- end
- end,
- case mnesia:transaction(F) of
- {atomic, Res} ->
- Res;
- {aborted, Reason} ->
- ?ERROR_MSG("mod_register: timeout check error: ~p~n",
- [Reason]),
- true
- end;
- true ->
- true
+ Timeout = ejabberd_config:get_local_option(
+ registration_timeout,
+ fun(TO) when is_integer(TO), TO > 0 ->
+ TO;
+ (infinity) ->
+ infinity;
+ (unlimited) ->
+ infinity
+ end, 600),
+ if is_integer(Timeout) ->
+ {MSec, Sec, _USec} = now(),
+ Priority = -(MSec * 1000000 + Sec),
+ CleanPriority = Priority + Timeout,
+ F = fun () ->
+ Treap = case mnesia:read(mod_register_ip, treap, write)
+ of
+ [] -> treap:empty();
+ [{mod_register_ip, treap, T}] -> T
+ end,
+ Treap1 = clean_treap(Treap, CleanPriority),
+ case treap:lookup(Source, Treap1) of
+ error ->
+ Treap2 = treap:insert(Source, Priority, [],
+ Treap1),
+ mnesia:write({mod_register_ip, treap, Treap2}),
+ true;
+ {ok, _, _} ->
+ mnesia:write({mod_register_ip, treap, Treap1}),
+ false
+ end
+ end,
+ case mnesia:transaction(F) of
+ {atomic, Res} -> Res;
+ {aborted, Reason} ->
+ ?ERROR_MSG("mod_register: timeout check error: ~p~n",
+ [Reason]),
+ true
+ end;
+ true -> true
end.
clean_treap(Treap, CleanPriority) ->
case treap:is_empty(Treap) of
- true ->
- Treap;
- false ->
- {_Key, Priority, _Value} = treap:get_root(Treap),
- if
- Priority > CleanPriority ->
- clean_treap(treap:delete_root(Treap), CleanPriority);
- true ->
- Treap
- end
+ true -> Treap;
+ false ->
+ {_Key, Priority, _Value} = treap:get_root(Treap),
+ if Priority > CleanPriority ->
+ clean_treap(treap:delete_root(Treap), CleanPriority);
+ true -> Treap
+ end
end.
-remove_timeout(undefined) ->
- true;
+remove_timeout(undefined) -> true;
remove_timeout(Source) ->
- Timeout = case ejabberd_config:get_local_option(registration_timeout) of
- undefined -> 600;
- TO -> TO
- end,
- if
- is_integer(Timeout) ->
- F = fun() ->
- Treap = case mnesia:read(mod_register_ip, treap,
- write) of
- [] ->
- treap:empty();
- [{mod_register_ip, treap, T}] -> T
- end,
- Treap1 = treap:delete(Source, Treap),
- mnesia:write({mod_register_ip, treap, Treap1}),
- ok
- end,
- case mnesia:transaction(F) of
- {atomic, ok} ->
- ok;
- {aborted, Reason} ->
- ?ERROR_MSG("mod_register: timeout remove error: ~p~n",
- [Reason]),
- ok
- end;
- true ->
- ok
+ Timeout = ejabberd_config:get_local_option(
+ registration_timeout,
+ fun(TO) when is_integer(TO), TO > 0 ->
+ TO;
+ (infinity) ->
+ infinity;
+ (unlimited) ->
+ infinity
+ end, 600),
+ if is_integer(Timeout) ->
+ F = fun () ->
+ Treap = case mnesia:read(mod_register_ip, treap, write)
+ of
+ [] -> treap:empty();
+ [{mod_register_ip, treap, T}] -> T
+ end,
+ Treap1 = treap:delete(Source, Treap),
+ mnesia:write({mod_register_ip, treap, Treap1}),
+ ok
+ end,
+ case mnesia:transaction(F) of
+ {atomic, ok} -> ok;
+ {aborted, Reason} ->
+ ?ERROR_MSG("mod_register: timeout remove error: "
+ "~p~n",
+ [Reason]),
+ ok
+ end;
+ true -> ok
end.
-ip_to_string(Source) when is_tuple(Source) -> inet_parse:ntoa(Source);
-ip_to_string(undefined) -> "undefined";
-ip_to_string(_) -> "unknown".
+ip_to_string(Source) when is_tuple(Source) ->
+ jlib:ip_to_list(Source);
+ip_to_string(undefined) -> <<"undefined">>;
+ip_to_string(_) -> <<"unknown">>.
get_time_string() -> write_time(erlang:localtime()).
%% Function copied from ejabberd_logger_h.erl and customized
-write_time({{Y,Mo,D},{H,Mi,S}}) ->
+
+write_time({{Y, Mo, D}, {H, Mi, S}}) ->
io_lib:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
[Y, Mo, D, H, Mi, S]).
process_xdata_submit(El) ->
- case xml:get_subtag(El, "x") of
- false ->
- error;
- Xdata ->
- Fields = jlib:parse_xdata_submit(Xdata),
- case catch {proplists:get_value("username", Fields),
- proplists:get_value("password", Fields)} of
- {[User|_], [Pass|_]} ->
- {ok, User, Pass};
- _ ->
- error
- end
+ case xml:get_subtag(El, <<"x">>) of
+ false -> error;
+ Xdata ->
+ Fields = jlib:parse_xdata_submit(Xdata),
+ case catch {proplists:get_value(<<"username">>, Fields),
+ proplists:get_value(<<"password">>, Fields)}
+ of
+ {[User | _], [Pass | _]} -> {ok, User, Pass};
+ _ -> error
+ end
end.
is_strong_password(Server, Password) ->
LServer = jlib:nameprep(Server),
- case gen_mod:get_module_opt(LServer, ?MODULE, password_strength, 0) of
- Entropy when is_number(Entropy), Entropy >= 0 ->
- if Entropy == 0 ->
- true;
- true ->
- ejabberd_auth:entropy(Password) >= Entropy
- end;
- Wrong ->
- ?WARNING_MSG("Wrong value for password_strength option: ~p",
- [Wrong]),
- true
+ case gen_mod:get_module_opt(LServer, ?MODULE, password_strength,
+ fun(N) when is_number(N), N>=0 -> N end,
+ 0) of
+ 0 ->
+ true;
+ Entropy ->
+ ejabberd_auth:entropy(Password) >= Entropy
end.
%%%
@@ -555,88 +609,79 @@ is_strong_password(Server, Password) ->
may_remove_resource({_, _, _} = From) ->
jlib:jid_remove_resource(From);
-may_remove_resource(From) ->
- From.
+may_remove_resource(From) -> From.
get_ip_access(Host) ->
- IPAccess = gen_mod:get_module_opt(Host, ?MODULE, ip_access, []),
- lists:flatmap(
- fun({Access, S}) ->
- case parse_ip_netmask(S) of
- {ok, IP, Mask} ->
- [{Access, IP, Mask}];
- error ->
- ?ERROR_MSG("mod_register: invalid "
- "network specification: ~p",
- [S]),
- []
- end
- end, IPAccess).
+ gen_mod:get_module_opt(Host, ?MODULE, ip_access,
+ fun(IPAccess) ->
+ lists:flatmap(
+ fun({Access, S}) ->
+ {ok, IP, Mask} =
+ parse_ip_netmask(
+ iolist_to_binary(S)),
+ [{Access, IP, Mask}]
+ end, IPAccess)
+ end, []).
parse_ip_netmask(S) ->
- case string:tokens(S, "/") of
- [IPStr] ->
- case inet_parse:address(IPStr) of
- {ok, {_, _, _, _} = IP} ->
- {ok, IP, 32};
- {ok, {_, _, _, _, _, _, _, _} = IP} ->
- {ok, IP, 128};
- _ ->
- error
- end;
- [IPStr, MaskStr] ->
- case catch list_to_integer(MaskStr) of
- Mask when is_integer(Mask),
- Mask >= 0 ->
- case inet_parse:address(IPStr) of
- {ok, {_, _, _, _} = IP} when Mask =< 32 ->
- {ok, IP, Mask};
- {ok, {_, _, _, _, _, _, _, _} = IP} when Mask =< 128 ->
- {ok, IP, Mask};
- _ ->
- error
- end;
- _ ->
- error
- end;
- _ ->
- error
+ case str:tokens(S, <<"/">>) of
+ [IPStr] ->
+ case inet_parse:address(binary_to_list(IPStr)) of
+ {ok, {_, _, _, _} = IP} -> {ok, IP, 32};
+ {ok, {_, _, _, _, _, _, _, _} = IP} -> {ok, IP, 128};
+ _ -> error
+ end;
+ [IPStr, MaskStr] ->
+ case catch jlib:binary_to_integer(MaskStr) of
+ Mask when is_integer(Mask), Mask >= 0 ->
+ case inet_parse:address(binary_to_list(IPStr)) of
+ {ok, {_, _, _, _} = IP} when Mask =< 32 ->
+ {ok, IP, Mask};
+ {ok, {_, _, _, _, _, _, _, _} = IP} when Mask =< 128 ->
+ {ok, IP, Mask};
+ _ -> error
+ end;
+ _ -> error
+ end;
+ _ -> error
end.
-check_ip_access(_Source, []) ->
- allow;
+check_ip_access(_Source, []) -> allow;
check_ip_access({User, Server, Resource}, IPAccess) ->
case ejabberd_sm:get_user_ip(User, Server, Resource) of
- {IPAddress, _PortNumber} -> check_ip_access(IPAddress, IPAccess);
- _ -> true
+ {IPAddress, _PortNumber} ->
+ check_ip_access(IPAddress, IPAccess);
+ _ -> true
end;
check_ip_access({_, _, _, _} = IP,
[{Access, {_, _, _, _} = Net, Mask} | IPAccess]) ->
IPInt = ip_to_integer(IP),
NetInt = ip_to_integer(Net),
- M = bnot ((1 bsl (32 - Mask)) - 1),
- if
- IPInt band M =:= NetInt band M ->
- Access;
- true ->
- check_ip_access(IP, IPAccess)
+ M = bnot (1 bsl (32 - Mask) - 1),
+ if IPInt band M =:= NetInt band M -> Access;
+ true -> check_ip_access(IP, IPAccess)
end;
check_ip_access({_, _, _, _, _, _, _, _} = IP,
- [{Access, {_, _, _, _, _, _, _, _} = Net, Mask} | IPAccess]) ->
+ [{Access, {_, _, _, _, _, _, _, _} = Net, Mask}
+ | IPAccess]) ->
IPInt = ip_to_integer(IP),
NetInt = ip_to_integer(Net),
- M = bnot ((1 bsl (128 - Mask)) - 1),
- if
- IPInt band M =:= NetInt band M ->
- Access;
- true ->
- check_ip_access(IP, IPAccess)
+ M = bnot (1 bsl (128 - Mask) - 1),
+ if IPInt band M =:= NetInt band M -> Access;
+ true -> check_ip_access(IP, IPAccess)
end;
check_ip_access(IP, [_ | IPAccess]) ->
check_ip_access(IP, IPAccess).
ip_to_integer({IP1, IP2, IP3, IP4}) ->
- (((((IP1 bsl 8) bor IP2) bsl 8) bor IP3) bsl 8) bor IP4;
-ip_to_integer({IP1, IP2, IP3, IP4, IP5, IP6, IP7, IP8}) ->
- (((((((((((((IP1 bsl 16) bor IP2) bsl 16) bor IP3) bsl 16) bor IP4)
- bsl 16) bor IP5) bsl 16) bor IP6) bsl 16) bor IP7) bsl 16) bor IP8.
+ IP1 bsl 8 bor IP2 bsl 8 bor IP3 bsl 8 bor IP4;
+ip_to_integer({IP1, IP2, IP3, IP4, IP5, IP6, IP7,
+ IP8}) ->
+ IP1 bsl 16 bor IP2 bsl 16 bor IP3 bsl 16 bor IP4 bsl 16
+ bor IP5
+ bsl 16
+ bor IP6
+ bsl 16
+ bor IP7
+ bsl 16
+ bor IP8.
diff --git a/src/mod_roster.erl b/src/mod_roster.erl
index b15497fab..dfaf7496e 100644
--- a/src/mod_roster.erl
+++ b/src/mod_roster.erl
@@ -34,175 +34,173 @@
%%% No additional data is stored in DB.
-module(mod_roster).
+
-author('alexey@process-one.net').
-behaviour(gen_mod).
--export([start/2, stop/1,
- process_iq/3,
- process_local_iq/3,
- get_user_roster/2,
- get_subscription_lists/3,
- get_in_pending_subscriptions/3,
- in_subscription/6,
- out_subscription/4,
- set_items/3,
- remove_user/2,
- get_jid_info/4,
- item_to_xml/1,
- webadmin_page/3,
- webadmin_user/4,
- get_versioning_feature/2,
- roster_versioning_enabled/1,
- roster_version/2]).
+-export([start/2, stop/1, process_iq/3, export/1,
+ process_local_iq/3, get_user_roster/2,
+ get_subscription_lists/3, get_roster/2,
+ get_in_pending_subscriptions/3, in_subscription/6,
+ out_subscription/4, set_items/3, remove_user/2,
+ get_jid_info/4, item_to_xml/1, webadmin_page/3,
+ webadmin_user/4, get_versioning_feature/2,
+ roster_versioning_enabled/1, roster_version/2,
+ record_to_string/1, groups_to_string/1]).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
+
-include("mod_roster.hrl").
+
-include("web/ejabberd_http.hrl").
+
-include("web/ejabberd_web_admin.hrl").
+-export_type([subscription/0]).
start(Host, Opts) ->
- IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
+ IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
+ one_queue),
case gen_mod:db_type(Opts) of
- mnesia ->
- mnesia:create_table(roster,
- [{disc_copies, [node()]},
- {attributes, record_info(fields, roster)}]),
- mnesia:create_table(roster_version,
- [{disc_copies, [node()]},
- {attributes,
- record_info(fields, roster_version)}]),
- update_table(),
- mnesia:add_table_index(roster, us),
- mnesia:add_table_index(roster_version, us);
- _ ->
- ok
+ mnesia ->
+ mnesia:create_table(roster,
+ [{disc_copies, [node()]},
+ {attributes, record_info(fields, roster)}]),
+ mnesia:create_table(roster_version,
+ [{disc_copies, [node()]},
+ {attributes,
+ record_info(fields, roster_version)}]),
+ update_tables(),
+ mnesia:add_table_index(roster, us),
+ mnesia:add_table_index(roster_version, us);
+ _ -> ok
end,
- ejabberd_hooks:add(roster_get, Host,
- ?MODULE, get_user_roster, 50),
+ ejabberd_hooks:add(roster_get, Host, ?MODULE,
+ get_user_roster, 50),
ejabberd_hooks:add(roster_in_subscription, Host,
?MODULE, in_subscription, 50),
ejabberd_hooks:add(roster_out_subscription, Host,
?MODULE, out_subscription, 50),
ejabberd_hooks:add(roster_get_subscription_lists, Host,
?MODULE, get_subscription_lists, 50),
- ejabberd_hooks:add(roster_get_jid_info, Host,
- ?MODULE, get_jid_info, 50),
- ejabberd_hooks:add(remove_user, Host,
- ?MODULE, remove_user, 50),
- ejabberd_hooks:add(anonymous_purge_hook, Host,
- ?MODULE, remove_user, 50),
- ejabberd_hooks:add(resend_subscription_requests_hook, Host,
- ?MODULE, get_in_pending_subscriptions, 50),
+ ejabberd_hooks:add(roster_get_jid_info, Host, ?MODULE,
+ get_jid_info, 50),
+ ejabberd_hooks:add(remove_user, Host, ?MODULE,
+ remove_user, 50),
+ ejabberd_hooks:add(anonymous_purge_hook, Host, ?MODULE,
+ remove_user, 50),
+ ejabberd_hooks:add(resend_subscription_requests_hook,
+ Host, ?MODULE, get_in_pending_subscriptions, 50),
ejabberd_hooks:add(roster_get_versioning_feature, Host,
?MODULE, get_versioning_feature, 50),
- ejabberd_hooks:add(webadmin_page_host, Host,
- ?MODULE, webadmin_page, 50),
- ejabberd_hooks:add(webadmin_user, Host,
- ?MODULE, webadmin_user, 50),
- gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_ROSTER,
- ?MODULE, process_iq, IQDisc).
+ ejabberd_hooks:add(webadmin_page_host, Host, ?MODULE,
+ webadmin_page, 50),
+ ejabberd_hooks:add(webadmin_user, Host, ?MODULE,
+ webadmin_user, 50),
+ gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
+ ?NS_ROSTER, ?MODULE, process_iq, IQDisc).
stop(Host) ->
- ejabberd_hooks:delete(roster_get, Host,
- ?MODULE, get_user_roster, 50),
+ ejabberd_hooks:delete(roster_get, Host, ?MODULE,
+ get_user_roster, 50),
ejabberd_hooks:delete(roster_in_subscription, Host,
?MODULE, in_subscription, 50),
ejabberd_hooks:delete(roster_out_subscription, Host,
?MODULE, out_subscription, 50),
- ejabberd_hooks:delete(roster_get_subscription_lists, Host,
- ?MODULE, get_subscription_lists, 50),
+ ejabberd_hooks:delete(roster_get_subscription_lists,
+ Host, ?MODULE, get_subscription_lists, 50),
ejabberd_hooks:delete(roster_get_jid_info, Host,
?MODULE, get_jid_info, 50),
- ejabberd_hooks:delete(remove_user, Host,
- ?MODULE, remove_user, 50),
+ ejabberd_hooks:delete(remove_user, Host, ?MODULE,
+ remove_user, 50),
ejabberd_hooks:delete(anonymous_purge_hook, Host,
?MODULE, remove_user, 50),
- ejabberd_hooks:delete(resend_subscription_requests_hook, Host,
- ?MODULE, get_in_pending_subscriptions, 50),
- ejabberd_hooks:delete(roster_get_versioning_feature, Host,
- ?MODULE, get_versioning_feature, 50),
- ejabberd_hooks:delete(webadmin_page_host, Host,
- ?MODULE, webadmin_page, 50),
- ejabberd_hooks:delete(webadmin_user, Host,
- ?MODULE, webadmin_user, 50),
- gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_ROSTER).
-
+ ejabberd_hooks:delete(resend_subscription_requests_hook,
+ Host, ?MODULE, get_in_pending_subscriptions, 50),
+ ejabberd_hooks:delete(roster_get_versioning_feature,
+ Host, ?MODULE, get_versioning_feature, 50),
+ ejabberd_hooks:delete(webadmin_page_host, Host, ?MODULE,
+ webadmin_page, 50),
+ ejabberd_hooks:delete(webadmin_user, Host, ?MODULE,
+ webadmin_user, 50),
+ gen_iq_handler:remove_iq_handler(ejabberd_sm, Host,
+ ?NS_ROSTER).
process_iq(From, To, IQ) ->
#iq{sub_el = SubEl} = IQ,
#jid{lserver = LServer} = From,
case lists:member(LServer, ?MYHOSTS) of
- true ->
- process_local_iq(From, To, IQ);
- _ ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_ITEM_NOT_FOUND]}
+ true -> process_local_iq(From, To, IQ);
+ _ ->
+ IQ#iq{type = error,
+ sub_el = [SubEl, ?ERR_ITEM_NOT_FOUND]}
end.
process_local_iq(From, To, #iq{type = Type} = IQ) ->
case Type of
- set ->
- process_iq_set(From, To, IQ);
- get ->
- process_iq_get(From, To, IQ)
+ set -> process_iq_set(From, To, IQ);
+ get -> process_iq_get(From, To, IQ)
end.
roster_hash(Items) ->
- sha:sha(term_to_binary(
- lists:sort(
- [R#roster{groups = lists:sort(Grs)} ||
- R = #roster{groups = Grs} <- Items]))).
-
+ sha:sha(term_to_binary(lists:sort([R#roster{groups =
+ lists:sort(Grs)}
+ || R = #roster{groups = Grs}
+ <- Items]))).
+
roster_versioning_enabled(Host) ->
- gen_mod:get_module_opt(Host, ?MODULE, versioning, false).
+ gen_mod:get_module_opt(Host, ?MODULE, versioning,
+ fun(B) when is_boolean(B) -> B end,
+ false).
roster_version_on_db(Host) ->
- gen_mod:get_module_opt(Host, ?MODULE, store_current_id, false).
+ gen_mod:get_module_opt(Host, ?MODULE, store_current_id,
+ fun(B) when is_boolean(B) -> B end,
+ false).
%% Returns a list that may contain an xmlelement with the XEP-237 feature if it's enabled.
get_versioning_feature(Acc, Host) ->
case roster_versioning_enabled(Host) of
- true ->
- Feature = {xmlelement,
- "ver",
- [{"xmlns", ?NS_ROSTER_VER}],
- []},
- [Feature | Acc];
- false -> []
+ true ->
+ Feature = #xmlel{name = <<"ver">>,
+ attrs = [{<<"xmlns">>, ?NS_ROSTER_VER}],
+ children = []},
+ [Feature | Acc];
+ false -> []
end.
roster_version(LServer, LUser) ->
US = {LUser, LServer},
case roster_version_on_db(LServer) of
- true ->
- case read_roster_version(LUser, LServer) of
- error ->
- not_found;
- V ->
- V
- end;
- false ->
- roster_hash(ejabberd_hooks:run_fold(roster_get, LServer, [], [US]))
+ true ->
+ case read_roster_version(LUser, LServer) of
+ error -> not_found;
+ V -> V
+ end;
+ false ->
+ roster_hash(ejabberd_hooks:run_fold(roster_get, LServer,
+ [], [US]))
end.
read_roster_version(LUser, LServer) ->
- read_roster_version(LUser, LServer, gen_mod:db_type(LServer, ?MODULE)).
+ read_roster_version(LUser, LServer,
+ gen_mod:db_type(LServer, ?MODULE)).
read_roster_version(LUser, LServer, mnesia) ->
US = {LUser, LServer},
case mnesia:dirty_read(roster_version, US) of
- [#roster_version{version = V}] -> V;
- [] -> error
+ [#roster_version{version = V}] -> V;
+ [] -> error
end;
read_roster_version(LServer, LUser, odbc) ->
Username = ejabberd_odbc:escape(LUser),
- case odbc_queries:get_roster_version(LServer, Username) of
- {selected, ["version"], [{Version}]} ->
- Version;
- {selected, ["version"], []} ->
- error
+ case odbc_queries:get_roster_version(LServer, Username)
+ of
+ {selected, [<<"version">>], [[Version]]} -> Version;
+ {selected, [<<"version">>], []} -> error
end.
write_roster_version(LUser, LServer) ->
@@ -214,27 +212,30 @@ write_roster_version_t(LUser, LServer) ->
write_roster_version(LUser, LServer, InTransaction) ->
Ver = sha:sha(term_to_binary(now())),
write_roster_version(LUser, LServer, InTransaction, Ver,
- gen_mod:db_type(LServer, ?MODULE)),
+ gen_mod:db_type(LServer, ?MODULE)),
Ver.
-write_roster_version(LUser, LServer, InTransaction, Ver, mnesia) ->
+write_roster_version(LUser, LServer, InTransaction, Ver,
+ mnesia) ->
US = {LUser, LServer},
if InTransaction ->
- mnesia:write(#roster_version{us = US, version = Ver});
+ mnesia:write(#roster_version{us = US, version = Ver});
true ->
- mnesia:dirty_write(#roster_version{us = US, version = Ver})
+ mnesia:dirty_write(#roster_version{us = US,
+ version = Ver})
end;
-write_roster_version(LUser, LServer, InTransaction, Ver, odbc) ->
+write_roster_version(LUser, LServer, InTransaction, Ver,
+ odbc) ->
Username = ejabberd_odbc:escape(LUser),
EVer = ejabberd_odbc:escape(Ver),
if InTransaction ->
- odbc_queries:set_roster_version(Username, EVer);
+ odbc_queries:set_roster_version(Username, EVer);
true ->
- odbc_queries:sql_transaction(
- LServer,
- fun() ->
- odbc_queries:set_roster_version(Username, EVer)
- end)
+ odbc_queries:sql_transaction(LServer,
+ fun () ->
+ odbc_queries:set_roster_version(Username,
+ EVer)
+ end)
end.
%% Load roster from DB only if neccesary.
@@ -247,156 +248,172 @@ process_iq_get(From, To, #iq{sub_el = SubEl} = IQ) ->
LUser = From#jid.luser,
LServer = From#jid.lserver,
US = {LUser, LServer},
- try
- {ItemsToSend, VersionToSend} =
- case {xml:get_tag_attr("ver", SubEl),
- roster_versioning_enabled(LServer),
- roster_version_on_db(LServer)} of
- {{value, RequestedVersion}, true, true} ->
- %% Retrieve version from DB. Only load entire roster
- %% when neccesary.
- case read_roster_version(LUser, LServer) of
- error ->
- RosterVersion = write_roster_version(LUser, LServer),
- {lists:map(
- fun item_to_xml/1,
- ejabberd_hooks:run_fold(
- roster_get, To#jid.lserver, [], [US])),
- RosterVersion};
- RequestedVersion ->
- {false, false};
- NewVersion ->
- {lists:map(
- fun item_to_xml/1,
- ejabberd_hooks:run_fold(
- roster_get, To#jid.lserver, [], [US])),
- NewVersion}
- end;
- {{value, RequestedVersion}, true, false} ->
- RosterItems = ejabberd_hooks:run_fold(
- roster_get, To#jid.lserver, [] , [US]),
- case roster_hash(RosterItems) of
- RequestedVersion ->
- {false, false};
- New ->
- {lists:map(fun item_to_xml/1, RosterItems), New}
- end;
- _ ->
- {lists:map(
- fun item_to_xml/1,
- ejabberd_hooks:run_fold(
- roster_get, To#jid.lserver, [], [US])),
- false}
- end,
- IQ#iq{type = result,
- sub_el = case {ItemsToSend, VersionToSend} of
- {false, false} ->
- [];
- {Items, false} ->
- [{xmlelement, "query",
- [{"xmlns", ?NS_ROSTER}], Items}];
- {Items, Version} ->
- [{xmlelement, "query",
- [{"xmlns", ?NS_ROSTER}, {"ver", Version}],
- Items}]
- end}
+ try {ItemsToSend, VersionToSend} = case
+ {xml:get_tag_attr(<<"ver">>, SubEl),
+ roster_versioning_enabled(LServer),
+ roster_version_on_db(LServer)}
+ of
+ {{value, RequestedVersion}, true,
+ true} ->
+ case read_roster_version(LUser,
+ LServer)
+ of
+ error ->
+ RosterVersion =
+ write_roster_version(LUser,
+ LServer),
+ {lists:map(fun item_to_xml/1,
+ ejabberd_hooks:run_fold(roster_get,
+ To#jid.lserver,
+ [],
+ [US])),
+ RosterVersion};
+ RequestedVersion ->
+ {false, false};
+ NewVersion ->
+ {lists:map(fun item_to_xml/1,
+ ejabberd_hooks:run_fold(roster_get,
+ To#jid.lserver,
+ [],
+ [US])),
+ NewVersion}
+ end;
+ {{value, RequestedVersion}, true,
+ false} ->
+ RosterItems =
+ ejabberd_hooks:run_fold(roster_get,
+ To#jid.lserver,
+ [],
+ [US]),
+ case roster_hash(RosterItems) of
+ RequestedVersion ->
+ {false, false};
+ New ->
+ {lists:map(fun item_to_xml/1,
+ RosterItems),
+ New}
+ end;
+ _ ->
+ {lists:map(fun item_to_xml/1,
+ ejabberd_hooks:run_fold(roster_get,
+ To#jid.lserver,
+ [],
+ [US])),
+ false}
+ end,
+ IQ#iq{type = result,
+ sub_el =
+ case {ItemsToSend, VersionToSend} of
+ {false, false} -> [];
+ {Items, false} ->
+ [#xmlel{name = <<"query">>,
+ attrs = [{<<"xmlns">>, ?NS_ROSTER}],
+ children = Items}];
+ {Items, Version} ->
+ [#xmlel{name = <<"query">>,
+ attrs =
+ [{<<"xmlns">>, ?NS_ROSTER},
+ {<<"ver">>, Version}],
+ children = Items}]
+ end}
catch
- _:_ ->
- IQ#iq{type =error, sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]}
+ _:_ ->
+ IQ#iq{type = error,
+ sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]}
end.
get_user_roster(Acc, {LUser, LServer}) ->
Items = get_roster(LUser, LServer),
- lists:filter(fun(#roster{subscription = none, ask = in}) ->
- false;
- (_) ->
- true
- end, Items) ++ Acc.
+ lists:filter(fun (#roster{subscription = none,
+ ask = in}) ->
+ false;
+ (_) -> true
+ end,
+ Items)
+ ++ Acc.
get_roster(LUser, LServer) ->
- get_roster(LUser, LServer, gen_mod:db_type(LServer, ?MODULE)).
+ get_roster(LUser, LServer,
+ gen_mod:db_type(LServer, ?MODULE)).
get_roster(LUser, LServer, mnesia) ->
US = {LUser, LServer},
- case catch mnesia:dirty_index_read(roster, US, #roster.us) of
- Items when is_list(Items) ->
- Items;
- _ ->
- []
+ case catch mnesia:dirty_index_read(roster, US,
+ #roster.us)
+ of
+ Items when is_list(Items)-> Items;
+ _ -> []
end;
get_roster(LUser, LServer, odbc) ->
Username = ejabberd_odbc:escape(LUser),
case catch odbc_queries:get_roster(LServer, Username) of
- {selected, ["username", "jid", "nick", "subscription", "ask",
- "askmessage", "server", "subscribe", "type"],
- Items} when is_list(Items) ->
- JIDGroups = case catch odbc_queries:get_roster_jid_groups(LServer, Username) of
- {selected, ["jid","grp"], JGrps}
+ {selected,
+ [<<"username">>, <<"jid">>, <<"nick">>,
+ <<"subscription">>, <<"ask">>, <<"askmessage">>,
+ <<"server">>, <<"subscribe">>, <<"type">>],
+ Items}
+ when is_list(Items) ->
+ JIDGroups = case catch
+ odbc_queries:get_roster_jid_groups(LServer,
+ Username)
+ of
+ {selected, [<<"jid">>, <<"grp">>], JGrps}
when is_list(JGrps) ->
- JGrps;
- _ ->
- []
- end,
- GroupsDict =
- lists:foldl(
- fun({J, G}, Acc) ->
- dict:append(J, G, Acc)
- end, dict:new(), JIDGroups),
- RItems = lists:flatmap(
- fun(I) ->
- case raw_to_record(LServer, I) of
- %% Bad JID in database:
- error ->
- [];
- R ->
- SJID = jlib:jid_to_string(R#roster.jid),
- Groups =
- case dict:find(SJID, GroupsDict) of
- {ok, Gs} -> Gs;
- error -> []
- end,
- [R#roster{groups = Groups}]
- end
- end, Items),
- RItems;
- _ ->
- []
+ JGrps;
+ _ -> []
+ end,
+ GroupsDict = lists:foldl(fun ([J, G], Acc) ->
+ dict:append(J, G, Acc)
+ end,
+ dict:new(), JIDGroups),
+ RItems = lists:flatmap(fun (I) ->
+ case raw_to_record(LServer, I) of
+ %% Bad JID in database:
+ error -> [];
+ R ->
+ SJID =
+ jlib:jid_to_string(R#roster.jid),
+ Groups = case dict:find(SJID,
+ GroupsDict)
+ of
+ {ok, Gs} -> Gs;
+ error -> []
+ end,
+ [R#roster{groups = Groups}]
+ end
+ end,
+ Items),
+ RItems;
+ _ -> []
end.
-
item_to_xml(Item) ->
- Attrs1 = [{"jid", jlib:jid_to_string(Item#roster.jid)}],
+ Attrs1 = [{<<"jid">>,
+ jlib:jid_to_string(Item#roster.jid)}],
Attrs2 = case Item#roster.name of
- "" ->
- Attrs1;
- Name ->
- [{"name", Name} | Attrs1]
+ <<"">> -> Attrs1;
+ Name -> [{<<"name">>, Name} | Attrs1]
end,
Attrs3 = case Item#roster.subscription of
- none ->
- [{"subscription", "none"} | Attrs2];
- from ->
- [{"subscription", "from"} | Attrs2];
- to ->
- [{"subscription", "to"} | Attrs2];
- both ->
- [{"subscription", "both"} | Attrs2];
- remove ->
- [{"subscription", "remove"} | Attrs2]
+ none -> [{<<"subscription">>, <<"none">>} | Attrs2];
+ from -> [{<<"subscription">>, <<"from">>} | Attrs2];
+ to -> [{<<"subscription">>, <<"to">>} | Attrs2];
+ both -> [{<<"subscription">>, <<"both">>} | Attrs2];
+ remove -> [{<<"subscription">>, <<"remove">>} | Attrs2]
end,
Attrs4 = case ask_to_pending(Item#roster.ask) of
- out ->
- [{"ask", "subscribe"} | Attrs3];
- both ->
- [{"ask", "subscribe"} | Attrs3];
- _ ->
- Attrs3
+ out -> [{<<"ask">>, <<"subscribe">>} | Attrs3];
+ both -> [{<<"ask">>, <<"subscribe">>} | Attrs3];
+ _ -> Attrs3
end,
- SubEls1 = lists:map(fun(G) ->
- {xmlelement, "group", [], [{xmlcdata, G}]}
- end, Item#roster.groups),
+ SubEls1 = lists:map(fun (G) ->
+ #xmlel{name = <<"group">>, attrs = [],
+ children = [{xmlcdata, G}]}
+ end,
+ Item#roster.groups),
SubEls = SubEls1 ++ Item#roster.xs,
- {xmlelement, "item", Attrs4, SubEls}.
+ #xmlel{name = <<"item">>, attrs = Attrs4,
+ children = SubEls}.
get_roster_by_jid_t(LUser, LServer, LJID) ->
DBType = gen_mod:db_type(LServer, ?MODULE),
@@ -404,500 +421,504 @@ get_roster_by_jid_t(LUser, LServer, LJID) ->
get_roster_by_jid_t(LUser, LServer, LJID, mnesia) ->
case mnesia:read({roster, {LUser, LServer, LJID}}) of
- [] ->
- #roster{usj = {LUser, LServer, LJID},
- us = {LUser, LServer},
- jid = LJID};
- [I] ->
- I#roster{jid = LJID,
- name = "",
- groups = [],
- xs = []}
+ [] ->
+ #roster{usj = {LUser, LServer, LJID},
+ us = {LUser, LServer}, jid = LJID};
+ [I] ->
+ I#roster{jid = LJID, name = <<"">>, groups = [],
+ xs = []}
end;
get_roster_by_jid_t(LUser, LServer, LJID, odbc) ->
Username = ejabberd_odbc:escape(LUser),
SJID = ejabberd_odbc:escape(jlib:jid_to_string(LJID)),
{selected,
- ["username", "jid", "nick", "subscription",
- "ask", "askmessage", "server", "subscribe", "type"],
- Res} = odbc_queries:get_roster_by_jid(LServer, Username, SJID),
+ [<<"username">>, <<"jid">>, <<"nick">>,
+ <<"subscription">>, <<"ask">>, <<"askmessage">>,
+ <<"server">>, <<"subscribe">>, <<"type">>],
+ Res} =
+ odbc_queries:get_roster_by_jid(LServer, Username, SJID),
case Res of
- [] ->
- #roster{usj = {LUser, LServer, LJID},
- us = {LUser, LServer},
- jid = LJID};
- [I] ->
- R = raw_to_record(LServer, I),
- case R of
- %% Bad JID in database:
- error ->
- #roster{usj = {LUser, LServer, LJID},
- us = {LUser, LServer},
- jid = LJID};
- _ ->
- R#roster{
- usj = {LUser, LServer, LJID},
- us = {LUser, LServer},
- jid = LJID,
- name = ""}
- end
+ [] ->
+ #roster{usj = {LUser, LServer, LJID},
+ us = {LUser, LServer}, jid = LJID};
+ [I] ->
+ R = raw_to_record(LServer, I),
+ case R of
+ %% Bad JID in database:
+ error ->
+ #roster{usj = {LUser, LServer, LJID},
+ us = {LUser, LServer}, jid = LJID};
+ _ ->
+ R#roster{usj = {LUser, LServer, LJID},
+ us = {LUser, LServer}, jid = LJID, name = <<"">>}
+ end
end.
process_iq_set(From, To, #iq{sub_el = SubEl} = IQ) ->
- {xmlelement, _Name, _Attrs, Els} = SubEl,
- lists:foreach(fun(El) -> process_item_set(From, To, El) end, Els),
+ #xmlel{children = Els} = SubEl,
+ lists:foreach(fun (El) -> process_item_set(From, To, El)
+ end,
+ Els),
IQ#iq{type = result, sub_el = []}.
-process_item_set(From, To, {xmlelement, _Name, Attrs, Els}) ->
- JID1 = jlib:string_to_jid(xml:get_attr_s("jid", Attrs)),
- #jid{user = User, luser = LUser, lserver = LServer} = From,
+process_item_set(From, To,
+ #xmlel{attrs = Attrs, children = Els}) ->
+ JID1 = jlib:string_to_jid(xml:get_attr_s(<<"jid">>,
+ Attrs)),
+ #jid{user = User, luser = LUser, lserver = LServer} =
+ From,
case JID1 of
- error ->
- ok;
- _ ->
- LJID = jlib:jid_tolower(JID1),
- F = fun() ->
- Item = get_roster_by_jid_t(LUser, LServer, LJID),
- Item1 = process_item_attrs(Item, Attrs),
- Item2 = process_item_els(Item1, Els),
- case Item2#roster.subscription of
- remove ->
- del_roster_t(LUser, LServer, LJID);
- _ ->
- update_roster_t(LUser, LServer, LJID, Item2)
- end,
- %% If the item exist in shared roster, take the
- %% subscription information from there:
- Item3 = ejabberd_hooks:run_fold(roster_process_item,
- LServer, Item2, [LServer]),
- case roster_version_on_db(LServer) of
- true -> write_roster_version_t(LUser, LServer);
- false -> ok
- end,
- {Item, Item3}
- end,
- case transaction(LServer, F) of
- {atomic, {OldItem, Item}} ->
- push_item(User, LServer, To, Item),
- case Item#roster.subscription of
- remove ->
- send_unsubscribing_presence(From, OldItem),
- ok;
- _ ->
- ok
- end;
- E ->
- ?DEBUG("ROSTER: roster item set error: ~p~n", [E]),
- ok
- end
+ error -> ok;
+ _ ->
+ LJID = jlib:jid_tolower(JID1),
+ F = fun () ->
+ Item = get_roster_by_jid_t(LUser, LServer, LJID),
+ Item1 = process_item_attrs(Item, Attrs),
+ Item2 = process_item_els(Item1, Els),
+ case Item2#roster.subscription of
+ remove -> del_roster_t(LUser, LServer, LJID);
+ _ -> update_roster_t(LUser, LServer, LJID, Item2)
+ end,
+ Item3 = ejabberd_hooks:run_fold(roster_process_item,
+ LServer, Item2,
+ [LServer]),
+ case roster_version_on_db(LServer) of
+ true -> write_roster_version_t(LUser, LServer);
+ false -> ok
+ end,
+ {Item, Item3}
+ end,
+ case transaction(LServer, F) of
+ {atomic, {OldItem, Item}} ->
+ push_item(User, LServer, To, Item),
+ case Item#roster.subscription of
+ remove ->
+ send_unsubscribing_presence(From, OldItem), ok;
+ _ -> ok
+ end;
+ E ->
+ ?DEBUG("ROSTER: roster item set error: ~p~n", [E]), ok
+ end
end;
-process_item_set(_From, _To, _) ->
- ok.
+process_item_set(_From, _To, _) -> ok.
process_item_attrs(Item, [{Attr, Val} | Attrs]) ->
case Attr of
- "jid" ->
- case jlib:string_to_jid(Val) of
- error ->
- process_item_attrs(Item, Attrs);
- JID1 ->
- JID = {JID1#jid.luser, JID1#jid.lserver, JID1#jid.lresource},
- process_item_attrs(Item#roster{jid = JID}, Attrs)
- end;
- "name" ->
- process_item_attrs(Item#roster{name = Val}, Attrs);
- "subscription" ->
- case Val of
- "remove" ->
- process_item_attrs(Item#roster{subscription = remove},
- Attrs);
- _ ->
- process_item_attrs(Item, Attrs)
- end;
- "ask" ->
- process_item_attrs(Item, Attrs);
- _ ->
- process_item_attrs(Item, Attrs)
+ <<"jid">> ->
+ case jlib:string_to_jid(Val) of
+ error -> process_item_attrs(Item, Attrs);
+ JID1 ->
+ JID = {JID1#jid.luser, JID1#jid.lserver,
+ JID1#jid.lresource},
+ process_item_attrs(Item#roster{jid = JID}, Attrs)
+ end;
+ <<"name">> ->
+ process_item_attrs(Item#roster{name = Val}, Attrs);
+ <<"subscription">> ->
+ case Val of
+ <<"remove">> ->
+ process_item_attrs(Item#roster{subscription = remove},
+ Attrs);
+ _ -> process_item_attrs(Item, Attrs)
+ end;
+ <<"ask">> -> process_item_attrs(Item, Attrs);
+ _ -> process_item_attrs(Item, Attrs)
end;
-process_item_attrs(Item, []) ->
- Item.
-
+process_item_attrs(Item, []) -> Item.
-process_item_els(Item, [{xmlelement, Name, Attrs, SEls} | Els]) ->
+process_item_els(Item,
+ [#xmlel{name = Name, attrs = Attrs, children = SEls}
+ | Els]) ->
case Name of
- "group" ->
- Groups = [xml:get_cdata(SEls) | Item#roster.groups],
- process_item_els(Item#roster{groups = Groups}, Els);
- _ ->
- case xml:get_attr_s("xmlns", Attrs) of
- "" ->
- process_item_els(Item, Els);
- _ ->
- XEls = [{xmlelement, Name, Attrs, SEls} | Item#roster.xs],
- process_item_els(Item#roster{xs = XEls}, Els)
- end
+ <<"group">> ->
+ Groups = [xml:get_cdata(SEls) | Item#roster.groups],
+ process_item_els(Item#roster{groups = Groups}, Els);
+ _ ->
+ case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ <<"">> -> process_item_els(Item, Els);
+ _ ->
+ XEls = [#xmlel{name = Name, attrs = Attrs,
+ children = SEls}
+ | Item#roster.xs],
+ process_item_els(Item#roster{xs = XEls}, Els)
+ end
end;
process_item_els(Item, [{xmlcdata, _} | Els]) ->
process_item_els(Item, Els);
-process_item_els(Item, []) ->
- Item.
-
+process_item_els(Item, []) -> Item.
push_item(User, Server, From, Item) ->
- ejabberd_sm:route(jlib:make_jid("", "", ""),
- jlib:make_jid(User, Server, ""),
- {xmlelement, "broadcast", [],
- [{item,
- Item#roster.jid,
- Item#roster.subscription}]}),
+ ejabberd_sm:route(jlib:make_jid(<<"">>, <<"">>, <<"">>),
+ jlib:make_jid(User, Server, <<"">>),
+ {broadcast, {item, Item#roster.jid,
+ Item#roster.subscription}}),
case roster_versioning_enabled(Server) of
- true ->
- push_item_version(Server, User, From, Item, roster_version(Server, User));
- false ->
- lists:foreach(fun(Resource) ->
- push_item(User, Server, Resource, From, Item)
- end, ejabberd_sm:get_user_resources(User, Server))
+ true ->
+ push_item_version(Server, User, From, Item,
+ roster_version(Server, User));
+ false ->
+ lists:foreach(fun (Resource) ->
+ push_item(User, Server, Resource, From, Item)
+ end,
+ ejabberd_sm:get_user_resources(User, Server))
end.
-% TODO: don't push to those who didn't load roster
push_item(User, Server, Resource, From, Item) ->
- push_item(User, Server, Resource, From, Item, not_found).
+ push_item(User, Server, Resource, From, Item,
+ not_found).
-push_item(User, Server, Resource, From, Item, RosterVersion) ->
+push_item(User, Server, Resource, From, Item,
+ RosterVersion) ->
ExtraAttrs = case RosterVersion of
- not_found -> [];
- _ -> [{"ver", RosterVersion}]
- end,
+ not_found -> [];
+ _ -> [{<<"ver">>, RosterVersion}]
+ end,
ResIQ = #iq{type = set, xmlns = ?NS_ROSTER,
- id = "push" ++ randoms:get_string(),
- sub_el = [{xmlelement, "query",
- [{"xmlns", ?NS_ROSTER}|ExtraAttrs],
- [item_to_xml(Item)]}]},
- ejabberd_router:route(
- From,
- jlib:make_jid(User, Server, Resource),
- jlib:iq_to_xml(ResIQ)).
-
%% @doc Roster push, calculate and include the version attribute.
%% TODO: don't push to those who didn't load roster
-push_item_version(Server, User, From, Item, RosterVersion) ->
- lists:foreach(fun(Resource) ->
- push_item(User, Server, Resource, From, Item, RosterVersion)
- end, ejabberd_sm:get_user_resources(User, Server)).
+ id = <<"push", (randoms:get_string())/binary>>,
+ sub_el =
+ [#xmlel{name = <<"query">>,
+ attrs = [{<<"xmlns">>, ?NS_ROSTER} | ExtraAttrs],
+ children = [item_to_xml(Item)]}]},
+ ejabberd_router:route(From,
+ jlib:make_jid(User, Server, Resource),
+ jlib:iq_to_xml(ResIQ)).
+
+push_item_version(Server, User, From, Item,
+ RosterVersion) ->
+ lists:foreach(fun (Resource) ->
+ push_item(User, Server, Resource, From, Item,
+ RosterVersion)
+ end,
+ ejabberd_sm:get_user_resources(User, Server)).
get_subscription_lists(Acc, User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
DBType = gen_mod:db_type(LServer, ?MODULE),
- Items = get_subscription_lists(Acc, LUser, LServer, DBType),
+ Items = get_subscription_lists(Acc, LUser, LServer,
+ DBType),
fill_subscription_lists(LServer, Items, [], []).
get_subscription_lists(_, LUser, LServer, mnesia) ->
US = {LUser, LServer},
case mnesia:dirty_index_read(roster, US, #roster.us) of
- Items when is_list(Items) ->
- Items;
- _ ->
- []
+ Items when is_list(Items) -> Items;
+ _ -> []
end;
get_subscription_lists(_, LUser, LServer, odbc) ->
Username = ejabberd_odbc:escape(LUser),
case catch odbc_queries:get_roster(LServer, Username) of
- {selected, ["username", "jid", "nick", "subscription", "ask",
- "askmessage", "server", "subscribe", "type"],
- Items} when is_list(Items) ->
- Items;
- _ ->
- []
+ {selected,
+ [<<"username">>, <<"jid">>, <<"nick">>,
+ <<"subscription">>, <<"ask">>, <<"askmessage">>,
+ <<"server">>, <<"subscribe">>, <<"type">>],
+ Items}
+ when is_list(Items) ->
+ Items;
+ _ -> []
end.
-fill_subscription_lists(LServer, [#roster{} = I | Is], F, T) ->
+fill_subscription_lists(LServer, [#roster{} = I | Is],
+ F, T) ->
J = element(3, I#roster.usj),
case I#roster.subscription of
- both ->
- fill_subscription_lists(LServer, Is, [J | F], [J | T]);
- from ->
- fill_subscription_lists(LServer, Is, [J | F], T);
- to ->
- fill_subscription_lists(LServer, Is, F, [J | T]);
- _ ->
- fill_subscription_lists(LServer, Is, F, T)
+ both ->
+ fill_subscription_lists(LServer, Is, [J | F], [J | T]);
+ from ->
+ fill_subscription_lists(LServer, Is, [J | F], T);
+ to -> fill_subscription_lists(LServer, Is, F, [J | T]);
+ _ -> fill_subscription_lists(LServer, Is, F, T)
end;
fill_subscription_lists(LServer, [RawI | Is], F, T) ->
I = raw_to_record(LServer, RawI),
case I of
- %% Bad JID in database:
- error ->
- fill_subscription_lists(LServer, Is, F, T);
- _ ->
- fill_subscription_lists(LServer, [I | Is], F, T)
+ %% Bad JID in database:
+ error -> fill_subscription_lists(LServer, Is, F, T);
+ _ -> fill_subscription_lists(LServer, [I | Is], F, T)
end;
-fill_subscription_lists(_LServer, [], F, T) ->
- {F, T}.
+fill_subscription_lists(_LServer, [], F, T) -> {F, T}.
ask_to_pending(subscribe) -> out;
ask_to_pending(unsubscribe) -> none;
ask_to_pending(Ask) -> Ask.
-
roster_subscribe_t(LUser, LServer, LJID, Item) ->
DBType = gen_mod:db_type(LServer, ?MODULE),
roster_subscribe_t(LUser, LServer, LJID, Item, DBType).
-roster_subscribe_t(_LUser, _LServer, _LJID, Item, mnesia) ->
+roster_subscribe_t(_LUser, _LServer, _LJID, Item,
+ mnesia) ->
mnesia:write(Item);
roster_subscribe_t(LUser, LServer, LJID, Item, odbc) ->
ItemVals = record_to_string(Item),
Username = ejabberd_odbc:escape(LUser),
SJID = ejabberd_odbc:escape(jlib:jid_to_string(LJID)),
- odbc_queries:roster_subscribe(LServer, Username, SJID, ItemVals).
+ odbc_queries:roster_subscribe(LServer, Username, SJID,
+ ItemVals).
transaction(LServer, F) ->
case gen_mod:db_type(LServer, ?MODULE) of
- mnesia ->
- mnesia:transaction(F);
- odbc ->
- ejabberd_odbc:sql_transaction(LServer, F)
+ mnesia -> mnesia:transaction(F);
+ odbc -> ejabberd_odbc:sql_transaction(LServer, F)
end.
in_subscription(_, User, Server, JID, Type, Reason) ->
- process_subscription(in, User, Server, JID, Type, Reason).
+ process_subscription(in, User, Server, JID, Type,
+ Reason).
out_subscription(User, Server, JID, Type) ->
- process_subscription(out, User, Server, JID, Type, []).
+ process_subscription(out, User, Server, JID, Type, <<"">>).
get_roster_by_jid_with_groups_t(LUser, LServer, LJID) ->
DBType = gen_mod:db_type(LServer, ?MODULE),
- get_roster_by_jid_with_groups_t(LUser, LServer, LJID, DBType).
+ get_roster_by_jid_with_groups_t(LUser, LServer, LJID,
+ DBType).
-get_roster_by_jid_with_groups_t(LUser, LServer, LJID, mnesia) ->
+get_roster_by_jid_with_groups_t(LUser, LServer, LJID,
+ mnesia) ->
case mnesia:read({roster, {LUser, LServer, LJID}}) of
- [] ->
- #roster{usj = {LUser, LServer, LJID},
- us = {LUser, LServer},
- jid = LJID};
- [I] ->
- I
+ [] ->
+ #roster{usj = {LUser, LServer, LJID},
+ us = {LUser, LServer}, jid = LJID};
+ [I] -> I
end;
-get_roster_by_jid_with_groups_t(LUser, LServer, LJID, odbc) ->
+get_roster_by_jid_with_groups_t(LUser, LServer, LJID,
+ odbc) ->
Username = ejabberd_odbc:escape(LUser),
SJID = ejabberd_odbc:escape(jlib:jid_to_string(LJID)),
- case odbc_queries:get_roster_by_jid(LServer, Username, SJID) of
- {selected,
- ["username", "jid", "nick", "subscription", "ask",
- "askmessage", "server", "subscribe", "type"],
- [I]} ->
- %% raw_to_record can return error, but
- %% jlib_to_string would fail before this point
- R = raw_to_record(LServer, I),
- Groups =
- case odbc_queries:get_roster_groups(LServer, Username, SJID) of
- {selected, ["grp"], JGrps} when is_list(JGrps) ->
- [JGrp || {JGrp} <- JGrps];
- _ ->
- []
- end,
- R#roster{groups = Groups};
- {selected,
- ["username", "jid", "nick", "subscription", "ask",
- "askmessage", "server", "subscribe", "type"],
- []} ->
- #roster{usj = {LUser, LServer, LJID},
- us = {LUser, LServer},
- jid = LJID}
+ case odbc_queries:get_roster_by_jid(LServer, Username,
+ SJID)
+ of
+ {selected,
+ [<<"username">>, <<"jid">>, <<"nick">>,
+ <<"subscription">>, <<"ask">>, <<"askmessage">>,
+ <<"server">>, <<"subscribe">>, <<"type">>],
+ [I]} ->
+ R = raw_to_record(LServer, I),
+ Groups = case odbc_queries:get_roster_groups(LServer,
+ Username, SJID)
+ of
+ {selected, [<<"grp">>], JGrps} when is_list(JGrps) ->
+ [JGrp || [JGrp] <- JGrps];
+ _ -> []
+ end,
+ R#roster{groups = Groups};
+ {selected,
+ [<<"username">>, <<"jid">>, <<"nick">>,
+ <<"subscription">>, <<"ask">>, <<"askmessage">>,
+ <<"server">>, <<"subscribe">>, <<"type">>],
+ []} ->
+ #roster{usj = {LUser, LServer, LJID},
+ us = {LUser, LServer}, jid = LJID}
end.
-process_subscription(Direction, User, Server, JID1, Type, Reason) ->
+process_subscription(Direction, User, Server, JID1,
+ Type, Reason) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
LJID = jlib:jid_tolower(JID1),
- F = fun() ->
- Item = get_roster_by_jid_with_groups_t(
- LUser, LServer, LJID),
+ F = fun () ->
+ Item = get_roster_by_jid_with_groups_t(LUser, LServer,
+ LJID),
NewState = case Direction of
- out ->
- out_state_change(Item#roster.subscription,
- Item#roster.ask,
- Type);
- in ->
- in_state_change(Item#roster.subscription,
- Item#roster.ask,
- Type)
+ out ->
+ out_state_change(Item#roster.subscription,
+ Item#roster.ask, Type);
+ in ->
+ in_state_change(Item#roster.subscription,
+ Item#roster.ask, Type)
end,
AutoReply = case Direction of
- out ->
- none;
- in ->
- in_auto_reply(Item#roster.subscription,
- Item#roster.ask,
- Type)
+ out -> none;
+ in ->
+ in_auto_reply(Item#roster.subscription,
+ Item#roster.ask, Type)
end,
AskMessage = case NewState of
- {_, both} -> Reason;
- {_, in} -> Reason;
- _ -> ""
+ {_, both} -> Reason;
+ {_, in} -> Reason;
+ _ -> <<"">>
end,
case NewState of
- none ->
- {none, AutoReply};
- {none, none} when Item#roster.subscription == none,
- Item#roster.ask == in ->
- del_roster_t(LUser, LServer, LJID),
- {none, AutoReply};
- {Subscription, Pending} ->
- NewItem = Item#roster{subscription = Subscription,
- ask = Pending,
- askmessage = list_to_binary(AskMessage)},
- roster_subscribe_t(LUser, LServer, LJID, NewItem),
- case roster_version_on_db(LServer) of
- true -> write_roster_version_t(LUser, LServer);
- false -> ok
- end,
- {{push, NewItem}, AutoReply}
+ none -> {none, AutoReply};
+ {none, none}
+ when Item#roster.subscription == none,
+ Item#roster.ask == in ->
+ del_roster_t(LUser, LServer, LJID), {none, AutoReply};
+ {Subscription, Pending} ->
+ NewItem = Item#roster{subscription = Subscription,
+ ask = Pending,
+ askmessage =
+ iolist_to_binary(AskMessage)},
+ roster_subscribe_t(LUser, LServer, LJID, NewItem),
+ case roster_version_on_db(LServer) of
+ true -> write_roster_version_t(LUser, LServer);
+ false -> ok
+ end,
+ {{push, NewItem}, AutoReply}
end
end,
case transaction(LServer, F) of
- {atomic, {Push, AutoReply}} ->
- case AutoReply of
- none ->
- ok;
- _ ->
- T = case AutoReply of
- subscribed -> "subscribed";
- unsubscribed -> "unsubscribed"
- end,
- ejabberd_router:route(
- jlib:make_jid(User, Server, ""), JID1,
- {xmlelement, "presence", [{"type", T}], []})
- end,
- case Push of
- {push, Item} ->
- if
- Item#roster.subscription == none,
- Item#roster.ask == in ->
- ok;
- true ->
- push_item(User, Server,
- jlib:make_jid(User, Server, ""), Item)
+ {atomic, {Push, AutoReply}} ->
+ case AutoReply of
+ none -> ok;
+ _ ->
+ T = case AutoReply of
+ subscribed -> <<"subscribed">>;
+ unsubscribed -> <<"unsubscribed">>
end,
- true;
- none ->
- false
- end;
- _ ->
- false
+ ejabberd_router:route(jlib:make_jid(User, Server,
+ <<"">>),
+ JID1,
+ #xmlel{name = <<"presence">>,
+ attrs = [{<<"type">>, T}],
+ children = []})
+ end,
+ case Push of
+ {push, Item} ->
+ if Item#roster.subscription == none,
+ Item#roster.ask == in ->
+ ok;
+ true ->
+ push_item(User, Server,
+ jlib:make_jid(User, Server, <<"">>), Item)
+ end,
+ true;
+ none -> false
+ end;
+ _ -> false
end.
%% in_state_change(Subscription, Pending, Type) -> NewState
%% NewState = none | {NewSubscription, NewPending}
-ifdef(ROSTER_GATEWAY_WORKAROUND).
+
-define(NNSD, {to, none}).
+
-define(NISD, {to, in}).
+
-else.
+
-define(NNSD, none).
+
-define(NISD, none).
+
-endif.
-in_state_change(none, none, subscribe) -> {none, in};
-in_state_change(none, none, subscribed) -> ?NNSD;
-in_state_change(none, none, unsubscribe) -> none;
+in_state_change(none, none, subscribe) -> {none, in};
+in_state_change(none, none, subscribed) -> ?NNSD;
+in_state_change(none, none, unsubscribe) -> none;
in_state_change(none, none, unsubscribed) -> none;
-in_state_change(none, out, subscribe) -> {none, both};
-in_state_change(none, out, subscribed) -> {to, none};
-in_state_change(none, out, unsubscribe) -> none;
-in_state_change(none, out, unsubscribed) -> {none, none};
-in_state_change(none, in, subscribe) -> none;
-in_state_change(none, in, subscribed) -> ?NISD;
-in_state_change(none, in, unsubscribe) -> {none, none};
-in_state_change(none, in, unsubscribed) -> none;
-in_state_change(none, both, subscribe) -> none;
-in_state_change(none, both, subscribed) -> {to, in};
-in_state_change(none, both, unsubscribe) -> {none, out};
+in_state_change(none, out, subscribe) -> {none, both};
+in_state_change(none, out, subscribed) -> {to, none};
+in_state_change(none, out, unsubscribe) -> none;
+in_state_change(none, out, unsubscribed) ->
+ {none, none};
+in_state_change(none, in, subscribe) -> none;
+in_state_change(none, in, subscribed) -> ?NISD;
+in_state_change(none, in, unsubscribe) -> {none, none};
+in_state_change(none, in, unsubscribed) -> none;
+in_state_change(none, both, subscribe) -> none;
+in_state_change(none, both, subscribed) -> {to, in};
+in_state_change(none, both, unsubscribe) -> {none, out};
in_state_change(none, both, unsubscribed) -> {none, in};
-in_state_change(to, none, subscribe) -> {to, in};
-in_state_change(to, none, subscribed) -> none;
-in_state_change(to, none, unsubscribe) -> none;
-in_state_change(to, none, unsubscribed) -> {none, none};
-in_state_change(to, in, subscribe) -> none;
-in_state_change(to, in, subscribed) -> none;
-in_state_change(to, in, unsubscribe) -> {to, none};
-in_state_change(to, in, unsubscribed) -> {none, in};
-in_state_change(from, none, subscribe) -> none;
-in_state_change(from, none, subscribed) -> {both, none};
-in_state_change(from, none, unsubscribe) -> {none, none};
+in_state_change(to, none, subscribe) -> {to, in};
+in_state_change(to, none, subscribed) -> none;
+in_state_change(to, none, unsubscribe) -> none;
+in_state_change(to, none, unsubscribed) -> {none, none};
+in_state_change(to, in, subscribe) -> none;
+in_state_change(to, in, subscribed) -> none;
+in_state_change(to, in, unsubscribe) -> {to, none};
+in_state_change(to, in, unsubscribed) -> {none, in};
+in_state_change(from, none, subscribe) -> none;
+in_state_change(from, none, subscribed) -> {both, none};
+in_state_change(from, none, unsubscribe) ->
+ {none, none};
in_state_change(from, none, unsubscribed) -> none;
-in_state_change(from, out, subscribe) -> none;
-in_state_change(from, out, subscribed) -> {both, none};
-in_state_change(from, out, unsubscribe) -> {none, out};
-in_state_change(from, out, unsubscribed) -> {from, none};
-in_state_change(both, none, subscribe) -> none;
-in_state_change(both, none, subscribed) -> none;
-in_state_change(both, none, unsubscribe) -> {to, none};
-in_state_change(both, none, unsubscribed) -> {from, none}.
-
-out_state_change(none, none, subscribe) -> {none, out};
-out_state_change(none, none, subscribed) -> none;
-out_state_change(none, none, unsubscribe) -> none;
+in_state_change(from, out, subscribe) -> none;
+in_state_change(from, out, subscribed) -> {both, none};
+in_state_change(from, out, unsubscribe) -> {none, out};
+in_state_change(from, out, unsubscribed) ->
+ {from, none};
+in_state_change(both, none, subscribe) -> none;
+in_state_change(both, none, subscribed) -> none;
+in_state_change(both, none, unsubscribe) -> {to, none};
+in_state_change(both, none, unsubscribed) ->
+ {from, none}.
+
+out_state_change(none, none, subscribe) -> {none, out};
+out_state_change(none, none, subscribed) -> none;
+out_state_change(none, none, unsubscribe) -> none;
out_state_change(none, none, unsubscribed) -> none;
-out_state_change(none, out, subscribe) -> {none, out}; %% We need to resend query (RFC3921, section 9.2)
-out_state_change(none, out, subscribed) -> none;
-out_state_change(none, out, unsubscribe) -> {none, none};
-out_state_change(none, out, unsubscribed) -> none;
-out_state_change(none, in, subscribe) -> {none, both};
-out_state_change(none, in, subscribed) -> {from, none};
-out_state_change(none, in, unsubscribe) -> none;
-out_state_change(none, in, unsubscribed) -> {none, none};
-out_state_change(none, both, subscribe) -> none;
-out_state_change(none, both, subscribed) -> {from, out};
-out_state_change(none, both, unsubscribe) -> {none, in};
-out_state_change(none, both, unsubscribed) -> {none, out};
-out_state_change(to, none, subscribe) -> none;
-out_state_change(to, none, subscribed) -> {both, none};
-out_state_change(to, none, unsubscribe) -> {none, none};
-out_state_change(to, none, unsubscribed) -> none;
-out_state_change(to, in, subscribe) -> none;
-out_state_change(to, in, subscribed) -> {both, none};
-out_state_change(to, in, unsubscribe) -> {none, in};
-out_state_change(to, in, unsubscribed) -> {to, none};
-out_state_change(from, none, subscribe) -> {from, out};
-out_state_change(from, none, subscribed) -> none;
-out_state_change(from, none, unsubscribe) -> none;
-out_state_change(from, none, unsubscribed) -> {none, none};
-out_state_change(from, out, subscribe) -> none;
-out_state_change(from, out, subscribed) -> none;
-out_state_change(from, out, unsubscribe) -> {from, none};
-out_state_change(from, out, unsubscribed) -> {none, out};
-out_state_change(both, none, subscribe) -> none;
-out_state_change(both, none, subscribed) -> none;
-out_state_change(both, none, unsubscribe) -> {from, none};
-out_state_change(both, none, unsubscribed) -> {to, none}.
-
-in_auto_reply(from, none, subscribe) -> subscribed;
-in_auto_reply(from, out, subscribe) -> subscribed;
-in_auto_reply(both, none, subscribe) -> subscribed;
-in_auto_reply(none, in, unsubscribe) -> unsubscribed;
-in_auto_reply(none, both, unsubscribe) -> unsubscribed;
-in_auto_reply(to, in, unsubscribe) -> unsubscribed;
-in_auto_reply(from, none, unsubscribe) -> unsubscribed;
-in_auto_reply(from, out, unsubscribe) -> unsubscribed;
-in_auto_reply(both, none, unsubscribe) -> unsubscribed;
-in_auto_reply(_, _, _) -> none.
-
+out_state_change(none, out, subscribe) ->
+ {none,
+ out}; %% We need to resend query (RFC3921, section 9.2)
+out_state_change(none, out, subscribed) -> none;
+out_state_change(none, out, unsubscribe) ->
+ {none, none};
+out_state_change(none, out, unsubscribed) -> none;
+out_state_change(none, in, subscribe) -> {none, both};
+out_state_change(none, in, subscribed) -> {from, none};
+out_state_change(none, in, unsubscribe) -> none;
+out_state_change(none, in, unsubscribed) ->
+ {none, none};
+out_state_change(none, both, subscribe) -> none;
+out_state_change(none, both, subscribed) -> {from, out};
+out_state_change(none, both, unsubscribe) -> {none, in};
+out_state_change(none, both, unsubscribed) ->
+ {none, out};
+out_state_change(to, none, subscribe) -> none;
+out_state_change(to, none, subscribed) -> {both, none};
+out_state_change(to, none, unsubscribe) -> {none, none};
+out_state_change(to, none, unsubscribed) -> none;
+out_state_change(to, in, subscribe) -> none;
+out_state_change(to, in, subscribed) -> {both, none};
+out_state_change(to, in, unsubscribe) -> {none, in};
+out_state_change(to, in, unsubscribed) -> {to, none};
+out_state_change(from, none, subscribe) -> {from, out};
+out_state_change(from, none, subscribed) -> none;
+out_state_change(from, none, unsubscribe) -> none;
+out_state_change(from, none, unsubscribed) ->
+ {none, none};
+out_state_change(from, out, subscribe) -> none;
+out_state_change(from, out, subscribed) -> none;
+out_state_change(from, out, unsubscribe) ->
+ {from, none};
+out_state_change(from, out, unsubscribed) ->
+ {none, out};
+out_state_change(both, none, subscribe) -> none;
+out_state_change(both, none, subscribed) -> none;
+out_state_change(both, none, unsubscribe) ->
+ {from, none};
+out_state_change(both, none, unsubscribed) ->
+ {to, none}.
+
+in_auto_reply(from, none, subscribe) -> subscribed;
+in_auto_reply(from, out, subscribe) -> subscribed;
+in_auto_reply(both, none, subscribe) -> subscribed;
+in_auto_reply(none, in, unsubscribe) -> unsubscribed;
+in_auto_reply(none, both, unsubscribe) -> unsubscribed;
+in_auto_reply(to, in, unsubscribe) -> unsubscribed;
+in_auto_reply(from, none, unsubscribe) -> unsubscribed;
+in_auto_reply(from, out, unsubscribe) -> unsubscribed;
+in_auto_reply(both, none, unsubscribe) -> unsubscribed;
+in_auto_reply(_, _, _) -> none.
remove_user(User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
- remove_user(LUser, LServer, gen_mod:db_type(LServer, ?MODULE)).
+ remove_user(LUser, LServer,
+ gen_mod:db_type(LServer, ?MODULE)).
remove_user(LUser, LServer, mnesia) ->
US = {LUser, LServer},
send_unsubscription_to_rosteritems(LUser, LServer),
- F = fun() ->
- lists:foreach(fun(R) ->
- mnesia:delete_object(R)
- end,
+ F = fun () ->
+ lists:foreach(fun (R) -> mnesia:delete_object(R) end,
mnesia:index_read(roster, US, #roster.us))
- end,
+ end,
mnesia:transaction(F);
remove_user(LUser, LServer, odbc) ->
Username = ejabberd_odbc:escape(LUser),
@@ -910,8 +931,8 @@ remove_user(LUser, LServer, odbc) ->
%% Both or To, send a "unsubscribe" presence stanza.
send_unsubscription_to_rosteritems(LUser, LServer) ->
RosterItems = get_user_roster([], {LUser, LServer}),
- From = jlib:make_jid({LUser, LServer, ""}),
- lists:foreach(fun(RosterItem) ->
+ From = jlib:make_jid({LUser, LServer, <<"">>}),
+ lists:foreach(fun (RosterItem) ->
send_unsubscribing_presence(From, RosterItem)
end,
RosterItems).
@@ -919,56 +940,54 @@ send_unsubscription_to_rosteritems(LUser, LServer) ->
%% @spec (From::jid(), Item::roster()) -> ok
send_unsubscribing_presence(From, Item) ->
IsTo = case Item#roster.subscription of
- both -> true;
- to -> true;
- _ -> false
+ both -> true;
+ to -> true;
+ _ -> false
end,
IsFrom = case Item#roster.subscription of
- both -> true;
- from -> true;
- _ -> false
+ both -> true;
+ from -> true;
+ _ -> false
end,
if IsTo ->
- send_presence_type(
- jlib:jid_remove_resource(From),
- jlib:make_jid(Item#roster.jid), "unsubscribe");
+ send_presence_type(jlib:jid_remove_resource(From),
+ jlib:make_jid(Item#roster.jid),
+ <<"unsubscribe">>);
true -> ok
end,
if IsFrom ->
- send_presence_type(
- jlib:jid_remove_resource(From),
- jlib:make_jid(Item#roster.jid), "unsubscribed");
+ send_presence_type(jlib:jid_remove_resource(From),
+ jlib:make_jid(Item#roster.jid),
+ <<"unsubscribed">>);
true -> ok
end,
ok.
send_presence_type(From, To, Type) ->
- ejabberd_router:route(
- From, To,
- {xmlelement, "presence",
- [{"type", Type}],
- []}).
-
+ ejabberd_router:route(From, To,
+ #xmlel{name = <<"presence">>,
+ attrs = [{<<"type">>, Type}], children = []}).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
set_items(User, Server, SubEl) ->
- {xmlelement, _Name, _Attrs, Els} = SubEl,
+ #xmlel{children = Els} = SubEl,
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
- F = fun() ->
- lists:foreach(
- fun(El) ->
- process_item_set_t(LUser, LServer, El)
- end, Els)
- end,
+ F = fun () ->
+ lists:foreach(fun (El) ->
+ process_item_set_t(LUser, LServer, El)
+ end,
+ Els)
+ end,
transaction(LServer, F).
update_roster_t(LUser, LServer, LJID, Item) ->
DBType = gen_mod:db_type(LServer, ?MODULE),
update_roster_t(LUser, LServer, LJID, Item, DBType).
-update_roster_t(_LUser, _LServer,_LJID, Item, mnesia) ->
+update_roster_t(_LUser, _LServer, _LJID, Item,
+ mnesia) ->
mnesia:write(Item);
update_roster_t(LUser, LServer, LJID, Item, odbc) ->
Username = ejabberd_odbc:escape(LUser),
@@ -988,142 +1007,145 @@ del_roster_t(LUser, LServer, LJID, odbc) ->
SJID = ejabberd_odbc:escape(jlib:jid_to_string(LJID)),
odbc_queries:del_roster(LServer, Username, SJID).
-process_item_set_t(LUser, LServer, {xmlelement, _Name, Attrs, Els}) ->
- JID1 = jlib:string_to_jid(xml:get_attr_s("jid", Attrs)),
+process_item_set_t(LUser, LServer,
+ #xmlel{attrs = Attrs, children = Els}) ->
+ JID1 = jlib:string_to_jid(xml:get_attr_s(<<"jid">>,
+ Attrs)),
case JID1 of
- error ->
- ok;
- _ ->
- JID = {JID1#jid.user, JID1#jid.server, JID1#jid.resource},
- LJID = {JID1#jid.luser, JID1#jid.lserver, JID1#jid.lresource},
- Item = #roster{usj = {LUser, LServer, LJID},
- us = {LUser, LServer},
- jid = JID},
- Item1 = process_item_attrs_ws(Item, Attrs),
- Item2 = process_item_els(Item1, Els),
- case Item2#roster.subscription of
- remove ->
- del_roster_t(LUser, LServer, LJID);
- _ ->
- update_roster_t(LUser, LServer, LJID, Item2)
- end
+ error -> ok;
+ _ ->
+ JID = {JID1#jid.user, JID1#jid.server,
+ JID1#jid.resource},
+ LJID = {JID1#jid.luser, JID1#jid.lserver,
+ JID1#jid.lresource},
+ Item = #roster{usj = {LUser, LServer, LJID},
+ us = {LUser, LServer}, jid = JID},
+ Item1 = process_item_attrs_ws(Item, Attrs),
+ Item2 = process_item_els(Item1, Els),
+ case Item2#roster.subscription of
+ remove -> del_roster_t(LUser, LServer, LJID);
+ _ -> update_roster_t(LUser, LServer, LJID, Item2)
+ end
end;
-process_item_set_t(_LUser, _LServer, _) ->
- ok.
+process_item_set_t(_LUser, _LServer, _) -> ok.
process_item_attrs_ws(Item, [{Attr, Val} | Attrs]) ->
case Attr of
- "jid" ->
- case jlib:string_to_jid(Val) of
- error ->
- process_item_attrs_ws(Item, Attrs);
- JID1 ->
- JID = {JID1#jid.luser, JID1#jid.lserver, JID1#jid.lresource},
- process_item_attrs_ws(Item#roster{jid = JID}, Attrs)
- end;
- "name" ->
- process_item_attrs_ws(Item#roster{name = Val}, Attrs);
- "subscription" ->
- case Val of
- "remove" ->
- process_item_attrs_ws(Item#roster{subscription = remove},
- Attrs);
- "none" ->
- process_item_attrs_ws(Item#roster{subscription = none},
- Attrs);
- "both" ->
- process_item_attrs_ws(Item#roster{subscription = both},
- Attrs);
- "from" ->
- process_item_attrs_ws(Item#roster{subscription = from},
- Attrs);
- "to" ->
- process_item_attrs_ws(Item#roster{subscription = to},
- Attrs);
- _ ->
- process_item_attrs_ws(Item, Attrs)
- end;
- "ask" ->
- process_item_attrs_ws(Item, Attrs);
- _ ->
- process_item_attrs_ws(Item, Attrs)
+ <<"jid">> ->
+ case jlib:string_to_jid(Val) of
+ error -> process_item_attrs_ws(Item, Attrs);
+ JID1 ->
+ JID = {JID1#jid.luser, JID1#jid.lserver,
+ JID1#jid.lresource},
+ process_item_attrs_ws(Item#roster{jid = JID}, Attrs)
+ end;
+ <<"name">> ->
+ process_item_attrs_ws(Item#roster{name = Val}, Attrs);
+ <<"subscription">> ->
+ case Val of
+ <<"remove">> ->
+ process_item_attrs_ws(Item#roster{subscription =
+ remove},
+ Attrs);
+ <<"none">> ->
+ process_item_attrs_ws(Item#roster{subscription = none},
+ Attrs);
+ <<"both">> ->
+ process_item_attrs_ws(Item#roster{subscription = both},
+ Attrs);
+ <<"from">> ->
+ process_item_attrs_ws(Item#roster{subscription = from},
+ Attrs);
+ <<"to">> ->
+ process_item_attrs_ws(Item#roster{subscription = to},
+ Attrs);
+ _ -> process_item_attrs_ws(Item, Attrs)
+ end;
+ <<"ask">> -> process_item_attrs_ws(Item, Attrs);
+ _ -> process_item_attrs_ws(Item, Attrs)
end;
-process_item_attrs_ws(Item, []) ->
- Item.
+process_item_attrs_ws(Item, []) -> Item.
get_in_pending_subscriptions(Ls, User, Server) ->
LServer = jlib:nameprep(Server),
get_in_pending_subscriptions(Ls, User, Server,
- gen_mod:db_type(LServer, ?MODULE)).
+ gen_mod:db_type(LServer, ?MODULE)).
-get_in_pending_subscriptions(Ls, User, Server, mnesia) ->
- JID = jlib:make_jid(User, Server, ""),
+get_in_pending_subscriptions(Ls, User, Server,
+ mnesia) ->
+ JID = jlib:make_jid(User, Server, <<"">>),
US = {JID#jid.luser, JID#jid.lserver},
case mnesia:dirty_index_read(roster, US, #roster.us) of
- Result when is_list(Result) ->
- Ls ++ lists:map(
- fun(R) ->
- Message = R#roster.askmessage,
- Status = if is_binary(Message) ->
- binary_to_list(Message);
- true ->
- ""
- end,
- {xmlelement, "presence",
- [{"from", jlib:jid_to_string(R#roster.jid)},
- {"to", jlib:jid_to_string(JID)},
- {"type", "subscribe"}],
- [{xmlelement, "status", [],
- [{xmlcdata, Status}]}]}
- end,
- lists:filter(
- fun(R) ->
- case R#roster.ask of
- in -> true;
- both -> true;
- _ -> false
- end
+ Result when is_list(Result) ->
+ Ls ++
+ lists:map(fun (R) ->
+ Message = R#roster.askmessage,
+ Status = if is_binary(Message) -> (Message);
+ true -> <<"">>
+ end,
+ #xmlel{name = <<"presence">>,
+ attrs =
+ [{<<"from">>,
+ jlib:jid_to_string(R#roster.jid)},
+ {<<"to">>, jlib:jid_to_string(JID)},
+ {<<"type">>, <<"subscribe">>}],
+ children =
+ [#xmlel{name = <<"status">>,
+ attrs = [],
+ children =
+ [{xmlcdata, Status}]}]}
end,
- Result));
- _ ->
- Ls
+ lists:filter(fun (R) ->
+ case R#roster.ask of
+ in -> true;
+ both -> true;
+ _ -> false
+ end
+ end,
+ Result));
+ _ -> Ls
end;
get_in_pending_subscriptions(Ls, User, Server, odbc) ->
- JID = jlib:make_jid(User, Server, ""),
+ JID = jlib:make_jid(User, Server, <<"">>),
LUser = JID#jid.luser,
LServer = JID#jid.lserver,
Username = ejabberd_odbc:escape(LUser),
case catch odbc_queries:get_roster(LServer, Username) of
- {selected, ["username", "jid", "nick", "subscription", "ask",
- "askmessage", "server", "subscribe", "type"],
- Items} when is_list(Items) ->
- Ls ++ lists:map(
- fun(R) ->
- Message = R#roster.askmessage,
- {xmlelement, "presence",
- [{"from", jlib:jid_to_string(R#roster.jid)},
- {"to", jlib:jid_to_string(JID)},
- {"type", "subscribe"}],
- [{xmlelement, "status", [],
- [{xmlcdata, Message}]}]}
- end,
- lists:flatmap(
- fun(I) ->
- case raw_to_record(LServer, I) of
- %% Bad JID in database:
- error ->
- [];
- R ->
- case R#roster.ask of
- in -> [R];
- both -> [R];
- _ -> []
- end
- end
+ {selected,
+ [<<"username">>, <<"jid">>, <<"nick">>,
+ <<"subscription">>, <<"ask">>, <<"askmessage">>,
+ <<"server">>, <<"subscribe">>, <<"type">>],
+ Items}
+ when is_list(Items) ->
+ Ls ++
+ lists:map(fun (R) ->
+ Message = R#roster.askmessage,
+ #xmlel{name = <<"presence">>,
+ attrs =
+ [{<<"from">>,
+ jlib:jid_to_string(R#roster.jid)},
+ {<<"to">>, jlib:jid_to_string(JID)},
+ {<<"type">>, <<"subscribe">>}],
+ children =
+ [#xmlel{name = <<"status">>,
+ attrs = [],
+ children =
+ [{xmlcdata, Message}]}]}
end,
- Items));
- _ ->
- Ls
+ lists:flatmap(fun (I) ->
+ case raw_to_record(LServer, I) of
+ %% Bad JID in database:
+ error -> [];
+ R ->
+ case R#roster.ask of
+ in -> [R];
+ both -> [R];
+ _ -> []
+ end
+ end
+ end,
+ Items));
+ _ -> Ls
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -1132,198 +1154,188 @@ read_subscription_and_groups(User, Server, LJID) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
read_subscription_and_groups(LUser, LServer, LJID,
- gen_mod:db_type(LServer, ?MODULE)).
-
-read_subscription_and_groups(LUser, LServer, LJID, mnesia) ->
- case catch mnesia:dirty_read(roster, {LUser, LServer, LJID}) of
- [#roster{subscription = Subscription, groups = Groups}] ->
- {Subscription, Groups};
- _ ->
- error
+ gen_mod:db_type(LServer, ?MODULE)).
+
+read_subscription_and_groups(LUser, LServer, LJID,
+ mnesia) ->
+ case catch mnesia:dirty_read(roster,
+ {LUser, LServer, LJID})
+ of
+ [#roster{subscription = Subscription,
+ groups = Groups}] ->
+ {Subscription, Groups};
+ _ -> error
end;
-read_subscription_and_groups(LUser, LServer, LJID, odbc) ->
+read_subscription_and_groups(LUser, LServer, LJID,
+ odbc) ->
Username = ejabberd_odbc:escape(LUser),
SJID = ejabberd_odbc:escape(jlib:jid_to_string(LJID)),
- case catch odbc_queries:get_subscription(LServer, Username, SJID) of
- {selected, ["subscription"], [{SSubscription}]} ->
- Subscription = case SSubscription of
- "B" -> both;
- "T" -> to;
- "F" -> from;
- _ -> none
- end,
- Groups = case catch odbc_queries:get_rostergroup_by_jid(
- LServer, Username, SJID) of
- {selected, ["grp"], JGrps} when is_list(JGrps) ->
- [JGrp || {JGrp} <- JGrps];
- _ ->
- []
- end,
- {Subscription, Groups};
- _ ->
- error
+ case catch odbc_queries:get_subscription(LServer,
+ Username, SJID)
+ of
+ {selected, [<<"subscription">>], [{SSubscription}]} ->
+ Subscription = case SSubscription of
+ <<"B">> -> both;
+ <<"T">> -> to;
+ <<"F">> -> from;
+ _ -> none
+ end,
+ Groups = case catch
+ odbc_queries:get_rostergroup_by_jid(LServer, Username,
+ SJID)
+ of
+ {selected, [<<"grp">>], JGrps} when is_list(JGrps) ->
+ [JGrp || [JGrp] <- JGrps];
+ _ -> []
+ end,
+ {Subscription, Groups};
+ _ -> error
end.
get_jid_info(_, User, Server, JID) ->
LJID = jlib:jid_tolower(JID),
case read_subscription_and_groups(User, Server, LJID) of
- {Subscription, Groups} ->
- {Subscription, Groups};
- error ->
- LRJID = jlib:jid_tolower(jlib:jid_remove_resource(JID)),
- if
- LRJID == LJID ->
- {none, []};
- true ->
- case read_subscription_and_groups(
- User, Server, LRJID) of
- {Subscription, Groups} ->
- {Subscription, Groups};
- error ->
- {none, []}
- end
- end
+ {Subscription, Groups} -> {Subscription, Groups};
+ error ->
+ LRJID = jlib:jid_tolower(jlib:jid_remove_resource(JID)),
+ if LRJID == LJID -> {none, []};
+ true ->
+ case read_subscription_and_groups(User, Server, LRJID)
+ of
+ {Subscription, Groups} -> {Subscription, Groups};
+ error -> {none, []}
+ end
+ end
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-raw_to_record(LServer, {User, SJID, Nick, SSubscription, SAsk, SAskMessage,
- _SServer, _SSubscribe, _SType}) ->
+raw_to_record(LServer,
+ [User, SJID, Nick, SSubscription, SAsk, SAskMessage,
+ _SServer, _SSubscribe, _SType]) ->
case jlib:string_to_jid(SJID) of
- error ->
- error;
- JID ->
- LJID = jlib:jid_tolower(JID),
- Subscription = case SSubscription of
- "B" -> both;
- "T" -> to;
- "F" -> from;
- _ -> none
- end,
- Ask = case SAsk of
- "S" -> subscribe;
- "U" -> unsubscribe;
- "B" -> both;
- "O" -> out;
- "I" -> in;
- _ -> none
- end,
- #roster{usj = {User, LServer, LJID},
- us = {User, LServer},
- jid = LJID,
- name = Nick,
- subscription = Subscription,
- ask = Ask,
- askmessage = SAskMessage}
+ error -> error;
+ JID ->
+ LJID = jlib:jid_tolower(JID),
+ Subscription = case SSubscription of
+ <<"B">> -> both;
+ <<"T">> -> to;
+ <<"F">> -> from;
+ _ -> none
+ end,
+ Ask = case SAsk of
+ <<"S">> -> subscribe;
+ <<"U">> -> unsubscribe;
+ <<"B">> -> both;
+ <<"O">> -> out;
+ <<"I">> -> in;
+ _ -> none
+ end,
+ #roster{usj = {User, LServer, LJID},
+ us = {User, LServer}, jid = LJID, name = Nick,
+ subscription = Subscription, ask = Ask,
+ askmessage = SAskMessage}
end.
record_to_string(#roster{us = {User, _Server},
- jid = JID,
- name = Name,
- subscription = Subscription,
- ask = Ask,
- askmessage = AskMessage}) ->
+ jid = JID, name = Name, subscription = Subscription,
+ ask = Ask, askmessage = AskMessage}) ->
Username = ejabberd_odbc:escape(User),
- SJID = ejabberd_odbc:escape(jlib:jid_to_string(jlib:jid_tolower(JID))),
+ SJID =
+ ejabberd_odbc:escape(jlib:jid_to_string(jlib:jid_tolower(JID))),
Nick = ejabberd_odbc:escape(Name),
SSubscription = case Subscription of
- both -> "B";
- to -> "T";
- from -> "F";
- none -> "N"
+ both -> <<"B">>;
+ to -> <<"T">>;
+ from -> <<"F">>;
+ none -> <<"N">>
end,
SAsk = case Ask of
- subscribe -> "S";
- unsubscribe -> "U";
- both -> "B";
- out -> "O";
- in -> "I";
- none -> "N"
+ subscribe -> <<"S">>;
+ unsubscribe -> <<"U">>;
+ both -> <<"B">>;
+ out -> <<"O">>;
+ in -> <<"I">>;
+ none -> <<"N">>
end,
SAskMessage = ejabberd_odbc:escape(AskMessage),
- [Username, SJID, Nick, SSubscription, SAsk, SAskMessage, "N", "", "item"].
+ [Username, SJID, Nick, SSubscription, SAsk, SAskMessage,
+ <<"N">>, <<"">>, <<"item">>].
groups_to_string(#roster{us = {User, _Server},
- jid = JID,
- groups = Groups}) ->
+ jid = JID, groups = Groups}) ->
Username = ejabberd_odbc:escape(User),
- SJID = ejabberd_odbc:escape(jlib:jid_to_string(jlib:jid_tolower(JID))),
+ SJID =
+ ejabberd_odbc:escape(jlib:jid_to_string(jlib:jid_tolower(JID))),
+ lists:foldl(fun (<<"">>, Acc) -> Acc;
+ (Group, Acc) ->
+ G = ejabberd_odbc:escape(Group),
+ [[Username, SJID, G] | Acc]
+ end,
+ [], Groups).
- %% Empty groups do not need to be converted to string to be inserted in
- %% the database
- lists:foldl(
- fun([], Acc) -> Acc;
- (Group, Acc) ->
- G = ejabberd_odbc:escape(Group),
- [[Username, SJID, G]|Acc] end, [], Groups).
+update_tables() ->
+ update_roster_table(),
+ update_roster_version_table().
-update_table() ->
+update_roster_table() ->
Fields = record_info(fields, roster),
case mnesia:table_info(roster, attributes) of
- Fields ->
- ok;
- [uj, user, jid, name, subscription, ask, groups, xattrs, xs] ->
- convert_table1(Fields);
- [usj, us, jid, name, subscription, ask, groups, xattrs, xs] ->
- convert_table2(Fields);
- _ ->
- ?INFO_MSG("Recreating roster table", []),
- mnesia:transform_table(roster, ignore, Fields)
+ Fields ->
+ ejabberd_config:convert_table_to_binary(
+ roster, Fields, set,
+ fun(#roster{usj = {U, _, _}}) -> U end,
+ fun(#roster{usj = {U, S, {LU, LS, LR}},
+ us = {U1, S1},
+ jid = {U2, S2, R2},
+ name = Name,
+ groups = Gs,
+ askmessage = Ask,
+ xs = Xs} = R) ->
+ R#roster{usj = {iolist_to_binary(U),
+ iolist_to_binary(S),
+ {iolist_to_binary(LU),
+ iolist_to_binary(LS),
+ iolist_to_binary(LR)}},
+ us = {iolist_to_binary(U1),
+ iolist_to_binary(S1)},
+ jid = {iolist_to_binary(U2),
+ iolist_to_binary(S2),
+ iolist_to_binary(R2)},
+ name = iolist_to_binary(Name),
+ groups = [iolist_to_binary(G) || G <- Gs],
+ askmessage = iolist_to_binary(Ask),
+ xs = [xml:to_xmlel(X) || X <- Xs]}
+ end);
+ _ ->
+ ?INFO_MSG("Recreating roster table", []),
+ mnesia:transform_table(roster, ignore, Fields)
end.
-
%% Convert roster table to support virtual host
-convert_table1(Fields) ->
- ?INFO_MSG("Virtual host support: converting roster table from "
- "{uj, user, jid, name, subscription, ask, groups, xattrs, xs} format", []),
- Host = ?MYNAME,
- {atomic, ok} = mnesia:create_table(
- mod_roster_tmp_table,
- [{disc_only_copies, [node()]},
- {type, bag},
- {local_content, true},
- {record_name, roster},
- {attributes, record_info(fields, roster)}]),
- mnesia:del_table_index(roster, user),
- mnesia:transform_table(roster, ignore, Fields),
- F1 = fun() ->
- mnesia:write_lock_table(mod_roster_tmp_table),
- mnesia:foldl(
- fun(#roster{usj = {U, JID}, us = U} = R, _) ->
- mnesia:dirty_write(
- mod_roster_tmp_table,
- R#roster{usj = {U, Host, JID},
- us = {U, Host}})
- end, ok, roster)
- end,
- mnesia:transaction(F1),
- mnesia:clear_table(roster),
- F2 = fun() ->
- mnesia:write_lock_table(roster),
- mnesia:foldl(
- fun(R, _) ->
- mnesia:dirty_write(R)
- end, ok, mod_roster_tmp_table)
- end,
- mnesia:transaction(F2),
- mnesia:delete_table(mod_roster_tmp_table).
-
-
%% Convert roster table: xattrs fields become
-convert_table2(Fields) ->
- ?INFO_MSG("Converting roster table from "
- "{usj, us, jid, name, subscription, ask, groups, xattrs, xs} format", []),
- mnesia:transform_table(roster, ignore, Fields).
-
+update_roster_version_table() ->
+ Fields = record_info(fields, roster_version),
+ case mnesia:table_info(roster_version, attributes) of
+ Fields ->
+ ejabberd_config:convert_table_to_binary(
+ roster_version, Fields, set,
+ fun(#roster_version{us = {U, _}}) -> U end,
+ fun(#roster_version{us = {U, S}, version = Ver} = R) ->
+ R#roster_version{us = {iolist_to_binary(U),
+ iolist_to_binary(S)},
+ version = iolist_to_binary(Ver)}
+ end);
+ _ ->
+ ?INFO_MSG("Recreating roster_version table", []),
+ mnesia:transform_table(roster_version, ignore, Fields)
+ end.
webadmin_page(_, Host,
- #request{us = _US,
- path = ["user", U, "roster"],
- q = Query,
- lang = Lang} = _Request) ->
- Res = user_roster(U, Host, Query, Lang),
- {stop, Res};
-
+ #request{us = _US, path = [<<"user">>, U, <<"roster">>],
+ q = Query, lang = Lang} =
+ _Request) ->
+ Res = user_roster(U, Host, Query, Lang), {stop, Res};
webadmin_page(Acc, _, _) -> Acc.
user_roster(User, Server, Query, Lang) ->
@@ -1331,167 +1343,228 @@ user_roster(User, Server, Query, Lang) ->
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
Items1 = get_roster(LUser, LServer),
- Res = user_roster_parse_query(User, Server, Items1, Query),
+ Res = user_roster_parse_query(User, Server, Items1,
+ Query),
Items = get_roster(LUser, LServer),
SItems = lists:sort(Items),
- FItems =
- case SItems of
- [] ->
- [?CT("None")];
- _ ->
- [?XE("table",
- [?XE("thead",
- [?XE("tr",
- [?XCT("td", "Jabber ID"),
- ?XCT("td", "Nickname"),
- ?XCT("td", "Subscription"),
- ?XCT("td", "Pending"),
- ?XCT("td", "Groups")
- ])]),
- ?XE("tbody",
- lists:map(
- fun(R) ->
- Groups =
- lists:flatmap(
- fun(Group) ->
- [?C(Group), ?BR]
- end, R#roster.groups),
- Pending = ask_to_pending(R#roster.ask),
- TDJID = build_contact_jid_td(R#roster.jid),
- ?XE("tr",
- [TDJID,
- ?XAC("td", [{"class", "valign"}],
- R#roster.name),
- ?XAC("td", [{"class", "valign"}],
- atom_to_list(R#roster.subscription)),
- ?XAC("td", [{"class", "valign"}],
- atom_to_list(Pending)),
- ?XAE("td", [{"class", "valign"}], Groups),
- if
- Pending == in ->
- ?XAE("td", [{"class", "valign"}],
- [?INPUTT("submit",
- "validate" ++
- ejabberd_web_admin:term_to_id(R#roster.jid),
- "Validate")]);
- true ->
- ?X("td")
- end,
- ?XAE("td", [{"class", "valign"}],
- [?INPUTT("submit",
- "remove" ++
- ejabberd_web_admin:term_to_id(R#roster.jid),
- "Remove")])])
- end, SItems))])]
- end,
- [?XC("h1", ?T("Roster of ") ++ us_to_list(US))] ++
- case Res of
- ok -> [?XREST("Submitted")];
- error -> [?XREST("Bad format")];
- nothing -> []
- end ++
- [?XAE("form", [{"action", ""}, {"method", "post"}],
- FItems ++
- [?P,
- ?INPUT("text", "newjid", ""), ?C(" "),
- ?INPUTT("submit", "addjid", "Add Jabber ID")
- ])].
+ FItems = case SItems of
+ [] -> [?CT(<<"None">>)];
+ _ ->
+ [?XE(<<"table">>,
+ [?XE(<<"thead">>,
+ [?XE(<<"tr">>,
+ [?XCT(<<"td">>, <<"Jabber ID">>),
+ ?XCT(<<"td">>, <<"Nickname">>),
+ ?XCT(<<"td">>, <<"Subscription">>),
+ ?XCT(<<"td">>, <<"Pending">>),
+ ?XCT(<<"td">>, <<"Groups">>)])]),
+ ?XE(<<"tbody">>,
+ (lists:map(fun (R) ->
+ Groups = lists:flatmap(fun
+ (Group) ->
+ [?C(Group),
+ ?BR]
+ end,
+ R#roster.groups),
+ Pending =
+ ask_to_pending(R#roster.ask),
+ TDJID =
+ build_contact_jid_td(R#roster.jid),
+ ?XE(<<"tr">>,
+ [TDJID,
+ ?XAC(<<"td">>,
+ [{<<"class">>,
+ <<"valign">>}],
+ (R#roster.name)),
+ ?XAC(<<"td">>,
+ [{<<"class">>,
+ <<"valign">>}],
+ (iolist_to_binary(atom_to_list(R#roster.subscription)))),
+ ?XAC(<<"td">>,
+ [{<<"class">>,
+ <<"valign">>}],
+ (iolist_to_binary(atom_to_list(Pending)))),
+ ?XAE(<<"td">>,
+ [{<<"class">>,
+ <<"valign">>}],
+ Groups),
+ if Pending == in ->
+ ?XAE(<<"td">>,
+ [{<<"class">>,
+ <<"valign">>}],
+ [?INPUTT(<<"submit">>,
+ <<"validate",
+ (ejabberd_web_admin:term_to_id(R#roster.jid))/binary>>,
+ <<"Validate">>)]);
+ true -> ?X(<<"td">>)
+ end,
+ ?XAE(<<"td">>,
+ [{<<"class">>,
+ <<"valign">>}],
+ [?INPUTT(<<"submit">>,
+ <<"remove",
+ (ejabberd_web_admin:term_to_id(R#roster.jid))/binary>>,
+ <<"Remove">>)])])
+ end,
+ SItems)))])]
+ end,
+ [?XC(<<"h1">>,
+ (<<(?T(<<"Roster of ">>))/binary, (us_to_list(US))/binary>>))]
+ ++
+ case Res of
+ ok -> [?XREST(<<"Submitted">>)];
+ error -> [?XREST(<<"Bad format">>)];
+ nothing -> []
+ end
+ ++
+ [?XAE(<<"form">>,
+ [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
+ (FItems ++
+ [?P, ?INPUT(<<"text">>, <<"newjid">>, <<"">>),
+ ?C(<<" ">>),
+ ?INPUTT(<<"submit">>, <<"addjid">>,
+ <<"Add Jabber ID">>)]))].
build_contact_jid_td(RosterJID) ->
- %% Convert {U, S, R} into {jid, U, S, R, U, S, R}:
ContactJID = jlib:make_jid(RosterJID),
- JIDURI = case {ContactJID#jid.luser, ContactJID#jid.lserver} of
- {"", _} -> "";
- {CUser, CServer} ->
- case lists:member(CServer, ?MYHOSTS) of
- false -> "";
- true -> "/admin/server/" ++ CServer ++ "/user/" ++ CUser ++ "/"
- end
+ JIDURI = case {ContactJID#jid.luser,
+ ContactJID#jid.lserver}
+ of
+ {<<"">>, _} -> <<"">>;
+ {CUser, CServer} ->
+ case lists:member(CServer, ?MYHOSTS) of
+ false -> <<"">>;
+ true ->
+ <<"/admin/server/", CServer/binary, "/user/",
+ CUser/binary, "/">>
+ end
end,
case JIDURI of
- [] ->
- ?XAC("td", [{"class", "valign"}], jlib:jid_to_string(RosterJID));
- URI when is_list(URI) ->
- ?XAE("td", [{"class", "valign"}], [?AC(JIDURI, jlib:jid_to_string(RosterJID))])
+ <<>> ->
+ ?XAC(<<"td">>, [{<<"class">>, <<"valign">>}],
+ (jlib:jid_to_string(RosterJID)));
+ URI when is_binary(URI) ->
+ ?XAE(<<"td">>, [{<<"class">>, <<"valign">>}],
+ [?AC(JIDURI, (jlib:jid_to_string(RosterJID)))])
end.
user_roster_parse_query(User, Server, Items, Query) ->
- case lists:keysearch("addjid", 1, Query) of
- {value, _} ->
- case lists:keysearch("newjid", 1, Query) of
- {value, {_, undefined}} ->
- error;
- {value, {_, SJID}} ->
- case jlib:string_to_jid(SJID) of
- JID when is_record(JID, jid) ->
- user_roster_subscribe_jid(User, Server, JID),
- ok;
- error ->
- error
- end;
- false ->
- error
- end;
- false ->
- case catch user_roster_item_parse_query(
- User, Server, Items, Query) of
- submitted ->
- ok;
- {'EXIT', _Reason} ->
- error;
- _ ->
- nothing
- end
+ case lists:keysearch(<<"addjid">>, 1, Query) of
+ {value, _} ->
+ case lists:keysearch(<<"newjid">>, 1, Query) of
+ {value, {_, SJID}} ->
+ case jlib:string_to_jid(SJID) of
+ JID when is_record(JID, jid) ->
+ user_roster_subscribe_jid(User, Server, JID), ok;
+ error -> error
+ end;
+ false -> error
+ end;
+ false ->
+ case catch user_roster_item_parse_query(User, Server,
+ Items, Query)
+ of
+ submitted -> ok;
+ {'EXIT', _Reason} -> error;
+ _ -> nothing
+ end
end.
-
user_roster_subscribe_jid(User, Server, JID) ->
out_subscription(User, Server, JID, subscribe),
- UJID = jlib:make_jid(User, Server, ""),
- ejabberd_router:route(
- UJID, JID, {xmlelement, "presence", [{"type", "subscribe"}], []}).
-
-user_roster_item_parse_query(User, Server, Items, Query) ->
- lists:foreach(
- fun(R) ->
- JID = R#roster.jid,
- case lists:keysearch(
- "validate" ++ ejabberd_web_admin:term_to_id(JID), 1, Query) of
- {value, _} ->
- JID1 = jlib:make_jid(JID),
- out_subscription(
- User, Server, JID1, subscribed),
- UJID = jlib:make_jid(User, Server, ""),
- ejabberd_router:route(
- UJID, JID1, {xmlelement, "presence",
- [{"type", "subscribed"}], []}),
- throw(submitted);
- false ->
- case lists:keysearch(
- "remove" ++ ejabberd_web_admin:term_to_id(JID), 1, Query) of
- {value, _} ->
- UJID = jlib:make_jid(User, Server, ""),
- process_iq(
- UJID, UJID,
- #iq{type = set,
- sub_el = {xmlelement, "query",
- [{"xmlns", ?NS_ROSTER}],
- [{xmlelement, "item",
- [{"jid", jlib:jid_to_string(JID)},
- {"subscription", "remove"}],
- []}]}}),
- throw(submitted);
- false ->
- ok
- end
-
- end
- end, Items),
+ UJID = jlib:make_jid(User, Server, <<"">>),
+ ejabberd_router:route(UJID, JID,
+ #xmlel{name = <<"presence">>,
+ attrs = [{<<"type">>, <<"subscribe">>}],
+ children = []}).
+
+user_roster_item_parse_query(User, Server, Items,
+ Query) ->
+ lists:foreach(fun (R) ->
+ JID = R#roster.jid,
+ case lists:keysearch(<<"validate",
+ (ejabberd_web_admin:term_to_id(JID))/binary>>,
+ 1, Query)
+ of
+ {value, _} ->
+ JID1 = jlib:make_jid(JID),
+ out_subscription(User, Server, JID1,
+ subscribed),
+ UJID = jlib:make_jid(User, Server, <<"">>),
+ ejabberd_router:route(UJID, JID1,
+ #xmlel{name =
+ <<"presence">>,
+ attrs =
+ [{<<"type">>,
+ <<"subscribed">>}],
+ children = []}),
+ throw(submitted);
+ false ->
+ case lists:keysearch(<<"remove",
+ (ejabberd_web_admin:term_to_id(JID))/binary>>,
+ 1, Query)
+ of
+ {value, _} ->
+ UJID = jlib:make_jid(User, Server,
+ <<"">>),
+ process_iq(UJID, UJID,
+ #iq{type = set,
+ sub_el =
+ #xmlel{name =
+ <<"query">>,
+ attrs =
+ [{<<"xmlns">>,
+ ?NS_ROSTER}],
+ children =
+ [#xmlel{name
+ =
+ <<"item">>,
+ attrs
+ =
+ [{<<"jid">>,
+ jlib:jid_to_string(JID)},
+ {<<"subscription">>,
+ <<"remove">>}],
+ children
+ =
+ []}]}}),
+ throw(submitted);
+ false -> ok
+ end
+ end
+ end,
+ Items),
nothing.
us_to_list({User, Server}) ->
- jlib:jid_to_string({User, Server, ""}).
+ jlib:jid_to_string({User, Server, <<"">>}).
webadmin_user(Acc, _User, _Server, Lang) ->
- Acc ++ [?XE("h3", [?ACT("roster/", "Roster")])].
-
+ Acc ++
+ [?XE(<<"h3">>, [?ACT(<<"roster/">>, <<"Roster">>)])].
+
+export(_Server) ->
+ [{roster,
+ fun(Host, #roster{usj = {LUser, LServer, LJID}} = R)
+ when LServer == Host ->
+ Username = ejabberd_odbc:escape(LUser),
+ SJID = ejabberd_odbc:escape(jlib:jid_to_string(LJID)),
+ ItemVals = record_to_string(R),
+ ItemGroups = groups_to_string(R),
+ odbc_queries:update_roster_sql(Username, SJID,
+ ItemVals, ItemGroups);
+ (_Host, _R) ->
+ []
+ end},
+ {roster_version,
+ fun(Host, #roster_version{us = {LUser, LServer}, version = Ver})
+ when LServer == Host ->
+ Username = ejabberd_odbc:escape(LUser),
+ SVer = ejabberd_odbc:escape(Ver),
+ [[<<"delete from roster_version where username='">>,
+ Username, <<"';">>],
+ [<<"insert into roster_version(username, version) values('">>,
+ Username, <<"', '">>, SVer, <<"');">>]];
+ (_Host, _R) ->
+ []
+ end}].
diff --git a/src/mod_roster.hrl b/src/mod_roster.hrl
index 2fc2c76c0..4751eec38 100644
--- a/src/mod_roster.hrl
+++ b/src/mod_roster.hrl
@@ -19,15 +19,24 @@
%%%
%%%----------------------------------------------------------------------
--record(roster, {usj,
- us,
- jid,
- name = "",
- subscription = none,
- ask = none,
- groups = [],
- askmessage = [],
- xs = []}).
+-record(roster,
+{
+ usj = {<<>>, <<>>, {<<>>, <<>>, <<>>}} :: {binary(), binary(), ljid()} | '_',
+ us = {<<>>, <<>>} :: {binary(), binary()} | '_',
+ jid = {<<>>, <<>>, <<>>} :: ljid(),
+ name = <<>> :: binary() | '_',
+ subscription = none :: subscription() | '_',
+ ask = none :: ask() | '_',
+ groups = [] :: [binary()] | '_',
+ askmessage = <<"">> :: binary() | '_',
+ xs = [] :: [xmlel()] | '_'
+}).
--record(roster_version, {us,
- version}).
+-record(roster_version,
+{
+ us = {<<>>, <<>>} :: {binary(), binary()},
+ version = <<>> :: binary()
+}).
+
+-type ask() :: none | in | out | both | subscribe | unsubscribe.
+-type subscription() :: none | both | from | to | remove.
diff --git a/src/mod_service_log.erl b/src/mod_service_log.erl
index 10385ebed..2a0def2b5 100644
--- a/src/mod_service_log.erl
+++ b/src/mod_service_log.erl
@@ -25,6 +25,7 @@
%%%----------------------------------------------------------------------
-module(mod_service_log).
+
-author('alexey@process-one.net').
-behaviour(gen_mod).
@@ -35,18 +36,19 @@
log_user_receive/4]).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
start(Host, _Opts) ->
- ejabberd_hooks:add(user_send_packet, Host,
- ?MODULE, log_user_send, 50),
- ejabberd_hooks:add(user_receive_packet, Host,
- ?MODULE, log_user_receive, 50),
+ ejabberd_hooks:add(user_send_packet, Host, ?MODULE,
+ log_user_send, 50),
+ ejabberd_hooks:add(user_receive_packet, Host, ?MODULE,
+ log_user_receive, 50),
ok.
stop(Host) ->
- ejabberd_hooks:delete(user_send_packet, Host,
- ?MODULE, log_user_send, 50),
+ ejabberd_hooks:delete(user_send_packet, Host, ?MODULE,
+ log_user_send, 50),
ejabberd_hooks:delete(user_receive_packet, Host,
?MODULE, log_user_receive, 50),
ok.
@@ -57,21 +59,39 @@ log_user_send(From, To, Packet) ->
log_user_receive(_JID, From, To, Packet) ->
log_packet(From, To, Packet, To#jid.lserver).
-
-log_packet(From, To, {xmlelement, Name, Attrs, Els}, Host) ->
- Loggers = gen_mod:get_module_opt(Host, ?MODULE, loggers, []),
- ServerJID = #jid{user = "", server = Host, resource = "",
- luser = "", lserver = Host, lresource = ""},
- NewAttrs = jlib:replace_from_to_attrs(jlib:jid_to_string(From),
- jlib:jid_to_string(To),
- Attrs),
- FixedPacket = {xmlelement, Name, NewAttrs, Els},
- lists:foreach(
- fun(Logger) ->
- ejabberd_router:route(
- ServerJID,
- #jid{user = "", server = Logger, resource = "",
- luser = "", lserver = Logger, lresource = ""},
- {xmlelement, "route", [], [FixedPacket]})
- end, Loggers).
-
+log_packet(From, To,
+ #xmlel{name = Name, attrs = Attrs, children = Els},
+ Host) ->
+ Loggers = gen_mod:get_module_opt(Host, ?MODULE, loggers,
+ fun(L) ->
+ lists:map(
+ fun(S) ->
+ B = iolist_to_binary(S),
+ N = jlib:nameprep(B),
+ if N /= error ->
+ N
+ end
+ end, L)
+ end, []),
+ ServerJID = #jid{user = <<"">>, server = Host,
+ resource = <<"">>, luser = <<"">>, lserver = Host,
+ lresource = <<"">>},
+ NewAttrs =
+ jlib:replace_from_to_attrs(jlib:jid_to_string(From),
+ jlib:jid_to_string(To), Attrs),
+ FixedPacket = #xmlel{name = Name, attrs = NewAttrs,
+ children = Els},
+ lists:foreach(fun (Logger) ->
+ ejabberd_router:route(ServerJID,
+ #jid{user = <<"">>,
+ server = Logger,
+ resource = <<"">>,
+ luser = <<"">>,
+ lserver = Logger,
+ lresource = <<"">>},
+ #xmlel{name = <<"route">>,
+ attrs = [],
+ children =
+ [FixedPacket]})
+ end,
+ Loggers).
diff --git a/src/mod_shared_roster.erl b/src/mod_shared_roster.erl
index 567c7ea57..057deba20 100644
--- a/src/mod_shared_roster.erl
+++ b/src/mod_shared_roster.erl
@@ -25,190 +25,189 @@
%%%----------------------------------------------------------------------
-module(mod_shared_roster).
+
-author('alexey@process-one.net').
-behaviour(gen_mod).
--export([start/2, stop/1,
- item_to_xml/1,
- webadmin_menu/3, webadmin_page/3,
- get_user_roster/2,
- get_subscription_lists/3,
- get_jid_info/4,
- process_item/2,
- in_subscription/6,
- out_subscription/4,
- user_available/1,
- unset_presence/4,
- register_user/2,
- remove_user/2,
- list_groups/1,
- create_group/2,
- create_group/3,
- delete_group/2,
- get_group_opts/2,
- set_group_opts/3,
- get_group_users/2,
- get_group_explicit_users/2,
- is_user_in_group/3,
- add_user_to_group/3,
- remove_user_from_group/3]).
+-export([start/2, stop/1, item_to_xml/1, export/1,
+ webadmin_menu/3, webadmin_page/3, get_user_roster/2,
+ get_subscription_lists/3, get_jid_info/4,
+ process_item/2, in_subscription/6, out_subscription/4,
+ user_available/1, unset_presence/4, register_user/2,
+ remove_user/2, list_groups/1, create_group/2,
+ create_group/3, delete_group/2, get_group_opts/2,
+ set_group_opts/3, get_group_users/2,
+ get_group_explicit_users/2, is_user_in_group/3,
+ add_user_to_group/3, remove_user_from_group/3]).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
+
-include("mod_roster.hrl").
+
-include("web/ejabberd_http.hrl").
+
-include("web/ejabberd_web_admin.hrl").
--record(sr_group, {group_host, opts}).
--record(sr_user, {us, group_host}).
+-record(sr_group, {group_host = {<<"">>, <<"">>} :: {'$1' | binary(), '$2' | binary()},
+ opts = [] :: list() | '_' | '$2'}).
+
+-record(sr_user, {us = {<<"">>, <<"">>} :: {binary(), binary()},
+ group_host = {<<"">>, <<"">>} :: {binary(), binary()}}).
start(Host, Opts) ->
case gen_mod:db_type(Opts) of
- mnesia ->
- mnesia:create_table(sr_group,
- [{disc_copies, [node()]},
- {attributes, record_info(fields, sr_group)}]),
- mnesia:create_table(sr_user,
- [{disc_copies, [node()]},
- {type, bag},
- {attributes, record_info(fields, sr_user)}]),
- mnesia:add_table_index(sr_user, group_host);
- _ ->
- ok
+ mnesia ->
+ mnesia:create_table(sr_group,
+ [{disc_copies, [node()]},
+ {attributes, record_info(fields, sr_group)}]),
+ mnesia:create_table(sr_user,
+ [{disc_copies, [node()]}, {type, bag},
+ {attributes, record_info(fields, sr_user)}]),
+ update_tables(),
+ mnesia:add_table_index(sr_user, group_host);
+ _ -> ok
end,
- ejabberd_hooks:add(webadmin_menu_host, Host,
- ?MODULE, webadmin_menu, 70),
- ejabberd_hooks:add(webadmin_page_host, Host,
- ?MODULE, webadmin_page, 50),
- ejabberd_hooks:add(roster_get, Host,
- ?MODULE, get_user_roster, 70),
+ ejabberd_hooks:add(webadmin_menu_host, Host, ?MODULE,
+ webadmin_menu, 70),
+ ejabberd_hooks:add(webadmin_page_host, Host, ?MODULE,
+ webadmin_page, 50),
+ ejabberd_hooks:add(roster_get, Host, ?MODULE,
+ get_user_roster, 70),
ejabberd_hooks:add(roster_in_subscription, Host,
- ?MODULE, in_subscription, 30),
+ ?MODULE, in_subscription, 30),
ejabberd_hooks:add(roster_out_subscription, Host,
- ?MODULE, out_subscription, 30),
+ ?MODULE, out_subscription, 30),
ejabberd_hooks:add(roster_get_subscription_lists, Host,
?MODULE, get_subscription_lists, 70),
- ejabberd_hooks:add(roster_get_jid_info, Host,
- ?MODULE, get_jid_info, 70),
- ejabberd_hooks:add(roster_process_item, Host,
- ?MODULE, process_item, 50),
- ejabberd_hooks:add(user_available_hook, Host,
- ?MODULE, user_available, 50),
- ejabberd_hooks:add(unset_presence_hook, Host,
- ?MODULE, unset_presence, 50),
- ejabberd_hooks:add(register_user, Host,
- ?MODULE, register_user, 50),
- ejabberd_hooks:add(anonymous_purge_hook, Host,
- ?MODULE, remove_user, 50),
- ejabberd_hooks:add(remove_user, Host,
- ?MODULE, remove_user, 50).
+ ejabberd_hooks:add(roster_get_jid_info, Host, ?MODULE,
+ get_jid_info, 70),
+ ejabberd_hooks:add(roster_process_item, Host, ?MODULE,
+ process_item, 50),
+ ejabberd_hooks:add(user_available_hook, Host, ?MODULE,
+ user_available, 50),
+ ejabberd_hooks:add(unset_presence_hook, Host, ?MODULE,
+ unset_presence, 50),
+ ejabberd_hooks:add(register_user, Host, ?MODULE,
+ register_user, 50),
+ ejabberd_hooks:add(anonymous_purge_hook, Host, ?MODULE,
+ remove_user, 50),
+ ejabberd_hooks:add(remove_user, Host, ?MODULE,
+ remove_user, 50).
+
%%ejabberd_hooks:add(remove_user, Host,
%% ?MODULE, remove_user, 50),
stop(Host) ->
- ejabberd_hooks:delete(webadmin_menu_host, Host,
- ?MODULE, webadmin_menu, 70),
- ejabberd_hooks:delete(webadmin_page_host, Host,
- ?MODULE, webadmin_page, 50),
- ejabberd_hooks:delete(roster_get, Host,
- ?MODULE, get_user_roster, 70),
+ ejabberd_hooks:delete(webadmin_menu_host, Host, ?MODULE,
+ webadmin_menu, 70),
+ ejabberd_hooks:delete(webadmin_page_host, Host, ?MODULE,
+ webadmin_page, 50),
+ ejabberd_hooks:delete(roster_get, Host, ?MODULE,
+ get_user_roster, 70),
ejabberd_hooks:delete(roster_in_subscription, Host,
- ?MODULE, in_subscription, 30),
+ ?MODULE, in_subscription, 30),
ejabberd_hooks:delete(roster_out_subscription, Host,
- ?MODULE, out_subscription, 30),
- ejabberd_hooks:delete(roster_get_subscription_lists, Host,
- ?MODULE, get_subscription_lists, 70),
+ ?MODULE, out_subscription, 30),
+ ejabberd_hooks:delete(roster_get_subscription_lists,
+ Host, ?MODULE, get_subscription_lists, 70),
ejabberd_hooks:delete(roster_get_jid_info, Host,
- ?MODULE, get_jid_info, 70),
+ ?MODULE, get_jid_info, 70),
ejabberd_hooks:delete(roster_process_item, Host,
?MODULE, process_item, 50),
ejabberd_hooks:delete(user_available_hook, Host,
?MODULE, user_available, 50),
ejabberd_hooks:delete(unset_presence_hook, Host,
?MODULE, unset_presence, 50),
- ejabberd_hooks:delete(register_user, Host,
- ?MODULE, register_user, 50),
+ ejabberd_hooks:delete(register_user, Host, ?MODULE,
+ register_user, 50),
ejabberd_hooks:delete(anonymous_purge_hook, Host,
?MODULE, remove_user, 50),
- ejabberd_hooks:delete(remove_user, Host,
- ?MODULE, remove_user, 50).
%%ejabberd_hooks:delete(remove_user, Host,
%% ?MODULE, remove_user, 50),
-
+ ejabberd_hooks:delete(remove_user, Host, ?MODULE,
+ remove_user,
+ 50).%%ejabberd_hooks:delete(remove_user, Host,
+ %% ?MODULE, remove_user, 50),
get_user_roster(Items, US) ->
{U, S} = US,
DisplayedGroups = get_user_displayed_groups(US),
- %% Get shared roster users in all groups and remove self:
- SRUsers =
- lists:foldl(
- fun(Group, Acc1) ->
- GroupName = get_group_name(S, Group),
- lists:foldl(
- fun(User, Acc2) ->
- if User == US -> Acc2;
- true -> dict:append(User,
- GroupName,
- Acc2)
- end
- end, Acc1, get_group_users(S, Group))
- end, dict:new(), DisplayedGroups),
-
- %% If partially subscribed users are also in shared roster, show them as
- %% totally subscribed:
- {NewItems1, SRUsersRest} =
- lists:mapfoldl(
- fun(Item, SRUsers1) ->
- {_, _, {U1, S1, _}} = Item#roster.usj,
- US1 = {U1, S1},
- case dict:find(US1, SRUsers1) of
- {ok, _GroupNames} ->
- {Item#roster{subscription = both, ask = none},
- dict:erase(US1, SRUsers1)};
- error ->
- {Item, SRUsers1}
- end
- end, SRUsers, Items),
-
- %% Export items in roster format:
+ SRUsers = lists:foldl(fun (Group, Acc1) ->
+ GroupName = get_group_name(S, Group),
+ lists:foldl(fun (User, Acc2) ->
+ if User == US -> Acc2;
+ true ->
+ dict:append(User,
+ GroupName,
+ Acc2)
+ end
+ end,
+ Acc1, get_group_users(S, Group))
+ end,
+ dict:new(), DisplayedGroups),
+ {NewItems1, SRUsersRest} = lists:mapfoldl(fun (Item,
+ SRUsers1) ->
+ {_, _, {U1, S1, _}} =
+ Item#roster.usj,
+ US1 = {U1, S1},
+ case dict:find(US1,
+ SRUsers1)
+ of
+ {ok, _GroupNames} ->
+ {Item#roster{subscription
+ =
+ both,
+ ask =
+ none},
+ dict:erase(US1,
+ SRUsers1)};
+ error ->
+ {Item, SRUsers1}
+ end
+ end,
+ SRUsers, Items),
ModVcard = get_vcard_module(S),
- SRItems = [#roster{usj = {U, S, {U1, S1, ""}},
- us = US,
- jid = {U1, S1, ""},
+ SRItems = [#roster{usj = {U, S, {U1, S1, <<"">>}},
+ us = US, jid = {U1, S1, <<"">>},
name = get_rosteritem_name(ModVcard, U1, S1),
- subscription = both,
- ask = none,
- groups = GroupNames} ||
- {{U1, S1}, GroupNames} <- dict:to_list(SRUsersRest)],
+ subscription = both, ask = none, groups = GroupNames}
+ || {{U1, S1}, GroupNames} <- dict:to_list(SRUsersRest)],
SRItems ++ NewItems1.
get_vcard_module(Server) ->
Modules = gen_mod:loaded_modules(Server),
- [M || M <- Modules,
- (M == mod_vcard) or (M == mod_vcard_ldap)].
+ [M
+ || M <- Modules,
+ (M == mod_vcard) or (M == mod_vcard_ldap)].
-get_rosteritem_name([], _, _) ->
- "";
+get_rosteritem_name([], _, _) -> <<"">>;
get_rosteritem_name([ModVcard], U, S) ->
- From = jlib:make_jid("", S, ?MODULE),
- To = jlib:make_jid(U, S, ""),
- IQ = {iq,"",get,"vcard-temp","",
- {xmlelement,"vCard",[{"xmlns","vcard-temp"}],[]}},
+ From = jlib:make_jid(<<"">>, S, jlib:atom_to_binary(?MODULE)),
+ To = jlib:make_jid(U, S, <<"">>),
+ IQ = {iq, <<"">>, get, <<"vcard-temp">>, <<"">>,
+ #xmlel{name = <<"vCard">>,
+ attrs = [{<<"xmlns">>, <<"vcard-temp">>}],
+ children = []}},
IQ_Vcard = ModVcard:process_sm_iq(From, To, IQ),
- try get_rosteritem_name_vcard(IQ_Vcard#iq.sub_el)
- catch E1:E2 ->
- ?ERROR_MSG("Error ~p found when trying to get the vCard of ~s@~s "
- "in ~p:~n ~p", [E1, U, S, ModVcard, E2]),
- ""
+ try get_rosteritem_name_vcard(IQ_Vcard#iq.sub_el) catch
+ E1:E2 ->
+ ?ERROR_MSG("Error ~p found when trying to get the "
+ "vCard of ~s@~s in ~p:~n ~p",
+ [E1, U, S, ModVcard, E2]),
+ <<"">>
end.
-get_rosteritem_name_vcard([]) ->
- "";
+get_rosteritem_name_vcard([]) -> <<"">>;
get_rosteritem_name_vcard([Vcard]) ->
- case xml:get_path_s(Vcard, [{elem, "NICKNAME"}, cdata]) of
- "" -> xml:get_path_s(Vcard, [{elem, "FN"}, cdata]);
- Nickname -> Nickname
+ case xml:get_path_s(Vcard,
+ [{elem, <<"NICKNAME">>}, cdata])
+ of
+ <<"">> ->
+ xml:get_path_s(Vcard, [{elem, <<"FN">>}, cdata]);
+ Nickname -> Nickname
end.
%% This function rewrites the roster entries when moving or renaming
@@ -219,423 +218,390 @@ process_item(RosterItem, Host) ->
NameTo = RosterItem#roster.name,
USTo = {UserTo, ServerTo},
DisplayedGroups = get_user_displayed_groups(USFrom),
- CommonGroups = lists:filter(fun(Group) ->
+ CommonGroups = lists:filter(fun (Group) ->
is_user_in_group(USTo, Group, Host)
- end, DisplayedGroups),
+ end,
+ DisplayedGroups),
case CommonGroups of
- [] -> RosterItem;
- %% Roster item cannot be removed: We simply reset the original groups:
- _ when RosterItem#roster.subscription == remove ->
- GroupNames = lists:map(fun(Group) ->
- get_group_name(Host, Group)
- end, CommonGroups),
- RosterItem#roster{subscription = both, ask = none,
- groups=[GroupNames]};
- %% Both users have at least a common shared group,
- %% So each user can see the other
- _ ->
- %% Check if the list of groups of the new roster item
- %% include at least a new one
- case lists:subtract(RosterItem#roster.groups, CommonGroups) of
- %% If it doesn't, then remove this user from any
- %% existing roster groups.
- [] ->
- %% Remove pending subscription by setting it
- %% unsubscribed.
-
- %% Remove pending out subscription
- mod_roster:out_subscription(UserTo, ServerTo,
- jlib:make_jid(UserFrom, ServerFrom, ""),
- unsubscribe),
-
- %% Remove pending in subscription
- mod_roster:in_subscription(aaaa, UserFrom, ServerFrom,
- jlib:make_jid(UserTo, ServerTo, ""),
- unsubscribe, ""),
-
- %% But we're still subscribed, so respond as such.
- RosterItem#roster{subscription = both, ask = none};
- %% If so, it means the user wants to add that contact
- %% to his personal roster
- PersonalGroups ->
- %% Store roster items in From and To rosters
- set_new_rosteritems(UserFrom, ServerFrom,
- UserTo, ServerTo, ResourceTo, NameTo,
- PersonalGroups)
- end
+ [] -> RosterItem;
+ %% Roster item cannot be removed: We simply reset the original groups:
+ _ when RosterItem#roster.subscription == remove ->
+ GroupNames = lists:map(fun (Group) ->
+ get_group_name(Host, Group)
+ end,
+ CommonGroups),
+ RosterItem#roster{subscription = both, ask = none,
+ groups = GroupNames};
+ %% Both users have at least a common shared group,
+ %% So each user can see the other
+ _ ->
+ case lists:subtract(RosterItem#roster.groups,
+ CommonGroups)
+ of
+ %% If it doesn't, then remove this user from any
+ %% existing roster groups.
+ [] ->
+ mod_roster:out_subscription(UserTo, ServerTo,
+ jlib:make_jid(UserFrom, ServerFrom,
+ <<"">>),
+ unsubscribe),
+ mod_roster:in_subscription(aaaa, UserFrom, ServerFrom,
+ jlib:make_jid(UserTo, ServerTo,
+ <<"">>),
+ unsubscribe, <<"">>),
+ RosterItem#roster{subscription = both, ask = none};
+ %% If so, it means the user wants to add that contact
+ %% to his personal roster
+ PersonalGroups ->
+ set_new_rosteritems(UserFrom, ServerFrom, UserTo,
+ ServerTo, ResourceTo, NameTo,
+ PersonalGroups)
+ end
end.
-build_roster_record(User1, Server1, User2, Server2, Name2, Groups) ->
- USR2 = {User2, Server2, ""},
+build_roster_record(User1, Server1, User2, Server2,
+ Name2, Groups) ->
+ USR2 = {User2, Server2, <<"">>},
#roster{usj = {User1, Server1, USR2},
- us = {User1, Server1},
- jid = USR2,
- name = Name2,
- subscription = both,
- ask = none,
- groups = Groups
- }.
-
-set_new_rosteritems(UserFrom, ServerFrom,
- UserTo, ServerTo, ResourceTo, NameTo, GroupsFrom) ->
+ us = {User1, Server1}, jid = USR2, name = Name2,
+ subscription = both, ask = none, groups = Groups}.
+
+set_new_rosteritems(UserFrom, ServerFrom, UserTo,
+ ServerTo, ResourceTo, NameTo, GroupsFrom) ->
RIFrom = build_roster_record(UserFrom, ServerFrom,
UserTo, ServerTo, NameTo, GroupsFrom),
set_item(UserFrom, ServerFrom, ResourceTo, RIFrom),
- JIDTo = jlib:make_jid(UserTo, ServerTo, ""),
-
- JIDFrom = jlib:make_jid(UserFrom, ServerFrom, ""),
- RITo = build_roster_record(UserTo, ServerTo,
- UserFrom, ServerFrom, UserFrom,[]),
- set_item(UserTo, ServerTo, "", RITo),
-
- %% From requests
- mod_roster:out_subscription(UserFrom, ServerFrom, JIDTo, subscribe),
- mod_roster:in_subscription(aaa, UserTo, ServerTo, JIDFrom, subscribe, ""),
-
- %% To accepts
- mod_roster:out_subscription(UserTo, ServerTo, JIDFrom, subscribed),
- mod_roster:in_subscription(aaa, UserFrom, ServerFrom, JIDTo, subscribed, ""),
-
- %% To requests
- mod_roster:out_subscription(UserTo, ServerTo, JIDFrom, subscribe),
- mod_roster:in_subscription(aaa, UserFrom, ServerFrom, JIDTo, subscribe, ""),
-
- %% From accepts
- mod_roster:out_subscription(UserFrom, ServerFrom, JIDTo, subscribed),
- mod_roster:in_subscription(aaa, UserTo, ServerTo, JIDFrom, subscribed, ""),
-
+ JIDTo = jlib:make_jid(UserTo, ServerTo, <<"">>),
+ JIDFrom = jlib:make_jid(UserFrom, ServerFrom, <<"">>),
+ RITo = build_roster_record(UserTo, ServerTo, UserFrom,
+ ServerFrom, UserFrom, []),
+ set_item(UserTo, ServerTo, <<"">>, RITo),
+ mod_roster:out_subscription(UserFrom, ServerFrom, JIDTo,
+ subscribe),
+ mod_roster:in_subscription(aaa, UserTo, ServerTo,
+ JIDFrom, subscribe, <<"">>),
+ mod_roster:out_subscription(UserTo, ServerTo, JIDFrom,
+ subscribed),
+ mod_roster:in_subscription(aaa, UserFrom, ServerFrom,
+ JIDTo, subscribed, <<"">>),
+ mod_roster:out_subscription(UserTo, ServerTo, JIDFrom,
+ subscribe),
+ mod_roster:in_subscription(aaa, UserFrom, ServerFrom,
+ JIDTo, subscribe, <<"">>),
+ mod_roster:out_subscription(UserFrom, ServerFrom, JIDTo,
+ subscribed),
+ mod_roster:in_subscription(aaa, UserTo, ServerTo,
+ JIDFrom, subscribed, <<"">>),
RIFrom.
set_item(User, Server, Resource, Item) ->
ResIQ = #iq{type = set, xmlns = ?NS_ROSTER,
- id = "push" ++ randoms:get_string(),
- sub_el = [{xmlelement, "query",
- [{"xmlns", ?NS_ROSTER}],
- [mod_roster:item_to_xml(Item)]}]},
- ejabberd_router:route(
- jlib:make_jid(User, Server, Resource),
- jlib:make_jid("", Server, ""),
- jlib:iq_to_xml(ResIQ)).
-
+ id = <<"push", (randoms:get_string())/binary>>,
+ sub_el =
+ [#xmlel{name = <<"query">>,
+ attrs = [{<<"xmlns">>, ?NS_ROSTER}],
+ children = [mod_roster:item_to_xml(Item)]}]},
+ ejabberd_router:route(jlib:make_jid(User, Server,
+ Resource),
+ jlib:make_jid(<<"">>, Server, <<"">>),
+ jlib:iq_to_xml(ResIQ)).
get_subscription_lists({F, T}, User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
DisplayedGroups = get_user_displayed_groups(US),
- SRUsers =
- lists:usort(
- lists:flatmap(
- fun(Group) ->
- get_group_users(LServer, Group)
- end, DisplayedGroups)),
- SRJIDs = [{U1, S1, ""} || {U1, S1} <- SRUsers],
+ SRUsers = lists:usort(lists:flatmap(fun (Group) ->
+ get_group_users(LServer, Group)
+ end,
+ DisplayedGroups)),
+ SRJIDs = [{U1, S1, <<"">>} || {U1, S1} <- SRUsers],
{lists:usort(SRJIDs ++ F), lists:usort(SRJIDs ++ T)}.
-get_jid_info({Subscription, Groups}, User, Server, JID) ->
+get_jid_info({Subscription, Groups}, User, Server,
+ JID) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
{U1, S1, _} = jlib:jid_tolower(JID),
US1 = {U1, S1},
DisplayedGroups = get_user_displayed_groups(US),
- SRUsers =
- lists:foldl(
- fun(Group, Acc1) ->
- lists:foldl(
- fun(User1, Acc2) ->
- dict:append(
- User1, get_group_name(LServer, Group), Acc2)
- end, Acc1, get_group_users(LServer, Group))
- end, dict:new(), DisplayedGroups),
+ SRUsers = lists:foldl(fun (Group, Acc1) ->
+ lists:foldl(fun (User1, Acc2) ->
+ dict:append(User1,
+ get_group_name(LServer,
+ Group),
+ Acc2)
+ end,
+ Acc1,
+ get_group_users(LServer, Group))
+ end,
+ dict:new(), DisplayedGroups),
case dict:find(US1, SRUsers) of
- {ok, GroupNames} ->
- NewGroups = if
- Groups == [] -> GroupNames;
- true -> Groups
- end,
- {both, NewGroups};
- error ->
- {Subscription, Groups}
+ {ok, GroupNames} ->
+ NewGroups = if Groups == [] -> GroupNames;
+ true -> Groups
+ end,
+ {both, NewGroups};
+ error -> {Subscription, Groups}
end.
-in_subscription(Acc, User, Server, JID, Type, _Reason) ->
+in_subscription(Acc, User, Server, JID, Type,
+ _Reason) ->
process_subscription(in, User, Server, JID, Type, Acc).
-out_subscription(UserFrom, ServerFrom, JIDTo, unsubscribed) ->
- %% Remove pending out subscription
+out_subscription(UserFrom, ServerFrom, JIDTo,
+ unsubscribed) ->
#jid{luser = UserTo, lserver = ServerTo} = JIDTo,
- JIDFrom = jlib:make_jid(UserFrom, UserTo, ""),
- mod_roster:out_subscription(UserTo, ServerTo, JIDFrom, unsubscribe),
-
- %% Remove pending in subscription
- mod_roster:in_subscription(aaaa, UserFrom, ServerFrom, JIDTo, unsubscribe, ""),
-
- process_subscription(out, UserFrom, ServerFrom, JIDTo, unsubscribed, false);
+ JIDFrom = jlib:make_jid(UserFrom, ServerFrom, <<"">>),
+ mod_roster:out_subscription(UserTo, ServerTo, JIDFrom,
+ unsubscribe),
+ mod_roster:in_subscription(aaaa, UserFrom, ServerFrom,
+ JIDTo, unsubscribe, <<"">>),
+ process_subscription(out, UserFrom, ServerFrom, JIDTo,
+ unsubscribed, false);
out_subscription(User, Server, JID, Type) ->
- process_subscription(out, User, Server, JID, Type, false).
+ process_subscription(out, User, Server, JID, Type,
+ false).
-process_subscription(Direction, User, Server, JID, _Type, Acc) ->
+process_subscription(Direction, User, Server, JID,
+ _Type, Acc) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
- {U1, S1, _} = jlib:jid_tolower(jlib:jid_remove_resource(JID)),
+ {U1, S1, _} =
+ jlib:jid_tolower(jlib:jid_remove_resource(JID)),
US1 = {U1, S1},
DisplayedGroups = get_user_displayed_groups(US),
- SRUsers =
- lists:usort(
- lists:flatmap(
- fun(Group) ->
- get_group_users(LServer, Group)
- end, DisplayedGroups)),
+ SRUsers = lists:usort(lists:flatmap(fun (Group) ->
+ get_group_users(LServer, Group)
+ end,
+ DisplayedGroups)),
case lists:member(US1, SRUsers) of
- true ->
- case Direction of
- in ->
- {stop, false};
- out ->
- stop
- end;
- false ->
- Acc
+ true ->
+ case Direction of
+ in -> {stop, false};
+ out -> stop
+ end;
+ false -> Acc
end.
list_groups(Host) ->
list_groups(Host, gen_mod:db_type(Host, ?MODULE)).
list_groups(Host, mnesia) ->
- mnesia:dirty_select(
- sr_group,
- [{#sr_group{group_host = {'$1', '$2'},
- _ = '_'},
- [{'==', '$2', Host}],
- ['$1']}]);
+ mnesia:dirty_select(sr_group,
+ [{#sr_group{group_host = {'$1', '$2'}, _ = '_'},
+ [{'==', '$2', Host}], ['$1']}]);
list_groups(Host, odbc) ->
- case ejabberd_odbc:sql_query(
- Host, ["select name from sr_group;"]) of
- {selected, ["name"], Rs} ->
- [G || {G} <- Rs];
- _ ->
- []
+ case ejabberd_odbc:sql_query(Host,
+ [<<"select name from sr_group;">>])
+ of
+ {selected, [<<"name">>], Rs} -> [G || [G] <- Rs];
+ _ -> []
end.
groups_with_opts(Host) ->
groups_with_opts(Host, gen_mod:db_type(Host, ?MODULE)).
groups_with_opts(Host, mnesia) ->
- Gs = mnesia:dirty_select(
- sr_group,
- [{#sr_group{group_host={'$1', Host}, opts='$2', _='_'},
- [],
- [['$1','$2']] }]),
- lists:map(fun([G,O]) -> {G, O} end, Gs);
+ Gs = mnesia:dirty_select(sr_group,
+ [{#sr_group{group_host = {'$1', Host}, opts = '$2',
+ _ = '_'},
+ [], [['$1', '$2']]}]),
+ lists:map(fun ([G, O]) -> {G, O} end, Gs);
groups_with_opts(Host, odbc) ->
- case ejabberd_odbc:sql_query(
- Host, ["select name, opts from sr_group;"]) of
- {selected, ["name", "opts"], Rs} ->
- [{G, ejabberd_odbc:decode_term(Opts)} || {G, Opts} <- Rs];
- _ ->
- []
+ case ejabberd_odbc:sql_query(Host,
+ [<<"select name, opts from sr_group;">>])
+ of
+ {selected, [<<"name">>, <<"opts">>], Rs} ->
+ [{G, opts_to_binary(ejabberd_odbc:decode_term(Opts))}
+ || [G, Opts] <- Rs];
+ _ -> []
end.
create_group(Host, Group) ->
create_group(Host, Group, []).
create_group(Host, Group, Opts) ->
- create_group(Host, Group, Opts, gen_mod:db_type(Host, ?MODULE)).
+ create_group(Host, Group, Opts,
+ gen_mod:db_type(Host, ?MODULE)).
create_group(Host, Group, Opts, mnesia) ->
R = #sr_group{group_host = {Group, Host}, opts = Opts},
- F = fun() ->
- mnesia:write(R)
- end,
+ F = fun () -> mnesia:write(R) end,
mnesia:transaction(F);
create_group(Host, Group, Opts, odbc) ->
SGroup = ejabberd_odbc:escape(Group),
SOpts = ejabberd_odbc:encode_term(Opts),
- F = fun() ->
- odbc_queries:update_t("sr_group",
- ["name", "opts"],
- [SGroup, SOpts],
- ["name='", SGroup, "'"])
- end,
+ F = fun () ->
+ odbc_queries:update_t(<<"sr_group">>,
+ [<<"name">>, <<"opts">>], [SGroup, SOpts],
+ [<<"name='">>, SGroup, <<"'">>])
+ end,
ejabberd_odbc:sql_transaction(Host, F).
delete_group(Host, Group) ->
- delete_group(Host, Group, gen_mod:db_type(Host, ?MODULE)).
+ delete_group(Host, Group,
+ gen_mod:db_type(Host, ?MODULE)).
delete_group(Host, Group, mnesia) ->
GroupHost = {Group, Host},
- F = fun() ->
- %% Delete the group ...
+ F = fun () ->
mnesia:delete({sr_group, GroupHost}),
- %% ... and its users
- Users = mnesia:index_read(sr_user, GroupHost, #sr_user.group_host),
- lists:foreach(fun(UserEntry) ->
+ Users = mnesia:index_read(sr_user, GroupHost,
+ #sr_user.group_host),
+ lists:foreach(fun (UserEntry) ->
mnesia:delete_object(UserEntry)
- end, Users)
+ end,
+ Users)
end,
mnesia:transaction(F);
delete_group(Host, Group, odbc) ->
SGroup = ejabberd_odbc:escape(Group),
- F = fun() ->
- ejabberd_odbc:sql_query_t(
- ["delete from sr_group where name='", SGroup, "';"]),
- ejabberd_odbc:sql_query_t(
- ["delete from sr_user where grp='", SGroup, "';"])
- end,
+ F = fun () ->
+ ejabberd_odbc:sql_query_t([<<"delete from sr_group where name='">>,
+ SGroup, <<"';">>]),
+ ejabberd_odbc:sql_query_t([<<"delete from sr_user where grp='">>,
+ SGroup, <<"';">>])
+ end,
ejabberd_odbc:sql_transaction(Host, F).
get_group_opts(Host, Group) ->
- get_group_opts(Host, Group, gen_mod:db_type(Host, ?MODULE)).
+ get_group_opts(Host, Group,
+ gen_mod:db_type(Host, ?MODULE)).
get_group_opts(Host, Group, mnesia) ->
case catch mnesia:dirty_read(sr_group, {Group, Host}) of
- [#sr_group{opts = Opts}] ->
- Opts;
- _ ->
- error
+ [#sr_group{opts = Opts}] -> Opts;
+ _ -> error
end;
get_group_opts(Host, Group, odbc) ->
SGroup = ejabberd_odbc:escape(Group),
- case catch ejabberd_odbc:sql_query(
- Host, ["select opts from sr_group "
- "where name='", SGroup, "';"]) of
- {selected, ["opts"], [{SOpts}]} ->
- ejabberd_odbc:decode_term(SOpts);
- _ ->
- error
+ case catch ejabberd_odbc:sql_query(Host,
+ [<<"select opts from sr_group where name='">>,
+ SGroup, <<"';">>])
+ of
+ {selected, [<<"opts">>], [[SOpts]]} ->
+ opts_to_binary(ejabberd_odbc:decode_term(SOpts));
+ _ -> error
end.
set_group_opts(Host, Group, Opts) ->
- set_group_opts(Host, Group, Opts, gen_mod:db_type(Host, ?MODULE)).
+ set_group_opts(Host, Group, Opts,
+ gen_mod:db_type(Host, ?MODULE)).
set_group_opts(Host, Group, Opts, mnesia) ->
R = #sr_group{group_host = {Group, Host}, opts = Opts},
- F = fun() ->
- mnesia:write(R)
- end,
+ F = fun () -> mnesia:write(R) end,
mnesia:transaction(F);
set_group_opts(Host, Group, Opts, odbc) ->
SGroup = ejabberd_odbc:escape(Group),
SOpts = ejabberd_odbc:encode_term(Opts),
- F = fun() ->
- odbc_queries:update_t("sr_group",
- ["name", "opts"],
- [SGroup, SOpts],
- ["name='", SGroup, "'"])
- end,
+ F = fun () ->
+ odbc_queries:update_t(<<"sr_group">>,
+ [<<"name">>, <<"opts">>], [SGroup, SOpts],
+ [<<"name='">>, SGroup, <<"'">>])
+ end,
ejabberd_odbc:sql_transaction(Host, F).
get_user_groups(US) ->
Host = element(2, US),
DBType = gen_mod:db_type(Host, ?MODULE),
- get_user_groups(US, Host, DBType) ++ get_special_users_groups(Host).
+ get_user_groups(US, Host, DBType) ++
+ get_special_users_groups(Host).
get_user_groups(US, Host, mnesia) ->
case catch mnesia:dirty_read(sr_user, US) of
- Rs when is_list(Rs) ->
- [Group || #sr_user{group_host = {Group, H}} <- Rs, H == Host];
- _ ->
- []
+ Rs when is_list(Rs) ->
+ [Group
+ || #sr_user{group_host = {Group, H}} <- Rs, H == Host];
+ _ -> []
end;
get_user_groups(US, Host, odbc) ->
SJID = make_jid_s(US),
- case catch ejabberd_odbc:sql_query(
- Host, ["select grp from sr_user "
- "where jid='", SJID, "';"]) of
- {selected, ["grp"], Rs} ->
- [G || {G} <- Rs];
- _ ->
- []
+ case catch ejabberd_odbc:sql_query(Host,
+ [<<"select grp from sr_user where jid='">>,
+ SJID, <<"';">>])
+ of
+ {selected, [<<"grp">>], Rs} -> [G || [G] <- Rs];
+ _ -> []
end.
is_group_enabled(Host1, Group1) ->
{Host, Group} = split_grouphost(Host1, Group1),
case get_group_opts(Host, Group) of
- error ->
- false;
- Opts ->
- not lists:member(disabled, Opts)
+ error -> false;
+ Opts -> not lists:member(disabled, Opts)
end.
%% @spec (Host::string(), Group::string(), Opt::atom(), Default) -> OptValue | Default
get_group_opt(Host, Group, Opt, Default) ->
case get_group_opts(Host, Group) of
- error ->
- Default;
- Opts ->
- case lists:keysearch(Opt, 1, Opts) of
- {value, {_, Val}} ->
- Val;
- false ->
- Default
- end
+ error -> Default;
+ Opts ->
+ case lists:keysearch(Opt, 1, Opts) of
+ {value, {_, Val}} -> Val;
+ false -> Default
+ end
end.
get_online_users(Host) ->
- lists:usort([{U, S} || {U, S, _} <- ejabberd_sm:get_vh_session_list(Host)]).
+ lists:usort([{U, S}
+ || {U, S, _} <- ejabberd_sm:get_vh_session_list(Host)]).
get_group_users(Host1, Group1) ->
{Host, Group} = split_grouphost(Host1, Group1),
case get_group_opt(Host, Group, all_users, false) of
- true ->
- ejabberd_auth:get_vh_registered_users(Host);
- false ->
- []
- end ++
- case get_group_opt(Host, Group, online_users, false) of
- true ->
- get_online_users(Host);
- false ->
- []
- end ++
- get_group_explicit_users(Host, Group).
+ true -> ejabberd_auth:get_vh_registered_users(Host);
+ false -> []
+ end
+ ++
+ case get_group_opt(Host, Group, online_users, false) of
+ true -> get_online_users(Host);
+ false -> []
+ end
+ ++ get_group_explicit_users(Host, Group).
get_group_users(Host, Group, GroupOpts) ->
case proplists:get_value(all_users, GroupOpts, false) of
- true ->
- ejabberd_auth:get_vh_registered_users(Host);
- false ->
- []
- end ++
- case proplists:get_value(online_users, GroupOpts, false) of
- true ->
- get_online_users(Host);
- false ->
- []
- end ++
- get_group_explicit_users(Host, Group).
-
%% @spec (Host::string(), Group::string()) -> [{User::string(), Server::string()}]
+ true -> ejabberd_auth:get_vh_registered_users(Host);
+ false -> []
+ end
+ ++
+ case proplists:get_value(online_users, GroupOpts, false)
+ of
+ true -> get_online_users(Host);
+ false -> []
+ end
+ ++ get_group_explicit_users(Host, Group).
+
get_group_explicit_users(Host, Group) ->
- get_group_explicit_users(Host, Group, gen_mod:db_type(Host, ?MODULE)).
+ get_group_explicit_users(Host, Group,
+ gen_mod:db_type(Host, ?MODULE)).
get_group_explicit_users(Host, Group, mnesia) ->
- Read = (catch mnesia:dirty_index_read(
- sr_user,
- {Group, Host},
- #sr_user.group_host)),
+ Read = (catch mnesia:dirty_index_read(sr_user,
+ {Group, Host}, #sr_user.group_host)),
case Read of
- Rs when is_list(Rs) ->
- [R#sr_user.us || R <- Rs];
- _ ->
- []
+ Rs when is_list(Rs) -> [R#sr_user.us || R <- Rs];
+ _ -> []
end;
get_group_explicit_users(Host, Group, odbc) ->
SGroup = ejabberd_odbc:escape(Group),
- case catch ejabberd_odbc:sql_query(
- Host, ["select jid from sr_user "
- "where grp='", SGroup, "';"]) of
- {selected, ["jid"], Rs} ->
- lists:map(
- fun({JID}) ->
- {U, S, _} = jlib:jid_tolower(
- jlib:string_to_jid(JID)),
- {U, S}
- end, Rs);
- _ ->
- []
+ case catch ejabberd_odbc:sql_query(Host,
+ [<<"select jid from sr_user where grp='">>,
+ SGroup, <<"';">>])
+ of
+ {selected, [<<"jid">>], Rs} ->
+ lists:map(fun ([JID]) ->
+ {U, S, _} =
+ jlib:jid_tolower(jlib:string_to_jid(JID)),
+ {U, S}
+ end,
+ Rs);
+ _ -> []
end.
get_group_name(Host1, Group1) ->
@@ -644,211 +610,233 @@ get_group_name(Host1, Group1) ->
%% Get list of names of groups that have @all@/@online@/etc in the memberlist
get_special_users_groups(Host) ->
- lists:filter(
- fun(Group) ->
- get_group_opt(Host, Group, all_users, false)
- orelse get_group_opt(Host, Group, online_users, false)
- end,
- list_groups(Host)).
-
%% Get list of names of groups that have @online@ in the memberlist
-get_special_users_groups_online(Host) ->
- lists:filter(
- fun(Group) ->
- get_group_opt(Host, Group, online_users, false)
- end,
- list_groups(Host)).
+ lists:filter(fun (Group) ->
+ get_group_opt(Host, Group, all_users, false) orelse
+ get_group_opt(Host, Group, online_users, false)
+ end,
+ list_groups(Host)).
+get_special_users_groups_online(Host) ->
%% Given two lists of groupnames and their options,
%% return the list of displayed groups to the second list
-displayed_groups(GroupsOpts, SelectedGroupsOpts) ->
- DisplayedGroups =
- lists:usort(
- lists:flatmap(
- fun({_Group, Opts}) ->
- [G || G <- proplists:get_value(displayed_groups, Opts, []),
- not lists:member(disabled, Opts)]
- end, SelectedGroupsOpts)),
- [G || G <- DisplayedGroups,
- not lists:member(disabled, proplists:get_value(G, GroupsOpts, []))].
+ lists:filter(fun (Group) ->
+ get_group_opt(Host, Group, online_users, false)
+ end,
+ list_groups(Host)).
+displayed_groups(GroupsOpts, SelectedGroupsOpts) ->
%% Given a list of group names with options,
%% for those that have @all@ in memberlist,
%% get the list of groups displayed
+ DisplayedGroups = lists:usort(lists:flatmap(fun
+ ({_Group, Opts}) ->
+ [G
+ || G
+ <- proplists:get_value(displayed_groups,
+ Opts,
+ []),
+ not
+ lists:member(disabled,
+ Opts)]
+ end,
+ SelectedGroupsOpts)),
+ [G
+ || G <- DisplayedGroups,
+ not
+ lists:member(disabled,
+ proplists:get_value(G, GroupsOpts, []))].
+
get_special_displayed_groups(GroupsOpts) ->
- Groups = lists:filter(
- fun({_Group, Opts}) ->
- proplists:get_value(all_users, Opts, false)
- end, GroupsOpts),
+ Groups = lists:filter(fun ({_Group, Opts}) ->
+ proplists:get_value(all_users, Opts, false)
+ end,
+ GroupsOpts),
displayed_groups(GroupsOpts, Groups).
%% Given a username and server, and a list of group names with options,
%% for the list of groups of that server that user is member
%% get the list of groups displayed
get_user_displayed_groups(LUser, LServer, GroupsOpts) ->
- Groups = get_user_displayed_groups(LUser, LServer, GroupsOpts,
- gen_mod:db_type(LServer, ?MODULE)),
+ Groups = get_user_displayed_groups(LUser, LServer,
+ GroupsOpts,
+ gen_mod:db_type(LServer, ?MODULE)),
displayed_groups(GroupsOpts, Groups).
-get_user_displayed_groups(LUser, LServer, GroupsOpts, mnesia) ->
- case catch mnesia:dirty_read(sr_user, {LUser, LServer}) of
- Rs when is_list(Rs) ->
- [{Group, proplists:get_value(Group, GroupsOpts, [])} ||
- #sr_user{group_host = {Group, H}} <- Rs, H == LServer];
- _ ->
- []
+get_user_displayed_groups(LUser, LServer, GroupsOpts,
+ mnesia) ->
+ case catch mnesia:dirty_read(sr_user, {LUser, LServer})
+ of
+ Rs when is_list(Rs) ->
+ [{Group, proplists:get_value(Group, GroupsOpts, [])}
+ || #sr_user{group_host = {Group, H}} <- Rs,
+ H == LServer];
+ _ -> []
end;
-get_user_displayed_groups(LUser, LServer, GroupsOpts, odbc) ->
+get_user_displayed_groups(LUser, LServer, GroupsOpts,
+ odbc) ->
SJID = make_jid_s(LUser, LServer),
- case catch ejabberd_odbc:sql_query(
- LServer, ["select grp from sr_user "
- "where jid='", SJID, "';"]) of
- {selected, ["grp"], Rs} ->
- [{Group, proplists:get_value(Group, GroupsOpts, [])} ||
- {Group} <- Rs];
- _ ->
- []
+ case catch ejabberd_odbc:sql_query(LServer,
+ [<<"select grp from sr_user where jid='">>,
+ SJID, <<"';">>])
+ of
+ {selected, [<<"grp">>], Rs} ->
+ [{Group, proplists:get_value(Group, GroupsOpts, [])}
+ || [Group] <- Rs];
+ _ -> []
end.
%% @doc Get the list of groups that are displayed to this user
get_user_displayed_groups(US) ->
Host = element(2, US),
- DisplayedGroups1 =
- lists:usort(
- lists:flatmap(
- fun(Group) ->
- case is_group_enabled(Host, Group) of
- true ->
- get_group_opt(Host, Group, displayed_groups, []);
- false ->
- []
- end
- end, get_user_groups(US))),
- [Group || Group <- DisplayedGroups1, is_group_enabled(Host, Group)].
+ DisplayedGroups1 = lists:usort(lists:flatmap(fun
+ (Group) ->
+ case
+ is_group_enabled(Host,
+ Group)
+ of
+ true ->
+ get_group_opt(Host,
+ Group,
+ displayed_groups,
+ []);
+ false -> []
+ end
+ end,
+ get_user_groups(US))),
+ [Group
+ || Group <- DisplayedGroups1,
+ is_group_enabled(Host, Group)].
is_user_in_group(US, Group, Host) ->
- is_user_in_group(US, Group, Host, gen_mod:db_type(Host, ?MODULE)).
+ is_user_in_group(US, Group, Host,
+ gen_mod:db_type(Host, ?MODULE)).
is_user_in_group(US, Group, Host, mnesia) ->
- case catch mnesia:dirty_match_object(
- #sr_user{us=US, group_host={Group, Host}}) of
- [] -> lists:member(US, get_group_users(Host, Group));
- _ -> true
+ case catch mnesia:dirty_match_object(#sr_user{us = US,
+ group_host = {Group, Host}})
+ of
+ [] -> lists:member(US, get_group_users(Host, Group));
+ _ -> true
end;
is_user_in_group(US, Group, Host, odbc) ->
SJID = make_jid_s(US),
SGroup = ejabberd_odbc:escape(Group),
- case catch ejabberd_odbc:sql_query(
- Host, ["select * from sr_user where "
- "jid='", SJID, "' and grp='", SGroup, "';"]) of
- {selected, _, []} ->
- lists:member(US, get_group_users(Host, Group));
- _ ->
- true
+ case catch ejabberd_odbc:sql_query(Host,
+ [<<"select * from sr_user where jid='">>,
+ SJID, <<"' and grp='">>, SGroup,
+ <<"';">>])
+ of
+ {selected, _, []} ->
+ lists:member(US, get_group_users(Host, Group));
+ _ -> true
end.
%% @spec (Host::string(), {User::string(), Server::string()}, Group::string()) -> {atomic, ok}
add_user_to_group(Host, US, Group) ->
{LUser, LServer} = US,
- case ejabberd_regexp:run(LUser, "^@.+@$") of
- match ->
- GroupOpts = ?MODULE:get_group_opts(Host, Group),
- MoreGroupOpts =
- case LUser of
- "@all@" -> [{all_users, true}];
- "@online@" -> [{online_users, true}];
- _ -> []
- end,
- ?MODULE:set_group_opts(
- Host, Group,
- GroupOpts ++ MoreGroupOpts);
- nomatch ->
- %% Push this new user to members of groups where this group is displayed
- push_user_to_displayed(LUser, LServer, Group, Host, both),
- %% Push members of groups that are displayed to this group
- push_displayed_to_user(LUser, LServer, Group, Host, both),
- add_user_to_group(Host, US, Group, gen_mod:db_type(Host, ?MODULE))
+ case ejabberd_regexp:run(LUser, <<"^@.+@$">>) of
+ match ->
+ GroupOpts = (?MODULE):get_group_opts(Host, Group),
+ MoreGroupOpts = case LUser of
+ <<"@all@">> -> [{all_users, true}];
+ <<"@online@">> -> [{online_users, true}];
+ _ -> []
+ end,
+ (?MODULE):set_group_opts(Host, Group,
+ GroupOpts ++ MoreGroupOpts);
+ nomatch ->
+ push_user_to_displayed(LUser, LServer, Group, Host,
+ both),
+ push_displayed_to_user(LUser, LServer, Group, Host,
+ both),
+ add_user_to_group(Host, US, Group,
+ gen_mod:db_type(Host, ?MODULE))
end.
add_user_to_group(Host, US, Group, mnesia) ->
R = #sr_user{us = US, group_host = {Group, Host}},
- F = fun() ->
- mnesia:write(R)
- end,
+ F = fun () -> mnesia:write(R) end,
mnesia:transaction(F);
add_user_to_group(Host, US, Group, odbc) ->
SJID = make_jid_s(US),
SGroup = ejabberd_odbc:escape(Group),
- F = fun() ->
- odbc_queries:update_t(
- "sr_user",
- ["jid", "grp"],
- [SJID, SGroup],
- ["jid='", SJID, "' and grp='", SGroup, "'"])
- end,
+ F = fun () ->
+ odbc_queries:update_t(<<"sr_user">>,
+ [<<"jid">>, <<"grp">>], [SJID, SGroup],
+ [<<"jid='">>, SJID, <<"' and grp='">>,
+ SGroup, <<"'">>])
+ end,
ejabberd_odbc:sql_transaction(Host, F).
-push_displayed_to_user(LUser, LServer, Group, Host, Subscription) ->
+push_displayed_to_user(LUser, LServer, Group, Host,
+ Subscription) ->
GroupsOpts = groups_with_opts(LServer),
GroupOpts = proplists:get_value(Group, GroupsOpts, []),
- DisplayedGroups = proplists:get_value(displayed_groups, GroupOpts, []),
- [push_members_to_user(LUser, LServer, DGroup, Host, Subscription) || DGroup <- DisplayedGroups].
+ DisplayedGroups = proplists:get_value(displayed_groups,
+ GroupOpts, []),
+ [push_members_to_user(LUser, LServer, DGroup, Host,
+ Subscription)
+ || DGroup <- DisplayedGroups].
remove_user_from_group(Host, US, Group) ->
{LUser, LServer} = US,
- case ejabberd_regexp:run(LUser, "^@.+@$") of
- match ->
- GroupOpts = ?MODULE:get_group_opts(Host, Group),
- NewGroupOpts =
- case LUser of
- "@all@" ->
- lists:filter(fun(X) -> X/={all_users,true} end, GroupOpts);
- "@online@" ->
- lists:filter(fun(X) -> X/={online_users,true} end, GroupOpts)
- end,
- ?MODULE:set_group_opts(Host, Group, NewGroupOpts);
- nomatch ->
- Result = remove_user_from_group(Host, US, Group,
- gen_mod:db_type(Host, ?MODULE)),
- %% Push removal of the old user to members of groups where the group that this user was members was displayed
- push_user_to_displayed(LUser, LServer, Group, Host, remove),
- %% Push removal of members of groups that where displayed to the group which this user has left
- push_displayed_to_user(LUser, LServer, Group, Host, remove),
- Result
+ case ejabberd_regexp:run(LUser, <<"^@.+@$">>) of
+ match ->
+ GroupOpts = (?MODULE):get_group_opts(Host, Group),
+ NewGroupOpts = case LUser of
+ <<"@all@">> ->
+ lists:filter(fun (X) -> X /= {all_users, true}
+ end,
+ GroupOpts);
+ <<"@online@">> ->
+ lists:filter(fun (X) -> X /= {online_users, true}
+ end,
+ GroupOpts)
+ end,
+ (?MODULE):set_group_opts(Host, Group, NewGroupOpts);
+ nomatch ->
+ Result = remove_user_from_group(Host, US, Group,
+ gen_mod:db_type(Host, ?MODULE)),
+ push_user_to_displayed(LUser, LServer, Group, Host,
+ remove),
+ push_displayed_to_user(LUser, LServer, Group, Host,
+ remove),
+ Result
end.
remove_user_from_group(Host, US, Group, mnesia) ->
R = #sr_user{us = US, group_host = {Group, Host}},
- F = fun() ->
- mnesia:delete_object(R)
- end,
+ F = fun () -> mnesia:delete_object(R) end,
mnesia:transaction(F);
remove_user_from_group(Host, US, Group, odbc) ->
SJID = make_jid_s(US),
SGroup = ejabberd_odbc:escape(Group),
- F = fun() ->
- ejabberd_odbc:sql_query_t(
- ["delete from sr_user where jid='",
- SJID, "' and grp='", SGroup, "';"]),
- ok
- end,
+ F = fun () ->
+ ejabberd_odbc:sql_query_t([<<"delete from sr_user where jid='">>,
+ SJID, <<"' and grp='">>, SGroup,
+ <<"';">>]),
+ ok
+ end,
ejabberd_odbc:sql_transaction(Host, F).
-push_members_to_user(LUser, LServer, Group, Host, Subscription) ->
+push_members_to_user(LUser, LServer, Group, Host,
+ Subscription) ->
GroupsOpts = groups_with_opts(LServer),
GroupOpts = proplists:get_value(Group, GroupsOpts, []),
GroupName = proplists:get_value(name, GroupOpts, Group),
Members = get_group_users(Host, Group),
- lists:foreach(
- fun({U, S}) ->
- push_roster_item(LUser, LServer, U, S, GroupName, Subscription)
- end, Members).
+ lists:foreach(fun ({U, S}) ->
+ push_roster_item(LUser, LServer, U, S, GroupName,
+ Subscription)
+ end,
+ Members).
register_user(User, Server) ->
- %% Get list of groups where this user is member
Groups = get_user_groups({User, Server}),
- %% Push this user to members of groups where is displayed a group which this user is member
- [push_user_to_displayed(User, Server, Group, Server, both) || Group <- Groups].
+ [push_user_to_displayed(User, Server, Group, Server,
+ both)
+ || Group <- Groups].
remove_user(User, Server) ->
push_user_to_members(User, Server, remove).
@@ -857,106 +845,115 @@ push_user_to_members(User, Server, Subscription) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
GroupsOpts = groups_with_opts(LServer),
- SpecialGroups = get_special_displayed_groups(GroupsOpts),
- UserGroups = get_user_displayed_groups(LUser, LServer, GroupsOpts),
- lists:foreach(
- fun(Group) ->
- remove_user_from_group(LServer, {LUser, LServer}, Group),
- GroupOpts = proplists:get_value(Group, GroupsOpts, []),
- GroupName = proplists:get_value(name, GroupOpts, Group),
- lists:foreach(
- fun({U, S}) ->
- push_roster_item(U, S, LUser, LServer, GroupName, Subscription)
- end, get_group_users(LServer, Group, GroupOpts))
- end, lists:usort(SpecialGroups++UserGroups)).
-
-push_user_to_displayed(LUser, LServer, Group, Host, Subscription) ->
+ SpecialGroups =
+ get_special_displayed_groups(GroupsOpts),
+ UserGroups = get_user_displayed_groups(LUser, LServer,
+ GroupsOpts),
+ lists:foreach(fun (Group) ->
+ remove_user_from_group(LServer, {LUser, LServer},
+ Group),
+ GroupOpts = proplists:get_value(Group, GroupsOpts,
+ []),
+ GroupName = proplists:get_value(name, GroupOpts,
+ Group),
+ lists:foreach(fun ({U, S}) ->
+ push_roster_item(U, S, LUser,
+ LServer,
+ GroupName,
+ Subscription)
+ end,
+ get_group_users(LServer, Group,
+ GroupOpts))
+ end,
+ lists:usort(SpecialGroups ++ UserGroups)).
+
+push_user_to_displayed(LUser, LServer, Group, Host,
+ Subscription) ->
GroupsOpts = groups_with_opts(Host),
GroupOpts = proplists:get_value(Group, GroupsOpts, []),
GroupName = proplists:get_value(name, GroupOpts, Group),
- DisplayedToGroupsOpts = displayed_to_groups(Group, Host),
- [push_user_to_group(LUser, LServer, GroupD, Host, GroupName, Subscription) || {GroupD, _Opts} <- DisplayedToGroupsOpts].
-
-push_user_to_group(LUser, LServer, Group, Host, GroupName, Subscription) ->
- lists:foreach(
- fun({U, S}) when (U == LUser) and (S == LServer) -> ok;
- ({U, S}) ->
- push_roster_item(U, S, LUser, LServer, GroupName, Subscription)
- end, get_group_users(Host, Group)).
+ DisplayedToGroupsOpts = displayed_to_groups(Group,
+ Host),
+ [push_user_to_group(LUser, LServer, GroupD, Host,
+ GroupName, Subscription)
+ || {GroupD, _Opts} <- DisplayedToGroupsOpts].
+
+push_user_to_group(LUser, LServer, Group, Host,
+ GroupName, Subscription) ->
+ lists:foreach(fun ({U, S})
+ when (U == LUser) and (S == LServer) ->
+ ok;
+ ({U, S}) ->
+ push_roster_item(U, S, LUser, LServer, GroupName,
+ Subscription)
+ end,
+ get_group_users(Host, Group)).
%% Get list of groups to which this group is displayed
displayed_to_groups(GroupName, LServer) ->
GroupsOpts = groups_with_opts(LServer),
- lists:filter(
- fun({_Group, Opts}) ->
- lists:member(GroupName, proplists:get_value(displayed_groups, Opts, []))
- end, GroupsOpts).
+ lists:filter(fun ({_Group, Opts}) ->
+ lists:member(GroupName,
+ proplists:get_value(displayed_groups,
+ Opts, []))
+ end,
+ GroupsOpts).
push_item(User, Server, From, Item) ->
- %% It was
- %% ejabberd_sm:route(jlib:make_jid("", "", ""),
- %% jlib:make_jid(User, Server, "")
- %% why?
- ejabberd_sm:route(From, jlib:make_jid(User, Server, ""),
- {xmlelement, "broadcast", [],
- [{item,
- Item#roster.jid,
- Item#roster.subscription}]}),
- Stanza = jlib:iq_to_xml(
- #iq{type = set, xmlns = ?NS_ROSTER,
- id = "push" ++ randoms:get_string(),
- sub_el = [{xmlelement, "query",
- [{"xmlns", ?NS_ROSTER}],
- [item_to_xml(Item)]}]}),
- lists:foreach(
- fun(Resource) ->
- JID = jlib:make_jid(User, Server, Resource),
- ejabberd_router:route(JID, JID, Stanza)
- end, ejabberd_sm:get_user_resources(User, Server)).
-
-push_roster_item(User, Server, ContactU, ContactS, GroupName, Subscription) ->
- Item = #roster{usj = {User, Server, {ContactU, ContactS, ""}},
- us = {User, Server},
- jid = {ContactU, ContactS, ""},
- name = "",
- subscription = Subscription,
- ask = none,
+ ejabberd_sm:route(From,
+ jlib:make_jid(User, Server, <<"">>),
+ {broadcast, {item, Item#roster.jid,
+ Item#roster.subscription}}),
+ Stanza = jlib:iq_to_xml(#iq{type = set,
+ xmlns = ?NS_ROSTER,
+ id = <<"push", (randoms:get_string())/binary>>,
+ sub_el =
+ [#xmlel{name = <<"query">>,
+ attrs = [{<<"xmlns">>, ?NS_ROSTER}],
+ children = [item_to_xml(Item)]}]}),
+ lists:foreach(fun (Resource) ->
+ JID = jlib:make_jid(User, Server, Resource),
+ ejabberd_router:route(JID, JID, Stanza)
+ end,
+ ejabberd_sm:get_user_resources(User, Server)).
+
+push_roster_item(User, Server, ContactU, ContactS,
+ GroupName, Subscription) ->
+ Item = #roster{usj =
+ {User, Server, {ContactU, ContactS, <<"">>}},
+ us = {User, Server}, jid = {ContactU, ContactS, <<"">>},
+ name = <<"">>, subscription = Subscription, ask = none,
groups = [GroupName]},
- push_item(User, Server, jlib:make_jid("", Server, ""), Item).
+ push_item(User, Server,
+ jlib:make_jid(<<"">>, Server, <<"">>), Item).
item_to_xml(Item) ->
- Attrs1 = [{"jid", jlib:jid_to_string(Item#roster.jid)}],
+ Attrs1 = [{<<"jid">>,
+ jlib:jid_to_string(Item#roster.jid)}],
Attrs2 = case Item#roster.name of
- "" ->
- Attrs1;
- Name ->
- [{"name", Name} | Attrs1]
+ <<"">> -> Attrs1;
+ Name -> [{<<"name">>, Name} | Attrs1]
end,
Attrs3 = case Item#roster.subscription of
- none ->
- [{"subscription", "none"} | Attrs2];
- from ->
- [{"subscription", "from"} | Attrs2];
- to ->
- [{"subscription", "to"} | Attrs2];
- both ->
- [{"subscription", "both"} | Attrs2];
- remove ->
- [{"subscription", "remove"} | Attrs2]
+ none -> [{<<"subscription">>, <<"none">>} | Attrs2];
+ from -> [{<<"subscription">>, <<"from">>} | Attrs2];
+ to -> [{<<"subscription">>, <<"to">>} | Attrs2];
+ both -> [{<<"subscription">>, <<"both">>} | Attrs2];
+ remove -> [{<<"subscription">>, <<"remove">>} | Attrs2]
end,
Attrs4 = case ask_to_pending(Item#roster.ask) of
- out ->
- [{"ask", "subscribe"} | Attrs3];
- both ->
- [{"ask", "subscribe"} | Attrs3];
- _ ->
- Attrs3
+ out -> [{<<"ask">>, <<"subscribe">>} | Attrs3];
+ both -> [{<<"ask">>, <<"subscribe">>} | Attrs3];
+ _ -> Attrs3
end,
- SubEls1 = lists:map(fun(G) ->
- {xmlelement, "group", [], [{xmlcdata, G}]}
- end, Item#roster.groups),
+ SubEls1 = lists:map(fun (G) ->
+ #xmlel{name = <<"group">>, attrs = [],
+ children = [{xmlcdata, G}]}
+ end,
+ Item#roster.groups),
SubEls = SubEls1 ++ Item#roster.xs,
- {xmlelement, "item", Attrs4, SubEls}.
+ #xmlel{name = <<"item">>, attrs = Attrs4,
+ children = SubEls}.
ask_to_pending(subscribe) -> out;
ask_to_pending(unsubscribe) -> none;
@@ -965,47 +962,41 @@ ask_to_pending(Ask) -> Ask.
user_available(New) ->
LUser = New#jid.luser,
LServer = New#jid.lserver,
- Resources = ejabberd_sm:get_user_resources(LUser, LServer),
+ Resources = ejabberd_sm:get_user_resources(LUser,
+ LServer),
?DEBUG("user_available for ~p @ ~p (~p resources)",
[LUser, LServer, length(Resources)]),
case length(Resources) of
- %% first session for this user
- 1 ->
- %% This is a simplification - we ignore he 'display'
- %% property - @online@ is always reflective.
- OnlineGroups = get_special_users_groups_online(LServer),
- lists:foreach(
- fun(OG) ->
- ?DEBUG("user_available: pushing ~p @ ~p grp ~p",
- [LUser, LServer, OG ]),
- push_user_to_displayed(LUser, LServer, OG, LServer, both)
- end, OnlineGroups);
- _ ->
- ok
+ %% first session for this user
+ 1 ->
+ OnlineGroups = get_special_users_groups_online(LServer),
+ lists:foreach(fun (OG) ->
+ ?DEBUG("user_available: pushing ~p @ ~p grp ~p",
+ [LUser, LServer, OG]),
+ push_user_to_displayed(LUser, LServer, OG,
+ LServer, both)
+ end,
+ OnlineGroups);
+ _ -> ok
end.
unset_presence(LUser, LServer, Resource, Status) ->
- Resources = ejabberd_sm:get_user_resources(LUser, LServer),
- ?DEBUG("unset_presence for ~p @ ~p / ~p -> ~p (~p resources)",
+ Resources = ejabberd_sm:get_user_resources(LUser,
+ LServer),
+ ?DEBUG("unset_presence for ~p @ ~p / ~p -> ~p "
+ "(~p resources)",
[LUser, LServer, Resource, Status, length(Resources)]),
- %% if user has no resources left...
case length(Resources) of
- 0 ->
- %% This is a simplification - we ignore he 'display'
- %% property - @online@ is always reflective.
- OnlineGroups = get_special_users_groups_online(LServer),
- %% for each of these groups...
- lists:foreach(
- fun(OG) ->
- %% Push removal of the old user to members of groups
- %% where the group that this uwas members was displayed
- push_user_to_displayed(LUser, LServer, OG, LServer, remove),
- %% Push removal of members of groups that where
- %% displayed to the group which thiuser has left
- push_displayed_to_user(LUser, LServer, OG, LServer,remove)
- end, OnlineGroups);
- _ ->
- ok
+ 0 ->
+ OnlineGroups = get_special_users_groups_online(LServer),
+ lists:foreach(fun (OG) ->
+ push_user_to_displayed(LUser, LServer, OG,
+ LServer, remove),
+ push_displayed_to_user(LUser, LServer, OG,
+ LServer, remove)
+ end,
+ OnlineGroups);
+ _ -> ok
end.
%%---------------------
@@ -1013,275 +1004,332 @@ unset_presence(LUser, LServer, Resource, Status) ->
%%---------------------
webadmin_menu(Acc, _Host, Lang) ->
- [{"shared-roster", ?T("Shared Roster Groups")} | Acc].
+ [{<<"shared-roster">>, ?T(<<"Shared Roster Groups">>)}
+ | Acc].
webadmin_page(_, Host,
- #request{us = _US,
- path = ["shared-roster"],
- q = Query,
- lang = Lang} = _Request) ->
+ #request{us = _US, path = [<<"shared-roster">>],
+ q = Query, lang = Lang} =
+ _Request) ->
Res = list_shared_roster_groups(Host, Query, Lang),
{stop, Res};
-
webadmin_page(_, Host,
- #request{us = _US,
- path = ["shared-roster", Group],
- q = Query,
- lang = Lang} = _Request) ->
+ #request{us = _US, path = [<<"shared-roster">>, Group],
+ q = Query, lang = Lang} =
+ _Request) ->
Res = shared_roster_group(Host, Group, Query, Lang),
{stop, Res};
-
webadmin_page(Acc, _, _) -> Acc.
list_shared_roster_groups(Host, Query, Lang) ->
Res = list_sr_groups_parse_query(Host, Query),
- SRGroups = ?MODULE:list_groups(Host),
- FGroups =
- ?XAE("table", [],
- [?XE("tbody",
- lists:map(
- fun(Group) ->
- ?XE("tr",
- [?XE("td", [?INPUT("checkbox", "selected",
- Group)]),
- ?XE("td", [?AC(Group ++ "/", Group)])
- ]
- )
- end, lists:sort(SRGroups)) ++
- [?XE("tr",
- [?X("td"),
- ?XE("td", [?INPUT("text", "namenew", "")]),
- ?XE("td", [?INPUTT("submit", "addnew", "Add New")])
- ]
- )]
- )]),
- ?H1GL(?T("Shared Roster Groups"), "modsharedroster", "mod_shared_roster") ++
- case Res of
- ok -> [?XREST("Submitted")];
- error -> [?XREST("Bad format")];
- nothing -> []
- end ++
- [?XAE("form", [{"action", ""}, {"method", "post"}],
- [FGroups,
- ?BR,
- ?INPUTT("submit", "delete", "Delete Selected")
- ])
- ].
+ SRGroups = (?MODULE):list_groups(Host),
+ FGroups = (?XAE(<<"table">>, [],
+ [?XE(<<"tbody">>,
+ (lists:map(fun (Group) ->
+ ?XE(<<"tr">>,
+ [?XE(<<"td">>,
+ [?INPUT(<<"checkbox">>,
+ <<"selected">>,
+ Group)]),
+ ?XE(<<"td">>,
+ [?AC(<<Group/binary, "/">>,
+ Group)])])
+ end,
+ lists:sort(SRGroups))
+ ++
+ [?XE(<<"tr">>,
+ [?X(<<"td">>),
+ ?XE(<<"td">>,
+ [?INPUT(<<"text">>, <<"namenew">>,
+ <<"">>)]),
+ ?XE(<<"td">>,
+ [?INPUTT(<<"submit">>, <<"addnew">>,
+ <<"Add New">>)])])]))])),
+ (?H1GL((?T(<<"Shared Roster Groups">>)),
+ <<"modsharedroster">>, <<"mod_shared_roster">>))
+ ++
+ case Res of
+ ok -> [?XREST(<<"Submitted">>)];
+ error -> [?XREST(<<"Bad format">>)];
+ nothing -> []
+ end
+ ++
+ [?XAE(<<"form">>,
+ [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
+ [FGroups, ?BR,
+ ?INPUTT(<<"submit">>, <<"delete">>,
+ <<"Delete Selected">>)])].
list_sr_groups_parse_query(Host, Query) ->
- case lists:keysearch("addnew", 1, Query) of
- {value, _} ->
- list_sr_groups_parse_addnew(Host, Query);
- _ ->
- case lists:keysearch("delete", 1, Query) of
- {value, _} ->
- list_sr_groups_parse_delete(Host, Query);
- _ ->
- nothing
- end
+ case lists:keysearch(<<"addnew">>, 1, Query) of
+ {value, _} -> list_sr_groups_parse_addnew(Host, Query);
+ _ ->
+ case lists:keysearch(<<"delete">>, 1, Query) of
+ {value, _} -> list_sr_groups_parse_delete(Host, Query);
+ _ -> nothing
+ end
end.
list_sr_groups_parse_addnew(Host, Query) ->
- case lists:keysearch("namenew", 1, Query) of
- {value, {_, Group}} when Group /= "" ->
- ?MODULE:create_group(Host, Group),
- ok;
- _ ->
- error
+ case lists:keysearch(<<"namenew">>, 1, Query) of
+ {value, {_, Group}} when Group /= <<"">> ->
+ (?MODULE):create_group(Host, Group), ok;
+ _ -> error
end.
list_sr_groups_parse_delete(Host, Query) ->
- SRGroups = ?MODULE:list_groups(Host),
- lists:foreach(
- fun(Group) ->
- case lists:member({"selected", Group}, Query) of
- true ->
- ?MODULE:delete_group(Host, Group);
- _ ->
- ok
- end
- end, SRGroups),
+ SRGroups = (?MODULE):list_groups(Host),
+ lists:foreach(fun (Group) ->
+ case lists:member({<<"selected">>, Group}, Query) of
+ true -> (?MODULE):delete_group(Host, Group);
+ _ -> ok
+ end
+ end,
+ SRGroups),
ok.
-
shared_roster_group(Host, Group, Query, Lang) ->
- Res = shared_roster_group_parse_query(Host, Group, Query),
- GroupOpts = ?MODULE:get_group_opts(Host, Group),
- Name = get_opt(GroupOpts, name, ""),
- Description = get_opt(GroupOpts, description, ""),
+ Res = shared_roster_group_parse_query(Host, Group,
+ Query),
+ GroupOpts = (?MODULE):get_group_opts(Host, Group),
+ Name = get_opt(GroupOpts, name, <<"">>),
+ Description = get_opt(GroupOpts, description, <<"">>),
AllUsers = get_opt(GroupOpts, all_users, false),
OnlineUsers = get_opt(GroupOpts, online_users, false),
- %%Disabled = false,
- DisplayedGroups = get_opt(GroupOpts, displayed_groups, []),
- Members = ?MODULE:get_group_explicit_users(Host, Group),
- FMembers =
- if
- AllUsers ->
- "@all@\n";
- true ->
- []
- end ++
- if
- OnlineUsers ->
- "@online@\n";
- true ->
- []
- end ++
- [[us_to_list(Member), $\n] || Member <- Members],
- FDisplayedGroups = [[DG, $\n] || DG <- DisplayedGroups],
- DescNL = length(ejabberd_regexp:split(Description, "\n")),
- FGroup =
- ?XAE("table", [{"class", "withtextareas"}],
- [?XE("tbody",
- [?XE("tr",
- [?XCT("td", "Name:"),
- ?XE("td", [?INPUT("text", "name", Name)])
- ]
- ),
- ?XE("tr",
- [?XCT("td", "Description:"),
- ?XE("td", [
- ?TEXTAREA("description", integer_to_list(lists:max([3, DescNL])), "20", Description)
- ]
- )
- ]
- ),
- ?XE("tr",
- [?XCT("td", "Members:"),
- ?XE("td", [
- ?TEXTAREA("members", integer_to_list(lists:max([3, length(FMembers)])), "20", FMembers)
- ]
- )
- ]
- ),
- ?XE("tr",
- [?XCT("td", "Displayed Groups:"),
- ?XE("td", [
- ?TEXTAREA("dispgroups", integer_to_list(lists:max([3, length(FDisplayedGroups)])), "20", FDisplayedGroups)
- ]
- )
- ]
- )]
- )]),
- ?H1GL(?T("Shared Roster Groups"), "modsharedroster", "mod_shared_roster") ++
- [?XC("h2", ?T("Group ") ++ Group)] ++
+ DisplayedGroups = get_opt(GroupOpts, displayed_groups,
+ []),
+ Members = (?MODULE):get_group_explicit_users(Host,
+ Group),
+ FMembers = iolist_to_binary(
+ [if AllUsers -> <<"@all@\n">>;
+ true -> <<"">>
+ end,
+ if OnlineUsers -> <<"@online@\n">>;
+ true -> <<"">>
+ end,
+ [[us_to_list(Member), $\n] || Member <- Members]]),
+ FDisplayedGroups = [<<DG/binary, $\n>> || DG <- DisplayedGroups],
+ DescNL = length(ejabberd_regexp:split(Description,
+ <<"\n">>)),
+ FGroup = (?XAE(<<"table">>,
+ [{<<"class">>, <<"withtextareas">>}],
+ [?XE(<<"tbody">>,
+ [?XE(<<"tr">>,
+ [?XCT(<<"td">>, <<"Name:">>),
+ ?XE(<<"td">>,
+ [?INPUT(<<"text">>, <<"name">>, Name)])]),
+ ?XE(<<"tr">>,
+ [?XCT(<<"td">>, <<"Description:">>),
+ ?XE(<<"td">>,
+ [?TEXTAREA(<<"description">>,
+ jlib:integer_to_binary(lists:max([3,
+ DescNL])),
+ <<"20">>, Description)])]),
+ ?XE(<<"tr">>,
+ [?XCT(<<"td">>, <<"Members:">>),
+ ?XE(<<"td">>,
+ [?TEXTAREA(<<"members">>,
+ jlib:integer_to_binary(lists:max([3,
+ byte_size(FMembers)])),
+ <<"20">>, FMembers)])]),
+ ?XE(<<"tr">>,
+ [?XCT(<<"td">>, <<"Displayed Groups:">>),
+ ?XE(<<"td">>,
+ [?TEXTAREA(<<"dispgroups">>,
+ jlib:integer_to_binary(lists:max([3, length(FDisplayedGroups)])),
+ <<"20">>,
+ list_to_binary(FDisplayedGroups))])])])])),
+ (?H1GL((?T(<<"Shared Roster Groups">>)),
+ <<"modsharedroster">>, <<"mod_shared_roster">>))
+ ++
+ [?XC(<<"h2">>, <<(?T(<<"Group ">>))/binary, Group/binary>>)] ++
case Res of
- ok -> [?XREST("Submitted")];
- error -> [?XREST("Bad format")];
- nothing -> []
- end ++
- [?XAE("form", [{"action", ""}, {"method", "post"}],
- [FGroup,
- ?BR,
- ?INPUTT("submit", "submit", "Submit")
- ])
- ].
+ ok -> [?XREST(<<"Submitted">>)];
+ error -> [?XREST(<<"Bad format">>)];
+ nothing -> []
+ end
+ ++
+ [?XAE(<<"form">>,
+ [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
+ [FGroup, ?BR,
+ ?INPUTT(<<"submit">>, <<"submit">>, <<"Submit">>)])].
shared_roster_group_parse_query(Host, Group, Query) ->
- case lists:keysearch("submit", 1, Query) of
- {value, _} ->
- {value, {_, Name}} = lists:keysearch("name", 1, Query),
- {value, {_, Description}} = lists:keysearch("description", 1, Query),
- {value, {_, SMembers}} = lists:keysearch("members", 1, Query),
- {value, {_, SDispGroups}} = lists:keysearch("dispgroups", 1, Query),
- NameOpt =
- if
- Name == "" -> [];
- true -> [{name, Name}]
- end,
- DescriptionOpt =
- if
- Description == "" -> [];
- true -> [{description, Description}]
- end,
- DispGroups = string:tokens(SDispGroups, "\r\n"),
- DispGroupsOpt =
- if
- DispGroups == [] -> [];
- true -> [{displayed_groups, DispGroups}]
- end,
-
- OldMembers = ?MODULE:get_group_explicit_users(
- Host, Group),
- SJIDs = string:tokens(SMembers, ", \r\n"),
- NewMembers =
- lists:foldl(
- fun(_SJID, error) -> error;
- (SJID, USs) ->
- case SJID of
- "@all@" ->
- USs;
- "@online@" ->
- USs;
- _ ->
- case jlib:string_to_jid(SJID) of
- JID when is_record(JID, jid) ->
- [{JID#jid.luser, JID#jid.lserver} | USs];
- error ->
- error
- end
- end
- end, [], SJIDs),
- AllUsersOpt =
- case lists:member("@all@", SJIDs) of
- true -> [{all_users, true}];
- false -> []
- end,
- OnlineUsersOpt =
- case lists:member("@online@", SJIDs) of
- true -> [{online_users, true}];
- false -> []
- end,
-
- ?MODULE:set_group_opts(
- Host, Group,
- NameOpt ++ DispGroupsOpt ++ DescriptionOpt ++ AllUsersOpt ++ OnlineUsersOpt),
-
- if
- NewMembers == error -> error;
- true ->
- AddedMembers = NewMembers -- OldMembers,
- RemovedMembers = OldMembers -- NewMembers,
- lists:foreach(
- fun(US) ->
- ?MODULE:remove_user_from_group(
- Host, US, Group)
- end, RemovedMembers),
- lists:foreach(
- fun(US) ->
- ?MODULE:add_user_to_group(
- Host, US, Group)
- end, AddedMembers),
- ok
- end;
- _ ->
- nothing
+ case lists:keysearch(<<"submit">>, 1, Query) of
+ {value, _} ->
+ {value, {_, Name}} = lists:keysearch(<<"name">>, 1,
+ Query),
+ {value, {_, Description}} =
+ lists:keysearch(<<"description">>, 1, Query),
+ {value, {_, SMembers}} = lists:keysearch(<<"members">>,
+ 1, Query),
+ {value, {_, SDispGroups}} =
+ lists:keysearch(<<"dispgroups">>, 1, Query),
+ NameOpt = if Name == <<"">> -> [];
+ true -> [{name, Name}]
+ end,
+ DescriptionOpt = if Description == <<"">> -> [];
+ true -> [{description, Description}]
+ end,
+ DispGroups = str:tokens(SDispGroups, <<"\r\n">>),
+ DispGroupsOpt = if DispGroups == [] -> [];
+ true -> [{displayed_groups, DispGroups}]
+ end,
+ OldMembers = (?MODULE):get_group_explicit_users(Host,
+ Group),
+ SJIDs = str:tokens(SMembers, <<", \r\n">>),
+ NewMembers = lists:foldl(fun (_SJID, error) -> error;
+ (SJID, USs) ->
+ case SJID of
+ <<"@all@">> -> USs;
+ <<"@online@">> -> USs;
+ _ ->
+ case jlib:string_to_jid(SJID)
+ of
+ JID
+ when is_record(JID,
+ jid) ->
+ [{JID#jid.luser,
+ JID#jid.lserver}
+ | USs];
+ error -> error
+ end
+ end
+ end,
+ [], SJIDs),
+ AllUsersOpt = case lists:member(<<"@all@">>, SJIDs) of
+ true -> [{all_users, true}];
+ false -> []
+ end,
+ OnlineUsersOpt = case lists:member(<<"@online@">>,
+ SJIDs)
+ of
+ true -> [{online_users, true}];
+ false -> []
+ end,
+ (?MODULE):set_group_opts(Host, Group,
+ NameOpt ++
+ DispGroupsOpt ++
+ DescriptionOpt ++
+ AllUsersOpt ++ OnlineUsersOpt),
+ if NewMembers == error -> error;
+ true ->
+ AddedMembers = NewMembers -- OldMembers,
+ RemovedMembers = OldMembers -- NewMembers,
+ lists:foreach(fun (US) ->
+ (?MODULE):remove_user_from_group(Host,
+ US,
+ Group)
+ end,
+ RemovedMembers),
+ lists:foreach(fun (US) ->
+ (?MODULE):add_user_to_group(Host, US,
+ Group)
+ end,
+ AddedMembers),
+ ok
+ end;
+ _ -> nothing
end.
get_opt(Opts, Opt, Default) ->
case lists:keysearch(Opt, 1, Opts) of
- {value, {_, Val}} ->
- Val;
- false ->
- Default
+ {value, {_, Val}} -> Val;
+ false -> Default
end.
us_to_list({User, Server}) ->
- jlib:jid_to_string({User, Server, ""}).
+ jlib:jid_to_string({User, Server, <<"">>}).
split_grouphost(Host, Group) ->
- case string:tokens(Group, "@") of
- [GroupName, HostName] ->
- {HostName, GroupName};
- [_] ->
- {Host, Group}
+ case str:tokens(Group, <<"@">>) of
+ [GroupName, HostName] -> {HostName, GroupName};
+ [_] -> {Host, Group}
end.
make_jid_s(U, S) ->
- ejabberd_odbc:escape(
- jlib:jid_to_string(
- jlib:jid_tolower(
- jlib:make_jid(U, S, "")))).
+ ejabberd_odbc:escape(jlib:jid_to_string(jlib:jid_tolower(jlib:make_jid(U,
+ S,
+ <<"">>)))).
+
+make_jid_s({U, S}) -> make_jid_s(U, S).
+
+opts_to_binary(Opts) ->
+ lists:map(
+ fun({name, Name}) ->
+ {name, iolist_to_binary(Name)};
+ ({description, Desc}) ->
+ {description, iolist_to_binary(Desc)};
+ ({displayed_groups, Gs}) ->
+ {displayed_groups, [iolist_to_binary(G) || G <- Gs]};
+ (Opt) ->
+ Opt
+ end, Opts).
+
+update_tables() ->
+ update_sr_group_table(),
+ update_sr_user_table().
+
+update_sr_group_table() ->
+ Fields = record_info(fields, sr_group),
+ case mnesia:table_info(sr_group, attributes) of
+ Fields ->
+ ejabberd_config:convert_table_to_binary(
+ sr_group, Fields, set,
+ fun(#sr_group{group_host = {G, _}}) -> G end,
+ fun(#sr_group{group_host = {G, H},
+ opts = Opts} = R) ->
+ R#sr_group{group_host = {iolist_to_binary(G),
+ iolist_to_binary(H)},
+ opts = opts_to_binary(Opts)}
+ end);
+ _ ->
+ ?INFO_MSG("Recreating sr_group table", []),
+ mnesia:transform_table(sr_group, ignore, Fields)
+ end.
+
+update_sr_user_table() ->
+ Fields = record_info(fields, sr_user),
+ case mnesia:table_info(sr_user, attributes) of
+ Fields ->
+ ejabberd_config:convert_table_to_binary(
+ sr_user, Fields, bag,
+ fun(#sr_user{us = {U, _}}) -> U end,
+ fun(#sr_user{us = {U, S}, group_host = {G, H}} = R) ->
+ R#sr_user{us = {iolist_to_binary(U), iolist_to_binary(S)},
+ group_host = {iolist_to_binary(G),
+ iolist_to_binary(H)}}
+ end);
+ _ ->
+ ?INFO_MSG("Recreating sr_user table", []),
+ mnesia:transform_table(sr_user, ignore, Fields)
+ end.
-make_jid_s({U, S}) ->
- make_jid_s(U, S).
+export(_Server) ->
+ [{sr_group,
+ fun(Host, #sr_group{group_host = {Group, LServer}, opts = Opts})
+ when LServer == Host ->
+ SGroup = ejabberd_odbc:escape(Group),
+ SOpts = ejabberd_odbc:encode_term(Opts),
+ [[<<"delete from sr_group where name='">>, Group, <<"';">>],
+ [<<"insert into sr_group(name, opts) values ('">>,
+ SGroup, <<"', '">>, SOpts, <<"');">>]];
+ (_Host, _R) ->
+ []
+ end},
+ {sr_user,
+ fun(Host, #sr_user{us = {U, S}, group_host = {Group, LServer}})
+ when LServer == Host ->
+ SGroup = ejabberd_odbc:escape(Group),
+ SJID = ejabberd_odbc:escape(
+ jlib:jid_to_string(
+ jlib:jid_tolower(
+ jlib:make_jid(U, S, <<"">>)))),
+ [[<<"delete from sr_user where jid='">>, SJID,
+ <<"'and grp='">>, Group, <<"';">>],
+ [<<"insert into sr_user(jid, grp) values ('">>,
+ SJID, <<"', '">>, SGroup, <<"');">>]];
+ (_Host, _R) ->
+ []
+ end}].
diff --git a/src/mod_shared_roster_ldap.erl b/src/mod_shared_roster_ldap.erl
index 5716748f3..3ddba08bc 100644
--- a/src/mod_shared_roster_ldap.erl
+++ b/src/mod_shared_roster_ldap.erl
@@ -28,57 +28,62 @@
-module(mod_shared_roster_ldap).
-behaviour(gen_server).
+
-behaviour(gen_mod).
%% API
-export([start_link/2, start/2, stop/1]).
%% gen_server callbacks
--export([init/1, handle_call/3, handle_cast/2, handle_info/2,
- terminate/2, code_change/3]).
-
--export([get_user_roster/2,
- get_subscription_lists/3,
- get_jid_info/4,
- process_item/2,
- in_subscription/6,
+-export([init/1, handle_call/3, handle_cast/2,
+ handle_info/2, terminate/2, code_change/3]).
+
+-export([get_user_roster/2, get_subscription_lists/3,
+ get_jid_info/4, process_item/2, in_subscription/6,
out_subscription/4]).
-include("ejabberd.hrl").
+-include("jlib.hrl").
-include("mod_roster.hrl").
+
-include("eldap/eldap.hrl").
-define(CACHE_SIZE, 1000).
--define(USER_CACHE_VALIDITY, 300). %% in seconds
--define(GROUP_CACHE_VALIDITY, 300). %% in seconds
--define(LDAP_SEARCH_TIMEOUT, 5). %% Timeout for LDAP search queries in seconds
-
--record(state, {host,
- eldap_id,
- servers,
- backups,
- port,
- tls_options,
- dn,
- base,
- password,
- uid,
- deref_aliases,
- group_attr,
- group_desc,
- user_desc,
- user_uid,
- uid_format,
- uid_format_re,
- filter,
- ufilter,
- rfilter,
- gfilter,
- auth_check,
- user_cache_size,
- group_cache_size,
- user_cache_validity,
- group_cache_validity}).
+
+-define(USER_CACHE_VALIDITY, 300).
+
+-define(GROUP_CACHE_VALIDITY, 300).
+
+-define(LDAP_SEARCH_TIMEOUT, 5).
+
+-record(state,
+ {host = <<"">> :: binary(),
+ eldap_id = <<"">> :: binary(),
+ servers = [] :: [binary()],
+ backups = [] :: [binary()],
+ port = ?LDAP_PORT :: inet:port_number(),
+ tls_options = [] :: list(),
+ dn = <<"">> :: binary(),
+ base = <<"">> :: binary(),
+ password = <<"">> :: binary(),
+ uid = <<"">> :: binary(),
+ deref_aliases = never :: never | searching |
+ finding | always,
+ group_attr = <<"">> :: binary(),
+ group_desc = <<"">> :: binary(),
+ user_desc = <<"">> :: binary(),
+ user_uid = <<"">> :: binary(),
+ uid_format = <<"">> :: binary(),
+ uid_format_re = <<"">> :: binary(),
+ filter = <<"">> :: binary(),
+ ufilter = <<"">> :: binary(),
+ rfilter = <<"">> :: binary(),
+ gfilter = <<"">> :: binary(),
+ auth_check = true :: boolean(),
+ user_cache_size = ?CACHE_SIZE :: non_neg_integer(),
+ group_cache_size = ?CACHE_SIZE :: non_neg_integer(),
+ user_cache_validity = ?USER_CACHE_VALIDITY :: non_neg_integer(),
+ group_cache_validity = ?GROUP_CACHE_VALIDITY :: non_neg_integer()}).
-record(group_info, {desc, members}).
@@ -87,14 +92,13 @@
%%====================================================================
start_link(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?MODULE),
- gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
+ gen_server:start_link({local, Proc}, ?MODULE,
+ [Host, Opts], []).
start(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?MODULE),
- ChildSpec = {
- Proc, {?MODULE, start_link, [Host, Opts]},
- permanent, 1000, worker, [?MODULE]
- },
+ ChildSpec = {Proc, {?MODULE, start_link, [Host, Opts]},
+ permanent, 1000, worker, [?MODULE]},
supervisor:start_child(ejabberd_sup, ChildSpec).
stop(Host) ->
@@ -107,51 +111,49 @@ stop(Host) ->
%%--------------------------------------------------------------------
get_user_roster(Items, {U, S} = US) ->
SRUsers = get_user_to_groups_map(US, true),
- %% If partially subscribed users are also in shared roster,
- %% show them as totally subscribed:
- {NewItems1, SRUsersRest} =
- lists:mapfoldl(
- fun(Item, SRUsers1) ->
- {_, _, {U1, S1, _}} = Item#roster.usj,
- US1 = {U1, S1},
- case dict:find(US1, SRUsers1) of
- {ok, _GroupNames} ->
- {Item#roster{subscription = both, ask = none},
- dict:erase(US1, SRUsers1)};
- error ->
- {Item, SRUsers1}
- end
- end, SRUsers, Items),
- %% Export items in roster format:
- SRItems = [#roster{usj = {U, S, {U1, S1, ""}},
- us = US,
- jid = {U1, S1, ""},
- name = get_user_name(U1,S1),
- subscription = both,
- ask = none,
- groups = GroupNames} ||
- {{U1, S1}, GroupNames} <- dict:to_list(SRUsersRest)],
+ {NewItems1, SRUsersRest} = lists:mapfoldl(fun (Item,
+ SRUsers1) ->
+ {_, _, {U1, S1, _}} =
+ Item#roster.usj,
+ US1 = {U1, S1},
+ case dict:find(US1,
+ SRUsers1)
+ of
+ {ok, _GroupNames} ->
+ {Item#roster{subscription
+ =
+ both,
+ ask =
+ none},
+ dict:erase(US1,
+ SRUsers1)};
+ error ->
+ {Item, SRUsers1}
+ end
+ end,
+ SRUsers, Items),
+ SRItems = [#roster{usj = {U, S, {U1, S1, <<"">>}},
+ us = US, jid = {U1, S1, <<"">>},
+ name = get_user_name(U1, S1), subscription = both,
+ ask = none, groups = GroupNames}
+ || {{U1, S1}, GroupNames} <- dict:to_list(SRUsersRest)],
SRItems ++ NewItems1.
%% This function in use to rewrite the roster entries when moving or renaming
%% them in the user contact list.
process_item(RosterItem, _Host) ->
USFrom = RosterItem#roster.us,
- {User,Server,_Resource} = RosterItem#roster.jid,
- USTo = {User,Server},
+ {User, Server, _Resource} = RosterItem#roster.jid,
+ USTo = {User, Server},
Map = get_user_to_groups_map(USFrom, false),
case dict:find(USTo, Map) of
- error ->
- RosterItem;
- {ok, []} ->
- RosterItem;
- {ok, GroupNames} when RosterItem#roster.subscription == remove ->
- %% Roster item cannot be removed:
- %% We simply reset the original groups:
- RosterItem#roster{subscription = both, ask = none,
- groups=GroupNames};
- _ ->
- RosterItem#roster{subscription = both, ask = none}
+ error -> RosterItem;
+ {ok, []} -> RosterItem;
+ {ok, GroupNames}
+ when RosterItem#roster.subscription == remove ->
+ RosterItem#roster{subscription = both, ask = none,
+ groups = GroupNames};
+ _ -> RosterItem#roster{subscription = both, ask = none}
end.
get_subscription_lists({F, T}, User, Server) ->
@@ -159,16 +161,15 @@ get_subscription_lists({F, T}, User, Server) ->
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
DisplayedGroups = get_user_displayed_groups(US),
- SRUsers =
- lists:usort(
- lists:flatmap(
- fun(Group) ->
- get_group_users(LServer, Group)
- end, DisplayedGroups)),
- SRJIDs = [{U1, S1, ""} || {U1, S1} <- SRUsers],
+ SRUsers = lists:usort(lists:flatmap(fun (Group) ->
+ get_group_users(LServer, Group)
+ end,
+ DisplayedGroups)),
+ SRJIDs = [{U1, S1, <<"">>} || {U1, S1} <- SRUsers],
{lists:usort(SRJIDs ++ F), lists:usort(SRJIDs ++ T)}.
-get_jid_info({Subscription, Groups}, User, Server, JID) ->
+get_jid_info({Subscription, Groups}, User, Server,
+ JID) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
@@ -176,45 +177,42 @@ get_jid_info({Subscription, Groups}, User, Server, JID) ->
US1 = {U1, S1},
SRUsers = get_user_to_groups_map(US, false),
case dict:find(US1, SRUsers) of
- {ok, GroupNames} ->
- NewGroups = if
- Groups == [] -> GroupNames;
- true -> Groups
- end,
- {both, NewGroups};
- error ->
- {Subscription, Groups}
+ {ok, GroupNames} ->
+ NewGroups = if Groups == [] -> GroupNames;
+ true -> Groups
+ end,
+ {both, NewGroups};
+ error -> {Subscription, Groups}
end.
-in_subscription(Acc, User, Server, JID, Type, _Reason) ->
+in_subscription(Acc, User, Server, JID, Type,
+ _Reason) ->
process_subscription(in, User, Server, JID, Type, Acc).
out_subscription(User, Server, JID, Type) ->
- process_subscription(out, User, Server, JID, Type, false).
+ process_subscription(out, User, Server, JID, Type,
+ false).
-process_subscription(Direction, User, Server, JID, _Type, Acc) ->
+process_subscription(Direction, User, Server, JID,
+ _Type, Acc) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
- {U1, S1, _} = jlib:jid_tolower(jlib:jid_remove_resource(JID)),
+ {U1, S1, _} =
+ jlib:jid_tolower(jlib:jid_remove_resource(JID)),
US1 = {U1, S1},
DisplayedGroups = get_user_displayed_groups(US),
- SRUsers =
- lists:usort(
- lists:flatmap(
- fun(Group) ->
- get_group_users(LServer, Group)
- end, DisplayedGroups)),
+ SRUsers = lists:usort(lists:flatmap(fun (Group) ->
+ get_group_users(LServer, Group)
+ end,
+ DisplayedGroups)),
case lists:member(US1, SRUsers) of
- true ->
- case Direction of
- in ->
- {stop, false};
- out ->
- stop
- end;
- false ->
- Acc
+ true ->
+ case Direction of
+ in -> {stop, false};
+ out -> stop
+ end;
+ false -> Acc
end.
%%====================================================================
@@ -223,63 +221,54 @@ process_subscription(Direction, User, Server, JID, _Type, Acc) ->
init([Host, Opts]) ->
State = parse_options(Host, Opts),
cache_tab:new(shared_roster_ldap_user,
- [{max_size, State#state.user_cache_size},
- {lru, false}, % We don't need LRU algorithm
+ [{max_size, State#state.user_cache_size}, {lru, false},
{life_time, State#state.user_cache_validity}]),
cache_tab:new(shared_roster_ldap_group,
- [{max_size, State#state.group_cache_size},
- {lru, false}, % We don't need LRU algorithm
+ [{max_size, State#state.group_cache_size}, {lru, false},
{life_time, State#state.group_cache_validity}]),
- ejabberd_hooks:add(roster_get, Host,
- ?MODULE, get_user_roster, 70),
+ ejabberd_hooks:add(roster_get, Host, ?MODULE,
+ get_user_roster, 70),
ejabberd_hooks:add(roster_in_subscription, Host,
- ?MODULE, in_subscription, 30),
+ ?MODULE, in_subscription, 30),
ejabberd_hooks:add(roster_out_subscription, Host,
- ?MODULE, out_subscription, 30),
+ ?MODULE, out_subscription, 30),
ejabberd_hooks:add(roster_get_subscription_lists, Host,
?MODULE, get_subscription_lists, 70),
- ejabberd_hooks:add(roster_get_jid_info, Host,
- ?MODULE, get_jid_info, 70),
- ejabberd_hooks:add(roster_process_item, Host,
- ?MODULE, process_item, 50),
+ ejabberd_hooks:add(roster_get_jid_info, Host, ?MODULE,
+ get_jid_info, 70),
+ ejabberd_hooks:add(roster_process_item, Host, ?MODULE,
+ process_item, 50),
eldap_pool:start_link(State#state.eldap_id,
- State#state.servers,
- State#state.backups,
- State#state.port,
- State#state.dn,
- State#state.password,
- State#state.tls_options),
+ State#state.servers, State#state.backups,
+ State#state.port, State#state.dn,
+ State#state.password, State#state.tls_options),
{ok, State}.
handle_call(get_state, _From, State) ->
{reply, {ok, State}, State};
-
handle_call(_Request, _From, State) ->
{reply, {error, badarg}, State}.
-handle_cast(_Msg, State) ->
- {noreply, State}.
+handle_cast(_Msg, State) -> {noreply, State}.
-handle_info(_Info, State) ->
- {noreply, State}.
+handle_info(_Info, State) -> {noreply, State}.
terminate(_Reason, State) ->
Host = State#state.host,
- ejabberd_hooks:delete(roster_get, Host,
- ?MODULE, get_user_roster, 70),
+ ejabberd_hooks:delete(roster_get, Host, ?MODULE,
+ get_user_roster, 70),
ejabberd_hooks:delete(roster_in_subscription, Host,
- ?MODULE, in_subscription, 30),
+ ?MODULE, in_subscription, 30),
ejabberd_hooks:delete(roster_out_subscription, Host,
- ?MODULE, out_subscription, 30),
- ejabberd_hooks:delete(roster_get_subscription_lists, Host,
- ?MODULE, get_subscription_lists, 70),
+ ?MODULE, out_subscription, 30),
+ ejabberd_hooks:delete(roster_get_subscription_lists,
+ Host, ?MODULE, get_subscription_lists, 70),
ejabberd_hooks:delete(roster_get_jid_info, Host,
- ?MODULE, get_jid_info, 70),
+ ?MODULE, get_jid_info, 70),
ejabberd_hooks:delete(roster_process_item, Host,
?MODULE, process_item, 50).
-code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
+code_change(_OldVsn, State, _Extra) -> {ok, State}.
%%--------------------------------------------------------------------
%%% Internal functions
@@ -288,417 +277,325 @@ code_change(_OldVsn, State, _Extra) ->
%% members of. Skip the user himself iff SkipUS is true.
get_user_to_groups_map({_, Server} = US, SkipUS) ->
DisplayedGroups = get_user_displayed_groups(US),
- lists:foldl(
- fun(Group, Dict1) ->
- GroupName = get_group_name(Server, Group),
- lists:foldl(
- fun(Contact, Dict) ->
- if SkipUS, Contact == US ->
- Dict;
- true ->
- dict:append(Contact, GroupName, Dict)
- end
- end, Dict1, get_group_users(Server, Group))
- end, dict:new(), DisplayedGroups).
-
%% Pass given FilterParseArgs to eldap_filter:parse, and if successful, run and
%% return the resulting filter, retrieving given AttributesList. Return the
%% result entries. On any error silently return an empty list of results.
%%
%% Eldap server ID and base DN for the query are both retrieved from the State
%% record.
+ lists:foldl(fun (Group, Dict1) ->
+ GroupName = get_group_name(Server, Group),
+ lists:foldl(fun (Contact, Dict) ->
+ if SkipUS, Contact == US -> Dict;
+ true ->
+ dict:append(Contact,
+ GroupName, Dict)
+ end
+ end,
+ Dict1, get_group_users(Server, Group))
+ end,
+ dict:new(), DisplayedGroups).
+
eldap_search(State, FilterParseArgs, AttributesList) ->
case apply(eldap_filter, parse, FilterParseArgs) of
- {ok, EldapFilter} ->
- %% Filter parsing succeeded
- case eldap_pool:search(State#state.eldap_id,
- [{base, State#state.base},
- {filter, EldapFilter},
- {timeout, ?LDAP_SEARCH_TIMEOUT},
- {deref_aliases, State#state.deref_aliases},
- {attributes, AttributesList}]) of
- #eldap_search_result{entries = Es} ->
- %% A result with entries. Return their list.
- Es;
- _ ->
- %% Something else. Pretend we got no results.
- []
- end;
- _ ->
- %% Filter parsing failed. Pretend we got no results.
- []
+ {ok, EldapFilter} ->
+ case eldap_pool:search(State#state.eldap_id,
+ [{base, State#state.base},
+ {filter, EldapFilter},
+ {timeout, ?LDAP_SEARCH_TIMEOUT},
+ {deref_aliases, State#state.deref_aliases},
+ {attributes, AttributesList}])
+ of
+ #eldap_search_result{entries = Es} ->
+ %% A result with entries. Return their list.
+ Es;
+ _ ->
+ %% Something else. Pretend we got no results.
+ []
+ end;
+ _ ->
+ %% Filter parsing failed. Pretend we got no results.
+ []
end.
get_user_displayed_groups({User, Host}) ->
{ok, State} = eldap_utils:get_state(Host, ?MODULE),
GroupAttr = State#state.group_attr,
- Entries = eldap_search(
- State,
- [eldap_filter:do_sub(State#state.rfilter, [{"%u", User}])],
- [GroupAttr]),
- Reply = lists:flatmap(
- fun(#eldap_entry{attributes = Attrs}) ->
- case Attrs of
- [{GroupAttr, ValuesList}] ->
- ValuesList;
- _ ->
- []
- end
- end, Entries),
+ Entries = eldap_search(State,
+ [eldap_filter:do_sub(State#state.rfilter,
+ [{<<"%u">>, User}])],
+ [GroupAttr]),
+ Reply = lists:flatmap(fun (#eldap_entry{attributes =
+ Attrs}) ->
+ case Attrs of
+ [{GroupAttr, ValuesList}] -> ValuesList;
+ _ -> []
+ end
+ end,
+ Entries),
lists:usort(Reply).
get_group_users(Host, Group) ->
{ok, State} = eldap_utils:get_state(Host, ?MODULE),
- case cache_tab:dirty_lookup(
- shared_roster_ldap_group,
- {Group, Host},
- fun() -> search_group_info(State, Group) end) of
- {ok, #group_info{members = Members}} when Members /= undefined ->
- Members;
- _ ->
- []
+ case cache_tab:dirty_lookup(shared_roster_ldap_group,
+ {Group, Host},
+ fun () -> search_group_info(State, Group) end)
+ of
+ {ok, #group_info{members = Members}}
+ when Members /= undefined ->
+ Members;
+ _ -> []
end.
get_group_name(Host, Group) ->
{ok, State} = eldap_utils:get_state(Host, ?MODULE),
- case cache_tab:dirty_lookup(
- shared_roster_ldap_group,
- {Group, Host},
- fun() -> search_group_info(State, Group) end) of
- {ok, #group_info{desc = GroupName}} when GroupName /= undefined ->
- GroupName;
- _ ->
- Group
+ case cache_tab:dirty_lookup(shared_roster_ldap_group,
+ {Group, Host},
+ fun () -> search_group_info(State, Group) end)
+ of
+ {ok, #group_info{desc = GroupName}}
+ when GroupName /= undefined ->
+ GroupName;
+ _ -> Group
end.
get_user_name(User, Host) ->
{ok, State} = eldap_utils:get_state(Host, ?MODULE),
- case cache_tab:dirty_lookup(
- shared_roster_ldap_user,
- {User, Host},
- fun() -> search_user_name(State, User) end) of
- {ok, UserName} ->
- UserName;
- error ->
- User
+ case cache_tab:dirty_lookup(shared_roster_ldap_user,
+ {User, Host},
+ fun () -> search_user_name(State, User) end)
+ of
+ {ok, UserName} -> UserName;
+ error -> User
end.
search_group_info(State, Group) ->
- Extractor =
- case State#state.uid_format_re of
- "" ->
- fun(UID) ->
- catch eldap_utils:get_user_part(UID, State#state.uid_format)
- end;
- _ ->
- fun(UID) ->
- catch get_user_part_re(UID, State#state.uid_format_re)
- end
- end,
+ Extractor = case State#state.uid_format_re of
+ <<"">> ->
+ fun (UID) ->
+ catch eldap_utils:get_user_part(UID,
+ State#state.uid_format)
+ end;
+ _ ->
+ fun (UID) ->
+ catch get_user_part_re(UID,
+ State#state.uid_format_re)
+ end
+ end,
AuthChecker = case State#state.auth_check of
- true -> fun ejabberd_auth:is_user_exists/2;
- _ -> fun(_U, _S) -> true end
+ true -> fun ejabberd_auth:is_user_exists/2;
+ _ -> fun (_U, _S) -> true end
end,
Host = State#state.host,
- case eldap_search(
- State,
- [eldap_filter:do_sub(State#state.gfilter, [{"%g", Group}])],
- [State#state.group_attr, State#state.group_desc, State#state.uid]) of
- [] ->
- error;
- LDAPEntries ->
- {GroupDesc, MembersLists} =
- lists:foldl(
- fun(#eldap_entry{attributes=Attrs}, {DescAcc, JIDsAcc}) ->
- case {eldap_utils:get_ldap_attr(State#state.group_attr, Attrs),
- eldap_utils:get_ldap_attr(State#state.group_desc, Attrs),
- lists:keysearch(State#state.uid, 1, Attrs)} of
- {ID, Desc, {value, {GroupMemberAttr, Members}}}
- when ID /= "", GroupMemberAttr == State#state.uid ->
- JIDs = lists:foldl(
- fun({ok, UID}, L) ->
- PUID = jlib:nodeprep(UID),
- case PUID of
- error -> L;
- _ ->
- case AuthChecker(PUID, Host) of
- true -> [{PUID, Host} | L];
- _ -> L
- end
- end;
- (_, L) ->
- L
- end, [], lists:map(Extractor, Members)),
- {Desc, [JIDs|JIDsAcc]};
- _ ->
- {DescAcc, JIDsAcc}
- end
- end, {Group, []}, LDAPEntries),
- {ok, #group_info{desc = GroupDesc,
- members = lists:usort(lists:flatten(MembersLists))}}
+ case eldap_search(State,
+ [eldap_filter:do_sub(State#state.gfilter,
+ [{<<"%g">>, Group}])],
+ [State#state.group_attr, State#state.group_desc,
+ State#state.uid])
+ of
+ [] -> error;
+ LDAPEntries ->
+ {GroupDesc, MembersLists} = lists:foldl(fun
+ (#eldap_entry{attributes =
+ Attrs},
+ {DescAcc, JIDsAcc}) ->
+ case
+ {eldap_utils:get_ldap_attr(State#state.group_attr,
+ Attrs),
+ eldap_utils:get_ldap_attr(State#state.group_desc,
+ Attrs),
+ lists:keysearch(State#state.uid,
+ 1,
+ Attrs)}
+ of
+ {ID, Desc,
+ {value,
+ {GroupMemberAttr,
+ Members}}}
+ when ID /= <<"">>,
+ GroupMemberAttr
+ ==
+ State#state.uid ->
+ JIDs =
+ lists:foldl(fun
+ ({ok,
+ UID},
+ L) ->
+ PUID =
+ jlib:nodeprep(UID),
+ case
+ PUID
+ of
+ error ->
+ L;
+ _ ->
+ case
+ AuthChecker(PUID,
+ Host)
+ of
+ true ->
+ [{PUID,
+ Host}
+ | L];
+ _ ->
+ L
+ end
+ end;
+ (_,
+ L) ->
+ L
+ end,
+ [],
+ lists:map(Extractor,
+ Members)),
+ {Desc,
+ [JIDs
+ | JIDsAcc]};
+ _ ->
+ {DescAcc, JIDsAcc}
+ end
+ end,
+ {Group, []}, LDAPEntries),
+ {ok,
+ #group_info{desc = GroupDesc,
+ members = lists:usort(lists:flatten(MembersLists))}}
end.
search_user_name(State, User) ->
- case eldap_search(
- State,
- [eldap_filter:do_sub(State#state.ufilter, [{"%u", User}])],
- [State#state.user_desc, State#state.user_uid]) of
- [#eldap_entry{attributes=Attrs}|_] ->
- case {eldap_utils:get_ldap_attr(State#state.user_uid, Attrs),
- eldap_utils:get_ldap_attr(State#state.user_desc, Attrs)} of
- {UID, Desc} when UID /= "" ->
- %% By returning "" get_ldap_attr means "not found"
- {ok, Desc};
- _ ->
- error
- end;
- [] ->
- error
+ case eldap_search(State,
+ [eldap_filter:do_sub(State#state.ufilter,
+ [{<<"%u">>, User}])],
+ [State#state.user_desc, State#state.user_uid])
+ of
+ [#eldap_entry{attributes = Attrs} | _] ->
+ case {eldap_utils:get_ldap_attr(State#state.user_uid,
+ Attrs),
+ eldap_utils:get_ldap_attr(State#state.user_desc, Attrs)}
+ of
+ {UID, Desc} when UID /= <<"">> -> {ok, Desc};
+ _ -> error
+ end;
+ [] -> error
end.
%% Getting User ID part by regex pattern
get_user_part_re(String, Pattern) ->
case catch re:run(String, Pattern) of
- {match, Captured} ->
- {First, Len} = lists:nth(2,Captured),
- Result = string:sub_string(String, First+1, First+Len),
- {ok,Result};
- _ -> {error, badmatch}
+ {match, Captured} ->
+ {First, Len} = lists:nth(2, Captured),
+ Result = str:sub_string(String, First + 1, First + Len),
+ {ok, Result};
+ _ -> {error, badmatch}
end.
parse_options(Host, Opts) ->
- Eldap_ID = atom_to_list(gen_mod:get_module_proc(Host, ?MODULE)),
- LDAPServers = case gen_mod:get_opt(ldap_servers, Opts, undefined) of
- undefined ->
- ejabberd_config:get_local_option({ldap_servers, Host});
- S -> S
- end,
- LDAPBackups = case gen_mod:get_opt(ldap_backups, Opts, undefined) of
- undefined ->
- ejabberd_config:get_local_option({ldap_servers, Host});
- Backups -> Backups
- end,
- LDAPEncrypt = case gen_mod:get_opt(ldap_encrypt, Opts, undefined) of
- undefined ->
- ejabberd_config:get_local_option({ldap_encrypt, Host});
- E -> E
- end,
- LDAPTLSVerify = case gen_mod:get_opt(ldap_tls_verify, Opts, undefined) of
- undefined ->
- ejabberd_config:get_local_option({ldap_tls_verify, Host});
- Verify -> Verify
- end,
- LDAPTLSCAFile = case gen_mod:get_opt(ldap_tls_cacertfile, Opts, undefined) of
- undefined ->
- ejabberd_config:get_local_option({ldap_tls_cacertfile, Host});
- CAFile -> CAFile
- end,
- LDAPTLSDepth = case gen_mod:get_opt(ldap_tls_depth, Opts, undefined) of
- undefined ->
- ejabberd_config:get_local_option({ldap_tls_depth, Host});
- Depth ->
- Depth
- end,
- LDAPPort = case gen_mod:get_opt(ldap_port, Opts, undefined) of
- undefined ->
- case ejabberd_config:get_local_option({ldap_port, Host}) of
- undefined -> case LDAPEncrypt of
- tls -> ?LDAPS_PORT;
- starttls -> ?LDAP_PORT;
- _ -> ?LDAP_PORT
- end;
- P -> P
- end;
- P -> P
- end,
- LDAPBase = case gen_mod:get_opt(ldap_base, Opts, undefined) of
- undefined ->
- ejabberd_config:get_local_option({ldap_base, Host});
- B -> B
- end,
- GroupAttr = case gen_mod:get_opt(ldap_groupattr, Opts, undefined) of
- undefined -> "cn";
- GA -> GA
- end,
- GroupDesc = case gen_mod:get_opt(ldap_groupdesc, Opts, undefined) of
- undefined -> GroupAttr;
- GD -> GD
- end,
- UserDesc = case gen_mod:get_opt(ldap_userdesc, Opts, undefined) of
- undefined -> "cn";
- UD -> UD
- end,
- UserUID = case gen_mod:get_opt(ldap_useruid, Opts, undefined) of
- undefined -> "cn";
- UU -> UU
- end,
- UIDAttr = case gen_mod:get_opt(ldap_memberattr, Opts, undefined) of
- undefined -> "memberUid";
- UA -> UA
- end,
- UIDAttrFormat = case gen_mod:get_opt(ldap_memberattr_format, Opts, undefined) of
- undefined -> "%u";
- UAF -> UAF
- end,
- UIDAttrFormatRe =
- case gen_mod:get_opt(ldap_memberattr_format_re, Opts, undefined) of
- undefined -> "";
- UAFre -> case catch re:compile(UAFre) of
- {ok, MP} ->
- MP;
- _ ->
- ?ERROR_MSG("Invalid ldap_memberattr_format_re '~s' "
- "or no RE support in this erlang version. "
- "ldap_memberattr_format '~s' will be used "
- "instead.", [UAFre, UIDAttrFormat]),
- ""
- end
- end,
- AuthCheck = case gen_mod:get_opt(ldap_auth_check, Opts, undefined) of
- undefined -> true;
- on -> true;
- AC -> AC
- end,
- RootDN = case gen_mod:get_opt(ldap_rootdn, Opts, undefined) of
- undefined ->
- case ejabberd_config:get_local_option({ldap_rootdn, Host}) of
- undefined -> "";
- RDN -> RDN
- end;
- RDN -> RDN
- end,
- Password = case gen_mod:get_opt(ldap_password, Opts, undefined) of
- undefined ->
- case ejabberd_config:get_local_option({ldap_password, Host}) of
- undefined -> "";
- Pass -> Pass
- end;
- Pass -> Pass
- end,
- UserCacheValidity =
- case gen_mod:get_opt(ldap_user_cache_validity, Opts, undefined) of
- undefined ->
- case ejabberd_config:get_local_option({ldap_user_cache_validity, Host}) of
- undefined -> ?USER_CACHE_VALIDITY;
- UVSeconds -> UVSeconds
- end;
- UVSeconds -> UVSeconds
- end,
- GroupCacheValidity =
- case gen_mod:get_opt(ldap_group_cache_validity, Opts, undefined) of
- undefined ->
- case ejabberd_config:get_local_option({ldap_group_cache_validity, Host}) of
- undefined -> ?GROUP_CACHE_VALIDITY;
- GVSeconds -> GVSeconds
- end;
- GVSeconds -> GVSeconds
- end,
- UserCacheSize =
- case gen_mod:get_opt(ldap_user_cache_size, Opts, undefined) of
- undefined ->
- case ejabberd_config:get_local_option({ldap_user_cache_size, Host}) of
- undefined -> ?CACHE_SIZE;
- USSeconds -> USSeconds
- end;
- USSeconds -> USSeconds
- end,
- GroupCacheSize =
- case gen_mod:get_opt(ldap_group_cache_size, Opts, undefined) of
- undefined ->
- case ejabberd_config:get_local_option({ldap_group_cache_size, Host}) of
- undefined -> ?CACHE_SIZE;
- GSSeconds -> GSSeconds
- end;
- GSSeconds -> GSSeconds
- end,
- ConfigFilter = case gen_mod:get_opt(ldap_filter, Opts, undefined) of
- undefined ->
- ejabberd_config:get_local_option({ldap_filter, Host});
- F ->
- F
- end,
- ConfigUserFilter = case gen_mod:get_opt(ldap_ufilter, Opts, undefined) of
- undefined ->
- ejabberd_config:get_local_option({ldap_ufilter, Host});
- UF -> UF
- end,
-
- ConfigGroupFilter = case gen_mod:get_opt(ldap_gfilter, Opts, undefined) of
- undefined ->
- ejabberd_config:get_local_option({ldap_gfilter, Host});
- GF -> GF
- end,
-
- RosterFilter = case gen_mod:get_opt(ldap_rfilter, Opts, undefined) of
- undefined ->
- ejabberd_config:get_local_option({ldap_rfilter, Host});
- RF ->
- RF
- end,
- lists:foreach(fun eldap_utils:check_filter/1,
- [ConfigFilter, ConfigUserFilter,
- ConfigGroupFilter, RosterFilter]),
- SubFilter = "(&("++UIDAttr++"="++UIDAttrFormat++")("++GroupAttr++"=%g))",
+ Eldap_ID = jlib:atom_to_binary(gen_mod:get_module_proc(Host, ?MODULE)),
+ Cfg = eldap_utils:get_config(Host, Opts),
+ GroupAttr = gen_mod:get_opt(ldap_groupattr, Opts,
+ fun iolist_to_binary/1,
+ <<"cn">>),
+ GroupDesc = gen_mod:get_opt(ldap_groupdesc, Opts,
+ fun iolist_to_binary/1,
+ GroupAttr),
+ UserDesc = gen_mod:get_opt(ldap_userdesc, Opts,
+ fun iolist_to_binary/1,
+ <<"cn">>),
+ UserUID = gen_mod:get_opt(ldap_useruid, Opts,
+ fun iolist_to_binary/1,
+ <<"cn">>),
+ UIDAttr = gen_mod:get_opt(ldap_memberattr, Opts,
+ fun iolist_to_binary/1,
+ <<"memberUid">>),
+ UIDAttrFormat = gen_mod:get_opt(ldap_memberattr_format, Opts,
+ fun iolist_to_binary/1,
+ <<"%u">>),
+ UIDAttrFormatRe = gen_mod:get_opt(ldap_memberattr_format_re, Opts,
+ fun(S) ->
+ Re = iolist_to_binary(S),
+ {ok, MP} = re:compile(Re),
+ MP
+ end, <<"">>),
+ AuthCheck = gen_mod:get_opt(ldap_auth_check, Opts,
+ fun(on) -> true;
+ (off) -> false;
+ (false) -> false;
+ (true) -> true
+ end, true),
+ UserCacheValidity = eldap_utils:get_opt(
+ {ldap_user_cache_validity, Host}, Opts,
+ fun(I) when is_integer(I), I>0 -> I end,
+ ?USER_CACHE_VALIDITY),
+ GroupCacheValidity = eldap_utils:get_opt(
+ {ldap_group_cache_validity, Host}, Opts,
+ fun(I) when is_integer(I), I>0 -> I end,
+ ?GROUP_CACHE_VALIDITY),
+ UserCacheSize = eldap_utils:get_opt(
+ {ldap_user_cache_size, Host}, Opts,
+ fun(I) when is_integer(I), I>0 -> I end,
+ ?CACHE_SIZE),
+ GroupCacheSize = eldap_utils:get_opt(
+ {ldap_group_cache_size, Host}, Opts,
+ fun(I) when is_integer(I), I>0 -> I end,
+ ?CACHE_SIZE),
+ ConfigFilter = eldap_utils:get_opt({ldap_filter, Host}, Opts,
+ fun check_filter/1, <<"">>),
+ ConfigUserFilter = eldap_utils:get_opt({ldap_ufilter, Host}, Opts,
+ fun check_filter/1, <<"">>),
+ ConfigGroupFilter = eldap_utils:get_opt({ldap_gfilter, Host}, Opts,
+ fun check_filter/1, <<"">>),
+ RosterFilter = eldap_utils:get_opt({ldap_rfilter, Host}, Opts,
+ fun check_filter/1, <<"">>),
+ SubFilter = <<"(&(", UIDAttr/binary, "=",
+ UIDAttrFormat/binary, ")(", GroupAttr/binary, "=%g))">>,
UserSubFilter = case ConfigUserFilter of
- undefined -> eldap_filter:do_sub(SubFilter, [{"%g", "*"}]);
- "" -> eldap_filter:do_sub(SubFilter, [{"%g", "*"}]);
- UString -> UString
- end,
+ <<"">> ->
+ eldap_filter:do_sub(SubFilter, [{<<"%g">>, <<"*">>}]);
+ UString -> UString
+ end,
GroupSubFilter = case ConfigGroupFilter of
- undefined -> eldap_filter:do_sub(SubFilter, [{"%u", "*"}]);
- "" -> eldap_filter:do_sub(SubFilter, [{"%u", "*"}]);
- GString -> GString
- end,
+ <<"">> ->
+ eldap_filter:do_sub(SubFilter,
+ [{<<"%u">>, <<"*">>}]);
+ GString -> GString
+ end,
Filter = case ConfigFilter of
- undefined -> SubFilter;
- "" -> SubFilter;
- _ -> "(&" ++ SubFilter ++ ConfigFilter ++ ")"
+ <<"">> -> SubFilter;
+ _ ->
+ <<"(&", SubFilter/binary, ConfigFilter/binary, ")">>
end,
UserFilter = case ConfigFilter of
- undefined -> UserSubFilter;
- "" -> UserSubFilter;
- _ -> "(&" ++ UserSubFilter ++ ConfigFilter ++ ")"
+ <<"">> -> UserSubFilter;
+ _ ->
+ <<"(&", UserSubFilter/binary, ConfigFilter/binary, ")">>
end,
GroupFilter = case ConfigFilter of
- undefined -> GroupSubFilter;
- "" -> GroupSubFilter;
- _ -> "(&" ++ GroupSubFilter ++ ConfigFilter ++ ")"
+ <<"">> -> GroupSubFilter;
+ _ ->
+ <<"(&", GroupSubFilter/binary, ConfigFilter/binary,
+ ")">>
end,
- DerefAliases = case gen_mod:get_opt(deref_aliases, Opts, undefined) of
- undefined ->
- case ejabberd_config:get_local_option(
- {deref_aliases, Host}) of
- undefined -> never;
- D -> D
- end;
- D -> D
- end,
- #state{host = Host,
- eldap_id = Eldap_ID,
- servers = LDAPServers,
- backups = LDAPBackups,
- port = LDAPPort,
- tls_options = [{encrypt, LDAPEncrypt},
- {tls_verify, LDAPTLSVerify},
- {tls_cacertfile, LDAPTLSCAFile},
- {tls_depth, LDAPTLSDepth}],
- dn = RootDN,
- base = LDAPBase,
- password = Password,
+ #state{host = Host, eldap_id = Eldap_ID,
+ servers = Cfg#eldap_config.servers,
+ backups = Cfg#eldap_config.backups,
+ port = Cfg#eldap_config.port,
+ tls_options = Cfg#eldap_config.tls_options,
+ dn = Cfg#eldap_config.dn,
+ password = Cfg#eldap_config.password,
+ base = Cfg#eldap_config.base,
+ deref_aliases = Cfg#eldap_config.deref_aliases,
uid = UIDAttr,
- deref_aliases = DerefAliases,
- group_attr = GroupAttr,
- group_desc = GroupDesc,
- user_desc = UserDesc,
- user_uid = UserUID,
+ group_attr = GroupAttr, group_desc = GroupDesc,
+ user_desc = UserDesc, user_uid = UserUID,
uid_format = UIDAttrFormat,
- uid_format_re = UIDAttrFormatRe,
- filter = Filter,
- ufilter = UserFilter,
- rfilter = RosterFilter,
- gfilter = GroupFilter,
- auth_check = AuthCheck,
+ uid_format_re = UIDAttrFormatRe, filter = Filter,
+ ufilter = UserFilter, rfilter = RosterFilter,
+ gfilter = GroupFilter, auth_check = AuthCheck,
user_cache_size = UserCacheSize,
user_cache_validity = UserCacheValidity,
group_cache_size = GroupCacheSize,
group_cache_validity = GroupCacheValidity}.
+
+check_filter(F) ->
+ NewF = iolist_to_binary(F),
+ {ok, _} = eldap_filter:parse(NewF),
+ NewF.
diff --git a/src/mod_sic.erl b/src/mod_sic.erl
index d5bff5501..fbaa165e8 100644
--- a/src/mod_sic.erl
+++ b/src/mod_sic.erl
@@ -25,66 +25,67 @@
%%%----------------------------------------------------------------------
-module(mod_sic).
+
-author('karim.gemayel@process-one.net').
-behaviour(gen_mod).
--export([start/2,
- stop/1,
- process_local_iq/3,
- process_sm_iq/3
- ]).
+-export([start/2, stop/1, process_local_iq/3,
+ process_sm_iq/3]).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
--define(NS_SIC, "urn:xmpp:sic:0").
+-define(NS_SIC, <<"urn:xmpp:sic:0">>).
start(Host, Opts) ->
- IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
+ IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
+ one_queue),
gen_iq_handler:add_iq_handler(ejabberd_local, Host,
?NS_SIC, ?MODULE, process_local_iq, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
?NS_SIC, ?MODULE, process_sm_iq, IQDisc).
stop(Host) ->
- gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_SIC),
- gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_SIC).
+ gen_iq_handler:remove_iq_handler(ejabberd_local, Host,
+ ?NS_SIC),
+ gen_iq_handler:remove_iq_handler(ejabberd_sm, Host,
+ ?NS_SIC).
-
-process_local_iq(#jid{user = User, server = Server, resource = Resource}, _To,
- #iq{type = 'get', sub_el = _SubEl} = IQ) ->
+process_local_iq(#jid{user = User, server = Server,
+ resource = Resource},
+ _To, #iq{type = get, sub_el = _SubEl} = IQ) ->
get_ip({User, Server, Resource}, IQ);
-
-process_local_iq(_From, _To, #iq{type = 'set', sub_el = SubEl} = IQ) ->
+process_local_iq(_From, _To,
+ #iq{type = set, sub_el = SubEl} = IQ) ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}.
-
-process_sm_iq(#jid{user = User, server = Server, resource = Resource},
+process_sm_iq(#jid{user = User, server = Server,
+ resource = Resource},
#jid{user = User, server = Server},
- #iq{type = 'get', sub_el = _SubEl} = IQ) ->
+ #iq{type = get, sub_el = _SubEl} = IQ) ->
get_ip({User, Server, Resource}, IQ);
-
-process_sm_iq(_From, _To, #iq{type = 'get', sub_el = SubEl} = IQ) ->
+process_sm_iq(_From, _To,
+ #iq{type = get, sub_el = SubEl} = IQ) ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]};
-
-process_sm_iq(_From, _To, #iq{type = 'set', sub_el = SubEl} = IQ) ->
+process_sm_iq(_From, _To,
+ #iq{type = set, sub_el = SubEl} = IQ) ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}.
get_ip({User, Server, Resource},
- #iq{sub_el = {xmlelement, Name, Attrs, _} = SubEl} = IQ) ->
+ #iq{sub_el =
+ #xmlel{name = Name, attrs = Attrs} = SubEl} =
+ IQ) ->
case ejabberd_sm:get_user_ip(User, Server, Resource) of
- {IP, _} when is_tuple(IP) ->
- IQ#iq{
- type = 'result',
- sub_el = [
- {xmlelement, Name, Attrs,
- [{xmlcdata, list_to_binary(inet_parse:ntoa(IP))}]}
- ]
- };
- _ ->
- IQ#iq{
- type = 'error',
- sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]
- }
+ {IP, _} when is_tuple(IP) ->
+ IQ#iq{type = result,
+ sub_el =
+ [#xmlel{name = Name, attrs = Attrs,
+ children =
+ [{xmlcdata,
+ iolist_to_binary(jlib:ip_to_list(IP))}]}]};
+ _ ->
+ IQ#iq{type = error,
+ sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]}
end.
diff --git a/src/mod_stats.erl b/src/mod_stats.erl
index 7c62e1e83..f988626fd 100644
--- a/src/mod_stats.erl
+++ b/src/mod_stats.erl
@@ -25,227 +25,241 @@
%%%----------------------------------------------------------------------
-module(mod_stats).
+
-author('alexey@process-one.net').
-behaviour(gen_mod).
--export([start/2,
- stop/1,
- process_local_iq/3]).
+-export([start/2, stop/1, process_local_iq/3]).
+-include("ejabberd.hrl").
-include("jlib.hrl").
start(Host, Opts) ->
- IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
- gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_STATS,
- ?MODULE, process_local_iq, IQDisc).
+ IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
+ one_queue),
+ gen_iq_handler:add_iq_handler(ejabberd_local, Host,
+ ?NS_STATS, ?MODULE, process_local_iq, IQDisc).
stop(Host) ->
- gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_STATS).
-
+ gen_iq_handler:remove_iq_handler(ejabberd_local, Host,
+ ?NS_STATS).
-process_local_iq(_From, To, #iq{id = _ID, type = Type,
- xmlns = XMLNS, sub_el = SubEl} = IQ) ->
- %%Lang = xml:get_tag_attr_s("xml:lang", SubEl),
+process_local_iq(_From, To,
+ #iq{id = _ID, type = Type, xmlns = XMLNS,
+ sub_el = SubEl} =
+ IQ) ->
case Type of
- set ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
- get ->
- {xmlelement, _, _Attrs, Els} = SubEl,
- Node = string:tokens(xml:get_tag_attr_s("node", SubEl), "/"),
- Names = get_names(Els, []),
-
- case get_local_stats(To#jid.server, Node, Names) of
- {result, Res} ->
- IQ#iq{type = result,
- sub_el = [{xmlelement, "query",
- [{"xmlns", XMLNS}],
- Res}]};
- {error, Error} ->
- IQ#iq{type = error, sub_el = [SubEl, Error]}
- end
+ set ->
+ IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+ get ->
+ #xmlel{children = Els} = SubEl,
+ Node = str:tokens(xml:get_tag_attr_s(<<"node">>, SubEl),
+ <<"/">>),
+ Names = get_names(Els, []),
+ case get_local_stats(To#jid.server, Node, Names) of
+ {result, Res} ->
+ IQ#iq{type = result,
+ sub_el =
+ [#xmlel{name = <<"query">>,
+ attrs = [{<<"xmlns">>, XMLNS}],
+ children = Res}]};
+ {error, Error} ->
+ IQ#iq{type = error, sub_el = [SubEl, Error]}
+ end
end.
-
-get_names([], Res) ->
- Res;
-get_names([{xmlelement, "stat", Attrs, _} | Els], Res) ->
- Name = xml:get_attr_s("name", Attrs),
+get_names([], Res) -> Res;
+get_names([#xmlel{name = <<"stat">>, attrs = Attrs}
+ | Els],
+ Res) ->
+ Name = xml:get_attr_s(<<"name">>, Attrs),
case Name of
- "" ->
- get_names(Els, Res);
- _ ->
- get_names(Els, [Name | Res])
+ <<"">> -> get_names(Els, Res);
+ _ -> get_names(Els, [Name | Res])
end;
-get_names([_ | Els], Res) ->
- get_names(Els, Res).
-
+get_names([_ | Els], Res) -> get_names(Els, Res).
--define(STAT(Name), {xmlelement, "stat", [{"name", Name}], []}).
+-define(STAT(Name),
+ #xmlel{name = <<"stat">>, attrs = [{<<"name">>, Name}],
+ children = []}).
get_local_stats(_Server, [], []) ->
{result,
- [?STAT("users/online"),
- ?STAT("users/total"),
- ?STAT("users/all-hosts/online"),
- ?STAT("users/all-hosts/total")
- ]};
-
+ [?STAT(<<"users/online">>), ?STAT(<<"users/total">>),
+ ?STAT(<<"users/all-hosts/online">>),
+ ?STAT(<<"users/all-hosts/total">>)]};
get_local_stats(Server, [], Names) ->
- {result, lists:map(fun(Name) ->
- get_local_stat(Server, [], Name)
- end, Names)};
-
-get_local_stats(_Server, ["running nodes", _], []) ->
{result,
- [?STAT("time/uptime"),
- ?STAT("time/cputime"),
- ?STAT("users/online"),
- ?STAT("transactions/committed"),
- ?STAT("transactions/aborted"),
- ?STAT("transactions/restarted"),
- ?STAT("transactions/logged")
- ]};
-
-get_local_stats(_Server, ["running nodes", ENode], Names) ->
+ lists:map(fun (Name) -> get_local_stat(Server, [], Name)
+ end,
+ Names)};
+get_local_stats(_Server, [<<"running nodes">>, _],
+ []) ->
+ {result,
+ [?STAT(<<"time/uptime">>), ?STAT(<<"time/cputime">>),
+ ?STAT(<<"users/online">>),
+ ?STAT(<<"transactions/committed">>),
+ ?STAT(<<"transactions/aborted">>),
+ ?STAT(<<"transactions/restarted">>),
+ ?STAT(<<"transactions/logged">>)]};
+get_local_stats(_Server, [<<"running nodes">>, ENode],
+ Names) ->
case search_running_node(ENode) of
- false ->
- {error, ?ERR_ITEM_NOT_FOUND};
- Node ->
- {result,
- lists:map(fun(Name) -> get_node_stat(Node, Name) end, Names)}
+ false -> {error, ?ERR_ITEM_NOT_FOUND};
+ Node ->
+ {result,
+ lists:map(fun (Name) -> get_node_stat(Node, Name) end,
+ Names)}
end;
-
get_local_stats(_Server, _, _) ->
{error, ?ERR_FEATURE_NOT_IMPLEMENTED}.
-
-
-define(STATVAL(Val, Unit),
- {xmlelement, "stat",
- [{"name", Name},
- {"units", Unit},
- {"value", Val}
- ], []}).
+ #xmlel{name = <<"stat">>,
+ attrs =
+ [{<<"name">>, Name}, {<<"units">>, Unit},
+ {<<"value">>, Val}],
+ children = []}).
-define(STATERR(Code, Desc),
- {xmlelement, "stat",
- [{"name", Name}],
- [{xmlelement, "error",
- [{"code", Code}],
- [{xmlcdata, Desc}]}]}).
-
-
-get_local_stat(Server, [], Name) when Name == "users/online" ->
+ #xmlel{name = <<"stat">>, attrs = [{<<"name">>, Name}],
+ children =
+ [#xmlel{name = <<"error">>,
+ attrs = [{<<"code">>, Code}],
+ children = [{xmlcdata, Desc}]}]}).
+
+get_local_stat(Server, [], Name)
+ when Name == <<"users/online">> ->
case catch ejabberd_sm:get_vh_session_list(Server) of
- {'EXIT', _Reason} ->
- ?STATERR("500", "Internal Server Error");
- Users ->
- ?STATVAL(integer_to_list(length(Users)), "users")
+ {'EXIT', _Reason} ->
+ ?STATERR(<<"500">>, <<"Internal Server Error">>);
+ Users ->
+ ?STATVAL((iolist_to_binary(integer_to_list(length(Users)))),
+ <<"users">>)
end;
-
-get_local_stat(Server, [], Name) when Name == "users/total" ->
- %%LServer = jlib:nameprep(Server),
- case catch ejabberd_auth:get_vh_registered_users_number(Server) of
- {'EXIT', _Reason} ->
- ?STATERR("500", "Internal Server Error");
- NUsers ->
- ?STATVAL(integer_to_list(NUsers), "users")
+get_local_stat(Server, [], Name)
+ when Name == <<"users/total">> ->
+ case catch
+ ejabberd_auth:get_vh_registered_users_number(Server)
+ of
+ {'EXIT', _Reason} ->
+ ?STATERR(<<"500">>, <<"Internal Server Error">>);
+ NUsers ->
+ ?STATVAL((iolist_to_binary(integer_to_list(NUsers))),
+ <<"users">>)
end;
-
-get_local_stat(_Server, [], Name) when Name == "users/all-hosts/online" ->
+get_local_stat(_Server, [], Name)
+ when Name == <<"users/all-hosts/online">> ->
case catch mnesia:table_info(session, size) of
- {'EXIT', _Reason} ->
- ?STATERR("500", "Internal Server Error");
- Users ->
- ?STATVAL(integer_to_list(Users), "users")
+ {'EXIT', _Reason} ->
+ ?STATERR(<<"500">>, <<"Internal Server Error">>);
+ Users ->
+ ?STATVAL((iolist_to_binary(integer_to_list(Users))),
+ <<"users">>)
end;
-
-get_local_stat(_Server, [], Name) when Name == "users/all-hosts/total" ->
- NumUsers = lists:foldl(
- fun(Host, Total) ->
- ejabberd_auth:get_vh_registered_users_number(Host)
- + Total
- end, 0, ejabberd_config:get_global_option(hosts)),
- ?STATVAL(integer_to_list(NumUsers), "users");
-
+get_local_stat(_Server, [], Name)
+ when Name == <<"users/all-hosts/total">> ->
+ NumUsers = lists:foldl(fun (Host, Total) ->
+ ejabberd_auth:get_vh_registered_users_number(Host)
+ + Total
+ end,
+ 0, ?MYHOSTS),
+ ?STATVAL((iolist_to_binary(integer_to_list(NumUsers))),
+ <<"users">>);
get_local_stat(_Server, _, Name) ->
- ?STATERR("404", "Not Found").
-
-
-
-get_node_stat(Node, Name) when Name == "time/uptime" ->
- case catch rpc:call(Node, erlang, statistics, [wall_clock]) of
- {badrpc, _Reason} ->
- ?STATERR("500", "Internal Server Error");
- CPUTime ->
- ?STATVAL(
- io_lib:format("~.3f", [element(1, CPUTime)/1000]), "seconds")
+ ?STATERR(<<"404">>, <<"Not Found">>).
+
+get_node_stat(Node, Name)
+ when Name == <<"time/uptime">> ->
+ case catch rpc:call(Node, erlang, statistics,
+ [wall_clock])
+ of
+ {badrpc, _Reason} ->
+ ?STATERR(<<"500">>, <<"Internal Server Error">>);
+ CPUTime ->
+ ?STATVAL(list_to_binary(
+ io_lib:format("~.3f",
+ [element(1, CPUTime) / 1000])),
+ <<"seconds">>)
end;
-
-get_node_stat(Node, Name) when Name == "time/cputime" ->
- case catch rpc:call(Node, erlang, statistics, [runtime]) of
- {badrpc, _Reason} ->
- ?STATERR("500", "Internal Server Error");
- RunTime ->
- ?STATVAL(
- io_lib:format("~.3f", [element(1, RunTime)/1000]), "seconds")
+get_node_stat(Node, Name)
+ when Name == <<"time/cputime">> ->
+ case catch rpc:call(Node, erlang, statistics, [runtime])
+ of
+ {badrpc, _Reason} ->
+ ?STATERR(<<"500">>, <<"Internal Server Error">>);
+ RunTime ->
+ ?STATVAL(list_to_binary(
+ io_lib:format("~.3f",
+ [element(1, RunTime) / 1000])),
+ <<"seconds">>)
end;
-
-get_node_stat(Node, Name) when Name == "users/online" ->
- case catch rpc:call(Node, ejabberd_sm, dirty_get_my_sessions_list, []) of
- {badrpc, _Reason} ->
- ?STATERR("500", "Internal Server Error");
- Users ->
- ?STATVAL(integer_to_list(length(Users)), "users")
+get_node_stat(Node, Name)
+ when Name == <<"users/online">> ->
+ case catch rpc:call(Node, ejabberd_sm,
+ dirty_get_my_sessions_list, [])
+ of
+ {badrpc, _Reason} ->
+ ?STATERR(<<"500">>, <<"Internal Server Error">>);
+ Users ->
+ ?STATVAL((iolist_to_binary(integer_to_list(length(Users)))),
+ <<"users">>)
end;
-
-get_node_stat(Node, Name) when Name == "transactions/committed" ->
- case catch rpc:call(Node, mnesia, system_info, [transaction_commits]) of
- {badrpc, _Reason} ->
- ?STATERR("500", "Internal Server Error");
- Transactions ->
- ?STATVAL(integer_to_list(Transactions), "transactions")
+get_node_stat(Node, Name)
+ when Name == <<"transactions/committed">> ->
+ case catch rpc:call(Node, mnesia, system_info,
+ [transaction_commits])
+ of
+ {badrpc, _Reason} ->
+ ?STATERR(<<"500">>, <<"Internal Server Error">>);
+ Transactions ->
+ ?STATVAL((iolist_to_binary(integer_to_list(Transactions))),
+ <<"transactions">>)
end;
-
-get_node_stat(Node, Name) when Name == "transactions/aborted" ->
- case catch rpc:call(Node, mnesia, system_info, [transaction_failures]) of
- {badrpc, _Reason} ->
- ?STATERR("500", "Internal Server Error");
- Transactions ->
- ?STATVAL(integer_to_list(Transactions), "transactions")
+get_node_stat(Node, Name)
+ when Name == <<"transactions/aborted">> ->
+ case catch rpc:call(Node, mnesia, system_info,
+ [transaction_failures])
+ of
+ {badrpc, _Reason} ->
+ ?STATERR(<<"500">>, <<"Internal Server Error">>);
+ Transactions ->
+ ?STATVAL((iolist_to_binary(integer_to_list(Transactions))),
+ <<"transactions">>)
end;
-
-get_node_stat(Node, Name) when Name == "transactions/restarted" ->
- case catch rpc:call(Node, mnesia, system_info, [transaction_restarts]) of
- {badrpc, _Reason} ->
- ?STATERR("500", "Internal Server Error");
- Transactions ->
- ?STATVAL(integer_to_list(Transactions), "transactions")
+get_node_stat(Node, Name)
+ when Name == <<"transactions/restarted">> ->
+ case catch rpc:call(Node, mnesia, system_info,
+ [transaction_restarts])
+ of
+ {badrpc, _Reason} ->
+ ?STATERR(<<"500">>, <<"Internal Server Error">>);
+ Transactions ->
+ ?STATVAL((iolist_to_binary(integer_to_list(Transactions))),
+ <<"transactions">>)
end;
-
-get_node_stat(Node, Name) when Name == "transactions/logged" ->
- case catch rpc:call(Node, mnesia, system_info, [transaction_log_writes]) of
- {badrpc, _Reason} ->
- ?STATERR("500", "Internal Server Error");
- Transactions ->
- ?STATVAL(integer_to_list(Transactions), "transactions")
+get_node_stat(Node, Name)
+ when Name == <<"transactions/logged">> ->
+ case catch rpc:call(Node, mnesia, system_info,
+ [transaction_log_writes])
+ of
+ {badrpc, _Reason} ->
+ ?STATERR(<<"500">>, <<"Internal Server Error">>);
+ Transactions ->
+ ?STATVAL((iolist_to_binary(integer_to_list(Transactions))),
+ <<"transactions">>)
end;
-
get_node_stat(_, Name) ->
- ?STATERR("404", "Not Found").
-
+ ?STATERR(<<"404">>, <<"Not Found">>).
search_running_node(SNode) ->
- search_running_node(SNode, mnesia:system_info(running_db_nodes)).
+ search_running_node(SNode,
+ mnesia:system_info(running_db_nodes)).
-search_running_node(_, []) ->
- false;
+search_running_node(_, []) -> false;
search_running_node(SNode, [Node | Nodes]) ->
- case atom_to_list(Node) of
- SNode ->
- Node;
- _ ->
- search_running_node(SNode, Nodes)
+ case iolist_to_binary(atom_to_list(Node)) of
+ SNode -> Node;
+ _ -> search_running_node(SNode, Nodes)
end.
-
diff --git a/src/mod_time.erl b/src/mod_time.erl
index ce2865a5b..a97391e7d 100644
--- a/src/mod_time.erl
+++ b/src/mod_time.erl
@@ -2,6 +2,7 @@
%%% File : mod_time.erl
%%% Author : Alexey Shchepin <alexey@process-one.net>
%%% Purpose :
+%%% Purpose :
%%% Created : 18 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
@@ -25,67 +26,84 @@
%%%----------------------------------------------------------------------
-module(mod_time).
+
-author('alexey@process-one.net').
-behaviour(gen_mod).
--export([start/2,
- stop/1,
- process_local_iq90/3, % TODO: Remove once XEP-0090 is Obsolete
+-export([start/2, stop/1, process_local_iq90/3,
process_local_iq/3]).
+ % TODO: Remove once XEP-0090 is Obsolete
+
-include("ejabberd.hrl").
--include("jlib.hrl").
+-include("jlib.hrl").
start(Host, Opts) ->
- IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
- %% TODO: Remove the next two lines once XEP-0090 is Obsolete
- gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_TIME90,
- ?MODULE, process_local_iq90, IQDisc),
- gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_TIME,
- ?MODULE, process_local_iq, IQDisc).
+ IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
+ one_queue),
+ gen_iq_handler:add_iq_handler(ejabberd_local, Host,
+ ?NS_TIME90, ?MODULE, process_local_iq90,
+ IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_local, Host,
+ ?NS_TIME, ?MODULE, process_local_iq, IQDisc).
stop(Host) ->
- %% TODO: Remove the next line once XEP-0090 is Obsolete
- gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_TIME90),
- gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_TIME).
+ gen_iq_handler:remove_iq_handler(ejabberd_local, Host,
+ ?NS_TIME90),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, Host,
+ ?NS_TIME).
%% TODO: Remove this function once XEP-0090 is Obsolete
-process_local_iq90(_From, _To, #iq{type = Type, sub_el = SubEl} = IQ) ->
+process_local_iq90(_From, _To,
+ #iq{type = Type, sub_el = SubEl} = IQ) ->
case Type of
- set ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
- get ->
- UTC = jlib:timestamp_to_iso(calendar:universal_time()),
- IQ#iq{type = result,
- sub_el = [{xmlelement, "query",
- [{"xmlns", ?NS_TIME90}],
- [{xmlelement, "utc", [],
- [{xmlcdata, UTC}]}]}]}
+ set ->
+ IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+ get ->
+ UTC = jlib:timestamp_to_iso(calendar:universal_time()),
+ IQ#iq{type = result,
+ sub_el =
+ [#xmlel{name = <<"query">>,
+ attrs = [{<<"xmlns">>, ?NS_TIME90}],
+ children =
+ [#xmlel{name = <<"utc">>, attrs = [],
+ children = [{xmlcdata, UTC}]}]}]}
end.
-process_local_iq(_From, _To, #iq{type = Type, sub_el = SubEl} = IQ) ->
+process_local_iq(_From, _To,
+ #iq{type = Type, sub_el = SubEl} = IQ) ->
case Type of
- set ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
- get ->
- Now = now(),
- Now_universal = calendar:now_to_universal_time(Now),
- Now_local = calendar:now_to_local_time(Now),
- {UTC, UTC_diff} = jlib:timestamp_to_iso(Now_universal, utc),
- Seconds_diff = calendar:datetime_to_gregorian_seconds(Now_local)
- - calendar:datetime_to_gregorian_seconds(Now_universal),
- {Hd, Md, _} = calendar:seconds_to_time(abs(Seconds_diff)),
- {_, TZO_diff} = jlib:timestamp_to_iso({{0, 0, 0}, {0, 0, 0}}, {sign(Seconds_diff), {Hd, Md}}),
- IQ#iq{type = result,
- sub_el = [{xmlelement, "time",
- [{"xmlns", ?NS_TIME}],
- [{xmlelement, "tzo", [],
- [{xmlcdata, TZO_diff}]},
- {xmlelement, "utc", [],
- [{xmlcdata, UTC ++ UTC_diff}]}]}]}
+ set ->
+ IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+ get ->
+ Now = now(),
+ Now_universal = calendar:now_to_universal_time(Now),
+ Now_local = calendar:now_to_local_time(Now),
+ {UTC, UTC_diff} = jlib:timestamp_to_iso(Now_universal,
+ utc),
+ Seconds_diff =
+ calendar:datetime_to_gregorian_seconds(Now_local) -
+ calendar:datetime_to_gregorian_seconds(Now_universal),
+ {Hd, Md, _} =
+ calendar:seconds_to_time(abs(Seconds_diff)),
+ {_, TZO_diff} = jlib:timestamp_to_iso({{0, 1, 1},
+ {0, 0, 0}},
+ {sign(Seconds_diff), {Hd, Md}}),
+ IQ#iq{type = result,
+ sub_el =
+ [#xmlel{name = <<"time">>,
+ attrs = [{<<"xmlns">>, ?NS_TIME}],
+ children =
+ [#xmlel{name = <<"tzo">>, attrs = [],
+ children = [{xmlcdata, TZO_diff}]},
+ #xmlel{name = <<"utc">>, attrs = [],
+ children =
+ [{xmlcdata,
+ <<UTC/binary,
+ UTC_diff/binary>>}]}]}]}
end.
-sign(N) when N < 0 -> "-";
-sign(_) -> "+".
+sign(N) when N < 0 -> <<"-">>;
+sign(_) -> <<"+">>.
diff --git a/src/mod_vcard.erl b/src/mod_vcard.erl
index 3b70fe21f..901098398 100644
--- a/src/mod_vcard.erl
+++ b/src/mod_vcard.erl
@@ -25,644 +25,668 @@
%%%----------------------------------------------------------------------
-module(mod_vcard).
+
-author('alexey@process-one.net').
-behaviour(gen_mod).
--export([start/2, init/3, stop/1,
- get_sm_features/5,
- process_local_iq/3,
- process_sm_iq/3,
- reindex_vcards/0,
- remove_user/2]).
+-export([start/2, init/3, stop/1, get_sm_features/5,
+ process_local_iq/3, process_sm_iq/3, reindex_vcards/0,
+ remove_user/2, export/1]).
-include("ejabberd.hrl").
--include("jlib.hrl").
+-include("jlib.hrl").
-define(JUD_MATCHES, 30).
--record(vcard_search, {us,
- user, luser,
- fn, lfn,
- family, lfamily,
- given, lgiven,
- middle, lmiddle,
- nickname, lnickname,
- bday, lbday,
- ctry, lctry,
- locality, llocality,
- email, lemail,
- orgname, lorgname,
- orgunit, lorgunit
- }).
--record(vcard, {us, vcard}).
+-record(vcard_search,
+ {us, user, luser, fn, lfn, family, lfamily, given,
+ lgiven, middle, lmiddle, nickname, lnickname, bday,
+ lbday, ctry, lctry, locality, llocality, email, lemail,
+ orgname, lorgname, orgunit, lorgunit}).
+
+-record(vcard, {us = {<<"">>, <<"">>} :: {binary(), binary()},
+ vcard = #xmlel{} :: xmlel()}).
-define(PROCNAME, ejabberd_mod_vcard).
start(Host, Opts) ->
case gen_mod:db_type(Opts) of
- mnesia ->
- mnesia:create_table(vcard,
- [{disc_only_copies, [node()]},
- {attributes,
- record_info(fields, vcard)}]),
- mnesia:create_table(vcard_search,
- [{disc_copies, [node()]},
- {attributes,
- record_info(fields, vcard_search)}]),
- update_tables(),
- mnesia:add_table_index(vcard_search, luser),
- mnesia:add_table_index(vcard_search, lfn),
- mnesia:add_table_index(vcard_search, lfamily),
- mnesia:add_table_index(vcard_search, lgiven),
- mnesia:add_table_index(vcard_search, lmiddle),
- mnesia:add_table_index(vcard_search, lnickname),
- mnesia:add_table_index(vcard_search, lbday),
- mnesia:add_table_index(vcard_search, lctry),
- mnesia:add_table_index(vcard_search, llocality),
- mnesia:add_table_index(vcard_search, lemail),
- mnesia:add_table_index(vcard_search, lorgname),
- mnesia:add_table_index(vcard_search, lorgunit);
- _ ->
- ok
+ mnesia ->
+ mnesia:create_table(vcard,
+ [{disc_only_copies, [node()]},
+ {attributes, record_info(fields, vcard)}]),
+ mnesia:create_table(vcard_search,
+ [{disc_copies, [node()]},
+ {attributes,
+ record_info(fields, vcard_search)}]),
+ update_tables(),
+ mnesia:add_table_index(vcard_search, luser),
+ mnesia:add_table_index(vcard_search, lfn),
+ mnesia:add_table_index(vcard_search, lfamily),
+ mnesia:add_table_index(vcard_search, lgiven),
+ mnesia:add_table_index(vcard_search, lmiddle),
+ mnesia:add_table_index(vcard_search, lnickname),
+ mnesia:add_table_index(vcard_search, lbday),
+ mnesia:add_table_index(vcard_search, lctry),
+ mnesia:add_table_index(vcard_search, llocality),
+ mnesia:add_table_index(vcard_search, lemail),
+ mnesia:add_table_index(vcard_search, lorgname),
+ mnesia:add_table_index(vcard_search, lorgunit);
+ _ -> ok
end,
- ejabberd_hooks:add(remove_user, Host,
- ?MODULE, remove_user, 50),
- IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
- gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_VCARD,
- ?MODULE, process_local_iq, IQDisc),
- gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_VCARD,
- ?MODULE, process_sm_iq, IQDisc),
- ejabberd_hooks:add(disco_sm_features, Host, ?MODULE, get_sm_features, 50),
- MyHost = gen_mod:get_opt_host(Host, Opts, "vjud.@HOST@"),
- Search = gen_mod:get_opt(search, Opts, true),
+ ejabberd_hooks:add(remove_user, Host, ?MODULE,
+ remove_user, 50),
+ IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
+ one_queue),
+ gen_iq_handler:add_iq_handler(ejabberd_local, Host,
+ ?NS_VCARD, ?MODULE, process_local_iq, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
+ ?NS_VCARD, ?MODULE, process_sm_iq, IQDisc),
+ ejabberd_hooks:add(disco_sm_features, Host, ?MODULE,
+ get_sm_features, 50),
+ MyHost = gen_mod:get_opt_host(Host, Opts,
+ <<"vjud.@HOST@">>),
+ Search = gen_mod:get_opt(search, Opts,
+ fun(B) when is_boolean(B) -> B end,
+ true),
register(gen_mod:get_module_proc(Host, ?PROCNAME),
spawn(?MODULE, init, [MyHost, Host, Search])).
-
init(Host, ServerHost, Search) ->
case Search of
- false ->
- loop(Host, ServerHost);
- _ ->
- ejabberd_router:register_route(Host),
- loop(Host, ServerHost)
+ false -> loop(Host, ServerHost);
+ _ ->
+ ejabberd_router:register_route(Host),
+ loop(Host, ServerHost)
end.
loop(Host, ServerHost) ->
receive
- {route, From, To, Packet} ->
- case catch do_route(ServerHost, From, To, Packet) of
- {'EXIT', Reason} ->
- ?ERROR_MSG("~p", [Reason]);
- _ ->
- ok
- end,
- loop(Host, ServerHost);
- stop ->
- ejabberd_router:unregister_route(Host),
- ok;
- _ ->
- loop(Host, ServerHost)
+ {route, From, To, Packet} ->
+ case catch do_route(ServerHost, From, To, Packet) of
+ {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]);
+ _ -> ok
+ end,
+ loop(Host, ServerHost);
+ stop -> ejabberd_router:unregister_route(Host), ok;
+ _ -> loop(Host, ServerHost)
end.
stop(Host) ->
- ejabberd_hooks:delete(remove_user, Host,
- ?MODULE, remove_user, 50),
- gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_VCARD),
- gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_VCARD),
- ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, get_sm_features, 50),
+ ejabberd_hooks:delete(remove_user, Host, ?MODULE,
+ remove_user, 50),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, Host,
+ ?NS_VCARD),
+ gen_iq_handler:remove_iq_handler(ejabberd_sm, Host,
+ ?NS_VCARD),
+ ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE,
+ get_sm_features, 50),
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
Proc ! stop,
{wait, Proc}.
-get_sm_features({error, _Error} = Acc, _From, _To, _Node, _Lang) ->
+get_sm_features({error, _Error} = Acc, _From, _To,
+ _Node, _Lang) ->
Acc;
-
get_sm_features(Acc, _From, _To, Node, _Lang) ->
case Node of
- [] ->
- case Acc of
- {result, Features} ->
- {result, [?NS_DISCO_INFO, ?NS_VCARD | Features]};
- empty ->
- {result, [?NS_DISCO_INFO, ?NS_VCARD]}
- end;
- _ ->
- Acc
- end.
-
-process_local_iq(_From, _To, #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
- case Type of
- set ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
- get ->
- IQ#iq{type = result,
- sub_el = [{xmlelement, "vCard",
- [{"xmlns", ?NS_VCARD}],
- [{xmlelement, "FN", [],
- [{xmlcdata, "ejabberd"}]},
- {xmlelement, "URL", [],
- [{xmlcdata, ?EJABBERD_URI}]},
- {xmlelement, "DESC", [],
- [{xmlcdata,
- translate:translate(
- Lang,
- "Erlang Jabber Server") ++
- "\nCopyright (c) 2002-2013 ProcessOne"}]},
- {xmlelement, "BDAY", [],
- [{xmlcdata, "2002-11-16"}]}
- ]}]}
+ <<"">> ->
+ case Acc of
+ {result, Features} ->
+ {result, [?NS_DISCO_INFO, ?NS_VCARD | Features]};
+ empty -> {result, [?NS_DISCO_INFO, ?NS_VCARD]}
+ end;
+ _ -> Acc
end.
+process_local_iq(_From, _To,
+ #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
+ case Type of
+ set ->
+ IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+ get ->
+ IQ#iq{type = result,
+ sub_el =
+ [#xmlel{name = <<"vCard">>,
+ attrs = [{<<"xmlns">>, ?NS_VCARD}],
+ children =
+ [#xmlel{name = <<"FN">>, attrs = [],
+ children =
+ [{xmlcdata, <<"ejabberd">>}]},
+ #xmlel{name = <<"URL">>, attrs = [],
+ children = [{xmlcdata, ?EJABBERD_URI}]},
+ #xmlel{name = <<"DESC">>, attrs = [],
+ children =
+ [{xmlcdata,
+ <<(translate:translate(Lang,
+ <<"Erlang Jabber Server">>))/binary,
+ "\nCopyright (c) 2002-2013 ProcessOne">>}]},
+ #xmlel{name = <<"BDAY">>, attrs = [],
+ children =
+ [{xmlcdata, <<"2002-11-16">>}]}]}]}
+ end.
-process_sm_iq(From, To, #iq{type = Type, sub_el = SubEl} = IQ) ->
+process_sm_iq(From, To,
+ #iq{type = Type, sub_el = SubEl} = IQ) ->
case Type of
- set ->
- #jid{user = User, lserver = LServer} = From,
- case lists:member(LServer, ?MYHOSTS) of
- true ->
- set_vcard(User, LServer, SubEl),
- IQ#iq{type = result, sub_el = []};
- false ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}
- end;
- get ->
- #jid{luser = LUser, lserver = LServer} = To,
- case get_vcard(LUser, LServer) of
- error ->
- IQ#iq{type = error,
- sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]};
- Els ->
- IQ#iq{type = result, sub_el = Els}
- end
+ set ->
+ #jid{user = User, lserver = LServer} = From,
+ case lists:member(LServer, ?MYHOSTS) of
+ true ->
+ set_vcard(User, LServer, SubEl),
+ IQ#iq{type = result, sub_el = []};
+ false ->
+ IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}
+ end;
+ get ->
+ #jid{luser = LUser, lserver = LServer} = To,
+ case get_vcard(LUser, LServer) of
+ error ->
+ IQ#iq{type = error,
+ sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]};
+ Els -> IQ#iq{type = result, sub_el = Els}
+ end
end.
get_vcard(LUser, LServer) ->
- get_vcard(LUser, LServer, gen_mod:db_type(LServer, ?MODULE)).
+ get_vcard(LUser, LServer,
+ gen_mod:db_type(LServer, ?MODULE)).
get_vcard(LUser, LServer, mnesia) ->
US = {LUser, LServer},
- F = fun() ->
- mnesia:read({vcard, US})
- end,
+ F = fun () -> mnesia:read({vcard, US}) end,
case mnesia:transaction(F) of
- {atomic, Rs} ->
- lists:map(fun(R) ->
- R#vcard.vcard
- end, Rs);
- {aborted, _Reason} ->
- error
+ {atomic, Rs} ->
+ lists:map(fun (R) -> R#vcard.vcard end, Rs);
+ {aborted, _Reason} -> error
end;
get_vcard(LUser, LServer, odbc) ->
Username = ejabberd_odbc:escape(LUser),
case catch odbc_queries:get_vcard(LServer, Username) of
- {selected, ["vcard"], [{SVCARD}]} ->
- case xml_stream:parse_element(SVCARD) of
- {error, _Reason} ->
- error;
- VCARD ->
- [VCARD]
- end;
- {selected, ["vcard"], []} ->
- [];
- _ ->
- error
+ {selected, [<<"vcard">>], [[SVCARD]]} ->
+ case xml_stream:parse_element(SVCARD) of
+ {error, _Reason} -> error;
+ VCARD -> [VCARD]
+ end;
+ {selected, [<<"vcard">>], []} -> [];
+ _ -> error
end.
set_vcard(User, LServer, VCARD) ->
- FN = xml:get_path_s(VCARD, [{elem, "FN"}, cdata]),
- Family = xml:get_path_s(VCARD, [{elem, "N"}, {elem, "FAMILY"}, cdata]),
- Given = xml:get_path_s(VCARD, [{elem, "N"}, {elem, "GIVEN"}, cdata]),
- Middle = xml:get_path_s(VCARD, [{elem, "N"}, {elem, "MIDDLE"}, cdata]),
- Nickname = xml:get_path_s(VCARD, [{elem, "NICKNAME"}, cdata]),
- BDay = xml:get_path_s(VCARD, [{elem, "BDAY"}, cdata]),
- CTRY = xml:get_path_s(VCARD, [{elem, "ADR"}, {elem, "CTRY"}, cdata]),
- Locality = xml:get_path_s(VCARD, [{elem, "ADR"}, {elem, "LOCALITY"},cdata]),
- EMail1 = xml:get_path_s(VCARD, [{elem, "EMAIL"}, {elem, "USERID"},cdata]),
- EMail2 = xml:get_path_s(VCARD, [{elem, "EMAIL"}, cdata]),
- OrgName = xml:get_path_s(VCARD, [{elem, "ORG"}, {elem, "ORGNAME"}, cdata]),
- OrgUnit = xml:get_path_s(VCARD, [{elem, "ORG"}, {elem, "ORGUNIT"}, cdata]),
+ FN = xml:get_path_s(VCARD, [{elem, <<"FN">>}, cdata]),
+ Family = xml:get_path_s(VCARD,
+ [{elem, <<"N">>}, {elem, <<"FAMILY">>}, cdata]),
+ Given = xml:get_path_s(VCARD,
+ [{elem, <<"N">>}, {elem, <<"GIVEN">>}, cdata]),
+ Middle = xml:get_path_s(VCARD,
+ [{elem, <<"N">>}, {elem, <<"MIDDLE">>}, cdata]),
+ Nickname = xml:get_path_s(VCARD,
+ [{elem, <<"NICKNAME">>}, cdata]),
+ BDay = xml:get_path_s(VCARD,
+ [{elem, <<"BDAY">>}, cdata]),
+ CTRY = xml:get_path_s(VCARD,
+ [{elem, <<"ADR">>}, {elem, <<"CTRY">>}, cdata]),
+ Locality = xml:get_path_s(VCARD,
+ [{elem, <<"ADR">>}, {elem, <<"LOCALITY">>},
+ cdata]),
+ EMail1 = xml:get_path_s(VCARD,
+ [{elem, <<"EMAIL">>}, {elem, <<"USERID">>}, cdata]),
+ EMail2 = xml:get_path_s(VCARD,
+ [{elem, <<"EMAIL">>}, cdata]),
+ OrgName = xml:get_path_s(VCARD,
+ [{elem, <<"ORG">>}, {elem, <<"ORGNAME">>}, cdata]),
+ OrgUnit = xml:get_path_s(VCARD,
+ [{elem, <<"ORG">>}, {elem, <<"ORGUNIT">>}, cdata]),
EMail = case EMail1 of
- "" ->
- EMail2;
- _ ->
- EMail1
+ <<"">> -> EMail2;
+ _ -> EMail1
end,
-
- LUser = jlib:nodeprep(User),
- LFN = string2lower(FN),
- LFamily = string2lower(Family),
- LGiven = string2lower(Given),
- LMiddle = string2lower(Middle),
+ LUser = jlib:nodeprep(User),
+ LFN = string2lower(FN),
+ LFamily = string2lower(Family),
+ LGiven = string2lower(Given),
+ LMiddle = string2lower(Middle),
LNickname = string2lower(Nickname),
- LBDay = string2lower(BDay),
- LCTRY = string2lower(CTRY),
+ LBDay = string2lower(BDay),
+ LCTRY = string2lower(CTRY),
LLocality = string2lower(Locality),
- LEMail = string2lower(EMail),
- LOrgName = string2lower(OrgName),
- LOrgUnit = string2lower(OrgUnit),
-
- if
- (LUser == error) or
- (LFN == error) or
- (LFamily == error) or
- (LGiven == error) or
- (LMiddle == error) or
- (LNickname == error) or
- (LBDay == error) or
- (LCTRY == error) or
- (LLocality == error) or
- (LEMail == error) or
- (LOrgName == error) or
- (LOrgUnit == error) ->
- {error, badarg};
- true ->
- case gen_mod:db_type(LServer, ?MODULE) of
- mnesia ->
- US = {LUser, LServer},
- F = fun() ->
- mnesia:write(#vcard{us = US, vcard = VCARD}),
- mnesia:write(
- #vcard_search{us = US,
- user = {User, LServer},
- luser = LUser,
- fn = FN, lfn = LFN,
- family = Family, lfamily = LFamily,
- given = Given, lgiven = LGiven,
- middle = Middle, lmiddle = LMiddle,
- nickname = Nickname, lnickname = LNickname,
- bday = BDay, lbday = LBDay,
- ctry = CTRY, lctry = LCTRY,
- locality = Locality, llocality = LLocality,
- email = EMail, lemail = LEMail,
- orgname = OrgName, lorgname = LOrgName,
- orgunit = OrgUnit, lorgunit = LOrgUnit
- })
- end,
- mnesia:transaction(F);
- odbc ->
- Username = ejabberd_odbc:escape(User),
- LUsername = ejabberd_odbc:escape(LUser),
- SVCARD = ejabberd_odbc:escape(
- xml:element_to_binary(VCARD)),
-
- SFN = ejabberd_odbc:escape(FN),
- SLFN = ejabberd_odbc:escape(LFN),
- SFamily = ejabberd_odbc:escape(Family),
- SLFamily = ejabberd_odbc:escape(LFamily),
- SGiven = ejabberd_odbc:escape(Given),
- SLGiven = ejabberd_odbc:escape(LGiven),
- SMiddle = ejabberd_odbc:escape(Middle),
- SLMiddle = ejabberd_odbc:escape(LMiddle),
- SNickname = ejabberd_odbc:escape(Nickname),
- SLNickname = ejabberd_odbc:escape(LNickname),
- SBDay = ejabberd_odbc:escape(BDay),
- SLBDay = ejabberd_odbc:escape(LBDay),
- SCTRY = ejabberd_odbc:escape(CTRY),
- SLCTRY = ejabberd_odbc:escape(LCTRY),
- SLocality = ejabberd_odbc:escape(Locality),
- SLLocality = ejabberd_odbc:escape(LLocality),
- SEMail = ejabberd_odbc:escape(EMail),
- SLEMail = ejabberd_odbc:escape(LEMail),
- SOrgName = ejabberd_odbc:escape(OrgName),
- SLOrgName = ejabberd_odbc:escape(LOrgName),
- SOrgUnit = ejabberd_odbc:escape(OrgUnit),
- SLOrgUnit = ejabberd_odbc:escape(LOrgUnit),
-
- odbc_queries:set_vcard(LServer, LUsername, SBDay, SCTRY, SEMail,
- SFN, SFamily, SGiven, SLBDay, SLCTRY,
- SLEMail, SLFN, SLFamily, SLGiven,
- SLLocality, SLMiddle, SLNickname,
- SLOrgName, SLOrgUnit, SLocality,
- SMiddle, SNickname, SOrgName,
- SOrgUnit, SVCARD, Username)
- end,
- ejabberd_hooks:run(vcard_set, LServer, [LUser, LServer, VCARD])
+ LEMail = string2lower(EMail),
+ LOrgName = string2lower(OrgName),
+ LOrgUnit = string2lower(OrgUnit),
+ if (LUser == error) ->
+ {error, badarg};
+ true ->
+ case gen_mod:db_type(LServer, ?MODULE) of
+ mnesia ->
+ US = {LUser, LServer},
+ F = fun () ->
+ mnesia:write(#vcard{us = US, vcard = VCARD}),
+ mnesia:write(#vcard_search{us = US,
+ user = {User, LServer},
+ luser = LUser, fn = FN,
+ lfn = LFN,
+ family = Family,
+ lfamily = LFamily,
+ given = Given,
+ lgiven = LGiven,
+ middle = Middle,
+ lmiddle = LMiddle,
+ nickname = Nickname,
+ lnickname = LNickname,
+ bday = BDay,
+ lbday = LBDay,
+ ctry = CTRY,
+ lctry = LCTRY,
+ locality = Locality,
+ llocality = LLocality,
+ email = EMail,
+ lemail = LEMail,
+ orgname = OrgName,
+ lorgname = LOrgName,
+ orgunit = OrgUnit,
+ lorgunit = LOrgUnit})
+ end,
+ mnesia:transaction(F);
+ odbc ->
+ Username = ejabberd_odbc:escape(User),
+ LUsername = ejabberd_odbc:escape(LUser),
+ SVCARD =
+ ejabberd_odbc:escape(xml:element_to_binary(VCARD)),
+ SFN = ejabberd_odbc:escape(FN),
+ SLFN = ejabberd_odbc:escape(LFN),
+ SFamily = ejabberd_odbc:escape(Family),
+ SLFamily = ejabberd_odbc:escape(LFamily),
+ SGiven = ejabberd_odbc:escape(Given),
+ SLGiven = ejabberd_odbc:escape(LGiven),
+ SMiddle = ejabberd_odbc:escape(Middle),
+ SLMiddle = ejabberd_odbc:escape(LMiddle),
+ SNickname = ejabberd_odbc:escape(Nickname),
+ SLNickname = ejabberd_odbc:escape(LNickname),
+ SBDay = ejabberd_odbc:escape(BDay),
+ SLBDay = ejabberd_odbc:escape(LBDay),
+ SCTRY = ejabberd_odbc:escape(CTRY),
+ SLCTRY = ejabberd_odbc:escape(LCTRY),
+ SLocality = ejabberd_odbc:escape(Locality),
+ SLLocality = ejabberd_odbc:escape(LLocality),
+ SEMail = ejabberd_odbc:escape(EMail),
+ SLEMail = ejabberd_odbc:escape(LEMail),
+ SOrgName = ejabberd_odbc:escape(OrgName),
+ SLOrgName = ejabberd_odbc:escape(LOrgName),
+ SOrgUnit = ejabberd_odbc:escape(OrgUnit),
+ SLOrgUnit = ejabberd_odbc:escape(LOrgUnit),
+ odbc_queries:set_vcard(LServer, LUsername, SBDay, SCTRY,
+ SEMail, SFN, SFamily, SGiven, SLBDay,
+ SLCTRY, SLEMail, SLFN, SLFamily,
+ SLGiven, SLLocality, SLMiddle,
+ SLNickname, SLOrgName, SLOrgUnit,
+ SLocality, SMiddle, SNickname, SOrgName,
+ SOrgUnit, SVCARD, Username)
+ end,
+ ejabberd_hooks:run(vcard_set, LServer,
+ [LUser, LServer, VCARD])
end.
string2lower(String) ->
case stringprep:tolower(String) of
- Lower when is_list(Lower) -> Lower;
- error -> string:to_lower(String)
+ Lower when is_binary(Lower) -> Lower;
+ error -> str:to_lower(String)
end.
-define(TLFIELD(Type, Label, Var),
- {xmlelement, "field", [{"type", Type},
- {"label", translate:translate(Lang, Label)},
- {"var", Var}], []}).
-
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"type">>, Type},
+ {<<"label">>, translate:translate(Lang, Label)},
+ {<<"var">>, Var}],
+ children = []}).
-define(FORM(JID),
- [{xmlelement, "instructions", [],
- [{xmlcdata, translate:translate(Lang, "You need an x:data capable client to search")}]},
- {xmlelement, "x", [{"xmlns", ?NS_XDATA}, {"type", "form"}],
- [{xmlelement, "title", [],
- [{xmlcdata, translate:translate(Lang, "Search users in ") ++
- jlib:jid_to_string(JID)}]},
- {xmlelement, "instructions", [],
- [{xmlcdata, translate:translate(Lang, "Fill in the form to search "
- "for any matching Jabber User "
- "(Add * to the end of field to "
- "match substring)")}]},
- ?TLFIELD("text-single", "User", "user"),
- ?TLFIELD("text-single", "Full Name", "fn"),
- ?TLFIELD("text-single", "Name", "first"),
- ?TLFIELD("text-single", "Middle Name", "middle"),
- ?TLFIELD("text-single", "Family Name", "last"),
- ?TLFIELD("text-single", "Nickname", "nick"),
- ?TLFIELD("text-single", "Birthday", "bday"),
- ?TLFIELD("text-single", "Country", "ctry"),
- ?TLFIELD("text-single", "City", "locality"),
- ?TLFIELD("text-single", "Email", "email"),
- ?TLFIELD("text-single", "Organization Name", "orgname"),
- ?TLFIELD("text-single", "Organization Unit", "orgunit")
- ]}]).
-
-
-
+ [#xmlel{name = <<"instructions">>, attrs = [],
+ children =
+ [{xmlcdata,
+ translate:translate(Lang,
+ <<"You need an x:data capable client to "
+ "search">>)}]},
+ #xmlel{name = <<"x">>,
+ attrs =
+ [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"form">>}],
+ children =
+ [#xmlel{name = <<"title">>, attrs = [],
+ children =
+ [{xmlcdata,
+ <<(translate:translate(Lang,
+ <<"Search users in ">>))/binary,
+ (jlib:jid_to_string(JID))/binary>>}]},
+ #xmlel{name = <<"instructions">>, attrs = [],
+ children =
+ [{xmlcdata,
+ translate:translate(Lang,
+ <<"Fill in the form to search for any matching "
+ "Jabber User (Add * to the end of field "
+ "to match substring)">>)}]},
+ ?TLFIELD(<<"text-single">>, <<"User">>, <<"user">>),
+ ?TLFIELD(<<"text-single">>, <<"Full Name">>, <<"fn">>),
+ ?TLFIELD(<<"text-single">>, <<"Name">>, <<"first">>),
+ ?TLFIELD(<<"text-single">>, <<"Middle Name">>,
+ <<"middle">>),
+ ?TLFIELD(<<"text-single">>, <<"Family Name">>,
+ <<"last">>),
+ ?TLFIELD(<<"text-single">>, <<"Nickname">>, <<"nick">>),
+ ?TLFIELD(<<"text-single">>, <<"Birthday">>, <<"bday">>),
+ ?TLFIELD(<<"text-single">>, <<"Country">>, <<"ctry">>),
+ ?TLFIELD(<<"text-single">>, <<"City">>, <<"locality">>),
+ ?TLFIELD(<<"text-single">>, <<"Email">>, <<"email">>),
+ ?TLFIELD(<<"text-single">>, <<"Organization Name">>,
+ <<"orgname">>),
+ ?TLFIELD(<<"text-single">>, <<"Organization Unit">>,
+ <<"orgunit">>)]}]).
do_route(ServerHost, From, To, Packet) ->
#jid{user = User, resource = Resource} = To,
- if
- (User /= "") or (Resource /= "") ->
- Err = jlib:make_error_reply(Packet, ?ERR_SERVICE_UNAVAILABLE),
- ejabberd_router:route(To, From, Err);
- true ->
- IQ = jlib:iq_query_info(Packet),
- case IQ of
- #iq{type = Type, xmlns = ?NS_SEARCH, lang = Lang, sub_el = SubEl} ->
- case Type of
- set ->
- XDataEl = find_xdata_el(SubEl),
- case XDataEl of
- false ->
- Err = jlib:make_error_reply(
- Packet, ?ERR_BAD_REQUEST),
- ejabberd_router:route(To, From, Err);
- _ ->
- XData = jlib:parse_xdata_submit(XDataEl),
- case XData of
- invalid ->
- Err = jlib:make_error_reply(
- Packet,
- ?ERR_BAD_REQUEST),
- ejabberd_router:route(To, From,
- Err);
- _ ->
- ResIQ =
- IQ#iq{
- type = result,
- sub_el =
- [{xmlelement,
- "query",
- [{"xmlns", ?NS_SEARCH}],
- [{xmlelement, "x",
- [{"xmlns", ?NS_XDATA},
- {"type", "result"}],
- search_result(Lang, To, ServerHost, XData)
- }]}]},
- ejabberd_router:route(
- To, From, jlib:iq_to_xml(ResIQ))
- end
- end;
- get ->
- ResIQ = IQ#iq{type = result,
- sub_el = [{xmlelement,
- "query",
- [{"xmlns", ?NS_SEARCH}],
- ?FORM(To)
- }]},
- ejabberd_router:route(To,
- From,
- jlib:iq_to_xml(ResIQ))
- end;
- #iq{type = Type, xmlns = ?NS_DISCO_INFO, lang = Lang} ->
- case Type of
- set ->
- Err = jlib:make_error_reply(
- Packet, ?ERR_NOT_ALLOWED),
- ejabberd_router:route(To, From, Err);
- get ->
- Info = ejabberd_hooks:run_fold(
- disco_info, ServerHost, [],
- [ServerHost, ?MODULE, "", ""]),
- ResIQ =
- IQ#iq{type = result,
- sub_el = [{xmlelement,
- "query",
- [{"xmlns", ?NS_DISCO_INFO}],
- [{xmlelement, "identity",
- [{"category", "directory"},
- {"type", "user"},
- {"name",
- translate:translate(Lang, "vCard User Search")}],
- []},
- {xmlelement, "feature",
- [{"var", ?NS_DISCO_INFO}], []},
- {xmlelement, "feature",
- [{"var", ?NS_SEARCH}], []},
- {xmlelement, "feature",
- [{"var", ?NS_VCARD}], []}
- ] ++ Info
- }]},
- ejabberd_router:route(To,
- From,
- jlib:iq_to_xml(ResIQ))
- end;
- #iq{type = Type, xmlns = ?NS_DISCO_ITEMS} ->
- case Type of
- set ->
- Err = jlib:make_error_reply(
- Packet, ?ERR_NOT_ALLOWED),
- ejabberd_router:route(To, From, Err);
- get ->
- ResIQ =
- IQ#iq{type = result,
- sub_el = [{xmlelement,
- "query",
- [{"xmlns", ?NS_DISCO_ITEMS}],
- []}]},
- ejabberd_router:route(To,
- From,
- jlib:iq_to_xml(ResIQ))
- end;
- #iq{type = get, xmlns = ?NS_VCARD, lang = Lang} ->
- ResIQ =
- IQ#iq{type = result,
- sub_el = [{xmlelement,
- "vCard",
- [{"xmlns", ?NS_VCARD}],
- iq_get_vcard(Lang)}]},
- ejabberd_router:route(To,
- From,
- jlib:iq_to_xml(ResIQ));
- _ ->
- Err = jlib:make_error_reply(Packet,
- ?ERR_SERVICE_UNAVAILABLE),
- ejabberd_router:route(To, From, Err)
- end
+ if (User /= <<"">>) or (Resource /= <<"">>) ->
+ Err = jlib:make_error_reply(Packet,
+ ?ERR_SERVICE_UNAVAILABLE),
+ ejabberd_router:route(To, From, Err);
+ true ->
+ IQ = jlib:iq_query_info(Packet),
+ case IQ of
+ #iq{type = Type, xmlns = ?NS_SEARCH, lang = Lang,
+ sub_el = SubEl} ->
+ case Type of
+ set ->
+ XDataEl = find_xdata_el(SubEl),
+ case XDataEl of
+ false ->
+ Err = jlib:make_error_reply(Packet,
+ ?ERR_BAD_REQUEST),
+ ejabberd_router:route(To, From, Err);
+ _ ->
+ XData = jlib:parse_xdata_submit(XDataEl),
+ case XData of
+ invalid ->
+ Err = jlib:make_error_reply(Packet,
+ ?ERR_BAD_REQUEST),
+ ejabberd_router:route(To, From, Err);
+ _ ->
+ ResIQ = IQ#iq{type = result,
+ sub_el =
+ [#xmlel{name = <<"query">>,
+ attrs =
+ [{<<"xmlns">>,
+ ?NS_SEARCH}],
+ children =
+ [#xmlel{name =
+ <<"x">>,
+ attrs =
+ [{<<"xmlns">>,
+ ?NS_XDATA},
+ {<<"type">>,
+ <<"result">>}],
+ children
+ =
+ search_result(Lang,
+ To,
+ ServerHost,
+ XData)}]}]},
+ ejabberd_router:route(To, From,
+ jlib:iq_to_xml(ResIQ))
+ end
+ end;
+ get ->
+ ResIQ = IQ#iq{type = result,
+ sub_el =
+ [#xmlel{name = <<"query">>,
+ attrs =
+ [{<<"xmlns">>,
+ ?NS_SEARCH}],
+ children = ?FORM(To)}]},
+ ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ))
+ end;
+ #iq{type = Type, xmlns = ?NS_DISCO_INFO, lang = Lang} ->
+ case Type of
+ set ->
+ Err = jlib:make_error_reply(Packet, ?ERR_NOT_ALLOWED),
+ ejabberd_router:route(To, From, Err);
+ get ->
+ Info = ejabberd_hooks:run_fold(disco_info, ServerHost,
+ [],
+ [ServerHost, ?MODULE,
+ <<"">>, <<"">>]),
+ ResIQ = IQ#iq{type = result,
+ sub_el =
+ [#xmlel{name = <<"query">>,
+ attrs =
+ [{<<"xmlns">>,
+ ?NS_DISCO_INFO}],
+ children =
+ [#xmlel{name =
+ <<"identity">>,
+ attrs =
+ [{<<"category">>,
+ <<"directory">>},
+ {<<"type">>,
+ <<"user">>},
+ {<<"name">>,
+ translate:translate(Lang,
+ <<"vCard User Search">>)}],
+ children = []},
+ #xmlel{name =
+ <<"feature">>,
+ attrs =
+ [{<<"var">>,
+ ?NS_DISCO_INFO}],
+ children = []},
+ #xmlel{name =
+ <<"feature">>,
+ attrs =
+ [{<<"var">>,
+ ?NS_SEARCH}],
+ children = []},
+ #xmlel{name =
+ <<"feature">>,
+ attrs =
+ [{<<"var">>,
+ ?NS_VCARD}],
+ children = []}]
+ ++ Info}]},
+ ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ))
+ end;
+ #iq{type = Type, xmlns = ?NS_DISCO_ITEMS} ->
+ case Type of
+ set ->
+ Err = jlib:make_error_reply(Packet, ?ERR_NOT_ALLOWED),
+ ejabberd_router:route(To, From, Err);
+ get ->
+ ResIQ = IQ#iq{type = result,
+ sub_el =
+ [#xmlel{name = <<"query">>,
+ attrs =
+ [{<<"xmlns">>,
+ ?NS_DISCO_ITEMS}],
+ children = []}]},
+ ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ))
+ end;
+ #iq{type = get, xmlns = ?NS_VCARD, lang = Lang} ->
+ ResIQ = IQ#iq{type = result,
+ sub_el =
+ [#xmlel{name = <<"vCard">>,
+ attrs = [{<<"xmlns">>, ?NS_VCARD}],
+ children = iq_get_vcard(Lang)}]},
+ ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ));
+ _ ->
+ Err = jlib:make_error_reply(Packet,
+ ?ERR_SERVICE_UNAVAILABLE),
+ ejabberd_router:route(To, From, Err)
+ end
end.
iq_get_vcard(Lang) ->
- [{xmlelement, "FN", [],
- [{xmlcdata, "ejabberd/mod_vcard"}]},
- {xmlelement, "URL", [],
- [{xmlcdata, ?EJABBERD_URI}]},
- {xmlelement, "DESC", [],
- [{xmlcdata, translate:translate(
- Lang,
- "ejabberd vCard module") ++
- "\nCopyright (c) 2003-2013 ProcessOne"}]}].
-
-find_xdata_el({xmlelement, _Name, _Attrs, SubEls}) ->
+ [#xmlel{name = <<"FN">>, attrs = [],
+ children = [{xmlcdata, <<"ejabberd/mod_vcard">>}]},
+ #xmlel{name = <<"URL">>, attrs = [],
+ children = [{xmlcdata, ?EJABBERD_URI}]},
+ #xmlel{name = <<"DESC">>, attrs = [],
+ children =
+ [{xmlcdata,
+ <<(translate:translate(Lang,
+ <<"ejabberd vCard module">>))/binary,
+ "\nCopyright (c) 2003-2013 ProcessOne">>}]}].
+
+find_xdata_el(#xmlel{children = SubEls}) ->
find_xdata_el1(SubEls).
-find_xdata_el1([]) ->
- false;
-find_xdata_el1([{xmlelement, Name, Attrs, SubEls} | Els]) ->
- case xml:get_attr_s("xmlns", Attrs) of
- ?NS_XDATA ->
- {xmlelement, Name, Attrs, SubEls};
- _ ->
- find_xdata_el1(Els)
+find_xdata_el1([]) -> false;
+find_xdata_el1([#xmlel{name = Name, attrs = Attrs,
+ children = SubEls}
+ | Els]) ->
+ case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ ?NS_XDATA ->
+ #xmlel{name = Name, attrs = Attrs, children = SubEls};
+ _ -> find_xdata_el1(Els)
end;
-find_xdata_el1([_ | Els]) ->
- find_xdata_el1(Els).
+find_xdata_el1([_ | Els]) -> find_xdata_el1(Els).
-define(LFIELD(Label, Var),
- {xmlelement, "field", [{"label", translate:translate(Lang, Label)},
- {"var", Var}], []}).
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"label">>, translate:translate(Lang, Label)},
+ {<<"var">>, Var}],
+ children = []}).
search_result(Lang, JID, ServerHost, Data) ->
- [{xmlelement, "title", [],
- [{xmlcdata, translate:translate(Lang, "Search Results for ") ++
- jlib:jid_to_string(JID)}]},
- {xmlelement, "reported", [],
- [?TLFIELD("text-single", "Jabber ID", "jid"),
- ?TLFIELD("text-single", "Full Name", "fn"),
- ?TLFIELD("text-single", "Name", "first"),
- ?TLFIELD("text-single", "Middle Name", "middle"),
- ?TLFIELD("text-single", "Family Name", "last"),
- ?TLFIELD("text-single", "Nickname", "nick"),
- ?TLFIELD("text-single", "Birthday", "bday"),
- ?TLFIELD("text-single", "Country", "ctry"),
- ?TLFIELD("text-single", "City", "locality"),
- ?TLFIELD("text-single", "Email", "email"),
- ?TLFIELD("text-single", "Organization Name", "orgname"),
- ?TLFIELD("text-single", "Organization Unit", "orgunit")
- ]}] ++ lists:map(fun(R) -> record_to_item(ServerHost, R) end,
- search(ServerHost, Data)).
+ [#xmlel{name = <<"title">>, attrs = [],
+ children =
+ [{xmlcdata,
+ <<(translate:translate(Lang,
+ <<"Search Results for ">>))/binary,
+ (jlib:jid_to_string(JID))/binary>>}]},
+ #xmlel{name = <<"reported">>, attrs = [],
+ children =
+ [?TLFIELD(<<"text-single">>, <<"Jabber ID">>,
+ <<"jid">>),
+ ?TLFIELD(<<"text-single">>, <<"Full Name">>, <<"fn">>),
+ ?TLFIELD(<<"text-single">>, <<"Name">>, <<"first">>),
+ ?TLFIELD(<<"text-single">>, <<"Middle Name">>,
+ <<"middle">>),
+ ?TLFIELD(<<"text-single">>, <<"Family Name">>,
+ <<"last">>),
+ ?TLFIELD(<<"text-single">>, <<"Nickname">>, <<"nick">>),
+ ?TLFIELD(<<"text-single">>, <<"Birthday">>, <<"bday">>),
+ ?TLFIELD(<<"text-single">>, <<"Country">>, <<"ctry">>),
+ ?TLFIELD(<<"text-single">>, <<"City">>, <<"locality">>),
+ ?TLFIELD(<<"text-single">>, <<"Email">>, <<"email">>),
+ ?TLFIELD(<<"text-single">>, <<"Organization Name">>,
+ <<"orgname">>),
+ ?TLFIELD(<<"text-single">>, <<"Organization Unit">>,
+ <<"orgunit">>)]}]
+ ++
+ lists:map(fun (R) -> record_to_item(ServerHost, R) end,
+ search(ServerHost, Data)).
-define(FIELD(Var, Val),
- {xmlelement, "field", [{"var", Var}],
- [{xmlelement, "value", [],
- [{xmlcdata, Val}]}]}).
-
-record_to_item(LServer, {Username, FN, Family, Given, Middle,
- Nickname, BDay, CTRY, Locality,
- EMail, OrgName, OrgUnit}) ->
- {xmlelement, "item", [],
- [
- ?FIELD("jid", Username ++ "@" ++ LServer),
- ?FIELD("fn", FN),
- ?FIELD("last", Family),
- ?FIELD("first", Given),
- ?FIELD("middle", Middle),
- ?FIELD("nick", Nickname),
- ?FIELD("bday", BDay),
- ?FIELD("ctry", CTRY),
- ?FIELD("locality", Locality),
- ?FIELD("email", EMail),
- ?FIELD("orgname", OrgName),
- ?FIELD("orgunit", OrgUnit)
- ]
- };
+ #xmlel{name = <<"field">>, attrs = [{<<"var">>, Var}],
+ children =
+ [#xmlel{name = <<"value">>, attrs = [],
+ children = [{xmlcdata, Val}]}]}).
+
+record_to_item(LServer,
+ [Username, FN, Family, Given, Middle, Nickname, BDay,
+ CTRY, Locality, EMail, OrgName, OrgUnit]) ->
+ #xmlel{name = <<"item">>, attrs = [],
+ children =
+ [?FIELD(<<"jid">>,
+ <<Username/binary, "@", LServer/binary>>),
+ ?FIELD(<<"fn">>, FN), ?FIELD(<<"last">>, Family),
+ ?FIELD(<<"first">>, Given),
+ ?FIELD(<<"middle">>, Middle),
+ ?FIELD(<<"nick">>, Nickname), ?FIELD(<<"bday">>, BDay),
+ ?FIELD(<<"ctry">>, CTRY),
+ ?FIELD(<<"locality">>, Locality),
+ ?FIELD(<<"email">>, EMail),
+ ?FIELD(<<"orgname">>, OrgName),
+ ?FIELD(<<"orgunit">>, OrgUnit)]};
record_to_item(_LServer, #vcard_search{} = R) ->
{User, Server} = R#vcard_search.user,
- {xmlelement, "item", [],
- [
- ?FIELD("jid", User ++ "@" ++ Server),
- ?FIELD("fn", R#vcard_search.fn),
- ?FIELD("last", R#vcard_search.family),
- ?FIELD("first", R#vcard_search.given),
- ?FIELD("middle", R#vcard_search.middle),
- ?FIELD("nick", R#vcard_search.nickname),
- ?FIELD("bday", R#vcard_search.bday),
- ?FIELD("ctry", R#vcard_search.ctry),
- ?FIELD("locality", R#vcard_search.locality),
- ?FIELD("email", R#vcard_search.email),
- ?FIELD("orgname", R#vcard_search.orgname),
- ?FIELD("orgunit", R#vcard_search.orgunit)
- ]
- }.
-
+ #xmlel{name = <<"item">>, attrs = [],
+ children =
+ [?FIELD(<<"jid">>, <<User/binary, "@", Server/binary>>),
+ ?FIELD(<<"fn">>, (R#vcard_search.fn)),
+ ?FIELD(<<"last">>, (R#vcard_search.family)),
+ ?FIELD(<<"first">>, (R#vcard_search.given)),
+ ?FIELD(<<"middle">>, (R#vcard_search.middle)),
+ ?FIELD(<<"nick">>, (R#vcard_search.nickname)),
+ ?FIELD(<<"bday">>, (R#vcard_search.bday)),
+ ?FIELD(<<"ctry">>, (R#vcard_search.ctry)),
+ ?FIELD(<<"locality">>, (R#vcard_search.locality)),
+ ?FIELD(<<"email">>, (R#vcard_search.email)),
+ ?FIELD(<<"orgname">>, (R#vcard_search.orgname)),
+ ?FIELD(<<"orgunit">>, (R#vcard_search.orgunit))]}.
search(LServer, Data) ->
- DBType = gen_mod:db_type(LServer, ?MODULE),
+ DBType = gen_mod:db_type(LServer, ?MODULE),
MatchSpec = make_matchspec(LServer, Data, DBType),
- AllowReturnAll = gen_mod:get_module_opt(LServer, ?MODULE,
- allow_return_all, false),
+ AllowReturnAll = gen_mod:get_module_opt(LServer, ?MODULE, allow_return_all,
+ fun(B) when is_boolean(B) -> B end,
+ false),
search(LServer, MatchSpec, AllowReturnAll, DBType).
search(LServer, MatchSpec, AllowReturnAll, mnesia) ->
- if
- (MatchSpec == #vcard_search{_ = '_'}) and (not AllowReturnAll) ->
- [];
- true ->
- case catch mnesia:dirty_select(vcard_search,
- [{MatchSpec, [], ['$_']}]) of
- {'EXIT', Reason} ->
- ?ERROR_MSG("~p", [Reason]),
- [];
- Rs ->
- case gen_mod:get_module_opt(LServer, ?MODULE,
- matches, ?JUD_MATCHES) of
- infinity ->
- Rs;
- Val when is_integer(Val) and (Val > 0) ->
- lists:sublist(Rs, Val);
- Val ->
- ?ERROR_MSG("Illegal option value ~p. "
- "Default value ~p substituted.",
- [{matches, Val}, ?JUD_MATCHES]),
- lists:sublist(Rs, ?JUD_MATCHES)
- end
- end
+ if (MatchSpec == #vcard_search{_ = '_'}) and
+ not AllowReturnAll ->
+ [];
+ true ->
+ case catch mnesia:dirty_select(vcard_search,
+ [{MatchSpec, [], ['$_']}])
+ of
+ {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]), [];
+ Rs ->
+ case gen_mod:get_module_opt(LServer, ?MODULE, matches,
+ fun(infinity) -> infinity;
+ (I) when is_integer(I),
+ I>0 ->
+ I
+ end, ?JUD_MATCHES) of
+ infinity ->
+ Rs;
+ Val ->
+ lists:sublist(Rs, Val)
+ end
+ end
end;
search(LServer, MatchSpec, AllowReturnAll, odbc) ->
- if
- (MatchSpec == "") and (not AllowReturnAll) ->
- [];
- true ->
- Limit = case gen_mod:get_module_opt(LServer, ?MODULE,
- matches, ?JUD_MATCHES) of
- infinity ->
- "";
- Val when is_integer(Val) and (Val > 0) ->
- [" LIMIT ", integer_to_list(Val)];
- Val ->
- ?ERROR_MSG("Illegal option value ~p. "
- "Default value ~p substituted.",
- [{matches, Val}, ?JUD_MATCHES]),
- [" LIMIT ", integer_to_list(?JUD_MATCHES)]
- end,
- case catch ejabberd_odbc:sql_query(
- LServer,
- ["select username, fn, family, given, middle, "
- " nickname, bday, ctry, locality, "
- " email, orgname, orgunit from vcard_search ",
- MatchSpec, Limit, ";"]) of
- {selected, ["username", "fn", "family", "given", "middle",
- "nickname", "bday", "ctry", "locality",
- "email", "orgname", "orgunit"],
- Rs} when is_list(Rs) ->
- Rs;
- Error ->
- ?ERROR_MSG("~p", [Error]),
- []
- end
+ if (MatchSpec == <<"">>) and not AllowReturnAll -> [];
+ true ->
+ Limit = case gen_mod:get_module_opt(LServer, ?MODULE, matches,
+ fun(infinity) -> infinity;
+ (I) when is_integer(I),
+ I>0 ->
+ I
+ end, ?JUD_MATCHES) of
+ infinity ->
+ <<"">>;
+ Val ->
+ [<<" LIMIT ">>,
+ jlib:integer_to_binary(Val)]
+ end,
+ case catch ejabberd_odbc:sql_query(LServer,
+ [<<"select username, fn, family, given, "
+ "middle, nickname, bday, ctry, "
+ "locality, email, orgname, orgunit "
+ "from vcard_search ">>,
+ MatchSpec, Limit, <<";">>])
+ of
+ {selected,
+ [<<"username">>, <<"fn">>, <<"family">>, <<"given">>,
+ <<"middle">>, <<"nickname">>, <<"bday">>, <<"ctry">>,
+ <<"locality">>, <<"email">>, <<"orgname">>,
+ <<"orgunit">>],
+ Rs}
+ when is_list(Rs) ->
+ Rs;
+ Error -> ?ERROR_MSG("~p", [Error]), []
+ end
end.
make_matchspec(LServer, Data, mnesia) ->
@@ -670,203 +694,197 @@ make_matchspec(LServer, Data, mnesia) ->
Match = filter_fields(Data, GlobMatch, LServer, mnesia),
Match;
make_matchspec(LServer, Data, odbc) ->
- filter_fields(Data, "", LServer, odbc).
+ filter_fields(Data, <<"">>, LServer, odbc).
-filter_fields([], Match, _LServer, mnesia) ->
- Match;
+filter_fields([], Match, _LServer, mnesia) -> Match;
filter_fields([], Match, _LServer, odbc) ->
case Match of
- "" ->
- "";
- _ ->
- [" where ", Match]
+ <<"">> -> <<"">>;
+ _ -> [<<" where ">>, Match]
end;
-filter_fields([{SVar, [Val]} | Ds], Match, LServer, mnesia)
- when is_list(Val) and (Val /= "") ->
+filter_fields([{SVar, [Val]} | Ds], Match, LServer,
+ mnesia)
+ when is_binary(Val) and (Val /= <<"">>) ->
LVal = string2lower(Val),
NewMatch = case SVar of
- "user" ->
- case gen_mod:get_module_opt(LServer, ?MODULE,
- search_all_hosts, true) of
- true ->
- Match#vcard_search{luser = make_val(LVal)};
- false ->
- Host = find_my_host(LServer),
- Match#vcard_search{us = {make_val(LVal), Host}}
- end;
- "fn" -> Match#vcard_search{lfn = make_val(LVal)};
- "last" -> Match#vcard_search{lfamily = make_val(LVal)};
- "first" -> Match#vcard_search{lgiven = make_val(LVal)};
- "middle" -> Match#vcard_search{lmiddle = make_val(LVal)};
- "nick" -> Match#vcard_search{lnickname = make_val(LVal)};
- "bday" -> Match#vcard_search{lbday = make_val(LVal)};
- "ctry" -> Match#vcard_search{lctry = make_val(LVal)};
- "locality" -> Match#vcard_search{llocality = make_val(LVal)};
- "email" -> Match#vcard_search{lemail = make_val(LVal)};
- "orgname" -> Match#vcard_search{lorgname = make_val(LVal)};
- "orgunit" -> Match#vcard_search{lorgunit = make_val(LVal)};
- _ -> Match
+ <<"user">> ->
+ case gen_mod:get_module_opt(LServer, ?MODULE,
+ search_all_hosts,
+ fun(B) when is_boolean(B) ->
+ B
+ end, true)
+ of
+ true -> Match#vcard_search{luser = make_val(LVal)};
+ false ->
+ Host = find_my_host(LServer),
+ Match#vcard_search{us = {make_val(LVal), Host}}
+ end;
+ <<"fn">> -> Match#vcard_search{lfn = make_val(LVal)};
+ <<"last">> ->
+ Match#vcard_search{lfamily = make_val(LVal)};
+ <<"first">> ->
+ Match#vcard_search{lgiven = make_val(LVal)};
+ <<"middle">> ->
+ Match#vcard_search{lmiddle = make_val(LVal)};
+ <<"nick">> ->
+ Match#vcard_search{lnickname = make_val(LVal)};
+ <<"bday">> ->
+ Match#vcard_search{lbday = make_val(LVal)};
+ <<"ctry">> ->
+ Match#vcard_search{lctry = make_val(LVal)};
+ <<"locality">> ->
+ Match#vcard_search{llocality = make_val(LVal)};
+ <<"email">> ->
+ Match#vcard_search{lemail = make_val(LVal)};
+ <<"orgname">> ->
+ Match#vcard_search{lorgname = make_val(LVal)};
+ <<"orgunit">> ->
+ Match#vcard_search{lorgunit = make_val(LVal)};
+ _ -> Match
end,
filter_fields(Ds, NewMatch, LServer, mnesia);
-filter_fields([{SVar, [Val]} | Ds], Match, LServer, odbc)
- when is_list(Val) and (Val /= "") ->
+filter_fields([{SVar, [Val]} | Ds], Match, LServer,
+ odbc)
+ when is_binary(Val) and (Val /= <<"">>) ->
LVal = string2lower(Val),
NewMatch = case SVar of
- "user" -> make_val(Match, "lusername", LVal);
- "fn" -> make_val(Match, "lfn", LVal);
- "last" -> make_val(Match, "lfamily", LVal);
- "first" -> make_val(Match, "lgiven", LVal);
- "middle" -> make_val(Match, "lmiddle", LVal);
- "nick" -> make_val(Match, "lnickname", LVal);
- "bday" -> make_val(Match, "lbday", LVal);
- "ctry" -> make_val(Match, "lctry", LVal);
- "locality" -> make_val(Match, "llocality", LVal);
- "email" -> make_val(Match, "lemail", LVal);
- "orgname" -> make_val(Match, "lorgname", LVal);
- "orgunit" -> make_val(Match, "lorgunit", LVal);
- _ -> Match
+ <<"user">> -> make_val(Match, <<"lusername">>, LVal);
+ <<"fn">> -> make_val(Match, <<"lfn">>, LVal);
+ <<"last">> -> make_val(Match, <<"lfamily">>, LVal);
+ <<"first">> -> make_val(Match, <<"lgiven">>, LVal);
+ <<"middle">> -> make_val(Match, <<"lmiddle">>, LVal);
+ <<"nick">> -> make_val(Match, <<"lnickname">>, LVal);
+ <<"bday">> -> make_val(Match, <<"lbday">>, LVal);
+ <<"ctry">> -> make_val(Match, <<"lctry">>, LVal);
+ <<"locality">> ->
+ make_val(Match, <<"llocality">>, LVal);
+ <<"email">> -> make_val(Match, <<"lemail">>, LVal);
+ <<"orgname">> -> make_val(Match, <<"lorgname">>, LVal);
+ <<"orgunit">> -> make_val(Match, <<"lorgunit">>, LVal);
+ _ -> Match
end,
filter_fields(Ds, NewMatch, LServer, odbc);
filter_fields([_ | Ds], Match, LServer, DBType) ->
filter_fields(Ds, Match, LServer, DBType).
make_val(Match, Field, Val) ->
- Condition =
- case lists:suffix("*", Val) of
- true ->
- Val1 = lists:sublist(Val, length(Val) - 1),
- SVal = ejabberd_odbc:escape_like(Val1) ++ "%",
- [Field, " LIKE '", SVal, "'"];
- _ ->
- SVal = ejabberd_odbc:escape(Val),
- [Field, " = '", SVal, "'"]
- end,
+ Condition = case str:suffix(<<"*">>, Val) of
+ true ->
+ Val1 = str:substr(Val, 1, byte_size(Val) - 1),
+ SVal = <<(ejabberd_odbc:escape_like(Val1))/binary,
+ "%">>,
+ [Field, <<" LIKE '">>, SVal, <<"'">>];
+ _ ->
+ SVal = ejabberd_odbc:escape(Val),
+ [Field, <<" = '">>, SVal, <<"'">>]
+ end,
case Match of
- "" ->
- Condition;
- _ ->
- [Match, " and ", Condition]
+ <<"">> -> Condition;
+ _ -> [Match, <<" and ">>, Condition]
end.
make_val(Val) ->
- case lists:suffix("*", Val) of
- true ->
- lists:sublist(Val, length(Val) - 1) ++ '_';
- _ ->
- Val
+ case str:suffix(<<"*">>, Val) of
+ true -> [str:substr(Val, 1, byte_size(Val) - 1)] ++ '_';
+ _ -> Val
end.
find_my_host(LServer) ->
- Parts = string:tokens(LServer, "."),
+ Parts = str:tokens(LServer, <<".">>),
find_my_host(Parts, ?MYHOSTS).
-find_my_host([], _Hosts) ->
- ?MYNAME;
+find_my_host([], _Hosts) -> ?MYNAME;
find_my_host([_ | Tail] = Parts, Hosts) ->
Domain = parts_to_string(Parts),
case lists:member(Domain, Hosts) of
- true ->
- Domain;
- false ->
- find_my_host(Tail, Hosts)
+ true -> Domain;
+ false -> find_my_host(Tail, Hosts)
end.
parts_to_string(Parts) ->
- string:strip(lists:flatten(lists:map(fun(S) -> [S, $.] end, Parts)),
- right, $.).
-
-
+ str:strip(list_to_binary(
+ lists:map(fun (S) -> <<S/binary, $.>> end, Parts)),
+ right, $.).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
set_vcard_t(R, _) ->
US = R#vcard.us,
- User = US,
+ User = US,
VCARD = R#vcard.vcard,
-
- FN = xml:get_path_s(VCARD, [{elem, "FN"}, cdata]),
- Family = xml:get_path_s(VCARD, [{elem, "N"}, {elem, "FAMILY"}, cdata]),
- Given = xml:get_path_s(VCARD, [{elem, "N"}, {elem, "GIVEN"}, cdata]),
- Middle = xml:get_path_s(VCARD, [{elem, "N"}, {elem, "MIDDLE"}, cdata]),
- Nickname = xml:get_path_s(VCARD, [{elem, "NICKNAME"}, cdata]),
- BDay = xml:get_path_s(VCARD, [{elem, "BDAY"}, cdata]),
- CTRY = xml:get_path_s(VCARD, [{elem, "ADR"}, {elem, "CTRY"}, cdata]),
- Locality = xml:get_path_s(VCARD, [{elem, "ADR"}, {elem, "LOCALITY"},cdata]),
- EMail = xml:get_path_s(VCARD, [{elem, "EMAIL"}, cdata]),
- OrgName = xml:get_path_s(VCARD, [{elem, "ORG"}, {elem, "ORGNAME"}, cdata]),
- OrgUnit = xml:get_path_s(VCARD, [{elem, "ORG"}, {elem, "ORGUNIT"}, cdata]),
-
+ FN = xml:get_path_s(VCARD, [{elem, <<"FN">>}, cdata]),
+ Family = xml:get_path_s(VCARD,
+ [{elem, <<"N">>}, {elem, <<"FAMILY">>}, cdata]),
+ Given = xml:get_path_s(VCARD,
+ [{elem, <<"N">>}, {elem, <<"GIVEN">>}, cdata]),
+ Middle = xml:get_path_s(VCARD,
+ [{elem, <<"N">>}, {elem, <<"MIDDLE">>}, cdata]),
+ Nickname = xml:get_path_s(VCARD,
+ [{elem, <<"NICKNAME">>}, cdata]),
+ BDay = xml:get_path_s(VCARD,
+ [{elem, <<"BDAY">>}, cdata]),
+ CTRY = xml:get_path_s(VCARD,
+ [{elem, <<"ADR">>}, {elem, <<"CTRY">>}, cdata]),
+ Locality = xml:get_path_s(VCARD,
+ [{elem, <<"ADR">>}, {elem, <<"LOCALITY">>},
+ cdata]),
+ EMail = xml:get_path_s(VCARD,
+ [{elem, <<"EMAIL">>}, cdata]),
+ OrgName = xml:get_path_s(VCARD,
+ [{elem, <<"ORG">>}, {elem, <<"ORGNAME">>}, cdata]),
+ OrgUnit = xml:get_path_s(VCARD,
+ [{elem, <<"ORG">>}, {elem, <<"ORGUNIT">>}, cdata]),
{LUser, _LServer} = US,
- LFN = string2lower(FN),
- LFamily = string2lower(Family),
- LGiven = string2lower(Given),
- LMiddle = string2lower(Middle),
+ LFN = string2lower(FN),
+ LFamily = string2lower(Family),
+ LGiven = string2lower(Given),
+ LMiddle = string2lower(Middle),
LNickname = string2lower(Nickname),
- LBDay = string2lower(BDay),
- LCTRY = string2lower(CTRY),
+ LBDay = string2lower(BDay),
+ LCTRY = string2lower(CTRY),
LLocality = string2lower(Locality),
- LEMail = string2lower(EMail),
- LOrgName = string2lower(OrgName),
- LOrgUnit = string2lower(OrgUnit),
-
- if
- (LUser == error) or
- (LFN == error) or
- (LFamily == error) or
- (LGiven == error) or
- (LMiddle == error) or
- (LNickname == error) or
- (LBDay == error) or
- (LCTRY == error) or
- (LLocality == error) or
- (LEMail == error) or
- (LOrgName == error) or
- (LOrgUnit == error) ->
- {error, badarg};
- true ->
- mnesia:write(
- #vcard_search{us = US,
- user = User, luser = LUser,
- fn = FN, lfn = LFN,
- family = Family, lfamily = LFamily,
- given = Given, lgiven = LGiven,
- middle = Middle, lmiddle = LMiddle,
- nickname = Nickname, lnickname = LNickname,
- bday = BDay, lbday = LBDay,
- ctry = CTRY, lctry = LCTRY,
- locality = Locality, llocality = LLocality,
- email = EMail, lemail = LEMail,
- orgname = OrgName, lorgname = LOrgName,
- orgunit = OrgUnit, lorgunit = LOrgUnit
- })
- end.
-
+ LEMail = string2lower(EMail),
+ LOrgName = string2lower(OrgName),
+ LOrgUnit = string2lower(OrgUnit),
+ mnesia:write(#vcard_search{us = US, user = User,
+ luser = LUser, fn = FN, lfn = LFN,
+ family = Family, lfamily = LFamily,
+ given = Given, lgiven = LGiven,
+ middle = Middle, lmiddle = LMiddle,
+ nickname = Nickname,
+ lnickname = LNickname, bday = BDay,
+ lbday = LBDay, ctry = CTRY, lctry = LCTRY,
+ locality = Locality,
+ llocality = LLocality, email = EMail,
+ lemail = LEMail, orgname = OrgName,
+ lorgname = LOrgName, orgunit = OrgUnit,
+ lorgunit = LOrgUnit}).
reindex_vcards() ->
- F = fun() ->
- mnesia:foldl(fun set_vcard_t/2, [], vcard)
+ F = fun () -> mnesia:foldl(fun set_vcard_t/2, [], vcard)
end,
mnesia:transaction(F).
-
remove_user(User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
- remove_user(LUser, LServer, gen_mod:db_type(LServer, ?MODULE)).
+ remove_user(LUser, LServer,
+ gen_mod:db_type(LServer, ?MODULE)).
remove_user(LUser, LServer, mnesia) ->
US = {LUser, LServer},
- F = fun() ->
+ F = fun () ->
mnesia:delete({vcard, US}),
mnesia:delete({vcard_search, US})
end,
mnesia:transaction(F);
remove_user(LUser, LServer, odbc) ->
Username = ejabberd_odbc:escape(LUser),
- ejabberd_odbc:sql_transaction(
- LServer,
- [["delete from vcard where username='", Username, "';"],
- ["delete from vcard_search where lusername='", Username, "';"]]).
+ ejabberd_odbc:sql_transaction(LServer,
+ [[<<"delete from vcard where username='">>,
+ Username, <<"';">>],
+ [<<"delete from vcard_search where lusername='">>,
+ Username, <<"';">>]]).
update_tables() ->
update_vcard_table(),
@@ -875,141 +893,115 @@ update_tables() ->
update_vcard_table() ->
Fields = record_info(fields, vcard),
case mnesia:table_info(vcard, attributes) of
- Fields ->
- ok;
- [user, vcard] ->
- ?INFO_MSG("Converting vcard table from "
- "{user, vcard} format", []),
- Host = ?MYNAME,
- {atomic, ok} = mnesia:create_table(
- mod_vcard_tmp_table,
- [{disc_only_copies, [node()]},
- {type, bag},
- {local_content, true},
- {record_name, vcard},
- {attributes, record_info(fields, vcard)}]),
- mnesia:transform_table(vcard, ignore, Fields),
- F1 = fun() ->
- mnesia:write_lock_table(mod_vcard_tmp_table),
- mnesia:foldl(
- fun(#vcard{us = U} = R, _) ->
- mnesia:dirty_write(
- mod_vcard_tmp_table,
- R#vcard{us = {U, Host}})
- end, ok, vcard)
- end,
- mnesia:transaction(F1),
- mnesia:clear_table(vcard),
- F2 = fun() ->
- mnesia:write_lock_table(vcard),
- mnesia:foldl(
- fun(R, _) ->
- mnesia:dirty_write(R)
- end, ok, mod_vcard_tmp_table)
- end,
- mnesia:transaction(F2),
- mnesia:delete_table(mod_vcard_tmp_table);
- _ ->
- ?INFO_MSG("Recreating vcard table", []),
- mnesia:transform_table(vcard, ignore, Fields)
+ Fields ->
+ ejabberd_config:convert_table_to_binary(
+ vcard, Fields, set,
+ fun(#vcard{us = {U, _}}) -> U end,
+ fun(#vcard{us = {U, S}, vcard = El} = R) ->
+ R#vcard{us = {iolist_to_binary(U),
+ iolist_to_binary(S)},
+ vcard = xml:to_xmlel(El)}
+ end);
+ _ ->
+ ?INFO_MSG("Recreating vcard table", []),
+ mnesia:transform_table(vcard, ignore, Fields)
end.
-
update_vcard_search_table() ->
Fields = record_info(fields, vcard_search),
case mnesia:table_info(vcard_search, attributes) of
- Fields ->
- ok;
- [user, luser,
- fn, lfn,
- family, lfamily,
- given, lgiven,
- middle, lmiddle,
- nickname, lnickname,
- bday, lbday,
- ctry, lctry,
- locality, llocality,
- email, lemail,
- orgname, lorgname,
- orgunit, lorgunit] ->
- ?INFO_MSG("Converting vcard_search table from "
- "{user, luser, fn, lfn, family, lfamily, given, lgiven, middle, lmiddle, nickname, lnickname, bday, lbday, ctry, lctry, locality, llocality, email, lemail, orgname, lorgname, orgunit, lorgunit} format", []),
- Host = ?MYNAME,
- {atomic, ok} = mnesia:create_table(
- mod_vcard_tmp_table,
- [{disc_only_copies, [node()]},
- {type, bag},
- {local_content, true},
- {record_name, vcard_search},
- {attributes, record_info(fields, vcard_search)}]),
- F1 = fun() ->
- mnesia:write_lock_table(mod_vcard_tmp_table),
- mnesia:foldl(
- fun({vcard_search,
- User, LUser,
- FN, LFN,
- Family, LFamily,
- Given, LGiven,
- Middle, LMiddle,
- Nickname, LNickname,
- BDay, LBDay,
- CTRY, LCTRY,
- Locality, LLocality,
- EMail, LEMail,
- OrgName, LOrgName,
- OrgUnit, LOrgUnit
- }, _) ->
- mnesia:dirty_write(
- mod_vcard_tmp_table,
- #vcard_search{
- us = {LUser, Host},
- user = {User, Host},
- luser = LUser,
- fn = FN, lfn = LFN,
- family = Family, lfamily = LFamily,
- given = Given, lgiven = LGiven,
- middle = Middle, lmiddle = LMiddle,
- nickname = Nickname, lnickname = LNickname,
- bday = BDay, lbday = LBDay,
- ctry = CTRY, lctry = LCTRY,
- locality = Locality, llocality = LLocality,
- email = EMail, lemail = LEMail,
- orgname = OrgName, lorgname = LOrgName,
- orgunit = OrgUnit, lorgunit = LOrgUnit
- })
- end, ok, vcard_search)
- end,
- mnesia:transaction(F1),
- lists:foreach(fun(I) ->
- mnesia:del_table_index(
- vcard_search,
- element(I, {vcard_search,
- user, luser,
- fn, lfn,
- family, lfamily,
- given, lgiven,
- middle, lmiddle,
- nickname, lnickname,
- bday, lbday,
- ctry, lctry,
- locality, llocality,
- email, lemail,
- orgname, lorgname,
- orgunit, lorgunit}))
- end, mnesia:table_info(vcard_search, index)),
- mnesia:clear_table(vcard_search),
- mnesia:transform_table(vcard_search, ignore, Fields),
- F2 = fun() ->
- mnesia:write_lock_table(vcard_search),
- mnesia:foldl(
- fun(R, _) ->
- mnesia:dirty_write(R)
- end, ok, mod_vcard_tmp_table)
- end,
- mnesia:transaction(F2),
- mnesia:delete_table(mod_vcard_tmp_table);
- _ ->
- ?INFO_MSG("Recreating vcard_search table", []),
- mnesia:transform_table(vcard_search, ignore, Fields)
+ Fields ->
+ ejabberd_config:convert_table_to_binary(
+ vcard_search, Fields, set,
+ fun(#vcard_search{us = {U, _}}) -> U end,
+ fun(#vcard_search{} = VS) ->
+ [vcard_search | L] = tuple_to_list(VS),
+ NewL = lists:map(
+ fun({U, S}) ->
+ {iolist_to_binary(U),
+ iolist_to_binary(S)};
+ (Str) ->
+ iolist_to_binary(Str)
+ end, L),
+ list_to_tuple([vcard_search | NewL])
+ end);
+ _ ->
+ ?INFO_MSG("Recreating vcard_search table", []),
+ mnesia:transform_table(vcard_search, ignore, Fields)
end.
+export(_Server) ->
+ [{vcard,
+ fun(Host, #vcard{us = {LUser, LServer}, vcard = VCARD})
+ when LServer == Host ->
+ Username = ejabberd_odbc:escape(LUser),
+ SVCARD =
+ ejabberd_odbc:escape(xml:element_to_binary(VCARD)),
+ [[<<"delete from vcard where username='">>, Username, <<"';">>],
+ [<<"insert into vcard(username, vcard) values ('">>,
+ Username, <<"', '">>, SVCARD, <<"');">>]];
+ (_Host, _R) ->
+ []
+ end},
+ {vcard_search,
+ fun(Host, #vcard_search{user = {User, LServer}, luser = LUser,
+ fn = FN, lfn = LFN, family = Family,
+ lfamily = LFamily, given = Given,
+ lgiven = LGiven, middle = Middle,
+ lmiddle = LMiddle, nickname = Nickname,
+ lnickname = LNickname, bday = BDay,
+ lbday = LBDay, ctry = CTRY, lctry = LCTRY,
+ locality = Locality, llocality = LLocality,
+ email = EMail, lemail = LEMail,
+ orgname = OrgName, lorgname = LOrgName,
+ orgunit = OrgUnit, lorgunit = LOrgUnit})
+ when LServer == Host ->
+ Username = ejabberd_odbc:escape(User),
+ LUsername = ejabberd_odbc:escape(LUser),
+ SFN = ejabberd_odbc:escape(FN),
+ SLFN = ejabberd_odbc:escape(LFN),
+ SFamily = ejabberd_odbc:escape(Family),
+ SLFamily = ejabberd_odbc:escape(LFamily),
+ SGiven = ejabberd_odbc:escape(Given),
+ SLGiven = ejabberd_odbc:escape(LGiven),
+ SMiddle = ejabberd_odbc:escape(Middle),
+ SLMiddle = ejabberd_odbc:escape(LMiddle),
+ SNickname = ejabberd_odbc:escape(Nickname),
+ SLNickname = ejabberd_odbc:escape(LNickname),
+ SBDay = ejabberd_odbc:escape(BDay),
+ SLBDay = ejabberd_odbc:escape(LBDay),
+ SCTRY = ejabberd_odbc:escape(CTRY),
+ SLCTRY = ejabberd_odbc:escape(LCTRY),
+ SLocality = ejabberd_odbc:escape(Locality),
+ SLLocality = ejabberd_odbc:escape(LLocality),
+ SEMail = ejabberd_odbc:escape(EMail),
+ SLEMail = ejabberd_odbc:escape(LEMail),
+ SOrgName = ejabberd_odbc:escape(OrgName),
+ SLOrgName = ejabberd_odbc:escape(LOrgName),
+ SOrgUnit = ejabberd_odbc:escape(OrgUnit),
+ SLOrgUnit = ejabberd_odbc:escape(LOrgUnit),
+ [[<<"delete from vcard_search where lusername='">>,
+ LUsername, <<"';">>],
+ [<<"insert into vcard_search( username, "
+ "lusername, fn, lfn, family, lfamily, "
+ " given, lgiven, middle, lmiddle, "
+ "nickname, lnickname, bday, lbday, "
+ "ctry, lctry, locality, llocality, "
+ " email, lemail, orgname, lorgname, "
+ "orgunit, lorgunit)values (">>,
+ <<" '">>, Username, <<"', '">>, LUsername,
+ <<"', '">>, SFN, <<"', '">>, SLFN,
+ <<"', '">>, SFamily, <<"', '">>, SLFamily,
+ <<"', '">>, SGiven, <<"', '">>, SLGiven,
+ <<"', '">>, SMiddle, <<"', '">>, SLMiddle,
+ <<"', '">>, SNickname, <<"', '">>, SLNickname,
+ <<"', '">>, SBDay, <<"', '">>, SLBDay,
+ <<"', '">>, SCTRY, <<"', '">>, SLCTRY,
+ <<"', '">>, SLocality, <<"', '">>, SLLocality,
+ <<"', '">>, SEMail, <<"', '">>, SLEMail,
+ <<"', '">>, SOrgName, <<"', '">>, SLOrgName,
+ <<"', '">>, SOrgUnit, <<"', '">>, SLOrgUnit,
+ <<"');">>]];
+ (_Host, _R) ->
+ []
+ end}].
diff --git a/src/mod_vcard_ldap.erl b/src/mod_vcard_ldap.erl
index d3e60774a..be9bc7c53 100644
--- a/src/mod_vcard_ldap.erl
+++ b/src/mod_vcard_ldap.erl
@@ -25,125 +25,108 @@
%%%----------------------------------------------------------------------
-module(mod_vcard_ldap).
+
-author('alexey@process-one.net').
-behaviour(gen_server).
+
-behaviour(gen_mod).
%% gen_server callbacks.
--export([init/1,
- handle_info/2,
- handle_call/3,
- handle_cast/2,
- terminate/2,
- code_change/3
- ]).
-
--export([start/2,
- start_link/2,
- stop/1,
- get_sm_features/5,
- process_local_iq/3,
- process_sm_iq/3,
- remove_user/1,
- route/4
- ]).
+-export([init/1, handle_info/2, handle_call/3,
+ handle_cast/2, terminate/2, code_change/3]).
+
+-export([start/2, start_link/2, stop/1,
+ get_sm_features/5, process_local_iq/3, process_sm_iq/3,
+ remove_user/1, route/4]).
-include("ejabberd.hrl").
+
-include("eldap/eldap.hrl").
+
-include("jlib.hrl").
-define(PROCNAME, ejabberd_mod_vcard_ldap).
--record(state, {serverhost,
- myhost,
- eldap_id,
- search,
- servers,
- backups,
- port,
- tls_options,
- dn,
- base,
- password,
- uids,
- vcard_map,
- vcard_map_attrs,
- user_filter,
- search_filter,
- search_fields,
- search_reported,
- search_reported_attrs,
- deref_aliases,
- matches
- }).
+-record(state,
+ {serverhost = <<"">> :: binary(),
+ myhost = <<"">> :: binary(),
+ eldap_id = <<"">> :: binary(),
+ search = true :: boolean(),
+ servers = [] :: [binary()],
+ backups = [] :: [binary()],
+ port = ?LDAP_PORT :: inet:port_number(),
+ tls_options = [] :: list(),
+ dn = <<"">> :: binary(),
+ base = <<"">> :: binary(),
+ password = <<"">> :: binary(),
+ uids = [] :: [{binary()} | {binary(), binary()}],
+ vcard_map = [] :: [{binary(), binary(), [binary()]}],
+ vcard_map_attrs = [] :: [binary()],
+ user_filter = <<"">> :: binary(),
+ search_filter :: eldap:filter(),
+ search_fields = [] :: [{binary(), binary()}],
+ search_reported = [] :: [{binary(), binary()}],
+ search_reported_attrs = [] :: [binary()],
+ deref_aliases = never :: never | searching | finding | always,
+ matches = 0 :: non_neg_integer()}).
-define(VCARD_MAP,
- [{"NICKNAME", "%u", []},
- {"FN", "%s", ["displayName"]},
- {"FAMILY", "%s", ["sn"]},
- {"GIVEN", "%s", ["givenName"]},
- {"MIDDLE", "%s", ["initials"]},
- {"ORGNAME", "%s", ["o"]},
- {"ORGUNIT", "%s", ["ou"]},
- {"CTRY", "%s", ["c"]},
- {"LOCALITY", "%s", ["l"]},
- {"STREET", "%s", ["street"]},
- {"REGION", "%s", ["st"]},
- {"PCODE", "%s", ["postalCode"]},
- {"TITLE", "%s", ["title"]},
- {"URL", "%s", ["labeleduri"]},
- {"DESC", "%s", ["description"]},
- {"TEL", "%s", ["telephoneNumber"]},
- {"EMAIL", "%s", ["mail"]},
- {"BDAY", "%s", ["birthDay"]},
- {"ROLE", "%s", ["employeeType"]},
- {"PHOTO", "%s", ["jpegPhoto"]}
- ]).
+ [{<<"NICKNAME">>, <<"%u">>, []},
+ {<<"FN">>, <<"%s">>, [<<"displayName">>]},
+ {<<"FAMILY">>, <<"%s">>, [<<"sn">>]},
+ {<<"GIVEN">>, <<"%s">>, [<<"givenName">>]},
+ {<<"MIDDLE">>, <<"%s">>, [<<"initials">>]},
+ {<<"ORGNAME">>, <<"%s">>, [<<"o">>]},
+ {<<"ORGUNIT">>, <<"%s">>, [<<"ou">>]},
+ {<<"CTRY">>, <<"%s">>, [<<"c">>]},
+ {<<"LOCALITY">>, <<"%s">>, [<<"l">>]},
+ {<<"STREET">>, <<"%s">>, [<<"street">>]},
+ {<<"REGION">>, <<"%s">>, [<<"st">>]},
+ {<<"PCODE">>, <<"%s">>, [<<"postalCode">>]},
+ {<<"TITLE">>, <<"%s">>, [<<"title">>]},
+ {<<"URL">>, <<"%s">>, [<<"labeleduri">>]},
+ {<<"DESC">>, <<"%s">>, [<<"description">>]},
+ {<<"TEL">>, <<"%s">>, [<<"telephoneNumber">>]},
+ {<<"EMAIL">>, <<"%s">>, [<<"mail">>]},
+ {<<"BDAY">>, <<"%s">>, [<<"birthDay">>]},
+ {<<"ROLE">>, <<"%s">>, [<<"employeeType">>]},
+ {<<"PHOTO">>, <<"%s">>, [<<"jpegPhoto">>]}]).
-define(SEARCH_FIELDS,
- [{"User", "%u"},
- {"Full Name", "displayName"},
- {"Given Name", "givenName"},
- {"Middle Name", "initials"},
- {"Family Name", "sn"},
- {"Nickname", "%u"},
- {"Birthday", "birthDay"},
- {"Country", "c"},
- {"City", "l"},
- {"Email", "mail"},
- {"Organization Name", "o"},
- {"Organization Unit", "ou"}
- ]).
+ [{<<"User">>, <<"%u">>},
+ {<<"Full Name">>, <<"displayName">>},
+ {<<"Given Name">>, <<"givenName">>},
+ {<<"Middle Name">>, <<"initials">>},
+ {<<"Family Name">>, <<"sn">>},
+ {<<"Nickname">>, <<"%u">>},
+ {<<"Birthday">>, <<"birthDay">>},
+ {<<"Country">>, <<"c">>}, {<<"City">>, <<"l">>},
+ {<<"Email">>, <<"mail">>},
+ {<<"Organization Name">>, <<"o">>},
+ {<<"Organization Unit">>, <<"ou">>}]).
-define(SEARCH_REPORTED,
- [{"Full Name", "FN"},
- {"Given Name", "FIRST"},
- {"Middle Name", "MIDDLE"},
- {"Family Name", "LAST"},
- {"Nickname", "NICK"},
- {"Birthday", "BDAY"},
- {"Country", "CTRY"},
- {"City", "LOCALITY"},
- {"Email", "EMAIL"},
- {"Organization Name", "ORGNAME"},
- {"Organization Unit", "ORGUNIT"}
- ]).
-
-%% Unused callbacks.
-handle_cast(_Request, State) ->
- {noreply, State}.
-code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
-%% -----
-
+ [{<<"Full Name">>, <<"FN">>},
+ {<<"Given Name">>, <<"FIRST">>},
+ {<<"Middle Name">>, <<"MIDDLE">>},
+ {<<"Family Name">>, <<"LAST">>},
+ {<<"Nickname">>, <<"NICK">>},
+ {<<"Birthday">>, <<"BDAY">>},
+ {<<"Country">>, <<"CTRY">>},
+ {<<"City">>, <<"LOCALITY">>},
+ {<<"Email">>, <<"EMAIL">>},
+ {<<"Organization Name">>, <<"ORGNAME">>},
+ {<<"Organization Unit">>, <<"ORGUNIT">>}]).
+
+handle_cast(_Request, State) -> {noreply, State}.
+
+code_change(_OldVsn, State, _Extra) -> {ok, State}.
start(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
- ChildSpec = {
- Proc, {?MODULE, start_link, [Host, Opts]},
- transient, 1000, worker, [?MODULE]
- },
+ ChildSpec = {Proc, {?MODULE, start_link, [Host, Opts]},
+ transient, 1000, worker, [?MODULE]},
supervisor:start_child(ejabberd_sup, ChildSpec).
stop(Host) ->
@@ -154,125 +137,126 @@ stop(Host) ->
terminate(_Reason, State) ->
Host = State#state.serverhost,
- gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_VCARD),
- gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_VCARD),
- ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, get_sm_features, 50),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, Host,
+ ?NS_VCARD),
+ gen_iq_handler:remove_iq_handler(ejabberd_sm, Host,
+ ?NS_VCARD),
+ ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE,
+ get_sm_features, 50),
case State#state.search of
- true ->
- ejabberd_router:unregister_route(State#state.myhost);
- _ ->
- ok
+ true ->
+ ejabberd_router:unregister_route(State#state.myhost);
+ _ -> ok
end.
start_link(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
- gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
+ gen_server:start_link({local, Proc}, ?MODULE,
+ [Host, Opts], []).
init([Host, Opts]) ->
State = parse_options(Host, Opts),
- IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
- gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_VCARD,
- ?MODULE, process_local_iq, IQDisc),
- gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_VCARD,
- ?MODULE, process_sm_iq, IQDisc),
- ejabberd_hooks:add(disco_sm_features, Host, ?MODULE, get_sm_features, 50),
+ IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
+ one_queue),
+ gen_iq_handler:add_iq_handler(ejabberd_local, Host,
+ ?NS_VCARD, ?MODULE, process_local_iq, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
+ ?NS_VCARD, ?MODULE, process_sm_iq, IQDisc),
+ ejabberd_hooks:add(disco_sm_features, Host, ?MODULE,
+ get_sm_features, 50),
eldap_pool:start_link(State#state.eldap_id,
- State#state.servers,
- State#state.backups,
- State#state.port,
- State#state.dn,
- State#state.password,
- State#state.tls_options),
+ State#state.servers, State#state.backups,
+ State#state.port, State#state.dn,
+ State#state.password, State#state.tls_options),
case State#state.search of
- true ->
- ejabberd_router:register_route(State#state.myhost);
- _ ->
- ok
+ true ->
+ ejabberd_router:register_route(State#state.myhost);
+ _ -> ok
end,
{ok, State}.
handle_info({route, From, To, Packet}, State) ->
case catch do_route(State, From, To, Packet) of
- Pid when is_pid(Pid) ->
- ok;
- _ ->
- Err = jlib:make_error_reply(Packet, ?ERR_INTERNAL_SERVER_ERROR),
- ejabberd_router:route(To, From, Err)
+ Pid when is_pid(Pid) -> ok;
+ _ ->
+ Err = jlib:make_error_reply(Packet,
+ ?ERR_INTERNAL_SERVER_ERROR),
+ ejabberd_router:route(To, From, Err)
end,
{noreply, State};
+handle_info(_Info, State) -> {noreply, State}.
-handle_info(_Info, State) ->
- {noreply, State}.
-
-get_sm_features({error, _Error} = Acc, _From, _To, _Node, _Lang) ->
+get_sm_features({error, _Error} = Acc, _From, _To,
+ _Node, _Lang) ->
Acc;
get_sm_features(Acc, _From, _To, Node, _Lang) ->
case Node of
- [] ->
- case Acc of
- {result, Features} ->
- {result, [?NS_VCARD | Features]};
- empty ->
- {result, [?NS_VCARD]}
- end;
- _ ->
- Acc
+ <<"">> ->
+ case Acc of
+ {result, Features} -> {result, [?NS_VCARD | Features]};
+ empty -> {result, [?NS_VCARD]}
+ end;
+ _ -> Acc
end.
-process_local_iq(_From, _To, #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
+process_local_iq(_From, _To,
+ #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
case Type of
- set ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
- get ->
- IQ#iq{type = result,
- sub_el = [{xmlelement, "vCard",
- [{"xmlns", ?NS_VCARD}],
- [{xmlelement, "FN", [],
- [{xmlcdata, "ejabberd"}]},
- {xmlelement, "URL", [],
- [{xmlcdata, ?EJABBERD_URI}]},
- {xmlelement, "DESC", [],
- [{xmlcdata,
- translate:translate(
- Lang,
- "Erlang Jabber Server") ++
- "\nCopyright (c) 2002-2013 ProcessOne"}]},
- {xmlelement, "BDAY", [],
- [{xmlcdata, "2002-11-16"}]}
- ]}]}
+ set ->
+ IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+ get ->
+ IQ#iq{type = result,
+ sub_el =
+ [#xmlel{name = <<"vCard">>,
+ attrs = [{<<"xmlns">>, ?NS_VCARD}],
+ children =
+ [#xmlel{name = <<"FN">>, attrs = [],
+ children =
+ [{xmlcdata, <<"ejabberd">>}]},
+ #xmlel{name = <<"URL">>, attrs = [],
+ children = [{xmlcdata, ?EJABBERD_URI}]},
+ #xmlel{name = <<"DESC">>, attrs = [],
+ children =
+ [{xmlcdata,
+ <<(translate:translate(Lang,
+ <<"Erlang Jabber Server">>))/binary,
+ "\nCopyright (c) 2002-2013 ProcessOne">>}]},
+ #xmlel{name = <<"BDAY">>, attrs = [],
+ children =
+ [{xmlcdata, <<"2002-11-16">>}]}]}]}
end.
-process_sm_iq(_From, #jid{lserver=LServer} = To, #iq{sub_el = SubEl} = IQ) ->
+process_sm_iq(_From, #jid{lserver = LServer} = To,
+ #iq{sub_el = SubEl} = IQ) ->
case catch process_vcard_ldap(To, IQ, LServer) of
- {'EXIT', _} ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]};
- Other ->
- Other
+ {'EXIT', _} ->
+ IQ#iq{type = error,
+ sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]};
+ Other -> Other
end.
process_vcard_ldap(To, IQ, Server) ->
{ok, State} = eldap_utils:get_state(Server, ?PROCNAME),
#iq{type = Type, sub_el = SubEl} = IQ,
case Type of
- set ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
- get ->
- #jid{luser = LUser} = To,
- LServer = State#state.serverhost,
- case ejabberd_auth:is_user_exists(LUser, LServer) of
- true ->
- VCardMap = State#state.vcard_map,
- case find_ldap_user(LUser, State) of
- #eldap_entry{attributes = Attributes} ->
- Vcard = ldap_attributes_to_vcard(Attributes, VCardMap, {LUser, LServer}),
- IQ#iq{type = result, sub_el = Vcard};
- _ ->
- IQ#iq{type = result, sub_el = []}
- end;
- _ ->
- IQ#iq{type = result, sub_el = []}
- end
- end.
+ set ->
+ IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+ get ->
+ #jid{luser = LUser} = To,
+ LServer = State#state.serverhost,
+ case ejabberd_auth:is_user_exists(LUser, LServer) of
+ true ->
+ VCardMap = State#state.vcard_map,
+ case find_ldap_user(LUser, State) of
+ #eldap_entry{attributes = Attributes} ->
+ Vcard = ldap_attributes_to_vcard(Attributes, VCardMap,
+ {LUser, LServer}),
+ IQ#iq{type = result, sub_el = Vcard};
+ _ -> IQ#iq{type = result, sub_el = []}
+ end;
+ _ -> IQ#iq{type = result, sub_el = []}
+ end
+ end.
handle_call(get_state, _From, State) ->
{reply, {ok, State}, State};
@@ -286,124 +270,167 @@ find_ldap_user(User, State) ->
RFC2254_Filter = State#state.user_filter,
Eldap_ID = State#state.eldap_id,
VCardAttrs = State#state.vcard_map_attrs,
- case eldap_filter:parse(RFC2254_Filter, [{"%u", User}]) of
- {ok, EldapFilter} ->
- case eldap_pool:search(Eldap_ID,
- [{base, Base},
- {filter, EldapFilter},
- {deref_aliases, State#state.deref_aliases},
- {attributes, VCardAttrs}]) of
- #eldap_search_result{entries = [E | _]} ->
- E;
- _ ->
- false
- end;
- _ ->
- false
+ case eldap_filter:parse(RFC2254_Filter,
+ [{<<"%u">>, User}])
+ of
+ {ok, EldapFilter} ->
+ case eldap_pool:search(Eldap_ID,
+ [{base, Base}, {filter, EldapFilter},
+ {deref_aliases, State#state.deref_aliases},
+ {attributes, VCardAttrs}])
+ of
+ #eldap_search_result{entries = [E | _]} -> E;
+ _ -> false
+ end;
+ _ -> false
end.
ldap_attributes_to_vcard(Attributes, VCardMap, UD) ->
- Attrs = lists:map(
- fun({VCardName, _, _}) ->
- {stringprep:tolower(VCardName),
- map_vcard_attr(VCardName, Attributes, VCardMap, UD)}
- end, VCardMap),
- Elts = [ldap_attribute_to_vcard(vCard, Attr) || Attr <- Attrs],
- NElts = [ldap_attribute_to_vcard(vCardN, Attr) || Attr <- Attrs],
- OElts = [ldap_attribute_to_vcard(vCardO, Attr) || Attr <- Attrs],
- AElts = [ldap_attribute_to_vcard(vCardA, Attr) || Attr <- Attrs],
- [{xmlelement, "vCard", [{"xmlns", ?NS_VCARD}],
- lists:append([X || X <- Elts, X /= none],
- [{xmlelement,"N",[], [X || X <- NElts, X /= none]},
- {xmlelement,"ORG",[], [X || X <- OElts, X /= none]},
- {xmlelement,"ADR",[], [X || X <- AElts, X /= none]}])
- }].
-
-ldap_attribute_to_vcard(vCard, {"fn", Value}) ->
- {xmlelement,"FN",[],[{xmlcdata,Value}]};
-
-ldap_attribute_to_vcard(vCard, {"nickname", Value}) ->
- {xmlelement,"NICKNAME",[],[{xmlcdata,Value}]};
-
-ldap_attribute_to_vcard(vCard, {"title", Value}) ->
- {xmlelement,"TITLE",[],[{xmlcdata,Value}]};
-
-ldap_attribute_to_vcard(vCard, {"bday", Value}) ->
- {xmlelement,"BDAY",[],[{xmlcdata,Value}]};
-
-ldap_attribute_to_vcard(vCard, {"url", Value}) ->
- {xmlelement,"URL",[],[{xmlcdata,Value}]};
-
-ldap_attribute_to_vcard(vCard, {"desc", Value}) ->
- {xmlelement,"DESC",[],[{xmlcdata,Value}]};
-
-ldap_attribute_to_vcard(vCard, {"role", Value}) ->
- {xmlelement,"ROLE",[],[{xmlcdata,Value}]};
-
-ldap_attribute_to_vcard(vCard, {"tel", Value}) ->
- {xmlelement,"TEL",[],[{xmlelement,"VOICE",[],[]},
- {xmlelement,"WORK",[],[]},
- {xmlelement,"NUMBER",[],[{xmlcdata,Value}]}]};
-
-ldap_attribute_to_vcard(vCard, {"email", Value}) ->
- {xmlelement,"EMAIL",[],[{xmlelement,"INTERNET",[],[]},
- {xmlelement,"PREF",[],[]},
- {xmlelement,"USERID",[],[{xmlcdata,Value}]}]};
-
-ldap_attribute_to_vcard(vCard, {"photo", Value}) ->
- {xmlelement,"PHOTO",[],[
- {xmlelement,"TYPE",[],[{xmlcdata,"image/jpeg"}]},
- {xmlelement,"BINVAL",[],[{xmlcdata, jlib:encode_base64(Value)}]}]};
-
-ldap_attribute_to_vcard(vCardN, {"family", Value}) ->
- {xmlelement,"FAMILY",[],[{xmlcdata,Value}]};
-
-ldap_attribute_to_vcard(vCardN, {"given", Value}) ->
- {xmlelement,"GIVEN",[],[{xmlcdata,Value}]};
-
-ldap_attribute_to_vcard(vCardN, {"middle", Value}) ->
- {xmlelement,"MIDDLE",[],[{xmlcdata,Value}]};
-
-ldap_attribute_to_vcard(vCardO, {"orgname", Value}) ->
- {xmlelement,"ORGNAME",[],[{xmlcdata,Value}]};
-
-ldap_attribute_to_vcard(vCardO, {"orgunit", Value}) ->
- {xmlelement,"ORGUNIT",[],[{xmlcdata,Value}]};
-
-ldap_attribute_to_vcard(vCardA, {"locality", Value}) ->
- {xmlelement,"LOCALITY",[],[{xmlcdata,Value}]};
-
-ldap_attribute_to_vcard(vCardA, {"street", Value}) ->
- {xmlelement,"STREET",[],[{xmlcdata,Value}]};
-
-ldap_attribute_to_vcard(vCardA, {"ctry", Value}) ->
- {xmlelement,"CTRY",[],[{xmlcdata,Value}]};
-
-ldap_attribute_to_vcard(vCardA, {"region", Value}) ->
- {xmlelement,"REGION",[],[{xmlcdata,Value}]};
-
-ldap_attribute_to_vcard(vCardA, {"pcode", Value}) ->
- {xmlelement,"PCODE",[],[{xmlcdata,Value}]};
-
-ldap_attribute_to_vcard(_, _) ->
- none.
+ Attrs = lists:map(fun ({VCardName, _, _}) ->
+ {stringprep:tolower(VCardName),
+ map_vcard_attr(VCardName, Attributes, VCardMap,
+ UD)}
+ end,
+ VCardMap),
+ Elts = [ldap_attribute_to_vcard(vCard, Attr)
+ || Attr <- Attrs],
+ NElts = [ldap_attribute_to_vcard(vCardN, Attr)
+ || Attr <- Attrs],
+ OElts = [ldap_attribute_to_vcard(vCardO, Attr)
+ || Attr <- Attrs],
+ AElts = [ldap_attribute_to_vcard(vCardA, Attr)
+ || Attr <- Attrs],
+ [#xmlel{name = <<"vCard">>,
+ attrs = [{<<"xmlns">>, ?NS_VCARD}],
+ children =
+ lists:append([X || X <- Elts, X /= none],
+ [#xmlel{name = <<"N">>, attrs = [],
+ children = [X || X <- NElts, X /= none]},
+ #xmlel{name = <<"ORG">>, attrs = [],
+ children = [X || X <- OElts, X /= none]},
+ #xmlel{name = <<"ADR">>, attrs = [],
+ children =
+ [X || X <- AElts, X /= none]}])}].
+
+ldap_attribute_to_vcard(vCard, {<<"fn">>, Value}) ->
+ #xmlel{name = <<"FN">>, attrs = [],
+ children = [{xmlcdata, Value}]};
+ldap_attribute_to_vcard(vCard,
+ {<<"nickname">>, Value}) ->
+ #xmlel{name = <<"NICKNAME">>, attrs = [],
+ children = [{xmlcdata, Value}]};
+ldap_attribute_to_vcard(vCard, {<<"title">>, Value}) ->
+ #xmlel{name = <<"TITLE">>, attrs = [],
+ children = [{xmlcdata, Value}]};
+ldap_attribute_to_vcard(vCard, {<<"bday">>, Value}) ->
+ #xmlel{name = <<"BDAY">>, attrs = [],
+ children = [{xmlcdata, Value}]};
+ldap_attribute_to_vcard(vCard, {<<"url">>, Value}) ->
+ #xmlel{name = <<"URL">>, attrs = [],
+ children = [{xmlcdata, Value}]};
+ldap_attribute_to_vcard(vCard, {<<"desc">>, Value}) ->
+ #xmlel{name = <<"DESC">>, attrs = [],
+ children = [{xmlcdata, Value}]};
+ldap_attribute_to_vcard(vCard, {<<"role">>, Value}) ->
+ #xmlel{name = <<"ROLE">>, attrs = [],
+ children = [{xmlcdata, Value}]};
+ldap_attribute_to_vcard(vCard, {<<"tel">>, Value}) ->
+ #xmlel{name = <<"TEL">>, attrs = [],
+ children =
+ [#xmlel{name = <<"VOICE">>, attrs = [], children = []},
+ #xmlel{name = <<"WORK">>, attrs = [], children = []},
+ #xmlel{name = <<"NUMBER">>, attrs = [],
+ children = [{xmlcdata, Value}]}]};
+ldap_attribute_to_vcard(vCard, {<<"email">>, Value}) ->
+ #xmlel{name = <<"EMAIL">>, attrs = [],
+ children =
+ [#xmlel{name = <<"INTERNET">>, attrs = [],
+ children = []},
+ #xmlel{name = <<"PREF">>, attrs = [], children = []},
+ #xmlel{name = <<"USERID">>, attrs = [],
+ children = [{xmlcdata, Value}]}]};
+ldap_attribute_to_vcard(vCard, {<<"photo">>, Value}) ->
+ #xmlel{name = <<"PHOTO">>, attrs = [],
+ children =
+ [#xmlel{name = <<"TYPE">>, attrs = [],
+ children = [{xmlcdata, <<"image/jpeg">>}]},
+ #xmlel{name = <<"BINVAL">>, attrs = [],
+ children = [{xmlcdata, jlib:encode_base64(Value)}]}]};
+ldap_attribute_to_vcard(vCardN,
+ {<<"family">>, Value}) ->
+ #xmlel{name = <<"FAMILY">>, attrs = [],
+ children = [{xmlcdata, Value}]};
+ldap_attribute_to_vcard(vCardN, {<<"given">>, Value}) ->
+ #xmlel{name = <<"GIVEN">>, attrs = [],
+ children = [{xmlcdata, Value}]};
+ldap_attribute_to_vcard(vCardN,
+ {<<"middle">>, Value}) ->
+ #xmlel{name = <<"MIDDLE">>, attrs = [],
+ children = [{xmlcdata, Value}]};
+ldap_attribute_to_vcard(vCardO,
+ {<<"orgname">>, Value}) ->
+ #xmlel{name = <<"ORGNAME">>, attrs = [],
+ children = [{xmlcdata, Value}]};
+ldap_attribute_to_vcard(vCardO,
+ {<<"orgunit">>, Value}) ->
+ #xmlel{name = <<"ORGUNIT">>, attrs = [],
+ children = [{xmlcdata, Value}]};
+ldap_attribute_to_vcard(vCardA,
+ {<<"locality">>, Value}) ->
+ #xmlel{name = <<"LOCALITY">>, attrs = [],
+ children = [{xmlcdata, Value}]};
+ldap_attribute_to_vcard(vCardA,
+ {<<"street">>, Value}) ->
+ #xmlel{name = <<"STREET">>, attrs = [],
+ children = [{xmlcdata, Value}]};
+ldap_attribute_to_vcard(vCardA, {<<"ctry">>, Value}) ->
+ #xmlel{name = <<"CTRY">>, attrs = [],
+ children = [{xmlcdata, Value}]};
+ldap_attribute_to_vcard(vCardA,
+ {<<"region">>, Value}) ->
+ #xmlel{name = <<"REGION">>, attrs = [],
+ children = [{xmlcdata, Value}]};
+ldap_attribute_to_vcard(vCardA, {<<"pcode">>, Value}) ->
+ #xmlel{name = <<"PCODE">>, attrs = [],
+ children = [{xmlcdata, Value}]};
+ldap_attribute_to_vcard(_, _) -> none.
-define(TLFIELD(Type, Label, Var),
- {xmlelement, "field", [{"type", Type},
- {"label", translate:translate(Lang, Label)},
- {"var", Var}], []}).
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"type">>, Type},
+ {<<"label">>, translate:translate(Lang, Label)},
+ {<<"var">>, Var}],
+ children = []}).
-define(FORM(JID, SearchFields),
- [{xmlelement, "instructions", [],
- [{xmlcdata, translate:translate(Lang, "You need an x:data capable client to search")}]},
- {xmlelement, "x", [{"xmlns", ?NS_XDATA}, {"type", "form"}],
- [{xmlelement, "title", [],
- [{xmlcdata, translate:translate(Lang, "Search users in ") ++
- jlib:jid_to_string(JID)}]},
- {xmlelement, "instructions", [],
- [{xmlcdata, translate:translate(Lang, "Fill in fields to search "
- "for any matching Jabber User")}]}
- ] ++ lists:map(fun({X,Y}) -> ?TLFIELD("text-single", X, Y) end, SearchFields)}]).
+ [#xmlel{name = <<"instructions">>, attrs = [],
+ children =
+ [{xmlcdata,
+ translate:translate(Lang,
+ <<"You need an x:data capable client to "
+ "search">>)}]},
+ #xmlel{name = <<"x">>,
+ attrs =
+ [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"form">>}],
+ children =
+ [#xmlel{name = <<"title">>, attrs = [],
+ children =
+ [{xmlcdata,
+ <<(translate:translate(Lang,
+ <<"Search users in ">>))/binary,
+ (jlib:jid_to_string(JID))/binary>>}]},
+ #xmlel{name = <<"instructions">>, attrs = [],
+ children =
+ [{xmlcdata,
+ translate:translate(Lang,
+ <<"Fill in fields to search for any matching "
+ "Jabber User">>)}]}]
+ ++
+ lists:map(fun ({X, Y}) ->
+ ?TLFIELD(<<"text-single">>, X, Y)
+ end,
+ SearchFields)}]).
do_route(State, From, To, Packet) ->
spawn(?MODULE, route, [State, From, To, Packet]).
@@ -411,162 +438,186 @@ do_route(State, From, To, Packet) ->
route(State, From, To, Packet) ->
#jid{user = User, resource = Resource} = To,
ServerHost = State#state.serverhost,
- if
- (User /= "") or (Resource /= "") ->
- Err = jlib:make_error_reply(Packet, ?ERR_SERVICE_UNAVAILABLE),
- ejabberd_router:route(To, From, Err);
- true ->
- IQ = jlib:iq_query_info(Packet),
- case IQ of
- #iq{type = Type, xmlns = ?NS_SEARCH, lang = Lang, sub_el = SubEl} ->
- case Type of
- set ->
- XDataEl = find_xdata_el(SubEl),
- case XDataEl of
- false ->
- Err = jlib:make_error_reply(
- Packet, ?ERR_BAD_REQUEST),
- ejabberd_router:route(To, From, Err);
- _ ->
- XData = jlib:parse_xdata_submit(XDataEl),
- case XData of
- invalid ->
- Err = jlib:make_error_reply(
- Packet,
- ?ERR_BAD_REQUEST),
- ejabberd_router:route(To, From,
- Err);
- _ ->
- ResIQ =
- IQ#iq{
- type = result,
- sub_el =
- [{xmlelement,
- "query",
- [{"xmlns", ?NS_SEARCH}],
- [{xmlelement, "x",
- [{"xmlns", ?NS_XDATA},
- {"type", "result"}],
- search_result(Lang, To, State, XData)
- }]}]},
- ejabberd_router:route(
- To, From, jlib:iq_to_xml(ResIQ))
- end
- end;
- get ->
- SearchFields = State#state.search_fields,
- ResIQ = IQ#iq{type = result,
- sub_el = [{xmlelement,
- "query",
- [{"xmlns", ?NS_SEARCH}],
- ?FORM(To, SearchFields)
- }]},
- ejabberd_router:route(To,
- From,
- jlib:iq_to_xml(ResIQ))
- end;
- #iq{type = Type, xmlns = ?NS_DISCO_INFO, lang = Lang} ->
- case Type of
- set ->
- Err = jlib:make_error_reply(
- Packet, ?ERR_NOT_ALLOWED),
- ejabberd_router:route(To, From, Err);
- get ->
- Info = ejabberd_hooks:run_fold(
- disco_info, ServerHost, [],
- [ServerHost, ?MODULE, "", ""]),
- ResIQ =
- IQ#iq{type = result,
- sub_el = [{xmlelement,
- "query",
- [{"xmlns", ?NS_DISCO_INFO}],
- [{xmlelement, "identity",
- [{"category", "directory"},
- {"type", "user"},
- {"name",
- translate:translate(Lang, "vCard User Search")}],
- []},
- {xmlelement, "feature",
- [{"var", ?NS_SEARCH}], []},
- {xmlelement, "feature",
- [{"var", ?NS_VCARD}], []}
- ] ++ Info
- }]},
- ejabberd_router:route(To,
- From,
- jlib:iq_to_xml(ResIQ))
- end;
- #iq{type = Type, xmlns = ?NS_DISCO_ITEMS} ->
- case Type of
- set ->
- Err = jlib:make_error_reply(
- Packet, ?ERR_NOT_ALLOWED),
- ejabberd_router:route(To, From, Err);
- get ->
- ResIQ =
- IQ#iq{type = result,
- sub_el = [{xmlelement,
- "query",
- [{"xmlns", ?NS_DISCO_ITEMS}],
- []}]},
- ejabberd_router:route(To,
- From,
- jlib:iq_to_xml(ResIQ))
- end;
- #iq{type = get, xmlns = ?NS_VCARD, lang = Lang} ->
- ResIQ =
- IQ#iq{type = result,
- sub_el = [{xmlelement,
- "vCard",
- [{"xmlns", ?NS_VCARD}],
- iq_get_vcard(Lang)}]},
- ejabberd_router:route(To,
- From,
- jlib:iq_to_xml(ResIQ));
- _ ->
- Err = jlib:make_error_reply(Packet,
- ?ERR_SERVICE_UNAVAILABLE),
- ejabberd_router:route(To, From, Err)
- end
+ if (User /= <<"">>) or (Resource /= <<"">>) ->
+ Err = jlib:make_error_reply(Packet,
+ ?ERR_SERVICE_UNAVAILABLE),
+ ejabberd_router:route(To, From, Err);
+ true ->
+ IQ = jlib:iq_query_info(Packet),
+ case IQ of
+ #iq{type = Type, xmlns = ?NS_SEARCH, lang = Lang,
+ sub_el = SubEl} ->
+ case Type of
+ set ->
+ XDataEl = find_xdata_el(SubEl),
+ case XDataEl of
+ false ->
+ Err = jlib:make_error_reply(Packet,
+ ?ERR_BAD_REQUEST),
+ ejabberd_router:route(To, From, Err);
+ _ ->
+ XData = jlib:parse_xdata_submit(XDataEl),
+ case XData of
+ invalid ->
+ Err = jlib:make_error_reply(Packet,
+ ?ERR_BAD_REQUEST),
+ ejabberd_router:route(To, From, Err);
+ _ ->
+ ResIQ = IQ#iq{type = result,
+ sub_el =
+ [#xmlel{name = <<"query">>,
+ attrs =
+ [{<<"xmlns">>,
+ ?NS_SEARCH}],
+ children =
+ [#xmlel{name =
+ <<"x">>,
+ attrs =
+ [{<<"xmlns">>,
+ ?NS_XDATA},
+ {<<"type">>,
+ <<"result">>}],
+ children
+ =
+ search_result(Lang,
+ To,
+ State,
+ XData)}]}]},
+ ejabberd_router:route(To, From,
+ jlib:iq_to_xml(ResIQ))
+ end
+ end;
+ get ->
+ SearchFields = State#state.search_fields,
+ ResIQ = IQ#iq{type = result,
+ sub_el =
+ [#xmlel{name = <<"query">>,
+ attrs =
+ [{<<"xmlns">>,
+ ?NS_SEARCH}],
+ children =
+ ?FORM(To, SearchFields)}]},
+ ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ))
+ end;
+ #iq{type = Type, xmlns = ?NS_DISCO_INFO, lang = Lang} ->
+ case Type of
+ set ->
+ Err = jlib:make_error_reply(Packet, ?ERR_NOT_ALLOWED),
+ ejabberd_router:route(To, From, Err);
+ get ->
+ Info = ejabberd_hooks:run_fold(disco_info, ServerHost,
+ [],
+ [ServerHost, ?MODULE,
+ <<"">>, <<"">>]),
+ ResIQ = IQ#iq{type = result,
+ sub_el =
+ [#xmlel{name = <<"query">>,
+ attrs =
+ [{<<"xmlns">>,
+ ?NS_DISCO_INFO}],
+ children =
+ [#xmlel{name =
+ <<"identity">>,
+ attrs =
+ [{<<"category">>,
+ <<"directory">>},
+ {<<"type">>,
+ <<"user">>},
+ {<<"name">>,
+ translate:translate(Lang,
+ <<"vCard User Search">>)}],
+ children = []},
+ #xmlel{name =
+ <<"feature">>,
+ attrs =
+ [{<<"var">>,
+ ?NS_SEARCH}],
+ children = []},
+ #xmlel{name =
+ <<"feature">>,
+ attrs =
+ [{<<"var">>,
+ ?NS_VCARD}],
+ children = []}]
+ ++ Info}]},
+ ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ))
+ end;
+ #iq{type = Type, xmlns = ?NS_DISCO_ITEMS} ->
+ case Type of
+ set ->
+ Err = jlib:make_error_reply(Packet, ?ERR_NOT_ALLOWED),
+ ejabberd_router:route(To, From, Err);
+ get ->
+ ResIQ = IQ#iq{type = result,
+ sub_el =
+ [#xmlel{name = <<"query">>,
+ attrs =
+ [{<<"xmlns">>,
+ ?NS_DISCO_ITEMS}],
+ children = []}]},
+ ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ))
+ end;
+ #iq{type = get, xmlns = ?NS_VCARD, lang = Lang} ->
+ ResIQ = IQ#iq{type = result,
+ sub_el =
+ [#xmlel{name = <<"vCard">>,
+ attrs = [{<<"xmlns">>, ?NS_VCARD}],
+ children = iq_get_vcard(Lang)}]},
+ ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ));
+ _ ->
+ Err = jlib:make_error_reply(Packet,
+ ?ERR_SERVICE_UNAVAILABLE),
+ ejabberd_router:route(To, From, Err)
+ end
end.
iq_get_vcard(Lang) ->
- [{xmlelement, "FN", [],
- [{xmlcdata, "ejabberd/mod_vcard"}]},
- {xmlelement, "URL", [],
- [{xmlcdata, ?EJABBERD_URI}]},
- {xmlelement, "DESC", [],
- [{xmlcdata, translate:translate(
- Lang,
- "ejabberd vCard module") ++
- "\nCopyright (c) 2003-2013 ProcessOne"}]}].
+ [#xmlel{name = <<"FN">>, attrs = [],
+ children = [{xmlcdata, <<"ejabberd/mod_vcard">>}]},
+ #xmlel{name = <<"URL">>, attrs = [],
+ children = [{xmlcdata, ?EJABBERD_URI}]},
+ #xmlel{name = <<"DESC">>, attrs = [],
+ children =
+ [{xmlcdata,
+ <<(translate:translate(Lang,
+ <<"ejabberd vCard module">>))/binary,
+ "\nCopyright (c) 2003-2013 ProcessOne">>}]}].
-define(LFIELD(Label, Var),
- {xmlelement, "field", [{"label", translate:translate(Lang, Label)},
- {"var", Var}], []}).
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"label">>, translate:translate(Lang, Label)},
+ {<<"var">>, Var}],
+ children = []}).
search_result(Lang, JID, State, Data) ->
SearchReported = State#state.search_reported,
- Header = [{xmlelement, "title", [],
- [{xmlcdata, translate:translate(Lang, "Search Results for ") ++
- jlib:jid_to_string(JID)}]},
- {xmlelement, "reported", [],
- [?TLFIELD("text-single", "Jabber ID", "jid")] ++
- lists:map(
- fun({Name, Value}) -> ?TLFIELD("text-single", Name, Value) end,
- SearchReported)
- }],
+ Header = [#xmlel{name = <<"title">>, attrs = [],
+ children =
+ [{xmlcdata,
+ <<(translate:translate(Lang,
+ <<"Search Results for ">>))/binary,
+ (jlib:jid_to_string(JID))/binary>>}]},
+ #xmlel{name = <<"reported">>, attrs = [],
+ children =
+ [?TLFIELD(<<"text-single">>, <<"Jabber ID">>,
+ <<"jid">>)]
+ ++
+ lists:map(fun ({Name, Value}) ->
+ ?TLFIELD(<<"text-single">>, Name,
+ Value)
+ end,
+ SearchReported)}],
case search(State, Data) of
- error ->
- Header;
- Result ->
- Header ++ Result
+ error -> Header;
+ Result -> Header ++ Result
end.
-define(FIELD(Var, Val),
- {xmlelement, "field", [{"var", Var}],
- [{xmlelement, "value", [],
- [{xmlcdata, Val}]}]}).
+ #xmlel{name = <<"field">>, attrs = [{<<"var">>, Var}],
+ children =
+ [#xmlel{name = <<"value">>, attrs = [],
+ children = [{xmlcdata, Val}]}]}).
search(State, Data) ->
Base = State#state.base,
@@ -575,247 +626,203 @@ search(State, Data) ->
UIDs = State#state.uids,
Limit = State#state.matches,
ReportedAttrs = State#state.search_reported_attrs,
- Filter = eldap:'and'([SearchFilter, eldap_utils:make_filter(Data, UIDs)]),
+ Filter = eldap:'and'([SearchFilter,
+ eldap_utils:make_filter(Data, UIDs)]),
case eldap_pool:search(Eldap_ID,
- [{base, Base},
- {filter, Filter},
- {limit, Limit},
- {deref_aliases, State#state.deref_aliases},
- {attributes, ReportedAttrs}]) of
- #eldap_search_result{entries = E} ->
- search_items(E, State);
- _ ->
- error
+ [{base, Base}, {filter, Filter}, {limit, Limit},
+ {deref_aliases, State#state.deref_aliases},
+ {attributes, ReportedAttrs}])
+ of
+ #eldap_search_result{entries = E} ->
+ search_items(E, State);
+ _ -> error
end.
search_items(Entries, State) ->
LServer = State#state.serverhost,
SearchReported = State#state.search_reported,
VCardMap = State#state.vcard_map,
- UIDs = State#state.uids,
- Attributes = lists:map(
- fun(E) ->
- #eldap_entry{attributes = Attrs} = E,
- Attrs
- end, Entries),
- lists:flatmap(
- fun(Attrs) ->
- case eldap_utils:find_ldap_attrs(UIDs, Attrs) of
- {U, UIDAttrFormat} ->
- case eldap_utils:get_user_part(U, UIDAttrFormat) of
- {ok, Username} ->
- case ejabberd_auth:is_user_exists(Username, LServer) of
- true ->
- RFields = lists:map(
- fun({_, VCardName}) ->
- {VCardName,
- map_vcard_attr(
- VCardName,
- Attrs,
- VCardMap,
- {Username, ?MYNAME})}
- end, SearchReported),
- Result = [?FIELD("jid", Username ++ "@" ++ LServer)] ++
- [?FIELD(Name, Value) || {Name, Value} <- RFields],
- [{xmlelement, "item", [], Result}];
- _ ->
- []
- end;
- _ ->
- []
- end;
- "" ->
- []
- end
- end, Attributes).
-
-remove_user(_User) ->
- true.
+ UIDs = State#state.uids,
+ Attributes = lists:map(fun (E) ->
+ #eldap_entry{attributes = Attrs} = E, Attrs
+ end,
+ Entries),
+ lists:flatmap(fun (Attrs) ->
+ case eldap_utils:find_ldap_attrs(UIDs, Attrs) of
+ {U, UIDAttrFormat} ->
+ case eldap_utils:get_user_part(U, UIDAttrFormat)
+ of
+ {ok, Username} ->
+ case
+ ejabberd_auth:is_user_exists(Username,
+ LServer)
+ of
+ true ->
+ RFields = lists:map(fun ({_,
+ VCardName}) ->
+ {VCardName,
+ map_vcard_attr(VCardName,
+ Attrs,
+ VCardMap,
+ {Username,
+ ?MYNAME})}
+ end,
+ SearchReported),
+ Result = [?FIELD(<<"jid">>,
+ <<Username/binary,
+ "@",
+ LServer/binary>>)]
+ ++
+ [?FIELD(Name, Value)
+ || {Name, Value}
+ <- RFields],
+ [#xmlel{name = <<"item">>,
+ attrs = [],
+ children = Result}];
+ _ -> []
+ end;
+ _ -> []
+ end;
+ <<"">> -> []
+ end
+ end,
+ Attributes).
+
+remove_user(_User) -> true.
%%%-----------------------
%%% Auxiliary functions.
%%%-----------------------
map_vcard_attr(VCardName, Attributes, Pattern, UD) ->
- Res = lists:filter(
- fun({Name, _, _}) ->
- eldap_utils:case_insensitive_match(Name, VCardName)
- end, Pattern),
+ Res = lists:filter(fun ({Name, _, _}) ->
+ eldap_utils:case_insensitive_match(Name,
+ VCardName)
+ end,
+ Pattern),
case Res of
- [{_, Str, Attrs}] ->
- process_pattern(Str, UD,
- [eldap_utils:get_ldap_attr(X, Attributes) || X<-Attrs]);
- _ -> ""
+ [{_, Str, Attrs}] ->
+ process_pattern(Str, UD,
+ [eldap_utils:get_ldap_attr(X, Attributes)
+ || X <- Attrs]);
+ _ -> <<"">>
end.
process_pattern(Str, {User, Domain}, AttrValues) ->
- eldap_filter:do_sub(
- Str,
- [{"%u", User},{"%d", Domain}] ++
- [{"%s", V, 1} || V <- AttrValues]).
+ eldap_filter:do_sub(Str,
+ [{<<"%u">>, User}, {<<"%d">>, Domain}] ++
+ [{<<"%s">>, V, 1} || V <- AttrValues]).
-find_xdata_el({xmlelement, _Name, _Attrs, SubEls}) ->
+find_xdata_el(#xmlel{children = SubEls}) ->
find_xdata_el1(SubEls).
-find_xdata_el1([]) ->
- false;
-find_xdata_el1([{xmlelement, Name, Attrs, SubEls} | Els]) ->
- case xml:get_attr_s("xmlns", Attrs) of
- ?NS_XDATA ->
- {xmlelement, Name, Attrs, SubEls};
- _ ->
- find_xdata_el1(Els)
+find_xdata_el1([]) -> false;
+find_xdata_el1([#xmlel{name = Name, attrs = Attrs,
+ children = SubEls}
+ | Els]) ->
+ case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ ?NS_XDATA ->
+ #xmlel{name = Name, attrs = Attrs, children = SubEls};
+ _ -> find_xdata_el1(Els)
end;
-find_xdata_el1([_ | Els]) ->
- find_xdata_el1(Els).
+find_xdata_el1([_ | Els]) -> find_xdata_el1(Els).
parse_options(Host, Opts) ->
- MyHost = gen_mod:get_opt_host(Host, Opts, "vjud.@HOST@"),
- Search = gen_mod:get_opt(search, Opts, true),
- Matches = case gen_mod:get_opt(matches, Opts, 30) of
- infinity -> 0;
- N -> N
- end,
- Eldap_ID = atom_to_list(gen_mod:get_module_proc(Host, ?PROCNAME)),
- LDAPServers = case gen_mod:get_opt(ldap_servers, Opts, undefined) of
- undefined ->
- ejabberd_config:get_local_option({ldap_servers, Host});
- S -> S
- end,
- LDAPBackups = case gen_mod:get_opt(ldap_backups, Opts, undefined) of
- undefined ->
- ejabberd_config:get_local_option({ldap_servers, Host});
- Backups -> Backups
- end,
- LDAPEncrypt = case gen_mod:get_opt(ldap_encrypt, Opts, undefined) of
- undefined ->
- ejabberd_config:get_local_option({ldap_encrypt, Host});
- E -> E
- end,
- LDAPTLSVerify = case gen_mod:get_opt(ldap_tls_verify, Opts, undefined) of
- undefined ->
- ejabberd_config:get_local_option({ldap_tls_verify, Host});
- Verify -> Verify
- end,
- LDAPTLSCAFile = case gen_mod:get_opt(ldap_tls_cacertfile, Opts, undefined) of
- undefined ->
- ejabberd_config:get_local_option({ldap_tls_cacertfile, Host});
- CAFile -> CAFile
- end,
- LDAPTLSDepth = case gen_mod:get_opt(ldap_tls_depth, Opts, undefined) of
- undefined ->
- ejabberd_config:get_local_option({ldap_tls_depth, Host});
- Depth ->
- Depth
- end,
- LDAPPortTemp = case gen_mod:get_opt(ldap_port, Opts, undefined) of
- undefined ->
- ejabberd_config:get_local_option({ldap_port, Host});
- PT -> PT
- end,
- LDAPPort = case LDAPPortTemp of
- undefined ->
- case LDAPEncrypt of
- tls -> ?LDAPS_PORT;
- starttls -> ?LDAP_PORT;
- _ -> ?LDAP_PORT
- end;
- P -> P
- end,
- LDAPBase = case gen_mod:get_opt(ldap_base, Opts, undefined) of
- undefined ->
- ejabberd_config:get_local_option({ldap_base, Host});
- B -> B
- end,
- UIDs = case gen_mod:get_opt(ldap_uids, Opts, undefined) of
- undefined ->
- case ejabberd_config:get_local_option({ldap_uids, Host}) of
- undefined -> [{"uid", "%u"}];
- UI -> eldap_utils:uids_domain_subst(Host, UI)
- end;
- UI -> eldap_utils:uids_domain_subst(Host, UI)
- end,
- RootDN = case gen_mod:get_opt(ldap_rootdn, Opts, undefined) of
- undefined ->
- case ejabberd_config:get_local_option({ldap_rootdn, Host}) of
- undefined -> "";
- RDN -> RDN
- end;
- RDN -> RDN
- end,
- Password = case gen_mod:get_opt(ldap_password, Opts, undefined) of
- undefined ->
- case ejabberd_config:get_local_option({ldap_password, Host}) of
- undefined -> "";
- Pass -> Pass
- end;
- Pass -> Pass
- end,
- SubFilter = lists:flatten(eldap_utils:generate_subfilter(UIDs)),
- UserFilter = case gen_mod:get_opt(ldap_filter, Opts, undefined) of
- undefined ->
- case ejabberd_config:get_local_option({ldap_filter, Host}) of
- undefined -> SubFilter;
- "" -> SubFilter;
- F ->
- eldap_utils:check_filter(F),
- "(&" ++ SubFilter ++ F ++ ")"
- end;
- "" -> SubFilter;
- F ->
- eldap_utils:check_filter(F),
- "(&" ++ SubFilter ++ F ++ ")"
- end,
- {ok, SearchFilter} = eldap_filter:parse(
- eldap_filter:do_sub(UserFilter, [{"%u","*"}])),
- VCardMap = gen_mod:get_opt(ldap_vcard_map, Opts, ?VCARD_MAP),
- SearchFields = gen_mod:get_opt(ldap_search_fields, Opts, ?SEARCH_FIELDS),
- SearchReported = gen_mod:get_opt(ldap_search_reported, Opts, ?SEARCH_REPORTED),
- %% In search requests we need to fetch only attributes defined
- %% in vcard-map and search-reported. In some cases,
- %% this will essentially reduce network traffic from an LDAP server.
- UIDAttrs = [UAttr || {UAttr, _} <- UIDs],
- VCardMapAttrs = lists:usort(
- lists:append([A || {_, _, A} <- VCardMap]) ++ UIDAttrs),
- SearchReportedAttrs =
- lists:usort(lists:flatmap(
- fun({_, N}) ->
- case lists:keysearch(N, 1, VCardMap) of
- {value, {_, _, L}} -> L;
- _ -> []
- end
- end, SearchReported) ++ UIDAttrs),
- DerefAliases = case gen_mod:get_opt(deref_aliases, Opts, undefined) of
- undefined ->
- case ejabberd_config:get_local_option(
- {deref_aliases, Host}) of
- undefined -> never;
- D -> D
- end;
- D -> D
- end,
- #state{serverhost = Host,
- myhost = MyHost,
- eldap_id = Eldap_ID,
- search = Search,
- servers = LDAPServers,
- backups = LDAPBackups,
- port = LDAPPort,
- tls_options = [{encrypt, LDAPEncrypt},
- {tls_verify, LDAPTLSVerify},
- {tls_cacertfile, LDAPTLSCAFile},
- {tls_depth, LDAPTLSDepth}],
- dn = RootDN,
- base = LDAPBase,
- password = Password,
- uids = UIDs,
- vcard_map = VCardMap,
+ MyHost = gen_mod:get_opt_host(Host, Opts,
+ <<"vjud.@HOST@">>),
+ Search = gen_mod:get_opt(search, Opts,
+ fun(B) when is_boolean(B) -> B end,
+ true),
+ Matches = gen_mod:get_opt(matches, Opts,
+ fun(infinity) -> 0;
+ (I) when is_integer(I), I>0 -> I
+ end, 30),
+ Eldap_ID = jlib:atom_to_binary(gen_mod:get_module_proc(Host, ?PROCNAME)),
+ Cfg = eldap_utils:get_config(Host, Opts),
+ UIDsTemp = eldap_utils:get_opt(
+ {ldap_uids, Host}, Opts,
+ fun(Us) ->
+ lists:map(
+ fun({U, P}) ->
+ {iolist_to_binary(U),
+ iolist_to_binary(P)};
+ ({U}) ->
+ {iolist_to_binary(U)}
+ end, Us)
+ end, [{<<"uid">>, <<"%u">>}]),
+ UIDs = eldap_utils:uids_domain_subst(Host, UIDsTemp),
+ SubFilter = eldap_utils:generate_subfilter(UIDs),
+ UserFilter = case eldap_utils:get_opt(
+ {ldap_filter, Host}, Opts,
+ fun check_filter/1, <<"">>) of
+ <<"">> ->
+ SubFilter;
+ F ->
+ <<"(&", SubFilter/binary, F/binary, ")">>
+ end,
+ {ok, SearchFilter} =
+ eldap_filter:parse(eldap_filter:do_sub(UserFilter,
+ [{<<"%u">>, <<"*">>}])),
+ VCardMap = gen_mod:get_opt(ldap_vcard_map, Opts,
+ fun(Ls) ->
+ lists:map(
+ fun({S, P, L}) ->
+ {iolist_to_binary(S),
+ iolist_to_binary(P),
+ [iolist_to_binary(E)
+ || E <- L]}
+ end, Ls)
+ end, ?VCARD_MAP),
+ SearchFields = gen_mod:get_opt(ldap_search_fields, Opts,
+ fun(Ls) ->
+ [{iolist_to_binary(S),
+ iolist_to_binary(P)}
+ || {S, P} <- Ls]
+ end, ?SEARCH_FIELDS),
+ SearchReported = gen_mod:get_opt(ldap_search_reported, Opts,
+ fun(Ls) ->
+ [{iolist_to_binary(S),
+ iolist_to_binary(P)}
+ || {S, P} <- Ls]
+ end, ?SEARCH_REPORTED),
+ UIDAttrs = [UAttr || {UAttr, _} <- UIDs],
+ VCardMapAttrs = lists:usort(lists:append([A
+ || {_, _, A} <- VCardMap])
+ ++ UIDAttrs),
+ SearchReportedAttrs = lists:usort(lists:flatmap(fun ({_,
+ N}) ->
+ case
+ lists:keysearch(N,
+ 1,
+ VCardMap)
+ of
+ {value,
+ {_, _, L}} ->
+ L;
+ _ -> []
+ end
+ end,
+ SearchReported)
+ ++ UIDAttrs),
+ #state{serverhost = Host, myhost = MyHost,
+ eldap_id = Eldap_ID, search = Search,
+ servers = Cfg#eldap_config.servers,
+ backups = Cfg#eldap_config.backups,
+ port = Cfg#eldap_config.port,
+ tls_options = Cfg#eldap_config.tls_options,
+ dn = Cfg#eldap_config.dn,
+ password = Cfg#eldap_config.password,
+ base = Cfg#eldap_config.base,
+ deref_aliases = Cfg#eldap_config.deref_aliases,
+ uids = UIDs, vcard_map = VCardMap,
vcard_map_attrs = VCardMapAttrs,
- user_filter = UserFilter,
- search_filter = SearchFilter,
+ user_filter = UserFilter, search_filter = SearchFilter,
search_fields = SearchFields,
search_reported = SearchReported,
search_reported_attrs = SearchReportedAttrs,
- deref_aliases = DerefAliases,
- matches = Matches
- }.
+ matches = Matches}.
+
+check_filter(F) ->
+ NewF = iolist_to_binary(F),
+ {ok, _} = eldap_filter:parse(NewF),
+ NewF.
diff --git a/src/mod_vcard_xupdate.erl b/src/mod_vcard_xupdate.erl
index 3ee632aaa..f1747859f 100644
--- a/src/mod_vcard_xupdate.erl
+++ b/src/mod_vcard_xupdate.erl
@@ -10,17 +10,17 @@
-behaviour(gen_mod).
%% gen_mod callbacks
--export([start/2,
- stop/1]).
+-export([start/2, stop/1]).
%% hooks
--export([update_presence/3,
- vcard_set/3]).
+-export([update_presence/3, vcard_set/3, export/1]).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
--record(vcard_xupdate, {us, hash}).
+-record(vcard_xupdate, {us = {<<>>, <<>>} :: {binary(), binary()},
+ hash = <<>> :: binary()}).
%%====================================================================
%% gen_mod callbacks
@@ -28,48 +28,48 @@
start(Host, Opts) ->
case gen_mod:db_type(Opts) of
- mnesia ->
- mnesia:create_table(vcard_xupdate,
- [{disc_copies, [node()]},
- {attributes,
- record_info(fields, vcard_xupdate)}]);
- _ ->
- ok
+ mnesia ->
+ mnesia:create_table(vcard_xupdate,
+ [{disc_copies, [node()]},
+ {attributes,
+ record_info(fields, vcard_xupdate)}]),
+ update_table();
+ _ -> ok
end,
- ejabberd_hooks:add(c2s_update_presence, Host,
- ?MODULE, update_presence, 100),
- ejabberd_hooks:add(vcard_set, Host,
- ?MODULE, vcard_set, 100),
+ ejabberd_hooks:add(c2s_update_presence, Host, ?MODULE,
+ update_presence, 100),
+ ejabberd_hooks:add(vcard_set, Host, ?MODULE, vcard_set,
+ 100),
ok.
stop(Host) ->
ejabberd_hooks:delete(c2s_update_presence, Host,
?MODULE, update_presence, 100),
- ejabberd_hooks:delete(vcard_set, Host,
- ?MODULE, vcard_set, 100),
+ ejabberd_hooks:delete(vcard_set, Host, ?MODULE,
+ vcard_set, 100),
ok.
%%====================================================================
%% Hooks
%%====================================================================
-update_presence({xmlelement, "presence", Attrs, _Els} = Packet, User, Host) ->
- case xml:get_attr_s("type", Attrs) of
- [] ->
- presence_with_xupdate(Packet, User, Host);
- _ ->
- Packet
+update_presence(#xmlel{name = <<"presence">>, attrs = Attrs} = Packet,
+ User, Host) ->
+ case xml:get_attr_s(<<"type">>, Attrs) of
+ <<>> -> presence_with_xupdate(Packet, User, Host);
+ _ -> Packet
end;
-update_presence(Packet, _User, _Host) ->
- Packet.
+update_presence(Packet, _User, _Host) -> Packet.
vcard_set(LUser, LServer, VCARD) ->
US = {LUser, LServer},
- case xml:get_path_s(VCARD, [{elem, "PHOTO"}, {elem, "BINVAL"}, cdata]) of
- [] ->
- remove_xupdate(LUser, LServer);
- BinVal ->
- add_xupdate(LUser, LServer, sha:sha(jlib:decode_base64(BinVal)))
+ case xml:get_path_s(VCARD,
+ [{elem, <<"PHOTO">>}, {elem, <<"BINVAL">>}, cdata])
+ of
+ <<>> -> remove_xupdate(LUser, LServer);
+ BinVal ->
+ add_xupdate(LUser, LServer,
+ sha:sha(jlib:decode_base64(BinVal)))
end,
ejabberd_sm:force_update_presence(US).
@@ -78,77 +78,83 @@ vcard_set(LUser, LServer, VCARD) ->
%%====================================================================
add_xupdate(LUser, LServer, Hash) ->
- add_xupdate(LUser, LServer, Hash, gen_mod:db_type(LServer, ?MODULE)).
+ add_xupdate(LUser, LServer, Hash,
+ gen_mod:db_type(LServer, ?MODULE)).
add_xupdate(LUser, LServer, Hash, mnesia) ->
- F = fun() ->
- mnesia:write(#vcard_xupdate{us = {LUser, LServer}, hash = Hash})
- end,
+ F = fun () ->
+ mnesia:write(#vcard_xupdate{us = {LUser, LServer},
+ hash = Hash})
+ end,
mnesia:transaction(F);
add_xupdate(LUser, LServer, Hash, odbc) ->
Username = ejabberd_odbc:escape(LUser),
SHash = ejabberd_odbc:escape(Hash),
- F = fun() ->
- odbc_queries:update_t(
- "vcard_xupdate",
- ["username", "hash"],
- [Username, SHash],
- ["username='", Username, "'"])
- end,
+ F = fun () ->
+ odbc_queries:update_t(<<"vcard_xupdate">>,
+ [<<"username">>, <<"hash">>],
+ [Username, SHash],
+ [<<"username='">>, Username, <<"'">>])
+ end,
ejabberd_odbc:sql_transaction(LServer, F).
get_xupdate(LUser, LServer) ->
- get_xupdate(LUser, LServer, gen_mod:db_type(LServer, ?MODULE)).
+ get_xupdate(LUser, LServer,
+ gen_mod:db_type(LServer, ?MODULE)).
get_xupdate(LUser, LServer, mnesia) ->
- case mnesia:dirty_read(vcard_xupdate, {LUser, LServer}) of
- [#vcard_xupdate{hash = Hash}] ->
- Hash;
- _ ->
- undefined
+ case mnesia:dirty_read(vcard_xupdate, {LUser, LServer})
+ of
+ [#vcard_xupdate{hash = Hash}] -> Hash;
+ _ -> undefined
end;
get_xupdate(LUser, LServer, odbc) ->
Username = ejabberd_odbc:escape(LUser),
- case ejabberd_odbc:sql_query(
- LServer, ["select hash from vcard_xupdate "
- "where username='", Username, "';"]) of
- {selected, ["hash"], [{Hash}]} ->
- Hash;
- _ ->
- undefined
+ case ejabberd_odbc:sql_query(LServer,
+ [<<"select hash from vcard_xupdate where "
+ "username='">>,
+ Username, <<"';">>])
+ of
+ {selected, [<<"hash">>], [[Hash]]} -> Hash;
+ _ -> undefined
end.
remove_xupdate(LUser, LServer) ->
- remove_xupdate(LUser, LServer, gen_mod:db_type(LServer, ?MODULE)).
+ remove_xupdate(LUser, LServer,
+ gen_mod:db_type(LServer, ?MODULE)).
remove_xupdate(LUser, LServer, mnesia) ->
- F = fun() ->
- mnesia:delete({vcard_xupdate, {LUser, LServer}})
- end,
+ F = fun () ->
+ mnesia:delete({vcard_xupdate, {LUser, LServer}})
+ end,
mnesia:transaction(F);
remove_xupdate(LUser, LServer, odbc) ->
Username = ejabberd_odbc:escape(LUser),
- F = fun() ->
- ejabberd_odbc:sql_query_t(
- ["delete from vcard_xupdate where "
- "username='", Username, "';"])
- end,
+ F = fun () ->
+ ejabberd_odbc:sql_query_t([<<"delete from vcard_xupdate where username='">>,
+ Username, <<"';">>])
+ end,
ejabberd_odbc:sql_transaction(LServer, F).
%%%----------------------------------------------------------------------
%%% Presence stanza rebuilding
%%%----------------------------------------------------------------------
-presence_with_xupdate({xmlelement, "presence", Attrs, Els}, User, Host) ->
+presence_with_xupdate(#xmlel{name = <<"presence">>,
+ attrs = Attrs, children = Els},
+ User, Host) ->
XPhotoEl = build_xphotoel(User, Host),
Els2 = presence_with_xupdate2(Els, [], XPhotoEl),
- {xmlelement, "presence", Attrs, Els2}.
+ #xmlel{name = <<"presence">>, attrs = Attrs,
+ children = Els2}.
presence_with_xupdate2([], Els2, XPhotoEl) ->
lists:reverse([XPhotoEl | Els2]);
%% This clause assumes that the x element contains only the XMLNS attribute:
-presence_with_xupdate2([{xmlelement, "x", [{"xmlns", ?NS_VCARD_UPDATE}], _}
- | Els], Els2, XPhotoEl) ->
+presence_with_xupdate2([#xmlel{name = <<"x">>,
+ attrs = [{<<"xmlns">>, ?NS_VCARD_UPDATE}]}
+ | Els],
+ Els2, XPhotoEl) ->
presence_with_xupdate2(Els, Els2, XPhotoEl);
presence_with_xupdate2([El | Els], Els2, XPhotoEl) ->
presence_with_xupdate2(Els, [El | Els2], XPhotoEl).
@@ -156,10 +162,43 @@ presence_with_xupdate2([El | Els], Els2, XPhotoEl) ->
build_xphotoel(User, Host) ->
Hash = get_xupdate(User, Host),
PhotoSubEls = case Hash of
- Hash when is_list(Hash) ->
- [{xmlcdata, Hash}];
- _ ->
- []
+ Hash when is_binary(Hash) -> [{xmlcdata, Hash}];
+ _ -> []
end,
- PhotoEl = [{xmlelement, "photo", [], PhotoSubEls}],
- {xmlelement, "x", [{"xmlns", ?NS_VCARD_UPDATE}], PhotoEl}.
+ PhotoEl = [#xmlel{name = <<"photo">>, attrs = [],
+ children = PhotoSubEls}],
+ #xmlel{name = <<"x">>,
+ attrs = [{<<"xmlns">>, ?NS_VCARD_UPDATE}],
+ children = PhotoEl}.
+
+update_table() ->
+ Fields = record_info(fields, vcard_xupdate),
+ case mnesia:table_info(vcard_xupdate, attributes) of
+ Fields ->
+ ejabberd_config:convert_table_to_binary(
+ vcard_xupdate, Fields, set,
+ fun(#vcard_xupdate{us = {U, _}}) -> U end,
+ fun(#vcard_xupdate{us = {U, S}, hash = Hash} = R) ->
+ R#vcard_xupdate{us = {iolist_to_binary(U),
+ iolist_to_binary(S)},
+ hash = iolist_to_binary(Hash)}
+ end);
+ _ ->
+ ?INFO_MSG("Recreating vcard_xupdate table", []),
+ mnesia:transform_table(vcard_xupdate, ignore, Fields)
+ end.
+
+export(_Server) ->
+ [{vcard_xupdate,
+ fun(Host, #vcard_xupdate{us = {LUser, LServer}, hash = Hash})
+ when LServer == Host ->
+ Username = ejabberd_odbc:escape(LUser),
+ SHash = ejabberd_odbc:escape(Hash),
+ [[<<"delete from vcard_xupdate where username='">>,
+ Username, <<"';">>],
+ [<<"insert into vcard_xupdate(username, "
+ "hash) values ('">>,
+ Username, <<"', '">>, SHash, <<"');">>]];
+ (_Host, _R) ->
+ []
+ end}].
diff --git a/src/mod_version.erl b/src/mod_version.erl
index 7cda907cc..088961a6b 100644
--- a/src/mod_version.erl
+++ b/src/mod_version.erl
@@ -25,66 +25,70 @@
%%%----------------------------------------------------------------------
-module(mod_version).
+
-author('alexey@process-one.net').
-behaviour(gen_mod).
--export([start/2,
- stop/1,
- process_local_iq/3]).
+-export([start/2, stop/1, process_local_iq/3]).
-include("ejabberd.hrl").
--include("jlib.hrl").
-
+-include("jlib.hrl").
start(Host, Opts) ->
- IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
- gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_VERSION,
- ?MODULE, process_local_iq, IQDisc).
+ IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
+ one_queue),
+ gen_iq_handler:add_iq_handler(ejabberd_local, Host,
+ ?NS_VERSION, ?MODULE, process_local_iq,
+ IQDisc).
stop(Host) ->
- gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_VERSION).
-
+ gen_iq_handler:remove_iq_handler(ejabberd_local, Host,
+ ?NS_VERSION).
-process_local_iq(_From, To, #iq{id = _ID, type = Type,
- xmlns = _XMLNS, sub_el = SubEl} = IQ) ->
+process_local_iq(_From, To,
+ #iq{id = _ID, type = Type, xmlns = _XMLNS,
+ sub_el = SubEl} =
+ IQ) ->
case Type of
- set ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
- get ->
- Host = To#jid.lserver,
- OS = case gen_mod:get_module_opt(Host, ?MODULE, show_os, true) of
- true -> [get_os()];
- false -> []
- end,
- IQ#iq{type = result,
- sub_el = [{xmlelement, "query",
- [{"xmlns", ?NS_VERSION}],
- [{xmlelement, "name", [],
- [{xmlcdata, "ejabberd"}]},
- {xmlelement, "version", [],
- [{xmlcdata, ?VERSION}]}
- ] ++ OS
- }]}
+ set ->
+ IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+ get ->
+ Host = To#jid.lserver,
+ OS = case gen_mod:get_module_opt(Host, ?MODULE, show_os,
+ fun(B) when is_boolean(B) -> B end,
+ true)
+ of
+ true -> [get_os()];
+ false -> []
+ end,
+ IQ#iq{type = result,
+ sub_el =
+ [#xmlel{name = <<"query">>,
+ attrs = [{<<"xmlns">>, ?NS_VERSION}],
+ children =
+ [#xmlel{name = <<"name">>, attrs = [],
+ children =
+ [{xmlcdata, <<"ejabberd">>}]},
+ #xmlel{name = <<"version">>, attrs = [],
+ children = [{xmlcdata, ?VERSION}]}]
+ ++ OS}]}
end.
-
get_os() ->
OSType = case os:type() of
- {Osfamily, Osname} ->
- atom_to_list(Osfamily) ++ "/" ++
- atom_to_list(Osname);
- Osfamily ->
- atom_to_list(Osfamily)
+ {Osfamily, Osname} ->
+ <<(iolist_to_binary(atom_to_list(Osfamily)))/binary,
+ "/", (iolist_to_binary(atom_to_list(Osname)))/binary>>;
+ Osfamily -> iolist_to_binary(atom_to_list(Osfamily))
end,
OSVersion = case os:version() of
- {Major, Minor, Release} ->
- lists:flatten(
- io_lib:format("~w.~w.~w",
- [Major, Minor, Release]));
- VersionString ->
- VersionString
+ {Major, Minor, Release} ->
+ iolist_to_binary(io_lib:format("~w.~w.~w",
+ [Major, Minor, Release]));
+ VersionString -> VersionString
end,
- OS = OSType ++ " " ++ OSVersion,
- {xmlelement, "os", [], [{xmlcdata, OS}]}.
+ OS = <<OSType/binary, " ", OSVersion/binary>>,
+ #xmlel{name = <<"os">>, attrs = [],
+ children = [{xmlcdata, OS}]}.
diff --git a/src/odbc/Makefile.in b/src/odbc/Makefile.in
index d51439fb9..3f4898d3a 100644
--- a/src/odbc/Makefile.in
+++ b/src/odbc/Makefile.in
@@ -14,7 +14,7 @@ EFLAGS += -pz ..
# make debug=true to compile Erlang module with debug informations.
ifdef debug
- EFLAGS+=+debug_info +export_all
+ EFLAGS+=+debug_info
endif
OUTDIR = ..
diff --git a/src/odbc/ejabberd_odbc.erl b/src/odbc/ejabberd_odbc.erl
index a399a53d1..1cb157c05 100644
--- a/src/odbc/ejabberd_odbc.erl
+++ b/src/odbc/ejabberd_odbc.erl
@@ -25,6 +25,7 @@
%%%----------------------------------------------------------------------
-module(ejabberd_odbc).
+
-author('alexey@process-one.net').
-define(GEN_FSM, p1_fsm).
@@ -45,68 +46,87 @@
keep_alive/1]).
%% gen_fsm callbacks
--export([init/1,
- handle_event/3,
- handle_sync_event/4,
- handle_info/3,
- terminate/3,
- print_state/1,
+-export([init/1, handle_event/3, handle_sync_event/4,
+ handle_info/3, terminate/3, print_state/1,
code_change/4]).
%% gen_fsm states
--export([connecting/2,
- connecting/3,
- session_established/2,
- session_established/3]).
+-export([connecting/2, connecting/3,
+ session_established/2, session_established/3]).
-include("ejabberd.hrl").
--record(state, {db_ref,
- db_type,
- start_interval,
- host,
- max_pending_requests_len,
- pending_requests}).
+-record(state,
+ {db_ref = self() :: pid(),
+ db_type = odbc :: pgsql | mysql | odbc,
+ start_interval = 0 :: non_neg_integer(),
+ host = <<"">> :: binary(),
+ max_pending_requests_len :: non_neg_integer(),
+ pending_requests = {0, queue:new()} :: {non_neg_integer(), queue()}}).
-define(STATE_KEY, ejabberd_odbc_state).
+
-define(NESTING_KEY, ejabberd_odbc_nesting_level).
+
-define(TOP_LEVEL_TXN, 0).
+
-define(MAX_TRANSACTION_RESTARTS, 10).
+
-define(PGSQL_PORT, 5432).
+
-define(MYSQL_PORT, 3306).
--define(TRANSACTION_TIMEOUT, 60000). % milliseconds
+-define(TRANSACTION_TIMEOUT, 60000).
+
-define(KEEPALIVE_TIMEOUT, 60000).
--define(KEEPALIVE_QUERY, "SELECT 1;").
+
+-define(KEEPALIVE_QUERY, <<"SELECT 1;">>).
%%-define(DBGFSM, true).
-ifdef(DBGFSM).
+
-define(FSMOPTS, [{debug, [trace]}]).
+
-else.
+
-define(FSMOPTS, []).
+
-endif.
%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
start(Host) ->
- ?GEN_FSM:start(ejabberd_odbc, [Host], fsm_limit_opts() ++ ?FSMOPTS).
+ (?GEN_FSM):start(ejabberd_odbc, [Host],
+ fsm_limit_opts() ++ (?FSMOPTS)).
start_link(Host, StartInterval) ->
- ?GEN_FSM:start_link(ejabberd_odbc, [Host, StartInterval],
- fsm_limit_opts() ++ ?FSMOPTS).
+ (?GEN_FSM):start_link(ejabberd_odbc,
+ [Host, StartInterval],
+ fsm_limit_opts() ++ (?FSMOPTS)).
+
+-type sql_query() :: [sql_query() | binary()].
+-type sql_query_result() :: {updated, non_neg_integer()} |
+ {error, binary()} |
+ {selected, [binary()],
+ [[binary()]]}.
+
+-spec sql_query(binary(), sql_query()) -> sql_query_result().
sql_query(Host, Query) ->
sql_call(Host, {sql_query, Query}).
%% SQL transaction based on a list of queries
%% This function automatically
-sql_transaction(Host, Queries) when is_list(Queries) ->
- F = fun() ->
- lists:foreach(fun(Query) ->
- sql_query_t(Query)
- end,
+-spec sql_transaction(binary(), [sql_query()] | fun(() -> any())) ->
+ {atomic, any()} |
+ {aborted, any()}.
+
+sql_transaction(Host, Queries)
+ when is_list(Queries) ->
+ F = fun () ->
+ lists:foreach(fun (Query) -> sql_query_t(Query) end,
Queries)
end,
sql_transaction(Host, F);
@@ -115,67 +135,64 @@ sql_transaction(Host, F) when is_function(F) ->
sql_call(Host, {sql_transaction, F}).
%% SQL bloc, based on a erlang anonymous function (F = fun)
-sql_bloc(Host, F) ->
- sql_call(Host, {sql_bloc, F}).
+sql_bloc(Host, F) -> sql_call(Host, {sql_bloc, F}).
sql_call(Host, Msg) ->
case get(?STATE_KEY) of
- undefined ->
- ?GEN_FSM:sync_send_event(ejabberd_odbc_sup:get_random_pid(Host),
- {sql_cmd, Msg, now()}, ?TRANSACTION_TIMEOUT);
- _State ->
- nested_op(Msg)
+ undefined ->
+ (?GEN_FSM):sync_send_event(ejabberd_odbc_sup:get_random_pid(Host),
+ {sql_cmd, Msg, now()},
+ ?TRANSACTION_TIMEOUT);
+ _State -> nested_op(Msg)
end.
-% perform a harmless query on all opened connexions to avoid connexion close.
keep_alive(PID) ->
- ?GEN_FSM:sync_send_event(PID, {sql_cmd, {sql_query, ?KEEPALIVE_QUERY}, now()},
- ?KEEPALIVE_TIMEOUT).
+ (?GEN_FSM):sync_send_event(PID,
+ {sql_cmd, {sql_query, ?KEEPALIVE_QUERY}, now()},
+ ?KEEPALIVE_TIMEOUT).
+
+-spec sql_query_t(sql_query()) -> sql_query_result().
%% This function is intended to be used from inside an sql_transaction:
sql_query_t(Query) ->
QRes = sql_query_internal(Query),
case QRes of
- {error, Reason} ->
- throw({aborted, Reason});
- Rs when is_list(Rs) ->
- case lists:keysearch(error, 1, Rs) of
- {value, {error, Reason}} ->
- throw({aborted, Reason});
- _ ->
- QRes
- end;
- _ ->
- QRes
+ {error, Reason} -> throw({aborted, Reason});
+ Rs when is_list(Rs) ->
+ case lists:keysearch(error, 1, Rs) of
+ {value, {error, Reason}} -> throw({aborted, Reason});
+ _ -> QRes
+ end;
+ _ -> QRes
end.
%% Escape character that will confuse an SQL engine
-escape(S) when is_list(S) ->
- [odbc_queries:escape(C) || C <- S];
-escape(S) when is_binary(S) ->
- escape(binary_to_list(S)).
+escape(S) ->
+ << <<(odbc_queries:escape(Char))/binary>> || <<Char>> <= S >>.
%% Escape character that will confuse an SQL engine
%% Percent and underscore only need to be escaped for pattern matching like
%% statement
-escape_like(S) when is_list(S) ->
- [escape_like(C) || C <- S];
-escape_like($%) -> "\\%";
-escape_like($_) -> "\\_";
-escape_like(C) -> odbc_queries:escape(C).
-
-to_bool("t") -> true;
-to_bool("true") -> true;
-to_bool("1") -> true;
+escape_like(S) when is_binary(S) ->
+ << <<(escape_like(C))/binary>> || <<C>> <= S >>;
+escape_like($%) -> <<"\\%">>;
+escape_like($_) -> <<"\\_">>;
+escape_like(C) when is_integer(C), C >= 0, C =< 255 -> odbc_queries:escape(C).
+
+to_bool(<<"t">>) -> true;
+to_bool(<<"true">>) -> true;
+to_bool(<<"1">>) -> true;
to_bool(true) -> true;
to_bool(1) -> true;
to_bool(_) -> false.
encode_term(Term) ->
- escape(lists:flatten(io_lib:print(Term))).
+ escape(list_to_binary(
+ erl_prettypr:format(erl_syntax:abstract(Term)))).
-decode_term(Str) ->
- {ok, Tokens, _} = erl_scan:string(Str ++ "."),
+decode_term(Bin) ->
+ Str = binary_to_list(<<Bin/binary, ".">>),
+ {ok, Tokens, _} = erl_scan:string(Str),
{ok, Term} = erl_parse:parse_term(Tokens),
Term.
@@ -183,76 +200,83 @@ decode_term(Str) ->
%%% Callback functions from gen_fsm
%%%----------------------------------------------------------------------
init([Host, StartInterval]) ->
- case ejabberd_config:get_local_option({odbc_keepalive_interval, Host}) of
- KeepaliveInterval when is_integer(KeepaliveInterval) ->
- timer:apply_interval(KeepaliveInterval*1000, ?MODULE,
- keep_alive, [self()]);
- undefined ->
- ok;
- _Other ->
- ?ERROR_MSG("Wrong odbc_keepalive_interval definition '~p'"
- " for host ~p.~n", [_Other, Host])
+ case ejabberd_config:get_local_option(
+ {odbc_keepalive_interval, Host},
+ fun(I) when is_integer(I), I>0 -> I end) of
+ undefined ->
+ ok;
+ KeepaliveInterval ->
+ timer:apply_interval(KeepaliveInterval * 1000, ?MODULE,
+ keep_alive, [self()])
end,
[DBType | _] = db_opts(Host),
- ?GEN_FSM:send_event(self(), connect),
+ (?GEN_FSM):send_event(self(), connect),
ejabberd_odbc_sup:add_pid(Host, self()),
- {ok, connecting, #state{db_type = DBType,
- host = Host,
- max_pending_requests_len = max_fsm_queue(),
- pending_requests = {0, queue:new()},
- start_interval = StartInterval}}.
+ {ok, connecting,
+ #state{db_type = DBType, host = Host,
+ max_pending_requests_len = max_fsm_queue(),
+ pending_requests = {0, queue:new()},
+ start_interval = StartInterval}}.
connecting(connect, #state{host = Host} = State) ->
ConnectRes = case db_opts(Host) of
- [mysql | Args] ->
- apply(fun mysql_connect/5, Args);
- [pgsql | Args] ->
- apply(fun pgsql_connect/5, Args);
- [odbc | Args] ->
- apply(fun odbc_connect/1, Args)
+ [mysql | Args] -> apply(fun mysql_connect/5, Args);
+ [pgsql | Args] -> apply(fun pgsql_connect/5, Args);
+ [odbc | Args] -> apply(fun odbc_connect/1, Args)
end,
{_, PendingRequests} = State#state.pending_requests,
case ConnectRes of
- {ok, Ref} ->
- erlang:monitor(process, Ref),
- lists:foreach(
- fun(Req) ->
- ?GEN_FSM:send_event(self(), Req)
- end, queue:to_list(PendingRequests)),
- {next_state, session_established,
- State#state{db_ref = Ref,
- pending_requests = {0, queue:new()}}};
- {error, Reason} ->
- ?INFO_MSG("~p connection failed:~n"
- "** Reason: ~p~n"
- "** Retry after: ~p seconds",
- [State#state.db_type, Reason,
- State#state.start_interval div 1000]),
- ?GEN_FSM:send_event_after(State#state.start_interval,
+ {ok, Ref} ->
+ erlang:monitor(process, Ref),
+ lists:foreach(fun (Req) ->
+ (?GEN_FSM):send_event(self(), Req)
+ end,
+ queue:to_list(PendingRequests)),
+ {next_state, session_established,
+ State#state{db_ref = Ref,
+ pending_requests = {0, queue:new()}}};
+ {error, Reason} ->
+ ?INFO_MSG("~p connection failed:~n** Reason: ~p~n** "
+ "Retry after: ~p seconds",
+ [State#state.db_type, Reason,
+ State#state.start_interval div 1000]),
+ (?GEN_FSM):send_event_after(State#state.start_interval,
connect),
- {next_state, connecting, State}
+ {next_state, connecting, State}
end;
connecting(Event, State) ->
- ?WARNING_MSG("unexpected event in 'connecting': ~p", [Event]),
+ ?WARNING_MSG("unexpected event in 'connecting': ~p",
+ [Event]),
{next_state, connecting, State}.
-connecting({sql_cmd, {sql_query, ?KEEPALIVE_QUERY}, _Timestamp}, From, State) ->
- ?GEN_FSM:reply(From, {error, "SQL connection failed"}),
+connecting({sql_cmd, {sql_query, ?KEEPALIVE_QUERY},
+ _Timestamp},
+ From, State) ->
+ (?GEN_FSM):reply(From,
+ {error, <<"SQL connection failed">>}),
{next_state, connecting, State};
-connecting({sql_cmd, Command, Timestamp} = Req, From, State) ->
- ?DEBUG("queuing pending request while connecting:~n\t~p", [Req]),
+connecting({sql_cmd, Command, Timestamp} = Req, From,
+ State) ->
+ ?DEBUG("queuing pending request while connecting:~n\t~p",
+ [Req]),
{Len, PendingRequests} = State#state.pending_requests,
- NewPendingRequests =
- if Len < State#state.max_pending_requests_len ->
- {Len + 1, queue:in({sql_cmd, Command, From, Timestamp}, PendingRequests)};
- true ->
- lists:foreach(
- fun({sql_cmd, _, To, _Timestamp}) ->
- ?GEN_FSM:reply(
- To, {error, "SQL connection failed"})
- end, queue:to_list(PendingRequests)),
- {1, queue:from_list([{sql_cmd, Command, From, Timestamp}])}
- end,
+ NewPendingRequests = if Len <
+ State#state.max_pending_requests_len ->
+ {Len + 1,
+ queue:in({sql_cmd, Command, From, Timestamp},
+ PendingRequests)};
+ true ->
+ lists:foreach(fun ({sql_cmd, _, To,
+ _Timestamp}) ->
+ (?GEN_FSM):reply(To,
+ {error,
+ <<"SQL connection failed">>})
+ end,
+ queue:to_list(PendingRequests)),
+ {1,
+ queue:from_list([{sql_cmd, Command, From,
+ Timestamp}])}
+ end,
{next_state, connecting,
State#state{pending_requests = NewPendingRequests}};
connecting(Request, {Who, _Ref}, State) ->
@@ -260,17 +284,21 @@ connecting(Request, {Who, _Ref}, State) ->
[Request, Who]),
{reply, {error, badarg}, connecting, State}.
-session_established({sql_cmd, Command, Timestamp}, From, State) ->
+session_established({sql_cmd, Command, Timestamp}, From,
+ State) ->
run_sql_cmd(Command, From, State, Timestamp);
session_established(Request, {Who, _Ref}, State) ->
- ?WARNING_MSG("unexpected call ~p from ~p in 'session_established'",
+ ?WARNING_MSG("unexpected call ~p from ~p in 'session_establ"
+ "ished'",
[Request, Who]),
{reply, {error, badarg}, session_established, State}.
-session_established({sql_cmd, Command, From, Timestamp}, State) ->
+session_established({sql_cmd, Command, From, Timestamp},
+ State) ->
run_sql_cmd(Command, From, State, Timestamp);
session_established(Event, State) ->
- ?WARNING_MSG("unexpected event in 'session_established': ~p", [Event]),
+ ?WARNING_MSG("unexpected event in 'session_established': ~p",
+ [Event]),
{next_state, session_established, State}.
handle_event(_Event, StateName, State) ->
@@ -284,22 +312,20 @@ code_change(_OldVsn, StateName, State, _Extra) ->
%% We receive the down signal when we loose the MySQL connection (we are
%% monitoring the connection)
-handle_info({'DOWN', _MonitorRef, process, _Pid, _Info}, _StateName, State) ->
- ?GEN_FSM:send_event(self(), connect),
+handle_info({'DOWN', _MonitorRef, process, _Pid, _Info},
+ _StateName, State) ->
+ (?GEN_FSM):send_event(self(), connect),
{next_state, connecting, State};
handle_info(Info, StateName, State) ->
- ?WARNING_MSG("unexpected info in ~p: ~p", [StateName, Info]),
+ ?WARNING_MSG("unexpected info in ~p: ~p",
+ [StateName, Info]),
{next_state, StateName, State}.
terminate(_Reason, _StateName, State) ->
ejabberd_odbc_sup:remove_pid(State#state.host, self()),
case State#state.db_type of
- mysql ->
- %% old versions of mysql driver don't have the stop function
- %% so the catch
- catch mysql_conn:stop(State#state.db_ref);
- _ ->
- ok
+ mysql -> catch mysql_conn:stop(State#state.db_ref);
+ _ -> ok
end,
ok.
@@ -308,23 +334,23 @@ terminate(_Reason, _StateName, State) ->
%% Purpose: Prepare the state to be printed on error log
%% Returns: State to print
%%----------------------------------------------------------------------
-print_state(State) ->
- State.
+print_state(State) -> State.
+
%%%----------------------------------------------------------------------
%%% Internal functions
%%%----------------------------------------------------------------------
run_sql_cmd(Command, From, State, Timestamp) ->
case timer:now_diff(now(), Timestamp) div 1000 of
- Age when Age < ?TRANSACTION_TIMEOUT ->
- put(?NESTING_KEY, ?TOP_LEVEL_TXN),
- put(?STATE_KEY, State),
- abort_on_driver_error(outer_op(Command), From);
- Age ->
- ?ERROR_MSG("Database was not available or too slow,"
- " discarding ~p milliseconds old request~n~p~n",
- [Age, Command]),
- {next_state, session_established, State}
+ Age when Age < (?TRANSACTION_TIMEOUT) ->
+ put(?NESTING_KEY, ?TOP_LEVEL_TXN),
+ put(?STATE_KEY, State),
+ abort_on_driver_error(outer_op(Command), From);
+ Age ->
+ ?ERROR_MSG("Database was not available or too slow, "
+ "discarding ~p milliseconds old request~n~p~n",
+ [Age, Command]),
+ {next_state, session_established, State}
end.
%% Only called by handle_call, only handles top level operations.
@@ -332,143 +358,125 @@ run_sql_cmd(Command, From, State, Timestamp) ->
outer_op({sql_query, Query}) ->
sql_query_internal(Query);
outer_op({sql_transaction, F}) ->
- outer_transaction(F, ?MAX_TRANSACTION_RESTARTS, "");
-outer_op({sql_bloc, F}) ->
- execute_bloc(F).
+ outer_transaction(F, ?MAX_TRANSACTION_RESTARTS, <<"">>);
+outer_op({sql_bloc, F}) -> execute_bloc(F).
%% Called via sql_query/transaction/bloc from client code when inside a
%% nested operation
nested_op({sql_query, Query}) ->
- %% XXX - use sql_query_t here insted? Most likely would break
- %% callers who expect {error, _} tuples (sql_query_t turns
- %% these into throws)
sql_query_internal(Query);
nested_op({sql_transaction, F}) ->
NestingLevel = get(?NESTING_KEY),
- if NestingLevel =:= ?TOP_LEVEL_TXN ->
- %% First transaction inside a (series of) sql_blocs
- outer_transaction(F, ?MAX_TRANSACTION_RESTARTS, "");
- true ->
- %% Transaction inside a transaction
- inner_transaction(F)
+ if NestingLevel =:= (?TOP_LEVEL_TXN) ->
+ outer_transaction(F, ?MAX_TRANSACTION_RESTARTS, <<"">>);
+ true -> inner_transaction(F)
end;
-nested_op({sql_bloc, F}) ->
- execute_bloc(F).
+nested_op({sql_bloc, F}) -> execute_bloc(F).
%% Never retry nested transactions - only outer transactions
inner_transaction(F) ->
PreviousNestingLevel = get(?NESTING_KEY),
case get(?NESTING_KEY) of
- ?TOP_LEVEL_TXN ->
- {backtrace, T} = process_info(self(), backtrace),
- ?ERROR_MSG("inner transaction called at outer txn level. Trace: ~s",
- [T]),
- erlang:exit(implementation_faulty);
- _N -> ok
+ ?TOP_LEVEL_TXN ->
+ {backtrace, T} = process_info(self(), backtrace),
+ ?ERROR_MSG("inner transaction called at outer txn "
+ "level. Trace: ~s",
+ [T]),
+ erlang:exit(implementation_faulty);
+ _N -> ok
end,
put(?NESTING_KEY, PreviousNestingLevel + 1),
Result = (catch F()),
put(?NESTING_KEY, PreviousNestingLevel),
case Result of
- {aborted, Reason} ->
- {aborted, Reason};
- {'EXIT', Reason} ->
- {'EXIT', Reason};
- {atomic, Res} ->
- {atomic, Res};
- Res ->
- {atomic, Res}
+ {aborted, Reason} -> {aborted, Reason};
+ {'EXIT', Reason} -> {'EXIT', Reason};
+ {atomic, Res} -> {atomic, Res};
+ Res -> {atomic, Res}
end.
outer_transaction(F, NRestarts, _Reason) ->
PreviousNestingLevel = get(?NESTING_KEY),
case get(?NESTING_KEY) of
- ?TOP_LEVEL_TXN ->
- ok;
- _N ->
- {backtrace, T} = process_info(self(), backtrace),
- ?ERROR_MSG("outer transaction called at inner txn level. Trace: ~s",
- [T]),
- erlang:exit(implementation_faulty)
+ ?TOP_LEVEL_TXN -> ok;
+ _N ->
+ {backtrace, T} = process_info(self(), backtrace),
+ ?ERROR_MSG("outer transaction called at inner txn "
+ "level. Trace: ~s",
+ [T]),
+ erlang:exit(implementation_faulty)
end,
- sql_query_internal("begin;"),
+ sql_query_internal(<<"begin;">>),
put(?NESTING_KEY, PreviousNestingLevel + 1),
Result = (catch F()),
put(?NESTING_KEY, PreviousNestingLevel),
case Result of
- {aborted, Reason} when NRestarts > 0 ->
- %% Retry outer transaction upto NRestarts times.
- sql_query_internal("rollback;"),
- outer_transaction(F, NRestarts - 1, Reason);
- {aborted, Reason} when NRestarts =:= 0 ->
- %% Too many retries of outer transaction.
- ?ERROR_MSG("SQL transaction restarts exceeded~n"
- "** Restarts: ~p~n"
- "** Last abort reason: ~p~n"
- "** Stacktrace: ~p~n"
- "** When State == ~p",
- [?MAX_TRANSACTION_RESTARTS, Reason,
- erlang:get_stacktrace(), get(?STATE_KEY)]),
- sql_query_internal("rollback;"),
- {aborted, Reason};
- {'EXIT', Reason} ->
- %% Abort sql transaction on EXIT from outer txn only.
- sql_query_internal("rollback;"),
- {aborted, Reason};
- Res ->
- %% Commit successful outer txn
- sql_query_internal("commit;"),
- {atomic, Res}
+ {aborted, Reason} when NRestarts > 0 ->
+ sql_query_internal(<<"rollback;">>),
+ outer_transaction(F, NRestarts - 1, Reason);
+ {aborted, Reason} when NRestarts =:= 0 ->
+ ?ERROR_MSG("SQL transaction restarts exceeded~n** "
+ "Restarts: ~p~n** Last abort reason: "
+ "~p~n** Stacktrace: ~p~n** When State "
+ "== ~p",
+ [?MAX_TRANSACTION_RESTARTS, Reason,
+ erlang:get_stacktrace(), get(?STATE_KEY)]),
+ sql_query_internal(<<"rollback;">>),
+ {aborted, Reason};
+ {'EXIT', Reason} ->
+ sql_query_internal(<<"rollback;">>), {aborted, Reason};
+ Res -> sql_query_internal(<<"commit;">>), {atomic, Res}
end.
execute_bloc(F) ->
- %% We don't alter ?NESTING_KEY here as only SQL transactions alter
- %% txn nesting
case catch F() of
- {aborted, Reason} ->
- {aborted, Reason};
- {'EXIT', Reason} ->
- {aborted, Reason};
- Res ->
- {atomic, Res}
+ {aborted, Reason} -> {aborted, Reason};
+ {'EXIT', Reason} -> {aborted, Reason};
+ Res -> {atomic, Res}
end.
sql_query_internal(Query) ->
State = get(?STATE_KEY),
Res = case State#state.db_type of
- odbc ->
- odbc:sql_query(State#state.db_ref, Query);
- pgsql ->
- pgsql_to_odbc(pgsql:squery(State#state.db_ref, Query));
- mysql ->
- ?DEBUG("MySQL, Send query~n~p~n", [Query]),
- R = mysql_to_odbc(mysql_conn:fetch(State#state.db_ref,
- Query, self())),
- %% ?INFO_MSG("MySQL, Received result~n~p~n", [R]),
- R
- end,
+ odbc ->
+ to_odbc(odbc:sql_query(State#state.db_ref, Query,
+ (?TRANSACTION_TIMEOUT) - 1000));
+ pgsql ->
+ pgsql_to_odbc(pgsql:squery(State#state.db_ref, Query));
+ mysql ->
+ ?DEBUG("MySQL, Send query~n~p~n", [Query]),
+ %%squery to be able to specify result_type = binary
+ %%[Query] because mysql_conn expect query to be a list (elements can be binaries, or iolist)
+ %% but doesn't accept just a binary
+ R = mysql_to_odbc(mysql_conn:squery(State#state.db_ref,
+ [Query], self(),
+ [{timeout, (?TRANSACTION_TIMEOUT) - 1000},
+ {result_type, binary}])),
+ %% ?INFO_MSG("MySQL, Received result~n~p~n", [R]),
+ R
+ end,
case Res of
- {error, "No SQL-driver information available."} ->
- % workaround for odbc bug
- {updated, 0};
- _Else -> Res
+ {error, <<"No SQL-driver information available.">>} ->
+ {updated, 0};
+ _Else -> Res
end.
%% Generate the OTP callback return tuple depending on the driver result.
-abort_on_driver_error({error, "query timed out"} = Reply, From) ->
- %% mysql driver error
- ?GEN_FSM:reply(From, Reply),
+abort_on_driver_error({error, <<"query timed out">>} =
+ Reply,
+ From) ->
+ (?GEN_FSM):reply(From, Reply),
{stop, timeout, get(?STATE_KEY)};
-abort_on_driver_error({error, "Failed sending data on socket" ++ _} = Reply,
+abort_on_driver_error({error,
+ <<"Failed sending data on socket", _/binary>>} =
+ Reply,
From) ->
- %% mysql driver error
- ?GEN_FSM:reply(From, Reply),
+ (?GEN_FSM):reply(From, Reply),
{stop, closed, get(?STATE_KEY)};
abort_on_driver_error(Reply, From) ->
- ?GEN_FSM:reply(From, Reply),
+ (?GEN_FSM):reply(From, Reply),
{next_state, session_established, get(?STATE_KEY)}.
-
%% == pure ODBC code
%% part of init/1
@@ -482,44 +490,54 @@ odbc_connect(SQLServer) ->
%% part of init/1
%% Open a database connection to PostgreSQL
pgsql_connect(Server, Port, DB, Username, Password) ->
- pgsql:connect(Server, DB, Username, Password, Port).
+ case pgsql:connect([{host, Server},
+ {database, DB},
+ {user, Username},
+ {password, Password},
+ {port, Port},
+ {as_binary, true}]) of
+ {ok, Ref} ->
+ pgsql:squery(Ref, [<<"alter database ">>, DB, <<" set ">>,
+ <<"standard_conforming_strings='off';">>]),
+ {ok, Ref};
+ Err ->
+ Err
+ end.
%% Convert PostgreSQL query result to Erlang ODBC result formalism
pgsql_to_odbc({ok, PGSQLResult}) ->
case PGSQLResult of
- [Item] ->
- pgsql_item_to_odbc(Item);
- Items ->
- [pgsql_item_to_odbc(Item) || Item <- Items]
+ [Item] -> pgsql_item_to_odbc(Item);
+ Items -> [pgsql_item_to_odbc(Item) || Item <- Items]
end.
-pgsql_item_to_odbc({"SELECT" ++ _, Rows, Recs}) ->
- {selected,
- [element(1, Row) || Row <- Rows],
- [list_to_tuple(Rec) || Rec <- Recs]};
-pgsql_item_to_odbc("INSERT " ++ OIDN) ->
- [_OID, N] = string:tokens(OIDN, " "),
- {updated, list_to_integer(N)};
-pgsql_item_to_odbc("DELETE " ++ N) ->
- {updated, list_to_integer(N)};
-pgsql_item_to_odbc("UPDATE " ++ N) ->
- {updated, list_to_integer(N)};
-pgsql_item_to_odbc({error, Error}) ->
- {error, Error};
-pgsql_item_to_odbc(_) ->
- {updated,undefined}.
+pgsql_item_to_odbc({<<"SELECT", _/binary>>, Rows,
+ Recs}) ->
+ {selected, [element(1, Row) || Row <- Rows], Recs};
+pgsql_item_to_odbc(<<"INSERT ", OIDN/binary>>) ->
+ [_OID, N] = str:tokens(OIDN, <<" ">>),
+ {updated, jlib:binary_to_integer(N)};
+pgsql_item_to_odbc(<<"DELETE ", N/binary>>) ->
+ {updated, jlib:binary_to_integer(N)};
+pgsql_item_to_odbc(<<"UPDATE ", N/binary>>) ->
+ {updated, jlib:binary_to_integer(N)};
+pgsql_item_to_odbc({error, Error}) -> {error, Error};
+pgsql_item_to_odbc(_) -> {updated, undefined}.
%% == Native MySQL code
%% part of init/1
%% Open a database connection to MySQL
mysql_connect(Server, Port, DB, Username, Password) ->
- case mysql_conn:start(Server, Port, Username, Password, DB, fun log/3) of
- {ok, Ref} ->
- mysql_conn:fetch(Ref, ["set names 'utf8';"], self()),
- {ok, Ref};
- Err ->
- Err
+ case mysql_conn:start(binary_to_list(Server), Port,
+ binary_to_list(Username), binary_to_list(Password),
+ binary_to_list(DB), fun log/3)
+ of
+ {ok, Ref} ->
+ mysql_conn:fetch(Ref, [<<"set names 'utf8';">>],
+ self()),
+ {ok, Ref};
+ Err -> Err
end.
%% Convert MySQL query result to Erlang ODBC result formalism
@@ -528,58 +546,67 @@ mysql_to_odbc({updated, MySQLRes}) ->
mysql_to_odbc({data, MySQLRes}) ->
mysql_item_to_odbc(mysql:get_result_field_info(MySQLRes),
mysql:get_result_rows(MySQLRes));
-mysql_to_odbc({error, MySQLRes}) when is_list(MySQLRes) ->
+mysql_to_odbc({error, MySQLRes})
+ when is_binary(MySQLRes) ->
{error, MySQLRes};
mysql_to_odbc({error, MySQLRes}) ->
{error, mysql:get_result_reason(MySQLRes)}.
%% When tabular data is returned, convert it to the ODBC formalism
mysql_item_to_odbc(Columns, Recs) ->
- %% For now, there is a bug and we do not get the correct value from MySQL
- %% module:
- {selected,
- [element(2, Column) || Column <- Columns],
- [list_to_tuple(Rec) || Rec <- Recs]}.
+ {selected, [element(2, Column) || Column <- Columns], Recs}.
+
+to_odbc({selected, Columns, Recs}) ->
+ {selected, Columns, [tuple_to_list(Rec) || Rec <- Recs]};
+to_odbc(Res) ->
+ Res.
-% log function used by MySQL driver
log(Level, Format, Args) ->
case Level of
- debug ->
- ?DEBUG(Format, Args);
- normal ->
- ?INFO_MSG(Format, Args);
- error ->
- ?ERROR_MSG(Format, Args)
+ debug -> ?DEBUG(Format, Args);
+ normal -> ?INFO_MSG(Format, Args);
+ error -> ?ERROR_MSG(Format, Args)
end.
db_opts(Host) ->
- case ejabberd_config:get_local_option({odbc_server, Host}) of
- %% Default pgsql port
- {pgsql, Server, DB, User, Pass} ->
- [pgsql, Server, ?PGSQL_PORT, DB, User, Pass];
- {pgsql, Server, Port, DB, User, Pass} when is_integer(Port) ->
- [pgsql, Server, Port, DB, User, Pass];
- %% Default mysql port
- {mysql, Server, DB, User, Pass} ->
- [mysql, Server, ?MYSQL_PORT, DB, User, Pass];
- {mysql, Server, Port, DB, User, Pass} when is_integer(Port) ->
- [mysql, Server, Port, DB, User, Pass];
- SQLServer when is_list(SQLServer) ->
- [odbc, SQLServer]
+ case ejabberd_config:get_local_option(
+ {odbc_server, Host},
+ fun({Type, Server, DB, User, Pass}) ->
+ {Type,
+ iolist_to_binary(Server),
+ case Type of
+ mysql -> ?MYSQL_PORT;
+ pgsql -> ?PGSQL_PORT
+ end,
+ iolist_to_binary(DB),
+ iolist_to_binary(User),
+ iolist_to_binary(Pass)};
+ ({Type, Server, Port, DB, User, Pass})
+ when ((Type == mysql) or (Type == pgsql))
+ and (is_integer(Port) and ((Port > 0)
+ and (Port < 65536))) ->
+ {Type,
+ iolist_to_binary(Server),
+ Port,
+ iolist_to_binary(DB),
+ iolist_to_binary(User),
+ iolist_to_binary(Pass)};
+ (S) ->
+ iolist_to_binary(S)
+ end, <<"localhost">>) of
+ {Type, Server, Port, DB, User, Pass} ->
+ [Type, Server, Port, DB, User, Pass];
+ SQLServer ->
+ [odbc, SQLServer]
end.
max_fsm_queue() ->
- case ejabberd_config:get_local_option(max_fsm_queue) of
- N when is_integer(N), N>0 ->
- N;
- _ ->
- undefined
- end.
+ ejabberd_config:get_local_option(
+ max_fsm_queue,
+ fun(N) when is_integer(N), N > 0 -> N end).
fsm_limit_opts() ->
case max_fsm_queue() of
- N when is_integer(N) ->
- [{max_queue, N}];
- _ ->
- []
+ N when is_integer(N) -> [{max_queue, N}];
+ _ -> []
end.
diff --git a/src/odbc/ejabberd_odbc_sup.erl b/src/odbc/ejabberd_odbc_sup.erl
index 442b0f906..0c748d147 100644
--- a/src/odbc/ejabberd_odbc_sup.erl
+++ b/src/odbc/ejabberd_odbc_sup.erl
@@ -25,79 +25,53 @@
%%%----------------------------------------------------------------------
-module(ejabberd_odbc_sup).
+
-author('alexey@process-one.net').
%% API
--export([start_link/1,
- init/1,
- add_pid/2,
- remove_pid/2,
- get_pids/1,
- get_random_pid/1
- ]).
+-export([start_link/1, init/1, add_pid/2, remove_pid/2,
+ get_pids/1, get_random_pid/1]).
-include("ejabberd.hrl").
-define(DEFAULT_POOL_SIZE, 10).
--define(DEFAULT_ODBC_START_INTERVAL, 30). % 30 seconds
-% time to wait for the supervisor to start its child before returning
-% a timeout error to the request
--define(CONNECT_TIMEOUT, 500). % milliseconds
+-define(DEFAULT_ODBC_START_INTERVAL, 30).
+-define(CONNECT_TIMEOUT, 500).
-record(sql_pool, {host, pid}).
start_link(Host) ->
mnesia:create_table(sql_pool,
- [{ram_copies, [node()]},
- {type, bag},
+ [{ram_copies, [node()]}, {type, bag},
{local_content, true},
{attributes, record_info(fields, sql_pool)}]),
mnesia:add_table_copy(sql_pool, node(), ram_copies),
- F = fun() ->
- mnesia:delete({sql_pool, Host})
- end,
+ F = fun () -> mnesia:delete({sql_pool, Host}) end,
mnesia:ets(F),
- supervisor:start_link({local, gen_mod:get_module_proc(Host, ?MODULE)},
+ supervisor:start_link({local,
+ gen_mod:get_module_proc(Host, ?MODULE)},
?MODULE, [Host]).
init([Host]) ->
- PoolSize = case ejabberd_config:get_local_option({odbc_pool_size, Host}) of
- I when is_integer(I) ->
- I;
- undefined ->
- ?DEFAULT_POOL_SIZE;
- Other ->
- ?ERROR_MSG("Wrong odbc_pool_size definition '~p' "
- "for host ~p, default to ~p~n",
- [Other, Host, ?DEFAULT_POOL_SIZE]),
- ?DEFAULT_POOL_SIZE
- end,
- StartInterval = case ejabberd_config:get_local_option({odbc_start_interval,
- Host}) of
- Interval when is_integer(Interval) ->
- Interval;
- undefined ->
- ?DEFAULT_ODBC_START_INTERVAL;
- _Other2 ->
- ?ERROR_MSG("Wrong odbc_start_interval "
- "definition '~p' for host ~p, "
- "defaulting to ~p~n",
- [_Other2, Host,
- ?DEFAULT_ODBC_START_INTERVAL]),
- ?DEFAULT_ODBC_START_INTERVAL
- end,
- {ok, {{one_for_one, PoolSize*10, 1},
- lists:map(
- fun(I) ->
- {I,
- {ejabberd_odbc, start_link, [Host, StartInterval*1000]},
- transient,
- 2000,
- worker,
- [?MODULE]}
- end, lists:seq(1, PoolSize))}}.
+ PoolSize = ejabberd_config:get_local_option(
+ {odbc_pool_size, Host},
+ fun(I) when is_integer(I), I>0 -> I end,
+ ?DEFAULT_POOL_SIZE),
+ StartInterval = ejabberd_config:get_local_option(
+ {odbc_start_interval, Host},
+ fun(I) when is_integer(I), I>0 -> I end,
+ ?DEFAULT_ODBC_START_INTERVAL),
+ {ok,
+ {{one_for_one, PoolSize * 10, 1},
+ lists:map(fun (I) ->
+ {I,
+ {ejabberd_odbc, start_link,
+ [Host, StartInterval * 1000]},
+ transient, 2000, worker, [?MODULE]}
+ end,
+ lists:seq(1, PoolSize))}}.
get_pids(Host) ->
Rs = mnesia:dirty_read(sql_pool, Host),
@@ -108,17 +82,13 @@ get_random_pid(Host) ->
lists:nth(erlang:phash(now(), length(Pids)), Pids).
add_pid(Host, Pid) ->
- F = fun() ->
- mnesia:write(
- #sql_pool{host = Host,
- pid = Pid})
+ F = fun () ->
+ mnesia:write(#sql_pool{host = Host, pid = Pid})
end,
mnesia:ets(F).
remove_pid(Host, Pid) ->
- F = fun() ->
- mnesia:delete_object(
- #sql_pool{host = Host,
- pid = Pid})
+ F = fun () ->
+ mnesia:delete_object(#sql_pool{host = Host, pid = Pid})
end,
mnesia:ets(F).
diff --git a/src/odbc/mysql.sql b/src/odbc/mysql.sql
index 17c7d9b76..976230117 100644
--- a/src/odbc/mysql.sql
+++ b/src/odbc/mysql.sql
@@ -273,3 +273,12 @@ CREATE TABLE motd (
xml text,
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
) CHARACTER SET utf8;
+
+CREATE TABLE caps_features (
+ node varchar(250) NOT NULL,
+ subnode varchar(250) NOT NULL,
+ feature text,
+ created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
+) CHARACTER SET utf8;
+
+CREATE INDEX i_caps_features_node_subnode ON caps_features(node(75), subnode(75));
diff --git a/src/odbc/odbc_queries.erl b/src/odbc/odbc_queries.erl
index bd4812609..66da7906f 100644
--- a/src/odbc/odbc_queries.erl
+++ b/src/odbc/odbc_queries.erl
@@ -25,52 +25,28 @@
%%%----------------------------------------------------------------------
-module(odbc_queries).
+
-author("mremond@process-one.net").
--export([get_db_type/0,
- update_t/4,
- sql_transaction/2,
- get_last/2,
- set_last_t/4,
- del_last/2,
- get_password/2,
- set_password_t/3,
- add_user/3,
- del_user/2,
- del_user_return_password/3,
- list_users/1,
- list_users/2,
- users_number/1,
- users_number/2,
- add_spool_sql/2,
- add_spool/2,
- get_and_del_spool_msg_t/2,
- del_spool_msg/2,
- get_roster/2,
- get_roster_jid_groups/2,
- get_roster_groups/3,
- del_user_roster_t/2,
- get_roster_by_jid/3,
- get_rostergroup_by_jid/3,
- del_roster/3,
- del_roster_sql/2,
- update_roster/5,
- update_roster_sql/4,
- roster_subscribe/4,
- get_subscription/3,
- set_private_data/4,
- set_private_data_sql/3,
- get_private_data/3,
- del_user_private_storage/2,
- get_default_privacy_list/2,
- get_default_privacy_list_t/1,
- get_privacy_list_names/2,
- get_privacy_list_names_t/1,
- get_privacy_list_id/3,
- get_privacy_list_id_t/2,
- get_privacy_list_data/3,
+-export([get_db_type/0, update_t/4, sql_transaction/2,
+ get_last/2, set_last_t/4, del_last/2, get_password/2,
+ set_password_t/3, add_user/3, del_user/2,
+ del_user_return_password/3, list_users/1, list_users/2,
+ users_number/1, users_number/2, add_spool_sql/2,
+ add_spool/2, get_and_del_spool_msg_t/2, del_spool_msg/2,
+ get_roster/2, get_roster_jid_groups/2,
+ get_roster_groups/3, del_user_roster_t/2,
+ get_roster_by_jid/3, get_rostergroup_by_jid/3,
+ del_roster/3, del_roster_sql/2, update_roster/5,
+ update_roster_sql/4, roster_subscribe/4,
+ get_subscription/3, set_private_data/4,
+ set_private_data_sql/3, get_private_data/3, get_private_data/2,
+ del_user_private_storage/2, get_default_privacy_list/2,
+ get_default_privacy_list_t/1, get_privacy_list_names/2,
+ get_privacy_list_names_t/1, get_privacy_list_id/3,
+ get_privacy_list_id_t/2, get_privacy_list_data/3,
get_privacy_list_data_by_id/2,
- get_privacy_list_data_by_id_t/1,
+ get_privacy_list_data_by_id_t/1,
set_default_privacy_list/2,
unset_default_privacy_list/2,
remove_privacy_list/2,
@@ -88,8 +64,11 @@
%-define(generic, true).
%-define(mssql, true).
-ifndef(mssql).
+
-undef(generic).
+
-define(generic, true).
+
-endif.
-include("ejabberd.hrl").
@@ -97,49 +76,49 @@
%% Almost a copy of string:join/2.
%% We use this version because string:join/2 is relatively
%% new function (introduced in R12B-0).
-join([], _Sep) ->
- [];
-join([H|T], Sep) ->
- [H, [[Sep, X] || X <- T]].
+join([], _Sep) -> [];
+join([H | T], Sep) -> [H, [[Sep, X] || X <- T]].
%% -----------------
%% Generic queries
-ifdef(generic).
-get_db_type() ->
- generic.
+get_db_type() -> generic.
%% Safe atomic update.
update_t(Table, Fields, Vals, Where) ->
- UPairs = lists:zipwith(fun(A, B) -> A ++ "='" ++ B ++ "'" end,
+ UPairs = lists:zipwith(fun (A, B) ->
+ <<A/binary, "='", B/binary, "'">>
+ end,
Fields, Vals),
- case ejabberd_odbc:sql_query_t(
- ["update ", Table, " set ",
- join(UPairs, ", "),
- " where ", Where, ";"]) of
- {updated, 1} ->
- ok;
- _ ->
- ejabberd_odbc:sql_query_t(
- ["insert into ", Table, "(", join(Fields, ", "),
- ") values ('", join(Vals, "', '"), "');"])
+ case ejabberd_odbc:sql_query_t([<<"update ">>, Table,
+ <<" set ">>, join(UPairs, <<", ">>),
+ <<" where ">>, Where, <<";">>])
+ of
+ {updated, 1} -> ok;
+ _ ->
+ ejabberd_odbc:sql_query_t([<<"insert into ">>, Table,
+ <<"(">>, join(Fields, <<", ">>),
+ <<") values ('">>, join(Vals, <<"', '">>),
+ <<"');">>])
end.
update(LServer, Table, Fields, Vals, Where) ->
- UPairs = lists:zipwith(fun(A, B) -> A ++ "='" ++ B ++ "'" end,
+ UPairs = lists:zipwith(fun (A, B) ->
+ <<A/binary, "='", B/binary, "'">>
+ end,
Fields, Vals),
- case ejabberd_odbc:sql_query(
- LServer,
- ["update ", Table, " set ",
- join(UPairs, ", "),
- " where ", Where, ";"]) of
- {updated, 1} ->
- ok;
- _ ->
- ejabberd_odbc:sql_query(
- LServer,
- ["insert into ", Table, "(", join(Fields, ", "),
- ") values ('", join(Vals, "', '"), "');"])
+ case ejabberd_odbc:sql_query(LServer,
+ [<<"update ">>, Table, <<" set ">>,
+ join(UPairs, <<", ">>), <<" where ">>, Where,
+ <<";">>])
+ of
+ {updated, 1} -> ok;
+ _ ->
+ ejabberd_odbc:sql_query(LServer,
+ [<<"insert into ">>, Table, <<"(">>,
+ join(Fields, <<", ">>), <<") values ('">>,
+ join(Vals, <<"', '">>), <<"');">>])
end.
%% F can be either a fun or a list of queries
@@ -149,758 +128,810 @@ sql_transaction(LServer, F) ->
ejabberd_odbc:sql_transaction(LServer, F).
get_last(LServer, Username) ->
- ejabberd_odbc:sql_query(
- LServer,
- ["select seconds, state from last "
- "where username='", Username, "'"]).
+ ejabberd_odbc:sql_query(LServer,
+ [<<"select seconds, state from last where "
+ "username='">>,
+ Username, <<"'">>]).
set_last_t(LServer, Username, Seconds, State) ->
- update(LServer, "last", ["username", "seconds", "state"],
+ update(LServer, <<"last">>,
+ [<<"username">>, <<"seconds">>, <<"state">>],
[Username, Seconds, State],
- ["username='", Username, "'"]).
+ [<<"username='">>, Username, <<"'">>]).
del_last(LServer, Username) ->
- ejabberd_odbc:sql_query(
- LServer,
- ["delete from last where username='", Username, "'"]).
+ ejabberd_odbc:sql_query(LServer,
+ [<<"delete from last where username='">>, Username,
+ <<"'">>]).
get_password(LServer, Username) ->
- ejabberd_odbc:sql_query(
- LServer,
- ["select password from users "
- "where username='", Username, "';"]).
+ ejabberd_odbc:sql_query(LServer,
+ [<<"select password from users where username='">>,
+ Username, <<"';">>]).
set_password_t(LServer, Username, Pass) ->
- ejabberd_odbc:sql_transaction(
- LServer,
- fun() ->
- update_t("users", ["username", "password"],
- [Username, Pass],
- ["username='", Username ,"'"])
- end).
+ ejabberd_odbc:sql_transaction(LServer,
+ fun () ->
+ update_t(<<"users">>,
+ [<<"username">>,
+ <<"password">>],
+ [Username, Pass],
+ [<<"username='">>, Username,
+ <<"'">>])
+ end).
add_user(LServer, Username, Pass) ->
- ejabberd_odbc:sql_query(
- LServer,
- ["insert into users(username, password) "
- "values ('", Username, "', '", Pass, "');"]).
+ ejabberd_odbc:sql_query(LServer,
+ [<<"insert into users(username, password) "
+ "values ('">>,
+ Username, <<"', '">>, Pass, <<"');">>]).
del_user(LServer, Username) ->
- ejabberd_odbc:sql_query(
- LServer,
- ["delete from users where username='", Username ,"';"]).
+ ejabberd_odbc:sql_query(LServer,
+ [<<"delete from users where username='">>, Username,
+ <<"';">>]).
del_user_return_password(_LServer, Username, Pass) ->
- P = ejabberd_odbc:sql_query_t(
- ["select password from users where username='",
- Username, "';"]),
- ejabberd_odbc:sql_query_t(["delete from users "
- "where username='", Username,
- "' and password='", Pass, "';"]),
+ P =
+ ejabberd_odbc:sql_query_t([<<"select password from users where username='">>,
+ Username, <<"';">>]),
+ ejabberd_odbc:sql_query_t([<<"delete from users where username='">>,
+ Username, <<"' and password='">>, Pass,
+ <<"';">>]),
P.
list_users(LServer) ->
- ejabberd_odbc:sql_query(
- LServer,
- "select username from users").
-
-list_users(LServer, [{from, Start}, {to, End}]) when is_integer(Start) and
- is_integer(End) ->
- list_users(LServer, [{limit, End-Start+1}, {offset, Start-1}]);
-list_users(LServer, [{prefix, Prefix}, {from, Start}, {to, End}]) when is_list(Prefix) and
- is_integer(Start) and
- is_integer(End) ->
- list_users(LServer, [{prefix, Prefix}, {limit, End-Start+1}, {offset, Start-1}]);
-
-list_users(LServer, [{limit, Limit}, {offset, Offset}]) when is_integer(Limit) and
- is_integer(Offset) ->
- ejabberd_odbc:sql_query(
- LServer,
- io_lib:format(
- "select username from users " ++
- "order by username " ++
- "limit ~w offset ~w", [Limit, Offset]));
-list_users(LServer, [{prefix, Prefix},
- {limit, Limit},
- {offset, Offset}]) when is_list(Prefix) and
- is_integer(Limit) and
- is_integer(Offset) ->
- ejabberd_odbc:sql_query(
- LServer,
- io_lib:format("select username from users " ++
- "where username like '~s%' " ++
- "order by username " ++
- "limit ~w offset ~w ", [Prefix, Limit, Offset])).
+ ejabberd_odbc:sql_query(LServer,
+ [<<"select username from users">>]).
+
+list_users(LServer, [{from, Start}, {to, End}])
+ when is_integer(Start) and is_integer(End) ->
+ list_users(LServer,
+ [{limit, End - Start + 1}, {offset, Start - 1}]);
+list_users(LServer,
+ [{prefix, Prefix}, {from, Start}, {to, End}])
+ when is_binary(Prefix) and is_integer(Start) and
+ is_integer(End) ->
+ list_users(LServer,
+ [{prefix, Prefix}, {limit, End - Start + 1},
+ {offset, Start - 1}]);
+list_users(LServer, [{limit, Limit}, {offset, Offset}])
+ when is_integer(Limit) and is_integer(Offset) ->
+ ejabberd_odbc:sql_query(LServer,
+ [list_to_binary(
+ io_lib:format(
+ "select username from users " ++
+ "order by username " ++
+ "limit ~w offset ~w",
+ [Limit, Offset]))]);
+list_users(LServer,
+ [{prefix, Prefix}, {limit, Limit}, {offset, Offset}])
+ when is_binary(Prefix) and is_integer(Limit) and
+ is_integer(Offset) ->
+ ejabberd_odbc:sql_query(LServer,
+ [list_to_binary(
+ io_lib:format(
+ "select username from users " ++
+ "where username like '~s%' " ++
+ "order by username " ++
+ "limit ~w offset ~w ",
+ [Prefix, Limit, Offset]))]).
users_number(LServer) ->
- case element(1, ejabberd_config:get_local_option({odbc_server, LServer})) of
- pgsql ->
- case ejabberd_config:get_local_option({pgsql_users_number_estimate, LServer}) of
- true ->
- ejabberd_odbc:sql_query(
- LServer,
- "select reltuples from pg_class where oid = 'users'::regclass::oid");
- _ ->
- ejabberd_odbc:sql_query(
- LServer,
- "select count(*) from users")
- end;
- _ ->
- ejabberd_odbc:sql_query(
- LServer,
- "select count(*) from users")
+ case element(1,
+ ejabberd_config:get_local_option(
+ {odbc_server, LServer}, fun(V) -> V end))
+ of
+ pgsql ->
+ case
+ ejabberd_config:get_local_option(
+ {pgsql_users_number_estimate, LServer},
+ fun(V) when is_boolean(V) -> V end,
+ false)
+ of
+ true ->
+ ejabberd_odbc:sql_query(LServer,
+ [<<"select reltuples from pg_class where "
+ "oid = 'users'::regclass::oid">>]);
+ _ ->
+ ejabberd_odbc:sql_query(LServer,
+ [<<"select count(*) from users">>])
+ end;
+ _ ->
+ ejabberd_odbc:sql_query(LServer,
+ [<<"select count(*) from users">>])
end.
-users_number(LServer, [{prefix, Prefix}]) when is_list(Prefix) ->
- ejabberd_odbc:sql_query(
- LServer,
- io_lib:fwrite("select count(*) from users " ++
- %% Warning: Escape prefix at higher level to prevent SQL
- %% injection.
- "where username like '~s%'", [Prefix]));
+users_number(LServer, [{prefix, Prefix}])
+ when is_binary(Prefix) ->
+ ejabberd_odbc:sql_query(LServer,
+ [list_to_binary(
+ io_lib:fwrite(
+ "select count(*) from users " ++
+ %% Warning: Escape prefix at higher level to prevent SQL
+ %% injection.
+ "where username like '~s%'",
+ [Prefix]))]);
users_number(LServer, []) ->
users_number(LServer).
add_spool_sql(Username, XML) ->
- ["insert into spool(username, xml) "
- "values ('", Username, "', '",
- XML,
- "');"].
+ [<<"insert into spool(username, xml) values ('">>,
+ Username, <<"', '">>, XML, <<"');">>].
add_spool(LServer, Queries) ->
- ejabberd_odbc:sql_transaction(
- LServer, Queries).
+ ejabberd_odbc:sql_transaction(LServer, Queries).
get_and_del_spool_msg_t(LServer, Username) ->
- F = fun() ->
- Result = ejabberd_odbc:sql_query_t(
- ["select username, xml from spool where username='", Username, "'"
- " order by seq;"]),
- ejabberd_odbc:sql_query_t(
- ["delete from spool where username='", Username, "';"]),
+ F = fun () ->
+ Result =
+ ejabberd_odbc:sql_query_t([<<"select username, xml from spool where "
+ "username='">>,
+ Username,
+ <<"' order by seq;">>]),
+ ejabberd_odbc:sql_query_t([<<"delete from spool where username='">>,
+ Username, <<"';">>]),
Result
end,
- ejabberd_odbc:sql_transaction(LServer,F).
+ ejabberd_odbc:sql_transaction(LServer, F).
del_spool_msg(LServer, Username) ->
- ejabberd_odbc:sql_query(
- LServer,
- ["delete from spool where username='", Username, "';"]).
+ ejabberd_odbc:sql_query(LServer,
+ [<<"delete from spool where username='">>, Username,
+ <<"';">>]).
get_roster(LServer, Username) ->
- ejabberd_odbc:sql_query(
- LServer,
- ["select username, jid, nick, subscription, ask, "
- "askmessage, server, subscribe, type from rosterusers "
- "where username='", Username, "'"]).
+ ejabberd_odbc:sql_query(LServer,
+ [<<"select username, jid, nick, subscription, "
+ "ask, askmessage, server, subscribe, "
+ "type from rosterusers where username='">>,
+ Username, <<"'">>]).
get_roster_jid_groups(LServer, Username) ->
- ejabberd_odbc:sql_query(
- LServer,
- ["select jid, grp from rostergroups "
- "where username='", Username, "'"]).
+ ejabberd_odbc:sql_query(LServer,
+ [<<"select jid, grp from rostergroups where "
+ "username='">>,
+ Username, <<"'">>]).
get_roster_groups(_LServer, Username, SJID) ->
- ejabberd_odbc:sql_query_t(
- ["select grp from rostergroups "
- "where username='", Username, "' "
- "and jid='", SJID, "';"]).
+ ejabberd_odbc:sql_query_t([<<"select grp from rostergroups where username='">>,
+ Username, <<"' and jid='">>, SJID, <<"';">>]).
del_user_roster_t(LServer, Username) ->
- ejabberd_odbc:sql_transaction(
- LServer,
- fun() ->
- ejabberd_odbc:sql_query_t(
- ["delete from rosterusers "
- " where username='", Username, "';"]),
- ejabberd_odbc:sql_query_t(
- ["delete from rostergroups "
- " where username='", Username, "';"])
- end).
+ ejabberd_odbc:sql_transaction(LServer,
+ fun () ->
+ ejabberd_odbc:sql_query_t([<<"delete from rosterusers where "
+ "username='">>,
+ Username,
+ <<"';">>]),
+ ejabberd_odbc:sql_query_t([<<"delete from rostergroups where "
+ "username='">>,
+ Username,
+ <<"';">>])
+ end).
get_roster_by_jid(_LServer, Username, SJID) ->
- ejabberd_odbc:sql_query_t(
- ["select username, jid, nick, subscription, "
- "ask, askmessage, server, subscribe, type from rosterusers "
- "where username='", Username, "' "
- "and jid='", SJID, "';"]).
+ ejabberd_odbc:sql_query_t([<<"select username, jid, nick, subscription, "
+ "ask, askmessage, server, subscribe, "
+ "type from rosterusers where username='">>,
+ Username, <<"' and jid='">>, SJID, <<"';">>]).
get_rostergroup_by_jid(LServer, Username, SJID) ->
- ejabberd_odbc:sql_query(
- LServer,
- ["select grp from rostergroups "
- "where username='", Username, "' "
- "and jid='", SJID, "'"]).
+ ejabberd_odbc:sql_query(LServer,
+ [<<"select grp from rostergroups where username='">>,
+ Username, <<"' and jid='">>, SJID, <<"'">>]).
del_roster(_LServer, Username, SJID) ->
- ejabberd_odbc:sql_query_t(
- ["delete from rosterusers "
- " where username='", Username, "' "
- " and jid='", SJID, "';"]),
- ejabberd_odbc:sql_query_t(
- ["delete from rostergroups "
- " where username='", Username, "' "
- " and jid='", SJID, "';"]).
+ ejabberd_odbc:sql_query_t([<<"delete from rosterusers where "
+ "username='">>,
+ Username, <<"' and jid='">>, SJID,
+ <<"';">>]),
+ ejabberd_odbc:sql_query_t([<<"delete from rostergroups where "
+ "username='">>,
+ Username, <<"' and jid='">>, SJID,
+ <<"';">>]).
del_roster_sql(Username, SJID) ->
- [["delete from rosterusers "
- " where username='", Username, "' "
- " and jid='", SJID, "';"],
- ["delete from rostergroups "
- " where username='", Username, "' "
- " and jid='", SJID, "';"]].
-
-update_roster(_LServer, Username, SJID, ItemVals, ItemGroups) ->
- update_t("rosterusers",
- ["username", "jid", "nick", "subscription", "ask",
- "askmessage", "server", "subscribe", "type"],
+ [[<<"delete from rosterusers where "
+ "username='">>,
+ Username, <<"' and jid='">>, SJID, <<"';">>],
+ [<<"delete from rostergroups where "
+ "username='">>,
+ Username, <<"' and jid='">>, SJID, <<"';">>]].
+
+update_roster(_LServer, Username, SJID, ItemVals,
+ ItemGroups) ->
+ update_t(<<"rosterusers">>,
+ [<<"username">>, <<"jid">>, <<"nick">>,
+ <<"subscription">>, <<"ask">>, <<"askmessage">>,
+ <<"server">>, <<"subscribe">>, <<"type">>],
ItemVals,
- ["username='", Username, "' and jid='", SJID, "'"]),
- ejabberd_odbc:sql_query_t(
- ["delete from rostergroups "
- " where username='", Username, "' "
- " and jid='", SJID, "';"]),
- lists:foreach(fun(ItemGroup) ->
- ejabberd_odbc:sql_query_t(
- ["insert into rostergroups("
- " username, jid, grp) "
- " values ('", join(ItemGroup, "', '"), "');"])
+ [<<"username='">>, Username, <<"' and jid='">>, SJID,
+ <<"'">>]),
+ ejabberd_odbc:sql_query_t([<<"delete from rostergroups where "
+ "username='">>,
+ Username, <<"' and jid='">>, SJID,
+ <<"';">>]),
+ lists:foreach(fun (ItemGroup) ->
+ ejabberd_odbc:sql_query_t([<<"insert into rostergroups( "
+ " username, jid, grp) values ('">>,
+ join(ItemGroup,
+ <<"', '">>),
+ <<"');">>])
end,
ItemGroups).
-update_roster_sql(Username, SJID, ItemVals, ItemGroups) ->
- [["delete from rosterusers "
- " where username='", Username, "' "
- " and jid='", SJID, "';"],
- ["insert into rosterusers("
- " username, jid, nick, "
- " subscription, ask, askmessage, "
- " server, subscribe, type) "
- " values ('", join(ItemVals, "', '"), "');"],
- ["delete from rostergroups "
- " where username='", Username, "' "
- " and jid='", SJID, "';"]] ++
- [["insert into rostergroups("
- " username, jid, grp) "
- " values ('", join(ItemGroup, "', '"), "');"] ||
- ItemGroup <- ItemGroups].
+update_roster_sql(Username, SJID, ItemVals,
+ ItemGroups) ->
+ [[<<"delete from rosterusers where "
+ "username='">>,
+ Username, <<"' and jid='">>, SJID, <<"';">>],
+ [<<"insert into rosterusers( "
+ " username, jid, nick, "
+ " subscription, ask, askmessage, "
+ " server, subscribe, type) "
+ "values ('">>,
+ join(ItemVals, <<"', '">>), <<"');">>],
+ [<<"delete from rostergroups where "
+ "username='">>,
+ Username, <<"' and jid='">>, SJID, <<"';">>]]
+ ++
+ [[<<"insert into rostergroups( "
+ " username, jid, grp) values ('">>,
+ join(ItemGroup, <<"', '">>), <<"');">>]
+ || ItemGroup <- ItemGroups].
roster_subscribe(_LServer, Username, SJID, ItemVals) ->
- update_t("rosterusers",
- ["username", "jid", "nick", "subscription", "ask",
- "askmessage", "server", "subscribe", "type"],
+ update_t(<<"rosterusers">>,
+ [<<"username">>, <<"jid">>, <<"nick">>,
+ <<"subscription">>, <<"ask">>, <<"askmessage">>,
+ <<"server">>, <<"subscribe">>, <<"type">>],
ItemVals,
- ["username='", Username, "' and jid='", SJID, "'"]).
+ [<<"username='">>, Username, <<"' and jid='">>, SJID,
+ <<"'">>]).
get_subscription(LServer, Username, SJID) ->
- ejabberd_odbc:sql_query(
- LServer,
- ["select subscription from rosterusers "
- "where username='", Username, "' "
- "and jid='", SJID, "'"]).
+ ejabberd_odbc:sql_query(LServer,
+ [<<"select subscription from rosterusers "
+ "where username='">>,
+ Username, <<"' and jid='">>, SJID, <<"'">>]).
set_private_data(_LServer, Username, LXMLNS, SData) ->
- update_t("private_storage",
- ["username", "namespace", "data"],
- [Username, LXMLNS, SData],
- ["username='", Username, "' and namespace='", LXMLNS, "'"]).
+ update_t(<<"private_storage">>,
+ [<<"username">>, <<"namespace">>, <<"data">>],
+ [Username, LXMLNS, SData],
+ [<<"username='">>, Username, <<"' and namespace='">>,
+ LXMLNS, <<"'">>]).
set_private_data_sql(Username, LXMLNS, SData) ->
- [["delete from private_storage "
- "where username='", Username, "' and "
- "namespace='", LXMLNS, "';"],
- ["insert into private_storage(username, namespace, data) "
- "values ('", Username, "', '", LXMLNS, "', "
- "'", SData, "');"]].
+ [[<<"delete from private_storage where username='">>,
+ Username, <<"' and namespace='">>, LXMLNS, <<"';">>],
+ [<<"insert into private_storage(username, "
+ "namespace, data) values ('">>,
+ Username, <<"', '">>, LXMLNS, <<"', '">>, SData,
+ <<"');">>]].
get_private_data(LServer, Username, LXMLNS) ->
- ejabberd_odbc:sql_query(
- LServer,
- ["select data from private_storage "
- "where username='", Username, "' and "
- "namespace='", LXMLNS, "';"]).
+ ejabberd_odbc:sql_query(LServer,
+ [<<"select data from private_storage where "
+ "username='">>,
+ Username, <<"' and namespace='">>, LXMLNS,
+ <<"';">>]).
+
+get_private_data(LServer, Username) ->
+ ejabberd_odbc:sql_query(LServer,
+ [<<"select namespace, data from private_storage "
+ "where username='">>, Username, <<"';">>]).
del_user_private_storage(LServer, Username) ->
- ejabberd_odbc:sql_query(
- LServer,
- ["delete from private_storage where username='", Username, "';"]).
-
-set_vcard(LServer, LUsername, SBDay, SCTRY, SEMail, SFN, SFamily, SGiven,
- SLBDay, SLCTRY, SLEMail, SLFN, SLFamily, SLGiven, SLLocality,
- SLMiddle, SLNickname, SLOrgName, SLOrgUnit, SLocality, SMiddle,
- SNickname, SOrgName, SOrgUnit, SVCARD, Username) ->
- ejabberd_odbc:sql_transaction(
- LServer,
- fun() ->
- update_t("vcard", ["username", "vcard"],
- [LUsername, SVCARD],
- ["username='", LUsername, "'"]),
- update_t("vcard_search",
- ["username", "lusername", "fn", "lfn", "family",
- "lfamily", "given", "lgiven", "middle", "lmiddle",
- "nickname", "lnickname", "bday", "lbday", "ctry",
- "lctry", "locality", "llocality", "email", "lemail",
- "orgname", "lorgname", "orgunit", "lorgunit"],
- [Username, LUsername, SFN, SLFN, SFamily, SLFamily,
- SGiven, SLGiven, SMiddle, SLMiddle, SNickname,
- SLNickname, SBDay, SLBDay, SCTRY, SLCTRY,
- SLocality, SLLocality, SEMail, SLEMail, SOrgName,
- SLOrgName, SOrgUnit, SLOrgUnit],
- ["lusername='", LUsername, "'"])
- end).
+ ejabberd_odbc:sql_query(LServer,
+ [<<"delete from private_storage where username='">>,
+ Username, <<"';">>]).
+
+set_vcard(LServer, LUsername, SBDay, SCTRY, SEMail, SFN,
+ SFamily, SGiven, SLBDay, SLCTRY, SLEMail, SLFN,
+ SLFamily, SLGiven, SLLocality, SLMiddle, SLNickname,
+ SLOrgName, SLOrgUnit, SLocality, SMiddle, SNickname,
+ SOrgName, SOrgUnit, SVCARD, Username) ->
+ ejabberd_odbc:sql_transaction(LServer,
+ fun () ->
+ update_t(<<"vcard">>,
+ [<<"username">>,
+ <<"vcard">>],
+ [LUsername, SVCARD],
+ [<<"username='">>, LUsername,
+ <<"'">>]),
+ update_t(<<"vcard_search">>,
+ [<<"username">>,
+ <<"lusername">>, <<"fn">>,
+ <<"lfn">>, <<"family">>,
+ <<"lfamily">>, <<"given">>,
+ <<"lgiven">>, <<"middle">>,
+ <<"lmiddle">>,
+ <<"nickname">>,
+ <<"lnickname">>, <<"bday">>,
+ <<"lbday">>, <<"ctry">>,
+ <<"lctry">>, <<"locality">>,
+ <<"llocality">>,
+ <<"email">>, <<"lemail">>,
+ <<"orgname">>,
+ <<"lorgname">>,
+ <<"orgunit">>,
+ <<"lorgunit">>],
+ [Username, LUsername, SFN,
+ SLFN, SFamily, SLFamily,
+ SGiven, SLGiven, SMiddle,
+ SLMiddle, SNickname,
+ SLNickname, SBDay, SLBDay,
+ SCTRY, SLCTRY, SLocality,
+ SLLocality, SEMail, SLEMail,
+ SOrgName, SLOrgName,
+ SOrgUnit, SLOrgUnit],
+ [<<"lusername='">>,
+ LUsername, <<"'">>])
+ end).
get_vcard(LServer, Username) ->
- ejabberd_odbc:sql_query(
- LServer,
- ["select vcard from vcard "
- "where username='", Username, "';"]).
+ ejabberd_odbc:sql_query(LServer,
+ [<<"select vcard from vcard where username='">>,
+ Username, <<"';">>]).
get_default_privacy_list(LServer, Username) ->
- ejabberd_odbc:sql_query(
- LServer,
- ["select name from privacy_default_list "
- "where username='", Username, "';"]).
+ ejabberd_odbc:sql_query(LServer,
+ [<<"select name from privacy_default_list "
+ "where username='">>,
+ Username, <<"';">>]).
get_default_privacy_list_t(Username) ->
- ejabberd_odbc:sql_query_t(
- ["select name from privacy_default_list "
- "where username='", Username, "';"]).
+ ejabberd_odbc:sql_query_t([<<"select name from privacy_default_list "
+ "where username='">>,
+ Username, <<"';">>]).
get_privacy_list_names(LServer, Username) ->
- ejabberd_odbc:sql_query(
- LServer,
- ["select name from privacy_list "
- "where username='", Username, "';"]).
+ ejabberd_odbc:sql_query(LServer,
+ [<<"select name from privacy_list where "
+ "username='">>,
+ Username, <<"';">>]).
get_privacy_list_names_t(Username) ->
- ejabberd_odbc:sql_query_t(
- ["select name from privacy_list "
- "where username='", Username, "';"]).
+ ejabberd_odbc:sql_query_t([<<"select name from privacy_list where "
+ "username='">>,
+ Username, <<"';">>]).
get_privacy_list_id(LServer, Username, SName) ->
- ejabberd_odbc:sql_query(
- LServer,
- ["select id from privacy_list "
- "where username='", Username, "' and name='", SName, "';"]).
+ ejabberd_odbc:sql_query(LServer,
+ [<<"select id from privacy_list where username='">>,
+ Username, <<"' and name='">>, SName, <<"';">>]).
get_privacy_list_id_t(Username, SName) ->
- ejabberd_odbc:sql_query_t(
- ["select id from privacy_list "
- "where username='", Username, "' and name='", SName, "';"]).
+ ejabberd_odbc:sql_query_t([<<"select id from privacy_list where username='">>,
+ Username, <<"' and name='">>, SName, <<"';">>]).
get_privacy_list_data(LServer, Username, SName) ->
- ejabberd_odbc:sql_query(
- LServer,
- ["select t, value, action, ord, match_all, match_iq, "
- "match_message, match_presence_in, match_presence_out "
- "from privacy_list_data "
- "where id = (select id from privacy_list where "
- " username='", Username, "' and name='", SName, "') "
- "order by ord;"]).
+ ejabberd_odbc:sql_query(LServer,
+ [<<"select t, value, action, ord, match_all, "
+ "match_iq, match_message, match_presence_in, "
+ "match_presence_out from privacy_list_data "
+ "where id = (select id from privacy_list "
+ "where username='">>,
+ Username, <<"' and name='">>, SName,
+ <<"') order by ord;">>]).
get_privacy_list_data_by_id(LServer, ID) ->
- ejabberd_odbc:sql_query(
- LServer,
- ["select t, value, action, ord, match_all, match_iq, "
- "match_message, match_presence_in, match_presence_out "
- "from privacy_list_data "
- "where id='", ID, "' order by ord;"]).
+ ejabberd_odbc:sql_query(LServer,
+ [<<"select t, value, action, ord, match_all, "
+ "match_iq, match_message, match_presence_in, "
+ "match_presence_out from privacy_list_data "
+ "where id='">>,
+ ID, <<"' order by ord;">>]).
get_privacy_list_data_by_id_t(ID) ->
- ejabberd_odbc:sql_query_t(
- ["select t, value, action, ord, match_all, match_iq, "
- "match_message, match_presence_in, match_presence_out "
- "from privacy_list_data "
- "where id='", ID, "' order by ord;"]).
+ ejabberd_odbc:sql_query_t([<<"select t, value, action, ord, match_all, "
+ "match_iq, match_message, match_presence_in, "
+ "match_presence_out from privacy_list_data "
+ "where id='">>,
+ ID, <<"' order by ord;">>]).
set_default_privacy_list(Username, SName) ->
- update_t("privacy_default_list", ["username", "name"],
- [Username, SName], ["username='", Username, "'"]).
+ update_t(<<"privacy_default_list">>,
+ [<<"username">>, <<"name">>], [Username, SName],
+ [<<"username='">>, Username, <<"'">>]).
unset_default_privacy_list(LServer, Username) ->
- ejabberd_odbc:sql_query(
- LServer,
- ["delete from privacy_default_list "
- " where username='", Username, "';"]).
+ ejabberd_odbc:sql_query(LServer,
+ [<<"delete from privacy_default_list "
+ " where username='">>,
+ Username, <<"';">>]).
remove_privacy_list(Username, SName) ->
- ejabberd_odbc:sql_query_t(
- ["delete from privacy_list "
- "where username='", Username, "' and name='", SName, "';"]).
+ ejabberd_odbc:sql_query_t([<<"delete from privacy_list where username='">>,
+ Username, <<"' and name='">>, SName, <<"';">>]).
add_privacy_list(Username, SName) ->
- ejabberd_odbc:sql_query_t(
- ["insert into privacy_list(username, name) "
- "values ('", Username, "', '", SName, "');"]).
+ ejabberd_odbc:sql_query_t([<<"insert into privacy_list(username, name) "
+ "values ('">>,
+ Username, <<"', '">>, SName, <<"');">>]).
set_privacy_list(ID, RItems) ->
- ejabberd_odbc:sql_query_t(
- ["delete from privacy_list_data "
- "where id='", ID, "';"]),
- lists:foreach(fun(Items) ->
- ejabberd_odbc:sql_query_t(
- ["insert into privacy_list_data("
- "id, t, value, action, ord, match_all, match_iq, "
- "match_message, match_presence_in, "
- "match_presence_out "
- ") "
- "values ('", ID, "', '",
- join(Items, "', '"), "');"])
- end, RItems).
+ ejabberd_odbc:sql_query_t([<<"delete from privacy_list_data where "
+ "id='">>,
+ ID, <<"';">>]),
+ lists:foreach(fun (Items) ->
+ ejabberd_odbc:sql_query_t([<<"insert into privacy_list_data(id, t, "
+ "value, action, ord, match_all, match_iq, "
+ "match_message, match_presence_in, match_prese"
+ "nce_out ) values ('">>,
+ ID, <<"', '">>,
+ join(Items, <<"', '">>),
+ <<"');">>])
+ end,
+ RItems).
del_privacy_lists(LServer, Server, Username) ->
- ejabberd_odbc:sql_query(
- LServer,
- ["delete from privacy_list where username='", Username, "';"]),
- ejabberd_odbc:sql_query(
- LServer,
- ["delete from privacy_list_data where value='", Username++"@"++Server, "';"]),
- ejabberd_odbc:sql_query(
- LServer,
- ["delete from privacy_default_list where username='", Username, "';"]).
-
%% Characters to escape
-escape($\0) -> "\\0";
-escape($\n) -> "\\n";
-escape($\t) -> "\\t";
-escape($\b) -> "\\b";
-escape($\r) -> "\\r";
-escape($') -> "''";
-escape($") -> "\\\"";
-escape($\\) -> "\\\\";
-escape(C) -> C.
-
%% Count number of records in a table given a where clause
-count_records_where(LServer, Table, WhereClause) ->
- ejabberd_odbc:sql_query(
- LServer,
- ["select count(*) from ", Table, " ", WhereClause, ";"]).
+ ejabberd_odbc:sql_query(LServer,
+ [<<"delete from privacy_list where username='">>,
+ Username, <<"';">>]),
+ ejabberd_odbc:sql_query(LServer,
+ [<<"delete from privacy_list_data where "
+ "value='">>,
+ <<Username/binary, "@", Server/binary>>,
+ <<"';">>]),
+ ejabberd_odbc:sql_query(LServer,
+ [<<"delete from privacy_default_list where "
+ "username='">>,
+ Username, <<"';">>]).
+
+escape($\000) -> <<"\\0">>;
+escape($\n) -> <<"\\n">>;
+escape($\t) -> <<"\\t">>;
+escape($\b) -> <<"\\b">>;
+escape($\r) -> <<"\\r">>;
+escape($') -> <<"''">>;
+escape($") -> <<"\\\"">>;
+escape($\\) -> <<"\\\\">>;
+escape(C) -> <<C>>.
+count_records_where(LServer, Table, WhereClause) ->
+ ejabberd_odbc:sql_query(LServer,
+ [<<"select count(*) from ">>, Table, <<" ">>,
+ WhereClause, <<";">>]).
get_roster_version(LServer, LUser) ->
- ejabberd_odbc:sql_query(LServer,
- ["select version from roster_version where username = '", LUser, "'"]).
+ ejabberd_odbc:sql_query(LServer,
+ [<<"select version from roster_version where "
+ "username = '">>,
+ LUser, <<"'">>]).
+
set_roster_version(LUser, Version) ->
- update_t("roster_version", ["username", "version"], [LUser, Version], ["username = '", LUser, "'"]).
+ update_t(<<"roster_version">>,
+ [<<"username">>, <<"version">>], [LUser, Version],
+ [<<"username = '">>, LUser, <<"'">>]).
+
-endif.
%% -----------------
%% MSSQL queries
-ifdef(mssql).
-get_db_type() ->
- mssql.
-
%% Queries can be either a fun or a list of queries
-sql_transaction(LServer, Queries) when is_list(Queries) ->
- %% SQL transaction based on a list of queries
- %% This function automatically
- F = fun() ->
- lists:foreach(fun(Query) ->
- ejabberd_odbc:sql_query(LServer, Query)
- end, Queries)
- end,
+get_db_type() -> mssql.
+
+sql_transaction(LServer, Queries)
+ when is_list(Queries) ->
+ F = fun () ->
+ lists:foreach(fun (Query) ->
+ ejabberd_odbc:sql_query(LServer, Query)
+ end,
+ Queries)
+ end,
{atomic, catch F()};
sql_transaction(_LServer, FQueries) ->
{atomic, catch FQueries()}.
get_last(LServer, Username) ->
- ejabberd_odbc:sql_query(
- LServer,
- ["EXECUTE dbo.get_last '", Username, "'"]).
+ ejabberd_odbc:sql_query(LServer,
+ [<<"EXECUTE dbo.get_last '">>, Username, <<"'">>]).
set_last_t(LServer, Username, Seconds, State) ->
- Result = ejabberd_odbc:sql_query(
- LServer,
- ["EXECUTE dbo.set_last '", Username, "', '", Seconds,
- "', '", State, "'"]),
+ Result = ejabberd_odbc:sql_query(LServer,
+ [<<"EXECUTE dbo.set_last '">>, Username,
+ <<"', '">>, Seconds, <<"', '">>, State,
+ <<"'">>]),
{atomic, Result}.
del_last(LServer, Username) ->
- ejabberd_odbc:sql_query(
- LServer,
- ["EXECUTE dbo.del_last '", Username, "'"]).
+ ejabberd_odbc:sql_query(LServer,
+ [<<"EXECUTE dbo.del_last '">>, Username, <<"'">>]).
get_password(LServer, Username) ->
- ejabberd_odbc:sql_query(
- LServer,
- ["EXECUTE dbo.get_password '", Username, "'"]).
+ ejabberd_odbc:sql_query(LServer,
+ [<<"EXECUTE dbo.get_password '">>, Username,
+ <<"'">>]).
set_password_t(LServer, Username, Pass) ->
- Result = ejabberd_odbc:sql_query(
- LServer,
- ["EXECUTE dbo.set_password '", Username, "', '", Pass, "'"]),
+ Result = ejabberd_odbc:sql_query(LServer,
+ [<<"EXECUTE dbo.set_password '">>,
+ Username, <<"', '">>, Pass, <<"'">>]),
{atomic, Result}.
add_user(LServer, Username, Pass) ->
- ejabberd_odbc:sql_query(
- LServer,
- ["EXECUTE dbo.add_user '", Username, "', '", Pass, "'"]).
+ ejabberd_odbc:sql_query(LServer,
+ [<<"EXECUTE dbo.add_user '">>, Username, <<"', '">>,
+ Pass, <<"'">>]).
del_user(LServer, Username) ->
- ejabberd_odbc:sql_query(
- LServer,
- ["EXECUTE dbo.del_user '", Username ,"'"]).
+ ejabberd_odbc:sql_query(LServer,
+ [<<"EXECUTE dbo.del_user '">>, Username, <<"'">>]).
del_user_return_password(LServer, Username, Pass) ->
- ejabberd_odbc:sql_query(
- LServer,
- ["EXECUTE dbo.del_user_return_password '", Username, "'"]),
+ ejabberd_odbc:sql_query(LServer,
+ [<<"EXECUTE dbo.del_user_return_password '">>,
+ Username, <<"'">>]),
Pass.
list_users(LServer) ->
- ejabberd_odbc:sql_query(
- LServer,
- "EXECUTE dbo.list_users").
+ ejabberd_odbc:sql_query(LServer,
+ <<"EXECUTE dbo.list_users">>).
-list_users(LServer, _) ->
- % scope listing not supported
- list_users(LServer).
+list_users(LServer, _) -> list_users(LServer).
users_number(LServer) ->
- ejabberd_odbc:sql_query(
- LServer,
- "select count(*) from users with (nolock)").
+ ejabberd_odbc:sql_query(LServer,
+ <<"select count(*) from users with (nolock)">>).
-users_number(LServer, _) ->
- % scope listing not supported
- users_number(LServer).
+users_number(LServer, _) -> users_number(LServer).
add_spool_sql(Username, XML) ->
- ["EXECUTE dbo.add_spool '", Username, "' , '",XML,"'"].
+ [<<"EXECUTE dbo.add_spool '">>, Username, <<"' , '">>,
+ XML, <<"'">>].
add_spool(LServer, Queries) ->
- lists:foreach(fun(Query) ->
+ lists:foreach(fun (Query) ->
ejabberd_odbc:sql_query(LServer, Query)
end,
Queries).
get_and_del_spool_msg_t(LServer, Username) ->
- [Result] = case ejabberd_odbc:sql_query(
- LServer,
- ["EXECUTE dbo.get_and_del_spool_msg '", Username, "'"]) of
- Rs when is_list(Rs) ->
- lists:filter(fun({selected, _Header, _Row}) ->
- true;
- ({updated, _N}) ->
- false
+ [Result] = case ejabberd_odbc:sql_query(LServer,
+ [<<"EXECUTE dbo.get_and_del_spool_msg '">>,
+ Username, <<"'">>])
+ of
+ Rs when is_list(Rs) ->
+ lists:filter(fun ({selected, _Header, _Row}) -> true;
+ ({updated, _N}) -> false
end,
Rs);
- Rs -> [Rs]
+ Rs -> [Rs]
end,
{atomic, Result}.
del_spool_msg(LServer, Username) ->
- ejabberd_odbc:sql_query(
- LServer,
- ["EXECUTE dbo.del_spool_msg '", Username, "'"]).
+ ejabberd_odbc:sql_query(LServer,
+ [<<"EXECUTE dbo.del_spool_msg '">>, Username,
+ <<"'">>]).
get_roster(LServer, Username) ->
- ejabberd_odbc:sql_query(
- LServer,
- ["EXECUTE dbo.get_roster '", Username, "'"]).
+ ejabberd_odbc:sql_query(LServer,
+ [<<"EXECUTE dbo.get_roster '">>, Username,
+ <<"'">>]).
get_roster_jid_groups(LServer, Username) ->
- ejabberd_odbc:sql_query(
- LServer,
- ["EXECUTE dbo.get_roster_jid_groups '", Username, "'"]).
+ ejabberd_odbc:sql_query(LServer,
+ [<<"EXECUTE dbo.get_roster_jid_groups '">>,
+ Username, <<"'">>]).
get_roster_groups(LServer, Username, SJID) ->
- ejabberd_odbc:sql_query(
- LServer,
- ["EXECUTE dbo.get_roster_groups '", Username, "' , '", SJID, "'"]).
+ ejabberd_odbc:sql_query(LServer,
+ [<<"EXECUTE dbo.get_roster_groups '">>, Username,
+ <<"' , '">>, SJID, <<"'">>]).
del_user_roster_t(LServer, Username) ->
- Result = ejabberd_odbc:sql_query(
- LServer,
- ["EXECUTE dbo.del_user_roster '", Username, "'"]),
+ Result = ejabberd_odbc:sql_query(LServer,
+ [<<"EXECUTE dbo.del_user_roster '">>,
+ Username, <<"'">>]),
{atomic, Result}.
get_roster_by_jid(LServer, Username, SJID) ->
- ejabberd_odbc:sql_query(
- LServer,
- ["EXECUTE dbo.get_roster_by_jid '", Username, "' , '", SJID, "'"]).
+ ejabberd_odbc:sql_query(LServer,
+ [<<"EXECUTE dbo.get_roster_by_jid '">>, Username,
+ <<"' , '">>, SJID, <<"'">>]).
get_rostergroup_by_jid(LServer, Username, SJID) ->
- ejabberd_odbc:sql_query(
- LServer,
- ["EXECUTE dbo.get_rostergroup_by_jid '", Username, "' , '", SJID, "'"]).
+ ejabberd_odbc:sql_query(LServer,
+ [<<"EXECUTE dbo.get_rostergroup_by_jid '">>,
+ Username, <<"' , '">>, SJID, <<"'">>]).
del_roster(LServer, Username, SJID) ->
- ejabberd_odbc:sql_query(
- LServer,
- ["EXECUTE dbo.del_roster '", Username, "', '", SJID, "'"]).
+ ejabberd_odbc:sql_query(LServer,
+ [<<"EXECUTE dbo.del_roster '">>, Username,
+ <<"', '">>, SJID, <<"'">>]).
del_roster_sql(Username, SJID) ->
- ["EXECUTE dbo.del_roster '", Username, "', '", SJID, "'"].
+ [<<"EXECUTE dbo.del_roster '">>, Username, <<"', '">>,
+ SJID, <<"'">>].
-update_roster(LServer, Username, SJID, ItemVals, ItemGroups) ->
- Query1 = ["EXECUTE dbo.del_roster '", Username, "', '", SJID, "' "],
+update_roster(LServer, Username, SJID, ItemVals,
+ ItemGroups) ->
+ Query1 = [<<"EXECUTE dbo.del_roster '">>, Username,
+ <<"', '">>, SJID, <<"' ">>],
ejabberd_odbc:sql_query(LServer, lists:flatten(Query1)),
- Query2 = ["EXECUTE dbo.add_roster_user ", ItemVals],
+ Query2 = [<<"EXECUTE dbo.add_roster_user ">>, ItemVals],
ejabberd_odbc:sql_query(LServer, lists:flatten(Query2)),
- Query3 = ["EXECUTE dbo.del_roster_groups '", Username, "', '", SJID, "' "],
+ Query3 = [<<"EXECUTE dbo.del_roster_groups '">>,
+ Username, <<"', '">>, SJID, <<"' ">>],
ejabberd_odbc:sql_query(LServer, lists:flatten(Query3)),
- lists:foreach(fun(ItemGroup) ->
- Query = ["EXECUTE dbo.add_roster_group ",
+ lists:foreach(fun (ItemGroup) ->
+ Query = [<<"EXECUTE dbo.add_roster_group ">>,
ItemGroup],
- ejabberd_odbc:sql_query(LServer,
- lists:flatten(Query))
+ ejabberd_odbc:sql_query(LServer, lists:flatten(Query))
end,
ItemGroups).
-update_roster_sql(Username, SJID, ItemVals, ItemGroups) ->
- ["BEGIN TRANSACTION ",
- "EXECUTE dbo.del_roster_groups '", Username, "','", SJID, "' ",
- "EXECUTE dbo.add_roster_user ", ItemVals, " "] ++
- [lists:flatten("EXECUTE dbo.add_roster_group ", ItemGroup, " ")
- || ItemGroup <- ItemGroups] ++
- ["COMMIT"].
+update_roster_sql(Username, SJID, ItemVals,
+ ItemGroups) ->
+ [<<"BEGIN TRANSACTION ">>,
+ <<"EXECUTE dbo.del_roster_groups '">>, Username,
+ <<"','">>, SJID, <<"' ">>,
+ <<"EXECUTE dbo.add_roster_user ">>, ItemVals, <<" ">>]
+ ++
+ [lists:flatten(<<"EXECUTE dbo.add_roster_group ">>,
+ ItemGroup, <<" ">>)
+ || ItemGroup <- ItemGroups]
+ ++ [<<"COMMIT">>].
roster_subscribe(LServer, _Username, _SJID, ItemVals) ->
- catch ejabberd_odbc:sql_query(
- LServer,
- ["EXECUTE dbo.add_roster_user ", ItemVals]).
+ catch ejabberd_odbc:sql_query(LServer,
+ [<<"EXECUTE dbo.add_roster_user ">>,
+ ItemVals]).
get_subscription(LServer, Username, SJID) ->
- ejabberd_odbc:sql_query(
- LServer,
- ["EXECUTE dbo.get_subscription '", Username, "' , '", SJID, "'"]).
+ ejabberd_odbc:sql_query(LServer,
+ [<<"EXECUTE dbo.get_subscription '">>, Username,
+ <<"' , '">>, SJID, <<"'">>]).
set_private_data(LServer, Username, LXMLNS, SData) ->
- ejabberd_odbc:sql_query(
- LServer,
- set_private_data_sql(Username, LXMLNS, SData)).
+ ejabberd_odbc:sql_query(LServer,
+ set_private_data_sql(Username, LXMLNS, SData)).
set_private_data_sql(Username, LXMLNS, SData) ->
- ["EXECUTE dbo.set_private_data '", Username, "' , '", LXMLNS, "' , '", SData, "'"].
+ [<<"EXECUTE dbo.set_private_data '">>, Username,
+ <<"' , '">>, LXMLNS, <<"' , '">>, SData, <<"'">>].
get_private_data(LServer, Username, LXMLNS) ->
- ejabberd_odbc:sql_query(
- LServer,
- ["EXECUTE dbo.get_private_data '", Username, "' , '", LXMLNS, "'"]).
+ ejabberd_odbc:sql_query(LServer,
+ [<<"EXECUTE dbo.get_private_data '">>, Username,
+ <<"' , '">>, LXMLNS, <<"'">>]).
del_user_private_storage(LServer, Username) ->
- ejabberd_odbc:sql_query(
- LServer,
- ["EXECUTE dbo.del_user_storage '", Username, "'"]).
-
-set_vcard(LServer, LUsername, SBDay, SCTRY, SEMail, SFN, SFamily, SGiven,
- SLBDay, SLCTRY, SLEMail, SLFN, SLFamily, SLGiven, SLLocality,
- SLMiddle, SLNickname, SLOrgName, SLOrgUnit, SLocality, SMiddle,
- SNickname, SOrgName, SOrgUnit, SVCARD, Username) ->
- ejabberd_odbc:sql_query(
- LServer,
- ["EXECUTE dbo.set_vcard '", SVCARD, "' , '", Username, "' , '", LUsername, "' , '",
- SFN, "' , '", SLFN, "' , '", SFamily, "' , '", SLFamily, "' , '",
- SGiven, "' , '", SLGiven, "' , '", SMiddle, "' , '", SLMiddle, "' , '",
- SNickname, "' , '", SLNickname, "' , '", SBDay, "' , '", SLBDay, "' , '",
- SCTRY, "' , '", SLCTRY, "' , '", SLocality, "' , '", SLLocality, "' , '",
- SEMail, "' , '", SLEMail, "' , '", SOrgName, "' , '", SLOrgName, "' , '",
- SOrgUnit, "' , '", SLOrgUnit, "'"]).
+ ejabberd_odbc:sql_query(LServer,
+ [<<"EXECUTE dbo.del_user_storage '">>, Username,
+ <<"'">>]).
+
+set_vcard(LServer, LUsername, SBDay, SCTRY, SEMail, SFN,
+ SFamily, SGiven, SLBDay, SLCTRY, SLEMail, SLFN,
+ SLFamily, SLGiven, SLLocality, SLMiddle, SLNickname,
+ SLOrgName, SLOrgUnit, SLocality, SMiddle, SNickname,
+ SOrgName, SOrgUnit, SVCARD, Username) ->
+ ejabberd_odbc:sql_query(LServer,
+ [<<"EXECUTE dbo.set_vcard '">>, SVCARD, <<"' , '">>,
+ Username, <<"' , '">>, LUsername, <<"' , '">>, SFN,
+ <<"' , '">>, SLFN, <<"' , '">>, SFamily,
+ <<"' , '">>, SLFamily, <<"' , '">>, SGiven,
+ <<"' , '">>, SLGiven, <<"' , '">>, SMiddle,
+ <<"' , '">>, SLMiddle, <<"' , '">>, SNickname,
+ <<"' , '">>, SLNickname, <<"' , '">>, SBDay,
+ <<"' , '">>, SLBDay, <<"' , '">>, SCTRY,
+ <<"' , '">>, SLCTRY, <<"' , '">>, SLocality,
+ <<"' , '">>, SLLocality, <<"' , '">>, SEMail,
+ <<"' , '">>, SLEMail, <<"' , '">>, SOrgName,
+ <<"' , '">>, SLOrgName, <<"' , '">>, SOrgUnit,
+ <<"' , '">>, SLOrgUnit, <<"'">>]).
get_vcard(LServer, Username) ->
- ejabberd_odbc:sql_query(
- LServer,
- ["EXECUTE dbo.get_vcard '", Username, "'"]).
+ ejabberd_odbc:sql_query(LServer,
+ [<<"EXECUTE dbo.get_vcard '">>, Username, <<"'">>]).
get_default_privacy_list(LServer, Username) ->
- ejabberd_odbc:sql_query(
- LServer,
- ["EXECUTE dbo.get_default_privacy_list '", Username, "'"]).
+ ejabberd_odbc:sql_query(LServer,
+ [<<"EXECUTE dbo.get_default_privacy_list '">>,
+ Username, <<"'">>]).
get_default_privacy_list_t(Username) ->
- ejabberd_odbc:sql_query_t(
- ["EXECUTE dbo.get_default_privacy_list '", Username, "'"]).
+ ejabberd_odbc:sql_query_t([<<"EXECUTE dbo.get_default_privacy_list '">>,
+ Username, <<"'">>]).
get_privacy_list_names(LServer, Username) ->
- ejabberd_odbc:sql_query(
- LServer,
- ["EXECUTE dbo.get_privacy_list_names '", Username, "'"]).
+ ejabberd_odbc:sql_query(LServer,
+ [<<"EXECUTE dbo.get_privacy_list_names '">>,
+ Username, <<"'">>]).
get_privacy_list_names_t(Username) ->
- ejabberd_odbc:sql_query_t(
- ["EXECUTE dbo.get_privacy_list_names '", Username, "'"]).
+ ejabberd_odbc:sql_query_t([<<"EXECUTE dbo.get_privacy_list_names '">>,
+ Username, <<"'">>]).
get_privacy_list_id(LServer, Username, SName) ->
- ejabberd_odbc:sql_query(
- LServer,
- ["EXECUTE dbo.get_privacy_list_id '", Username, "' , '", SName, "'"]).
+ ejabberd_odbc:sql_query(LServer,
+ [<<"EXECUTE dbo.get_privacy_list_id '">>, Username,
+ <<"' , '">>, SName, <<"'">>]).
get_privacy_list_id_t(Username, SName) ->
- ejabberd_odbc:sql_query_t(
- ["EXECUTE dbo.get_privacy_list_id '", Username, "' , '", SName, "'"]).
+ ejabberd_odbc:sql_query_t([<<"EXECUTE dbo.get_privacy_list_id '">>,
+ Username, <<"' , '">>, SName, <<"'">>]).
get_privacy_list_data(LServer, Username, SName) ->
- ejabberd_odbc:sql_query(
- LServer,
- ["EXECUTE dbo.get_privacy_list_data '", Username, "' , '", SName, "'"]).
+ ejabberd_odbc:sql_query(LServer,
+ [<<"EXECUTE dbo.get_privacy_list_data '">>,
+ Username, <<"' , '">>, SName, <<"'">>]).
get_privacy_list_data_by_id(LServer, ID) ->
- ejabberd_odbc:sql_query(
- LServer,
- ["EXECUTE dbo.get_privacy_list_data_by_id '", ID, "'"]).
+ ejabberd_odbc:sql_query(LServer,
+ [<<"EXECUTE dbo.get_privacy_list_data_by_id '">>,
+ ID, <<"'">>]).
get_privacy_list_data_by_id_t(ID) ->
- ejabberd_odbc:sql_query_t(
- ["EXECUTE dbo.get_privacy_list_data_by_id '", ID, "'"]).
+ ejabberd_odbc:sql_query_t([<<"EXECUTE dbo.get_privacy_list_data_by_id '">>,
+ ID, <<"'">>]).
set_default_privacy_list(Username, SName) ->
- ejabberd_odbc:sql_query_t(
- ["EXECUTE dbo.set_default_privacy_list '", Username, "' , '", SName, "'"]).
+ ejabberd_odbc:sql_query_t([<<"EXECUTE dbo.set_default_privacy_list '">>,
+ Username, <<"' , '">>, SName, <<"'">>]).
unset_default_privacy_list(LServer, Username) ->
- ejabberd_odbc:sql_query(
- LServer,
- ["EXECUTE dbo.unset_default_privacy_list '", Username, "'"]).
+ ejabberd_odbc:sql_query(LServer,
+ [<<"EXECUTE dbo.unset_default_privacy_list '">>,
+ Username, <<"'">>]).
remove_privacy_list(Username, SName) ->
- ejabberd_odbc:sql_query_t(
- ["EXECUTE dbo.remove_privacy_list '", Username, "' , '", SName, "'"]).
+ ejabberd_odbc:sql_query_t([<<"EXECUTE dbo.remove_privacy_list '">>,
+ Username, <<"' , '">>, SName, <<"'">>]).
add_privacy_list(Username, SName) ->
- ejabberd_odbc:sql_query_t(
- ["EXECUTE dbo.add_privacy_list '", Username, "' , '", SName, "'"]).
+ ejabberd_odbc:sql_query_t([<<"EXECUTE dbo.add_privacy_list '">>,
+ Username, <<"' , '">>, SName, <<"'">>]).
set_privacy_list(ID, RItems) ->
- ejabberd_odbc:sql_query_t(
- ["EXECUTE dbo.del_privacy_list_by_id '", ID, "'"]),
-
- lists:foreach(fun(Items) ->
- ejabberd_odbc:sql_query_t(
- ["EXECUTE dbo.set_privacy_list '", ID, "', '", join(Items, "', '"), "'"])
- end, RItems).
+ ejabberd_odbc:sql_query_t([<<"EXECUTE dbo.del_privacy_list_by_id '">>,
+ ID, <<"'">>]),
+ lists:foreach(fun (Items) ->
+ ejabberd_odbc:sql_query_t([<<"EXECUTE dbo.set_privacy_list '">>,
+ ID, <<"', '">>,
+ join(Items, <<"', '">>),
+ <<"'">>])
+ end,
+ RItems).
del_privacy_lists(LServer, Server, Username) ->
- ejabberd_odbc:sql_query(
- LServer,
- ["EXECUTE dbo.del_privacy_lists @Server='", Server ,"' @username='", Username, "'"]).
-
%% Characters to escape
-escape($\0) -> "\\0";
-escape($\t) -> "\\t";
-escape($\b) -> "\\b";
-escape($\r) -> "\\r";
-escape($') -> "\''";
-escape($") -> "\\\"";
-escape(C) -> C.
-
%% Count number of records in a table given a where clause
+ ejabberd_odbc:sql_query(LServer,
+ [<<"EXECUTE dbo.del_privacy_lists @Server='">>,
+ Server, <<"' @username='">>, Username, <<"'">>]).
+
+escape($\000) -> <<"\\0">>;
+escape($\t) -> <<"\\t">>;
+escape($\b) -> <<"\\b">>;
+escape($\r) -> <<"\\r">>;
+escape($') -> <<"''">>;
+escape($") -> <<"\\\"">>;
+escape(C) -> C.
+
count_records_where(LServer, Table, WhereClause) ->
- ejabberd_odbc:sql_query(
- LServer,
- ["select count(*) from ", Table, " with (nolock) ", WhereClause]).
+ ejabberd_odbc:sql_query(LServer,
+ [<<"select count(*) from ">>, Table,
+ <<" with (nolock) ">>, WhereClause]).
get_roster_version(LServer, LUser) ->
- ejabberd_odbc:sql_query(
- LServer,
- ["EXECUTE dbo.get_roster_version '", LUser, "'"]).
+ ejabberd_odbc:sql_query(LServer,
+ [<<"EXECUTE dbo.get_roster_version '">>, LUser,
+ <<"'">>]).
set_roster_version(Username, Version) ->
- %% This function doesn't know the vhost, so we hope it's the first one defined:
- LServer = ?MYNAME,
- ejabberd_odbc:sql_query(
- LServer,
- ["EXECUTE dbo.set_roster_version '", Username, "', '", Version, "'"]).
+ LServer = (?MYNAME),
+ ejabberd_odbc:sql_query(LServer,
+ [<<"EXECUTE dbo.set_roster_version '">>, Username,
+ <<"', '">>, Version, <<"'">>]).
+
-endif.
diff --git a/src/odbc/pg.sql b/src/odbc/pg.sql
index 4ed897e79..0b641d575 100644
--- a/src/odbc/pg.sql
+++ b/src/odbc/pg.sql
@@ -274,3 +274,12 @@ CREATE TABLE motd (
xml text,
created_at TIMESTAMP NOT NULL DEFAULT now()
);
+
+CREATE TABLE caps_features (
+ node text NOT NULL,
+ subnode text NOT NULL,
+ feature text,
+ created_at TIMESTAMP NOT NULL DEFAULT now()
+);
+
+CREATE INDEX i_caps_features_node_subnode ON caps_features USING btree (node, subnode);
diff --git a/src/p1_fsm.erl b/src/p1_fsm.erl
index 647ba3bf2..885dc0223 100644
--- a/src/p1_fsm.erl
+++ b/src/p1_fsm.erl
@@ -126,8 +126,6 @@
start_timer/2,send_event_after/2,cancel_timer/1,
enter_loop/4, enter_loop/5, enter_loop/6, wake_hib/7]).
--export([behaviour_info/1]).
-
%% Internal exports
-export([init_it/6, print_event/3,
system_continue/3,
@@ -139,17 +137,53 @@
%%% Internal gen_fsm state
%%% This state is used to defined resource control values:
--record(limits, {max_queue}).
+-record(limits, {max_queue :: non_neg_integer()}).
%%% ---------------------------------------------------
%%% Interface functions.
%%% ---------------------------------------------------
-behaviour_info(callbacks) ->
- [{init,1},{handle_event,3},{handle_sync_event,4},{handle_info,3},
- {terminate,3},{code_change,4}, {print_state,1}];
-behaviour_info(_Other) ->
- undefined.
+-callback init(Args :: term()) ->
+ {ok, StateName :: atom(), StateData :: term()} |
+ {ok, StateName :: atom(), StateData :: term(), timeout() | hibernate} |
+ {stop, Reason :: term()} | ignore.
+-callback handle_event(Event :: term(), StateName :: atom(),
+ StateData :: term()) ->
+ {next_state, NextStateName :: atom(), NewStateData :: term()} |
+ {next_state, NextStateName :: atom(), NewStateData :: term(),
+ timeout() | hibernate} |
+ {migrate, NewStateData :: term(),
+ {Node :: atom(), M :: atom(), F :: atom(), A :: list()},
+ Timeout :: timeout()} |
+ {stop, Reason :: term(), NewStateData :: term()}.
+-callback handle_sync_event(Event :: term(), From :: {pid(), Tag :: term()},
+ StateName :: atom(), StateData :: term()) ->
+ {reply, Reply :: term(), NextStateName :: atom(), NewStateData :: term()} |
+ {reply, Reply :: term(), NextStateName :: atom(), NewStateData :: term(),
+ timeout() | hibernate} |
+ {next_state, NextStateName :: atom(), NewStateData :: term()} |
+ {next_state, NextStateName :: atom(), NewStateData :: term(),
+ timeout() | hibernate} |
+ {migrate, NewStateData :: term(),
+ {Node :: atom(), M :: atom(), F :: atom(), A :: list()},
+ Timeout :: timeout()} |
+ {stop, Reason :: term(), Reply :: term(), NewStateData :: term()} |
+ {stop, Reason :: term(), NewStateData :: term()}.
+-callback handle_info(Info :: term(), StateName :: atom(),
+ StateData :: term()) ->
+ {next_state, NextStateName :: atom(), NewStateData :: term()} |
+ {next_state, NextStateName :: atom(), NewStateData :: term(),
+ timeout() | hibernate} |
+ {migrate, NewStateData :: term(),
+ {Node :: atom(), M :: atom(), F :: atom(), A :: list()},
+ Timeout :: timeout()} |
+ {stop, Reason :: normal | term(), NewStateData :: term()}.
+-callback terminate(Reason :: normal | shutdown | {shutdown, term()}
+ | term(), StateName :: atom(), StateData :: term()) ->
+ term().
+-callback code_change(OldVsn :: term() | {down, term()}, StateName :: atom(),
+ StateData :: term(), Extra :: term()) ->
+ {ok, NextStateName :: atom(), NewStateData :: term()}.
%%% ---------------------------------------------------
%%% Starts a generic state machine.
diff --git a/src/p1_mnesia.erl b/src/p1_mnesia.erl
index 6d9018640..97acfc352 100644
--- a/src/p1_mnesia.erl
+++ b/src/p1_mnesia.erl
@@ -4,16 +4,19 @@
%% Erlang Public License along with this software. If not, it can be
%% retrieved via the world wide web at http://www.erlang.org/.
%%
+%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%%
+%%
%% The Initial Developer of the Original Code is ProcessOne.
%% Portions created by ProcessOne are Copyright 2006-2013, ProcessOne
%% All Rights Reserved.''
-module(p1_mnesia).
+
-author('mickael.remond@process-one.net').
-export([count_records/2]).
@@ -23,21 +26,21 @@
%% The count has been written to use the fewest possible memory by
%% getting the record by small increment and by using continuation.
-define(BATCHSIZE, 100).
+
count_records(Tab, MatchExpression) ->
- %% the result contains list of [] for each match: We do not need
- %% actual values as we only count the data.
- case mnesia:select(Tab, [{MatchExpression, [], [[]]}], ?BATCHSIZE, read) of
- {Result,Cont} ->
- Count = length(Result),
- count_records_cont(Cont, Count);
- '$end_of_table' ->
- 0
+ case mnesia:select(Tab, [{MatchExpression, [], [[]]}],
+ ?BATCHSIZE, read)
+ of
+ {Result, Cont} ->
+ Count = length(Result),
+ count_records_cont(Cont, Count);
+ '$end_of_table' -> 0
end.
+
count_records_cont(Cont, Count) ->
case mnesia:select(Cont) of
- {Result,Cont} ->
- NewCount = Count + length(Result),
- count_records_cont(Cont, NewCount);
- '$end_of_table' ->
- Count
+ {Result, Cont} ->
+ NewCount = Count + length(Result),
+ count_records_cont(Cont, NewCount);
+ '$end_of_table' -> Count
end.
diff --git a/src/pam/Makefile.in b/src/pam/Makefile.in
index cae37437d..bde289402 100644
--- a/src/pam/Makefile.in
+++ b/src/pam/Makefile.in
@@ -14,7 +14,7 @@ EFLAGS += -pz ..
# make debug=true to compile Erlang module with debug informations.
ifdef debug
- EFLAGS+=+debug_info +export_all
+ EFLAGS+=+debug_info
endif
ERLSHLIBS = ../epam
diff --git a/src/pam/epam.erl b/src/pam/epam.erl
index 5b0da0679..e0ea1719a 100644
--- a/src/pam/epam.erl
+++ b/src/pam/epam.erl
@@ -25,40 +25,46 @@
%%%-------------------------------------------------------------------
-module(epam).
+
-author('xram@jabber.ru').
-behaviour(gen_server).
-include_lib("kernel/include/file.hrl").
+
-include("ejabberd.hrl").
%% API
-export([start_link/0, start/0, stop/0]).
+
-export([authenticate/3, acct_mgmt/2]).
%% gen_server callbacks
--export([init/1, handle_call/3, handle_cast/2, handle_info/2,
- terminate/2, code_change/3]).
+-export([init/1, handle_call/3, handle_cast/2,
+ handle_info/2, terminate/2, code_change/3]).
--define(WARNING, "File ~p is world-wide executable. "
- "This is a possible security hole in your system. "
- "This file must be setted root on execution "
- "and only ejabberd must be able to read/execute it. "
- "You have been warned :)").
+-define(WARNING,
+ "File ~p is world-wide executable. This "
+ "is a possible security hole in your "
+ "system. This file must be setted root "
+ "on execution and only ejabberd must "
+ "be able to read/execute it. You have "
+ "been warned :)").
-define(PROCNAME, ?MODULE).
+
-define(CMD_AUTH, 0).
+
-define(CMD_ACCT, 1).
+
-record(state, {port}).
%%====================================================================
%% API
%%====================================================================
start() ->
- ChildSpec = {
- ?PROCNAME, {?MODULE, start_link, []},
- transient, 1000, worker, [?MODULE]
- },
+ ChildSpec = {?PROCNAME, {?MODULE, start_link, []},
+ transient, 1000, worker, [?MODULE]},
supervisor:start_child(ejabberd_sup, ChildSpec).
stop() ->
@@ -67,40 +73,47 @@ stop() ->
supervisor:delete_child(ejabberd_sup, ?PROCNAME).
start_link() ->
- gen_server:start_link({local, ?PROCNAME}, ?MODULE, [], []).
+ gen_server:start_link({local, ?PROCNAME}, ?MODULE, [],
+ []).
-authenticate(Srv, User, Pass) when is_list(Srv), is_list(User), is_list(Pass) ->
- gen_server:call(?PROCNAME, {authenticate, Srv, User, Pass}).
+authenticate(Srv, User, Pass)
+ when is_binary(Srv), is_binary(User), is_binary(Pass) ->
+ gen_server:call(?PROCNAME,
+ {authenticate, Srv, User, Pass}).
-acct_mgmt(Srv, User) when is_list(Srv), is_list(User) ->
+acct_mgmt(Srv, User)
+ when is_binary(Srv), is_binary(User) ->
gen_server:call(?PROCNAME, {acct_mgmt, Srv, User}).
%%====================================================================
%% gen_server callbacks
%%====================================================================
init([]) ->
- FileName = filename:join(ejabberd:get_bin_path(), "epam"),
+ FileName = filename:join(ejabberd:get_bin_path(),
+ "epam"),
case file:read_file_info(FileName) of
- {ok, Info} ->
- Mode = Info#file_info.mode band 16#801,
- if Mode == 16#801 ->
- ?WARNING_MSG(?WARNING, [FileName]);
- true -> ok
- end,
- Port = open_port({spawn, FileName}, [{packet, 2}, binary, exit_status]),
- {ok, #state{port = Port}};
- {error, Reason} ->
- ?ERROR_MSG("Can't open file ~p: ~p", [FileName, Reason]),
- error
+ {ok, Info} ->
+ Mode = Info#file_info.mode band 2049,
+ if Mode == 2049 -> ?WARNING_MSG((?WARNING), [FileName]);
+ true -> ok
+ end,
+ Port = open_port({spawn, FileName},
+ [{packet, 2}, binary, exit_status]),
+ {ok, #state{port = Port}};
+ {error, Reason} ->
+ ?ERROR_MSG("Can't open file ~p: ~p",
+ [FileName, Reason]),
+ error
end.
terminate(_Reason, #state{port = Port}) ->
- catch port_close(Port),
- ok.
+ catch port_close(Port), ok.
-handle_call({authenticate, Srv, User, Pass}, From, State) ->
+handle_call({authenticate, Srv, User, Pass}, From,
+ State) ->
Port = State#state.port,
- Data = term_to_binary({?CMD_AUTH, From, {Srv, User, Pass}}),
+ Data = term_to_binary({?CMD_AUTH, From,
+ {Srv, User, Pass}}),
port_command(Port, Data),
{noreply, State};
handle_call({acct_mgmt, Srv, User}, From, State) ->
@@ -113,24 +126,23 @@ handle_call(stop, _From, State) ->
handle_call(_Request, _From, State) ->
{reply, bad_request, State}.
-handle_info({Port, {data, Data}}, #state{port=Port} = State) ->
+handle_info({Port, {data, Data}},
+ #state{port = Port} = State) ->
case binary_to_term(Data) of
- {Cmd, To, Reply} when Cmd==?CMD_AUTH; Cmd==?CMD_ACCT ->
- gen_server:reply(To, Reply);
- Err ->
- ?ERROR_MSG("Got invalid reply from ~p: ~p", [Port, Err])
+ {Cmd, To, Reply}
+ when Cmd == (?CMD_AUTH); Cmd == (?CMD_ACCT) ->
+ gen_server:reply(To, Reply);
+ Err ->
+ ?ERROR_MSG("Got invalid reply from ~p: ~p", [Port, Err])
end,
{noreply, State};
-handle_info({Port, {exit_status, _}}, #state{port=Port} = State) ->
- %% We can restart the port here, but, I think, it is not a good idea,
- %% since we can run into infinity loop. So let the supervisor restart us.
+handle_info({Port, {exit_status, _}},
+ #state{port = Port} = State) ->
{stop, port_died, State};
handle_info(Msg, State) ->
?WARNING_MSG("Got unexpected message: ~p", [Msg]),
{noreply, State}.
-handle_cast(_Msg, State) ->
- {noreply, State}.
+handle_cast(_Msg, State) -> {noreply, State}.
-code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
+code_change(_OldVsn, State, _Extra) -> {ok, State}.
diff --git a/src/randoms.erl b/src/randoms.erl
index 3dc714b6f..0fd1f304c 100644
--- a/src/randoms.erl
+++ b/src/randoms.erl
@@ -25,35 +25,28 @@
%%%----------------------------------------------------------------------
-module(randoms).
+
-author('alexey@process-one.net').
-export([get_string/0]).
-export([start/0, init/0]).
-
start() ->
register(random_generator, spawn(randoms, init, [])).
init() ->
- {A1, A2, A3} = now(),
- random:seed(A1,A2,A3),
- loop().
+ {A1, A2, A3} = now(), random:seed(A1, A2, A3), loop().
loop() ->
receive
- {From, get_random, N} ->
- From ! {random, random:uniform(N)},
- loop();
- _ ->
- loop()
+ {From, get_random, N} ->
+ From ! {random, random:uniform(N)}, loop();
+ _ -> loop()
end.
-
get_string() ->
- random_generator ! {self(), get_random, 65536*65536},
+ random_generator ! {self(), get_random, 65536 * 65536},
receive
- {random, R} ->
- integer_to_list(R)
+ {random, R} -> jlib:integer_to_binary(R)
end.
-
diff --git a/src/scram.erl b/src/scram.erl
index cfb86e5b0..94b3324fd 100644
--- a/src/scram.erl
+++ b/src/scram.erl
@@ -25,58 +25,63 @@
%%%----------------------------------------------------------------------
-module(scram).
+
-author('stephen.roettger@googlemail.com').
%% External exports
--export([salted_password/3,
- stored_key/1,
- server_key/1,
- server_signature/2,
- client_signature/2,
- client_key/1,
- client_key/2
- ]).
-
%% ejabberd doesn't implement SASLPREP, so we use the similar RESOURCEPREP instead
+-export([salted_password/3, stored_key/1, server_key/1,
+ server_signature/2, client_signature/2, client_key/1,
+ client_key/2]).
+
+-spec salted_password(binary(), binary(), non_neg_integer()) -> binary().
+
salted_password(Password, Salt, IterationCount) ->
- hi(jlib:resourceprep(Password), Salt, IterationCount).
+ hi(jlib:resourceprep(Password), Salt, IterationCount).
+
+-spec client_key(binary()) -> binary().
client_key(SaltedPassword) ->
- crypto:sha_mac(SaltedPassword, "Client Key").
+ crypto:sha_mac(SaltedPassword, <<"Client Key">>).
-stored_key(ClientKey) ->
- crypto:sha(ClientKey).
+-spec stored_key(binary()) -> binary().
+
+stored_key(ClientKey) -> crypto:sha(ClientKey).
+
+-spec server_key(binary()) -> binary().
server_key(SaltedPassword) ->
- crypto:sha_mac(SaltedPassword, "Server Key").
+ crypto:sha_mac(SaltedPassword, <<"Server Key">>).
+
+-spec client_signature(binary(), binary()) -> binary().
client_signature(StoredKey, AuthMessage) ->
- crypto:sha_mac(StoredKey, AuthMessage).
+ crypto:sha_mac(StoredKey, AuthMessage).
+
+-spec client_key(binary(), binary()) -> binary().
client_key(ClientProof, ClientSignature) ->
- list_to_binary(lists:zipwith(fun(X, Y) ->
- X bxor Y
- end,
- binary_to_list(ClientProof),
- binary_to_list(ClientSignature))).
+ list_to_binary(lists:zipwith(fun (X, Y) -> X bxor Y end,
+ binary_to_list(ClientProof),
+ binary_to_list(ClientSignature))).
+
+-spec server_signature(binary(), binary()) -> binary().
server_signature(ServerKey, AuthMessage) ->
- crypto:sha_mac(ServerKey, AuthMessage).
+ crypto:sha_mac(ServerKey, AuthMessage).
hi(Password, Salt, IterationCount) ->
- U1 = crypto:sha_mac(Password, string:concat(binary_to_list(Salt), [0,0,0,1])),
- list_to_binary(lists:zipwith(fun(X, Y) ->
- X bxor Y
- end,
- binary_to_list(U1),
- binary_to_list(hi_round(Password, U1, IterationCount-1)))).
+ U1 = crypto:sha_mac(Password, <<Salt/binary, 0, 0, 0, 1>>),
+ list_to_binary(lists:zipwith(fun (X, Y) -> X bxor Y end,
+ binary_to_list(U1),
+ binary_to_list(hi_round(Password, U1,
+ IterationCount - 1)))).
hi_round(Password, UPrev, 1) ->
- crypto:sha_mac(Password, UPrev);
+ crypto:sha_mac(Password, UPrev);
hi_round(Password, UPrev, IterationCount) ->
- U = crypto:sha_mac(Password, UPrev),
- list_to_binary(lists:zipwith(fun(X, Y) ->
- X bxor Y
- end,
- binary_to_list(U),
- binary_to_list(hi_round(Password, U, IterationCount-1)))).
+ U = crypto:sha_mac(Password, UPrev),
+ list_to_binary(lists:zipwith(fun (X, Y) -> X bxor Y end,
+ binary_to_list(U),
+ binary_to_list(hi_round(Password, U,
+ IterationCount - 1)))).
diff --git a/src/sha.erl b/src/sha.erl
index 2cb5bd749..28df507e4 100644
--- a/src/sha.erl
+++ b/src/sha.erl
@@ -25,13 +25,16 @@
%%%----------------------------------------------------------------------
-module(sha).
+
-author('alexey@process-one.net').
--export([start/0, sha/1, sha1/1, sha224/1, sha256/1, sha384/1,
- sha512/1]).
+-export([start/0, sha/1, sha1/1, sha224/1, sha256/1,
+ sha384/1, sha512/1, to_hexlist/1]).
-ifdef(HAVE_MD2).
+
-export([md2/1]).
+
-endif.
-include("ejabberd.hrl").
@@ -40,58 +43,71 @@
start() ->
crypto:start(),
- Res = case erl_ddll:load_driver(ejabberd:get_so_path(), ?DRIVER) of
- ok -> ok;
- {error, already_loaded} -> ok;
- Err -> Err
+ Res = case erl_ddll:load_driver(ejabberd:get_so_path(),
+ ?DRIVER)
+ of
+ ok -> ok;
+ {error, already_loaded} -> ok;
+ Err -> Err
end,
case Res of
- ok ->
- Port = open_port({spawn, atom_to_list(?DRIVER)}, [binary]),
- register(?DRIVER, Port);
- {error, Reason} ->
- ?CRITICAL_MSG("unable to load driver '~s': ~s",
- [driver_path(), erl_ddll:format_error(Reason)])
+ ok ->
+ Port = open_port({spawn, atom_to_list(?DRIVER)},
+ [binary]),
+ register(?DRIVER, Port);
+ {error, Reason} ->
+ ?CRITICAL_MSG("unable to load driver '~s': ~s",
+ [driver_path(), erl_ddll:format_error(Reason)])
end.
-digit_to_xchar(D) when (D >= 0) and (D < 10) ->
- D + 48;
-digit_to_xchar(D) ->
- D + 87.
+digit_to_xchar(D) when (D >= 0) and (D < 10) -> D + 48;
+digit_to_xchar(D) -> D + 87.
+
+-spec sha(binary()) -> binary().
sha(Text) ->
Bin = crypto:sha(Text),
- lists:reverse(ints_to_rxstr(binary_to_list(Bin), [])).
+ to_hexlist(Bin).
+
+-spec to_hexlist(binary()) -> binary().
-ints_to_rxstr([], Res) ->
- Res;
+to_hexlist(Bin) ->
+ iolist_to_binary(lists:reverse(ints_to_rxstr(binary_to_list(Bin), []))).
+
+ints_to_rxstr([], Res) -> Res;
ints_to_rxstr([N | Ns], Res) ->
- ints_to_rxstr(Ns, [digit_to_xchar(N rem 16),
- digit_to_xchar(N div 16) | Res]).
+ ints_to_rxstr(Ns,
+ [digit_to_xchar(N rem 16), digit_to_xchar(N div 16)
+ | Res]).
+
+-spec sha1(binary()) -> binary().
+-spec sha224(binary()) -> binary().
+-spec sha256(binary()) -> binary().
+-spec sha384(binary()) -> binary().
+-spec sha512(binary()) -> binary().
-sha1(Text) ->
- crypto:sha(Text).
+sha1(Text) -> crypto:sha(Text).
-sha224(Text) ->
- erlang:port_control(?DRIVER, 224, Text).
+sha224(Text) -> erlang:port_control(?DRIVER, 224, Text).
-sha256(Text) ->
- erlang:port_control(?DRIVER, 256, Text).
+sha256(Text) -> erlang:port_control(?DRIVER, 256, Text).
-sha384(Text) ->
- erlang:port_control(?DRIVER, 384, Text).
+sha384(Text) -> erlang:port_control(?DRIVER, 384, Text).
-sha512(Text) ->
- erlang:port_control(?DRIVER, 512, Text).
+sha512(Text) -> erlang:port_control(?DRIVER, 512, Text).
-ifdef(HAVE_MD2).
-md2(Text) ->
- erlang:port_control(?DRIVER, 2, Text).
+
+-spec md2(binary()) -> binary().
+
+md2(Text) -> erlang:port_control(?DRIVER, 2, Text).
+
-endif.
driver_path() ->
Suffix = case os:type() of
- {win32, _} -> ".dll";
- _ -> ".so"
+ {win32, _} -> ".dll";
+ _ -> ".so"
end,
- filename:join(ejabberd:get_so_path(), atom_to_list(?DRIVER) ++ Suffix).
+ filename:join(ejabberd:get_so_path(),
+ atom_to_list(?DRIVER) ++ Suffix).
diff --git a/src/shaper.erl b/src/shaper.erl
index 815416837..c86acd701 100644
--- a/src/shaper.erl
+++ b/src/shaper.erl
@@ -25,54 +25,63 @@
%%%----------------------------------------------------------------------
-module(shaper).
+
-author('alexey@process-one.net').
-export([new/1, new1/1, update/2]).
-include("ejabberd.hrl").
--record(maxrate, {maxrate, lastrate, lasttime}).
+-record(maxrate, {maxrate = 0 :: integer(),
+ lastrate = 0.0 :: float(),
+ lasttime = 0 :: integer()}).
+
+-type maxrate() :: none | #maxrate{}.
+
+-type shaper() :: maxrate() | {maxrate(), integer()}.
+-export_type([shaper/0]).
+
+-spec new(atom()) -> maxrate().
new(Name) ->
- Data = case ejabberd_config:get_global_option({shaper, Name, global}) of
- undefined ->
- none;
- D ->
- D
- end,
+ Data = ejabberd_config:get_global_option(
+ {shaper, Name, global},
+ fun({maxrate, R}) when is_integer(R), R>0 ->
+ {maxrate, R};
+ (none) ->
+ none
+ end, none),
new1(Data).
+-spec new1(none | {maxrate, integer()}) -> maxrate().
-new1(none) ->
- none;
+new1(none) -> none;
new1({maxrate, MaxRate}) ->
- #maxrate{maxrate = MaxRate,
- lastrate = 0,
+ #maxrate{maxrate = MaxRate, lastrate = 0.0,
lasttime = now_to_usec(now())}.
+-spec update(maxrate(), integer()) -> {maxrate(), integer()}.
-update(none, _Size) ->
- {none, 0};
+update(none, _Size) -> {none, 0};
update(#maxrate{} = State, Size) ->
MinInterv = 1000 * Size /
- (2 * State#maxrate.maxrate - State#maxrate.lastrate),
- Interv = (now_to_usec(now()) - State#maxrate.lasttime) / 1000,
+ (2 * State#maxrate.maxrate - State#maxrate.lastrate),
+ Interv = (now_to_usec(now()) - State#maxrate.lasttime) /
+ 1000,
?DEBUG("State: ~p, Size=~p~nM=~p, I=~p~n",
- [State, Size, MinInterv, Interv]),
- Pause = if
- MinInterv > Interv ->
- 1 + trunc(MinInterv - Interv);
- true ->
- 0
+ [State, Size, MinInterv, Interv]),
+ Pause = if MinInterv > Interv ->
+ 1 + trunc(MinInterv - Interv);
+ true -> 0
end,
NextNow = now_to_usec(now()) + Pause * 1000,
- {State#maxrate{
- lastrate = (State#maxrate.lastrate +
- 1000000 * Size / (NextNow - State#maxrate.lasttime))/2,
- lasttime = NextNow},
+ {State#maxrate{lastrate =
+ (State#maxrate.lastrate +
+ 1000000 * Size / (NextNow - State#maxrate.lasttime))
+ / 2,
+ lasttime = NextNow},
Pause}.
-
now_to_usec({MSec, Sec, USec}) ->
- (MSec*1000000 + Sec)*1000000 + USec.
+ (MSec * 1000000 + Sec) * 1000000 + USec.
diff --git a/src/str.erl b/src/str.erl
new file mode 100644
index 000000000..d4d448594
--- /dev/null
+++ b/src/str.erl
@@ -0,0 +1,287 @@
+%%%----------------------------------------------------------------------
+%%% File : str.erl
+%%% Author : Evgeniy Khramtsov <ekhramtsov@process-one.net>
+%%% Purpose : Provide binary string manipulations
+%%% Created : 23 Feb 2012 by Evgeniy Khramtsov <ekhramtsov@process-one.net>
+%%%
+%%%
+%%% ejabberd, Copyright (C) 2002-2013 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., 59 Temple Place, Suite 330, Boston, MA
+%%% 02111-1307 USA
+%%%
+%%%----------------------------------------------------------------------
+
+-module(str).
+
+-author('ekhramtsov@process-one.net').
+
+%% API
+-export([equal/2,
+ concat/2,
+ rchr/2,
+ str/2,
+ rstr/2,
+ span/2,
+ cspan/2,
+ copies/2,
+ words/1,
+ words/2,
+ sub_word/2,
+ sub_word/3,
+ strip/1,
+ strip/2,
+ len/1,
+ tokens/2,
+ left/2,
+ left/3,
+ right/2,
+ right/3,
+ centre/2,
+ centre/3,
+ sub_string/2,
+ sub_string/3,
+ to_upper/1,
+ join/2,
+ substr/2,
+ chr/2,
+ chars/3,
+ chars/2,
+ substr/3,
+ strip/3,
+ to_lower/1,
+ to_float/1,
+ prefix/2,
+ suffix/2,
+ to_integer/1]).
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+-spec len(binary()) -> non_neg_integer().
+
+len(B) ->
+ byte_size(B).
+
+-spec equal(binary(), binary()) -> boolean().
+
+equal(B1, B2) ->
+ B1 == B2.
+
+-spec concat(binary(), binary()) -> binary().
+
+concat(B1, B2) ->
+ <<B1/binary, B2/binary>>.
+
+-spec rchr(binary(), char()) -> non_neg_integer().
+
+rchr(B, C) ->
+ string:rchr(binary_to_list(B), C).
+
+-spec str(binary(), binary()) -> non_neg_integer().
+
+str(B1, B2) ->
+ string:str(binary_to_list(B1), binary_to_list(B2)).
+
+-spec rstr(binary(), binary()) -> non_neg_integer().
+
+rstr(B1, B2) ->
+ string:rstr(binary_to_list(B1), binary_to_list(B2)).
+
+-spec span(binary(), binary()) -> non_neg_integer().
+
+span(B1, B2) ->
+ string:span(binary_to_list(B1), binary_to_list(B2)).
+
+-spec cspan(binary(), binary()) -> non_neg_integer().
+
+cspan(B1, B2) ->
+ string:cspan(binary_to_list(B1), binary_to_list(B2)).
+
+-spec copies(binary(), non_neg_integer()) -> binary().
+
+copies(B, N) ->
+ iolist_to_binary(string:copies(binary_to_list(B), N)).
+
+-spec words(binary()) -> pos_integer().
+
+words(B) ->
+ string:words(binary_to_list(B)).
+
+-spec words(binary(), char()) -> pos_integer().
+
+words(B, C) ->
+ string:words(binary_to_list(B), C).
+
+-spec sub_word(binary(), integer()) -> binary().
+
+sub_word(B, N) ->
+ iolist_to_binary(string:sub_word(binary_to_list(B), N)).
+
+-spec sub_word(binary(), integer(), char()) -> binary().
+
+sub_word(B, N, C) ->
+ iolist_to_binary(string:sub_word(binary_to_list(B), N, C)).
+
+-spec strip(binary()) -> binary().
+
+strip(B) ->
+ iolist_to_binary(string:strip(binary_to_list(B))).
+
+-spec strip(binary(), both | left | right) -> binary().
+
+strip(B, D) ->
+ iolist_to_binary(string:strip(binary_to_list(B), D)).
+
+-spec left(binary(), non_neg_integer()) -> binary().
+
+left(B, N) ->
+ iolist_to_binary(string:left(binary_to_list(B), N)).
+
+-spec left(binary(), non_neg_integer(), char()) -> binary().
+
+left(B, N, C) ->
+ iolist_to_binary(string:left(binary_to_list(B), N, C)).
+
+-spec right(binary(), non_neg_integer()) -> binary().
+
+right(B, N) ->
+ iolist_to_binary(string:right(binary_to_list(B), N)).
+
+-spec right(binary(), non_neg_integer(), char()) -> binary().
+
+right(B, N, C) ->
+ iolist_to_binary(string:right(binary_to_list(B), N, C)).
+
+-spec centre(binary(), non_neg_integer()) -> binary().
+
+centre(B, N) ->
+ iolist_to_binary(string:centre(binary_to_list(B), N)).
+
+-spec centre(binary(), non_neg_integer(), char()) -> binary().
+
+centre(B, N, C) ->
+ iolist_to_binary(string:centre(binary_to_list(B), N, C)).
+
+-spec sub_string(binary(), pos_integer()) -> binary().
+
+sub_string(B, N) ->
+ iolist_to_binary(string:sub_string(binary_to_list(B), N)).
+
+-spec sub_string(binary(), pos_integer(), pos_integer()) -> binary().
+
+sub_string(B, S, E) ->
+ iolist_to_binary(string:sub_string(binary_to_list(B), S, E)).
+
+-spec to_upper(binary()) -> binary();
+ (char()) -> char().
+
+to_upper(B) when is_binary(B) ->
+ iolist_to_binary(string:to_upper(binary_to_list(B)));
+to_upper(C) ->
+ string:to_upper(C).
+
+-spec join([binary()], binary() | char()) -> binary().
+
+join(L, Sep) ->
+ iolist_to_binary(join_s(L, Sep)).
+
+-spec substr(binary(), pos_integer()) -> binary().
+
+substr(B, N) ->
+ iolist_to_binary(string:substr(binary_to_list(B), N)).
+
+-spec chr(binary(), char()) -> non_neg_integer().
+
+chr(B, C) ->
+ string:chr(binary_to_list(B), C).
+
+-spec chars(char(), non_neg_integer(), binary()) -> binary().
+
+chars(C, N, B) ->
+ iolist_to_binary(string:chars(C, N, binary_to_list(B))).
+
+-spec chars(char(), non_neg_integer()) -> binary().
+
+chars(C, N) ->
+ iolist_to_binary(string:chars(C, N)).
+
+-spec substr(binary(), pos_integer(), non_neg_integer()) -> binary().
+
+substr(B, S, E) ->
+ iolist_to_binary(string:substr(binary_to_list(B), S, E)).
+
+-spec strip(binary(), both | left | right, char()) -> binary().
+
+strip(B, D, C) ->
+ iolist_to_binary(string:strip(binary_to_list(B), D, C)).
+
+-spec to_lower(binary()) -> binary();
+ (char()) -> char().
+
+to_lower(B) when is_binary(B) ->
+ iolist_to_binary(string:to_lower(binary_to_list(B)));
+to_lower(C) ->
+ string:to_lower(C).
+
+-spec tokens(binary(), binary()) -> [binary()].
+
+tokens(B1, B2) ->
+ [iolist_to_binary(T) ||
+ T <- string:tokens(binary_to_list(B1), binary_to_list(B2))].
+
+-spec to_float(binary()) -> {float(), binary()} | {error, no_float}.
+
+to_float(B) ->
+ case string:to_float(binary_to_list(B)) of
+ {error, R} ->
+ {error, R};
+ {Float, Rest} ->
+ {Float, iolist_to_binary(Rest)}
+ end.
+
+-spec to_integer(binary()) -> {integer(), binary()} | {error, no_integer}.
+
+to_integer(B) ->
+ case string:to_integer(binary_to_list(B)) of
+ {error, R} ->
+ {error, R};
+ {Int, Rest} ->
+ {Int, iolist_to_binary(Rest)}
+ end.
+
+-spec prefix(binary(), binary()) -> boolean().
+
+prefix(Prefix, B) ->
+ Size = byte_size(Prefix),
+ case B of
+ <<Prefix:Size/binary, _/binary>> ->
+ true;
+ _ ->
+ false
+ end.
+
+-spec suffix(binary(), binary()) -> boolean().
+
+suffix(B1, B2) ->
+ lists:suffix(binary_to_list(B1), binary_to_list(B2)).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+join_s([], _Sep) ->
+ [];
+join_s([H|T], Sep) ->
+ [H, [[Sep, X] || X <- T]].
diff --git a/src/stringprep/Makefile.in b/src/stringprep/Makefile.in
index 2b78bc953..7c4997d26 100644
--- a/src/stringprep/Makefile.in
+++ b/src/stringprep/Makefile.in
@@ -24,7 +24,7 @@ EFLAGS += -pz ..
# make debug=true to compile Erlang module with debug informations.
ifdef debug
- EFLAGS+=+debug_info +export_all
+ EFLAGS+=+debug_info
endif
ERLSHLIBS = ../stringprep_drv.so
diff --git a/src/stringprep/stringprep.erl b/src/stringprep/stringprep.erl
index 6b9ec2be3..1b39693c2 100644
--- a/src/stringprep/stringprep.erl
+++ b/src/stringprep/stringprep.erl
@@ -25,89 +25,81 @@
%%%----------------------------------------------------------------------
-module(stringprep).
+
-author('alexey@process-one.net').
-behaviour(gen_server).
--export([start/0, start_link/0,
- tolower/1,
- nameprep/1,
- nodeprep/1,
- resourceprep/1]).
+-export([start/0, start_link/0, tolower/1, nameprep/1,
+ nodeprep/1, resourceprep/1]).
%% Internal exports, call-back functions.
--export([init/1,
- handle_call/3,
- handle_cast/2,
- handle_info/2,
- code_change/3,
- terminate/2]).
+-export([init/1, handle_call/3, handle_cast/2,
+ handle_info/2, code_change/3, terminate/2]).
-define(STRINGPREP_PORT, stringprep_port).
-define(NAMEPREP_COMMAND, 1).
+
-define(NODEPREP_COMMAND, 2).
+
-define(RESOURCEPREP_COMMAND, 3).
start() ->
gen_server:start({local, ?MODULE}, ?MODULE, [], []).
start_link() ->
- gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
+ gen_server:start_link({local, ?MODULE}, ?MODULE, [],
+ []).
init([]) ->
- case erl_ddll:load_driver(ejabberd:get_so_path(), stringprep_drv) of
- ok -> ok;
- {error, already_loaded} -> ok
+ case erl_ddll:load_driver(ejabberd:get_so_path(),
+ stringprep_drv)
+ of
+ ok -> ok;
+ {error, already_loaded} -> ok
end,
Port = open_port({spawn, "stringprep_drv"}, []),
register(?STRINGPREP_PORT, Port),
{ok, Port}.
-
%%% --------------------------------------------------------
%%% The call-back functions.
%%% --------------------------------------------------------
-handle_call(_, _, State) ->
- {noreply, State}.
+handle_call(_, _, State) -> {noreply, State}.
-handle_cast(_, State) ->
- {noreply, State}.
+handle_cast(_, State) -> {noreply, State}.
handle_info({'EXIT', Port, Reason}, Port) ->
{stop, {port_died, Reason}, Port};
handle_info({'EXIT', _Pid, _Reason}, Port) ->
{noreply, Port};
-handle_info(_, State) ->
- {noreply, State}.
+handle_info(_, State) -> {noreply, State}.
+
+code_change(_OldVsn, State, _Extra) -> {ok, State}.
-code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
+terminate(_Reason, Port) -> Port ! {self, close}, ok.
-terminate(_Reason, Port) ->
- Port ! {self, close},
- ok.
+-spec tolower(binary()) -> binary() | error.
+tolower(String) -> control(0, String).
+-spec nameprep(binary()) -> binary() | error.
-tolower(String) ->
- control(0, String).
+nameprep(String) -> control(?NAMEPREP_COMMAND, String).
-nameprep(String) ->
- control(?NAMEPREP_COMMAND, String).
+-spec nodeprep(binary()) -> binary() | error.
-nodeprep(String) ->
- control(?NODEPREP_COMMAND, String).
+nodeprep(String) -> control(?NODEPREP_COMMAND, String).
+
+-spec resourceprep(binary()) -> binary() | error.
resourceprep(String) ->
control(?RESOURCEPREP_COMMAND, String).
control(Command, String) ->
case port_control(?STRINGPREP_PORT, Command, String) of
- [0 | _] -> error;
- [1 | Res] -> Res
+ <<0, _/binary>> -> error;
+ <<1, Res/binary>> -> Res
end.
-
-
-
diff --git a/src/stringprep/stringprep_drv.c b/src/stringprep/stringprep_drv.c
index 569b45217..cada92372 100644
--- a/src/stringprep/stringprep_drv.c
+++ b/src/stringprep/stringprep_drv.c
@@ -50,7 +50,7 @@ static ErlDrvData stringprep_erl_start(ErlDrvPort port, char *buff)
stringprep_data* d = (stringprep_data*)driver_alloc(sizeof(stringprep_data));
d->port = port;
- //set_port_control_flags(port, PORT_CONTROL_FLAG_BINARY);
+ set_port_control_flags(port, PORT_CONTROL_FLAG_BINARY);
return (ErlDrvData)d;
}
@@ -147,36 +147,36 @@ static int compose(int ch1, int ch2)
if (ruc <= 0x7F) { \
if (pos >= size) { \
size = 2*size + 1; \
- rstring = driver_realloc(rstring, size); \
+ rstring = driver_realloc_binary(rstring, size); \
} \
- rstring[pos] = (char) ruc; \
+ rstring->orig_bytes[pos] = (char) ruc; \
pos++; \
} else if (ruc <= 0x7FF) { \
if (pos + 1 >= size) { \
size = 2*size + 2; \
- rstring = driver_realloc(rstring, size); \
+ rstring = driver_realloc_binary(rstring, size); \
} \
- rstring[pos] = (char) ((ruc >> 6) | 0xC0); \
- rstring[pos+1] = (char) ((ruc | 0x80) & 0xBF); \
+ rstring->orig_bytes[pos] = (char) ((ruc >> 6) | 0xC0); \
+ rstring->orig_bytes[pos+1] = (char) ((ruc | 0x80) & 0xBF); \
pos += 2; \
} else if (ruc <= 0xFFFF) { \
if (pos + 2 >= size) { \
size = 2*size + 3; \
- rstring = driver_realloc(rstring, size); \
+ rstring = driver_realloc_binary(rstring, size); \
} \
- rstring[pos] = (char) ((ruc >> 12) | 0xE0); \
- rstring[pos+1] = (char) (((ruc >> 6) | 0x80) & 0xBF); \
- rstring[pos+2] = (char) ((ruc | 0x80) & 0xBF); \
+ rstring->orig_bytes[pos] = (char) ((ruc >> 12) | 0xE0); \
+ rstring->orig_bytes[pos+1] = (char) (((ruc >> 6) | 0x80) & 0xBF); \
+ rstring->orig_bytes[pos+2] = (char) ((ruc | 0x80) & 0xBF); \
pos += 3; \
} else if (ruc <= 0x1FFFFF) { \
if (pos + 3 >= size) { \
size = 2*size + 4; \
- rstring = driver_realloc(rstring, size); \
+ rstring = driver_realloc_binary(rstring, size); \
} \
- rstring[pos] = (char) ((ruc >> 18) | 0xF0); \
- rstring[pos+1] = (char) (((ruc >> 12) | 0x80) & 0xBF); \
- rstring[pos+2] = (char) (((ruc >> 6) | 0x80) & 0xBF); \
- rstring[pos+3] = (char) ((ruc | 0x80) & 0xBF); \
+ rstring->orig_bytes[pos] = (char) ((ruc >> 18) | 0xF0); \
+ rstring->orig_bytes[pos+1] = (char) (((ruc >> 12) | 0x80) & 0xBF); \
+ rstring->orig_bytes[pos+2] = (char) (((ruc >> 6) | 0x80) & 0xBF); \
+ rstring->orig_bytes[pos+3] = (char) ((ruc | 0x80) & 0xBF); \
pos += 4; \
}
@@ -216,7 +216,7 @@ static ErlDrvSSizeT stringprep_erl_control(ErlDrvData drv_data,
int size;
int info;
int prohibit = 0, tolower = 0;
- char *rstring;
+ ErlDrvBinary *rstring;
int *mc;
int *str32;
int str32len, str32pos = 0;
@@ -228,8 +228,8 @@ static ErlDrvSSizeT stringprep_erl_control(ErlDrvData drv_data,
size = len + 1;
- rstring = driver_alloc(size);
- rstring[0] = 0;
+ rstring = driver_alloc_binary(size);
+ rstring->orig_bytes[0] = 0;
str32len = len + 1;
@@ -302,7 +302,7 @@ static ErlDrvSSizeT stringprep_erl_control(ErlDrvData drv_data,
}
if (bad) {
- *rbuf = rstring;
+ *rbuf = (char *)rstring;
driver_free(str32);
return 1;
}
@@ -331,8 +331,8 @@ static ErlDrvSSizeT stringprep_erl_control(ErlDrvData drv_data,
}
if (str32pos == 0) {
- rstring[0] = 1;
- *rbuf = rstring;
+ rstring->orig_bytes[0] = 1;
+ *rbuf = (char *)rstring;
driver_free(str32);
return 1;
}
@@ -373,7 +373,7 @@ static ErlDrvSSizeT stringprep_erl_control(ErlDrvData drv_data,
ruc = str32[i];
info = GetUniCharInfo(ruc);
if (info & prohibit) {
- *rbuf = rstring;
+ *rbuf = (char *)rstring;
driver_free(str32);
return 1;
}
@@ -384,13 +384,13 @@ static ErlDrvSSizeT stringprep_erl_control(ErlDrvData drv_data,
}
if (have_ral && (!first_ral || !last_ral || have_l)) {
- *rbuf = rstring;
+ *rbuf = (char *)rstring;
driver_free(str32);
return 1;
}
- rstring[0] = 1;
- *rbuf = rstring;
+ rstring->orig_bytes[0] = 1;
+ *rbuf = (char *)rstring;
driver_free(str32);
return pos;
diff --git a/src/stringprep/stringprep_sup.erl b/src/stringprep/stringprep_sup.erl
index 373aa10d9..828f85498 100644
--- a/src/stringprep/stringprep_sup.erl
+++ b/src/stringprep/stringprep_sup.erl
@@ -59,10 +59,6 @@ start_link() ->
%% specifications.
%%--------------------------------------------------------------------
init([]) ->
- StringPrep = {stringprep,
- {stringprep, start_link, []},
- permanent,
- brutal_kill,
- worker,
- [stringprep]},
- {ok,{{one_for_all,10,1}, [StringPrep]}}.
+ StringPrep = {stringprep, {stringprep, start_link, []},
+ permanent, brutal_kill, worker, [stringprep]},
+ {ok, {{one_for_all, 10, 1}, [StringPrep]}}.
diff --git a/src/stun/Makefile.in b/src/stun/Makefile.in
index b6e28d953..e77da8452 100644
--- a/src/stun/Makefile.in
+++ b/src/stun/Makefile.in
@@ -14,7 +14,7 @@ EFLAGS += -pz ..
# make debug=true to compile Erlang module with debug informations.
ifdef debug
- EFLAGS+=+debug_info +export_all
+ EFLAGS+=+debug_info
endif
OUTDIR = ..
diff --git a/src/stun/ejabberd_stun.erl b/src/stun/ejabberd_stun.erl
index dbc681cb7..1046fff11 100644
--- a/src/stun/ejabberd_stun.erl
+++ b/src/stun/ejabberd_stun.erl
@@ -30,35 +30,31 @@
-behaviour(gen_fsm).
%% API
--export([start_link/2,
- start/2,
- socket_type/0,
+-export([start_link/2, start/2, socket_type/0,
udp_recv/5]).
%% gen_fsm callbacks
--export([init/1,
- handle_event/3,
- handle_sync_event/4,
- handle_info/3,
- terminate/3,
- code_change/4]).
+-export([init/1, handle_event/3, handle_sync_event/4,
+ handle_info/3, terminate/3, code_change/4]).
%% gen_fsm states
--export([wait_for_tls/2,
- session_established/2]).
+-export([wait_for_tls/2, session_established/2]).
-include("ejabberd.hrl").
+
-include("stun.hrl").
--define(MAX_BUF_SIZE, 64*1024). %% 64kb
--define(TIMEOUT, 10000). %% 10 sec
+-define(MAX_BUF_SIZE, 64 * 1024).
+
+-define(TIMEOUT, 10000).
--record(state, {sock,
- sock_mod = gen_tcp,
- certfile,
- peer,
- tref,
- buf = <<>>}).
+-record(state,
+ {sock :: inet:socket() | tls:tls_socket(),
+ sock_mod = gen_tcp :: gen_udp | gen_tcp | tls,
+ certfile :: binary(),
+ peer = {{0,0,0,0}, 0} :: {inet:ip_address(), inet:port_number()},
+ tref = make_ref() :: reference(),
+ buf = <<>> :: binary()}).
%%====================================================================
%% API
@@ -69,23 +65,20 @@ start({gen_tcp, Sock}, Opts) ->
start_link(Sock, Opts) ->
gen_fsm:start_link(?MODULE, [Sock, Opts], []).
-socket_type() ->
- raw.
+socket_type() -> raw.
udp_recv(Sock, Addr, Port, Data, _Opts) ->
case stun_codec:decode(Data) of
- {ok, Msg, <<>>} ->
- ?DEBUG("got:~n~p", [Msg]),
- case process(Addr, Port, Msg) of
- RespMsg when is_record(RespMsg, stun) ->
- ?DEBUG("sent:~n~p", [RespMsg]),
- Data1 = stun_codec:encode(RespMsg),
- gen_udp:send(Sock, Addr, Port, Data1);
- _ ->
- ok
- end;
- _ ->
- ok
+ {ok, Msg, <<>>} ->
+ ?DEBUG("got:~n~p", [Msg]),
+ case process(Addr, Port, Msg) of
+ RespMsg when is_record(RespMsg, stun) ->
+ ?DEBUG("sent:~n~p", [RespMsg]),
+ Data1 = stun_codec:encode(RespMsg),
+ gen_udp:send(Sock, Addr, Port, Data1);
+ _ -> ok
+ end;
+ _ -> ok
end.
%%====================================================================
@@ -93,38 +86,38 @@ udp_recv(Sock, Addr, Port, Data, _Opts) ->
%%====================================================================
init([Sock, Opts]) ->
case inet:peername(Sock) of
- {ok, Addr} ->
- inet:setopts(Sock, [{active, once}]),
- TRef = erlang:start_timer(?TIMEOUT, self(), stop),
- State = #state{sock = Sock, peer = Addr, tref = TRef},
- case proplists:get_value(certfile, Opts) of
- undefined ->
- {ok, session_established, State};
- CertFile ->
- {ok, wait_for_tls, State#state{certfile = CertFile}}
- end;
- Err ->
- Err
+ {ok, Addr} ->
+ inet:setopts(Sock, [{active, once}]),
+ TRef = erlang:start_timer(?TIMEOUT, self(), stop),
+ State = #state{sock = Sock, peer = Addr, tref = TRef},
+ case proplists:get_value(certfile, Opts) of
+ undefined -> {ok, session_established, State};
+ CertFile ->
+ {ok, wait_for_tls, State#state{certfile = CertFile}}
+ end;
+ Err -> Err
end.
wait_for_tls(Event, State) ->
- ?INFO_MSG("unexpected event in wait_for_tls: ~p", [Event]),
+ ?INFO_MSG("unexpected event in wait_for_tls: ~p",
+ [Event]),
{next_state, wait_for_tls, State}.
-session_established(Msg, State) when is_record(Msg, stun) ->
+session_established(Msg, State)
+ when is_record(Msg, stun) ->
?DEBUG("got:~n~p", [Msg]),
{Addr, Port} = State#state.peer,
case process(Addr, Port, Msg) of
- Resp when is_record(Resp, stun) ->
- ?DEBUG("sent:~n~p", [Resp]),
- Data = stun_codec:encode(Resp),
- (State#state.sock_mod):send(State#state.sock, Data);
- _ ->
- ok
+ Resp when is_record(Resp, stun) ->
+ ?DEBUG("sent:~n~p", [Resp]),
+ Data = stun_codec:encode(Resp),
+ (State#state.sock_mod):send(State#state.sock, Data);
+ _ -> ok
end,
{next_state, session_established, State};
session_established(Event, State) ->
- ?INFO_MSG("unexpected event in session_established: ~p", [Event]),
+ ?INFO_MSG("unexpected event in session_established: ~p",
+ [Event]),
{next_state, session_established, State}.
handle_event(_Event, StateName, State) ->
@@ -133,42 +126,38 @@ handle_event(_Event, StateName, State) ->
handle_sync_event(_Event, _From, StateName, State) ->
{reply, {error, badarg}, StateName, State}.
-handle_info({tcp, Sock, TLSData}, wait_for_tls, State) ->
+handle_info({tcp, Sock, TLSData}, wait_for_tls,
+ State) ->
Buf = <<(State#state.buf)/binary, TLSData/binary>>,
- %% Check if the initial message is a TLS handshake
case Buf of
- _ when size(Buf) < 3 ->
- {next_state, wait_for_tls,
- update_state(State#state{buf = Buf})};
- <<_:16, 1, _/binary>> ->
- TLSOpts = [{certfile, State#state.certfile}],
- {ok, TLSSock} = tls:tcp_to_tls(Sock, TLSOpts),
- NewState = State#state{sock = TLSSock,
- buf = <<>>,
- sock_mod = tls},
- case tls:recv_data(TLSSock, Buf) of
- {ok, Data} ->
- process_data(session_established, NewState, Data);
- _Err ->
- {stop, normal, NewState}
- end;
- _ ->
- process_data(session_established, State, TLSData)
+ _ when byte_size(Buf) < 3 ->
+ {next_state, wait_for_tls,
+ update_state(State#state{buf = Buf})};
+ <<_:16, 1, _/binary>> ->
+ TLSOpts = [{certfile, State#state.certfile}],
+ {ok, TLSSock} = tls:tcp_to_tls(Sock, TLSOpts),
+ NewState = State#state{sock = TLSSock, buf = <<>>,
+ sock_mod = tls},
+ case tls:recv_data(TLSSock, Buf) of
+ {ok, Data} ->
+ process_data(session_established, NewState, Data);
+ _Err -> {stop, normal, NewState}
+ end;
+ _ -> process_data(session_established, State, TLSData)
end;
handle_info({tcp, _Sock, TLSData}, StateName,
#state{sock_mod = tls} = State) ->
case tls:recv_data(State#state.sock, TLSData) of
- {ok, Data} ->
- process_data(StateName, State, Data);
- _Err ->
- {stop, normal, State}
+ {ok, Data} -> process_data(StateName, State, Data);
+ _Err -> {stop, normal, State}
end;
handle_info({tcp, _Sock, Data}, StateName, State) ->
process_data(StateName, State, Data);
handle_info({tcp_closed, _Sock}, _StateName, State) ->
?DEBUG("connection reset by peer", []),
{stop, normal, State};
-handle_info({tcp_error, _Sock, Reason}, _StateName, State) ->
+handle_info({tcp_error, _Sock, Reason}, _StateName,
+ State) ->
?DEBUG("connection error: ~p", [Reason]),
{stop, normal, State};
handle_info({timeout, TRef, stop}, _StateName,
@@ -188,58 +177,55 @@ code_change(_OldVsn, StateName, State, _Extra) ->
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
-process(Addr, Port, #stun{class = request, unsupported = []} = Msg) ->
+process(Addr, Port,
+ #stun{class = request, unsupported = []} = Msg) ->
Resp = prepare_response(Msg),
- if Msg#stun.method == ?STUN_METHOD_BINDING ->
- case stun_codec:version(Msg) of
- old ->
- Resp#stun{class = response,
- 'MAPPED-ADDRESS' = {Addr, Port}};
- new ->
- Resp#stun{class = response,
- 'XOR-MAPPED-ADDRESS' = {Addr, Port}}
- end;
+ if Msg#stun.method == (?STUN_METHOD_BINDING) ->
+ case stun_codec:version(Msg) of
+ old ->
+ Resp#stun{class = response,
+ 'MAPPED-ADDRESS' = {Addr, Port}};
+ new ->
+ Resp#stun{class = response,
+ 'XOR-MAPPED-ADDRESS' = {Addr, Port}}
+ end;
true ->
- Resp#stun{class = error,
- 'ERROR-CODE' = {405, <<"Method Not Allowed">>}}
+ Resp#stun{class = error,
+ 'ERROR-CODE' = {405, <<"Method Not Allowed">>}}
end;
process(_Addr, _Port, #stun{class = request} = Msg) ->
Resp = prepare_response(Msg),
Resp#stun{class = error,
'UNKNOWN-ATTRIBUTES' = Msg#stun.unsupported,
'ERROR-CODE' = {420, stun_codec:reason(420)}};
-process(_Addr, _Port, _Msg) ->
- pass.
+process(_Addr, _Port, _Msg) -> pass.
prepare_response(Msg) ->
- Version = list_to_binary("ejabberd " ++ ?VERSION),
- #stun{method = Msg#stun.method,
- magic = Msg#stun.magic,
- trid = Msg#stun.trid,
- 'SOFTWARE' = Version}.
+ Version = <<"ejabberd ", (iolist_to_binary(?VERSION))/binary>>,
+ #stun{method = Msg#stun.method, magic = Msg#stun.magic,
+ trid = Msg#stun.trid, 'SOFTWARE' = Version}.
-process_data(NextStateName, #state{buf = Buf} = State, Data) ->
+process_data(NextStateName, #state{buf = Buf} = State,
+ Data) ->
NewBuf = <<Buf/binary, Data/binary>>,
case stun_codec:decode(NewBuf) of
- {ok, Msg, Tail} ->
- gen_fsm:send_event(self(), Msg),
- process_data(NextStateName, State#state{buf = <<>>}, Tail);
- empty ->
- NewState = State#state{buf = <<>>},
- {next_state, NextStateName, update_state(NewState)};
- more when size(NewBuf) < ?MAX_BUF_SIZE ->
- NewState = State#state{buf = NewBuf},
- {next_state, NextStateName, update_state(NewState)};
- _ ->
- {stop, normal, State}
+ {ok, Msg, Tail} ->
+ gen_fsm:send_event(self(), Msg),
+ process_data(NextStateName, State#state{buf = <<>>},
+ Tail);
+ empty ->
+ NewState = State#state{buf = <<>>},
+ {next_state, NextStateName, update_state(NewState)};
+ more when byte_size(NewBuf) < (?MAX_BUF_SIZE) ->
+ NewState = State#state{buf = NewBuf},
+ {next_state, NextStateName, update_state(NewState)};
+ _ -> {stop, normal, State}
end.
update_state(#state{sock = Sock} = State) ->
case State#state.sock_mod of
- gen_tcp ->
- inet:setopts(Sock, [{active, once}]);
- SockMod ->
- SockMod:setopts(Sock, [{active, once}])
+ gen_tcp -> inet:setopts(Sock, [{active, once}]);
+ SockMod -> SockMod:setopts(Sock, [{active, once}])
end,
cancel_timer(State#state.tref),
TRef = erlang:start_timer(?TIMEOUT, self(), stop),
@@ -247,13 +233,7 @@ update_state(#state{sock = Sock} = State) ->
cancel_timer(TRef) ->
case erlang:cancel_timer(TRef) of
- false ->
- receive
- {timeout, TRef, _} ->
- ok
- after 0 ->
- ok
- end;
- _ ->
- ok
+ false ->
+ receive {timeout, TRef, _} -> ok after 0 -> ok end;
+ _ -> ok
end.
diff --git a/src/stun/stun.hrl b/src/stun/stun.hrl
index bd25c67f9..251cf83cc 100644
--- a/src/stun/stun.hrl
+++ b/src/stun/stun.hrl
@@ -28,51 +28,61 @@
%% I know, this is terrible. Refer to 'STUN Message Structure' of
%% RFC5389 to understand this.
-define(STUN_METHOD(Type),
- ((Type band 16#3e00) bsr 2) bor
- ((Type band 16#e0) bsr 1) bor (Type band 16#f)).
+ Type band 15872 bsr 2 bor (Type band 224 bsr 1) bor
+ Type band 15).
+
-define(STUN_CLASS(Type),
- ((Type band 16#100) bsr 7) bor
- ((Type band 16#10) bsr 4)).
+ Type band 256 bsr 7 bor (Type band 16 bsr 4)).
+
-define(STUN_TYPE(C, M),
- (((M band 16#f80) bsl 2)
- bor ((M band 16#70) bsl 1)
- bor (M band 16#f) )
- bor (((C band 16#2) bsl 7) bor ((C band 16#1) bsl 4))).
+%% Comprehension-required range (0x0000-0x7FFF)
+%% Comprehension-optional range (0x8000-0xFFFF)
+ M band 3968 bsl 2 bor (M band 112 bsl 1) bor M band 15
+ bor (C band 2 bsl 7 bor (C band 1 bsl 4))).
--define(is_required(A), (A =< 16#7fff)).
+-define(is_required(A), A =< 32767).
--define(STUN_METHOD_BINDING, 16#001).
+-define(STUN_METHOD_BINDING, 1).
-%% Comprehension-required range (0x0000-0x7FFF)
--define(STUN_ATTR_MAPPED_ADDRESS, 16#0001).
--define(STUN_ATTR_USERNAME, 16#0006).
--define(STUN_ATTR_MESSAGE_INTEGRITY, 16#0008).
--define(STUN_ATTR_ERROR_CODE, 16#0009).
--define(STUN_ATTR_UNKNOWN_ATTRIBUTES, 16#000a).
--define(STUN_ATTR_REALM, 16#0014).
--define(STUN_ATTR_NONCE, 16#0015).
--define(STUN_ATTR_XOR_MAPPED_ADDRESS, 16#0020).
+-define(STUN_ATTR_MAPPED_ADDRESS, 1).
-%% Comprehension-optional range (0x8000-0xFFFF)
--define(STUN_ATTR_SOFTWARE, 16#8022).
--define(STUN_ATTR_ALTERNATE_SERVER, 16#8023).
--define(STUN_ATTR_FINGERPRINT, 16#8028).
-
--record(stun, {class,
- method,
- magic = ?STUN_MAGIC,
- trid,
- unsupported = [],
- 'SOFTWARE',
- 'ALTERNATE-SERVER',
- 'MAPPED-ADDRESS',
- 'XOR-MAPPED-ADDRESS',
- 'USERNAME',
- 'REALM',
- 'NONCE',
- 'MESSAGE-INTEGRITY',
- 'ERROR-CODE',
- 'UNKNOWN-ATTRIBUTES' = []}).
+-define(STUN_ATTR_USERNAME, 6).
+
+-define(STUN_ATTR_MESSAGE_INTEGRITY, 8).
+
+-define(STUN_ATTR_ERROR_CODE, 9).
+
+-define(STUN_ATTR_UNKNOWN_ATTRIBUTES, 10).
+
+-define(STUN_ATTR_REALM, 20).
+
+-define(STUN_ATTR_NONCE, 21).
+
+-define(STUN_ATTR_XOR_MAPPED_ADDRESS, 32).
+
+-define(STUN_ATTR_SOFTWARE, 32802).
+
+-define(STUN_ATTR_ALTERNATE_SERVER, 32803).
+
+-define(STUN_ATTR_FINGERPRINT, 32808).
+
+-record(stun,
+ {class = request :: request | response | error | indication,
+ method = ?STUN_METHOD_BINDING :: non_neg_integer(),
+ magic = ?STUN_MAGIC :: non_neg_integer(),
+ trid = 0 :: non_neg_integer() ,
+ unsupported = [] :: [non_neg_integer()],
+ 'SOFTWARE',
+ 'ALTERNATE-SERVER',
+ 'MAPPED-ADDRESS',
+ 'XOR-MAPPED-ADDRESS',
+ 'USERNAME',
+ 'REALM',
+ 'NONCE',
+ 'MESSAGE-INTEGRITY',
+ 'ERROR-CODE',
+ 'UNKNOWN-ATTRIBUTES' = []}).
%% Workarounds.
%%-define(NO_PADDING, true).
+
diff --git a/src/stun/stun_codec.erl b/src/stun/stun_codec.erl
index 2f4723dca..4d489e070 100644
--- a/src/stun/stun_codec.erl
+++ b/src/stun/stun_codec.erl
@@ -26,16 +26,11 @@
-module(stun_codec).
%% API
--export([decode/1,
- encode/1,
- version/1,
- reason/1,
+-export([decode/1, encode/1, version/1, reason/1,
pp/1]).
%% Tests
--export([test_udp/2,
- test_tcp/2,
- test_tls/2,
+-export([test_udp/2, test_tcp/2, test_tls/2,
test_public/0]).
-include("stun.hrl").
@@ -44,42 +39,34 @@
%% API
%%====================================================================
decode(<<0:2, Type:14, Len:16, Magic:32, TrID:96,
- Body:Len/binary, Tail/binary>>) ->
+ Body:Len/binary, Tail/binary>>) ->
case catch decode(Type, Magic, TrID, Body) of
- {'EXIT', _} ->
- {error, unparsed};
- Res ->
- {ok, Res, Tail}
+ {'EXIT', _} -> {error, unparsed};
+ Res -> {ok, Res, Tail}
end;
-decode(<<0:2, _/binary>>) ->
- more;
-decode(<<>>) ->
- empty;
-decode(_) ->
- {error, unparsed}.
-
-encode(#stun{class = Class,
- method = Method,
- magic = Magic,
- trid = TrID} = Msg) ->
+decode(<<0:2, _/binary>>) -> more;
+decode(<<>>) -> empty;
+decode(_) -> {error, unparsed}.
+
+encode(#stun{class = Class, method = Method,
+ magic = Magic, trid = TrID} =
+ Msg) ->
ClassCode = case Class of
- request -> 0;
- indication -> 1;
- response -> 2;
- error -> 3
+ request -> 0;
+ indication -> 1;
+ response -> 2;
+ error -> 3
end,
- Type = ?STUN_TYPE(ClassCode, Method),
+ Type = (?STUN_TYPE(ClassCode, Method)),
Attrs = enc_attrs(Msg),
- Len = size(Attrs),
- <<0:2, Type:14, Len:16, Magic:32, TrID:96, Attrs/binary>>.
+ Len = byte_size(Attrs),
+ <<0:2, Type:14, Len:16, Magic:32, TrID:96,
+ Attrs/binary>>.
-pp(Term) ->
- io_lib_pretty:print(Term, fun pp/2).
+pp(Term) -> io_lib_pretty:print(Term, fun pp/2).
-version(#stun{magic = ?STUN_MAGIC}) ->
- new;
-version(#stun{}) ->
- old.
+version(#stun{magic = ?STUN_MAGIC}) -> new;
+version(#stun{}) -> old.
reason(300) -> <<"Try Alternate">>;
reason(400) -> <<"Bad Request">>;
@@ -93,43 +80,41 @@ reason(_) -> <<"Undefined Error">>.
%% Internal functions
%%====================================================================
decode(Type, Magic, TrID, Body) ->
- Method = ?STUN_METHOD(Type),
+ Method = (?STUN_METHOD(Type)),
Class = case ?STUN_CLASS(Type) of
- 0 -> request;
- 1 -> indication;
- 2 -> response;
- 3 -> error
+ 0 -> request;
+ 1 -> indication;
+ 2 -> response;
+ 3 -> error
end,
- dec_attrs(Body, #stun{class = Class,
- method = Method,
- magic = Magic,
- trid = TrID}).
+ dec_attrs(Body,
+ #stun{class = Class, method = Method, magic = Magic,
+ trid = TrID}).
dec_attrs(<<Type:16, Len:16, Rest/binary>>, Msg) ->
PaddLen = padd_len(Len),
<<Val:Len/binary, _:PaddLen, Tail/binary>> = Rest,
NewMsg = dec_attr(Type, Val, Msg),
- if Type == ?STUN_ATTR_MESSAGE_INTEGRITY ->
- NewMsg;
- true ->
- dec_attrs(Tail, NewMsg)
+ if Type == (?STUN_ATTR_MESSAGE_INTEGRITY) -> NewMsg;
+ true -> dec_attrs(Tail, NewMsg)
end;
-dec_attrs(<<>>, Msg) ->
- Msg.
+dec_attrs(<<>>, Msg) -> Msg.
enc_attrs(Msg) ->
- list_to_binary(
- [enc_attr(?STUN_ATTR_SOFTWARE, Msg#stun.'SOFTWARE'),
- enc_addr(?STUN_ATTR_MAPPED_ADDRESS, Msg#stun.'MAPPED-ADDRESS'),
- enc_xor_addr(?STUN_ATTR_XOR_MAPPED_ADDRESS,
- Msg#stun.magic, Msg#stun.trid,
- Msg#stun.'XOR-MAPPED-ADDRESS'),
- enc_addr(?STUN_ATTR_ALTERNATE_SERVER, Msg#stun.'ALTERNATE-SERVER'),
- enc_attr(?STUN_ATTR_USERNAME, Msg#stun.'USERNAME'),
- enc_attr(?STUN_ATTR_REALM, Msg#stun.'REALM'),
- enc_attr(?STUN_ATTR_NONCE, Msg#stun.'NONCE'),
- enc_error_code(Msg#stun.'ERROR-CODE'),
- enc_unknown_attrs(Msg#stun.'UNKNOWN-ATTRIBUTES')]).
+ iolist_to_binary([enc_attr(?STUN_ATTR_SOFTWARE,
+ Msg#stun.'SOFTWARE'),
+ enc_addr(?STUN_ATTR_MAPPED_ADDRESS,
+ Msg#stun.'MAPPED-ADDRESS'),
+ enc_xor_addr(?STUN_ATTR_XOR_MAPPED_ADDRESS,
+ Msg#stun.magic, Msg#stun.trid,
+ Msg#stun.'XOR-MAPPED-ADDRESS'),
+ enc_addr(?STUN_ATTR_ALTERNATE_SERVER,
+ Msg#stun.'ALTERNATE-SERVER'),
+ enc_attr(?STUN_ATTR_USERNAME, Msg#stun.'USERNAME'),
+ enc_attr(?STUN_ATTR_REALM, Msg#stun.'REALM'),
+ enc_attr(?STUN_ATTR_NONCE, Msg#stun.'NONCE'),
+ enc_error_code(Msg#stun.'ERROR-CODE'),
+ enc_unknown_attrs(Msg#stun.'UNKNOWN-ATTRIBUTES')]).
dec_attr(?STUN_ATTR_MAPPED_ADDRESS, Val, Msg) ->
<<_, Family, Port:16, AddrBin/binary>> = Val,
@@ -139,7 +124,8 @@ dec_attr(?STUN_ATTR_XOR_MAPPED_ADDRESS, Val, Msg) ->
<<_, Family, XPort:16, XAddr/binary>> = Val,
Magic = Msg#stun.magic,
Port = XPort bxor (Magic bsr 16),
- Addr = dec_xor_addr(Family, Magic, Msg#stun.trid, XAddr),
+ Addr = dec_xor_addr(Family, Magic, Msg#stun.trid,
+ XAddr),
Msg#stun{'XOR-MAPPED-ADDRESS' = {Addr, Port}};
dec_attr(?STUN_ATTR_SOFTWARE, Val, Msg) ->
Msg#stun{'SOFTWARE' = Val};
@@ -157,55 +143,52 @@ dec_attr(?STUN_ATTR_ALTERNATE_SERVER, Val, Msg) ->
Msg#stun{'ALTERNATE-SERVER' = {IP, Port}};
dec_attr(?STUN_ATTR_ERROR_CODE, Val, Msg) ->
<<_:21, Class:3, Number:8, Reason/binary>> = Val,
- if Class >=3, Class =< 6, Number >=0, Number =< 99 ->
- Code = Class * 100 + Number,
- Msg#stun{'ERROR-CODE' = {Code, Reason}}
+ if Class >= 3, Class =< 6, Number >= 0, Number =< 99 ->
+ Code = Class * 100 + Number,
+ Msg#stun{'ERROR-CODE' = {Code, Reason}}
end;
dec_attr(?STUN_ATTR_UNKNOWN_ATTRIBUTES, Val, Msg) ->
Attrs = dec_unknown_attrs(Val, []),
Msg#stun{'UNKNOWN-ATTRIBUTES' = Attrs};
dec_attr(Attr, _Val, #stun{unsupported = Attrs} = Msg)
- when Attr =< 16#7fff ->
- Msg#stun{unsupported = [Attr|Attrs]};
-dec_attr(_Attr, _Val, Msg) ->
- Msg.
-
-dec_addr(1, <<A1, A2, A3, A4>>) ->
- {A1, A2, A3, A4};
-dec_addr(2, <<A1:16, A2:16, A3:16, A4:16,
- A5:16, A6:16, A7:16, A8:16>>) ->
+ when Attr =< 32767 ->
+ Msg#stun{unsupported = [Attr | Attrs]};
+dec_attr(_Attr, _Val, Msg) -> Msg.
+
+dec_addr(1, <<A1, A2, A3, A4>>) -> {A1, A2, A3, A4};
+dec_addr(2,
+ <<A1:16, A2:16, A3:16, A4:16, A5:16, A6:16, A7:16,
+ A8:16>>) ->
{A1, A2, A3, A4, A5, A6, A7, A8}.
dec_xor_addr(1, Magic, _TrID, <<XAddr:32>>) ->
- Addr = XAddr bxor Magic,
- dec_addr(1, <<Addr:32>>);
+ Addr = XAddr bxor Magic, dec_addr(1, <<Addr:32>>);
dec_xor_addr(2, Magic, TrID, <<XAddr:128>>) ->
- Addr = XAddr bxor ((Magic bsl 96) bor TrID),
+ Addr = XAddr bxor (Magic bsl 96 bor TrID),
dec_addr(2, <<Addr:128>>).
dec_unknown_attrs(<<Attr:16, Tail/binary>>, Acc) ->
- dec_unknown_attrs(Tail, [Attr|Acc]);
-dec_unknown_attrs(<<>>, Acc) ->
- lists:reverse(Acc).
+ dec_unknown_attrs(Tail, [Attr | Acc]);
+dec_unknown_attrs(<<>>, Acc) -> lists:reverse(Acc).
-enc_attr(_Attr, undefined) ->
- <<>>;
+enc_attr(_Attr, undefined) -> <<>>;
enc_attr(Attr, Val) ->
- Len = size(Val),
+ Len = byte_size(Val),
PaddLen = padd_len(Len),
<<Attr:16, Len:16, Val/binary, 0:PaddLen>>.
-enc_addr(_Type, undefined) ->
- <<>>;
+enc_addr(_Type, undefined) -> <<>>;
enc_addr(Type, {{A1, A2, A3, A4}, Port}) ->
enc_attr(Type, <<0, 1, Port:16, A1, A2, A3, A4>>);
-enc_addr(Type, {{A1, A2, A3, A4, A5, A6, A7, A8}, Port}) ->
- enc_attr(Type, <<0, 2, Port:16, A1:16, A2:16, A3:16,
- A4:16, A5:16, A6:16, A7:16, A8:16>>).
+enc_addr(Type,
+ {{A1, A2, A3, A4, A5, A6, A7, A8}, Port}) ->
+ enc_attr(Type,
+ <<0, 2, Port:16, A1:16, A2:16, A3:16, A4:16, A5:16,
+ A6:16, A7:16, A8:16>>).
-enc_xor_addr(_Type, _Magic, _TrID, undefined) ->
- <<>>;
-enc_xor_addr(Type, Magic, _TrID, {{A1, A2, A3, A4}, Port}) ->
+enc_xor_addr(_Type, _Magic, _TrID, undefined) -> <<>>;
+enc_xor_addr(Type, Magic, _TrID,
+ {{A1, A2, A3, A4}, Port}) ->
XPort = Port bxor (Magic bsr 16),
<<Addr:32>> = <<A1, A2, A3, A4>>,
XAddr = Addr bxor Magic,
@@ -213,51 +196,46 @@ enc_xor_addr(Type, Magic, _TrID, {{A1, A2, A3, A4}, Port}) ->
enc_xor_addr(Type, Magic, TrID,
{{A1, A2, A3, A4, A5, A6, A7, A8}, Port}) ->
XPort = Port bxor (Magic bsr 16),
- <<Addr:128>> = <<A1:16, A2:16, A3:16, A4:16,
- A5:16, A6:16, A7:16, A8:16>>,
- XAddr = Addr bxor ((Magic bsl 96) bor TrID),
+ <<Addr:128>> = <<A1:16, A2:16, A3:16, A4:16, A5:16,
+ A6:16, A7:16, A8:16>>,
+ XAddr = Addr bxor (Magic bsl 96 bor TrID),
enc_attr(Type, <<0, 2, XPort:16, XAddr:128>>).
-enc_error_code(undefined) ->
- <<>>;
+enc_error_code(undefined) -> <<>>;
enc_error_code({Code, Reason}) ->
Class = Code div 100,
Number = Code rem 100,
enc_attr(?STUN_ATTR_ERROR_CODE,
<<0:21, Class:3, Number:8, Reason/binary>>).
-enc_unknown_attrs([]) ->
- <<>>;
+enc_unknown_attrs([]) -> <<>>;
enc_unknown_attrs(Attrs) ->
enc_attr(?STUN_ATTR_UNKNOWN_ATTRIBUTES,
- list_to_binary([<<Attr:16>> || Attr <- Attrs])).
-
%%====================================================================
%% Auxiliary functions
%%====================================================================
-pp(Tag, N) ->
- try
- pp1(Tag, N)
- catch _:_ ->
- no
- end.
+ iolist_to_binary([<<Attr:16>> || Attr <- Attrs])).
+
+pp(Tag, N) -> try pp1(Tag, N) catch _:_ -> no end.
pp1(stun, N) ->
N = record_info(size, stun) - 1,
record_info(fields, stun);
-pp1(_, _) ->
- no.
+pp1(_, _) -> no.
%% Workaround for stupid clients.
-ifdef(NO_PADDING).
-padd_len(_Len) ->
- 0.
+
+padd_len(_Len) -> 0.
+
-else.
+
padd_len(Len) ->
case Len rem 4 of
- 0 -> 0;
- N -> 8*(4-N)
+ 0 -> 0;
+ N -> 8 * (4 - N)
end.
+
-endif.
%%====================================================================
@@ -265,65 +243,49 @@ padd_len(Len) ->
%%====================================================================
bind_msg() ->
Msg = #stun{method = ?STUN_METHOD_BINDING,
- class = request,
- trid = random:uniform(1 bsl 96),
+ class = request, trid = random:uniform(1 bsl 96),
'SOFTWARE' = <<"test">>},
encode(Msg).
-test_udp(Addr, Port) ->
- test(Addr, Port, gen_udp).
+test_udp(Addr, Port) -> test(Addr, Port, gen_udp).
-test_tcp(Addr, Port) ->
- test(Addr, Port, gen_tcp).
+test_tcp(Addr, Port) -> test(Addr, Port, gen_tcp).
-test_tls(Addr, Port) ->
- test(Addr, Port, ssl).
+test_tls(Addr, Port) -> test(Addr, Port, ssl).
test(Addr, Port, Mod) ->
Res = case Mod of
- gen_udp ->
- Mod:open(0, [binary, {active, false}]);
- _ ->
- Mod:connect(Addr, Port,
- [binary, {active, false}], 1000)
+ gen_udp -> Mod:open(0, [binary, {active, false}]);
+ _ ->
+ Mod:connect(Addr, Port, [binary, {active, false}], 1000)
end,
case Res of
- {ok, Sock} ->
- if Mod == gen_udp ->
- Mod:send(Sock, Addr, Port, bind_msg());
- true ->
- Mod:send(Sock, bind_msg())
- end,
- case Mod:recv(Sock, 0, 1000) of
- {ok, {_, _, Data}} ->
- try_dec(Data);
- {ok, Data} ->
- try_dec(Data);
- Err ->
- io:format("err: ~p~n", [Err])
- end,
- Mod:close(Sock);
- Err ->
- io:format("err: ~p~n", [Err])
+ {ok, Sock} ->
+ if Mod == gen_udp ->
+ Mod:send(Sock, Addr, Port, bind_msg());
+ true -> Mod:send(Sock, bind_msg())
+ end,
+ case Mod:recv(Sock, 0, 1000) of
+ {ok, {_, _, Data}} -> try_dec(Data);
+ {ok, Data} -> try_dec(Data);
+ Err -> io:format("err: ~p~n", [Err])
+ end,
+ Mod:close(Sock);
+ Err -> io:format("err: ~p~n", [Err])
end.
try_dec(Data) ->
case decode(Data) of
- {ok, Msg, _} ->
- io:format("got:~n~s~n", [pp(Msg)]);
- Err ->
- io:format("err: ~p~n", [Err])
+ {ok, Msg, _} -> io:format("got:~n~s~n", [pp(Msg)]);
+ Err -> io:format("err: ~p~n", [Err])
end.
public_servers() ->
[{"stun.ekiga.net", 3478, 3478, 5349},
- {"stun.fwdnet.net", 3478, 3478, 5349},
{"stun.ideasip.com", 3478, 3478, 5349},
- {"stun01.sipphone.com", 3478, 3478, 5349},
{"stun.softjoys.com", 3478, 3478, 5349},
{"stun.voipbuster.com", 3478, 3478, 5349},
{"stun.voxgratia.org", 3478, 3478, 5349},
- {"stun.xten.com", 3478, 3478, 5349},
{"stunserver.org", 3478, 3478, 5349},
{"stun.sipgate.net", 10000, 10000, 5349},
{"numb.viagenie.ca", 3478, 3478, 5349},
@@ -332,12 +294,12 @@ public_servers() ->
test_public() ->
ssl:start(),
- lists:foreach(
- fun({Addr, UDPPort, TCPPort, TLSPort}) ->
- io:format("trying ~s:~p on UDP... ", [Addr, UDPPort]),
- test_udp(Addr, UDPPort),
- io:format("trying ~s:~p on TCP... ", [Addr, TCPPort]),
- test_tcp(Addr, TCPPort),
- io:format("trying ~s:~p on TLS... ", [Addr, TLSPort]),
- test_tls(Addr, TLSPort)
- end, public_servers()).
+ lists:foreach(fun ({Addr, UDPPort, TCPPort, TLSPort}) ->
+ io:format("trying ~s:~p on UDP... ", [Addr, UDPPort]),
+ test_udp(Addr, UDPPort),
+ io:format("trying ~s:~p on TCP... ", [Addr, TCPPort]),
+ test_tcp(Addr, TCPPort),
+ io:format("trying ~s:~p on TLS... ", [Addr, TLSPort]),
+ test_tls(Addr, TLSPort)
+ end,
+ public_servers()).
diff --git a/src/tls/Makefile.in b/src/tls/Makefile.in
index ee40f93ec..203c88ac9 100644
--- a/src/tls/Makefile.in
+++ b/src/tls/Makefile.in
@@ -27,7 +27,7 @@ EFLAGS += -pz ..
# make debug=true to compile Erlang module with debug informations.
ifdef debug
- EFLAGS+=+debug_info +export_all
+ EFLAGS+=+debug_info
endif
ifeq (@md2@, true)
diff --git a/src/tls/tls.erl b/src/tls/tls.erl
index bf896b02c..74a62709b 100644
--- a/src/tls/tls.erl
+++ b/src/tls/tls.erl
@@ -25,347 +25,362 @@
%%%----------------------------------------------------------------------
-module(tls).
+
-author('alexey@process-one.net').
-behaviour(gen_server).
--export([start/0, start_link/0,
- tcp_to_tls/2, tls_to_tcp/1,
- send/2,
- recv/2, recv/3,
- recv_data/2,
- setopts/2,
- sockname/1, peername/1,
- controlling_process/2,
- close/1,
- get_peer_certificate/1,
- get_verify_result/1,
- get_cert_verify_string/2,
- test/0]).
+-export([start/0, start_link/0, tcp_to_tls/2,
+ tls_to_tcp/1, send/2, recv/2, recv/3, recv_data/2,
+ setopts/2, sockname/1, peername/1,
+ controlling_process/2, close/1, get_peer_certificate/1,
+ get_verify_result/1, get_cert_verify_string/2, test/0]).
%% Internal exports, call-back functions.
--export([init/1,
- handle_call/3,
- handle_cast/2,
- handle_info/2,
- code_change/3,
- terminate/2]).
+-export([init/1, handle_call/3, handle_cast/2,
+ handle_info/2, code_change/3, terminate/2]).
-include("ejabberd.hrl").
-define(SET_CERTIFICATE_FILE_ACCEPT, 1).
+
-define(SET_CERTIFICATE_FILE_CONNECT, 2).
--define(SET_ENCRYPTED_INPUT, 3).
+
+-define(SET_ENCRYPTED_INPUT, 3).
+
-define(SET_DECRYPTED_OUTPUT, 4).
+
-define(GET_ENCRYPTED_OUTPUT, 5).
--define(GET_DECRYPTED_INPUT, 6).
+
+-define(GET_DECRYPTED_INPUT, 6).
+
-define(GET_PEER_CERTIFICATE, 7).
--define(GET_VERIFY_RESULT, 8).
--define(VERIFY_NONE, 16#10000).
--ifdef(SSL40).
--define(CERT_DECODE, {public_key, pkix_decode_cert, plain}).
--define(CERT_SELFSIGNED, {public_key, pkix_is_self_signed}).
--else.
--define(CERT_DECODE, {ssl_pkix, decode_cert, [pkix]}).
--define(CERT_SELFSIGNED, {erlang, is_atom}). %% Dummy function for old OTPs
--endif.
+-define(GET_VERIFY_RESULT, 8).
+
+-define(VERIFY_NONE, 65536).
+
+-record(tlssock, {tcpsock :: inet:socket(),
+ tlsport :: port()}).
+-type tls_socket() :: #tlssock{}.
--record(tlssock, {tcpsock, tlsport}).
+-type cert() :: any(). %% TODO
+
+-export_type([tls_socket/0]).
start() ->
gen_server:start({local, ?MODULE}, ?MODULE, [], []).
start_link() ->
- gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
+ gen_server:start_link({local, ?MODULE}, ?MODULE, [],
+ []).
init([]) ->
- case erl_ddll:load_driver(ejabberd:get_so_path(), tls_drv) of
- ok -> ok;
- {error, already_loaded} -> ok
+ case erl_ddll:load_driver(ejabberd:get_so_path(),
+ tls_drv)
+ of
+ ok -> ok;
+ {error, already_loaded} -> ok
end,
Port = open_port({spawn, "tls_drv"}, [binary]),
- Res = port_control(Port, ?SET_CERTIFICATE_FILE_ACCEPT, "./ssl.pem" ++ [0]),
+ Res = port_control(Port, ?SET_CERTIFICATE_FILE_ACCEPT,
+ <<"./ssl.pem", 0>>),
case Res of
- <<0>> ->
- %ets:new(iconv_table, [set, public, named_table]),
- %ets:insert(iconv_table, {port, Port}),
- {ok, Port};
- <<1, Error/binary>> ->
- {error, binary_to_list(Error)}
+ <<0>> -> {ok, Port};
+ <<1, Error/binary>> -> {error, (Error)}
end.
-
%%% --------------------------------------------------------
%%% The call-back functions.
%%% --------------------------------------------------------
-handle_call(_, _, State) ->
- {noreply, State}.
+handle_call(_, _, State) -> {noreply, State}.
-handle_cast(_, State) ->
- {noreply, State}.
+handle_cast(_, State) -> {noreply, State}.
handle_info({'EXIT', Port, Reason}, Port) ->
{stop, {port_died, Reason}, Port};
-
handle_info({'EXIT', _Pid, _Reason}, Port) ->
{noreply, Port};
+handle_info(_, State) -> {noreply, State}.
-handle_info(_, State) ->
- {noreply, State}.
+code_change(_OldVsn, State, _Extra) -> {ok, State}.
-code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
-
-terminate(_Reason, Port) ->
- Port ! {self, close},
- ok.
+terminate(_Reason, Port) -> Port ! {self, close}, ok.
+-spec tcp_to_tls(inet:socket(),
+ [{atom(), any()}]) -> {'error','no_certfile' | binary()} |
+ {ok, tls_socket()}.
tcp_to_tls(TCPSocket, Options) ->
case lists:keysearch(certfile, 1, Options) of
- {value, {certfile, CertFile}} ->
- case erl_ddll:load_driver(ejabberd:get_so_path(), tls_drv) of
- ok -> ok;
- {error, already_loaded} -> ok
- end,
- Port = open_port({spawn, "tls_drv"}, [binary]),
- Flags =
- case lists:member(verify_none, Options) of
- true ->
- ?VERIFY_NONE;
- false ->
- 0
- end,
- Command = case lists:member(connect, Options) of
- true ->
- ?SET_CERTIFICATE_FILE_CONNECT;
- false ->
- ?SET_CERTIFICATE_FILE_ACCEPT
- end,
- case port_control(Port, Command bor Flags, CertFile ++ [0]) of
- <<0>> ->
- {ok, #tlssock{tcpsock = TCPSocket, tlsport = Port}};
- <<1, Error/binary>> ->
- {error, binary_to_list(Error)}
- end;
- false ->
- {error, no_certfile}
+ {value, {certfile, CertFile}} ->
+ case erl_ddll:load_driver(ejabberd:get_so_path(),
+ tls_drv)
+ of
+ ok -> ok;
+ {error, already_loaded} -> ok
+ end,
+ Port = open_port({spawn, "tls_drv"}, [binary]),
+ Flags = case lists:member(verify_none, Options) of
+ true -> ?VERIFY_NONE;
+ false -> 0
+ end,
+ Command = case lists:member(connect, Options) of
+ true -> ?SET_CERTIFICATE_FILE_CONNECT;
+ false -> ?SET_CERTIFICATE_FILE_ACCEPT
+ end,
+ CertFile1 = iolist_to_binary(CertFile),
+ case port_control(Port, Command bor Flags,
+ <<CertFile1/binary, 0>>)
+ of
+ <<0>> ->
+ {ok, #tlssock{tcpsock = TCPSocket, tlsport = Port}};
+ <<1, Error/binary>> -> {error, (Error)}
+ end;
+ false -> {error, no_certfile}
end.
-
-tls_to_tcp(#tlssock{tcpsock = TCPSocket, tlsport = Port}) ->
- port_close(Port),
- TCPSocket.
-
-recv(Socket, Length) ->
- recv(Socket, Length, infinity).
-recv(#tlssock{tcpsock = TCPSocket, tlsport = Port} = TLSSock,
+
+-spec tls_to_tcp(tls_socket()) -> inet:socket().
+
+tls_to_tcp(#tlssock{tcpsock = TCPSocket,
+ tlsport = Port}) ->
+ port_close(Port), TCPSocket.
+
+recv(Socket, Length) -> recv(Socket, Length, infinity).
+
+-spec recv(tls_socket(), non_neg_integer(),
+ timeout()) -> {error, inet:posix()} |
+ {error, binary()} |
+ {ok, binary()}.
+
+recv(#tlssock{tcpsock = TCPSocket, tlsport = Port} =
+ TLSSock,
Length, Timeout) ->
- case port_control(Port, ?GET_DECRYPTED_INPUT, <<Length:32>>) of
- <<0>> ->
- case gen_tcp:recv(TCPSocket, 0, Timeout) of
- {ok, Packet} ->
- recv_data(TLSSock, Packet, Length);
- {error, _Reason} = Error ->
- Error
- end;
- <<0, In/binary>> ->
- {ok, In};
- <<1, Error/binary>> ->
- {error, binary_to_list(Error)}
+ case port_control(Port, ?GET_DECRYPTED_INPUT,
+ <<Length:32>>)
+ of
+ <<0>> ->
+ case gen_tcp:recv(TCPSocket, 0, Timeout) of
+ {ok, Packet} -> recv_data(TLSSock, Packet, Length);
+ {error, _Reason} = Error -> Error
+ end;
+ <<0, In/binary>> -> {ok, In};
+ <<1, Error/binary>> -> {error, (Error)}
end.
recv_data(TLSSock, Packet) ->
recv_data(TLSSock, Packet, 0).
+-spec recv_data(tls_socket(), binary(),
+ non_neg_integer()) -> {error, inet:posix() | binary()} |
+ {ok, binary()}.
+
recv_data(TLSSock, Packet, Length) ->
case catch recv_data1(TLSSock, Packet, Length) of
- {'EXIT', Reason} ->
- {error, Reason};
- Res ->
- Res
+ {'EXIT', Reason} -> {error, Reason};
+ Res -> Res
end.
-recv_data1(#tlssock{tcpsock = TCPSocket, tlsport = Port}, Packet, Length) ->
+recv_data1(#tlssock{tcpsock = TCPSocket,
+ tlsport = Port},
+ Packet, Length) ->
case port_control(Port, ?SET_ENCRYPTED_INPUT, Packet) of
- <<0>> ->
- case port_control(Port, ?GET_DECRYPTED_INPUT, <<Length:32>>) of
- <<0, In/binary>> ->
- case port_control(Port, ?GET_ENCRYPTED_OUTPUT, []) of
- <<0, Out/binary>> ->
- case gen_tcp:send(TCPSocket, Out) of
- ok ->
- %?PRINT("IN: ~p~n", [{TCPSocket, binary_to_list(In)}]),
- {ok, In};
- Error ->
- Error
- end;
- <<1, Error/binary>> ->
- {error, binary_to_list(Error)}
- end;
- <<1, Error/binary>> ->
- {error, binary_to_list(Error)}
- end;
- <<1, Error/binary>> ->
- {error, binary_to_list(Error)}
+ <<0>> ->
+ case port_control(Port, ?GET_DECRYPTED_INPUT,
+ <<Length:32>>)
+ of
+ <<0, In/binary>> ->
+ case port_control(Port, ?GET_ENCRYPTED_OUTPUT, []) of
+ <<0, Out/binary>> ->
+ case gen_tcp:send(TCPSocket, Out) of
+ ok -> {ok, In};
+ Error -> Error
+ end;
+ <<1, Error/binary>> -> {error, (Error)}
+ end;
+ <<1, Error/binary>> -> {error, (Error)}
+ end;
+ <<1, Error/binary>> -> {error, (Error)}
end.
-send(#tlssock{tcpsock = TCPSocket, tlsport = Port} = TLSSock, Packet) ->
- case port_control(Port, ?SET_DECRYPTED_OUTPUT, Packet) of
- <<0>> ->
- %?PRINT("OUT: ~p~n", [{TCPSocket, lists:flatten(Packet)}]),
- case port_control(Port, ?GET_ENCRYPTED_OUTPUT, []) of
- <<0, Out/binary>> ->
- gen_tcp:send(TCPSocket, Out);
- <<1, Error/binary>> ->
- {error, binary_to_list(Error)}
- end;
- <<1, Error/binary>> ->
- {error, binary_to_list(Error)};
- <<2>> -> % Dirty hack
- receive
- {timeout, _Timer, _} ->
- {error, timeout}
- after 100 ->
- send(TLSSock, Packet)
- end
+-spec send(tls_socket(), binary()) -> ok | {error, inet:posix() |
+ binary() | timeout}.
+
+send(#tlssock{tcpsock = TCPSocket, tlsport = Port} =
+ TLSSock,
+ Packet) ->
+ case port_control(Port, ?SET_DECRYPTED_OUTPUT, Packet)
+ of
+ <<0>> ->
+ case port_control(Port, ?GET_ENCRYPTED_OUTPUT, []) of
+ <<0, Out/binary>> -> gen_tcp:send(TCPSocket, Out);
+ <<1, Error/binary>> -> {error, (Error)}
+ end;
+ <<1, Error/binary>> -> {error, (Error)};
+ <<2>> -> % Dirty hack
+ receive
+ {timeout, _Timer, _} -> {error, timeout}
+ after 100 -> send(TLSSock, Packet)
+ end
end.
+-spec setopts(tls_socket(), list()) -> ok | {error, inet:posix()}.
setopts(#tlssock{tcpsock = TCPSocket}, Opts) ->
inet:setopts(TCPSocket, Opts).
+-spec sockname(tls_socket()) -> {ok, {inet:ip_address(), inet:port_number()}} |
+ {error, inet:posix()}.
+
sockname(#tlssock{tcpsock = TCPSocket}) ->
inet:sockname(TCPSocket).
peername(#tlssock{tcpsock = TCPSocket}) ->
inet:peername(TCPSocket).
-controlling_process(#tlssock{tcpsock = TCPSocket}, Pid) ->
+controlling_process(#tlssock{tcpsock = TCPSocket},
+ Pid) ->
gen_tcp:controlling_process(TCPSocket, Pid).
close(#tlssock{tcpsock = TCPSocket, tlsport = Port}) ->
- gen_tcp:close(TCPSocket),
- port_close(Port).
+ gen_tcp:close(TCPSocket), port_close(Port).
+
+-spec get_peer_certificate(tls_socket()) -> error | {ok, cert()}.
get_peer_certificate(#tlssock{tlsport = Port}) ->
case port_control(Port, ?GET_PEER_CERTIFICATE, []) of
- <<0, BCert/binary>> ->
- {CertMod, CertFun, CertSecondArg} = ?CERT_DECODE,
- case catch apply(CertMod, CertFun, [BCert, CertSecondArg]) of
- {ok, Cert} -> %% returned by R13 and older
- {ok, Cert};
- {'Certificate', _, _, _} = Cert ->
- {ok, Cert};
- _ ->
- error
- end;
- <<1>> ->
- error
+ <<0, BCert/binary>> ->
+ case catch public_key:pkix_decode_cert(BCert, plain)
+ of
+ {ok, Cert} -> {ok, Cert};
+ {'Certificate', _, _, _} = Cert -> {ok, Cert};
+ _ -> error
+ end;
+ <<1>> -> error
end.
+-spec get_verify_result(tls_socket()) -> byte().
+
get_verify_result(#tlssock{tlsport = Port}) ->
<<Res>> = port_control(Port, ?GET_VERIFY_RESULT, []),
Res.
-
test() ->
- case erl_ddll:load_driver(ejabberd:get_so_path(), tls_drv) of
- ok -> ok;
- {error, already_loaded} -> ok
+ case erl_ddll:load_driver(ejabberd:get_so_path(),
+ tls_drv)
+ of
+ ok -> ok;
+ {error, already_loaded} -> ok
end,
Port = open_port({spawn, "tls_drv"}, [binary]),
?PRINT("open_port: ~p~n", [Port]),
PCRes = port_control(Port, ?SET_CERTIFICATE_FILE_ACCEPT,
- "./ssl.pem" ++ [0]),
+ <<"./ssl.pem", 0>>),
?PRINT("port_control: ~p~n", [PCRes]),
- {ok, ListenSocket} = gen_tcp:listen(1234, [binary,
- {packet, 0},
- {active, true},
- {reuseaddr, true},
- {nodelay, true}]),
+ {ok, ListenSocket} = gen_tcp:listen(1234,
+ [binary, {packet, 0}, {active, true},
+ {reuseaddr, true}, {nodelay, true}]),
?PRINT("listen: ~p~n", [ListenSocket]),
{ok, Socket} = gen_tcp:accept(ListenSocket),
?PRINT("accept: ~p~n", [Socket]),
loop(Port, Socket).
-
loop(Port, Socket) ->
receive
- {tcp, Socket, Data} ->
- %?PRINT("read: ~p~n", [Data]),
- Res = port_control(Port, ?SET_ENCRYPTED_INPUT, Data),
- ?PRINT("SET_ENCRYPTED_INPUT: ~p~n", [Res]),
-
- DIRes = port_control(Port, ?GET_DECRYPTED_INPUT, Data),
- ?PRINT("GET_DECRYPTED_INPUT: ~p~n", [DIRes]),
- case DIRes of
- <<0, In/binary>> ->
- ?PRINT("input: ~s~n", [binary_to_list(In)]);
- <<1, DIError/binary>> ->
- ?PRINT("GET_DECRYPTED_INPUT error: ~p~n", [binary_to_list(DIError)])
- end,
-
- EORes = port_control(Port, ?GET_ENCRYPTED_OUTPUT, Data),
- ?PRINT("GET_ENCRYPTED_OUTPUT: ~p~n", [EORes]),
- case EORes of
- <<0, Out/binary>> ->
- gen_tcp:send(Socket, Out);
- <<1, EOError/binary>> ->
- ?PRINT("GET_ENCRYPTED_OUTPUT error: ~p~n", [binary_to_list(EOError)])
- end,
-
-
- loop(Port, Socket);
- Msg ->
- ?PRINT("receive: ~p~n", [Msg]),
- loop(Port, Socket)
+ {tcp, Socket, Data} ->
+ Res = port_control(Port, ?SET_ENCRYPTED_INPUT, Data),
+ ?PRINT("SET_ENCRYPTED_INPUT: ~p~n", [Res]),
+ DIRes = port_control(Port, ?GET_DECRYPTED_INPUT, Data),
+ ?PRINT("GET_DECRYPTED_INPUT: ~p~n", [DIRes]),
+ case DIRes of
+ <<0, In/binary>> -> ?PRINT("input: ~s~n", [(In)]);
+ <<1, DIError/binary>> ->
+ ?PRINT("GET_DECRYPTED_INPUT error: ~p~n", [(DIError)])
+ end,
+ EORes = port_control(Port, ?GET_ENCRYPTED_OUTPUT, Data),
+ ?PRINT("GET_ENCRYPTED_OUTPUT: ~p~n", [EORes]),
+ case EORes of
+ <<0, Out/binary>> -> gen_tcp:send(Socket, Out);
+ <<1, EOError/binary>> ->
+ ?PRINT("GET_ENCRYPTED_OUTPUT error: ~p~n", [(EOError)])
+ end,
+ loop(Port, Socket);
+ Msg ->
+ ?PRINT("receive: ~p~n", [Msg]), loop(Port, Socket)
end.
+-spec get_cert_verify_string(number(), cert()) -> binary().
get_cert_verify_string(CertVerifyRes, Cert) ->
- BCert = public_key:pkix_encode('Certificate', Cert, plain),
- {CertMod, CertFun} = ?CERT_SELFSIGNED,
- IsSelfsigned = apply(CertMod, CertFun, [BCert]),
+ BCert = public_key:pkix_encode('Certificate', Cert,
+ plain),
+ IsSelfsigned = public_key:pkix_is_self_signed(BCert),
case {CertVerifyRes, IsSelfsigned} of
- {21, true} -> "self-signed certificate";
- _ -> cert_verify_code(CertVerifyRes)
+ {21, true} -> <<"self-signed certificate">>;
+ _ -> cert_verify_code(CertVerifyRes)
end.
%% http://www.openssl.org/docs/apps/verify.html
-cert_verify_code(0) -> "ok";
-cert_verify_code(2) -> "unable to get issuer certificate";
-cert_verify_code(3) -> "unable to get certificate CRL";
-cert_verify_code(4) -> "unable to decrypt certificate's signature";
-cert_verify_code(5) -> "unable to decrypt CRL's signature";
-cert_verify_code(6) -> "unable to decode issuer public key";
-cert_verify_code(7) -> "certificate signature failure";
-cert_verify_code(8) -> "CRL signature failure";
-cert_verify_code(9) -> "certificate is not yet valid";
-cert_verify_code(10) -> "certificate has expired";
-cert_verify_code(11) -> "CRL is not yet valid";
-cert_verify_code(12) -> "CRL has expired";
-cert_verify_code(13) -> "format error in certificate's notBefore field";
-cert_verify_code(14) -> "format error in certificate's notAfter field";
-cert_verify_code(15) -> "format error in CRL's lastUpdate field";
-cert_verify_code(16) -> "format error in CRL's nextUpdate field";
-cert_verify_code(17) -> "out of memory";
-cert_verify_code(18) -> "self signed certificate";
-cert_verify_code(19) -> "self signed certificate in certificate chain";
-cert_verify_code(20) -> "unable to get local issuer certificate";
-cert_verify_code(21) -> "unable to verify the first certificate";
-cert_verify_code(22) -> "certificate chain too long";
-cert_verify_code(23) -> "certificate revoked";
-cert_verify_code(24) -> "invalid CA certificate";
-cert_verify_code(25) -> "path length constraint exceeded";
-cert_verify_code(26) -> "unsupported certificate purpose";
-cert_verify_code(27) -> "certificate not trusted";
-cert_verify_code(28) -> "certificate rejected";
-cert_verify_code(29) -> "subject issuer mismatch";
-cert_verify_code(30) -> "authority and subject key identifier mismatch";
-cert_verify_code(31) -> "authority and issuer serial number mismatch";
-cert_verify_code(32) -> "key usage does not include certificate signing";
-cert_verify_code(50) -> "application verification failure";
-cert_verify_code(X) -> "Unknown OpenSSL error code: " ++ integer_to_list(X).
+cert_verify_code(0) -> <<"ok">>;
+cert_verify_code(2) ->
+ <<"unable to get issuer certificate">>;
+cert_verify_code(3) ->
+ <<"unable to get certificate CRL">>;
+cert_verify_code(4) ->
+ <<"unable to decrypt certificate's signature">>;
+cert_verify_code(5) ->
+ <<"unable to decrypt CRL's signature">>;
+cert_verify_code(6) ->
+ <<"unable to decode issuer public key">>;
+cert_verify_code(7) ->
+ <<"certificate signature failure">>;
+cert_verify_code(8) -> <<"CRL signature failure">>;
+cert_verify_code(9) ->
+ <<"certificate is not yet valid">>;
+cert_verify_code(10) -> <<"certificate has expired">>;
+cert_verify_code(11) -> <<"CRL is not yet valid">>;
+cert_verify_code(12) -> <<"CRL has expired">>;
+cert_verify_code(13) ->
+ <<"format error in certificate's notBefore "
+ "field">>;
+cert_verify_code(14) ->
+ <<"format error in certificate's notAfter "
+ "field">>;
+cert_verify_code(15) ->
+ <<"format error in CRL's lastUpdate field">>;
+cert_verify_code(16) ->
+ <<"format error in CRL's nextUpdate field">>;
+cert_verify_code(17) -> <<"out of memory">>;
+cert_verify_code(18) -> <<"self signed certificate">>;
+cert_verify_code(19) ->
+ <<"self signed certificate in certificate "
+ "chain">>;
+cert_verify_code(20) ->
+ <<"unable to get local issuer certificate">>;
+cert_verify_code(21) ->
+ <<"unable to verify the first certificate">>;
+cert_verify_code(22) ->
+ <<"certificate chain too long">>;
+cert_verify_code(23) -> <<"certificate revoked">>;
+cert_verify_code(24) -> <<"invalid CA certificate">>;
+cert_verify_code(25) ->
+ <<"path length constraint exceeded">>;
+cert_verify_code(26) ->
+ <<"unsupported certificate purpose">>;
+cert_verify_code(27) -> <<"certificate not trusted">>;
+cert_verify_code(28) -> <<"certificate rejected">>;
+cert_verify_code(29) -> <<"subject issuer mismatch">>;
+cert_verify_code(30) ->
+ <<"authority and subject key identifier "
+ "mismatch">>;
+cert_verify_code(31) ->
+ <<"authority and issuer serial number mismatch">>;
+cert_verify_code(32) ->
+ <<"key usage does not include certificate "
+ "signing">>;
+cert_verify_code(50) ->
+ <<"application verification failure">>;
+cert_verify_code(X) ->
+ <<"Unknown OpenSSL error code: ", (jlib:integer_to_binary(X))/binary>>.
diff --git a/src/translate.erl b/src/translate.erl
index 42eabd988..3257608f7 100644
--- a/src/translate.erl
+++ b/src/translate.erl
@@ -25,151 +25,136 @@
%%%----------------------------------------------------------------------
-module(translate).
+
-author('alexey@process-one.net').
--export([start/0,
- load_dir/1,
- load_file/2,
+-export([start/0, load_dir/1, load_file/2,
translate/2]).
-include("ejabberd.hrl").
start() ->
ets:new(translations, [named_table, public]),
- Dir =
- case os:getenv("EJABBERD_MSGS_PATH") of
+ Dir = case os:getenv("EJABBERD_MSGS_PATH") of
false ->
case code:priv_dir(ejabberd) of
- {error, _} ->
- ?MSGS_DIR;
- Path ->
- filename:join([Path, "msgs"])
+ {error, _} -> ?MSGS_DIR;
+ Path -> filename:join([Path, "msgs"])
end;
- Path ->
- Path
- end,
- load_dir(Dir),
+ Path -> Path
+ end,
+ load_dir(iolist_to_binary(Dir)),
ok.
+-spec load_dir(binary()) -> ok.
+
load_dir(Dir) ->
case file:list_dir(Dir) of
- {ok, Files} ->
- MsgFiles = lists:filter(
- fun(FN) ->
- case string:len(FN) > 4 of
- true ->
- string:substr(
- FN,
- string:len(FN) - 3) == ".msg";
- _ ->
- false
- end
- end, Files),
- lists:foreach(
- fun(FN) ->
- LP = ascii_tolower(
- string:substr(FN, 1, string:len(FN) - 4)),
- L = case string:tokens(LP, ".") of
- [Language] -> Language;
- [Language, _Project] -> Language
- end,
- load_file(L, Dir ++ "/" ++ FN)
- end, MsgFiles),
- ok;
- {error, Reason} ->
- ?ERROR_MSG("~p", [Reason])
+ {ok, Files} ->
+ MsgFiles = lists:filter(fun (FN) ->
+ case length(FN) > 4 of
+ true ->
+ string:substr(FN, length(FN) - 3)
+ == ".msg";
+ _ -> false
+ end
+ end,
+ Files),
+ lists:foreach(fun (FNS) ->
+ FN = list_to_binary(FNS),
+ LP = ascii_tolower(str:substr(FN, 1,
+ byte_size(FN) - 4)),
+ L = case str:tokens(LP, <<".">>) of
+ [Language] -> Language;
+ [Language, _Project] -> Language
+ end,
+ load_file(L, <<Dir/binary, "/", FN/binary>>)
+ end,
+ MsgFiles),
+ ok;
+ {error, Reason} -> ?ERROR_MSG("~p", [Reason])
end.
load_file(Lang, File) ->
case file:consult(File) of
- {ok, Terms} ->
- lists:foreach(fun({Orig, Trans}) ->
- Trans1 = case Trans of
- "" ->
- Orig;
- _ ->
- Trans
- end,
- ets:insert(translations,
- {{Lang, Orig}, Trans1})
- end, Terms);
- %% Code copied from ejabberd_config.erl
- {error, {_LineNumber, erl_parse, _ParseMessage} = Reason} ->
- ExitText = lists:flatten(File ++ " approximately in the line "
- ++ file:format_error(Reason)),
- ?ERROR_MSG("Problem loading translation file ~n~s", [ExitText]),
- exit(ExitText);
- {error, Reason} ->
- ExitText = lists:flatten(File ++ ": " ++ file:format_error(Reason)),
- ?ERROR_MSG("Problem loading translation file ~n~s", [ExitText]),
- exit(ExitText)
+ {ok, Terms} ->
+ lists:foreach(fun ({Orig, Trans}) ->
+ Trans1 = case Trans of
+ <<"">> -> Orig;
+ _ -> Trans
+ end,
+ ets:insert(translations,
+ {{Lang, iolist_to_binary(Orig)},
+ iolist_to_binary(Trans1)})
+ end,
+ Terms);
+ %% Code copied from ejabberd_config.erl
+ {error,
+ {_LineNumber, erl_parse, _ParseMessage} = Reason} ->
+ ExitText = iolist_to_binary([File,
+ " approximately in the line ",
+ file:format_error(Reason)]),
+ ?ERROR_MSG("Problem loading translation file ~n~s",
+ [ExitText]),
+ exit(ExitText);
+ {error, Reason} ->
+ ExitText = iolist_to_binary([File, ": ",
+ file:format_error(Reason)]),
+ ?ERROR_MSG("Problem loading translation file ~n~s",
+ [ExitText]),
+ exit(ExitText)
end.
+-spec translate(binary(), binary()) -> binary().
+
translate(Lang, Msg) ->
LLang = ascii_tolower(Lang),
case ets:lookup(translations, {LLang, Msg}) of
- [{_, Trans}] ->
- Trans;
- _ ->
- ShortLang = case string:tokens(LLang, "-") of
- [] ->
- LLang;
- [SL | _] ->
- SL
- end,
- case ShortLang of
- "en" ->
- Msg;
- LLang ->
- translate(Msg);
- _ ->
- case ets:lookup(translations, {ShortLang, Msg}) of
- [{_, Trans}] ->
- Trans;
- _ ->
- translate(Msg)
- end
- end
+ [{_, Trans}] -> Trans;
+ _ ->
+ ShortLang = case str:tokens(LLang, <<"-">>) of
+ [] -> LLang;
+ [SL | _] -> SL
+ end,
+ case ShortLang of
+ <<"en">> -> Msg;
+ LLang -> translate(Msg);
+ _ ->
+ case ets:lookup(translations, {ShortLang, Msg}) of
+ [{_, Trans}] -> Trans;
+ _ -> translate(Msg)
+ end
+ end
end.
translate(Msg) ->
case ?MYLANG of
- undefined ->
- Msg;
- "en" ->
- Msg;
- Lang ->
- LLang = ascii_tolower(Lang),
- case ets:lookup(translations, {LLang, Msg}) of
- [{_, Trans}] ->
- Trans;
- _ ->
- ShortLang = case string:tokens(LLang, "-") of
- [] ->
- LLang;
- [SL | _] ->
- SL
- end,
- case ShortLang of
- "en" ->
- Msg;
- Lang ->
- Msg;
- _ ->
- case ets:lookup(translations, {ShortLang, Msg}) of
- [{_, Trans}] ->
- Trans;
- _ ->
- Msg
- end
- end
- end
+ <<"en">> -> Msg;
+ Lang ->
+ LLang = ascii_tolower(Lang),
+ case ets:lookup(translations, {LLang, Msg}) of
+ [{_, Trans}] -> Trans;
+ _ ->
+ ShortLang = case str:tokens(LLang, <<"-">>) of
+ [] -> LLang;
+ [SL | _] -> SL
+ end,
+ case ShortLang of
+ <<"en">> -> Msg;
+ Lang -> Msg;
+ _ ->
+ case ets:lookup(translations, {ShortLang, Msg}) of
+ [{_, Trans}] -> Trans;
+ _ -> Msg
+ end
+ end
+ end
end.
-ascii_tolower([C | Cs]) when C >= $A, C =< $Z ->
- [C + ($a - $A) | ascii_tolower(Cs)];
-ascii_tolower([C | Cs]) ->
- [C | ascii_tolower(Cs)];
-ascii_tolower([]) ->
- [].
+ascii_tolower(B) ->
+ iolist_to_binary(ascii_tolower_s(binary_to_list(B))).
+ascii_tolower_s([C | Cs]) when C >= $A, C =< $Z ->
+ [C + ($a - $A) | ascii_tolower_s(Cs)];
+ascii_tolower_s([C | Cs]) -> [C | ascii_tolower_s(Cs)];
+ascii_tolower_s([]) -> [].
diff --git a/src/treap.erl b/src/treap.erl
index 1826f02cc..607f2c8ab 100644
--- a/src/treap.erl
+++ b/src/treap.erl
@@ -26,19 +26,17 @@
-module(treap).
--export([empty/0,
- insert/4,
- delete/2,
- delete_root/1,
- get_root/1,
- lookup/2,
- is_empty/1,
- fold/3,
- from_list/1,
+-export([empty/0, insert/4, delete/2, delete_root/1,
+ get_root/1, lookup/2, is_empty/1, fold/3, from_list/1,
to_list/1]).
-empty() ->
- nil.
+-type hashkey() :: {non_neg_integer(), any()}.
+
+-type treap() :: {hashkey(), any(), any(), treap(), treap()} | nil.
+
+-export_type([treap/0]).
+
+empty() -> nil.
insert(Key, Priority, Value, Tree) ->
HashKey = {erlang:phash2(Key), Key},
@@ -46,147 +44,124 @@ insert(Key, Priority, Value, Tree) ->
insert1(nil, HashKey, Priority, Value) ->
{HashKey, Priority, Value, nil, nil};
-insert1({HashKey1, Priority1, Value1, Left, Right} = Tree,
+insert1({HashKey1, Priority1, Value1, Left, Right} =
+ Tree,
HashKey, Priority, Value) ->
- if
- HashKey < HashKey1 ->
- heapify({HashKey1, Priority1, Value1,
- insert1(Left, HashKey, Priority, Value),
- Right});
- HashKey > HashKey1 ->
- heapify({HashKey1, Priority1, Value1,
- Left,
- insert1(Right, HashKey, Priority, Value)});
- Priority == Priority1 ->
- {HashKey, Priority, Value, Left, Right};
- true ->
- insert1(delete_root(Tree), HashKey, Priority, Value)
+ if HashKey < HashKey1 ->
+ heapify({HashKey1, Priority1, Value1,
+ insert1(Left, HashKey, Priority, Value), Right});
+ HashKey > HashKey1 ->
+ heapify({HashKey1, Priority1, Value1, Left,
+ insert1(Right, HashKey, Priority, Value)});
+ Priority == Priority1 ->
+ {HashKey, Priority, Value, Left, Right};
+ true ->
+ insert1(delete_root(Tree), HashKey, Priority, Value)
end.
-heapify({_HashKey, _Priority, _Value, nil, nil} = Tree) ->
+heapify({_HashKey, _Priority, _Value, nil, nil} =
+ Tree) ->
Tree;
-heapify({HashKey, Priority, Value,
- nil = Left,
- {HashKeyR, PriorityR, ValueR, LeftR, RightR}} = Tree) ->
- if
- PriorityR > Priority ->
- {HashKeyR, PriorityR, ValueR,
- {HashKey, Priority, Value, Left, LeftR},
- RightR};
- true ->
- Tree
+heapify({HashKey, Priority, Value, nil = Left,
+ {HashKeyR, PriorityR, ValueR, LeftR, RightR}} =
+ Tree) ->
+ if PriorityR > Priority ->
+ {HashKeyR, PriorityR, ValueR,
+ {HashKey, Priority, Value, Left, LeftR}, RightR};
+ true -> Tree
end;
heapify({HashKey, Priority, Value,
{HashKeyL, PriorityL, ValueL, LeftL, RightL},
- nil = Right} = Tree) ->
- if
- PriorityL > Priority ->
- {HashKeyL, PriorityL, ValueL,
- LeftL,
- {HashKey, Priority, Value, RightL, Right}};
- true ->
- Tree
+ nil = Right} =
+ Tree) ->
+ if PriorityL > Priority ->
+ {HashKeyL, PriorityL, ValueL, LeftL,
+ {HashKey, Priority, Value, RightL, Right}};
+ true -> Tree
end;
heapify({HashKey, Priority, Value,
{HashKeyL, PriorityL, ValueL, LeftL, RightL} = Left,
- {HashKeyR, PriorityR, ValueR, LeftR, RightR} = Right} = Tree) ->
- if
- PriorityR > Priority ->
- {HashKeyR, PriorityR, ValueR,
- {HashKey, Priority, Value, Left, LeftR},
- RightR};
- PriorityL > Priority ->
- {HashKeyL, PriorityL, ValueL,
- LeftL,
- {HashKey, Priority, Value, RightL, Right}};
- true ->
- Tree
+ {HashKeyR, PriorityR, ValueR, LeftR, RightR} = Right} =
+ Tree) ->
+ if PriorityR > Priority ->
+ {HashKeyR, PriorityR, ValueR,
+ {HashKey, Priority, Value, Left, LeftR}, RightR};
+ PriorityL > Priority ->
+ {HashKeyL, PriorityL, ValueL, LeftL,
+ {HashKey, Priority, Value, RightL, Right}};
+ true -> Tree
end.
-
delete(Key, Tree) ->
HashKey = {erlang:phash2(Key), Key},
delete1(HashKey, Tree).
-delete1(_HashKey, nil) ->
- nil;
-delete1(HashKey, {HashKey1, Priority1, Value1, Left, Right} = Tree) ->
- if
- HashKey < HashKey1 ->
- {HashKey1, Priority1, Value1, delete1(HashKey, Left), Right};
- HashKey > HashKey1 ->
- {HashKey1, Priority1, Value1, Left, delete1(HashKey, Right)};
- true ->
- delete_root(Tree)
+delete1(_HashKey, nil) -> nil;
+delete1(HashKey,
+ {HashKey1, Priority1, Value1, Left, Right} = Tree) ->
+ if HashKey < HashKey1 ->
+ {HashKey1, Priority1, Value1, delete1(HashKey, Left),
+ Right};
+ HashKey > HashKey1 ->
+ {HashKey1, Priority1, Value1, Left,
+ delete1(HashKey, Right)};
+ true -> delete_root(Tree)
end.
delete_root({HashKey, Priority, Value, Left, Right}) ->
case {Left, Right} of
- {nil, nil} ->
- nil;
- {_, nil} ->
- Left;
- {nil, _} ->
- Right;
- {{HashKeyL, PriorityL, ValueL, LeftL, RightL},
- {HashKeyR, PriorityR, ValueR, LeftR, RightR}} ->
- if
- PriorityL > PriorityR ->
- {HashKeyL, PriorityL, ValueL,
- LeftL,
- delete_root({HashKey, Priority, Value, RightL, Right})};
- true ->
- {HashKeyR, PriorityR, ValueR,
- delete_root({HashKey, Priority, Value, Left, LeftR}),
- RightR}
- end
+ {nil, nil} -> nil;
+ {_, nil} -> Left;
+ {nil, _} -> Right;
+ {{HashKeyL, PriorityL, ValueL, LeftL, RightL},
+ {HashKeyR, PriorityR, ValueR, LeftR, RightR}} ->
+ if PriorityL > PriorityR ->
+ {HashKeyL, PriorityL, ValueL, LeftL,
+ delete_root({HashKey, Priority, Value, RightL, Right})};
+ true ->
+ {HashKeyR, PriorityR, ValueR,
+ delete_root({HashKey, Priority, Value, Left, LeftR}),
+ RightR}
+ end
end.
-is_empty(nil) ->
- true;
-is_empty({_HashKey, _Priority, _Value, _Left, _Right}) ->
+is_empty(nil) -> true;
+is_empty({_HashKey, _Priority, _Value, _Left,
+ _Right}) ->
false.
-get_root({{_Hash, Key}, Priority, Value, _Left, _Right}) ->
+get_root({{_Hash, Key}, Priority, Value, _Left,
+ _Right}) ->
{Key, Priority, Value}.
-
lookup(Key, Tree) ->
HashKey = {erlang:phash2(Key), Key},
lookup1(Tree, HashKey).
-lookup1(nil, _HashKey) ->
- error;
-lookup1({HashKey1, Priority1, Value1, Left, Right}, HashKey) ->
- if
- HashKey < HashKey1 ->
- lookup1(Left, HashKey);
- HashKey > HashKey1 ->
- lookup1(Right, HashKey);
- true ->
- {ok, Priority1, Value1}
+lookup1(nil, _HashKey) -> error;
+lookup1({HashKey1, Priority1, Value1, Left, Right},
+ HashKey) ->
+ if HashKey < HashKey1 -> lookup1(Left, HashKey);
+ HashKey > HashKey1 -> lookup1(Right, HashKey);
+ true -> {ok, Priority1, Value1}
end.
-fold(_F, Acc, nil) ->
- Acc;
-fold(F, Acc, {{_Hash, Key}, Priority, Value, Left, Right}) ->
+fold(_F, Acc, nil) -> Acc;
+fold(F, Acc,
+ {{_Hash, Key}, Priority, Value, Left, Right}) ->
Acc1 = F({Key, Priority, Value}, Acc),
Acc2 = fold(F, Acc1, Left),
fold(F, Acc2, Right).
-to_list(Tree) ->
- to_list(Tree, []).
+to_list(Tree) -> to_list(Tree, []).
-to_list(nil, Acc) ->
- Acc;
+to_list(nil, Acc) -> Acc;
to_list(Tree, Acc) ->
Root = get_root(Tree),
- to_list(delete_root(Tree), [Root|Acc]).
+ to_list(delete_root(Tree), [Root | Acc]).
-from_list(List) ->
- from_list(List, nil).
+from_list(List) -> from_list(List, nil).
-from_list([{Key, Priority, Value}|Tail], Tree) ->
+from_list([{Key, Priority, Value} | Tail], Tree) ->
from_list(Tail, insert(Key, Priority, Value, Tree));
-from_list([], Tree) ->
- Tree.
+from_list([], Tree) -> Tree.
diff --git a/src/web/Makefile.in b/src/web/Makefile.in
index 77f801410..5ac2fe5e6 100644
--- a/src/web/Makefile.in
+++ b/src/web/Makefile.in
@@ -15,7 +15,7 @@ EFLAGS += -pz ..
# make debug=true to compile Erlang module with debug informations.
ifdef debug
- EFLAGS+=+debug_info +export_all
+ EFLAGS+=+debug_info
endif
SOURCES = $(wildcard *.erl)
diff --git a/src/web/ejabberd_http.erl b/src/web/ejabberd_http.erl
index 612a544bd..be0af4157 100644
--- a/src/web/ejabberd_http.erl
+++ b/src/web/ejabberd_http.erl
@@ -25,21 +25,20 @@
%%%----------------------------------------------------------------------
-module(ejabberd_http).
+
-author('alexey@process-one.net').
%% External exports
--export([start/2,
- start_link/2,
- become_controller/1,
- socket_type/0,
- receive_headers/1,
- url_encode/1]).
+-export([start/2, start_link/2, become_controller/1,
+ socket_type/0, receive_headers/1, url_encode/1]).
%% Callbacks
-export([init/2]).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
+
-include("ejabberd_http.hrl").
-record(state, {sockmod,
@@ -69,79 +68,77 @@
trail = <<>>
}).
-
-define(XHTML_DOCTYPE,
- "<?xml version='1.0'?>\n"
- "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" "
- "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n").
+ <<"<?xml version='1.0'?>\n<!DOCTYPE html "
+ "PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//"
+ "EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1"
+ "-transitional.dtd\">\n">>).
-define(HTML_DOCTYPE,
- "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" "
- "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n").
-
+ <<"<!DOCTYPE html PUBLIC \"-//W3C//DTD "
+ "XHTML 1.0 Transitional//EN\" \"http://www.w3."
+ "org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"
+ "">>).
start(SockData, Opts) ->
- supervisor:start_child(ejabberd_http_sup, [SockData, Opts]).
+ supervisor:start_child(ejabberd_http_sup,
+ [SockData, Opts]).
start_link(SockData, Opts) ->
- {ok, proc_lib:spawn_link(ejabberd_http, init, [SockData, Opts])}.
+ {ok,
+ proc_lib:spawn_link(ejabberd_http, init,
+ [SockData, Opts])}.
init({SockMod, Socket}, Opts) ->
TLSEnabled = lists:member(tls, Opts),
- TLSOpts1 = lists:filter(fun({certfile, _}) -> true;
- (_) -> false
- end, Opts),
+ TLSOpts1 = lists:filter(fun ({certfile, _}) -> true;
+ (_) -> false
+ end,
+ Opts),
TLSOpts = [verify_none | TLSOpts1],
- {SockMod1, Socket1} =
- if
- TLSEnabled ->
- inet:setopts(Socket, [{recbuf, 8192}]),
- {ok, TLSSocket} = tls:tcp_to_tls(Socket, TLSOpts),
- {tls, TLSSocket};
- true ->
- {SockMod, Socket}
- end,
+ {SockMod1, Socket1} = if TLSEnabled ->
+ inet:setopts(Socket, [{recbuf, 8192}]),
+ {ok, TLSSocket} = tls:tcp_to_tls(Socket,
+ TLSOpts),
+ {tls, TLSSocket};
+ true -> {SockMod, Socket}
+ end,
case SockMod1 of
- gen_tcp ->
- inet:setopts(Socket1, [{packet, http}, {recbuf, 8192}]);
- _ ->
- ok
+ gen_tcp ->
+ inet:setopts(Socket1, [{packet, http_bin}, {recbuf, 8192}]);
+ _ -> ok
end,
-
- %% XXX bard: for backward compatibility, expand in Opts:
- %% web_admin -> {["admin"], ejabberd_web_admin}
- %% http_bind -> {["http-bind"], mod_http_bind}
- %% http_poll -> {["http-poll"], ejabberd_http_poll}
- %% register -> {["register"], mod_register_web}
-
- RequestHandlers =
- case lists:keysearch(request_handlers, 1, Opts) of
- {value, {request_handlers, H}} -> H;
- false -> []
- end ++
- case lists:member(captcha, Opts) of
- true -> [{["captcha"], ejabberd_captcha}];
- false -> []
- end ++
- case lists:member(register, Opts) of
- true -> [{["register"], mod_register_web}];
- false -> []
- end ++
- case lists:member(web_admin, Opts) of
- true -> [{["admin"], ejabberd_web_admin}];
- false -> []
- end ++
- case lists:member(http_bind, Opts) of
- true -> [{["http-bind"], mod_http_bind}];
- false -> []
- end ++
- case lists:member(http_poll, Opts) of
- true -> [{["http-poll"], ejabberd_http_poll}];
- false -> []
- end,
+ Captcha = case lists:member(captcha, Opts) of
+ true -> [{[<<"captcha">>], ejabberd_captcha}];
+ false -> []
+ end,
+ Register = case lists:member(register, Opts) of
+ true -> [{[<<"register">>], mod_register_web}];
+ false -> []
+ end,
+ Admin = case lists:member(web_admin, Opts) of
+ true -> [{[<<"admin">>], ejabberd_web_admin}];
+ false -> []
+ end,
+ Bind = case lists:member(http_bind, Opts) of
+ true -> [{[<<"http-bind">>], mod_http_bind}];
+ false -> []
+ end,
+ Poll = case lists:member(http_poll, Opts) of
+ true -> [{[<<"http-poll">>], ejabberd_http_poll}];
+ false -> []
+ end,
+ DefinedHandlers = case lists:keysearch(request_handlers,
+ 1, Opts)
+ of
+ {value, {request_handlers, H}} -> H;
+ false -> []
+ end,
+ RequestHandlers = DefinedHandlers ++ Captcha ++ Register ++
+ Admin ++ Bind ++ Poll,
?DEBUG("S: ~p~n", [RequestHandlers]),
- DefaultHost = gen_mod:get_opt(default_host, Opts, undefined),
+ DefaultHost = gen_mod:get_opt(default_host, Opts, fun(A) -> A end, undefined),
?INFO_MSG("started: ~p", [{SockMod1, Socket1}]),
State = #state{sockmod = SockMod1,
@@ -150,25 +147,26 @@ init({SockMod, Socket}, Opts) ->
request_handlers = RequestHandlers},
receive_headers(State).
-
-become_controller(_Pid) ->
- ok.
+become_controller(_Pid) -> ok.
socket_type() ->
raw.
send_text(State, Text) ->
- case catch (State#state.sockmod):send(State#state.socket, Text) of
- ok -> ok;
- {error, timeout} ->
- ?INFO_MSG("Timeout on ~p:send",[State#state.sockmod]),
- exit(normal);
- Error ->
- ?DEBUG("Error in ~p:send: ~p",[State#state.sockmod, Error]),
- exit(normal)
+ case catch
+ (State#state.sockmod):send(State#state.socket, Text)
+ of
+ ok -> ok;
+ {error, timeout} ->
+ ?INFO_MSG("Timeout on ~p:send", [State#state.sockmod]),
+ exit(normal);
+ Error ->
+ ?DEBUG("Error in ~p:send: ~p",
+ [State#state.sockmod, Error]),
+ exit(normal)
end.
-receive_headers(#state{trail=Trail} = State) ->
+receive_headers(#state{trail = Trail} = State) ->
SockMod = State#state.sockmod,
Socket = State#state.socket,
Data = SockMod:recv(Socket, 0, 300000),
@@ -192,12 +190,14 @@ receive_headers(#state{trail=Trail} = State) ->
parse_headers(#state{trail = <<>>} = State) ->
receive_headers(State);
-parse_headers(#state{request_method = Method, trail = Data} = State) ->
+parse_headers(#state{request_method = Method,
+ trail = Data} =
+ State) ->
PktType = case Method of
undefined -> http;
_ -> httph
end,
- case decode_packet(PktType, Data) of
+ case erlang:decode_packet(PktType, Data) of
{ok, Pkt, Rest} ->
NewState = process_header(State#state{trail = Rest}, {ok, Pkt}),
case NewState#state.end_of_request of
@@ -216,99 +216,99 @@ process_header(State, Data) ->
SockMod = State#state.sockmod,
Socket = State#state.socket,
case Data of
- {ok, {http_request, Method, Uri, Version}} ->
- KeepAlive = case Version of
- {1, 1} ->
- true;
- _ ->
- false
- end,
- Path = case Uri of
- {absoluteURI, _Scheme, _Host, _Port, P} -> {abs_path, P};
- _ -> Uri
- end,
- State#state{request_method = Method,
- request_version = Version,
- request_path = Path,
- request_keepalive = KeepAlive};
- {ok, {http_header, _, 'Connection'=Name, _, Conn}} ->
- KeepAlive1 = case jlib:tolower(Conn) of
- "keep-alive" ->
- true;
- "close" ->
- false;
- _ ->
- State#state.request_keepalive
- end,
- State#state{request_keepalive = KeepAlive1,
- request_headers=add_header(Name, Conn, State)};
- {ok, {http_header, _, 'Authorization'=Name, _, Auth}} ->
- State#state{request_auth = parse_auth(Auth),
- request_headers=add_header(Name, Auth, State)};
- {ok, {http_header, _, 'Content-Length'=Name, _, SLen}} ->
- case catch list_to_integer(SLen) of
- Len when is_integer(Len) ->
- State#state{request_content_length = Len,
- request_headers=add_header(Name, SLen, State)};
- _ ->
- State
- end;
- {ok, {http_header, _, 'Accept-Language'=Name, _, Langs}} ->
- State#state{request_lang = parse_lang(Langs),
- request_headers=add_header(Name, Langs, State)};
- {ok, {http_header, _, 'Host'=Name, _, Host}} ->
- State#state{request_host = Host,
- request_headers=add_header(Name, Host, State)};
- {ok, {http_header, _, Name, _, Value}} ->
- State#state{request_headers=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);
- {ok, http_eoh} ->
- ?DEBUG("(~w) http query: ~w ~s~n",
- [State#state.socket,
- State#state.request_method,
- element(2, State#state.request_path)]),
- {HostProvided, Port, TP} = get_transfer_protocol(SockMod,
- State#state.request_host),
- Host = get_host_really_served(State#state.default_host, HostProvided),
- State2 = State#state{request_host = Host,
- request_port = Port,
- request_tp = TP},
- Out = process_request(State2),
- send_text(State2, Out),
- case State2#state.request_keepalive of
- true ->
- case SockMod of
- gen_tcp ->
- inet:setopts(Socket, [{packet, http}]);
- _ ->
- ok
- end,
- #state{sockmod = SockMod,
- socket = Socket,
- request_handlers = State#state.request_handlers};
- _ ->
- #state{end_of_request = true,
- request_handlers = State#state.request_handlers}
- end;
- {error, _Reason} ->
- #state{end_of_request = true,
- request_handlers = State#state.request_handlers};
- _ ->
- #state{end_of_request = true,
- request_handlers = State#state.request_handlers}
+ {ok, {http_request, Method, Uri, Version}} ->
+ KeepAlive = case Version of
+ {1, 1} -> true;
+ _ -> false
+ end,
+ Path = case Uri of
+ {absoluteURI, _Scheme, _Host, _Port, P} ->
+ {abs_path, P};
+ {abs_path, P} ->
+ {abs_path, P};
+ _ -> Uri
+ end,
+ State#state{request_method = Method,
+ request_version = Version, request_path = Path,
+ request_keepalive = KeepAlive};
+ {ok, {http_header, _, 'Connection' = Name, _, Conn}} ->
+ KeepAlive1 = case jlib:tolower(Conn) of
+ <<"keep-alive">> -> true;
+ <<"close">> -> false;
+ _ -> State#state.request_keepalive
+ end,
+ State#state{request_keepalive = KeepAlive1,
+ request_headers = add_header(Name, Conn, State)};
+ {ok,
+ {http_header, _, 'Authorization' = Name, _, Auth}} ->
+ State#state{request_auth = parse_auth(Auth),
+ request_headers = add_header(Name, Auth, State)};
+ {ok,
+ {http_header, _, 'Content-Length' = Name, _, SLen}} ->
+ case catch jlib:binary_to_integer(SLen) of
+ Len when is_integer(Len) ->
+ State#state{request_content_length = Len,
+ request_headers = add_header(Name, SLen, State)};
+ _ -> State
+ end;
+ {ok,
+ {http_header, _, 'Accept-Language' = Name, _, Langs}} ->
+ State#state{request_lang = parse_lang(Langs),
+ request_headers = add_header(Name, Langs, State)};
+ {ok, {http_header, _, 'Host' = Name, _, Host}} ->
+ State#state{request_host = Host,
+ request_headers = add_header(Name, Host, State)};
+ {ok, {http_header, _, Name, _, Value}} ->
+ State#state{request_headers =
+ 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);
+ {ok, http_eoh} ->
+ ?DEBUG("(~w) http query: ~w ~s~n",
+ [State#state.socket, State#state.request_method,
+ element(2, State#state.request_path)]),
+ {HostProvided, Port, TP} =
+ get_transfer_protocol(SockMod,
+ State#state.request_host),
+ Host = get_host_really_served(State#state.default_host,
+ HostProvided),
+ State2 = State#state{request_host = Host,
+ request_port = Port, request_tp = TP},
+ Out = process_request(State2),
+ send_text(State2, Out),
+ case State2#state.request_keepalive of
+ true ->
+ case SockMod of
+ gen_tcp -> inet:setopts(Socket, [{packet, http_bin}]);
+ _ -> ok
+ end,
+ #state{sockmod = SockMod, socket = Socket,
+ request_handlers = State#state.request_handlers};
+ _ ->
+ #state{end_of_request = true,
+ request_handlers = State#state.request_handlers}
+ end;
+ {error, _Reason} ->
+ #state{end_of_request = true,
+ request_handlers = State#state.request_handlers};
+ _ ->
+ #state{end_of_request = true,
+ request_handlers = State#state.request_handlers}
end.
-add_header(Name, Value, State) ->
+add_header(Name, Value, State)->
[{Name, Value} | State#state.request_headers].
get_host_really_served(undefined, Provided) ->
Provided;
get_host_really_served(Default, Provided) ->
case lists:member(Provided, ?MYHOSTS) of
- true -> Provided;
- false -> Default
+ true -> Provided;
+ false -> Default
end.
%% @spec (SockMod, HostPort) -> {Host::string(), Port::integer(), TP}
@@ -320,16 +320,14 @@ get_host_really_served(Default, Provided) ->
%% Note that HostPort can be a string of a host like "example.org",
%% or a string of a host and port like "example.org:5280".
get_transfer_protocol(SockMod, HostPort) ->
- [Host | PortList] = string:tokens(HostPort, ":"),
+ [Host | PortList] = str:tokens(HostPort, <<":">>),
case {SockMod, PortList} of
- {gen_tcp, []} ->
- {Host, 80, http};
- {gen_tcp, [Port]} ->
- {Host, list_to_integer(Port), http};
- {tls, []} ->
- {Host, 443, https};
- {tls, [Port]} ->
- {Host, list_to_integer(Port), https}
+ {gen_tcp, []} -> {Host, 80, http};
+ {gen_tcp, [Port]} ->
+ {Host, jlib:binary_to_integer(Port), http};
+ {tls, []} -> {Host, 443, https};
+ {tls, [Port]} ->
+ {Host, jlib:binary_to_integer(Port), https}
end.
%% XXX bard: search through request handlers looking for one that
@@ -338,33 +336,31 @@ get_transfer_protocol(SockMod, HostPort) ->
process([], _) ->
ejabberd_web:error(not_found);
process(Handlers, Request) ->
- [{HandlerPathPrefix, HandlerModule} | HandlersLeft] = Handlers,
-
- case (lists:prefix(HandlerPathPrefix, Request#request.path) or
- (HandlerPathPrefix==Request#request.path)) of
- true ->
- ?DEBUG("~p matches ~p", [Request#request.path, HandlerPathPrefix]),
- %% LocalPath is the path "local to the handler", i.e. if
- %% the handler was registered to handle "/test/" and the
- %% requested path is "/test/foo/bar", the local path is
- %% ["foo", "bar"]
- LocalPath = lists:nthtail(length(HandlerPathPrefix), Request#request.path),
- R = HandlerModule:process(LocalPath, Request),
- ejabberd_hooks:run(http_request_debug, [{LocalPath, Request}]),
- R;
- false ->
- process(HandlersLeft, Request)
+ %% Only the first element in the path prefix is checked
+ [{HandlerPathPrefix, HandlerModule} | HandlersLeft] =
+ Handlers,
+ case lists:prefix(HandlerPathPrefix,
+ Request#request.path)
+ or (HandlerPathPrefix == Request#request.path)
+ of
+ true ->
+ ?DEBUG("~p matches ~p",
+ [Request#request.path, HandlerPathPrefix]),
+ LocalPath = lists:nthtail(length(HandlerPathPrefix),
+ Request#request.path),
+ ?DEBUG("~p", [Request#request.headers]),
+ R = HandlerModule:process(LocalPath, Request),
+ ejabberd_hooks:run(http_request_debug,
+ [{LocalPath, Request}]),
+ R;
+ false -> process(HandlersLeft, Request)
end.
process_request(#state{request_method = Method,
- request_path = {abs_path, Path},
- request_auth = Auth,
- request_lang = Lang,
- request_handlers = RequestHandlers,
- request_host = Host,
- request_port = Port,
- request_tp = TP,
- request_headers = RequestHeaders,
+ request_path = {abs_path, Path}, request_auth = Auth,
+ request_lang = Lang, request_handlers = RequestHandlers,
+ request_host = Host, request_port = Port,
+ request_tp = TP, request_headers = RequestHeaders,
sockmod = SockMod,
socket = Socket} = State)
when Method=:='GET' orelse Method=:='HEAD' orelse Method=:='DELETE' orelse Method=:='OPTIONS' ->
@@ -372,7 +368,7 @@ process_request(#state{request_method = Method,
{'EXIT', _} ->
make_bad_request(State);
{NPath, Query} ->
- LPath = [path_decode(NPE) || NPE <- string:tokens(NPath, "/")],
+ LPath = [path_decode(NPE) || NPE <- str:tokens(NPath, <<"/">>)],
LQuery = case (catch parse_urlencoded(Query)) of
{'EXIT', _Reason} ->
[];
@@ -403,10 +399,10 @@ process_request(#state{request_method = Method,
%% procedure (process) that handles dispatching based on
%% URL path prefix.
case process(RequestHandlers, Request) of
- El when element(1, El) == xmlelement ->
+ El when element(1, El) == xmlel ->
make_xhtml_output(State, 200, [], El);
{Status, Headers, El} when
- element(1, El) == xmlelement ->
+ element(1, El) == xmlel ->
make_xhtml_output(State, Status, Headers, El);
Output when is_list(Output) or is_binary(Output) ->
make_text_output(State, 200, [], Output);
@@ -414,34 +410,26 @@ process_request(#state{request_method = Method,
make_text_output(State, Status, Headers, Output)
end
end;
-
process_request(#state{request_method = Method,
- request_path = {abs_path, Path},
- request_auth = Auth,
- request_content_length = Len,
- request_lang = Lang,
- sockmod = SockMod,
- socket = Socket,
- request_host = Host,
- request_port = Port,
- request_tp = TP,
+ request_path = {abs_path, Path}, request_auth = Auth,
+ request_content_length = Len, request_lang = Lang,
+ sockmod = SockMod, socket = Socket, request_host = Host,
+ request_port = Port, request_tp = TP,
request_headers = RequestHeaders,
- request_handlers = RequestHandlers} = State)
- when (Method=:='POST' orelse Method=:='PUT') andalso is_integer(Len) ->
- {ok, IPHere} =
- case SockMod of
- gen_tcp ->
- inet:peername(Socket);
- _ ->
- SockMod:peername(Socket)
- end,
- XFF = proplists:get_value('X-Forwarded-For', RequestHeaders, []),
+ request_handlers = RequestHandlers} =
+ State)
+ when (Method =:= 'POST' orelse Method =:= 'PUT') andalso
+ is_integer(Len) ->
+ {ok, IPHere} = case SockMod of
+ gen_tcp -> inet:peername(Socket);
+ _ -> SockMod:peername(Socket)
+ end,
+ XFF = proplists:get_value('X-Forwarded-For',
+ RequestHeaders, []),
IP = analyze_ip_xff(IPHere, XFF, Host),
case SockMod of
- gen_tcp ->
- inet:setopts(Socket, [{packet, 0}]);
- _ ->
- ok
+ gen_tcp -> inet:setopts(Socket, [{packet, 0}]);
+ _ -> ok
end,
Data = recv_data(State, Len),
?DEBUG("client data: ~p~n", [Data]),
@@ -449,7 +437,7 @@ process_request(#state{request_method = Method,
{'EXIT', _} ->
make_bad_request(State);
{NPath, _Query} ->
- LPath = [path_decode(NPE) || NPE <- string:tokens(NPath, "/")],
+ LPath = [path_decode(NPE) || NPE <- str:tokens(NPath, <<"/">>)],
LQuery = case (catch parse_urlencoded(Data)) of
{'EXIT', _Reason} ->
[];
@@ -468,10 +456,10 @@ process_request(#state{request_method = Method,
headers = RequestHeaders,
ip = IP},
case process(RequestHandlers, Request) of
- El when element(1, El) == xmlelement ->
+ El when element(1, El) == xmlel ->
make_xhtml_output(State, 200, [], El);
{Status, Headers, El} when
- element(1, El) == xmlelement ->
+ element(1, El) == xmlel ->
make_xhtml_output(State, Status, Headers, El);
Output when is_list(Output) or is_binary(Output) ->
make_text_output(State, 200, [], Output);
@@ -479,198 +467,166 @@ process_request(#state{request_method = Method,
make_text_output(State, Status, Headers, Output)
end
end;
-
-process_request(State) ->
- make_bad_request(State).
+process_request(State) -> make_bad_request(State).
make_bad_request(State) ->
- make_xhtml_output(State,
- 400,
- [],
- ejabberd_web:make_xhtml([{xmlelement, "h1", [],
- [{xmlcdata, "400 Bad Request"}]}])).
-
%% Support for X-Forwarded-From
-analyze_ip_xff(IP, [], _Host) ->
- IP;
+ make_xhtml_output(State, 400, [],
+ ejabberd_web:make_xhtml([#xmlel{name = <<"h1">>,
+ attrs = [],
+ children =
+ [{xmlcdata,
+ <<"400 Bad Request">>}]}])).
+
+analyze_ip_xff(IP, [], _Host) -> IP;
analyze_ip_xff({IPLast, Port}, XFF, Host) ->
- [ClientIP | ProxiesIPs] = string:tokens(XFF, ", ")
- ++ [inet_parse:ntoa(IPLast)],
- TrustedProxies = case ejabberd_config:get_local_option(
- {trusted_proxies, Host}) of
- undefined -> [];
- TPs -> TPs
- end,
- IPClient = case is_ipchain_trusted(ProxiesIPs, TrustedProxies) of
- true ->
- {ok, IPFirst} = inet_parse:address(ClientIP),
- ?DEBUG("The IP ~w was replaced with ~w due to header "
- "X-Forwarded-For: ~s", [IPLast, IPFirst, XFF]),
- IPFirst;
- false ->
- IPLast
+ [ClientIP | ProxiesIPs] = str:tokens(XFF, <<", ">>) ++
+ [jlib:ip_to_list(IPLast)],
+ TrustedProxies = ejabberd_config:get_local_option(
+ {trusted_proxies, Host},
+ fun(TPs) ->
+ [iolist_to_binary(TP) || TP <- TPs]
+ end, []),
+ IPClient = case is_ipchain_trusted(ProxiesIPs,
+ TrustedProxies)
+ of
+ true ->
+ {ok, IPFirst} = inet_parse:address(
+ binary_to_list(ClientIP)),
+ ?DEBUG("The IP ~w was replaced with ~w due to "
+ "header X-Forwarded-For: ~s",
+ [IPLast, IPFirst, XFF]),
+ IPFirst;
+ false -> IPLast
end,
{IPClient, Port}.
-is_ipchain_trusted(_UserIPs, all) ->
- true;
+
+is_ipchain_trusted(_UserIPs, all) -> true;
is_ipchain_trusted(UserIPs, TrustedIPs) ->
- [] == UserIPs -- ["127.0.0.1" | TrustedIPs].
+ [] == UserIPs -- [<<"127.0.0.1">> | TrustedIPs].
-recv_data(State, Len) ->
- recv_data(State, Len, []).
+recv_data(State, Len) -> recv_data(State, Len, <<>>).
-recv_data(_State, 0, Acc) ->
- binary_to_list(list_to_binary(Acc));
+recv_data(_State, 0, Acc) -> (iolist_to_binary(Acc));
recv_data(State, Len, Acc) ->
case State#state.trail of
- <<>> ->
- case (State#state.sockmod):recv(State#state.socket, Len, 300000) of
- {ok, Data} ->
- recv_data(State, Len - size(Data), [Acc | [Data]]);
- _ ->
- ""
- end;
- _ ->
- Trail = binary_to_list(State#state.trail),
- recv_data(State#state{trail = <<>>}, Len - length(Trail), [Acc | Trail])
+ <<>> ->
+ case (State#state.sockmod):recv(State#state.socket, Len,
+ 300000)
+ of
+ {ok, Data} ->
+ recv_data(State, Len - byte_size(Data), <<Acc/binary, Data/binary>>);
+ _ -> <<"">>
+ end;
+ _ ->
+ Trail = (State#state.trail),
+ recv_data(State#state{trail = <<>>},
+ Len - byte_size(Trail), <<Acc/binary, Trail/binary>>)
end.
-
make_xhtml_output(State, Status, Headers, XHTML) ->
Data = case lists:member(html, Headers) of
- true ->
- list_to_binary([?HTML_DOCTYPE,
- element_to_string(XHTML)]);
- _ ->
- list_to_binary([?XHTML_DOCTYPE,
- element_to_string(XHTML)])
+ true ->
+ iolist_to_binary([?HTML_DOCTYPE,
+ xml:element_to_binary(XHTML)]);
+ _ ->
+ iolist_to_binary([?XHTML_DOCTYPE,
+ xml:element_to_binary(XHTML)])
end,
- Headers1 = case lists:keysearch("Content-Type", 1, Headers) of
- {value, _} ->
- [{"Content-Length", integer_to_list(size(Data))} |
- Headers];
- _ ->
- [{"Content-Type", "text/html; charset=utf-8"},
- {"Content-Length", integer_to_list(size(Data))} |
- Headers]
+ Headers1 = case lists:keysearch(<<"Content-Type">>, 1,
+ Headers)
+ of
+ {value, _} ->
+ [{<<"Content-Length">>,
+ iolist_to_binary(integer_to_list(byte_size(Data)))}
+ | Headers];
+ _ ->
+ [{<<"Content-Type">>, <<"text/html; charset=utf-8">>},
+ {<<"Content-Length">>,
+ iolist_to_binary(integer_to_list(byte_size(Data)))}
+ | Headers]
end,
HeadersOut = case {State#state.request_version,
- State#state.request_keepalive} of
- {{1, 1}, true} -> Headers1;
- {_, true} ->
- [{"Connection", "keep-alive"} | Headers1];
- {_, false} ->
- % not required for http versions < 1.1
- % but would make no harm
- [{"Connection", "close"} | Headers1]
+ State#state.request_keepalive}
+ of
+ {{1, 1}, true} -> Headers1;
+ {_, true} ->
+ [{<<"Connection">>, <<"keep-alive">>} | Headers1];
+ {_, false} ->
+ [{<<"Connection">>, <<"close">>} | Headers1]
end,
-
Version = case State#state.request_version of
- {1, 1} -> "HTTP/1.1 ";
- _ -> "HTTP/1.0 "
+ {1, 1} -> <<"HTTP/1.1 ">>;
+ _ -> <<"HTTP/1.0 ">>
end,
-
- H = lists:map(fun({Attr, Val}) ->
- [Attr, ": ", Val, "\r\n"];
- (_) ->
- []
- end, HeadersOut),
- SL = [Version, integer_to_list(Status), " ",
- code_to_phrase(Status), "\r\n"],
-
+ H = lists:map(fun ({Attr, Val}) ->
+ [Attr, <<": ">>, Val, <<"\r\n">>];
+ (_) -> []
+ end,
+ HeadersOut),
+ SL = [Version,
+ iolist_to_binary(integer_to_list(Status)), <<" ">>,
+ code_to_phrase(Status), <<"\r\n">>],
Data2 = case State#state.request_method of
- 'HEAD' -> "";
- _ -> Data
- end,
-
- [SL, H, "\r\n", Data2].
-
-make_text_output(State, Status, Headers, Text) when is_list(Text) ->
- make_text_output(State, Status, Headers, list_to_binary(Text));
-
-make_text_output(State, Status, Headers, Data) when is_binary(Data) ->
- Headers1 = case lists:keysearch("Content-Type", 1, Headers) of
- {value, _} ->
- [{"Content-Length", integer_to_list(size(Data))} |
- Headers];
- _ ->
- [{"Content-Type", "text/html; charset=utf-8"},
- {"Content-Length", integer_to_list(size(Data))} |
- Headers]
+ 'HEAD' -> <<"">>;
+ _ -> Data
+ end,
+ [SL, H, <<"\r\n">>, Data2].
+
+make_text_output(State, Status, Headers, Text) ->
+ make_text_output(State, Status, <<"">>, Headers, Text).
+
+make_text_output(State, Status, Reason, Headers, Text) ->
+ Data = iolist_to_binary(Text),
+ Headers1 = case lists:keysearch(<<"Content-Type">>, 1,
+ Headers)
+ of
+ {value, _} ->
+ [{<<"Content-Length">>,
+ jlib:integer_to_binary(byte_size(Data))}
+ | Headers];
+ _ ->
+ [{<<"Content-Type">>, <<"text/html; charset=utf-8">>},
+ {<<"Content-Length">>,
+ jlib:integer_to_binary(byte_size(Data))}
+ | Headers]
end,
-
HeadersOut = case {State#state.request_version,
- State#state.request_keepalive} of
- {{1, 1}, true} -> Headers1;
- {_, true} ->
- [{"Connection", "keep-alive"} | Headers1];
- {_, false} ->
- % not required for http versions < 1.1
- % but would make no harm
- [{"Connection", "close"} | Headers1]
+ State#state.request_keepalive}
+ of
+ {{1, 1}, true} -> Headers1;
+ {_, true} ->
+ [{<<"Connection">>, <<"keep-alive">>} | Headers1];
+ {_, false} ->
+ [{<<"Connection">>, <<"close">>} | Headers1]
end,
-
Version = case State#state.request_version of
- {1, 1} -> "HTTP/1.1 ";
- _ -> "HTTP/1.0 "
+ {1, 1} -> <<"HTTP/1.1 ">>;
+ _ -> <<"HTTP/1.0 ">>
end,
-
- H = lists:map(fun({Attr, Val}) ->
- [Attr, ": ", Val, "\r\n"]
- end, HeadersOut),
- SL = [Version, integer_to_list(Status), " ",
- code_to_phrase(Status), "\r\n"],
-
+ H = lists:map(fun ({Attr, Val}) ->
+ [Attr, <<": ">>, Val, <<"\r\n">>]
+ end,
+ HeadersOut),
+ NewReason = case Reason of
+ <<"">> -> code_to_phrase(Status);
+ _ -> Reason
+ end,
+ SL = [Version,
+ jlib:integer_to_binary(Status), <<" ">>,
+ NewReason, <<"\r\n">>],
Data2 = case State#state.request_method of
- 'HEAD' -> "";
- _ -> Data
- end,
-
- [SL, H, "\r\n", Data2].
-
+ 'HEAD' -> <<"">>;
+ _ -> Data
+ end,
+ [SL, H, <<"\r\n">>, Data2].
parse_lang(Langs) ->
- case string:tokens(Langs, ",; ") of
- [First | _] ->
- First;
- [] ->
- "en"
+ case str:tokens(Langs, <<",; ">>) of
+ [First | _] -> First;
+ [] -> <<"en">>
end.
-element_to_string(El) ->
- case El of
- {xmlelement, Name, Attrs, Els} ->
- if
- Els /= [] ->
- [$<, Name, attrs_to_list(Attrs), $>,
- [element_to_string(E) || E <- Els],
- $<, $/, Name, $>];
- true ->
- [$<, Name, attrs_to_list(Attrs), $/, $>]
- end;
- {xmlcdata, CData} ->
- crypt(CData)
- end.
-
-attrs_to_list(Attrs) ->
- [attr_to_list(A) || A <- Attrs].
-
-attr_to_list({Name, Value}) ->
- [$\s, crypt(Name), $=, $", crypt(Value), $"].
-
-crypt(S) when is_list(S) ->
- [case C of
- $& -> "&amp;";
- $< -> "&lt;";
- $> -> "&gt;";
- $" -> "&quot;";
- $' -> "&#39;";
- _ -> C
- end || C <- S];
-crypt(S) when is_binary(S) ->
- crypt(binary_to_list(S)).
-
-
% Code below is taken (with some modifications) from the yaws webserver, which
% is distributed under the folowing license:
%
@@ -684,444 +640,207 @@ crypt(S) when is_binary(S) ->
% 2. Redistributions in binary form must reproduce the above copyright
% notice as well as this list of conditions.
-
%% @doc Split the URL and return {Path, QueryPart}
url_decode_q_split(Path) ->
- url_decode_q_split(Path, []).
-url_decode_q_split([$?|T], Ack) ->
+ url_decode_q_split(Path, <<>>).
+
+url_decode_q_split(<<$?, T/binary>>, Acc) ->
%% Don't decode the query string here, that is parsed separately.
- {path_norm_reverse(Ack), T};
-url_decode_q_split([H|T], Ack) when H /= 0 ->
- url_decode_q_split(T, [H|Ack]);
-url_decode_q_split([], Ack) ->
- {path_norm_reverse(Ack), []}.
+ {path_norm_reverse(Acc), T};
+url_decode_q_split(<<H, T/binary>>, Acc) when H /= 0 ->
+ url_decode_q_split(T, <<H, Acc/binary>>);
+url_decode_q_split(<<>>, Ack) ->
+ {path_norm_reverse(Ack), <<>>}.
%% @doc Decode a part of the URL and return string()
-path_decode(Path) ->
- path_decode(Path, []).
-path_decode([$%, Hi, Lo | Tail], Ack) ->
+path_decode(Path) -> path_decode(Path, <<>>).
+
+path_decode(<<$%, Hi, Lo, Tail/binary>>, Acc) ->
Hex = hex_to_integer([Hi, Lo]),
- if Hex == 0 -> exit(badurl);
+ if Hex == 0 -> exit(badurl);
true -> ok
end,
- path_decode(Tail, [Hex|Ack]);
-path_decode([H|T], Ack) when H /= 0 ->
- path_decode(T, [H|Ack]);
-path_decode([], Ack) ->
- lists:reverse(Ack).
-
-path_norm_reverse("/" ++ T) -> start_dir(0, "/", T);
-path_norm_reverse( T) -> start_dir(0, "", T).
-
-start_dir(N, Path, ".." ) -> rest_dir(N, Path, "");
-start_dir(N, Path, "/" ++ T ) -> start_dir(N , Path, T);
-start_dir(N, Path, "./" ++ T ) -> start_dir(N , Path, T);
-start_dir(N, Path, "../" ++ T ) -> start_dir(N + 1, Path, T);
-start_dir(N, Path, T ) -> rest_dir (N , Path, T).
-
-rest_dir (_N, Path, [] ) -> case Path of
- [] -> "/";
- _ -> Path
- end;
-rest_dir (0, Path, [ $/ | T ] ) -> start_dir(0 , [ $/ | Path ], T);
-rest_dir (N, Path, [ $/ | T ] ) -> start_dir(N - 1, Path , T);
-rest_dir (0, Path, [ H | T ] ) -> rest_dir (0 , [ H | Path ], T);
-rest_dir (N, Path, [ _H | T ] ) -> rest_dir (N , Path , T).
+ path_decode(Tail, <<Acc/binary, Hex>>);
+path_decode(<<H, T/binary>>, Acc) when H /= 0 ->
+ path_decode(T, <<Acc/binary, H>>);
+path_decode(<<>>, Acc) -> Acc.
+
+path_norm_reverse(<<"/", T/binary>>) -> start_dir(0, <<"/">>, T);
+path_norm_reverse(T) -> start_dir(0, <<"">>, T).
+
+start_dir(N, Path, <<"..">>) -> rest_dir(N, Path, <<"">>);
+start_dir(N, Path, <<"/", T/binary>>) -> start_dir(N, Path, T);
+start_dir(N, Path, <<"./", T/binary>>) -> start_dir(N, Path, T);
+start_dir(N, Path, <<"../", T/binary>>) ->
+ start_dir(N + 1, Path, T);
+start_dir(N, Path, T) -> rest_dir(N, Path, T).
+
+rest_dir(_N, Path, <<>>) ->
+ case Path of
+ <<>> -> <<"/">>;
+ _ -> Path
+ end;
+rest_dir(0, Path, <<$/, T/binary>>) ->
+ start_dir(0, <<$/, Path/binary>>, T);
+rest_dir(N, Path, <<$/, T/binary>>) ->
+ start_dir(N - 1, Path, T);
+rest_dir(0, Path, <<H, T/binary>>) ->
+ rest_dir(0, <<H, Path/binary>>, T);
+rest_dir(N, Path, <<_H, T/binary>>) -> rest_dir(N, Path, T).
%% hex_to_integer
-
hex_to_integer(Hex) ->
- case catch erlang:list_to_integer(Hex, 16) of
- {'EXIT', _} ->
- old_hex_to_integer(Hex);
- X ->
- X
+ case catch list_to_integer(Hex, 16) of
+ {'EXIT', _} -> old_hex_to_integer(Hex);
+ X -> X
end.
-
old_hex_to_integer(Hex) ->
DEHEX = fun (H) when H >= $a, H =< $f -> H - $a + 10;
(H) when H >= $A, H =< $F -> H - $A + 10;
(H) when H >= $0, H =< $9 -> H - $0
end,
- lists:foldl(fun(E, Acc) -> Acc*16+DEHEX(E) end, 0, Hex).
-
-code_to_phrase(100) -> "Continue";
-code_to_phrase(101) -> "Switching Protocols ";
-code_to_phrase(200) -> "OK";
-code_to_phrase(201) -> "Created";
-code_to_phrase(202) -> "Accepted";
-code_to_phrase(203) -> "Non-Authoritative Information";
-code_to_phrase(204) -> "No Content";
-code_to_phrase(205) -> "Reset Content";
-code_to_phrase(206) -> "Partial Content";
-code_to_phrase(300) -> "Multiple Choices";
-code_to_phrase(301) -> "Moved Permanently";
-code_to_phrase(302) -> "Found";
-code_to_phrase(303) -> "See Other";
-code_to_phrase(304) -> "Not Modified";
-code_to_phrase(305) -> "Use Proxy";
-code_to_phrase(306) -> "(Unused)";
-code_to_phrase(307) -> "Temporary Redirect";
-code_to_phrase(400) -> "Bad Request";
-code_to_phrase(401) -> "Unauthorized";
-code_to_phrase(402) -> "Payment Required";
-code_to_phrase(403) -> "Forbidden";
-code_to_phrase(404) -> "Not Found";
-code_to_phrase(405) -> "Method Not Allowed";
-code_to_phrase(406) -> "Not Acceptable";
-code_to_phrase(407) -> "Proxy Authentication Required";
-code_to_phrase(408) -> "Request Timeout";
-code_to_phrase(409) -> "Conflict";
-code_to_phrase(410) -> "Gone";
-code_to_phrase(411) -> "Length Required";
-code_to_phrase(412) -> "Precondition Failed";
-code_to_phrase(413) -> "Request Entity Too Large";
-code_to_phrase(414) -> "Request-URI Too Long";
-code_to_phrase(415) -> "Unsupported Media Type";
-code_to_phrase(416) -> "Requested Range Not Satisfiable";
-code_to_phrase(417) -> "Expectation Failed";
-code_to_phrase(500) -> "Internal Server Error";
-code_to_phrase(501) -> "Not Implemented";
-code_to_phrase(502) -> "Bad Gateway";
-code_to_phrase(503) -> "Service Unavailable";
-code_to_phrase(504) -> "Gateway Timeout";
-code_to_phrase(505) -> "HTTP Version Not Supported".
-
-
-parse_auth(_Orig = "Basic " ++ Auth64) ->
- case decode_base64(Auth64) of
- {error, _Err} ->
- undefined;
- Auth ->
- %% Auth should be a string with the format: user@server:password
- %% Note that password can contain additional characters '@' and ':'
- case string:chr(Auth, $:) of
- 0 ->
- undefined;
- SplitIndex ->
- {User, [$: | Pass]} = lists:split(SplitIndex-1, Auth),
- {User, Pass}
- end
+ lists:foldl(fun (E, Acc) -> Acc * 16 + DEHEX(E) end, 0,
+ Hex).
+
+code_to_phrase(100) -> <<"Continue">>;
+code_to_phrase(101) -> <<"Switching Protocols ">>;
+code_to_phrase(200) -> <<"OK">>;
+code_to_phrase(201) -> <<"Created">>;
+code_to_phrase(202) -> <<"Accepted">>;
+code_to_phrase(203) ->
+ <<"Non-Authoritative Information">>;
+code_to_phrase(204) -> <<"No Content">>;
+code_to_phrase(205) -> <<"Reset Content">>;
+code_to_phrase(206) -> <<"Partial Content">>;
+code_to_phrase(300) -> <<"Multiple Choices">>;
+code_to_phrase(301) -> <<"Moved Permanently">>;
+code_to_phrase(302) -> <<"Found">>;
+code_to_phrase(303) -> <<"See Other">>;
+code_to_phrase(304) -> <<"Not Modified">>;
+code_to_phrase(305) -> <<"Use Proxy">>;
+code_to_phrase(306) -> <<"(Unused)">>;
+code_to_phrase(307) -> <<"Temporary Redirect">>;
+code_to_phrase(400) -> <<"Bad Request">>;
+code_to_phrase(401) -> <<"Unauthorized">>;
+code_to_phrase(402) -> <<"Payment Required">>;
+code_to_phrase(403) -> <<"Forbidden">>;
+code_to_phrase(404) -> <<"Not Found">>;
+code_to_phrase(405) -> <<"Method Not Allowed">>;
+code_to_phrase(406) -> <<"Not Acceptable">>;
+code_to_phrase(407) ->
+ <<"Proxy Authentication Required">>;
+code_to_phrase(408) -> <<"Request Timeout">>;
+code_to_phrase(409) -> <<"Conflict">>;
+code_to_phrase(410) -> <<"Gone">>;
+code_to_phrase(411) -> <<"Length Required">>;
+code_to_phrase(412) -> <<"Precondition Failed">>;
+code_to_phrase(413) -> <<"Request Entity Too Large">>;
+code_to_phrase(414) -> <<"Request-URI Too Long">>;
+code_to_phrase(415) -> <<"Unsupported Media Type">>;
+code_to_phrase(416) ->
+ <<"Requested Range Not Satisfiable">>;
+code_to_phrase(417) -> <<"Expectation Failed">>;
+code_to_phrase(500) -> <<"Internal Server Error">>;
+code_to_phrase(501) -> <<"Not Implemented">>;
+code_to_phrase(502) -> <<"Bad Gateway">>;
+code_to_phrase(503) -> <<"Service Unavailable">>;
+code_to_phrase(504) -> <<"Gateway Timeout">>;
+code_to_phrase(505) -> <<"HTTP Version Not Supported">>.
+
+parse_auth(<<"Basic ", Auth64/binary>>) ->
+ Auth = jlib:decode_base64(Auth64),
+ %% Auth should be a string with the format: user@server:password
+ %% Note that password can contain additional characters '@' and ':'
+ case str:chr(Auth, $:) of
+ 0 ->
+ undefined;
+ Pos ->
+ {User, <<$:, Pass/binary>>} = erlang:split_binary(Auth, Pos-1),
+ {User, Pass}
end;
-parse_auth(_) ->
- undefined.
-
-
-
-decode_base64([]) ->
- [];
-decode_base64([Sextet1,Sextet2,$=,$=|Rest]) ->
- Bits2x6=
- (d(Sextet1) bsl 18) bor
- (d(Sextet2) bsl 12),
- Octet1=Bits2x6 bsr 16,
- [Octet1|decode_base64(Rest)];
-decode_base64([Sextet1,Sextet2,Sextet3,$=|Rest]) ->
- Bits3x6=
- (d(Sextet1) bsl 18) bor
- (d(Sextet2) bsl 12) bor
- (d(Sextet3) bsl 6),
- Octet1=Bits3x6 bsr 16,
- Octet2=(Bits3x6 bsr 8) band 16#ff,
- [Octet1,Octet2|decode_base64(Rest)];
-decode_base64([Sextet1,Sextet2,Sextet3,Sextet4|Rest]) ->
- Bits4x6=
- (d(Sextet1) bsl 18) bor
- (d(Sextet2) bsl 12) bor
- (d(Sextet3) bsl 6) bor
- d(Sextet4),
- Octet1=Bits4x6 bsr 16,
- Octet2=(Bits4x6 bsr 8) band 16#ff,
- Octet3=Bits4x6 band 16#ff,
- [Octet1,Octet2,Octet3|decode_base64(Rest)];
-decode_base64(_CatchAll) ->
- {error, bad_base64}.
-
-d(X) when X >= $A, X =<$Z ->
- X-65;
-d(X) when X >= $a, X =<$z ->
- X-71;
-d(X) when X >= $0, X =<$9 ->
- X+4;
-d($+) -> 62;
-d($/) -> 63;
-d(_) -> 63.
-
+parse_auth(<<_/binary>>) -> undefined.
parse_urlencoded(S) ->
- parse_urlencoded(S, nokey, [], key).
+ parse_urlencoded(S, nokey, <<>>, key).
-parse_urlencoded([$%, Hi, Lo | Tail], Last, Cur, State) ->
+parse_urlencoded(<<$%, Hi, Lo, Tail/binary>>, Last, Cur,
+ State) ->
Hex = hex_to_integer([Hi, Lo]),
- parse_urlencoded(Tail, Last, [Hex | Cur], State);
-
-parse_urlencoded([$& | Tail], _Last, Cur, key) ->
- [{lists:reverse(Cur), ""} |
- parse_urlencoded(Tail, nokey, [], key)]; %% cont keymode
-
-parse_urlencoded([$& | Tail], Last, Cur, value) ->
- V = {Last, lists:reverse(Cur)},
- [V | parse_urlencoded(Tail, nokey, [], key)];
-
-parse_urlencoded([$+ | Tail], Last, Cur, State) ->
- parse_urlencoded(Tail, Last, [$\s | Cur], State);
-
-parse_urlencoded([$= | Tail], _Last, Cur, key) ->
- parse_urlencoded(Tail, lists:reverse(Cur), [], value); %% change mode
-
-parse_urlencoded([H | Tail], Last, Cur, State) ->
- parse_urlencoded(Tail, Last, [H|Cur], State);
-
-parse_urlencoded([], Last, Cur, _State) ->
- [{Last, lists:reverse(Cur)}];
-
-parse_urlencoded(undefined, _, _, _) ->
- [].
-
-
-url_encode([H|T]) ->
- if
- H >= $a, $z >= H ->
- [H|url_encode(T)];
- H >= $A, $Z >= H ->
- [H|url_encode(T)];
- H >= $0, $9 >= H ->
- [H|url_encode(T)];
- H == $_; H == $.; H == $-; H == $/; H == $: -> % FIXME: more..
- [H|url_encode(T)];
- true ->
- case integer_to_hex(H) of
- [X, Y] ->
- [$%, X, Y | url_encode(T)];
- [X] ->
- [$%, $0, X | url_encode(T)]
- end
+ parse_urlencoded(Tail, Last, <<Cur/binary, Hex>>, State);
+parse_urlencoded(<<$&, Tail/binary>>, _Last, Cur, key) ->
+ [{Cur, <<"">>} | parse_urlencoded(Tail,
+ nokey, <<>>,
+ key)]; %% cont keymode
+parse_urlencoded(<<$&, Tail/binary>>, Last, Cur, value) ->
+ V = {Last, Cur},
+ [V | parse_urlencoded(Tail, nokey, <<>>, key)];
+parse_urlencoded(<<$+, Tail/binary>>, Last, Cur, State) ->
+ parse_urlencoded(Tail, Last, <<Cur/binary, $\s>>, State);
+parse_urlencoded(<<$=, Tail/binary>>, _Last, Cur, key) ->
+ parse_urlencoded(Tail, Cur, <<>>,
+ value); %% change mode
+parse_urlencoded(<<H, Tail/binary>>, Last, Cur, State) ->
+ parse_urlencoded(Tail, Last, <<Cur/binary, H>>, State);
+parse_urlencoded(<<>>, Last, Cur, _State) ->
+ [{Last, Cur}];
+parse_urlencoded(undefined, _, _, _) -> [].
+
+
+url_encode(A) ->
+ url_encode(A, <<>>).
+
+url_encode(<<H:8, T/binary>>, Acc) when
+ (H >= $a andalso H =< $z) orelse
+ (H >= $A andalso H =< $Z) orelse
+ (H >= $0 andalso H =< $9) orelse
+ H == $_ orelse
+ H == $. orelse
+ H == $- orelse
+ H == $/ orelse
+ H == $: ->
+ url_encode(T, <<Acc/binary, H>>);
+url_encode(<<H:8, T/binary>>, Acc) ->
+ case integer_to_hex(H) of
+ [X, Y] -> url_encode(T, <<Acc/binary, $%, X, Y>>);
+ [X] -> url_encode(T, <<Acc/binary, $%, $0, X>>)
end;
+url_encode(<<>>, Acc) ->
+ Acc.
-url_encode([]) ->
- [].
integer_to_hex(I) ->
case catch erlang:integer_to_list(I, 16) of
- {'EXIT', _} ->
- old_integer_to_hex(I);
- Int ->
- Int
+ {'EXIT', _} -> old_integer_to_hex(I);
+ Int -> Int
end.
-
-old_integer_to_hex(I) when I<10 ->
- integer_to_list(I);
-old_integer_to_hex(I) when I<16 ->
- [I-10+$A];
-old_integer_to_hex(I) when I>=16 ->
- N = trunc(I/16),
+old_integer_to_hex(I) when I < 10 -> integer_to_list(I);
+old_integer_to_hex(I) when I < 16 -> [I - 10 + $A];
+old_integer_to_hex(I) when I >= 16 ->
+ N = trunc(I / 16),
old_integer_to_hex(N) ++ old_integer_to_hex(I rem 16).
-
% The following code is mostly taken from yaws_ssl.erl
-extract_line(_, <<>>, _) ->
- none;
-extract_line(0, <<"\r", Rest/binary>>, Line) ->
- extract_line(1, Rest, Line);
-extract_line(0, <<A:8, Rest/binary>>, Line) ->
- extract_line(0, Rest, <<Line/binary, A>>);
-extract_line(1, <<"\n", Rest/binary>>, Line) ->
- {Line, Rest};
-extract_line(1, Data, Line) ->
- extract_line(0, Data, <<Line/binary, "\r">>).
-
-decode_packet(_, <<"\r\n", Rest/binary>>) ->
- {ok, http_eoh, Rest};
-decode_packet(Type, Data) ->
- case extract_line(0, Data, <<>>) of
- {LineB, Rest} ->
- Line = binary_to_list(LineB),
- Result = case Type of
- http ->
- parse_req(Line);
- httph ->
- parse_header_line(Line)
- end,
- case Result of
- {ok, H} ->
- {ok, H, Rest};
- Err ->
- {error, Err}
- end;
- _ ->
- {more, undefined}
- end.
-
-get_word(Line)->
- {Word, T} = lists:splitwith(fun(X)-> X /= $\ end, Line),
- {Word, lists:dropwhile(fun(X) -> X == $\ end, T)}.
-
+toupper(C) when C >= $a andalso C =< $z -> C - 32;
+toupper(C) -> C.
-parse_req(Line) ->
- {MethodStr, L1} = get_word(Line),
- ?DEBUG("Method: ~p~n", [MethodStr]),
- case L1 of
- [] ->
- bad_request;
- _ ->
- {URI, L2} = get_word(L1),
- {VersionStr, L3} = get_word(L2),
- ?DEBUG("URI: ~p~nVersion: ~p~nL3: ~p~n",
- [URI, VersionStr, L3]),
- case L3 of
- [] ->
- Method = case MethodStr of
- "GET" -> 'GET';
- "POST" -> 'POST';
- "HEAD" -> 'HEAD';
- "OPTIONS" -> 'OPTIONS';
- "TRACE" -> 'TRACE';
- "PUT" -> 'PUT';
- "DELETE" -> 'DELETE';
- S -> S
- end,
- Path = case URI of
- "*" ->
- % Is this correct?
- "*";
- _ ->
- case string:str(URI, "://") of
- 0 ->
- % Relative URI
- % ex: /index.html
- {abs_path, URI};
- N ->
- % Absolute URI
- % ex: http://localhost/index.html
+tolower(C) when C >= $A andalso C =< $Z -> C + 32;
+tolower(C) -> C.
- % Remove scheme
- % ex: URI2 = localhost/index.html
- URI2 = string:substr(URI, N + 3),
- % Look for the start of the path
- % (or the lack of a path thereof)
- case string:chr(URI2, $/) of
- 0 -> {abs_path, "/"};
- M -> {abs_path,
- string:substr(URI2, M + 1)}
- end
- end
- end,
- case VersionStr of
- [] ->
- {ok, {http_request, Method, Path, {0,9}}};
- "HTTP/1.0" ->
- {ok, {http_request, Method, Path, {1,0}}};
- "HTTP/1.1" ->
- {ok, {http_request, Method, Path, {1,1}}};
- _ ->
- bad_request
- end;
- _ ->
- bad_request
- end
- end.
+normalize_header_name(Name) ->
+ normalize_header_name(Name, [], true).
-
-toupper(C) when C >= $a andalso C =< $z ->
- C - 32;
-toupper(C) ->
- C.
-
-tolower(C) when C >= $A andalso C =< $Z ->
- C + 32;
-tolower(C) ->
- C.
-
-
-parse_header_line(Line) ->
- parse_header_line(Line, "", true).
-
-parse_header_line("", _, _) ->
- bad_request;
-parse_header_line(":" ++ Rest, Name, _) ->
- encode_header(lists:reverse(Name), Rest);
-parse_header_line("-" ++ Rest, Name, _) ->
- parse_header_line(Rest, "-" ++ Name, true);
-parse_header_line([C | Rest], Name, true) ->
- parse_header_line(Rest, [toupper(C) | Name], false);
-parse_header_line([C | Rest], Name, false) ->
- parse_header_line(Rest, [tolower(C) | Name], false).
-
-
-encode_header("Connection", Con) ->
- {ok, {http_header, undefined, 'Connection', undefined, strip_spaces(Con)}};
-encode_header("Host", Con) ->
- {ok, {http_header, undefined, 'Host', undefined, strip_spaces(Con)}};
-encode_header("Accept", Con) ->
- {ok, {http_header, undefined, 'Accept', undefined, strip_spaces(Con)}};
-encode_header("If-Modified-Since", Con) ->
- {ok, {http_header, undefined, 'If-Modified-Since', undefined, strip_spaces(Con)}};
-encode_header("If-Match", Con) ->
- {ok, {http_header, undefined, 'If-Match', undefined, strip_spaces(Con)}};
-encode_header("If-None-Match", Con) ->
- {ok, {http_header, undefined, 'If-None-Match', undefined, strip_spaces(Con)}};
-encode_header("If-Range", Con) ->
- {ok, {http_header, undefined, 'If-Range', undefined, strip_spaces(Con)}};
-encode_header("If-Unmodified-Since", Con) ->
- {ok, {http_header, undefined, 'If-Unmodified-Since', undefined, strip_spaces(Con)}};
-encode_header("Range", Con) ->
- {ok, {http_header, undefined, 'Range', undefined, strip_spaces(Con)}};
-encode_header("User-Agent", Con) ->
- {ok, {http_header, undefined, 'User-Agent', undefined, strip_spaces(Con)}};
-encode_header("Accept-Ranges", Con) ->
- {ok, {http_header, undefined, 'Accept-Ranges', undefined, strip_spaces(Con)}};
-encode_header("Authorization", Con) ->
- {ok, {http_header, undefined, 'Authorization', undefined, strip_spaces(Con)}};
-encode_header("Keep-Alive", Con) ->
- {ok, {http_header, undefined, 'Keep-Alive', undefined, strip_spaces(Con)}};
-encode_header("Referer", Con) ->
- {ok, {http_header, undefined, 'Referer', undefined, strip_spaces(Con)}};
-encode_header("Content-Type", Con) ->
- {ok, {http_header, undefined, 'Content-Type', undefined, strip_spaces(Con)}};
-encode_header("Content-Length", Con) ->
- {ok, {http_header, undefined, 'Content-Length', undefined, strip_spaces(Con)}};
-encode_header("Cookie", Con) ->
- {ok, {http_header, undefined, 'Cookie', undefined, strip_spaces(Con)}};
-encode_header("Accept-Language", Con) ->
- {ok, {http_header, undefined, 'Accept-Language', undefined, strip_spaces(Con)}};
-encode_header("Accept-Encoding", Con) ->
- {ok, {http_header, undefined, 'Accept-Encoding', undefined, strip_spaces(Con)}};
-encode_header(Name, Val) ->
- {ok, {http_header, undefined, Name, undefined, strip_spaces(Val)}}.
-
-
-is_space($\s) ->
- true;
-is_space($\r) ->
- true;
-is_space($\n) ->
- true;
-is_space($\t) ->
- true;
-is_space(_) ->
- false.
-
-
-strip_spaces(String) ->
- strip_spaces(String, both).
+normalize_header_name(<<"">>, Acc, _) ->
+ iolist_to_binary(Acc);
+normalize_header_name(<<"-", Rest/binary>>, Acc, _) ->
+ normalize_header_name(Rest, [Acc, "-"], true);
+normalize_header_name(<<C:8, Rest/binary>>, Acc, true) ->
+ normalize_header_name(Rest, [Acc, toupper(C)], false);
+normalize_header_name(<<C:8, Rest/binary>>, Acc, false) ->
+ normalize_header_name(Rest, [Acc, tolower(C)], false).
%% strip_spaces(String, left) ->
%% drop_spaces(String);
-strip_spaces(String, right) ->
- lists:reverse(drop_spaces(lists:reverse(String)));
-strip_spaces(String, both) ->
- strip_spaces(drop_spaces(String), right).
-
-drop_spaces([]) ->
- [];
-drop_spaces(YS=[X|XS]) ->
- case is_space(X) of
- true ->
- drop_spaces(XS);
- false ->
- YS
- end.
diff --git a/src/web/ejabberd_http.hrl b/src/web/ejabberd_http.hrl
index 4f3dd17fc..542dc0cdb 100644
--- a/src/web/ejabberd_http.hrl
+++ b/src/web/ejabberd_http.hrl
@@ -19,16 +19,18 @@
%%%
%%%----------------------------------------------------------------------
--record(request, {method,
- path,
- q = [],
- us,
- auth,
- lang = "",
- data = "",
- ip,
- host, % string()
- port, % integer()
- tp, % transfer protocol = http | https
- headers
- }).
+-record(request,
+ {method, % :: method(),
+ path = [] :: [binary()],
+ q = [] :: [{binary() | nokey, binary()}],
+ us = {<<>>, <<>>} :: {binary(), binary()},
+ auth :: {binary(), binary()} |
+ {auth_jid, {binary(), binary()}, jlib:jid()},
+ lang = <<"">> :: binary(),
+ data = <<"">> :: binary(),
+ ip :: {inet:ip_address(), inet:port_number()},
+ host = <<"">> :: binary(),
+ port = 5280 :: inet:port_number(),
+ tp = http, % :: protocol(),
+ headers = [] :: [{atom() | binary(), binary()}]}).
+
diff --git a/src/web/ejabberd_http_bind.erl b/src/web/ejabberd_http_bind.erl
index 02b8d27b0..913291672 100644
--- a/src/web/ejabberd_http_bind.erl
+++ b/src/web/ejabberd_http_bind.erl
@@ -41,11 +41,15 @@
process_request/2]).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
+
-include("ejabberd_http.hrl").
+
-include("http_bind.hrl").
--record(http_bind, {id, pid, to, hold, wait, process_delay, version}).
+-record(http_bind,
+ {id, pid, to, hold, wait, process_delay, version}).
-define(NULL_PEER, {{0, 0, 0, 0}, 0}).
@@ -89,28 +93,39 @@
%%-define(DBGFSM, true).
-ifdef(DBGFSM).
+
-define(FSMOPTS, [{debug, [trace]}]).
+
-else.
+
-define(FSMOPTS, []).
+
-endif.
--define(BOSH_VERSION, "1.8").
--define(NS_CLIENT, "jabber:client").
--define(NS_BOSH, "urn:xmpp:xbosh").
--define(NS_HTTP_BIND, "http://jabber.org/protocol/httpbind").
+%% Wait 100ms before continue processing, to allow the client provide more related stanzas.
+-define(BOSH_VERSION, <<"1.8">>).
--define(MAX_REQUESTS, 2). % number of simultaneous requests
--define(MIN_POLLING, 2000000). % don't poll faster than that or we will
- % shoot you (time in microsec)
--define(MAX_WAIT, 3600). % max num of secs to keep a request on hold
--define(MAX_INACTIVITY, 30000). % msecs to wait before terminating
- % idle sessions
--define(MAX_PAUSE, 120). % may num of sec a client is allowed to pause
- % the session
+-define(NS_CLIENT, <<"jabber:client">>).
+
+-define(NS_BOSH, <<"urn:xmpp:xbosh">>).
+
+-define(NS_HTTP_BIND,
+ <<"http://jabber.org/protocol/httpbind">>).
+
+-define(MAX_REQUESTS, 2).
+
+-define(MIN_POLLING, 2000000).
+
+-define(MAX_WAIT, 3600).
+
+-define(MAX_INACTIVITY, 30000).
+
+-define(MAX_PAUSE, 120).
-%% Wait 100ms before continue processing, to allow the client provide more related stanzas.
-define(PROCESS_DELAY_DEFAULT, 100).
+
-define(PROCESS_DELAY_MIN, 0).
+
-define(PROCESS_DELAY_MAX, 1000).
%% Line copied from mod_http_bind.erl
@@ -134,10 +149,12 @@ start_link(Sid, Key, IP) ->
gen_fsm:start_link(?MODULE, [Sid, Key, IP], ?FSMOPTS).
send({http_bind, FsmRef, _IP}, Packet) ->
- gen_fsm:sync_send_all_state_event(FsmRef, {send, Packet}).
+ gen_fsm:sync_send_all_state_event(FsmRef,
+ {send, Packet}).
send_xml({http_bind, FsmRef, _IP}, Packet) ->
- gen_fsm:sync_send_all_state_event(FsmRef, {send_xml, Packet}).
+ gen_fsm:sync_send_all_state_event(FsmRef,
+ {send_xml, Packet}).
setopts({http_bind, FsmRef, _IP}, Opts) ->
case lists:member({active, once}, Opts) of
@@ -147,142 +164,143 @@ setopts({http_bind, FsmRef, _IP}, Opts) ->
ok
end.
-controlling_process(_Socket, _Pid) ->
- ok.
+controlling_process(_Socket, _Pid) -> ok.
custom_receiver({http_bind, FsmRef, _IP}) ->
{receiver, ?MODULE, FsmRef}.
become_controller(FsmRef, C2SPid) ->
- gen_fsm:send_all_state_event(FsmRef, {become_controller, C2SPid}).
+ gen_fsm:send_all_state_event(FsmRef,
+ {become_controller, C2SPid}).
reset_stream({http_bind, _FsmRef, _IP}) ->
ok.
change_shaper({http_bind, FsmRef, _IP}, Shaper) ->
- gen_fsm:send_all_state_event(FsmRef, {change_shaper, Shaper}).
+ gen_fsm:send_all_state_event(FsmRef,
+ {change_shaper, Shaper}).
monitor({http_bind, FsmRef, _IP}) ->
erlang:monitor(process, FsmRef).
close({http_bind, FsmRef, _IP}) ->
- catch gen_fsm:sync_send_all_state_event(FsmRef, {stop, close}).
+ catch gen_fsm:sync_send_all_state_event(FsmRef,
+ {stop, close}).
-sockname(_Socket) ->
- {ok, ?NULL_PEER}.
+sockname(_Socket) -> {ok, ?NULL_PEER}.
-peername({http_bind, _FsmRef, IP}) ->
- {ok, IP}.
+peername({http_bind, _FsmRef, IP}) -> {ok, IP}.
%% Entry point for data coming from client through ejabberd HTTP server:
process_request(Data, IP) ->
Opts1 = ejabberd_c2s_config:get_c2s_limits(),
Opts = [{xml_socket, true} | Opts1],
- MaxStanzaSize =
- case lists:keysearch(max_stanza_size, 1, Opts) of
- {value, {_, Size}} -> Size;
- _ -> infinity
- end,
+ MaxStanzaSize = case lists:keysearch(max_stanza_size, 1,
+ Opts)
+ of
+ {value, {_, Size}} -> Size;
+ _ -> infinity
+ end,
PayloadSize = iolist_size(Data),
- case catch parse_request(Data, PayloadSize, MaxStanzaSize) of
- %% No existing session:
- {ok, {"", Rid, Attrs, Payload}} ->
- case xml:get_attr_s("to",Attrs) of
- "" ->
- ?DEBUG("Session not created (Improper addressing)", []),
- {200, ?HEADER, "<body type='terminate' "
- "condition='improper-addressing' "
- "xmlns='" ++ ?NS_HTTP_BIND ++ "'/>"};
- XmppDomain ->
- %% create new session
- Sid = sha:sha(term_to_binary({now(), make_ref()})),
- case start(XmppDomain, Sid, "", IP) of
- {error, _} ->
- {200, ?HEADER, "<body type='terminate' "
- "condition='internal-server-error' "
- "xmlns='" ++ ?NS_HTTP_BIND ++ "'>BOSH module not started</body>"};
- {ok, Pid} ->
- handle_session_start(
- Pid, XmppDomain, Sid, Rid, Attrs,
- Payload, PayloadSize, IP)
- end
- end;
- %% Existing session
- {ok, {Sid, Rid, Attrs, Payload1}} ->
- StreamStart =
- case xml:get_attr_s("xmpp:restart",Attrs) of
- "true" ->
- true;
- _ ->
- false
- end,
- Payload2 = case xml:get_attr_s("type",Attrs) of
- "terminate" ->
- %% close stream
- Payload1 ++ [{xmlstreamend, "stream:stream"}];
- _ ->
- Payload1
- end,
- handle_http_put(Sid, Rid, Attrs, Payload2, PayloadSize,
- StreamStart, IP);
- {size_limit, Sid} ->
- case mnesia:dirty_read({http_bind, Sid}) of
- [] ->
- {404, ?HEADER, ""};
- [#http_bind{pid = FsmRef}] ->
- gen_fsm:sync_send_all_state_event(FsmRef, {stop, close}),
- {200, ?HEADER, "<body type='terminate' "
- "condition='undefined-condition' "
- "xmlns='" ++ ?NS_HTTP_BIND ++ "'>Request Too Large</body>"}
- end;
- _ ->
- ?DEBUG("Received bad request: ~p", [Data]),
- {400, ?HEADER, ""}
+ case catch parse_request(Data, PayloadSize,
+ MaxStanzaSize)
+ of
+ %% No existing session:
+ {ok, {<<"">>, Rid, Attrs, Payload}} ->
+ case xml:get_attr_s(<<"to">>, Attrs) of
+ <<"">> ->
+ ?DEBUG("Session not created (Improper addressing)", []),
+ {200, ?HEADER,
+ <<"<body type='terminate' condition='improper-ad"
+ "dressing' xmlns='",
+ (?NS_HTTP_BIND)/binary, "'/>">>};
+ XmppDomain ->
+ Sid = sha:sha(term_to_binary({now(), make_ref()})),
+ case start(XmppDomain, Sid, <<"">>, IP) of
+ {error, _} ->
+ {500, ?HEADER,
+ <<"<body type='terminate' condition='internal-se"
+ "rver-error' xmlns='",
+ (?NS_HTTP_BIND)/binary,
+ "'>Internal Server Error</body>">>};
+ {ok, Pid} ->
+ handle_session_start(Pid, XmppDomain, Sid, Rid, Attrs,
+ Payload, PayloadSize, IP)
+ end
+ end;
+ %% Existing session
+ {ok, {Sid, Rid, Attrs, Payload1}} ->
+ StreamStart = case xml:get_attr_s(<<"xmpp:restart">>,
+ Attrs)
+ of
+ <<"true">> -> true;
+ _ -> false
+ end,
+ Payload2 = case xml:get_attr_s(<<"type">>, Attrs) of
+ <<"terminate">> ->
+ Payload1 ++ [{xmlstreamend, <<"stream:stream">>}];
+ _ -> Payload1
+ end,
+ handle_http_put(Sid, Rid, Attrs, Payload2, PayloadSize,
+ StreamStart, IP);
+ {size_limit, Sid} ->
+ case mnesia:dirty_read({http_bind, Sid}) of
+ {error, _} -> {404, ?HEADER, <<"">>};
+ {ok, #http_bind{pid = FsmRef}} ->
+ gen_fsm:sync_send_all_state_event(FsmRef,
+ {stop, close}),
+ {200, ?HEADER,
+ <<"<body type='terminate' condition='undefined-c"
+ "ondition' xmlns='",
+ (?NS_HTTP_BIND)/binary, "'>Request Too Large</body>">>}
+ end;
+ _ ->
+ ?DEBUG("Received bad request: ~p", [Data]),
+ {400, ?HEADER, <<"">>}
end.
handle_session_start(Pid, XmppDomain, Sid, Rid, Attrs,
Payload, PayloadSize, IP) ->
?DEBUG("got pid: ~p", [Pid]),
- Wait = case string:to_integer(xml:get_attr_s("wait",Attrs)) of
- {error, _} ->
- ?MAX_WAIT;
- {CWait, _} ->
- if
- (CWait > ?MAX_WAIT) ->
- ?MAX_WAIT;
- true ->
- CWait
- end
+ Wait = case str:to_integer(xml:get_attr_s(<<"wait">>,
+ Attrs))
+ of
+ {error, _} -> ?MAX_WAIT;
+ {CWait, _} ->
+ if CWait > (?MAX_WAIT) -> ?MAX_WAIT;
+ true -> CWait
+ end
end,
- Hold = case string:to_integer(xml:get_attr_s("hold",Attrs)) of
- {error, _} ->
- (?MAX_REQUESTS - 1);
- {CHold, _} ->
- if
- (CHold > (?MAX_REQUESTS - 1)) ->
- (?MAX_REQUESTS - 1);
- true ->
- CHold
- end
+ Hold = case str:to_integer(xml:get_attr_s(<<"hold">>,
+ Attrs))
+ of
+ {error, _} -> (?MAX_REQUESTS) - 1;
+ {CHold, _} ->
+ if CHold > (?MAX_REQUESTS) - 1 -> (?MAX_REQUESTS) - 1;
+ true -> CHold
+ end
end,
- Pdelay = case string:to_integer(xml:get_attr_s("process-delay",Attrs)) of
- {error, _} ->
- ?PROCESS_DELAY_DEFAULT;
- {CPdelay, _} when
- (?PROCESS_DELAY_MIN =< CPdelay) and
- (CPdelay =< ?PROCESS_DELAY_MAX) ->
- CPdelay;
- {CPdelay, _} ->
- lists:max([lists:min([CPdelay, ?PROCESS_DELAY_MAX]), ?PROCESS_DELAY_MIN])
+ Pdelay = case
+ str:to_integer(xml:get_attr_s(<<"process-delay">>,
+ Attrs))
+ of
+ {error, _} -> ?PROCESS_DELAY_DEFAULT;
+ {CPdelay, _}
+ when ((?PROCESS_DELAY_MIN) =< CPdelay) and
+ (CPdelay =< (?PROCESS_DELAY_MAX)) ->
+ CPdelay;
+ {CPdelay, _} ->
+ lists:max([lists:min([CPdelay, ?PROCESS_DELAY_MAX]),
+ ?PROCESS_DELAY_MIN])
end,
- Version =
- case catch list_to_float(
- xml:get_attr_s("ver", Attrs)) of
- {'EXIT', _} -> 0.0;
- V -> V
- end,
- XmppVersion = xml:get_attr_s("xmpp:version", Attrs),
+ Version = case catch
+ list_to_float(binary_to_list(xml:get_attr_s(<<"ver">>, Attrs)))
+ of
+ {'EXIT', _} -> 0.0;
+ V -> V
+ end,
+ XmppVersion = xml:get_attr_s(<<"xmpp:version">>, Attrs),
?DEBUG("Create session: ~p", [Sid]),
mnesia:dirty_write(
#http_bind{id = Sid,
@@ -309,16 +327,8 @@ handle_session_start(Pid, XmppDomain, Sid, Rid, Attrs,
%%----------------------------------------------------------------------
init([Sid, Key, IP]) ->
?DEBUG("started: ~p", [{Sid, Key, IP}]),
-
- %% Read c2s options from the first ejabberd_c2s configuration in
- %% the config file listen section
- %% TODO: We should have different access and shaper values for
- %% each connector. The default behaviour should be however to use
- %% the default c2s restrictions if not defined for the current
- %% connector.
Opts1 = ejabberd_c2s_config:get_c2s_limits(),
Opts = [{xml_socket, true} | Opts1],
-
Shaper = none,
ShaperState = shaper:new(Shaper),
Socket = {http_bind, self(), IP},
@@ -340,22 +350,21 @@ init([Sid, Key, IP]) ->
%%----------------------------------------------------------------------
handle_event({become_controller, C2SPid}, StateName, StateData) ->
case StateData#state.input of
- cancel ->
- {next_state, StateName, StateData#state{
- waiting_input = C2SPid}};
- Input ->
- lists:foreach(
- fun(Event) ->
- C2SPid ! Event
- end, queue:to_list(Input)),
- {next_state, StateName, StateData#state{
- input = queue:new(),
- waiting_input = C2SPid}}
+ cancel ->
+ {next_state, StateName,
+ StateData#state{waiting_input = C2SPid}};
+ Input ->
+ lists:foreach(fun (Event) -> C2SPid ! Event end,
+ queue:to_list(Input)),
+ {next_state, StateName,
+ StateData#state{input = queue:new(),
+ waiting_input = C2SPid}}
end;
-
-handle_event({change_shaper, Shaper}, StateName, StateData) ->
+handle_event({change_shaper, Shaper}, StateName,
+ StateData) ->
NewShaperState = shaper:new(Shaper),
- {next_state, StateName, StateData#state{shaper_state = NewShaperState}};
+ {next_state, StateName,
+ StateData#state{shaper_state = NewShaperState}};
handle_event(_Event, StateName, StateData) ->
{next_state, StateName, StateData}.
@@ -372,13 +381,16 @@ handle_sync_event({send_xml, Packet}, _From, StateName,
#state{http_receiver = undefined} = StateData) ->
Output = [Packet | StateData#state.output],
Reply = ok,
- {reply, Reply, StateName, StateData#state{output = Output}};
+ {reply, Reply, StateName,
+ StateData#state{output = Output}};
handle_sync_event({send_xml, Packet}, _From, StateName,
#state{out_of_order_receiver = true} = StateData) ->
Output = [Packet | StateData#state.output],
Reply = ok,
- {reply, Reply, StateName, StateData#state{output = Output}};
-handle_sync_event({send_xml, Packet}, _From, StateName, StateData) ->
+ {reply, Reply, StateName,
+ StateData#state{output = Output}};
+handle_sync_event({send_xml, Packet}, _From, StateName,
+ StateData) ->
Output = [Packet | StateData#state.output],
cancel_timer(StateData#state.timer),
Timer = set_inactivity_timer(StateData#state.pause,
@@ -387,19 +399,14 @@ handle_sync_event({send_xml, Packet}, _From, StateName, StateData) ->
gen_fsm:reply(StateData#state.http_receiver, HTTPReply),
cancel_timer(StateData#state.wait_timer),
Rid = StateData#state.rid,
- ReqList = [#hbr{rid = Rid,
- key = StateData#state.key,
- out = Output
- } |
- [El || El <- StateData#state.req_list,
- El#hbr.rid /= Rid ]
- ],
+ ReqList = [#hbr{rid = Rid, key = StateData#state.key,
+ out = Output}
+ | [El
+ || El <- StateData#state.req_list, El#hbr.rid /= Rid]],
Reply = ok,
{reply, Reply, StateName,
- StateData#state{output = [],
- http_receiver = undefined,
- req_list = ReqList,
- wait_timer = undefined,
+ StateData#state{output = [], http_receiver = undefined,
+ req_list = ReqList, wait_timer = undefined,
timer = Timer}};
handle_sync_event({stop,close}, _From, _StateName, StateData) ->
@@ -412,32 +419,30 @@ handle_sync_event({stop,Reason}, _From, _StateName, StateData) ->
?DEBUG("Closing bind session ~p - Reason: ~p", [StateData#state.id, Reason]),
Reply = ok,
{stop, normal, Reply, StateData};
-
%% HTTP PUT: Receive packets from the client
-handle_sync_event(#http_put{rid = Rid},
- _From, StateName, StateData)
- when StateData#state.shaper_timer /= undefined ->
- Pause =
- case erlang:read_timer(StateData#state.shaper_timer) of
- false ->
- 0;
- P -> P
- end,
+handle_sync_event(#http_put{rid = Rid}, _From,
+ StateName, StateData)
+ when StateData#state.shaper_timer /= undefined ->
+ Pause = case
+ erlang:read_timer(StateData#state.shaper_timer)
+ of
+ false -> 0;
+ P -> P
+ end,
Reply = {wait, Pause},
?DEBUG("Shaper timer for RID ~p: ~p", [Rid, Reply]),
{reply, Reply, StateName, StateData};
-
-handle_sync_event(#http_put{payload_size = PayloadSize} = Request,
+handle_sync_event(#http_put{payload_size =
+ PayloadSize} =
+ Request,
_From, StateName, StateData) ->
- ?DEBUG("New request: ~p",[Request]),
- %% Updating trafic shaper
+ ?DEBUG("New request: ~p", [Request]),
{NewShaperState, NewShaperTimer} =
- update_shaper(StateData#state.shaper_state, PayloadSize),
-
+ update_shaper(StateData#state.shaper_state,
+ PayloadSize),
handle_http_put_event(Request, StateName,
StateData#state{shaper_state = NewShaperState,
shaper_timer = NewShaperTimer});
-
%% HTTP GET: send packets to the client
handle_sync_event({http_get, Rid, Wait, Hold}, From, StateName, StateData) ->
%% setup timer
@@ -490,14 +495,13 @@ handle_sync_event({http_get, Rid, Wait, Hold}, From, StateName, StateData) ->
req_list = ReqList}}
end
end;
-
-handle_sync_event(peername, _From, StateName, StateData) ->
+handle_sync_event(peername, _From, StateName,
+ StateData) ->
Reply = {ok, StateData#state.ip},
{reply, Reply, StateName, StateData};
-
-handle_sync_event(_Event, _From, StateName, StateData) ->
- Reply = ok,
- {reply, Reply, StateName, StateData}.
+handle_sync_event(_Event, _From, StateName,
+ StateData) ->
+ Reply = ok, {reply, Reply, StateName, StateData}.
code_change(_OldVsn, StateName, StateData, _Extra) ->
{ok, StateName, StateData}.
@@ -510,35 +514,30 @@ code_change(_OldVsn, StateName, StateData, _Extra) ->
%%----------------------------------------------------------------------
%% We reached the max_inactivity timeout:
handle_info({timeout, Timer, _}, _StateName,
- #state{id=SID, timer = Timer} = StateData) ->
- ?INFO_MSG("Session timeout. Closing the HTTP bind session: ~p", [SID]),
+ #state{id = SID, timer = Timer} = StateData) ->
+ ?INFO_MSG("Session timeout. Closing the HTTP bind "
+ "session: ~p",
+ [SID]),
{stop, normal, StateData};
-
handle_info({timeout, WaitTimer, _}, StateName,
#state{wait_timer = WaitTimer} = StateData) ->
- if
- StateData#state.http_receiver /= undefined ->
- cancel_timer(StateData#state.timer),
- Timer = set_inactivity_timer(StateData#state.pause,
- StateData#state.max_inactivity),
- gen_fsm:reply(StateData#state.http_receiver, {ok, empty}),
- Rid = StateData#state.rid,
- ReqList = [#hbr{rid = Rid,
- key = StateData#state.key,
- out = []
- } |
- [El || El <- StateData#state.req_list,
- El#hbr.rid /= Rid ]
- ],
- {next_state, StateName,
- StateData#state{http_receiver = undefined,
- req_list = ReqList,
- wait_timer = undefined,
- timer = Timer}};
- true ->
- {next_state, StateName, StateData}
+ if StateData#state.http_receiver /= undefined ->
+ cancel_timer(StateData#state.timer),
+ Timer = set_inactivity_timer(StateData#state.pause,
+ StateData#state.max_inactivity),
+ gen_fsm:reply(StateData#state.http_receiver,
+ {ok, empty}),
+ Rid = StateData#state.rid,
+ ReqList = [#hbr{rid = Rid, key = StateData#state.key,
+ out = []}
+ | [El
+ || El <- StateData#state.req_list, El#hbr.rid /= Rid]],
+ {next_state, StateName,
+ StateData#state{http_receiver = undefined,
+ req_list = ReqList, wait_timer = undefined,
+ timer = Timer}};
+ true -> {next_state, StateName, StateData}
end;
-
handle_info({timeout, ShaperTimer, _}, StateName,
#state{shaper_timer = ShaperTimer} = StateData) ->
{next_state, StateName, StateData#state{shaper_timer = undefined}};
@@ -552,14 +551,14 @@ handle_info(_, StateName, StateData) ->
%% Returns: any
%%----------------------------------------------------------------------
terminate(_Reason, _StateName, StateData) ->
- ?DEBUG("terminate: Deleting session ~s", [StateData#state.id]),
+ ?DEBUG("terminate: Deleting session ~s",
+ [StateData#state.id]),
mnesia:dirty_delete({http_bind, StateData#state.id}),
- send_receiver_reply(StateData#state.http_receiver, {ok, terminate}),
+ send_receiver_reply(StateData#state.http_receiver,
+ {ok, terminate}),
case StateData#state.waiting_input of
- false ->
- ok;
- C2SPid ->
- gen_fsm:send_event(C2SPid, closed)
+ false -> ok;
+ C2SPid -> gen_fsm:send_event(C2SPid, closed)
end,
ok.
@@ -568,250 +567,227 @@ terminate(_Reason, _StateName, StateData) ->
%%%----------------------------------------------------------------------
%% PUT / Get processing:
-handle_http_put_event(#http_put{rid = Rid, attrs = Attrs,
- hold = Hold} = Request,
+handle_http_put_event(#http_put{rid = Rid,
+ attrs = Attrs, hold = Hold} =
+ Request,
StateName, StateData) ->
- ?DEBUG("New request: ~p",[Request]),
- %% Check if Rid valid
- RidAllow = rid_allow(StateData#state.rid, Rid, Attrs, Hold,
- StateData#state.max_pause),
-
- %% Check if Rid is in sequence or out of sequence:
+ ?DEBUG("New request: ~p", [Request]),
+ RidAllow = rid_allow(StateData#state.rid, Rid, Attrs,
+ Hold, StateData#state.max_pause),
case RidAllow of
- buffer ->
- ?DEBUG("Buffered request: ~p", [Request]),
- %% Request is out of sequence:
- PendingRequests = StateData#state.unprocessed_req_list,
- %% In case an existing RID was already buffered:
- Requests = lists:keydelete(Rid, 2, PendingRequests),
- ReqList = [#hbr{rid = Rid,
- key = StateData#state.key,
- out = []
- } |
- [El || El <- StateData#state.req_list,
- El#hbr.rid > (Rid - 1 - Hold)]
- ],
- ?DEBUG("reqlist: ~p", [ReqList]),
- UnprocessedReqList = [Request | Requests],
- cancel_timer(StateData#state.timer),
- Timer = set_inactivity_timer(0, StateData#state.max_inactivity),
- {reply, ok, StateName,
- StateData#state{unprocessed_req_list = UnprocessedReqList,
- req_list = ReqList,
- timer = Timer}, hibernate};
-
- _ ->
- %% Request is in sequence:
- process_http_put(Request, StateName, StateData, RidAllow)
+ buffer ->
+ ?DEBUG("Buffered request: ~p", [Request]),
+ PendingRequests = StateData#state.unprocessed_req_list,
+ Requests = lists:keydelete(Rid, 2, PendingRequests),
+ ReqList = [#hbr{rid = Rid, key = StateData#state.key,
+ out = []}
+ | [El
+ || El <- StateData#state.req_list,
+ El#hbr.rid > Rid - 1 - Hold]],
+ ?DEBUG("reqlist: ~p", [ReqList]),
+ UnprocessedReqList = [Request | Requests],
+ cancel_timer(StateData#state.timer),
+ Timer = set_inactivity_timer(0,
+ StateData#state.max_inactivity),
+ {reply, ok, StateName,
+ StateData#state{unprocessed_req_list =
+ UnprocessedReqList,
+ req_list = ReqList, timer = Timer},
+ hibernate};
+ _ ->
+ process_http_put(Request, StateName, StateData,
+ RidAllow)
end.
-process_http_put(#http_put{rid = Rid, attrs = Attrs, payload = Payload,
- hold = Hold, stream = StreamTo,
- ip = IP} = Request,
+process_http_put(#http_put{rid = Rid, attrs = Attrs,
+ payload = Payload, hold = Hold, stream = StreamTo,
+ ip = IP} =
+ Request,
StateName, StateData, RidAllow) ->
?DEBUG("Actually processing request: ~p", [Request]),
- %% Check if key valid
- Key = xml:get_attr_s("key", Attrs),
- NewKey = xml:get_attr_s("newkey", Attrs),
- KeyAllow =
- case RidAllow of
- repeat ->
- true;
- false ->
- false;
- {true, _} ->
- case StateData#state.key of
- "" ->
- true;
- OldKey ->
- NextKey = sha:sha(Key),
- ?DEBUG("Key/OldKey/NextKey: ~s/~s/~s", [Key, OldKey, NextKey]),
- if
- OldKey == NextKey ->
- true;
- true ->
- ?DEBUG("wrong key: ~s",[Key]),
- false
- end
- end
- end,
+ Key = xml:get_attr_s(<<"key">>, Attrs),
+ NewKey = xml:get_attr_s(<<"newkey">>, Attrs),
+ KeyAllow = case RidAllow of
+ repeat -> true;
+ false -> false;
+ {true, _} ->
+ case StateData#state.key of
+ <<"">> -> true;
+ OldKey ->
+ NextKey = sha:sha(Key),
+ ?DEBUG("Key/OldKey/NextKey: ~s/~s/~s",
+ [Key, OldKey, NextKey]),
+ if OldKey == NextKey -> true;
+ true -> ?DEBUG("wrong key: ~s", [Key]), false
+ end
+ end
+ end,
TNow = tnow(),
- LastPoll = if
- Payload == [] ->
- TNow;
- true ->
- 0
+ LastPoll = if Payload == [] -> TNow;
+ true -> 0
end,
- if
- (Payload == []) and
- (Hold == 0) and
- (TNow - StateData#state.last_poll < ?MIN_POLLING) ->
- Reply = {error, polling_too_frequently},
- {reply, Reply, StateName, StateData};
- KeyAllow ->
- case RidAllow of
- false ->
- Reply = {error, not_exists},
- {reply, Reply, StateName, StateData};
- repeat ->
- ?DEBUG("REPEATING ~p", [Rid]),
- case [El#hbr.out ||
- El <- StateData#state.req_list,
- El#hbr.rid == Rid] of
- [] ->
- {error, not_exists};
- [Out | _XS] ->
- if (Rid == StateData#state.rid) and
- (StateData#state.http_receiver /= undefined) ->
- {reply, ok, StateName, StateData};
- true ->
- Reply = {repeat, lists:reverse(Out)},
- {reply, Reply, StateName, StateData#state{last_poll = LastPoll}}
- end
- end;
- {true, Pause} ->
- SaveKey = if
- NewKey == "" ->
- Key;
- true ->
- NewKey
- end,
- ?DEBUG(" -- SaveKey: ~s~n", [SaveKey]),
-
- %% save request
- ReqList1 =
- [El || El <- StateData#state.req_list,
- El#hbr.rid > (Rid - 1 - Hold)],
- ReqList =
- case lists:keymember(Rid, #hbr.rid, ReqList1) of
- true ->
- ReqList1;
- false ->
- [#hbr{rid = Rid,
- key = StateData#state.key,
- out = []
- } |
- ReqList1
- ]
- end,
- ?DEBUG("reqlist: ~p", [ReqList]),
-
- %% setup next timer
- cancel_timer(StateData#state.timer),
- Timer = set_inactivity_timer(Pause,
- StateData#state.max_inactivity),
- case StateData#state.waiting_input of
- false ->
- Input =
- lists:foldl(
- fun queue:in/2,
- StateData#state.input, Payload),
- Reply = ok,
- process_buffered_request(Reply, StateName,
- StateData#state{input = Input,
- rid = Rid,
- key = SaveKey,
- ctime = TNow,
- timer = Timer,
- pause = Pause,
- last_poll = LastPoll,
- req_list = ReqList,
- ip = IP
- });
- C2SPid ->
- case StreamTo of
- {To, ""} ->
- gen_fsm:send_event(
- C2SPid,
- {xmlstreamstart, "stream:stream",
- [{"to", To},
- {"xmlns", ?NS_CLIENT},
- {"xmlns:stream", ?NS_STREAM}]});
- {To, Version} ->
- gen_fsm:send_event(
- C2SPid,
- {xmlstreamstart, "stream:stream",
- [{"to", To},
- {"xmlns", ?NS_CLIENT},
- {"version", Version},
- {"xmlns:stream", ?NS_STREAM}]});
- _ ->
- ok
- end,
-
- MaxInactivity = get_max_inactivity(StreamTo, StateData#state.max_inactivity),
- MaxPause = get_max_inactivity(StreamTo, StateData#state.max_pause),
-
- ?DEBUG("really sending now: ~p", [Payload]),
- lists:foreach(
- fun({xmlstreamend, End}) ->
- gen_fsm:send_event(
- C2SPid, {xmlstreamend, End});
- (El) ->
- gen_fsm:send_event(
- C2SPid, {xmlstreamelement, El})
- end, Payload),
- Reply = ok,
- process_buffered_request(Reply, StateName,
- StateData#state{input = queue:new(),
- rid = Rid,
- key = SaveKey,
- ctime = TNow,
- timer = Timer,
- pause = Pause,
- last_poll = LastPoll,
- req_list = ReqList,
- max_inactivity = MaxInactivity,
- max_pause = MaxPause,
- ip = IP
- })
- end
- end;
- true ->
- Reply = {error, bad_key},
- {reply, Reply, StateName, StateData}
+ if (Payload == []) and (Hold == 0) and
+ (TNow - StateData#state.last_poll < (?MIN_POLLING)) ->
+ Reply = {error, polling_too_frequently},
+ {reply, Reply, StateName, StateData};
+ KeyAllow ->
+ case RidAllow of
+ false ->
+ Reply = {error, not_exists},
+ {reply, Reply, StateName, StateData};
+ repeat ->
+ ?DEBUG("REPEATING ~p", [Rid]),
+ case [El#hbr.out
+ || El <- StateData#state.req_list, El#hbr.rid == Rid]
+ of
+ [] -> {error, not_exists};
+ [Out | _XS] ->
+ if (Rid == StateData#state.rid) and
+ (StateData#state.http_receiver /= undefined) ->
+ {reply, ok, StateName, StateData};
+ true ->
+ Reply = {repeat, lists:reverse(Out)},
+ {reply, Reply, StateName,
+ StateData#state{last_poll = LastPoll}}
+ end
+ end;
+ {true, Pause} ->
+ SaveKey = if NewKey == <<"">> -> Key;
+ true -> NewKey
+ end,
+ ?DEBUG(" -- SaveKey: ~s~n", [SaveKey]),
+ ReqList1 = [El
+ || El <- StateData#state.req_list,
+ El#hbr.rid > Rid - 1 - Hold],
+ ReqList = case lists:keymember(Rid, #hbr.rid, ReqList1)
+ of
+ true -> ReqList1;
+ false ->
+ [#hbr{rid = Rid, key = StateData#state.key,
+ out = []}
+ | ReqList1]
+ end,
+ ?DEBUG("reqlist: ~p", [ReqList]),
+ cancel_timer(StateData#state.timer),
+ Timer = set_inactivity_timer(Pause,
+ StateData#state.max_inactivity),
+ case StateData#state.waiting_input of
+ false ->
+ Input = lists:foldl(fun queue:in/2,
+ StateData#state.input, Payload),
+ Reply = ok,
+ process_buffered_request(Reply, StateName,
+ StateData#state{input = Input,
+ rid = Rid,
+ key = SaveKey,
+ ctime = TNow,
+ timer = Timer,
+ pause = Pause,
+ last_poll =
+ LastPoll,
+ req_list =
+ ReqList,
+ ip = IP});
+ C2SPid ->
+ case StreamTo of
+ {To, <<"">>} ->
+ gen_fsm:send_event(C2SPid,
+ {xmlstreamstart,
+ <<"stream:stream">>,
+ [{<<"to">>, To},
+ {<<"xmlns">>, ?NS_CLIENT},
+ {<<"xmlns:stream">>,
+ ?NS_STREAM}]});
+ {To, Version} ->
+ gen_fsm:send_event(C2SPid,
+ {xmlstreamstart,
+ <<"stream:stream">>,
+ [{<<"to">>, To},
+ {<<"xmlns">>, ?NS_CLIENT},
+ {<<"version">>, Version},
+ {<<"xmlns:stream">>,
+ ?NS_STREAM}]});
+ _ -> ok
+ end,
+ MaxInactivity = get_max_inactivity(StreamTo,
+ StateData#state.max_inactivity),
+ MaxPause = get_max_inactivity(StreamTo,
+ StateData#state.max_pause),
+ ?DEBUG("really sending now: ~p", [Payload]),
+ lists:foreach(fun ({xmlstreamend, End}) ->
+ gen_fsm:send_event(C2SPid,
+ {xmlstreamend,
+ End});
+ (El) ->
+ gen_fsm:send_event(C2SPid,
+ {xmlstreamelement,
+ El})
+ end,
+ Payload),
+ Reply = ok,
+ process_buffered_request(Reply, StateName,
+ StateData#state{input =
+ queue:new(),
+ rid = Rid,
+ key = SaveKey,
+ ctime = TNow,
+ timer = Timer,
+ pause = Pause,
+ last_poll =
+ LastPoll,
+ req_list =
+ ReqList,
+ max_inactivity =
+ MaxInactivity,
+ max_pause =
+ MaxPause,
+ ip = IP})
+ end
+ end;
+ true ->
+ Reply = {error, bad_key},
+ {reply, Reply, StateName, StateData}
end.
process_buffered_request(Reply, StateName, StateData) ->
Rid = StateData#state.rid,
Requests = StateData#state.unprocessed_req_list,
- case lists:keysearch(Rid+1, 2, Requests) of
- {value, Request} ->
- ?DEBUG("Processing buffered request: ~p", [Request]),
- NewRequests = lists:keydelete(Rid+1, 2, Requests),
- handle_http_put_event(
- Request, StateName,
- StateData#state{unprocessed_req_list = NewRequests});
- _ ->
- {reply, Reply, StateName, StateData, hibernate}
+ case lists:keysearch(Rid + 1, 2, Requests) of
+ {value, Request} ->
+ ?DEBUG("Processing buffered request: ~p", [Request]),
+ NewRequests = lists:keydelete(Rid + 1, 2, Requests),
+ handle_http_put_event(Request, StateName,
+ StateData#state{unprocessed_req_list =
+ NewRequests});
+ _ -> {reply, Reply, StateName, StateData, hibernate}
end.
-handle_http_put(Sid, Rid, Attrs, Payload, PayloadSize, StreamStart, IP) ->
- case http_put(Sid, Rid, Attrs, Payload, PayloadSize, StreamStart, IP) of
- {error, not_exists} ->
- ?DEBUG("no session associated with sid: ~p", [Sid]),
- {404, ?HEADER, ""};
- {{error, Reason}, Sess} ->
- ?DEBUG("Error on HTTP put. Reason: ~p", [Reason]),
- handle_http_put_error(Reason, Sess);
- {{repeat, OutPacket}, Sess} ->
- ?DEBUG("http_put said 'repeat!' ...~nOutPacket: ~p", [OutPacket]),
- send_outpacket(Sess, OutPacket);
- {{wait, Pause}, _Sess} ->
- ?DEBUG("Trafic Shaper: Delaying request ~p", [Rid]),
- timer:sleep(Pause),
- %{200, ?HEADER,
- % xml:element_to_binary(
- % {xmlelement, "body",
- % [{"xmlns", ?NS_HTTP_BIND},
- % {"type", "error"}], []})};
- handle_http_put(Sid, Rid, Attrs, Payload, PayloadSize,
- StreamStart, IP);
- {ok, Sess} ->
- prepare_response(Sess, Rid, [], StreamStart)
+handle_http_put(Sid, Rid, Attrs, Payload, PayloadSize,
+ StreamStart, IP) ->
+ case http_put(Sid, Rid, Attrs, Payload, PayloadSize,
+ StreamStart, IP)
+ of
+ {error, not_exists} ->
+ ?DEBUG("no session associated with sid: ~p", [Sid]),
+ {404, ?HEADER, <<"">>};
+ {{error, Reason}, Sess} ->
+ ?DEBUG("Error on HTTP put. Reason: ~p", [Reason]),
+ handle_http_put_error(Reason, Sess);
+ {{repeat, OutPacket}, Sess} ->
+ ?DEBUG("http_put said 'repeat!' ...~nOutPacket: ~p",
+ [OutPacket]),
+ send_outpacket(Sess, OutPacket);
+ {{wait, Pause}, _Sess} ->
+ ?DEBUG("Trafic Shaper: Delaying request ~p", [Rid]),
+ timer:sleep(Pause),
+ handle_http_put(Sid, Rid, Attrs, Payload, PayloadSize,
+ StreamStart, IP);
+ {ok, Sess} ->
+ prepare_response(Sess, Rid, [], StreamStart)
end.
-http_put(Sid, Rid, Attrs, Payload, PayloadSize, StreamStart, IP) ->
+http_put(Sid, Rid, Attrs, Payload, PayloadSize,
+ StreamStart, IP) ->
?DEBUG("Looking for session: ~p", [Sid]),
case mnesia:dirty_read({http_bind, Sid}) of
[] ->
@@ -822,7 +798,7 @@ http_put(Sid, Rid, Attrs, Payload, PayloadSize, StreamStart, IP) ->
true ->
{To, StreamVersion};
_ ->
- ""
+ <<"">>
end,
{gen_fsm:sync_send_all_state_event(
FsmRef, #http_put{rid = Rid, attrs = Attrs, payload = Payload,
@@ -830,425 +806,430 @@ http_put(Sid, Rid, Attrs, Payload, PayloadSize, StreamStart, IP) ->
stream = NewStream, ip = IP}, 30000), Sess}
end.
-handle_http_put_error(Reason, #http_bind{pid=FsmRef, version=Version})
- when Version >= 0 ->
- gen_fsm:sync_send_all_state_event(FsmRef, {stop, {put_error,Reason}}),
+handle_http_put_error(Reason,
+ #http_bind{pid = FsmRef, version = Version})
+ when Version >= 0 ->
+ gen_fsm:sync_send_all_state_event(FsmRef,
+ {stop, {put_error, Reason}}),
case Reason of
- not_exists ->
- {200, ?HEADER,
- xml:element_to_binary(
- {xmlelement, "body",
- [{"xmlns", ?NS_HTTP_BIND},
- {"type", "terminate"},
- {"condition", "item-not-found"}], []})};
- bad_key ->
- {200, ?HEADER,
- xml:element_to_binary(
- {xmlelement, "body",
- [{"xmlns", ?NS_HTTP_BIND},
- {"type", "terminate"},
- {"condition", "item-not-found"}], []})};
- polling_too_frequently ->
- {200, ?HEADER,
- xml:element_to_binary(
- {xmlelement, "body",
- [{"xmlns", ?NS_HTTP_BIND},
- {"type", "terminate"},
- {"condition", "policy-violation"}], []})}
+ not_exists ->
+ {200, ?HEADER,
+ xml:element_to_binary(#xmlel{name = <<"body">>,
+ attrs =
+ [{<<"xmlns">>, ?NS_HTTP_BIND},
+ {<<"type">>, <<"terminate">>},
+ {<<"condition">>,
+ <<"item-not-found">>}],
+ children = []})};
+ bad_key ->
+ {200, ?HEADER,
+ xml:element_to_binary(#xmlel{name = <<"body">>,
+ attrs =
+ [{<<"xmlns">>, ?NS_HTTP_BIND},
+ {<<"type">>, <<"terminate">>},
+ {<<"condition">>,
+ <<"item-not-found">>}],
+ children = []})};
+ polling_too_frequently ->
+ {200, ?HEADER,
+ xml:element_to_binary(#xmlel{name = <<"body">>,
+ attrs =
+ [{<<"xmlns">>, ?NS_HTTP_BIND},
+ {<<"type">>, <<"terminate">>},
+ {<<"condition">>,
+ <<"policy-violation">>}],
+ children = []})}
end;
-handle_http_put_error(Reason, #http_bind{pid=FsmRef}) ->
- gen_fsm:sync_send_all_state_event(FsmRef,{stop, {put_error_no_version, Reason}}),
+handle_http_put_error(Reason,
+ #http_bind{pid = FsmRef}) ->
+ gen_fsm:sync_send_all_state_event(FsmRef,
+ {stop, {put_error_no_version, Reason}}),
case Reason of
- not_exists -> %% bad rid
- ?DEBUG("Closing HTTP bind session (Bad rid).", []),
- {404, ?HEADER, ""};
- bad_key ->
- ?DEBUG("Closing HTTP bind session (Bad key).", []),
- {404, ?HEADER, ""};
- polling_too_frequently ->
- ?DEBUG("Closing HTTP bind session (User polling too frequently).", []),
- {403, ?HEADER, ""}
+ not_exists -> %% bad rid
+ ?DEBUG("Closing HTTP bind session (Bad rid).", []),
+ {404, ?HEADER, <<"">>};
+ bad_key ->
+ ?DEBUG("Closing HTTP bind session (Bad key).", []),
+ {404, ?HEADER, <<"">>};
+ polling_too_frequently ->
+ ?DEBUG("Closing HTTP bind session (User polling "
+ "too frequently).",
+ []),
+ {403, ?HEADER, <<"">>}
end.
%% Control RID ordering
rid_allow(none, _NewRid, _Attrs, _Hold, _MaxPause) ->
- %% First request - nothing saved so far
{true, 0};
rid_allow(OldRid, NewRid, Attrs, Hold, MaxPause) ->
- ?DEBUG("Previous rid / New rid: ~p/~p", [OldRid, NewRid]),
+ ?DEBUG("Previous rid / New rid: ~p/~p",
+ [OldRid, NewRid]),
if
- %% We did not miss any packet, we can process it immediately:
- NewRid == OldRid + 1 ->
- case catch list_to_integer(
- xml:get_attr_s("pause", Attrs)) of
- {'EXIT', _} ->
- {true, 0};
- Pause1 when Pause1 =< MaxPause ->
- ?DEBUG("got pause: ~p", [Pause1]),
- {true, Pause1};
- _ ->
- {true, 0}
- end;
- %% We have missed packets, we need to cached it to process it later on:
- (OldRid < NewRid) and
- (NewRid =< (OldRid + Hold + 1)) ->
- buffer;
- (NewRid =< OldRid) and
- (NewRid > OldRid - Hold - 1) ->
- repeat;
- true ->
- false
+ %% We did not miss any packet, we can process it immediately:
+ NewRid == OldRid + 1 ->
+ case catch
+ jlib:binary_to_integer(xml:get_attr_s(<<"pause">>,
+ Attrs))
+ of
+ {'EXIT', _} -> {true, 0};
+ Pause1 when Pause1 =< MaxPause ->
+ ?DEBUG("got pause: ~p", [Pause1]), {true, Pause1};
+ _ -> {true, 0}
+ end;
+ %% We have missed packets, we need to cached it to process it later on:
+ (OldRid < NewRid) and (NewRid =< OldRid + Hold + 1) ->
+ buffer;
+ (NewRid =< OldRid) and (NewRid > OldRid - Hold - 1) ->
+ repeat;
+ true -> false
end.
update_shaper(ShaperState, PayloadSize) ->
- {NewShaperState, Pause} = shaper:update(ShaperState, PayloadSize),
- if
- Pause > 0 ->
- ShaperTimer = erlang:start_timer(Pause, self(), activate), %% MR: Seems timer is not needed. Activate is not handled
- {NewShaperState, ShaperTimer};
- true ->
- {NewShaperState, undefined}
+ {NewShaperState, Pause} = shaper:update(ShaperState,
+ PayloadSize),
+ if Pause > 0 ->
+ ShaperTimer = erlang:start_timer(Pause, self(),
+ activate),
+ {NewShaperState, ShaperTimer};
+ true -> {NewShaperState, undefined}
end.
prepare_response(Sess, Rid, OutputEls, StreamStart) ->
- receive after Sess#http_bind.process_delay -> ok end,
+ receive after Sess#http_bind.process_delay -> ok end,
case catch http_get(Sess, Rid) of
- {ok, cancel} ->
- %% actually it would be better if we could completely
- %% cancel this request, but then we would have to hack
- %% ejabberd_http and I'm too lazy now
- {200, ?HEADER, "<body type='error' xmlns='"++?NS_HTTP_BIND++"'/>"};
- {ok, empty} ->
- {200, ?HEADER, "<body xmlns='"++?NS_HTTP_BIND++"'/>"};
- {ok, terminate} ->
- {200, ?HEADER, "<body type='terminate' xmlns='"++?NS_HTTP_BIND++"'/>"};
- {ok, ROutPacket} ->
- OutPacket = lists:reverse(ROutPacket),
- ?DEBUG("OutPacket: ~p", [OutputEls++OutPacket]),
- prepare_outpacket_response(Sess, Rid, OutputEls++OutPacket, StreamStart);
- {'EXIT', {shutdown, _}} ->
- {200, ?HEADER, "<body type='terminate' condition='system-shutdown' xmlns='"++?NS_HTTP_BIND++"'/>"};
- {'EXIT', _Reason} ->
- {200, ?HEADER, "<body type='terminate' xmlns='"++?NS_HTTP_BIND++"'/>"}
+ {ok, cancel} ->
+ {200, ?HEADER,
+ <<"<body type='error' xmlns='", (?NS_HTTP_BIND)/binary,
+ "'/>">>};
+ {ok, empty} ->
+ {200, ?HEADER,
+ <<"<body xmlns='", (?NS_HTTP_BIND)/binary, "'/>">>};
+ {ok, terminate} ->
+ {200, ?HEADER,
+ <<"<body type='terminate' xmlns='",
+ (?NS_HTTP_BIND)/binary, "'/>">>};
+ {ok, ROutPacket} ->
+ OutPacket = lists:reverse(ROutPacket),
+ ?DEBUG("OutPacket: ~p", [OutputEls ++ OutPacket]),
+ prepare_outpacket_response(Sess, Rid,
+ OutputEls ++ OutPacket, StreamStart);
+ {'EXIT', {shutdown, _}} ->
+ {200, ?HEADER,
+ <<"<body type='terminate' condition='system-shut"
+ "down' xmlns='",
+ (?NS_HTTP_BIND)/binary, "'/>">>};
+ {'EXIT', _Reason} ->
+ {200, ?HEADER,
+ <<"<body type='terminate' xmlns='",
+ (?NS_HTTP_BIND)/binary, "'/>">>}
end.
%% Send output payloads on establised sessions
-prepare_outpacket_response(Sess, _Rid, OutPacket, false) ->
+prepare_outpacket_response(Sess, _Rid, OutPacket,
+ false) ->
case catch send_outpacket(Sess, OutPacket) of
- {'EXIT', _Reason} ->
- ?DEBUG("Error in sending packet ~p ", [_Reason]),
- {200, ?HEADER,
- "<body type='terminate' xmlns='"++
- ?NS_HTTP_BIND++"'/>"};
- SendRes ->
- SendRes
+ {'EXIT', _Reason} ->
+ ?DEBUG("Error in sending packet ~p ", [_Reason]),
+ {200, ?HEADER,
+ <<"<body type='terminate' xmlns='",
+ (?NS_HTTP_BIND)/binary, "'/>">>};
+ SendRes -> SendRes
end;
%% Handle a new session along with its output payload
-prepare_outpacket_response(#http_bind{id=Sid, wait=Wait,
- hold=Hold, to=To}=_Sess,
- _Rid, OutPacket, true) ->
+prepare_outpacket_response(#http_bind{id = Sid,
+ wait = Wait, hold = Hold, to = To} =
+ _Sess,
+ _Rid, OutPacket, true) ->
case OutPacket of
- [{xmlstreamstart, _, OutAttrs} | Els] ->
- AuthID = xml:get_attr_s("id", OutAttrs),
- From = xml:get_attr_s("from", OutAttrs),
- Version = xml:get_attr_s("version", OutAttrs),
- OutEls =
- case Els of
- [] ->
- [];
- [{xmlstreamelement,
- {xmlelement, "stream:features",
- StreamAttribs, StreamEls}}
- | StreamTail] ->
- TypedTail =
- [check_default_xmlns(OEl) ||
- {xmlstreamelement, OEl} <-
- StreamTail],
- [{xmlelement, "stream:features",
- [{"xmlns:stream",
- ?NS_STREAM}] ++
- StreamAttribs, StreamEls}] ++
- TypedTail;
- StreamTail ->
- [check_default_xmlns(OEl) ||
- {xmlstreamelement, OEl} <-
- StreamTail]
- end,
- case OutEls of
- [{xmlelement,
- "stream:error",_,_}] ->
- {200, ?HEADER, "<body type='terminate' "
- "condition='host-unknown' "
- "xmlns='"++?NS_HTTP_BIND++"'/>"};
- _ ->
- BOSH_attribs =
- [{"authid", AuthID},
- {"xmlns:xmpp", ?NS_BOSH},
- {"xmlns:stream", ?NS_STREAM}] ++
- case OutEls of
- [] ->
- [];
- _ ->
- [{"xmpp:version", Version}]
- end,
- MaxInactivity = get_max_inactivity(To, ?MAX_INACTIVITY),
- MaxPause = get_max_pause(To),
- {200, ?HEADER,
- xml:element_to_binary(
- {xmlelement,"body",
- [{"xmlns",
- ?NS_HTTP_BIND},
- {"sid", Sid},
- {"wait", integer_to_list(Wait)},
- {"requests", integer_to_list(Hold+1)},
- {"inactivity",
- integer_to_list(
- trunc(MaxInactivity/1000))},
- {"maxpause",
- integer_to_list(MaxPause)},
- {"polling",
- integer_to_list(
- trunc(?MIN_POLLING/1000000))},
- {"ver", ?BOSH_VERSION},
- {"from", From},
- {"secure", "true"} %% we're always being secure
- ] ++ BOSH_attribs,OutEls})}
- end;
- _ ->
- {200, ?HEADER, "<body type='terminate' "
- "condition='internal-server-error' "
- "xmlns='"++?NS_HTTP_BIND++"'/>"}
+ [{xmlstreamstart, _, OutAttrs} | Els] ->
+ AuthID = xml:get_attr_s(<<"id">>, OutAttrs),
+ From = xml:get_attr_s(<<"from">>, OutAttrs),
+ Version = xml:get_attr_s(<<"version">>, OutAttrs),
+ OutEls = case Els of
+ [] -> [];
+ [{xmlstreamelement,
+ #xmlel{name = <<"stream:features">>,
+ attrs = StreamAttribs, children = StreamEls}}
+ | StreamTail] ->
+ TypedTail = [check_default_xmlns(OEl)
+ || {xmlstreamelement, OEl} <- StreamTail],
+ [#xmlel{name = <<"stream:features">>,
+ attrs =
+ [{<<"xmlns:stream">>, ?NS_STREAM}] ++
+ StreamAttribs,
+ children = StreamEls}]
+ ++ TypedTail;
+ StreamTail ->
+ [check_default_xmlns(OEl)
+ || {xmlstreamelement, OEl} <- StreamTail]
+ end,
+ case OutEls of
+ [#xmlel{name = <<"stream:error">>}] ->
+ {200, ?HEADER,
+ <<"<body type='terminate' condition='host-unknow"
+ "n' xmlns='",
+ (?NS_HTTP_BIND)/binary, "'/>">>};
+ _ ->
+ BOSH_attribs = [{<<"authid">>, AuthID},
+ {<<"xmlns:xmpp">>, ?NS_BOSH},
+ {<<"xmlns:stream">>, ?NS_STREAM}]
+ ++
+ case OutEls of
+ [] -> [];
+ _ -> [{<<"xmpp:version">>, Version}]
+ end,
+ MaxInactivity = get_max_inactivity(To, ?MAX_INACTIVITY),
+ MaxPause = get_max_pause(To),
+ {200, ?HEADER,
+ xml:element_to_binary(#xmlel{name = <<"body">>,
+ attrs =
+ [{<<"xmlns">>, ?NS_HTTP_BIND},
+ {<<"sid">>, Sid},
+ {<<"wait">>,
+ iolist_to_binary(integer_to_list(Wait))},
+ {<<"requests">>,
+ iolist_to_binary(integer_to_list(Hold
+ +
+ 1))},
+ {<<"inactivity">>,
+ iolist_to_binary(integer_to_list(trunc(MaxInactivity
+ /
+ 1000)))},
+ {<<"maxpause">>,
+ iolist_to_binary(integer_to_list(MaxPause))},
+ {<<"polling">>,
+ iolist_to_binary(integer_to_list(trunc((?MIN_POLLING)
+ /
+ 1000000)))},
+ {<<"ver">>, ?BOSH_VERSION},
+ {<<"from">>, From},
+ {<<"secure">>, <<"true">>}]
+ ++ BOSH_attribs,
+ children = OutEls})}
+ end;
+ _ ->
+ {200, ?HEADER,
+ <<"<body type='terminate' condition='internal-se"
+ "rver-error' xmlns='",
+ (?NS_HTTP_BIND)/binary, "'/>">>}
end.
-
-http_get(#http_bind{pid = FsmRef, wait = Wait, hold = Hold}, Rid) ->
- gen_fsm:sync_send_all_state_event(
- FsmRef, {http_get, Rid, Wait, Hold}, 2 * ?MAX_WAIT * 1000).
+http_get(#http_bind{pid = FsmRef, wait = Wait,
+ hold = Hold},
+ Rid) ->
+ gen_fsm:sync_send_all_state_event(FsmRef,
+ {http_get, Rid, Wait, Hold},
+ 2 * (?MAX_WAIT) * 1000).
send_outpacket(#http_bind{pid = FsmRef}, OutPacket) ->
case OutPacket of
- [] ->
- {200, ?HEADER, "<body xmlns='"++?NS_HTTP_BIND++"'/>"};
- [{xmlstreamend, _}] ->
- gen_fsm:sync_send_all_state_event(FsmRef,{stop,stream_closed}),
- {200, ?HEADER, "<body xmlns='"++?NS_HTTP_BIND++"'/>"};
- _ ->
- %% TODO: We parse to add a default namespace to packet,
- %% The spec says adding the jabber:client namespace if
- %% mandatory, even if some implementation do not do that
- %% change on packets.
- %% I think this should be an option to avoid modifying
- %% packet in most case.
- AllElements =
- lists:all(fun({xmlstreamelement,
- {xmlelement, "stream:error", _, _}}) -> false;
- ({xmlstreamelement, _}) -> true;
- ({xmlstreamraw, _}) -> true;
- (_) -> false
- end, OutPacket),
- case AllElements of
- true ->
- TypedEls = lists:foldl(fun({xmlstreamelement, El}, Acc) ->
- Acc ++
- [xml:element_to_string(
- check_default_xmlns(El)
- )];
- ({xmlstreamraw, R}, Acc) ->
- Acc ++ [R]
- end,
- [],
- OutPacket),
-
- Body = "<body xmlns='"++?NS_HTTP_BIND++"'>"
- ++ TypedEls ++
- "</body>",
- ?DEBUG(" --- outgoing data --- ~n~s~n --- END --- ~n",
- [Body]),
- {200, ?HEADER, Body};
- false ->
- case OutPacket of
- [{xmlstreamstart, _, _} | SEls] ->
- OutEls =
- case SEls of
- [{xmlstreamelement,
- {xmlelement,
- "stream:features",
- StreamAttribs, StreamEls}} |
- StreamTail] ->
- TypedTail =
- [check_default_xmlns(OEl) ||
- {xmlstreamelement, OEl} <-
- StreamTail],
- [{xmlelement,
- "stream:features",
- [{"xmlns:stream",
- ?NS_STREAM}] ++
- StreamAttribs, StreamEls}] ++
- TypedTail;
- StreamTail ->
- [check_default_xmlns(OEl) ||
- {xmlstreamelement, OEl} <-
- StreamTail]
- end,
- {200, ?HEADER,
- xml:element_to_binary(
- {xmlelement,"body",
- [{"xmlns",
- ?NS_HTTP_BIND}],
- OutEls})};
+ [] ->
+ {200, ?HEADER,
+ <<"<body xmlns='", (?NS_HTTP_BIND)/binary, "'/>">>};
+ [{xmlstreamend, _}] ->
+ gen_fsm:sync_send_all_state_event(FsmRef,
+ {stop, stream_closed}),
+ {200, ?HEADER,
+ <<"<body xmlns='", (?NS_HTTP_BIND)/binary, "'/>">>};
+ _ ->
+ AllElements = lists:all(fun ({xmlstreamelement,
+ #xmlel{name = <<"stream:error">>}}) ->
+ false;
+ ({xmlstreamelement, _}) -> true;
+ ({xmlstreamraw, _}) -> true;
+ (_) -> false
+ end,
+ OutPacket),
+ case AllElements of
+ true ->
+ TypedEls = lists:foldl(fun ({xmlstreamelement, El},
+ Acc) ->
+ Acc ++
+ [xml:element_to_binary(check_default_xmlns(El))];
+ ({xmlstreamraw, R}, Acc) ->
+ Acc ++ [R]
+ end,
+ [], OutPacket),
+ Body = <<"<body xmlns='", (?NS_HTTP_BIND)/binary, "'>",
+ (iolist_to_binary(TypedEls))/binary, "</body>">>,
+ ?DEBUG(" --- outgoing data --- ~n~s~n --- END "
+ "--- ~n",
+ [Body]),
+ {200, ?HEADER, Body};
+ false ->
+ case OutPacket of
+ [{xmlstreamstart, _, _} | SEls] ->
+ OutEls = case SEls of
+ [{xmlstreamelement,
+ #xmlel{name = <<"stream:features">>,
+ attrs = StreamAttribs,
+ children = StreamEls}}
+ | StreamTail] ->
+ TypedTail = [check_default_xmlns(OEl)
+ || {xmlstreamelement, OEl}
+ <- StreamTail],
+ [#xmlel{name = <<"stream:features">>,
+ attrs =
+ [{<<"xmlns:stream">>,
+ ?NS_STREAM}]
+ ++ StreamAttribs,
+ children = StreamEls}]
+ ++ TypedTail;
+ StreamTail ->
+ [check_default_xmlns(OEl)
+ || {xmlstreamelement, OEl} <- StreamTail]
+ end,
+ {200, ?HEADER,
+ xml:element_to_binary(#xmlel{name = <<"body">>,
+ attrs =
+ [{<<"xmlns">>,
+ ?NS_HTTP_BIND}],
+ children = OutEls})};
+ _ ->
+ SErrCond = lists:filter(fun ({xmlstreamelement,
+ #xmlel{name =
+ <<"stream:error">>}}) ->
+ true;
+ (_) -> false
+ end,
+ OutPacket),
+ StreamErrCond = case SErrCond of
+ [] -> null;
+ [{xmlstreamelement,
+ #xmlel{} = StreamErrorTag}
+ | _] ->
+ [StreamErrorTag]
+ end,
+ gen_fsm:sync_send_all_state_event(FsmRef,
+ {stop,
+ {stream_error,
+ OutPacket}}),
+ case StreamErrCond of
+ null ->
+ {200, ?HEADER,
+ <<"<body type='terminate' condition='internal-se"
+ "rver-error' xmlns='",
+ (?NS_HTTP_BIND)/binary, "'/>">>};
_ ->
- SErrCond =
- lists:filter(
- fun({xmlstreamelement,
- {xmlelement, "stream:error",
- _, _}}) ->
- true;
- (_) -> false
- end, OutPacket),
- StreamErrCond =
- case SErrCond of
- [] ->
- null;
- [{xmlstreamelement,
- {xmlelement, _, _, _Cond} =
- StreamErrorTag} | _] ->
- [StreamErrorTag]
- end,
- gen_fsm:sync_send_all_state_event(FsmRef,
- {stop, {stream_error,OutPacket}}),
- case StreamErrCond of
- null ->
- {200, ?HEADER,
- "<body type='terminate' "
- "condition='internal-server-error' "
- "xmlns='"++?NS_HTTP_BIND++"'/>"};
- _ ->
- {200, ?HEADER,
- "<body type='terminate' "
- "condition='remote-stream-error' "
- "xmlns='"++?NS_HTTP_BIND++"' " ++
- "xmlns:stream='"++?NS_STREAM++"'>" ++
- elements_to_string(StreamErrCond) ++
- "</body>"}
- end
- end
- end
+ {200, ?HEADER,
+ <<"<body type='terminate' condition='remote-stre"
+ "am-error' xmlns='",
+ (?NS_HTTP_BIND)/binary, "' ", "xmlns:stream='",
+ (?NS_STREAM)/binary, "'>",
+ (elements_to_string(StreamErrCond))/binary,
+ "</body>">>}
+ end
+ end
+ end
end.
parse_request(Data, PayloadSize, MaxStanzaSize) ->
- ?DEBUG("--- incoming data --- ~n~s~n --- END --- ", [Data]),
- %% MR: I do not think it works if put put several elements in the
- %% same body:
+ ?DEBUG("--- incoming data --- ~n~s~n --- END "
+ "--- ",
+ [Data]),
case xml_stream:parse_element(Data) of
- {xmlelement, "body", Attrs, Els} ->
- Xmlns = xml:get_attr_s("xmlns",Attrs),
- if
- Xmlns /= ?NS_HTTP_BIND ->
- {error, bad_request};
- true ->
- case catch list_to_integer(xml:get_attr_s("rid", Attrs)) of
- {'EXIT', _} ->
- {error, bad_request};
- Rid ->
- %% I guess this is to remove XMLCDATA: Is it really needed ?
- FixedEls =
- lists:filter(
- fun(I) ->
- case I of
- {xmlelement, _, _, _} ->
- true;
- _ ->
- false
- end
- end, Els),
- Sid = xml:get_attr_s("sid",Attrs),
- if
- PayloadSize =< MaxStanzaSize ->
- {ok, {Sid, Rid, Attrs, FixedEls}};
- true ->
- {size_limit, Sid}
- end
- end
- end;
- {xmlelement, _Name, _Attrs, _Els} ->
- {error, bad_request};
- {error, _Reason} ->
- {error, bad_request}
+ #xmlel{name = <<"body">>, attrs = Attrs,
+ children = Els} ->
+ Xmlns = xml:get_attr_s(<<"xmlns">>, Attrs),
+ if Xmlns /= (?NS_HTTP_BIND) -> {error, bad_request};
+ true ->
+ case catch
+ jlib:binary_to_integer(xml:get_attr_s(<<"rid">>,
+ Attrs))
+ of
+ {'EXIT', _} -> {error, bad_request};
+ Rid ->
+ FixedEls = lists:filter(fun (I) ->
+ case I of
+ #xmlel{} -> true;
+ _ -> false
+ end
+ end,
+ Els),
+ Sid = xml:get_attr_s(<<"sid">>, Attrs),
+ if PayloadSize =< MaxStanzaSize ->
+ {ok, {Sid, Rid, Attrs, FixedEls}};
+ true -> {size_limit, Sid}
+ end
+ end
+ end;
+ #xmlel{} -> {error, bad_request};
+ {error, _Reason} -> {error, bad_request}
end.
-send_receiver_reply(undefined, _Reply) ->
- ok;
+send_receiver_reply(undefined, _Reply) -> ok;
send_receiver_reply(Receiver, Reply) ->
gen_fsm:reply(Receiver, Reply).
-
%% Cancel timer and empty message queue.
-cancel_timer(undefined) ->
- ok;
+cancel_timer(undefined) -> ok;
cancel_timer(Timer) ->
erlang:cancel_timer(Timer),
- receive
- {timeout, Timer, _} ->
- ok
- after 0 ->
- ok
- end.
+ receive {timeout, Timer, _} -> ok after 0 -> ok end.
%% If client asked for a pause (pause > 0), we apply the pause value
%% as inactivity timer:
-set_inactivity_timer(Pause, _MaxInactivity) when Pause > 0 ->
- erlang:start_timer(Pause*1000, self(), []);
+set_inactivity_timer(Pause, _MaxInactivity)
+ when Pause > 0 ->
+ erlang:start_timer(Pause * 1000, self(), []);
%% Otherwise, we apply the max_inactivity value as inactivity timer:
set_inactivity_timer(_Pause, MaxInactivity) ->
erlang:start_timer(MaxInactivity, self(), []).
-
%% TODO: Use tail recursion and list reverse ?
-elements_to_string([]) ->
- [];
+elements_to_string([]) -> [];
elements_to_string([El | Els]) ->
- [xml:element_to_binary(El)|elements_to_string(Els)].
+ [xml:element_to_binary(El) | elements_to_string(Els)].
%% @spec (To, Default::integer()) -> integer()
%% where To = [] | {Host::string(), Version::string()}
get_max_inactivity({Host, _}, Default) ->
- case gen_mod:get_module_opt(Host, mod_http_bind, max_inactivity, undefined) of
- Seconds when is_integer(Seconds) ->
- Seconds * 1000;
- undefined ->
- Default
+ case gen_mod:get_module_opt(Host, mod_http_bind, max_inactivity,
+ fun(I) when is_integer(I), I>0 -> I end,
+ undefined)
+ of
+ Seconds when is_integer(Seconds) -> Seconds * 1000;
+ undefined -> Default
end;
-get_max_inactivity(_, Default) ->
- Default.
+get_max_inactivity(_, Default) -> Default.
get_max_pause({Host, _}) ->
- gen_mod:get_module_opt(Host, mod_http_bind, max_pause, ?MAX_PAUSE);
-get_max_pause(_) ->
- ?MAX_PAUSE.
+ gen_mod:get_module_opt(Host, mod_http_bind, max_pause,
+ fun(I) when is_integer(I), I>0 -> I end,
+ ?MAX_PAUSE);
+get_max_pause(_) -> ?MAX_PAUSE.
%% Current time as integer
tnow() ->
{TMegSec, TSec, TMSec} = now(),
(TMegSec * 1000000 + TSec) * 1000000 + TMSec.
-check_default_xmlns({xmlelement, Name, Attrs, Els} = El) ->
- case xml:get_tag_attr_s("xmlns", El) of
- "" -> {xmlelement, Name, [{"xmlns", ?NS_CLIENT} | Attrs], Els};
- _ -> El
+check_default_xmlns(#xmlel{name = Name, attrs = Attrs,
+ children = Els} =
+ El) ->
+ case xml:get_tag_attr_s(<<"xmlns">>, El) of
+ <<"">> ->
+ #xmlel{name = Name,
+ attrs = [{<<"xmlns">>, ?NS_CLIENT} | Attrs],
+ children = Els};
+ _ -> El
end;
-check_default_xmlns(El) ->
- El.
+check_default_xmlns(El) -> El.
%% Check that mod_http_bind has been defined in config file.
%% Print a warning in log file if this is not the case.
check_bind_module(XmppDomain) ->
case gen_mod:is_loaded(XmppDomain, mod_http_bind) of
- true -> ok;
- false -> ?ERROR_MSG("You are trying to use BOSH (HTTP Bind) in host ~p,"
- " but the module mod_http_bind is not started in"
- " that host. Configure your BOSH client to connect"
- " to the correct host, or add your desired host to"
- " the configuration, or check your 'modules'"
- " section in your ejabberd configuration file.",
- [XmppDomain])
+ true -> true;
+ false ->
+ ?ERROR_MSG("You are trying to use BOSH (HTTP Bind) "
+ "in host ~p, but the module mod_http_bind "
+ "is not started in that host. Configure "
+ "your BOSH client to connect to the correct "
+ "host, or add your desired host to the "
+ "configuration, or check your 'modules' "
+ "section in your ejabberd configuration "
+ "file.",
+ [XmppDomain]),
+ false
end.
diff --git a/src/web/ejabberd_http_poll.erl b/src/web/ejabberd_http_poll.erl
index 37be1358a..7648f5710 100644
--- a/src/web/ejabberd_http_poll.erl
+++ b/src/web/ejabberd_http_poll.erl
@@ -25,53 +25,52 @@
%%%----------------------------------------------------------------------
-module(ejabberd_http_poll).
+
-author('alexey@process-one.net').
-behaviour(gen_fsm).
%% External exports
--export([start_link/3,
- init/1,
- handle_event/3,
- handle_sync_event/4,
- code_change/4,
- handle_info/3,
- terminate/3,
- send/2,
- setopts/2,
- sockname/1, peername/1,
- controlling_process/2,
- close/1,
- process/2]).
+-export([start_link/3, init/1, handle_event/3,
+ handle_sync_event/4, code_change/4, handle_info/3,
+ terminate/3, send/2, setopts/2, sockname/1, peername/1,
+ controlling_process/2, close/1, process/2]).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
+
-include("ejabberd_http.hrl").
--record(http_poll, {id, pid}).
+-record(http_poll, {id :: pid() | binary(), pid :: pid()}).
+
+-type poll_socket() :: #http_poll{}.
+-export_type([poll_socket/0]).
--record(state, {id,
- key,
- socket,
- output = "",
- input = "",
- waiting_input = false, %% {ReceiverPid, Tag}
- last_receiver,
- http_poll_timeout,
- timer}).
+-record(state,
+ {id, key, socket, output = <<"">>, input = <<"">>,
+ waiting_input = false, last_receiver, http_poll_timeout,
+ timer}).
%-define(DBGFSM, true).
-ifdef(DBGFSM).
+
-define(FSMOPTS, [{debug, [trace]}]).
+
-else.
+
-define(FSMOPTS, []).
+
-endif.
--define(HTTP_POLL_TIMEOUT, 300000).
--define(CT, {"Content-Type", "text/xml; charset=utf-8"}).
--define(BAD_REQUEST, [?CT, {"Set-Cookie", "ID=-3:0; expires=-1"}]).
+-define(HTTP_POLL_TIMEOUT, 300).
+-define(CT,
+ {<<"Content-Type">>, <<"text/xml; charset=utf-8">>}).
+
+-define(BAD_REQUEST,
+ [?CT, {<<"Set-Cookie">>, <<"ID=-3:0; expires=-1">>}]).
%%%----------------------------------------------------------------------
%%% API
@@ -86,41 +85,37 @@ start_link(ID, Key, IP) ->
gen_fsm:start_link(?MODULE, [ID, Key, IP], ?FSMOPTS).
send({http_poll, FsmRef, _IP}, Packet) ->
- gen_fsm:sync_send_all_state_event(FsmRef, {send, Packet}).
+ gen_fsm:sync_send_all_state_event(FsmRef,
+ {send, Packet}).
setopts({http_poll, FsmRef, _IP}, Opts) ->
case lists:member({active, once}, Opts) of
- true ->
- gen_fsm:send_all_state_event(FsmRef, {activate, self()});
- _ ->
- ok
+ true ->
+ gen_fsm:send_all_state_event(FsmRef,
+ {activate, self()});
+ _ -> ok
end.
-sockname(_Socket) ->
- {ok, {{0, 0, 0, 0}, 0}}.
+sockname(_Socket) -> {ok, {{0, 0, 0, 0}, 0}}.
-peername({http_poll, _FsmRef, IP}) ->
- {ok, IP}.
+peername({http_poll, _FsmRef, IP}) -> {ok, IP}.
-controlling_process(_Socket, _Pid) ->
- ok.
+controlling_process(_Socket, _Pid) -> ok.
close({http_poll, FsmRef, _IP}) ->
catch gen_fsm:sync_send_all_state_event(FsmRef, close).
-
-process([], #request{data = Data,
- ip = IP} = _Request) ->
+process([],
+ #request{data = Data, ip = IP} = _Request) ->
case catch parse_request(Data) of
{ok, ID1, Key, NewKey, Packet} ->
ID = if
- (ID1 == "0") or (ID1 == "mobile") ->
+ (ID1 == <<"0">>) or (ID1 == <<"mobile">>) ->
NewID = sha:sha(term_to_binary({now(), make_ref()})),
- {ok, Pid} = start(NewID, "", IP),
+ {ok, Pid} = start(NewID, <<"">>, IP),
mnesia:transaction(
fun() ->
- mnesia:write(#http_poll{id = NewID,
- pid = Pid})
+ mnesia:write(#http_poll{id = NewID, pid = Pid})
end),
NewID;
true ->
@@ -128,57 +123,72 @@ process([], #request{data = Data,
end,
case http_put(ID, Key, NewKey, Packet) of
{error, not_exists} ->
- {200, ?BAD_REQUEST, ""};
+ {200, ?BAD_REQUEST, <<"">>};
{error, bad_key} ->
- {200, ?BAD_REQUEST, ""};
+ {200, ?BAD_REQUEST, <<"">>};
ok ->
receive
after 100 -> ok
end,
case http_get(ID) of
{error, not_exists} ->
- {200, ?BAD_REQUEST, ""};
+ {200, ?BAD_REQUEST, <<"">>};
{ok, OutPacket} ->
if
ID == ID1 ->
- Cookie = "ID=" ++ ID ++ "; expires=-1",
- {200, [?CT, {"Set-Cookie", Cookie}],
+ Cookie = <<"ID=", ID/binary, "; expires=-1">>,
+ {200, [?CT, {<<"Set-Cookie">>, Cookie}],
OutPacket};
- ID1 == "mobile" ->
+ ID1 == <<"mobile">> ->
{200, [?CT], [ID, $\n, OutPacket]};
true ->
- Cookie = "ID=" ++ ID ++ "; expires=-1",
- {200, [?CT, {"Set-Cookie", Cookie}],
+ Cookie = <<"ID=", ID/binary, "; expires=-1">>,
+ {200, [?CT, {<<"Set-Cookie">>, Cookie}],
OutPacket}
end
end
end;
_ ->
HumanHTMLxmlel = get_human_html_xmlel(),
- {200, [?CT, {"Set-Cookie", "ID=-2:0; expires=-1"}], HumanHTMLxmlel}
+ {200, [?CT, {<<"Set-Cookie">>, <<"ID=-2:0; expires=-1">>}], HumanHTMLxmlel}
end;
process(_, _Request) ->
- {400, [], {xmlelement, "h1", [],
- [{xmlcdata, "400 Bad Request"}]}}.
+ {400, [],
+ #xmlel{name = <<"h1">>, attrs = [],
+ children = [{xmlcdata, <<"400 Bad Request">>}]}}.
%% Code copied from mod_http_bind.erl and customized
get_human_html_xmlel() ->
- Heading = "ejabberd " ++ atom_to_list(?MODULE),
- {xmlelement, "html", [{"xmlns", "http://www.w3.org/1999/xhtml"}],
- [{xmlelement, "head", [],
- [{xmlelement, "title", [], [{xmlcdata, Heading}]}]},
- {xmlelement, "body", [],
- [{xmlelement, "h1", [], [{xmlcdata, Heading}]},
- {xmlelement, "p", [],
- [{xmlcdata, "An implementation of "},
- {xmlelement, "a",
- [{"href", "http://xmpp.org/extensions/xep-0025.html"}],
- [{xmlcdata, "Jabber HTTP Polling (XEP-0025)"}]}]},
- {xmlelement, "p", [],
- [{xmlcdata, "This web page is only informative. "
- "To use HTTP-Poll you need a Jabber/XMPP client that supports it."}
- ]}
- ]}]}.
+ Heading = <<"ejabberd ",
+ (iolist_to_binary(atom_to_list(?MODULE)))/binary>>,
+ #xmlel{name = <<"html">>,
+ attrs =
+ [{<<"xmlns">>, <<"http://www.w3.org/1999/xhtml">>}],
+ children =
+ [#xmlel{name = <<"head">>, attrs = [],
+ children =
+ [#xmlel{name = <<"title">>, attrs = [],
+ children = [{xmlcdata, Heading}]}]},
+ #xmlel{name = <<"body">>, attrs = [],
+ children =
+ [#xmlel{name = <<"h1">>, attrs = [],
+ children = [{xmlcdata, Heading}]},
+ #xmlel{name = <<"p">>, attrs = [],
+ children =
+ [{xmlcdata, <<"An implementation of ">>},
+ #xmlel{name = <<"a">>,
+ attrs =
+ [{<<"href">>,
+ <<"http://xmpp.org/extensions/xep-0025.html">>}],
+ children =
+ [{xmlcdata,
+ <<"Jabber HTTP Polling (XEP-0025)">>}]}]},
+ #xmlel{name = <<"p">>, attrs = [],
+ children =
+ [{xmlcdata,
+ <<"This web page is only informative. To "
+ "use HTTP-Poll you need a Jabber/XMPP "
+ "client that supports it.">>}]}]}]}.
%%%----------------------------------------------------------------------
%%% Callback functions from gen_fsm
@@ -193,39 +203,27 @@ get_human_html_xmlel() ->
%%----------------------------------------------------------------------
init([ID, Key, IP]) ->
?INFO_MSG("started: ~p", [{ID, Key, IP}]),
-
- %% Read c2s options from the first ejabberd_c2s configuration in
- %% the config file listen section
- %% TODO: We should have different access and shaper values for
- %% each connector. The default behaviour should be however to use
- %% the default c2s restrictions if not defined for the current
- %% connector.
Opts = ejabberd_c2s_config:get_c2s_limits(),
-
- HTTPPollTimeout = case ejabberd_config:get_local_option({http_poll_timeout,
- ?MYNAME}) of
- %% convert seconds of option into milliseconds
- Int when is_integer(Int) -> Int*1000;
- undefined -> ?HTTP_POLL_TIMEOUT
- end,
-
+ HTTPPollTimeout = ejabberd_config:get_local_option(
+ {http_poll_timeout, ?MYNAME},
+ fun(I) when is_integer(I), I>0 -> I end,
+ ?HTTP_POLL_TIMEOUT) * 1000,
Socket = {http_poll, self(), IP},
- ejabberd_socket:start(ejabberd_c2s, ?MODULE, Socket, Opts),
+ ejabberd_socket:start(ejabberd_c2s, ?MODULE, Socket,
+ Opts),
Timer = erlang:start_timer(HTTPPollTimeout, self(), []),
- {ok, loop, #state{id = ID,
- key = Key,
- socket = Socket,
- http_poll_timeout = HTTPPollTimeout,
- timer = Timer}}.
+ {ok, loop,
+ #state{id = ID, key = Key, socket = Socket,
+ http_poll_timeout = HTTPPollTimeout, timer = Timer}}.
%%----------------------------------------------------------------------
%% Func: StateName/2
%% Returns: {next_state, NextStateName, NextStateData} |
%% {next_state, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData}
+%% {stop, Reason, NewStateData}
%%----------------------------------------------------------------------
-
%%----------------------------------------------------------------------
%% Func: StateName/3
%% Returns: {next_state, NextStateName, NextStateData} |
@@ -234,6 +232,7 @@ init([ID, Key, IP]) ->
%% {reply, Reply, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData} |
%% {stop, Reason, Reply, NewStateData}
+%% {stop, Reason, Reply, NewStateData}
%%----------------------------------------------------------------------
%state_name(Event, From, StateData) ->
% Reply = ok,
@@ -247,18 +246,17 @@ init([ID, Key, IP]) ->
%%----------------------------------------------------------------------
handle_event({activate, From}, StateName, StateData) ->
case StateData#state.input of
- "" ->
- {next_state, StateName,
- StateData#state{waiting_input = {From, ok}}};
- Input ->
- Receiver = From,
- Receiver ! {tcp, StateData#state.socket, list_to_binary(Input)},
- {next_state, StateName, StateData#state{input = "",
- waiting_input = false,
- last_receiver = Receiver
- }}
+ <<"">> ->
+ {next_state, StateName,
+ StateData#state{waiting_input = {From, ok}}};
+ Input ->
+ Receiver = From,
+ Receiver !
+ {tcp, StateData#state.socket, iolist_to_binary(Input)},
+ {next_state, StateName,
+ StateData#state{input = <<"">>, waiting_input = false,
+ last_receiver = Receiver}}
end;
-
handle_event(_Event, StateName, StateData) ->
{next_state, StateName, StateData}.
@@ -271,68 +269,60 @@ handle_event(_Event, StateName, StateData) ->
%% {stop, Reason, NewStateData} |
%% {stop, Reason, Reply, NewStateData}
%%----------------------------------------------------------------------
-handle_sync_event({send, Packet}, _From, StateName, StateData) ->
- Packet2 = if
- is_binary(Packet) ->
- binary_to_list(Packet);
- true ->
- Packet
+handle_sync_event({send, Packet}, _From, StateName,
+ StateData) ->
+ Packet2 = if is_binary(Packet) -> (Packet);
+ true -> Packet
end,
- Output = StateData#state.output ++ [lists:flatten(Packet2)],
+ Output = StateData#state.output ++
+ [lists:flatten(Packet2)],
Reply = ok,
- {reply, Reply, StateName, StateData#state{output = Output}};
-
+ {reply, Reply, StateName,
+ StateData#state{output = Output}};
handle_sync_event(stop, _From, _StateName, StateData) ->
- Reply = ok,
- {stop, normal, Reply, StateData};
-
+ Reply = ok, {stop, normal, Reply, StateData};
handle_sync_event({http_put, Key, NewKey, Packet},
_From, StateName, StateData) ->
Allow = case StateData#state.key of
- "" ->
- true;
- OldKey ->
- NextKey = jlib:encode_base64(
- binary_to_list(crypto:sha(Key))),
- if
- OldKey == NextKey ->
- true;
- true ->
- false
- end
+ <<"">> -> true;
+ OldKey ->
+ NextKey = jlib:encode_base64((crypto:sha(Key))),
+ if OldKey == NextKey -> true;
+ true -> false
+ end
end,
- if
- Allow ->
- case StateData#state.waiting_input of
- false ->
- Input = [StateData#state.input|Packet],
- Reply = ok,
- {reply, Reply, StateName, StateData#state{input = Input,
- key = NewKey}};
- {Receiver, _Tag} ->
- Receiver ! {tcp, StateData#state.socket,
- list_to_binary(Packet)},
- cancel_timer(StateData#state.timer),
- Timer = erlang:start_timer(StateData#state.http_poll_timeout, self(), []),
- Reply = ok,
- {reply, Reply, StateName,
- StateData#state{waiting_input = false,
- last_receiver = Receiver,
- key = NewKey,
- timer = Timer}}
- end;
- true ->
- Reply = {error, bad_key},
- {reply, Reply, StateName, StateData}
+ if Allow ->
+ case StateData#state.waiting_input of
+ false ->
+ Input = [StateData#state.input | Packet],
+ Reply = ok,
+ {reply, Reply, StateName,
+ StateData#state{input = Input, key = NewKey}};
+ {Receiver, _Tag} ->
+ Receiver !
+ {tcp, StateData#state.socket, iolist_to_binary(Packet)},
+ cancel_timer(StateData#state.timer),
+ Timer =
+ erlang:start_timer(StateData#state.http_poll_timeout,
+ self(), []),
+ Reply = ok,
+ {reply, Reply, StateName,
+ StateData#state{waiting_input = false,
+ last_receiver = Receiver, key = NewKey,
+ timer = Timer}}
+ end;
+ true ->
+ Reply = {error, bad_key},
+ {reply, Reply, StateName, StateData}
end;
-
-handle_sync_event(http_get, _From, StateName, StateData) ->
+handle_sync_event(http_get, _From, StateName,
+ StateData) ->
Reply = {ok, StateData#state.output},
- {reply, Reply, StateName, StateData#state{output = ""}};
-
-handle_sync_event(_Event, _From, StateName, StateData) ->
- Reply = ok,
- {reply, Reply, StateName, StateData}.
+ {reply, Reply, StateName,
+ StateData#state{output = <<"">>}};
+handle_sync_event(_Event, _From, StateName,
+ StateData) ->
+ Reply = ok, {reply, Reply, StateName, StateData}.
code_change(_OldVsn, StateName, StateData, _Extra) ->
{ok, StateName, StateData}.
@@ -346,7 +336,6 @@ code_change(_OldVsn, StateName, StateData, _Extra) ->
handle_info({timeout, Timer, _}, _StateName,
#state{timer = Timer} = StateData) ->
{stop, normal, StateData};
-
handle_info(_, StateName, StateData) ->
{next_state, StateName, StateData}.
@@ -361,17 +350,14 @@ terminate(_Reason, _StateName, StateData) ->
mnesia:delete({http_poll, StateData#state.id})
end),
case StateData#state.waiting_input of
- false ->
- %% We are testing this case due to "socket activation": If we pass
- %% here and the "socket" is not ready to receive, the tcp_closed
- %% will be lost.
- case StateData#state.last_receiver of
- undefined -> ok;
- Receiver ->
- Receiver ! {tcp_closed, StateData#state.socket}
- end;
- {Receiver, _Tag} ->
- Receiver ! {tcp_closed, StateData#state.socket}
+ false ->
+ case StateData#state.last_receiver of
+ undefined -> ok;
+ Receiver ->
+ Receiver ! {tcp_closed, StateData#state.socket}
+ end;
+ {Receiver, _Tag} ->
+ Receiver ! {tcp_closed, StateData#state.socket}
end,
catch resend_messages(StateData#state.output),
ok.
@@ -397,58 +383,46 @@ http_get(ID) ->
gen_fsm:sync_send_all_state_event(FsmRef, http_get)
end.
-
parse_request(Data) ->
- Comma = string:chr(Data, $,),
- Header = lists:sublist(Data, Comma - 1),
- Packet = lists:nthtail(Comma, Data),
- {ID, Key, NewKey} =
- case string:tokens(Header, ";") of
- [ID1] ->
- {ID1, "", ""};
- [ID1, Key1] ->
- {ID1, Key1, Key1};
- [ID1, Key1, NewKey1] ->
- {ID1, Key1, NewKey1}
- end,
+ Comma = str:chr(Data, $,),
+ Header = str:substr(Data, 1, Comma - 1),
+ Packet = str:substr(Data, Comma + 1, byte_size(Data)),
+ {ID, Key, NewKey} = case str:tokens(Header, <<";">>) of
+ [ID1] -> {ID1, <<"">>, <<"">>};
+ [ID1, Key1] -> {ID1, Key1, Key1};
+ [ID1, Key1, NewKey1] -> {ID1, Key1, NewKey1}
+ end,
{ok, ID, Key, NewKey, Packet}.
-
cancel_timer(Timer) ->
erlang:cancel_timer(Timer),
- receive
- {timeout, Timer, _} ->
- ok
- after 0 ->
- ok
- end.
+ receive {timeout, Timer, _} -> ok after 0 -> ok end.
%% Resend the polled messages
resend_messages(Messages) ->
- lists:foreach(fun(Packet) ->
- resend_message(Packet)
- end, Messages).
-
%% This function is used to resend messages that have been polled but not
%% delivered.
+ lists:foreach(fun (Packet) -> resend_message(Packet)
+ end,
+ Messages).
+
resend_message(Packet) ->
- {xmlelement, Name, _, _} = ParsedPacket = xml_stream:parse_element(Packet),
- %% Avoid sending <stream:error>
- if Name == "iq"; Name == "message"; Name == "presence" ->
- From = get_jid("from", ParsedPacket),
- To = get_jid("to", ParsedPacket),
- ?DEBUG("Resend ~p ~p ~p~n",[From,To, ParsedPacket]),
- ejabberd_router:route(From, To, ParsedPacket);
- true ->
- ok
+ #xmlel{name = Name} = ParsedPacket =
+ xml_stream:parse_element(Packet),
+ if Name == <<"iq">>;
+ Name == <<"message">>;
+ Name == <<"presence">> ->
+ From = get_jid(<<"from">>, ParsedPacket),
+ To = get_jid(<<"to">>, ParsedPacket),
+ ?DEBUG("Resend ~p ~p ~p~n", [From, To, ParsedPacket]),
+ ejabberd_router:route(From, To, ParsedPacket);
+ true -> ok
end.
%% Type can be "from" or "to"
%% Parsed packet is a parsed Jabber packet.
get_jid(Type, ParsedPacket) ->
case xml:get_tag_attr(Type, ParsedPacket) of
- {value, StringJid} ->
- jlib:string_to_jid(StringJid);
- false ->
- jlib:make_jid("","","")
+ {value, StringJid} -> jlib:string_to_jid(StringJid);
+ false -> jlib:make_jid(<<"">>, <<"">>, <<"">>)
end.
diff --git a/src/web/ejabberd_web.erl b/src/web/ejabberd_web.erl
index e75f61d09..8c7ccaf69 100644
--- a/src/web/ejabberd_web.erl
+++ b/src/web/ejabberd_web.erl
@@ -2,6 +2,7 @@
%%% File : ejabberd_web.erl
%%% Author : Alexey Shchepin <alexey@process-one.net>
%%% Purpose :
+%%% Purpose :
%%% Created : 28 Feb 2004 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
@@ -25,56 +26,80 @@
%%%----------------------------------------------------------------------
-module(ejabberd_web).
+
-author('alexey@process-one.net').
%% External exports
--export([make_xhtml/1, make_xhtml/2,
- error/1]).
+-export([make_xhtml/1, make_xhtml/2, error/1]).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
--include("ejabberd_http.hrl").
+-include("ejabberd_http.hrl").
%% XXX bard: there are variants of make_xhtml in ejabberd_http and
%% ejabberd_web_admin. It might be a good idea to centralize it here
%% and also create an ejabberd_web.hrl file holding the macros, so
%% that third parties can use ejabberd_web as an "utility" library.
-make_xhtml(Els) ->
- make_xhtml([], Els).
+make_xhtml(Els) -> make_xhtml([], Els).
make_xhtml(HeadEls, Els) ->
- {xmlelement, "html", [{"xmlns", "http://www.w3.org/1999/xhtml"},
- {"xml:lang", "en"},
- {"lang", "en"}],
- [{xmlelement, "head", [],
- [{xmlelement, "meta", [{"http-equiv", "Content-Type"},
- {"content", "text/html; charset=utf-8"}], []}
- | HeadEls]},
- {xmlelement, "body", [], Els}
- ]}.
-
-
--define(X(Name), {xmlelement, Name, [], []}).
--define(XA(Name, Attrs), {xmlelement, Name, Attrs, []}).
--define(XE(Name, Els), {xmlelement, Name, [], Els}).
--define(XAE(Name, Attrs, Els), {xmlelement, Name, Attrs, Els}).
+ #xmlel{name = <<"html">>,
+ attrs =
+ [{<<"xmlns">>, <<"http://www.w3.org/1999/xhtml">>},
+ {<<"xml:lang">>, <<"en">>}, {<<"lang">>, <<"en">>}],
+ children =
+ [#xmlel{name = <<"head">>, attrs = [],
+ children =
+ [#xmlel{name = <<"meta">>,
+ attrs =
+ [{<<"http-equiv">>, <<"Content-Type">>},
+ {<<"content">>,
+ <<"text/html; charset=utf-8">>}],
+ children = []}
+ | HeadEls]},
+ #xmlel{name = <<"body">>, attrs = [], children = Els}]}.
+
+-define(X(Name),
+ #xmlel{name = Name, attrs = [], children = []}).
+
+-define(XA(Name, Attrs),
+ #xmlel{name = Name, attrs = Attrs, children = []}).
+
+-define(XE(Name, Els),
+ #xmlel{name = Name, attrs = [], children = Els}).
+
+-define(XAE(Name, Attrs, Els),
+ #xmlel{name = Name, attrs = Attrs, children = Els}).
+
-define(C(Text), {xmlcdata, Text}).
+
-define(XC(Name, Text), ?XE(Name, [?C(Text)])).
--define(XAC(Name, Attrs, Text), ?XAE(Name, Attrs, [?C(Text)])).
--define(LI(Els), ?XE("li", Els)).
--define(A(URL, Els), ?XAE("a", [{"href", URL}], Els)).
+-define(XAC(Name, Attrs, Text),
+ ?XAE(Name, Attrs, [?C(Text)])).
+
+-define(LI(Els), ?XE(<<"li">>, Els)).
+
+-define(A(URL, Els),
+ ?XAE(<<"a">>, [{<<"href">>, URL}], Els)).
+
-define(AC(URL, Text), ?A(URL, [?C(Text)])).
--define(P, ?X("p")).
--define(BR, ?X("br")).
+
+-define(P, ?X(<<"p">>)).
+
+-define(BR, ?X(<<"br">>)).
+
-define(INPUT(Type, Name, Value),
- ?XA("input", [{"type", Type},
- {"name", Name},
- {"value", Value}])).
+ ?XA(<<"input">>,
+ [{<<"type">>, Type}, {<<"name">>, Name},
+ {<<"value">>, Value}])).
error(not_found) ->
- {404, [], make_xhtml([?XC("h1", "404 Not Found")])};
+ {404, [],
+ make_xhtml([?XC(<<"h1">>, <<"404 Not Found">>)])};
error(not_allowed) ->
- {401, [], make_xhtml([?XC("h1", "401 Unauthorized")])}.
+ {401, [],
+ make_xhtml([?XC(<<"h1">>, <<"401 Unauthorized">>)])}.
diff --git a/src/web/ejabberd_web_admin.erl b/src/web/ejabberd_web_admin.erl
index c01b9d9f9..73c7ab52a 100644
--- a/src/web/ejabberd_web_admin.erl
+++ b/src/web/ejabberd_web_admin.erl
@@ -27,25 +27,27 @@
%%%% definitions
-module(ejabberd_web_admin).
+
-author('alexey@process-one.net').
%% External exports
--export([process/2,
- list_users/4,
- list_users_in_diapason/4,
- pretty_print_xml/1,
+-export([process/2, list_users/4,
+ list_users_in_diapason/4, pretty_print_xml/1,
term_to_id/1]).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
+
-include("ejabberd_http.hrl").
+
-include("ejabberd_web_admin.hrl").
-define(INPUTATTRS(Type, Name, Value, Attrs),
- ?XA("input", Attrs ++
- [{"type", Type},
- {"name", Name},
- {"value", Value}])).
+ ?XA(<<"input">>,
+ (Attrs ++
+ [{<<"type">>, Type}, {<<"name">>, Name},
+ {<<"value">>, Value}]))).
%%%==================================
%%%% get_acl_access
@@ -54,66 +56,67 @@
%% where Method = 'GET' | 'POST'
%% All accounts can access those URLs
-get_acl_rule([],_) -> {"localhost", [all]};
-get_acl_rule(["style.css"],_) -> {"localhost", [all]};
-get_acl_rule(["logo.png"],_) -> {"localhost", [all]};
-get_acl_rule(["logo-fill.png"],_) -> {"localhost", [all]};
-get_acl_rule(["favicon.ico"],_) -> {"localhost", [all]};
-get_acl_rule(["additions.js"],_) -> {"localhost", [all]};
+get_acl_rule([], _) -> {<<"localhost">>, [all]};
+get_acl_rule([<<"style.css">>], _) ->
+ {<<"localhost">>, [all]};
+get_acl_rule([<<"logo.png">>], _) ->
+ {<<"localhost">>, [all]};
+get_acl_rule([<<"logo-fill.png">>], _) ->
+ {<<"localhost">>, [all]};
+get_acl_rule([<<"favicon.ico">>], _) ->
+ {<<"localhost">>, [all]};
+get_acl_rule([<<"additions.js">>], _) ->
+ {<<"localhost">>, [all]};
%% This page only displays vhosts that the user is admin:
-get_acl_rule(["vhosts"],_) -> {"localhost", [all]};
-
+get_acl_rule([<<"vhosts">>], _) ->
+ {<<"localhost">>, [all]};
%% The pages of a vhost are only accesible if the user is admin of that vhost:
-get_acl_rule(["server", VHost | _RPath], Method)
- when Method=:='GET' orelse Method=:='HEAD' ->
+get_acl_rule([<<"server">>, VHost | _RPath], Method)
+ when Method =:= 'GET' orelse Method =:= 'HEAD' ->
{VHost, [configure, webadmin_view]};
-get_acl_rule(["server", VHost | _RPath], 'POST') -> {VHost, [configure]};
-
+get_acl_rule([<<"server">>, VHost | _RPath], 'POST') ->
+ {VHost, [configure]};
%% Default rule: only global admins can access any other random page
get_acl_rule(_RPath, Method)
- when Method=:='GET' orelse Method=:='HEAD' ->
+ when Method =:= 'GET' orelse Method =:= 'HEAD' ->
{global, [configure, webadmin_view]};
get_acl_rule(_RPath, 'POST') -> {global, [configure]}.
is_acl_match(Host, Rules, Jid) ->
- lists:any(
- fun(Rule) ->
- allow == acl:match_rule(Host, Rule, Jid)
- end,
- Rules).
+ lists:any(fun (Rule) ->
+ allow == acl:match_rule(Host, Rule, Jid)
+ end,
+ Rules).
%%%==================================
%%%% Menu Items Access
get_jid(Auth, HostHTTP, Method) ->
case get_auth_admin(Auth, HostHTTP, [], Method) of
- {ok, {User, Server}} ->
- jlib:make_jid(User, Server, "");
- {unauthorized, Error} ->
- ?ERROR_MSG("Unauthorized ~p: ~p", [Auth, Error]),
- throw({unauthorized, Auth})
+ {ok, {User, Server}} ->
+ jlib:make_jid(User, Server, <<"">>);
+ {unauthorized, Error} ->
+ ?ERROR_MSG("Unauthorized ~p: ~p", [Auth, Error]),
+ throw({unauthorized, Auth})
end.
get_menu_items(global, cluster, Lang, JID) ->
{Base, _, Items} = make_server_menu([], [], Lang, JID),
- lists:map(
- fun({URI, Name}) ->
- {Base++URI++"/", Name};
- ({URI, Name, _SubMenu}) ->
- {Base++URI++"/", Name}
- end,
- Items
- );
+ lists:map(fun ({URI, Name}) ->
+ {<<Base/binary, URI/binary, "/">>, Name};
+ ({URI, Name, _SubMenu}) ->
+ {<<Base/binary, URI/binary, "/">>, Name}
+ end,
+ Items);
get_menu_items(Host, cluster, Lang, JID) ->
{Base, _, Items} = make_host_menu(Host, [], Lang, JID),
- lists:map(
- fun({URI, Name}) ->
- {Base++URI++"/", Name};
- ({URI, Name, _SubMenu}) ->
- {Base++URI++"/", Name}
- end,
- Items
- ).
+ lists:map(fun ({URI, Name}) ->
+ {<<Base/binary, URI/binary, "/">>, Name};
+ ({URI, Name, _SubMenu}) ->
+ {<<Base/binary, URI/binary, "/">>, Name}
+ end,
+ Items).
+
%% get_menu_items(Host, Node, Lang, JID) ->
%% {Base, _, Items} = make_host_node_menu(Host, Node, Lang, JID),
%% lists:map(
@@ -130,7 +133,7 @@ is_allowed_path(BasePath, {Path, _}, JID) ->
is_allowed_path(BasePath, {Path, _, _}, JID) ->
is_allowed_path(BasePath ++ [Path], JID).
-is_allowed_path(["admin" | Path], JID) ->
+is_allowed_path([<<"admin">> | Path], JID) ->
is_allowed_path(Path, JID);
is_allowed_path(Path, JID) ->
{HostOfRule, AccessRule} = get_acl_rule(Path, 'GET'),
@@ -147,122 +150,132 @@ is_allowed_path(Path, JID) ->
%% where Path = [string()]
%% URL = string()
%% Convert "admin/user/tom" -> ["admin", "user", "tom"]
-url_to_path(URL) ->
- string:tokens(URL, "/").
-
+url_to_path(URL) -> str:tokens(URL, <<"/">>).
+
%%%==================================
%%%% process/2
-process(["doc", LocalFile], _Request) ->
+process([<<"doc">>, LocalFile], _Request) ->
DocPath = case os:getenv("EJABBERD_DOC_PATH") of
- P when is_list(P) -> P;
- false -> "/share/doc/ejabberd/"
+ P when is_list(P) -> P;
+ false -> <<"/share/doc/ejabberd/">>
end,
- %% Code based in mod_http_fileserver
FileName = filename:join(DocPath, LocalFile),
case file:read_file(FileName) of
- {ok, FileContents} ->
- ?DEBUG("Delivering content.", []),
- {200,
- [{"Server", "ejabberd"}],
- FileContents};
- {error, Error} ->
- ?DEBUG("Delivering error: ~p", [Error]),
- Help = " " ++ FileName ++ " - Try to specify the path to ejabberd documentation "
- "with the environment variable EJABBERD_DOC_PATH. Check the ejabberd Guide for more information.",
- case Error of
- eacces -> {403, [], "Forbidden"++Help};
- enoent -> {404, [], "Not found"++Help};
- _Else -> {404, [], atom_to_list(Error)++Help}
- end
+ {ok, FileContents} ->
+ ?DEBUG("Delivering content.", []),
+ {200, [{<<"Server">>, <<"ejabberd">>}], FileContents};
+ {error, Error} ->
+ ?DEBUG("Delivering error: ~p", [Error]),
+ Help = <<" ", FileName/binary,
+ " - Try to specify the path to ejabberd "
+ "documentation with the environment variable "
+ "EJABBERD_DOC_PATH. Check the ejabberd "
+ "Guide for more information.">>,
+ case Error of
+ eacces -> {403, [], <<"Forbidden", Help/binary>>};
+ enoent -> {404, [], <<"Not found", Help/binary>>};
+ _Else ->
+ {404, [], <<(iolist_to_binary(atom_to_list(Error)))/binary, Help/binary>>}
+ end
end;
-
-process(["server", SHost | RPath] = Path, #request{auth = Auth, lang = Lang, host = HostHTTP, method = Method} = Request) ->
+process([<<"server">>, SHost | RPath] = Path,
+ #request{auth = Auth, lang = Lang, host = HostHTTP,
+ method = Method} =
+ Request) ->
Host = jlib:nameprep(SHost),
case lists:member(Host, ?MYHOSTS) of
- true ->
- case get_auth_admin(Auth, HostHTTP, Path, Method) of
- {ok, {User, Server}} ->
- AJID = get_jid(Auth, HostHTTP, Method),
- process_admin(Host, Request#request{path = RPath,
- auth = {auth_jid, Auth, AJID},
- us = {User, Server}});
- {unauthorized, "no-auth-provided"} ->
- {401,
- [{"WWW-Authenticate", "basic realm=\"ejabberd\""}],
- ejabberd_web:make_xhtml([?XCT("h1", "Unauthorized")])};
- {unauthorized, Error} ->
- {BadUser, _BadPass} = Auth,
- {IPT, _Port} = Request#request.ip,
- IPS = inet_parse:ntoa(IPT),
- ?WARNING_MSG("Access of ~p from ~p failed with error: ~p",
- [BadUser, IPS, Error]),
- {401,
- [{"WWW-Authenticate",
- "basic realm=\"auth error, retry login to ejabberd\""}],
- ejabberd_web:make_xhtml([?XCT("h1", "Unauthorized")])}
- end;
- false ->
- ejabberd_web:error(not_found)
+ true ->
+ case get_auth_admin(Auth, HostHTTP, Path, Method) of
+ {ok, {User, Server}} ->
+ AJID = get_jid(Auth, HostHTTP, Method),
+ process_admin(Host,
+ Request#request{path = RPath,
+ auth = {auth_jid, Auth, AJID},
+ us = {User, Server}});
+ {unauthorized, <<"no-auth-provided">>} ->
+ {401,
+ [{<<"WWW-Authenticate">>,
+ <<"basic realm=\"ejabberd\"">>}],
+ ejabberd_web:make_xhtml([?XCT(<<"h1">>,
+ <<"Unauthorized">>)])};
+ {unauthorized, Error} ->
+ {BadUser, _BadPass} = Auth,
+ {IPT, _Port} = Request#request.ip,
+ IPS = jlib:ip_to_list(IPT),
+ ?WARNING_MSG("Access of ~p from ~p failed with error: ~p",
+ [BadUser, IPS, Error]),
+ {401,
+ [{<<"WWW-Authenticate">>,
+ <<"basic realm=\"auth error, retry login "
+ "to ejabberd\"">>}],
+ ejabberd_web:make_xhtml([?XCT(<<"h1">>,
+ <<"Unauthorized">>)])}
+ end;
+ false -> ejabberd_web:error(not_found)
end;
-
-process(RPath, #request{auth = Auth, lang = Lang, host = HostHTTP, method = Method} = Request) ->
+process(RPath,
+ #request{auth = Auth, lang = Lang, host = HostHTTP,
+ method = Method} =
+ Request) ->
case get_auth_admin(Auth, HostHTTP, RPath, Method) of
- {ok, {User, Server}} ->
- AJID = get_jid(Auth, HostHTTP, Method),
- process_admin(global, Request#request{path = RPath,
- auth = {auth_jid, Auth, AJID},
- us = {User, Server}});
- {unauthorized, "no-auth-provided"} ->
- {401,
- [{"WWW-Authenticate", "basic realm=\"ejabberd\""}],
- ejabberd_web:make_xhtml([?XCT("h1", "Unauthorized")])};
- {unauthorized, Error} ->
- {BadUser, _BadPass} = Auth,
- {IPT, _Port} = Request#request.ip,
- IPS = inet_parse:ntoa(IPT),
- ?WARNING_MSG("Access of ~p from ~p failed with error: ~p",
- [BadUser, IPS, Error]),
- {401,
- [{"WWW-Authenticate",
- "basic realm=\"auth error, retry login to ejabberd\""}],
- ejabberd_web:make_xhtml([?XCT("h1", "Unauthorized")])}
+ {ok, {User, Server}} ->
+ AJID = get_jid(Auth, HostHTTP, Method),
+ process_admin(global,
+ Request#request{path = RPath,
+ auth = {auth_jid, Auth, AJID},
+ us = {User, Server}});
+ {unauthorized, <<"no-auth-provided">>} ->
+ {401,
+ [{<<"WWW-Authenticate">>,
+ <<"basic realm=\"ejabberd\"">>}],
+ ejabberd_web:make_xhtml([?XCT(<<"h1">>,
+ <<"Unauthorized">>)])};
+ {unauthorized, Error} ->
+ {BadUser, _BadPass} = Auth,
+ {IPT, _Port} = Request#request.ip,
+ IPS = jlib:ip_to_list(IPT),
+ ?WARNING_MSG("Access of ~p from ~p failed with error: ~p",
+ [BadUser, IPS, Error]),
+ {401,
+ [{<<"WWW-Authenticate">>,
+ <<"basic realm=\"auth error, retry login "
+ "to ejabberd\"">>}],
+ ejabberd_web:make_xhtml([?XCT(<<"h1">>,
+ <<"Unauthorized">>)])}
end.
get_auth_admin(Auth, HostHTTP, RPath, Method) ->
case Auth of
- {SJID, Pass} ->
- {HostOfRule, AccessRule} = get_acl_rule(RPath, Method),
- case jlib:string_to_jid(SJID) of
- error ->
- {unauthorized, "badformed-jid"};
- #jid{user = "", server = User} ->
- %% If the user only specified username, not username@server
- get_auth_account(HostOfRule, AccessRule, User, HostHTTP, Pass);
- #jid{user = User, server = Server} ->
- get_auth_account(HostOfRule, AccessRule, User, Server, Pass)
- end;
- undefined ->
- {unauthorized, "no-auth-provided"}
+ {SJID, Pass} ->
+ {HostOfRule, AccessRule} = get_acl_rule(RPath, Method),
+ case jlib:string_to_jid(SJID) of
+ error -> {unauthorized, <<"badformed-jid">>};
+ #jid{user = <<"">>, server = User} ->
+ get_auth_account(HostOfRule, AccessRule, User, HostHTTP,
+ Pass);
+ #jid{user = User, server = Server} ->
+ get_auth_account(HostOfRule, AccessRule, User, Server,
+ Pass)
+ end;
+ undefined -> {unauthorized, <<"no-auth-provided">>}
end.
-get_auth_account(HostOfRule, AccessRule, User, Server, Pass) ->
+get_auth_account(HostOfRule, AccessRule, User, Server,
+ Pass) ->
case ejabberd_auth:check_password(User, Server, Pass) of
- true ->
- case is_acl_match(HostOfRule, AccessRule,
- jlib:make_jid(User, Server, "")) of
- false ->
- {unauthorized, "unprivileged-account"};
- true ->
- {ok, {User, Server}}
- end;
- false ->
- case ejabberd_auth:is_user_exists(User, Server) of
- true ->
- {unauthorized, "bad-password"};
- false ->
- {unauthorized, "inexistent-account"}
- end
+ true ->
+ case is_acl_match(HostOfRule, AccessRule,
+ jlib:make_jid(User, Server, <<"">>))
+ of
+ false -> {unauthorized, <<"unprivileged-account">>};
+ true -> {ok, {User, Server}}
+ end;
+ false ->
+ case ejabberd_auth:is_user_exists(User, Server) of
+ true -> {unauthorized, <<"bad-password">>};
+ false -> {unauthorized, <<"inexistent-account">>}
+ end
end.
%%%==================================
@@ -276,1205 +289,1003 @@ make_xhtml(Els, Host, Lang, JID) ->
%% Node = cluster | atom()
%% JID = jid()
make_xhtml(Els, Host, Node, Lang, JID) ->
- Base = get_base_path(Host, cluster), %% Enforcing 'cluster' on purpose here
+ Base = get_base_path(Host, cluster),
MenuItems = make_navigation(Host, Node, Lang, JID),
{200, [html],
- {xmlelement, "html", [{"xmlns", "http://www.w3.org/1999/xhtml"},
- {"xml:lang", Lang},
- {"lang", Lang}],
- [{xmlelement, "head", [],
- [?XCT("title", "ejabberd Web Admin"),
- {xmlelement, "meta", [{"http-equiv", "Content-Type"},
- {"content", "text/html; charset=utf-8"}], []},
- {xmlelement, "script", [{"src", Base ++ "/additions.js"},
- {"type", "text/javascript"}], [?C(" ")]},
- {xmlelement, "link", [{"href", Base ++ "favicon.ico"},
- {"type", "image/x-icon"},
- {"rel", "shortcut icon"}], []},
- {xmlelement, "link", [{"href", Base ++ "style.css"},
- {"type", "text/css"},
- {"rel", "stylesheet"}], []}]},
- ?XE("body",
- [?XAE("div",
- [{"id", "container"}],
- [?XAE("div",
- [{"id", "header"}],
- [?XE("h1",
- [?ACT("/admin/", "ejabberd Web Admin")]
- )]),
- ?XAE("div",
- [{"id", "navigation"}],
- [?XE("ul",
- MenuItems
- )]),
- ?XAE("div",
- [{"id", "content"}],
- Els),
- ?XAE("div",
- [{"id", "clearcopyright"}],
- [{xmlcdata, ""}])]),
- ?XAE("div",
- [{"id", "copyrightouter"}],
- [?XAE("div",
- [{"id", "copyright"}],
- [?XC("p",
- "ejabberd (c) 2002-2013 ProcessOne")
- ])])])
- ]}}.
-
-get_base_path(global, cluster) -> "/admin/";
-get_base_path(Host, cluster) -> "/admin/server/" ++ Host ++ "/";
-get_base_path(global, Node) -> "/admin/node/" ++ atom_to_list(Node) ++ "/";
-get_base_path(Host, Node) -> "/admin/server/" ++ Host ++ "/node/" ++ atom_to_list(Node) ++ "/".
+ #xmlel{name = <<"html">>,
+ attrs =
+ [{<<"xmlns">>, <<"http://www.w3.org/1999/xhtml">>},
+ {<<"xml:lang">>, Lang}, {<<"lang">>, Lang}],
+ children =
+ [#xmlel{name = <<"head">>, attrs = [],
+ children =
+ [?XCT(<<"title">>, <<"ejabberd Web Admin">>),
+ #xmlel{name = <<"meta">>,
+ attrs =
+ [{<<"http-equiv">>, <<"Content-Type">>},
+ {<<"content">>,
+ <<"text/html; charset=utf-8">>}],
+ children = []},
+ #xmlel{name = <<"script">>,
+ attrs =
+ [{<<"src">>,
+ <<Base/binary, "/additions.js">>},
+ {<<"type">>, <<"text/javascript">>}],
+ children = [?C(<<" ">>)]},
+ #xmlel{name = <<"link">>,
+ attrs =
+ [{<<"href">>,
+ <<Base/binary, "favicon.ico">>},
+ {<<"type">>, <<"image/x-icon">>},
+ {<<"rel">>, <<"shortcut icon">>}],
+ children = []},
+ #xmlel{name = <<"link">>,
+ attrs =
+ [{<<"href">>,
+ <<Base/binary, "style.css">>},
+ {<<"type">>, <<"text/css">>},
+ {<<"rel">>, <<"stylesheet">>}],
+ children = []}]},
+ ?XE(<<"body">>,
+ [?XAE(<<"div">>, [{<<"id">>, <<"container">>}],
+ [?XAE(<<"div">>, [{<<"id">>, <<"header">>}],
+ [?XE(<<"h1">>,
+ [?ACT(<<"/admin/">>,
+ <<"ejabberd Web Admin">>)])]),
+ ?XAE(<<"div">>, [{<<"id">>, <<"navigation">>}],
+ [?XE(<<"ul">>, MenuItems)]),
+ ?XAE(<<"div">>, [{<<"id">>, <<"content">>}], Els),
+ ?XAE(<<"div">>, [{<<"id">>, <<"clearcopyright">>}],
+ [{xmlcdata, <<"">>}])]),
+ ?XAE(<<"div">>, [{<<"id">>, <<"copyrightouter">>}],
+ [?XAE(<<"div">>, [{<<"id">>, <<"copyright">>}],
+ [?XC(<<"p">>,
+ <<"ejabberd (c) 2002-2013 ProcessOne">>)])])])]}}.
+
+get_base_path(global, cluster) -> <<"/admin/">>;
+get_base_path(Host, cluster) ->
+ <<"/admin/server/", Host/binary, "/">>;
+get_base_path(global, Node) ->
+ <<"/admin/node/",
+ (iolist_to_binary(atom_to_list(Node)))/binary, "/">>;
+get_base_path(Host, Node) ->
+ <<"/admin/server/", Host/binary, "/node/",
+ (iolist_to_binary(atom_to_list(Node)))/binary, "/">>.
%%%==================================
%%%% css & images
additions_js() ->
-"
-function selectAll() {
- for(i=0;i<document.forms[0].elements.length;i++)
- { var e = document.forms[0].elements[i];
- if(e.type == 'checkbox')
- { e.checked = true; }
- }
-}
-function unSelectAll() {
- for(i=0;i<document.forms[0].elements.length;i++)
- { var e = document.forms[0].elements[i];
- if(e.type == 'checkbox')
- { e.checked = false; }
- }
-}
-".
+ <<"\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">>.
css(Host) ->
Base = get_base_path(Host, cluster),
- "
-html,body {
- background: white;
- margin: 0;
- padding: 0;
- height: 100%;
-}
-
-#container {
- padding: 0;
- margin: 0;
- min-height: 100%;
- height: 100%;
- margin-bottom: -30px;
-}
-
-html>body #container {
- height: auto;
-}
-
-#header h1 {
- width: 100%;
- height: 55px;
- padding: 0;
- margin: 0;
- background: transparent url(\"" ++ Base ++ "logo-fill.png\");
-}
-
-#header h1 a {
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 55px;
- padding: 0;
- margin: 0;
- background: transparent url(\"" ++ Base ++ "logo.png\") no-repeat;
- display: block;
- text-indent: -700em;
-}
-
-#clearcopyright {
- display: block;
- width: 100%;
- height: 30px;
-}
-
-#copyrightouter {
- display: table;
- width: 100%;
- height: 30px;
-}
-
-#copyright {
- display: table-cell;
- vertical-align: bottom;
- width: 100%;
- height: 30px;
-}
-
-#copyright p {
- margin-left: 0;
- margin-right: 0;
- margin-top: 5px;
- margin-bottom: 0;
- padding-left: 0;
- padding-right: 0;
- padding-top: 1px;
- padding-bottom: 1px;
- width: 100%;
- color: #ffffff;
- background-color: #fe8a00;
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 7pt;
- font-weight: bold;
- text-align: center;
-}
-
-#navigation ul {
- position: absolute;
- top: 65px;
- left: 0;
- padding: 0 1px 1px 1px;
- margin: 0;
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 8pt;
- font-weight: bold;
- border-top: 1px solid #d47911;
- width: 17em;
-}
-
-#navigation ul li {
- list-style: none;
- margin: 0;
- text-align: left;
- display: inline;
-}
-
-#navigation ul li a {
- margin: 0;
- display: block;
- padding: 3px 6px 3px 9px;
- border-left: 1em solid #ffc78c;
- border-right: 1px solid #d47911;
- border-bottom: 1px solid #d47911;
- background: #ffe3c9;
- text-decoration: none;
-}
-
-#navigation ul li a:link {
- color: #844;
-}
-
-#navigation ul li a:visited {
- color: #766;
-}
-
-#navigation ul li a:hover {
- border-color: #fc8800;
- color: #FFF;
- background: #332;
-}
-
-ul li #navhead a, ul li #navheadsub a, ul li #navheadsubsub a {
- text-align: center;
- border-top: 1px solid #d47911;
- border-bottom: 2px solid #d47911;
- background: #FED6A6;
-}
-
-#navheadsub, #navitemsub {
- border-left: 7px solid white;
- margin-left: 2px;
-}
-
-#navheadsubsub, #navitemsubsub {
- border-left: 14px solid white;
- margin-left: 4px;
-}
-
-#lastactivity li {
- font-weight: bold;
- border: 1px solid #d6760e;
- background-color: #fff2e8;
- padding: 2px;
- margin-bottom: -1px;
-}
-
-td.copy {
- color: #ffffff;
- background-color: #fe8a00;
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 7pt;
- font-weight: bold;
- text-align: center;
-}
-
-input {
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 10pt;
- border: 1px solid #d6760e;
- color: #723202;
- background-color: #fff2e8;
- vertical-align: middle;
- margin-bottom: 0px;
- padding: 0.1em;
-}
-
-input[type=submit] {
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 8pt;
- font-weight: bold;
- color: #ffffff;
- background-color: #fe8a00;
- border: 1px solid #d6760e;
-}
-
-textarea {
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 10pt;
- border: 1px solid #d6760e;
- color: #723202;
- background-color: #fff2e8;
-}
-
-select {
- border: 1px solid #d6760e;
- color: #723202;
- background-color: #fff2e8;
- vertical-align: middle;
- margin-bottom: 0px;
- padding: 0.1em;
-}
-
-thead {
- color: #000000;
- background-color: #ffffff;
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 10pt;
- font-weight: bold;
-}
-
-tr.head {
- color: #ffffff;
- background-color: #3b547a;
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 9pt;
- font-weight: bold;
- text-align: center;
-}
-
-tr.oddraw {
- color: #412c75;
- background-color: #ccd4df;
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 9pt;
- font-weight: normal;
- text-align: center;
-}
-
-tr.evenraw {
- color: #412c75;
- background-color: #dbe0e8;
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 9pt;
- font-weight: normal;
- text-align: center;
-}
-
-td.leftheader {
- color: #412c75;
- background-color: #ccccc1;
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 9pt;
- font-weight: bold;
- padding-left: 5px;
- padding-top: 2px;
- padding-bottom: 2px;
- margin-top: 0px;
- margin-bottom: 0px;
-}
-
-td.leftcontent {
- color: #000044;
- background-color: #e6e6df;
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 7pt;
- font-weight: normal;
- padding-left: 5px;
- padding-right: 5px;
- padding-top: 2px;
- padding-bottom: 2px;
- margin-top: 0px;
- margin-bottom: 0px;
-}
-
-td.rightcontent {
- color: #000044;
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 10pt;
- font-weight: normal;
- text-align: justify;
- padding-left: 10px;
- padding-right: 10px;
- padding-bottom: 5px;
-}
-
-
-h1 {
- color: #000044;
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 14pt;
- font-weight: bold;
- text-align: center;
- padding-top: 2px;
- padding-bottom: 2px;
- margin-top: 0px;
- margin-bottom: 0px;
-}
-
-h2 {
- color: #000044;
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 12pt;
- font-weight: bold;
- text-align: center;
- padding-top: 2px;
- padding-bottom: 2px;
- margin-top: 0px;
- margin-bottom: 0px;
-}
-
-h3 {
- color: #000044;
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 10pt;
- font-weight: bold;
- text-align: left;
- padding-top: 20px;
- padding-bottom: 2px;
- margin-top: 0px;
- margin-bottom: 0px;
-}
-
-#content a:link {
- color: #990000;
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 10pt;
- font-weight: bold;
- text-decoration: underline;
-}
-#content a:visited {
- color: #990000;
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 10pt;
- font-weight: bold;
- text-decoration: underline;
-}
-#content a:hover {
- color: #cc6600;
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 10pt;
- font-weight: bold;
- text-decoration: underline;
-}
-
-
-#content ul li {
- list-style-type: disc;
- font-size: 10pt;
- /*font-size: 7pt;*/
- padding-left: 10px;
-}
-
-#content ul.nolistyle>li {
- list-style-type: none;
-}
-
-#content li.big {
- font-size: 10pt;
-}
-
-#content {
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 10pt;
- padding-left: 17em;
- padding-top: 5px;
-}
-
-div.guidelink {
- text-align: right;
- padding-right: 1em;
-}
-
-table.withtextareas>tbody>tr>td {
- vertical-align: top;
-}
-
-p.result {
- border: 1px;
- border-style: dashed;
- border-color: #FE8A02;
- padding: 1em;
- margin-right: 1em;
- background: #FFE3C9;
-}
-
-*.alignright {
- font-size: 10pt;
- text-align: right;
-}
-
-".
+ <<"\nhtml,body {\n background: white;\n "
+ " margin: 0;\n padding: 0;\n height: "
+ "100%;\n}\n\n#container {\n padding: "
+ "0;\n margin: 0;\n min-height: 100%;\n "
+ " height: 100%;\n margin-bottom: -30px;\n}\n\n"
+ "html>body #container {\n height: auto;\n}\n\n"
+ "#header h1 {\n width: 100%;\n height: "
+ "55px;\n padding: 0;\n margin: 0;\n "
+ " background: transparent url(\"",
+ Base/binary,
+ "logo-fill.png\");\n}\n\n#header h1 a "
+ "{\n position: absolute;\n top: 0;\n "
+ " left: 0;\n width: 100%;\n height: "
+ "55px;\n padding: 0;\n margin: 0;\n "
+ " background: transparent url(\"",
+ Base/binary,
+ "logo.png\") no-repeat;\n display: block;\n "
+ " text-indent: -700em;\n}\n\n#clearcopyright "
+ "{\n display: block;\n width: 100%;\n "
+ " height: 30px;\n}\n\n#copyrightouter "
+ "{\n display: table;\n width: 100%;\n "
+ " height: 30px;\n}\n\n#copyright {\n "
+ " display: table-cell;\n vertical-align: "
+ "bottom;\n width: 100%;\n height: 30px;\n}\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: "
+ "1px;\n padding-bottom: 1px;\n width: "
+ "100%;\n color: #ffffff;\n background-color: "
+ "#fe8a00;\n font-family: Verdana, Arial, "
+ "Helvetica, sans-serif; \n font-size: "
+ "7pt;\n font-weight: bold;\n text-align: "
+ "center;\n}\n\n#navigation ul {\n position: "
+ "absolute;\n top: 65px;\n left: 0;\n "
+ " padding: 0 1px 1px 1px;\n margin: "
+ "0;\n font-family: Verdana, Arial, Helvetica, "
+ "sans-serif; \n font-size: 8pt;\n font-weigh"
+ "t: bold;\n border-top: 1px solid #d47911;\n "
+ " width: 17em;\n}\n\n#navigation ul li "
+ "{\n list-style: none;\n margin: 0;\n "
+ " text-align: left;\n display: inline;\n}\n\n"
+ "#navigation ul li a {\n margin: 0;\n "
+ " display: block;\n padding: 3px 6px "
+ "3px 9px;\n border-left: 1em solid #ffc78c;\n "
+ " border-right: 1px solid #d47911;\n "
+ " border-bottom: 1px solid #d47911;\n "
+ " background: #ffe3c9;\n text-decoration: "
+ "none;\n}\n\n#navigation ul li a:link "
+ "{\n color: #844;\n}\n\n#navigation "
+ "ul li a:visited {\n color: #766;\n}\n\n#navig"
+ "ation ul li a:hover {\n border-color: "
+ "#fc8800;\n color: #FFF;\n background: "
+ "#332;\n}\n\nul li #navhead a, ul li "
+ "#navheadsub a, ul li #navheadsubsub "
+ "a {\n text-align: center;\n border-top: "
+ "1px solid #d47911;\n border-bottom: "
+ "2px solid #d47911;\n background: #FED6A6;\n}\n\n"
+ "#navheadsub, #navitemsub {\n border-left: "
+ "7px solid white;\n margin-left: 2px;\n}\n\n#"
+ "navheadsubsub, #navitemsubsub {\n border-lef"
+ "t: 14px solid white;\n margin-left: "
+ "4px;\n}\n\n#lastactivity li {\n font-weight: "
+ "bold;\n border: 1px solid #d6760e;\n "
+ " background-color: #fff2e8;\n padding: "
+ "2px;\n margin-bottom: -1px;\n}\n\ntd.copy "
+ "{\n color: #ffffff;\n background-color: "
+ "#fe8a00;\n font-family: Verdana, Arial, "
+ "Helvetica, sans-serif; \n font-size: "
+ "7pt;\n font-weight: bold;\n text-align: "
+ "center;\n}\n\ninput {\n font-family: "
+ "Verdana, Arial, Helvetica, sans-serif; "
+ "\n font-size: 10pt;\n border: 1px "
+ "solid #d6760e;\n color: #723202;\n "
+ " background-color: #fff2e8;\n vertical-align"
+ ": middle;\n margin-bottom: 0px;\n "
+ "padding: 0.1em;\n}\n\ninput[type=submit] "
+ "{\n font-family: Verdana, Arial, Helvetica, "
+ "sans-serif; \n font-size: 8pt;\n font-weigh"
+ "t: bold;\n color: #ffffff;\n background-col"
+ "or: #fe8a00;\n border: 1px solid #d6760e;\n}\n\n"
+ "textarea {\n font-family: Verdana, "
+ "Arial, Helvetica, sans-serif; \n font-size: "
+ "10pt;\n border: 1px solid #d6760e;\n "
+ " color: #723202;\n background-color: "
+ "#fff2e8;\n}\n\nselect {\n border: 1px "
+ "solid #d6760e;\n color: #723202;\n "
+ " background-color: #fff2e8;\n vertical-align"
+ ": middle;\n margin-bottom: 0px; \n "
+ " padding: 0.1em;\n}\n\nthead {\n color: "
+ "#000000;\n background-color: #ffffff;\n "
+ " font-family: Verdana, Arial, Helvetica, "
+ "sans-serif; \n font-size: 10pt;\n "
+ "font-weight: bold;\n}\n\ntr.head {\n "
+ " color: #ffffff;\n background-color: "
+ "#3b547a;\n font-family: Verdana, Arial, "
+ "Helvetica, sans-serif; \n font-size: "
+ "9pt;\n font-weight: bold;\n text-align: "
+ "center;\n}\n\ntr.oddraw {\n color: "
+ "#412c75;\n background-color: #ccd4df;\n "
+ " font-family: Verdana, Arial, Helvetica, "
+ "sans-serif; \n font-size: 9pt;\n font-weigh"
+ "t: normal;\n text-align: center;\n}\n\ntr.ev"
+ "enraw {\n color: #412c75;\n background-colo"
+ "r: #dbe0e8;\n font-family: Verdana, "
+ "Arial, Helvetica, sans-serif; \n font-size: "
+ "9pt;\n font-weight: normal;\n text-align: "
+ "center;\n}\n\ntd.leftheader {\n color: "
+ "#412c75;\n background-color: #ccccc1;\n "
+ " font-family: Verdana, Arial, Helvetica, "
+ "sans-serif; \n font-size: 9pt;\n font-weigh"
+ "t: bold;\n padding-left: 5px;\n padding-top"
+ ": 2px;\n padding-bottom: 2px;\n margin-top: "
+ "0px;\n margin-bottom: 0px;\n}\n\ntd.leftcont"
+ "ent {\n color: #000044;\n background-color: "
+ "#e6e6df;\n font-family: Verdana, Arial, "
+ "Helvetica, sans-serif; \n font-size: "
+ "7pt;\n font-weight: normal;\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\ntd.rightcon"
+ "tent {\n color: #000044;\n font-family: "
+ "Verdana, Arial, Helvetica, sans-serif; "
+ "\n font-size: 10pt;\n font-weight: "
+ "normal;\n text-align: justify;\n padding-le"
+ "ft: 10px;\n padding-right: 10px;\n "
+ " padding-bottom: 5px;\n}\n\n\nh1 {\n "
+ " color: #000044;\n font-family: Verdana, "
+ "Arial, Helvetica, sans-serif; \n font-size: "
+ "14pt;\n font-weight: bold;\n text-align: "
+ "center;\n padding-top: 2px;\n padding-botto"
+ "m: 2px;\n margin-top: 0px;\n margin-bottom: "
+ "0px;\n}\n\nh2 {\n color: #000044;\n "
+ " font-family: Verdana, Arial, Helvetica, "
+ "sans-serif; \n font-size: 12pt;\n "
+ "font-weight: bold;\n text-align: center;\n "
+ " padding-top: 2px;\n padding-bottom: "
+ "2px;\n margin-top: 0px;\n margin-bottom: "
+ "0px;\n}\n\nh3 {\n color: #000044;\n "
+ " font-family: Verdana, Arial, Helvetica, "
+ "sans-serif; \n font-size: 10pt;\n "
+ "font-weight: bold;\n text-align: left;\n "
+ " padding-top: 20px;\n padding-bottom: "
+ "2px;\n margin-top: 0px;\n margin-bottom: "
+ "0px;\n}\n\n#content a:link {\n color: "
+ "#990000; \n font-family: Verdana, Arial, "
+ "Helvetica, sans-serif; \n font-size: "
+ "10pt;\n font-weight: bold;\n text-decoratio"
+ "n: underline;\n}\n#content a:visited "
+ "{\n color: #990000; \n font-family: "
+ "Verdana, Arial, Helvetica, sans-serif; "
+ "\n font-size: 10pt;\n font-weight: "
+ "bold;\n text-decoration: underline;\n}\n#con"
+ "tent a:hover {\n color: #cc6600; \n "
+ " font-family: Verdana, Arial, Helvetica, "
+ "sans-serif; \n font-size: 10pt;\n "
+ "font-weight: bold;\n text-decoration: "
+ "underline;\n}\n\n\n#content ul li {\n "
+ " list-style-type: disc;\n font-size: "
+ "10pt;\n /*font-size: 7pt;*/\n padding-left: "
+ "10px;\n}\n\n#content ul.nolistyle>li "
+ "{\n list-style-type: none;\n}\n\n#content "
+ "li.big {\n font-size: 10pt;\n}\n\n#content "
+ "{\n font-family: Verdana, Arial, Helvetica, "
+ "sans-serif; \n font-size: 10pt;\n "
+ "padding-left: 17em;\n padding-top: "
+ "5px;\n}\n\ndiv.guidelink {\n text-align: "
+ "right;\n padding-right: 1em;\n}\n\ntable.wit"
+ "htextareas>tbody>tr>td {\n vertical-align: "
+ "top;\n}\n\np.result {\n border: 1px;\n "
+ " border-style: dashed;\n border-color: "
+ "#FE8A02;\n padding: 1em;\n margin-right: "
+ "1em;\n background: #FFE3C9;\n}\n\n*.alignrig"
+ "ht {\n font-size: 10pt;\n text-align: "
+ "right;\n}\n\n">>.
favicon() ->
- jlib:decode_base64(
- "AAABAAEAEBAQAAEABAAoAQAAFgAAACgAAAAQAAAAIAAAAAEABAAAAAAAAAA"
- "AAAAAAAAAAAAAAAAAAAAAAAAJf+cAAIPsAAGC8gAVhecAAIr8ACiR7wBBmO"
- "cAUKPsAFun8ABhqeoAgLryAJLB8ACz1PcAv9r7AMvi+gAAAAAAAgICARMhI"
- "CAkJCQkQkFCQgICN2d2cSMgJCRevdvVQkICAlqYh5MgICQkXrRCQkJCMgI7"
- "kiAjICAUFF2swkFBQRQUXazCQUFBAgI7kiAgICAkJF60QkJCQgICOpiHkyA"
- "gJCRevdvlQkICAjdndnMgICQkJCRCQkJCAgICARAgICAAAAAAAAAAAAAAAA"
- "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
- "AAAAAAAAAAA").
+ jlib:decode_base64(<<"AAABAAEAEBAQAAEABAAoAQAAFgAAACgAAAAQAAAAIAAAA"
+ "AEABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJf+cAAI"
+ "PsAAGC8gAVhecAAIr8ACiR7wBBmOcAUKPsAFun8ABhqeo"
+ "AgLryAJLB8ACz1PcAv9r7AMvi+gAAAAAAAgICARMhICAk"
+ "JCQkQkFCQgICN2d2cSMgJCRevdvVQkICAlqYh5MgICQkX"
+ "rRCQkJCMgI7kiAjICAUFF2swkFBQRQUXazCQUFBAgI7ki"
+ "AgICAkJF60QkJCQgICOpiHkyAgJCRevdvlQkICAjdndnM"
+ "gICQkJCRCQkJCAgICARAgICAAAAAAAAAAAAAAAAAAAAAA"
+ "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+ "AAAAAAAAAAAAAAAAAAA">>).
logo() ->
- jlib:decode_base64(
- "iVBORw0KGgoAAAANSUhEUgAAAVcAAAA3CAMAAACPbPnEAAAAAXNSR0IArs4c"
- "6QAAAEtQTFRFcTIA1XcE/YsA/40E/pIH/JYc/5kg/54i/KIu/6U6/apE/61H"
- "/61P/bFX/7Vh/bda/rpq/L5s/8J2/cJ8/8qI/86Y/9aj/9mt/+bJ7EGiPwAA"
- "AZRJREFUeNrt28lug0AQhGHajrPv+/s/aVwpDlgE0gQ3tqO/DhxihMg33VJ7"
- "JmmCVKSJlVJ4bZQ93Jl/zjJv+8tzcMUVV1xxLXIlRfPAZptYrbf5YeW618PW"
- "yvG8w/g9ZwquuJ6Y6+bbdY0rrifhSmrmgUulVXbVDq3H39Zy6Cf9+8c7JNM/"
- "mXeY8+SMRmuIK6644oprkSupmQdulLhQdup1qJKmrmWmVpb5NN9LUyddu7nn"
- "LYkrrrjiimuVK6mZB+6VuFbiXJk8v/bnv0PVa+Yd5tdr/x7vCfqbgPsfV1xx"
- "xRXXKldSMw+8KPGgxJWyU7WZE538p0vOr/lOm/q7dPf+bOVKvVXiUcEVV1xx"
- "xbXMldTMA29KPCtxp7T6XpvxE6/9nm/l987mnG9l5u/8jO4Ot9uTEq8Krrji"
- "iiuuZa6kZh74UFpli3sO61btMfyHyWGv/RMs7wB67ne32/BdwRVXXHHFtcyV"
- "1MwDn0qrbHHvyPT/Dsarla/R/1GpQydYPhf0bqC/A7jz7YkrrrjiimuVK6nI"
- "F5dWoNvcLcs/AAAAAElFTkSuQmCC").
+ jlib:decode_base64(<<"iVBORw0KGgoAAAANSUhEUgAAAVcAAAA3CAMAAACPbPnEA"
+ "AAAAXNSR0IArs4c6QAAAEtQTFRFcTIA1XcE/YsA/40E/p"
+ "IH/JYc/5kg/54i/KIu/6U6/apE/61H/61P/bFX/7Vh/bd"
+ "a/rpq/L5s/8J2/cJ8/8qI/86Y/9aj/9mt/+bJ7EGiPwAA"
+ "AZRJREFUeNrt28lug0AQhGHajrPv+/s/aVwpDlgE0gQ3t"
+ "qO/DhxihMg33VJ7JmmCVKSJlVJ4bZQ93Jl/zjJv+8tzcM"
+ "UVV1xxLXIlRfPAZptYrbf5YeW618PWyvG8w/g9ZwquuJ6"
+ "Y6+bbdY0rrifhSmrmgUulVXbVDq3H39Zy6Cf9+8c7JNM/"
+ "mXeY8+SMRmuIK6644oprkSupmQdulLhQdup1qJKmrmWmV"
+ "pb5NN9LUyddu7nnLYkrrrjiimuVK6mZB+6VuFbiXJk8v/"
+ "bnv0PVa+Yd5tdr/x7vCfqbgPsfV1xxxRXXKldSMw+8KPG"
+ "gxJWyU7WZE538p0vOr/lOm/q7dPf+bOVKvVXiUcEVV1xx"
+ "xbXMldTMA29KPCtxp7T6XpvxE6/9nm/l987mnG9l5u/8j"
+ "O4Ot9uTEq8KrrjiiiuuZa6kZh74UFpli3sO61btMfyHyW"
+ "Gv/RMs7wB67ne32/BdwRVXXHHFtcyV1MwDn0qrbHHvyPT"
+ "/Dsarla/R/1GpQydYPhf0bqC/A7jz7YkrrrjiimuVK6nI"
+ "F5dWoNvcLcs/AAAAAElFTkSuQmCC">>).
logo_fill() ->
- jlib:decode_base64(
- "iVBORw0KGgoAAAANSUhEUgAAAAYAAAA3BAMAAADdxCZzAAAAAXNSR0IArs4c"
- "6QAAAB5QTFRF1nYO/ooC/o4O/pIS/p4q/q5K/rpq/sqM/tam/ubGzn/S/AAA"
- "AEFJREFUCNdlw0sRwCAQBUE+gSRHLGABC1jAAhbWAhZwC+88XdXOXb4UlFAr"
- "SmwN5ekdJY2BkudEec1QvrVQ/r3xOlK9HsTvertmAAAAAElFTkSuQmCC").
+ jlib:decode_base64(<<"iVBORw0KGgoAAAANSUhEUgAAAAYAAAA3BAMAAADdxCZzA"
+ "AAAAXNSR0IArs4c6QAAAB5QTFRF1nYO/ooC/o4O/pIS/p"
+ "4q/q5K/rpq/sqM/tam/ubGzn/S/AAAAEFJREFUCNdlw0s"
+ "RwCAQBUE+gSRHLGABC1jAAhbWAhZwC+88XdXOXb4UlFAr"
+ "SmwN5ekdJY2BkudEec1QvrVQ/r3xOlK9HsTvertmAAAAA"
+ "ElFTkSuQmCC">>).
%%%==================================
%%%% process_admin
process_admin(global,
- #request{path = [],
- auth = {_, _, AJID},
+ #request{path = [], auth = {_, _, AJID},
lang = Lang}) ->
- %%Base = get_base_path(global, cluster),
- make_xhtml(?H1GL(?T("Administration"), "toc", "Contents") ++
- [?XE("ul",
- [?LI([?ACT(MIU, MIN)]) || {MIU, MIN} <- get_menu_items(global, cluster, Lang, AJID)]
- )
- ], global, Lang, AJID);
-
+ make_xhtml((?H1GL((?T(<<"Administration">>)), <<"toc">>,
+ <<"Contents">>))
+ ++
+ [?XE(<<"ul">>,
+ [?LI([?ACT(MIU, MIN)])
+ || {MIU, MIN}
+ <- get_menu_items(global, cluster, Lang, AJID)])],
+ global, Lang, AJID);
process_admin(Host,
- #request{path = [],
- auth = {_, _Auth, AJID},
+ #request{path = [], auth = {_, _Auth, AJID},
lang = Lang}) ->
- %%Base = get_base_path(Host, cluster),
- make_xhtml([?XCT("h1", "Administration"),
- ?XE("ul",
- [?LI([?ACT(MIU, MIN)]) || {MIU, MIN} <- get_menu_items(Host, cluster, Lang, AJID)]
- )
- ], Host, Lang, AJID);
-
-process_admin(Host, #request{path = ["style.css"]}) ->
- {200, [{"Content-Type", "text/css"}, last_modified(), cache_control_public()], css(Host)};
-
-process_admin(_Host, #request{path = ["favicon.ico"]}) ->
- {200, [{"Content-Type", "image/x-icon"}, last_modified(), cache_control_public()], favicon()};
-
-process_admin(_Host, #request{path = ["logo.png"]}) ->
- {200, [{"Content-Type", "image/png"}, last_modified(), cache_control_public()], logo()};
-
-process_admin(_Host, #request{path = ["logo-fill.png"]}) ->
- {200, [{"Content-Type", "image/png"}, last_modified(), cache_control_public()], logo_fill()};
-
-process_admin(_Host, #request{path = ["additions.js"]}) ->
- {200, [{"Content-Type", "text/javascript"}, last_modified(), cache_control_public()], additions_js()};
-
+ make_xhtml([?XCT(<<"h1">>, <<"Administration">>),
+ ?XE(<<"ul">>,
+ [?LI([?ACT(MIU, MIN)])
+ || {MIU, MIN}
+ <- get_menu_items(Host, cluster, Lang, AJID)])],
+ Host, Lang, AJID);
process_admin(Host,
- #request{path = ["acls-raw"],
- q = Query,
- auth = {_, _Auth, AJID},
- lang = Lang}) ->
-
- Res = case lists:keysearch("acls", 1, Query) of
- {value, {_, String}} ->
- case erl_scan:string(String) of
- {ok, Tokens, _} ->
- case erl_parse:parse_term(Tokens) of
- {ok, NewACLs} ->
- case acl:add_list(Host, NewACLs, true) of
- ok ->
- ok;
- _ ->
- error
- end;
- _ ->
- error
- end;
- _ ->
- error
- end;
- _ ->
- nothing
+ #request{path = [<<"style.css">>]}) ->
+ {200,
+ [{<<"Content-Type">>, <<"text/css">>}, last_modified(),
+ cache_control_public()],
+ css(Host)};
+process_admin(_Host,
+ #request{path = [<<"favicon.ico">>]}) ->
+ {200,
+ [{<<"Content-Type">>, <<"image/x-icon">>},
+ last_modified(), cache_control_public()],
+ favicon()};
+process_admin(_Host,
+ #request{path = [<<"logo.png">>]}) ->
+ {200,
+ [{<<"Content-Type">>, <<"image/png">>}, last_modified(),
+ cache_control_public()],
+ logo()};
+process_admin(_Host,
+ #request{path = [<<"logo-fill.png">>]}) ->
+ {200,
+ [{<<"Content-Type">>, <<"image/png">>}, last_modified(),
+ cache_control_public()],
+ logo_fill()};
+process_admin(_Host,
+ #request{path = [<<"additions.js">>]}) ->
+ {200,
+ [{<<"Content-Type">>, <<"text/javascript">>},
+ last_modified(), cache_control_public()],
+ additions_js()};
+process_admin(Host,
+ #request{path = [<<"acls-raw">>], q = Query,
+ auth = {_, _Auth, AJID}, lang = Lang}) ->
+ Res = case lists:keysearch(<<"acls">>, 1, Query) of
+ {value, {_, String}} ->
+ case erl_scan:string(binary_to_list(String)) of
+ {ok, Tokens, _} ->
+ case erl_parse:parse_term(Tokens) of
+ {ok, NewACLs} ->
+ case acl:add_list(Host, NewACLs, true) of
+ ok -> ok;
+ _ -> error
+ end;
+ _ -> error
+ end;
+ _ -> error
+ end;
+ _ -> nothing
end,
- ACLs = lists:keysort(2, ets:select(acl, [{{acl, {'$1', Host}, '$2'},
- [], [{{acl, '$1', '$2'}}]}])),
+ ACLs = lists:keysort(2,
+ ets:select(acl,
+ [{{acl, {'$1', Host}, '$2'}, [],
+ [{{acl, '$1', '$2'}}]}])),
{NumLines, ACLsP} = term_to_paragraph(ACLs, 80),
- make_xhtml(?H1GL(?T("Access Control Lists"), "ACLDefinition", "ACL Definition") ++
- case Res of
- ok -> [?XREST("Submitted")];
- error -> [?XREST("Bad format")];
+ make_xhtml((?H1GL((?T(<<"Access Control Lists">>)),
+ <<"ACLDefinition">>, <<"ACL Definition">>))
+ ++
+ case Res of
+ ok -> [?XREST(<<"Submitted">>)];
+ error -> [?XREST(<<"Bad format">>)];
nothing -> []
- end ++
- [?XAE("form", [{"action", ""}, {"method", "post"}],
- [?TEXTAREA("acls", integer_to_list(lists:max([16, NumLines])), "80", ACLsP++"."),
- ?BR,
- ?INPUTT("submit", "submit", "Submit")
- ])
- ], Host, Lang, AJID);
-
+ end
+ ++
+ [?XAE(<<"form">>,
+ [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
+ [?TEXTAREA(<<"acls">>,
+ (iolist_to_binary(integer_to_list(lists:max([16,
+ NumLines])))),
+ <<"80">>, <<(iolist_to_binary(ACLsP))/binary, ".">>),
+ ?BR,
+ ?INPUTT(<<"submit">>, <<"submit">>, <<"Submit">>)])],
+ Host, Lang, AJID);
process_admin(Host,
- #request{method = Method,
- path = ["acls"],
- auth = {_, _Auth, AJID},
- q = Query,
- lang = Lang}) ->
+ #request{method = Method, path = [<<"acls">>],
+ auth = {_, _Auth, AJID}, q = Query, lang = Lang}) ->
?DEBUG("query: ~p", [Query]),
Res = case Method of
- 'POST' ->
- case catch acl_parse_query(Host, Query) of
- {'EXIT', _} ->
- error;
- NewACLs ->
- ?INFO_MSG("NewACLs at ~s: ~p", [Host, NewACLs]),
- case acl:add_list(Host, NewACLs, true) of
- ok ->
- ?INFO_MSG("NewACLs: ok", []),
- ok;
- _ ->
- error
- end
- end;
- _ ->
- nothing
+ 'POST' ->
+ case catch acl_parse_query(Host, Query) of
+ {'EXIT', _} -> error;
+ NewACLs ->
+ ?INFO_MSG("NewACLs at ~s: ~p", [Host, NewACLs]),
+ case acl:add_list(Host, NewACLs, true) of
+ ok -> ?INFO_MSG("NewACLs: ok", []), ok;
+ _ -> error
+ end
+ end;
+ _ -> nothing
end,
- ACLs = lists:keysort(
- 2, ets:select(acl, [{{acl, {'$1', Host}, '$2'},
- [], [{{acl, '$1', '$2'}}]}])),
- make_xhtml(?H1GL(?T("Access Control Lists"), "ACLDefinition", "ACL Definition") ++
- case Res of
- ok -> [?XREST("Submitted")];
- error -> [?XREST("Bad format")];
+ ACLs = lists:keysort(2,
+ ets:select(acl,
+ [{{acl, {'$1', Host}, '$2'}, [],
+ [{{acl, '$1', '$2'}}]}])),
+ make_xhtml((?H1GL((?T(<<"Access Control Lists">>)),
+ <<"ACLDefinition">>, <<"ACL Definition">>))
+ ++
+ case Res of
+ ok -> [?XREST(<<"Submitted">>)];
+ error -> [?XREST(<<"Bad format">>)];
nothing -> []
- end ++
- [?XE("p", [?ACT("../acls-raw/", "Raw")])] ++
- [?XAE("form", [{"action", ""}, {"method", "post"}],
- [acls_to_xhtml(ACLs),
- ?BR,
- ?INPUTT("submit", "delete", "Delete Selected"),
- ?C(" "),
- ?INPUTT("submit", "submit", "Submit")
- ])
- ], Host, Lang, AJID);
-
+ end
+ ++
+ [?XE(<<"p">>, [?ACT(<<"../acls-raw/">>, <<"Raw">>)])] ++
+ [?XAE(<<"form">>,
+ [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
+ [acls_to_xhtml(ACLs), ?BR,
+ ?INPUTT(<<"submit">>, <<"delete">>,
+ <<"Delete Selected">>),
+ ?C(<<" ">>),
+ ?INPUTT(<<"submit">>, <<"submit">>,
+ <<"Submit">>)])],
+ Host, Lang, AJID);
process_admin(Host,
- #request{path = ["access-raw"],
- auth = {_, _Auth, AJID},
- q = Query,
- lang = Lang}) ->
- SetAccess =
- fun(Rs) ->
- mnesia:transaction(
- fun() ->
- Os = mnesia:select(
- config,
- [{{config, {access, '$1', Host}, '$2'},
- [],
- ['$_']}]),
- lists:foreach(fun(O) ->
- mnesia:delete_object(O)
- end, Os),
- lists:foreach(
- fun({access, Name, Rules}) ->
- mnesia:write({config,
- {access, Name, Host},
- Rules})
- end, Rs)
- end)
- end,
- Res = case lists:keysearch("access", 1, Query) of
- {value, {_, String}} ->
- case erl_scan:string(String) of
- {ok, Tokens, _} ->
- case erl_parse:parse_term(Tokens) of
- {ok, Rs} ->
- case SetAccess(Rs) of
- {atomic, _} ->
- ok;
- _ ->
- error
- end;
- _ ->
- error
- end;
- _ ->
- error
- end;
- _ ->
- nothing
+ #request{path = [<<"access-raw">>],
+ auth = {_, _Auth, AJID}, q = Query, lang = Lang}) ->
+ SetAccess = fun (Rs) ->
+ mnesia:transaction(fun () ->
+ Os = mnesia:select(config,
+ [{{config,
+ {access,
+ '$1',
+ Host},
+ '$2'},
+ [],
+ ['$_']}]),
+ lists:foreach(fun (O) ->
+ mnesia:delete_object(O)
+ end,
+ Os),
+ lists:foreach(fun ({access,
+ Name,
+ Rules}) ->
+ mnesia:write({config,
+ {access,
+ Name,
+ Host},
+ Rules})
+ end,
+ Rs)
+ end)
+ end,
+ Res = case lists:keysearch(<<"access">>, 1, Query) of
+ {value, {_, String}} ->
+ case erl_scan:string(binary_to_list(String)) of
+ {ok, Tokens, _} ->
+ case erl_parse:parse_term(Tokens) of
+ {ok, Rs} ->
+ case SetAccess(Rs) of
+ {atomic, _} -> ok;
+ _ -> error
+ end;
+ _ -> error
+ end;
+ _ -> error
+ end;
+ _ -> nothing
end,
- Access =
- ets:select(config,
- [{{config, {access, '$1', Host}, '$2'},
- [],
- [{{access, '$1', '$2'}}]}]),
- {NumLines, AccessP} = term_to_paragraph(Access, 80),
- make_xhtml(?H1GL(?T("Access Rules"), "AccessRights", "Access Rights") ++
- case Res of
- ok -> [?XREST("Submitted")];
- error -> [?XREST("Bad format")];
+ Access = ets:select(config,
+ [{{config, {access, '$1', Host}, '$2'}, [],
+ [{{access, '$1', '$2'}}]}]),
+ {NumLines, AccessP} = term_to_paragraph(lists:keysort(2,Access), 80),
+ make_xhtml((?H1GL((?T(<<"Access Rules">>)),
+ <<"AccessRights">>, <<"Access Rights">>))
+ ++
+ case Res of
+ ok -> [?XREST(<<"Submitted">>)];
+ error -> [?XREST(<<"Bad format">>)];
nothing -> []
- end ++
- [?XAE("form", [{"action", ""}, {"method", "post"}],
- [?TEXTAREA("access", integer_to_list(lists:max([16, NumLines])), "80", AccessP++"."),
- ?BR,
- ?INPUTT("submit", "submit", "Submit")
- ])
- ], Host, Lang, AJID);
-
+ end
+ ++
+ [?XAE(<<"form">>,
+ [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
+ [?TEXTAREA(<<"access">>,
+ (iolist_to_binary(integer_to_list(lists:max([16,
+ NumLines])))),
+ <<"80">>, <<(iolist_to_binary(AccessP))/binary, ".">>),
+ ?BR,
+ ?INPUTT(<<"submit">>, <<"submit">>, <<"Submit">>)])],
+ Host, Lang, AJID);
process_admin(Host,
- #request{method = Method,
- path = ["access"],
- q = Query,
- auth = {_, _Auth, AJID},
- lang = Lang}) ->
+ #request{method = Method, path = [<<"access">>],
+ q = Query, auth = {_, _Auth, AJID}, lang = Lang}) ->
?DEBUG("query: ~p", [Query]),
Res = case Method of
- 'POST' ->
- case catch access_parse_query(Host, Query) of
- {'EXIT', _} ->
- error;
- ok ->
- ok
- end;
- _ ->
- nothing
+ 'POST' ->
+ case catch access_parse_query(Host, Query) of
+ {'EXIT', _} -> error;
+ ok -> ok
+ end;
+ _ -> nothing
end,
- AccessRules =
- ets:select(config,
- [{{config, {access, '$1', Host}, '$2'},
- [],
- [{{access, '$1', '$2'}}]}]),
- make_xhtml(?H1GL(?T("Access Rules"), "AccessRights", "Access Rights") ++
- case Res of
- ok -> [?XREST("Submitted")];
- error -> [?XREST("Bad format")];
+ AccessRules = ets:select(config,
+ [{{config, {access, '$1', Host}, '$2'}, [],
+ [{{access, '$1', '$2'}}]}]),
+ make_xhtml((?H1GL((?T(<<"Access Rules">>)),
+ <<"AccessRights">>, <<"Access Rights">>))
+ ++
+ case Res of
+ ok -> [?XREST(<<"Submitted">>)];
+ error -> [?XREST(<<"Bad format">>)];
nothing -> []
- end ++
- [?XE("p", [?ACT("../access-raw/", "Raw")])] ++
- [?XAE("form", [{"action", ""}, {"method", "post"}],
- [access_rules_to_xhtml(AccessRules, Lang),
- ?BR,
- ?INPUTT("submit", "delete", "Delete Selected")
- ])
- ], Host, Lang, AJID);
-
+ end
+ ++
+ [?XE(<<"p">>, [?ACT(<<"../access-raw/">>, <<"Raw">>)])]
+ ++
+ [?XAE(<<"form">>,
+ [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
+ [access_rules_to_xhtml(AccessRules, Lang), ?BR,
+ ?INPUTT(<<"submit">>, <<"delete">>,
+ <<"Delete Selected">>)])],
+ Host, Lang, AJID);
process_admin(Host,
- #request{path = ["access", SName],
- q = Query,
- auth = {_, _Auth, AJID},
- lang = Lang}) ->
+ #request{path = [<<"access">>, SName], q = Query,
+ auth = {_, _Auth, AJID}, lang = Lang}) ->
?DEBUG("query: ~p", [Query]),
- Name = list_to_atom(SName),
- Res = case lists:keysearch("rules", 1, Query) of
- {value, {_, String}} ->
- case parse_access_rule(String) of
- {ok, Rs} ->
- ejabberd_config:add_global_option(
- {access, Name, Host}, Rs),
- ok;
- _ ->
- error
- end;
- _ ->
- nothing
+ Name = jlib:binary_to_atom(SName),
+ Res = case lists:keysearch(<<"rules">>, 1, Query) of
+ {value, {_, String}} ->
+ case parse_access_rule(String) of
+ {ok, Rs} ->
+ ejabberd_config:add_global_option({access, Name, Host},
+ Rs),
+ ok;
+ _ -> error
+ end;
+ _ -> nothing
end,
- Rules = case ejabberd_config:get_global_option({access, Name, Host}) of
- undefined ->
- [];
- Rs1 ->
- Rs1
+ Rules = case ejabberd_config:get_global_option(
+ {access, Name, Host}, fun(V) -> V end)
+ of
+ undefined -> [];
+ Rs1 -> Rs1
end,
- make_xhtml([?XC("h1",
- io_lib:format(?T("~s access rule configuration"), [SName]))] ++
- case Res of
- ok -> [?XREST("Submitted")];
- error -> [?XREST("Bad format")];
+ make_xhtml([?XC(<<"h1">>,
+ list_to_binary(io_lib:format(
+ ?T(<<"~s access rule configuration">>),
+ [SName])))]
+ ++
+ case Res of
+ ok -> [?XREST(<<"Submitted">>)];
+ error -> [?XREST(<<"Bad format">>)];
nothing -> []
- end ++
- [?XAE("form", [{"action", ""}, {"method", "post"}],
- [access_rule_to_xhtml(Rules),
- ?BR,
- ?INPUTT("submit", "submit", "Submit")
- ])
- ], Host, Lang, AJID);
-
+ end
+ ++
+ [?XAE(<<"form">>,
+ [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
+ [access_rule_to_xhtml(Rules), ?BR,
+ ?INPUTT(<<"submit">>, <<"submit">>, <<"Submit">>)])],
+ Host, Lang, AJID);
process_admin(global,
- #request{path = ["vhosts"],
- auth = {_, _Auth, AJID},
+ #request{path = [<<"vhosts">>], auth = {_, _Auth, AJID},
lang = Lang}) ->
Res = list_vhosts(Lang, AJID),
- make_xhtml(?H1GL(?T("Virtual Hosts"), "virtualhost", "Virtual Hosting") ++ Res, global, Lang, AJID);
-
+ make_xhtml((?H1GL((?T(<<"Virtual Hosts">>)),
+ <<"virtualhost">>, <<"Virtual Hosting">>))
+ ++ Res,
+ global, Lang, AJID);
process_admin(Host,
- #request{path = ["users"],
- q = Query,
- auth = {_, _Auth, AJID},
- lang = Lang}) when is_list(Host) ->
+ #request{path = [<<"users">>], q = Query,
+ auth = {_, _Auth, AJID}, lang = Lang})
+ when is_binary(Host) ->
Res = list_users(Host, Query, Lang, fun url_func/1),
- make_xhtml([?XCT("h1", "Users")] ++ Res, Host, Lang, AJID);
-
+ make_xhtml([?XCT(<<"h1">>, <<"Users">>)] ++ Res, Host,
+ Lang, AJID);
process_admin(Host,
- #request{path = ["users", Diap],
- auth = {_, _Auth, AJID},
- lang = Lang}) when is_list(Host) ->
- Res = list_users_in_diapason(Host, Diap, Lang, fun url_func/1),
- make_xhtml([?XCT("h1", "Users")] ++ Res, Host, Lang, AJID);
-
+ #request{path = [<<"users">>, Diap],
+ auth = {_, _Auth, AJID}, lang = Lang})
+ when is_binary(Host) ->
+ Res = list_users_in_diapason(Host, Diap, Lang,
+ fun url_func/1),
+ make_xhtml([?XCT(<<"h1">>, <<"Users">>)] ++ Res, Host,
+ Lang, AJID);
process_admin(Host,
- #request{
- path = ["online-users"],
- auth = {_, _Auth, AJID},
- lang = Lang}) when is_list(Host) ->
+ #request{path = [<<"online-users">>],
+ auth = {_, _Auth, AJID}, lang = Lang})
+ when is_binary(Host) ->
Res = list_online_users(Host, Lang),
- make_xhtml([?XCT("h1", "Online Users")] ++ Res, Host, Lang, AJID);
-
+ make_xhtml([?XCT(<<"h1">>, <<"Online Users">>)] ++ Res,
+ Host, Lang, AJID);
process_admin(Host,
- #request{path = ["last-activity"],
- auth = {_, _Auth, AJID},
- q = Query,
- lang = Lang}) when is_list(Host) ->
+ #request{path = [<<"last-activity">>],
+ auth = {_, _Auth, AJID}, q = Query, lang = Lang})
+ when is_binary(Host) ->
?DEBUG("query: ~p", [Query]),
- Month = case lists:keysearch("period", 1, Query) of
- {value, {_, Val}} ->
- Val;
- _ ->
- "month"
+ Month = case lists:keysearch(<<"period">>, 1, Query) of
+ {value, {_, Val}} -> Val;
+ _ -> <<"month">>
end,
- Res = case lists:keysearch("ordinary", 1, Query) of
- {value, {_, _}} ->
- list_last_activity(Host, Lang, false, Month);
- _ ->
- list_last_activity(Host, Lang, true, Month)
+ Res = case lists:keysearch(<<"ordinary">>, 1, Query) of
+ {value, {_, _}} ->
+ list_last_activity(Host, Lang, false, Month);
+ _ -> list_last_activity(Host, Lang, true, Month)
end,
- make_xhtml([?XCT("h1", "Users Last Activity")] ++
- [?XAE("form", [{"action", ""}, {"method", "post"}],
- [?CT("Period: "),
- ?XAE("select", [{"name", "period"}],
- lists:map(
- fun({O, V}) ->
- Sel = if
- O == Month -> [{"selected", "selected"}];
- true -> []
- end,
- ?XAC("option",
- Sel ++ [{"value", O}], V)
- end, [{"month", ?T("Last month")},
- {"year", ?T("Last year")},
- {"all", ?T("All activity")}])),
- ?C(" "),
- ?INPUTT("submit", "ordinary", "Show Ordinary Table"),
- ?C(" "),
- ?INPUTT("submit", "integral", "Show Integral Table")
- ])] ++
- Res, Host, Lang, AJID);
-
+ make_xhtml([?XCT(<<"h1">>, <<"Users Last Activity">>)]
+ ++
+ [?XAE(<<"form">>,
+ [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
+ [?CT(<<"Period: ">>),
+ ?XAE(<<"select">>, [{<<"name">>, <<"period">>}],
+ (lists:map(fun ({O, V}) ->
+ Sel = if O == Month ->
+ [{<<"selected">>,
+ <<"selected">>}];
+ true -> []
+ end,
+ ?XAC(<<"option">>,
+ (Sel ++
+ [{<<"value">>, O}]),
+ V)
+ end,
+ [{<<"month">>, ?T(<<"Last month">>)},
+ {<<"year">>, ?T(<<"Last year">>)},
+ {<<"all">>,
+ ?T(<<"All activity">>)}]))),
+ ?C(<<" ">>),
+ ?INPUTT(<<"submit">>, <<"ordinary">>,
+ <<"Show Ordinary Table">>),
+ ?C(<<" ">>),
+ ?INPUTT(<<"submit">>, <<"integral">>,
+ <<"Show Integral Table">>)])]
+ ++ Res,
+ Host, Lang, AJID);
process_admin(Host,
- #request{path = ["stats"],
- auth = {_, _Auth, AJID},
+ #request{path = [<<"stats">>], auth = {_, _Auth, AJID},
lang = Lang}) ->
Res = get_stats(Host, Lang),
- make_xhtml([?XCT("h1", "Statistics")] ++ Res, Host, Lang, AJID);
-
+ make_xhtml([?XCT(<<"h1">>, <<"Statistics">>)] ++ Res,
+ Host, Lang, AJID);
process_admin(Host,
- #request{path = ["user", U],
- auth = {_, _Auth, AJID},
- q = Query,
- lang = Lang}) ->
+ #request{path = [<<"user">>, U],
+ auth = {_, _Auth, AJID}, q = Query, lang = Lang}) ->
case ejabberd_auth:is_user_exists(U, Host) of
- true ->
- Res = user_info(U, Host, Query, Lang),
- make_xhtml(Res, Host, Lang, AJID);
- false ->
- make_xhtml([?XCT("h1", "Not Found")], Host, Lang, AJID)
+ true ->
+ Res = user_info(U, Host, Query, Lang),
+ make_xhtml(Res, Host, Lang, AJID);
+ false ->
+ make_xhtml([?XCT(<<"h1">>, <<"Not Found">>)], Host,
+ Lang, AJID)
end;
-
process_admin(Host,
- #request{path = ["nodes"],
- auth = {_, _Auth, AJID},
+ #request{path = [<<"nodes">>], auth = {_, _Auth, AJID},
lang = Lang}) ->
Res = get_nodes(Lang),
make_xhtml(Res, Host, Lang, AJID);
-
process_admin(Host,
- #request{path = ["node", SNode | NPath],
- auth = {_, _Auth, AJID},
- q = Query,
- lang = Lang}) ->
+ #request{path = [<<"node">>, SNode | NPath],
+ auth = {_, _Auth, AJID}, q = Query, lang = Lang}) ->
case search_running_node(SNode) of
- false ->
- make_xhtml([?XCT("h1", "Node not found")], Host, Lang, AJID);
- Node ->
- Res = get_node(Host, Node, NPath, Query, Lang),
- make_xhtml(Res, Host, Node, Lang, AJID)
+ false ->
+ make_xhtml([?XCT(<<"h1">>, <<"Node not found">>)], Host,
+ Lang, AJID);
+ Node ->
+ Res = get_node(Host, Node, NPath, Query, Lang),
+ make_xhtml(Res, Host, Node, Lang, AJID)
end;
-
%%%==================================
%%%% process_admin default case
-
-process_admin(Host, #request{lang = Lang,
- auth = {_, _Auth, AJID}
- } = Request) ->
+process_admin(Host,
+ #request{lang = Lang, auth = {_, _Auth, AJID}} =
+ Request) ->
{Hook, Opts} = case Host of
- global -> {webadmin_page_main, [Request]};
- Host -> {webadmin_page_host, [Host, Request]}
+ global -> {webadmin_page_main, [Request]};
+ Host -> {webadmin_page_host, [Host, Request]}
end,
case ejabberd_hooks:run_fold(Hook, Host, [], Opts) of
- [] -> setelement(1, make_xhtml([?XC("h1", "Not Found")], Host, Lang, AJID), 404);
- Res -> make_xhtml(Res, Host, Lang, AJID)
+ [] ->
+ setelement(1,
+ make_xhtml([?XC(<<"h1">>, <<"Not Found">>)], Host, Lang,
+ AJID),
+ 404);
+ Res -> make_xhtml(Res, Host, Lang, AJID)
end.
%%%==================================
%%%% acl
acls_to_xhtml(ACLs) ->
- ?XAE("table", [],
- [?XE("tbody",
- lists:map(
- fun({acl, Name, Spec} = ACL) ->
- SName = atom_to_list(Name),
- ID = term_to_id(ACL),
- ?XE("tr",
- [?XE("td", [?INPUT("checkbox", "selected", ID)]),
- ?XC("td", SName)] ++
- acl_spec_to_xhtml(ID, Spec)
- )
- end, ACLs) ++
- [?XE("tr",
- [?X("td"),
- ?XE("td", [?INPUT("text", "namenew", "")])
- ] ++
- acl_spec_to_xhtml("new", {user, ""})
- )]
- )]).
-
-acl_spec_to_text({user, U}) ->
- {user, U};
-
-acl_spec_to_text({server, S}) ->
- {server, S};
-
+ ?XAE(<<"table">>, [],
+ [?XE(<<"tbody">>,
+ (lists:map(fun ({acl, Name, Spec} = ACL) ->
+ SName = iolist_to_binary(atom_to_list(Name)),
+ ID = term_to_id(ACL),
+ ?XE(<<"tr">>,
+ ([?XE(<<"td">>,
+ [?INPUT(<<"checkbox">>,
+ <<"selected">>, ID)]),
+ ?XC(<<"td">>, SName)]
+ ++ acl_spec_to_xhtml(ID, Spec)))
+ end,
+ ACLs)
+ ++
+ [?XE(<<"tr">>,
+ ([?X(<<"td">>),
+ ?XE(<<"td">>,
+ [?INPUT(<<"text">>, <<"namenew">>, <<"">>)])]
+ ++ acl_spec_to_xhtml(<<"new">>, {user, <<"">>})))]))]).
+
+acl_spec_to_text({user, U}) -> {user, U};
+acl_spec_to_text({server, S}) -> {server, S};
acl_spec_to_text({user, U, S}) ->
- {user, U ++ "@" ++ S};
-
+ {user, <<U/binary, "@", S/binary>>};
acl_spec_to_text({user_regexp, RU}) ->
{user_regexp, RU};
-
acl_spec_to_text({user_regexp, RU, S}) ->
- {user_regexp, RU ++ "@" ++ S};
-
+ {user_regexp, <<RU/binary, "@", S/binary>>};
acl_spec_to_text({server_regexp, RS}) ->
{server_regexp, RS};
-
acl_spec_to_text({node_regexp, RU, RS}) ->
- {node_regexp, RU ++ "@" ++ RS};
-
-acl_spec_to_text({user_glob, RU}) ->
- {user_glob, RU};
-
+ {node_regexp, <<RU/binary, "@", RS/binary>>};
+acl_spec_to_text({user_glob, RU}) -> {user_glob, RU};
acl_spec_to_text({user_glob, RU, S}) ->
- {user_glob, RU ++ "@" ++ S};
-
+ {user_glob, <<RU/binary, "@", S/binary>>};
acl_spec_to_text({server_glob, RS}) ->
{server_glob, RS};
-
acl_spec_to_text({node_glob, RU, RS}) ->
- {node_glob, RU ++ "@" ++ RS};
-
-acl_spec_to_text(all) ->
- {all, ""};
-
-acl_spec_to_text(Spec) ->
- {raw, term_to_string(Spec)}.
+ {node_glob, <<RU/binary, "@", RS/binary>>};
+acl_spec_to_text(all) -> {all, <<"">>};
+acl_spec_to_text(Spec) -> {raw, term_to_string(Spec)}.
acl_spec_to_xhtml(ID, Spec) ->
{Type, Str} = acl_spec_to_text(Spec),
[acl_spec_select(ID, Type), ?ACLINPUT(Str)].
acl_spec_select(ID, Opt) ->
- ?XE("td",
- [?XAE("select", [{"name", "type" ++ ID}],
- lists:map(
- fun(O) ->
- Sel = if
- O == Opt -> [{"selected", "selected"}];
- true -> []
- end,
- ?XAC("option",
- Sel ++ [{"value", atom_to_list(O)}],
- atom_to_list(O))
- end, [user, server, user_regexp, server_regexp,
- node_regexp, user_glob, server_glob, node_glob, all, raw]))]).
-
+ ?XE(<<"td">>,
+ [?XAE(<<"select">>,
+ [{<<"name">>, <<"type", ID/binary>>}],
+ (lists:map(fun (O) ->
+ Sel = if O == Opt ->
+ [{<<"selected">>,
+ <<"selected">>}];
+ true -> []
+ end,
+ ?XAC(<<"option">>,
+ (Sel ++
+ [{<<"value">>,
+ iolist_to_binary(atom_to_list(O))}]),
+ (iolist_to_binary(atom_to_list(O))))
+ end,
+ [user, server, user_regexp, server_regexp, node_regexp,
+ user_glob, server_glob, node_glob, all, raw])))]).
%% @spec (T::any()) -> StringLine::string()
term_to_string(T) ->
- StringParagraph = lists:flatten(io_lib:format("~1000000p", [T])),
- %% Remove from the string all the carriage returns characters
- ejabberd_regexp:greplace(StringParagraph, "\\n ", "").
+ StringParagraph =
+ iolist_to_binary(io_lib:format("~1000000p", [T])),
+ ejabberd_regexp:greplace(StringParagraph, <<"\\n ">>,
+ <<"">>).
%% @spec (T::any(), Cols::integer()) -> {NumLines::integer(), Paragraph::string()}
term_to_paragraph(T, Cols) ->
- Paragraph = erl_prettypr:format(erl_syntax:abstract(T), [{paper, Cols}]),
- FieldList = ejabberd_regexp:split(Paragraph, "\n"),
+ Paragraph = list_to_binary(erl_prettypr:format(erl_syntax:abstract(T),
+ [{paper, Cols}])),
+ FieldList = ejabberd_regexp:split(Paragraph, <<"\n">>),
NumLines = length(FieldList),
{NumLines, Paragraph}.
-term_to_id(T) ->
- jlib:encode_base64(binary_to_list(term_to_binary(T))).
-
+term_to_id(T) -> jlib:encode_base64((term_to_binary(T))).
acl_parse_query(Host, Query) ->
- ACLs = ets:select(acl, [{{acl, {'$1', Host}, '$2'},
- [], [{{acl, '$1', '$2'}}]}]),
- case lists:keysearch("submit", 1, Query) of
- {value, _} ->
- acl_parse_submit(ACLs, Query);
- _ ->
- case lists:keysearch("delete", 1, Query) of
- {value, _} ->
- acl_parse_delete(ACLs, Query)
- end
+ ACLs = ets:select(acl,
+ [{{acl, {'$1', Host}, '$2'}, [],
+ [{{acl, '$1', '$2'}}]}]),
+ case lists:keysearch(<<"submit">>, 1, Query) of
+ {value, _} -> acl_parse_submit(ACLs, Query);
+ _ ->
+ case lists:keysearch(<<"delete">>, 1, Query) of
+ {value, _} -> acl_parse_delete(ACLs, Query)
+ end
end.
acl_parse_submit(ACLs, Query) ->
- NewACLs =
- lists:map(
- fun({acl, Name, Spec} = ACL) ->
- %%SName = atom_to_list(Name),
- ID = term_to_id(ACL),
- case {lists:keysearch("type" ++ ID, 1, Query),
- lists:keysearch("value" ++ ID, 1, Query)} of
- {{value, {_, T}}, {value, {_, V}}} ->
- {Type, Str} = acl_spec_to_text(Spec),
- case {atom_to_list(Type), Str} of
- {T, V} ->
- ACL;
- _ ->
- NewSpec = string_to_spec(T, V),
- {acl, Name, NewSpec}
- end;
- _ ->
- ACL
- end
- end, ACLs),
- NewACL = case {lists:keysearch("namenew", 1, Query),
- lists:keysearch("typenew", 1, Query),
- lists:keysearch("valuenew", 1, Query)} of
- {{value, {_, ""}}, _, _} ->
- [];
- {{value, {_, N}}, {value, {_, T}}, {value, {_, V}}} ->
- NewName = list_to_atom(N),
- NewSpec = string_to_spec(T, V),
- [{acl, NewName, NewSpec}];
- _ ->
- []
+ NewACLs = lists:map(fun ({acl, Name, Spec} = ACL) ->
+ ID = term_to_id(ACL),
+ case {lists:keysearch(<<"type", ID/binary>>, 1,
+ Query),
+ lists:keysearch(<<"value", ID/binary>>, 1,
+ Query)}
+ of
+ {{value, {_, T}}, {value, {_, V}}} ->
+ {Type, Str} = acl_spec_to_text(Spec),
+ case
+ {iolist_to_binary(atom_to_list(Type)),
+ Str}
+ of
+ {T, V} -> ACL;
+ _ ->
+ NewSpec = string_to_spec(T, V),
+ {acl, Name, NewSpec}
+ end;
+ _ -> ACL
+ end
+ end,
+ ACLs),
+ NewACL = case {lists:keysearch(<<"namenew">>, 1, Query),
+ lists:keysearch(<<"typenew">>, 1, Query),
+ lists:keysearch(<<"valuenew">>, 1, Query)}
+ of
+ {{value, {_, <<"">>}}, _, _} -> [];
+ {{value, {_, N}}, {value, {_, T}}, {value, {_, V}}} ->
+ NewName = jlib:binary_to_atom(N),
+ NewSpec = string_to_spec(T, V),
+ [{acl, NewName, NewSpec}];
+ _ -> []
end,
NewACLs ++ NewACL.
-string_to_spec("user", Val) ->
+string_to_spec(<<"user">>, Val) ->
string_to_spec2(user, Val);
-string_to_spec("server", Val) ->
- {server, Val};
-string_to_spec("user_regexp", Val) ->
+string_to_spec(<<"server">>, Val) -> {server, Val};
+string_to_spec(<<"user_regexp">>, Val) ->
string_to_spec2(user_regexp, Val);
-string_to_spec("server_regexp", Val) ->
+string_to_spec(<<"server_regexp">>, Val) ->
{server_regexp, Val};
-string_to_spec("node_regexp", Val) ->
- #jid{luser = U, lserver = S, resource = ""} = jlib:string_to_jid(Val),
+string_to_spec(<<"node_regexp">>, Val) ->
+ #jid{luser = U, lserver = S, resource = <<"">>} =
+ jlib:string_to_jid(Val),
{node_regexp, U, S};
-string_to_spec("user_glob", Val) ->
+string_to_spec(<<"user_glob">>, Val) ->
string_to_spec2(user_glob, Val);
-string_to_spec("server_glob", Val) ->
+string_to_spec(<<"server_glob">>, Val) ->
{server_glob, Val};
-string_to_spec("node_glob", Val) ->
- #jid{luser = U, lserver = S, resource = ""} = jlib:string_to_jid(Val),
+string_to_spec(<<"node_glob">>, Val) ->
+ #jid{luser = U, lserver = S, resource = <<"">>} =
+ jlib:string_to_jid(Val),
{node_glob, U, S};
-string_to_spec("all", _) ->
- all;
-string_to_spec("raw", Val) ->
- {ok, Tokens, _} = erl_scan:string(Val ++ "."),
+string_to_spec(<<"all">>, _) -> all;
+string_to_spec(<<"raw">>, Val) ->
+ {ok, Tokens, _} = erl_scan:string(binary_to_list(<<Val/binary, ".">>)),
{ok, NewSpec} = erl_parse:parse_term(Tokens),
NewSpec.
string_to_spec2(ACLName, Val) ->
- #jid{luser = U, lserver = S, resource = ""} = jlib:string_to_jid(Val),
+ #jid{luser = U, lserver = S, resource = <<"">>} =
+ jlib:string_to_jid(Val),
case U of
- "" ->
- {ACLName, S};
- _ ->
- {ACLName, U, S}
+ <<"">> -> {ACLName, S};
+ _ -> {ACLName, U, S}
end.
-
acl_parse_delete(ACLs, Query) ->
- NewACLs =
- lists:filter(
- fun({acl, _Name, _Spec} = ACL) ->
- ID = term_to_id(ACL),
- not lists:member({"selected", ID}, Query)
- end, ACLs),
+ NewACLs = lists:filter(fun ({acl, _Name, _Spec} =
+ ACL) ->
+ ID = term_to_id(ACL),
+ not lists:member({<<"selected">>, ID}, Query)
+ end,
+ ACLs),
NewACLs.
-
access_rules_to_xhtml(AccessRules, Lang) ->
- ?XAE("table", [],
- [?XE("tbody",
- lists:map(
- fun({access, Name, Rules} = Access) ->
- SName = atom_to_list(Name),
- ID = term_to_id(Access),
- ?XE("tr",
- [?XE("td", [?INPUT("checkbox", "selected", ID)]),
- ?XE("td", [?AC(SName ++ "/", SName)]),
- ?XC("td", term_to_string(Rules))
- ]
- )
- end, AccessRules) ++
- [?XE("tr",
- [?X("td"),
- ?XE("td", [?INPUT("text", "namenew", "")]),
- ?XE("td", [?INPUTT("submit", "addnew", "Add New")])
- ]
- )]
- )]).
+ ?XAE(<<"table">>, [],
+ [?XE(<<"tbody">>,
+ (lists:map(fun ({access, Name, Rules} = Access) ->
+ SName = iolist_to_binary(atom_to_list(Name)),
+ ID = term_to_id(Access),
+ ?XE(<<"tr">>,
+ [?XE(<<"td">>,
+ [?INPUT(<<"checkbox">>,
+ <<"selected">>, ID)]),
+ ?XE(<<"td">>,
+ [?AC(<<SName/binary, "/">>, SName)]),
+ ?XC(<<"td">>, (term_to_string(Rules)))])
+ end,
+ lists:keysort(2,AccessRules))
+ ++
+ [?XE(<<"tr">>,
+ [?X(<<"td">>),
+ ?XE(<<"td">>,
+ [?INPUT(<<"text">>, <<"namenew">>, <<"">>)]),
+ ?XE(<<"td">>,
+ [?INPUTT(<<"submit">>, <<"addnew">>,
+ <<"Add New">>)])])]))]).
access_parse_query(Host, Query) ->
- AccessRules =
- ets:select(config,
- [{{config, {access, '$1', Host}, '$2'},
- [],
- [{{access, '$1', '$2'}}]}]),
- case lists:keysearch("addnew", 1, Query) of
- {value, _} ->
- access_parse_addnew(AccessRules, Host, Query);
- _ ->
- case lists:keysearch("delete", 1, Query) of
- {value, _} ->
- access_parse_delete(AccessRules, Host, Query)
- end
+ AccessRules = ets:select(config,
+ [{{config, {access, '$1', Host}, '$2'}, [],
+ [{{access, '$1', '$2'}}]}]),
+ case lists:keysearch(<<"addnew">>, 1, Query) of
+ {value, _} ->
+ access_parse_addnew(AccessRules, Host, Query);
+ _ ->
+ case lists:keysearch(<<"delete">>, 1, Query) of
+ {value, _} ->
+ access_parse_delete(AccessRules, Host, Query)
+ end
end.
access_parse_addnew(_AccessRules, Host, Query) ->
- case lists:keysearch("namenew", 1, Query) of
- {value, {_, String}} when String /= "" ->
- Name = list_to_atom(String),
- ejabberd_config:add_global_option({access, Name, Host}, []),
- ok
+ case lists:keysearch(<<"namenew">>, 1, Query) of
+ {value, {_, String}} when String /= <<"">> ->
+ Name = jlib:binary_to_atom(String),
+ ejabberd_config:add_global_option({access, Name, Host},
+ []),
+ ok
end.
access_parse_delete(AccessRules, Host, Query) ->
- lists:foreach(
- fun({access, Name, _Rules} = AccessRule) ->
- ID = term_to_id(AccessRule),
- case lists:member({"selected", ID}, Query) of
- true ->
- mnesia:transaction(
- fun() ->
- mnesia:delete({config, {access, Name, Host}})
- end);
- _ ->
- ok
- end
- end, AccessRules),
+ lists:foreach(fun ({access, Name, _Rules} =
+ AccessRule) ->
+ ID = term_to_id(AccessRule),
+ case lists:member({<<"selected">>, ID}, Query) of
+ true ->
+ mnesia:transaction(fun () ->
+ mnesia:delete({config,
+ {access,
+ Name,
+ Host}})
+ end);
+ _ -> ok
+ end
+ end,
+ AccessRules),
ok.
-
-
-
access_rule_to_xhtml(Rules) ->
- Text = lists:flatmap(
- fun({Access, ACL} = _Rule) ->
- SAccess = element_to_list(Access),
- SACL = atom_to_list(ACL),
- SAccess ++ "\s\t" ++ SACL ++ "\n"
- end, Rules),
- ?XAC("textarea", [{"name", "rules"},
- {"rows", "16"},
- {"cols", "80"}],
- Text).
+ Text = lists:flatmap(fun ({Access, ACL} = _Rule) ->
+ SAccess = element_to_list(Access),
+ SACL = atom_to_list(ACL),
+ [SAccess, " \t", SACL, "\n"]
+ end,
+ Rules),
+ ?XAC(<<"textarea">>,
+ [{<<"name">>, <<"rules">>}, {<<"rows">>, <<"16">>},
+ {<<"cols">>, <<"80">>}],
+ list_to_binary(Text)).
parse_access_rule(Text) ->
- Strings = string:tokens(Text, "\r\n"),
- case catch lists:flatmap(
- fun(String) ->
- case string:tokens(String, "\s\t") of
- [Access, ACL] ->
- [{list_to_element(Access), list_to_atom(ACL)}];
- [] ->
- []
- end
- end, Strings) of
- {'EXIT', _Reason} ->
- error;
- Rs ->
- {ok, Rs}
+ Strings = str:tokens(Text, <<"\r\n">>),
+ case catch lists:flatmap(fun (String) ->
+ case str:tokens(String, <<" \t">>) of
+ [Access, ACL] ->
+ [{list_to_element(Access),
+ jlib:binary_to_atom(ACL)}];
+ [] -> []
+ end
+ end,
+ Strings)
+ of
+ {'EXIT', _Reason} -> error;
+ Rs -> {ok, Rs}
end.
%%%==================================
%%%% list_vhosts
list_vhosts(Lang, JID) ->
- Hosts = ?MYHOSTS,
- HostsAllowed = lists:filter(
- fun(Host) ->
- is_acl_match(Host, [configure, webadmin_view], JID)
- end,
- Hosts
- ),
+ Hosts = (?MYHOSTS),
+ HostsAllowed = lists:filter(fun (Host) ->
+ is_acl_match(Host,
+ [configure, webadmin_view],
+ JID)
+ end,
+ Hosts),
list_vhosts2(Lang, HostsAllowed).
list_vhosts2(Lang, Hosts) ->
SHosts = lists:sort(Hosts),
- [?XE("table",
- [?XE("thead",
- [?XE("tr",
- [?XCT("td", "Host"),
- ?XCT("td", "Registered Users"),
- ?XCT("td", "Online Users")
- ])]),
- ?XE("tbody",
- lists:map(
- fun(Host) ->
- OnlineUsers =
- length(ejabberd_sm:get_vh_session_list(Host)),
- RegisteredUsers =
- ejabberd_auth:get_vh_registered_users_number(Host),
- ?XE("tr",
- [?XE("td", [?AC("../server/" ++ Host ++ "/", Host)]),
- ?XC("td", pretty_string_int(RegisteredUsers)),
- ?XC("td", pretty_string_int(OnlineUsers))
- ])
- end, SHosts)
- )])].
+ [?XE(<<"table">>,
+ [?XE(<<"thead">>,
+ [?XE(<<"tr">>,
+ [?XCT(<<"td">>, <<"Host">>),
+ ?XCT(<<"td">>, <<"Registered Users">>),
+ ?XCT(<<"td">>, <<"Online Users">>)])]),
+ ?XE(<<"tbody">>,
+ (lists:map(fun (Host) ->
+ OnlineUsers =
+ length(ejabberd_sm:get_vh_session_list(Host)),
+ RegisteredUsers =
+ ejabberd_auth:get_vh_registered_users_number(Host),
+ ?XE(<<"tr">>,
+ [?XE(<<"td">>,
+ [?AC(<<"../server/", Host/binary,
+ "/">>,
+ Host)]),
+ ?XC(<<"td">>,
+ (pretty_string_int(RegisteredUsers))),
+ ?XC(<<"td">>,
+ (pretty_string_int(OnlineUsers)))])
+ end,
+ SHosts)))])].
%%%==================================
%%%% list_users
@@ -1483,387 +1294,425 @@ list_users(Host, Query, Lang, URLFunc) ->
Res = list_users_parse_query(Query, Host),
Users = ejabberd_auth:get_vh_registered_users(Host),
SUsers = lists:sort([{S, U} || {U, S} <- Users]),
- FUsers =
- case length(SUsers) of
- N when N =< 100 ->
- [list_given_users(Host, SUsers, "../", Lang, URLFunc)];
- N ->
- NParts = trunc(math:sqrt(N * 0.618)) + 1,
- M = trunc(N / NParts) + 1,
- lists:flatmap(
- fun(K) ->
- L = K + M - 1,
- %%Node = integer_to_list(K) ++ "-" ++ integer_to_list(L),
- Last = if L < N -> su_to_list(lists:nth(L, SUsers));
- true -> su_to_list(lists:last(SUsers))
+ FUsers = case length(SUsers) of
+ N when N =< 100 ->
+ [list_given_users(Host, SUsers, <<"../">>, Lang,
+ URLFunc)];
+ N ->
+ NParts = trunc(math:sqrt(N * 6.17999999999999993783e-1))
+ + 1,
+ M = trunc(N / NParts) + 1,
+ lists:flatmap(fun (K) ->
+ L = K + M - 1,
+ Last = if L < N ->
+ su_to_list(lists:nth(L,
+ SUsers));
+ true ->
+ su_to_list(lists:last(SUsers))
+ end,
+ Name = <<(su_to_list(lists:nth(K,
+ SUsers)))/binary,
+ $\s, 226, 128, 148, $\s,
+ Last/binary>>,
+ [?AC((URLFunc({user_diapason, K, L})),
+ Name),
+ ?BR]
end,
- Name =
- su_to_list(lists:nth(K, SUsers)) ++
- [$\s, 226, 128, 148, $\s] ++
- Last,
- [?AC(URLFunc({user_diapason, K, L}), Name), ?BR]
- end, lists:seq(1, N, M))
- end,
+ lists:seq(1, N, M))
+ end,
case Res of
- ok -> [?XREST("Submitted")];
- error -> [?XREST("Bad format")];
- nothing -> []
- end ++
- [?XAE("form", [{"action", ""}, {"method", "post"}],
- [?XE("table",
- [?XE("tr",
- [?XC("td", ?T("User") ++ ":"),
- ?XE("td", [?INPUT("text", "newusername", "")]),
- ?XE("td", [?C([" @ ", Host])])
- ]),
- ?XE("tr",
- [?XC("td", ?T("Password") ++ ":"),
- ?XE("td", [?INPUT("password", "newuserpassword", "")]),
- ?X("td")
- ]),
- ?XE("tr",
- [?X("td"),
- ?XAE("td", [{"class", "alignright"}],
- [?INPUTT("submit", "addnewuser", "Add User")]),
- ?X("td")
- ])]),
- ?P] ++
- FUsers)].
-
%% Parse user creation query and try register:
+ ok -> [?XREST(<<"Submitted">>)];
+ error -> [?XREST(<<"Bad format">>)];
+ nothing -> []
+ end
+ ++
+ [?XAE(<<"form">>,
+ [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
+ ([?XE(<<"table">>,
+ [?XE(<<"tr">>,
+ [?XC(<<"td">>, <<(?T(<<"User">>))/binary, ":">>),
+ ?XE(<<"td">>,
+ [?INPUT(<<"text">>, <<"newusername">>, <<"">>)]),
+ ?XE(<<"td">>, [?C(<<" @ ", Host/binary>>)])]),
+ ?XE(<<"tr">>,
+ [?XC(<<"td">>, <<(?T(<<"Password">>))/binary, ":">>),
+ ?XE(<<"td">>,
+ [?INPUT(<<"password">>, <<"newuserpassword">>,
+ <<"">>)]),
+ ?X(<<"td">>)]),
+ ?XE(<<"tr">>,
+ [?X(<<"td">>),
+ ?XAE(<<"td">>, [{<<"class">>, <<"alignright">>}],
+ [?INPUTT(<<"submit">>, <<"addnewuser">>,
+ <<"Add User">>)]),
+ ?X(<<"td">>)])]),
+ ?P]
+ ++ FUsers))].
+
list_users_parse_query(Query, Host) ->
- case lists:keysearch("addnewuser", 1, Query) of
- {value, _} ->
- {value, {_, Username}} =
- lists:keysearch("newusername", 1, Query),
- {value, {_, Password}} =
- lists:keysearch("newuserpassword", 1, Query),
- case jlib:string_to_jid(Username++"@"++Host) of
- error ->
- error;
- #jid{user = User, server = Server} ->
- case ejabberd_auth:try_register(User, Server, Password) of
- {error, _Reason} ->
- error;
- _ ->
- ok
- end
- end;
- false ->
- nothing
+ case lists:keysearch(<<"addnewuser">>, 1, Query) of
+ {value, _} ->
+ {value, {_, Username}} =
+ lists:keysearch(<<"newusername">>, 1, Query),
+ {value, {_, Password}} =
+ lists:keysearch(<<"newuserpassword">>, 1, Query),
+ case jlib:string_to_jid(<<Username/binary, "@",
+ Host/binary>>)
+ of
+ error -> error;
+ #jid{user = User, server = Server} ->
+ case ejabberd_auth:try_register(User, Server, Password)
+ of
+ {error, _Reason} -> error;
+ _ -> ok
+ end
+ end;
+ false -> nothing
end.
-
list_users_in_diapason(Host, Diap, Lang, URLFunc) ->
Users = ejabberd_auth:get_vh_registered_users(Host),
SUsers = lists:sort([{S, U} || {U, S} <- Users]),
- [S1, S2] = ejabberd_regexp:split(Diap, "-"),
- N1 = list_to_integer(S1),
- N2 = list_to_integer(S2),
+ [S1, S2] = ejabberd_regexp:split(Diap, <<"-">>),
+ N1 = jlib:binary_to_integer(S1),
+ N2 = jlib:binary_to_integer(S2),
Sub = lists:sublist(SUsers, N1, N2 - N1 + 1),
- [list_given_users(Host, Sub, "../../", Lang, URLFunc)].
+ [list_given_users(Host, Sub, <<"../../">>, Lang,
+ URLFunc)].
list_given_users(Host, Users, Prefix, Lang, URLFunc) ->
ModOffline = get_offlinemsg_module(Host),
- ?XE("table",
- [?XE("thead",
- [?XE("tr",
- [?XCT("td", "User"),
- ?XCT("td", "Offline Messages"),
- ?XCT("td", "Last Activity")])]),
- ?XE("tbody",
- lists:map(
- fun(_SU = {Server, User}) ->
- US = {User, Server},
- QueueLenStr = get_offlinemsg_length(ModOffline, User, Server),
- FQueueLen = [?AC(URLFunc({users_queue, Prefix,
- User, Server}),
- QueueLenStr)],
- FLast =
- case ejabberd_sm:get_user_resources(User, Server) of
- [] ->
- case mod_last:get_last_info(User, Server) of
- not_found ->
- ?T("Never");
- {ok, Shift, _Status} ->
- TimeStamp = {Shift div 1000000,
- Shift rem 1000000,
- 0},
- {{Year, Month, Day}, {Hour, Minute, Second}} =
- calendar:now_to_local_time(TimeStamp),
- lists:flatten(
- io_lib:format(
- "~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
- [Year, Month, Day, Hour, Minute, Second]))
- end;
- _ ->
- ?T("Online")
- end,
- ?XE("tr",
- [?XE("td",
- [?AC(URLFunc({user, Prefix,
- ejabberd_http:url_encode(User),
- Server}),
- us_to_list(US))]),
- ?XE("td", FQueueLen),
- ?XC("td", FLast)])
- end, Users)
- )]).
+ ?XE(<<"table">>,
+ [?XE(<<"thead">>,
+ [?XE(<<"tr">>,
+ [?XCT(<<"td">>, <<"User">>),
+ ?XCT(<<"td">>, <<"Offline Messages">>),
+ ?XCT(<<"td">>, <<"Last Activity">>)])]),
+ ?XE(<<"tbody">>,
+ (lists:map(fun (_SU = {Server, User}) ->
+ US = {User, Server},
+ QueueLenStr = get_offlinemsg_length(ModOffline,
+ User,
+ Server),
+ FQueueLen = [?AC((URLFunc({users_queue, Prefix,
+ User, Server})),
+ QueueLenStr)],
+ FLast = case
+ ejabberd_sm:get_user_resources(User,
+ Server)
+ of
+ [] ->
+ case mod_last:get_last_info(User,
+ Server)
+ of
+ not_found -> ?T(<<"Never">>);
+ {ok, Shift, _Status} ->
+ TimeStamp = {Shift div
+ 1000000,
+ Shift rem
+ 1000000,
+ 0},
+ {{Year, Month, Day},
+ {Hour, Minute, Second}} =
+ calendar:now_to_local_time(TimeStamp),
+ iolist_to_binary(io_lib:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
+ [Year,
+ Month,
+ Day,
+ Hour,
+ Minute,
+ Second]))
+ end;
+ _ -> ?T(<<"Online">>)
+ end,
+ ?XE(<<"tr">>,
+ [?XE(<<"td">>,
+ [?AC((URLFunc({user, Prefix,
+ ejabberd_http:url_encode(User),
+ Server})),
+ (us_to_list(US)))]),
+ ?XE(<<"td">>, FQueueLen),
+ ?XC(<<"td">>, FLast)])
+ end,
+ Users)))]).
get_offlinemsg_length(ModOffline, User, Server) ->
case ModOffline of
- none -> "disabled";
- _ -> pretty_string_int(ModOffline:get_queue_length(User, Server))
+ none -> <<"disabled">>;
+ _ ->
+ pretty_string_int(ModOffline:get_queue_length(User,
+ Server))
end.
get_offlinemsg_module(Server) ->
case gen_mod:is_loaded(Server, mod_offline) of
- true ->
- mod_offline;
- false ->
- none
+ true -> mod_offline;
+ false -> none
end.
get_lastactivity_menuitem_list(Server) ->
case gen_mod:db_type(Server, mod_last) of
- mnesia -> [{"last-activity", "Last Activity"}];
- _ -> []
+ mnesia -> [{<<"last-activity">>, <<"Last Activity">>}];
+ _ -> []
end.
us_to_list({User, Server}) ->
- jlib:jid_to_string({User, Server, ""}).
+ jlib:jid_to_string({User, Server, <<"">>}).
su_to_list({Server, User}) ->
- jlib:jid_to_string({User, Server, ""}).
+ jlib:jid_to_string({User, Server, <<"">>}).
%%%==================================
%%%% get_stats
get_stats(global, Lang) ->
OnlineUsers = mnesia:table_info(session, size),
- RegisteredUsers = lists:foldl(
- fun(Host, Total) ->
- ejabberd_auth:get_vh_registered_users_number(Host) + Total
- end, 0, ejabberd_config:get_global_option(hosts)),
+ RegisteredUsers = lists:foldl(fun (Host, Total) ->
+ ejabberd_auth:get_vh_registered_users_number(Host)
+ + Total
+ end,
+ 0, ?MYHOSTS),
S2SConns = ejabberd_s2s:dirty_get_connections(),
S2SConnections = length(S2SConns),
- S2SServers = length(lists:usort([element(2, C) || C <- S2SConns])),
- [?XAE("table", [],
- [?XE("tbody",
- [?XE("tr", [?XCT("td", "Registered Users:"),
- ?XC("td", pretty_string_int(RegisteredUsers))]),
- ?XE("tr", [?XCT("td", "Online Users:"),
- ?XC("td", pretty_string_int(OnlineUsers))]),
- ?XE("tr", [?XCT("td", "Outgoing s2s Connections:"),
- ?XC("td", pretty_string_int(S2SConnections))]),
- ?XE("tr", [?XCT("td", "Outgoing s2s Servers:"),
- ?XC("td", pretty_string_int(S2SServers))])
- ])
- ])];
-
+ S2SServers = length(lists:usort([element(2, C)
+ || C <- S2SConns])),
+ [?XAE(<<"table">>, [],
+ [?XE(<<"tbody">>,
+ [?XE(<<"tr">>,
+ [?XCT(<<"td">>, <<"Registered Users:">>),
+ ?XC(<<"td">>, (pretty_string_int(RegisteredUsers)))]),
+ ?XE(<<"tr">>,
+ [?XCT(<<"td">>, <<"Online Users:">>),
+ ?XC(<<"td">>, (pretty_string_int(OnlineUsers)))]),
+ ?XE(<<"tr">>,
+ [?XCT(<<"td">>, <<"Outgoing s2s Connections:">>),
+ ?XC(<<"td">>, (pretty_string_int(S2SConnections)))]),
+ ?XE(<<"tr">>,
+ [?XCT(<<"td">>, <<"Outgoing s2s Servers:">>),
+ ?XC(<<"td">>, (pretty_string_int(S2SServers)))])])])];
get_stats(Host, Lang) ->
- OnlineUsers = length(ejabberd_sm:get_vh_session_list(Host)),
- RegisteredUsers = ejabberd_auth:get_vh_registered_users_number(Host),
- [?XAE("table", [],
- [?XE("tbody",
- [?XE("tr", [?XCT("td", "Registered Users:"),
- ?XC("td", pretty_string_int(RegisteredUsers))]),
- ?XE("tr", [?XCT("td", "Online Users:"),
- ?XC("td", pretty_string_int(OnlineUsers))])
- ])
- ])].
-
+ OnlineUsers =
+ length(ejabberd_sm:get_vh_session_list(Host)),
+ RegisteredUsers =
+ ejabberd_auth:get_vh_registered_users_number(Host),
+ [?XAE(<<"table">>, [],
+ [?XE(<<"tbody">>,
+ [?XE(<<"tr">>,
+ [?XCT(<<"td">>, <<"Registered Users:">>),
+ ?XC(<<"td">>, (pretty_string_int(RegisteredUsers)))]),
+ ?XE(<<"tr">>,
+ [?XCT(<<"td">>, <<"Online Users:">>),
+ ?XC(<<"td">>, (pretty_string_int(OnlineUsers)))])])])].
list_online_users(Host, _Lang) ->
- Users = [{S, U} || {U, S, _R} <- ejabberd_sm:get_vh_session_list(Host)],
+ Users = [{S, U}
+ || {U, S, _R} <- ejabberd_sm:get_vh_session_list(Host)],
SUsers = lists:usort(Users),
- lists:flatmap(
- fun({_S, U} = SU) ->
- [?AC("../user/" ++ ejabberd_http:url_encode(U) ++ "/",
- su_to_list(SU)),
- ?BR]
- end, SUsers).
+ lists:flatmap(fun ({_S, U} = SU) ->
+ [?AC(<<"../user/",
+ (ejabberd_http:url_encode(U))/binary, "/">>,
+ (su_to_list(SU))),
+ ?BR]
+ end,
+ SUsers).
user_info(User, Server, Query, Lang) ->
LServer = jlib:nameprep(Server),
US = {jlib:nodeprep(User), LServer},
Res = user_parse_query(User, Server, Query),
- Resources = ejabberd_sm:get_user_resources(User, Server),
+ Resources = ejabberd_sm:get_user_resources(User,
+ Server),
FResources =
- case Resources of
- [] ->
- [?CT("None")];
- _ ->
- [?XE("ul",
- lists:map(fun(R) ->
- FIP = case ejabberd_sm:get_user_info(
- User, Server, R) of
- offline ->
- "";
- Info when is_list(Info) ->
- Node = proplists:get_value(node, Info),
- Conn = proplists:get_value(conn, Info),
- {IP, Port} = proplists:get_value(ip, Info),
- ConnS = case Conn of
- c2s -> "plain";
- c2s_tls -> "tls";
- c2s_compressed -> "zlib";
- c2s_compressed_tls -> "tls+zlib";
- http_bind -> "http-bind";
- http_poll -> "http-poll"
- end,
- " (" ++
- ConnS ++ "://" ++
- inet_parse:ntoa(IP) ++
- ":" ++
- integer_to_list(Port)
- ++ "#" ++ atom_to_list(Node)
- ++ ")"
- end,
- ?LI([?C(R ++ FIP)])
- end, lists:sort(Resources)))]
- end,
+ case Resources of
+ [] -> [?CT(<<"None">>)];
+ _ ->
+ [?XE(<<"ul">>,
+ (lists:map(
+ fun (R) ->
+ FIP = case
+ ejabberd_sm:get_user_info(User,
+ Server,
+ R)
+ of
+ offline -> <<"">>;
+ Info
+ when
+ is_list(Info) ->
+ Node =
+ proplists:get_value(node,
+ Info),
+ Conn =
+ proplists:get_value(conn,
+ Info),
+ {IP, Port} =
+ proplists:get_value(ip,
+ Info),
+ ConnS = case Conn of
+ c2s ->
+ <<"plain">>;
+ c2s_tls ->
+ <<"tls">>;
+ c2s_compressed ->
+ <<"zlib">>;
+ c2s_compressed_tls ->
+ <<"tls+zlib">>;
+ http_bind ->
+ <<"http-bind">>;
+ http_poll ->
+ <<"http-poll">>
+ end,
+ <<" (", ConnS/binary,
+ "://",
+ (jlib:ip_to_list(IP))/binary,
+ ":",
+ (jlib:integer_to_binary(Port))/binary,
+ "#",
+ (jlib:atom_to_binary(Node))/binary,
+ ")">>
+ end,
+ ?LI([?C((<<R/binary, FIP/binary>>))])
+ end,
+ lists:sort(Resources))))]
+ end,
Password = ejabberd_auth:get_password_s(User, Server),
- FPassword = [?INPUT("password", "password", Password), ?C(" "),
- ?INPUTT("submit", "chpassword", "Change Password")],
- UserItems = ejabberd_hooks:run_fold(webadmin_user, LServer, [],
- [User, Server, Lang]),
- %% Code copied from list_given_users/5:
- LastActivity = case ejabberd_sm:get_user_resources(User, Server) of
- [] ->
- case mod_last:get_last_info(User, Server) of
- not_found ->
- ?T("Never");
- {ok, Shift, _Status} ->
- TimeStamp = {Shift div 1000000,
- Shift rem 1000000,
- 0},
- {{Year, Month, Day}, {Hour, Minute, Second}} =
- calendar:now_to_local_time(TimeStamp),
- lists:flatten(
- io_lib:format(
- "~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
- [Year, Month, Day, Hour, Minute, Second]))
- end;
- _ ->
- ?T("Online")
+ FPassword = [?INPUT(<<"password">>, <<"password">>,
+ Password),
+ ?C(<<" ">>),
+ ?INPUTT(<<"submit">>, <<"chpassword">>,
+ <<"Change Password">>)],
+ UserItems = ejabberd_hooks:run_fold(webadmin_user,
+ LServer, [], [User, Server, Lang]),
+ LastActivity = case ejabberd_sm:get_user_resources(User,
+ Server)
+ of
+ [] ->
+ case mod_last:get_last_info(User, Server) of
+ not_found -> ?T(<<"Never">>);
+ {ok, Shift, _Status} ->
+ TimeStamp = {Shift div 1000000,
+ Shift rem 1000000, 0},
+ {{Year, Month, Day}, {Hour, Minute, Second}} =
+ calendar:now_to_local_time(TimeStamp),
+ iolist_to_binary(io_lib:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
+ [Year, Month, Day,
+ Hour, Minute,
+ Second]))
+ end;
+ _ -> ?T(<<"Online">>)
end,
- [?XC("h1", ?T("User ") ++ us_to_list(US))] ++
- case Res of
- ok -> [?XREST("Submitted")];
- error -> [?XREST("Bad format")];
- nothing -> []
- end ++
- [?XAE("form", [{"action", ""}, {"method", "post"}],
- [?XCT("h3", "Connected Resources:")] ++ FResources ++
- [?XCT("h3", "Password:")] ++ FPassword ++
- [?XCT("h3", "Last Activity")] ++ [?C(LastActivity)] ++
- UserItems ++
- [?P, ?INPUTT("submit", "removeuser", "Remove User")])].
-
+ [?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"User ~s">>),
+ [us_to_list(US)])))]
+ ++
+ case Res of
+ ok -> [?XREST(<<"Submitted">>)];
+ error -> [?XREST(<<"Bad format">>)];
+ nothing -> []
+ end
+ ++
+ [?XAE(<<"form">>,
+ [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
+ ([?XCT(<<"h3">>, <<"Connected Resources:">>)] ++
+ FResources ++
+ [?XCT(<<"h3">>, <<"Password:">>)] ++
+ FPassword ++
+ [?XCT(<<"h3">>, <<"Last Activity">>)] ++
+ [?C(LastActivity)] ++
+ UserItems ++
+ [?P,
+ ?INPUTT(<<"submit">>, <<"removeuser">>,
+ <<"Remove User">>)]))].
user_parse_query(User, Server, Query) ->
- lists:foldl(fun({Action, _Value}, Acc) when Acc == nothing ->
- user_parse_query1(Action, User, Server, Query);
- ({_Action, _Value}, Acc) ->
- Acc
- end, nothing, Query).
-
-user_parse_query1("password", _User, _Server, _Query) ->
+ lists:foldl(fun ({Action, _Value}, Acc)
+ when Acc == nothing ->
+ user_parse_query1(Action, User, Server, Query);
+ ({_Action, _Value}, Acc) -> Acc
+ end,
+ nothing, Query).
+
+user_parse_query1(<<"password">>, _User, _Server,
+ _Query) ->
nothing;
-user_parse_query1("chpassword", User, Server, Query) ->
- case lists:keysearch("password", 1, Query) of
- {value, {_, undefined}} ->
- error;
- {value, {_, Password}} ->
- ejabberd_auth:set_password(User, Server, Password),
- ok;
- _ ->
- error
+user_parse_query1(<<"chpassword">>, User, Server,
+ Query) ->
+ case lists:keysearch(<<"password">>, 1, Query) of
+ {value, {_, Password}} ->
+ ejabberd_auth:set_password(User, Server, Password), ok;
+ _ -> error
end;
-user_parse_query1("removeuser", User, Server, _Query) ->
- ejabberd_auth:remove_user(User, Server),
- ok;
+user_parse_query1(<<"removeuser">>, User, Server,
+ _Query) ->
+ ejabberd_auth:remove_user(User, Server), ok;
user_parse_query1(Action, User, Server, Query) ->
- case ejabberd_hooks:run_fold(webadmin_user_parse_query, Server, [], [Action, User, Server, Query]) of
- [] -> nothing;
- Res -> Res
+ case ejabberd_hooks:run_fold(webadmin_user_parse_query,
+ Server, [], [Action, User, Server, Query])
+ of
+ [] -> nothing;
+ Res -> Res
end.
-
-
list_last_activity(Host, Lang, Integral, Period) ->
{MegaSecs, Secs, _MicroSecs} = now(),
TimeStamp = MegaSecs * 1000000 + Secs,
case Period of
- "all" ->
- TS = 0,
- Days = infinity;
- "year" ->
- TS = TimeStamp - 366 * 86400,
- Days = 366;
- _ ->
- TS = TimeStamp - 31 * 86400,
- Days = 31
+ <<"all">> -> TS = 0, Days = infinity;
+ <<"year">> -> TS = TimeStamp - 366 * 86400, Days = 366;
+ _ -> TS = TimeStamp - 31 * 86400, Days = 31
end,
- case catch mnesia:dirty_select(
- last_activity, [{{last_activity, {'_', Host}, '$1', '_'},
- [{'>', '$1', TS}],
- [{'trunc', {'/',
- {'-', TimeStamp, '$1'},
- 86400}}]}]) of
- {'EXIT', _Reason} ->
- [];
- Vals ->
- Hist = histogram(Vals, Integral),
- if
- Hist == [] ->
- [?CT("No Data")];
- true ->
- Left = if
- Days == infinity ->
- 0;
- true ->
- Days - length(Hist)
- end,
- Tail = if
- Integral ->
- lists:duplicate(Left, lists:last(Hist));
- true ->
- lists:duplicate(Left, 0)
- end,
- Max = lists:max(Hist),
- [?XAE("ol",
- [{"id", "lastactivity"}, {"start", "0"}],
- [?XAE("li",
- [{"style",
- "width:" ++ integer_to_list(
- trunc(90 * V / Max)) ++ "%;"}],
- [{xmlcdata, pretty_string_int(V)}])
- || V <- Hist ++ Tail])]
- end
+ case catch mnesia:dirty_select(last_activity,
+ [{{last_activity, {'_', Host}, '$1', '_'},
+ [{'>', '$1', TS}],
+ [{trunc,
+ {'/', {'-', TimeStamp, '$1'}, 86400}}]}])
+ of
+ {'EXIT', _Reason} -> [];
+ Vals ->
+ Hist = histogram(Vals, Integral),
+ if Hist == [] -> [?CT(<<"No Data">>)];
+ true ->
+ Left = if Days == infinity -> 0;
+ true -> Days - length(Hist)
+ end,
+ Tail = if Integral ->
+ lists:duplicate(Left, lists:last(Hist));
+ true -> lists:duplicate(Left, 0)
+ end,
+ Max = lists:max(Hist),
+ [?XAE(<<"ol">>,
+ [{<<"id">>, <<"lastactivity">>},
+ {<<"start">>, <<"0">>}],
+ [?XAE(<<"li">>,
+ [{<<"style">>,
+ <<"width:",
+ (iolist_to_binary(integer_to_list(trunc(90 * V
+ /
+ Max))))/binary,
+ "%;">>}],
+ [{xmlcdata, pretty_string_int(V)}])
+ || V <- Hist ++ Tail])]
+ end
end.
histogram(Values, Integral) ->
histogram(lists:sort(Values), Integral, 0, 0, []).
-histogram([H | T], Integral, Current, Count, Hist) when Current == H ->
+histogram([H | T], Integral, Current, Count, Hist)
+ when Current == H ->
histogram(T, Integral, Current, Count + 1, Hist);
-
-histogram([H | _] = Values, Integral, Current, Count, Hist) when Current < H ->
- if
- Integral ->
- histogram(Values, Integral, Current + 1, Count, [Count | Hist]);
- true ->
- histogram(Values, Integral, Current + 1, 0, [Count | Hist])
+histogram([H | _] = Values, Integral, Current, Count,
+ Hist)
+ when Current < H ->
+ if Integral ->
+ histogram(Values, Integral, Current + 1, Count,
+ [Count | Hist]);
+ true ->
+ histogram(Values, Integral, Current + 1, 0,
+ [Count | Hist])
end;
-
histogram([], _Integral, _Current, Count, Hist) ->
- if
- Count > 0 ->
- lists:reverse([Count | Hist]);
- true ->
- lists:reverse(Hist)
+ if Count > 0 -> lists:reverse([Count | Hist]);
+ true -> lists:reverse(Hist)
end.
%%%==================================
@@ -1871,842 +1720,1007 @@ histogram([], _Integral, _Current, Count, Hist) ->
get_nodes(Lang) ->
RunningNodes = mnesia:system_info(running_db_nodes),
- StoppedNodes = lists:usort(mnesia:system_info(db_nodes) ++
- mnesia:system_info(extra_db_nodes)) --
- RunningNodes,
- FRN = if
- RunningNodes == [] ->
- ?CT("None");
- true ->
- ?XE("ul",
- lists:map(
- fun(N) ->
- S = atom_to_list(N),
- ?LI([?AC("../node/" ++ S ++ "/", S)])
- end, lists:sort(RunningNodes)))
+ StoppedNodes = lists:usort(mnesia:system_info(db_nodes)
+ ++ mnesia:system_info(extra_db_nodes))
+ -- RunningNodes,
+ FRN = if RunningNodes == [] -> ?CT(<<"None">>);
+ true ->
+ ?XE(<<"ul">>,
+ (lists:map(fun (N) ->
+ S = iolist_to_binary(atom_to_list(N)),
+ ?LI([?AC(<<"../node/", S/binary, "/">>,
+ S)])
+ end,
+ lists:sort(RunningNodes))))
end,
- FSN = if
- StoppedNodes == [] ->
- ?CT("None");
- true ->
- ?XE("ul",
- lists:map(
- fun(N) ->
- S = atom_to_list(N),
- ?LI([?C(S)])
- end, lists:sort(StoppedNodes)))
+ FSN = if StoppedNodes == [] -> ?CT(<<"None">>);
+ true ->
+ ?XE(<<"ul">>,
+ (lists:map(fun (N) ->
+ S = iolist_to_binary(atom_to_list(N)),
+ ?LI([?C(S)])
+ end,
+ lists:sort(StoppedNodes))))
end,
- [?XCT("h1", "Nodes"),
- ?XCT("h3", "Running Nodes"),
- FRN,
- ?XCT("h3", "Stopped Nodes"),
- FSN].
+ [?XCT(<<"h1">>, <<"Nodes">>),
+ ?XCT(<<"h3">>, <<"Running Nodes">>), FRN,
+ ?XCT(<<"h3">>, <<"Stopped Nodes">>), FSN].
search_running_node(SNode) ->
- search_running_node(SNode, mnesia:system_info(running_db_nodes)).
+ search_running_node(SNode,
+ mnesia:system_info(running_db_nodes)).
-search_running_node(_, []) ->
- false;
+search_running_node(_, []) -> false;
search_running_node(SNode, [Node | Nodes]) ->
- case atom_to_list(Node) of
- SNode ->
- Node;
- _ ->
- search_running_node(SNode, Nodes)
+ case iolist_to_binary(atom_to_list(Node)) of
+ SNode -> Node;
+ _ -> search_running_node(SNode, Nodes)
end.
get_node(global, Node, [], Query, Lang) ->
Res = node_parse_query(Node, Query),
Base = get_base_path(global, Node),
MenuItems2 = make_menu_items(global, Node, Base, Lang),
- [?XC("h1", ?T("Node ") ++ atom_to_list(Node))] ++
- case Res of
- ok -> [?XREST("Submitted")];
- error -> [?XREST("Bad format")];
- nothing -> []
- end ++
- [?XE("ul",
- [?LI([?ACT(Base ++ "db/", "Database")]),
- ?LI([?ACT(Base ++ "backup/", "Backup")]),
- ?LI([?ACT(Base ++ "ports/", "Listened Ports")]),
- ?LI([?ACT(Base ++ "stats/", "Statistics")]),
- ?LI([?ACT(Base ++ "update/", "Update")])
- ] ++ MenuItems2),
- ?XAE("form", [{"action", ""}, {"method", "post"}],
- [?INPUTT("submit", "restart", "Restart"),
- ?C(" "),
- ?INPUTT("submit", "stop", "Stop")])
- ];
-
+ [?XC(<<"h1">>,
+ list_to_binary(io_lib:format(?T(<<"Node ~p">>), [Node])))]
+ ++
+ case Res of
+ ok -> [?XREST(<<"Submitted">>)];
+ error -> [?XREST(<<"Bad format">>)];
+ nothing -> []
+ end
+ ++
+ [?XE(<<"ul">>,
+ ([?LI([?ACT(<<Base/binary, "db/">>, <<"Database">>)]),
+ ?LI([?ACT(<<Base/binary, "backup/">>, <<"Backup">>)]),
+ ?LI([?ACT(<<Base/binary, "ports/">>,
+ <<"Listened Ports">>)]),
+ ?LI([?ACT(<<Base/binary, "stats/">>,
+ <<"Statistics">>)]),
+ ?LI([?ACT(<<Base/binary, "update/">>, <<"Update">>)])]
+ ++ MenuItems2)),
+ ?XAE(<<"form">>,
+ [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
+ [?INPUTT(<<"submit">>, <<"restart">>, <<"Restart">>),
+ ?C(<<" ">>),
+ ?INPUTT(<<"submit">>, <<"stop">>, <<"Stop">>)])];
get_node(Host, Node, [], _Query, Lang) ->
Base = get_base_path(Host, Node),
MenuItems2 = make_menu_items(Host, Node, Base, Lang),
- [?XC("h1", ?T("Node ") ++ atom_to_list(Node)),
- ?XE("ul",
- [?LI([?ACT(Base ++ "modules/", "Modules")])] ++ MenuItems2)
- ];
-
-get_node(global, Node, ["db"], Query, Lang) ->
+ [?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"Node ~p">>), [Node]))),
+ ?XE(<<"ul">>,
+ ([?LI([?ACT(<<Base/binary, "modules/">>,
+ <<"Modules">>)])]
+ ++ MenuItems2))];
+get_node(global, Node, [<<"db">>], Query, Lang) ->
case rpc:call(Node, mnesia, system_info, [tables]) of
- {badrpc, _Reason} ->
- [?XCT("h1", "RPC Call Error")];
- Tables ->
- ResS = case node_db_parse_query(Node, Tables, Query) of
- nothing -> [];
- ok -> [?XREST("Submitted")]
- end,
- STables = lists:sort(Tables),
- Rows = lists:map(
- fun(Table) ->
- STable = atom_to_list(Table),
- TInfo =
- case rpc:call(Node,
- mnesia,
- table_info,
- [Table, all]) of
- {badrpc, _} ->
- [];
- I ->
- I
- end,
- {Type, Size, Memory} =
- case {lists:keysearch(storage_type, 1, TInfo),
- lists:keysearch(size, 1, TInfo),
- lists:keysearch(memory, 1, TInfo)} of
- {{value, {storage_type, T}},
- {value, {size, S}},
- {value, {memory, M}}} ->
- {T, S, M};
- _ ->
- {unknown, 0, 0}
- end,
- ?XE("tr",
- [?XC("td", STable),
- ?XE("td", [db_storage_select(
- STable, Type, Lang)]),
- ?XAC("td", [{"class", "alignright"}],
- pretty_string_int(Size)),
- ?XAC("td", [{"class", "alignright"}],
- pretty_string_int(Memory))
- ])
- end, STables),
- [?XC("h1", ?T("Database Tables at ") ++ atom_to_list(Node))] ++
- ResS ++
- [?XAE("form", [{"action", ""}, {"method", "post"}],
- [?XAE("table", [],
- [?XE("thead",
- [?XE("tr",
- [?XCT("td", "Name"),
- ?XCT("td", "Storage Type"),
- ?XCT("td", "Elements"), %% Elements/items/records inserted in the table
- ?XCT("td", "Memory") %% Words or Bytes allocated to the table on this node
- ])]),
- ?XE("tbody",
- Rows ++
- [?XE("tr",
- [?XAE("td", [{"colspan", "4"},
- {"class", "alignright"}],
- [?INPUTT("submit", "submit",
- "Submit")])
- ])]
- )])])]
+ {badrpc, _Reason} ->
+ [?XCT(<<"h1">>, <<"RPC Call Error">>)];
+ Tables ->
+ ResS = case node_db_parse_query(Node, Tables, Query) of
+ nothing -> [];
+ ok -> [?XREST(<<"Submitted">>)]
+ end,
+ STables = lists:sort(Tables),
+ Rows = lists:map(fun (Table) ->
+ STable =
+ iolist_to_binary(atom_to_list(Table)),
+ TInfo = case rpc:call(Node, mnesia,
+ table_info,
+ [Table, all])
+ of
+ {badrpc, _} -> [];
+ I -> I
+ end,
+ {Type, Size, Memory} = case
+ {lists:keysearch(storage_type,
+ 1,
+ TInfo),
+ lists:keysearch(size,
+ 1,
+ TInfo),
+ lists:keysearch(memory,
+ 1,
+ TInfo)}
+ of
+ {{value,
+ {storage_type,
+ T}},
+ {value, {size, S}},
+ {value,
+ {memory, M}}} ->
+ {T, S, M};
+ _ -> {unknown, 0, 0}
+ end,
+ ?XE(<<"tr">>,
+ [?XC(<<"td">>, STable),
+ ?XE(<<"td">>,
+ [db_storage_select(STable, Type,
+ Lang)]),
+ ?XAC(<<"td">>,
+ [{<<"class">>, <<"alignright">>}],
+ (pretty_string_int(Size))),
+ ?XAC(<<"td">>,
+ [{<<"class">>, <<"alignright">>}],
+ (pretty_string_int(Memory)))])
+ end,
+ STables),
+ [?XC(<<"h1">>,
+ list_to_binary(io_lib:format(?T(<<"Database Tables at ~p">>),
+ [Node]))
+ )]
+ ++
+ ResS ++
+ [?XAE(<<"form">>,
+ [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
+ [?XAE(<<"table">>, [],
+ [?XE(<<"thead">>,
+ [?XE(<<"tr">>,
+ [?XCT(<<"td">>, <<"Name">>),
+ ?XCT(<<"td">>, <<"Storage Type">>),
+ ?XCT(<<"td">>, <<"Elements">>),
+ ?XCT(<<"td">>, <<"Memory">>)])]),
+ ?XE(<<"tbody">>,
+ (Rows ++
+ [?XE(<<"tr">>,
+ [?XAE(<<"td">>,
+ [{<<"colspan">>, <<"4">>},
+ {<<"class">>, <<"alignright">>}],
+ [?INPUTT(<<"submit">>,
+ <<"submit">>,
+ <<"Submit">>)])])]))])])]
end;
-
-get_node(global, Node, ["backup"], Query, Lang) ->
+get_node(global, Node, [<<"backup">>], Query, Lang) ->
HomeDirRaw = case {os:getenv("HOME"), os:type()} of
- {EnvHome, _} when is_list(EnvHome) -> EnvHome;
- {false, {win32, _Osname}} -> "C:/";
- {false, _} -> "/tmp/"
- end,
+ {EnvHome, _} when is_list(EnvHome) -> list_to_binary(EnvHome);
+ {false, {win32, _Osname}} -> <<"C:/">>;
+ {false, _} -> <<"/tmp/">>
+ end,
HomeDir = filename:nativename(HomeDirRaw),
ResS = case node_backup_parse_query(Node, Query) of
- nothing -> [];
- ok -> [?XREST("Submitted")];
- {error, Error} -> [?XRES(?T("Error") ++": " ++ io_lib:format("~p", [Error]))]
+ nothing -> [];
+ ok -> [?XREST(<<"Submitted">>)];
+ {error, Error} ->
+ [?XRES(<<(?T(<<"Error">>))/binary, ": ",
+ (list_to_binary(io_lib:format("~p", [Error])))/binary>>)]
end,
- ?H1GL(?T("Backup of ") ++ atom_to_list(Node), "list-eja-commands", "List of ejabberd Commands") ++
- ResS ++
- [?XCT("p", "Please note that these options will only backup the builtin Mnesia database. If you are using the ODBC module, you also need to backup your SQL database separately."),
- ?XAE("form", [{"action", ""}, {"method", "post"}],
- [?XAE("table", [],
- [?XE("tbody",
- [?XE("tr",
- [?XCT("td", "Store binary backup:"),
- ?XE("td", [?INPUT("text", "storepath",
- filename:join(HomeDir, "ejabberd.backup"))]),
- ?XE("td", [?INPUTT("submit", "store",
- "OK")])
- ]),
- ?XE("tr",
- [?XCT("td", "Restore binary backup immediately:"),
- ?XE("td", [?INPUT("text", "restorepath",
- filename:join(HomeDir, "ejabberd.backup"))]),
- ?XE("td", [?INPUTT("submit", "restore",
- "OK")])
- ]),
- ?XE("tr",
- [?XCT("td",
- "Restore binary backup after next ejabberd restart (requires less memory):"),
- ?XE("td", [?INPUT("text", "fallbackpath",
- filename:join(HomeDir, "ejabberd.backup"))]),
- ?XE("td", [?INPUTT("submit", "fallback",
- "OK")])
- ]),
- ?XE("tr",
- [?XCT("td", "Store plain text backup:"),
- ?XE("td", [?INPUT("text", "dumppath",
- filename:join(HomeDir, "ejabberd.dump"))]),
- ?XE("td", [?INPUTT("submit", "dump",
- "OK")])
- ]),
- ?XE("tr",
- [?XCT("td", "Restore plain text backup immediately:"),
- ?XE("td", [?INPUT("text", "loadpath",
- filename:join(HomeDir, "ejabberd.dump"))]),
- ?XE("td", [?INPUTT("submit", "load",
- "OK")])
- ]),
- ?XE("tr",
- [?XCT("td", "Import users data from a PIEFXIS file (XEP-0227):"),
- ?XE("td", [?INPUT("text", "import_piefxis_filepath",
- filename:join(HomeDir, "users.xml"))]),
- ?XE("td", [?INPUTT("submit", "import_piefxis_file",
- "OK")])
- ]),
- ?XE("tr",
- [?XCT("td", "Export data of all users in the server to PIEFXIS files (XEP-0227):"),
- ?XE("td", [?INPUT("text", "export_piefxis_dirpath",
- HomeDir)]),
- ?XE("td", [?INPUTT("submit", "export_piefxis_dir",
- "OK")])
- ]),
- ?XE("tr",
- [?XE("td", [?CT("Export data of users in a host to PIEFXIS files (XEP-0227):"),
- ?C(" "),
- ?INPUT("text", "export_piefxis_host_dirhost", ?MYNAME)]),
- ?XE("td", [?INPUT("text", "export_piefxis_host_dirpath", HomeDir)]),
- ?XE("td", [?INPUTT("submit", "export_piefxis_host_dir",
- "OK")])
- ]),
- ?XE("tr",
- [?XCT("td", "Import user data from jabberd14 spool file:"),
- ?XE("td", [?INPUT("text", "import_filepath",
- filename:join(HomeDir, "user1.xml"))]),
- ?XE("td", [?INPUTT("submit", "import_file",
- "OK")])
- ]),
- ?XE("tr",
- [?XCT("td", "Import users data from jabberd14 spool directory:"),
- ?XE("td", [?INPUT("text", "import_dirpath",
- "/var/spool/jabber/")]),
- ?XE("td", [?INPUTT("submit", "import_dir",
- "OK")])
- ])
- ])
- ])])];
-
-get_node(global, Node, ["ports"], Query, Lang) ->
- Ports = rpc:call(Node, ejabberd_config, get_local_option, [listen]),
- Res = case catch node_ports_parse_query(Node, Ports, Query) of
- submitted ->
- ok;
- {'EXIT', _Reason} ->
- error;
- {is_added, ok} ->
- ok;
- {is_added, {error, Reason}} ->
- {error, io_lib:format("~p", [Reason])};
- _ ->
- nothing
+ (?H1GL(list_to_binary(io_lib:format(?T(<<"Backup of ~p">>), [Node])),
+ <<"list-eja-commands">>,
+ <<"List of ejabberd Commands">>))
+ ++
+ ResS ++
+ [?XCT(<<"p">>,
+ <<"Please note that these options will "
+ "only backup the builtin Mnesia database. "
+ "If you are using the ODBC module, you "
+ "also need to backup your SQL database "
+ "separately.">>),
+ ?XAE(<<"form">>,
+ [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
+ [?XAE(<<"table">>, [],
+ [?XE(<<"tbody">>,
+ [?XE(<<"tr">>,
+ [?XCT(<<"td">>, <<"Store binary backup:">>),
+ ?XE(<<"td">>,
+ [?INPUT(<<"text">>, <<"storepath">>,
+ (filename:join(HomeDir,
+ "ejabberd.backup")))]),
+ ?XE(<<"td">>,
+ [?INPUTT(<<"submit">>, <<"store">>,
+ <<"OK">>)])]),
+ ?XE(<<"tr">>,
+ [?XCT(<<"td">>,
+ <<"Restore binary backup immediately:">>),
+ ?XE(<<"td">>,
+ [?INPUT(<<"text">>, <<"restorepath">>,
+ (filename:join(HomeDir,
+ "ejabberd.backup")))]),
+ ?XE(<<"td">>,
+ [?INPUTT(<<"submit">>, <<"restore">>,
+ <<"OK">>)])]),
+ ?XE(<<"tr">>,
+ [?XCT(<<"td">>,
+ <<"Restore binary backup after next ejabberd "
+ "restart (requires less memory):">>),
+ ?XE(<<"td">>,
+ [?INPUT(<<"text">>, <<"fallbackpath">>,
+ (filename:join(HomeDir,
+ "ejabberd.backup")))]),
+ ?XE(<<"td">>,
+ [?INPUTT(<<"submit">>, <<"fallback">>,
+ <<"OK">>)])]),
+ ?XE(<<"tr">>,
+ [?XCT(<<"td">>, <<"Store plain text backup:">>),
+ ?XE(<<"td">>,
+ [?INPUT(<<"text">>, <<"dumppath">>,
+ (filename:join(HomeDir,
+ "ejabberd.dump")))]),
+ ?XE(<<"td">>,
+ [?INPUTT(<<"submit">>, <<"dump">>,
+ <<"OK">>)])]),
+ ?XE(<<"tr">>,
+ [?XCT(<<"td">>,
+ <<"Restore plain text backup immediately:">>),
+ ?XE(<<"td">>,
+ [?INPUT(<<"text">>, <<"loadpath">>,
+ (filename:join(HomeDir,
+ "ejabberd.dump")))]),
+ ?XE(<<"td">>,
+ [?INPUTT(<<"submit">>, <<"load">>,
+ <<"OK">>)])]),
+ ?XE(<<"tr">>,
+ [?XCT(<<"td">>,
+ <<"Import users data from a PIEFXIS file "
+ "(XEP-0227):">>),
+ ?XE(<<"td">>,
+ [?INPUT(<<"text">>,
+ <<"import_piefxis_filepath">>,
+ (filename:join(HomeDir,
+ "users.xml")))]),
+ ?XE(<<"td">>,
+ [?INPUTT(<<"submit">>,
+ <<"import_piefxis_file">>,
+ <<"OK">>)])]),
+ ?XE(<<"tr">>,
+ [?XCT(<<"td">>,
+ <<"Export data of all users in the server "
+ "to PIEFXIS files (XEP-0227):">>),
+ ?XE(<<"td">>,
+ [?INPUT(<<"text">>,
+ <<"export_piefxis_dirpath">>,
+ HomeDir)]),
+ ?XE(<<"td">>,
+ [?INPUTT(<<"submit">>,
+ <<"export_piefxis_dir">>,
+ <<"OK">>)])]),
+ ?XE(<<"tr">>,
+ [?XE(<<"td">>,
+ [?CT(<<"Export data of users in a host to PIEFXIS "
+ "files (XEP-0227):">>),
+ ?C(<<" ">>),
+ ?INPUT(<<"text">>,
+ <<"export_piefxis_host_dirhost">>,
+ (?MYNAME))]),
+ ?XE(<<"td">>,
+ [?INPUT(<<"text">>,
+ <<"export_piefxis_host_dirpath">>,
+ HomeDir)]),
+ ?XE(<<"td">>,
+ [?INPUTT(<<"submit">>,
+ <<"export_piefxis_host_dir">>,
+ <<"OK">>)])]),
+ ?XE(<<"tr">>,
+ [?XE(<<"td">>,
+ [?CT(<<"Export all tables as SQL queries "
+ "to a file:">>),
+ ?C(<<" ">>),
+ ?INPUT(<<"text">>,
+ <<"export_sql_filehost">>,
+ (?MYNAME))]),
+ ?XE(<<"td">>,
+ [?INPUT(<<"text">>,
+ <<"export_sql_filepath">>,
+ (filename:join(HomeDir,
+ "db.sql")))]),
+ ?XE(<<"td">>,
+ [?INPUTT(<<"submit">>, <<"export_sql_file">>,
+ <<"OK">>)])]),
+ ?XE(<<"tr">>,
+ [?XCT(<<"td">>,
+ <<"Import user data from jabberd14 spool "
+ "file:">>),
+ ?XE(<<"td">>,
+ [?INPUT(<<"text">>, <<"import_filepath">>,
+ (filename:join(HomeDir,
+ "user1.xml")))]),
+ ?XE(<<"td">>,
+ [?INPUTT(<<"submit">>, <<"import_file">>,
+ <<"OK">>)])]),
+ ?XE(<<"tr">>,
+ [?XCT(<<"td">>,
+ <<"Import users data from jabberd14 spool "
+ "directory:">>),
+ ?XE(<<"td">>,
+ [?INPUT(<<"text">>, <<"import_dirpath">>,
+ <<"/var/spool/jabber/">>)]),
+ ?XE(<<"td">>,
+ [?INPUTT(<<"submit">>, <<"import_dir">>,
+ <<"OK">>)])])])])])];
+get_node(global, Node, [<<"ports">>], Query, Lang) ->
+ Ports = rpc:call(Node, ejabberd_config,
+ get_local_option, [listen,
+ {ejabberd_listener, validate_cfg},
+ []]),
+ Res = case catch node_ports_parse_query(Node, Ports,
+ Query)
+ of
+ submitted -> ok;
+ {'EXIT', _Reason} -> error;
+ {is_added, ok} -> ok;
+ {is_added, {error, Reason}} ->
+ {error, iolist_to_binary(io_lib:format("~p", [Reason]))};
+ _ -> nothing
end,
- %% TODO: This sorting does not work when [{{Port, IP}, Module, Opts}]
- NewPorts = lists:sort(
- rpc:call(Node, ejabberd_config, get_local_option, [listen])),
- H1String = ?T("Listened Ports at ") ++ atom_to_list(Node),
- ?H1GL(H1String, "listened", "Listening Ports") ++
- case Res of
- ok -> [?XREST("Submitted")];
- error -> [?XREST("Bad format")];
- {error, ReasonT} -> [?XRES(?T("Error") ++ ": " ++ ReasonT)];
- nothing -> []
- end ++
- [?XAE("form", [{"action", ""}, {"method", "post"}],
- [node_ports_to_xhtml(NewPorts, Lang)])
- ];
-
-get_node(Host, Node, ["modules"], Query, Lang) when is_list(Host) ->
- Modules = rpc:call(Node, gen_mod, loaded_modules_with_opts, [Host]),
- Res = case catch node_modules_parse_query(Host, Node, Modules, Query) of
- submitted ->
- ok;
- {'EXIT', Reason} ->
- ?INFO_MSG("~p~n", [Reason]),
- error;
- _ ->
- nothing
+ NewPorts = lists:sort(rpc:call(Node, ejabberd_config,
+ get_local_option,
+ [listen,
+ {ejabberd_listener, validate_cfg},
+ []])),
+ H1String = <<(?T(<<"Listened Ports at ">>))/binary,
+ (iolist_to_binary(atom_to_list(Node)))/binary>>,
+ (?H1GL(H1String, <<"listened">>, <<"Listening Ports">>))
+ ++
+ case Res of
+ ok -> [?XREST(<<"Submitted">>)];
+ error -> [?XREST(<<"Bad format">>)];
+ {error, ReasonT} ->
+ [?XRES(<<(?T(<<"Error">>))/binary, ": ",
+ ReasonT/binary>>)];
+ nothing -> []
+ end
+ ++
+ [?XAE(<<"form">>,
+ [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
+ [node_ports_to_xhtml(NewPorts, Lang)])];
+get_node(Host, Node, [<<"modules">>], Query, Lang)
+ when is_binary(Host) ->
+ Modules = rpc:call(Node, gen_mod,
+ loaded_modules_with_opts, [Host]),
+ Res = case catch node_modules_parse_query(Host, Node,
+ Modules, Query)
+ of
+ submitted -> ok;
+ {'EXIT', Reason} -> ?INFO_MSG("~p~n", [Reason]), error;
+ _ -> nothing
end,
- NewModules = lists:sort(
- rpc:call(Node, gen_mod, loaded_modules_with_opts, [Host])),
- H1String = ?T("Modules at ") ++ atom_to_list(Node),
- ?H1GL(H1String, "modoverview", "Modules Overview") ++
- case Res of
- ok -> [?XREST("Submitted")];
- error -> [?XREST("Bad format")];
- nothing -> []
- end ++
- [?XAE("form", [{"action", ""}, {"method", "post"}],
- [node_modules_to_xhtml(NewModules, Lang)])
- ];
-
-get_node(global, Node, ["stats"], _Query, Lang) ->
- UpTime = rpc:call(Node, erlang, statistics, [wall_clock]),
- UpTimeS = io_lib:format("~.3f", [element(1, UpTime)/1000]),
+ NewModules = lists:sort(rpc:call(Node, gen_mod,
+ loaded_modules_with_opts, [Host])),
+ H1String = list_to_binary(io_lib:format(?T(<<"Modules at ~p">>), [Node])),
+ (?H1GL(H1String, <<"modoverview">>,
+ <<"Modules Overview">>))
+ ++
+ case Res of
+ ok -> [?XREST(<<"Submitted">>)];
+ error -> [?XREST(<<"Bad format">>)];
+ nothing -> []
+ end
+ ++
+ [?XAE(<<"form">>,
+ [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
+ [node_modules_to_xhtml(NewModules, Lang)])];
+get_node(global, Node, [<<"stats">>], _Query, Lang) ->
+ UpTime = rpc:call(Node, erlang, statistics,
+ [wall_clock]),
+ UpTimeS = list_to_binary(io_lib:format("~.3f",
+ [element(1, UpTime) / 1000])),
CPUTime = rpc:call(Node, erlang, statistics, [runtime]),
- CPUTimeS = io_lib:format("~.3f", [element(1, CPUTime)/1000]),
+ CPUTimeS = list_to_binary(io_lib:format("~.3f",
+ [element(1, CPUTime) / 1000])),
OnlineUsers = mnesia:table_info(session, size),
- TransactionsCommitted =
- rpc:call(Node, mnesia, system_info, [transaction_commits]),
- TransactionsAborted =
- rpc:call(Node, mnesia, system_info, [transaction_failures]),
- TransactionsRestarted =
- rpc:call(Node, mnesia, system_info, [transaction_restarts]),
- TransactionsLogged =
- rpc:call(Node, mnesia, system_info, [transaction_log_writes]),
-
- [?XC("h1", io_lib:format(?T("Statistics of ~p"), [Node])),
- ?XAE("table", [],
- [?XE("tbody",
- [?XE("tr", [?XCT("td", "Uptime:"),
- ?XAC("td", [{"class", "alignright"}],
- UpTimeS)]),
- ?XE("tr", [?XCT("td", "CPU Time:"),
- ?XAC("td", [{"class", "alignright"}],
- CPUTimeS)]),
- ?XE("tr", [?XCT("td", "Online Users:"),
- ?XAC("td", [{"class", "alignright"}],
- pretty_string_int(OnlineUsers))]),
- ?XE("tr", [?XCT("td", "Transactions Committed:"),
- ?XAC("td", [{"class", "alignright"}],
- pretty_string_int(TransactionsCommitted))]),
- ?XE("tr", [?XCT("td", "Transactions Aborted:"),
- ?XAC("td", [{"class", "alignright"}],
- pretty_string_int(TransactionsAborted))]),
- ?XE("tr", [?XCT("td", "Transactions Restarted:"),
- ?XAC("td", [{"class", "alignright"}],
- pretty_string_int(TransactionsRestarted))]),
- ?XE("tr", [?XCT("td", "Transactions Logged:"),
- ?XAC("td", [{"class", "alignright"}],
- pretty_string_int(TransactionsLogged))])
- ])
- ])];
-
-get_node(global, Node, ["update"], Query, Lang) ->
+ TransactionsCommitted = rpc:call(Node, mnesia,
+ system_info, [transaction_commits]),
+ TransactionsAborted = rpc:call(Node, mnesia,
+ system_info, [transaction_failures]),
+ TransactionsRestarted = rpc:call(Node, mnesia,
+ system_info, [transaction_restarts]),
+ TransactionsLogged = rpc:call(Node, mnesia, system_info,
+ [transaction_log_writes]),
+ [?XC(<<"h1">>,
+ list_to_binary(io_lib:format(?T(<<"Statistics of ~p">>), [Node]))),
+ ?XAE(<<"table">>, [],
+ [?XE(<<"tbody">>,
+ [?XE(<<"tr">>,
+ [?XCT(<<"td">>, <<"Uptime:">>),
+ ?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}],
+ UpTimeS)]),
+ ?XE(<<"tr">>,
+ [?XCT(<<"td">>, <<"CPU Time:">>),
+ ?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}],
+ CPUTimeS)]),
+ ?XE(<<"tr">>,
+ [?XCT(<<"td">>, <<"Online Users:">>),
+ ?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}],
+ (pretty_string_int(OnlineUsers)))]),
+ ?XE(<<"tr">>,
+ [?XCT(<<"td">>, <<"Transactions Committed:">>),
+ ?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}],
+ (pretty_string_int(TransactionsCommitted)))]),
+ ?XE(<<"tr">>,
+ [?XCT(<<"td">>, <<"Transactions Aborted:">>),
+ ?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}],
+ (pretty_string_int(TransactionsAborted)))]),
+ ?XE(<<"tr">>,
+ [?XCT(<<"td">>, <<"Transactions Restarted:">>),
+ ?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}],
+ (pretty_string_int(TransactionsRestarted)))]),
+ ?XE(<<"tr">>,
+ [?XCT(<<"td">>, <<"Transactions Logged:">>),
+ ?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}],
+ (pretty_string_int(TransactionsLogged)))])])])];
+get_node(global, Node, [<<"update">>], Query, Lang) ->
rpc:call(Node, code, purge, [ejabberd_update]),
Res = node_update_parse_query(Node, Query),
rpc:call(Node, code, load_file, [ejabberd_update]),
- {ok, _Dir, UpdatedBeams, Script, LowLevelScript, Check} =
+ {ok, _Dir, UpdatedBeams, Script, LowLevelScript,
+ Check} =
rpc:call(Node, ejabberd_update, update_info, []),
- Mods =
- case UpdatedBeams of
- [] ->
- ?CT("None");
- _ ->
- BeamsLis =
- lists:map(
- fun(Beam) ->
- BeamString = atom_to_list(Beam),
- ?LI([
- ?INPUT("checkbox", "selected", BeamString),
- %%?XA("input", [{"checked", ""}, %% Selected by default
- %% {"type", "checkbox"},
- %% {"name", "selected"},
- %% {"value", BeamString}]),
- ?C(BeamString)])
- end,
- UpdatedBeams),
- SelectButtons =
- [?BR,
- ?INPUTATTRS("button", "selectall", "Select All",
- [{"onClick", "selectAll()"}]),
- ?C(" "),
- ?INPUTATTRS("button", "unselectall", "Unselect All",
- [{"onClick", "unSelectAll()"}])],
- %%?XE("ul", BeamsLis)
- ?XAE("ul", [{"class", "nolistyle"}], BeamsLis ++ SelectButtons)
- end,
- FmtScript = ?XC("pre", io_lib:format("~p", [Script])),
- FmtLowLevelScript = ?XC("pre", io_lib:format("~p", [LowLevelScript])),
- [?XC("h1", ?T("Update ") ++ atom_to_list(Node))] ++
- case Res of
- ok -> [?XREST("Submitted")];
- {error, ErrorText} -> [?XREST("Error: " ++ ErrorText)];
- nothing -> []
- end ++
- [?XAE("form", [{"action", ""}, {"method", "post"}],
- [
- ?XCT("h2", "Update plan"),
- ?XCT("h3", "Modified modules"), Mods,
- ?XCT("h3", "Update script"), FmtScript,
- ?XCT("h3", "Low level update script"), FmtLowLevelScript,
- ?XCT("h3", "Script check"), ?XC("pre", atom_to_list(Check)),
+ Mods = case UpdatedBeams of
+ [] -> ?CT(<<"None">>);
+ _ ->
+ BeamsLis = lists:map(fun (Beam) ->
+ BeamString =
+ iolist_to_binary(atom_to_list(Beam)),
+ ?LI([?INPUT(<<"checkbox">>,
+ <<"selected">>,
+ BeamString),
+ ?C(BeamString)])
+ end,
+ UpdatedBeams),
+ SelectButtons = [?BR,
+ ?INPUTATTRS(<<"button">>, <<"selectall">>,
+ <<"Select All">>,
+ [{<<"onClick">>,
+ <<"selectAll()">>}]),
+ ?C(<<" ">>),
+ ?INPUTATTRS(<<"button">>, <<"unselectall">>,
+ <<"Unselect All">>,
+ [{<<"onClick">>,
+ <<"unSelectAll()">>}])],
+ ?XAE(<<"ul">>, [{<<"class">>, <<"nolistyle">>}],
+ (BeamsLis ++ SelectButtons))
+ end,
+ FmtScript = (?XC(<<"pre">>,
+ list_to_binary(io_lib:format("~p", [Script])))),
+ FmtLowLevelScript = (?XC(<<"pre">>,
+ list_to_binary(io_lib:format("~p", [LowLevelScript])))),
+ [?XC(<<"h1">>,
+ list_to_binary(io_lib:format(?T(<<"Update ~p">>), [Node])))]
+ ++
+ case Res of
+ ok -> [?XREST(<<"Submitted">>)];
+ {error, ErrorText} ->
+ [?XREST(<<"Error: ", ErrorText/binary>>)];
+ nothing -> []
+ end
+ ++
+ [?XAE(<<"form">>,
+ [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
+ [?XCT(<<"h2">>, <<"Update plan">>),
+ ?XCT(<<"h3">>, <<"Modified modules">>), Mods,
+ ?XCT(<<"h3">>, <<"Update script">>), FmtScript,
+ ?XCT(<<"h3">>, <<"Low level update script">>),
+ FmtLowLevelScript, ?XCT(<<"h3">>, <<"Script check">>),
+ ?XC(<<"pre">>, (iolist_to_binary(Check))),
?BR,
- ?INPUTT("submit", "update", "Update")
- ])
- ];
-
+ ?INPUTT(<<"submit">>, <<"update">>, <<"Update">>)])];
get_node(Host, Node, NPath, Query, Lang) ->
{Hook, Opts} = case Host of
- global -> {webadmin_page_node, [Node, NPath, Query, Lang]};
- Host -> {webadmin_page_hostnode, [Host, Node, NPath, Query, Lang]}
+ global ->
+ {webadmin_page_node, [Node, NPath, Query, Lang]};
+ Host ->
+ {webadmin_page_hostnode,
+ [Host, Node, NPath, Query, Lang]}
end,
case ejabberd_hooks:run_fold(Hook, Host, [], Opts) of
- [] -> [?XC("h1", "Not Found")];
- Res -> Res
+ [] -> [?XC(<<"h1">>, <<"Not Found">>)];
+ Res -> Res
end.
%%%==================================
%%%% node parse
node_parse_query(Node, Query) ->
- case lists:keysearch("restart", 1, Query) of
- {value, _} ->
- case rpc:call(Node, init, restart, []) of
- {badrpc, _Reason} ->
- error;
- _ ->
- ok
- end;
- _ ->
- case lists:keysearch("stop", 1, Query) of
- {value, _} ->
- case rpc:call(Node, init, stop, []) of
- {badrpc, _Reason} ->
- error;
- _ ->
- ok
- end;
- _ ->
- nothing
- end
+ case lists:keysearch(<<"restart">>, 1, Query) of
+ {value, _} ->
+ case rpc:call(Node, init, restart, []) of
+ {badrpc, _Reason} -> error;
+ _ -> ok
+ end;
+ _ ->
+ case lists:keysearch(<<"stop">>, 1, Query) of
+ {value, _} ->
+ case rpc:call(Node, init, stop, []) of
+ {badrpc, _Reason} -> error;
+ _ -> ok
+ end;
+ _ -> nothing
+ end
end.
-
db_storage_select(ID, Opt, Lang) ->
- ?XAE("select", [{"name", "table" ++ ID}],
- lists:map(
- fun({O, Desc}) ->
- Sel = if
- O == Opt -> [{"selected", "selected"}];
- true -> []
- end,
- ?XACT("option",
- Sel ++ [{"value", atom_to_list(O)}],
- Desc)
- end, [{ram_copies, "RAM copy"},
- {disc_copies, "RAM and disc copy"},
- {disc_only_copies, "Disc only copy"},
- {unknown, "Remote copy"},
- {delete_content, "Delete content"},
- {delete_table, "Delete table"}])).
-
-node_db_parse_query(_Node, _Tables, [{nokey,[]}]) ->
+ ?XAE(<<"select">>,
+ [{<<"name">>, <<"table", ID/binary>>}],
+ (lists:map(fun ({O, Desc}) ->
+ Sel = if O == Opt ->
+ [{<<"selected">>, <<"selected">>}];
+ true -> []
+ end,
+ ?XACT(<<"option">>,
+ (Sel ++
+ [{<<"value">>,
+ iolist_to_binary(atom_to_list(O))}]),
+ Desc)
+ end,
+ [{ram_copies, <<"RAM copy">>},
+ {disc_copies, <<"RAM and disc copy">>},
+ {disc_only_copies, <<"Disc only copy">>},
+ {unknown, <<"Remote copy">>},
+ {delete_content, <<"Delete content">>},
+ {delete_table, <<"Delete table">>}]))).
+
+node_db_parse_query(_Node, _Tables, [{nokey, <<>>}]) ->
nothing;
node_db_parse_query(Node, Tables, Query) ->
- lists:foreach(
- fun(Table) ->
- STable = atom_to_list(Table),
- case lists:keysearch("table" ++ STable, 1, Query) of
- {value, {_, SType}} ->
- Type = case SType of
- "unknown" -> unknown;
- "ram_copies" -> ram_copies;
- "disc_copies" -> disc_copies;
- "disc_only_copies" -> disc_only_copies;
- "delete_content" -> delete_content;
- "delete_table" -> delete_table;
- _ -> false
- end,
- if
- Type == false ->
- ok;
- Type == delete_content ->
- mnesia:clear_table(Table);
- Type == delete_table ->
- mnesia:delete_table(Table);
- Type == unknown ->
- mnesia:del_table_copy(Table, Node);
- true ->
- case mnesia:add_table_copy(Table, Node, Type) of
- {aborted, _} ->
- mnesia:change_table_copy_type(
- Table, Node, Type);
- _ ->
- ok
- end
- end;
- _ ->
- ok
- end
- end, Tables),
+ lists:foreach(fun (Table) ->
+ STable = iolist_to_binary(atom_to_list(Table)),
+ case lists:keysearch(<<"table", STable/binary>>, 1,
+ Query)
+ of
+ {value, {_, SType}} ->
+ Type = case SType of
+ <<"unknown">> -> unknown;
+ <<"ram_copies">> -> ram_copies;
+ <<"disc_copies">> -> disc_copies;
+ <<"disc_only_copies">> ->
+ disc_only_copies;
+ <<"delete_content">> -> delete_content;
+ <<"delete_table">> -> delete_table;
+ _ -> false
+ end,
+ if Type == false -> ok;
+ Type == delete_content ->
+ mnesia:clear_table(Table);
+ Type == delete_table ->
+ mnesia:delete_table(Table);
+ Type == unknown ->
+ mnesia:del_table_copy(Table, Node);
+ true ->
+ case mnesia:add_table_copy(Table, Node,
+ Type)
+ of
+ {aborted, _} ->
+ mnesia:change_table_copy_type(Table,
+ Node,
+ Type);
+ _ -> ok
+ end
+ end;
+ _ -> ok
+ end
+ end,
+ Tables),
ok.
-node_backup_parse_query(_Node, [{nokey,[]}]) ->
+node_backup_parse_query(_Node, [{nokey, <<>>}]) ->
nothing;
node_backup_parse_query(Node, Query) ->
- lists:foldl(
- fun(Action, nothing) ->
- case lists:keysearch(Action, 1, Query) of
- {value, _} ->
- case lists:keysearch(Action ++ "path", 1, Query) of
- {value, {_, Path}} ->
- Res =
- case Action of
- "store" ->
- rpc:call(Node, mnesia,
- backup, [Path]);
- "restore" ->
- rpc:call(Node, ejabberd_admin,
- restore, [Path]);
- "fallback" ->
- rpc:call(Node, mnesia,
- install_fallback, [Path]);
- "dump" ->
- rpc:call(Node, ejabberd_admin,
- dump_to_textfile, [Path]);
- "load" ->
- rpc:call(Node, mnesia,
- load_textfile, [Path]);
- "import_piefxis_file" ->
- rpc:call(Node, ejabberd_piefxis,
- import_file, [Path]);
- "export_piefxis_dir" ->
- rpc:call(Node, ejabberd_piefxis,
- export_server, [Path]);
- "export_piefxis_host_dir" ->
- {value, {_, Host}} = lists:keysearch(Action ++ "host", 1, Query),
- rpc:call(Node, ejabberd_piefxis,
- export_host, [Path, Host]);
- "import_file" ->
- rpc:call(Node, ejabberd_admin,
- import_file, [Path]);
- "import_dir" ->
- rpc:call(Node, ejabberd_admin,
- import_dir, [Path])
- end,
- case Res of
- {error, Reason} ->
- {error, Reason};
- {badrpc, Reason} ->
- {badrpc, Reason};
- _ ->
- ok
+ lists:foldl(fun (Action, nothing) ->
+ case lists:keysearch(Action, 1, Query) of
+ {value, _} ->
+ case lists:keysearch(<<Action/binary, "path">>, 1,
+ Query)
+ of
+ {value, {_, Path}} ->
+ Res = case Action of
+ <<"store">> ->
+ rpc:call(Node, mnesia, backup,
+ [binary_to_list(Path)]);
+ <<"restore">> ->
+ rpc:call(Node, ejabberd_admin,
+ restore, [Path]);
+ <<"fallback">> ->
+ rpc:call(Node, mnesia,
+ install_fallback,
+ [binary_to_list(Path)]);
+ <<"dump">> ->
+ rpc:call(Node, ejabberd_admin,
+ dump_to_textfile,
+ [Path]);
+ <<"load">> ->
+ rpc:call(Node, mnesia,
+ load_textfile,
+ [binary_to_list(Path)]);
+ <<"import_piefxis_file">> ->
+ rpc:call(Node, ejabberd_piefxis,
+ import_file, [Path]);
+ <<"export_piefxis_dir">> ->
+ rpc:call(Node, ejabberd_piefxis,
+ export_server, [Path]);
+ <<"export_piefxis_host_dir">> ->
+ {value, {_, Host}} =
+ lists:keysearch(<<Action/binary,
+ "host">>,
+ 1, Query),
+ rpc:call(Node, ejabberd_piefxis,
+ export_host,
+ [Path, Host]);
+ <<"export_sql_file">> ->
+ {value, {_, Host}} =
+ lists:keysearch(<<Action/binary,
+ "host">>,
+ 1, Query),
+ rpc:call(Node, ejd2odbc,
+ export, [Host, Path]);
+ <<"import_file">> ->
+ rpc:call(Node, ejabberd_admin,
+ import_file, [Path]);
+ <<"import_dir">> ->
+ rpc:call(Node, ejabberd_admin,
+ import_dir, [Path])
+ end,
+ case Res of
+ {error, Reason} -> {error, Reason};
+ {badrpc, Reason} -> {badrpc, Reason};
+ _ -> ok
+ end;
+ OtherError -> {error, OtherError}
end;
- OtherError ->
- {error, OtherError}
- end;
- _ ->
- nothing
- end;
- (_Action, Res) ->
- Res
- end, nothing, ["store", "restore", "fallback", "dump", "load", "import_file", "import_dir",
- "import_piefxis_file", "export_piefxis_dir", "export_piefxis_host_dir"]).
+ _ -> nothing
+ end;
+ (_Action, Res) -> Res
+ end,
+ nothing,
+ [<<"store">>, <<"restore">>, <<"fallback">>, <<"dump">>,
+ <<"load">>, <<"import_file">>, <<"import_dir">>,
+ <<"import_piefxis_file">>, <<"export_piefxis_dir">>,
+ <<"export_piefxis_host_dir">>, <<"export_sql_file">>]).
node_ports_to_xhtml(Ports, Lang) ->
- ?XAE("table", [{"class", "withtextareas"}],
- [?XE("thead",
- [?XE("tr",
- [?XCT("td", "Port"),
- ?XCT("td", "IP"),
- ?XCT("td", "Protocol"),
- ?XCT("td", "Module"),
- ?XCT("td", "Options")
- ])]),
- ?XE("tbody",
- lists:map(
- fun({PortIP, Module, Opts} = _E) ->
- {_Port, SPort, _TIP, SIP, SSPort, NetProt, OptsClean} =
- get_port_data(PortIP, Opts),
- SModule = atom_to_list(Module),
- {NumLines, SOptsClean} = term_to_paragraph(OptsClean, 40),
- %%ID = term_to_id(E),
- ?XE("tr",
- [?XAE("td", [{"size", "6"}], [?C(SPort)]),
- ?XAE("td", [{"size", "15"}], [?C(SIP)]),
- ?XAE("td", [{"size", "4"}], [?C(atom_to_list(NetProt))]),
- ?XE("td", [?INPUTS("text", "module" ++ SSPort,
- SModule, "15")]),
- ?XE("td", [?TEXTAREA("opts" ++ SSPort, integer_to_list(NumLines), "35", SOptsClean)]),
- ?XE("td", [?INPUTT("submit", "add" ++ SSPort,
- "Update")]),
- ?XE("td", [?INPUTT("submit", "delete" ++ SSPort,
- "Delete")])
- ]
- )
- end, Ports) ++
- [?XE("tr",
- [?XE("td", [?INPUTS("text", "portnew", "", "6")]),
- ?XE("td", [?INPUTS("text", "ipnew", "0.0.0.0", "15")]),
- ?XE("td", [make_netprot_html("tcp")]),
- ?XE("td", [?INPUTS("text", "modulenew", "", "15")]),
- ?XE("td", [?TEXTAREA("optsnew", "2", "35", "[]")]),
- ?XAE("td", [{"colspan", "2"}],
- [?INPUTT("submit", "addnew", "Add New")])
- ]
- )]
- )]).
+ ?XAE(<<"table">>, [{<<"class">>, <<"withtextareas">>}],
+ [?XE(<<"thead">>,
+ [?XE(<<"tr">>,
+ [?XCT(<<"td">>, <<"Port">>), ?XCT(<<"td">>, <<"IP">>),
+ ?XCT(<<"td">>, <<"Protocol">>),
+ ?XCT(<<"td">>, <<"Module">>),
+ ?XCT(<<"td">>, <<"Options">>)])]),
+ ?XE(<<"tbody">>,
+ (lists:map(fun ({PortIP, Module, Opts} = _E) ->
+ {_Port, SPort, _TIP, SIP, SSPort, NetProt,
+ OptsClean} =
+ get_port_data(PortIP, Opts),
+ SModule =
+ iolist_to_binary(atom_to_list(Module)),
+ {NumLines, SOptsClean} =
+ term_to_paragraph(OptsClean, 40),
+ ?XE(<<"tr">>,
+ [?XAE(<<"td">>, [{<<"size">>, <<"6">>}],
+ [?C(SPort)]),
+ ?XAE(<<"td">>, [{<<"size">>, <<"15">>}],
+ [?C(SIP)]),
+ ?XAE(<<"td">>, [{<<"size">>, <<"4">>}],
+ [?C((iolist_to_binary(atom_to_list(NetProt))))]),
+ ?XE(<<"td">>,
+ [?INPUTS(<<"text">>,
+ <<"module", SSPort/binary>>,
+ SModule, <<"15">>)]),
+ ?XE(<<"td">>,
+ [?TEXTAREA(<<"opts", SSPort/binary>>,
+ (iolist_to_binary(integer_to_list(NumLines))),
+ <<"35">>, SOptsClean)]),
+ ?XE(<<"td">>,
+ [?INPUTT(<<"submit">>,
+ <<"add", SSPort/binary>>,
+ <<"Update">>)]),
+ ?XE(<<"td">>,
+ [?INPUTT(<<"submit">>,
+ <<"delete", SSPort/binary>>,
+ <<"Delete">>)])])
+ end,
+ Ports)
+ ++
+ [?XE(<<"tr">>,
+ [?XE(<<"td">>,
+ [?INPUTS(<<"text">>, <<"portnew">>, <<"">>,
+ <<"6">>)]),
+ ?XE(<<"td">>,
+ [?INPUTS(<<"text">>, <<"ipnew">>, <<"0.0.0.0">>,
+ <<"15">>)]),
+ ?XE(<<"td">>, [make_netprot_html(<<"tcp">>)]),
+ ?XE(<<"td">>,
+ [?INPUTS(<<"text">>, <<"modulenew">>, <<"">>,
+ <<"15">>)]),
+ ?XE(<<"td">>,
+ [?TEXTAREA(<<"optsnew">>, <<"2">>, <<"35">>,
+ <<"[]">>)]),
+ ?XAE(<<"td">>, [{<<"colspan">>, <<"2">>}],
+ [?INPUTT(<<"submit">>, <<"addnew">>,
+ <<"Add New">>)])])]))]).
make_netprot_html(NetProt) ->
- ?XAE("select", [{"name", "netprotnew"}],
- lists:map(
- fun(O) ->
- Sel = if
- O == NetProt -> [{"selected", "selected"}];
- true -> []
- end,
- ?XAC("option",
- Sel ++ [{"value", O}],
- O)
- end, ["tcp", "udp"])).
+ ?XAE(<<"select">>, [{<<"name">>, <<"netprotnew">>}],
+ (lists:map(fun (O) ->
+ Sel = if O == NetProt ->
+ [{<<"selected">>, <<"selected">>}];
+ true -> []
+ end,
+ ?XAC(<<"option">>, (Sel ++ [{<<"value">>, O}]), O)
+ end,
+ [<<"tcp">>, <<"udp">>]))).
get_port_data(PortIP, Opts) ->
- {Port, IPT, IPS, _IPV, NetProt, OptsClean} = ejabberd_listener:parse_listener_portip(PortIP, Opts),
- SPort = io_lib:format("~p", [Port]),
-
- SSPort = lists:flatten(
- lists:map(
- fun(N) -> io_lib:format("~.16b", [N]) end,
- binary_to_list(crypto:md5(SPort++IPS++atom_to_list(NetProt))))),
+ {Port, IPT, IPS, _IPV, NetProt, OptsClean} =
+ ejabberd_listener:parse_listener_portip(PortIP, Opts),
+ SPort = jlib:integer_to_binary(Port),
+ SSPort = list_to_binary(
+ lists:map(fun (N) ->
+ io_lib:format("~.16b", [N])
+ end,
+ binary_to_list(
+ crypto:md5(
+ [SPort, IPS, atom_to_list(NetProt)])))),
{Port, SPort, IPT, IPS, SSPort, NetProt, OptsClean}.
-
node_ports_parse_query(Node, Ports, Query) ->
- lists:foreach(
- fun({PortIpNetp, Module1, Opts1}) ->
- {Port, _SPort, TIP, _SIP, SSPort, NetProt, _OptsClean} =
- get_port_data(PortIpNetp, Opts1),
- case lists:keysearch("add" ++ SSPort, 1, Query) of
- {value, _} ->
- PortIpNetp2 = {Port, TIP, NetProt},
- {{value, {_, SModule}}, {value, {_, SOpts}}} =
- {lists:keysearch("module" ++ SSPort, 1, Query),
- lists:keysearch("opts" ++ SSPort, 1, Query)},
- Module = list_to_atom(SModule),
- {ok, Tokens, _} = erl_scan:string(SOpts ++ "."),
- {ok, Opts} = erl_parse:parse_term(Tokens),
- rpc:call(Node, ejabberd_listener, delete_listener,
- [PortIpNetp2, Module1]),
- R=rpc:call(Node, ejabberd_listener, add_listener,
- [PortIpNetp2, Module, Opts]),
- throw({is_added, R});
- _ ->
- case lists:keysearch("delete" ++ SSPort, 1, Query) of
- {value, _} ->
- rpc:call(Node, ejabberd_listener, delete_listener,
- [PortIpNetp, Module1]),
- throw(submitted);
- _ ->
- ok
- end
- end
- end, Ports),
- case lists:keysearch("addnew", 1, Query) of
- {value, _} ->
- {{value, {_, SPort}},
- {value, {_, STIP}}, %% It is a string that may represent a tuple
- {value, {_, SNetProt}},
- {value, {_, SModule}},
- {value, {_, SOpts}}} =
- {lists:keysearch("portnew", 1, Query),
- lists:keysearch("ipnew", 1, Query),
- lists:keysearch("netprotnew", 1, Query),
- lists:keysearch("modulenew", 1, Query),
- lists:keysearch("optsnew", 1, Query)},
- {ok, Toks, _} = erl_scan:string(SPort ++ "."),
- {ok, Port2} = erl_parse:parse_term(Toks),
- {ok, ToksIP, _} = erl_scan:string(STIP ++ "."),
- STIP2 = case erl_parse:parse_term(ToksIP) of
- {ok, IPTParsed} -> IPTParsed;
- {error, _} -> STIP
- end,
- Module = list_to_atom(SModule),
- NetProt2 = list_to_atom(SNetProt),
- {ok, Tokens, _} = erl_scan:string(SOpts ++ "."),
- {ok, Opts} = erl_parse:parse_term(Tokens),
- {Port2, _SPort, IP2, _SIP, _SSPort, NetProt2, OptsClean} =
- get_port_data({Port2, STIP2, NetProt2}, Opts),
- R=rpc:call(Node, ejabberd_listener, add_listener,
+ lists:foreach(fun ({PortIpNetp, Module1, Opts1}) ->
+ {Port, _SPort, TIP, _SIP, SSPort, NetProt,
+ _OptsClean} =
+ get_port_data(PortIpNetp, Opts1),
+ case lists:keysearch(<<"add", SSPort/binary>>, 1,
+ Query)
+ of
+ {value, _} ->
+ PortIpNetp2 = {Port, TIP, NetProt},
+ {{value, {_, SModule}}, {value, {_, SOpts}}} =
+ {lists:keysearch(<<"module",
+ SSPort/binary>>,
+ 1, Query),
+ lists:keysearch(<<"opts", SSPort/binary>>,
+ 1, Query)},
+ Module = jlib:binary_to_atom(SModule),
+ {ok, Tokens, _} =
+ erl_scan:string(binary_to_list(SOpts) ++ "."),
+ {ok, Opts} = erl_parse:parse_term(Tokens),
+ rpc:call(Node, ejabberd_listener,
+ delete_listener,
+ [PortIpNetp2, Module1]),
+ R = rpc:call(Node, ejabberd_listener,
+ add_listener,
+ [PortIpNetp2, Module, Opts]),
+ throw({is_added, R});
+ _ ->
+ case lists:keysearch(<<"delete",
+ SSPort/binary>>,
+ 1, Query)
+ of
+ {value, _} ->
+ rpc:call(Node, ejabberd_listener,
+ delete_listener,
+ [PortIpNetp, Module1]),
+ throw(submitted);
+ _ -> ok
+ end
+ end
+ end,
+ Ports),
+ case lists:keysearch(<<"addnew">>, 1, Query) of
+ {value, _} ->
+ {{value, {_, SPort}}, {value, {_, STIP}},
+ {value, {_, SNetProt}}, {value, {_, SModule}},
+ {value, {_, SOpts}}} =
+ {lists:keysearch(<<"portnew">>, 1, Query),
+ lists:keysearch(<<"ipnew">>, 1, Query),
+ lists:keysearch(<<"netprotnew">>, 1, Query),
+ lists:keysearch(<<"modulenew">>, 1, Query),
+ lists:keysearch(<<"optsnew">>, 1, Query)},
+ {ok, Toks, _} = erl_scan:string(binary_to_list(<<SPort/binary, ".">>)),
+ {ok, Port2} = erl_parse:parse_term(Toks),
+ {ok, ToksIP, _} = erl_scan:string(binary_to_list(<<STIP/binary, ".">>)),
+ STIP2 = case erl_parse:parse_term(ToksIP) of
+ {ok, IPTParsed} -> IPTParsed;
+ {error, _} -> STIP
+ end,
+ Module = jlib:binary_to_atom(SModule),
+ NetProt2 = jlib:binary_to_atom(SNetProt),
+ {ok, Tokens, _} = erl_scan:string(binary_to_list(<<SOpts/binary, ".">>)),
+ {ok, Opts} = erl_parse:parse_term(Tokens),
+ {Port2, _SPort, IP2, _SIP, _SSPort, NetProt2,
+ OptsClean} =
+ get_port_data({Port2, STIP2, NetProt2}, Opts),
+ R = rpc:call(Node, ejabberd_listener, add_listener,
[{Port2, IP2, NetProt2}, Module, OptsClean]),
- throw({is_added, R});
- _ ->
- ok
+ throw({is_added, R});
+ _ -> ok
end.
node_modules_to_xhtml(Modules, Lang) ->
- ?XAE("table", [{"class", "withtextareas"}],
- [?XE("thead",
- [?XE("tr",
- [?XCT("td", "Module"),
- ?XCT("td", "Options")
- ])]),
- ?XE("tbody",
- lists:map(
- fun({Module, Opts} = _E) ->
- SModule = atom_to_list(Module),
- {NumLines, SOpts} = term_to_paragraph(Opts, 40),
- %%ID = term_to_id(E),
- ?XE("tr",
- [?XC("td", SModule),
- ?XE("td", [?TEXTAREA("opts" ++ SModule, integer_to_list(NumLines), "40", SOpts)]),
- ?XE("td", [?INPUTT("submit", "restart" ++ SModule,
- "Restart")]),
- ?XE("td", [?INPUTT("submit", "stop" ++ SModule,
- "Stop")])
- ]
- )
- end, Modules) ++
- [?XE("tr",
- [?XE("td", [?INPUT("text", "modulenew", "")]),
- ?XE("td", [?TEXTAREA("optsnew", "2", "40", "[]")]),
- ?XAE("td", [{"colspan", "2"}],
- [?INPUTT("submit", "start", "Start")])
- ]
- )]
- )]).
+ ?XAE(<<"table">>, [{<<"class">>, <<"withtextareas">>}],
+ [?XE(<<"thead">>,
+ [?XE(<<"tr">>,
+ [?XCT(<<"td">>, <<"Module">>),
+ ?XCT(<<"td">>, <<"Options">>)])]),
+ ?XE(<<"tbody">>,
+ (lists:map(fun ({Module, Opts} = _E) ->
+ SModule =
+ iolist_to_binary(atom_to_list(Module)),
+ {NumLines, SOpts} = term_to_paragraph(Opts,
+ 40),
+ ?XE(<<"tr">>,
+ [?XC(<<"td">>, SModule),
+ ?XE(<<"td">>,
+ [?TEXTAREA(<<"opts", SModule/binary>>,
+ (iolist_to_binary(integer_to_list(NumLines))),
+ <<"40">>, SOpts)]),
+ ?XE(<<"td">>,
+ [?INPUTT(<<"submit">>,
+ <<"restart",
+ SModule/binary>>,
+ <<"Restart">>)]),
+ ?XE(<<"td">>,
+ [?INPUTT(<<"submit">>,
+ <<"stop", SModule/binary>>,
+ <<"Stop">>)])])
+ end,
+ Modules)
+ ++
+ [?XE(<<"tr">>,
+ [?XE(<<"td">>,
+ [?INPUT(<<"text">>, <<"modulenew">>, <<"">>)]),
+ ?XE(<<"td">>,
+ [?TEXTAREA(<<"optsnew">>, <<"2">>, <<"40">>,
+ <<"[]">>)]),
+ ?XAE(<<"td">>, [{<<"colspan">>, <<"2">>}],
+ [?INPUTT(<<"submit">>, <<"start">>,
+ <<"Start">>)])])]))]).
node_modules_parse_query(Host, Node, Modules, Query) ->
- lists:foreach(
- fun({Module, _Opts1}) ->
- SModule = atom_to_list(Module),
- case lists:keysearch("restart" ++ SModule, 1, Query) of
- {value, _} ->
- {value, {_, SOpts}} =
- lists:keysearch("opts" ++ SModule, 1, Query),
- {ok, Tokens, _} = erl_scan:string(SOpts ++ "."),
- {ok, Opts} = erl_parse:parse_term(Tokens),
- rpc:call(Node, gen_mod, stop_module, [Host, Module]),
- rpc:call(Node, gen_mod, start_module, [Host, Module, Opts]),
- throw(submitted);
- _ ->
- case lists:keysearch("stop" ++ SModule, 1, Query) of
- {value, _} ->
- rpc:call(Node, gen_mod, stop_module, [Host, Module]),
- throw(submitted);
- _ ->
- ok
- end
- end
- end, Modules),
- case lists:keysearch("start", 1, Query) of
- {value, _} ->
- {{value, {_, SModule}},
- {value, {_, SOpts}}} =
- {lists:keysearch("modulenew", 1, Query),
- lists:keysearch("optsnew", 1, Query)},
- Module = list_to_atom(SModule),
- {ok, Tokens, _} = erl_scan:string(SOpts ++ "."),
- {ok, Opts} = erl_parse:parse_term(Tokens),
- rpc:call(Node, gen_mod, start_module, [Host, Module, Opts]),
- throw(submitted);
- _ ->
- ok
+ lists:foreach(fun ({Module, _Opts1}) ->
+ SModule = iolist_to_binary(atom_to_list(Module)),
+ case lists:keysearch(<<"restart", SModule/binary>>, 1,
+ Query)
+ of
+ {value, _} ->
+ {value, {_, SOpts}} = lists:keysearch(<<"opts",
+ SModule/binary>>,
+ 1, Query),
+ {ok, Tokens, _} =
+ erl_scan:string(binary_to_list(<<SOpts/binary, ".">>)),
+ {ok, Opts} = erl_parse:parse_term(Tokens),
+ rpc:call(Node, gen_mod, stop_module,
+ [Host, Module]),
+ rpc:call(Node, gen_mod, start_module,
+ [Host, Module, Opts]),
+ throw(submitted);
+ _ ->
+ case lists:keysearch(<<"stop", SModule/binary>>,
+ 1, Query)
+ of
+ {value, _} ->
+ rpc:call(Node, gen_mod, stop_module,
+ [Host, Module]),
+ throw(submitted);
+ _ -> ok
+ end
+ end
+ end,
+ Modules),
+ case lists:keysearch(<<"start">>, 1, Query) of
+ {value, _} ->
+ {{value, {_, SModule}}, {value, {_, SOpts}}} =
+ {lists:keysearch(<<"modulenew">>, 1, Query),
+ lists:keysearch(<<"optsnew">>, 1, Query)},
+ Module = jlib:binary_to_atom(SModule),
+ {ok, Tokens, _} = erl_scan:string(binary_to_list(<<SOpts/binary, ".">>)),
+ {ok, Opts} = erl_parse:parse_term(Tokens),
+ rpc:call(Node, gen_mod, start_module,
+ [Host, Module, Opts]),
+ throw(submitted);
+ _ -> ok
end.
-
node_update_parse_query(Node, Query) ->
- case lists:keysearch("update", 1, Query) of
- {value, _} ->
- ModulesToUpdateStrings = proplists:get_all_values("selected",Query),
- ModulesToUpdate = [list_to_atom(M) || M <- ModulesToUpdateStrings],
- case rpc:call(Node, ejabberd_update, update, [ModulesToUpdate]) of
- {ok, _} ->
- ok;
- {error, Error} ->
- ?ERROR_MSG("~p~n", [Error]),
- {error, io_lib:format("~p", [Error])};
- {badrpc, Error} ->
- ?ERROR_MSG("Bad RPC: ~p~n", [Error]),
- {error, "Bad RPC: " ++ io_lib:format("~p", [Error])}
- end;
- _ ->
- nothing
+ case lists:keysearch(<<"update">>, 1, Query) of
+ {value, _} ->
+ ModulesToUpdateStrings =
+ proplists:get_all_values(<<"selected">>, Query),
+ ModulesToUpdate = [jlib:binary_to_atom(M)
+ || M <- ModulesToUpdateStrings],
+ case rpc:call(Node, ejabberd_update, update,
+ [ModulesToUpdate])
+ of
+ {ok, _} -> ok;
+ {error, Error} ->
+ ?ERROR_MSG("~p~n", [Error]),
+ {error, iolist_to_binary(io_lib:format("~p", [Error]))};
+ {badrpc, Error} ->
+ ?ERROR_MSG("Bad RPC: ~p~n", [Error]),
+ {error,
+ <<"Bad RPC: ", (iolist_to_binary(io_lib:format("~p", [Error])))/binary>>}
+ end;
+ _ -> nothing
end.
-
pretty_print_xml(El) ->
- lists:flatten(pretty_print_xml(El, "")).
+ list_to_binary(pretty_print_xml(El, <<"">>)).
pretty_print_xml({xmlcdata, CData}, Prefix) ->
- [Prefix, CData, $\n];
-pretty_print_xml({xmlelement, Name, Attrs, Els}, Prefix) ->
+ IsBlankCData = lists:all(
+ fun($\f) -> true;
+ ($\r) -> true;
+ ($\n) -> true;
+ ($\t) -> true;
+ ($\v) -> true;
+ ($ ) -> true;
+ (_) -> false
+ end, binary_to_list(CData)),
+ if IsBlankCData ->
+ [];
+ true ->
+ [Prefix, CData, $\n]
+ end;
+pretty_print_xml(#xmlel{name = Name, attrs = Attrs,
+ children = Els},
+ Prefix) ->
[Prefix, $<, Name,
case Attrs of
- [] ->
- [];
- [{Attr, Val} | RestAttrs] ->
- AttrPrefix = [Prefix,
- string:copies(" ", length(Name) + 2)],
- [$\s, Attr, $=, $', xml:crypt(Val), $' |
- lists:map(fun({Attr1, Val1}) ->
- [$\n, AttrPrefix,
- Attr1, $=, $', xml:crypt(Val1), $']
- end, RestAttrs)]
+ [] -> [];
+ [{Attr, Val} | RestAttrs] ->
+ AttrPrefix = [Prefix,
+ str:copies(<<" ">>, byte_size(Name) + 2)],
+ [$\s, Attr, $=, $', xml:crypt(Val) | [$',
+ lists:map(fun ({Attr1,
+ Val1}) ->
+ [$\n,
+ AttrPrefix,
+ Attr1, $=,
+ $',
+ xml:crypt(Val1),
+ $']
+ end,
+ RestAttrs)]]
end,
- if
- Els == [] ->
- "/>\n";
- true ->
- OnlyCData = lists:all(fun({xmlcdata, _}) -> true;
- ({xmlelement, _, _, _}) -> false
- end, Els),
- if
- OnlyCData ->
- [$>,
- xml:get_cdata(Els),
- $<, $/, Name, $>, $\n
- ];
- true ->
- [$>, $\n,
- lists:map(fun(E) ->
- pretty_print_xml(E, [Prefix, " "])
- end, Els),
- Prefix, $<, $/, Name, $>, $\n
- ]
- end
+ if Els == [] -> <<"/>\n">>;
+ true ->
+ OnlyCData = lists:all(fun ({xmlcdata, _}) -> true;
+ (#xmlel{}) -> false
+ end,
+ Els),
+ if OnlyCData ->
+ [$>, xml:get_cdata(Els), $<, $/, Name, $>, $\n];
+ true ->
+ [$>, $\n,
+ lists:map(fun (E) ->
+ pretty_print_xml(E, [Prefix, <<" ">>])
+ end,
+ Els),
+ Prefix, $<, $/, Name, $>, $\n]
+ end
end].
-element_to_list(X) when is_atom(X) -> atom_to_list(X);
-element_to_list(X) when is_integer(X) -> integer_to_list(X).
+element_to_list(X) when is_atom(X) ->
+ iolist_to_binary(atom_to_list(X));
+element_to_list(X) when is_integer(X) ->
+ iolist_to_binary(integer_to_list(X)).
-list_to_element(List) ->
- {ok, Tokens, _} = erl_scan:string(List),
+list_to_element(Bin) ->
+ {ok, Tokens, _} = erl_scan:string(binary_to_list(Bin)),
[{_, _, Element}] = Tokens,
Element.
url_func({user_diapason, From, To}) ->
- integer_to_list(From) ++ "-" ++ integer_to_list(To) ++ "/";
+ <<(iolist_to_binary(integer_to_list(From)))/binary, "-",
+ (iolist_to_binary(integer_to_list(To)))/binary, "/">>;
url_func({users_queue, Prefix, User, _Server}) ->
- Prefix ++ "user/" ++ User ++ "/queue/";
+ <<Prefix/binary, "user/", User/binary, "/queue/">>;
url_func({user, Prefix, User, _Server}) ->
- Prefix ++ "user/" ++ User ++ "/".
+ <<Prefix/binary, "user/", User/binary, "/">>.
last_modified() ->
- {"Last-Modified", "Mon, 25 Feb 2008 13:23:30 GMT"}.
+ {<<"Last-Modified">>,
+ <<"Mon, 25 Feb 2008 13:23:30 GMT">>}.
+
cache_control_public() ->
- {"Cache-Control", "public"}.
+ {<<"Cache-Control">>, <<"public">>}.
%% Transform 1234567890 into "1,234,567,890"
pretty_string_int(Integer) when is_integer(Integer) ->
- pretty_string_int(integer_to_list(Integer));
-pretty_string_int(String) when is_list(String) ->
- {_, Result} = lists:foldl(
- fun(NewNumber, {3, Result}) ->
- {1, [NewNumber, $, | Result]};
- (NewNumber, {CountAcc, Result}) ->
- {CountAcc+1, [NewNumber | Result]}
- end,
- {0, ""},
- lists:reverse(String)),
+ pretty_string_int(iolist_to_binary(integer_to_list(Integer)));
+pretty_string_int(String) when is_binary(String) ->
+ {_, Result} = lists:foldl(fun (NewNumber, {3, Result}) ->
+ {1, <<NewNumber, $,, Result/binary>>};
+ (NewNumber, {CountAcc, Result}) ->
+ {CountAcc + 1, <<NewNumber, Result/binary>>}
+ end,
+ {0, <<"">>}, lists:reverse(binary_to_list(String))),
Result.
%%%==================================
@@ -2725,94 +2739,103 @@ make_navigation(Host, Node, Lang, JID) ->
%% URL = string()
%% Title = string()
make_navigation_menu(Host, Node, Lang, JID) ->
- HostNodeMenu = make_host_node_menu(Host, Node, Lang, JID),
- HostMenu = make_host_menu(Host, HostNodeMenu, Lang, JID),
+ HostNodeMenu = make_host_node_menu(Host, Node, Lang,
+ JID),
+ HostMenu = make_host_menu(Host, HostNodeMenu, Lang,
+ JID),
NodeMenu = make_node_menu(Host, Node, Lang),
make_server_menu(HostMenu, NodeMenu, Lang, JID).
%% @spec (Host, Node, Base, Lang) -> [LI]
make_menu_items(global, cluster, Base, Lang) ->
HookItems = get_menu_items_hook(server, Lang),
- make_menu_items(Lang, {Base, "", HookItems});
-
+ make_menu_items(Lang, {Base, <<"">>, HookItems});
make_menu_items(global, Node, Base, Lang) ->
HookItems = get_menu_items_hook({node, Node}, Lang),
- make_menu_items(Lang, {Base, "", HookItems});
-
+ make_menu_items(Lang, {Base, <<"">>, HookItems});
make_menu_items(Host, cluster, Base, Lang) ->
HookItems = get_menu_items_hook({host, Host}, Lang),
- make_menu_items(Lang, {Base, "", HookItems});
-
+ make_menu_items(Lang, {Base, <<"">>, HookItems});
make_menu_items(Host, Node, Base, Lang) ->
- HookItems = get_menu_items_hook({hostnode, Host, Node}, Lang),
- make_menu_items(Lang, {Base, "", HookItems}).
-
+ HookItems = get_menu_items_hook({hostnode, Host, Node},
+ Lang),
+ make_menu_items(Lang, {Base, <<"">>, HookItems}).
make_host_node_menu(global, _, _Lang, _JID) ->
- {"", "", []};
+ {<<"">>, <<"">>, []};
make_host_node_menu(_, cluster, _Lang, _JID) ->
- {"", "", []};
+ {<<"">>, <<"">>, []};
make_host_node_menu(Host, Node, Lang, JID) ->
HostNodeBase = get_base_path(Host, Node),
- HostNodeFixed = [{"modules/", "Modules"}]
- ++ get_menu_items_hook({hostnode, Host, Node}, Lang),
+ HostNodeFixed = [{<<"modules/">>, <<"Modules">>}] ++
+ get_menu_items_hook({hostnode, Host, Node}, Lang),
HostNodeBasePath = url_to_path(HostNodeBase),
- HostNodeFixed2 = [Tuple || Tuple <- HostNodeFixed, is_allowed_path(HostNodeBasePath, Tuple, JID)],
- {HostNodeBase, atom_to_list(Node), HostNodeFixed2}.
+ HostNodeFixed2 = [Tuple
+ || Tuple <- HostNodeFixed,
+ is_allowed_path(HostNodeBasePath, Tuple, JID)],
+ {HostNodeBase, iolist_to_binary(atom_to_list(Node)),
+ HostNodeFixed2}.
make_host_menu(global, _HostNodeMenu, _Lang, _JID) ->
- {"", "", []};
+ {<<"">>, <<"">>, []};
make_host_menu(Host, HostNodeMenu, Lang, JID) ->
HostBase = get_base_path(Host, cluster),
- HostFixed = [{"acls", "Access Control Lists"},
- {"access", "Access Rules"},
- {"users", "Users"},
- {"online-users", "Online Users"}]
- ++ get_lastactivity_menuitem_list(Host) ++
- [{"nodes", "Nodes", HostNodeMenu},
- {"stats", "Statistics"}]
- ++ get_menu_items_hook({host, Host}, Lang),
+ HostFixed = [{<<"acls">>, <<"Access Control Lists">>},
+ {<<"access">>, <<"Access Rules">>},
+ {<<"users">>, <<"Users">>},
+ {<<"online-users">>, <<"Online Users">>}]
+ ++
+ get_lastactivity_menuitem_list(Host) ++
+ [{<<"nodes">>, <<"Nodes">>, HostNodeMenu},
+ {<<"stats">>, <<"Statistics">>}]
+ ++ get_menu_items_hook({host, Host}, Lang),
HostBasePath = url_to_path(HostBase),
- HostFixed2 = [Tuple || Tuple <- HostFixed, is_allowed_path(HostBasePath, Tuple, JID)],
+ HostFixed2 = [Tuple
+ || Tuple <- HostFixed,
+ is_allowed_path(HostBasePath, Tuple, JID)],
{HostBase, Host, HostFixed2}.
make_node_menu(_Host, cluster, _Lang) ->
- {"", "", []};
+ {<<"">>, <<"">>, []};
make_node_menu(global, Node, Lang) ->
NodeBase = get_base_path(global, Node),
- NodeFixed = [{"db/", "Database"},
- {"backup/", "Backup"},
- {"ports/", "Listened Ports"},
- {"stats/", "Statistics"},
- {"update/", "Update"}]
- ++ get_menu_items_hook({node, Node}, Lang),
- {NodeBase, atom_to_list(Node), NodeFixed};
+ NodeFixed = [{<<"db/">>, <<"Database">>},
+ {<<"backup/">>, <<"Backup">>},
+ {<<"ports/">>, <<"Listened Ports">>},
+ {<<"stats/">>, <<"Statistics">>},
+ {<<"update/">>, <<"Update">>}]
+ ++ get_menu_items_hook({node, Node}, Lang),
+ {NodeBase, iolist_to_binary(atom_to_list(Node)),
+ NodeFixed};
make_node_menu(_Host, _Node, _Lang) ->
- {"", "", []}.
+ {<<"">>, <<"">>, []}.
make_server_menu(HostMenu, NodeMenu, Lang, JID) ->
Base = get_base_path(global, cluster),
- Fixed = [{"acls", "Access Control Lists"},
- {"access", "Access Rules"},
- {"vhosts", "Virtual Hosts", HostMenu},
- {"nodes", "Nodes", NodeMenu},
- {"stats", "Statistics"}]
- ++ get_menu_items_hook(server, Lang),
+ Fixed = [{<<"acls">>, <<"Access Control Lists">>},
+ {<<"access">>, <<"Access Rules">>},
+ {<<"vhosts">>, <<"Virtual Hosts">>, HostMenu},
+ {<<"nodes">>, <<"Nodes">>, NodeMenu},
+ {<<"stats">>, <<"Statistics">>}]
+ ++ get_menu_items_hook(server, Lang),
BasePath = url_to_path(Base),
- Fixed2 = [Tuple || Tuple <- Fixed, is_allowed_path(BasePath, Tuple, JID)],
- {Base, "ejabberd", Fixed2}.
-
+ Fixed2 = [Tuple
+ || Tuple <- Fixed,
+ is_allowed_path(BasePath, Tuple, JID)],
+ {Base, <<"ejabberd">>, Fixed2}.
get_menu_items_hook({hostnode, Host, Node}, Lang) ->
- ejabberd_hooks:run_fold(webadmin_menu_hostnode, Host, [], [Host, Node, Lang]);
+ ejabberd_hooks:run_fold(webadmin_menu_hostnode, Host,
+ [], [Host, Node, Lang]);
get_menu_items_hook({host, Host}, Lang) ->
- ejabberd_hooks:run_fold(webadmin_menu_host, Host, [], [Host, Lang]);
+ ejabberd_hooks:run_fold(webadmin_menu_host, Host, [],
+ [Host, Lang]);
get_menu_items_hook({node, Node}, Lang) ->
- ejabberd_hooks:run_fold(webadmin_menu_node, [], [Node, Lang]);
+ ejabberd_hooks:run_fold(webadmin_menu_node, [],
+ [Node, Lang]);
get_menu_items_hook(server, Lang) ->
ejabberd_hooks:run_fold(webadmin_menu_main, [], [Lang]).
-
%% @spec (Lang::string(), Menu) -> [LI]
%% where Menu = {MURI::string(), MName::string(), Items::[Item]}
%% Item = {IURI::string(), IName::string()} | {IURI::string(), IName::string(), Menu}
@@ -2821,39 +2844,50 @@ make_menu_items(Lang, Menu) ->
make_menu_items2(Lang, Deep, {MURI, MName, _} = Menu) ->
Res = case MName of
- "" -> [];
- _ -> [make_menu_item(header, Deep, MURI, MName, Lang) ]
+ <<"">> -> [];
+ _ -> [make_menu_item(header, Deep, MURI, MName, Lang)]
end,
make_menu_items2(Lang, Deep, Menu, Res).
-make_menu_items2(_, _Deep, {_, _, []}, Res) ->
- Res;
-
-make_menu_items2(Lang, Deep, {MURI, MName, [Item | Items]}, Res) ->
+make_menu_items2(_, _Deep, {_, _, []}, Res) -> Res;
+make_menu_items2(Lang, Deep,
+ {MURI, MName, [Item | Items]}, Res) ->
Res2 = case Item of
- {IURI, IName} ->
- [make_menu_item(item, Deep, MURI++IURI++"/", IName, Lang) | Res];
- {IURI, IName, SubMenu} ->
- %%ResTemp = [?LI([?ACT(MURI ++ IURI ++ "/", IName)]) | Res],
- ResTemp = [make_menu_item(item, Deep, MURI++IURI++"/", IName, Lang) | Res],
- ResSubMenu = make_menu_items2(Lang, Deep+1, SubMenu),
- ResSubMenu ++ ResTemp
+ {IURI, IName} ->
+ [make_menu_item(item, Deep,
+ <<MURI/binary, IURI/binary, "/">>, IName, Lang)
+ | Res];
+ {IURI, IName, SubMenu} ->
+ ResTemp = [make_menu_item(item, Deep,
+ <<MURI/binary, IURI/binary, "/">>,
+ IName, Lang)
+ | Res],
+ ResSubMenu = make_menu_items2(Lang, Deep + 1, SubMenu),
+ ResSubMenu ++ ResTemp
end,
- make_menu_items2(Lang, Deep, {MURI, MName, Items}, Res2).
+ make_menu_items2(Lang, Deep, {MURI, MName, Items},
+ Res2).
make_menu_item(header, 1, URI, Name, _Lang) ->
- ?LI([?XAE("div", [{"id", "navhead"}], [?AC(URI, Name)] )]);
+ ?LI([?XAE(<<"div">>, [{<<"id">>, <<"navhead">>}],
+ [?AC(URI, Name)])]);
make_menu_item(header, 2, URI, Name, _Lang) ->
- ?LI([?XAE("div", [{"id", "navheadsub"}], [?AC(URI, Name)] )]);
+ ?LI([?XAE(<<"div">>, [{<<"id">>, <<"navheadsub">>}],
+ [?AC(URI, Name)])]);
make_menu_item(header, 3, URI, Name, _Lang) ->
- ?LI([?XAE("div", [{"id", "navheadsubsub"}], [?AC(URI, Name)] )]);
+ ?LI([?XAE(<<"div">>, [{<<"id">>, <<"navheadsubsub">>}],
+ [?AC(URI, Name)])]);
make_menu_item(item, 1, URI, Name, Lang) ->
- ?LI([?XAE("div", [{"id", "navitem"}], [?ACT(URI, Name)] )]);
+ ?LI([?XAE(<<"div">>, [{<<"id">>, <<"navitem">>}],
+ [?ACT(URI, Name)])]);
make_menu_item(item, 2, URI, Name, Lang) ->
- ?LI([?XAE("div", [{"id", "navitemsub"}], [?ACT(URI, Name)] )]);
+ ?LI([?XAE(<<"div">>, [{<<"id">>, <<"navitemsub">>}],
+ [?ACT(URI, Name)])]);
make_menu_item(item, 3, URI, Name, Lang) ->
- ?LI([?XAE("div", [{"id", "navitemsubsub"}], [?ACT(URI, Name)] )]).
+ ?LI([?XAE(<<"div">>, [{<<"id">>, <<"navitemsubsub">>}],
+ [?ACT(URI, Name)])]).
%%%==================================
%%% vim: set foldmethod=marker foldmarker=%%%%,%%%=:
+
diff --git a/src/web/ejabberd_web_admin.hrl b/src/web/ejabberd_web_admin.hrl
index 646e1c172..6c939f9e9 100644
--- a/src/web/ejabberd_web_admin.hrl
+++ b/src/web/ejabberd_web_admin.hrl
@@ -19,58 +19,87 @@
%%%
%%%----------------------------------------------------------------------
--define(X(Name), {xmlelement, Name, [], []}).
--define(XA(Name, Attrs), {xmlelement, Name, Attrs, []}).
--define(XE(Name, Els), {xmlelement, Name, [], Els}).
--define(XAE(Name, Attrs, Els), {xmlelement, Name, Attrs, Els}).
+-define(X(Name),
+ #xmlel{name = Name, attrs = [], children = []}).
+
+-define(XA(Name, Attrs),
+ #xmlel{name = Name, attrs = Attrs, children = []}).
+
+-define(XE(Name, Els),
+ #xmlel{name = Name, attrs = [], children = Els}).
+
+-define(XAE(Name, Attrs, Els),
+ #xmlel{name = Name, attrs = Attrs, children = Els}).
+
-define(C(Text), {xmlcdata, Text}).
+
-define(XC(Name, Text), ?XE(Name, [?C(Text)])).
--define(XAC(Name, Attrs, Text), ?XAE(Name, Attrs, [?C(Text)])).
+
+-define(XAC(Name, Attrs, Text),
+ ?XAE(Name, Attrs, [?C(Text)])).
-define(T(Text), translate:translate(Lang, Text)).
--define(CT(Text), ?C(?T(Text))).
--define(XCT(Name, Text), ?XC(Name, ?T(Text))).
--define(XACT(Name, Attrs, Text), ?XAC(Name, Attrs, ?T(Text))).
--define(LI(Els), ?XE("li", Els)).
--define(A(URL, Els), ?XAE("a", [{"href", URL}], Els)).
+-define(CT(Text), ?C((?T(Text)))).
+
+-define(XCT(Name, Text), ?XC(Name, (?T(Text)))).
+
+-define(XACT(Name, Attrs, Text),
+ ?XAC(Name, Attrs, (?T(Text)))).
+
+-define(LI(Els), ?XE(<<"li">>, Els)).
+
+-define(A(URL, Els),
+ ?XAE(<<"a">>, [{<<"href">>, URL}], Els)).
+
-define(AC(URL, Text), ?A(URL, [?C(Text)])).
--define(ACT(URL, Text), ?AC(URL, ?T(Text))).
--define(P, ?X("p")).
--define(BR, ?X("br")).
+
+-define(ACT(URL, Text), ?AC(URL, (?T(Text)))).
+
+-define(P, ?X(<<"p">>)).
+
+-define(BR, ?X(<<"br">>)).
+
-define(INPUT(Type, Name, Value),
- ?XA("input", [{"type", Type},
- {"name", Name},
- {"value", Value}])).
--define(INPUTT(Type, Name, Value), ?INPUT(Type, Name, ?T(Value))).
+ ?XA(<<"input">>,
+ [{<<"type">>, Type}, {<<"name">>, Name},
+ {<<"value">>, Value}])).
+
+-define(INPUTT(Type, Name, Value),
+ ?INPUT(Type, Name, (?T(Value)))).
+
-define(INPUTS(Type, Name, Value, Size),
- ?XA("input", [{"type", Type},
- {"name", Name},
- {"value", Value},
- {"size", Size}])).
--define(INPUTST(Type, Name, Value, Size), ?INPUT(Type, Name, ?T(Value), Size)).
--define(ACLINPUT(Text), ?XE("td", [?INPUT("text", "value" ++ ID, Text)])).
+ ?XA(<<"input">>,
+ [{<<"type">>, Type}, {<<"name">>, Name},
+ {<<"value">>, Value}, {<<"size">>, Size}])).
+
+-define(INPUTST(Type, Name, Value, Size),
+ ?INPUT(Type, Name, (?T(Value)), Size)).
+
+-define(ACLINPUT(Text),
+ ?XE(<<"td">>,
+ [?INPUT(<<"text">>, <<"value", ID/binary>>, Text)])).
-define(TEXTAREA(Name, Rows, Cols, Value),
- ?XAC("textarea", [{"name", Name},
- {"rows", Rows},
- {"cols", Cols}],
+ ?XAC(<<"textarea">>,
+ [{<<"name">>, Name}, {<<"rows">>, Rows},
+ {<<"cols">>, Cols}],
Value)).
%% Build an xmlelement for result
--define(XRES(Text), ?XAC("p", [{"class", "result"}], Text)).
--define(XREST(Text), ?XRES(?T(Text))).
+-define(XRES(Text),
+ ?XAC(<<"p">>, [{<<"class">>, <<"result">>}], Text)).
%% Guide Link
--define(GL(Ref, Title),
- ?XAE("div",
- [{"class", "guidelink"}],
- [?XAE("a",
- [{"href", "/admin/doc/guide.html#"++ Ref},
- {"target", "_blank"}],
- [?C("[Guide: " ++ Title ++ "]")])
- ])).
+-define(XREST(Text), ?XRES((?T(Text)))).
+-define(GL(Ref, Title),
+ ?XAE(<<"div">>, [{<<"class">>, <<"guidelink">>}],
+ [?XAE(<<"a">>,
+ [{<<"href">>, <<"/admin/doc/guide.html#", Ref/binary>>},
+ {<<"target">>, <<"_blank">>}],
+ [?C(<<"[Guide: ", Title/binary, "]">>)])])).
%% h1 with a Guide Link
--define(H1GL(Name, Ref, Title), [?XC("h1", Name), ?GL(Ref, Title)]).
+-define(H1GL(Name, Ref, Title),
+ [?XC(<<"h1">>, Name), ?GL(Ref, Title)]).
diff --git a/src/web/http_bind.hrl b/src/web/http_bind.hrl
index ea9586ea5..2eac91e9d 100644
--- a/src/web/http_bind.hrl
+++ b/src/web/http_bind.hrl
@@ -19,14 +19,29 @@
%%%
%%%----------------------------------------------------------------------
--define(CT_XML, {"Content-Type", "text/xml; charset=utf-8"}).
--define(CT_PLAIN, {"Content-Type", "text/plain"}).
+-define(CT_XML,
+ {<<"Content-Type">>, <<"text/xml; charset=utf-8">>}).
--define(AC_ALLOW_ORIGIN, {"Access-Control-Allow-Origin", "*"}).
--define(AC_ALLOW_METHODS, {"Access-Control-Allow-Methods", "GET, POST, OPTIONS"}).
--define(AC_ALLOW_HEADERS, {"Access-Control-Allow-Headers", "Content-Type"}).
--define(AC_MAX_AGE, {"Access-Control-Max-Age", "86400"}).
+-define(CT_PLAIN,
+ {<<"Content-Type">>, <<"text/plain">>}).
--define(OPTIONS_HEADER, [?CT_PLAIN, ?AC_ALLOW_ORIGIN, ?AC_ALLOW_METHODS,
- ?AC_ALLOW_HEADERS, ?AC_MAX_AGE]).
--define(HEADER, [?CT_XML, ?AC_ALLOW_ORIGIN, ?AC_ALLOW_HEADERS]).
+-define(AC_ALLOW_ORIGIN,
+ {<<"Access-Control-Allow-Origin">>, <<"*">>}).
+
+-define(AC_ALLOW_METHODS,
+ {<<"Access-Control-Allow-Methods">>,
+ <<"GET, POST, OPTIONS">>}).
+
+-define(AC_ALLOW_HEADERS,
+ {<<"Access-Control-Allow-Headers">>,
+ <<"Content-Type">>}).
+
+-define(AC_MAX_AGE,
+ {<<"Access-Control-Max-Age">>, <<"86400">>}).
+
+-define(OPTIONS_HEADER,
+ [?CT_PLAIN, ?AC_ALLOW_ORIGIN, ?AC_ALLOW_METHODS,
+ ?AC_ALLOW_HEADERS, ?AC_MAX_AGE]).
+
+-define(HEADER,
+ [?CT_XML, ?AC_ALLOW_ORIGIN, ?AC_ALLOW_HEADERS]).
diff --git a/src/web/mod_http_bind.erl b/src/web/mod_http_bind.erl
index 9112f6e59..164c07d33 100644
--- a/src/web/mod_http_bind.erl
+++ b/src/web/mod_http_bind.erl
@@ -31,73 +31,84 @@
%%%----------------------------------------------------------------------
-module(mod_http_bind).
+
-author('steve@zeank.in-berlin.de').
%%-define(ejabberd_debug, true).
-behaviour(gen_mod).
--export([
- start/2,
- stop/1,
- process/2
- ]).
+-export([start/2, stop/1, process/2]).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
+
-include("ejabberd_http.hrl").
+
-include("http_bind.hrl").
-define(PROCNAME_MHB, ejabberd_mod_http_bind).
%% Duplicated from ejabberd_http_bind.
%% TODO: move to hrl file.
--record(http_bind, {id, pid, to, hold, wait, process_delay, version}).
+-record(http_bind,
+ {id, pid, to, hold, wait, process_delay, version}).
%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
-process([], #request{method = 'POST',
- data = []}) ->
+process([], #request{method = 'POST', data = <<>>}) ->
?DEBUG("Bad Request: no data", []),
- {400, ?HEADER, {xmlelement, "h1", [],
- [{xmlcdata, "400 Bad Request"}]}};
-process([], #request{method = 'POST',
- data = Data,
- ip = IP}) ->
+ {400, ?HEADER,
+ #xmlel{name = <<"h1">>, children = [{xmlcdata, <<"400 Bad Request">>}]}};
+process([],
+ #request{method = 'POST', data = Data, ip = IP}) ->
?DEBUG("Incoming data: ~s", [Data]),
ejabberd_http_bind:process_request(Data, IP);
-process([], #request{method = 'GET',
- data = []}) ->
+process([], #request{method = 'GET', data = <<>>}) ->
{200, ?HEADER, get_human_html_xmlel()};
-process([], #request{method = 'OPTIONS',
- data = []}) ->
- {200, ?OPTIONS_HEADER, []};
+process([], #request{method = 'OPTIONS', data = <<>>}) ->
+ {200, ?OPTIONS_HEADER, <<>>};
process([], #request{method = 'HEAD'}) ->
- {200, ?HEADER, []};
+ {200, ?HEADER, <<>>};
process(_Path, _Request) ->
?DEBUG("Bad Request: ~p", [_Request]),
- {400, ?HEADER, {xmlelement, "h1", [],
- [{xmlcdata, "400 Bad Request"}]}}.
+ {400, ?HEADER,
+ #xmlel{name = <<"h1">>, children = [{xmlcdata, <<"400 Bad Request">>}]}}.
get_human_html_xmlel() ->
- Heading = "ejabberd " ++ atom_to_list(?MODULE),
- {xmlelement, "html", [{"xmlns", "http://www.w3.org/1999/xhtml"}],
- [{xmlelement, "head", [],
- [{xmlelement, "title", [], [{xmlcdata, Heading}]}]},
- {xmlelement, "body", [],
- [{xmlelement, "h1", [], [{xmlcdata, Heading}]},
- {xmlelement, "p", [],
- [{xmlcdata, "An implementation of "},
- {xmlelement, "a",
- [{"href", "http://xmpp.org/extensions/xep-0206.html"}],
- [{xmlcdata, "XMPP over BOSH (XEP-0206)"}]}]},
- {xmlelement, "p", [],
- [{xmlcdata, "This web page is only informative. "
- "To use HTTP-Bind you need a Jabber/XMPP client that supports it."}
- ]}
- ]}]}.
+ Heading = <<"ejabberd ",
+ (iolist_to_binary(atom_to_list(?MODULE)))/binary>>,
+ #xmlel{name = <<"html">>,
+ attrs =
+ [{<<"xmlns">>, <<"http://www.w3.org/1999/xhtml">>}],
+ children =
+ [#xmlel{name = <<"head">>,
+ children =
+ [#xmlel{name = <<"title">>,
+ children = [{xmlcdata, Heading}]}]},
+ #xmlel{name = <<"body">>,
+ children =
+ [#xmlel{name = <<"h1">>,
+ children = [{xmlcdata, Heading}]},
+ #xmlel{name = <<"p">>,
+ children =
+ [{xmlcdata, <<"An implementation of ">>},
+ #xmlel{name = <<"a">>,
+ attrs =
+ [{<<"href">>,
+ <<"http://xmpp.org/extensions/xep-0206.html">>}],
+ children =
+ [{xmlcdata,
+ <<"XMPP over BOSH (XEP-0206)">>}]}]},
+ #xmlel{name = <<"p">>,
+ children =
+ [{xmlcdata,
+ <<"This web page is only informative. To "
+ "use HTTP-Bind you need a Jabber/XMPP "
+ "client that supports it.">>}]}]}]}.
%%%----------------------------------------------------------------------
%%% BEHAVIOUR CALLBACKS
@@ -105,14 +116,10 @@ get_human_html_xmlel() ->
start(Host, _Opts) ->
setup_database(),
Proc = gen_mod:get_module_proc(Host, ?PROCNAME_MHB),
- ChildSpec =
- {Proc,
- {ejabberd_tmp_sup, start_link,
- [Proc, ejabberd_http_bind]},
- permanent,
- infinity,
- supervisor,
- [ejabberd_tmp_sup]},
+ ChildSpec = {Proc,
+ {ejabberd_tmp_sup, start_link,
+ [Proc, ejabberd_http_bind]},
+ permanent, infinity, supervisor, [ejabberd_tmp_sup]},
supervisor:start_child(ejabberd_sup, ChildSpec).
stop(Host) ->
diff --git a/src/web/mod_http_fileserver.erl b/src/web/mod_http_fileserver.erl
index 71a627208..8fe0c0fc4 100644
--- a/src/web/mod_http_fileserver.erl
+++ b/src/web/mod_http_fileserver.erl
@@ -25,6 +25,7 @@
%%%----------------------------------------------------------------------
-module(mod_http_fileserver).
+
-author('mmirra@process-one.net').
-behaviour(gen_mod).
@@ -47,58 +48,66 @@
-export([reopen_log/1]).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
+
-include_lib("kernel/include/file.hrl").
%%-include("ejabberd_http.hrl").
%% TODO: When ejabberd-modules SVN gets the new ejabberd_http.hrl, delete this code:
--record(request, {method,
- path,
- q = [],
- us,
- auth,
- lang = "",
- data = "",
- ip,
- host, % string()
- port, % integer()
- tp, % transfer protocol = http | https
- headers
- }).
+-record(request,
+ {method, path, q = [], us, auth, lang = <<"">>,
+ data = <<"">>, ip, host, port, tp, headers}).
-ifdef(SSL40).
+
-define(STRING2LOWER, string).
+
-else.
+
-ifdef(SSL39).
+
-define(STRING2LOWER, string).
+
-else.
+
-define(STRING2LOWER, httpd_util).
+
-endif.
+
-endif.
--record(state, {host, docroot, accesslog, accesslogfd, directory_indices,
- custom_headers, default_content_type, content_types = []}).
+-record(state,
+ {host, docroot, accesslog, accesslogfd,
+ directory_indices, custom_headers, default_content_type,
+ content_types = []}).
-define(PROCNAME, ejabberd_mod_http_fileserver).
%% Response is {DataSize, Code, [{HeaderKey, HeaderValue}], Data}
--define(HTTP_ERR_FILE_NOT_FOUND, {-1, 404, [], "Not found"}).
--define(HTTP_ERR_FORBIDDEN, {-1, 403, [], "Forbidden"}).
-
--define(DEFAULT_CONTENT_TYPE, "application/octet-stream").
--define(DEFAULT_CONTENT_TYPES, [{".css", "text/css"},
- {".gif", "image/gif"},
- {".html", "text/html"},
- {".jar", "application/java-archive"},
- {".jpeg", "image/jpeg"},
- {".jpg", "image/jpeg"},
- {".js", "text/javascript"},
- {".png", "image/png"},
- {".svg", "image/svg+xml"},
- {".txt", "text/plain"},
- {".xml", "application/xml"},
- {".xpi", "application/x-xpinstall"},
- {".xul", "application/vnd.mozilla.xul+xml"}]).
+-define(HTTP_ERR_FILE_NOT_FOUND,
+ {-1, 404, [], <<"Not found">>}).
+
+-define(HTTP_ERR_FORBIDDEN,
+ {-1, 403, [], <<"Forbidden">>}).
+
+-define(DEFAULT_CONTENT_TYPE,
+ <<"application/octet-stream">>).
+
+-define(DEFAULT_CONTENT_TYPES,
+ [{<<".css">>, <<"text/css">>},
+ {<<".gif">>, <<"image/gif">>},
+ {<<".html">>, <<"text/html">>},
+ {<<".jar">>, <<"application/java-archive">>},
+ {<<".jpeg">>, <<"image/jpeg">>},
+ {<<".jpg">>, <<"image/jpeg">>},
+ {<<".js">>, <<"text/javascript">>},
+ {<<".png">>, <<"image/png">>},
+ {<<".svg">>, <<"image/svg+xml">>},
+ {<<".txt">>, <<"text/plain">>},
+ {<<".xml">>, <<"application/xml">>},
+ {<<".xpi">>, <<"application/x-xpinstall">>},
+ {<<".xul">>, <<"application/vnd.mozilla.xul+xml">>}]).
-compile(export_all).
@@ -162,22 +171,34 @@ init([Host, Opts]) ->
end.
initialize(Host, Opts) ->
- DocRoot = gen_mod:get_opt(docroot, Opts, undefined),
+ DocRoot = gen_mod:get_opt(docroot, Opts, fun(A) -> A end, undefined),
check_docroot_defined(DocRoot, Host),
DRInfo = check_docroot_exists(DocRoot),
check_docroot_is_dir(DRInfo, DocRoot),
check_docroot_is_readable(DRInfo, DocRoot),
- AccessLog = gen_mod:get_opt(accesslog, Opts, undefined),
+ AccessLog = gen_mod:get_opt(accesslog, Opts,
+ fun iolist_to_binary/1,
+ undefined),
AccessLogFD = try_open_log(AccessLog, Host),
- DirectoryIndices = gen_mod:get_opt(directory_indices, Opts, []),
- CustomHeaders = gen_mod:get_opt(custom_headers, Opts, []),
+ DirectoryIndices = gen_mod:get_opt(directory_indices, Opts,
+ fun(L) when is_list(L) -> L end,
+ []),
+ CustomHeaders = gen_mod:get_opt(custom_headers, Opts,
+ fun(L) when is_list(L) -> L end,
+ []),
DefaultContentType = gen_mod:get_opt(default_content_type, Opts,
+ fun iolist_to_binary/1,
?DEFAULT_CONTENT_TYPE),
- ContentTypes = build_list_content_types(gen_mod:get_opt(content_types, Opts, []), ?DEFAULT_CONTENT_TYPES),
+ ContentTypes = build_list_content_types(
+ gen_mod:get_opt(content_types, Opts,
+ fun(L) when is_list(L) -> L end,
+ []),
+ ?DEFAULT_CONTENT_TYPES),
?INFO_MSG("initialize: ~n ~p", [ContentTypes]),%+++
{DocRoot, AccessLog, AccessLogFD, DirectoryIndices,
CustomHeaders, DefaultContentType, ContentTypes}.
+
%% @spec (AdminCTs::[CT], Default::[CT]) -> [CT]
%% where CT = {Extension::string(), Value}
%% Value = string() | undefined
@@ -187,32 +208,36 @@ initialize(Host, Opts) ->
build_list_content_types(AdminCTsUnsorted, DefaultCTsUnsorted) ->
AdminCTs = lists:ukeysort(1, AdminCTsUnsorted),
DefaultCTs = lists:ukeysort(1, DefaultCTsUnsorted),
- CTsUnfiltered = lists:ukeymerge(1, AdminCTs, DefaultCTs),
- [{Extension, Value} || {Extension, Value} <- CTsUnfiltered, Value /= undefined].
+ CTsUnfiltered = lists:ukeymerge(1, AdminCTs,
+ DefaultCTs),
+ [{Extension, Value}
+ || {Extension, Value} <- CTsUnfiltered,
+ Value /= undefined].
check_docroot_defined(DocRoot, Host) ->
case DocRoot of
- undefined -> throw({undefined_docroot_option, Host});
- _ -> ok
+ undefined -> throw({undefined_docroot_option, Host});
+ _ -> ok
end.
check_docroot_exists(DocRoot) ->
case file:read_file_info(DocRoot) of
- {error, Reason} -> throw({error_access_docroot, DocRoot, Reason});
- {ok, FI} -> FI
+ {error, Reason} ->
+ throw({error_access_docroot, DocRoot, Reason});
+ {ok, FI} -> FI
end.
check_docroot_is_dir(DRInfo, DocRoot) ->
case DRInfo#file_info.type of
- directory -> ok;
- _ -> throw({docroot_not_directory, DocRoot})
+ directory -> ok;
+ _ -> throw({docroot_not_directory, DocRoot})
end.
check_docroot_is_readable(DRInfo, DocRoot) ->
case DRInfo#file_info.access of
- read -> ok;
- read_write -> ok;
- _ -> throw({docroot_not_readable, DocRoot})
+ read -> ok;
+ read_write -> ok;
+ _ -> throw({docroot_not_readable, DocRoot})
end.
try_open_log(undefined, _Host) ->
@@ -341,12 +366,14 @@ serve_index(FileName, [Index | T], CH, DefaultContentType, ContentTypes) ->
%% and serve it up.
serve_file(FileInfo, FileName, CustomHeaders, DefaultContentType, ContentTypes) ->
?DEBUG("Delivering: ~s", [FileName]),
+ ContentType = content_type(FileName, DefaultContentType,
+ ContentTypes),
{ok, FileContents} = file:read_file(FileName),
- ContentType = content_type(FileName, DefaultContentType, ContentTypes),
- {FileInfo#file_info.size,
- 200, [{"Server", "ejabberd"},
- {"Last-Modified", last_modified(FileInfo)},
- {"Content-Type", ContentType} | CustomHeaders],
+ {FileInfo#file_info.size, 200,
+ [{<<"Server">>, <<"ejabberd">>},
+ {<<"Last-Modified">>, last_modified(FileInfo)},
+ {<<"Content-Type">>, ContentType}
+ | CustomHeaders],
FileContents}.
%%----------------------------------------------------------------------
@@ -406,8 +433,8 @@ add_to_log(File, FileSize, Code, Request) ->
find_header(Header, Headers, Default) ->
case lists:keysearch(Header, 1, Headers) of
- {value, {_, Value}} -> Value;
- false -> Default
+ {value, {_, Value}} -> Value;
+ false -> Default
end.
%%----------------------------------------------------------------------
@@ -426,8 +453,8 @@ join([H | T], Separator) ->
content_type(Filename, DefaultContentType, ContentTypes) ->
Extension = ?STRING2LOWER:to_lower(filename:extension(Filename)),
case lists:keysearch(Extension, 1, ContentTypes) of
- {value, {_, ContentType}} -> ContentType;
- false -> DefaultContentType
+ {value, {_, ContentType}} -> ContentType;
+ false -> DefaultContentType
end.
last_modified(FileInfo) ->
diff --git a/src/web/mod_register_web.erl b/src/web/mod_register_web.erl
index acb022351..0ccba9945 100644
--- a/src/web/mod_register_web.erl
+++ b/src/web/mod_register_web.erl
@@ -51,18 +51,19 @@
%%% * Optionally register request is forwarded to admin, no account created.
-module(mod_register_web).
+
-author('badlop@process-one.net').
-behaviour(gen_mod).
--export([start/2,
- stop/1,
- process/2
- ]).
+-export([start/2, stop/1, process/2]).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
+
-include("ejabberd_http.hrl").
+
-include("ejabberd_web_admin.hrl").
%%%----------------------------------------------------------------------
@@ -70,11 +71,10 @@
%%%----------------------------------------------------------------------
start(_Host, _Opts) ->
- %% case gen_mod:get_opt(docroot, Opts, undefined) of
+ %% case gen_mod:get_opt(docroot, Opts, fun(A) -> A end, undefined) of
ok.
-stop(_Host) ->
- ok.
+stop(_Host) -> ok.
%%%----------------------------------------------------------------------
%%% HTTP handlers
@@ -82,55 +82,64 @@ stop(_Host) ->
process([], #request{method = 'GET', lang = Lang}) ->
index_page(Lang);
-
-process(["register.css"], #request{method = 'GET'}) ->
+process([<<"register.css">>],
+ #request{method = 'GET'}) ->
serve_css();
-
-process(["new"], #request{method = 'GET', lang = Lang, host = Host, ip = IP}) ->
- {Addr, _Port} = IP,
- form_new_get(Host, Lang, Addr);
-
-process(["delete"], #request{method = 'GET', lang = Lang, host = Host}) ->
+process([<<"new">>],
+ #request{method = 'GET', lang = Lang, host = Host,
+ ip = IP}) ->
+ {Addr, _Port} = IP, form_new_get(Host, Lang, Addr);
+process([<<"delete">>],
+ #request{method = 'GET', lang = Lang, host = Host}) ->
form_del_get(Host, Lang);
-
-process(["change_password"], #request{method = 'GET', lang = Lang, host = Host}) ->
+process([<<"change_password">>],
+ #request{method = 'GET', lang = Lang, host = Host}) ->
form_changepass_get(Host, Lang);
-
-process(["new"], #request{method = 'POST', q = Q, ip = {Ip,_Port}, lang = Lang, host = Host}) ->
+process([<<"new">>],
+ #request{method = 'POST', q = Q, ip = {Ip, _Port},
+ lang = Lang, host = Host}) ->
case form_new_post(Q, Host) of
- {success, ok, {Username, Host, _Password}} ->
- Jid = jlib:make_jid(Username, Host, ""),
- send_registration_notifications(Jid, Ip),
- Text = ?T("Your Jabber account was successfully created."),
- {200, [], Text};
- Error ->
- ErrorText = ?T("There was an error creating the account: ") ++
- ?T(get_error_text(Error)),
- {404, [], ErrorText}
+ {success, ok, {Username, Host, _Password}} ->
+ Jid = jlib:make_jid(Username, Host, <<"">>),
+ mod_register:send_registration_notifications(?MODULE, Jid, Ip),
+ Text = (?T(<<"Your Jabber account was successfully "
+ "created.">>)),
+ {200, [], Text};
+ Error ->
+ ErrorText =
+ list_to_binary([?T(<<"There was an error creating the account: ">>),
+ ?T(get_error_text(Error))]),
+ {404, [], ErrorText}
end;
-
-process(["delete"], #request{method = 'POST', q = Q, lang = Lang, host = Host}) ->
+process([<<"delete">>],
+ #request{method = 'POST', q = Q, lang = Lang,
+ host = Host}) ->
case form_del_post(Q, Host) of
- {atomic, ok} ->
- Text = ?T("Your Jabber account was successfully deleted."),
- {200, [], Text};
- Error ->
- ErrorText = ?T("There was an error deleting the account: ") ++
- ?T(get_error_text(Error)),
- {404, [], ErrorText}
+ {atomic, ok} ->
+ Text = (?T(<<"Your Jabber account was successfully "
+ "deleted.">>)),
+ {200, [], Text};
+ Error ->
+ ErrorText =
+ list_to_binary([?T(<<"There was an error deleting the account: ">>),
+ ?T(get_error_text(Error))]),
+ {404, [], ErrorText}
end;
-
%% TODO: Currently only the first vhost is usable. The web request record
%% should include the host where the POST was sent.
-process(["change_password"], #request{method = 'POST', q = Q, lang = Lang, host = Host}) ->
+process([<<"change_password">>],
+ #request{method = 'POST', q = Q, lang = Lang,
+ host = Host}) ->
case form_changepass_post(Q, Host) of
- {atomic, ok} ->
- Text = ?T("The password of your Jabber account was successfully changed."),
- {200, [], Text};
- Error ->
- ErrorText = ?T("There was an error changing the password: ") ++
- ?T(get_error_text(Error)),
- {404, [], ErrorText}
+ {atomic, ok} ->
+ Text = (?T(<<"The password of your Jabber account "
+ "was successfully changed.">>)),
+ {200, [], Text};
+ Error ->
+ ErrorText =
+ list_to_binary([?T(<<"There was an error changing the password: ">>),
+ ?T(get_error_text(Error))]),
+ {404, [], ErrorText}
end.
%%%----------------------------------------------------------------------
@@ -138,48 +147,48 @@ process(["change_password"], #request{method = 'POST', q = Q, lang = Lang, host
%%%----------------------------------------------------------------------
serve_css() ->
- {200, [{"Content-Type", "text/css"},
- last_modified(), cache_control_public()], css()}.
+ {200,
+ [{<<"Content-Type">>, <<"text/css">>}, last_modified(),
+ cache_control_public()],
+ css()}.
last_modified() ->
- {"Last-Modified", "Mon, 25 Feb 2008 13:23:30 GMT"}.
+ {<<"Last-Modified">>,
+ <<"Mon, 25 Feb 2008 13:23:30 GMT">>}.
+
cache_control_public() ->
- {"Cache-Control", "public"}.
+ {<<"Cache-Control">>, <<"public">>}.
css() ->
- "html,body {
-background: white;
-margin: 0;
-padding: 0;
-height: 100%;
-}".
+ <<"html,body {\nbackground: white;\nmargin: "
+ "0;\npadding: 0;\nheight: 100%;\n}">>.
%%%----------------------------------------------------------------------
%%% Index page
%%%----------------------------------------------------------------------
index_page(Lang) ->
- HeadEls = [
- ?XCT("title", "Jabber Account Registration"),
- ?XA("link",
- [{"href", "/register/register.css"},
- {"type", "text/css"},
- {"rel", "stylesheet"}])
- ],
- Els=[
- ?XACT("h1",
- [{"class", "title"}, {"style", "text-align:center;"}],
- "Jabber Account Registration"),
- ?XE("ul", [
- ?XE("li", [?ACT("new", "Register a Jabber account")]),
- ?XE("li", [?ACT("change_password", "Change Password")]),
- ?XE("li", [?ACT("delete", "Unregister a Jabber account")])
- ]
- )
- ],
+ HeadEls = [?XCT(<<"title">>,
+ <<"Jabber Account Registration">>),
+ ?XA(<<"link">>,
+ [{<<"href">>, <<"/register/register.css">>},
+ {<<"type">>, <<"text/css">>},
+ {<<"rel">>, <<"stylesheet">>}])],
+ Els = [?XACT(<<"h1">>,
+ [{<<"class">>, <<"title">>},
+ {<<"style">>, <<"text-align:center;">>}],
+ <<"Jabber Account Registration">>),
+ ?XE(<<"ul">>,
+ [?XE(<<"li">>,
+ [?ACT(<<"new">>, <<"Register a Jabber account">>)]),
+ ?XE(<<"li">>,
+ [?ACT(<<"change_password">>, <<"Change Password">>)]),
+ ?XE(<<"li">>,
+ [?ACT(<<"delete">>,
+ <<"Unregister a Jabber account">>)])])],
{200,
- [{"Server", "ejabberd"},
- {"Content-Type", "text/html"}],
+ [{<<"Server">>, <<"ejabberd">>},
+ {<<"Content-Type">>, <<"text/html">>}],
ejabberd_web:make_xhtml(HeadEls, Els)}.
%%%----------------------------------------------------------------------
@@ -188,149 +197,114 @@ index_page(Lang) ->
form_new_get(Host, Lang, IP) ->
CaptchaEls = build_captcha_li_list(Lang, IP),
- HeadEls = [
- ?XCT("title", "Register a Jabber account"),
- ?XA("link",
- [{"href", "/register/register.css"},
- {"type", "text/css"},
- {"rel", "stylesheet"}])
- ],
- Els=[
- ?XACT("h1",
- [{"class", "title"}, {"style", "text-align:center;"}],
- "Register a Jabber account"),
- ?XCT("p",
- "This page allows to create a Jabber account in this Jabber server. "
- "Your JID (Jabber IDentifier) will be of the form: username@server. "
- "Please read carefully the instructions to fill correctly the fields."),
- %% <!-- JID's take the form of 'username@server.com'. For example, my JID is 'kirjava@jabber.org'.
- %% The maximum length for a JID is 255 characters. -->
- ?XAE("form", [{"action", ""}, {"method", "post"}],
- [
- ?XE("ol", [
- ?XE("li", [
- ?CT("Username:"),
- ?C(" "),
- ?INPUTS("text", "username", "", "20"),
- ?BR,
- ?XE("ul", [
- ?XCT("li", "This is case insensitive: macbeth is the same that MacBeth and Macbeth."),
- ?XC("li", ?T("Characters not allowed:") ++ " \" & ' / : < > @ ")
- ])
- ]),
- ?XE("li", [
- ?CT("Server:"),
- ?C(" "),
- ?C(Host)
- ]),
- ?XE("li", [
- ?CT("Password:"),
- ?C(" "),
- ?INPUTS("password", "password", "", "20"),
- ?BR,
- ?XE("ul", [
- ?XCT("li", "Don't tell your password to anybody, "
- "not even the administrators of the Jabber server."),
- ?XCT("li", "You can later change your password using a Jabber client."),
- ?XCT("li", "Some Jabber clients can store your password in your computer. "
- "Use that feature only if you trust your computer is safe."),
- ?XCT("li", "Memorize your password, or write it in a paper placed in a safe place. "
- "In Jabber there isn't an automated way to recover your password if you forget it.")
- ])
- ]),
- ?XE("li", [
- ?CT("Password Verification:"),
- ?C(" "),
- ?INPUTS("password", "password2", "", "20")
- ])] ++ CaptchaEls ++ [
- %% Nombre</b> (opcional)<b>:</b> <input type="text" size="20" name="name" maxlength="255"> <br /> <br /> -->
- %%
- %% Direcci&oacute;n de correo</b> (opcional)<b>:</b> <input type="text" size="20" name="email" maxlength="255"> <br /> <br /> -->
- ?XE("li", [
- ?INPUTT("submit", "register", "Register")
- ])
- ])
- ])
- ],
+ HeadEls = [?XCT(<<"title">>,
+ <<"Register a Jabber account">>),
+ ?XA(<<"link">>,
+ [{<<"href">>, <<"/register/register.css">>},
+ {<<"type">>, <<"text/css">>},
+ {<<"rel">>, <<"stylesheet">>}])],
+ Els = [?XACT(<<"h1">>,
+ [{<<"class">>, <<"title">>},
+ {<<"style">>, <<"text-align:center;">>}],
+ <<"Register a Jabber account">>),
+ ?XCT(<<"p">>,
+ <<"This page allows to create a Jabber "
+ "account in this Jabber server. Your "
+ "JID (Jabber IDentifier) will be of the "
+ "form: username@server. Please read carefully "
+ "the instructions to fill correctly the "
+ "fields.">>),
+ ?XAE(<<"form">>,
+ [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
+ [?XE(<<"ol">>,
+ ([?XE(<<"li">>,
+ [?CT(<<"Username:">>), ?C(<<" ">>),
+ ?INPUTS(<<"text">>, <<"username">>, <<"">>,
+ <<"20">>),
+ ?BR,
+ ?XE(<<"ul">>,
+ [?XCT(<<"li">>,
+ <<"This is case insensitive: macbeth is "
+ "the same that MacBeth and Macbeth.">>),
+ ?XC(<<"li">>,
+ <<(?T(<<"Characters not allowed:">>))/binary,
+ " \" & ' / : < > @ ">>)])]),
+ ?XE(<<"li">>,
+ [?CT(<<"Server:">>), ?C(<<" ">>), ?C(Host)]),
+ ?XE(<<"li">>,
+ [?CT(<<"Password:">>), ?C(<<" ">>),
+ ?INPUTS(<<"password">>, <<"password">>, <<"">>,
+ <<"20">>),
+ ?BR,
+ ?XE(<<"ul">>,
+ [?XCT(<<"li">>,
+ <<"Don't tell your password to anybody, "
+ "not even the administrators of the Jabber "
+ "server.">>),
+ ?XCT(<<"li">>,
+ <<"You can later change your password using "
+ "a Jabber client.">>),
+ ?XCT(<<"li">>,
+ <<"Some Jabber clients can store your password "
+ "in your computer. Use that feature only "
+ "if you trust your computer is safe.">>),
+ ?XCT(<<"li">>,
+ <<"Memorize your password, or write it "
+ "in a paper placed in a safe place. In "
+ "Jabber there isn't an automated way "
+ "to recover your password if you forget "
+ "it.">>)])]),
+ ?XE(<<"li">>,
+ [?CT(<<"Password Verification:">>), ?C(<<" ">>),
+ ?INPUTS(<<"password">>, <<"password2">>, <<"">>,
+ <<"20">>)])]
+ ++
+ CaptchaEls ++
+ [?XE(<<"li">>,
+ [?INPUTT(<<"submit">>, <<"register">>,
+ <<"Register">>)])]))])],
{200,
- [{"Server", "ejabberd"},
- {"Content-Type", "text/html"}],
+ [{<<"Server">>, <<"ejabberd">>},
+ {<<"Content-Type">>, <<"text/html">>}],
ejabberd_web:make_xhtml(HeadEls, Els)}.
%% Copied from mod_register.erl
-send_registration_notifications(UJID, Source) ->
- Host = UJID#jid.lserver,
- case gen_mod:get_module_opt(Host, ?MODULE, registration_watchers, []) of
- [] -> ok;
- JIDs when is_list(JIDs) ->
- Body = lists:flatten(
- io_lib:format(
- "[~s] The account ~s was registered from IP address ~s "
- "on node ~w using ~p.",
- [get_time_string(), jlib:jid_to_string(UJID),
- ip_to_string(Source), node(), ?MODULE])),
- lists:foreach(
- fun(S) ->
- case jlib:string_to_jid(S) of
- error -> ok;
- JID ->
- ejabberd_router:route(
- jlib:make_jid("", Host, ""),
- JID,
- {xmlelement, "message", [{"type", "chat"}],
- [{xmlelement, "body", [],
- [{xmlcdata, Body}]}]})
- end
- end, JIDs);
- _ ->
- ok
- end.
-ip_to_string(Source) when is_tuple(Source) -> inet_parse:ntoa(Source);
-ip_to_string(undefined) -> "undefined";
-ip_to_string(_) -> "unknown".
-get_time_string() -> write_time(erlang:localtime()).
%% Function copied from ejabberd_logger_h.erl and customized
-write_time({{Y,Mo,D},{H,Mi,S}}) ->
- io_lib:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
- [Y, Mo, D, H, Mi, S]).
-
%%%----------------------------------------------------------------------
%%% Formulary new POST
%%%----------------------------------------------------------------------
form_new_post(Q, Host) ->
case catch get_register_parameters(Q) of
- [Username, Password, Password, Id, Key] ->
- form_new_post(Username, Host, Password, {Id, Key});
- [_Username, _Password, _Password2, false, false] ->
- {error, passwords_not_identical};
- [_Username, _Password, _Password2, Id, Key] ->
- ejabberd_captcha:check_captcha(Id, Key), %% This deletes the captcha
- {error, passwords_not_identical};
- _ ->
- {error, wrong_parameters}
+ [Username, Password, Password, Id, Key] ->
+ form_new_post(Username, Host, Password, {Id, Key});
+ [_Username, _Password, _Password2, false, false] ->
+ {error, passwords_not_identical};
+ [_Username, _Password, _Password2, Id, Key] ->
+ ejabberd_captcha:check_captcha(Id, Key),
+ {error, passwords_not_identical};
+ _ -> {error, wrong_parameters}
end.
get_register_parameters(Q) ->
- lists:map(
- fun(Key) ->
- case lists:keysearch(Key, 1, Q) of
- {value, {_Key, Value}} -> Value;
- false -> false
- end
- end,
- ["username", "password", "password2", "id", "key"]).
-
-form_new_post(Username, Host, Password, {false, false}) ->
+ lists:map(fun (Key) ->
+ case lists:keysearch(Key, 1, Q) of
+ {value, {_Key, Value}} -> Value;
+ false -> false
+ end
+ end,
+ [<<"username">>, <<"password">>, <<"password2">>,
+ <<"id">>, <<"key">>]).
+
+form_new_post(Username, Host, Password,
+ {false, false}) ->
register_account(Username, Host, Password);
form_new_post(Username, Host, Password, {Id, Key}) ->
case ejabberd_captcha:check_captcha(Id, Key) of
- captcha_valid ->
- register_account(Username, Host, Password);
- captcha_non_valid ->
- {error, captcha_non_valid};
- captcha_not_found ->
- {error, captcha_non_valid}
+ captcha_valid ->
+ register_account(Username, Host, Password);
+ captcha_non_valid -> {error, captcha_non_valid};
+ captcha_not_found -> {error, captcha_non_valid}
end.
%%%----------------------------------------------------------------------
@@ -339,28 +313,26 @@ form_new_post(Username, Host, Password, {Id, Key}) ->
build_captcha_li_list(Lang, IP) ->
case ejabberd_captcha:is_feature_available() of
- true -> build_captcha_li_list2(Lang, IP);
- false -> []
+ true -> build_captcha_li_list2(Lang, IP);
+ false -> []
end.
build_captcha_li_list2(Lang, IP) ->
- SID = "",
- From = #jid{user = "", server = "test", resource = ""},
- To = #jid{user = "", server = "test", resource = ""},
+ SID = <<"">>,
+ From = #jid{user = <<"">>, server = <<"test">>,
+ resource = <<"">>},
+ To = #jid{user = <<"">>, server = <<"test">>,
+ resource = <<"">>},
Args = [],
- case ejabberd_captcha:create_captcha(SID, From, To, Lang, IP, Args) of
- {ok, Id, _} ->
- {_, {CImg,CText,CId,CKey}} =
- ejabberd_captcha:build_captcha_html(Id, Lang),
- [?XE("li", [CText,
- ?C(" "),
- CId,
- CKey,
- ?BR,
- CImg]
- )];
- _ ->
- []
+ case ejabberd_captcha:create_captcha(SID, From, To,
+ Lang, IP, Args)
+ of
+ {ok, Id, _} ->
+ {_, {CImg, CText, CId, CKey}} =
+ ejabberd_captcha:build_captcha_html(Id, Lang),
+ [?XE(<<"li">>,
+ [CText, ?C(<<" ">>), CId, CKey, ?BR, CImg])];
+ _ -> []
end.
%%%----------------------------------------------------------------------
@@ -368,54 +340,42 @@ build_captcha_li_list2(Lang, IP) ->
%%%----------------------------------------------------------------------
form_changepass_get(Host, Lang) ->
- HeadEls = [
- ?XCT("title", "Change Password"),
- ?XA("link",
- [{"href", "/register/register.css"},
- {"type", "text/css"},
- {"rel", "stylesheet"}])
- ],
- Els=[
- ?XACT("h1",
- [{"class", "title"}, {"style", "text-align:center;"}],
- "Change Password"),
- ?XAE("form", [{"action", ""}, {"method", "post"}],
- [
- ?XE("ol", [
- ?XE("li", [
- ?CT("Username:"),
- ?C(" "),
- ?INPUTS("text", "username", "", "20")
- ]),
- ?XE("li", [
- ?CT("Server:"),
- ?C(" "),
- ?C(Host)
- ]),
- ?XE("li", [
- ?CT("Old Password:"),
- ?C(" "),
- ?INPUTS("password", "passwordold", "", "20")
- ]),
- ?XE("li", [
- ?CT("New Password:"),
- ?C(" "),
- ?INPUTS("password", "password", "", "20")
- ]),
- ?XE("li", [
- ?CT("Password Verification:"),
- ?C(" "),
- ?INPUTS("password", "password2", "", "20")
- ]),
- ?XE("li", [
- ?INPUTT("submit", "changepass", "Change Password")
- ])
- ])
- ])
- ],
+ HeadEls = [?XCT(<<"title">>, <<"Change Password">>),
+ ?XA(<<"link">>,
+ [{<<"href">>, <<"/register/register.css">>},
+ {<<"type">>, <<"text/css">>},
+ {<<"rel">>, <<"stylesheet">>}])],
+ Els = [?XACT(<<"h1">>,
+ [{<<"class">>, <<"title">>},
+ {<<"style">>, <<"text-align:center;">>}],
+ <<"Change Password">>),
+ ?XAE(<<"form">>,
+ [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
+ [?XE(<<"ol">>,
+ [?XE(<<"li">>,
+ [?CT(<<"Username:">>), ?C(<<" ">>),
+ ?INPUTS(<<"text">>, <<"username">>, <<"">>,
+ <<"20">>)]),
+ ?XE(<<"li">>,
+ [?CT(<<"Server:">>), ?C(<<" ">>), ?C(Host)]),
+ ?XE(<<"li">>,
+ [?CT(<<"Old Password:">>), ?C(<<" ">>),
+ ?INPUTS(<<"password">>, <<"passwordold">>, <<"">>,
+ <<"20">>)]),
+ ?XE(<<"li">>,
+ [?CT(<<"New Password:">>), ?C(<<" ">>),
+ ?INPUTS(<<"password">>, <<"password">>, <<"">>,
+ <<"20">>)]),
+ ?XE(<<"li">>,
+ [?CT(<<"Password Verification:">>), ?C(<<" ">>),
+ ?INPUTS(<<"password">>, <<"password2">>, <<"">>,
+ <<"20">>)]),
+ ?XE(<<"li">>,
+ [?INPUTT(<<"submit">>, <<"changepass">>,
+ <<"Change Password">>)])])])],
{200,
- [{"Server", "ejabberd"},
- {"Content-Type", "text/html"}],
+ [{<<"Server">>, <<"ejabberd">>},
+ {<<"Content-Type">>, <<"text/html">>}],
ejabberd_web:make_xhtml(HeadEls, Els)}.
%%%----------------------------------------------------------------------
@@ -424,64 +384,60 @@ form_changepass_get(Host, Lang) ->
form_changepass_post(Q, Host) ->
case catch get_changepass_parameters(Q) of
- [Username, PasswordOld, Password, Password] ->
- try_change_password(Username, Host, PasswordOld, Password);
- [_Username, _PasswordOld, _Password, _Password2] ->
- {error, passwords_not_identical};
- _ ->
- {error, wrong_parameters}
+ [Username, PasswordOld, Password, Password] ->
+ try_change_password(Username, Host, PasswordOld,
+ Password);
+ [_Username, _PasswordOld, _Password, _Password2] ->
+ {error, passwords_not_identical};
+ _ -> {error, wrong_parameters}
end.
get_changepass_parameters(Q) ->
- lists:map(
- fun(Key) ->
- {value, {_Key, Value}} = lists:keysearch(Key, 1, Q),
- Value
- end,
- ["username", "passwordold", "password", "password2"]).
-
%% @spec(Username,Host,PasswordOld,Password) -> {atomic, ok} |
%% {error, account_doesnt_exist} |
%% {error, password_not_changed} |
%% {error, password_incorrect}
-try_change_password(Username, Host, PasswordOld, Password) ->
- try change_password(Username, Host, PasswordOld, Password) of
- {atomic, ok} ->
- {atomic, ok}
+ lists:map(fun (Key) ->
+ {value, {_Key, Value}} = lists:keysearch(Key, 1, Q),
+ Value
+ end,
+ [<<"username">>, <<"passwordold">>, <<"password">>,
+ <<"password2">>]).
+
+try_change_password(Username, Host, PasswordOld,
+ Password) ->
+ try change_password(Username, Host, PasswordOld,
+ Password)
+ of
+ {atomic, ok} -> {atomic, ok}
catch
- error:{badmatch, Error} ->
- {error, Error}
+ error:{badmatch, Error} -> {error, Error}
end.
-change_password(Username, Host, PasswordOld, Password) ->
- %% Check the account exists
+change_password(Username, Host, PasswordOld,
+ Password) ->
account_exists = check_account_exists(Username, Host),
-
- %% Check the old password is correct
- password_correct = check_password(Username, Host, PasswordOld),
-
- %% This function always returns: ok
- %% Change the password
- ok = ejabberd_auth:set_password(Username, Host, Password),
-
- %% Check the new password is correct
+ password_correct = check_password(Username, Host,
+ PasswordOld),
+ ok = ejabberd_auth:set_password(Username, Host,
+ Password),
case check_password(Username, Host, Password) of
- password_correct ->
- {atomic, ok};
- password_incorrect ->
- {error, password_not_changed}
+ password_correct -> {atomic, ok};
+ password_incorrect -> {error, password_not_changed}
end.
check_account_exists(Username, Host) ->
case ejabberd_auth:is_user_exists(Username, Host) of
- true -> account_exists;
- false -> account_doesnt_exist
+ true -> account_exists;
+ false -> account_doesnt_exist
end.
check_password(Username, Host, Password) ->
- case ejabberd_auth:check_password(Username, Host, Password) of
- true -> password_correct;
- false -> password_incorrect
+ case ejabberd_auth:check_password(Username, Host,
+ Password)
+ of
+ true -> password_correct;
+ false -> password_incorrect
end.
%%%----------------------------------------------------------------------
@@ -489,46 +445,38 @@ check_password(Username, Host, Password) ->
%%%----------------------------------------------------------------------
form_del_get(Host, Lang) ->
- HeadEls = [
- ?XCT("title", "Unregister a Jabber account"),
- ?XA("link",
- [{"href", "/register/register.css"},
- {"type", "text/css"},
- {"rel", "stylesheet"}])
- ],
- Els=[
- ?XACT("h1",
- [{"class", "title"}, {"style", "text-align:center;"}],
- "Unregister a Jabber account"),
- ?XCT("p",
- "This page allows to unregister a Jabber account in this Jabber server."),
- ?XAE("form", [{"action", ""}, {"method", "post"}],
- [
- ?XE("ol", [
- ?XE("li", [
- ?CT("Username:"),
- ?C(" "),
- ?INPUTS("text", "username", "", "20")
- ]),
- ?XE("li", [
- ?CT("Server:"),
- ?C(" "),
- ?C(Host)
- ]),
- ?XE("li", [
- ?CT("Password:"),
- ?C(" "),
- ?INPUTS("password", "password", "", "20")
- ]),
- ?XE("li", [
- ?INPUTT("submit", "unregister", "Unregister")
- ])
- ])
- ])
- ],
+ HeadEls = [?XCT(<<"title">>,
+ <<"Unregister a Jabber account">>),
+ ?XA(<<"link">>,
+ [{<<"href">>, <<"/register/register.css">>},
+ {<<"type">>, <<"text/css">>},
+ {<<"rel">>, <<"stylesheet">>}])],
+ Els = [?XACT(<<"h1">>,
+ [{<<"class">>, <<"title">>},
+ {<<"style">>, <<"text-align:center;">>}],
+ <<"Unregister a Jabber account">>),
+ ?XCT(<<"p">>,
+ <<"This page allows to unregister a Jabber "
+ "account in this Jabber server.">>),
+ ?XAE(<<"form">>,
+ [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
+ [?XE(<<"ol">>,
+ [?XE(<<"li">>,
+ [?CT(<<"Username:">>), ?C(<<" ">>),
+ ?INPUTS(<<"text">>, <<"username">>, <<"">>,
+ <<"20">>)]),
+ ?XE(<<"li">>,
+ [?CT(<<"Server:">>), ?C(<<" ">>), ?C(Host)]),
+ ?XE(<<"li">>,
+ [?CT(<<"Password:">>), ?C(<<" ">>),
+ ?INPUTS(<<"password">>, <<"password">>, <<"">>,
+ <<"20">>)]),
+ ?XE(<<"li">>,
+ [?INPUTT(<<"submit">>, <<"unregister">>,
+ <<"Unregister">>)])])])],
{200,
- [{"Server", "ejabberd"},
- {"Content-Type", "text/html"}],
+ [{<<"Server">>, <<"ejabberd">>},
+ {<<"Content-Type">>, <<"text/html">>}],
ejabberd_web:make_xhtml(HeadEls, Els)}.
%% @spec(Username, Host, Password) -> {success, ok, {Username, Host, Password} |
@@ -536,16 +484,18 @@ form_del_get(Host, Lang) ->
%% {error, not_allowed} |
%% {error, invalid_jid}
register_account(Username, Host, Password) ->
- case jlib:make_jid(Username, Host, "") of
- error -> {error, invalid_jid};
- _ -> register_account2(Username, Host, Password)
+ case jlib:make_jid(Username, Host, <<"">>) of
+ error -> {error, invalid_jid};
+ _ -> register_account2(Username, Host, Password)
end.
+
register_account2(Username, Host, Password) ->
- case ejabberd_auth:try_register(Username, Host, Password) of
- {atomic, Res} ->
- {success, Res, {Username, Host, Password}};
- Other ->
- Other
+ case ejabberd_auth:try_register(Username, Host,
+ Password)
+ of
+ {atomic, Res} ->
+ {success, Res, {Username, Host, Password}};
+ Other -> Other
end.
%%%----------------------------------------------------------------------
@@ -554,47 +504,37 @@ register_account2(Username, Host, Password) ->
form_del_post(Q, Host) ->
case catch get_unregister_parameters(Q) of
- [Username, Password] ->
- try_unregister_account(Username, Host, Password);
- _ ->
- {error, wrong_parameters}
+ [Username, Password] ->
+ try_unregister_account(Username, Host, Password);
+ _ -> {error, wrong_parameters}
end.
get_unregister_parameters(Q) ->
- lists:map(
- fun(Key) ->
- {value, {_Key, Value}} = lists:keysearch(Key, 1, Q),
- Value
- end,
- ["username", "password"]).
-
%% @spec(Username, Host, Password) -> {atomic, ok} |
%% {error, account_doesnt_exist} |
%% {error, account_exists} |
%% {error, password_incorrect}
+ lists:map(fun (Key) ->
+ {value, {_Key, Value}} = lists:keysearch(Key, 1, Q),
+ Value
+ end,
+ [<<"username">>, <<"password">>]).
+
try_unregister_account(Username, Host, Password) ->
try unregister_account(Username, Host, Password) of
- {atomic, ok} ->
- {atomic, ok}
+ {atomic, ok} -> {atomic, ok}
catch
- error:{badmatch, Error} ->
- {error, Error}
+ error:{badmatch, Error} -> {error, Error}
end.
unregister_account(Username, Host, Password) ->
- %% Check the account exists
account_exists = check_account_exists(Username, Host),
-
- %% Check the password is correct
- password_correct = check_password(Username, Host, Password),
-
- %% This function always returns: ok
- ok = ejabberd_auth:remove_user(Username, Host, Password),
-
- %% Check the account does not exist anymore
- account_doesnt_exist = check_account_exists(Username, Host),
-
- %% If we reached this point, return success
+ password_correct = check_password(Username, Host,
+ Password),
+ ok = ejabberd_auth:remove_user(Username, Host,
+ Password),
+ account_doesnt_exist = check_account_exists(Username,
+ Host),
{atomic, ok}.
%%%----------------------------------------------------------------------
@@ -602,24 +542,24 @@ unregister_account(Username, Host, Password) ->
%%%----------------------------------------------------------------------
get_error_text({error, captcha_non_valid}) ->
- "The captcha you entered is wrong";
+ <<"The captcha you entered is wrong">>;
get_error_text({success, exists, _}) ->
get_error_text({atomic, exists});
get_error_text({atomic, exists}) ->
- "The account already exists";
+ <<"The account already exists">>;
get_error_text({error, password_incorrect}) ->
- "Incorrect password";
+ <<"Incorrect password">>;
get_error_text({error, invalid_jid}) ->
- "The username is not valid";
+ <<"The username is not valid">>;
get_error_text({error, not_allowed}) ->
- "Not allowed";
+ <<"Not allowed">>;
get_error_text({error, account_doesnt_exist}) ->
- "Account doesn't exist";
+ <<"Account doesn't exist">>;
get_error_text({error, account_exists}) ->
- "The account was not deleted";
+ <<"The account was not deleted">>;
get_error_text({error, password_not_changed}) ->
- "The password was not changed";
+ <<"The password was not changed">>;
get_error_text({error, passwords_not_identical}) ->
- "The passwords are different";
+ <<"The passwords are different">>;
get_error_text({error, wrong_parameters}) ->
- "Wrong parameters in the web formulary".
+ <<"Wrong parameters in the web formulary">>.
diff --git a/src/xml.c b/src/xml.c
index e363fed74..0a600e9f0 100644
--- a/src/xml.c
+++ b/src/xml.c
@@ -29,7 +29,7 @@ static int make_element(ErlNifEnv* env, struct buf *rbuf, ERL_NIF_TERM el);
static int load(ErlNifEnv* env, void** priv, ERL_NIF_TERM load_info)
{
- atom_xmlelement = enif_make_atom(env, "xmlelement");
+ atom_xmlelement = enif_make_atom(env, "xmlel");
atom_xmlcdata = enif_make_atom(env, "xmlcdata");
return 0;
}
@@ -57,10 +57,11 @@ inline void resize_buf(ErlNifEnv* env, struct buf *rbuf, int len_to_add)
{
int new_len = rbuf->len + len_to_add;
- if (new_len >= rbuf->limit) {
- rbuf->limit = ((new_len / 1024) + 1) * 1024;
- rbuf->b = ENIF_REALLOC(rbuf->b, rbuf->limit);
- };
+ if (new_len > rbuf->limit) {
+ while (new_len > rbuf->limit)
+ rbuf->limit *= 2;
+ rbuf->b = ENIF_REALLOC(rbuf->b, rbuf->limit);
+ }
}
static void buf_add_char(ErlNifEnv* env, struct buf *rbuf, unsigned char c)
diff --git a/src/xml.erl b/src/xml.erl
index 6e126240c..7cd3acdfb 100644
--- a/src/xml.erl
+++ b/src/xml.erl
@@ -25,110 +25,127 @@
%%%----------------------------------------------------------------------
-module(xml).
+
-author('alexey@process-one.net').
--export([element_to_string/1,
- element_to_binary/1,
- crypt/1, make_text_node/1,
- remove_cdata/1,
- get_cdata/1, get_tag_cdata/1,
- get_attr/2, get_attr_s/2,
- get_tag_attr/2, get_tag_attr_s/2,
- get_subtag/2, get_subtag_cdata/2,
- append_subtags/2,
- get_path_s/2,
- start/0,
- replace_tag_attr/3]).
+-export([element_to_binary/1,
+ crypt/1, make_text_node/1, remove_cdata/1,
+ remove_subtags/3, get_cdata/1, get_tag_cdata/1,
+ get_attr/2, get_attr_s/2, get_tag_attr/2,
+ get_tag_attr_s/2, get_subtag/2, get_subtag_cdata/2,
+ append_subtags/2, get_path_s/2, start/0,
+ replace_tag_attr/3, to_xmlel/1]).
+-include("jlib.hrl").
-include("ejabberd.hrl").
%% Select at compile time how to escape characters in binary text
%% nodes.
%% Can be choosen with ./configure --enable-full-xml
-ifdef(FULL_XML_SUPPORT).
+
-define(ESCAPE_BINARY(CData), make_text_node(CData)).
+
-else.
+
-define(ESCAPE_BINARY(CData), crypt(CData)).
+
-endif.
%% Replace element_to_binary/1 with NIF
%% Can be choosen with ./configure --enable-nif
-ifdef(NIF).
+
start() ->
SOPath = filename:join(ejabberd:get_so_path(), "xml"),
case catch erlang:load_nif(SOPath, 0) of
- ok ->
- ok;
- Err ->
- ?WARNING_MSG("unable to load xml NIF: ~p", [Err])
+ ok -> ok;
+ Err -> ?WARNING_MSG("unable to load xml NIF: ~p", [Err])
end.
+
-else.
-start() ->
- ok.
+
+start() -> ok.
+
-endif.
+%%
+-spec(element_to_binary/1 ::
+(
+ El :: xmlel() | cdata())
+ -> binary()
+).
+
element_to_binary(El) ->
iolist_to_binary(element_to_string(El)).
+%%
+-spec(element_to_string/1 ::
+(
+ El :: xmlel() | cdata())
+ -> string()
+).
+
element_to_string(El) ->
case catch element_to_string_nocatch(El) of
- {'EXIT', Reason} ->
- erlang:error({badxml, El, Reason});
- Result ->
- Result
+ {'EXIT', Reason} -> erlang:error({badxml, El, Reason});
+ Result -> Result
end.
+-spec(element_to_string_nocatch/1 ::
+(
+ El :: xmlel() | cdata())
+ -> iolist()
+).
+
element_to_string_nocatch(El) ->
case El of
- {xmlelement, Name, Attrs, Els} ->
- if
- Els /= [] ->
- [$<, Name, attrs_to_list(Attrs), $>,
- [element_to_string_nocatch(E) || E <- Els],
- $<, $/, Name, $>];
- true ->
- [$<, Name, attrs_to_list(Attrs), $/, $>]
- end;
- %% We do not crypt CDATA binary, but we enclose it in XML CDATA
- {xmlcdata, CData} when is_binary(CData) ->
- ?ESCAPE_BINARY(CData);
- %% We crypt list and possibly binaries if full XML usage is
- %% disabled unsupported (implies a conversion to list).
- {xmlcdata, CData} ->
- crypt(CData)
+ #xmlel{name = Name, attrs = Attrs, children = Els} ->
+ if Els /= [] ->
+ [$<, Name, attrs_to_list(Attrs), $>,
+ [element_to_string_nocatch(E) || E <- Els], $<, $/,
+ Name, $>];
+ true -> [$<, Name, attrs_to_list(Attrs), $/, $>]
+ end;
+ %% We do not crypt CDATA binary, but we enclose it in XML CDATA
+ {xmlcdata, CData} ->
+ ?ESCAPE_BINARY(CData)
end.
-attrs_to_list(Attrs) ->
- [attr_to_list(A) || A <- Attrs].
+attrs_to_list(Attrs) -> [attr_to_list(A) || A <- Attrs].
attr_to_list({Name, Value}) ->
[$\s, Name, $=, $', crypt(Value), $'].
-crypt(S) when is_list(S) ->
- [case C of
- $& -> "&amp;";
- $< -> "&lt;";
- $> -> "&gt;";
- $" -> "&quot;";
- $' -> "&apos;";
- _ when is_list(C); is_binary(C) -> crypt(C);
- _ -> C
- end || C <- S];
-crypt(S) when is_binary(S) ->
- crypt(binary_to_list(S)).
-
%% Make a cdata_binary depending on what characters it contains
+crypt(S) ->
+ << <<(case C of
+ $& -> <<"&amp;">>;
+ $< -> <<"&lt;">>;
+ $> -> <<"&gt;">>;
+ $" -> <<"&quot;">>;
+ $' -> <<"&apos;">>;
+ _ -> <<C>>
+ end)/binary>>
+ || <<C>> <= S >>.
+
+%%
+-spec(make_text_node/1 ::
+(
+ CData :: binary())
+ -> binary()
+).
+
make_text_node(CData) ->
case cdata_need_escape(CData) of
- cdata ->
- CDATA1 = <<"<![CDATA[">>,
- CDATA2 = <<"]]>">>,
- list_to_binary([CDATA1, CData, CDATA2]);
- none ->
- CData;
- {cdata, EndTokens} ->
- EscapedCData = escape_cdata(CData, EndTokens),
- list_to_binary(EscapedCData)
+ cdata ->
+ CDATA1 = <<"<![CDATA[">>,
+ CDATA2 = <<"]]>">>,
+ iolist_to_binary([CDATA1, CData, CDATA2]);
+ none -> CData;
+ {cdata, EndTokens} ->
+ EscapedCData = escape_cdata(CData, EndTokens),
+ iolist_to_binary(EscapedCData)
end.
%% Returns escape type needed for the text node
@@ -137,134 +154,289 @@ make_text_node(CData) ->
%% tokens, so that they can be escaped
cdata_need_escape(CData) ->
cdata_need_escape(CData, 0, false, []).
-cdata_need_escape(<<>>, _, false, _) ->
- none;
-cdata_need_escape(<<>>, _, true, []) ->
- cdata;
+
+cdata_need_escape(<<>>, _, false, _) -> none;
+cdata_need_escape(<<>>, _, true, []) -> cdata;
cdata_need_escape(<<>>, _, true, CDataEndTokens) ->
{cdata, lists:reverse(CDataEndTokens)};
-cdata_need_escape(<<$],$],$>,Rest/binary>>, CurrentPosition,
- _XMLEscape, CDataEndTokens) ->
+cdata_need_escape(<<$], $], $>, Rest/binary>>,
+ CurrentPosition, _XMLEscape, CDataEndTokens) ->
NewPosition = CurrentPosition + 3,
cdata_need_escape(Rest, NewPosition, true,
- [CurrentPosition+1|CDataEndTokens]);
+ [CurrentPosition + 1 | CDataEndTokens]);
%% Only <, & need to be escaped in XML text node
%% See reference: http://www.w3.org/TR/xml11/#syntax
-cdata_need_escape(<<$<,Rest/binary>>, CurrentPosition,
- _XMLEscape, CDataEndTokens) ->
- cdata_need_escape(Rest, CurrentPosition+1, true, CDataEndTokens);
-cdata_need_escape(<<$&,Rest/binary>>, CurrentPosition,
- _XMLEscape, CDataEndTokens) ->
- cdata_need_escape(Rest, CurrentPosition+1, true, CDataEndTokens);
-cdata_need_escape(<<_:8,Rest/binary>>, CurrentPosition,
+cdata_need_escape(<<$<, Rest/binary>>, CurrentPosition,
+ _XMLEscape, CDataEndTokens) ->
+ cdata_need_escape(Rest, CurrentPosition + 1, true,
+ CDataEndTokens);
+cdata_need_escape(<<$&, Rest/binary>>, CurrentPosition,
+ _XMLEscape, CDataEndTokens) ->
+ cdata_need_escape(Rest, CurrentPosition + 1, true,
+ CDataEndTokens);
+cdata_need_escape(<<_:8, Rest/binary>>, CurrentPosition,
XMLEscape, CDataEndTokens) ->
- cdata_need_escape(Rest, CurrentPosition+1, XMLEscape,
- CDataEndTokens).
-
%% escape cdata that contain CDATA end tokens
%% EndTokens is a list of position of end tokens (integer)
%% This is supposed to be a very rare case: You need to generate several
%% fields, splitting it in the middle of the end token.
%% See example: http://en.wikipedia.org/wiki/CDATA#Uses_of_CDATA_sections
+ cdata_need_escape(Rest, CurrentPosition + 1, XMLEscape,
+ CDataEndTokens).
+
escape_cdata(CData, EndTokens) ->
escape_cdata(CData, 0, EndTokens, []).
+
escape_cdata(<<>>, _CurrentPosition, [], Acc) ->
lists:reverse(Acc);
escape_cdata(Rest, CurrentPosition, [], Acc) ->
CDATA1 = <<"<![CDATA[">>,
CDATA2 = <<"]]>">>,
- escape_cdata(<<>>, CurrentPosition, [], [CDATA2, Rest, CDATA1|Acc]);
-escape_cdata(CData, Index, [Pos|Positions], Acc) ->
+ escape_cdata(<<>>, CurrentPosition, [],
+ [CDATA2, Rest, CDATA1 | Acc]);
+escape_cdata(CData, Index, [Pos | Positions], Acc) ->
CDATA1 = <<"<![CDATA[">>,
CDATA2 = <<"]]>">>,
- Split = Pos-Index,
- {Part, Rest} = split_binary(CData, Split+1),
- %% Note: We build the list in reverse to optimize construction
- escape_cdata(Rest, Pos+1, Positions, [CDATA2, Part, CDATA1|Acc]).
-
-remove_cdata_p({xmlelement, _Name, _Attrs, _Els}) -> true;
+ Split = Pos - Index,
+ {Part, Rest} = split_binary(CData, Split + 1),
+ escape_cdata(Rest, Pos + 1, Positions,
+ [CDATA2, Part, CDATA1 | Acc]).
+
+%%
+-spec(remove_cdata_p/1 ::
+(
+ El :: xmlel() | cdata())
+ -> boolean()
+).
+
+remove_cdata_p(#xmlel{}) -> true;
remove_cdata_p(_) -> false.
+%%
+-spec(remove_cdata/1 ::
+(
+ L :: [xmlel() | cdata()])
+ -> [xmlel()]
+).
+
remove_cdata(L) -> [E || E <- L, remove_cdata_p(E)].
+-spec(remove_subtags/3 ::
+(
+ Xmlel :: xmlel(),
+ Name :: binary(),
+ Attr :: attr())
+ -> Xmlel :: xmlel()
+).
+
+remove_subtags(#xmlel{name = TagName, attrs = TagAttrs, children = Els},
+ Name, Attr) ->
+ #xmlel{name = TagName, attrs = TagAttrs,
+ children = remove_subtags1(Els, [], Name, Attr)}.
+
+%%
+-spec(remove_subtags1/4 ::
+(
+ Els :: [xmlel() | cdata()],
+ NewEls :: [xmlel()],
+ Name :: binary(),
+ Attr :: attr())
+ -> NewEls :: [xmlel()]
+).
+
+remove_subtags1([], NewEls, _Name, _Attr) ->
+ lists:reverse(NewEls);
+remove_subtags1([El | Els], NewEls, Name,
+ {AttrName, AttrValue} = Attr) ->
+ case El of
+ #xmlel{name = Name, attrs = Attrs} ->
+ case get_attr(AttrName, Attrs) of
+ false ->
+ remove_subtags1(Els, [El | NewEls], Name, Attr);
+ {value, AttrValue} ->
+ remove_subtags1(Els, NewEls, Name, Attr);
+ _ -> remove_subtags1(Els, [El | NewEls], Name, Attr)
+ end;
+ _ -> remove_subtags1(Els, [El | NewEls], Name, Attr)
+ end.
+
+-spec(get_cdata/1 ::
+(
+ L :: [xmlel() | cdata()])
+ -> binary()
+).
+
get_cdata(L) ->
- binary_to_list(list_to_binary(get_cdata(L, ""))).
+ (iolist_to_binary(get_cdata(L, <<"">>))).
-get_cdata([{xmlcdata, CData} | L], S) ->
- get_cdata(L, [S, CData]);
-get_cdata([_ | L], S) ->
- get_cdata(L, S);
-get_cdata([], S) ->
- S.
+-spec(get_cdata/2 ::
+(
+ L :: [xmlel() | cdata()],
+ S :: binary() | iolist())
+ -> binary() | iolist()
+).
-get_tag_cdata({xmlelement, _Name, _Attrs, Els}) ->
- get_cdata(Els).
+get_cdata([{xmlcdata, CData} | L], S) ->
+ get_cdata(L, [S, CData]);
+get_cdata([_ | L], S) -> get_cdata(L, S);
+get_cdata([], S) -> S.
+
+-spec(get_tag_cdata/1 ::
+(
+ Xmlel :: xmlel())
+ -> binary()
+).
+
+get_tag_cdata(#xmlel{children = Els}) -> get_cdata(Els).
+
+%%
+-spec(get_attr/2 ::
+(
+ AttrName :: binary(),
+ Attrs :: [attr()])
+ -> {value, binary()}
+ | false
+).
get_attr(AttrName, Attrs) ->
case lists:keysearch(AttrName, 1, Attrs) of
- {value, {_, Val}} ->
- {value, Val};
- _ ->
- false
+ {value, {_, Val}} -> {value, Val};
+ _ -> false
end.
+%%
+-spec(get_attr_s/2 ::
+(
+ AttrName :: binary(),
+ Attrs :: [attr()])
+ -> Val :: binary()
+).
+
get_attr_s(AttrName, Attrs) ->
case lists:keysearch(AttrName, 1, Attrs) of
- {value, {_, Val}} ->
- Val;
- _ ->
- ""
+ {value, {_, Val}} -> Val;
+ _ -> <<"">>
end.
-get_tag_attr(AttrName, {xmlelement, _Name, Attrs, _Els}) ->
+%%
+-spec(get_tag_attr/2 ::
+(
+ AttrName :: binary(),
+ Xmlel :: xmlel())
+ -> {value, binary()}
+ | false
+).
+
+get_tag_attr(AttrName, #xmlel{attrs = Attrs}) ->
get_attr(AttrName, Attrs).
-get_tag_attr_s(AttrName, {xmlelement, _Name, Attrs, _Els}) ->
+%%
+-spec(get_tag_attr_s/2 ::
+(
+ AttrName :: binary(),
+ Xmlel :: xmlel())
+ -> binary()
+).
+
+get_tag_attr_s(AttrName, #xmlel{attrs = Attrs}) ->
get_attr_s(AttrName, Attrs).
+%%
+-spec(get_subtag/2 ::
+(
+ Xmlel :: xmlel(),
+ Name :: binary())
+ -> xmlel() | false
+).
-get_subtag({xmlelement, _, _, Els}, Name) ->
+get_subtag(#xmlel{children = Els}, Name) ->
get_subtag1(Els, Name).
+%%
+-spec(get_subtag1/2 ::
+(
+ Els :: [xmlel() | cdata()],
+ Name :: binary())
+ -> xmlel() | false
+).
+
get_subtag1([El | Els], Name) ->
case El of
- {xmlelement, Name, _, _} ->
- El;
- _ ->
- get_subtag1(Els, Name)
+ #xmlel{name = Name} -> El;
+ _ -> get_subtag1(Els, Name)
end;
-get_subtag1([], _) ->
- false.
+get_subtag1([], _) -> false.
+
+%%
+-spec(get_subtag_cdata/2 ::
+(
+ Tag :: xmlel(),
+ Name :: binary())
+ -> binary()
+).
get_subtag_cdata(Tag, Name) ->
case get_subtag(Tag, Name) of
- false ->
- "";
- Subtag ->
- get_tag_cdata(Subtag)
+ false -> <<"">>;
+ Subtag -> get_tag_cdata(Subtag)
end.
-append_subtags({xmlelement, Name, Attrs, SubTags1}, SubTags2) ->
- {xmlelement, Name, Attrs, SubTags1 ++ SubTags2}.
-
-get_path_s(El, []) ->
- El;
+%%
+-spec(append_subtags/2 ::
+(
+ Xmlel :: xmlel(),
+ SubTags2 :: [xmlel() | cdata()])
+ -> Xmlel :: xmlel()
+).
+
+append_subtags(#xmlel{name = Name, attrs = Attrs, children = SubTags1}, SubTags2) ->
+ #xmlel{name = Name, attrs = Attrs, children = SubTags1 ++ SubTags2}.
+
+%%
+-spec(get_path_s/2 ::
+(
+ El :: xmlel(),
+ Path :: [{elem, Name::binary()}
+ |{attr, Name::binary()}
+ |cdata])
+ -> xmlel()
+ | binary()
+).
+
+get_path_s(El, []) -> El;
get_path_s(El, [{elem, Name} | Path]) ->
case get_subtag(El, Name) of
- false ->
- "";
- SubEl ->
- get_path_s(SubEl, Path)
+ false -> <<"">>;
+ SubEl -> get_path_s(SubEl, Path)
end;
get_path_s(El, [{attr, Name}]) ->
get_tag_attr_s(Name, El);
-get_path_s(El, [cdata]) ->
- get_tag_cdata(El).
-
-
-replace_tag_attr(Attr, Value, {xmlelement, Name, Attrs, Els}) ->
- Attrs1 = lists:keydelete(Attr, 1, Attrs),
- Attrs2 = [{Attr, Value} | Attrs1],
- {xmlelement, Name, Attrs2, Els}.
-
-
+get_path_s(El, [cdata]) -> get_tag_cdata(El).
+
+%%
+-spec(replace_tag_attr/3 ::
+(
+ Name :: binary(),
+ Value :: binary(),
+ Xmlel :: xmlel())
+ -> Xmlel :: #xmlel{
+ name :: binary(),
+ attrs :: [attr(),...],
+ children :: [xmlel() | cdata()]
+ }
+).
+
+replace_tag_attr(Name, Value, Xmlel) ->
+ Xmlel#xmlel{
+ attrs = [{Name, Value} | lists:keydelete(Name, 1, Xmlel#xmlel.attrs)]
+ }.
+
+-spec to_xmlel(xmlelement() | xmlel()) -> xmlel().
+
+to_xmlel({_, Name, Attrs, Els}) ->
+ #xmlel{name = iolist_to_binary(Name),
+ attrs = [{iolist_to_binary(K), iolist_to_binary(V)}
+ || {K, V} <- Attrs],
+ children = [to_xmlel(El) || El <- Els]};
+to_xmlel({xmlcdata, CData}) ->
+ {xmlcdata, iolist_to_binary(CData)}.
diff --git a/src/xml_stream.erl b/src/xml_stream.erl
index e1eb7287c..17244aff0 100644
--- a/src/xml_stream.erl
+++ b/src/xml_stream.erl
@@ -25,23 +25,48 @@
%%%----------------------------------------------------------------------
-module(xml_stream).
+
-author('alexey@process-one.net').
--export([new/1,
- new/2,
- parse/2,
- close/1,
+-export([new/1, new/2, parse/2, close/1,
parse_element/1]).
-define(XML_START, 0).
--define(XML_END, 1).
+
+-define(XML_END, 1).
+
-define(XML_CDATA, 2).
+
-define(XML_ERROR, 3).
-define(PARSE_COMMAND, 0).
+
-define(PARSE_FINAL_COMMAND, 1).
--record(xml_stream_state, {callback_pid, port, stack, size, maxsize}).
+-record(xml_stream_state,
+ {callback_pid = self() :: pid(),
+ port :: port(),
+ stack = [] :: stack(),
+ size = 0 :: non_neg_integer(),
+ maxsize = infinity :: non_neg_integer() | infinity}).
+
+-type xml_stream_el() :: {xmlstreamraw, binary()} |
+ {xmlstreamcdata, binary()} |
+ {xmlstreamelement, xmlel()} |
+ {xmlstreamend, binary()} |
+ {xmlstreamstart, binary(), [attr()]} |
+ {xmlstreamerror, binary()}.
+
+-type xml_stream_state() :: #xml_stream_state{}.
+-type stack() :: [xmlel()].
+-type event() :: {?XML_START, {binary(), [attr()]}} |
+ {?XML_END, binary()} |
+ {?XML_CDATA, binary()} |
+ {?XML_ERROR, binary()}.
+
+-export_type([xml_stream_state/0, xml_stream_el/0]).
+
+-include("jlib.hrl").
process_data(CallbackPid, Stack, Data) ->
case Data of
@@ -55,7 +80,7 @@ process_data(CallbackPid, Stack, Data) ->
%% anymore.
[xmlstreamstart];
true ->
- [{xmlelement, Name, Attrs, []} | Stack]
+ [#xmlel{name = Name, attrs = Attrs, children = []} | Stack]
end;
{?XML_END, EndName} ->
case Stack of
@@ -63,14 +88,15 @@ process_data(CallbackPid, Stack, Data) ->
catch gen_fsm:send_event(CallbackPid,
{xmlstreamend, EndName}),
[];
- [{xmlelement, Name, Attrs, Els}, xmlstreamstart] ->
- NewEl = {xmlelement, Name, Attrs, lists:reverse(Els)},
+ [#xmlel{name = Name, attrs = Attrs, children = Els}, xmlstreamstart] ->
+ NewEl = #xmlel{name = Name, attrs = Attrs, children = lists:reverse(Els)},
catch gen_fsm:send_event(CallbackPid,
{xmlstreamelement, NewEl}),
[xmlstreamstart];
- [{xmlelement, Name, Attrs, Els}, {xmlelement, Name1, Attrs1, Els1} | Tail] ->
- NewEl = {xmlelement, Name, Attrs, lists:reverse(Els)},
- [{xmlelement, Name1, Attrs1, [NewEl | Els1]} | Tail]
+ [#xmlel{name = Name, attrs = Attrs, children = Els},
+ #xmlel{name = Name1, attrs = Attrs1, children = Els1} | Tail] ->
+ NewEl = #xmlel{name = Name, attrs = Attrs, children = lists:reverse(Els)},
+ [#xmlel{name = Name1, attrs = Attrs1, children = [NewEl | Els1]} | Tail]
end;
{?XML_CDATA, CData} ->
case Stack of
@@ -80,64 +106,75 @@ process_data(CallbackPid, Stack, Data) ->
%% This does not change the semantic: the split in
%% several CDATA nodes depends on the TCP/IP packet
%% fragmentation
- [{xmlelement, Name, Attrs,
- [{xmlcdata, PreviousCData}|Els]} | Tail] ->
- [{xmlelement, Name, Attrs,
- [{xmlcdata, list_to_binary([PreviousCData, CData])} | Els]} | Tail];
+ [#xmlel{name = Name, attrs = Attrs,
+ children = [{xmlcdata, PreviousCData} | Els]}
+ | Tail] ->
+ [#xmlel{name = Name, attrs = Attrs,
+ children =
+ [{xmlcdata,
+ iolist_to_binary([PreviousCData, CData])}
+ | Els]}
+ | Tail];
%% No previous CDATA
- [{xmlelement, Name, Attrs, Els} | Tail] ->
- [{xmlelement, Name, Attrs, [{xmlcdata, CData} | Els]} |
- Tail];
+ [#xmlel{name = Name, attrs = Attrs, children = Els}
+ | Tail] ->
+ [#xmlel{name = Name, attrs = Attrs,
+ children = [{xmlcdata, CData} | Els]}
+ | Tail];
[] -> []
end;
{?XML_ERROR, Err} ->
catch gen_fsm:send_event(CallbackPid, {xmlstreamerror, Err})
end.
+-spec new(pid()) -> xml_stream_state().
+
+new(CallbackPid) -> new(CallbackPid, infinity).
-new(CallbackPid) ->
- new(CallbackPid, infinity).
+-spec new(pid(), non_neg_integer() | infinity) -> xml_stream_state().
new(CallbackPid, MaxSize) ->
Port = open_port({spawn, "expat_erl"}, [binary]),
#xml_stream_state{callback_pid = CallbackPid,
- port = Port,
- stack = [],
- size = 0,
- maxsize = MaxSize}.
+ port = Port, stack = [], size = 0, maxsize = MaxSize}.
+-spec parse(xml_stream_state(), iodata()) -> xml_stream_state().
parse(#xml_stream_state{callback_pid = CallbackPid,
- port = Port,
- stack = Stack,
- size = Size,
- maxsize = MaxSize} = State, Str) ->
- StrSize = if
- is_list(Str) -> length(Str);
- is_binary(Str) -> size(Str)
- end,
+ port = Port, stack = Stack, size = Size,
+ maxsize = MaxSize} =
+ State,
+ Str) ->
+ StrSize = byte_size(Str),
Res = port_control(Port, ?PARSE_COMMAND, Str),
- {NewStack, NewSize} =
- lists:foldl(
- fun(Data, {St, Sz}) ->
- NewSt = process_data(CallbackPid, St, Data),
- case NewSt of
- [_] -> {NewSt, 0};
- _ -> {NewSt, Sz}
- end
- end, {Stack, Size + StrSize}, binary_to_term(Res)),
- if
- NewSize > MaxSize ->
- catch gen_fsm:send_event(CallbackPid,
- {xmlstreamerror, "XML stanza is too big"});
- true ->
- ok
+ {NewStack, NewSize} = lists:foldl(fun (Data,
+ {St, Sz}) ->
+ NewSt = process_data(CallbackPid,
+ St, Data),
+ case NewSt of
+ [_] -> {NewSt, 0};
+ _ -> {NewSt, Sz}
+ end
+ end,
+ {Stack, Size + StrSize},
+ binary_to_term(Res)),
+ if NewSize > MaxSize ->
+ catch gen_fsm:send_event(CallbackPid,
+ {xmlstreamerror,
+ <<"XML stanza is too big">>});
+ true -> ok
end,
- State#xml_stream_state{stack = NewStack, size = NewSize}.
+ State#xml_stream_state{stack = NewStack,
+ size = NewSize}.
+
+-spec close(xml_stream_state()) -> true.
close(#xml_stream_state{port = Port}) ->
port_close(Port).
+-spec parse_element(iodata()) -> xmlel() |
+ {error, parse_error} |
+ {error, binary()}.
parse_element(Str) ->
Port = open_port({spawn, "expat_erl"}, [binary]),
@@ -148,42 +185,49 @@ parse_element(Str) ->
process_element_events(Events) ->
process_element_events(Events, []).
+-spec process_element_events([event()], stack()) -> xmlel() |
+ {error, parse_error} |
+ {error, binary()}.
+
process_element_events([], _Stack) ->
{error, parse_error};
process_element_events([Event | Events], Stack) ->
case Event of
- {?XML_START, {Name, Attrs}} ->
- process_element_events(
- Events, [{xmlelement, Name, Attrs, []} | Stack]);
- {?XML_END, _EndName} ->
- case Stack of
- [{xmlelement, Name, Attrs, Els} | Tail] ->
- NewEl = {xmlelement, Name, Attrs, lists:reverse(Els)},
- case Tail of
- [] ->
- if
- Events == [] ->
- NewEl;
- true ->
- {error, parse_error}
- end;
- [{xmlelement, Name1, Attrs1, Els1} | Tail1] ->
- process_element_events(
- Events,
- [{xmlelement, Name1, Attrs1, [NewEl | Els1]} |
- Tail1])
- end
- end;
- {?XML_CDATA, CData} ->
- case Stack of
- [{xmlelement, Name, Attrs, Els} | Tail] ->
- process_element_events(
- Events,
- [{xmlelement, Name, Attrs, [{xmlcdata, CData} | Els]} |
- Tail]);
- [] ->
- process_element_events(Events, [])
- end;
- {?XML_ERROR, Err} ->
- {error, Err}
+ {?XML_START, {Name, Attrs}} ->
+ process_element_events(Events,
+ [#xmlel{name = Name, attrs = Attrs,
+ children = []}
+ | Stack]);
+ {?XML_END, _EndName} ->
+ case Stack of
+ [#xmlel{name = Name, attrs = Attrs, children = Els}
+ | Tail] ->
+ NewEl = #xmlel{name = Name, attrs = Attrs,
+ children = lists:reverse(Els)},
+ case Tail of
+ [] ->
+ if Events == [] -> NewEl;
+ true -> {error, parse_error}
+ end;
+ [#xmlel{name = Name1, attrs = Attrs1, children = Els1}
+ | Tail1] ->
+ process_element_events(Events,
+ [#xmlel{name = Name1,
+ attrs = Attrs1,
+ children = [NewEl | Els1]}
+ | Tail1])
+ end
+ end;
+ {?XML_CDATA, CData} ->
+ case Stack of
+ [#xmlel{name = Name, attrs = Attrs, children = Els}
+ | Tail] ->
+ process_element_events(Events,
+ [#xmlel{name = Name, attrs = Attrs,
+ children =
+ [{xmlcdata, CData} | Els]}
+ | Tail]);
+ [] -> process_element_events(Events, [])
+ end;
+ {?XML_ERROR, Err} -> {error, Err}
end.