25
1
mirror of https://github.com/processone/ejabberd.git synced 2024-11-22 16:20:52 +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 config() :: #config{}.
-type role() :: moderator | participant | visitor | none. -type role() :: moderator | participant | visitor | none.
-type affiliation() :: admin | member | outcast | owner | none.
-record(user, -record(user,
{ {
@ -120,5 +121,3 @@
host = <<>> :: binary() | '_' | '$2'}). host = <<>> :: binary() | '_' | '$2'}).
-type muc_online_users() :: #muc_online_users{}. -type muc_online_users() :: #muc_online_users{}.
-type muc_room_state() :: #state{}.

View File

@ -11,13 +11,20 @@
-record(csi, {type :: active | inactive}). -record(csi, {type :: active | inactive}).
-type csi() :: #csi{}. -type csi() :: #csi{}.
-record(hint, {type :: 'no-copy' | 'no-store' | 'no-storage' | -record(hint, {type :: 'no-copy' | 'no-store' | 'no-storage' | 'store' |
'store' | 'no-permanent-store'}). 'no-permanent-store' | 'no-permanent-storage'}).
-type hint() :: #hint{}. -type hint() :: #hint{}.
-record(feature_register, {}). -record(feature_register, {}).
-type feature_register() :: #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()}). -record(sasl_success, {text :: any()}).
-type sasl_success() :: #sasl_success{}. -type sasl_success() :: #sasl_success{}.
@ -55,6 +62,9 @@
stored :: non_neg_integer()}). stored :: non_neg_integer()}).
-type expire() :: #expire{}. -type expire() :: #expire{}.
-record(muc_unsubscribe, {}).
-type muc_unsubscribe() :: #muc_unsubscribe{}.
-record(pubsub_unsubscribe, {node :: binary(), -record(pubsub_unsubscribe, {node :: binary(),
jid :: any(), jid :: any(),
subid :: binary()}). subid :: binary()}).
@ -81,7 +91,7 @@
type :: 'member' | 'none' | 'outcast' | 'owner' | 'publish-only' | 'publisher'}). type :: 'member' | 'none' | 'outcast' | 'owner' | 'publish-only' | 'publisher'}).
-type pubsub_affiliation() :: #pubsub_affiliation{}. -type pubsub_affiliation() :: #pubsub_affiliation{}.
-record(muc_decline, {reason :: binary(), -record(muc_decline, {reason = <<>> :: 'undefined' | binary(),
from :: any(), from :: any(),
to :: any()}). to :: any()}).
-type muc_decline() :: #muc_decline{}. -type muc_decline() :: #muc_decline{}.
@ -90,9 +100,20 @@
xmlns :: binary()}). xmlns :: binary()}).
-type sm_a() :: #sm_a{}. -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, {}). -record(starttls_proceed, {}).
-type starttls_proceed() :: #starttls_proceed{}. -type starttls_proceed() :: #starttls_proceed{}.
-record(client_id, {id :: binary()}).
-type client_id() :: #client_id{}.
-record(sm_resumed, {h :: non_neg_integer(), -record(sm_resumed, {h :: non_neg_integer(),
previd :: binary(), previd :: binary(),
xmlns :: binary()}). xmlns :: binary()}).
@ -116,9 +137,19 @@
-record(gone, {uri :: binary()}). -record(gone, {uri :: binary()}).
-type gone() :: #gone{}. -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()]}). -record(private, {xml_els = [] :: [any()]}).
-type private() :: #private{}. -type private() :: #private{}.
-record(nick, {name :: binary()}).
-type nick() :: #nick{}.
-record(p1_ack, {}). -record(p1_ack, {}).
-type p1_ack() :: #p1_ack{}. -type p1_ack() :: #p1_ack{}.
@ -163,6 +194,9 @@
error = [] :: [{integer(),'undefined' | binary()}]}). error = [] :: [{integer(),'undefined' | binary()}]}).
-type stat() :: #stat{}. -type stat() :: #stat{}.
-record(addresses, {list = [] :: [#address{}]}).
-type addresses() :: #addresses{}.
-record('see-other-host', {host :: binary()}). -record('see-other-host', {host :: binary()}).
-type 'see-other-host'() :: #'see-other-host'{}. -type 'see-other-host'() :: #'see-other-host'{}.
@ -194,6 +228,9 @@
-record(pubsub_event, {items = [] :: [#pubsub_event_items{}]}). -record(pubsub_event, {items = [] :: [#pubsub_event_items{}]}).
-type pubsub_event() :: #pubsub_event{}. -type pubsub_event() :: #pubsub_event{}.
-record(muc_unique, {name = <<>> :: binary()}).
-type muc_unique() :: #muc_unique{}.
-record(sasl_response, {text :: any()}). -record(sasl_response, {text :: any()}).
-type sasl_response() :: #sasl_response{}. -type sasl_response() :: #sasl_response{}.
@ -217,19 +254,11 @@
-record(feature_csi, {xmlns :: binary()}). -record(feature_csi, {xmlns :: binary()}).
-type feature_csi() :: #feature_csi{}. -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(), -record(disco_item, {jid :: any(),
name :: binary(), name :: binary(),
node :: binary()}). node :: binary()}).
-type disco_item() :: #disco_item{}. -type disco_item() :: #disco_item{}.
-record(disco_items, {node :: binary(),
items = [] :: [#disco_item{}]}).
-type disco_items() :: #disco_items{}.
-record(unblock, {items = [] :: [any()]}). -record(unblock, {items = [] :: [any()]}).
-type unblock() :: #unblock{}. -type unblock() :: #unblock{}.
@ -239,10 +268,8 @@
-record(compression, {methods = [] :: [binary()]}). -record(compression, {methods = [] :: [binary()]}).
-type compression() :: #compression{}. -type compression() :: #compression{}.
-record(muc_owner_destroy, {jid :: any(), -record(muc_subscriptions, {list = [] :: [any()]}).
reason :: binary(), -type muc_subscriptions() :: #muc_subscriptions{}.
password :: binary()}).
-type muc_owner_destroy() :: #muc_owner_destroy{}.
-record(pubsub_subscription, {jid :: any(), -record(pubsub_subscription, {jid :: any(),
node :: binary(), node :: binary(),
@ -252,7 +279,7 @@
-record(muc_item, {actor :: #muc_actor{}, -record(muc_item, {actor :: #muc_actor{},
continue :: binary(), continue :: binary(),
reason :: binary(), reason = <<>> :: 'undefined' | binary(),
affiliation :: 'admin' | 'member' | 'none' | 'outcast' | 'owner', affiliation :: 'admin' | 'member' | 'none' | 'outcast' | 'owner',
role :: 'moderator' | 'none' | 'participant' | 'visitor', role :: 'moderator' | 'none' | 'participant' | 'visitor',
jid :: any(), jid :: any(),
@ -267,8 +294,8 @@
-record(mam_prefs, {xmlns :: binary(), -record(mam_prefs, {xmlns :: binary(),
default :: 'always' | 'never' | 'roster', default :: 'always' | 'never' | 'roster',
always = [] :: [any()], always :: [any()],
never = [] :: [any()]}). never :: [any()]}).
-type mam_prefs() :: #mam_prefs{}. -type mam_prefs() :: #mam_prefs{}.
-record(caps, {node :: binary(), -record(caps, {node :: binary(),
@ -350,13 +377,17 @@
-record(block_list, {items = [] :: [any()]}). -record(block_list, {items = [] :: [any()]}).
-type block_list() :: #block_list{}. -type block_list() :: #block_list{}.
-record(xdata_option, {label :: binary(),
value :: binary()}).
-type xdata_option() :: #xdata_option{}.
-record(xdata_field, {label :: binary(), -record(xdata_field, {label :: binary(),
type :: 'boolean' | 'fixed' | 'hidden' | 'jid-multi' | 'jid-single' | 'list-multi' | 'list-single' | 'text-multi' | 'text-private' | 'text-single', type :: 'boolean' | 'fixed' | 'hidden' | 'jid-multi' | 'jid-single' | 'list-multi' | 'list-single' | 'text-multi' | 'text-private' | 'text-single',
var :: binary(), var :: binary(),
required = false :: boolean(), required = false :: boolean(),
desc :: binary(), desc :: binary(),
values = [] :: [binary()], values = [] :: [binary()],
options = [] :: [binary()]}). options = [] :: [#xdata_option{}]}).
-type xdata_field() :: #xdata_field{}. -type xdata_field() :: #xdata_field{}.
-record(version, {name :: binary(), -record(version, {name :: binary(),
@ -364,11 +395,6 @@
os :: binary()}). os :: binary()}).
-type version() :: #version{}. -type version() :: #version{}.
-record(muc_invite, {reason :: binary(),
from :: any(),
to :: any()}).
-type muc_invite() :: #muc_invite{}.
-record(bind, {jid :: any(), -record(bind, {jid :: any(),
resource :: any()}). resource :: any()}).
-type bind() :: #bind{}. -type bind() :: #bind{}.
@ -376,13 +402,11 @@
-record(rosterver_feature, {}). -record(rosterver_feature, {}).
-type rosterver_feature() :: #rosterver_feature{}. -type rosterver_feature() :: #rosterver_feature{}.
-record(muc_user, {decline :: #muc_decline{}, -record(muc_invite, {reason = <<>> :: 'undefined' | binary(),
destroy :: #muc_user_destroy{}, from :: any(),
invites = [] :: [#muc_invite{}], to :: any(),
items = [] :: [#muc_item{}], continue :: binary()}).
status_codes = [] :: [pos_integer()], -type muc_invite() :: #muc_invite{}.
password :: binary()}).
-type muc_user() :: #muc_user{}.
-record(carbons_disable, {}). -record(carbons_disable, {}).
-type carbons_disable() :: #carbons_disable{}. -type carbons_disable() :: #carbons_disable{}.
@ -400,7 +424,7 @@
-type vcard_org() :: #vcard_org{}. -type vcard_org() :: #vcard_org{}.
-record(rsm_set, {'after' :: binary(), -record(rsm_set, {'after' :: binary(),
before :: 'none' | binary(), before :: binary(),
count :: non_neg_integer(), count :: non_neg_integer(),
first :: #rsm_first{}, first :: #rsm_first{},
index :: non_neg_integer(), index :: non_neg_integer(),
@ -414,6 +438,11 @@
complete :: any()}). complete :: any()}).
-type mam_fin() :: #mam_fin{}. -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(), -record(vcard_tel, {home = false :: boolean(),
work = false :: boolean(), work = false :: boolean(),
voice = false :: boolean(), voice = false :: boolean(),
@ -430,6 +459,20 @@
number :: binary()}). number :: binary()}).
-type vcard_tel() :: #vcard_tel{}. -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(), -record(vcard_key, {type :: binary(),
cred :: binary()}). cred :: binary()}).
-type vcard_key() :: #vcard_key{}. -type vcard_key() :: #vcard_key{}.
@ -530,14 +573,11 @@
start :: any(), start :: any(),
'end' :: any(), 'end' :: any(),
with :: any(), with :: any(),
withtext :: binary(),
rsm :: #rsm_set{}, rsm :: #rsm_set{},
xdata :: #xdata{}}). xdata :: #xdata{}}).
-type mam_query() :: #mam_query{}. -type mam_query() :: #mam_query{}.
-record(muc_owner, {destroy :: #muc_owner_destroy{},
config :: #xdata{}}).
-type muc_owner() :: #muc_owner{}.
-record(pubsub_options, {node :: binary(), -record(pubsub_options, {node :: binary(),
jid :: any(), jid :: any(),
subid :: binary(), subid :: binary(),
@ -592,6 +632,11 @@
fetch = false :: boolean()}). fetch = false :: boolean()}).
-type offline() :: #offline{}. -type offline() :: #offline{}.
-record(muc_owner, {destroy :: #muc_destroy{},
config :: #xdata{},
items = [] :: [#muc_item{}]}).
-type muc_owner() :: #muc_owner{}.
-record(sasl_mechanisms, {list = [] :: [binary()]}). -record(sasl_mechanisms, {list = [] :: [binary()]}).
-type sasl_mechanisms() :: #sasl_mechanisms{}. -type sasl_mechanisms() :: #sasl_mechanisms{}.
@ -709,6 +754,7 @@
-type xmpp_element() :: compression() | -type xmpp_element() :: compression() |
pubsub_subscription() | pubsub_subscription() |
xdata_option() |
version() | version() |
pubsub_affiliation() | pubsub_affiliation() |
muc_admin() | muc_admin() |
@ -726,7 +772,9 @@
rsm_set() | rsm_set() |
'see-other-host'() | 'see-other-host'() |
hint() | hint() |
stanza_id() |
starttls_proceed() | starttls_proceed() |
client_id() |
sm_resumed() | sm_resumed() |
forwarded() | forwarded() |
xevent() | xevent() |
@ -742,8 +790,11 @@
pubsub_event_item() | pubsub_event_item() |
muc_item() | muc_item() |
vcard_temp() | vcard_temp() |
address() |
sasl_success() | sasl_success() |
addresses() |
pubsub_event_items() | pubsub_event_items() |
muc_subscriptions() |
disco_items() | disco_items() |
pubsub_options() | pubsub_options() |
compress() | compress() |
@ -751,33 +802,25 @@
muc_history() | muc_history() |
identity() | identity() |
feature_csi() | feature_csi() |
muc_user_destroy() |
privacy_query() | privacy_query() |
delay() | delay() |
vcard_tel() | vcard_tel() |
vcard_logo() |
disco_info() |
vcard_geo() | vcard_geo() |
vcard_photo() | vcard_photo() |
feature_register() |
register() |
muc_owner() |
pubsub() | pubsub() |
sm_r() | muc_owner() |
muc_actor() | muc_actor() |
error() |
stream_error() |
muc_user() |
vcard_adr() |
carbons_private() | carbons_private() |
mix_leave() | mix_leave() |
muc_invite() | muc_subscribe() |
rosterver_feature() | rosterver_feature() |
muc_invite() |
vcard_xupdate() | vcard_xupdate() |
carbons_disable() | carbons_disable() |
bookmark_conference() | bookmark_conference() |
offline() | offline() |
time() | time() |
muc_unique() |
sasl_response() | sasl_response() |
pubsub_subscribe() | pubsub_subscribe() |
presence() | presence() |
@ -786,6 +829,7 @@
starttls_failure() | starttls_failure() |
sasl_challenge() | sasl_challenge() |
gone() | gone() |
x_conference() |
private() | private() |
compress_failure() | compress_failure() |
sasl_failure() | sasl_failure() |
@ -794,6 +838,7 @@
sm_resume() | sm_resume() |
carbons_enable() | carbons_enable() |
expire() | expire() |
muc_unsubscribe() |
pubsub_unsubscribe() | pubsub_unsubscribe() |
muc_decline() | muc_decline() |
chatstate() | chatstate() |
@ -803,6 +848,7 @@
search() | search() |
pubsub_publish() | pubsub_publish() |
unblock() | unblock() |
nick() |
p1_ack() | p1_ack() |
block() | block() |
mix_join() | mix_join() |
@ -832,11 +878,20 @@
starttls() | starttls() |
mam_prefs() | mam_prefs() |
sasl_mechanisms() | sasl_mechanisms() |
muc_destroy() |
vcard_key() | vcard_key() |
csi() | csi() |
roster_query() | roster_query() |
muc_owner_destroy() |
mam_query() | mam_query() |
bookmark_url() | bookmark_url() |
vcard_email() | 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() -> start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). 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) -> route(From, To, Packet) ->
case catch do_route(From, To, Packet) of 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. %% Route the error packet only if the originating packet is not an error itself.
%% RFC3920 9.3.1 %% 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, #xmlel{attrs = Attrs} = OrigPacket,
case <<"error">> == fxml:get_attr_s(<<"type">>, Attrs) of case <<"error">> == fxml:get_attr_s(<<"type">>, Attrs) of
false -> route(From, To, ErrPacket); false -> route(From, To, ErrPacket);
true -> ok 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. end.
-spec register_route(binary()) -> term(). -spec register_route(binary()) -> term().
@ -406,11 +414,16 @@ do_route(OrigFrom, OrigTo, OrigPacket) ->
end. end.
-spec do_route(jid(), jid(), xmlel() | xmpp_element(), #route{}) -> any(). -spec do_route(jid(), jid(), xmlel() | xmpp_element(), #route{}) -> any().
do_route(From, To, Packet, do_route(From, To, Packet, #route{local_hint = LocalHint,
#route{local_hint = {apply, Module, Function}, pid = Pid}) pid = Pid}) when is_pid(Pid) ->
when is_pid(Pid) andalso node(Pid) == node() -> try xmpp:decode(Packet, [ignore_els]) of
try Pkt ->
Module:Function(From, To, xmpp:decode(Packet, [ignore_els])) 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} -> catch error:{xmpp_codec, Why} ->
?ERROR_MSG("failed to decode xml element ~p when " ?ERROR_MSG("failed to decode xml element ~p when "
"routing from ~s to ~s: ~s", "routing from ~s to ~s: ~s",
@ -418,8 +431,6 @@ do_route(From, To, Packet,
xmpp:format_error(Why)]), xmpp:format_error(Why)]),
drop drop
end; 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) -> do_route(_From, _To, _Packet, _Route) ->
drop. drop.

View File

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

View File

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

View File

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

View File

@ -14,10 +14,10 @@
%% API %% API
-export([init/2, remove_user/2, remove_room/3, delete_old_messages/3, -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_lib("stdlib/include/ms_transform.hrl").
-include("jlib.hrl"). -include("xmpp.hrl").
-include("mod_mam.hrl"). -include("mod_mam.hrl").
-include("logger.hrl"). -include("logger.hrl").
-include("ejabberd_sql_pt.hrl"). -include("ejabberd_sql_pt.hrl").
@ -51,9 +51,7 @@ delete_old_messages(ServerHost, TimeStamp, Type) ->
ok. ok.
extended_fields() -> extended_fields() ->
[#xmlel{name = <<"field">>, [#xdata_field{type = 'text-single', var = <<"withtext">>}].
attrs = [{<<"type">>, <<"text-single">>},
{<<"var">>, <<"withtext">>}]}].
store(Pkt, LServer, {LUser, LHost}, Type, Peer, Nick, _Dir) -> store(Pkt, LServer, {LUser, LHost}, Type, Peer, Nick, _Dir) ->
TSinteger = p1_time_compat:system_time(micro_seconds), TSinteger = p1_time_compat:system_time(micro_seconds),
@ -126,13 +124,12 @@ get_prefs(LUser, LServer) ->
end. end.
select(LServer, JidRequestor, #jid{luser = LUser} = JidArchive, select(LServer, JidRequestor, #jid{luser = LUser} = JidArchive,
Start, End, With, RSM, MsgType) -> MAMQuery, MsgType) ->
User = case MsgType of User = case MsgType of
chat -> LUser; chat -> LUser;
{groupchat, _Role, _MUCState} -> jid:to_string(JidArchive) {groupchat, _Role, _MUCState} -> jid:to_string(JidArchive)
end, end,
{Query, CountQuery} = make_sql_query(User, LServer, {Query, CountQuery} = make_sql_query(User, LServer, MAMQuery),
Start, End, With, RSM),
% TODO from XEP-0313 v0.2: "To conserve resources, a server MAY place a % 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 % 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 % 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), case {ejabberd_sql:sql_query(LServer, Query),
ejabberd_sql:sql_query(LServer, CountQuery)} of ejabberd_sql:sql_query(LServer, CountQuery)} of
{{selected, _, Res}, {selected, _, [[Count]]}} -> {{selected, _, Res}, {selected, _, [[Count]]}} ->
{Max, Direction} = case RSM of {Max, Direction, _} = get_max_direction_id(MAMQuery#mam_query.rsm),
#rsm_in{max = M, direction = D} -> {M, D};
_ -> {undefined, undefined}
end,
{Res1, IsComplete} = {Res1, IsComplete} =
if Max >= 0 andalso Max /= undefined andalso length(Res) > Max -> if Max >= 0 andalso Max /= undefined andalso length(Res) > Max ->
if Direction == before -> if Direction == before ->
@ -200,15 +194,10 @@ usec_to_now(Int) ->
Sec = Secs rem 1000000, Sec = Secs rem 1000000,
{MSec, Sec, USec}. {MSec, Sec, USec}.
make_sql_query(User, LServer, Start, End, With, RSM) -> make_sql_query(User, LServer,
{Max, Direction, ID} = case RSM of #mam_query{start = Start, 'end' = End, with = With,
#rsm_in{} -> withtext = WithText, rsm = RSM}) ->
{RSM#rsm_in.max, {Max, Direction, ID} = get_max_direction_id(RSM),
RSM#rsm_in.direction,
RSM#rsm_in.id};
none ->
{none, none, <<>>}
end,
ODBCType = ejabberd_config:get_option( ODBCType = ejabberd_config:get_option(
{sql_type, LServer}, {sql_type, LServer},
ejabberd_sql:opt_type(sql_type)), ejabberd_sql:opt_type(sql_type)),
@ -228,12 +217,16 @@ make_sql_query(User, LServer, Start, End, With, RSM) ->
true -> true ->
[] []
end, end,
WithClause = case With of WithTextClause = case WithText of
{text, <<>>} -> {text, <<>>} ->
[]; [];
{text, Txt} -> {text, Txt} ->
[<<" and match (txt) against ('">>, [<<" and match (txt) against ('">>,
Escape(Txt), <<"')">>]; Escape(Txt), <<"')">>];
undefined ->
[]
end,
WithClause = case catch jid:tolower(With) of
{_, _, <<>>} -> {_, _, <<>>} ->
[<<" and bare_peer='">>, [<<" and bare_peer='">>,
Escape(jid:to_string(With)), Escape(jid:to_string(With)),
@ -242,7 +235,7 @@ make_sql_query(User, LServer, Start, End, With, RSM) ->
[<<" and peer='">>, [<<" and peer='">>,
Escape(jid:to_string(With)), Escape(jid:to_string(With)),
<<"'">>]; <<"'">>];
none -> _ ->
[] []
end, end,
PageClause = case catch jlib:binary_to_integer(ID) of 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 case Direction of
before -> before ->
[<<" AND timestamp < ">>, ID]; [<<" AND timestamp < ">>, ID];
aft -> 'after' ->
[<<" AND timestamp > ">>, ID]; [<<" AND timestamp > ">>, ID];
_ -> _ ->
[] []
@ -276,7 +269,7 @@ make_sql_query(User, LServer, Start, End, With, RSM) ->
Query = [<<"SELECT ">>, TopClause, <<" timestamp, xml, peer, kind, nick" Query = [<<"SELECT ">>, TopClause, <<" timestamp, xml, peer, kind, nick"
" FROM archive WHERE username='">>, " FROM archive WHERE username='">>,
SUser, <<"'">>, WithClause, StartClause, EndClause, SUser, <<"'">>, WithClause, WithTextClause, StartClause, EndClause,
PageClause], PageClause],
QueryPage = QueryPage =
@ -294,4 +287,20 @@ make_sql_query(User, LServer, Start, End, With, RSM) ->
end, end,
{QueryPage, {QueryPage,
[<<"SELECT COUNT(*) FROM archive WHERE username='">>, [<<"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, forget_room/3,
create_room/5, create_room/5,
shutdown_rooms/1, 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, broadcast_service_message/2,
export/1, export/1,
import/1, import/1,
@ -58,7 +63,7 @@
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-include("logger.hrl"). -include("logger.hrl").
-include("jlib.hrl"). -include("xmpp.hrl").
-include("mod_muc.hrl"). -include("mod_muc.hrl").
-record(state, -record(state,
@ -154,17 +159,6 @@ forget_room(ServerHost, Host, Name) ->
Mod = gen_mod:db_mod(LServer, ?MODULE), Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:forget_room(LServer, Host, Name). 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, <<"">>) -> false;
can_use_nick(ServerHost, Host, JID, Nick) -> can_use_nick(ServerHost, Host, JID, Nick) ->
LServer = jid:nameprep(ServerHost), LServer = jid:nameprep(ServerHost),
@ -176,6 +170,8 @@ can_use_nick(ServerHost, Host, JID, Nick) ->
%%==================================================================== %%====================================================================
init([Host, Opts]) -> 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, MyHost = gen_mod:get_opt_host(Host, Opts,
<<"conference.@HOST@">>), <<"conference.@HOST@">>),
Mod = gen_mod:db_mod(Host, Opts, ?MODULE), Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
@ -255,6 +251,18 @@ init([Host, Opts]) ->
RoomShaper = gen_mod:get_opt(room_shaper, Opts, RoomShaper = gen_mod:get_opt(room_shaper, Opts,
fun(A) when is_atom(A) -> A end, fun(A) when is_atom(A) -> A end,
none), 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), ejabberd_router:register_route(MyHost, Host),
load_permanent_rooms(MyHost, Host, load_permanent_rooms(MyHost, Host,
{Access, AccessCreate, AccessAdmin, AccessPersistent}, {Access, AccessCreate, AccessAdmin, AccessPersistent},
@ -314,8 +322,14 @@ handle_info({mnesia_system_event, {mnesia_down, Node}}, State) ->
{noreply, State}; {noreply, State};
handle_info(_Info, State) -> {noreply, State}. handle_info(_Info, State) -> {noreply, State}.
terminate(_Reason, State) -> terminate(_Reason, #state{host = MyHost}) ->
ejabberd_router:unregister_route(State#state.host), 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. ok.
code_change(_OldVsn, State, _Extra) -> {ok, State}. code_change(_OldVsn, State, _Extra) -> {ok, State}.
@ -331,197 +345,162 @@ do_route(Host, ServerHost, Access, HistorySize, RoomShaper,
allow -> allow ->
do_route1(Host, ServerHost, Access, HistorySize, RoomShaper, do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
From, To, Packet, DefRoomOpts); From, To, Packet, DefRoomOpts);
_ -> deny ->
#xmlel{attrs = Attrs} = Packet, Lang = xmpp:get_lang(Packet),
Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
ErrText = <<"Access denied by service policy">>, ErrText = <<"Access denied by service policy">>,
Err = jlib:make_error_reply(Packet, Err = xmpp:err_forbidden(ErrText, Lang),
?ERRT_FORBIDDEN(Lang, ErrText)), ejabberd_router:route_error(To, From, Packet, Err)
ejabberd_router:route_error(To, From, Err, Packet)
end. 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, do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
From, To, Packet, DefRoomOpts) -> From, To, Packet, DefRoomOpts) ->
{_AccessRoute, AccessCreate, AccessAdmin, _AccessPersistent} = Access, {_AccessRoute, AccessCreate, _AccessAdmin, _AccessPersistent} = Access,
{Room, _, Nick} = jid:tolower(To), {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 case mnesia:dirty_read(muc_online_room, {Room, Host}) of
[] -> [] ->
Type = fxml:get_attr_s(<<"type">>, Attrs), case Packet of
case {Name, Type} of #presence{type = available, lang = Lang} ->
{<<"presence">>, <<"">>} -> case check_user_can_create_room(
case check_user_can_create_room(ServerHost, ServerHost, AccessCreate, From, Room) and
AccessCreate, From, Room) and
check_create_roomid(ServerHost, Room) of check_create_roomid(ServerHost, Room) of
true -> true ->
{ok, Pid} = start_new_room(Host, ServerHost, Access, {ok, Pid} = start_new_room(
Host, ServerHost, Access,
Room, HistorySize, Room, HistorySize,
RoomShaper, From, Nick, DefRoomOpts), RoomShaper, From, Nick, DefRoomOpts),
register_room(Host, Room, Pid), register_room(Host, Room, Pid),
mod_muc_room:route(Pid, From, Nick, Packet), mod_muc_room:route(Pid, From, Nick, Packet),
ok; ok;
false -> false ->
Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
ErrText = <<"Room creation is denied by service policy">>, ErrText = <<"Room creation is denied by service policy">>,
Err = jlib:make_error_reply( Err = xmpp:make_error(
Packet, ?ERRT_FORBIDDEN(Lang, ErrText)), Packet, xmpp:err_forbidden(ErrText, Lang)),
ejabberd_router:route(To, From, Err) ejabberd_router:route(To, From, Err)
end; end;
_ -> _ ->
Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs), Lang = xmpp:get_lang(Packet),
ErrText = <<"Conference room does not exist">>, ErrText = <<"Conference room does not exist">>,
Err = jlib:make_error_reply(Packet, Err = xmpp:err_item_not_found(ErrText, Lang),
?ERRT_ITEM_NOT_FOUND(Lang, ErrText)), ejabberd_router:route_error(To, From, Packet, Err)
ejabberd_router:route(To, From, Err)
end; end;
[R] -> [R] ->
Pid = R#muc_online_room.pid, Pid = R#muc_online_room.pid,
?DEBUG("MUC: send to process ~p~n", [Pid]), ?DEBUG("MUC: send to process ~p~n", [Pid]),
mod_muc_room:route(Pid, From, Nick, Packet), mod_muc_room:route(Pid, From, Nick, Packet),
ok ok
end
end. 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, check_user_can_create_room(ServerHost, AccessCreate,
From, _RoomID) -> From, _RoomID) ->
case acl:match_rule(ServerHost, AccessCreate, From) of case acl:match_rule(ServerHost, AccessCreate, From) of
@ -583,61 +562,21 @@ register_room(Host, Room, Pid) ->
end, end,
mnesia:transaction(F). mnesia:transaction(F).
iq_disco_items(Host, From, Lang, undefined, undefined) ->
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) ->
Rooms = get_vh_rooms(Host), Rooms = get_vh_rooms(Host),
case erlang:length(Rooms) < ?MAX_ROOMS_DISCOITEMS of case erlang:length(Rooms) < ?MAX_ROOMS_DISCOITEMS of
true -> true ->
iq_disco_items_list(Host, Rooms, {get_disco_item, all, From, Lang}); iq_disco_items_list(Host, Rooms, {get_disco_item, all, From, Lang});
false -> false ->
iq_disco_items(Host, From, Lang, <<"nonemptyrooms">>, none) iq_disco_items(Host, From, Lang, <<"nonemptyrooms">>, undefined)
end; end;
iq_disco_items(Host, From, Lang, <<"nonemptyrooms">>, none) -> iq_disco_items(Host, From, Lang, <<"nonemptyrooms">>, undefined) ->
XmlEmpty = #xmlel{name = <<"item">>, Empty = #disco_item{jid = jid:make(<<"conference.localhost">>),
attrs = node = <<"emptyrooms">>,
[{<<"jid">>, <<"conference.localhost">>}, name = translate:translate(Lang, <<"Empty Rooms">>)},
{<<"node">>, <<"emptyrooms">>},
{<<"name">>, translate:translate(Lang, <<"Empty Rooms">>)}],
children = []},
Query = {get_disco_item, only_non_empty, From, Lang}, Query = {get_disco_item, only_non_empty, From, Lang},
[XmlEmpty | iq_disco_items_list(Host, get_vh_rooms(Host), Query)]; [Empty | iq_disco_items_list(Host, get_vh_rooms(Host), Query)];
iq_disco_items(Host, From, Lang, <<"emptyrooms">>, none) -> 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_list(Host, get_vh_rooms(Host), {get_disco_item, 0, From, Lang});
iq_disco_items(Host, From, Lang, _DiscoNode, Rsm) -> iq_disco_items(Host, From, Lang, _DiscoNode, Rsm) ->
{Rooms, RsmO} = get_vh_rooms(Host, 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, {get_disco_item, all, From, Lang}) ++ RsmOut.
iq_disco_items_list(Host, Rooms, Query) -> iq_disco_items_list(Host, Rooms, Query) ->
lists:zf(fun (#muc_online_room{name_host = lists:zf(
{Name, _Host}, fun(#muc_online_room{name_host = {Name, _Host}, pid = Pid}) ->
pid = Pid}) -> case catch gen_fsm:sync_send_all_state_event(Pid, Query, 100) of
case catch gen_fsm:sync_send_all_state_event(Pid,
Query,
100)
of
{item, Desc} -> {item, Desc} ->
flush(), flush(),
{true, {true, #disco_item{jid = jid:make(Name, Host),
#xmlel{name = <<"item">>, name = Desc}};
attrs = _ ->
[{<<"jid">>, false
jid:to_string({Name, Host,
<<"">>})},
{<<"name">>, Desc}],
children = []}};
_ -> false
end end
end, Rooms). end, Rooms).
get_vh_rooms(Host, #rsm_in{max=M, direction=Direction, id=I, index=Index})-> get_vh_rooms(_, _) ->
AllRooms = lists:sort(get_vh_rooms(Host)), todo.
Count = erlang:length(AllRooms), %% get_vh_rooms(Host, #rsm_in{max=M, direction=Direction, id=I, index=Index})->
Guard = case Direction of %% AllRooms = lists:sort(get_vh_rooms(Host)),
_ when Index =/= undefined -> [{'==', {element, 2, '$1'}, Host}]; %% Count = erlang:length(AllRooms),
aft -> [{'==', {element, 2, '$1'}, Host}, {'>=',{element, 1, '$1'} ,I}]; %% Guard = case Direction of
before when I =/= []-> [{'==', {element, 2, '$1'}, Host}, {'=<',{element, 1, '$1'} ,I}]; %% _ when Index =/= undefined -> [{'==', {element, 2, '$1'}, Host}];
_ -> [{'==', {element, 2, '$1'}, Host}] %% aft -> [{'==', {element, 2, '$1'}, Host}, {'>=',{element, 1, '$1'} ,I}];
end, %% before when I =/= []-> [{'==', {element, 2, '$1'}, Host}, {'=<',{element, 1, '$1'} ,I}];
L = lists:sort( %% _ -> [{'==', {element, 2, '$1'}, Host}]
mnesia:dirty_select(muc_online_room, %% end,
[{#muc_online_room{name_host = '$1', _ = '_'}, %% L = lists:sort(
Guard, %% mnesia:dirty_select(muc_online_room,
['$_']}])), %% [{#muc_online_room{name_host = '$1', _ = '_'},
L2 = if %% Guard,
Index == undefined andalso Direction == before -> %% ['$_']}])),
lists:reverse(lists:sublist(lists:reverse(L), 1, M)); %% L2 = if
Index == undefined -> %% Index == undefined andalso Direction == before ->
lists:sublist(L, 1, M); %% lists:reverse(lists:sublist(lists:reverse(L), 1, M));
Index > Count orelse Index < 0 -> %% Index == undefined ->
[]; %% lists:sublist(L, 1, M);
true -> %% Index > Count orelse Index < 0 ->
lists:sublist(L, Index+1, M) %% [];
end, %% true ->
if L2 == [] -> {L2, #rsm_out{count = Count}}; %% lists:sublist(L, Index+1, M)
true -> %% end,
H = hd(L2), %% if L2 == [] -> {L2, #rsm_out{count = Count}};
NewIndex = get_room_pos(H, AllRooms), %% true ->
T = lists:last(L2), %% H = hd(L2),
{F, _} = H#muc_online_room.name_host, %% NewIndex = get_room_pos(H, AllRooms),
{Last, _} = T#muc_online_room.name_host, %% T = lists:last(L2),
{L2, %% {F, _} = H#muc_online_room.name_host,
#rsm_out{first = F, last = Last, count = Count, %% {Last, _} = T#muc_online_room.name_host,
index = NewIndex}} %% {L2,
end. %% #rsm_out{first = F, last = Last, count = Count,
%% index = NewIndex}}
%% end.
get_subscribed_rooms(ServerHost, Host, From) -> get_subscribed_rooms(ServerHost, Host, From) ->
Rooms = get_rooms(ServerHost, Host), Rooms = get_rooms(ServerHost, Host),
@ -730,60 +662,32 @@ get_room_pos(Desired, [_ | Rooms], HeadPosition) ->
flush() -> receive _ -> flush() after 0 -> ok end. 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) -> get_nick(ServerHost, Host, From) ->
LServer = jid:nameprep(ServerHost), LServer = jid:nameprep(ServerHost),
Mod = gen_mod:db_mod(LServer, ?MODULE), Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:get_nick(LServer, Host, From). Mod:get_nick(LServer, Host, From).
iq_get_register_info(ServerHost, Host, From, Lang) -> iq_get_register_info(ServerHost, Host, From, Lang) ->
{Nick, Registered} = case get_nick(ServerHost, Host, {Nick, Registered} = case get_nick(ServerHost, Host, From) of
From) error -> {<<"">>, false};
of N -> {N, true}
error -> {<<"">>, []};
N ->
{N,
[#xmlel{name = <<"registered">>, attrs = [],
children = []}]}
end, end,
Registered ++ Title = <<(translate:translate(
[#xmlel{name = <<"instructions">>, attrs = [], Lang, <<"Nickname Registration at ">>))/binary, Host/binary>>,
children = Inst = translate:translate(Lang, <<"Enter nickname you want to register">>),
[{xmlcdata, Field = #xdata_field{type = 'text-single',
translate:translate(Lang, label = translate:translate(Lang, <<"Nickname">>),
<<"You need a client that supports x:data " var = <<"nick">>,
"to register the nickname">>)}]}, values = [Nick]},
#xmlel{name = <<"x">>, X = #xdata{type = form, title = Title,
attrs = [{<<"xmlns">>, ?NS_XDATA}, instructions = [Inst], fields = [Field]},
{<<"type">>, <<"form">>}], #register{nick = Nick,
children = registered = Registered,
[#xmlel{name = <<"title">>, attrs = [], instructions =
children = translate:translate(
[{xmlcdata, Lang, <<"You need a client that supports x:data "
<<(translate:translate(Lang, "to register the nickname">>),
<<"Nickname Registration at ">>))/binary, xdata = X}.
Host/binary>>}]},
#xmlel{name = <<"instructions">>, attrs = [],
children =
[{xmlcdata,
translate:translate(Lang,
<<"Enter nickname you want to register">>)}]},
?XFIELD(<<"text-single">>, <<"Nickname">>, <<"nick">>,
Nick)]}].
set_nick(ServerHost, Host, From, Nick) -> set_nick(ServerHost, Host, From, Nick) ->
LServer = jid:nameprep(ServerHost), LServer = jid:nameprep(ServerHost),
@ -793,66 +697,43 @@ set_nick(ServerHost, Host, From, Nick) ->
iq_set_register_info(ServerHost, Host, From, Nick, iq_set_register_info(ServerHost, Host, From, Nick,
Lang) -> Lang) ->
case set_nick(ServerHost, Host, From, Nick) of case set_nick(ServerHost, Host, From, Nick) of
{atomic, ok} -> {result, []}; {atomic, ok} -> {result, undefined};
{atomic, false} -> {atomic, false} ->
ErrText = <<"That nickname is registered by another " ErrText = <<"That nickname is registered by another "
"person">>, "person">>,
{error, ?ERRT_CONFLICT(Lang, ErrText)}; {error, xmpp:err_conflict(ErrText, Lang)};
_ -> _ ->
Txt = <<"Database failure">>, Txt = <<"Database failure">>,
{error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt)} {error, xmpp:err_internal_server_error(Txt, Lang)}
end. end.
process_iq_register_set(ServerHost, Host, From, SubEl, process_iq_register_set(ServerHost, Host, From,
Lang) -> #register{remove = true}, Lang) ->
#xmlel{children = Els} = SubEl, iq_set_register_info(ServerHost, Host, From, <<"">>, Lang);
case fxml:get_subtag(SubEl, <<"remove">>) of process_iq_register_set(_ServerHost, _Host, _From,
false -> #register{xdata = #xdata{type = cancel}}, _Lang) ->
case fxml:remove_cdata(Els) of {result, undefined};
[#xmlel{name = <<"x">>} = XEl] -> process_iq_register_set(ServerHost, Host, From,
case {fxml:get_tag_attr_s(<<"xmlns">>, XEl), #register{nick = Nick, xdata = XData}, Lang) ->
fxml:get_tag_attr_s(<<"type">>, XEl)}
of
{?NS_XDATA, <<"cancel">>} -> {result, []};
{?NS_XDATA, <<"submit">>} ->
XData = jlib:parse_xdata_submit(XEl),
case XData of case XData of
invalid -> #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">>, Txt = <<"Incorrect data form">>,
{error, ?ERRT_BAD_REQUEST(Lang, Txt)}; {error, xmpp:err_bad_request(Txt, Lang)};
_ when is_binary(Nick), Nick /= <<"">> ->
iq_set_register_info(ServerHost, Host, From, Nick, Lang);
_ -> _ ->
case lists:keysearch(<<"nick">>, 1, XData) of ErrText = <<"You must fill in field \"Nickname\" in the form">>,
{value, {_, [Nick]}} when Nick /= <<"">> -> {error, xmpp:err_not_acceptable(ErrText, Lang)}
iq_set_register_info(ServerHost, Host, From,
Nick, Lang);
_ ->
ErrText =
<<"You must fill in field \"Nickname\" "
"in the form">>,
{error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)}
end
end;
_ -> {error, ?ERR_BAD_REQUEST}
end;
_ -> {error, ?ERR_BAD_REQUEST}
end;
_ ->
iq_set_register_info(ServerHost, Host, From, <<"">>,
Lang)
end. 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) -> broadcast_service_message(Host, Msg) ->
lists:foreach( lists:foreach(
fun(#muc_online_room{pid = Pid}) -> fun(#muc_online_room{pid = Pid}) ->

View File

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

View File

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

View File

@ -16,22 +16,33 @@
set_type/2, set_to/2, set_from/2, set_id/2, 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, 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, format_error/1, is_stanza/1, set_subtag/2, get_subtag/2,
remove_subtag/2, has_subtag/2, decode_els/1, pp/1, remove_subtag/2, has_subtag/2, decode_els/1, decode_els/2,
get_name/1, get_text/1, mk_text/1, mk_text/2]). pp/1, get_name/1, get_text/1, mk_text/1, mk_text/2]).
%% XMPP errors %% XMPP errors
-export([err_bad_request/0, err_bad_request/2, -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_conflict/0, err_conflict/2,
err_feature_not_implemented/0, err_feature_not_implemented/2,
err_forbidden/0, err_forbidden/2, err_forbidden/0, err_forbidden/2,
err_not_acceptable/0, err_not_acceptable/2, err_gone/0, err_gone/2,
err_internal_server_error/0, err_internal_server_error/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_item_not_found/0, err_item_not_found/2,
err_jid_malformed/0, err_jid_malformed/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_not_authorized/0, err_not_authorized/2,
err_feature_not_implemented/0, err_feature_not_implemented/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 %% XMPP stream errors
-export([serr_bad_format/0, serr_bad_format/2, -export([serr_bad_format/0, serr_bad_format/2,
@ -246,9 +257,12 @@ decode(Pkt, _Opts) ->
(message()) -> message(); (message()) -> message();
(presence()) -> presence(). (presence()) -> presence().
decode_els(Stanza) -> decode_els(Stanza) ->
decode_els(Stanza, fun xmpp_codec:is_known_tag/1).
decode_els(Stanza, MatchFun) ->
Els = lists:map( Els = lists:map(
fun(#xmlel{} = El) -> fun(#xmlel{} = El) ->
case xmpp_codec:is_known_tag(El) of case MatchFun(El) of
true -> decode(El); true -> decode(El);
false -> El false -> El
end; end;
@ -287,10 +301,10 @@ set_subtag(Stanza, Tag) ->
set_els(Stanza, NewEls). set_els(Stanza, NewEls).
set_subtag([El|Els], Tag, TagName, XMLNS) -> set_subtag([El|Els], Tag, TagName, XMLNS) ->
case {get_name(El), get_ns(El)} of case match_tag(El, TagName, XMLNS) of
{TagName, XMLNS} -> true ->
[Tag|Els]; [Tag|Els];
_ -> false ->
[El|set_subtag(Els, Tag, TagName, XMLNS)] [El|set_subtag(Els, Tag, TagName, XMLNS)]
end; end;
set_subtag([], Tag, _, _) -> set_subtag([], Tag, _, _) ->
@ -304,14 +318,14 @@ get_subtag(Stanza, Tag) ->
get_subtag(Els, TagName, XMLNS). get_subtag(Els, TagName, XMLNS).
get_subtag([El|Els], TagName, XMLNS) -> get_subtag([El|Els], TagName, XMLNS) ->
case {get_name(El), get_ns(El)} of case match_tag(El, TagName, XMLNS) of
{TagName, XMLNS} -> true ->
try try
decode(El) decode(El)
catch _:{xmpp_codec, _Why} -> catch _:{xmpp_codec, _Why} ->
get_subtag(Els, TagName, XMLNS) get_subtag(Els, TagName, XMLNS)
end; end;
_ -> false ->
get_subtag(Els, TagName, XMLNS) get_subtag(Els, TagName, XMLNS)
end; end;
get_subtag([], _, _) -> get_subtag([], _, _) ->
@ -328,10 +342,10 @@ remove_subtag(Stanza, Tag) ->
set_els(Stanza, NewEls). set_els(Stanza, NewEls).
remove_subtag([El|Els], TagName, XMLNS) -> remove_subtag([El|Els], TagName, XMLNS) ->
case {get_name(El), get_ns(El)} of case match_tag(El, TagName, XMLNS) of
{TagName, XMLNS} -> true ->
remove_subtag(Els, TagName, XMLNS); remove_subtag(Els, TagName, XMLNS);
_ -> false ->
[El|remove_subtag(Els, TagName, XMLNS)] [El|remove_subtag(Els, TagName, XMLNS)]
end; end;
remove_subtag([], _, _) -> remove_subtag([], _, _) ->
@ -345,10 +359,10 @@ has_subtag(Stanza, Tag) ->
has_subtag(Els, TagName, XMLNS). has_subtag(Els, TagName, XMLNS).
has_subtag([El|Els], TagName, XMLNS) -> has_subtag([El|Els], TagName, XMLNS) ->
case {get_name(El), get_ns(El)} of case match_tag(El, TagName, XMLNS) of
{TagName, XMLNS} -> true ->
true; true;
_ -> false ->
has_subtag(Els, TagName, XMLNS) has_subtag(Els, TagName, XMLNS)
end; end;
has_subtag([], _, _) -> has_subtag([], _, _) ->
@ -385,14 +399,6 @@ err_bad_request() ->
err_bad_request(Text, Lang) -> err_bad_request(Text, Lang) ->
err(modify, 'bad-request', 400, 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(). -spec err_conflict() -> error().
err_conflict() -> err_conflict() ->
err(cancel, 'conflict', 409). err(cancel, 'conflict', 409).
@ -401,14 +407,6 @@ err_conflict() ->
err_conflict(Text, Lang) -> err_conflict(Text, Lang) ->
err(cancel, 'conflict', 409, 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(). -spec err_feature_not_implemented() -> error().
err_feature_not_implemented() -> err_feature_not_implemented() ->
err(cancel, 'feature-not-implemented', 501). err(cancel, 'feature-not-implemented', 501).
@ -417,14 +415,6 @@ err_feature_not_implemented() ->
err_feature_not_implemented(Text, Lang) -> err_feature_not_implemented(Text, Lang) ->
err(cancel, 'feature-not-implemented', 501, 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(). -spec err_forbidden() -> error().
err_forbidden() -> err_forbidden() ->
err(auth, 'forbidden', 403). err(auth, 'forbidden', 403).
@ -433,14 +423,18 @@ err_forbidden() ->
err_forbidden(Text, Lang) -> err_forbidden(Text, Lang) ->
err(auth, 'forbidden', 403, Text, Lang). err(auth, 'forbidden', 403, Text, Lang).
-spec err_not_acceptable() -> error(). %% RFC 6120 says error type SHOULD be "cancel".
err_not_acceptable() -> %% RFC 3920 and XEP-0082 says it SHOULD be "modify".
err(modify, 'not-acceptable', 406). -spec err_gone() -> error().
err_gone() ->
err(modify, 'gone', 302).
-spec err_not_acceptable(binary(), binary() | undefined) -> error(). -spec err_gone(binary(), binary() | undefined) -> error().
err_not_acceptable(Text, Lang) -> err_gone(Text, Lang) ->
err(modify, 'not-acceptable', 406, 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(). -spec err_internal_server_error() -> error().
err_internal_server_error() -> err_internal_server_error() ->
err(wait, 'internal-server-error', 500). err(wait, 'internal-server-error', 500).
@ -449,13 +443,13 @@ err_internal_server_error() ->
err_internal_server_error(Text, Lang) -> err_internal_server_error(Text, Lang) ->
err(wait, 'internal-server-error', 500, Text, Lang). err(wait, 'internal-server-error', 500, Text, Lang).
-spec err_service_unavailable() -> error(). -spec err_item_not_found() -> error().
err_service_unavailable() -> err_item_not_found() ->
err(cancel, 'service-unavailable', 503). err(cancel, 'item-not-found', 404).
-spec err_service_unavailable(binary(), binary() | undefined) -> error(). -spec err_item_not_found(binary(), binary() | undefined) -> error().
err_service_unavailable(Text, Lang) -> err_item_not_found(Text, Lang) ->
err(cancel, 'service-unavailable', 503, Text, Lang). err(cancel, 'item-not-found', 404, Text, Lang).
-spec err_jid_malformed() -> error(). -spec err_jid_malformed() -> error().
err_jid_malformed() -> err_jid_malformed() ->
@ -465,6 +459,22 @@ err_jid_malformed() ->
err_jid_malformed(Text, Lang) -> err_jid_malformed(Text, Lang) ->
err(modify, 'jid-malformed', 400, 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(). -spec err_not_authorized() -> error().
err_not_authorized() -> err_not_authorized() ->
err(auth, 'not-authorized', 401). err(auth, 'not-authorized', 401).
@ -473,6 +483,108 @@ err_not_authorized() ->
err_not_authorized(Text, Lang) -> err_not_authorized(Text, Lang) ->
err(auth, 'not-authorized', 401, 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 %%% Functions to construct stream errors
%%%=================================================================== %%%===================================================================
@ -712,3 +824,7 @@ add_ns(#xmlel{name = Name} = El) when Name == <<"message">>;
El#xmlel{attrs = Attrs}; El#xmlel{attrs = Attrs};
add_ns(El) -> add_ns(El) ->
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 %% API
-export([add_delay_info/3, add_delay_info/4, unwrap_carbon/1, -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"). -include("xmpp.hrl").
@ -76,6 +77,17 @@ is_standalone_chat_state(Stanza) ->
false false
end. 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 %%% Internal functions
%%%=================================================================== %%%===================================================================

View File

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