mirror of
https://github.com/processone/ejabberd.git
synced 2024-12-22 17:28:25 +01:00
Rewrite mod_mam and mod_muc to use XML generator
This commit is contained in:
parent
5d90292849
commit
179fcd9521
@ -71,6 +71,7 @@
|
||||
-type config() :: #config{}.
|
||||
|
||||
-type role() :: moderator | participant | visitor | none.
|
||||
-type affiliation() :: admin | member | outcast | owner | none.
|
||||
|
||||
-record(user,
|
||||
{
|
||||
@ -120,5 +121,3 @@
|
||||
host = <<>> :: binary() | '_' | '$2'}).
|
||||
|
||||
-type muc_online_users() :: #muc_online_users{}.
|
||||
|
||||
-type muc_room_state() :: #state{}.
|
||||
|
@ -11,13 +11,20 @@
|
||||
-record(csi, {type :: active | inactive}).
|
||||
-type csi() :: #csi{}.
|
||||
|
||||
-record(hint, {type :: 'no-copy' | 'no-store' | 'no-storage' |
|
||||
'store' | 'no-permanent-store'}).
|
||||
-record(hint, {type :: 'no-copy' | 'no-store' | 'no-storage' | 'store' |
|
||||
'no-permanent-store' | 'no-permanent-storage'}).
|
||||
-type hint() :: #hint{}.
|
||||
|
||||
-record(feature_register, {}).
|
||||
-type feature_register() :: #feature_register{}.
|
||||
|
||||
-record(address, {type :: 'bcc' | 'cc' | 'noreply' | 'ofrom' | 'replyroom' | 'replyto' | 'to',
|
||||
jid :: any(),
|
||||
desc :: binary(),
|
||||
node :: binary(),
|
||||
delivered :: any()}).
|
||||
-type address() :: #address{}.
|
||||
|
||||
-record(sasl_success, {text :: any()}).
|
||||
-type sasl_success() :: #sasl_success{}.
|
||||
|
||||
@ -55,6 +62,9 @@
|
||||
stored :: non_neg_integer()}).
|
||||
-type expire() :: #expire{}.
|
||||
|
||||
-record(muc_unsubscribe, {}).
|
||||
-type muc_unsubscribe() :: #muc_unsubscribe{}.
|
||||
|
||||
-record(pubsub_unsubscribe, {node :: binary(),
|
||||
jid :: any(),
|
||||
subid :: binary()}).
|
||||
@ -81,7 +91,7 @@
|
||||
type :: 'member' | 'none' | 'outcast' | 'owner' | 'publish-only' | 'publisher'}).
|
||||
-type pubsub_affiliation() :: #pubsub_affiliation{}.
|
||||
|
||||
-record(muc_decline, {reason :: binary(),
|
||||
-record(muc_decline, {reason = <<>> :: 'undefined' | binary(),
|
||||
from :: any(),
|
||||
to :: any()}).
|
||||
-type muc_decline() :: #muc_decline{}.
|
||||
@ -90,9 +100,20 @@
|
||||
xmlns :: binary()}).
|
||||
-type sm_a() :: #sm_a{}.
|
||||
|
||||
-record(muc_subscribe, {nick :: binary(),
|
||||
events = [] :: [binary()]}).
|
||||
-type muc_subscribe() :: #muc_subscribe{}.
|
||||
|
||||
-record(stanza_id, {by :: any(),
|
||||
id :: binary()}).
|
||||
-type stanza_id() :: #stanza_id{}.
|
||||
|
||||
-record(starttls_proceed, {}).
|
||||
-type starttls_proceed() :: #starttls_proceed{}.
|
||||
|
||||
-record(client_id, {id :: binary()}).
|
||||
-type client_id() :: #client_id{}.
|
||||
|
||||
-record(sm_resumed, {h :: non_neg_integer(),
|
||||
previd :: binary(),
|
||||
xmlns :: binary()}).
|
||||
@ -116,9 +137,19 @@
|
||||
-record(gone, {uri :: binary()}).
|
||||
-type gone() :: #gone{}.
|
||||
|
||||
-record(x_conference, {jid :: any(),
|
||||
password = <<>> :: binary(),
|
||||
reason = <<>> :: binary(),
|
||||
continue :: any(),
|
||||
thread = <<>> :: binary()}).
|
||||
-type x_conference() :: #x_conference{}.
|
||||
|
||||
-record(private, {xml_els = [] :: [any()]}).
|
||||
-type private() :: #private{}.
|
||||
|
||||
-record(nick, {name :: binary()}).
|
||||
-type nick() :: #nick{}.
|
||||
|
||||
-record(p1_ack, {}).
|
||||
-type p1_ack() :: #p1_ack{}.
|
||||
|
||||
@ -163,6 +194,9 @@
|
||||
error = [] :: [{integer(),'undefined' | binary()}]}).
|
||||
-type stat() :: #stat{}.
|
||||
|
||||
-record(addresses, {list = [] :: [#address{}]}).
|
||||
-type addresses() :: #addresses{}.
|
||||
|
||||
-record('see-other-host', {host :: binary()}).
|
||||
-type 'see-other-host'() :: #'see-other-host'{}.
|
||||
|
||||
@ -194,6 +228,9 @@
|
||||
-record(pubsub_event, {items = [] :: [#pubsub_event_items{}]}).
|
||||
-type pubsub_event() :: #pubsub_event{}.
|
||||
|
||||
-record(muc_unique, {name = <<>> :: binary()}).
|
||||
-type muc_unique() :: #muc_unique{}.
|
||||
|
||||
-record(sasl_response, {text :: any()}).
|
||||
-type sasl_response() :: #sasl_response{}.
|
||||
|
||||
@ -217,19 +254,11 @@
|
||||
-record(feature_csi, {xmlns :: binary()}).
|
||||
-type feature_csi() :: #feature_csi{}.
|
||||
|
||||
-record(muc_user_destroy, {reason :: binary(),
|
||||
jid :: any()}).
|
||||
-type muc_user_destroy() :: #muc_user_destroy{}.
|
||||
|
||||
-record(disco_item, {jid :: any(),
|
||||
name :: binary(),
|
||||
node :: binary()}).
|
||||
-type disco_item() :: #disco_item{}.
|
||||
|
||||
-record(disco_items, {node :: binary(),
|
||||
items = [] :: [#disco_item{}]}).
|
||||
-type disco_items() :: #disco_items{}.
|
||||
|
||||
-record(unblock, {items = [] :: [any()]}).
|
||||
-type unblock() :: #unblock{}.
|
||||
|
||||
@ -239,10 +268,8 @@
|
||||
-record(compression, {methods = [] :: [binary()]}).
|
||||
-type compression() :: #compression{}.
|
||||
|
||||
-record(muc_owner_destroy, {jid :: any(),
|
||||
reason :: binary(),
|
||||
password :: binary()}).
|
||||
-type muc_owner_destroy() :: #muc_owner_destroy{}.
|
||||
-record(muc_subscriptions, {list = [] :: [any()]}).
|
||||
-type muc_subscriptions() :: #muc_subscriptions{}.
|
||||
|
||||
-record(pubsub_subscription, {jid :: any(),
|
||||
node :: binary(),
|
||||
@ -252,7 +279,7 @@
|
||||
|
||||
-record(muc_item, {actor :: #muc_actor{},
|
||||
continue :: binary(),
|
||||
reason :: binary(),
|
||||
reason = <<>> :: 'undefined' | binary(),
|
||||
affiliation :: 'admin' | 'member' | 'none' | 'outcast' | 'owner',
|
||||
role :: 'moderator' | 'none' | 'participant' | 'visitor',
|
||||
jid :: any(),
|
||||
@ -267,8 +294,8 @@
|
||||
|
||||
-record(mam_prefs, {xmlns :: binary(),
|
||||
default :: 'always' | 'never' | 'roster',
|
||||
always = [] :: [any()],
|
||||
never = [] :: [any()]}).
|
||||
always :: [any()],
|
||||
never :: [any()]}).
|
||||
-type mam_prefs() :: #mam_prefs{}.
|
||||
|
||||
-record(caps, {node :: binary(),
|
||||
@ -350,13 +377,17 @@
|
||||
-record(block_list, {items = [] :: [any()]}).
|
||||
-type block_list() :: #block_list{}.
|
||||
|
||||
-record(xdata_option, {label :: binary(),
|
||||
value :: binary()}).
|
||||
-type xdata_option() :: #xdata_option{}.
|
||||
|
||||
-record(xdata_field, {label :: binary(),
|
||||
type :: 'boolean' | 'fixed' | 'hidden' | 'jid-multi' | 'jid-single' | 'list-multi' | 'list-single' | 'text-multi' | 'text-private' | 'text-single',
|
||||
var :: binary(),
|
||||
required = false :: boolean(),
|
||||
desc :: binary(),
|
||||
values = [] :: [binary()],
|
||||
options = [] :: [binary()]}).
|
||||
options = [] :: [#xdata_option{}]}).
|
||||
-type xdata_field() :: #xdata_field{}.
|
||||
|
||||
-record(version, {name :: binary(),
|
||||
@ -364,11 +395,6 @@
|
||||
os :: binary()}).
|
||||
-type version() :: #version{}.
|
||||
|
||||
-record(muc_invite, {reason :: binary(),
|
||||
from :: any(),
|
||||
to :: any()}).
|
||||
-type muc_invite() :: #muc_invite{}.
|
||||
|
||||
-record(bind, {jid :: any(),
|
||||
resource :: any()}).
|
||||
-type bind() :: #bind{}.
|
||||
@ -376,13 +402,11 @@
|
||||
-record(rosterver_feature, {}).
|
||||
-type rosterver_feature() :: #rosterver_feature{}.
|
||||
|
||||
-record(muc_user, {decline :: #muc_decline{},
|
||||
destroy :: #muc_user_destroy{},
|
||||
invites = [] :: [#muc_invite{}],
|
||||
items = [] :: [#muc_item{}],
|
||||
status_codes = [] :: [pos_integer()],
|
||||
password :: binary()}).
|
||||
-type muc_user() :: #muc_user{}.
|
||||
-record(muc_invite, {reason = <<>> :: 'undefined' | binary(),
|
||||
from :: any(),
|
||||
to :: any(),
|
||||
continue :: binary()}).
|
||||
-type muc_invite() :: #muc_invite{}.
|
||||
|
||||
-record(carbons_disable, {}).
|
||||
-type carbons_disable() :: #carbons_disable{}.
|
||||
@ -400,7 +424,7 @@
|
||||
-type vcard_org() :: #vcard_org{}.
|
||||
|
||||
-record(rsm_set, {'after' :: binary(),
|
||||
before :: 'none' | binary(),
|
||||
before :: binary(),
|
||||
count :: non_neg_integer(),
|
||||
first :: #rsm_first{},
|
||||
index :: non_neg_integer(),
|
||||
@ -414,6 +438,11 @@
|
||||
complete :: any()}).
|
||||
-type mam_fin() :: #mam_fin{}.
|
||||
|
||||
-record(disco_items, {node :: binary(),
|
||||
items = [] :: [#disco_item{}],
|
||||
rsm :: #rsm_set{}}).
|
||||
-type disco_items() :: #disco_items{}.
|
||||
|
||||
-record(vcard_tel, {home = false :: boolean(),
|
||||
work = false :: boolean(),
|
||||
voice = false :: boolean(),
|
||||
@ -430,6 +459,20 @@
|
||||
number :: binary()}).
|
||||
-type vcard_tel() :: #vcard_tel{}.
|
||||
|
||||
-record(muc_destroy, {xmlns :: binary(),
|
||||
jid :: any(),
|
||||
reason = <<>> :: 'undefined' | binary(),
|
||||
password :: binary()}).
|
||||
-type muc_destroy() :: #muc_destroy{}.
|
||||
|
||||
-record(muc_user, {decline :: #muc_decline{},
|
||||
destroy :: #muc_destroy{},
|
||||
invites = [] :: [#muc_invite{}],
|
||||
items = [] :: [#muc_item{}],
|
||||
status_codes = [] :: [pos_integer()],
|
||||
password :: binary()}).
|
||||
-type muc_user() :: #muc_user{}.
|
||||
|
||||
-record(vcard_key, {type :: binary(),
|
||||
cred :: binary()}).
|
||||
-type vcard_key() :: #vcard_key{}.
|
||||
@ -530,14 +573,11 @@
|
||||
start :: any(),
|
||||
'end' :: any(),
|
||||
with :: any(),
|
||||
withtext :: binary(),
|
||||
rsm :: #rsm_set{},
|
||||
xdata :: #xdata{}}).
|
||||
-type mam_query() :: #mam_query{}.
|
||||
|
||||
-record(muc_owner, {destroy :: #muc_owner_destroy{},
|
||||
config :: #xdata{}}).
|
||||
-type muc_owner() :: #muc_owner{}.
|
||||
|
||||
-record(pubsub_options, {node :: binary(),
|
||||
jid :: any(),
|
||||
subid :: binary(),
|
||||
@ -592,6 +632,11 @@
|
||||
fetch = false :: boolean()}).
|
||||
-type offline() :: #offline{}.
|
||||
|
||||
-record(muc_owner, {destroy :: #muc_destroy{},
|
||||
config :: #xdata{},
|
||||
items = [] :: [#muc_item{}]}).
|
||||
-type muc_owner() :: #muc_owner{}.
|
||||
|
||||
-record(sasl_mechanisms, {list = [] :: [binary()]}).
|
||||
-type sasl_mechanisms() :: #sasl_mechanisms{}.
|
||||
|
||||
@ -709,6 +754,7 @@
|
||||
|
||||
-type xmpp_element() :: compression() |
|
||||
pubsub_subscription() |
|
||||
xdata_option() |
|
||||
version() |
|
||||
pubsub_affiliation() |
|
||||
muc_admin() |
|
||||
@ -726,7 +772,9 @@
|
||||
rsm_set() |
|
||||
'see-other-host'() |
|
||||
hint() |
|
||||
stanza_id() |
|
||||
starttls_proceed() |
|
||||
client_id() |
|
||||
sm_resumed() |
|
||||
forwarded() |
|
||||
xevent() |
|
||||
@ -742,8 +790,11 @@
|
||||
pubsub_event_item() |
|
||||
muc_item() |
|
||||
vcard_temp() |
|
||||
address() |
|
||||
sasl_success() |
|
||||
addresses() |
|
||||
pubsub_event_items() |
|
||||
muc_subscriptions() |
|
||||
disco_items() |
|
||||
pubsub_options() |
|
||||
compress() |
|
||||
@ -751,33 +802,25 @@
|
||||
muc_history() |
|
||||
identity() |
|
||||
feature_csi() |
|
||||
muc_user_destroy() |
|
||||
privacy_query() |
|
||||
delay() |
|
||||
vcard_tel() |
|
||||
vcard_logo() |
|
||||
disco_info() |
|
||||
vcard_geo() |
|
||||
vcard_photo() |
|
||||
feature_register() |
|
||||
register() |
|
||||
muc_owner() |
|
||||
pubsub() |
|
||||
sm_r() |
|
||||
muc_owner() |
|
||||
muc_actor() |
|
||||
error() |
|
||||
stream_error() |
|
||||
muc_user() |
|
||||
vcard_adr() |
|
||||
carbons_private() |
|
||||
mix_leave() |
|
||||
muc_invite() |
|
||||
muc_subscribe() |
|
||||
rosterver_feature() |
|
||||
muc_invite() |
|
||||
vcard_xupdate() |
|
||||
carbons_disable() |
|
||||
bookmark_conference() |
|
||||
offline() |
|
||||
time() |
|
||||
muc_unique() |
|
||||
sasl_response() |
|
||||
pubsub_subscribe() |
|
||||
presence() |
|
||||
@ -786,6 +829,7 @@
|
||||
starttls_failure() |
|
||||
sasl_challenge() |
|
||||
gone() |
|
||||
x_conference() |
|
||||
private() |
|
||||
compress_failure() |
|
||||
sasl_failure() |
|
||||
@ -794,6 +838,7 @@
|
||||
sm_resume() |
|
||||
carbons_enable() |
|
||||
expire() |
|
||||
muc_unsubscribe() |
|
||||
pubsub_unsubscribe() |
|
||||
muc_decline() |
|
||||
chatstate() |
|
||||
@ -803,6 +848,7 @@
|
||||
search() |
|
||||
pubsub_publish() |
|
||||
unblock() |
|
||||
nick() |
|
||||
p1_ack() |
|
||||
block() |
|
||||
mix_join() |
|
||||
@ -832,11 +878,20 @@
|
||||
starttls() |
|
||||
mam_prefs() |
|
||||
sasl_mechanisms() |
|
||||
muc_destroy() |
|
||||
vcard_key() |
|
||||
csi() |
|
||||
roster_query() |
|
||||
muc_owner_destroy() |
|
||||
mam_query() |
|
||||
bookmark_url() |
|
||||
vcard_email() |
|
||||
vcard_label().
|
||||
vcard_label() |
|
||||
vcard_logo() |
|
||||
disco_info() |
|
||||
feature_register() |
|
||||
register() |
|
||||
sm_r() |
|
||||
error() |
|
||||
stream_error() |
|
||||
muc_user() |
|
||||
vcard_adr().
|
||||
|
@ -72,7 +72,7 @@
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||
|
||||
-spec route(jid(), jid(), xmlel() | xmpp_element()) -> ok.
|
||||
-spec route(jid(), jid(), xmlel() | stanza()) -> ok.
|
||||
|
||||
route(From, To, Packet) ->
|
||||
case catch do_route(From, To, Packet) of
|
||||
@ -85,13 +85,21 @@ route(From, To, Packet) ->
|
||||
|
||||
%% Route the error packet only if the originating packet is not an error itself.
|
||||
%% RFC3920 9.3.1
|
||||
-spec route_error(jid(), jid(), xmlel(), xmlel()) -> ok.
|
||||
-spec route_error(jid(), jid(), xmlel(), xmlel()) -> ok;
|
||||
(jid(), jid(), stanza(), error()) -> ok.
|
||||
|
||||
route_error(From, To, ErrPacket, OrigPacket) ->
|
||||
route_error(From, To, #xmlel{} = ErrPacket, #xmlel{} = OrigPacket) ->
|
||||
#xmlel{attrs = Attrs} = OrigPacket,
|
||||
case <<"error">> == fxml:get_attr_s(<<"type">>, Attrs) of
|
||||
false -> route(From, To, ErrPacket);
|
||||
true -> ok
|
||||
end;
|
||||
route_error(From, To, Packet, #error{} = Err) ->
|
||||
Type = xmpp:get_type(Packet),
|
||||
if Type == error; Type == result ->
|
||||
ok;
|
||||
true ->
|
||||
ejabberd_router:route(From, To, xmpp:make_error(Packet, Err))
|
||||
end.
|
||||
|
||||
-spec register_route(binary()) -> term().
|
||||
@ -406,11 +414,16 @@ do_route(OrigFrom, OrigTo, OrigPacket) ->
|
||||
end.
|
||||
|
||||
-spec do_route(jid(), jid(), xmlel() | xmpp_element(), #route{}) -> any().
|
||||
do_route(From, To, Packet,
|
||||
#route{local_hint = {apply, Module, Function}, pid = Pid})
|
||||
when is_pid(Pid) andalso node(Pid) == node() ->
|
||||
try
|
||||
Module:Function(From, To, xmpp:decode(Packet, [ignore_els]))
|
||||
do_route(From, To, Packet, #route{local_hint = LocalHint,
|
||||
pid = Pid}) when is_pid(Pid) ->
|
||||
try xmpp:decode(Packet, [ignore_els]) of
|
||||
Pkt ->
|
||||
case LocalHint of
|
||||
{apply, Module, Function} when node(Pid) == node() ->
|
||||
Module:Function(From, To, Pkt);
|
||||
_ ->
|
||||
Pid ! {route, From, To, Pkt}
|
||||
end
|
||||
catch error:{xmpp_codec, Why} ->
|
||||
?ERROR_MSG("failed to decode xml element ~p when "
|
||||
"routing from ~s to ~s: ~s",
|
||||
@ -418,8 +431,6 @@ do_route(From, To, Packet,
|
||||
xmpp:format_error(Why)]),
|
||||
drop
|
||||
end;
|
||||
do_route(From, To, Packet, #route{pid = Pid}) when is_pid(Pid) ->
|
||||
Pid ! {route, From, To, xmpp:encode(Packet)};
|
||||
do_route(_From, _To, _Packet, _Route) ->
|
||||
drop.
|
||||
|
||||
|
@ -295,7 +295,7 @@ process_sm_iq_items(#iq{type = get, lang = Lang,
|
||||
end;
|
||||
false ->
|
||||
Txt = <<"Not subscribed">>,
|
||||
xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang))
|
||||
xmpp:make_error(IQ, xmpp:err_subscription_required(Txt, Lang))
|
||||
end.
|
||||
|
||||
-spec get_sm_items({error, error()} | {result, [disco_item()]} | empty,
|
||||
@ -371,7 +371,7 @@ process_sm_iq_info(#iq{type = get, lang = Lang,
|
||||
end;
|
||||
false ->
|
||||
Txt = <<"Not subscribed">>,
|
||||
xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang))
|
||||
xmpp:make_error(IQ, xmpp:err_subscription_required(Txt, Lang))
|
||||
end.
|
||||
|
||||
-spec get_sm_identity([identity()], jid(), jid(),
|
||||
|
@ -140,7 +140,7 @@ process_sm_iq(#iq{from = From, to = To, lang = Lang} = IQ) ->
|
||||
end;
|
||||
true ->
|
||||
Txt = <<"Not subscribed">>,
|
||||
xmpp:make_error(IQ, xmpp:err_not_subscribed(Txt, Lang))
|
||||
xmpp:make_error(IQ, xmpp:err_subscription_required(Txt, Lang))
|
||||
end.
|
||||
|
||||
%% @spec (LUser::string(), LServer::string()) ->
|
||||
|
723
src/mod_mam.erl
723
src/mod_mam.erl
@ -34,12 +34,12 @@
|
||||
-export([start/2, stop/1, depends/2]).
|
||||
|
||||
-export([user_send_packet/4, user_send_packet_strip_tag/4, user_receive_packet/5,
|
||||
process_iq_v0_2/3, process_iq_v0_3/3, disco_sm_features/5,
|
||||
remove_user/2, remove_room/3, mod_opt_type/1, muc_process_iq/4,
|
||||
process_iq_v0_2/1, process_iq_v0_3/1, disco_sm_features/5,
|
||||
remove_user/2, remove_room/3, mod_opt_type/1, muc_process_iq/2,
|
||||
muc_filter_message/5, message_is_archived/5, delete_old_messages/2,
|
||||
get_commands_spec/0, msg_to_el/4, get_room_config/4, set_room_option/4]).
|
||||
|
||||
-include("jlib.hrl").
|
||||
-include("xmpp.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("mod_muc_room.hrl").
|
||||
-include("ejabberd_commands.hrl").
|
||||
@ -54,17 +54,12 @@
|
||||
-callback delete_old_messages(binary() | global,
|
||||
erlang:timestamp(),
|
||||
all | chat | groupchat) -> any().
|
||||
-callback extended_fields() -> [xmlel()].
|
||||
-callback extended_fields() -> [xdata_field()].
|
||||
-callback store(xmlel(), binary(), {binary(), binary()}, chat | groupchat,
|
||||
jid(), binary(), recv | send) -> {ok, binary()} | any().
|
||||
-callback write_prefs(binary(), binary(), #archive_prefs{}, binary()) -> ok | any().
|
||||
-callback get_prefs(binary(), binary()) -> {ok, #archive_prefs{}} | error.
|
||||
-callback select(binary(), jid(), jid(),
|
||||
none | erlang:timestamp(),
|
||||
none | erlang:timestamp(),
|
||||
none | ljid() | {text, binary()},
|
||||
none | #rsm_in{},
|
||||
chat | groupchat) ->
|
||||
-callback select(binary(), jid(), jid(), mam_query(), chat | groupchat) ->
|
||||
{[{binary(), non_neg_integer(), xmlel()}], boolean(), non_neg_integer()}.
|
||||
|
||||
%%%===================================================================
|
||||
@ -200,14 +195,10 @@ get_room_config(X, RoomState, _From, Lang) ->
|
||||
true -> <<"1">>;
|
||||
_ -> <<"0">>
|
||||
end,
|
||||
XField = #xmlel{name = <<"field">>,
|
||||
attrs =
|
||||
[{<<"type">>, <<"boolean">>},
|
||||
{<<"label">>, translate:translate(Lang, Label)},
|
||||
{<<"var">>, Var}],
|
||||
children =
|
||||
[#xmlel{name = <<"value">>, attrs = [],
|
||||
children = [{xmlcdata, Val}]}]},
|
||||
XField = #xdata_field{type = boolean,
|
||||
label = translate:translate(Lang, Label),
|
||||
var = Var,
|
||||
values = [Val]},
|
||||
X ++ [XField].
|
||||
|
||||
set_room_option(_Acc, <<"muc#roomconfig_mam">> = Opt, Vals, Lang) ->
|
||||
@ -222,7 +213,7 @@ set_room_option(_Acc, <<"muc#roomconfig_mam">> = Opt, Vals, Lang) ->
|
||||
catch _:{case_clause, _} ->
|
||||
Txt = <<"Value of '~s' should be boolean">>,
|
||||
ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])),
|
||||
{error, ?ERRT_BAD_REQUEST(Lang, ErrTxt)}
|
||||
{error, xmpp:err_bad_request(Lang, ErrTxt)}
|
||||
end;
|
||||
set_room_option(Acc, _Opt, _Vals, _Lang) ->
|
||||
Acc.
|
||||
@ -236,16 +227,7 @@ user_receive_packet(Pkt, C2SState, JID, Peer, To) ->
|
||||
NewPkt = strip_my_archived_tag(Pkt, LServer),
|
||||
case store_msg(C2SState, NewPkt, LUser, LServer, Peer, recv) of
|
||||
{ok, ID} ->
|
||||
Archived = #xmlel{name = <<"archived">>,
|
||||
attrs = [{<<"by">>, LServer},
|
||||
{<<"xmlns">>, ?NS_MAM_TMP},
|
||||
{<<"id">>, ID}]},
|
||||
StanzaID = #xmlel{name = <<"stanza-id">>,
|
||||
attrs = [{<<"by">>, LServer},
|
||||
{<<"xmlns">>, ?NS_SID_0},
|
||||
{<<"id">>, ID}]},
|
||||
NewEls = [Archived, StanzaID|NewPkt#xmlel.children],
|
||||
NewPkt#xmlel{children = NewEls};
|
||||
set_stanza_id(NewPkt, LServer, ID);
|
||||
_ ->
|
||||
NewPkt
|
||||
end;
|
||||
@ -259,19 +241,10 @@ user_send_packet(Pkt, C2SState, JID, Peer) ->
|
||||
case should_archive(Pkt, LServer) of
|
||||
true ->
|
||||
NewPkt = strip_my_archived_tag(Pkt, LServer),
|
||||
case store_msg(C2SState, jlib:replace_from_to(JID, Peer, NewPkt),
|
||||
case store_msg(C2SState, xmpp:set_from_to(NewPkt, JID, Peer),
|
||||
LUser, LServer, Peer, send) of
|
||||
{ok, ID} ->
|
||||
Archived = #xmlel{name = <<"archived">>,
|
||||
attrs = [{<<"by">>, LServer},
|
||||
{<<"xmlns">>, ?NS_MAM_TMP},
|
||||
{<<"id">>, ID}]},
|
||||
StanzaID = #xmlel{name = <<"stanza-id">>,
|
||||
attrs = [{<<"by">>, LServer},
|
||||
{<<"xmlns">>, ?NS_SID_0},
|
||||
{<<"id">>, ID}]},
|
||||
NewEls = [Archived, StanzaID|NewPkt#xmlel.children],
|
||||
NewPkt#xmlel{children = NewEls};
|
||||
set_stanza_id(NewPkt, LServer, ID);
|
||||
_ ->
|
||||
NewPkt
|
||||
end;
|
||||
@ -291,16 +264,7 @@ muc_filter_message(Pkt, #state{config = Config} = MUCState,
|
||||
StorePkt = strip_x_jid_tags(NewPkt),
|
||||
case store_muc(MUCState, StorePkt, RoomJID, From, FromNick) of
|
||||
{ok, ID} ->
|
||||
Archived = #xmlel{name = <<"archived">>,
|
||||
attrs = [{<<"by">>, LServer},
|
||||
{<<"xmlns">>, ?NS_MAM_TMP},
|
||||
{<<"id">>, ID}]},
|
||||
StanzaID = #xmlel{name = <<"stanza-id">>,
|
||||
attrs = [{<<"by">>, LServer},
|
||||
{<<"xmlns">>, ?NS_SID_0},
|
||||
{<<"id">>, ID}]},
|
||||
NewEls = [Archived, StanzaID|NewPkt#xmlel.children],
|
||||
NewPkt#xmlel{children = NewEls};
|
||||
set_stanza_id(NewPkt, LServer, ID);
|
||||
_ ->
|
||||
NewPkt
|
||||
end;
|
||||
@ -308,71 +272,98 @@ muc_filter_message(Pkt, #state{config = Config} = MUCState,
|
||||
Pkt
|
||||
end.
|
||||
|
||||
set_stanza_id(Pkt, LServer, ID) ->
|
||||
Archived = #mam_archived{by = jid:make(LServer), id = ID},
|
||||
StanzaID = #stanza_id{by = jid:make(LServer), id = ID},
|
||||
NewEls = [Archived, StanzaID|xmpp:get_els(Pkt)],
|
||||
xmpp:set_els(Pkt, NewEls).
|
||||
|
||||
% Query archive v0.2
|
||||
process_iq_v0_2(#jid{lserver = LServer} = From,
|
||||
#jid{lserver = LServer} = To,
|
||||
#iq{type = get, sub_el = #xmlel{name = <<"query">>} = SubEl} = IQ) ->
|
||||
Fs = parse_query_v0_2(SubEl),
|
||||
process_iq(LServer, From, To, IQ, SubEl, Fs, chat);
|
||||
process_iq_v0_2(From, To, IQ) ->
|
||||
process_iq(From, To, IQ).
|
||||
process_iq_v0_2(#iq{from = #jid{lserver = LServer},
|
||||
to = #jid{lserver = LServer},
|
||||
type = get, sub_els = [#mam_query{}]} = IQ) ->
|
||||
process_iq(LServer, IQ, chat);
|
||||
process_iq_v0_2(IQ) ->
|
||||
process_iq(IQ).
|
||||
|
||||
% Query archive v0.3
|
||||
process_iq_v0_3(#jid{lserver = LServer} = From,
|
||||
#jid{lserver = LServer} = To,
|
||||
#iq{type = set, sub_el = #xmlel{name = <<"query">>} = SubEl} = IQ) ->
|
||||
process_iq(LServer, From, To, IQ, SubEl, get_xdata_fields(SubEl), chat);
|
||||
process_iq_v0_3(#jid{lserver = LServer},
|
||||
#jid{lserver = LServer},
|
||||
#iq{type = get, sub_el = #xmlel{name = <<"query">>}} = IQ) ->
|
||||
process_iq_v0_3(#iq{from = #jid{lserver = LServer},
|
||||
to = #jid{lserver = LServer},
|
||||
type = set, sub_els = [#mam_query{}]} = IQ) ->
|
||||
process_iq(LServer, IQ, chat);
|
||||
process_iq_v0_3(#iq{from = #jid{lserver = LServer},
|
||||
to = #jid{lserver = LServer},
|
||||
type = get, sub_els = [#mam_query{}]} = IQ) ->
|
||||
process_iq(LServer, IQ);
|
||||
process_iq_v0_3(From, To, IQ) ->
|
||||
process_iq(From, To, IQ).
|
||||
process_iq_v0_3(IQ) ->
|
||||
process_iq(IQ).
|
||||
|
||||
muc_process_iq(#iq{type = set,
|
||||
sub_el = #xmlel{name = <<"query">>,
|
||||
attrs = Attrs} = SubEl} = IQ,
|
||||
MUCState, From, To) ->
|
||||
case fxml:get_attr_s(<<"xmlns">>, Attrs) of
|
||||
NS when NS == ?NS_MAM_0; NS == ?NS_MAM_1 ->
|
||||
muc_process_iq(IQ, MUCState, From, To, get_xdata_fields(SubEl));
|
||||
_ ->
|
||||
IQ
|
||||
muc_process_iq(#iq{type = T, lang = Lang,
|
||||
from = From,
|
||||
sub_els = [#mam_query{xmlns = NS}]} = IQ,
|
||||
MUCState)
|
||||
when (T == set andalso (NS == ?NS_MAM_0 orelse NS == ?NS_MAM_1)) orelse
|
||||
(T == get andalso NS == ?NS_MAM_TMP) ->
|
||||
case may_enter_room(From, MUCState) of
|
||||
true ->
|
||||
LServer = MUCState#state.server_host,
|
||||
Role = mod_muc_room:get_role(From, MUCState),
|
||||
process_iq(LServer, IQ, {groupchat, Role, MUCState});
|
||||
false ->
|
||||
Text = <<"Only members may query archives of this room">>,
|
||||
xmpp:make_error(IQ, xmpp:err_forbidden(Text, Lang))
|
||||
end;
|
||||
muc_process_iq(#iq{type = get,
|
||||
sub_el = #xmlel{name = <<"query">>,
|
||||
attrs = Attrs} = SubEl} = IQ,
|
||||
MUCState, From, To) ->
|
||||
case fxml:get_attr_s(<<"xmlns">>, Attrs) of
|
||||
?NS_MAM_TMP ->
|
||||
muc_process_iq(IQ, MUCState, From, To, parse_query_v0_2(SubEl));
|
||||
NS when NS == ?NS_MAM_0; NS == ?NS_MAM_1 ->
|
||||
LServer = MUCState#state.server_host,
|
||||
process_iq(LServer, IQ);
|
||||
_ ->
|
||||
IQ
|
||||
end;
|
||||
muc_process_iq(IQ, _MUCState, _From, _To) ->
|
||||
sub_els = [#mam_query{xmlns = NS}]} = IQ,
|
||||
MUCState) when NS == ?NS_MAM_0; NS == ?NS_MAM_1 ->
|
||||
LServer = MUCState#state.server_host,
|
||||
process_iq(LServer, IQ);
|
||||
muc_process_iq(IQ, _MUCState) ->
|
||||
IQ.
|
||||
|
||||
get_xdata_fields(SubEl) ->
|
||||
case {fxml:get_subtag_with_xmlns(SubEl, <<"x">>, ?NS_XDATA),
|
||||
fxml:get_subtag_with_xmlns(SubEl, <<"set">>, ?NS_RSM)} of
|
||||
{#xmlel{} = XData, false} ->
|
||||
jlib:parse_xdata_submit(XData);
|
||||
{#xmlel{} = XData, #xmlel{}} ->
|
||||
[{<<"set">>, SubEl} | jlib:parse_xdata_submit(XData)];
|
||||
{false, #xmlel{}} ->
|
||||
[{<<"set">>, SubEl}];
|
||||
{false, false} ->
|
||||
[]
|
||||
end.
|
||||
parse_query(#mam_query{xdata = #xdata{fields = Fs}} = Query, Lang) ->
|
||||
try
|
||||
lists:foldl(
|
||||
fun(#xdata_field{var = <<"start">>, values = [Data|_]}, Q) ->
|
||||
case jlib:datetime_string_to_timestamp(Data) of
|
||||
undefined -> throw({error, <<"start">>});
|
||||
{_, _, _} = TS -> Q#mam_query{start = TS}
|
||||
end;
|
||||
(#xdata_field{var = <<"end">>, values = [Data|_]}, Q) ->
|
||||
case jlib:datetime_string_to_timestamp(Data) of
|
||||
undefined -> throw({error, <<"end">>});
|
||||
{_, _, _} = TS -> Q#mam_query{'end' = TS}
|
||||
end;
|
||||
(#xdata_field{var = <<"with">>, values = [Data|_]}, Q) ->
|
||||
case jid:from_string(Data) of
|
||||
error -> throw({error, <<"with">>});
|
||||
J -> Q#mam_query{with = J}
|
||||
end;
|
||||
(#xdata_field{var = <<"withtext">>, values = [Data|_]}, Q) ->
|
||||
case Data of
|
||||
<<"">> -> throw({error, <<"withtext">>});
|
||||
_ -> Q#mam_query{withtext = Data}
|
||||
end;
|
||||
(#xdata_field{var = <<"FORM_TYPE">>, values = [NS|_]}, Q) ->
|
||||
case Query#mam_query.xmlns of
|
||||
NS -> Q;
|
||||
_ -> throw({error, <<"FORM_TYPE">>})
|
||||
end;
|
||||
(#xdata_field{}, Acc) ->
|
||||
Acc
|
||||
end, Query, Fs)
|
||||
catch throw:{error, Var} ->
|
||||
Txt = io_lib:format("Incorrect value of field '~s'", [Var]),
|
||||
{error, xmpp:err_bad_request(iolist_to_binary(Txt), Lang)}
|
||||
end;
|
||||
parse_query(Query, _Lang) ->
|
||||
Query.
|
||||
|
||||
disco_sm_features(empty, From, To, Node, Lang) ->
|
||||
disco_sm_features({result, []}, From, To, Node, Lang);
|
||||
disco_sm_features({result, OtherFeatures},
|
||||
#jid{luser = U, lserver = S},
|
||||
#jid{luser = U, lserver = S}, <<>>, _Lang) ->
|
||||
#jid{luser = U, lserver = S}, undefined, _Lang) ->
|
||||
{result, [?NS_MAM_TMP, ?NS_MAM_0, ?NS_MAM_1 | OtherFeatures]};
|
||||
disco_sm_features(Acc, _From, _To, _Node, _Lang) ->
|
||||
Acc.
|
||||
@ -440,210 +431,155 @@ delete_old_messages(_TypeBin, _Days) ->
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
|
||||
process_iq(LServer, #iq{sub_el = #xmlel{attrs = Attrs}} = IQ) ->
|
||||
NS = case fxml:get_attr_s(<<"xmlns">>, Attrs) of
|
||||
?NS_MAM_0 ->
|
||||
?NS_MAM_0;
|
||||
_ ->
|
||||
?NS_MAM_1
|
||||
end,
|
||||
CommonFields = [#xmlel{name = <<"field">>,
|
||||
attrs = [{<<"type">>, <<"hidden">>},
|
||||
{<<"var">>, <<"FORM_TYPE">>}],
|
||||
children = [#xmlel{name = <<"value">>,
|
||||
children = [{xmlcdata, NS}]}]},
|
||||
#xmlel{name = <<"field">>,
|
||||
attrs = [{<<"type">>, <<"jid-single">>},
|
||||
{<<"var">>, <<"with">>}]},
|
||||
#xmlel{name = <<"field">>,
|
||||
attrs = [{<<"type">>, <<"text-single">>},
|
||||
{<<"var">>, <<"start">>}]},
|
||||
#xmlel{name = <<"field">>,
|
||||
attrs = [{<<"type">>, <<"text-single">>},
|
||||
{<<"var">>, <<"end">>}]}],
|
||||
process_iq(LServer, #iq{sub_els = [#mam_query{xmlns = NS}]} = IQ) ->
|
||||
CommonFields = [#xdata_field{type = hidden,
|
||||
var = <<"FORM_TYPE">>,
|
||||
values = [NS]},
|
||||
#xdata_field{type = 'jid-single', var = <<"with">>},
|
||||
#xdata_field{type = 'text-single', var = <<"start">>},
|
||||
#xdata_field{type = 'text-single', var = <<"end">>}],
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
ExtendedFields = Mod:extended_fields(),
|
||||
Fields = ExtendedFields ++ CommonFields,
|
||||
Form = #xmlel{name = <<"x">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"form">>}],
|
||||
children = Fields},
|
||||
IQ#iq{type = result,
|
||||
sub_el = [#xmlel{name = <<"query">>,
|
||||
attrs = [{<<"xmlns">>, NS}],
|
||||
children = [Form]}]}.
|
||||
Fields = CommonFields ++ ExtendedFields,
|
||||
Form = #xdata{type = form, fields = Fields},
|
||||
xmpp:make_iq_result(IQ, #mam_query{xmlns = NS, xdata = Form}).
|
||||
|
||||
% Preference setting (both v0.2 & v0.3)
|
||||
process_iq(#jid{luser = LUser, lserver = LServer},
|
||||
#jid{lserver = LServer},
|
||||
#iq{type = set, lang = Lang, sub_el = #xmlel{name = <<"prefs">>} = SubEl} = IQ) ->
|
||||
try {case fxml:get_tag_attr_s(<<"default">>, SubEl) of
|
||||
<<"always">> -> always;
|
||||
<<"never">> -> never;
|
||||
<<"roster">> -> roster
|
||||
end,
|
||||
lists:foldl(
|
||||
fun(#xmlel{name = <<"always">>, children = Els}, {A, N}) ->
|
||||
{get_jids(Els) ++ A, N};
|
||||
(#xmlel{name = <<"never">>, children = Els}, {A, N}) ->
|
||||
{A, get_jids(Els) ++ N};
|
||||
(_, {A, N}) ->
|
||||
{A, N}
|
||||
end, {[], []}, SubEl#xmlel.children)} of
|
||||
{Default, {Always0, Never0}} ->
|
||||
Always = lists:usort(Always0),
|
||||
Never = lists:usort(Never0),
|
||||
case write_prefs(LUser, LServer, LServer, Default, Always, Never) of
|
||||
ok ->
|
||||
NewPrefs = prefs_el(Default, Always, Never, IQ#iq.xmlns),
|
||||
IQ#iq{type = result, sub_el = [NewPrefs]};
|
||||
_Err ->
|
||||
Txt = <<"Database failure">>,
|
||||
IQ#iq{type = error,
|
||||
sub_el = [SubEl, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt)]}
|
||||
end
|
||||
catch _:_ ->
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]}
|
||||
process_iq(#iq{type = set, lang = Lang,
|
||||
sub_els = [#mam_prefs{default = undefined, xmlns = NS}]} = IQ) ->
|
||||
Why = {missing_attr, <<"default">>, <<"prefs">>, NS},
|
||||
ErrTxt = xmpp:format_error(Why),
|
||||
xmpp:make_error(IQ, xmpp:err_bad_request(ErrTxt, Lang));
|
||||
process_iq(#iq{from = #jid{luser = LUser, lserver = LServer},
|
||||
to = #jid{lserver = LServer},
|
||||
type = set, lang = Lang,
|
||||
sub_els = [#mam_prefs{xmlns = NS,
|
||||
default = Default,
|
||||
always = Always0,
|
||||
never = Never0}]} = IQ) ->
|
||||
Always = lists:usort(get_jids(Always0)),
|
||||
Never = lists:usort(get_jids(Never0)),
|
||||
case write_prefs(LUser, LServer, LServer, Default, Always, Never) of
|
||||
ok ->
|
||||
NewPrefs = prefs_el(Default, Always, Never, NS),
|
||||
xmpp:make_iq_result(IQ, NewPrefs);
|
||||
_Err ->
|
||||
Txt = <<"Database failure">>,
|
||||
xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang))
|
||||
end;
|
||||
process_iq(#jid{luser = LUser, lserver = LServer},
|
||||
#jid{lserver = LServer},
|
||||
#iq{type = get, sub_el = #xmlel{name = <<"prefs">>}} = IQ) ->
|
||||
process_iq(#iq{from = #jid{luser = LUser, lserver = LServer},
|
||||
to = #jid{lserver = LServer},
|
||||
type = get, sub_els = [#mam_prefs{xmlns = NS}]} = IQ) ->
|
||||
Prefs = get_prefs(LUser, LServer),
|
||||
PrefsEl = prefs_el(Prefs#archive_prefs.default,
|
||||
Prefs#archive_prefs.always,
|
||||
Prefs#archive_prefs.never,
|
||||
IQ#iq.xmlns),
|
||||
IQ#iq{type = result, sub_el = [PrefsEl]};
|
||||
process_iq(_, _, #iq{sub_el = SubEl} = IQ) ->
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}.
|
||||
NS),
|
||||
xmpp:make_iq_result(IQ, PrefsEl);
|
||||
process_iq(IQ) ->
|
||||
xmpp:make_error(IQ, xmpp:err_not_allowed()).
|
||||
|
||||
process_iq(LServer, #jid{luser = LUser} = From, To, IQ, SubEl, Fs, MsgType) ->
|
||||
process_iq(LServer, #iq{from = #jid{luser = LUser}, lang = Lang,
|
||||
sub_els = [SubEl]} = IQ, MsgType) ->
|
||||
case MsgType of
|
||||
chat ->
|
||||
maybe_activate_mam(LUser, LServer);
|
||||
{groupchat, _Role, _MUCState} ->
|
||||
ok
|
||||
end,
|
||||
case catch lists:foldl(
|
||||
fun({<<"start">>, [Data|_]}, {_, End, With, RSM}) ->
|
||||
{{_, _, _} = jlib:datetime_string_to_timestamp(Data),
|
||||
End, With, RSM};
|
||||
({<<"end">>, [Data|_]}, {Start, _, With, RSM}) ->
|
||||
{Start,
|
||||
{_, _, _} = jlib:datetime_string_to_timestamp(Data),
|
||||
With, RSM};
|
||||
({<<"with">>, [Data|_]}, {Start, End, _, RSM}) ->
|
||||
{Start, End, jid:tolower(jid:from_string(Data)), RSM};
|
||||
({<<"withtext">>, [Data|_]}, {Start, End, _, RSM}) ->
|
||||
{Start, End, {text, Data}, RSM};
|
||||
({<<"set">>, El}, {Start, End, With, _}) ->
|
||||
{Start, End, With, jlib:rsm_decode(El)};
|
||||
(_, Acc) ->
|
||||
Acc
|
||||
end, {none, [], none, none}, Fs) of
|
||||
{'EXIT', _} ->
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]};
|
||||
{_Start, _End, _With, #rsm_in{index = Index}} when is_integer(Index) ->
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERR_FEATURE_NOT_IMPLEMENTED]};
|
||||
{Start, End, With, RSM} ->
|
||||
NS = fxml:get_tag_attr_s(<<"xmlns">>, SubEl),
|
||||
select_and_send(LServer, From, To, Start, End,
|
||||
With, limit_max(RSM, NS), IQ, MsgType)
|
||||
case parse_query(SubEl, Lang) of
|
||||
#mam_query{rsm = #rsm_set{index = I}} when is_integer(I) ->
|
||||
xmpp:make_error(IQ, xmpp:err_feature_not_implemented());
|
||||
#mam_query{rsm = RSM, xmlns = NS} = Query ->
|
||||
NewRSM = limit_max(RSM, NS),
|
||||
NewQuery = Query#mam_query{rsm = NewRSM},
|
||||
select_and_send(LServer, NewQuery, IQ, MsgType);
|
||||
{error, Err} ->
|
||||
xmpp:make_error(IQ, Err)
|
||||
end.
|
||||
|
||||
muc_process_iq(#iq{lang = Lang, sub_el = SubEl} = IQ, MUCState, From, To, Fs) ->
|
||||
case may_enter_room(From, MUCState) of
|
||||
should_archive(#message{type = T}, _LServer) when T == error; T == result ->
|
||||
false;
|
||||
should_archive(#message{body = Body} = Pkt, LServer) ->
|
||||
case is_resent(Pkt, LServer) of
|
||||
true ->
|
||||
LServer = MUCState#state.server_host,
|
||||
Role = mod_muc_room:get_role(From, MUCState),
|
||||
process_iq(LServer, From, To, IQ, SubEl, Fs,
|
||||
{groupchat, Role, MUCState});
|
||||
false;
|
||||
false ->
|
||||
Text = <<"Only members may query archives of this room">>,
|
||||
Error = ?ERRT_FORBIDDEN(Lang, Text),
|
||||
IQ#iq{type = error, sub_el = [SubEl, Error]}
|
||||
end.
|
||||
|
||||
parse_query_v0_2(Query) ->
|
||||
lists:flatmap(
|
||||
fun (#xmlel{name = <<"start">>} = El) ->
|
||||
[{<<"start">>, [fxml:get_tag_cdata(El)]}];
|
||||
(#xmlel{name = <<"end">>} = El) ->
|
||||
[{<<"end">>, [fxml:get_tag_cdata(El)]}];
|
||||
(#xmlel{name = <<"with">>} = El) ->
|
||||
[{<<"with">>, [fxml:get_tag_cdata(El)]}];
|
||||
(#xmlel{name = <<"withtext">>} = El) ->
|
||||
[{<<"withtext">>, [fxml:get_tag_cdata(El)]}];
|
||||
(#xmlel{name = <<"set">>}) ->
|
||||
[{<<"set">>, Query}];
|
||||
(_) ->
|
||||
[]
|
||||
end, Query#xmlel.children).
|
||||
|
||||
should_archive(#xmlel{name = <<"message">>} = Pkt, LServer) ->
|
||||
case fxml:get_attr_s(<<"type">>, Pkt#xmlel.attrs) of
|
||||
<<"error">> ->
|
||||
false;
|
||||
<<"groupchat">> ->
|
||||
false;
|
||||
_ ->
|
||||
case is_resent(Pkt, LServer) of
|
||||
true ->
|
||||
case check_store_hint(Pkt) of
|
||||
store ->
|
||||
true;
|
||||
no_store ->
|
||||
false;
|
||||
false ->
|
||||
case check_store_hint(Pkt) of
|
||||
store ->
|
||||
true;
|
||||
no_store ->
|
||||
false;
|
||||
none ->
|
||||
case fxml:get_subtag_cdata(Pkt, <<"body">>) of
|
||||
<<>> ->
|
||||
%% Empty body
|
||||
false;
|
||||
_ ->
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
||||
end;
|
||||
should_archive(#xmlel{}, _LServer) ->
|
||||
false.
|
||||
|
||||
strip_my_archived_tag(Pkt, LServer) ->
|
||||
NewEls = lists:filter(
|
||||
fun(#xmlel{name = Tag, attrs = Attrs})
|
||||
when Tag == <<"archived">>; Tag == <<"stanza-id">> ->
|
||||
case catch jid:nameprep(
|
||||
fxml:get_attr_s(
|
||||
<<"by">>, Attrs)) of
|
||||
LServer ->
|
||||
none ->
|
||||
case xmpp:get_text(Body) of
|
||||
<<>> ->
|
||||
%% Empty body
|
||||
false;
|
||||
_ ->
|
||||
true
|
||||
end;
|
||||
(_) ->
|
||||
true
|
||||
end, Pkt#xmlel.children),
|
||||
Pkt#xmlel{children = NewEls}.
|
||||
end
|
||||
end
|
||||
end;
|
||||
should_archive(_, _LServer) ->
|
||||
false.
|
||||
|
||||
strip_my_archived_tag(Pkt, LServer) ->
|
||||
NewPkt = xmpp:decode_els(
|
||||
Pkt, fun(El) ->
|
||||
case xmpp:get_name(El) of
|
||||
<<"archived">> ->
|
||||
xmpp:get_ns(El) == ?NS_MAM_TMP;
|
||||
<<"stanza-id">> ->
|
||||
xmpp:get_ns(El) == ?NS_SID_0;
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
end),
|
||||
NewEls = lists:filter(
|
||||
fun(#mam_archived{by = #jid{luser = <<>>} = By}) ->
|
||||
By#jid.lserver /= LServer;
|
||||
(#stanza_id{by = #jid{luser = <<>>} = By}) ->
|
||||
By#jid.lserver /= LServer;
|
||||
(_) ->
|
||||
true
|
||||
end, xmpp:get_els(NewPkt)),
|
||||
xmpp:set_els(NewPkt, NewEls).
|
||||
|
||||
strip_x_jid_tags(Pkt) ->
|
||||
NewPkt = xmpp:decode_els(
|
||||
Pkt, fun(El) ->
|
||||
case xmpp:get_name(El) of
|
||||
<<"x">> ->
|
||||
case xmpp:get_ns(El) of
|
||||
?NS_MUC_USER -> true;
|
||||
?NS_MUC_ADMIN -> true;
|
||||
?NS_MUC_OWNER -> true;
|
||||
_ -> false
|
||||
end;
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
end),
|
||||
NewEls = lists:filter(
|
||||
fun(#xmlel{name = <<"x">>} = XEl) ->
|
||||
not lists:any(fun(ItemEl) ->
|
||||
fxml:get_tag_attr(<<"jid">>, ItemEl)
|
||||
/= false
|
||||
end, fxml:get_subtags(XEl, <<"item">>));
|
||||
(_) ->
|
||||
true
|
||||
end, Pkt#xmlel.children),
|
||||
Pkt#xmlel{children = NewEls}.
|
||||
fun(El) ->
|
||||
Items = case El of
|
||||
#muc_user{items = Is} -> Is;
|
||||
#muc_admin{items = Is} -> Is;
|
||||
#muc_owner{items = Is} -> Is;
|
||||
_ -> []
|
||||
end,
|
||||
not lists:any(fun(#muc_item{jid = JID}) ->
|
||||
JID /= undefined
|
||||
end, Items)
|
||||
end, xmpp:get_els(NewPkt)),
|
||||
xmpp:set_els(NewPkt, NewEls).
|
||||
|
||||
should_archive_peer(C2SState,
|
||||
#archive_prefs{default = Default,
|
||||
always = Always,
|
||||
never = Never},
|
||||
Peer) ->
|
||||
LPeer = jid:tolower(Peer),
|
||||
LPeer = jid:remove_resource(jid:tolower(Peer)),
|
||||
case lists:member(LPeer, Always) of
|
||||
true ->
|
||||
true;
|
||||
@ -667,30 +603,30 @@ should_archive_peer(C2SState,
|
||||
end
|
||||
end.
|
||||
|
||||
should_archive_muc(Pkt) ->
|
||||
case fxml:get_attr_s(<<"type">>, Pkt#xmlel.attrs) of
|
||||
<<"groupchat">> ->
|
||||
case check_store_hint(Pkt) of
|
||||
store ->
|
||||
true;
|
||||
no_store ->
|
||||
false;
|
||||
none ->
|
||||
case fxml:get_subtag_cdata(Pkt, <<"body">>) of
|
||||
<<>> ->
|
||||
case fxml:get_subtag_cdata(Pkt, <<"subject">>) of
|
||||
<<>> ->
|
||||
false;
|
||||
_ ->
|
||||
true
|
||||
end;
|
||||
should_archive_muc(#message{type = groupchat,
|
||||
body = Body, subject = Subj} = Pkt) ->
|
||||
case check_store_hint(Pkt) of
|
||||
store ->
|
||||
true;
|
||||
no_store ->
|
||||
false;
|
||||
none ->
|
||||
case xmpp:get_text(Body) of
|
||||
<<"">> ->
|
||||
case xmpp:get_text(Subj) of
|
||||
<<"">> ->
|
||||
false;
|
||||
_ ->
|
||||
true
|
||||
end
|
||||
end;
|
||||
_ ->
|
||||
true
|
||||
end;
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
end;
|
||||
should_archive_muc(_) ->
|
||||
false.
|
||||
|
||||
check_store_hint(Pkt) ->
|
||||
case has_store_hint(Pkt) of
|
||||
@ -705,30 +641,24 @@ check_store_hint(Pkt) ->
|
||||
end
|
||||
end.
|
||||
|
||||
|
||||
-spec has_store_hint(message()) -> boolean().
|
||||
has_store_hint(Message) ->
|
||||
fxml:get_subtag_with_xmlns(Message, <<"store">>, ?NS_HINTS)
|
||||
/= false.
|
||||
xmpp:has_subtag(Message, #hint{type = 'store'}).
|
||||
|
||||
-spec has_no_store_hint(message()) -> boolean().
|
||||
has_no_store_hint(Message) ->
|
||||
fxml:get_subtag_with_xmlns(Message, <<"no-store">>, ?NS_HINTS)
|
||||
/= false orelse
|
||||
fxml:get_subtag_with_xmlns(Message, <<"no-storage">>, ?NS_HINTS)
|
||||
/= false orelse
|
||||
fxml:get_subtag_with_xmlns(Message, <<"no-permanent-store">>, ?NS_HINTS)
|
||||
/= false orelse
|
||||
fxml:get_subtag_with_xmlns(Message, <<"no-permanent-storage">>, ?NS_HINTS)
|
||||
/= false.
|
||||
xmpp:has_subtag(Message, #hint{type = 'no-store'}) orelse
|
||||
xmpp:has_subtag(Message, #hint{type = 'no-storage'}) orelse
|
||||
xmpp:has_subtag(Message, #hint{type = 'no-permanent-store'}) orelse
|
||||
xmpp:has_subtag(Message, #hint{type = 'no-permanent-storage'}).
|
||||
|
||||
-spec is_resent(message(), binary()) -> boolean().
|
||||
is_resent(Pkt, LServer) ->
|
||||
case fxml:get_subtag_with_xmlns(Pkt, <<"stanza-id">>, ?NS_SID_0) of
|
||||
#xmlel{attrs = Attrs} ->
|
||||
case fxml:get_attr(<<"by">>, Attrs) of
|
||||
{value, LServer} ->
|
||||
true;
|
||||
_ ->
|
||||
false
|
||||
end;
|
||||
false ->
|
||||
case xmpp:get_subtag(Pkt, #stanza_id{}) of
|
||||
#stanza_id{by = #jid{luser = <<>>, lserver = LServer}} ->
|
||||
true;
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
@ -744,7 +674,8 @@ store_msg(C2SState, Pkt, LUser, LServer, Peer, Dir) ->
|
||||
true ->
|
||||
US = {LUser, LServer},
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
Mod:store(Pkt, LServer, US, chat, Peer, <<"">>, Dir);
|
||||
El = xmpp:encode(Pkt),
|
||||
Mod:store(El, LServer, US, chat, Peer, <<"">>, Dir);
|
||||
false ->
|
||||
pass
|
||||
end.
|
||||
@ -755,7 +686,8 @@ store_muc(MUCState, Pkt, RoomJID, Peer, Nick) ->
|
||||
LServer = MUCState#state.server_host,
|
||||
{U, S, _} = jid:tolower(RoomJID),
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
Mod:store(Pkt, LServer, {U, S}, groupchat, Peer, Nick, recv);
|
||||
El = xmpp:encode(Pkt),
|
||||
Mod:store(El, LServer, {U, S}, groupchat, Peer, Nick, recv);
|
||||
false ->
|
||||
pass
|
||||
end.
|
||||
@ -796,20 +728,10 @@ get_prefs(LUser, LServer) ->
|
||||
end.
|
||||
|
||||
prefs_el(Default, Always, Never, NS) ->
|
||||
Default1 = jlib:atom_to_binary(Default),
|
||||
JFun = fun(L) ->
|
||||
[#xmlel{name = <<"jid">>,
|
||||
children = [{xmlcdata, jid:to_string(J)}]}
|
||||
|| J <- L]
|
||||
end,
|
||||
Always1 = #xmlel{name = <<"always">>,
|
||||
children = JFun(Always)},
|
||||
Never1 = #xmlel{name = <<"never">>,
|
||||
children = JFun(Never)},
|
||||
#xmlel{name = <<"prefs">>,
|
||||
attrs = [{<<"xmlns">>, NS},
|
||||
{<<"default">>, Default1}],
|
||||
children = [Always1, Never1]}.
|
||||
#mam_prefs{default = Default,
|
||||
always = [jid:make(LJ) || LJ <- Always],
|
||||
never = [jid:make(LJ) || LJ <- Never],
|
||||
xmlns = NS}.
|
||||
|
||||
maybe_activate_mam(LUser, LServer) ->
|
||||
ActivateOpt = gen_mod:get_module_opt(LServer, ?MODULE,
|
||||
@ -838,21 +760,19 @@ maybe_activate_mam(LUser, LServer) ->
|
||||
ok
|
||||
end.
|
||||
|
||||
select_and_send(LServer, From, To, Start, End, With, RSM, IQ, MsgType) ->
|
||||
{Msgs, IsComplete, Count} = select_and_start(LServer, From, To, Start, End,
|
||||
With, RSM, MsgType),
|
||||
select_and_send(LServer, Query, #iq{from = From, to = To} = IQ, MsgType) ->
|
||||
{Msgs, IsComplete, Count} =
|
||||
case MsgType of
|
||||
chat ->
|
||||
select(LServer, From, From, Query, MsgType);
|
||||
{groupchat, _Role, _MUCState} ->
|
||||
select(LServer, From, To, Query, MsgType)
|
||||
end,
|
||||
SortedMsgs = lists:keysort(2, Msgs),
|
||||
send(From, To, SortedMsgs, RSM, Count, IsComplete, IQ).
|
||||
send(SortedMsgs, Count, IsComplete, IQ).
|
||||
|
||||
select_and_start(LServer, From, To, Start, End, With, RSM, MsgType) ->
|
||||
case MsgType of
|
||||
chat ->
|
||||
select(LServer, From, From, Start, End, With, RSM, MsgType);
|
||||
{groupchat, _Role, _MUCState} ->
|
||||
select(LServer, From, To, Start, End, With, RSM, MsgType)
|
||||
end.
|
||||
|
||||
select(_LServer, JidRequestor, JidArchive, Start, End, _With, RSM,
|
||||
select(_LServer, JidRequestor, JidArchive,
|
||||
#mam_query{start = Start, 'end' = End, rsm = RSM},
|
||||
{groupchat, _Role, #state{config = #config{mam = false},
|
||||
history = History}} = MsgType) ->
|
||||
#lqueue{len = L, queue = Q} = History,
|
||||
@ -864,7 +784,7 @@ select(_LServer, JidRequestor, JidArchive, Start, End, _With, RSM,
|
||||
case match_interval(Now, Start, End) and
|
||||
match_rsm(Now, RSM) of
|
||||
true ->
|
||||
{[{jlib:integer_to_binary(TS), TS,
|
||||
{[{integer_to_binary(TS), TS,
|
||||
msg_to_el(#archive_msg{
|
||||
type = groupchat,
|
||||
timestamp = Now,
|
||||
@ -879,31 +799,27 @@ select(_LServer, JidRequestor, JidArchive, Start, End, _With, RSM,
|
||||
end, 0, queue:to_list(Q)),
|
||||
Msgs = lists:flatten(Msgs0),
|
||||
case RSM of
|
||||
#rsm_in{max = Max, direction = before} ->
|
||||
#rsm_set{max = Max, before = Before} when is_binary(Before) ->
|
||||
{NewMsgs, IsComplete} = filter_by_max(lists:reverse(Msgs), Max),
|
||||
{NewMsgs, IsComplete, L};
|
||||
#rsm_in{max = Max} ->
|
||||
#rsm_set{max = Max} ->
|
||||
{NewMsgs, IsComplete} = filter_by_max(Msgs, Max),
|
||||
{NewMsgs, IsComplete, L};
|
||||
_ ->
|
||||
{Msgs, true, L}
|
||||
end;
|
||||
select(LServer, JidRequestor, JidArchive, Start, End, With, RSM, MsgType) ->
|
||||
select(LServer, JidRequestor, JidArchive, Query, MsgType) ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
Mod:select(LServer, JidRequestor, JidArchive, Start, End, With, RSM,
|
||||
MsgType).
|
||||
Mod:select(LServer, JidRequestor, JidArchive, Query, MsgType).
|
||||
|
||||
msg_to_el(#archive_msg{timestamp = TS, packet = Pkt1, nick = Nick, peer = Peer},
|
||||
MsgType, JidRequestor, #jid{lserver = LServer} = JidArchive) ->
|
||||
Pkt2 = maybe_update_from_to(Pkt1, JidRequestor, JidArchive, Peer, MsgType,
|
||||
Nick),
|
||||
Pkt3 = #xmlel{name = <<"forwarded">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_FORWARD}],
|
||||
children = [fxml:replace_tag_attr(
|
||||
<<"xmlns">>, <<"jabber:client">>, Pkt2)]},
|
||||
jlib:add_delay_info(Pkt3, LServer, TS).
|
||||
#forwarded{sub_els = [Pkt2],
|
||||
delay = #delay{stamp = TS, from = jid:make(LServer)}}.
|
||||
|
||||
maybe_update_from_to(#xmlel{children = Els} = Pkt, JidRequestor, JidArchive,
|
||||
maybe_update_from_to(#message{sub_els = Els} = Pkt, JidRequestor, JidArchive,
|
||||
Peer, {groupchat, Role,
|
||||
#state{config = #config{anonymous = Anon}}},
|
||||
Nick) ->
|
||||
@ -919,18 +835,13 @@ maybe_update_from_to(#xmlel{children = Els} = Pkt, JidRequestor, JidArchive,
|
||||
end,
|
||||
Items = case ExposeJID of
|
||||
true ->
|
||||
[#xmlel{name = <<"x">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_MUC_USER}],
|
||||
children =
|
||||
[#xmlel{name = <<"item">>,
|
||||
attrs = [{<<"jid">>,
|
||||
jid:to_string(Peer)}]}]}];
|
||||
[#muc_user{items = [#muc_item{jid = Peer}]}];
|
||||
false ->
|
||||
[]
|
||||
end,
|
||||
Pkt1 = Pkt#xmlel{children = Items ++ Els},
|
||||
Pkt2 = jlib:replace_from(jid:replace_resource(JidArchive, Nick), Pkt1),
|
||||
jlib:remove_attr(<<"to">>, Pkt2);
|
||||
Pkt#message{from = jid:replace_resource(JidArchive, Nick),
|
||||
to = undefined,
|
||||
sub_els = Items ++ Els};
|
||||
maybe_update_from_to(Pkt, _JidRequestor, _JidArchive, _Peer, chat, _Nick) ->
|
||||
Pkt.
|
||||
|
||||
@ -966,62 +877,46 @@ is_bare_copy(#jid{luser = U, lserver = S, lresource = R}, To) ->
|
||||
false
|
||||
end.
|
||||
|
||||
send(From, To, Msgs, RSM, Count, IsComplete, #iq{sub_el = SubEl} = IQ) ->
|
||||
QID = fxml:get_tag_attr_s(<<"queryid">>, SubEl),
|
||||
NS = fxml:get_tag_attr_s(<<"xmlns">>, SubEl),
|
||||
QIDAttr = if QID /= <<>> ->
|
||||
[{<<"queryid">>, QID}];
|
||||
true ->
|
||||
[]
|
||||
end,
|
||||
CompleteAttr = if NS == ?NS_MAM_TMP ->
|
||||
[];
|
||||
NS == ?NS_MAM_0; NS == ?NS_MAM_1 ->
|
||||
[{<<"complete">>, jlib:atom_to_binary(IsComplete)}]
|
||||
end,
|
||||
-spec send([{binary(), integer(), xmlel()}],
|
||||
non_neg_integer(), boolean(), iq()) -> iq() | ignore.
|
||||
send(Msgs, Count, IsComplete,
|
||||
#iq{from = From, to = To,
|
||||
sub_els = [#mam_query{id = QID, xmlns = NS}]} = IQ) ->
|
||||
Els = lists:map(
|
||||
fun({ID, _IDInt, El}) ->
|
||||
#xmlel{name = <<"message">>,
|
||||
children = [#xmlel{name = <<"result">>,
|
||||
attrs = [{<<"xmlns">>, NS},
|
||||
{<<"id">>, ID}|QIDAttr],
|
||||
children = [El]}]}
|
||||
#message{sub_els = [#mam_result{xmlns = NS,
|
||||
id = ID,
|
||||
queryid = QID,
|
||||
sub_els = [El]}]}
|
||||
end, Msgs),
|
||||
RSMOut = make_rsm_out(Msgs, RSM, Count, QIDAttr ++ CompleteAttr, NS),
|
||||
RSMOut = make_rsm_out(Msgs, Count),
|
||||
Result = if NS == ?NS_MAM_TMP ->
|
||||
#mam_query{xmlns = NS, id = QID, rsm = RSMOut};
|
||||
true ->
|
||||
#mam_fin{id = QID, rsm = RSMOut, complete = IsComplete}
|
||||
end,
|
||||
if NS == ?NS_MAM_TMP; NS == ?NS_MAM_1 ->
|
||||
lists:foreach(
|
||||
fun(El) ->
|
||||
ejabberd_router:route(To, From, El)
|
||||
end, Els),
|
||||
IQ#iq{type = result, sub_el = RSMOut};
|
||||
xmpp:make_iq_result(IQ, Result);
|
||||
NS == ?NS_MAM_0 ->
|
||||
ejabberd_router:route(
|
||||
To, From, jlib:iq_to_xml(IQ#iq{type = result, sub_el = []})),
|
||||
ejabberd_router:route(To, From, xmpp:make_iq_result(IQ)),
|
||||
lists:foreach(
|
||||
fun(El) ->
|
||||
ejabberd_router:route(To, From, El)
|
||||
end, Els),
|
||||
ejabberd_router:route(
|
||||
To, From, #xmlel{name = <<"message">>,
|
||||
children = RSMOut}),
|
||||
ejabberd_router:route(To, From, #message{sub_els = [Result]}),
|
||||
ignore
|
||||
end.
|
||||
|
||||
make_rsm_out([], _, Count, Attrs, NS) ->
|
||||
Tag = if NS == ?NS_MAM_TMP -> <<"query">>;
|
||||
true -> <<"fin">>
|
||||
end,
|
||||
[#xmlel{name = Tag, attrs = [{<<"xmlns">>, NS}|Attrs],
|
||||
children = jlib:rsm_encode(#rsm_out{count = Count})}];
|
||||
make_rsm_out([{FirstID, _, _}|_] = Msgs, _, Count, Attrs, NS) ->
|
||||
-spec make_rsm_out([{binary(), integer(), xmlel()}], non_neg_integer()) -> rsm_set().
|
||||
make_rsm_out([], Count) ->
|
||||
#rsm_set{count = Count};
|
||||
make_rsm_out([{FirstID, _, _}|_] = Msgs, Count) ->
|
||||
{LastID, _, _} = lists:last(Msgs),
|
||||
Tag = if NS == ?NS_MAM_TMP -> <<"query">>;
|
||||
true -> <<"fin">>
|
||||
end,
|
||||
[#xmlel{name = Tag, attrs = [{<<"xmlns">>, NS}|Attrs],
|
||||
children = jlib:rsm_encode(
|
||||
#rsm_out{first = FirstID, count = Count,
|
||||
last = LastID})}].
|
||||
#rsm_set{first = #rsm_first{data = FirstID}, last = LastID, count = Count}.
|
||||
|
||||
filter_by_max(Msgs, undefined) ->
|
||||
{Msgs, true};
|
||||
@ -1030,23 +925,24 @@ filter_by_max(Msgs, Len) when is_integer(Len), Len >= 0 ->
|
||||
filter_by_max(_Msgs, _Junk) ->
|
||||
{[], true}.
|
||||
|
||||
-spec limit_max(rsm_set(), binary()) -> rsm_set().
|
||||
limit_max(RSM, ?NS_MAM_TMP) ->
|
||||
RSM; % XEP-0313 v0.2 doesn't require clients to support RSM.
|
||||
limit_max(#rsm_in{max = Max} = RSM, _NS) when not is_integer(Max) ->
|
||||
RSM#rsm_in{max = ?DEF_PAGE_SIZE};
|
||||
limit_max(#rsm_in{max = Max} = RSM, _NS) when Max > ?MAX_PAGE_SIZE ->
|
||||
RSM#rsm_in{max = ?MAX_PAGE_SIZE};
|
||||
limit_max(#rsm_set{max = Max} = RSM, _NS) when not is_integer(Max) ->
|
||||
RSM#rsm_set{max = ?DEF_PAGE_SIZE};
|
||||
limit_max(#rsm_set{max = Max} = RSM, _NS) when Max > ?MAX_PAGE_SIZE ->
|
||||
RSM#rsm_set{max = ?MAX_PAGE_SIZE};
|
||||
limit_max(RSM, _NS) ->
|
||||
RSM.
|
||||
|
||||
match_interval(Now, Start, End) ->
|
||||
(Now >= Start) and (Now =< End).
|
||||
|
||||
match_rsm(Now, #rsm_in{id = ID, direction = aft}) when ID /= <<"">> ->
|
||||
Now1 = (catch usec_to_now(jlib:binary_to_integer(ID))),
|
||||
match_rsm(Now, #rsm_set{'after' = ID}) when is_binary(ID), ID /= <<"">> ->
|
||||
Now1 = (catch usec_to_now(binary_to_integer(ID))),
|
||||
Now > Now1;
|
||||
match_rsm(Now, #rsm_in{id = ID, direction = before}) when ID /= <<"">> ->
|
||||
Now1 = (catch usec_to_now(jlib:binary_to_integer(ID))),
|
||||
match_rsm(Now, #rsm_set{before = ID}) when is_binary(ID), ID /= <<"">> ->
|
||||
Now1 = (catch usec_to_now(binary_to_integer(ID))),
|
||||
Now < Now1;
|
||||
match_rsm(_Now, _) ->
|
||||
true.
|
||||
@ -1066,15 +962,10 @@ datetime_to_now(DateTime, USecs) ->
|
||||
calendar:datetime_to_gregorian_seconds({{1970, 1, 1}, {0, 0, 0}}),
|
||||
{Seconds div 1000000, Seconds rem 1000000, USecs}.
|
||||
|
||||
get_jids(Els) ->
|
||||
lists:flatmap(
|
||||
fun(#xmlel{name = <<"jid">>} = El) ->
|
||||
J = jid:from_string(fxml:get_tag_cdata(El)),
|
||||
[jid:tolower(jid:remove_resource(J)),
|
||||
jid:tolower(J)];
|
||||
(_) ->
|
||||
[]
|
||||
end, Els).
|
||||
get_jids(undefined) ->
|
||||
[];
|
||||
get_jids(Js) ->
|
||||
[jid:tolower(jid:remove_resource(J)) || J <- Js].
|
||||
|
||||
get_commands_spec() ->
|
||||
[#ejabberd_commands{name = delete_old_mam_messages, tags = [purge],
|
||||
|
@ -12,10 +12,10 @@
|
||||
|
||||
%% API
|
||||
-export([init/2, remove_user/2, remove_room/3, delete_old_messages/3,
|
||||
extended_fields/0, store/7, write_prefs/4, get_prefs/2, select/8]).
|
||||
extended_fields/0, store/7, write_prefs/4, get_prefs/2, select/5]).
|
||||
|
||||
-include_lib("stdlib/include/ms_transform.hrl").
|
||||
-include("jlib.hrl").
|
||||
-include("xmpp.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("mod_mam.hrl").
|
||||
|
||||
@ -132,7 +132,8 @@ get_prefs(LUser, LServer) ->
|
||||
|
||||
select(_LServer, JidRequestor,
|
||||
#jid{luser = LUser, lserver = LServer} = JidArchive,
|
||||
Start, End, With, RSM, MsgType) ->
|
||||
#mam_query{start = Start, 'end' = End,
|
||||
with = With, rsm = RSM}, MsgType) ->
|
||||
MS = make_matchspec(LUser, LServer, Start, End, With),
|
||||
Msgs = mnesia:dirty_select(archive_msg, MS),
|
||||
SortedMsgs = lists:keysort(#archive_msg.timestamp, Msgs),
|
||||
@ -174,7 +175,7 @@ make_matchspec(LUser, LServer, Start, End, {_, _, _} = With) ->
|
||||
Peer == With ->
|
||||
Msg
|
||||
end);
|
||||
make_matchspec(LUser, LServer, Start, End, none) ->
|
||||
make_matchspec(LUser, LServer, Start, End, undefined) ->
|
||||
ets:fun2ms(
|
||||
fun(#archive_msg{timestamp = TS,
|
||||
us = US,
|
||||
@ -184,28 +185,27 @@ make_matchspec(LUser, LServer, Start, End, none) ->
|
||||
Msg
|
||||
end).
|
||||
|
||||
filter_by_rsm(Msgs, none) ->
|
||||
filter_by_rsm(Msgs, undefined) ->
|
||||
{Msgs, true};
|
||||
filter_by_rsm(_Msgs, #rsm_in{max = Max}) when Max < 0 ->
|
||||
filter_by_rsm(_Msgs, #rsm_set{max = Max}) when Max < 0 ->
|
||||
{[], true};
|
||||
filter_by_rsm(Msgs, #rsm_in{max = Max, direction = Direction, id = ID}) ->
|
||||
NewMsgs = case Direction of
|
||||
aft when ID /= <<"">> ->
|
||||
filter_by_rsm(Msgs, #rsm_set{max = Max, before = Before, 'after' = After}) ->
|
||||
NewMsgs = if is_binary(After), After /= <<"">> ->
|
||||
lists:filter(
|
||||
fun(#archive_msg{id = I}) ->
|
||||
?BIN_GREATER_THAN(I, ID)
|
||||
?BIN_GREATER_THAN(I, After)
|
||||
end, Msgs);
|
||||
before when ID /= <<"">> ->
|
||||
is_binary(Before), Before /= <<"">> ->
|
||||
lists:foldl(
|
||||
fun(#archive_msg{id = I} = Msg, Acc)
|
||||
when ?BIN_LESS_THAN(I, ID) ->
|
||||
when ?BIN_LESS_THAN(I, Before) ->
|
||||
[Msg|Acc];
|
||||
(_, Acc) ->
|
||||
Acc
|
||||
end, [], Msgs);
|
||||
before when ID == <<"">> ->
|
||||
is_binary(Before), Before == <<"">> ->
|
||||
lists:reverse(Msgs);
|
||||
_ ->
|
||||
true ->
|
||||
Msgs
|
||||
end,
|
||||
filter_by_max(NewMsgs, Max).
|
||||
|
@ -14,10 +14,10 @@
|
||||
|
||||
%% API
|
||||
-export([init/2, remove_user/2, remove_room/3, delete_old_messages/3,
|
||||
extended_fields/0, store/7, write_prefs/4, get_prefs/2, select/8]).
|
||||
extended_fields/0, store/7, write_prefs/4, get_prefs/2, select/5]).
|
||||
|
||||
-include_lib("stdlib/include/ms_transform.hrl").
|
||||
-include("jlib.hrl").
|
||||
-include("xmpp.hrl").
|
||||
-include("mod_mam.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("ejabberd_sql_pt.hrl").
|
||||
@ -51,9 +51,7 @@ delete_old_messages(ServerHost, TimeStamp, Type) ->
|
||||
ok.
|
||||
|
||||
extended_fields() ->
|
||||
[#xmlel{name = <<"field">>,
|
||||
attrs = [{<<"type">>, <<"text-single">>},
|
||||
{<<"var">>, <<"withtext">>}]}].
|
||||
[#xdata_field{type = 'text-single', var = <<"withtext">>}].
|
||||
|
||||
store(Pkt, LServer, {LUser, LHost}, Type, Peer, Nick, _Dir) ->
|
||||
TSinteger = p1_time_compat:system_time(micro_seconds),
|
||||
@ -126,13 +124,12 @@ get_prefs(LUser, LServer) ->
|
||||
end.
|
||||
|
||||
select(LServer, JidRequestor, #jid{luser = LUser} = JidArchive,
|
||||
Start, End, With, RSM, MsgType) ->
|
||||
MAMQuery, MsgType) ->
|
||||
User = case MsgType of
|
||||
chat -> LUser;
|
||||
{groupchat, _Role, _MUCState} -> jid:to_string(JidArchive)
|
||||
end,
|
||||
{Query, CountQuery} = make_sql_query(User, LServer,
|
||||
Start, End, With, RSM),
|
||||
{Query, CountQuery} = make_sql_query(User, LServer, MAMQuery),
|
||||
% TODO from XEP-0313 v0.2: "To conserve resources, a server MAY place a
|
||||
% reasonable limit on how many stanzas may be pushed to a client in one
|
||||
% request. If a query returns a number of stanzas greater than this limit
|
||||
@ -142,10 +139,7 @@ select(LServer, JidRequestor, #jid{luser = LUser} = JidArchive,
|
||||
case {ejabberd_sql:sql_query(LServer, Query),
|
||||
ejabberd_sql:sql_query(LServer, CountQuery)} of
|
||||
{{selected, _, Res}, {selected, _, [[Count]]}} ->
|
||||
{Max, Direction} = case RSM of
|
||||
#rsm_in{max = M, direction = D} -> {M, D};
|
||||
_ -> {undefined, undefined}
|
||||
end,
|
||||
{Max, Direction, _} = get_max_direction_id(MAMQuery#mam_query.rsm),
|
||||
{Res1, IsComplete} =
|
||||
if Max >= 0 andalso Max /= undefined andalso length(Res) > Max ->
|
||||
if Direction == before ->
|
||||
@ -200,15 +194,10 @@ usec_to_now(Int) ->
|
||||
Sec = Secs rem 1000000,
|
||||
{MSec, Sec, USec}.
|
||||
|
||||
make_sql_query(User, LServer, Start, End, With, RSM) ->
|
||||
{Max, Direction, ID} = case RSM of
|
||||
#rsm_in{} ->
|
||||
{RSM#rsm_in.max,
|
||||
RSM#rsm_in.direction,
|
||||
RSM#rsm_in.id};
|
||||
none ->
|
||||
{none, none, <<>>}
|
||||
end,
|
||||
make_sql_query(User, LServer,
|
||||
#mam_query{start = Start, 'end' = End, with = With,
|
||||
withtext = WithText, rsm = RSM}) ->
|
||||
{Max, Direction, ID} = get_max_direction_id(RSM),
|
||||
ODBCType = ejabberd_config:get_option(
|
||||
{sql_type, LServer},
|
||||
ejabberd_sql:opt_type(sql_type)),
|
||||
@ -228,12 +217,16 @@ make_sql_query(User, LServer, Start, End, With, RSM) ->
|
||||
true ->
|
||||
[]
|
||||
end,
|
||||
WithClause = case With of
|
||||
{text, <<>>} ->
|
||||
[];
|
||||
{text, Txt} ->
|
||||
[<<" and match (txt) against ('">>,
|
||||
Escape(Txt), <<"')">>];
|
||||
WithTextClause = case WithText of
|
||||
{text, <<>>} ->
|
||||
[];
|
||||
{text, Txt} ->
|
||||
[<<" and match (txt) against ('">>,
|
||||
Escape(Txt), <<"')">>];
|
||||
undefined ->
|
||||
[]
|
||||
end,
|
||||
WithClause = case catch jid:tolower(With) of
|
||||
{_, _, <<>>} ->
|
||||
[<<" and bare_peer='">>,
|
||||
Escape(jid:to_string(With)),
|
||||
@ -242,7 +235,7 @@ make_sql_query(User, LServer, Start, End, With, RSM) ->
|
||||
[<<" and peer='">>,
|
||||
Escape(jid:to_string(With)),
|
||||
<<"'">>];
|
||||
none ->
|
||||
_ ->
|
||||
[]
|
||||
end,
|
||||
PageClause = case catch jlib:binary_to_integer(ID) of
|
||||
@ -250,7 +243,7 @@ make_sql_query(User, LServer, Start, End, With, RSM) ->
|
||||
case Direction of
|
||||
before ->
|
||||
[<<" AND timestamp < ">>, ID];
|
||||
aft ->
|
||||
'after' ->
|
||||
[<<" AND timestamp > ">>, ID];
|
||||
_ ->
|
||||
[]
|
||||
@ -276,7 +269,7 @@ make_sql_query(User, LServer, Start, End, With, RSM) ->
|
||||
|
||||
Query = [<<"SELECT ">>, TopClause, <<" timestamp, xml, peer, kind, nick"
|
||||
" FROM archive WHERE username='">>,
|
||||
SUser, <<"'">>, WithClause, StartClause, EndClause,
|
||||
SUser, <<"'">>, WithClause, WithTextClause, StartClause, EndClause,
|
||||
PageClause],
|
||||
|
||||
QueryPage =
|
||||
@ -294,4 +287,20 @@ make_sql_query(User, LServer, Start, End, With, RSM) ->
|
||||
end,
|
||||
{QueryPage,
|
||||
[<<"SELECT COUNT(*) FROM archive WHERE username='">>,
|
||||
SUser, <<"'">>, WithClause, StartClause, EndClause, <<";">>]}.
|
||||
SUser, <<"'">>, WithClause, WithTextClause, StartClause, EndClause, <<";">>]}.
|
||||
|
||||
-spec get_max_direction_id(rsm_set() | undefined) ->
|
||||
{integer() | undefined,
|
||||
before | 'after' | undefined,
|
||||
binary()}.
|
||||
get_max_direction_id(RSM) ->
|
||||
case RSM of
|
||||
#rsm_set{max = Max, before = Before} when is_binary(Before) ->
|
||||
{Max, before, Before};
|
||||
#rsm_set{max = Max, 'after' = After} when is_binary(After) ->
|
||||
{Max, 'after', After};
|
||||
#rsm_set{max = Max} ->
|
||||
{Max, undefined, <<>>};
|
||||
_ ->
|
||||
{undefined, undefined, <<>>}
|
||||
end.
|
||||
|
675
src/mod_muc.erl
675
src/mod_muc.erl
@ -43,7 +43,12 @@
|
||||
forget_room/3,
|
||||
create_room/5,
|
||||
shutdown_rooms/1,
|
||||
process_iq_disco_items/4,
|
||||
process_disco_info/1,
|
||||
process_disco_items/1,
|
||||
process_vcard/1,
|
||||
process_register/1,
|
||||
process_muc_unique/1,
|
||||
process_mucsub/1,
|
||||
broadcast_service_message/2,
|
||||
export/1,
|
||||
import/1,
|
||||
@ -58,7 +63,7 @@
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
|
||||
-include("jlib.hrl").
|
||||
-include("xmpp.hrl").
|
||||
-include("mod_muc.hrl").
|
||||
|
||||
-record(state,
|
||||
@ -154,17 +159,6 @@ forget_room(ServerHost, Host, Name) ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
Mod:forget_room(LServer, Host, Name).
|
||||
|
||||
process_iq_disco_items(Host, From, To,
|
||||
#iq{lang = Lang} = IQ) ->
|
||||
Rsm = jlib:rsm_decode(IQ),
|
||||
DiscoNode = fxml:get_tag_attr_s(<<"node">>, IQ#iq.sub_el),
|
||||
Res = IQ#iq{type = result,
|
||||
sub_el =
|
||||
[#xmlel{name = <<"query">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_DISCO_ITEMS}],
|
||||
children = iq_disco_items(Host, From, Lang, DiscoNode, Rsm)}]},
|
||||
ejabberd_router:route(To, From, jlib:iq_to_xml(Res)).
|
||||
|
||||
can_use_nick(_ServerHost, _Host, _JID, <<"">>) -> false;
|
||||
can_use_nick(ServerHost, Host, JID, Nick) ->
|
||||
LServer = jid:nameprep(ServerHost),
|
||||
@ -176,6 +170,8 @@ can_use_nick(ServerHost, Host, JID, Nick) ->
|
||||
%%====================================================================
|
||||
|
||||
init([Host, Opts]) ->
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
|
||||
one_queue),
|
||||
MyHost = gen_mod:get_opt_host(Host, Opts,
|
||||
<<"conference.@HOST@">>),
|
||||
Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
|
||||
@ -255,6 +251,18 @@ init([Host, Opts]) ->
|
||||
RoomShaper = gen_mod:get_opt(room_shaper, Opts,
|
||||
fun(A) when is_atom(A) -> A end,
|
||||
none),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_REGISTER,
|
||||
?MODULE, process_register, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_VCARD,
|
||||
?MODULE, process_vcard, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_MUCSUB,
|
||||
?MODULE, process_mucsub, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_MUC_UNIQUE,
|
||||
?MODULE, process_muc_unique, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_INFO,
|
||||
?MODULE, process_disco_info, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_ITEMS,
|
||||
?MODULE, process_disco_items, IQDisc),
|
||||
ejabberd_router:register_route(MyHost, Host),
|
||||
load_permanent_rooms(MyHost, Host,
|
||||
{Access, AccessCreate, AccessAdmin, AccessPersistent},
|
||||
@ -314,8 +322,14 @@ handle_info({mnesia_system_event, {mnesia_down, Node}}, State) ->
|
||||
{noreply, State};
|
||||
handle_info(_Info, State) -> {noreply, State}.
|
||||
|
||||
terminate(_Reason, State) ->
|
||||
ejabberd_router:unregister_route(State#state.host),
|
||||
terminate(_Reason, #state{host = MyHost}) ->
|
||||
ejabberd_router:unregister_route(MyHost),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_REGISTER),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_VCARD),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_MUCSUB),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_MUC_UNIQUE),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_INFO),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_ITEMS),
|
||||
ok.
|
||||
|
||||
code_change(_OldVsn, State, _Extra) -> {ok, State}.
|
||||
@ -331,197 +345,162 @@ do_route(Host, ServerHost, Access, HistorySize, RoomShaper,
|
||||
allow ->
|
||||
do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
|
||||
From, To, Packet, DefRoomOpts);
|
||||
_ ->
|
||||
#xmlel{attrs = Attrs} = Packet,
|
||||
Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
|
||||
deny ->
|
||||
Lang = xmpp:get_lang(Packet),
|
||||
ErrText = <<"Access denied by service policy">>,
|
||||
Err = jlib:make_error_reply(Packet,
|
||||
?ERRT_FORBIDDEN(Lang, ErrText)),
|
||||
ejabberd_router:route_error(To, From, Err, Packet)
|
||||
Err = xmpp:err_forbidden(ErrText, Lang),
|
||||
ejabberd_router:route_error(To, From, Packet, Err)
|
||||
end.
|
||||
|
||||
|
||||
do_route1(_Host, _ServerHost, _Access, _HistorySize, _RoomShaper,
|
||||
From, #jid{luser = <<"">>, lresource = <<"">>} = To,
|
||||
#iq{} = IQ, _DefRoomOpts) ->
|
||||
ejabberd_local:process_iq(From, To, IQ);
|
||||
do_route1(Host, ServerHost, Access, _HistorySize, _RoomShaper,
|
||||
From, #jid{luser = <<"">>, lresource = <<"">>} = To,
|
||||
#message{lang = Lang, body = Body, type = Type} = Packet, _) ->
|
||||
{_AccessRoute, _AccessCreate, AccessAdmin, _AccessPersistent} = Access,
|
||||
if Type == error ->
|
||||
ok;
|
||||
true ->
|
||||
case acl:match_rule(ServerHost, AccessAdmin, From) of
|
||||
allow ->
|
||||
Msg = xmpp:get_text(Body),
|
||||
broadcast_service_message(Host, Msg);
|
||||
deny ->
|
||||
ErrText = <<"Only service administrators are allowed "
|
||||
"to send service messages">>,
|
||||
Err = xmpp:make_error(
|
||||
Packet, xmpp:err_forbidden(ErrText, Lang)),
|
||||
ejabberd_router:route(To, From, Err)
|
||||
end
|
||||
end;
|
||||
do_route1(_Host, _ServerHost, _Access, _HistorySize, _RoomShaper,
|
||||
From, #jid{luser = <<"">>} = To, Packet, _DefRoomOpts) ->
|
||||
Err = xmpp:err_item_not_found(),
|
||||
ejabberd_router:route_error(To, From, Packet, Err);
|
||||
do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
|
||||
From, To, Packet, DefRoomOpts) ->
|
||||
{_AccessRoute, AccessCreate, AccessAdmin, _AccessPersistent} = Access,
|
||||
{_AccessRoute, AccessCreate, _AccessAdmin, _AccessPersistent} = Access,
|
||||
{Room, _, Nick} = jid:tolower(To),
|
||||
#xmlel{name = Name, attrs = Attrs} = Packet,
|
||||
case Room of
|
||||
<<"">> ->
|
||||
case Nick of
|
||||
<<"">> ->
|
||||
case Name of
|
||||
<<"iq">> ->
|
||||
case jlib:iq_query_info(Packet) of
|
||||
#iq{type = get, xmlns = (?NS_DISCO_INFO) = XMLNS,
|
||||
sub_el = _SubEl, lang = Lang} =
|
||||
IQ ->
|
||||
Info = ejabberd_hooks:run_fold(disco_info,
|
||||
ServerHost, [],
|
||||
[ServerHost, ?MODULE,
|
||||
<<"">>, <<"">>]),
|
||||
Res = IQ#iq{type = result,
|
||||
sub_el =
|
||||
[#xmlel{name = <<"query">>,
|
||||
attrs =
|
||||
[{<<"xmlns">>, XMLNS}],
|
||||
children =
|
||||
iq_disco_info(
|
||||
ServerHost, Lang) ++
|
||||
Info}]},
|
||||
ejabberd_router:route(To, From,
|
||||
jlib:iq_to_xml(Res));
|
||||
#iq{type = get, xmlns = ?NS_DISCO_ITEMS} = IQ ->
|
||||
spawn(?MODULE, process_iq_disco_items,
|
||||
[Host, From, To, IQ]);
|
||||
#iq{type = get, xmlns = (?NS_REGISTER) = XMLNS,
|
||||
lang = Lang, sub_el = _SubEl} =
|
||||
IQ ->
|
||||
Res = IQ#iq{type = result,
|
||||
sub_el =
|
||||
[#xmlel{name = <<"query">>,
|
||||
attrs =
|
||||
[{<<"xmlns">>, XMLNS}],
|
||||
children =
|
||||
iq_get_register_info(ServerHost,
|
||||
Host,
|
||||
From,
|
||||
Lang)}]},
|
||||
ejabberd_router:route(To, From,
|
||||
jlib:iq_to_xml(Res));
|
||||
#iq{type = set, xmlns = (?NS_REGISTER) = XMLNS,
|
||||
lang = Lang, sub_el = SubEl} =
|
||||
IQ ->
|
||||
case process_iq_register_set(ServerHost, Host, From,
|
||||
SubEl, Lang)
|
||||
of
|
||||
{result, IQRes} ->
|
||||
Res = IQ#iq{type = result,
|
||||
sub_el =
|
||||
[#xmlel{name = <<"query">>,
|
||||
attrs =
|
||||
[{<<"xmlns">>,
|
||||
XMLNS}],
|
||||
children = IQRes}]},
|
||||
ejabberd_router:route(To, From,
|
||||
jlib:iq_to_xml(Res));
|
||||
{error, Error} ->
|
||||
Err = jlib:make_error_reply(Packet, Error),
|
||||
ejabberd_router:route(To, From, Err)
|
||||
end;
|
||||
#iq{type = get, xmlns = (?NS_VCARD) = XMLNS,
|
||||
lang = Lang, sub_el = _SubEl} =
|
||||
IQ ->
|
||||
Res = IQ#iq{type = result,
|
||||
sub_el =
|
||||
[#xmlel{name = <<"vCard">>,
|
||||
attrs =
|
||||
[{<<"xmlns">>, XMLNS}],
|
||||
children =
|
||||
iq_get_vcard(Lang)}]},
|
||||
ejabberd_router:route(To, From,
|
||||
jlib:iq_to_xml(Res));
|
||||
#iq{type = get, xmlns = ?NS_MUCSUB,
|
||||
sub_el = #xmlel{name = <<"subscriptions">>} = SubEl} = IQ ->
|
||||
RoomJIDs = get_subscribed_rooms(ServerHost, Host, From),
|
||||
Subs = lists:map(
|
||||
fun(J) ->
|
||||
#xmlel{name = <<"subscription">>,
|
||||
attrs = [{<<"jid">>,
|
||||
jid:to_string(J)}]}
|
||||
end, RoomJIDs),
|
||||
Res = IQ#iq{type = result,
|
||||
sub_el = [SubEl#xmlel{children = Subs}]},
|
||||
ejabberd_router:route(To, From, jlib:iq_to_xml(Res));
|
||||
#iq{type = get, xmlns = ?NS_MUC_UNIQUE} = IQ ->
|
||||
Res = IQ#iq{type = result,
|
||||
sub_el =
|
||||
[#xmlel{name = <<"unique">>,
|
||||
attrs =
|
||||
[{<<"xmlns">>,
|
||||
?NS_MUC_UNIQUE}],
|
||||
children =
|
||||
[iq_get_unique(From)]}]},
|
||||
ejabberd_router:route(To, From,
|
||||
jlib:iq_to_xml(Res));
|
||||
#iq{} ->
|
||||
Err = jlib:make_error_reply(Packet,
|
||||
?ERR_FEATURE_NOT_IMPLEMENTED),
|
||||
ejabberd_router:route(To, From, Err);
|
||||
_ -> ok
|
||||
end;
|
||||
<<"message">> ->
|
||||
case fxml:get_attr_s(<<"type">>, Attrs) of
|
||||
<<"error">> -> ok;
|
||||
_ ->
|
||||
case acl:match_rule(ServerHost, AccessAdmin, From)
|
||||
of
|
||||
allow ->
|
||||
Msg = fxml:get_path_s(Packet,
|
||||
[{elem, <<"body">>},
|
||||
cdata]),
|
||||
broadcast_service_message(Host, Msg);
|
||||
_ ->
|
||||
Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
|
||||
ErrText =
|
||||
<<"Only service administrators are allowed "
|
||||
"to send service messages">>,
|
||||
Err = jlib:make_error_reply(Packet,
|
||||
?ERRT_FORBIDDEN(Lang,
|
||||
ErrText)),
|
||||
ejabberd_router:route(To, From, Err)
|
||||
end
|
||||
end;
|
||||
<<"presence">> -> ok
|
||||
end;
|
||||
_ ->
|
||||
case fxml:get_attr_s(<<"type">>, Attrs) of
|
||||
<<"error">> -> ok;
|
||||
<<"result">> -> ok;
|
||||
_ ->
|
||||
Err = jlib:make_error_reply(Packet,
|
||||
?ERR_ITEM_NOT_FOUND),
|
||||
ejabberd_router:route(To, From, Err)
|
||||
end
|
||||
end;
|
||||
_ ->
|
||||
case mnesia:dirty_read(muc_online_room, {Room, Host}) of
|
||||
[] ->
|
||||
Type = fxml:get_attr_s(<<"type">>, Attrs),
|
||||
case {Name, Type} of
|
||||
{<<"presence">>, <<"">>} ->
|
||||
case check_user_can_create_room(ServerHost,
|
||||
AccessCreate, From, Room) and
|
||||
check_create_roomid(ServerHost, Room) of
|
||||
true ->
|
||||
{ok, Pid} = start_new_room(Host, ServerHost, Access,
|
||||
Room, HistorySize,
|
||||
RoomShaper, From, Nick, DefRoomOpts),
|
||||
register_room(Host, Room, Pid),
|
||||
mod_muc_room:route(Pid, From, Nick, Packet),
|
||||
ok;
|
||||
false ->
|
||||
Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
|
||||
ErrText = <<"Room creation is denied by service policy">>,
|
||||
Err = jlib:make_error_reply(
|
||||
Packet, ?ERRT_FORBIDDEN(Lang, ErrText)),
|
||||
ejabberd_router:route(To, From, Err)
|
||||
end;
|
||||
_ ->
|
||||
Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
|
||||
ErrText = <<"Conference room does not exist">>,
|
||||
Err = jlib:make_error_reply(Packet,
|
||||
?ERRT_ITEM_NOT_FOUND(Lang, ErrText)),
|
||||
case mnesia:dirty_read(muc_online_room, {Room, Host}) of
|
||||
[] ->
|
||||
case Packet of
|
||||
#presence{type = available, lang = Lang} ->
|
||||
case check_user_can_create_room(
|
||||
ServerHost, AccessCreate, From, Room) and
|
||||
check_create_roomid(ServerHost, Room) of
|
||||
true ->
|
||||
{ok, Pid} = start_new_room(
|
||||
Host, ServerHost, Access,
|
||||
Room, HistorySize,
|
||||
RoomShaper, From, Nick, DefRoomOpts),
|
||||
register_room(Host, Room, Pid),
|
||||
mod_muc_room:route(Pid, From, Nick, Packet),
|
||||
ok;
|
||||
false ->
|
||||
ErrText = <<"Room creation is denied by service policy">>,
|
||||
Err = xmpp:make_error(
|
||||
Packet, xmpp:err_forbidden(ErrText, Lang)),
|
||||
ejabberd_router:route(To, From, Err)
|
||||
end;
|
||||
[R] ->
|
||||
Pid = R#muc_online_room.pid,
|
||||
?DEBUG("MUC: send to process ~p~n", [Pid]),
|
||||
mod_muc_room:route(Pid, From, Nick, Packet),
|
||||
ok
|
||||
end
|
||||
_ ->
|
||||
Lang = xmpp:get_lang(Packet),
|
||||
ErrText = <<"Conference room does not exist">>,
|
||||
Err = xmpp:err_item_not_found(ErrText, Lang),
|
||||
ejabberd_router:route_error(To, From, Packet, Err)
|
||||
end;
|
||||
[R] ->
|
||||
Pid = R#muc_online_room.pid,
|
||||
?DEBUG("MUC: send to process ~p~n", [Pid]),
|
||||
mod_muc_room:route(Pid, From, Nick, Packet),
|
||||
ok
|
||||
end.
|
||||
|
||||
-spec process_vcard(iq()) -> iq().
|
||||
process_vcard(#iq{type = get, lang = Lang} = IQ) ->
|
||||
Desc = translate:translate(Lang, <<"ejabberd MUC module">>),
|
||||
Copyright = <<"Copyright (c) 2003-2016 ProcessOne">>,
|
||||
xmpp:make_iq_result(
|
||||
IQ, #vcard_temp{fn = <<"ejabberd/mod_muc">>,
|
||||
url = ?EJABBERD_URI,
|
||||
desc = <<Desc/binary, $\n, Copyright/binary>>});
|
||||
process_vcard(#iq{type = set, lang = Lang} = IQ) ->
|
||||
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
|
||||
xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)).
|
||||
|
||||
-spec process_register(iq()) -> iq().
|
||||
process_register(#iq{type = get, from = From, to = To, lang = Lang} = IQ) ->
|
||||
Host = To#jid.lserver,
|
||||
ServerHost = ejabberd_router:host_of_route(Host),
|
||||
xmpp:make_iq_result(IQ, iq_get_register_info(ServerHost, Host, From, Lang));
|
||||
process_register(#iq{type = set, from = From, to = To,
|
||||
lang = Lang, sub_els = [El]} = IQ) ->
|
||||
Host = To#jid.lserver,
|
||||
ServerHost = ejabberd_router:host_of_route(Host),
|
||||
case process_iq_register_set(ServerHost, Host, From, El, Lang) of
|
||||
{result, Result} ->
|
||||
xmpp:make_iq_result(IQ, Result);
|
||||
{error, Err} ->
|
||||
xmpp:make_error(IQ, Err)
|
||||
end.
|
||||
|
||||
-spec process_disco_info(iq()) -> iq().
|
||||
process_disco_info(#iq{type = set, lang = Lang} = IQ) ->
|
||||
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
|
||||
xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
|
||||
process_disco_info(#iq{type = get, to = To, lang = Lang,
|
||||
sub_els = [#disco_info{node = undefined}]} = IQ) ->
|
||||
ServerHost = ejabberd_router:host_of_route(To#jid.lserver),
|
||||
X = ejabberd_hooks:run_fold(disco_info, ServerHost, [],
|
||||
[ServerHost, ?MODULE, undefined, Lang]),
|
||||
MAMFeatures = case gen_mod:is_loaded(ServerHost, mod_mam) of
|
||||
true -> [?NS_MAM_TMP, ?NS_MAM_0, ?NS_MAM_1];
|
||||
false -> []
|
||||
end,
|
||||
Features = [?NS_DISCO_INFO, ?NS_DISCO_ITEMS,
|
||||
?NS_REGISTER, ?NS_MUC, ?NS_RSM,
|
||||
?NS_VCARD, ?NS_MUCSUB, ?NS_MUC_UNIQUE | MAMFeatures],
|
||||
Identity = #identity{category = <<"conference">>,
|
||||
type = <<"text">>,
|
||||
name = translate:translate(Lang, <<"Chatrooms">>)},
|
||||
xmpp:make_iq_result(
|
||||
IQ, #disco_info{features = Features,
|
||||
identities = [Identity],
|
||||
xdata = X});
|
||||
process_disco_info(#iq{type = get, lang = Lang} = IQ) ->
|
||||
xmpp:make_error(IQ, xmpp:err_item_not_found(<<"No info available">>, Lang)).
|
||||
|
||||
-spec process_disco_items(iq()) -> iq().
|
||||
process_disco_items(#iq{type = set, lang = Lang} = IQ) ->
|
||||
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
|
||||
xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
|
||||
process_disco_items(#iq{type = get, from = From, to = To, lang = Lang,
|
||||
sub_els = [#disco_items{node = Node, rsm = RSM}]} = IQ) ->
|
||||
Host = To#jid.lserver,
|
||||
xmpp:make_iq_result(
|
||||
IQ, #disco_items{node = Node,
|
||||
items = iq_disco_items(Host, From, Lang, Node, RSM)}).
|
||||
|
||||
-spec process_muc_unique(iq()) -> iq().
|
||||
process_muc_unique(#iq{type = set, lang = Lang} = IQ) ->
|
||||
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
|
||||
xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
|
||||
process_muc_unique(#iq{from = From, type = get} = IQ) ->
|
||||
Name = p1_sha:sha(term_to_binary([From, p1_time_compat:timestamp(),
|
||||
randoms:get_string()])),
|
||||
xmpp:make_iq_result(IQ, #muc_unique{name = Name}).
|
||||
|
||||
-spec process_mucsub(iq()) -> iq().
|
||||
process_mucsub(#iq{type = set, lang = Lang} = IQ) ->
|
||||
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
|
||||
xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
|
||||
process_mucsub(#iq{type = get, from = From, to = To} = IQ) ->
|
||||
Host = To#jid.lserver,
|
||||
ServerHost = ejabberd_router:host_of_route(Host),
|
||||
RoomJIDs = get_subscribed_rooms(ServerHost, Host, From),
|
||||
xmpp:make_iq_result(IQ, #muc_subscriptions{list = RoomJIDs}).
|
||||
|
||||
check_user_can_create_room(ServerHost, AccessCreate,
|
||||
From, _RoomID) ->
|
||||
case acl:match_rule(ServerHost, AccessCreate, From) of
|
||||
@ -583,61 +562,21 @@ register_room(Host, Room, Pid) ->
|
||||
end,
|
||||
mnesia:transaction(F).
|
||||
|
||||
|
||||
iq_disco_info(ServerHost, Lang) ->
|
||||
[#xmlel{name = <<"identity">>,
|
||||
attrs =
|
||||
[{<<"category">>, <<"conference">>},
|
||||
{<<"type">>, <<"text">>},
|
||||
{<<"name">>,
|
||||
translate:translate(Lang, <<"Chatrooms">>)}],
|
||||
children = []},
|
||||
#xmlel{name = <<"feature">>,
|
||||
attrs = [{<<"var">>, ?NS_DISCO_INFO}], children = []},
|
||||
#xmlel{name = <<"feature">>,
|
||||
attrs = [{<<"var">>, ?NS_DISCO_ITEMS}], children = []},
|
||||
#xmlel{name = <<"feature">>,
|
||||
attrs = [{<<"var">>, ?NS_MUC}], children = []},
|
||||
#xmlel{name = <<"feature">>,
|
||||
attrs = [{<<"var">>, ?NS_MUC_UNIQUE}], children = []},
|
||||
#xmlel{name = <<"feature">>,
|
||||
attrs = [{<<"var">>, ?NS_REGISTER}], children = []},
|
||||
#xmlel{name = <<"feature">>,
|
||||
attrs = [{<<"var">>, ?NS_RSM}], children = []},
|
||||
#xmlel{name = <<"feature">>,
|
||||
attrs = [{<<"var">>, ?NS_MUCSUB}], children = []},
|
||||
#xmlel{name = <<"feature">>,
|
||||
attrs = [{<<"var">>, ?NS_VCARD}], children = []}] ++
|
||||
case gen_mod:is_loaded(ServerHost, mod_mam) of
|
||||
true ->
|
||||
[#xmlel{name = <<"feature">>,
|
||||
attrs = [{<<"var">>, ?NS_MAM_TMP}]},
|
||||
#xmlel{name = <<"feature">>,
|
||||
attrs = [{<<"var">>, ?NS_MAM_0}]},
|
||||
#xmlel{name = <<"feature">>,
|
||||
attrs = [{<<"var">>, ?NS_MAM_1}]}];
|
||||
false ->
|
||||
[]
|
||||
end.
|
||||
|
||||
iq_disco_items(Host, From, Lang, <<>>, none) ->
|
||||
iq_disco_items(Host, From, Lang, undefined, undefined) ->
|
||||
Rooms = get_vh_rooms(Host),
|
||||
case erlang:length(Rooms) < ?MAX_ROOMS_DISCOITEMS of
|
||||
true ->
|
||||
iq_disco_items_list(Host, Rooms, {get_disco_item, all, From, Lang});
|
||||
false ->
|
||||
iq_disco_items(Host, From, Lang, <<"nonemptyrooms">>, none)
|
||||
iq_disco_items(Host, From, Lang, <<"nonemptyrooms">>, undefined)
|
||||
end;
|
||||
iq_disco_items(Host, From, Lang, <<"nonemptyrooms">>, none) ->
|
||||
XmlEmpty = #xmlel{name = <<"item">>,
|
||||
attrs =
|
||||
[{<<"jid">>, <<"conference.localhost">>},
|
||||
{<<"node">>, <<"emptyrooms">>},
|
||||
{<<"name">>, translate:translate(Lang, <<"Empty Rooms">>)}],
|
||||
children = []},
|
||||
iq_disco_items(Host, From, Lang, <<"nonemptyrooms">>, undefined) ->
|
||||
Empty = #disco_item{jid = jid:make(<<"conference.localhost">>),
|
||||
node = <<"emptyrooms">>,
|
||||
name = translate:translate(Lang, <<"Empty Rooms">>)},
|
||||
Query = {get_disco_item, only_non_empty, From, Lang},
|
||||
[XmlEmpty | iq_disco_items_list(Host, get_vh_rooms(Host), Query)];
|
||||
iq_disco_items(Host, From, Lang, <<"emptyrooms">>, none) ->
|
||||
[Empty | iq_disco_items_list(Host, get_vh_rooms(Host), Query)];
|
||||
iq_disco_items(Host, From, Lang, <<"emptyrooms">>, undefined) ->
|
||||
iq_disco_items_list(Host, get_vh_rooms(Host), {get_disco_item, 0, From, Lang});
|
||||
iq_disco_items(Host, From, Lang, _DiscoNode, Rsm) ->
|
||||
{Rooms, RsmO} = get_vh_rooms(Host, Rsm),
|
||||
@ -645,62 +584,55 @@ iq_disco_items(Host, From, Lang, _DiscoNode, Rsm) ->
|
||||
iq_disco_items_list(Host, Rooms, {get_disco_item, all, From, Lang}) ++ RsmOut.
|
||||
|
||||
iq_disco_items_list(Host, Rooms, Query) ->
|
||||
lists:zf(fun (#muc_online_room{name_host =
|
||||
{Name, _Host},
|
||||
pid = Pid}) ->
|
||||
case catch gen_fsm:sync_send_all_state_event(Pid,
|
||||
Query,
|
||||
100)
|
||||
of
|
||||
{item, Desc} ->
|
||||
flush(),
|
||||
{true,
|
||||
#xmlel{name = <<"item">>,
|
||||
attrs =
|
||||
[{<<"jid">>,
|
||||
jid:to_string({Name, Host,
|
||||
<<"">>})},
|
||||
{<<"name">>, Desc}],
|
||||
children = []}};
|
||||
_ -> false
|
||||
end
|
||||
end, Rooms).
|
||||
lists:zf(
|
||||
fun(#muc_online_room{name_host = {Name, _Host}, pid = Pid}) ->
|
||||
case catch gen_fsm:sync_send_all_state_event(Pid, Query, 100) of
|
||||
{item, Desc} ->
|
||||
flush(),
|
||||
{true, #disco_item{jid = jid:make(Name, Host),
|
||||
name = Desc}};
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
end, Rooms).
|
||||
|
||||
get_vh_rooms(Host, #rsm_in{max=M, direction=Direction, id=I, index=Index})->
|
||||
AllRooms = lists:sort(get_vh_rooms(Host)),
|
||||
Count = erlang:length(AllRooms),
|
||||
Guard = case Direction of
|
||||
_ when Index =/= undefined -> [{'==', {element, 2, '$1'}, Host}];
|
||||
aft -> [{'==', {element, 2, '$1'}, Host}, {'>=',{element, 1, '$1'} ,I}];
|
||||
before when I =/= []-> [{'==', {element, 2, '$1'}, Host}, {'=<',{element, 1, '$1'} ,I}];
|
||||
_ -> [{'==', {element, 2, '$1'}, Host}]
|
||||
end,
|
||||
L = lists:sort(
|
||||
mnesia:dirty_select(muc_online_room,
|
||||
[{#muc_online_room{name_host = '$1', _ = '_'},
|
||||
Guard,
|
||||
['$_']}])),
|
||||
L2 = if
|
||||
Index == undefined andalso Direction == before ->
|
||||
lists:reverse(lists:sublist(lists:reverse(L), 1, M));
|
||||
Index == undefined ->
|
||||
lists:sublist(L, 1, M);
|
||||
Index > Count orelse Index < 0 ->
|
||||
[];
|
||||
true ->
|
||||
lists:sublist(L, Index+1, M)
|
||||
end,
|
||||
if L2 == [] -> {L2, #rsm_out{count = Count}};
|
||||
true ->
|
||||
H = hd(L2),
|
||||
NewIndex = get_room_pos(H, AllRooms),
|
||||
T = lists:last(L2),
|
||||
{F, _} = H#muc_online_room.name_host,
|
||||
{Last, _} = T#muc_online_room.name_host,
|
||||
{L2,
|
||||
#rsm_out{first = F, last = Last, count = Count,
|
||||
index = NewIndex}}
|
||||
end.
|
||||
get_vh_rooms(_, _) ->
|
||||
todo.
|
||||
%% get_vh_rooms(Host, #rsm_in{max=M, direction=Direction, id=I, index=Index})->
|
||||
%% AllRooms = lists:sort(get_vh_rooms(Host)),
|
||||
%% Count = erlang:length(AllRooms),
|
||||
%% Guard = case Direction of
|
||||
%% _ when Index =/= undefined -> [{'==', {element, 2, '$1'}, Host}];
|
||||
%% aft -> [{'==', {element, 2, '$1'}, Host}, {'>=',{element, 1, '$1'} ,I}];
|
||||
%% before when I =/= []-> [{'==', {element, 2, '$1'}, Host}, {'=<',{element, 1, '$1'} ,I}];
|
||||
%% _ -> [{'==', {element, 2, '$1'}, Host}]
|
||||
%% end,
|
||||
%% L = lists:sort(
|
||||
%% mnesia:dirty_select(muc_online_room,
|
||||
%% [{#muc_online_room{name_host = '$1', _ = '_'},
|
||||
%% Guard,
|
||||
%% ['$_']}])),
|
||||
%% L2 = if
|
||||
%% Index == undefined andalso Direction == before ->
|
||||
%% lists:reverse(lists:sublist(lists:reverse(L), 1, M));
|
||||
%% Index == undefined ->
|
||||
%% lists:sublist(L, 1, M);
|
||||
%% Index > Count orelse Index < 0 ->
|
||||
%% [];
|
||||
%% true ->
|
||||
%% lists:sublist(L, Index+1, M)
|
||||
%% end,
|
||||
%% if L2 == [] -> {L2, #rsm_out{count = Count}};
|
||||
%% true ->
|
||||
%% H = hd(L2),
|
||||
%% NewIndex = get_room_pos(H, AllRooms),
|
||||
%% T = lists:last(L2),
|
||||
%% {F, _} = H#muc_online_room.name_host,
|
||||
%% {Last, _} = T#muc_online_room.name_host,
|
||||
%% {L2,
|
||||
%% #rsm_out{first = F, last = Last, count = Count,
|
||||
%% index = NewIndex}}
|
||||
%% end.
|
||||
|
||||
get_subscribed_rooms(ServerHost, Host, From) ->
|
||||
Rooms = get_rooms(ServerHost, Host),
|
||||
@ -730,60 +662,32 @@ get_room_pos(Desired, [_ | Rooms], HeadPosition) ->
|
||||
|
||||
flush() -> receive _ -> flush() after 0 -> ok end.
|
||||
|
||||
-define(XFIELD(Type, Label, Var, Val),
|
||||
#xmlel{name = <<"field">>,
|
||||
attrs =
|
||||
[{<<"type">>, Type},
|
||||
{<<"label">>, translate:translate(Lang, Label)},
|
||||
{<<"var">>, Var}],
|
||||
children =
|
||||
[#xmlel{name = <<"value">>, attrs = [],
|
||||
children = [{xmlcdata, Val}]}]}).
|
||||
|
||||
iq_get_unique(From) ->
|
||||
{xmlcdata,
|
||||
p1_sha:sha(term_to_binary([From, p1_time_compat:timestamp(),
|
||||
randoms:get_string()]))}.
|
||||
|
||||
get_nick(ServerHost, Host, From) ->
|
||||
LServer = jid:nameprep(ServerHost),
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
Mod:get_nick(LServer, Host, From).
|
||||
|
||||
iq_get_register_info(ServerHost, Host, From, Lang) ->
|
||||
{Nick, Registered} = case get_nick(ServerHost, Host,
|
||||
From)
|
||||
of
|
||||
error -> {<<"">>, []};
|
||||
N ->
|
||||
{N,
|
||||
[#xmlel{name = <<"registered">>, attrs = [],
|
||||
children = []}]}
|
||||
{Nick, Registered} = case get_nick(ServerHost, Host, From) of
|
||||
error -> {<<"">>, false};
|
||||
N -> {N, true}
|
||||
end,
|
||||
Registered ++
|
||||
[#xmlel{name = <<"instructions">>, attrs = [],
|
||||
children =
|
||||
[{xmlcdata,
|
||||
translate:translate(Lang,
|
||||
<<"You need a client that supports x:data "
|
||||
"to register the nickname">>)}]},
|
||||
#xmlel{name = <<"x">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_XDATA},
|
||||
{<<"type">>, <<"form">>}],
|
||||
children =
|
||||
[#xmlel{name = <<"title">>, attrs = [],
|
||||
children =
|
||||
[{xmlcdata,
|
||||
<<(translate:translate(Lang,
|
||||
<<"Nickname Registration at ">>))/binary,
|
||||
Host/binary>>}]},
|
||||
#xmlel{name = <<"instructions">>, attrs = [],
|
||||
children =
|
||||
[{xmlcdata,
|
||||
translate:translate(Lang,
|
||||
<<"Enter nickname you want to register">>)}]},
|
||||
?XFIELD(<<"text-single">>, <<"Nickname">>, <<"nick">>,
|
||||
Nick)]}].
|
||||
Title = <<(translate:translate(
|
||||
Lang, <<"Nickname Registration at ">>))/binary, Host/binary>>,
|
||||
Inst = translate:translate(Lang, <<"Enter nickname you want to register">>),
|
||||
Field = #xdata_field{type = 'text-single',
|
||||
label = translate:translate(Lang, <<"Nickname">>),
|
||||
var = <<"nick">>,
|
||||
values = [Nick]},
|
||||
X = #xdata{type = form, title = Title,
|
||||
instructions = [Inst], fields = [Field]},
|
||||
#register{nick = Nick,
|
||||
registered = Registered,
|
||||
instructions =
|
||||
translate:translate(
|
||||
Lang, <<"You need a client that supports x:data "
|
||||
"to register the nickname">>),
|
||||
xdata = X}.
|
||||
|
||||
set_nick(ServerHost, Host, From, Nick) ->
|
||||
LServer = jid:nameprep(ServerHost),
|
||||
@ -793,66 +697,43 @@ set_nick(ServerHost, Host, From, Nick) ->
|
||||
iq_set_register_info(ServerHost, Host, From, Nick,
|
||||
Lang) ->
|
||||
case set_nick(ServerHost, Host, From, Nick) of
|
||||
{atomic, ok} -> {result, []};
|
||||
{atomic, ok} -> {result, undefined};
|
||||
{atomic, false} ->
|
||||
ErrText = <<"That nickname is registered by another "
|
||||
"person">>,
|
||||
{error, ?ERRT_CONFLICT(Lang, ErrText)};
|
||||
{error, xmpp:err_conflict(ErrText, Lang)};
|
||||
_ ->
|
||||
Txt = <<"Database failure">>,
|
||||
{error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt)}
|
||||
{error, xmpp:err_internal_server_error(Txt, Lang)}
|
||||
end.
|
||||
|
||||
process_iq_register_set(ServerHost, Host, From, SubEl,
|
||||
Lang) ->
|
||||
#xmlel{children = Els} = SubEl,
|
||||
case fxml:get_subtag(SubEl, <<"remove">>) of
|
||||
false ->
|
||||
case fxml:remove_cdata(Els) of
|
||||
[#xmlel{name = <<"x">>} = XEl] ->
|
||||
case {fxml:get_tag_attr_s(<<"xmlns">>, XEl),
|
||||
fxml:get_tag_attr_s(<<"type">>, XEl)}
|
||||
of
|
||||
{?NS_XDATA, <<"cancel">>} -> {result, []};
|
||||
{?NS_XDATA, <<"submit">>} ->
|
||||
XData = jlib:parse_xdata_submit(XEl),
|
||||
case XData of
|
||||
invalid ->
|
||||
Txt = <<"Incorrect data form">>,
|
||||
{error, ?ERRT_BAD_REQUEST(Lang, Txt)};
|
||||
_ ->
|
||||
case lists:keysearch(<<"nick">>, 1, XData) of
|
||||
{value, {_, [Nick]}} when Nick /= <<"">> ->
|
||||
iq_set_register_info(ServerHost, Host, From,
|
||||
Nick, Lang);
|
||||
_ ->
|
||||
ErrText =
|
||||
<<"You must fill in field \"Nickname\" "
|
||||
"in the form">>,
|
||||
{error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)}
|
||||
end
|
||||
end;
|
||||
_ -> {error, ?ERR_BAD_REQUEST}
|
||||
end;
|
||||
_ -> {error, ?ERR_BAD_REQUEST}
|
||||
end;
|
||||
_ ->
|
||||
iq_set_register_info(ServerHost, Host, From, <<"">>,
|
||||
Lang)
|
||||
process_iq_register_set(ServerHost, Host, From,
|
||||
#register{remove = true}, Lang) ->
|
||||
iq_set_register_info(ServerHost, Host, From, <<"">>, Lang);
|
||||
process_iq_register_set(_ServerHost, _Host, _From,
|
||||
#register{xdata = #xdata{type = cancel}}, _Lang) ->
|
||||
{result, undefined};
|
||||
process_iq_register_set(ServerHost, Host, From,
|
||||
#register{nick = Nick, xdata = XData}, Lang) ->
|
||||
case XData of
|
||||
#xdata{type = submit, fields = Fs} ->
|
||||
case lists:keyfind(<<"nick">>, #xdata_field.var, Fs) of
|
||||
#xdata_field{values = [N]} ->
|
||||
iq_set_register_info(ServerHost, Host, From, N, Lang);
|
||||
_ ->
|
||||
ErrText = <<"You must fill in field \"Nickname\" in the form">>,
|
||||
{error, xmpp:err_not_acceptable(ErrText, Lang)}
|
||||
end;
|
||||
#xdata{} ->
|
||||
Txt = <<"Incorrect data form">>,
|
||||
{error, xmpp:err_bad_request(Txt, Lang)};
|
||||
_ when is_binary(Nick), Nick /= <<"">> ->
|
||||
iq_set_register_info(ServerHost, Host, From, Nick, Lang);
|
||||
_ ->
|
||||
ErrText = <<"You must fill in field \"Nickname\" in the form">>,
|
||||
{error, xmpp:err_not_acceptable(ErrText, Lang)}
|
||||
end.
|
||||
|
||||
iq_get_vcard(Lang) ->
|
||||
[#xmlel{name = <<"FN">>, attrs = [],
|
||||
children = [{xmlcdata, <<"ejabberd/mod_muc">>}]},
|
||||
#xmlel{name = <<"URL">>, attrs = [],
|
||||
children = [{xmlcdata, ?EJABBERD_URI}]},
|
||||
#xmlel{name = <<"DESC">>, attrs = [],
|
||||
children =
|
||||
[{xmlcdata,
|
||||
<<(translate:translate(Lang,
|
||||
<<"ejabberd MUC module">>))/binary,
|
||||
"\nCopyright (c) 2003-2016 ProcessOne">>}]}].
|
||||
|
||||
broadcast_service_message(Host, Msg) ->
|
||||
lists:foreach(
|
||||
fun(#muc_online_room{pid = Pid}) ->
|
||||
|
@ -24,7 +24,7 @@
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("jlib.hrl").
|
||||
-include("xmpp.hrl").
|
||||
-include("mod_muc_room.hrl").
|
||||
-include("mod_muc.hrl").
|
||||
-include("ejabberd_http.hrl").
|
||||
@ -241,7 +241,7 @@ web_menu_host(Acc, _Host, Lang) ->
|
||||
|
||||
-define(TDTD(L, N),
|
||||
?XE(<<"tr">>, [?XCT(<<"td">>, L),
|
||||
?XC(<<"td">>, jlib:integer_to_binary(N))
|
||||
?XC(<<"td">>, integer_to_binary(N))
|
||||
])).
|
||||
|
||||
web_page_main(_, #request{path=[<<"muc">>], lang = Lang} = _Request) ->
|
||||
@ -283,7 +283,7 @@ get_sort_query(Q) ->
|
||||
|
||||
get_sort_query2(Q) ->
|
||||
{value, {_, String}} = lists:keysearch(<<"sort">>, 1, Q),
|
||||
Integer = jlib:binary_to_integer(String),
|
||||
Integer = binary_to_integer(String),
|
||||
case Integer >= 0 of
|
||||
true -> {ok, {normal, Integer}};
|
||||
false -> {ok, {reverse, abs(Integer)}}
|
||||
@ -309,7 +309,7 @@ make_rooms_page(Host, Lang, {Sort_direction, Sort_column}) ->
|
||||
{Titles_TR, _} =
|
||||
lists:mapfoldl(
|
||||
fun(Title, Num_column) ->
|
||||
NCS = jlib:integer_to_binary(Num_column),
|
||||
NCS = integer_to_binary(Num_column),
|
||||
TD = ?XE(<<"td">>, [?CT(Title),
|
||||
?C(<<" ">>),
|
||||
?AC(<<"?sort=", NCS/binary>>, <<"<">>),
|
||||
@ -383,7 +383,7 @@ prepare_room_info(Room_info) ->
|
||||
Just_created,
|
||||
Title} = Room_info,
|
||||
[NameHost,
|
||||
jlib:integer_to_binary(Num_participants),
|
||||
integer_to_binary(Num_participants),
|
||||
Ts_last_message,
|
||||
jlib:atom_to_binary(Public),
|
||||
jlib:atom_to_binary(Persistent),
|
||||
@ -830,7 +830,7 @@ get_options(Config) ->
|
||||
Fields = [jlib:atom_to_binary(Field) || Field <- record_info(fields, config)],
|
||||
[config | ValuesRaw] = tuple_to_list(Config),
|
||||
Values = lists:map(fun(V) when is_atom(V) -> jlib:atom_to_binary(V);
|
||||
(V) when is_integer(V) -> jlib:integer_to_binary(V);
|
||||
(V) when is_integer(V) -> integer_to_binary(V);
|
||||
(V) when is_tuple(V); is_list(V) -> list_to_binary(hd(io_lib:format("~w", [V])));
|
||||
(V) -> V end, ValuesRaw),
|
||||
lists:zip(Fields, Values).
|
||||
|
@ -46,7 +46,7 @@
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
|
||||
-include("jlib.hrl").
|
||||
-include("xmpp.hrl").
|
||||
-include("mod_muc.hrl").
|
||||
-include("mod_muc_room.hrl").
|
||||
|
||||
@ -196,15 +196,13 @@ code_change(_OldVsn, State, _Extra) -> {ok, State}.
|
||||
add_to_log2(text, {Nick, Packet}, Room, Opts, State) ->
|
||||
case has_no_permanent_store_hint(Packet) of
|
||||
false ->
|
||||
case {fxml:get_subtag(Packet, <<"subject">>),
|
||||
fxml:get_subtag(Packet, <<"body">>)}
|
||||
of
|
||||
{false, false} -> ok;
|
||||
{false, SubEl} ->
|
||||
Message = {body, fxml:get_tag_cdata(SubEl)},
|
||||
case {Packet#message.subject, Packet#message.body} of
|
||||
{[], []} -> ok;
|
||||
{[], Body} ->
|
||||
Message = {body, xmpp:get_text(Body)},
|
||||
add_message_to_log(Nick, Message, Room, Opts, State);
|
||||
{SubEl, _} ->
|
||||
Message = {subject, fxml:get_tag_cdata(SubEl)},
|
||||
{Subj, _} ->
|
||||
Message = {subject, xmpp:get_text(Subj)},
|
||||
add_message_to_log(Nick, Message, Room, Opts, State)
|
||||
end;
|
||||
true -> ok
|
||||
@ -1035,7 +1033,7 @@ roomconfig_to_string(Options, Lang, FileFormat) ->
|
||||
max_users ->
|
||||
<<"<div class=\"rcot\">",
|
||||
OptText/binary, ": \"",
|
||||
(htmlize(jlib:integer_to_binary(T),
|
||||
(htmlize(integer_to_binary(T),
|
||||
FileFormat))/binary,
|
||||
"\"</div>">>;
|
||||
title ->
|
||||
@ -1053,7 +1051,7 @@ roomconfig_to_string(Options, Lang, FileFormat) ->
|
||||
allow_private_messages_from_visitors ->
|
||||
<<"<div class=\"rcot\">",
|
||||
OptText/binary, ": \"",
|
||||
(htmlize(?T((jlib:atom_to_binary(T))),
|
||||
(htmlize(?T(jlib:atom_to_binary(T)),
|
||||
FileFormat))/binary,
|
||||
"\"</div>">>;
|
||||
_ -> <<"\"", T/binary, "\"">>
|
||||
@ -1168,7 +1166,7 @@ get_room_occupants(RoomJIDString) ->
|
||||
[{U#user.jid, U#user.nick, U#user.role}
|
||||
|| {_, U} <- (?DICT):to_list(StateData#state.users)].
|
||||
|
||||
-spec get_room_state(binary(), binary()) -> muc_room_state().
|
||||
-spec get_room_state(binary(), binary()) -> mod_muc_room:state().
|
||||
|
||||
get_room_state(RoomName, MucService) ->
|
||||
case mnesia:dirty_read(muc_online_room,
|
||||
@ -1180,7 +1178,7 @@ get_room_state(RoomName, MucService) ->
|
||||
[] -> #state{}
|
||||
end.
|
||||
|
||||
-spec get_room_state(pid()) -> muc_room_state().
|
||||
-spec get_room_state(pid()) -> mod_muc_room:state().
|
||||
|
||||
get_room_state(RoomPid) ->
|
||||
{ok, R} = gen_fsm:sync_send_all_state_event(RoomPid,
|
||||
@ -1204,14 +1202,10 @@ fjoin(FileList) ->
|
||||
list_to_binary(filename:join([binary_to_list(File) || File <- FileList])).
|
||||
|
||||
has_no_permanent_store_hint(Packet) ->
|
||||
fxml:get_subtag_with_xmlns(Packet, <<"no-store">>, ?NS_HINTS)
|
||||
=/= false orelse
|
||||
fxml:get_subtag_with_xmlns(Packet, <<"no-storage">>, ?NS_HINTS)
|
||||
=/= false orelse
|
||||
fxml:get_subtag_with_xmlns(Packet, <<"no-permanent-store">>, ?NS_HINTS)
|
||||
=/= false orelse
|
||||
fxml:get_subtag_with_xmlns(Packet, <<"no-permanent-storage">>, ?NS_HINTS)
|
||||
=/= false.
|
||||
xmpp:has_subtag(Packet, #hint{type = 'no-store'}) orelse
|
||||
xmpp:has_subtag(Packet, #hint{type = 'no-storage'}) orelse
|
||||
xmpp:has_subtag(Packet, #hint{type = 'no-permanent-store'}) orelse
|
||||
xmpp:has_subtag(Packet, #hint{type = 'no-permanent-storage'}).
|
||||
|
||||
mod_opt_type(access_log) ->
|
||||
fun (A) when is_atom(A) -> A end;
|
||||
|
4453
src/mod_muc_room.erl
4453
src/mod_muc_room.erl
File diff suppressed because it is too large
Load Diff
@ -72,7 +72,7 @@ process_sm_iq(#iq{type = Type, lang = Lang,
|
||||
case filter_xmlels(Els0) of
|
||||
[] ->
|
||||
Txt = <<"No private data found in this query">>,
|
||||
xmpp:make_error(IQ, xmpp:err_bad_format(Txt, Lang));
|
||||
xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang));
|
||||
Data when Type == set ->
|
||||
set_data(LUser, LServer, Data),
|
||||
xmpp:make_iq_result(IQ);
|
||||
|
240
src/xmpp.erl
240
src/xmpp.erl
@ -16,22 +16,33 @@
|
||||
set_type/2, set_to/2, set_from/2, set_id/2,
|
||||
set_lang/2, set_error/2, set_els/2, set_from_to/3,
|
||||
format_error/1, is_stanza/1, set_subtag/2, get_subtag/2,
|
||||
remove_subtag/2, has_subtag/2, decode_els/1, pp/1,
|
||||
get_name/1, get_text/1, mk_text/1, mk_text/2]).
|
||||
remove_subtag/2, has_subtag/2, decode_els/1, decode_els/2,
|
||||
pp/1, get_name/1, get_text/1, mk_text/1, mk_text/2]).
|
||||
|
||||
%% XMPP errors
|
||||
-export([err_bad_request/0, err_bad_request/2,
|
||||
err_bad_format/0, err_bad_format/2,
|
||||
err_not_allowed/0, err_not_allowed/2,
|
||||
err_conflict/0, err_conflict/2,
|
||||
err_forbidden/0, err_forbidden/2,
|
||||
err_not_acceptable/0, err_not_acceptable/2,
|
||||
err_internal_server_error/0, err_internal_server_error/2,
|
||||
err_service_unavailable/0, err_service_unavailable/2,
|
||||
err_item_not_found/0, err_item_not_found/2,
|
||||
err_jid_malformed/0, err_jid_malformed/2,
|
||||
err_not_authorized/0, err_not_authorized/2,
|
||||
err_feature_not_implemented/0, err_feature_not_implemented/2]).
|
||||
err_conflict/0, err_conflict/2,
|
||||
err_feature_not_implemented/0, err_feature_not_implemented/2,
|
||||
err_forbidden/0, err_forbidden/2,
|
||||
err_gone/0, err_gone/2,
|
||||
err_internal_server_error/0, err_internal_server_error/2,
|
||||
err_item_not_found/0, err_item_not_found/2,
|
||||
err_jid_malformed/0, err_jid_malformed/2,
|
||||
err_not_acceptable/0, err_not_acceptable/2,
|
||||
err_not_allowed/0, err_not_allowed/2,
|
||||
err_not_authorized/0, err_not_authorized/2,
|
||||
err_payment_required/0, err_payment_required/2,
|
||||
err_policy_violation/0, err_policy_violation/2,
|
||||
err_recipient_unavailable/0, err_recipient_unavailable/2,
|
||||
err_redirect/0, err_redirect/2,
|
||||
err_registration_required/0, err_registration_required/2,
|
||||
err_remote_server_not_found/0, err_remote_server_not_found/2,
|
||||
err_remote_server_timeout/0, err_remote_server_timeout/2,
|
||||
err_resource_constraint/0, err_resource_constraint/2,
|
||||
err_service_unavailable/0, err_service_unavailable/2,
|
||||
err_subscription_required/0, err_subscription_required/2,
|
||||
err_undefined_condition/0, err_undefined_condition/2,
|
||||
err_unexpected_request/0, err_unexpected_request/2]).
|
||||
|
||||
%% XMPP stream errors
|
||||
-export([serr_bad_format/0, serr_bad_format/2,
|
||||
@ -246,9 +257,12 @@ decode(Pkt, _Opts) ->
|
||||
(message()) -> message();
|
||||
(presence()) -> presence().
|
||||
decode_els(Stanza) ->
|
||||
decode_els(Stanza, fun xmpp_codec:is_known_tag/1).
|
||||
|
||||
decode_els(Stanza, MatchFun) ->
|
||||
Els = lists:map(
|
||||
fun(#xmlel{} = El) ->
|
||||
case xmpp_codec:is_known_tag(El) of
|
||||
case MatchFun(El) of
|
||||
true -> decode(El);
|
||||
false -> El
|
||||
end;
|
||||
@ -287,10 +301,10 @@ set_subtag(Stanza, Tag) ->
|
||||
set_els(Stanza, NewEls).
|
||||
|
||||
set_subtag([El|Els], Tag, TagName, XMLNS) ->
|
||||
case {get_name(El), get_ns(El)} of
|
||||
{TagName, XMLNS} ->
|
||||
case match_tag(El, TagName, XMLNS) of
|
||||
true ->
|
||||
[Tag|Els];
|
||||
_ ->
|
||||
false ->
|
||||
[El|set_subtag(Els, Tag, TagName, XMLNS)]
|
||||
end;
|
||||
set_subtag([], Tag, _, _) ->
|
||||
@ -304,14 +318,14 @@ get_subtag(Stanza, Tag) ->
|
||||
get_subtag(Els, TagName, XMLNS).
|
||||
|
||||
get_subtag([El|Els], TagName, XMLNS) ->
|
||||
case {get_name(El), get_ns(El)} of
|
||||
{TagName, XMLNS} ->
|
||||
case match_tag(El, TagName, XMLNS) of
|
||||
true ->
|
||||
try
|
||||
decode(El)
|
||||
catch _:{xmpp_codec, _Why} ->
|
||||
get_subtag(Els, TagName, XMLNS)
|
||||
end;
|
||||
_ ->
|
||||
false ->
|
||||
get_subtag(Els, TagName, XMLNS)
|
||||
end;
|
||||
get_subtag([], _, _) ->
|
||||
@ -328,10 +342,10 @@ remove_subtag(Stanza, Tag) ->
|
||||
set_els(Stanza, NewEls).
|
||||
|
||||
remove_subtag([El|Els], TagName, XMLNS) ->
|
||||
case {get_name(El), get_ns(El)} of
|
||||
{TagName, XMLNS} ->
|
||||
case match_tag(El, TagName, XMLNS) of
|
||||
true ->
|
||||
remove_subtag(Els, TagName, XMLNS);
|
||||
_ ->
|
||||
false ->
|
||||
[El|remove_subtag(Els, TagName, XMLNS)]
|
||||
end;
|
||||
remove_subtag([], _, _) ->
|
||||
@ -345,10 +359,10 @@ has_subtag(Stanza, Tag) ->
|
||||
has_subtag(Els, TagName, XMLNS).
|
||||
|
||||
has_subtag([El|Els], TagName, XMLNS) ->
|
||||
case {get_name(El), get_ns(El)} of
|
||||
{TagName, XMLNS} ->
|
||||
case match_tag(El, TagName, XMLNS) of
|
||||
true ->
|
||||
true;
|
||||
_ ->
|
||||
false ->
|
||||
has_subtag(Els, TagName, XMLNS)
|
||||
end;
|
||||
has_subtag([], _, _) ->
|
||||
@ -385,14 +399,6 @@ err_bad_request() ->
|
||||
err_bad_request(Text, Lang) ->
|
||||
err(modify, 'bad-request', 400, Text, Lang).
|
||||
|
||||
-spec err_bad_format() -> error().
|
||||
err_bad_format() ->
|
||||
err(modify, 'bad-format', 406).
|
||||
|
||||
-spec err_bad_format(binary(), binary() | undefined) -> error().
|
||||
err_bad_format(Text, Lang) ->
|
||||
err(modify, 'bad-format', 406, Text, Lang).
|
||||
|
||||
-spec err_conflict() -> error().
|
||||
err_conflict() ->
|
||||
err(cancel, 'conflict', 409).
|
||||
@ -401,14 +407,6 @@ err_conflict() ->
|
||||
err_conflict(Text, Lang) ->
|
||||
err(cancel, 'conflict', 409, Text, Lang).
|
||||
|
||||
-spec err_not_allowed() -> error().
|
||||
err_not_allowed() ->
|
||||
err(cancel, 'not-allowed', 405).
|
||||
|
||||
-spec err_not_allowed(binary(), binary() | undefined) -> error().
|
||||
err_not_allowed(Text, Lang) ->
|
||||
err(cancel, 'not-allowed', 405, Text, Lang).
|
||||
|
||||
-spec err_feature_not_implemented() -> error().
|
||||
err_feature_not_implemented() ->
|
||||
err(cancel, 'feature-not-implemented', 501).
|
||||
@ -417,14 +415,6 @@ err_feature_not_implemented() ->
|
||||
err_feature_not_implemented(Text, Lang) ->
|
||||
err(cancel, 'feature-not-implemented', 501, Text, Lang).
|
||||
|
||||
-spec err_item_not_found() -> error().
|
||||
err_item_not_found() ->
|
||||
err(cancel, 'item-not-found', 404).
|
||||
|
||||
-spec err_item_not_found(binary(), binary() | undefined) -> error().
|
||||
err_item_not_found(Text, Lang) ->
|
||||
err(cancel, 'item-not-found', 404, Text, Lang).
|
||||
|
||||
-spec err_forbidden() -> error().
|
||||
err_forbidden() ->
|
||||
err(auth, 'forbidden', 403).
|
||||
@ -433,14 +423,18 @@ err_forbidden() ->
|
||||
err_forbidden(Text, Lang) ->
|
||||
err(auth, 'forbidden', 403, Text, Lang).
|
||||
|
||||
-spec err_not_acceptable() -> error().
|
||||
err_not_acceptable() ->
|
||||
err(modify, 'not-acceptable', 406).
|
||||
%% RFC 6120 says error type SHOULD be "cancel".
|
||||
%% RFC 3920 and XEP-0082 says it SHOULD be "modify".
|
||||
-spec err_gone() -> error().
|
||||
err_gone() ->
|
||||
err(modify, 'gone', 302).
|
||||
|
||||
-spec err_not_acceptable(binary(), binary() | undefined) -> error().
|
||||
err_not_acceptable(Text, Lang) ->
|
||||
err(modify, 'not-acceptable', 406, Text, Lang).
|
||||
-spec err_gone(binary(), binary() | undefined) -> error().
|
||||
err_gone(Text, Lang) ->
|
||||
err(modify, 'gone', 302, Text, Lang).
|
||||
|
||||
%% RFC 6120 sasy error type SHOULD be "cancel".
|
||||
%% RFC 3920 and XEP-0082 says it SHOULD be "wait".
|
||||
-spec err_internal_server_error() -> error().
|
||||
err_internal_server_error() ->
|
||||
err(wait, 'internal-server-error', 500).
|
||||
@ -449,13 +443,13 @@ err_internal_server_error() ->
|
||||
err_internal_server_error(Text, Lang) ->
|
||||
err(wait, 'internal-server-error', 500, Text, Lang).
|
||||
|
||||
-spec err_service_unavailable() -> error().
|
||||
err_service_unavailable() ->
|
||||
err(cancel, 'service-unavailable', 503).
|
||||
-spec err_item_not_found() -> error().
|
||||
err_item_not_found() ->
|
||||
err(cancel, 'item-not-found', 404).
|
||||
|
||||
-spec err_service_unavailable(binary(), binary() | undefined) -> error().
|
||||
err_service_unavailable(Text, Lang) ->
|
||||
err(cancel, 'service-unavailable', 503, Text, Lang).
|
||||
-spec err_item_not_found(binary(), binary() | undefined) -> error().
|
||||
err_item_not_found(Text, Lang) ->
|
||||
err(cancel, 'item-not-found', 404, Text, Lang).
|
||||
|
||||
-spec err_jid_malformed() -> error().
|
||||
err_jid_malformed() ->
|
||||
@ -465,6 +459,22 @@ err_jid_malformed() ->
|
||||
err_jid_malformed(Text, Lang) ->
|
||||
err(modify, 'jid-malformed', 400, Text, Lang).
|
||||
|
||||
-spec err_not_acceptable() -> error().
|
||||
err_not_acceptable() ->
|
||||
err(modify, 'not-acceptable', 406).
|
||||
|
||||
-spec err_not_acceptable(binary(), binary() | undefined) -> error().
|
||||
err_not_acceptable(Text, Lang) ->
|
||||
err(modify, 'not-acceptable', 406, Text, Lang).
|
||||
|
||||
-spec err_not_allowed() -> error().
|
||||
err_not_allowed() ->
|
||||
err(cancel, 'not-allowed', 405).
|
||||
|
||||
-spec err_not_allowed(binary(), binary() | undefined) -> error().
|
||||
err_not_allowed(Text, Lang) ->
|
||||
err(cancel, 'not-allowed', 405, Text, Lang).
|
||||
|
||||
-spec err_not_authorized() -> error().
|
||||
err_not_authorized() ->
|
||||
err(auth, 'not-authorized', 401).
|
||||
@ -473,6 +483,108 @@ err_not_authorized() ->
|
||||
err_not_authorized(Text, Lang) ->
|
||||
err(auth, 'not-authorized', 401, Text, Lang).
|
||||
|
||||
-spec err_payment_required() -> error().
|
||||
err_payment_required() ->
|
||||
err(auth, 'not-authorized', 402).
|
||||
|
||||
-spec err_payment_required(binary(), binary() | undefined) -> error().
|
||||
err_payment_required(Text, Lang) ->
|
||||
err(auth, 'not-authorized', 402, Text, Lang).
|
||||
|
||||
%% <policy-violation/> is defined in neither RFC 3920 nor XEP-0086.
|
||||
%% We choose '403' error code (as in <forbidden/>).
|
||||
-spec err_policy_violation() -> error().
|
||||
err_policy_violation() ->
|
||||
err(modify, 'policy-violation', 403).
|
||||
|
||||
-spec err_policy_violation(binary(), binary() | undefined) -> error().
|
||||
err_policy_violation(Text, Lang) ->
|
||||
err(modify, 'policy-violation', 403, Text, Lang).
|
||||
|
||||
-spec err_recipient_unavailable() -> error().
|
||||
err_recipient_unavailable() ->
|
||||
err(wait, 'recipient-unavailable', 404).
|
||||
|
||||
-spec err_recipient_unavailable(binary(), binary() | undefined) -> error().
|
||||
err_recipient_unavailable(Text, Lang) ->
|
||||
err(wait, 'recipient-unavailable', 404, Text, Lang).
|
||||
|
||||
-spec err_redirect() -> error().
|
||||
err_redirect() ->
|
||||
err(modify, 'redirect', 302).
|
||||
|
||||
-spec err_redirect(binary(), binary() | undefined) -> error().
|
||||
err_redirect(Text, Lang) ->
|
||||
err(modify, 'redirect', 302, Text, Lang).
|
||||
|
||||
-spec err_registration_required() -> error().
|
||||
err_registration_required() ->
|
||||
err(auth, 'registration-required', 407).
|
||||
|
||||
-spec err_registration_required(binary(), binary() | undefined) -> error().
|
||||
err_registration_required(Text, Lang) ->
|
||||
err(auth, 'registration-required', 407, Text, Lang).
|
||||
|
||||
-spec err_remote_server_not_found() -> error().
|
||||
err_remote_server_not_found() ->
|
||||
err(cancel, 'remote-server-not-found', 404).
|
||||
|
||||
-spec err_remote_server_not_found(binary(), binary() | undefined) -> error().
|
||||
err_remote_server_not_found(Text, Lang) ->
|
||||
err(cancel, 'remote-server-not-found', 404, Text, Lang).
|
||||
|
||||
-spec err_remote_server_timeout() -> error().
|
||||
err_remote_server_timeout() ->
|
||||
err(wait, 'remote-server-timeout', 504).
|
||||
|
||||
-spec err_remote_server_timeout(binary(), binary() | undefined) -> error().
|
||||
err_remote_server_timeout(Text, Lang) ->
|
||||
err(wait, 'remote-server-timeout', 504, Text, Lang).
|
||||
|
||||
-spec err_resource_constraint() -> error().
|
||||
err_resource_constraint() ->
|
||||
err(wait, 'resource-constraint', 500).
|
||||
|
||||
-spec err_resource_constraint(binary(), binary() | undefined) -> error().
|
||||
err_resource_constraint(Text, Lang) ->
|
||||
err(wait, 'resource-constraint', 500, Text, Lang).
|
||||
|
||||
-spec err_service_unavailable() -> error().
|
||||
err_service_unavailable() ->
|
||||
err(cancel, 'service-unavailable', 503).
|
||||
|
||||
-spec err_service_unavailable(binary(), binary() | undefined) -> error().
|
||||
err_service_unavailable(Text, Lang) ->
|
||||
err(cancel, 'service-unavailable', 503, Text, Lang).
|
||||
|
||||
-spec err_subscription_required() -> error().
|
||||
err_subscription_required() ->
|
||||
err(auth, 'subscription-required', 407).
|
||||
|
||||
-spec err_subscription_required(binary(), binary() | undefined) -> error().
|
||||
err_subscription_required(Text, Lang) ->
|
||||
err(auth, 'subscription-required', 407, Text, Lang).
|
||||
|
||||
%% No error type is defined for <undefined-confition/>.
|
||||
%% We choose "modify" as it's used in RFC 6120 example.
|
||||
-spec err_undefined_condition() -> error().
|
||||
err_undefined_condition() ->
|
||||
err(modify, 'undefined-condition', 500).
|
||||
|
||||
-spec err_undefined_condition(binary(), binary() | undefined) -> error().
|
||||
err_undefined_condition(Text, Lang) ->
|
||||
err(modify, 'undefined-condition', 500, Text, Lang).
|
||||
|
||||
%% RFC 6120 says error type SHOULD be "wait" or "modify".
|
||||
%% RFC 3920 and XEP-0082 says it SHOULD be "wait".
|
||||
-spec err_unexpected_request() -> error().
|
||||
err_unexpected_request() ->
|
||||
err(wait, 'unexpected-request', 400).
|
||||
|
||||
-spec err_unexpected_request(binary(), binary() | undefined) -> error().
|
||||
err_unexpected_request(Text, Lang) ->
|
||||
err(wait, 'unexpected-request', 400, Text, Lang).
|
||||
|
||||
%%%===================================================================
|
||||
%%% Functions to construct stream errors
|
||||
%%%===================================================================
|
||||
@ -712,3 +824,7 @@ add_ns(#xmlel{name = Name} = El) when Name == <<"message">>;
|
||||
El#xmlel{attrs = Attrs};
|
||||
add_ns(El) ->
|
||||
El.
|
||||
|
||||
-spec match_tag(xmlel() | xmpp_element(), binary(), binary()) -> boolean().
|
||||
match_tag(El, TagName, XMLNS) ->
|
||||
get_name(El) == TagName andalso get_ns(El) == XMLNS.
|
||||
|
2390
src/xmpp_codec.erl
2390
src/xmpp_codec.erl
File diff suppressed because it is too large
Load Diff
@ -10,7 +10,8 @@
|
||||
|
||||
%% API
|
||||
-export([add_delay_info/3, add_delay_info/4, unwrap_carbon/1,
|
||||
is_standalone_chat_state/1]).
|
||||
is_standalone_chat_state/1, get_xdata_values/2,
|
||||
has_xdata_var/2]).
|
||||
|
||||
-include("xmpp.hrl").
|
||||
|
||||
@ -76,6 +77,17 @@ is_standalone_chat_state(Stanza) ->
|
||||
false
|
||||
end.
|
||||
|
||||
-spec get_xdata_values(binary(), xdata()) -> [binary()].
|
||||
get_xdata_values(Var, #xdata{fields = Fields}) ->
|
||||
case lists:keyfind(Var, #xdata_field.var, Fields) of
|
||||
#xdata_field{values = Vals} -> Vals;
|
||||
false -> []
|
||||
end.
|
||||
|
||||
-spec has_xdata_var(binary(), xdata()) -> boolean().
|
||||
has_xdata_var(Var, #xdata{fields = Fields}) ->
|
||||
lists:keymember(Var, #xdata_field.var, Fields).
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
|
@ -224,10 +224,12 @@
|
||||
-xml(disco_items,
|
||||
#elem{name = <<"query">>,
|
||||
xmlns = <<"http://jabber.org/protocol/disco#items">>,
|
||||
result = {disco_items, '$node', '$items'},
|
||||
result = {disco_items, '$node', '$items', '$rsm'},
|
||||
attrs = [#attr{name = <<"node">>}],
|
||||
refs = [#ref{name = disco_item,
|
||||
label = '$items'}]}).
|
||||
label = '$items'},
|
||||
#ref{name = rsm_set, min = 0, max = 1,
|
||||
label = '$rsm'}]}).
|
||||
|
||||
-xml(private,
|
||||
#elem{name = <<"query">>,
|
||||
@ -468,6 +470,10 @@
|
||||
#elem{name = <<"not-authorized">>,
|
||||
xmlns = <<"urn:ietf:params:xml:ns:xmpp-stanzas">>,
|
||||
result = 'not-authorized'}).
|
||||
-xml(error_payment_required,
|
||||
#elem{name = <<"payment-required">>,
|
||||
xmlns = <<"urn:ietf:params:xml:ns:xmpp-stanzas">>,
|
||||
result = 'payment-required'}).
|
||||
-xml(error_policy_violation,
|
||||
#elem{name = <<"policy-violation">>,
|
||||
xmlns = <<"urn:ietf:params:xml:ns:xmpp-stanzas">>,
|
||||
@ -561,6 +567,8 @@
|
||||
min = 0, max = 1, label = '$reason'},
|
||||
#ref{name = error_not_authorized,
|
||||
min = 0, max = 1, label = '$reason'},
|
||||
#ref{name = error_payment_required,
|
||||
min = 0, max = 1, label = '$reason'},
|
||||
#ref{name = error_policy_violation,
|
||||
min = 0, max = 1, label = '$reason'},
|
||||
#ref{name = error_recipient_unavailable,
|
||||
@ -1582,7 +1590,8 @@
|
||||
-xml(xdata_field_option,
|
||||
#elem{name = <<"option">>,
|
||||
xmlns = <<"jabber:x:data">>,
|
||||
result = '$value',
|
||||
result = {xdata_option, '$label', '$value'},
|
||||
attrs = [#attr{name = <<"label">>}],
|
||||
refs = [#ref{name = xdata_field_value,
|
||||
label = '$value',
|
||||
min = 1, max = 1}]}).
|
||||
@ -1945,9 +1954,11 @@
|
||||
dec = {dec_utc, []},
|
||||
enc = {enc_utc, []}}]}).
|
||||
|
||||
-xml(muc_user_reason,
|
||||
-xml(muc_reason,
|
||||
#elem{name = <<"reason">>,
|
||||
xmlns = <<"http://jabber.org/protocol/muc#user">>,
|
||||
xmlns = [<<"http://jabber.org/protocol/muc#user">>,
|
||||
<<"http://jabber.org/protocol/muc#admin">>,
|
||||
<<"http://jabber.org/protocol/muc#owner">>],
|
||||
result = '$cdata'}).
|
||||
|
||||
-xml(muc_user_decline,
|
||||
@ -1960,31 +1971,39 @@
|
||||
#attr{name = <<"from">>,
|
||||
dec = {dec_jid, []},
|
||||
enc = {enc_jid, []}}],
|
||||
refs = [#ref{name = muc_user_reason, min = 0,
|
||||
refs = [#ref{name = muc_reason, min = 0,
|
||||
default = <<"">>,
|
||||
max = 1, label = '$reason'}]}).
|
||||
|
||||
-xml(muc_user_destroy,
|
||||
-xml(muc_destroy,
|
||||
#elem{name = <<"destroy">>,
|
||||
xmlns = <<"http://jabber.org/protocol/muc#user">>,
|
||||
result = {muc_user_destroy, '$reason', '$jid'},
|
||||
attrs = [#attr{name = <<"jid">>,
|
||||
xmlns = [<<"http://jabber.org/protocol/muc#user">>,
|
||||
<<"http://jabber.org/protocol/muc#owner">>],
|
||||
result = {muc_destroy, '$xmlns', '$jid', '$reason', '$password'},
|
||||
attrs = [#attr{name = <<"jid">>,
|
||||
dec = {dec_jid, []},
|
||||
enc = {enc_jid, []}}],
|
||||
refs = [#ref{name = muc_user_reason, min = 0,
|
||||
max = 1, label = '$reason'}]}).
|
||||
enc = {enc_jid, []}},
|
||||
#attr{name = <<"xmlns">>}],
|
||||
refs = [#ref{name = muc_reason, min = 0,
|
||||
default = <<"">>,
|
||||
max = 1, label = '$reason'},
|
||||
#ref{name = muc_password, min = 0, max = 1,
|
||||
label = '$password'}]}).
|
||||
|
||||
-xml(muc_user_invite,
|
||||
#elem{name = <<"invite">>,
|
||||
xmlns = <<"http://jabber.org/protocol/muc#user">>,
|
||||
result = {muc_invite, '$reason', '$from', '$to'},
|
||||
result = {muc_invite, '$reason', '$from', '$to', '$continue'},
|
||||
attrs = [#attr{name = <<"to">>,
|
||||
dec = {dec_jid, []},
|
||||
enc = {enc_jid, []}},
|
||||
#attr{name = <<"from">>,
|
||||
dec = {dec_jid, []},
|
||||
enc = {enc_jid, []}}],
|
||||
refs = [#ref{name = muc_user_reason, min = 0,
|
||||
max = 1, label = '$reason'}]}).
|
||||
refs = [#ref{name = muc_reason, min = 0, default = <<"">>,
|
||||
max = 1, label = '$reason'},
|
||||
#ref{name = muc_user_continue, min = 0, max = 1,
|
||||
label = '$continue'}]}).
|
||||
|
||||
-xml(muc_user_actor,
|
||||
#elem{name = <<"actor">>,
|
||||
@ -2018,7 +2037,7 @@
|
||||
min = 0, max = 1, label = '$actor'},
|
||||
#ref{name = muc_user_continue,
|
||||
min = 0, max = 1, label = '$continue'},
|
||||
#ref{name = muc_user_reason,
|
||||
#ref{name = muc_reason, default = <<"">>,
|
||||
min = 0, max = 1, label = '$reason'}],
|
||||
attrs = [#attr{name = <<"affiliation">>,
|
||||
dec = {dec_enum, [[admin, member, none,
|
||||
@ -2038,44 +2057,56 @@
|
||||
xmlns = <<"http://jabber.org/protocol/muc#user">>,
|
||||
result = {muc_user, '$decline', '$destroy', '$invites',
|
||||
'$items', '$status_codes', '$password'},
|
||||
attrs = [#attr{name = <<"password">>}],
|
||||
refs = [#ref{name = muc_user_decline, min = 0,
|
||||
max = 1, label = '$decline'},
|
||||
#ref{name = muc_user_destroy, min = 0, max = 1,
|
||||
#ref{name = muc_destroy, min = 0, max = 1,
|
||||
label = '$destroy'},
|
||||
#ref{name = muc_password, min = 0, max = 1,
|
||||
label = '$password'},
|
||||
#ref{name = muc_user_invite, label = '$invites'},
|
||||
#ref{name = muc_user_item, label = '$items'},
|
||||
#ref{name = muc_user_status, label = '$status_codes'}]}).
|
||||
|
||||
-xml(muc_owner_password,
|
||||
-xml(muc_password,
|
||||
#elem{name = <<"password">>,
|
||||
xmlns = <<"http://jabber.org/protocol/muc#owner">>,
|
||||
xmlns = [<<"http://jabber.org/protocol/muc#owner">>,
|
||||
<<"http://jabber.org/protocol/muc#user">>,
|
||||
<<"http://jabber.org/protocol/muc">>],
|
||||
result = '$cdata'}).
|
||||
|
||||
-xml(muc_owner_reason,
|
||||
#elem{name = <<"reason">>,
|
||||
xmlns = <<"http://jabber.org/protocol/muc#owner">>,
|
||||
result = '$cdata'}).
|
||||
|
||||
-xml(muc_owner_destroy,
|
||||
#elem{name = <<"destroy">>,
|
||||
xmlns = <<"http://jabber.org/protocol/muc#owner">>,
|
||||
result = {muc_owner_destroy, '$jid', '$reason', '$password'},
|
||||
attrs = [#attr{name = <<"jid">>,
|
||||
dec = {dec_jid, []},
|
||||
enc = {enc_jid, []}}],
|
||||
refs = [#ref{name = muc_owner_password, min = 0, max = 1,
|
||||
label = '$password'},
|
||||
#ref{name = muc_owner_reason, min = 0, max = 1,
|
||||
label = '$reason'}]}).
|
||||
|
||||
-xml(muc_owner,
|
||||
#elem{name = <<"query">>,
|
||||
xmlns = <<"http://jabber.org/protocol/muc#owner">>,
|
||||
result = {muc_owner, '$destroy', '$config'},
|
||||
refs = [#ref{name = muc_owner_destroy, min = 0, max = 1,
|
||||
label = '$destroy'},
|
||||
#ref{name = xdata, min = 0, max = 1, label = '$config'}]}).
|
||||
result = {muc_owner, '$destroy', '$config', '$items'},
|
||||
refs = [#ref{name = muc_destroy, min = 0, max = 1,
|
||||
label = '$destroy'},
|
||||
#ref{name = xdata, min = 0, max = 1,
|
||||
label = '$config'},
|
||||
#ref{name = muc_owner_item, label = '$items'}]}).
|
||||
|
||||
-xml(muc_owner_item,
|
||||
#elem{name = <<"item">>,
|
||||
xmlns = <<"http://jabber.org/protocol/muc#owner">>,
|
||||
result = {muc_item, '$actor', '$continue', '$reason',
|
||||
'$affiliation', '$role', '$jid', '$nick'},
|
||||
refs = [#ref{name = muc_admin_actor,
|
||||
min = 0, max = 1, label = '$actor'},
|
||||
#ref{name = muc_admin_continue,
|
||||
min = 0, max = 1, label = '$continue'},
|
||||
#ref{name = muc_reason, default = <<"">>,
|
||||
min = 0, max = 1, label = '$reason'}],
|
||||
attrs = [#attr{name = <<"affiliation">>,
|
||||
dec = {dec_enum, [[admin, member, none,
|
||||
outcast, owner]]},
|
||||
enc = {enc_enum, []}},
|
||||
#attr{name = <<"role">>,
|
||||
dec = {dec_enum, [[moderator, none,
|
||||
participant, visitor]]},
|
||||
enc = {enc_enum, []}},
|
||||
#attr{name = <<"jid">>,
|
||||
dec = {dec_jid, []},
|
||||
enc = {enc_jid, []}},
|
||||
#attr{name = <<"nick">>}]}).
|
||||
|
||||
-xml(muc_admin_item,
|
||||
#elem{name = <<"item">>,
|
||||
@ -2086,7 +2117,7 @@
|
||||
min = 0, max = 1, label = '$actor'},
|
||||
#ref{name = muc_admin_continue,
|
||||
min = 0, max = 1, label = '$continue'},
|
||||
#ref{name = muc_admin_reason,
|
||||
#ref{name = muc_reason, default = <<"">>,
|
||||
min = 0, max = 1, label = '$reason'}],
|
||||
attrs = [#attr{name = <<"affiliation">>,
|
||||
dec = {dec_enum, [[admin, member, none,
|
||||
@ -2116,11 +2147,6 @@
|
||||
result = '$thread',
|
||||
attrs = [#attr{name = <<"thread">>}]}).
|
||||
|
||||
-xml(muc_admin_reason,
|
||||
#elem{name = <<"reason">>,
|
||||
xmlns = <<"http://jabber.org/protocol/muc#admin">>,
|
||||
result = '$cdata'}).
|
||||
|
||||
-xml(muc_admin,
|
||||
#elem{name = <<"query">>,
|
||||
xmlns = <<"http://jabber.org/protocol/muc#admin">>,
|
||||
@ -2131,9 +2157,66 @@
|
||||
#elem{name = <<"x">>,
|
||||
xmlns = <<"http://jabber.org/protocol/muc">>,
|
||||
result = {muc, '$history', '$password'},
|
||||
attrs = [#attr{name = <<"password">>}],
|
||||
refs = [#ref{name = muc_history, min = 0, max = 1,
|
||||
label = '$history'}]}).
|
||||
label = '$history'},
|
||||
#ref{name = muc_password, min = 0, max = 1,
|
||||
label = '$password'}]}).
|
||||
|
||||
-xml(muc_unique,
|
||||
#elem{name = <<"unique">>,
|
||||
xmlns = <<"http://jabber.org/protocol/muc#unique">>,
|
||||
result = {muc_unique, '$name'},
|
||||
cdata = #cdata{default = <<"">>,
|
||||
label = '$name'}}).
|
||||
|
||||
-xml(x_conference,
|
||||
#elem{name = <<"x">>,
|
||||
xmlns = <<"jabber:x:conference">>,
|
||||
result = {x_conference, '$jid', '$password', '$reason',
|
||||
'$continue', '$thread'},
|
||||
attrs = [#attr{name = <<"jid">>,
|
||||
required = true,
|
||||
dec = {dec_jid, []},
|
||||
enc = {enc_jid, []}},
|
||||
#attr{name = <<"password">>, default = <<"">>},
|
||||
#attr{name = <<"reason">>, default = <<"">>},
|
||||
#attr{name = <<"thread">>, default = <<"">>},
|
||||
#attr{name = <<"continue">>,
|
||||
dec = {dec_bool, []},
|
||||
enc = {enc_bool, []}}]}).
|
||||
|
||||
-xml(muc_subscription,
|
||||
#elem{name = <<"subscription">>,
|
||||
xmlns = <<"urn:xmpp:mucsub:0">>,
|
||||
result = '$jid',
|
||||
attrs = [#attr{name = <<"jid">>,
|
||||
required = true,
|
||||
dec = {dec_jid, []},
|
||||
enc = {enc_jid, []}}]}).
|
||||
|
||||
-xml(muc_subscriptions,
|
||||
#elem{name = <<"subscriptions">>,
|
||||
xmlns = <<"urn:xmpp:mucsub:0">>,
|
||||
result = {muc_subscriptions, '$list'},
|
||||
refs = [#ref{name = muc_subscription, label = '$list'}]}).
|
||||
|
||||
-xml(muc_subscribe_event,
|
||||
#elem{name = <<"event">>,
|
||||
xmlns = <<"urn:xmpp:mucsub:0">>,
|
||||
result = '$node',
|
||||
attrs = [#attr{name = <<"node">>, required = true}]}).
|
||||
|
||||
-xml(muc_subscribe,
|
||||
#elem{name = <<"subscribe">>,
|
||||
xmlns = <<"urn:xmpp:mucsub:0">>,
|
||||
result = {muc_subscribe, '$nick', '$events'},
|
||||
attrs = [#attr{name = <<"nick">>, required = true}],
|
||||
refs = [#ref{name = muc_subscribe_event, label = '$events'}]}).
|
||||
|
||||
-xml(muc_unsubscribe,
|
||||
#elem{name = <<"unsubscribe">>,
|
||||
xmlns = <<"urn:xmpp:mucsub:0">>,
|
||||
result = {muc_unsubscribe}}).
|
||||
|
||||
-xml(rsm_after,
|
||||
#elem{name = <<"after">>,
|
||||
@ -2143,7 +2226,7 @@
|
||||
-xml(rsm_before,
|
||||
#elem{name = <<"before">>,
|
||||
xmlns = <<"http://jabber.org/protocol/rsm">>,
|
||||
cdata = #cdata{default = none},
|
||||
cdata = #cdata{default = <<"">>},
|
||||
result = '$cdata'}).
|
||||
|
||||
-xml(rsm_last,
|
||||
@ -2215,16 +2298,23 @@
|
||||
dec = {dec_jid, []},
|
||||
enc = {enc_jid, []}}}).
|
||||
|
||||
-xml(mam_withtext,
|
||||
#elem{name = <<"withtext">>,
|
||||
xmlns = <<"urn:xmpp:mam:tmp">>,
|
||||
result = '$cdata',
|
||||
cdata = #cdata{required = true}}).
|
||||
|
||||
-xml(mam_query,
|
||||
#elem{name = <<"query">>,
|
||||
xmlns = [<<"urn:xmpp:mam:0">>, <<"urn:xmpp:mam:1">>, <<"urn:xmpp:mam:tmp">>],
|
||||
result = {mam_query, '$xmlns', '$id', '$start', '$end', '$with',
|
||||
'$rsm', '$xdata'},
|
||||
'$withtext', '$rsm', '$xdata'},
|
||||
attrs = [#attr{name = <<"queryid">>, label = '$id'},
|
||||
#attr{name = <<"xmlns">>}],
|
||||
refs = [#ref{name = mam_start, min = 0, max = 1, label = '$start'},
|
||||
#ref{name = mam_end, min = 0, max = 1, label = '$end'},
|
||||
#ref{name = mam_with, min = 0, max = 1, label = '$with'},
|
||||
#ref{name = mam_withtext, min = 0, max = 1, label = '$withtext'},
|
||||
#ref{name = rsm_set, min = 0, max = 1, label = '$rsm'},
|
||||
#ref{name = xdata, min = 0, max = 1, label = '$xdata'}]}).
|
||||
|
||||
@ -2248,7 +2338,7 @@
|
||||
|
||||
-xml(mam_jid,
|
||||
#elem{name = <<"jid">>,
|
||||
xmlns = <<"urn:xmpp:mam:tmp">>,
|
||||
xmlns = [<<"urn:xmpp:mam:0">>, <<"urn:xmpp:mam:1">>, <<"urn:xmpp:mam:tmp">>],
|
||||
result = '$cdata',
|
||||
cdata = #cdata{required = true,
|
||||
dec = {dec_jid, []},
|
||||
@ -2256,15 +2346,15 @@
|
||||
|
||||
-xml(mam_never,
|
||||
#elem{name = <<"never">>,
|
||||
xmlns = <<"urn:xmpp:mam:tmp">>,
|
||||
xmlns = [<<"urn:xmpp:mam:0">>, <<"urn:xmpp:mam:1">>, <<"urn:xmpp:mam:tmp">>],
|
||||
result = '$jids',
|
||||
refs = [#ref{name = mam_jid, label = '$jids', default = []}]}).
|
||||
refs = [#ref{name = mam_jid, label = '$jids'}]}).
|
||||
|
||||
-xml(mam_always,
|
||||
#elem{name = <<"always">>,
|
||||
xmlns = <<"urn:xmpp:mam:tmp">>,
|
||||
xmlns = [<<"urn:xmpp:mam:0">>, <<"urn:xmpp:mam:1">>, <<"urn:xmpp:mam:tmp">>],
|
||||
result = '$jids',
|
||||
refs = [#ref{name = mam_jid, label = '$jids', default = []}]}).
|
||||
refs = [#ref{name = mam_jid, label = '$jids'}]}).
|
||||
|
||||
-xml(mam_prefs,
|
||||
#elem{name = <<"prefs">>,
|
||||
@ -2275,9 +2365,9 @@
|
||||
enc = {enc_enum, []}},
|
||||
#attr{name = <<"xmlns">>}],
|
||||
refs = [#ref{name = mam_always, label = '$always',
|
||||
min = 0, max = 1, default = []},
|
||||
min = 0, max = 1},
|
||||
#ref{name = mam_never, label = '$never',
|
||||
min = 0, max = 1, default = []}]}).
|
||||
min = 0, max = 1}]}).
|
||||
|
||||
-xml(mam_fin,
|
||||
#elem{name = <<"fin">>,
|
||||
@ -2538,8 +2628,8 @@
|
||||
#attr{name = <<"nick">>,
|
||||
label = '$nick'}]}).
|
||||
|
||||
-record(hint, {type :: 'no-copy' | 'no-store' | 'no-storage' |
|
||||
'store' | 'no-permanent-store'}).
|
||||
-record(hint, {type :: 'no-copy' | 'no-store' | 'no-storage' | 'store' |
|
||||
'no-permanent-store' | 'no-permanent-storage'}).
|
||||
-type hint() :: #hint{}.
|
||||
|
||||
-xml(hint_no_copy,
|
||||
@ -2567,6 +2657,11 @@
|
||||
xmlns = <<"urn:xmpp:hints">>,
|
||||
result = {hint, 'no-permanent-store'}}).
|
||||
|
||||
-xml(hint_no_permanent_storage,
|
||||
#elem{name = <<"no-permanent-storage">>,
|
||||
xmlns = <<"urn:xmpp:hints">>,
|
||||
result = {hint, 'no-permanent-storage'}}).
|
||||
|
||||
-xml(search_instructions,
|
||||
#elem{name = <<"instructions">>,
|
||||
xmlns = <<"jabber:iq:search">>,
|
||||
@ -2679,6 +2774,53 @@
|
||||
dec = {dec_int, [0, infinity]},
|
||||
enc = {enc_int, []}}]}).
|
||||
|
||||
-xml(nick,
|
||||
#elem{name = <<"nick">>,
|
||||
xmlns = <<"http://jabber.org/protocol/nick">>,
|
||||
result = {nick, '$name'},
|
||||
cdata = #cdata{label = '$name',
|
||||
required = true}}).
|
||||
|
||||
-xml(address,
|
||||
#elem{name = <<"address">>,
|
||||
xmlns = <<"http://jabber.org/protocol/address">>,
|
||||
result = {address, '$type', '$jid', '$desc', '$node', '$delivered'},
|
||||
attrs = [#attr{name = <<"type">>,
|
||||
required = true,
|
||||
dec = {dec_enum, [[bcc, cc, noreply, ofrom,
|
||||
replyroom, replyto, to]]},
|
||||
enc = {enc_enum, []}},
|
||||
#attr{name = <<"jid">>,
|
||||
enc = {enc_jid, []},
|
||||
dec = {dec_jid, []}},
|
||||
#attr{name = <<"desc">>},
|
||||
#attr{name = <<"node">>},
|
||||
#attr{name = <<"delivered">>,
|
||||
enc = {enc_bool, []},
|
||||
dec = {dec_bool, []}}]}).
|
||||
|
||||
-xml(addresses,
|
||||
#elem{name = <<"addresses">>,
|
||||
xmlns = <<"http://jabber.org/protocol/address">>,
|
||||
result = {addresses, '$list'},
|
||||
%% TODO: 'min' should be '1', but this is not implemented
|
||||
refs = [#ref{name = address, label = '$list'}]}).
|
||||
|
||||
-xml(stanza_id,
|
||||
#elem{name = <<"stanza-id">>,
|
||||
xmlns = <<"urn:xmpp:sid:0">>,
|
||||
result = {stanza_id, '$by', '$id'},
|
||||
attrs = [#attr{name = <<"id">>, required = true},
|
||||
#attr{name = <<"by">>, required = true,
|
||||
enc = {enc_jid, []},
|
||||
dec = {dec_jid, []}}]}).
|
||||
|
||||
-xml(client_id,
|
||||
#elem{name = <<"client-id">>,
|
||||
xmlns = <<"urn:xmpp:sid:0">>,
|
||||
result = {client_id, '$id'},
|
||||
attrs = [#attr{name = <<"id">>, required = true}]}).
|
||||
|
||||
dec_tzo(Val) ->
|
||||
[H1, M1] = str:tokens(Val, <<":">>),
|
||||
H = jlib:binary_to_integer(H1),
|
||||
|
Loading…
Reference in New Issue
Block a user