diff --git a/include/xmpp_codec.hrl b/include/xmpp_codec.hrl index 64a185a30..e348404b0 100644 --- a/include/xmpp_codec.hrl +++ b/include/xmpp_codec.hrl @@ -11,7 +11,8 @@ -record(csi, {type :: active | inactive}). -type csi() :: #csi{}. --record(hint, {type :: 'no-copy' | 'no-store' | 'store' | 'no-permanent-store'}). +-record(hint, {type :: 'no-copy' | 'no-store' | 'no-storage' | + 'store' | 'no-permanent-store'}). -type hint() :: #hint{}. -record(feature_register, {}). @@ -50,6 +51,10 @@ -record(carbons_private, {}). -type carbons_private() :: #carbons_private{}. +-record(expire, {seconds :: non_neg_integer(), + stored :: non_neg_integer()}). +-type expire() :: #expire{}. + -record(pubsub_unsubscribe, {node :: binary(), jid :: any(), subid :: binary()}). @@ -304,6 +309,13 @@ -record(sasl_abort, {}). -type sasl_abort() :: #sasl_abort{}. +-record(xevent, {offline = false :: boolean(), + delivered = false :: boolean(), + displayed = false :: boolean(), + composing = false :: boolean(), + id :: binary()}). +-type xevent() :: #xevent{}. + -record(vcard_email, {home = false :: boolean(), work = false :: boolean(), internet = false :: boolean(), @@ -717,6 +729,7 @@ starttls_proceed() | sm_resumed() | forwarded() | + xevent() | privacy_list() | text() | vcard_org() | @@ -735,12 +748,12 @@ pubsub_options() | compress() | bytestreams() | + muc_history() | identity() | feature_csi() | muc_user_destroy() | privacy_query() | delay() | - muc_history() | vcard_tel() | vcard_logo() | disco_info() | @@ -780,6 +793,7 @@ vcard_name() | sm_resume() | carbons_enable() | + expire() | pubsub_unsubscribe() | muc_decline() | chatstate() | diff --git a/src/mod_offline.erl b/src/mod_offline.erl index 799605c69..66edb6a7c 100644 --- a/src/mod_offline.erl +++ b/src/mod_offline.erl @@ -49,7 +49,7 @@ get_sm_identity/5, get_sm_items/5, get_info/5, - handle_offline_query/3, + handle_offline_query/1, remove_expired_messages/1, remove_old_messages/2, remove_user/2, @@ -73,7 +73,7 @@ -include("ejabberd.hrl"). -include("logger.hrl"). --include("jlib.hrl"). +-include("xmpp.hrl"). -include("ejabberd_http.hrl"). @@ -250,7 +250,7 @@ receive_all(US, Msgs, DBType) -> end end. -get_sm_features(Acc, _From, _To, <<"">>, _Lang) -> +get_sm_features(Acc, _From, _To, undefined, _Lang) -> Feats = case Acc of {result, I} -> I; _ -> [] @@ -268,12 +268,10 @@ get_sm_features(_Acc, #jid{luser = U, lserver = S}, #jid{luser = U, lserver = S} get_sm_features(Acc, _From, _To, _Node, _Lang) -> Acc. -get_sm_identity(_Acc, #jid{luser = U, lserver = S}, #jid{luser = U, lserver = S}, +get_sm_identity(Acc, #jid{luser = U, lserver = S}, #jid{luser = U, lserver = S}, ?NS_FLEX_OFFLINE, _Lang) -> - Identity = #xmlel{name = <<"identity">>, - attrs = [{<<"category">>, <<"automation">>}, - {<<"type">>, <<"message-list">>}]}, - [Identity]; + [#identity{category = <<"automation">>, + type = <<"message-list">>}|Acc]; get_sm_identity(Acc, _From, _To, _Node, _Lang) -> Acc. @@ -282,15 +280,16 @@ get_sm_items(_Acc, #jid{luser = U, lserver = S, lresource = R} = JID, ?NS_FLEX_OFFLINE, _Lang) -> case ejabberd_sm:get_session_pid(U, S, R) of Pid when is_pid(Pid) -> - Hdrs = read_message_headers(U, S), - BareJID = jid:to_string(jid:remove_resource(JID)), + Mod = gen_mod:db_mod(S, ?MODULE), + Hdrs = Mod:read_message_headers(U, S), + BareJID = jid:remove_resource(JID), Pid ! dont_ask_offline, {result, lists:map( - fun({Node, From, _To, _El}) -> - #xmlel{name = <<"item">>, - attrs = [{<<"jid">>, BareJID}, - {<<"node">>, Node}, - {<<"name">>, jid:to_string(From)}]} + fun({Seq, From, _To, _El}) -> + Node = integer_to_binary(Seq), + #disco_item{jid = BareJID, + node = Node, + name = jid:to_string(From)} end, Hdrs)}; none -> {result, []} @@ -298,6 +297,8 @@ get_sm_items(_Acc, #jid{luser = U, lserver = S, lresource = R} = JID, get_sm_items(Acc, _From, _To, _Node, _Lang) -> Acc. +-spec get_info([xdata()], jid(), jid(), + undefined | binary(), undefined | binary()) -> [xdata()]. get_info(_Acc, #jid{luser = U, lserver = S, lresource = R}, #jid{luser = U, lserver = S}, ?NS_FLEX_OFFLINE, _Lang) -> N = jlib:integer_to_binary(count_offline_messages(U, S)), @@ -307,50 +308,42 @@ get_info(_Acc, #jid{luser = U, lserver = S, lresource = R}, none -> ok end, - [#xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_XDATA}, - {<<"type">>, <<"result">>}], - children = [#xmlel{name = <<"field">>, - attrs = [{<<"var">>, <<"FORM_TYPE">>}, - {<<"type">>, <<"hidden">>}], - children = [#xmlel{name = <<"value">>, - children = [{xmlcdata, - ?NS_FLEX_OFFLINE}]}]}, - #xmlel{name = <<"field">>, - attrs = [{<<"var">>, <<"number_of_messages">>}], - children = [#xmlel{name = <<"value">>, - children = [{xmlcdata, N}]}]}]}]; + [#xdata{type = result, + fields = [#xdata_field{var = <<"FORM_TYPE">>, + type = hidden, + values = [?NS_FLEX_OFFLINE]}, + #xdata_field{var = <<"number_of_messages">>, + values = [N]}]}]; get_info(Acc, _From, _To, _Node, _Lang) -> Acc. -handle_offline_query(#jid{luser = U, lserver = S} = From, - #jid{luser = U, lserver = S} = _To, - #iq{type = Type, sub_el = SubEl} = IQ) -> +-spec handle_offline_query(iq()) -> iq(). +handle_offline_query(#iq{from = #jid{luser = U, lserver = S} = From, + to = #jid{luser = U, lserver = S} = _To, + type = Type, + sub_els = [#offline{purge = Purge, + items = Items, + fetch = Fetch}]} = IQ) -> case Type of get -> - case fxml:get_subtag(SubEl, <<"fetch">>) of - #xmlel{} -> - handle_offline_fetch(From); - false -> - handle_offline_items_view(From, SubEl) + if Fetch -> handle_offline_fetch(From); + true -> handle_offline_items_view(From, Items) end; set -> - case fxml:get_subtag(SubEl, <<"purge">>) of - #xmlel{} -> - delete_all_msgs(U, S); - false -> - handle_offline_items_remove(From, SubEl) + if Purge -> delete_all_msgs(U, S); + true -> handle_offline_items_remove(From, Items) end end, - IQ#iq{type = result, sub_el = []}; -handle_offline_query(_From, _To, #iq{sub_el = SubEl, lang = Lang} = IQ) -> + xmpp:make_iq_result(IQ); +handle_offline_query(#iq{lang = Lang} = IQ) -> Txt = <<"Query to another users is forbidden">>, - IQ#iq{type = error, sub_el = [SubEl, ?ERRT_FORBIDDEN(Lang, Txt)]}. + xmpp:make_error(IQ, xmpp:err_forbidden(Txt, Lang)). -handle_offline_items_view(JID, #xmlel{children = Items}) -> +-spec handle_offline_items_view(jid(), [offline_item()]) -> ok. +handle_offline_items_view(JID, Items) -> {U, S, R} = jid:tolower(JID), lists:foreach( - fun(Node) -> + fun(#offline_item{node = Node, action = view}) -> case fetch_msg_by_node(JID, Node) of {ok, OfflineMsg} -> case offline_msg_to_route(S, OfflineMsg) of @@ -367,40 +360,25 @@ handle_offline_items_view(JID, #xmlel{children = Items}) -> end; error -> ok - end - end, get_nodes_from_items(Items, <<"view">>)). - -handle_offline_items_remove(JID, #xmlel{children = Items}) -> - lists:foreach( - fun(Node) -> - remove_msg_by_node(JID, Node) - end, get_nodes_from_items(Items, <<"remove">>)). - -get_nodes_from_items(Items, Action) -> - lists:flatmap( - fun(#xmlel{name = <<"item">>, attrs = Attrs}) -> - case fxml:get_attr_s(<<"action">>, Attrs) of - Action -> - case fxml:get_attr_s(<<"node">>, Attrs) of - <<"">> -> - []; - TS -> - [TS] - end; - _ -> - [] end; (_) -> - [] + ok end, Items). -set_offline_tag(#xmlel{children = Els} = El, Node) -> - OfflineEl = #xmlel{name = <<"offline">>, - attrs = [{<<"xmlns">>, ?NS_FLEX_OFFLINE}], - children = [#xmlel{name = <<"item">>, - attrs = [{<<"node">>, Node}]}]}, - El#xmlel{children = [OfflineEl|Els]}. +-spec handle_offline_items_remove(jid(), [offline_item()]) -> ok. +handle_offline_items_remove(JID, Items) -> + lists:foreach( + fun(#offline_item{node = Node, action = remove}) -> + remove_msg_by_node(JID, Node); + (_) -> + ok + end, Items). +-spec set_offline_tag(message(), binary()) -> message(). +set_offline_tag(Msg, Node) -> + xmpp:set_subtag(Msg, #offline{items = [#offline_item{node = Node}]}). + +-spec handle_offline_fetch(jid()) -> ok. handle_offline_fetch(#jid{luser = U, lserver = S, lresource = R}) -> case ejabberd_sm:get_session_pid(U, S, R) of none -> @@ -414,6 +392,7 @@ handle_offline_fetch(#jid{luser = U, lserver = S, lresource = R}) -> end, read_message_headers(U, S)) end. +-spec fetch_msg_by_node(jid(), binary()) -> error | {ok, #offline_msg{}}. fetch_msg_by_node(To, Seq) -> case catch binary_to_integer(Seq) of I when is_integer(I), I >= 0 -> @@ -425,6 +404,7 @@ fetch_msg_by_node(To, Seq) -> error end. +-spec remove_msg_by_node(jid(), binary()) -> ok. remove_msg_by_node(To, Seq) -> case catch binary_to_integer(Seq) of I when is_integer(I), I>= 0 -> @@ -436,39 +416,38 @@ remove_msg_by_node(To, Seq) -> ok end. +-spec need_to_store(binary(), message()) -> boolean(). +need_to_store(_LServer, #message{type = error}) -> false; +need_to_store(_LServer, #message{type = groupchat}) -> false; +need_to_store(_LServer, #message{type = headline}) -> false; need_to_store(LServer, Packet) -> - Type = fxml:get_tag_attr_s(<<"type">>, Packet), - if (Type /= <<"error">>) and (Type /= <<"groupchat">>) - and (Type /= <<"headline">>) -> - case has_offline_tag(Packet) of - false -> - case check_store_hint(Packet) of - store -> - true; - no_store -> - false; - none -> - case gen_mod:get_module_opt( - LServer, ?MODULE, store_empty_body, - fun(V) when is_boolean(V) -> V; - (unless_chat_state) -> unless_chat_state - end, - unless_chat_state) of - false -> - fxml:get_subtag(Packet, <<"body">>) /= false; - unless_chat_state -> - not jlib:is_standalone_chat_state(Packet); - true -> - true - end - end; - true -> - false + case xmpp:has_subtag(Packet, #offline{}) of + false -> + case check_store_hint(Packet) of + store -> + true; + no_store -> + false; + none -> + case gen_mod:get_module_opt( + LServer, ?MODULE, store_empty_body, + fun(V) when is_boolean(V) -> V; + (unless_chat_state) -> unless_chat_state + end, + unless_chat_state) of + false -> + Packet#message.body /= []; + unless_chat_state -> + not xmpp_util:is_standalone_chat_state(Packet); + true -> + true + end end; - true -> + true -> false end. +-spec store_packet(jid(), jid(), message()) -> ok | stop. store_packet(From, To, Packet) -> case need_to_store(To#jid.lserver, Packet) of true -> @@ -476,18 +455,19 @@ store_packet(From, To, Packet) -> true -> #jid{luser = LUser, lserver = LServer} = To, TimeStamp = p1_time_compat:timestamp(), - #xmlel{children = Els} = Packet, - Expire = find_x_expire(TimeStamp, Els), + Expire = find_x_expire(TimeStamp, Packet), + El = xmpp:encode(Packet), gen_mod:get_module_proc(To#jid.lserver, ?PROCNAME) ! #offline_msg{us = {LUser, LServer}, timestamp = TimeStamp, expire = Expire, - from = From, to = To, packet = Packet}, + from = From, to = To, packet = El}, stop; _ -> ok end; false -> ok end. +-spec check_store_hint(message()) -> store | no_store | none. check_store_hint(Packet) -> case has_store_hint(Packet) of true -> @@ -501,89 +481,43 @@ check_store_hint(Packet) -> end end. +-spec has_store_hint(message()) -> boolean(). has_store_hint(Packet) -> - fxml:get_subtag_with_xmlns(Packet, <<"store">>, ?NS_HINTS) =/= false. + xmpp:has_subtag(Packet, #hint{type = 'store'}). +-spec has_no_store_hint(message()) -> boolean(). has_no_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. - -has_offline_tag(Packet) -> - fxml:get_subtag_with_xmlns(Packet, <<"offline">>, ?NS_FLEX_OFFLINE) =/= false. + xmpp:has_subtag(Packet, #hint{type = 'no-store'}) + orelse + xmpp:has_subtag(Packet, #hint{type = 'no-storage'}). %% Check if the packet has any content about XEP-0022 -check_event(From, To, Packet) -> - #xmlel{name = Name, attrs = Attrs, children = Els} = - Packet, - case find_x_event(Els) of - false -> true; - El -> - case fxml:get_subtag(El, <<"id">>) of - false -> - case fxml:get_subtag(El, <<"offline">>) of - false -> true; - _ -> - ID = case fxml:get_tag_attr_s(<<"id">>, Packet) of - <<"">> -> - #xmlel{name = <<"id">>, attrs = [], - children = []}; - S -> - #xmlel{name = <<"id">>, attrs = [], - children = [{xmlcdata, S}]} - end, - ejabberd_router:route(To, From, - #xmlel{name = Name, attrs = Attrs, - children = - [#xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, - ?NS_EVENT}], - children = - [ID, - #xmlel{name - = - <<"offline">>, - attrs - = - [], - children - = - []}]}]}), - true - end; - _ -> false - end +-spec check_event(jid(), jid(), message()) -> boolean(). +check_event(From, To, #message{id = ID} = Msg) -> + case xmpp:get_subtag(Msg, #xevent{}) of + false -> + true; + #xevent{id = undefined, offline = false} -> + true; + #xevent{id = undefined, offline = true} -> + NewMsg = Msg#message{sub_els = [#xevent{id = ID, offline = true}]}, + ejabberd_router:route(To, From, xmpp:set_from_to(NewMsg, To, From)), + true; + _ -> + false end. -%% Check if the packet has subelements about XEP-0022 -find_x_event([]) -> false; -find_x_event([{xmlcdata, _} | Els]) -> - find_x_event(Els); -find_x_event([El | Els]) -> - case fxml:get_tag_attr_s(<<"xmlns">>, El) of - ?NS_EVENT -> El; - _ -> find_x_event(Els) - end. - -find_x_expire(_, []) -> never; -find_x_expire(TimeStamp, [{xmlcdata, _} | Els]) -> - find_x_expire(TimeStamp, Els); -find_x_expire(TimeStamp, [El | Els]) -> - case fxml:get_tag_attr_s(<<"xmlns">>, El) of - ?NS_EXPIRE -> - Val = fxml:get_tag_attr_s(<<"seconds">>, El), - case catch jlib:binary_to_integer(Val) of - {'EXIT', _} -> never; - Int when Int > 0 -> - {MegaSecs, Secs, MicroSecs} = TimeStamp, - S = MegaSecs * 1000000 + Secs + Int, - MegaSecs1 = S div 1000000, - Secs1 = S rem 1000000, - {MegaSecs1, Secs1, MicroSecs}; - _ -> never - end; - _ -> find_x_expire(TimeStamp, Els) +-spec find_x_expire(erlang:timestamp(), message()) -> erlang:timestamp() | never. +find_x_expire(TimeStamp, Msg) -> + case xmpp:get_subtag(Msg, #expire{}) of + #expire{seconds = Int} -> + {MegaSecs, Secs, MicroSecs} = TimeStamp, + S = MegaSecs * 1000000 + Secs + Int, + MegaSecs1 = S div 1000000, + Secs1 = S rem 1000000, + {MegaSecs1, Secs1, MicroSecs}; + false -> + never end. resend_offline_messages(User, Server) -> @@ -612,10 +546,9 @@ pop_offline_messages(Ls, User, Server) -> end, lists:filter( fun(#offline_msg{packet = Pkt} = R) -> - #xmlel{children = Els} = Pkt, Expire = case R#offline_msg.expire of undefined -> - find_x_expire(TS, Els); + find_x_expire(TS, Pkt); Exp -> Exp end, @@ -648,17 +581,15 @@ remove_user(User, Server) -> %% Warn senders that their messages have been discarded: discard_warn_sender(Msgs) -> - lists:foreach(fun (#offline_msg{from = From, to = To, - packet = Packet}) -> - ErrText = <<"Your contact offline message queue is " - "full. The message has been discarded.">>, - Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet), - Err = jlib:make_error_reply(Packet, - ?ERRT_RESOURCE_CONSTRAINT(Lang, - ErrText)), - ejabberd_router:route(To, From, Err) - end, - Msgs). + lists:foreach( + fun(#offline_msg{from = From, to = To, packet = Packet}) -> + ErrText = <<"Your contact offline message queue is " + "full. The message has been discarded.">>, + Lang = xmpp:get_lang(Packet), + Err = xmpp:make_error( + Packet, xmpp:err_resource_constraint(ErrText, Lang)), + ejabberd_router:route(To, From, Err) + end, Msgs). webadmin_page(_, Host, #request{us = _US, path = [<<"user">>, U, <<"queue">>], @@ -668,29 +599,30 @@ webadmin_page(_, Host, webadmin_page(Acc, _, _) -> Acc. get_offline_els(LUser, LServer) -> - Mod = gen_mod:db_mod(LServer, ?MODULE), - Hdrs = Mod:read_message_headers(LUser, LServer), + Hdrs = read_message_headers(LUser, LServer), lists:map( fun({_Seq, From, To, Packet}) -> - jlib:replace_from_to(From, To, Packet) + xmpp:set_from_to(Packet, From, To) end, Hdrs). offline_msg_to_route(LServer, #offline_msg{} = R) -> - El = case R#offline_msg.timestamp of - undefined -> - R#offline_msg.packet; - TS -> - jlib:add_delay_info(R#offline_msg.packet, LServer, TS, - <<"Offline Storage">>) - end, - {route, R#offline_msg.from, R#offline_msg.to, El}. + Pkt = xmpp:decode(R#offline_msg.packet, [ignore_els]), + Pkt1 = case R#offline_msg.timestamp of + undefined -> + Pkt; + TS -> + xmpp_util:add_delay_info(Pkt, LServer, TS, + <<"Offline Storage">>) + end, + {route, R#offline_msg.from, R#offline_msg.to, Pkt1}. read_message_headers(LUser, LServer) -> Mod = gen_mod:db_mod(LServer, ?MODULE), lists:map( fun({Seq, From, To, El}) -> Node = integer_to_binary(Seq), - {Node, From, To, El} + Packet = xmpp:decode(El, [ignore_els]), + {Node, From, To, Packet} end, Mod:read_message_headers(LUser, LServer)). format_user_queue(Hdrs) -> @@ -826,6 +758,7 @@ webadmin_user(Acc, User, Server, Lang) -> ?INPUTT(<<"submit">>, <<"removealloffline">>, <<"Remove All Offline Messages">>)]. +-spec delete_all_msgs(binary(), binary()) -> {atomic, any()}. delete_all_msgs(User, Server) -> LUser = jid:nodeprep(User), LServer = jid:nameprep(Server), @@ -849,6 +782,7 @@ webadmin_user_parse_query(Acc, _Action, _User, _Server, Acc. %% Returns as integer the number of offline messages for a given user +-spec count_offline_messages(binary(), binary()) -> non_neg_integer(). count_offline_messages(User, Server) -> LUser = jid:nodeprep(User), LServer = jid:nameprep(Server), diff --git a/src/xmpp.erl b/src/xmpp.erl index ca6ed5e4c..f17eefa21 100644 --- a/src/xmpp.erl +++ b/src/xmpp.erl @@ -156,10 +156,12 @@ get_error(#iq{error = E}) -> E; get_error(#message{error = E}) -> E; get_error(#presence{error = E}) -> E. --spec get_els(iq() | message() | presence()) -> [xmpp_element() | xmlel()]. +-spec get_els(iq() | message() | presence()) -> [xmpp_element() | xmlel()]; + (xmlel()) -> [xmlel()]. get_els(#iq{sub_els = Els}) -> Els; get_els(#message{sub_els = Els}) -> Els; -get_els(#presence{sub_els = Els}) -> Els. +get_els(#presence{sub_els = Els}) -> Els; +get_els(#xmlel{children = Els}) -> [El || El = #xmlel{} <- Els]. -spec set_id(iq(), binary()) -> iq(); (message(), binary()) -> message(); diff --git a/src/xmpp_codec.erl b/src/xmpp_codec.erl index 568c5fbc7..976a7bfeb 100644 --- a/src/xmpp_codec.erl +++ b/src/xmpp_codec.erl @@ -15,6 +15,24 @@ decode(_el) -> decode(_el, []). decode({xmlel, _name, _attrs, _} = _el, Opts) -> IgnoreEls = proplists:get_bool(ignore_els, Opts), case {_name, get_attr(<<"xmlns">>, _attrs)} of + {<<"x">>, <<"jabber:x:expire">>} -> + decode_expire(<<"jabber:x:expire">>, IgnoreEls, _el); + {<<"x">>, <<"jabber:x:event">>} -> + decode_xevent(<<"jabber:x:event">>, IgnoreEls, _el); + {<<"id">>, <<"jabber:x:event">>} -> + decode_xevent_id(<<"jabber:x:event">>, IgnoreEls, _el); + {<<"composing">>, <<"jabber:x:event">>} -> + decode_xevent_composing(<<"jabber:x:event">>, IgnoreEls, + _el); + {<<"displayed">>, <<"jabber:x:event">>} -> + decode_xevent_displayed(<<"jabber:x:event">>, IgnoreEls, + _el); + {<<"delivered">>, <<"jabber:x:event">>} -> + decode_xevent_delivered(<<"jabber:x:event">>, IgnoreEls, + _el); + {<<"offline">>, <<"jabber:x:event">>} -> + decode_xevent_offline(<<"jabber:x:event">>, IgnoreEls, + _el); {<<"query">>, <<"jabber:iq:search">>} -> decode_search(<<"jabber:iq:search">>, IgnoreEls, _el); {<<"item">>, <<"jabber:iq:search">>} -> @@ -40,6 +58,9 @@ decode({xmlel, _name, _attrs, _} = _el, Opts) -> IgnoreEls, _el); {<<"store">>, <<"urn:xmpp:hints">>} -> decode_hint_store(<<"urn:xmpp:hints">>, IgnoreEls, _el); + {<<"no-storage">>, <<"urn:xmpp:hints">>} -> + decode_hint_no_storage(<<"urn:xmpp:hints">>, IgnoreEls, + _el); {<<"no-store">>, <<"urn:xmpp:hints">>} -> decode_hint_no_store(<<"urn:xmpp:hints">>, IgnoreEls, _el); @@ -1162,6 +1183,13 @@ decode({xmlel, _name, _attrs, _} = _el, Opts) -> is_known_tag({xmlel, _name, _attrs, _} = _el) -> case {_name, get_attr(<<"xmlns">>, _attrs)} of + {<<"x">>, <<"jabber:x:expire">>} -> true; + {<<"x">>, <<"jabber:x:event">>} -> true; + {<<"id">>, <<"jabber:x:event">>} -> true; + {<<"composing">>, <<"jabber:x:event">>} -> true; + {<<"displayed">>, <<"jabber:x:event">>} -> true; + {<<"delivered">>, <<"jabber:x:event">>} -> true; + {<<"offline">>, <<"jabber:x:event">>} -> true; {<<"query">>, <<"jabber:iq:search">>} -> true; {<<"item">>, <<"jabber:iq:search">>} -> true; {<<"email">>, <<"jabber:iq:search">>} -> true; @@ -1172,6 +1200,7 @@ is_known_tag({xmlel, _name, _attrs, _} = _el) -> {<<"no-permanent-store">>, <<"urn:xmpp:hints">>} -> true; {<<"store">>, <<"urn:xmpp:hints">>} -> true; + {<<"no-storage">>, <<"urn:xmpp:hints">>} -> true; {<<"no-store">>, <<"urn:xmpp:hints">>} -> true; {<<"no-copy">>, <<"urn:xmpp:hints">>} -> true; {<<"participant">>, <<"urn:xmpp:mix:0">>} -> true; @@ -2289,6 +2318,9 @@ encode({hint, 'no-copy'} = No_copy) -> encode({hint, 'no-store'} = No_store) -> encode_hint_no_store(No_store, [{<<"xmlns">>, <<"urn:xmpp:hints">>}]); +encode({hint, 'no-storage'} = No_storage) -> + encode_hint_no_storage(No_storage, + [{<<"xmlns">>, <<"urn:xmpp:hints">>}]); encode({hint, store} = Store) -> encode_hint_store(Store, [{<<"xmlns">>, <<"urn:xmpp:hints">>}]); @@ -2301,7 +2333,12 @@ encode({search_item, _, _, _, _, _} = Item) -> [{<<"xmlns">>, <<"jabber:iq:search">>}]); encode({search, _, _, _, _, _, _, _} = Query) -> encode_search(Query, - [{<<"xmlns">>, <<"jabber:iq:search">>}]). + [{<<"xmlns">>, <<"jabber:iq:search">>}]); +encode({xevent, _, _, _, _, _} = X) -> + encode_xevent(X, [{<<"xmlns">>, <<"jabber:x:event">>}]); +encode({expire, _, _} = X) -> + encode_expire(X, + [{<<"xmlns">>, <<"jabber:x:expire">>}]). get_name({last, _, _}) -> <<"query">>; get_name({version, _, _, _}) -> <<"query">>; @@ -2460,11 +2497,14 @@ get_name({mix_leave}) -> <<"leave">>; get_name({mix_participant, _, _}) -> <<"participant">>; get_name({hint, 'no-copy'}) -> <<"no-copy">>; get_name({hint, 'no-store'}) -> <<"no-store">>; +get_name({hint, 'no-storage'}) -> <<"no-storage">>; get_name({hint, store}) -> <<"store">>; get_name({hint, 'no-permanent-store'}) -> <<"no-permanent-store">>; get_name({search_item, _, _, _, _, _}) -> <<"item">>; -get_name({search, _, _, _, _, _, _, _}) -> <<"query">>. +get_name({search, _, _, _, _, _, _, _}) -> <<"query">>; +get_name({xevent, _, _, _, _, _}) -> <<"x">>; +get_name({expire, _, _}) -> <<"x">>. get_ns({last, _, _}) -> <<"jabber:iq:last">>; get_ns({version, _, _, _}) -> <<"jabber:iq:version">>; @@ -2685,13 +2725,16 @@ get_ns({mix_leave}) -> <<"urn:xmpp:mix:0">>; get_ns({mix_participant, _, _}) -> <<"urn:xmpp:mix:0">>; get_ns({hint, 'no-copy'}) -> <<"urn:xmpp:hints">>; get_ns({hint, 'no-store'}) -> <<"urn:xmpp:hints">>; +get_ns({hint, 'no-storage'}) -> <<"urn:xmpp:hints">>; get_ns({hint, store}) -> <<"urn:xmpp:hints">>; get_ns({hint, 'no-permanent-store'}) -> <<"urn:xmpp:hints">>; get_ns({search_item, _, _, _, _, _}) -> <<"jabber:iq:search">>; get_ns({search, _, _, _, _, _, _, _}) -> - <<"jabber:iq:search">>. + <<"jabber:iq:search">>; +get_ns({xevent, _, _, _, _, _}) -> <<"jabber:x:event">>; +get_ns({expire, _, _}) -> <<"jabber:x:expire">>. dec_int(Val) -> dec_int(Val, infinity, infinity). @@ -2908,6 +2951,9 @@ pp(hint, 1) -> [type]; pp(search_item, 5) -> [jid, first, last, nick, email]; pp(search, 7) -> [instructions, first, last, nick, email, items, xdata]; +pp(xevent, 5) -> + [offline, delivered, displayed, composing, id]; +pp(expire, 2) -> [seconds, stored]; pp(_, _) -> no. join([], _Sep) -> <<>>; @@ -2954,6 +3000,267 @@ dec_tzo(Val) -> M = jlib:binary_to_integer(M1), if H >= -12, H =< 12, M >= 0, M < 60 -> {H, M} end. +decode_expire(__TopXMLNS, __IgnoreEls, + {xmlel, <<"x">>, _attrs, _els}) -> + {Seconds, Stored} = decode_expire_attrs(__TopXMLNS, + _attrs, undefined, undefined), + {expire, Seconds, Stored}. + +decode_expire_attrs(__TopXMLNS, + [{<<"seconds">>, _val} | _attrs], _Seconds, Stored) -> + decode_expire_attrs(__TopXMLNS, _attrs, _val, Stored); +decode_expire_attrs(__TopXMLNS, + [{<<"stored">>, _val} | _attrs], Seconds, _Stored) -> + decode_expire_attrs(__TopXMLNS, _attrs, Seconds, _val); +decode_expire_attrs(__TopXMLNS, [_ | _attrs], Seconds, + Stored) -> + decode_expire_attrs(__TopXMLNS, _attrs, Seconds, + Stored); +decode_expire_attrs(__TopXMLNS, [], Seconds, Stored) -> + {decode_expire_attr_seconds(__TopXMLNS, Seconds), + decode_expire_attr_stored(__TopXMLNS, Stored)}. + +encode_expire({expire, Seconds, Stored}, + _xmlns_attrs) -> + _els = [], + _attrs = encode_expire_attr_stored(Stored, + encode_expire_attr_seconds(Seconds, + _xmlns_attrs)), + {xmlel, <<"x">>, _attrs, _els}. + +decode_expire_attr_seconds(__TopXMLNS, undefined) -> + erlang:error({xmpp_codec, + {missing_attr, <<"seconds">>, <<"x">>, __TopXMLNS}}); +decode_expire_attr_seconds(__TopXMLNS, _val) -> + case catch dec_int(_val, 0, infinity) of + {'EXIT', _} -> + erlang:error({xmpp_codec, + {bad_attr_value, <<"seconds">>, <<"x">>, __TopXMLNS}}); + _res -> _res + end. + +encode_expire_attr_seconds(_val, _acc) -> + [{<<"seconds">>, enc_int(_val)} | _acc]. + +decode_expire_attr_stored(__TopXMLNS, undefined) -> + undefined; +decode_expire_attr_stored(__TopXMLNS, _val) -> + case catch dec_int(_val, 0, infinity) of + {'EXIT', _} -> + erlang:error({xmpp_codec, + {bad_attr_value, <<"stored">>, <<"x">>, __TopXMLNS}}); + _res -> _res + end. + +encode_expire_attr_stored(undefined, _acc) -> _acc; +encode_expire_attr_stored(_val, _acc) -> + [{<<"stored">>, enc_int(_val)} | _acc]. + +decode_xevent(__TopXMLNS, __IgnoreEls, + {xmlel, <<"x">>, _attrs, _els}) -> + {Id, Displayed, Delivered, Offline, Composing} = + decode_xevent_els(__TopXMLNS, __IgnoreEls, _els, + undefined, false, false, false, false), + {xevent, Offline, Delivered, Displayed, Composing, Id}. + +decode_xevent_els(__TopXMLNS, __IgnoreEls, [], Id, + Displayed, Delivered, Offline, Composing) -> + {Id, Displayed, Delivered, Offline, Composing}; +decode_xevent_els(__TopXMLNS, __IgnoreEls, + [{xmlel, <<"offline">>, _attrs, _} = _el | _els], Id, + Displayed, Delivered, Offline, Composing) -> + case get_attr(<<"xmlns">>, _attrs) of + <<"">> when __TopXMLNS == <<"jabber:x:event">> -> + decode_xevent_els(__TopXMLNS, __IgnoreEls, _els, Id, + Displayed, Delivered, + decode_xevent_offline(__TopXMLNS, __IgnoreEls, _el), + Composing); + <<"jabber:x:event">> -> + decode_xevent_els(__TopXMLNS, __IgnoreEls, _els, Id, + Displayed, Delivered, + decode_xevent_offline(<<"jabber:x:event">>, + __IgnoreEls, _el), + Composing); + _ -> + decode_xevent_els(__TopXMLNS, __IgnoreEls, _els, Id, + Displayed, Delivered, Offline, Composing) + end; +decode_xevent_els(__TopXMLNS, __IgnoreEls, + [{xmlel, <<"delivered">>, _attrs, _} = _el | _els], Id, + Displayed, Delivered, Offline, Composing) -> + case get_attr(<<"xmlns">>, _attrs) of + <<"">> when __TopXMLNS == <<"jabber:x:event">> -> + decode_xevent_els(__TopXMLNS, __IgnoreEls, _els, Id, + Displayed, + decode_xevent_delivered(__TopXMLNS, __IgnoreEls, + _el), + Offline, Composing); + <<"jabber:x:event">> -> + decode_xevent_els(__TopXMLNS, __IgnoreEls, _els, Id, + Displayed, + decode_xevent_delivered(<<"jabber:x:event">>, + __IgnoreEls, _el), + Offline, Composing); + _ -> + decode_xevent_els(__TopXMLNS, __IgnoreEls, _els, Id, + Displayed, Delivered, Offline, Composing) + end; +decode_xevent_els(__TopXMLNS, __IgnoreEls, + [{xmlel, <<"displayed">>, _attrs, _} = _el | _els], Id, + Displayed, Delivered, Offline, Composing) -> + case get_attr(<<"xmlns">>, _attrs) of + <<"">> when __TopXMLNS == <<"jabber:x:event">> -> + decode_xevent_els(__TopXMLNS, __IgnoreEls, _els, Id, + decode_xevent_displayed(__TopXMLNS, __IgnoreEls, + _el), + Delivered, Offline, Composing); + <<"jabber:x:event">> -> + decode_xevent_els(__TopXMLNS, __IgnoreEls, _els, Id, + decode_xevent_displayed(<<"jabber:x:event">>, + __IgnoreEls, _el), + Delivered, Offline, Composing); + _ -> + decode_xevent_els(__TopXMLNS, __IgnoreEls, _els, Id, + Displayed, Delivered, Offline, Composing) + end; +decode_xevent_els(__TopXMLNS, __IgnoreEls, + [{xmlel, <<"composing">>, _attrs, _} = _el | _els], Id, + Displayed, Delivered, Offline, Composing) -> + case get_attr(<<"xmlns">>, _attrs) of + <<"">> when __TopXMLNS == <<"jabber:x:event">> -> + decode_xevent_els(__TopXMLNS, __IgnoreEls, _els, Id, + Displayed, Delivered, Offline, + decode_xevent_composing(__TopXMLNS, __IgnoreEls, + _el)); + <<"jabber:x:event">> -> + decode_xevent_els(__TopXMLNS, __IgnoreEls, _els, Id, + Displayed, Delivered, Offline, + decode_xevent_composing(<<"jabber:x:event">>, + __IgnoreEls, _el)); + _ -> + decode_xevent_els(__TopXMLNS, __IgnoreEls, _els, Id, + Displayed, Delivered, Offline, Composing) + end; +decode_xevent_els(__TopXMLNS, __IgnoreEls, + [{xmlel, <<"id">>, _attrs, _} = _el | _els], Id, + Displayed, Delivered, Offline, Composing) -> + case get_attr(<<"xmlns">>, _attrs) of + <<"">> when __TopXMLNS == <<"jabber:x:event">> -> + decode_xevent_els(__TopXMLNS, __IgnoreEls, _els, + decode_xevent_id(__TopXMLNS, __IgnoreEls, _el), + Displayed, Delivered, Offline, Composing); + <<"jabber:x:event">> -> + decode_xevent_els(__TopXMLNS, __IgnoreEls, _els, + decode_xevent_id(<<"jabber:x:event">>, __IgnoreEls, + _el), + Displayed, Delivered, Offline, Composing); + _ -> + decode_xevent_els(__TopXMLNS, __IgnoreEls, _els, Id, + Displayed, Delivered, Offline, Composing) + end; +decode_xevent_els(__TopXMLNS, __IgnoreEls, [_ | _els], + Id, Displayed, Delivered, Offline, Composing) -> + decode_xevent_els(__TopXMLNS, __IgnoreEls, _els, Id, + Displayed, Delivered, Offline, Composing). + +encode_xevent({xevent, Offline, Delivered, Displayed, + Composing, Id}, + _xmlns_attrs) -> + _els = lists:reverse('encode_xevent_$id'(Id, + 'encode_xevent_$displayed'(Displayed, + 'encode_xevent_$delivered'(Delivered, + 'encode_xevent_$offline'(Offline, + 'encode_xevent_$composing'(Composing, + [])))))), + _attrs = _xmlns_attrs, + {xmlel, <<"x">>, _attrs, _els}. + +'encode_xevent_$id'(undefined, _acc) -> _acc; +'encode_xevent_$id'(Id, _acc) -> + [encode_xevent_id(Id, []) | _acc]. + +'encode_xevent_$displayed'(false, _acc) -> _acc; +'encode_xevent_$displayed'(Displayed, _acc) -> + [encode_xevent_displayed(Displayed, []) | _acc]. + +'encode_xevent_$delivered'(false, _acc) -> _acc; +'encode_xevent_$delivered'(Delivered, _acc) -> + [encode_xevent_delivered(Delivered, []) | _acc]. + +'encode_xevent_$offline'(false, _acc) -> _acc; +'encode_xevent_$offline'(Offline, _acc) -> + [encode_xevent_offline(Offline, []) | _acc]. + +'encode_xevent_$composing'(false, _acc) -> _acc; +'encode_xevent_$composing'(Composing, _acc) -> + [encode_xevent_composing(Composing, []) | _acc]. + +decode_xevent_id(__TopXMLNS, __IgnoreEls, + {xmlel, <<"id">>, _attrs, _els}) -> + Cdata = decode_xevent_id_els(__TopXMLNS, __IgnoreEls, + _els, <<>>), + Cdata. + +decode_xevent_id_els(__TopXMLNS, __IgnoreEls, [], + Cdata) -> + decode_xevent_id_cdata(__TopXMLNS, Cdata); +decode_xevent_id_els(__TopXMLNS, __IgnoreEls, + [{xmlcdata, _data} | _els], Cdata) -> + decode_xevent_id_els(__TopXMLNS, __IgnoreEls, _els, + <>); +decode_xevent_id_els(__TopXMLNS, __IgnoreEls, + [_ | _els], Cdata) -> + decode_xevent_id_els(__TopXMLNS, __IgnoreEls, _els, + Cdata). + +encode_xevent_id(Cdata, _xmlns_attrs) -> + _els = encode_xevent_id_cdata(Cdata, []), + _attrs = _xmlns_attrs, + {xmlel, <<"id">>, _attrs, _els}. + +decode_xevent_id_cdata(__TopXMLNS, <<>>) -> undefined; +decode_xevent_id_cdata(__TopXMLNS, _val) -> _val. + +encode_xevent_id_cdata(undefined, _acc) -> _acc; +encode_xevent_id_cdata(_val, _acc) -> + [{xmlcdata, _val} | _acc]. + +decode_xevent_composing(__TopXMLNS, __IgnoreEls, + {xmlel, <<"composing">>, _attrs, _els}) -> + true. + +encode_xevent_composing(true, _xmlns_attrs) -> + _els = [], + _attrs = _xmlns_attrs, + {xmlel, <<"composing">>, _attrs, _els}. + +decode_xevent_displayed(__TopXMLNS, __IgnoreEls, + {xmlel, <<"displayed">>, _attrs, _els}) -> + true. + +encode_xevent_displayed(true, _xmlns_attrs) -> + _els = [], + _attrs = _xmlns_attrs, + {xmlel, <<"displayed">>, _attrs, _els}. + +decode_xevent_delivered(__TopXMLNS, __IgnoreEls, + {xmlel, <<"delivered">>, _attrs, _els}) -> + true. + +encode_xevent_delivered(true, _xmlns_attrs) -> + _els = [], + _attrs = _xmlns_attrs, + {xmlel, <<"delivered">>, _attrs, _els}. + +decode_xevent_offline(__TopXMLNS, __IgnoreEls, + {xmlel, <<"offline">>, _attrs, _els}) -> + true. + +encode_xevent_offline(true, _xmlns_attrs) -> + _els = [], + _attrs = _xmlns_attrs, + {xmlel, <<"offline">>, _attrs, _els}. + decode_search(__TopXMLNS, __IgnoreEls, {xmlel, <<"query">>, _attrs, _els}) -> {Xdata, Items, Instructions, Last, First, Nick, Email} = @@ -3459,6 +3766,16 @@ encode_hint_store({hint, store}, _xmlns_attrs) -> _attrs = _xmlns_attrs, {xmlel, <<"store">>, _attrs, _els}. +decode_hint_no_storage(__TopXMLNS, __IgnoreEls, + {xmlel, <<"no-storage">>, _attrs, _els}) -> + {hint, 'no-storage'}. + +encode_hint_no_storage({hint, 'no-storage'}, + _xmlns_attrs) -> + _els = [], + _attrs = _xmlns_attrs, + {xmlel, <<"no-storage">>, _attrs, _els}. + decode_hint_no_store(__TopXMLNS, __IgnoreEls, {xmlel, <<"no-store">>, _attrs, _els}) -> {hint, 'no-store'}. diff --git a/tools/xmpp_codec.spec b/tools/xmpp_codec.spec index 07d3a3dfd..a94b317de 100644 --- a/tools/xmpp_codec.spec +++ b/tools/xmpp_codec.spec @@ -2536,7 +2536,8 @@ #attr{name = <<"nick">>, label = '$nick'}]}). --record(hint, {type :: 'no-copy' | 'no-store' | 'store' | 'no-permanent-store'}). +-record(hint, {type :: 'no-copy' | 'no-store' | 'no-storage' | + 'store' | 'no-permanent-store'}). -type hint() :: #hint{}. -xml(hint_no_copy, @@ -2549,6 +2550,11 @@ xmlns = <<"urn:xmpp:hints">>, result = {hint, 'no-store'}}). +-xml(hint_no_storage, + #elem{name = <<"no-storage">>, + xmlns = <<"urn:xmpp:hints">>, + result = {hint, 'no-storage'}}). + -xml(hint_store, #elem{name = <<"store">>, xmlns = <<"urn:xmpp:hints">>, @@ -2621,6 +2627,56 @@ #ref{name = xdata, min = 0, max = 1, label = '$xdata'}]}). +-xml(xevent_offline, + #elem{name = <<"offline">>, + xmlns = <<"jabber:x:event">>, + result = true}). +-xml(xevent_delivered, + #elem{name = <<"delivered">>, + xmlns = <<"jabber:x:event">>, + result = true}). +-xml(xevent_displayed, + #elem{name = <<"displayed">>, + xmlns = <<"jabber:x:event">>, + result = true}). +-xml(xevent_composing, + #elem{name = <<"composing">>, + xmlns = <<"jabber:x:event">>, + result = true}). +-xml(xevent_id, + #elem{name = <<"id">>, + xmlns = <<"jabber:x:event">>, + cdata = #cdata{}, + result = '$cdata'}). + +-xml(xevent, + #elem{name = <<"x">>, + xmlns = <<"jabber:x:event">>, + result = {xevent, '$offline', '$delivered', '$displayed', + '$composing', '$id'}, + refs = [#ref{name = xevent_offline, min = 0, max = 1, + label = '$offline', default = false}, + #ref{name = xevent_delivered, min = 0, max = 1, + label = '$delivered', default = false}, + #ref{name = xevent_displayed, min = 0, max = 1, + label = '$displayed', default = false}, + #ref{name = xevent_composing, min = 0, max = 1, + label = '$composing', default = false}, + #ref{name = xevent_id, min = 0, max = 1, + label = '$id'}]}). + +-xml(expire, + #elem{name = <<"x">>, + xmlns = <<"jabber:x:expire">>, + result = {expire, '$seconds', '$stored'}, + attrs = [#attr{name = <<"seconds">>, + required = true, + dec = {dec_int, [0, infinity]}, + enc = {enc_int, []}}, + #attr{name = <<"stored">>, + dec = {dec_int, [0, infinity]}, + enc = {enc_int, []}}]}). + dec_tzo(Val) -> [H1, M1] = str:tokens(Val, <<":">>), H = jlib:binary_to_integer(H1),