aboutsummaryrefslogtreecommitdiff
path: root/src/mod_applepush.erl
diff options
context:
space:
mode:
authorChristophe Romain <christophe.romain@process-one.net>2012-09-11 15:45:59 +0200
committerChristophe Romain <christophe.romain@process-one.net>2012-09-11 15:45:59 +0200
commit011535f0de1a14d6f5f411035bff9eeafec1c612 (patch)
treee60951904fbdc14dc126450c4d7515f51188d4b7 /src/mod_applepush.erl
parentMerge branch '2.1.x' into 2.2.x (diff)
binary refactoring
Diffstat (limited to 'src/mod_applepush.erl')
-rw-r--r--src/mod_applepush.erl830
1 files changed, 830 insertions, 0 deletions
diff --git a/src/mod_applepush.erl b/src/mod_applepush.erl
new file mode 100644
index 000000000..c5a0eaea0
--- /dev/null
+++ b/src/mod_applepush.erl
@@ -0,0 +1,830 @@
+%%%----------------------------------------------------------------------
+%%% File : mod_applepush.erl
+%%% Author : Alexey Shchepin <alexey@process-one.net>
+%%% Purpose : Push module support
+%%% Created : 5 Jun 2009 by Alexey Shchepin <alexey@process-one.net>
+%%%
+%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
+%%%----------------------------------------------------------------------
+
+-module(mod_applepush).
+-author('alexey@process-one.net').
+
+-behaviour(gen_mod).
+
+-export([start/2,
+ stop/1,
+ push_notification/8,
+ push_notification_with_custom_fields/9,
+ enable_offline_notification/5,
+ disable_notification/3,
+ receive_offline_packet/3,
+ %% other clients may be online, but we still want to push to this one
+ send_offline_packet_notification/5,
+ resend_badge/1,
+ multi_resend_badge/1,
+ offline_resend_badge/0,
+ device_reset_badge/5,
+ apply_on_all_ios_for/3,
+ remove_user/2,
+ process_sm_iq/3]).
+
+
+%% Debug commands
+-export([get_tokens_by_jid/1]).
+
+
+-include("ejabberd.hrl").
+-include("jlib.hrl").
+-include("mod_privacy.hrl").
+
+-define(NS_P1_PUSH, <<"p1:push">>).
+-define(NS_P1_PUSHED, <<"p1:pushed">>).
+-define(NS_P1_ATTACHMENT, <<"http://process-one.net/attachement">>).
+
+-record(applepush_cache, {us, device_id, options}).
+
+
+start(Host, Opts) ->
+ case init_host(Host) of
+ true ->
+ mnesia:create_table(
+ applepush_cache,
+ [{disc_copies, [node()]},
+ {attributes, record_info(fields, applepush_cache)}, {type, bag}]),
+ mnesia:add_table_index(applepush_cache, device_id),
+ mnesia:add_table_copy(applepush_cache, node(), ram_copies),
+ ejabberd_hooks:add(p1_push_notification, Host,
+ ?MODULE, push_notification, 50),
+ ejabberd_hooks:add(p1_push_notification_custom, Host,
+ ?MODULE, push_notification_with_custom_fields, 50),
+ ejabberd_hooks:add(p1_push_enable_offline, Host,
+ ?MODULE, enable_offline_notification, 50),
+ ejabberd_hooks:add(p1_push_disable, Host,
+ ?MODULE, disable_notification, 50),
+ ejabberd_hooks:add(remove_user, Host,
+ ?MODULE, remove_user, 50),
+ ejabberd_hooks:add(offline_message_hook, Host,
+ ?MODULE, receive_offline_packet, 35),
+ IQDisc = gen_mod:get_opt(
+ iqdisc, Opts, fun gen_iq_handler:check_type/1,
+ one_queue),
+ gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_P1_PUSH,
+ ?MODULE, process_sm_iq, IQDisc);
+ false ->
+ ?ERROR_MSG("Cannot start ~s on host ~s.
+ Check you had the correct license for the domain and # of
+ registered users", [?MODULE, Host]),
+ ok
+ end.
+
+stop(Host) ->
+ ejabberd_hooks:delete(p1_push_notification, Host,
+ ?MODULE, push_notification, 50),
+ ejabberd_hooks:delete(p1_push_notification_custom, Host,
+ ?MODULE, push_notification_with_custom_fields, 50),
+ ejabberd_hooks:delete(p1_push_enable_offline, Host,
+ ?MODULE, enable_offline_notification, 50),
+ ejabberd_hooks:delete(p1_push_disable, Host,
+ ?MODULE, disable_notification, 50),
+ ejabberd_hooks:delete(remove_user, Host,
+ ?MODULE, remove_user, 50),
+ ejabberd_hooks:delete(offline_message_hook, Host,
+ ?MODULE, receive_offline_packet, 35),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_P1_PUSH).
+
+
+
+push_notification(Host, JID, Notification, Msg, Unread, Sound, AppID, Sender) ->
+ push_notification_with_custom_fields(Host, JID, Notification, Msg, Unread, Sound, AppID, Sender, []).
+
+push_notification_with_custom_fields(Host, JID, Notification, Msg, Unread, Sound, AppID, Sender, CustomFields) ->
+ Type = xml:get_path_s(Notification, [{elem, <<"type">>}, cdata]),
+ case Type of
+ <<"applepush">> ->
+ DeviceID = xml:get_path_s(Notification, [{elem, <<"id">>}, cdata]),
+ PushPacket = build_push_packet(DeviceID, Msg, Unread, Sound, Sender, JID, CustomFields),
+ route_push_notification(Host, JID, AppID, PushPacket),
+ stop;
+ _ ->
+ ok
+ end.
+build_push_packet(DeviceID, Msg, Unread, Sound, Sender, JID, CustomFields) ->
+ Badge = jlib:integer_to_binary(Unread),
+ SSound =
+ if
+ Sound -> <<"true">>;
+ true -> <<"false">>
+ end,
+ Receiver = jlib:jid_to_string(JID),
+ #xmlel{name = <<"message">>,
+ attrs = [],
+ children =
+ [#xmlel{name = <<"push">>, attrs = [{<<"xmlns">>, ?NS_P1_PUSH}],
+ children =
+ [#xmlel{name = <<"id">>, attrs = [],
+ children = [{xmlcdata, DeviceID}]},
+ #xmlel{name = <<"msg">>, attrs = [],
+ children = [{xmlcdata, Msg}]},
+ #xmlel{name = <<"badge">>, attrs = [],
+ children = [{xmlcdata, Badge}]},
+ #xmlel{name = <<"sound">>, attrs = [],
+ children = [{xmlcdata, SSound}]},
+ #xmlel{name = <<"from">>, attrs = [],
+ children = [{xmlcdata, Sender}]},
+ #xmlel{name = <<"to">>, attrs = [],
+ children = [{xmlcdata, Receiver}]}] ++
+ build_custom(CustomFields)
+ }
+ ]}.
+
+build_custom([]) -> [];
+build_custom(Fields) ->
+ [#xmlel{name = <<"custom">>, attrs = [],
+ children =
+ [#xmlel{name = <<"field">>, attrs = [{<<"name">>, Name}],
+ children =
+ [{xmlcdata, Value}]} || {Name, Value} <- Fields]}].
+
+
+route_push_notification(Host, JID, AppId, PushPacket) ->
+ PushService = get_push_service(Host, JID, AppId),
+ ServiceJID = jlib:make_jid(<<"">>, PushService, <<"">>),
+ ejabberd_router:route(JID, ServiceJID, PushPacket).
+
+
+enable_offline_notification(JID, Notification, SendBody, SendFrom, AppID1) ->
+ Type = xml:get_path_s(Notification, [{elem, <<"type">>}, cdata]),
+ case Type of
+ <<"applepush">> ->
+ DeviceID = xml:get_path_s(Notification, [{elem, <<"id">>}, cdata]),
+ case catch erlang:list_to_integer(binary_to_list(DeviceID), 16) of
+ ID1 when is_integer(ID1) ->
+ AppID =
+ case xml:get_path_s(Notification,
+ [{elem, <<"appid">>}, cdata]) of
+ <<"">> -> AppID1;
+ A -> A
+ end,
+ {MegaSecs, Secs, _MicroSecs} = now(),
+ TimeStamp = MegaSecs * 1000000 + Secs,
+ store_cache(JID, ID1, AppID, SendBody, SendFrom, TimeStamp);
+ _ ->
+ ok
+ end,
+ stop;
+ _ ->
+ ok
+ end.
+
+disable_notification(JID, Notification, _AppID) ->
+ Type = xml:get_path_s(Notification, [{elem, <<"type">>}, cdata]),
+ case Type of
+ <<"applepush">> ->
+ DeviceID = xml:get_path_s(Notification, [{elem, <<"id">>}, cdata]),
+ case catch erlang:list_to_integer(binary_to_list(DeviceID), 16) of
+ ID1 when is_integer(ID1) ->
+ delete_cache(JID, ID1),
+ stop;
+ _ ->
+ ok
+ end;
+ _ ->
+ ok
+ end.
+
+do_send_offline_packet_notification(From, To, Packet, ID, AppID, SendBody, SendFrom, BadgeCount) ->
+ Host = To#jid.lserver,
+ Body1 = xml:get_path_s(Packet, [{elem, <<"body">>}, cdata]),
+ Body =
+ case check_x_attachment(Packet) of
+ true ->
+ case Body1 of
+ <<"">> -> <<238, 128, 136>>;
+ _ ->
+ <<238, 128, 136, 32, Body1/binary>>
+ end;
+ false ->
+ Body1
+ end,
+ Pushed = check_x_pushed(Packet),
+ PushService = get_push_service(Host, To, AppID),
+ ServiceJID = jlib:make_jid(<<"">>, PushService, <<"">>),
+ if
+ Body == <<"">>;
+ Pushed ->
+ ok;
+ true ->
+ BFrom = jlib:jid_remove_resource(From),
+ SFrom = jlib:jid_to_string(BFrom),
+ IncludeBody =
+ case SendBody of
+ all ->
+ true;
+ first_per_user ->
+ BadgeCount == 1;
+ first ->
+ BadgeCount == 1;
+ none ->
+ false
+ end,
+ Msg =
+ if
+ IncludeBody ->
+ CBody = utf8_cut(Body, 100),
+ case SendFrom of
+ jid ->
+ prepend_sender(SFrom, CBody);
+ username ->
+ UnescapedFrom = unescape(BFrom#jid.user),
+ prepend_sender(
+ UnescapedFrom, CBody);
+ name ->
+ Name = get_roster_name(
+ To, BFrom),
+ prepend_sender(Name, CBody);
+ _ -> CBody
+ end;
+ true ->
+ <<"">>
+ end,
+ SSound =
+ if
+ IncludeBody -> <<"true">>;
+ true -> <<"false">>
+ end,
+ Badge = jlib:integer_to_binary(BadgeCount),
+ DeviceID = jlib:integer_to_binary(ID, 16),
+ STo = jlib:jid_to_string(To),
+ Packet1 =
+ #xmlel{name = <<"message">>,
+ attrs = [],
+ children =
+ [#xmlel{name = <<"push">>,
+ attrs = [{<<"xmlns">>, ?NS_P1_PUSH}],
+ children =
+ [#xmlel{name = <<"id">>, attrs = [],
+ children = [{xmlcdata, DeviceID}]},
+ #xmlel{name = <<"msg">>, attrs = [],
+ children = [{xmlcdata, Msg}]},
+ #xmlel{name = <<"badge">>, attrs = [],
+ children = [{xmlcdata, Badge}]},
+ #xmlel{name = <<"sound">>, attrs = [],
+ children = [{xmlcdata, SSound}]},
+ #xmlel{name = <<"from">>, attrs = [],
+ children = [{xmlcdata, SFrom}]},
+ #xmlel{name = <<"to">>, attrs = [],
+ children = [{xmlcdata, STo}]}]
+ }
+ ]},
+ ejabberd_router:route(To, ServiceJID, Packet1)
+ end.
+
+send_offline_packet_notification(From, To, Packet, SDeviceID, BadgeCount) ->
+ DeviceID =
+ if
+ is_binary(SDeviceID) -> binary_to_list(SDeviceID);
+ is_list(SDeviceID) -> SDeviceID
+ end,
+ case catch erlang:list_to_integer(DeviceID, 16) of
+ ID1 when is_integer(ID1) ->
+ case lookup_cache(To, ID1) of
+ [{ID, AppID, SendBody, SendFrom}] ->
+ do_send_offline_packet_notification(From, To, Packet, ID, AppID, SendBody, SendFrom, BadgeCount);
+ _ ->
+ ok
+ end;
+ _ ->
+ ok
+ end.
+receive_offline_packet(From, To, Packet) ->
+ ?DEBUG("mod_applepush offline~n\tfrom ~p~n\tto ~p~n\tpacket ~P~n",
+ [From, To, Packet, 8]),
+ Host = To#jid.lserver,
+ case gen_mod:is_loaded(Host, mod_applepush) of
+ true ->
+ case lookup_cache(To) of
+ false ->
+ ok;
+ DeviceList ->
+ Offline = ejabberd_hooks:run_fold(
+ count_offline_messages,
+ Host,
+ 0,
+ [To#jid.luser, Host]) + 1,
+ lists:foreach(fun({ID, AppID, SendBody, SendFrom}) ->
+ ?DEBUG("lookup: ~p~n", [{ID, AppID, SendBody, SendFrom}]),
+ do_send_offline_packet_notification(From, To, Packet, ID, AppID, SendBody, SendFrom, Offline)
+ end, DeviceList)
+ end;
+ false ->
+ ok
+ end.
+
+
+process_sm_iq(From, To, #iq{type = Type, sub_el = SubEl} = IQ) ->
+ case {Type, SubEl} of
+ {set, #xmlel{name = <<"disable">>}} ->
+ Host = To#jid.lserver,
+ SDeviceID = xml:get_tag_attr_s(<<"id">>, SubEl),
+ DeviceID =
+ erlang:list_to_integer(binary_to_list(SDeviceID), 16),
+ case lookup_cache(To, DeviceID) of
+ [{_ID, AppID, _SendBody, _SendFrom}] ->
+ PushService = get_push_service(Host, To, AppID),
+ ServiceJID = jlib:make_jid(<<"">>, PushService, <<"">>),
+ if
+ From#jid.lserver == ServiceJID#jid.lserver ->
+ delete_cache(To, DeviceID),
+ IQ#iq{type = result, sub_el = []};
+ true ->
+ IQ#iq{type = error,
+ sub_el = [SubEl, ?ERR_NOT_ALLOWED]}
+ end
+ end;
+ {set, _} ->
+ IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+ {get, _} ->
+ IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}
+ end.
+
+
+device_reset_badge(Host, To, DeviceID, AppID, Badge) ->
+ ?INFO_MSG("Sending reset badge (~p) push to ~p ~p", [Badge, To, DeviceID]),
+ PushService = get_push_service(Host, To, AppID),
+ ServiceJID = jlib:make_jid(<<"">>, PushService, <<"">>),
+ LBadge = jlib:integer_to_binary(Badge),
+ Packet1 =
+ #xmlel{name = <<"message">>, attrs = [],
+ children =
+ [#xmlel{name = <<"push">>, attrs = [{<<"xmlns">>, ?NS_P1_PUSH}],
+ children =
+ [#xmlel{name = <<"id">>, attrs = [],
+ children =
+ [{xmlcdata, DeviceID}]},
+ #xmlel{name = <<"badge">>, attrs = [],
+ children =
+ [{xmlcdata, LBadge}]}]}]},
+ ejabberd_router:route(To, ServiceJID, Packet1).
+
+
+
+resend_badge(To) ->
+ Host = To#jid.lserver,
+ case gen_mod:is_loaded(Host, mod_applepush) of
+ true ->
+ case lookup_cache(To) of
+ false ->
+ {error, "no cached data for the user"};
+ DeviceList ->
+ lists:foreach(fun({ID, AppID, SendBody, SendFrom}) ->
+ ?DEBUG("lookup: ~p~n", [{ID, AppID, SendBody, SendFrom}]),
+ PushService = get_push_service(Host, To, AppID),
+ ServiceJID = jlib:make_jid(<<"">>, PushService, <<"">>),
+ Offline = ejabberd_hooks:run_fold(
+ count_offline_messages,
+ Host,
+ 0,
+ [To#jid.luser, Host]),
+ if
+ Offline == 0 ->
+ ok;
+ true ->
+ Badge = jlib:integer_to_binary(Offline),
+ DeviceID = jlib:integer_to_binary(ID, 16),
+ Packet1 =
+ #xmlel{name = <<"message">>,
+ attrs = [],
+ children =
+ [#xmlel{name = <<"push">>,
+ attrs = [{<<"xmlns">>, ?NS_P1_PUSH}],
+ children =
+ [#xmlel{name = <<"id">>,
+ attrs = [],
+ children =
+ [{xmlcdata, DeviceID}]},
+ #xmlel{name = <<"badge">>,
+ attrs = [],
+ children =
+ [{xmlcdata, Badge}]}]}]},
+ ejabberd_router:route(To, ServiceJID, Packet1)
+ end
+ end, DeviceList)
+ end;
+ false ->
+ {error, "mod_applepush is not loaded"}
+ end.
+
+multi_resend_badge(JIDs) ->
+ lists:foreach(fun resend_badge/1, JIDs).
+
+offline_resend_badge() ->
+ USs = mnesia:dirty_all_keys(applepush_cache),
+ JIDs = lists:map(fun({U, S}) -> jlib:make_jid(U, S, <<"">>) end, USs),
+ multi_resend_badge(JIDs).
+
+lookup_cache(JID) ->
+ case get_storage(JID#jid.lserver) of
+ mnesia ->
+ lookup_cache_mnesia(JID);
+ sql ->
+ lookup_cache_sql(JID)
+ end.
+
+lookup_cache_mnesia(JID) ->
+ #jid{luser = LUser, lserver = LServer} = JID,
+ LUS = {LUser, LServer},
+ do_lookup_cache_mnesia(#applepush_cache{us = LUS, _ = '_'}).
+
+lookup_cache_sql(JID) ->
+ #jid{luser = LUser, lserver = LServer} = JID,
+ Username = ejabberd_odbc:escape(LUser),
+ do_lookup_cache_sql(
+ LServer,
+ [<<"select device_id, app_id, send_body, send_from from applepush_cache "
+ "where username='">>, Username, <<"'">>]).
+
+lookup_cache(JID, DeviceID) ->
+ case get_storage(JID#jid.lserver) of
+ mnesia ->
+ lookup_cache_mnesia(JID, DeviceID);
+ sql ->
+ lookup_cache_sql(JID, DeviceID)
+ end.
+
+lookup_cache_mnesia(JID, DeviceID) ->
+ #jid{luser = LUser, lserver = LServer} = JID,
+ LUS = {LUser, LServer},
+ do_lookup_cache_mnesia(#applepush_cache{device_id = DeviceID, us = LUS, _ = '_'}).
+
+lookup_cache_sql(JID, DeviceID) ->
+ #jid{luser = LUser, lserver = LServer} = JID,
+ Username = ejabberd_odbc:escape(LUser),
+ SDeviceID = erlang:integer_to_list(DeviceID, 16),
+ do_lookup_cache_sql(
+ LServer,
+ [<<"select device_id, app_id, send_body, send_from from applepush_cache "
+ "where username='">>, Username, <<"' and device_id='">>,
+ SDeviceID, <<"'">>]).
+
+do_lookup_cache_mnesia(MatchSpec) ->
+ case mnesia:dirty_match_object(MatchSpec) of
+ EntryList when is_list(EntryList) ->
+ lists:map(fun(#applepush_cache{device_id = DeviceID, options = Options}) ->
+ AppID = proplists:get_value(appid, Options, <<"applepush.localhost">>),
+ SendBody = proplists:get_value(send_body, Options, none),
+ SendFrom = proplists:get_value(send_from, Options, true),
+ {DeviceID, AppID, SendBody, SendFrom}
+ end, EntryList);
+ _ ->
+ false
+ end.
+
+do_lookup_cache_sql(LServer, Query) ->
+ case ejabberd_odbc:sql_query(LServer, Query) of
+ {selected, [<<"device_id">>, <<"app_id">>, <<"send_body">>, <<"send_from">>],
+ EntryList} ->
+ lists:map(
+ fun({SDeviceID, AppID, SSendBody, SSendFrom}) ->
+ DeviceID = erlang:list_to_integer(SDeviceID, 16),
+ SendBody =
+ case SSendBody of
+ <<"A">> -> all;
+ <<"U">> -> first_per_user;
+ <<"F">> -> first;
+ _ -> none
+ end,
+ SendFrom =
+ case SSendFrom of
+ <<"J">> -> jid;
+ <<"U">> -> username;
+ <<"N">> -> name;
+ _ -> none
+ end,
+ {DeviceID, AppID, SendBody, SendFrom}
+ end, EntryList);
+ _ ->
+ false
+ end.
+
+
+store_cache(JID, DeviceID, AppID, SendBody, SendFrom, TimeStamp) ->
+ case get_storage(JID#jid.lserver) of
+ mnesia ->
+ Options =
+ [{appid, AppID},
+ {send_body, SendBody},
+ {send_from, SendFrom},
+ {timestamp, TimeStamp}],
+ store_cache_mnesia(JID, DeviceID, Options);
+ sql ->
+ store_cache_sql(JID, DeviceID, AppID, SendBody, SendFrom)
+ end.
+
+
+store_cache_mnesia(JID, DeviceID, Options) ->
+ #jid{luser = LUser, lserver = LServer} = JID,
+ LUS = {LUser, LServer},
+ R = #applepush_cache{us = LUS,
+ device_id = DeviceID,
+ options = Options},
+ case catch mnesia:dirty_read(applepush_cache, LUS) of
+ [R] ->
+ ok;
+ _ ->
+ lists:foreach(
+ fun(R1) ->
+ mnesia:dirty_delete_object(R1)
+ end,
+ mnesia:dirty_index_read(
+ applepush_cache, DeviceID, #applepush_cache.device_id)),
+ catch mnesia:dirty_write(R)
+ end.
+
+store_cache_sql(JID, DeviceID, AppID, SendBody, SendFrom) ->
+ #jid{luser = LUser, lserver = LServer} = JID,
+ Username = ejabberd_odbc:escape(LUser),
+ SDeviceID = erlang:integer_to_list(DeviceID, 16),
+ SAppID = ejabberd_odbc:escape(AppID),
+ SSendBody =
+ case SendBody of
+ all -> <<"A">>;
+ first_per_user -> <<"U">>;
+ first -> <<"F">>;
+ _ -> <<"-">>
+ end,
+ SSendFrom =
+ case SendFrom of
+ jid -> <<"J">>;
+ username -> <<"U">>;
+ name -> <<"N">>;
+ _ -> <<"-">>
+ end,
+ F = fun() ->
+ ejabberd_odbc:sql_query_t(
+ [<<"delete from applepush_cache "
+ "where username='">>, Username, <<"' and ">>,
+ <<"device_id='">>, SDeviceID, <<"';">>]),
+ ejabberd_odbc:sql_query_t(
+ [<<"insert into applepush_cache(username, device_id, app_id, "
+ " send_body, send_from) "
+ "values ('">>, Username, <<"', '">>, SDeviceID, <<"', '">>,
+ SAppID, <<"', '">>, SSendBody, <<"', '">>, SSendFrom, <<"');">>])
+ end,
+ odbc_queries:sql_transaction(LServer, F).
+
+delete_cache(JID, DeviceID) ->
+ case get_storage(JID#jid.lserver) of
+ mnesia ->
+ delete_cache_mnesia(JID, DeviceID);
+ sql ->
+ delete_cache_sql(JID, DeviceID)
+ end.
+
+delete_cache_mnesia(JID, DeviceID) ->
+ #jid{luser = LUser, lserver = LServer} = JID,
+ LUS = {LUser, LServer},
+ [ mnesia:dirty_delete_object(Obj) || Obj <- mnesia:dirty_match_object(#applepush_cache{device_id = DeviceID, us = LUS, _ = '_'})].
+
+delete_cache_sql(JID, DeviceID) ->
+ #jid{luser = LUser, lserver = LServer} = JID,
+ Username = ejabberd_odbc:escape(LUser),
+ SDeviceID = erlang:integer_to_list(DeviceID, 16),
+ ejabberd_odbc:sql_query(
+ LServer,
+ [<<"delete from applepush_cache "
+ "where username='">>, Username, <<"' and ">>,
+ <<"device_id='">>, SDeviceID, <<"';">>]).
+
+delete_cache(JID) ->
+ case get_storage(JID#jid.lserver) of
+ mnesia ->
+ delete_cache_mnesia(JID);
+ sql ->
+ delete_cache_sql(JID)
+ end.
+
+delete_cache_mnesia(JID) ->
+ #jid{luser = LUser, lserver = LServer} = JID,
+ LUS = {LUser, LServer},
+ catch mnesia:dirty_delete(applepush_cache, LUS).
+
+delete_cache_sql(JID) ->
+ #jid{luser = LUser, lserver = LServer} = JID,
+ Username = ejabberd_odbc:escape(LUser),
+ ejabberd_odbc:sql_query(
+ LServer,
+ [<<"delete from applepush_cache "
+ "where username='">>, Username, <<"';">>]).
+
+
+remove_user(User, Server) ->
+ delete_cache(jlib:make_jid(User, Server, <<"">>)).
+
+
+prepend_sender(<<"">>, Body) ->
+ Body;
+prepend_sender(From, Body) ->
+ <<From/binary, ": ", Body/binary>>.
+
+utf8_cut(S, Bytes) -> utf8_cut(S, <<>>, <<>>, Bytes + 1).
+
+utf8_cut(_S, _Cur, Prev, 0) -> Prev;
+utf8_cut(<<>>, Cur, _Prev, _Bytes) -> Cur;
+utf8_cut(<<C, S/binary>>, Cur, Prev, Bytes) ->
+ if C bsr 6 == 2 ->
+ utf8_cut(S, <<Cur/binary, C>>, Prev, Bytes - 1);
+ true -> utf8_cut(S, <<Cur/binary, C>>, Cur, Bytes - 1)
+ end.
+
+-include("mod_roster.hrl").
+
+get_roster_name(To, JID) ->
+ User = To#jid.luser,
+ Server = To#jid.lserver,
+ RosterItems = ejabberd_hooks:run_fold(
+ roster_get, Server, [], [{User, Server}]),
+ JUser = JID#jid.luser,
+ JServer = JID#jid.lserver,
+ Item =
+ lists:foldl(
+ fun(_, Res = #roster{}) ->
+ Res;
+ (I, false) ->
+ case I#roster.jid of
+ {JUser, JServer, _} ->
+ I;
+ _ ->
+ false
+ end
+ end, false, RosterItems),
+ case Item of
+ false ->
+ unescape(JID#jid.user);
+ #roster{} ->
+ Item#roster.name
+ end.
+
+unescape(<<"">>) -> <<"">>;
+unescape(<<"\\20", S/binary>>) ->
+ <<"\s", (unescape(S))/binary>>;
+unescape(<<"\\22", S/binary>>) ->
+ <<"\"", (unescape(S))/binary>>;
+unescape(<<"\\26", S/binary>>) ->
+ <<"&", (unescape(S))/binary>>;
+unescape(<<"\\27", S/binary>>) ->
+ <<"'", (unescape(S))/binary>>;
+unescape(<<"\\2f", S/binary>>) ->
+ <<"/", (unescape(S))/binary>>;
+unescape(<<"\\3a", S/binary>>) ->
+ <<":", (unescape(S))/binary>>;
+unescape(<<"\\3c", S/binary>>) ->
+ <<"<", (unescape(S))/binary>>;
+unescape(<<"\\3e", S/binary>>) ->
+ <<">", (unescape(S))/binary>>;
+unescape(<<"\\40", S/binary>>) ->
+ <<"@", (unescape(S))/binary>>;
+unescape(<<"\\5c", S/binary>>) ->
+ <<"\\", (unescape(S))/binary>>;
+unescape(<<C, S/binary>>) -> <<C, (unescape(S))/binary>>.
+
+check_x_pushed(#xmlel{children = Els}) ->
+ check_x_pushed1(Els).
+
+check_x_pushed1([]) ->
+ false;
+check_x_pushed1([{xmlcdata, _} | Els]) ->
+ check_x_pushed1(Els);
+check_x_pushed1([El | Els]) ->
+ case xml:get_tag_attr_s(<<"xmlns">>, El) of
+ ?NS_P1_PUSHED ->
+ true;
+ _ ->
+ check_x_pushed1(Els)
+ end.
+
+check_x_attachment(#xmlel{children = Els}) ->
+ check_x_attachment1(Els).
+
+check_x_attachment1([]) ->
+ false;
+check_x_attachment1([{xmlcdata, _} | Els]) ->
+ check_x_attachment1(Els);
+check_x_attachment1([El | Els]) ->
+ case xml:get_tag_attr_s(<<"xmlns">>, El) of
+ ?NS_P1_ATTACHMENT ->
+ true;
+ _ ->
+ check_x_attachment1(Els)
+ end.
+
+
+get_push_service(Host, JID, AppID) ->
+ PushServices =
+ gen_mod:get_module_opt(
+ Host, ?MODULE,
+ push_services,
+ fun(L) when is_list(L) -> L end,
+ []),
+ PushService =
+ case lists:keysearch(AppID, 1, PushServices) of
+ false ->
+ DefaultServices =
+ gen_mod:get_module_opt(
+ Host, ?MODULE,
+ default_services,
+ fun(L) when is_list(L) -> L end,
+ []),
+ case lists:keysearch(JID#jid.lserver, 1, DefaultServices) of
+ false ->
+ gen_mod:get_module_opt(
+ Host, ?MODULE,
+ default_service,
+ fun(S) when is_binary(S) -> S end,
+ <<"applepush.localhost">>);
+ {value, {_, PS}} ->
+ PS
+ end;
+ {value, {AppID, PS}} ->
+ PS
+ end,
+ PushService.
+
+get_storage(Host) ->
+ gen_mod:get_module_opt(Host, ?MODULE, backend,
+ fun(mnesia) -> mnesia;
+ (sql) -> sql
+ end,
+ mnesia).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Internal module protection
+
+-define(VALID_HOSTS, []). % default is unlimited use
+-define(MAX_USERS, 0). % default is unlimited use
+
+init_host(VHost) ->
+ case ?VALID_HOSTS of
+ [] -> % unlimited use
+ true;
+ ValidList -> % limited use
+ init_host(VHost, ValidList)
+ end.
+init_host([], _) ->
+ false;
+init_host(VHost, ValidEncryptedList) ->
+ EncryptedHost = erlang:md5(lists:reverse(VHost)),
+ case lists:member(EncryptedHost, ValidEncryptedList) of
+ true ->
+ case ?MAX_USERS of
+ 0 -> true;
+ N -> ejabberd_auth:get_vh_registered_users_number(VHost) =< N
+ end;
+ false ->
+ case string:chr(VHost, $.) of
+ 0 -> false;
+ Pos -> init_host(string:substr(VHost, Pos+1), ValidEncryptedList)
+ end
+ end.
+
+%% Debug commands
+%% JID is of form
+get_tokens_by_jid(JIDString) when is_binary(JIDString) ->
+ get_tokens_by_jid(jlib:string_to_jid(JIDString));
+get_tokens_by_jid(#jid{luser = LUser, lserver = LServer}) ->
+ LUS = {LUser, LServer},
+ [erlang:integer_to_list(I, 16) || {applepush_cache,_,I,_} <-
+ mnesia:dirty_read(applepush_cache, LUS)].
+
+
+%% Apply OnlineFun([{DeviceID, Pid}]) on all online or standby, ios, sessions of the given jid
+%% and OfflineFun([DeviceID]) on all offline sessions of the given jid
+apply_on_all_ios_for(JID, OnlineFun, OfflineFun) when is_binary(JID) ->
+ apply_on_all_ios_for(jlib:string_to_jid(JID), OnlineFun, OfflineFun);
+
+apply_on_all_ios_for(JID = #jid{luser = User, lserver = Server}, OnlineFun, OfflineFun) ->
+ case lookup_cache(JID) of
+ false ->
+ ok;
+ Devices ->
+ %% as integer
+ AlliOSDeviceIdsInt = [ ID || {ID, _AppID, _SendBody, _SendFrom} <- Devices],
+ Pids = [ejabberd_sm:get_session_pid(User, Server, Resource) ||
+ Resource <- ejabberd_sm:get_user_resources(User, Server)],
+ %%catch because the c2s process may have been died
+
+ %%In HEX format
+ OnlineiOSDeviceIDs = [ {D,P} ||
+ {D, P} <- [ {catch ejabberd_c2s:get_ios_clientid(Pid), Pid} || Pid <- Pids], is_list(D)],
+ %%As integer
+ OnlineiOSDevicesIDsInt = [{erlang:list_to_integer(D, 16), P} || {D, P} <- OnlineiOSDeviceIDs],
+ OfflineiOSDevicesInt = lists:filter(fun(ID) -> not lists:keymember(ID, 1, OnlineiOSDevicesIDsInt) end,
+ AlliOSDeviceIdsInt),
+ OfflineiOSDevices = [erlang:integer_to_list(I, 16) || I <- OfflineiOSDevicesInt],
+ OnlineFun(OnlineiOSDeviceIDs),
+ OfflineFun(OfflineiOSDevices)
+ end.
+
+
+