]> granicus.if.org Git - ejabberd/commitdiff
Rewrite mod_offline to use XML generator
authorEvgeniy Khramtsov <ekhramtsov@process-one.net>
Tue, 19 Jul 2016 04:56:14 +0000 (07:56 +0300)
committerEvgeniy Khramtsov <ekhramtsov@process-one.net>
Tue, 19 Jul 2016 04:56:14 +0000 (07:56 +0300)
include/xmpp_codec.hrl
src/mod_offline.erl
src/xmpp.erl
src/xmpp_codec.erl
tools/xmpp_codec.spec

index 64a185a30c2ceaeb3701a74be24c5924ec2833d2..e348404b03f3d7d627a6eeb0b573a90e4ff227b0 100644 (file)
@@ -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, {}).
 -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()}).
 -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(),
                         starttls_proceed() |
                         sm_resumed() |
                         forwarded() |
+                        xevent() |
                         privacy_list() |
                         text() |
                         vcard_org() |
                         pubsub_options() |
                         compress() |
                         bytestreams() |
+                        muc_history() |
                         identity() |
                         feature_csi() |
                         muc_user_destroy() |
                         privacy_query() |
                         delay() |
-                        muc_history() |
                         vcard_tel() |
                         vcard_logo() |
                         disco_info() |
                         vcard_name() |
                         sm_resume() |
                         carbons_enable() |
+                        expire() |
                         pubsub_unsubscribe() |
                         muc_decline() |
                         chatstate() |
index 799605c69a3930216c95c78e51e7230b18aa307f..66edb6a7cf55f341f476a3cef908661177ab5282 100644 (file)
@@ -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">>)).
+             end;
+        (_) ->
+             ok
+      end, Items).
 
-handle_offline_items_remove(JID, #xmlel{children = Items}) ->
+-spec handle_offline_items_remove(jid(), [offline_item()]) -> ok.
+handle_offline_items_remove(JID, 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;
+      fun(#offline_item{node = Node, action = remove}) ->
+             remove_msg_by_node(JID, Node);
         (_) ->
-             []
+             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 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
-    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)
+-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.
 
-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),
index ca6ed5e4c7e0a33c68b5348cc981e0b967023082..f17eefa21ad4ed4e529ec3a75d4186da0c42669a 100644 (file)
@@ -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();
index 568c5fbc741236a3a59b6b1febfbc0cebe8e677b..976a7bfeb0b874ba32b6771d41d44ca7a483234f 100644 (file)
@@ -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,
+                        <<Cdata/binary, _data/binary>>);
+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'}.
index 07d3a3dfd112e6ca1ccc09aa22abb6860a7c7cc0..a94b317de4421b918c615b997f1b1e27a889cd23 100644 (file)
                    #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,
           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">>,
                   #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),