aboutsummaryrefslogtreecommitdiff
path: root/src/mod_offline.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/mod_offline.erl')
-rw-r--r--src/mod_offline.erl379
1 files changed, 165 insertions, 214 deletions
diff --git a/src/mod_offline.erl b/src/mod_offline.erl
index 1d9417117..dfe3c9e8e 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").
@@ -102,7 +102,7 @@
-callback read_message_headers(binary(), binary()) -> any().
-callback read_message(binary(), binary(), non_neg_integer()) ->
{ok, #offline_msg{}} | error.
--callback remove_message(binary(), binary(), non_neg_integer()) -> ok.
+-callback remove_message(binary(), binary(), non_neg_integer()) -> ok | {error, any()}.
-callback read_all_messages(binary(), binary()) -> [#offline_msg{}].
-callback remove_all_messages(binary(), binary()) -> {atomic, any()}.
-callback count_messages(binary(), binary()) -> non_neg_integer().
@@ -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,59 +297,70 @@ 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()], binary(), module(), binary(), binary()) -> [xdata()];
+ ([xdata()], jid(), jid(), binary(), 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)),
+ #jid{luser = U, lserver = S}, ?NS_FLEX_OFFLINE, Lang) ->
case ejabberd_sm:get_session_pid(U, S, R) of
Pid when is_pid(Pid) ->
Pid ! dont_ask_offline;
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 = flex_offline:encode(
+ [{number_of_messages, count_offline_messages(U, S)}],
+ fun(T) -> translate:translate(Lang, T) end)}];
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) ->
- case Type of
- get ->
- case fxml:get_subtag(SubEl, <<"fetch">>) of
- #xmlel{} ->
- handle_offline_fetch(From);
- false ->
- handle_offline_items_view(From, SubEl)
- end;
- set ->
- case fxml:get_subtag(SubEl, <<"purge">>) of
- #xmlel{} ->
- delete_all_msgs(U, S);
- false ->
- handle_offline_items_remove(From, SubEl)
- end
- end,
- IQ#iq{type = result, sub_el = []};
-handle_offline_query(_From, _To, #iq{sub_el = SubEl, lang = Lang} = IQ) ->
+-spec handle_offline_query(iq()) -> iq().
+handle_offline_query(#iq{from = #jid{luser = U1, lserver = S1},
+ to = #jid{luser = U2, lserver = S2},
+ lang = Lang,
+ sub_els = [#offline{}]} = IQ)
+ when {U1, S1} /= {U2, S2} ->
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_query(#iq{from = #jid{luser = U, lserver = S} = From,
+ to = #jid{luser = U, lserver = S} = _To,
+ type = Type, lang = Lang,
+ sub_els = [#offline{} = Offline]} = IQ) ->
+ case {Type, Offline} of
+ {get, #offline{fetch = true, items = [], purge = false}} ->
+ %% TODO: report database errors
+ handle_offline_fetch(From),
+ xmpp:make_iq_result(IQ);
+ {get, #offline{fetch = false, items = [_|_] = Items, purge = false}} ->
+ case handle_offline_items_view(From, Items) of
+ true -> xmpp:make_iq_result(IQ);
+ false -> xmpp:make_error(IQ, xmpp:err_item_not_found())
+ end;
+ {set, #offline{fetch = false, items = [], purge = true}} ->
+ case delete_all_msgs(U, S) of
+ {atomic, ok} ->
+ xmpp:make_iq_result(IQ);
+ _Err ->
+ Txt = <<"Database failure">>,
+ xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang))
+ end;
+ {set, #offline{fetch = false, items = [_|_] = Items, purge = false}} ->
+ case handle_offline_items_remove(From, Items) of
+ true -> xmpp:make_iq_result(IQ);
+ false -> xmpp:make_error(IQ, xmpp:err_item_not_found())
+ end;
+ _ ->
+ xmpp:make_error(IQ, xmpp:err_bad_request())
+ end;
+handle_offline_query(#iq{lang = Lang} = IQ) ->
+ Txt = <<"No module is handling this query">>,
+ xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)).
-handle_offline_items_view(JID, #xmlel{children = Items}) ->
+-spec handle_offline_items_view(jid(), [offline_item()]) -> boolean().
+handle_offline_items_view(JID, Items) ->
{U, S, R} = jid:tolower(JID),
- lists:foreach(
- fun(Node) ->
+ lists:foldl(
+ fun(#offline_item{node = Node, action = view}, Acc) ->
case fetch_msg_by_node(JID, Node) of
{ok, OfflineMsg} ->
case offline_msg_to_route(S, OfflineMsg) of
@@ -361,46 +371,28 @@ handle_offline_items_view(JID, #xmlel{children = Items}) ->
Pid ! {route, From, To, NewEl};
none ->
ok
- end;
+ end,
+ Acc or true;
error ->
- ok
+ Acc or false
end;
error ->
- ok
+ Acc or false
end
- end, get_nodes_from_items(Items, <<"view">>)).
+ end, false, Items).
-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;
- (_) ->
- []
- 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()]) -> boolean().
+handle_offline_items_remove(JID, Items) ->
+ lists:foldl(
+ fun(#offline_item{node = Node, action = remove}, Acc) ->
+ Acc or remove_msg_by_node(JID, Node)
+ end, false, 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 +406,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,33 +418,32 @@ fetch_msg_by_node(To, Seq) ->
error
end.
+-spec remove_msg_by_node(jid(), binary()) -> boolean().
remove_msg_by_node(To, Seq) ->
case catch binary_to_integer(Seq) of
I when is_integer(I), I>= 0 ->
LUser = To#jid.luser,
LServer = To#jid.lserver,
Mod = gen_mod:db_mod(LServer, ?MODULE),
- Mod:remove_message(LUser, LServer, I);
+ Mod:remove_message(LUser, LServer, I),
+ true;
_ ->
- ok
+ false
end.
-need_to_store(LServer, Packet) ->
- case has_offline_tag(Packet) of
+-spec need_to_store(binary(), message()) -> boolean().
+need_to_store(_LServer, #message{type = error}) -> false;
+need_to_store(LServer, #message{type = Type} = Packet) ->
+ case xmpp:has_subtag(Packet, #offline{}) of
false ->
- case {check_store_hint(Packet),
- fxml:get_tag_attr_s(<<"type">>, Packet)} of
- {_Hint, <<"error">>} ->
- false;
- {store, _Type} ->
+ case check_store_hint(Packet) of
+ store ->
true;
- {no_store, _Type} ->
+ no_store ->
false;
- {none, <<"groupchat">>} ->
+ none when Type == headline; Type == groupchat ->
false;
- {none, <<"headline">>} ->
- false;
- {none, _Type} ->
+ none ->
case gen_mod:get_module_opt(
LServer, ?MODULE, store_empty_body,
fun(V) when is_boolean(V) -> V;
@@ -461,15 +453,16 @@ need_to_store(LServer, Packet) ->
true ->
true;
false ->
- fxml:get_subtag(Packet, <<"body">>) /= false;
+ Packet#message.body /= [];
unless_chat_state ->
- not jlib:is_standalone_chat_state(Packet)
+ not xmpp_util:is_standalone_chat_state(Packet)
end
end;
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 ->
@@ -482,14 +475,12 @@ store_packet(From, To, Packet) ->
ok;
NewPacket ->
TimeStamp = p1_time_compat:timestamp(),
- #xmlel{children = Els} = NewPacket,
- Expire = find_x_expire(TimeStamp, Els),
+ Expire = find_x_expire(TimeStamp, NewPacket),
+ El = xmpp:encode(NewPacket),
gen_mod:get_module_proc(To#jid.lserver, ?PROCNAME) !
- #offline_msg{us = {LUser, LServer},
- timestamp = TimeStamp,
- expire = Expire,
- from = From, to = To,
- packet = NewPacket},
+ #offline_msg{us = {LUser, LServer},
+ timestamp = TimeStamp, expire = Expire,
+ from = From, to = To, packet = El},
stop
end;
_ -> ok
@@ -497,6 +488,7 @@ store_packet(From, To, Packet) ->
false -> ok
end.
+-spec check_store_hint(message()) -> store | no_store | none.
check_store_hint(Packet) ->
case has_store_hint(Packet) of
true ->
@@ -510,89 +502,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) ->
@@ -608,6 +554,9 @@ resend_offline_messages(User, Server) ->
_ -> ok
end.
+-spec pop_offline_messages([{route, jid(), jid(), message()}],
+ binary(), binary()) ->
+ [{route, jid(), jid(), message()}].
pop_offline_messages(Ls, User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
@@ -621,10 +570,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,
@@ -647,27 +595,27 @@ remove_old_messages(Days, Server) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:remove_old_messages(Days, LServer).
+-spec remove_user(binary(), binary()) -> ok.
remove_user(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
Mod = gen_mod:db_mod(LServer, ?MODULE),
- Mod:remove_user(LUser, LServer).
+ Mod:remove_user(LUser, LServer),
+ ok.
%% Helper functions:
%% 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">>],
@@ -677,29 +625,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, ?NS_CLIENT, [ignore_els]),
+ Pkt1 = case R#offline_msg.timestamp of
+ undefined ->
+ Pkt;
+ TS ->
+ xmpp_util:add_delay_info(Pkt, jid:make(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, ?NS_CLIENT, [ignore_els]),
+ {Node, From, To, Packet}
end, Mod:read_message_headers(LUser, LServer)).
format_user_queue(Hdrs) ->
@@ -835,6 +784,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),
@@ -858,6 +808,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),