25
1
mirror of https://github.com/processone/ejabberd.git synced 2024-12-22 17:28:25 +01:00

Rewrite mod_mam and mod_muc to use XML generator

This commit is contained in:
Evgeniy Khramtsov 2016-07-25 13:50:30 +03:00
parent 5d90292849
commit 179fcd9521
17 changed files with 4871 additions and 4232 deletions

View File

@ -71,6 +71,7 @@
-type config() :: #config{}.
-type role() :: moderator | participant | visitor | none.
-type affiliation() :: admin | member | outcast | owner | none.
-record(user,
{
@ -120,5 +121,3 @@
host = <<>> :: binary() | '_' | '$2'}).
-type muc_online_users() :: #muc_online_users{}.
-type muc_room_state() :: #state{}.

View File

@ -11,13 +11,20 @@
-record(csi, {type :: active | inactive}).
-type csi() :: #csi{}.
-record(hint, {type :: 'no-copy' | 'no-store' | 'no-storage' |
'store' | 'no-permanent-store'}).
-record(hint, {type :: 'no-copy' | 'no-store' | 'no-storage' | 'store' |
'no-permanent-store' | 'no-permanent-storage'}).
-type hint() :: #hint{}.
-record(feature_register, {}).
-type feature_register() :: #feature_register{}.
-record(address, {type :: 'bcc' | 'cc' | 'noreply' | 'ofrom' | 'replyroom' | 'replyto' | 'to',
jid :: any(),
desc :: binary(),
node :: binary(),
delivered :: any()}).
-type address() :: #address{}.
-record(sasl_success, {text :: any()}).
-type sasl_success() :: #sasl_success{}.
@ -55,6 +62,9 @@
stored :: non_neg_integer()}).
-type expire() :: #expire{}.
-record(muc_unsubscribe, {}).
-type muc_unsubscribe() :: #muc_unsubscribe{}.
-record(pubsub_unsubscribe, {node :: binary(),
jid :: any(),
subid :: binary()}).
@ -81,7 +91,7 @@
type :: 'member' | 'none' | 'outcast' | 'owner' | 'publish-only' | 'publisher'}).
-type pubsub_affiliation() :: #pubsub_affiliation{}.
-record(muc_decline, {reason :: binary(),
-record(muc_decline, {reason = <<>> :: 'undefined' | binary(),
from :: any(),
to :: any()}).
-type muc_decline() :: #muc_decline{}.
@ -90,9 +100,20 @@
xmlns :: binary()}).
-type sm_a() :: #sm_a{}.
-record(muc_subscribe, {nick :: binary(),
events = [] :: [binary()]}).
-type muc_subscribe() :: #muc_subscribe{}.
-record(stanza_id, {by :: any(),
id :: binary()}).
-type stanza_id() :: #stanza_id{}.
-record(starttls_proceed, {}).
-type starttls_proceed() :: #starttls_proceed{}.
-record(client_id, {id :: binary()}).
-type client_id() :: #client_id{}.
-record(sm_resumed, {h :: non_neg_integer(),
previd :: binary(),
xmlns :: binary()}).
@ -116,9 +137,19 @@
-record(gone, {uri :: binary()}).
-type gone() :: #gone{}.
-record(x_conference, {jid :: any(),
password = <<>> :: binary(),
reason = <<>> :: binary(),
continue :: any(),
thread = <<>> :: binary()}).
-type x_conference() :: #x_conference{}.
-record(private, {xml_els = [] :: [any()]}).
-type private() :: #private{}.
-record(nick, {name :: binary()}).
-type nick() :: #nick{}.
-record(p1_ack, {}).
-type p1_ack() :: #p1_ack{}.
@ -163,6 +194,9 @@
error = [] :: [{integer(),'undefined' | binary()}]}).
-type stat() :: #stat{}.
-record(addresses, {list = [] :: [#address{}]}).
-type addresses() :: #addresses{}.
-record('see-other-host', {host :: binary()}).
-type 'see-other-host'() :: #'see-other-host'{}.
@ -194,6 +228,9 @@
-record(pubsub_event, {items = [] :: [#pubsub_event_items{}]}).
-type pubsub_event() :: #pubsub_event{}.
-record(muc_unique, {name = <<>> :: binary()}).
-type muc_unique() :: #muc_unique{}.
-record(sasl_response, {text :: any()}).
-type sasl_response() :: #sasl_response{}.
@ -217,19 +254,11 @@
-record(feature_csi, {xmlns :: binary()}).
-type feature_csi() :: #feature_csi{}.
-record(muc_user_destroy, {reason :: binary(),
jid :: any()}).
-type muc_user_destroy() :: #muc_user_destroy{}.
-record(disco_item, {jid :: any(),
name :: binary(),
node :: binary()}).
-type disco_item() :: #disco_item{}.
-record(disco_items, {node :: binary(),
items = [] :: [#disco_item{}]}).
-type disco_items() :: #disco_items{}.
-record(unblock, {items = [] :: [any()]}).
-type unblock() :: #unblock{}.
@ -239,10 +268,8 @@
-record(compression, {methods = [] :: [binary()]}).
-type compression() :: #compression{}.
-record(muc_owner_destroy, {jid :: any(),
reason :: binary(),
password :: binary()}).
-type muc_owner_destroy() :: #muc_owner_destroy{}.
-record(muc_subscriptions, {list = [] :: [any()]}).
-type muc_subscriptions() :: #muc_subscriptions{}.
-record(pubsub_subscription, {jid :: any(),
node :: binary(),
@ -252,7 +279,7 @@
-record(muc_item, {actor :: #muc_actor{},
continue :: binary(),
reason :: binary(),
reason = <<>> :: 'undefined' | binary(),
affiliation :: 'admin' | 'member' | 'none' | 'outcast' | 'owner',
role :: 'moderator' | 'none' | 'participant' | 'visitor',
jid :: any(),
@ -267,8 +294,8 @@
-record(mam_prefs, {xmlns :: binary(),
default :: 'always' | 'never' | 'roster',
always = [] :: [any()],
never = [] :: [any()]}).
always :: [any()],
never :: [any()]}).
-type mam_prefs() :: #mam_prefs{}.
-record(caps, {node :: binary(),
@ -350,13 +377,17 @@
-record(block_list, {items = [] :: [any()]}).
-type block_list() :: #block_list{}.
-record(xdata_option, {label :: binary(),
value :: binary()}).
-type xdata_option() :: #xdata_option{}.
-record(xdata_field, {label :: binary(),
type :: 'boolean' | 'fixed' | 'hidden' | 'jid-multi' | 'jid-single' | 'list-multi' | 'list-single' | 'text-multi' | 'text-private' | 'text-single',
var :: binary(),
required = false :: boolean(),
desc :: binary(),
values = [] :: [binary()],
options = [] :: [binary()]}).
options = [] :: [#xdata_option{}]}).
-type xdata_field() :: #xdata_field{}.
-record(version, {name :: binary(),
@ -364,11 +395,6 @@
os :: binary()}).
-type version() :: #version{}.
-record(muc_invite, {reason :: binary(),
from :: any(),
to :: any()}).
-type muc_invite() :: #muc_invite{}.
-record(bind, {jid :: any(),
resource :: any()}).
-type bind() :: #bind{}.
@ -376,13 +402,11 @@
-record(rosterver_feature, {}).
-type rosterver_feature() :: #rosterver_feature{}.
-record(muc_user, {decline :: #muc_decline{},
destroy :: #muc_user_destroy{},
invites = [] :: [#muc_invite{}],
items = [] :: [#muc_item{}],
status_codes = [] :: [pos_integer()],
password :: binary()}).
-type muc_user() :: #muc_user{}.
-record(muc_invite, {reason = <<>> :: 'undefined' | binary(),
from :: any(),
to :: any(),
continue :: binary()}).
-type muc_invite() :: #muc_invite{}.
-record(carbons_disable, {}).
-type carbons_disable() :: #carbons_disable{}.
@ -400,7 +424,7 @@
-type vcard_org() :: #vcard_org{}.
-record(rsm_set, {'after' :: binary(),
before :: 'none' | binary(),
before :: binary(),
count :: non_neg_integer(),
first :: #rsm_first{},
index :: non_neg_integer(),
@ -414,6 +438,11 @@
complete :: any()}).
-type mam_fin() :: #mam_fin{}.
-record(disco_items, {node :: binary(),
items = [] :: [#disco_item{}],
rsm :: #rsm_set{}}).
-type disco_items() :: #disco_items{}.
-record(vcard_tel, {home = false :: boolean(),
work = false :: boolean(),
voice = false :: boolean(),
@ -430,6 +459,20 @@
number :: binary()}).
-type vcard_tel() :: #vcard_tel{}.
-record(muc_destroy, {xmlns :: binary(),
jid :: any(),
reason = <<>> :: 'undefined' | binary(),
password :: binary()}).
-type muc_destroy() :: #muc_destroy{}.
-record(muc_user, {decline :: #muc_decline{},
destroy :: #muc_destroy{},
invites = [] :: [#muc_invite{}],
items = [] :: [#muc_item{}],
status_codes = [] :: [pos_integer()],
password :: binary()}).
-type muc_user() :: #muc_user{}.
-record(vcard_key, {type :: binary(),
cred :: binary()}).
-type vcard_key() :: #vcard_key{}.
@ -530,14 +573,11 @@
start :: any(),
'end' :: any(),
with :: any(),
withtext :: binary(),
rsm :: #rsm_set{},
xdata :: #xdata{}}).
-type mam_query() :: #mam_query{}.
-record(muc_owner, {destroy :: #muc_owner_destroy{},
config :: #xdata{}}).
-type muc_owner() :: #muc_owner{}.
-record(pubsub_options, {node :: binary(),
jid :: any(),
subid :: binary(),
@ -592,6 +632,11 @@
fetch = false :: boolean()}).
-type offline() :: #offline{}.
-record(muc_owner, {destroy :: #muc_destroy{},
config :: #xdata{},
items = [] :: [#muc_item{}]}).
-type muc_owner() :: #muc_owner{}.
-record(sasl_mechanisms, {list = [] :: [binary()]}).
-type sasl_mechanisms() :: #sasl_mechanisms{}.
@ -709,6 +754,7 @@
-type xmpp_element() :: compression() |
pubsub_subscription() |
xdata_option() |
version() |
pubsub_affiliation() |
muc_admin() |
@ -726,7 +772,9 @@
rsm_set() |
'see-other-host'() |
hint() |
stanza_id() |
starttls_proceed() |
client_id() |
sm_resumed() |
forwarded() |
xevent() |
@ -742,8 +790,11 @@
pubsub_event_item() |
muc_item() |
vcard_temp() |
address() |
sasl_success() |
addresses() |
pubsub_event_items() |
muc_subscriptions() |
disco_items() |
pubsub_options() |
compress() |
@ -751,33 +802,25 @@
muc_history() |
identity() |
feature_csi() |
muc_user_destroy() |
privacy_query() |
delay() |
vcard_tel() |
vcard_logo() |
disco_info() |
vcard_geo() |
vcard_photo() |
feature_register() |
register() |
muc_owner() |
pubsub() |
sm_r() |
muc_owner() |
muc_actor() |
error() |
stream_error() |
muc_user() |
vcard_adr() |
carbons_private() |
mix_leave() |
muc_invite() |
muc_subscribe() |
rosterver_feature() |
muc_invite() |
vcard_xupdate() |
carbons_disable() |
bookmark_conference() |
offline() |
time() |
muc_unique() |
sasl_response() |
pubsub_subscribe() |
presence() |
@ -786,6 +829,7 @@
starttls_failure() |
sasl_challenge() |
gone() |
x_conference() |
private() |
compress_failure() |
sasl_failure() |
@ -794,6 +838,7 @@
sm_resume() |
carbons_enable() |
expire() |
muc_unsubscribe() |
pubsub_unsubscribe() |
muc_decline() |
chatstate() |
@ -803,6 +848,7 @@
search() |
pubsub_publish() |
unblock() |
nick() |
p1_ack() |
block() |
mix_join() |
@ -832,11 +878,20 @@
starttls() |
mam_prefs() |
sasl_mechanisms() |
muc_destroy() |
vcard_key() |
csi() |
roster_query() |
muc_owner_destroy() |
mam_query() |
bookmark_url() |
vcard_email() |
vcard_label().
vcard_label() |
vcard_logo() |
disco_info() |
feature_register() |
register() |
sm_r() |
error() |
stream_error() |
muc_user() |
vcard_adr().

View File

@ -72,7 +72,7 @@
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
-spec route(jid(), jid(), xmlel() | xmpp_element()) -> ok.
-spec route(jid(), jid(), xmlel() | stanza()) -> ok.
route(From, To, Packet) ->
case catch do_route(From, To, Packet) of
@ -85,13 +85,21 @@ 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.
-spec route_error(jid(), jid(), xmlel(), xmlel()) -> ok;
(jid(), jid(), stanza(), error()) -> ok.
route_error(From, To, ErrPacket, OrigPacket) ->
route_error(From, To, #xmlel{} = ErrPacket, #xmlel{} = OrigPacket) ->
#xmlel{attrs = Attrs} = OrigPacket,
case <<"error">> == fxml:get_attr_s(<<"type">>, Attrs) of
false -> route(From, To, ErrPacket);
true -> ok
end;
route_error(From, To, Packet, #error{} = Err) ->
Type = xmpp:get_type(Packet),
if Type == error; Type == result ->
ok;
true ->
ejabberd_router:route(From, To, xmpp:make_error(Packet, Err))
end.
-spec register_route(binary()) -> term().
@ -406,11 +414,16 @@ do_route(OrigFrom, OrigTo, OrigPacket) ->
end.
-spec do_route(jid(), jid(), xmlel() | xmpp_element(), #route{}) -> any().
do_route(From, To, Packet,
#route{local_hint = {apply, Module, Function}, pid = Pid})
when is_pid(Pid) andalso node(Pid) == node() ->
try
Module:Function(From, To, xmpp:decode(Packet, [ignore_els]))
do_route(From, To, Packet, #route{local_hint = LocalHint,
pid = Pid}) when is_pid(Pid) ->
try xmpp:decode(Packet, [ignore_els]) of
Pkt ->
case LocalHint of
{apply, Module, Function} when node(Pid) == node() ->
Module:Function(From, To, Pkt);
_ ->
Pid ! {route, From, To, Pkt}
end
catch error:{xmpp_codec, Why} ->
?ERROR_MSG("failed to decode xml element ~p when "
"routing from ~s to ~s: ~s",
@ -418,8 +431,6 @@ do_route(From, To, Packet,
xmpp:format_error(Why)]),
drop
end;
do_route(From, To, Packet, #route{pid = Pid}) when is_pid(Pid) ->
Pid ! {route, From, To, xmpp:encode(Packet)};
do_route(_From, _To, _Packet, _Route) ->
drop.

View File

@ -295,7 +295,7 @@ process_sm_iq_items(#iq{type = get, lang = Lang,
end;
false ->
Txt = <<"Not subscribed">>,
xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang))
xmpp:make_error(IQ, xmpp:err_subscription_required(Txt, Lang))
end.
-spec get_sm_items({error, error()} | {result, [disco_item()]} | empty,
@ -371,7 +371,7 @@ process_sm_iq_info(#iq{type = get, lang = Lang,
end;
false ->
Txt = <<"Not subscribed">>,
xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang))
xmpp:make_error(IQ, xmpp:err_subscription_required(Txt, Lang))
end.
-spec get_sm_identity([identity()], jid(), jid(),

View File

@ -140,7 +140,7 @@ process_sm_iq(#iq{from = From, to = To, lang = Lang} = IQ) ->
end;
true ->
Txt = <<"Not subscribed">>,
xmpp:make_error(IQ, xmpp:err_not_subscribed(Txt, Lang))
xmpp:make_error(IQ, xmpp:err_subscription_required(Txt, Lang))
end.
%% @spec (LUser::string(), LServer::string()) ->

View File

@ -34,12 +34,12 @@
-export([start/2, stop/1, depends/2]).
-export([user_send_packet/4, user_send_packet_strip_tag/4, user_receive_packet/5,
process_iq_v0_2/3, process_iq_v0_3/3, disco_sm_features/5,
remove_user/2, remove_room/3, mod_opt_type/1, muc_process_iq/4,
process_iq_v0_2/1, process_iq_v0_3/1, disco_sm_features/5,
remove_user/2, remove_room/3, mod_opt_type/1, muc_process_iq/2,
muc_filter_message/5, message_is_archived/5, delete_old_messages/2,
get_commands_spec/0, msg_to_el/4, get_room_config/4, set_room_option/4]).
-include("jlib.hrl").
-include("xmpp.hrl").
-include("logger.hrl").
-include("mod_muc_room.hrl").
-include("ejabberd_commands.hrl").
@ -54,17 +54,12 @@
-callback delete_old_messages(binary() | global,
erlang:timestamp(),
all | chat | groupchat) -> any().
-callback extended_fields() -> [xmlel()].
-callback extended_fields() -> [xdata_field()].
-callback store(xmlel(), binary(), {binary(), binary()}, chat | groupchat,
jid(), binary(), recv | send) -> {ok, binary()} | any().
-callback write_prefs(binary(), binary(), #archive_prefs{}, binary()) -> ok | any().
-callback get_prefs(binary(), binary()) -> {ok, #archive_prefs{}} | error.
-callback select(binary(), jid(), jid(),
none | erlang:timestamp(),
none | erlang:timestamp(),
none | ljid() | {text, binary()},
none | #rsm_in{},
chat | groupchat) ->
-callback select(binary(), jid(), jid(), mam_query(), chat | groupchat) ->
{[{binary(), non_neg_integer(), xmlel()}], boolean(), non_neg_integer()}.
%%%===================================================================
@ -200,14 +195,10 @@ get_room_config(X, RoomState, _From, Lang) ->
true -> <<"1">>;
_ -> <<"0">>
end,
XField = #xmlel{name = <<"field">>,
attrs =
[{<<"type">>, <<"boolean">>},
{<<"label">>, translate:translate(Lang, Label)},
{<<"var">>, Var}],
children =
[#xmlel{name = <<"value">>, attrs = [],
children = [{xmlcdata, Val}]}]},
XField = #xdata_field{type = boolean,
label = translate:translate(Lang, Label),
var = Var,
values = [Val]},
X ++ [XField].
set_room_option(_Acc, <<"muc#roomconfig_mam">> = Opt, Vals, Lang) ->
@ -222,7 +213,7 @@ set_room_option(_Acc, <<"muc#roomconfig_mam">> = Opt, Vals, Lang) ->
catch _:{case_clause, _} ->
Txt = <<"Value of '~s' should be boolean">>,
ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])),
{error, ?ERRT_BAD_REQUEST(Lang, ErrTxt)}
{error, xmpp:err_bad_request(Lang, ErrTxt)}
end;
set_room_option(Acc, _Opt, _Vals, _Lang) ->
Acc.
@ -236,16 +227,7 @@ user_receive_packet(Pkt, C2SState, JID, Peer, To) ->
NewPkt = strip_my_archived_tag(Pkt, LServer),
case store_msg(C2SState, NewPkt, LUser, LServer, Peer, recv) of
{ok, ID} ->
Archived = #xmlel{name = <<"archived">>,
attrs = [{<<"by">>, LServer},
{<<"xmlns">>, ?NS_MAM_TMP},
{<<"id">>, ID}]},
StanzaID = #xmlel{name = <<"stanza-id">>,
attrs = [{<<"by">>, LServer},
{<<"xmlns">>, ?NS_SID_0},
{<<"id">>, ID}]},
NewEls = [Archived, StanzaID|NewPkt#xmlel.children],
NewPkt#xmlel{children = NewEls};
set_stanza_id(NewPkt, LServer, ID);
_ ->
NewPkt
end;
@ -259,19 +241,10 @@ user_send_packet(Pkt, C2SState, JID, Peer) ->
case should_archive(Pkt, LServer) of
true ->
NewPkt = strip_my_archived_tag(Pkt, LServer),
case store_msg(C2SState, jlib:replace_from_to(JID, Peer, NewPkt),
case store_msg(C2SState, xmpp:set_from_to(NewPkt, JID, Peer),
LUser, LServer, Peer, send) of
{ok, ID} ->
Archived = #xmlel{name = <<"archived">>,
attrs = [{<<"by">>, LServer},
{<<"xmlns">>, ?NS_MAM_TMP},
{<<"id">>, ID}]},
StanzaID = #xmlel{name = <<"stanza-id">>,
attrs = [{<<"by">>, LServer},
{<<"xmlns">>, ?NS_SID_0},
{<<"id">>, ID}]},
NewEls = [Archived, StanzaID|NewPkt#xmlel.children],
NewPkt#xmlel{children = NewEls};
set_stanza_id(NewPkt, LServer, ID);
_ ->
NewPkt
end;
@ -291,16 +264,7 @@ muc_filter_message(Pkt, #state{config = Config} = MUCState,
StorePkt = strip_x_jid_tags(NewPkt),
case store_muc(MUCState, StorePkt, RoomJID, From, FromNick) of
{ok, ID} ->
Archived = #xmlel{name = <<"archived">>,
attrs = [{<<"by">>, LServer},
{<<"xmlns">>, ?NS_MAM_TMP},
{<<"id">>, ID}]},
StanzaID = #xmlel{name = <<"stanza-id">>,
attrs = [{<<"by">>, LServer},
{<<"xmlns">>, ?NS_SID_0},
{<<"id">>, ID}]},
NewEls = [Archived, StanzaID|NewPkt#xmlel.children],
NewPkt#xmlel{children = NewEls};
set_stanza_id(NewPkt, LServer, ID);
_ ->
NewPkt
end;
@ -308,71 +272,98 @@ muc_filter_message(Pkt, #state{config = Config} = MUCState,
Pkt
end.
set_stanza_id(Pkt, LServer, ID) ->
Archived = #mam_archived{by = jid:make(LServer), id = ID},
StanzaID = #stanza_id{by = jid:make(LServer), id = ID},
NewEls = [Archived, StanzaID|xmpp:get_els(Pkt)],
xmpp:set_els(Pkt, NewEls).
% Query archive v0.2
process_iq_v0_2(#jid{lserver = LServer} = From,
#jid{lserver = LServer} = To,
#iq{type = get, sub_el = #xmlel{name = <<"query">>} = SubEl} = IQ) ->
Fs = parse_query_v0_2(SubEl),
process_iq(LServer, From, To, IQ, SubEl, Fs, chat);
process_iq_v0_2(From, To, IQ) ->
process_iq(From, To, IQ).
process_iq_v0_2(#iq{from = #jid{lserver = LServer},
to = #jid{lserver = LServer},
type = get, sub_els = [#mam_query{}]} = IQ) ->
process_iq(LServer, IQ, chat);
process_iq_v0_2(IQ) ->
process_iq(IQ).
% Query archive v0.3
process_iq_v0_3(#jid{lserver = LServer} = From,
#jid{lserver = LServer} = To,
#iq{type = set, sub_el = #xmlel{name = <<"query">>} = SubEl} = IQ) ->
process_iq(LServer, From, To, IQ, SubEl, get_xdata_fields(SubEl), chat);
process_iq_v0_3(#jid{lserver = LServer},
#jid{lserver = LServer},
#iq{type = get, sub_el = #xmlel{name = <<"query">>}} = IQ) ->
process_iq_v0_3(#iq{from = #jid{lserver = LServer},
to = #jid{lserver = LServer},
type = set, sub_els = [#mam_query{}]} = IQ) ->
process_iq(LServer, IQ, chat);
process_iq_v0_3(#iq{from = #jid{lserver = LServer},
to = #jid{lserver = LServer},
type = get, sub_els = [#mam_query{}]} = IQ) ->
process_iq(LServer, IQ);
process_iq_v0_3(From, To, IQ) ->
process_iq(From, To, IQ).
process_iq_v0_3(IQ) ->
process_iq(IQ).
muc_process_iq(#iq{type = set,
sub_el = #xmlel{name = <<"query">>,
attrs = Attrs} = SubEl} = IQ,
MUCState, From, To) ->
case fxml:get_attr_s(<<"xmlns">>, Attrs) of
NS when NS == ?NS_MAM_0; NS == ?NS_MAM_1 ->
muc_process_iq(IQ, MUCState, From, To, get_xdata_fields(SubEl));
_ ->
IQ
muc_process_iq(#iq{type = T, lang = Lang,
from = From,
sub_els = [#mam_query{xmlns = NS}]} = IQ,
MUCState)
when (T == set andalso (NS == ?NS_MAM_0 orelse NS == ?NS_MAM_1)) orelse
(T == get andalso NS == ?NS_MAM_TMP) ->
case may_enter_room(From, MUCState) of
true ->
LServer = MUCState#state.server_host,
Role = mod_muc_room:get_role(From, MUCState),
process_iq(LServer, IQ, {groupchat, Role, MUCState});
false ->
Text = <<"Only members may query archives of this room">>,
xmpp:make_error(IQ, xmpp:err_forbidden(Text, Lang))
end;
muc_process_iq(#iq{type = get,
sub_el = #xmlel{name = <<"query">>,
attrs = Attrs} = SubEl} = IQ,
MUCState, From, To) ->
case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_MAM_TMP ->
muc_process_iq(IQ, MUCState, From, To, parse_query_v0_2(SubEl));
NS when NS == ?NS_MAM_0; NS == ?NS_MAM_1 ->
LServer = MUCState#state.server_host,
process_iq(LServer, IQ);
_ ->
IQ
end;
muc_process_iq(IQ, _MUCState, _From, _To) ->
sub_els = [#mam_query{xmlns = NS}]} = IQ,
MUCState) when NS == ?NS_MAM_0; NS == ?NS_MAM_1 ->
LServer = MUCState#state.server_host,
process_iq(LServer, IQ);
muc_process_iq(IQ, _MUCState) ->
IQ.
get_xdata_fields(SubEl) ->
case {fxml:get_subtag_with_xmlns(SubEl, <<"x">>, ?NS_XDATA),
fxml:get_subtag_with_xmlns(SubEl, <<"set">>, ?NS_RSM)} of
{#xmlel{} = XData, false} ->
jlib:parse_xdata_submit(XData);
{#xmlel{} = XData, #xmlel{}} ->
[{<<"set">>, SubEl} | jlib:parse_xdata_submit(XData)];
{false, #xmlel{}} ->
[{<<"set">>, SubEl}];
{false, false} ->
[]
end.
parse_query(#mam_query{xdata = #xdata{fields = Fs}} = Query, Lang) ->
try
lists:foldl(
fun(#xdata_field{var = <<"start">>, values = [Data|_]}, Q) ->
case jlib:datetime_string_to_timestamp(Data) of
undefined -> throw({error, <<"start">>});
{_, _, _} = TS -> Q#mam_query{start = TS}
end;
(#xdata_field{var = <<"end">>, values = [Data|_]}, Q) ->
case jlib:datetime_string_to_timestamp(Data) of
undefined -> throw({error, <<"end">>});
{_, _, _} = TS -> Q#mam_query{'end' = TS}
end;
(#xdata_field{var = <<"with">>, values = [Data|_]}, Q) ->
case jid:from_string(Data) of
error -> throw({error, <<"with">>});
J -> Q#mam_query{with = J}
end;
(#xdata_field{var = <<"withtext">>, values = [Data|_]}, Q) ->
case Data of
<<"">> -> throw({error, <<"withtext">>});
_ -> Q#mam_query{withtext = Data}
end;
(#xdata_field{var = <<"FORM_TYPE">>, values = [NS|_]}, Q) ->
case Query#mam_query.xmlns of
NS -> Q;
_ -> throw({error, <<"FORM_TYPE">>})
end;
(#xdata_field{}, Acc) ->
Acc
end, Query, Fs)
catch throw:{error, Var} ->
Txt = io_lib:format("Incorrect value of field '~s'", [Var]),
{error, xmpp:err_bad_request(iolist_to_binary(Txt), Lang)}
end;
parse_query(Query, _Lang) ->
Query.
disco_sm_features(empty, From, To, Node, Lang) ->
disco_sm_features({result, []}, From, To, Node, Lang);
disco_sm_features({result, OtherFeatures},
#jid{luser = U, lserver = S},
#jid{luser = U, lserver = S}, <<>>, _Lang) ->
#jid{luser = U, lserver = S}, undefined, _Lang) ->
{result, [?NS_MAM_TMP, ?NS_MAM_0, ?NS_MAM_1 | OtherFeatures]};
disco_sm_features(Acc, _From, _To, _Node, _Lang) ->
Acc.
@ -440,210 +431,155 @@ delete_old_messages(_TypeBin, _Days) ->
%%% Internal functions
%%%===================================================================
process_iq(LServer, #iq{sub_el = #xmlel{attrs = Attrs}} = IQ) ->
NS = case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_MAM_0 ->
?NS_MAM_0;
_ ->
?NS_MAM_1
end,
CommonFields = [#xmlel{name = <<"field">>,
attrs = [{<<"type">>, <<"hidden">>},
{<<"var">>, <<"FORM_TYPE">>}],
children = [#xmlel{name = <<"value">>,
children = [{xmlcdata, NS}]}]},
#xmlel{name = <<"field">>,
attrs = [{<<"type">>, <<"jid-single">>},
{<<"var">>, <<"with">>}]},
#xmlel{name = <<"field">>,
attrs = [{<<"type">>, <<"text-single">>},
{<<"var">>, <<"start">>}]},
#xmlel{name = <<"field">>,
attrs = [{<<"type">>, <<"text-single">>},
{<<"var">>, <<"end">>}]}],
process_iq(LServer, #iq{sub_els = [#mam_query{xmlns = NS}]} = IQ) ->
CommonFields = [#xdata_field{type = hidden,
var = <<"FORM_TYPE">>,
values = [NS]},
#xdata_field{type = 'jid-single', var = <<"with">>},
#xdata_field{type = 'text-single', var = <<"start">>},
#xdata_field{type = 'text-single', var = <<"end">>}],
Mod = gen_mod:db_mod(LServer, ?MODULE),
ExtendedFields = Mod:extended_fields(),
Fields = ExtendedFields ++ CommonFields,
Form = #xmlel{name = <<"x">>,
attrs = [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"form">>}],
children = Fields},
IQ#iq{type = result,
sub_el = [#xmlel{name = <<"query">>,
attrs = [{<<"xmlns">>, NS}],
children = [Form]}]}.
Fields = CommonFields ++ ExtendedFields,
Form = #xdata{type = form, fields = Fields},
xmpp:make_iq_result(IQ, #mam_query{xmlns = NS, xdata = Form}).
% Preference setting (both v0.2 & v0.3)
process_iq(#jid{luser = LUser, lserver = LServer},
#jid{lserver = LServer},
#iq{type = set, lang = Lang, sub_el = #xmlel{name = <<"prefs">>} = SubEl} = IQ) ->
try {case fxml:get_tag_attr_s(<<"default">>, SubEl) of
<<"always">> -> always;
<<"never">> -> never;
<<"roster">> -> roster
end,
lists:foldl(
fun(#xmlel{name = <<"always">>, children = Els}, {A, N}) ->
{get_jids(Els) ++ A, N};
(#xmlel{name = <<"never">>, children = Els}, {A, N}) ->
{A, get_jids(Els) ++ N};
(_, {A, N}) ->
{A, N}
end, {[], []}, SubEl#xmlel.children)} of
{Default, {Always0, Never0}} ->
Always = lists:usort(Always0),
Never = lists:usort(Never0),
case write_prefs(LUser, LServer, LServer, Default, Always, Never) of
ok ->
NewPrefs = prefs_el(Default, Always, Never, IQ#iq.xmlns),
IQ#iq{type = result, sub_el = [NewPrefs]};
_Err ->
Txt = <<"Database failure">>,
IQ#iq{type = error,
sub_el = [SubEl, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt)]}
end
catch _:_ ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]}
process_iq(#iq{type = set, lang = Lang,
sub_els = [#mam_prefs{default = undefined, xmlns = NS}]} = IQ) ->
Why = {missing_attr, <<"default">>, <<"prefs">>, NS},
ErrTxt = xmpp:format_error(Why),
xmpp:make_error(IQ, xmpp:err_bad_request(ErrTxt, Lang));
process_iq(#iq{from = #jid{luser = LUser, lserver = LServer},
to = #jid{lserver = LServer},
type = set, lang = Lang,
sub_els = [#mam_prefs{xmlns = NS,
default = Default,
always = Always0,
never = Never0}]} = IQ) ->
Always = lists:usort(get_jids(Always0)),
Never = lists:usort(get_jids(Never0)),
case write_prefs(LUser, LServer, LServer, Default, Always, Never) of
ok ->
NewPrefs = prefs_el(Default, Always, Never, NS),
xmpp:make_iq_result(IQ, NewPrefs);
_Err ->
Txt = <<"Database failure">>,
xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang))
end;
process_iq(#jid{luser = LUser, lserver = LServer},
#jid{lserver = LServer},
#iq{type = get, sub_el = #xmlel{name = <<"prefs">>}} = IQ) ->
process_iq(#iq{from = #jid{luser = LUser, lserver = LServer},
to = #jid{lserver = LServer},
type = get, sub_els = [#mam_prefs{xmlns = NS}]} = IQ) ->
Prefs = get_prefs(LUser, LServer),
PrefsEl = prefs_el(Prefs#archive_prefs.default,
Prefs#archive_prefs.always,
Prefs#archive_prefs.never,
IQ#iq.xmlns),
IQ#iq{type = result, sub_el = [PrefsEl]};
process_iq(_, _, #iq{sub_el = SubEl} = IQ) ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}.
NS),
xmpp:make_iq_result(IQ, PrefsEl);
process_iq(IQ) ->
xmpp:make_error(IQ, xmpp:err_not_allowed()).
process_iq(LServer, #jid{luser = LUser} = From, To, IQ, SubEl, Fs, MsgType) ->
process_iq(LServer, #iq{from = #jid{luser = LUser}, lang = Lang,
sub_els = [SubEl]} = IQ, MsgType) ->
case MsgType of
chat ->
maybe_activate_mam(LUser, LServer);
{groupchat, _Role, _MUCState} ->
ok
end,
case catch lists:foldl(
fun({<<"start">>, [Data|_]}, {_, End, With, RSM}) ->
{{_, _, _} = jlib:datetime_string_to_timestamp(Data),
End, With, RSM};
({<<"end">>, [Data|_]}, {Start, _, With, RSM}) ->
{Start,
{_, _, _} = jlib:datetime_string_to_timestamp(Data),
With, RSM};
({<<"with">>, [Data|_]}, {Start, End, _, RSM}) ->
{Start, End, jid:tolower(jid:from_string(Data)), RSM};
({<<"withtext">>, [Data|_]}, {Start, End, _, RSM}) ->
{Start, End, {text, Data}, RSM};
({<<"set">>, El}, {Start, End, With, _}) ->
{Start, End, With, jlib:rsm_decode(El)};
(_, Acc) ->
Acc
end, {none, [], none, none}, Fs) of
{'EXIT', _} ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]};
{_Start, _End, _With, #rsm_in{index = Index}} when is_integer(Index) ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_FEATURE_NOT_IMPLEMENTED]};
{Start, End, With, RSM} ->
NS = fxml:get_tag_attr_s(<<"xmlns">>, SubEl),
select_and_send(LServer, From, To, Start, End,
With, limit_max(RSM, NS), IQ, MsgType)
case parse_query(SubEl, Lang) of
#mam_query{rsm = #rsm_set{index = I}} when is_integer(I) ->
xmpp:make_error(IQ, xmpp:err_feature_not_implemented());
#mam_query{rsm = RSM, xmlns = NS} = Query ->
NewRSM = limit_max(RSM, NS),
NewQuery = Query#mam_query{rsm = NewRSM},
select_and_send(LServer, NewQuery, IQ, MsgType);
{error, Err} ->
xmpp:make_error(IQ, Err)
end.
muc_process_iq(#iq{lang = Lang, sub_el = SubEl} = IQ, MUCState, From, To, Fs) ->
case may_enter_room(From, MUCState) of
should_archive(#message{type = T}, _LServer) when T == error; T == result ->
false;
should_archive(#message{body = Body} = Pkt, LServer) ->
case is_resent(Pkt, LServer) of
true ->
LServer = MUCState#state.server_host,
Role = mod_muc_room:get_role(From, MUCState),
process_iq(LServer, From, To, IQ, SubEl, Fs,
{groupchat, Role, MUCState});
false;
false ->
Text = <<"Only members may query archives of this room">>,
Error = ?ERRT_FORBIDDEN(Lang, Text),
IQ#iq{type = error, sub_el = [SubEl, Error]}
end.
parse_query_v0_2(Query) ->
lists:flatmap(
fun (#xmlel{name = <<"start">>} = El) ->
[{<<"start">>, [fxml:get_tag_cdata(El)]}];
(#xmlel{name = <<"end">>} = El) ->
[{<<"end">>, [fxml:get_tag_cdata(El)]}];
(#xmlel{name = <<"with">>} = El) ->
[{<<"with">>, [fxml:get_tag_cdata(El)]}];
(#xmlel{name = <<"withtext">>} = El) ->
[{<<"withtext">>, [fxml:get_tag_cdata(El)]}];
(#xmlel{name = <<"set">>}) ->
[{<<"set">>, Query}];
(_) ->
[]
end, Query#xmlel.children).
should_archive(#xmlel{name = <<"message">>} = Pkt, LServer) ->
case fxml:get_attr_s(<<"type">>, Pkt#xmlel.attrs) of
<<"error">> ->
false;
<<"groupchat">> ->
false;
_ ->
case is_resent(Pkt, LServer) of
true ->
case check_store_hint(Pkt) of
store ->
true;
no_store ->
false;
false ->
case check_store_hint(Pkt) of
store ->
true;
no_store ->
false;
none ->
case fxml:get_subtag_cdata(Pkt, <<"body">>) of
<<>> ->
%% Empty body
false;
_ ->
true
end
end
end
end;
should_archive(#xmlel{}, _LServer) ->
false.
strip_my_archived_tag(Pkt, LServer) ->
NewEls = lists:filter(
fun(#xmlel{name = Tag, attrs = Attrs})
when Tag == <<"archived">>; Tag == <<"stanza-id">> ->
case catch jid:nameprep(
fxml:get_attr_s(
<<"by">>, Attrs)) of
LServer ->
none ->
case xmpp:get_text(Body) of
<<>> ->
%% Empty body
false;
_ ->
true
end;
(_) ->
true
end, Pkt#xmlel.children),
Pkt#xmlel{children = NewEls}.
end
end
end;
should_archive(_, _LServer) ->
false.
strip_my_archived_tag(Pkt, LServer) ->
NewPkt = xmpp:decode_els(
Pkt, fun(El) ->
case xmpp:get_name(El) of
<<"archived">> ->
xmpp:get_ns(El) == ?NS_MAM_TMP;
<<"stanza-id">> ->
xmpp:get_ns(El) == ?NS_SID_0;
_ ->
false
end
end),
NewEls = lists:filter(
fun(#mam_archived{by = #jid{luser = <<>>} = By}) ->
By#jid.lserver /= LServer;
(#stanza_id{by = #jid{luser = <<>>} = By}) ->
By#jid.lserver /= LServer;
(_) ->
true
end, xmpp:get_els(NewPkt)),
xmpp:set_els(NewPkt, NewEls).
strip_x_jid_tags(Pkt) ->
NewPkt = xmpp:decode_els(
Pkt, fun(El) ->
case xmpp:get_name(El) of
<<"x">> ->
case xmpp:get_ns(El) of
?NS_MUC_USER -> true;
?NS_MUC_ADMIN -> true;
?NS_MUC_OWNER -> true;
_ -> false
end;
_ ->
false
end
end),
NewEls = lists:filter(
fun(#xmlel{name = <<"x">>} = XEl) ->
not lists:any(fun(ItemEl) ->
fxml:get_tag_attr(<<"jid">>, ItemEl)
/= false
end, fxml:get_subtags(XEl, <<"item">>));
(_) ->
true
end, Pkt#xmlel.children),
Pkt#xmlel{children = NewEls}.
fun(El) ->
Items = case El of
#muc_user{items = Is} -> Is;
#muc_admin{items = Is} -> Is;
#muc_owner{items = Is} -> Is;
_ -> []
end,
not lists:any(fun(#muc_item{jid = JID}) ->
JID /= undefined
end, Items)
end, xmpp:get_els(NewPkt)),
xmpp:set_els(NewPkt, NewEls).
should_archive_peer(C2SState,
#archive_prefs{default = Default,
always = Always,
never = Never},
Peer) ->
LPeer = jid:tolower(Peer),
LPeer = jid:remove_resource(jid:tolower(Peer)),
case lists:member(LPeer, Always) of
true ->
true;
@ -667,30 +603,30 @@ should_archive_peer(C2SState,
end
end.
should_archive_muc(Pkt) ->
case fxml:get_attr_s(<<"type">>, Pkt#xmlel.attrs) of
<<"groupchat">> ->
case check_store_hint(Pkt) of
store ->
true;
no_store ->
false;
none ->
case fxml:get_subtag_cdata(Pkt, <<"body">>) of
<<>> ->
case fxml:get_subtag_cdata(Pkt, <<"subject">>) of
<<>> ->
false;
_ ->
true
end;
should_archive_muc(#message{type = groupchat,
body = Body, subject = Subj} = Pkt) ->
case check_store_hint(Pkt) of
store ->
true;
no_store ->
false;
none ->
case xmpp:get_text(Body) of
<<"">> ->
case xmpp:get_text(Subj) of
<<"">> ->
false;
_ ->
true
end
end;
_ ->
true
end;
_ ->
false
end.
end;
should_archive_muc(_) ->
false.
check_store_hint(Pkt) ->
case has_store_hint(Pkt) of
@ -705,30 +641,24 @@ check_store_hint(Pkt) ->
end
end.
-spec has_store_hint(message()) -> boolean().
has_store_hint(Message) ->
fxml:get_subtag_with_xmlns(Message, <<"store">>, ?NS_HINTS)
/= false.
xmpp:has_subtag(Message, #hint{type = 'store'}).
-spec has_no_store_hint(message()) -> boolean().
has_no_store_hint(Message) ->
fxml:get_subtag_with_xmlns(Message, <<"no-store">>, ?NS_HINTS)
/= false orelse
fxml:get_subtag_with_xmlns(Message, <<"no-storage">>, ?NS_HINTS)
/= false orelse
fxml:get_subtag_with_xmlns(Message, <<"no-permanent-store">>, ?NS_HINTS)
/= false orelse
fxml:get_subtag_with_xmlns(Message, <<"no-permanent-storage">>, ?NS_HINTS)
/= false.
xmpp:has_subtag(Message, #hint{type = 'no-store'}) orelse
xmpp:has_subtag(Message, #hint{type = 'no-storage'}) orelse
xmpp:has_subtag(Message, #hint{type = 'no-permanent-store'}) orelse
xmpp:has_subtag(Message, #hint{type = 'no-permanent-storage'}).
-spec is_resent(message(), binary()) -> boolean().
is_resent(Pkt, LServer) ->
case fxml:get_subtag_with_xmlns(Pkt, <<"stanza-id">>, ?NS_SID_0) of
#xmlel{attrs = Attrs} ->
case fxml:get_attr(<<"by">>, Attrs) of
{value, LServer} ->
true;
_ ->
false
end;
false ->
case xmpp:get_subtag(Pkt, #stanza_id{}) of
#stanza_id{by = #jid{luser = <<>>, lserver = LServer}} ->
true;
_ ->
false
end.
@ -744,7 +674,8 @@ store_msg(C2SState, Pkt, LUser, LServer, Peer, Dir) ->
true ->
US = {LUser, LServer},
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:store(Pkt, LServer, US, chat, Peer, <<"">>, Dir);
El = xmpp:encode(Pkt),
Mod:store(El, LServer, US, chat, Peer, <<"">>, Dir);
false ->
pass
end.
@ -755,7 +686,8 @@ store_muc(MUCState, Pkt, RoomJID, Peer, Nick) ->
LServer = MUCState#state.server_host,
{U, S, _} = jid:tolower(RoomJID),
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:store(Pkt, LServer, {U, S}, groupchat, Peer, Nick, recv);
El = xmpp:encode(Pkt),
Mod:store(El, LServer, {U, S}, groupchat, Peer, Nick, recv);
false ->
pass
end.
@ -796,20 +728,10 @@ get_prefs(LUser, LServer) ->
end.
prefs_el(Default, Always, Never, NS) ->
Default1 = jlib:atom_to_binary(Default),
JFun = fun(L) ->
[#xmlel{name = <<"jid">>,
children = [{xmlcdata, jid:to_string(J)}]}
|| J <- L]
end,
Always1 = #xmlel{name = <<"always">>,
children = JFun(Always)},
Never1 = #xmlel{name = <<"never">>,
children = JFun(Never)},
#xmlel{name = <<"prefs">>,
attrs = [{<<"xmlns">>, NS},
{<<"default">>, Default1}],
children = [Always1, Never1]}.
#mam_prefs{default = Default,
always = [jid:make(LJ) || LJ <- Always],
never = [jid:make(LJ) || LJ <- Never],
xmlns = NS}.
maybe_activate_mam(LUser, LServer) ->
ActivateOpt = gen_mod:get_module_opt(LServer, ?MODULE,
@ -838,21 +760,19 @@ maybe_activate_mam(LUser, LServer) ->
ok
end.
select_and_send(LServer, From, To, Start, End, With, RSM, IQ, MsgType) ->
{Msgs, IsComplete, Count} = select_and_start(LServer, From, To, Start, End,
With, RSM, MsgType),
select_and_send(LServer, Query, #iq{from = From, to = To} = IQ, MsgType) ->
{Msgs, IsComplete, Count} =
case MsgType of
chat ->
select(LServer, From, From, Query, MsgType);
{groupchat, _Role, _MUCState} ->
select(LServer, From, To, Query, MsgType)
end,
SortedMsgs = lists:keysort(2, Msgs),
send(From, To, SortedMsgs, RSM, Count, IsComplete, IQ).
send(SortedMsgs, Count, IsComplete, IQ).
select_and_start(LServer, From, To, Start, End, With, RSM, MsgType) ->
case MsgType of
chat ->
select(LServer, From, From, Start, End, With, RSM, MsgType);
{groupchat, _Role, _MUCState} ->
select(LServer, From, To, Start, End, With, RSM, MsgType)
end.
select(_LServer, JidRequestor, JidArchive, Start, End, _With, RSM,
select(_LServer, JidRequestor, JidArchive,
#mam_query{start = Start, 'end' = End, rsm = RSM},
{groupchat, _Role, #state{config = #config{mam = false},
history = History}} = MsgType) ->
#lqueue{len = L, queue = Q} = History,
@ -864,7 +784,7 @@ select(_LServer, JidRequestor, JidArchive, Start, End, _With, RSM,
case match_interval(Now, Start, End) and
match_rsm(Now, RSM) of
true ->
{[{jlib:integer_to_binary(TS), TS,
{[{integer_to_binary(TS), TS,
msg_to_el(#archive_msg{
type = groupchat,
timestamp = Now,
@ -879,31 +799,27 @@ select(_LServer, JidRequestor, JidArchive, Start, End, _With, RSM,
end, 0, queue:to_list(Q)),
Msgs = lists:flatten(Msgs0),
case RSM of
#rsm_in{max = Max, direction = before} ->
#rsm_set{max = Max, before = Before} when is_binary(Before) ->
{NewMsgs, IsComplete} = filter_by_max(lists:reverse(Msgs), Max),
{NewMsgs, IsComplete, L};
#rsm_in{max = Max} ->
#rsm_set{max = Max} ->
{NewMsgs, IsComplete} = filter_by_max(Msgs, Max),
{NewMsgs, IsComplete, L};
_ ->
{Msgs, true, L}
end;
select(LServer, JidRequestor, JidArchive, Start, End, With, RSM, MsgType) ->
select(LServer, JidRequestor, JidArchive, Query, MsgType) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:select(LServer, JidRequestor, JidArchive, Start, End, With, RSM,
MsgType).
Mod:select(LServer, JidRequestor, JidArchive, Query, MsgType).
msg_to_el(#archive_msg{timestamp = TS, packet = Pkt1, nick = Nick, peer = Peer},
MsgType, JidRequestor, #jid{lserver = LServer} = JidArchive) ->
Pkt2 = maybe_update_from_to(Pkt1, JidRequestor, JidArchive, Peer, MsgType,
Nick),
Pkt3 = #xmlel{name = <<"forwarded">>,
attrs = [{<<"xmlns">>, ?NS_FORWARD}],
children = [fxml:replace_tag_attr(
<<"xmlns">>, <<"jabber:client">>, Pkt2)]},
jlib:add_delay_info(Pkt3, LServer, TS).
#forwarded{sub_els = [Pkt2],
delay = #delay{stamp = TS, from = jid:make(LServer)}}.
maybe_update_from_to(#xmlel{children = Els} = Pkt, JidRequestor, JidArchive,
maybe_update_from_to(#message{sub_els = Els} = Pkt, JidRequestor, JidArchive,
Peer, {groupchat, Role,
#state{config = #config{anonymous = Anon}}},
Nick) ->
@ -919,18 +835,13 @@ maybe_update_from_to(#xmlel{children = Els} = Pkt, JidRequestor, JidArchive,
end,
Items = case ExposeJID of
true ->
[#xmlel{name = <<"x">>,
attrs = [{<<"xmlns">>, ?NS_MUC_USER}],
children =
[#xmlel{name = <<"item">>,
attrs = [{<<"jid">>,
jid:to_string(Peer)}]}]}];
[#muc_user{items = [#muc_item{jid = Peer}]}];
false ->
[]
end,
Pkt1 = Pkt#xmlel{children = Items ++ Els},
Pkt2 = jlib:replace_from(jid:replace_resource(JidArchive, Nick), Pkt1),
jlib:remove_attr(<<"to">>, Pkt2);
Pkt#message{from = jid:replace_resource(JidArchive, Nick),
to = undefined,
sub_els = Items ++ Els};
maybe_update_from_to(Pkt, _JidRequestor, _JidArchive, _Peer, chat, _Nick) ->
Pkt.
@ -966,62 +877,46 @@ is_bare_copy(#jid{luser = U, lserver = S, lresource = R}, To) ->
false
end.
send(From, To, Msgs, RSM, Count, IsComplete, #iq{sub_el = SubEl} = IQ) ->
QID = fxml:get_tag_attr_s(<<"queryid">>, SubEl),
NS = fxml:get_tag_attr_s(<<"xmlns">>, SubEl),
QIDAttr = if QID /= <<>> ->
[{<<"queryid">>, QID}];
true ->
[]
end,
CompleteAttr = if NS == ?NS_MAM_TMP ->
[];
NS == ?NS_MAM_0; NS == ?NS_MAM_1 ->
[{<<"complete">>, jlib:atom_to_binary(IsComplete)}]
end,
-spec send([{binary(), integer(), xmlel()}],
non_neg_integer(), boolean(), iq()) -> iq() | ignore.
send(Msgs, Count, IsComplete,
#iq{from = From, to = To,
sub_els = [#mam_query{id = QID, xmlns = NS}]} = IQ) ->
Els = lists:map(
fun({ID, _IDInt, El}) ->
#xmlel{name = <<"message">>,
children = [#xmlel{name = <<"result">>,
attrs = [{<<"xmlns">>, NS},
{<<"id">>, ID}|QIDAttr],
children = [El]}]}
#message{sub_els = [#mam_result{xmlns = NS,
id = ID,
queryid = QID,
sub_els = [El]}]}
end, Msgs),
RSMOut = make_rsm_out(Msgs, RSM, Count, QIDAttr ++ CompleteAttr, NS),
RSMOut = make_rsm_out(Msgs, Count),
Result = if NS == ?NS_MAM_TMP ->
#mam_query{xmlns = NS, id = QID, rsm = RSMOut};
true ->
#mam_fin{id = QID, rsm = RSMOut, complete = IsComplete}
end,
if NS == ?NS_MAM_TMP; NS == ?NS_MAM_1 ->
lists:foreach(
fun(El) ->
ejabberd_router:route(To, From, El)
end, Els),
IQ#iq{type = result, sub_el = RSMOut};
xmpp:make_iq_result(IQ, Result);
NS == ?NS_MAM_0 ->
ejabberd_router:route(
To, From, jlib:iq_to_xml(IQ#iq{type = result, sub_el = []})),
ejabberd_router:route(To, From, xmpp:make_iq_result(IQ)),
lists:foreach(
fun(El) ->
ejabberd_router:route(To, From, El)
end, Els),
ejabberd_router:route(
To, From, #xmlel{name = <<"message">>,
children = RSMOut}),
ejabberd_router:route(To, From, #message{sub_els = [Result]}),
ignore
end.
make_rsm_out([], _, Count, Attrs, NS) ->
Tag = if NS == ?NS_MAM_TMP -> <<"query">>;
true -> <<"fin">>
end,
[#xmlel{name = Tag, attrs = [{<<"xmlns">>, NS}|Attrs],
children = jlib:rsm_encode(#rsm_out{count = Count})}];
make_rsm_out([{FirstID, _, _}|_] = Msgs, _, Count, Attrs, NS) ->
-spec make_rsm_out([{binary(), integer(), xmlel()}], non_neg_integer()) -> rsm_set().
make_rsm_out([], Count) ->
#rsm_set{count = Count};
make_rsm_out([{FirstID, _, _}|_] = Msgs, Count) ->
{LastID, _, _} = lists:last(Msgs),
Tag = if NS == ?NS_MAM_TMP -> <<"query">>;
true -> <<"fin">>
end,
[#xmlel{name = Tag, attrs = [{<<"xmlns">>, NS}|Attrs],
children = jlib:rsm_encode(
#rsm_out{first = FirstID, count = Count,
last = LastID})}].
#rsm_set{first = #rsm_first{data = FirstID}, last = LastID, count = Count}.
filter_by_max(Msgs, undefined) ->
{Msgs, true};
@ -1030,23 +925,24 @@ filter_by_max(Msgs, Len) when is_integer(Len), Len >= 0 ->
filter_by_max(_Msgs, _Junk) ->
{[], true}.
-spec limit_max(rsm_set(), binary()) -> rsm_set().
limit_max(RSM, ?NS_MAM_TMP) ->
RSM; % XEP-0313 v0.2 doesn't require clients to support RSM.
limit_max(#rsm_in{max = Max} = RSM, _NS) when not is_integer(Max) ->
RSM#rsm_in{max = ?DEF_PAGE_SIZE};
limit_max(#rsm_in{max = Max} = RSM, _NS) when Max > ?MAX_PAGE_SIZE ->
RSM#rsm_in{max = ?MAX_PAGE_SIZE};
limit_max(#rsm_set{max = Max} = RSM, _NS) when not is_integer(Max) ->
RSM#rsm_set{max = ?DEF_PAGE_SIZE};
limit_max(#rsm_set{max = Max} = RSM, _NS) when Max > ?MAX_PAGE_SIZE ->
RSM#rsm_set{max = ?MAX_PAGE_SIZE};
limit_max(RSM, _NS) ->
RSM.
match_interval(Now, Start, End) ->
(Now >= Start) and (Now =< End).
match_rsm(Now, #rsm_in{id = ID, direction = aft}) when ID /= <<"">> ->
Now1 = (catch usec_to_now(jlib:binary_to_integer(ID))),
match_rsm(Now, #rsm_set{'after' = ID}) when is_binary(ID), ID /= <<"">> ->
Now1 = (catch usec_to_now(binary_to_integer(ID))),
Now > Now1;
match_rsm(Now, #rsm_in{id = ID, direction = before}) when ID /= <<"">> ->
Now1 = (catch usec_to_now(jlib:binary_to_integer(ID))),
match_rsm(Now, #rsm_set{before = ID}) when is_binary(ID), ID /= <<"">> ->
Now1 = (catch usec_to_now(binary_to_integer(ID))),
Now < Now1;
match_rsm(_Now, _) ->
true.
@ -1066,15 +962,10 @@ datetime_to_now(DateTime, USecs) ->
calendar:datetime_to_gregorian_seconds({{1970, 1, 1}, {0, 0, 0}}),
{Seconds div 1000000, Seconds rem 1000000, USecs}.
get_jids(Els) ->
lists:flatmap(
fun(#xmlel{name = <<"jid">>} = El) ->
J = jid:from_string(fxml:get_tag_cdata(El)),
[jid:tolower(jid:remove_resource(J)),
jid:tolower(J)];
(_) ->
[]
end, Els).
get_jids(undefined) ->
[];
get_jids(Js) ->
[jid:tolower(jid:remove_resource(J)) || J <- Js].
get_commands_spec() ->
[#ejabberd_commands{name = delete_old_mam_messages, tags = [purge],

View File

@ -12,10 +12,10 @@
%% API
-export([init/2, remove_user/2, remove_room/3, delete_old_messages/3,
extended_fields/0, store/7, write_prefs/4, get_prefs/2, select/8]).
extended_fields/0, store/7, write_prefs/4, get_prefs/2, select/5]).
-include_lib("stdlib/include/ms_transform.hrl").
-include("jlib.hrl").
-include("xmpp.hrl").
-include("logger.hrl").
-include("mod_mam.hrl").
@ -132,7 +132,8 @@ get_prefs(LUser, LServer) ->
select(_LServer, JidRequestor,
#jid{luser = LUser, lserver = LServer} = JidArchive,
Start, End, With, RSM, MsgType) ->
#mam_query{start = Start, 'end' = End,
with = With, rsm = RSM}, MsgType) ->
MS = make_matchspec(LUser, LServer, Start, End, With),
Msgs = mnesia:dirty_select(archive_msg, MS),
SortedMsgs = lists:keysort(#archive_msg.timestamp, Msgs),
@ -174,7 +175,7 @@ make_matchspec(LUser, LServer, Start, End, {_, _, _} = With) ->
Peer == With ->
Msg
end);
make_matchspec(LUser, LServer, Start, End, none) ->
make_matchspec(LUser, LServer, Start, End, undefined) ->
ets:fun2ms(
fun(#archive_msg{timestamp = TS,
us = US,
@ -184,28 +185,27 @@ make_matchspec(LUser, LServer, Start, End, none) ->
Msg
end).
filter_by_rsm(Msgs, none) ->
filter_by_rsm(Msgs, undefined) ->
{Msgs, true};
filter_by_rsm(_Msgs, #rsm_in{max = Max}) when Max < 0 ->
filter_by_rsm(_Msgs, #rsm_set{max = Max}) when Max < 0 ->
{[], true};
filter_by_rsm(Msgs, #rsm_in{max = Max, direction = Direction, id = ID}) ->
NewMsgs = case Direction of
aft when ID /= <<"">> ->
filter_by_rsm(Msgs, #rsm_set{max = Max, before = Before, 'after' = After}) ->
NewMsgs = if is_binary(After), After /= <<"">> ->
lists:filter(
fun(#archive_msg{id = I}) ->
?BIN_GREATER_THAN(I, ID)
?BIN_GREATER_THAN(I, After)
end, Msgs);
before when ID /= <<"">> ->
is_binary(Before), Before /= <<"">> ->
lists:foldl(
fun(#archive_msg{id = I} = Msg, Acc)
when ?BIN_LESS_THAN(I, ID) ->
when ?BIN_LESS_THAN(I, Before) ->
[Msg|Acc];
(_, Acc) ->
Acc
end, [], Msgs);
before when ID == <<"">> ->
is_binary(Before), Before == <<"">> ->
lists:reverse(Msgs);
_ ->
true ->
Msgs
end,
filter_by_max(NewMsgs, Max).

View File

@ -14,10 +14,10 @@
%% API
-export([init/2, remove_user/2, remove_room/3, delete_old_messages/3,
extended_fields/0, store/7, write_prefs/4, get_prefs/2, select/8]).
extended_fields/0, store/7, write_prefs/4, get_prefs/2, select/5]).
-include_lib("stdlib/include/ms_transform.hrl").
-include("jlib.hrl").
-include("xmpp.hrl").
-include("mod_mam.hrl").
-include("logger.hrl").
-include("ejabberd_sql_pt.hrl").
@ -51,9 +51,7 @@ delete_old_messages(ServerHost, TimeStamp, Type) ->
ok.
extended_fields() ->
[#xmlel{name = <<"field">>,
attrs = [{<<"type">>, <<"text-single">>},
{<<"var">>, <<"withtext">>}]}].
[#xdata_field{type = 'text-single', var = <<"withtext">>}].
store(Pkt, LServer, {LUser, LHost}, Type, Peer, Nick, _Dir) ->
TSinteger = p1_time_compat:system_time(micro_seconds),
@ -126,13 +124,12 @@ get_prefs(LUser, LServer) ->
end.
select(LServer, JidRequestor, #jid{luser = LUser} = JidArchive,
Start, End, With, RSM, MsgType) ->
MAMQuery, MsgType) ->
User = case MsgType of
chat -> LUser;
{groupchat, _Role, _MUCState} -> jid:to_string(JidArchive)
end,
{Query, CountQuery} = make_sql_query(User, LServer,
Start, End, With, RSM),
{Query, CountQuery} = make_sql_query(User, LServer, MAMQuery),
% TODO from XEP-0313 v0.2: "To conserve resources, a server MAY place a
% reasonable limit on how many stanzas may be pushed to a client in one
% request. If a query returns a number of stanzas greater than this limit
@ -142,10 +139,7 @@ select(LServer, JidRequestor, #jid{luser = LUser} = JidArchive,
case {ejabberd_sql:sql_query(LServer, Query),
ejabberd_sql:sql_query(LServer, CountQuery)} of
{{selected, _, Res}, {selected, _, [[Count]]}} ->
{Max, Direction} = case RSM of
#rsm_in{max = M, direction = D} -> {M, D};
_ -> {undefined, undefined}
end,
{Max, Direction, _} = get_max_direction_id(MAMQuery#mam_query.rsm),
{Res1, IsComplete} =
if Max >= 0 andalso Max /= undefined andalso length(Res) > Max ->
if Direction == before ->
@ -200,15 +194,10 @@ usec_to_now(Int) ->
Sec = Secs rem 1000000,
{MSec, Sec, USec}.
make_sql_query(User, LServer, Start, End, With, RSM) ->
{Max, Direction, ID} = case RSM of
#rsm_in{} ->
{RSM#rsm_in.max,
RSM#rsm_in.direction,
RSM#rsm_in.id};
none ->
{none, none, <<>>}
end,
make_sql_query(User, LServer,
#mam_query{start = Start, 'end' = End, with = With,
withtext = WithText, rsm = RSM}) ->
{Max, Direction, ID} = get_max_direction_id(RSM),
ODBCType = ejabberd_config:get_option(
{sql_type, LServer},
ejabberd_sql:opt_type(sql_type)),
@ -228,12 +217,16 @@ make_sql_query(User, LServer, Start, End, With, RSM) ->
true ->
[]
end,
WithClause = case With of
{text, <<>>} ->
[];
{text, Txt} ->
[<<" and match (txt) against ('">>,
Escape(Txt), <<"')">>];
WithTextClause = case WithText of
{text, <<>>} ->
[];
{text, Txt} ->
[<<" and match (txt) against ('">>,
Escape(Txt), <<"')">>];
undefined ->
[]
end,
WithClause = case catch jid:tolower(With) of
{_, _, <<>>} ->
[<<" and bare_peer='">>,
Escape(jid:to_string(With)),
@ -242,7 +235,7 @@ make_sql_query(User, LServer, Start, End, With, RSM) ->
[<<" and peer='">>,
Escape(jid:to_string(With)),
<<"'">>];
none ->
_ ->
[]
end,
PageClause = case catch jlib:binary_to_integer(ID) of
@ -250,7 +243,7 @@ make_sql_query(User, LServer, Start, End, With, RSM) ->
case Direction of
before ->
[<<" AND timestamp < ">>, ID];
aft ->
'after' ->
[<<" AND timestamp > ">>, ID];
_ ->
[]
@ -276,7 +269,7 @@ make_sql_query(User, LServer, Start, End, With, RSM) ->
Query = [<<"SELECT ">>, TopClause, <<" timestamp, xml, peer, kind, nick"
" FROM archive WHERE username='">>,
SUser, <<"'">>, WithClause, StartClause, EndClause,
SUser, <<"'">>, WithClause, WithTextClause, StartClause, EndClause,
PageClause],
QueryPage =
@ -294,4 +287,20 @@ make_sql_query(User, LServer, Start, End, With, RSM) ->
end,
{QueryPage,
[<<"SELECT COUNT(*) FROM archive WHERE username='">>,
SUser, <<"'">>, WithClause, StartClause, EndClause, <<";">>]}.
SUser, <<"'">>, WithClause, WithTextClause, StartClause, EndClause, <<";">>]}.
-spec get_max_direction_id(rsm_set() | undefined) ->
{integer() | undefined,
before | 'after' | undefined,
binary()}.
get_max_direction_id(RSM) ->
case RSM of
#rsm_set{max = Max, before = Before} when is_binary(Before) ->
{Max, before, Before};
#rsm_set{max = Max, 'after' = After} when is_binary(After) ->
{Max, 'after', After};
#rsm_set{max = Max} ->
{Max, undefined, <<>>};
_ ->
{undefined, undefined, <<>>}
end.

View File

@ -43,7 +43,12 @@
forget_room/3,
create_room/5,
shutdown_rooms/1,
process_iq_disco_items/4,
process_disco_info/1,
process_disco_items/1,
process_vcard/1,
process_register/1,
process_muc_unique/1,
process_mucsub/1,
broadcast_service_message/2,
export/1,
import/1,
@ -58,7 +63,7 @@
-include("ejabberd.hrl").
-include("logger.hrl").
-include("jlib.hrl").
-include("xmpp.hrl").
-include("mod_muc.hrl").
-record(state,
@ -154,17 +159,6 @@ forget_room(ServerHost, Host, Name) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:forget_room(LServer, Host, Name).
process_iq_disco_items(Host, From, To,
#iq{lang = Lang} = IQ) ->
Rsm = jlib:rsm_decode(IQ),
DiscoNode = fxml:get_tag_attr_s(<<"node">>, IQ#iq.sub_el),
Res = IQ#iq{type = result,
sub_el =
[#xmlel{name = <<"query">>,
attrs = [{<<"xmlns">>, ?NS_DISCO_ITEMS}],
children = iq_disco_items(Host, From, Lang, DiscoNode, 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 = jid:nameprep(ServerHost),
@ -176,6 +170,8 @@ can_use_nick(ServerHost, Host, JID, Nick) ->
%%====================================================================
init([Host, Opts]) ->
IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
one_queue),
MyHost = gen_mod:get_opt_host(Host, Opts,
<<"conference.@HOST@">>),
Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
@ -255,6 +251,18 @@ init([Host, Opts]) ->
RoomShaper = gen_mod:get_opt(room_shaper, Opts,
fun(A) when is_atom(A) -> A end,
none),
gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_REGISTER,
?MODULE, process_register, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_VCARD,
?MODULE, process_vcard, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_MUCSUB,
?MODULE, process_mucsub, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_MUC_UNIQUE,
?MODULE, process_muc_unique, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_INFO,
?MODULE, process_disco_info, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_ITEMS,
?MODULE, process_disco_items, IQDisc),
ejabberd_router:register_route(MyHost, Host),
load_permanent_rooms(MyHost, Host,
{Access, AccessCreate, AccessAdmin, AccessPersistent},
@ -314,8 +322,14 @@ handle_info({mnesia_system_event, {mnesia_down, Node}}, State) ->
{noreply, State};
handle_info(_Info, State) -> {noreply, State}.
terminate(_Reason, State) ->
ejabberd_router:unregister_route(State#state.host),
terminate(_Reason, #state{host = MyHost}) ->
ejabberd_router:unregister_route(MyHost),
gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_REGISTER),
gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_VCARD),
gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_MUCSUB),
gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_MUC_UNIQUE),
gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_INFO),
gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_ITEMS),
ok.
code_change(_OldVsn, State, _Extra) -> {ok, State}.
@ -331,197 +345,162 @@ do_route(Host, ServerHost, Access, HistorySize, RoomShaper,
allow ->
do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
From, To, Packet, DefRoomOpts);
_ ->
#xmlel{attrs = Attrs} = Packet,
Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
deny ->
Lang = xmpp:get_lang(Packet),
ErrText = <<"Access denied by service policy">>,
Err = jlib:make_error_reply(Packet,
?ERRT_FORBIDDEN(Lang, ErrText)),
ejabberd_router:route_error(To, From, Err, Packet)
Err = xmpp:err_forbidden(ErrText, Lang),
ejabberd_router:route_error(To, From, Packet, Err)
end.
do_route1(_Host, _ServerHost, _Access, _HistorySize, _RoomShaper,
From, #jid{luser = <<"">>, lresource = <<"">>} = To,
#iq{} = IQ, _DefRoomOpts) ->
ejabberd_local:process_iq(From, To, IQ);
do_route1(Host, ServerHost, Access, _HistorySize, _RoomShaper,
From, #jid{luser = <<"">>, lresource = <<"">>} = To,
#message{lang = Lang, body = Body, type = Type} = Packet, _) ->
{_AccessRoute, _AccessCreate, AccessAdmin, _AccessPersistent} = Access,
if Type == error ->
ok;
true ->
case acl:match_rule(ServerHost, AccessAdmin, From) of
allow ->
Msg = xmpp:get_text(Body),
broadcast_service_message(Host, Msg);
deny ->
ErrText = <<"Only service administrators are allowed "
"to send service messages">>,
Err = xmpp:make_error(
Packet, xmpp:err_forbidden(ErrText, Lang)),
ejabberd_router:route(To, From, Err)
end
end;
do_route1(_Host, _ServerHost, _Access, _HistorySize, _RoomShaper,
From, #jid{luser = <<"">>} = To, Packet, _DefRoomOpts) ->
Err = xmpp:err_item_not_found(),
ejabberd_router:route_error(To, From, Packet, Err);
do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
From, To, Packet, DefRoomOpts) ->
{_AccessRoute, AccessCreate, AccessAdmin, _AccessPersistent} = Access,
{_AccessRoute, AccessCreate, _AccessAdmin, _AccessPersistent} = Access,
{Room, _, Nick} = jid:tolower(To),
#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 =
[#xmlel{name = <<"query">>,
attrs =
[{<<"xmlns">>, XMLNS}],
children =
iq_disco_info(
ServerHost, 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;
#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_MUCSUB,
sub_el = #xmlel{name = <<"subscriptions">>} = SubEl} = IQ ->
RoomJIDs = get_subscribed_rooms(ServerHost, Host, From),
Subs = lists:map(
fun(J) ->
#xmlel{name = <<"subscription">>,
attrs = [{<<"jid">>,
jid:to_string(J)}]}
end, RoomJIDs),
Res = IQ#iq{type = result,
sub_el = [SubEl#xmlel{children = Subs}]},
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 fxml:get_attr_s(<<"type">>, Attrs) of
<<"error">> -> ok;
_ ->
case acl:match_rule(ServerHost, AccessAdmin, From)
of
allow ->
Msg = fxml:get_path_s(Packet,
[{elem, <<"body">>},
cdata]),
broadcast_service_message(Host, Msg);
_ ->
Lang = fxml: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 fxml: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;
_ ->
case mnesia:dirty_read(muc_online_room, {Room, Host}) of
[] ->
Type = fxml:get_attr_s(<<"type">>, Attrs),
case {Name, Type} of
{<<"presence">>, <<"">>} ->
case check_user_can_create_room(ServerHost,
AccessCreate, From, Room) and
check_create_roomid(ServerHost, Room) of
true ->
{ok, Pid} = start_new_room(Host, ServerHost, Access,
Room, HistorySize,
RoomShaper, From, Nick, DefRoomOpts),
register_room(Host, Room, Pid),
mod_muc_room:route(Pid, From, Nick, Packet),
ok;
false ->
Lang = fxml: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 = fxml:get_attr_s(<<"xml:lang">>, Attrs),
ErrText = <<"Conference room does not exist">>,
Err = jlib:make_error_reply(Packet,
?ERRT_ITEM_NOT_FOUND(Lang, ErrText)),
case mnesia:dirty_read(muc_online_room, {Room, Host}) of
[] ->
case Packet of
#presence{type = available, lang = Lang} ->
case check_user_can_create_room(
ServerHost, AccessCreate, From, Room) and
check_create_roomid(ServerHost, Room) of
true ->
{ok, Pid} = start_new_room(
Host, ServerHost, Access,
Room, HistorySize,
RoomShaper, From, Nick, DefRoomOpts),
register_room(Host, Room, Pid),
mod_muc_room:route(Pid, From, Nick, Packet),
ok;
false ->
ErrText = <<"Room creation is denied by service policy">>,
Err = xmpp:make_error(
Packet, xmpp:err_forbidden(ErrText, Lang)),
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 = xmpp:get_lang(Packet),
ErrText = <<"Conference room does not exist">>,
Err = xmpp:err_item_not_found(ErrText, Lang),
ejabberd_router:route_error(To, From, Packet, 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.
-spec process_vcard(iq()) -> iq().
process_vcard(#iq{type = get, lang = Lang} = IQ) ->
Desc = translate:translate(Lang, <<"ejabberd MUC module">>),
Copyright = <<"Copyright (c) 2003-2016 ProcessOne">>,
xmpp:make_iq_result(
IQ, #vcard_temp{fn = <<"ejabberd/mod_muc">>,
url = ?EJABBERD_URI,
desc = <<Desc/binary, $\n, Copyright/binary>>});
process_vcard(#iq{type = set, lang = Lang} = IQ) ->
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)).
-spec process_register(iq()) -> iq().
process_register(#iq{type = get, from = From, to = To, lang = Lang} = IQ) ->
Host = To#jid.lserver,
ServerHost = ejabberd_router:host_of_route(Host),
xmpp:make_iq_result(IQ, iq_get_register_info(ServerHost, Host, From, Lang));
process_register(#iq{type = set, from = From, to = To,
lang = Lang, sub_els = [El]} = IQ) ->
Host = To#jid.lserver,
ServerHost = ejabberd_router:host_of_route(Host),
case process_iq_register_set(ServerHost, Host, From, El, Lang) of
{result, Result} ->
xmpp:make_iq_result(IQ, Result);
{error, Err} ->
xmpp:make_error(IQ, Err)
end.
-spec process_disco_info(iq()) -> iq().
process_disco_info(#iq{type = set, lang = Lang} = IQ) ->
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
process_disco_info(#iq{type = get, to = To, lang = Lang,
sub_els = [#disco_info{node = undefined}]} = IQ) ->
ServerHost = ejabberd_router:host_of_route(To#jid.lserver),
X = ejabberd_hooks:run_fold(disco_info, ServerHost, [],
[ServerHost, ?MODULE, undefined, Lang]),
MAMFeatures = case gen_mod:is_loaded(ServerHost, mod_mam) of
true -> [?NS_MAM_TMP, ?NS_MAM_0, ?NS_MAM_1];
false -> []
end,
Features = [?NS_DISCO_INFO, ?NS_DISCO_ITEMS,
?NS_REGISTER, ?NS_MUC, ?NS_RSM,
?NS_VCARD, ?NS_MUCSUB, ?NS_MUC_UNIQUE | MAMFeatures],
Identity = #identity{category = <<"conference">>,
type = <<"text">>,
name = translate:translate(Lang, <<"Chatrooms">>)},
xmpp:make_iq_result(
IQ, #disco_info{features = Features,
identities = [Identity],
xdata = X});
process_disco_info(#iq{type = get, lang = Lang} = IQ) ->
xmpp:make_error(IQ, xmpp:err_item_not_found(<<"No info available">>, Lang)).
-spec process_disco_items(iq()) -> iq().
process_disco_items(#iq{type = set, lang = Lang} = IQ) ->
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
process_disco_items(#iq{type = get, from = From, to = To, lang = Lang,
sub_els = [#disco_items{node = Node, rsm = RSM}]} = IQ) ->
Host = To#jid.lserver,
xmpp:make_iq_result(
IQ, #disco_items{node = Node,
items = iq_disco_items(Host, From, Lang, Node, RSM)}).
-spec process_muc_unique(iq()) -> iq().
process_muc_unique(#iq{type = set, lang = Lang} = IQ) ->
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
process_muc_unique(#iq{from = From, type = get} = IQ) ->
Name = p1_sha:sha(term_to_binary([From, p1_time_compat:timestamp(),
randoms:get_string()])),
xmpp:make_iq_result(IQ, #muc_unique{name = Name}).
-spec process_mucsub(iq()) -> iq().
process_mucsub(#iq{type = set, lang = Lang} = IQ) ->
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
process_mucsub(#iq{type = get, from = From, to = To} = IQ) ->
Host = To#jid.lserver,
ServerHost = ejabberd_router:host_of_route(Host),
RoomJIDs = get_subscribed_rooms(ServerHost, Host, From),
xmpp:make_iq_result(IQ, #muc_subscriptions{list = RoomJIDs}).
check_user_can_create_room(ServerHost, AccessCreate,
From, _RoomID) ->
case acl:match_rule(ServerHost, AccessCreate, From) of
@ -583,61 +562,21 @@ register_room(Host, Room, Pid) ->
end,
mnesia:transaction(F).
iq_disco_info(ServerHost, Lang) ->
[#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_MUCSUB}], children = []},
#xmlel{name = <<"feature">>,
attrs = [{<<"var">>, ?NS_VCARD}], children = []}] ++
case gen_mod:is_loaded(ServerHost, mod_mam) of
true ->
[#xmlel{name = <<"feature">>,
attrs = [{<<"var">>, ?NS_MAM_TMP}]},
#xmlel{name = <<"feature">>,
attrs = [{<<"var">>, ?NS_MAM_0}]},
#xmlel{name = <<"feature">>,
attrs = [{<<"var">>, ?NS_MAM_1}]}];
false ->
[]
end.
iq_disco_items(Host, From, Lang, <<>>, none) ->
iq_disco_items(Host, From, Lang, undefined, undefined) ->
Rooms = get_vh_rooms(Host),
case erlang:length(Rooms) < ?MAX_ROOMS_DISCOITEMS of
true ->
iq_disco_items_list(Host, Rooms, {get_disco_item, all, From, Lang});
false ->
iq_disco_items(Host, From, Lang, <<"nonemptyrooms">>, none)
iq_disco_items(Host, From, Lang, <<"nonemptyrooms">>, undefined)
end;
iq_disco_items(Host, From, Lang, <<"nonemptyrooms">>, none) ->
XmlEmpty = #xmlel{name = <<"item">>,
attrs =
[{<<"jid">>, <<"conference.localhost">>},
{<<"node">>, <<"emptyrooms">>},
{<<"name">>, translate:translate(Lang, <<"Empty Rooms">>)}],
children = []},
iq_disco_items(Host, From, Lang, <<"nonemptyrooms">>, undefined) ->
Empty = #disco_item{jid = jid:make(<<"conference.localhost">>),
node = <<"emptyrooms">>,
name = translate:translate(Lang, <<"Empty Rooms">>)},
Query = {get_disco_item, only_non_empty, From, Lang},
[XmlEmpty | iq_disco_items_list(Host, get_vh_rooms(Host), Query)];
iq_disco_items(Host, From, Lang, <<"emptyrooms">>, none) ->
[Empty | iq_disco_items_list(Host, get_vh_rooms(Host), Query)];
iq_disco_items(Host, From, Lang, <<"emptyrooms">>, undefined) ->
iq_disco_items_list(Host, get_vh_rooms(Host), {get_disco_item, 0, From, Lang});
iq_disco_items(Host, From, Lang, _DiscoNode, Rsm) ->
{Rooms, RsmO} = get_vh_rooms(Host, Rsm),
@ -645,62 +584,55 @@ iq_disco_items(Host, From, Lang, _DiscoNode, Rsm) ->
iq_disco_items_list(Host, Rooms, {get_disco_item, all, From, Lang}) ++ RsmOut.
iq_disco_items_list(Host, Rooms, Query) ->
lists:zf(fun (#muc_online_room{name_host =
{Name, _Host},
pid = Pid}) ->
case catch gen_fsm:sync_send_all_state_event(Pid,
Query,
100)
of
{item, Desc} ->
flush(),
{true,
#xmlel{name = <<"item">>,
attrs =
[{<<"jid">>,
jid:to_string({Name, Host,
<<"">>})},
{<<"name">>, Desc}],
children = []}};
_ -> false
end
end, Rooms).
lists:zf(
fun(#muc_online_room{name_host = {Name, _Host}, pid = Pid}) ->
case catch gen_fsm:sync_send_all_state_event(Pid, Query, 100) of
{item, Desc} ->
flush(),
{true, #disco_item{jid = jid:make(Name, Host),
name = Desc}};
_ ->
false
end
end, Rooms).
get_vh_rooms(Host, #rsm_in{max=M, direction=Direction, id=I, index=Index})->
AllRooms = lists:sort(get_vh_rooms(Host)),
Count = erlang:length(AllRooms),
Guard = case Direction of
_ when Index =/= undefined -> [{'==', {element, 2, '$1'}, Host}];
aft -> [{'==', {element, 2, '$1'}, Host}, {'>=',{element, 1, '$1'} ,I}];
before when I =/= []-> [{'==', {element, 2, '$1'}, Host}, {'=<',{element, 1, '$1'} ,I}];
_ -> [{'==', {element, 2, '$1'}, Host}]
end,
L = lists:sort(
mnesia:dirty_select(muc_online_room,
[{#muc_online_room{name_host = '$1', _ = '_'},
Guard,
['$_']}])),
L2 = if
Index == undefined andalso Direction == before ->
lists:reverse(lists:sublist(lists:reverse(L), 1, M));
Index == undefined ->
lists:sublist(L, 1, M);
Index > Count orelse Index < 0 ->
[];
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}}
end.
get_vh_rooms(_, _) ->
todo.
%% get_vh_rooms(Host, #rsm_in{max=M, direction=Direction, id=I, index=Index})->
%% AllRooms = lists:sort(get_vh_rooms(Host)),
%% Count = erlang:length(AllRooms),
%% Guard = case Direction of
%% _ when Index =/= undefined -> [{'==', {element, 2, '$1'}, Host}];
%% aft -> [{'==', {element, 2, '$1'}, Host}, {'>=',{element, 1, '$1'} ,I}];
%% before when I =/= []-> [{'==', {element, 2, '$1'}, Host}, {'=<',{element, 1, '$1'} ,I}];
%% _ -> [{'==', {element, 2, '$1'}, Host}]
%% end,
%% L = lists:sort(
%% mnesia:dirty_select(muc_online_room,
%% [{#muc_online_room{name_host = '$1', _ = '_'},
%% Guard,
%% ['$_']}])),
%% L2 = if
%% Index == undefined andalso Direction == before ->
%% lists:reverse(lists:sublist(lists:reverse(L), 1, M));
%% Index == undefined ->
%% lists:sublist(L, 1, M);
%% Index > Count orelse Index < 0 ->
%% [];
%% 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}}
%% end.
get_subscribed_rooms(ServerHost, Host, From) ->
Rooms = get_rooms(ServerHost, Host),
@ -730,60 +662,32 @@ get_room_pos(Desired, [_ | Rooms], HeadPosition) ->
flush() -> receive _ -> flush() after 0 -> ok end.
-define(XFIELD(Type, Label, Var, Val),
#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,
p1_sha:sha(term_to_binary([From, p1_time_compat:timestamp(),
randoms:get_string()]))}.
get_nick(ServerHost, Host, From) ->
LServer = jid:nameprep(ServerHost),
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:get_nick(LServer, Host, From).
iq_get_register_info(ServerHost, Host, From, Lang) ->
{Nick, Registered} = case get_nick(ServerHost, Host,
From)
of
error -> {<<"">>, []};
N ->
{N,
[#xmlel{name = <<"registered">>, attrs = [],
children = []}]}
{Nick, Registered} = case get_nick(ServerHost, Host, From) of
error -> {<<"">>, false};
N -> {N, true}
end,
Registered ++
[#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},
{<<"type">>, <<"form">>}],
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)]}].
Title = <<(translate:translate(
Lang, <<"Nickname Registration at ">>))/binary, Host/binary>>,
Inst = translate:translate(Lang, <<"Enter nickname you want to register">>),
Field = #xdata_field{type = 'text-single',
label = translate:translate(Lang, <<"Nickname">>),
var = <<"nick">>,
values = [Nick]},
X = #xdata{type = form, title = Title,
instructions = [Inst], fields = [Field]},
#register{nick = Nick,
registered = Registered,
instructions =
translate:translate(
Lang, <<"You need a client that supports x:data "
"to register the nickname">>),
xdata = X}.
set_nick(ServerHost, Host, From, Nick) ->
LServer = jid:nameprep(ServerHost),
@ -793,66 +697,43 @@ set_nick(ServerHost, Host, From, Nick) ->
iq_set_register_info(ServerHost, Host, From, Nick,
Lang) ->
case set_nick(ServerHost, Host, From, Nick) of
{atomic, ok} -> {result, []};
{atomic, ok} -> {result, undefined};
{atomic, false} ->
ErrText = <<"That nickname is registered by another "
"person">>,
{error, ?ERRT_CONFLICT(Lang, ErrText)};
{error, xmpp:err_conflict(ErrText, Lang)};
_ ->
Txt = <<"Database failure">>,
{error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt)}
{error, xmpp:err_internal_server_error(Txt, Lang)}
end.
process_iq_register_set(ServerHost, Host, From, SubEl,
Lang) ->
#xmlel{children = Els} = SubEl,
case fxml:get_subtag(SubEl, <<"remove">>) of
false ->
case fxml:remove_cdata(Els) of
[#xmlel{name = <<"x">>} = XEl] ->
case {fxml:get_tag_attr_s(<<"xmlns">>, XEl),
fxml:get_tag_attr_s(<<"type">>, XEl)}
of
{?NS_XDATA, <<"cancel">>} -> {result, []};
{?NS_XDATA, <<"submit">>} ->
XData = jlib:parse_xdata_submit(XEl),
case XData of
invalid ->
Txt = <<"Incorrect data form">>,
{error, ?ERRT_BAD_REQUEST(Lang, Txt)};
_ ->
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)
process_iq_register_set(ServerHost, Host, From,
#register{remove = true}, Lang) ->
iq_set_register_info(ServerHost, Host, From, <<"">>, Lang);
process_iq_register_set(_ServerHost, _Host, _From,
#register{xdata = #xdata{type = cancel}}, _Lang) ->
{result, undefined};
process_iq_register_set(ServerHost, Host, From,
#register{nick = Nick, xdata = XData}, Lang) ->
case XData of
#xdata{type = submit, fields = Fs} ->
case lists:keyfind(<<"nick">>, #xdata_field.var, Fs) of
#xdata_field{values = [N]} ->
iq_set_register_info(ServerHost, Host, From, N, Lang);
_ ->
ErrText = <<"You must fill in field \"Nickname\" in the form">>,
{error, xmpp:err_not_acceptable(ErrText, Lang)}
end;
#xdata{} ->
Txt = <<"Incorrect data form">>,
{error, xmpp:err_bad_request(Txt, Lang)};
_ when is_binary(Nick), Nick /= <<"">> ->
iq_set_register_info(ServerHost, Host, From, Nick, Lang);
_ ->
ErrText = <<"You must fill in field \"Nickname\" in the form">>,
{error, xmpp:err_not_acceptable(ErrText, Lang)}
end.
iq_get_vcard(Lang) ->
[#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-2016 ProcessOne">>}]}].
broadcast_service_message(Host, Msg) ->
lists:foreach(
fun(#muc_online_room{pid = Pid}) ->

View File

@ -24,7 +24,7 @@
-include("ejabberd.hrl").
-include("logger.hrl").
-include("jlib.hrl").
-include("xmpp.hrl").
-include("mod_muc_room.hrl").
-include("mod_muc.hrl").
-include("ejabberd_http.hrl").
@ -241,7 +241,7 @@ web_menu_host(Acc, _Host, Lang) ->
-define(TDTD(L, N),
?XE(<<"tr">>, [?XCT(<<"td">>, L),
?XC(<<"td">>, jlib:integer_to_binary(N))
?XC(<<"td">>, integer_to_binary(N))
])).
web_page_main(_, #request{path=[<<"muc">>], lang = Lang} = _Request) ->
@ -283,7 +283,7 @@ get_sort_query(Q) ->
get_sort_query2(Q) ->
{value, {_, String}} = lists:keysearch(<<"sort">>, 1, Q),
Integer = jlib:binary_to_integer(String),
Integer = binary_to_integer(String),
case Integer >= 0 of
true -> {ok, {normal, Integer}};
false -> {ok, {reverse, abs(Integer)}}
@ -309,7 +309,7 @@ make_rooms_page(Host, Lang, {Sort_direction, Sort_column}) ->
{Titles_TR, _} =
lists:mapfoldl(
fun(Title, Num_column) ->
NCS = jlib:integer_to_binary(Num_column),
NCS = integer_to_binary(Num_column),
TD = ?XE(<<"td">>, [?CT(Title),
?C(<<" ">>),
?AC(<<"?sort=", NCS/binary>>, <<"<">>),
@ -383,7 +383,7 @@ prepare_room_info(Room_info) ->
Just_created,
Title} = Room_info,
[NameHost,
jlib:integer_to_binary(Num_participants),
integer_to_binary(Num_participants),
Ts_last_message,
jlib:atom_to_binary(Public),
jlib:atom_to_binary(Persistent),
@ -830,7 +830,7 @@ get_options(Config) ->
Fields = [jlib:atom_to_binary(Field) || Field <- record_info(fields, config)],
[config | ValuesRaw] = tuple_to_list(Config),
Values = lists:map(fun(V) when is_atom(V) -> jlib:atom_to_binary(V);
(V) when is_integer(V) -> jlib:integer_to_binary(V);
(V) when is_integer(V) -> integer_to_binary(V);
(V) when is_tuple(V); is_list(V) -> list_to_binary(hd(io_lib:format("~w", [V])));
(V) -> V end, ValuesRaw),
lists:zip(Fields, Values).

View File

@ -46,7 +46,7 @@
-include("ejabberd.hrl").
-include("logger.hrl").
-include("jlib.hrl").
-include("xmpp.hrl").
-include("mod_muc.hrl").
-include("mod_muc_room.hrl").
@ -196,15 +196,13 @@ code_change(_OldVsn, State, _Extra) -> {ok, State}.
add_to_log2(text, {Nick, Packet}, Room, Opts, State) ->
case has_no_permanent_store_hint(Packet) of
false ->
case {fxml:get_subtag(Packet, <<"subject">>),
fxml:get_subtag(Packet, <<"body">>)}
of
{false, false} -> ok;
{false, SubEl} ->
Message = {body, fxml:get_tag_cdata(SubEl)},
case {Packet#message.subject, Packet#message.body} of
{[], []} -> ok;
{[], Body} ->
Message = {body, xmpp:get_text(Body)},
add_message_to_log(Nick, Message, Room, Opts, State);
{SubEl, _} ->
Message = {subject, fxml:get_tag_cdata(SubEl)},
{Subj, _} ->
Message = {subject, xmpp:get_text(Subj)},
add_message_to_log(Nick, Message, Room, Opts, State)
end;
true -> ok
@ -1035,7 +1033,7 @@ roomconfig_to_string(Options, Lang, FileFormat) ->
max_users ->
<<"<div class=\"rcot\">",
OptText/binary, ": \"",
(htmlize(jlib:integer_to_binary(T),
(htmlize(integer_to_binary(T),
FileFormat))/binary,
"\"</div>">>;
title ->
@ -1053,7 +1051,7 @@ roomconfig_to_string(Options, Lang, FileFormat) ->
allow_private_messages_from_visitors ->
<<"<div class=\"rcot\">",
OptText/binary, ": \"",
(htmlize(?T((jlib:atom_to_binary(T))),
(htmlize(?T(jlib:atom_to_binary(T)),
FileFormat))/binary,
"\"</div>">>;
_ -> <<"\"", T/binary, "\"">>
@ -1168,7 +1166,7 @@ get_room_occupants(RoomJIDString) ->
[{U#user.jid, U#user.nick, U#user.role}
|| {_, U} <- (?DICT):to_list(StateData#state.users)].
-spec get_room_state(binary(), binary()) -> muc_room_state().
-spec get_room_state(binary(), binary()) -> mod_muc_room:state().
get_room_state(RoomName, MucService) ->
case mnesia:dirty_read(muc_online_room,
@ -1180,7 +1178,7 @@ get_room_state(RoomName, MucService) ->
[] -> #state{}
end.
-spec get_room_state(pid()) -> muc_room_state().
-spec get_room_state(pid()) -> mod_muc_room:state().
get_room_state(RoomPid) ->
{ok, R} = gen_fsm:sync_send_all_state_event(RoomPid,
@ -1204,14 +1202,10 @@ fjoin(FileList) ->
list_to_binary(filename:join([binary_to_list(File) || File <- FileList])).
has_no_permanent_store_hint(Packet) ->
fxml:get_subtag_with_xmlns(Packet, <<"no-store">>, ?NS_HINTS)
=/= false orelse
fxml:get_subtag_with_xmlns(Packet, <<"no-storage">>, ?NS_HINTS)
=/= false orelse
fxml:get_subtag_with_xmlns(Packet, <<"no-permanent-store">>, ?NS_HINTS)
=/= false orelse
fxml:get_subtag_with_xmlns(Packet, <<"no-permanent-storage">>, ?NS_HINTS)
=/= false.
xmpp:has_subtag(Packet, #hint{type = 'no-store'}) orelse
xmpp:has_subtag(Packet, #hint{type = 'no-storage'}) orelse
xmpp:has_subtag(Packet, #hint{type = 'no-permanent-store'}) orelse
xmpp:has_subtag(Packet, #hint{type = 'no-permanent-storage'}).
mod_opt_type(access_log) ->
fun (A) when is_atom(A) -> A end;

File diff suppressed because it is too large Load Diff

View File

@ -72,7 +72,7 @@ process_sm_iq(#iq{type = Type, lang = Lang,
case filter_xmlels(Els0) of
[] ->
Txt = <<"No private data found in this query">>,
xmpp:make_error(IQ, xmpp:err_bad_format(Txt, Lang));
xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang));
Data when Type == set ->
set_data(LUser, LServer, Data),
xmpp:make_iq_result(IQ);

View File

@ -16,22 +16,33 @@
set_type/2, set_to/2, set_from/2, set_id/2,
set_lang/2, set_error/2, set_els/2, set_from_to/3,
format_error/1, is_stanza/1, set_subtag/2, get_subtag/2,
remove_subtag/2, has_subtag/2, decode_els/1, pp/1,
get_name/1, get_text/1, mk_text/1, mk_text/2]).
remove_subtag/2, has_subtag/2, decode_els/1, decode_els/2,
pp/1, get_name/1, get_text/1, mk_text/1, mk_text/2]).
%% XMPP errors
-export([err_bad_request/0, err_bad_request/2,
err_bad_format/0, err_bad_format/2,
err_not_allowed/0, err_not_allowed/2,
err_conflict/0, err_conflict/2,
err_forbidden/0, err_forbidden/2,
err_not_acceptable/0, err_not_acceptable/2,
err_internal_server_error/0, err_internal_server_error/2,
err_service_unavailable/0, err_service_unavailable/2,
err_item_not_found/0, err_item_not_found/2,
err_jid_malformed/0, err_jid_malformed/2,
err_not_authorized/0, err_not_authorized/2,
err_feature_not_implemented/0, err_feature_not_implemented/2]).
err_conflict/0, err_conflict/2,
err_feature_not_implemented/0, err_feature_not_implemented/2,
err_forbidden/0, err_forbidden/2,
err_gone/0, err_gone/2,
err_internal_server_error/0, err_internal_server_error/2,
err_item_not_found/0, err_item_not_found/2,
err_jid_malformed/0, err_jid_malformed/2,
err_not_acceptable/0, err_not_acceptable/2,
err_not_allowed/0, err_not_allowed/2,
err_not_authorized/0, err_not_authorized/2,
err_payment_required/0, err_payment_required/2,
err_policy_violation/0, err_policy_violation/2,
err_recipient_unavailable/0, err_recipient_unavailable/2,
err_redirect/0, err_redirect/2,
err_registration_required/0, err_registration_required/2,
err_remote_server_not_found/0, err_remote_server_not_found/2,
err_remote_server_timeout/0, err_remote_server_timeout/2,
err_resource_constraint/0, err_resource_constraint/2,
err_service_unavailable/0, err_service_unavailable/2,
err_subscription_required/0, err_subscription_required/2,
err_undefined_condition/0, err_undefined_condition/2,
err_unexpected_request/0, err_unexpected_request/2]).
%% XMPP stream errors
-export([serr_bad_format/0, serr_bad_format/2,
@ -246,9 +257,12 @@ decode(Pkt, _Opts) ->
(message()) -> message();
(presence()) -> presence().
decode_els(Stanza) ->
decode_els(Stanza, fun xmpp_codec:is_known_tag/1).
decode_els(Stanza, MatchFun) ->
Els = lists:map(
fun(#xmlel{} = El) ->
case xmpp_codec:is_known_tag(El) of
case MatchFun(El) of
true -> decode(El);
false -> El
end;
@ -287,10 +301,10 @@ set_subtag(Stanza, Tag) ->
set_els(Stanza, NewEls).
set_subtag([El|Els], Tag, TagName, XMLNS) ->
case {get_name(El), get_ns(El)} of
{TagName, XMLNS} ->
case match_tag(El, TagName, XMLNS) of
true ->
[Tag|Els];
_ ->
false ->
[El|set_subtag(Els, Tag, TagName, XMLNS)]
end;
set_subtag([], Tag, _, _) ->
@ -304,14 +318,14 @@ get_subtag(Stanza, Tag) ->
get_subtag(Els, TagName, XMLNS).
get_subtag([El|Els], TagName, XMLNS) ->
case {get_name(El), get_ns(El)} of
{TagName, XMLNS} ->
case match_tag(El, TagName, XMLNS) of
true ->
try
decode(El)
catch _:{xmpp_codec, _Why} ->
get_subtag(Els, TagName, XMLNS)
end;
_ ->
false ->
get_subtag(Els, TagName, XMLNS)
end;
get_subtag([], _, _) ->
@ -328,10 +342,10 @@ remove_subtag(Stanza, Tag) ->
set_els(Stanza, NewEls).
remove_subtag([El|Els], TagName, XMLNS) ->
case {get_name(El), get_ns(El)} of
{TagName, XMLNS} ->
case match_tag(El, TagName, XMLNS) of
true ->
remove_subtag(Els, TagName, XMLNS);
_ ->
false ->
[El|remove_subtag(Els, TagName, XMLNS)]
end;
remove_subtag([], _, _) ->
@ -345,10 +359,10 @@ has_subtag(Stanza, Tag) ->
has_subtag(Els, TagName, XMLNS).
has_subtag([El|Els], TagName, XMLNS) ->
case {get_name(El), get_ns(El)} of
{TagName, XMLNS} ->
case match_tag(El, TagName, XMLNS) of
true ->
true;
_ ->
false ->
has_subtag(Els, TagName, XMLNS)
end;
has_subtag([], _, _) ->
@ -385,14 +399,6 @@ err_bad_request() ->
err_bad_request(Text, Lang) ->
err(modify, 'bad-request', 400, Text, Lang).
-spec err_bad_format() -> error().
err_bad_format() ->
err(modify, 'bad-format', 406).
-spec err_bad_format(binary(), binary() | undefined) -> error().
err_bad_format(Text, Lang) ->
err(modify, 'bad-format', 406, Text, Lang).
-spec err_conflict() -> error().
err_conflict() ->
err(cancel, 'conflict', 409).
@ -401,14 +407,6 @@ err_conflict() ->
err_conflict(Text, Lang) ->
err(cancel, 'conflict', 409, Text, Lang).
-spec err_not_allowed() -> error().
err_not_allowed() ->
err(cancel, 'not-allowed', 405).
-spec err_not_allowed(binary(), binary() | undefined) -> error().
err_not_allowed(Text, Lang) ->
err(cancel, 'not-allowed', 405, Text, Lang).
-spec err_feature_not_implemented() -> error().
err_feature_not_implemented() ->
err(cancel, 'feature-not-implemented', 501).
@ -417,14 +415,6 @@ err_feature_not_implemented() ->
err_feature_not_implemented(Text, Lang) ->
err(cancel, 'feature-not-implemented', 501, Text, Lang).
-spec err_item_not_found() -> error().
err_item_not_found() ->
err(cancel, 'item-not-found', 404).
-spec err_item_not_found(binary(), binary() | undefined) -> error().
err_item_not_found(Text, Lang) ->
err(cancel, 'item-not-found', 404, Text, Lang).
-spec err_forbidden() -> error().
err_forbidden() ->
err(auth, 'forbidden', 403).
@ -433,14 +423,18 @@ err_forbidden() ->
err_forbidden(Text, Lang) ->
err(auth, 'forbidden', 403, Text, Lang).
-spec err_not_acceptable() -> error().
err_not_acceptable() ->
err(modify, 'not-acceptable', 406).
%% RFC 6120 says error type SHOULD be "cancel".
%% RFC 3920 and XEP-0082 says it SHOULD be "modify".
-spec err_gone() -> error().
err_gone() ->
err(modify, 'gone', 302).
-spec err_not_acceptable(binary(), binary() | undefined) -> error().
err_not_acceptable(Text, Lang) ->
err(modify, 'not-acceptable', 406, Text, Lang).
-spec err_gone(binary(), binary() | undefined) -> error().
err_gone(Text, Lang) ->
err(modify, 'gone', 302, Text, Lang).
%% RFC 6120 sasy error type SHOULD be "cancel".
%% RFC 3920 and XEP-0082 says it SHOULD be "wait".
-spec err_internal_server_error() -> error().
err_internal_server_error() ->
err(wait, 'internal-server-error', 500).
@ -449,13 +443,13 @@ err_internal_server_error() ->
err_internal_server_error(Text, Lang) ->
err(wait, 'internal-server-error', 500, Text, Lang).
-spec err_service_unavailable() -> error().
err_service_unavailable() ->
err(cancel, 'service-unavailable', 503).
-spec err_item_not_found() -> error().
err_item_not_found() ->
err(cancel, 'item-not-found', 404).
-spec err_service_unavailable(binary(), binary() | undefined) -> error().
err_service_unavailable(Text, Lang) ->
err(cancel, 'service-unavailable', 503, Text, Lang).
-spec err_item_not_found(binary(), binary() | undefined) -> error().
err_item_not_found(Text, Lang) ->
err(cancel, 'item-not-found', 404, Text, Lang).
-spec err_jid_malformed() -> error().
err_jid_malformed() ->
@ -465,6 +459,22 @@ err_jid_malformed() ->
err_jid_malformed(Text, Lang) ->
err(modify, 'jid-malformed', 400, Text, Lang).
-spec err_not_acceptable() -> error().
err_not_acceptable() ->
err(modify, 'not-acceptable', 406).
-spec err_not_acceptable(binary(), binary() | undefined) -> error().
err_not_acceptable(Text, Lang) ->
err(modify, 'not-acceptable', 406, Text, Lang).
-spec err_not_allowed() -> error().
err_not_allowed() ->
err(cancel, 'not-allowed', 405).
-spec err_not_allowed(binary(), binary() | undefined) -> error().
err_not_allowed(Text, Lang) ->
err(cancel, 'not-allowed', 405, Text, Lang).
-spec err_not_authorized() -> error().
err_not_authorized() ->
err(auth, 'not-authorized', 401).
@ -473,6 +483,108 @@ err_not_authorized() ->
err_not_authorized(Text, Lang) ->
err(auth, 'not-authorized', 401, Text, Lang).
-spec err_payment_required() -> error().
err_payment_required() ->
err(auth, 'not-authorized', 402).
-spec err_payment_required(binary(), binary() | undefined) -> error().
err_payment_required(Text, Lang) ->
err(auth, 'not-authorized', 402, Text, Lang).
%% <policy-violation/> is defined in neither RFC 3920 nor XEP-0086.
%% We choose '403' error code (as in <forbidden/>).
-spec err_policy_violation() -> error().
err_policy_violation() ->
err(modify, 'policy-violation', 403).
-spec err_policy_violation(binary(), binary() | undefined) -> error().
err_policy_violation(Text, Lang) ->
err(modify, 'policy-violation', 403, Text, Lang).
-spec err_recipient_unavailable() -> error().
err_recipient_unavailable() ->
err(wait, 'recipient-unavailable', 404).
-spec err_recipient_unavailable(binary(), binary() | undefined) -> error().
err_recipient_unavailable(Text, Lang) ->
err(wait, 'recipient-unavailable', 404, Text, Lang).
-spec err_redirect() -> error().
err_redirect() ->
err(modify, 'redirect', 302).
-spec err_redirect(binary(), binary() | undefined) -> error().
err_redirect(Text, Lang) ->
err(modify, 'redirect', 302, Text, Lang).
-spec err_registration_required() -> error().
err_registration_required() ->
err(auth, 'registration-required', 407).
-spec err_registration_required(binary(), binary() | undefined) -> error().
err_registration_required(Text, Lang) ->
err(auth, 'registration-required', 407, Text, Lang).
-spec err_remote_server_not_found() -> error().
err_remote_server_not_found() ->
err(cancel, 'remote-server-not-found', 404).
-spec err_remote_server_not_found(binary(), binary() | undefined) -> error().
err_remote_server_not_found(Text, Lang) ->
err(cancel, 'remote-server-not-found', 404, Text, Lang).
-spec err_remote_server_timeout() -> error().
err_remote_server_timeout() ->
err(wait, 'remote-server-timeout', 504).
-spec err_remote_server_timeout(binary(), binary() | undefined) -> error().
err_remote_server_timeout(Text, Lang) ->
err(wait, 'remote-server-timeout', 504, Text, Lang).
-spec err_resource_constraint() -> error().
err_resource_constraint() ->
err(wait, 'resource-constraint', 500).
-spec err_resource_constraint(binary(), binary() | undefined) -> error().
err_resource_constraint(Text, Lang) ->
err(wait, 'resource-constraint', 500, Text, Lang).
-spec err_service_unavailable() -> error().
err_service_unavailable() ->
err(cancel, 'service-unavailable', 503).
-spec err_service_unavailable(binary(), binary() | undefined) -> error().
err_service_unavailable(Text, Lang) ->
err(cancel, 'service-unavailable', 503, Text, Lang).
-spec err_subscription_required() -> error().
err_subscription_required() ->
err(auth, 'subscription-required', 407).
-spec err_subscription_required(binary(), binary() | undefined) -> error().
err_subscription_required(Text, Lang) ->
err(auth, 'subscription-required', 407, Text, Lang).
%% No error type is defined for <undefined-confition/>.
%% We choose "modify" as it's used in RFC 6120 example.
-spec err_undefined_condition() -> error().
err_undefined_condition() ->
err(modify, 'undefined-condition', 500).
-spec err_undefined_condition(binary(), binary() | undefined) -> error().
err_undefined_condition(Text, Lang) ->
err(modify, 'undefined-condition', 500, Text, Lang).
%% RFC 6120 says error type SHOULD be "wait" or "modify".
%% RFC 3920 and XEP-0082 says it SHOULD be "wait".
-spec err_unexpected_request() -> error().
err_unexpected_request() ->
err(wait, 'unexpected-request', 400).
-spec err_unexpected_request(binary(), binary() | undefined) -> error().
err_unexpected_request(Text, Lang) ->
err(wait, 'unexpected-request', 400, Text, Lang).
%%%===================================================================
%%% Functions to construct stream errors
%%%===================================================================
@ -712,3 +824,7 @@ add_ns(#xmlel{name = Name} = El) when Name == <<"message">>;
El#xmlel{attrs = Attrs};
add_ns(El) ->
El.
-spec match_tag(xmlel() | xmpp_element(), binary(), binary()) -> boolean().
match_tag(El, TagName, XMLNS) ->
get_name(El) == TagName andalso get_ns(El) == XMLNS.

File diff suppressed because it is too large Load Diff

View File

@ -10,7 +10,8 @@
%% API
-export([add_delay_info/3, add_delay_info/4, unwrap_carbon/1,
is_standalone_chat_state/1]).
is_standalone_chat_state/1, get_xdata_values/2,
has_xdata_var/2]).
-include("xmpp.hrl").
@ -76,6 +77,17 @@ is_standalone_chat_state(Stanza) ->
false
end.
-spec get_xdata_values(binary(), xdata()) -> [binary()].
get_xdata_values(Var, #xdata{fields = Fields}) ->
case lists:keyfind(Var, #xdata_field.var, Fields) of
#xdata_field{values = Vals} -> Vals;
false -> []
end.
-spec has_xdata_var(binary(), xdata()) -> boolean().
has_xdata_var(Var, #xdata{fields = Fields}) ->
lists:keymember(Var, #xdata_field.var, Fields).
%%%===================================================================
%%% Internal functions
%%%===================================================================

View File

@ -224,10 +224,12 @@
-xml(disco_items,
#elem{name = <<"query">>,
xmlns = <<"http://jabber.org/protocol/disco#items">>,
result = {disco_items, '$node', '$items'},
result = {disco_items, '$node', '$items', '$rsm'},
attrs = [#attr{name = <<"node">>}],
refs = [#ref{name = disco_item,
label = '$items'}]}).
label = '$items'},
#ref{name = rsm_set, min = 0, max = 1,
label = '$rsm'}]}).
-xml(private,
#elem{name = <<"query">>,
@ -468,6 +470,10 @@
#elem{name = <<"not-authorized">>,
xmlns = <<"urn:ietf:params:xml:ns:xmpp-stanzas">>,
result = 'not-authorized'}).
-xml(error_payment_required,
#elem{name = <<"payment-required">>,
xmlns = <<"urn:ietf:params:xml:ns:xmpp-stanzas">>,
result = 'payment-required'}).
-xml(error_policy_violation,
#elem{name = <<"policy-violation">>,
xmlns = <<"urn:ietf:params:xml:ns:xmpp-stanzas">>,
@ -561,6 +567,8 @@
min = 0, max = 1, label = '$reason'},
#ref{name = error_not_authorized,
min = 0, max = 1, label = '$reason'},
#ref{name = error_payment_required,
min = 0, max = 1, label = '$reason'},
#ref{name = error_policy_violation,
min = 0, max = 1, label = '$reason'},
#ref{name = error_recipient_unavailable,
@ -1582,7 +1590,8 @@
-xml(xdata_field_option,
#elem{name = <<"option">>,
xmlns = <<"jabber:x:data">>,
result = '$value',
result = {xdata_option, '$label', '$value'},
attrs = [#attr{name = <<"label">>}],
refs = [#ref{name = xdata_field_value,
label = '$value',
min = 1, max = 1}]}).
@ -1945,9 +1954,11 @@
dec = {dec_utc, []},
enc = {enc_utc, []}}]}).
-xml(muc_user_reason,
-xml(muc_reason,
#elem{name = <<"reason">>,
xmlns = <<"http://jabber.org/protocol/muc#user">>,
xmlns = [<<"http://jabber.org/protocol/muc#user">>,
<<"http://jabber.org/protocol/muc#admin">>,
<<"http://jabber.org/protocol/muc#owner">>],
result = '$cdata'}).
-xml(muc_user_decline,
@ -1960,31 +1971,39 @@
#attr{name = <<"from">>,
dec = {dec_jid, []},
enc = {enc_jid, []}}],
refs = [#ref{name = muc_user_reason, min = 0,
refs = [#ref{name = muc_reason, min = 0,
default = <<"">>,
max = 1, label = '$reason'}]}).
-xml(muc_user_destroy,
-xml(muc_destroy,
#elem{name = <<"destroy">>,
xmlns = <<"http://jabber.org/protocol/muc#user">>,
result = {muc_user_destroy, '$reason', '$jid'},
attrs = [#attr{name = <<"jid">>,
xmlns = [<<"http://jabber.org/protocol/muc#user">>,
<<"http://jabber.org/protocol/muc#owner">>],
result = {muc_destroy, '$xmlns', '$jid', '$reason', '$password'},
attrs = [#attr{name = <<"jid">>,
dec = {dec_jid, []},
enc = {enc_jid, []}}],
refs = [#ref{name = muc_user_reason, min = 0,
max = 1, label = '$reason'}]}).
enc = {enc_jid, []}},
#attr{name = <<"xmlns">>}],
refs = [#ref{name = muc_reason, min = 0,
default = <<"">>,
max = 1, label = '$reason'},
#ref{name = muc_password, min = 0, max = 1,
label = '$password'}]}).
-xml(muc_user_invite,
#elem{name = <<"invite">>,
xmlns = <<"http://jabber.org/protocol/muc#user">>,
result = {muc_invite, '$reason', '$from', '$to'},
result = {muc_invite, '$reason', '$from', '$to', '$continue'},
attrs = [#attr{name = <<"to">>,
dec = {dec_jid, []},
enc = {enc_jid, []}},
#attr{name = <<"from">>,
dec = {dec_jid, []},
enc = {enc_jid, []}}],
refs = [#ref{name = muc_user_reason, min = 0,
max = 1, label = '$reason'}]}).
refs = [#ref{name = muc_reason, min = 0, default = <<"">>,
max = 1, label = '$reason'},
#ref{name = muc_user_continue, min = 0, max = 1,
label = '$continue'}]}).
-xml(muc_user_actor,
#elem{name = <<"actor">>,
@ -2018,7 +2037,7 @@
min = 0, max = 1, label = '$actor'},
#ref{name = muc_user_continue,
min = 0, max = 1, label = '$continue'},
#ref{name = muc_user_reason,
#ref{name = muc_reason, default = <<"">>,
min = 0, max = 1, label = '$reason'}],
attrs = [#attr{name = <<"affiliation">>,
dec = {dec_enum, [[admin, member, none,
@ -2038,44 +2057,56 @@
xmlns = <<"http://jabber.org/protocol/muc#user">>,
result = {muc_user, '$decline', '$destroy', '$invites',
'$items', '$status_codes', '$password'},
attrs = [#attr{name = <<"password">>}],
refs = [#ref{name = muc_user_decline, min = 0,
max = 1, label = '$decline'},
#ref{name = muc_user_destroy, min = 0, max = 1,
#ref{name = muc_destroy, min = 0, max = 1,
label = '$destroy'},
#ref{name = muc_password, min = 0, max = 1,
label = '$password'},
#ref{name = muc_user_invite, label = '$invites'},
#ref{name = muc_user_item, label = '$items'},
#ref{name = muc_user_status, label = '$status_codes'}]}).
-xml(muc_owner_password,
-xml(muc_password,
#elem{name = <<"password">>,
xmlns = <<"http://jabber.org/protocol/muc#owner">>,
xmlns = [<<"http://jabber.org/protocol/muc#owner">>,
<<"http://jabber.org/protocol/muc#user">>,
<<"http://jabber.org/protocol/muc">>],
result = '$cdata'}).
-xml(muc_owner_reason,
#elem{name = <<"reason">>,
xmlns = <<"http://jabber.org/protocol/muc#owner">>,
result = '$cdata'}).
-xml(muc_owner_destroy,
#elem{name = <<"destroy">>,
xmlns = <<"http://jabber.org/protocol/muc#owner">>,
result = {muc_owner_destroy, '$jid', '$reason', '$password'},
attrs = [#attr{name = <<"jid">>,
dec = {dec_jid, []},
enc = {enc_jid, []}}],
refs = [#ref{name = muc_owner_password, min = 0, max = 1,
label = '$password'},
#ref{name = muc_owner_reason, min = 0, max = 1,
label = '$reason'}]}).
-xml(muc_owner,
#elem{name = <<"query">>,
xmlns = <<"http://jabber.org/protocol/muc#owner">>,
result = {muc_owner, '$destroy', '$config'},
refs = [#ref{name = muc_owner_destroy, min = 0, max = 1,
label = '$destroy'},
#ref{name = xdata, min = 0, max = 1, label = '$config'}]}).
result = {muc_owner, '$destroy', '$config', '$items'},
refs = [#ref{name = muc_destroy, min = 0, max = 1,
label = '$destroy'},
#ref{name = xdata, min = 0, max = 1,
label = '$config'},
#ref{name = muc_owner_item, label = '$items'}]}).
-xml(muc_owner_item,
#elem{name = <<"item">>,
xmlns = <<"http://jabber.org/protocol/muc#owner">>,
result = {muc_item, '$actor', '$continue', '$reason',
'$affiliation', '$role', '$jid', '$nick'},
refs = [#ref{name = muc_admin_actor,
min = 0, max = 1, label = '$actor'},
#ref{name = muc_admin_continue,
min = 0, max = 1, label = '$continue'},
#ref{name = muc_reason, default = <<"">>,
min = 0, max = 1, label = '$reason'}],
attrs = [#attr{name = <<"affiliation">>,
dec = {dec_enum, [[admin, member, none,
outcast, owner]]},
enc = {enc_enum, []}},
#attr{name = <<"role">>,
dec = {dec_enum, [[moderator, none,
participant, visitor]]},
enc = {enc_enum, []}},
#attr{name = <<"jid">>,
dec = {dec_jid, []},
enc = {enc_jid, []}},
#attr{name = <<"nick">>}]}).
-xml(muc_admin_item,
#elem{name = <<"item">>,
@ -2086,7 +2117,7 @@
min = 0, max = 1, label = '$actor'},
#ref{name = muc_admin_continue,
min = 0, max = 1, label = '$continue'},
#ref{name = muc_admin_reason,
#ref{name = muc_reason, default = <<"">>,
min = 0, max = 1, label = '$reason'}],
attrs = [#attr{name = <<"affiliation">>,
dec = {dec_enum, [[admin, member, none,
@ -2116,11 +2147,6 @@
result = '$thread',
attrs = [#attr{name = <<"thread">>}]}).
-xml(muc_admin_reason,
#elem{name = <<"reason">>,
xmlns = <<"http://jabber.org/protocol/muc#admin">>,
result = '$cdata'}).
-xml(muc_admin,
#elem{name = <<"query">>,
xmlns = <<"http://jabber.org/protocol/muc#admin">>,
@ -2131,9 +2157,66 @@
#elem{name = <<"x">>,
xmlns = <<"http://jabber.org/protocol/muc">>,
result = {muc, '$history', '$password'},
attrs = [#attr{name = <<"password">>}],
refs = [#ref{name = muc_history, min = 0, max = 1,
label = '$history'}]}).
label = '$history'},
#ref{name = muc_password, min = 0, max = 1,
label = '$password'}]}).
-xml(muc_unique,
#elem{name = <<"unique">>,
xmlns = <<"http://jabber.org/protocol/muc#unique">>,
result = {muc_unique, '$name'},
cdata = #cdata{default = <<"">>,
label = '$name'}}).
-xml(x_conference,
#elem{name = <<"x">>,
xmlns = <<"jabber:x:conference">>,
result = {x_conference, '$jid', '$password', '$reason',
'$continue', '$thread'},
attrs = [#attr{name = <<"jid">>,
required = true,
dec = {dec_jid, []},
enc = {enc_jid, []}},
#attr{name = <<"password">>, default = <<"">>},
#attr{name = <<"reason">>, default = <<"">>},
#attr{name = <<"thread">>, default = <<"">>},
#attr{name = <<"continue">>,
dec = {dec_bool, []},
enc = {enc_bool, []}}]}).
-xml(muc_subscription,
#elem{name = <<"subscription">>,
xmlns = <<"urn:xmpp:mucsub:0">>,
result = '$jid',
attrs = [#attr{name = <<"jid">>,
required = true,
dec = {dec_jid, []},
enc = {enc_jid, []}}]}).
-xml(muc_subscriptions,
#elem{name = <<"subscriptions">>,
xmlns = <<"urn:xmpp:mucsub:0">>,
result = {muc_subscriptions, '$list'},
refs = [#ref{name = muc_subscription, label = '$list'}]}).
-xml(muc_subscribe_event,
#elem{name = <<"event">>,
xmlns = <<"urn:xmpp:mucsub:0">>,
result = '$node',
attrs = [#attr{name = <<"node">>, required = true}]}).
-xml(muc_subscribe,
#elem{name = <<"subscribe">>,
xmlns = <<"urn:xmpp:mucsub:0">>,
result = {muc_subscribe, '$nick', '$events'},
attrs = [#attr{name = <<"nick">>, required = true}],
refs = [#ref{name = muc_subscribe_event, label = '$events'}]}).
-xml(muc_unsubscribe,
#elem{name = <<"unsubscribe">>,
xmlns = <<"urn:xmpp:mucsub:0">>,
result = {muc_unsubscribe}}).
-xml(rsm_after,
#elem{name = <<"after">>,
@ -2143,7 +2226,7 @@
-xml(rsm_before,
#elem{name = <<"before">>,
xmlns = <<"http://jabber.org/protocol/rsm">>,
cdata = #cdata{default = none},
cdata = #cdata{default = <<"">>},
result = '$cdata'}).
-xml(rsm_last,
@ -2215,16 +2298,23 @@
dec = {dec_jid, []},
enc = {enc_jid, []}}}).
-xml(mam_withtext,
#elem{name = <<"withtext">>,
xmlns = <<"urn:xmpp:mam:tmp">>,
result = '$cdata',
cdata = #cdata{required = true}}).
-xml(mam_query,
#elem{name = <<"query">>,
xmlns = [<<"urn:xmpp:mam:0">>, <<"urn:xmpp:mam:1">>, <<"urn:xmpp:mam:tmp">>],
result = {mam_query, '$xmlns', '$id', '$start', '$end', '$with',
'$rsm', '$xdata'},
'$withtext', '$rsm', '$xdata'},
attrs = [#attr{name = <<"queryid">>, label = '$id'},
#attr{name = <<"xmlns">>}],
refs = [#ref{name = mam_start, min = 0, max = 1, label = '$start'},
#ref{name = mam_end, min = 0, max = 1, label = '$end'},
#ref{name = mam_with, min = 0, max = 1, label = '$with'},
#ref{name = mam_withtext, min = 0, max = 1, label = '$withtext'},
#ref{name = rsm_set, min = 0, max = 1, label = '$rsm'},
#ref{name = xdata, min = 0, max = 1, label = '$xdata'}]}).
@ -2248,7 +2338,7 @@
-xml(mam_jid,
#elem{name = <<"jid">>,
xmlns = <<"urn:xmpp:mam:tmp">>,
xmlns = [<<"urn:xmpp:mam:0">>, <<"urn:xmpp:mam:1">>, <<"urn:xmpp:mam:tmp">>],
result = '$cdata',
cdata = #cdata{required = true,
dec = {dec_jid, []},
@ -2256,15 +2346,15 @@
-xml(mam_never,
#elem{name = <<"never">>,
xmlns = <<"urn:xmpp:mam:tmp">>,
xmlns = [<<"urn:xmpp:mam:0">>, <<"urn:xmpp:mam:1">>, <<"urn:xmpp:mam:tmp">>],
result = '$jids',
refs = [#ref{name = mam_jid, label = '$jids', default = []}]}).
refs = [#ref{name = mam_jid, label = '$jids'}]}).
-xml(mam_always,
#elem{name = <<"always">>,
xmlns = <<"urn:xmpp:mam:tmp">>,
xmlns = [<<"urn:xmpp:mam:0">>, <<"urn:xmpp:mam:1">>, <<"urn:xmpp:mam:tmp">>],
result = '$jids',
refs = [#ref{name = mam_jid, label = '$jids', default = []}]}).
refs = [#ref{name = mam_jid, label = '$jids'}]}).
-xml(mam_prefs,
#elem{name = <<"prefs">>,
@ -2275,9 +2365,9 @@
enc = {enc_enum, []}},
#attr{name = <<"xmlns">>}],
refs = [#ref{name = mam_always, label = '$always',
min = 0, max = 1, default = []},
min = 0, max = 1},
#ref{name = mam_never, label = '$never',
min = 0, max = 1, default = []}]}).
min = 0, max = 1}]}).
-xml(mam_fin,
#elem{name = <<"fin">>,
@ -2538,8 +2628,8 @@
#attr{name = <<"nick">>,
label = '$nick'}]}).
-record(hint, {type :: 'no-copy' | 'no-store' | 'no-storage' |
'store' | 'no-permanent-store'}).
-record(hint, {type :: 'no-copy' | 'no-store' | 'no-storage' | 'store' |
'no-permanent-store' | 'no-permanent-storage'}).
-type hint() :: #hint{}.
-xml(hint_no_copy,
@ -2567,6 +2657,11 @@
xmlns = <<"urn:xmpp:hints">>,
result = {hint, 'no-permanent-store'}}).
-xml(hint_no_permanent_storage,
#elem{name = <<"no-permanent-storage">>,
xmlns = <<"urn:xmpp:hints">>,
result = {hint, 'no-permanent-storage'}}).
-xml(search_instructions,
#elem{name = <<"instructions">>,
xmlns = <<"jabber:iq:search">>,
@ -2679,6 +2774,53 @@
dec = {dec_int, [0, infinity]},
enc = {enc_int, []}}]}).
-xml(nick,
#elem{name = <<"nick">>,
xmlns = <<"http://jabber.org/protocol/nick">>,
result = {nick, '$name'},
cdata = #cdata{label = '$name',
required = true}}).
-xml(address,
#elem{name = <<"address">>,
xmlns = <<"http://jabber.org/protocol/address">>,
result = {address, '$type', '$jid', '$desc', '$node', '$delivered'},
attrs = [#attr{name = <<"type">>,
required = true,
dec = {dec_enum, [[bcc, cc, noreply, ofrom,
replyroom, replyto, to]]},
enc = {enc_enum, []}},
#attr{name = <<"jid">>,
enc = {enc_jid, []},
dec = {dec_jid, []}},
#attr{name = <<"desc">>},
#attr{name = <<"node">>},
#attr{name = <<"delivered">>,
enc = {enc_bool, []},
dec = {dec_bool, []}}]}).
-xml(addresses,
#elem{name = <<"addresses">>,
xmlns = <<"http://jabber.org/protocol/address">>,
result = {addresses, '$list'},
%% TODO: 'min' should be '1', but this is not implemented
refs = [#ref{name = address, label = '$list'}]}).
-xml(stanza_id,
#elem{name = <<"stanza-id">>,
xmlns = <<"urn:xmpp:sid:0">>,
result = {stanza_id, '$by', '$id'},
attrs = [#attr{name = <<"id">>, required = true},
#attr{name = <<"by">>, required = true,
enc = {enc_jid, []},
dec = {dec_jid, []}}]}).
-xml(client_id,
#elem{name = <<"client-id">>,
xmlns = <<"urn:xmpp:sid:0">>,
result = {client_id, '$id'},
attrs = [#attr{name = <<"id">>, required = true}]}).
dec_tzo(Val) ->
[H1, M1] = str:tokens(Val, <<":">>),
H = jlib:binary_to_integer(H1),