diff --git a/mod_profile/README.txt b/mod_profile/README.txt index 1f690e1..2892042 100644 --- a/mod_profile/README.txt +++ b/mod_profile/README.txt @@ -5,8 +5,6 @@ Author: Magnus Henoch mailto:henoch@dtek.chalmers.se xmpp:legoscia@jabber.cd.chalmers.se - Requirements: ejabberd 2.x.x - Does NOT work with ejabberd 13 or newer. This module supports storing and retrieving a profile according to diff --git a/mod_profile/src/mod_profile.erl b/mod_profile/src/mod_profile.erl index a0e9866..5be3faf 100644 --- a/mod_profile/src/mod_profile.erl +++ b/mod_profile/src/mod_profile.erl @@ -54,9 +54,9 @@ %%% %%% %%% -%%% +%%% %%% The server will return only the fields that were requested and the user had defined previously: -%%% +%%% %%% %%% %%% @@ -76,141 +76,143 @@ %%%% Headers -module(mod_profile). + -author('henoch@dtek.chalmers.se'). -behaviour(gen_mod). --export([start/2, stop/1, - process_sm_iq/3, - get_sm_features/5, - remove_user/2]). +-export([start/2, stop/1, process_sm_iq/3, + get_sm_features/5, remove_user/2]). -include("ejabberd.hrl"). + -include("jlib.hrl"). + -include("logger.hrl"). -record(profile, {us, fields}). --define(NS_PROFILE, "urn:xmpp:tmp:profile"). +-define(NS_PROFILE, <<"urn:xmpp:tmp:profile">>). %%%======================= %%%% gen_mod start(Host, Opts) -> - mnesia:create_table(profile, [{disc_only_copies, [node()]}, - {attributes, record_info(fields, profile)}]), - ejabberd_hooks:add(remove_user, Host, ?MODULE, remove_user, 50), - ejabberd_hooks:add(disco_sm_features, Host, ?MODULE, get_sm_features, 50), + mnesia:create_table(profile, + [{disc_only_copies, [node()]}, + {attributes, record_info(fields, profile)}]), + ejabberd_hooks:add(remove_user, Host, ?MODULE, + remove_user, 50), + ejabberd_hooks:add(disco_sm_features, Host, ?MODULE, + get_sm_features, 50), IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue), - gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_PROFILE, - ?MODULE, process_sm_iq, IQDisc). + gen_iq_handler:add_iq_handler(ejabberd_sm, Host, + ?NS_PROFILE, ?MODULE, process_sm_iq, IQDisc). stop(Host) -> - ejabberd_hooks:delete(remove_user, Host, ?MODULE, remove_user, 50), - ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, get_sm_features,50), - gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_PROFILE). + ejabberd_hooks:delete(remove_user, Host, ?MODULE, + remove_user, 50), + ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, + get_sm_features, 50), + gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, + ?NS_PROFILE). %%%======================= %%%% Hooks -get_sm_features({error, _} = Acc, _From, _To, _Node, _Lang) -> +get_sm_features({error, _} = Acc, _From, _To, _Node, + _Lang) -> Acc; get_sm_features(Acc, _From, _To, Node, _Lang) -> - %% XXX: this will make nonexistent users seem to exist. But - %% mod_adhoc and mod_vcard do that already. case Node of - [] -> - case Acc of - {result, Features} -> - {result, [?NS_PROFILE | Features]}; - empty -> - {result, [?NS_PROFILE]} - end; - _ -> - Acc + [] -> + case Acc of + {result, Features} -> + {result, [?NS_PROFILE | Features]}; + empty -> {result, [?NS_PROFILE]} + end; + _ -> Acc end. remove_user(User, Server) -> LUser = jlib:nodeprep(User), LServer = jlib:nameprep(Server), US = {LUser, LServer}, - F = fun() -> - mnesia:delete({profile, US}) - end, + F = fun () -> mnesia:delete({profile, US}) end, mnesia:transaction(F). %%%======================= %%%% IQ handler -process_sm_iq(From, To, #iq{type = Type, sub_el = SubEl} = IQ) -> +process_sm_iq(From, To, + #iq{type = Type, sub_el = SubEl} = IQ) -> case Type of - set -> - #jid{luser = LUser, lserver = LServer} = From, - process_sm_iq_set(LUser, LServer, SubEl, IQ); - get -> - #jid{luser = LUser, lserver = LServer} = To, - process_sm_iq_get(LUser, LServer, SubEl, IQ) + set -> + #jid{luser = LUser, lserver = LServer} = From, + process_sm_iq_set(LUser, LServer, SubEl, IQ); + get -> + #jid{luser = LUser, lserver = LServer} = To, + process_sm_iq_get(LUser, LServer, SubEl, IQ) end. process_sm_iq_set(LUser, LServer, SubEl, IQ) -> case lists:member(LServer, ?MYHOSTS) of - true -> - {xmlelement, _, _, SubSubEls} = SubEl, - ElsList = [El || {xmlelement, Name, _Attrs, _Els} = El - <- xml:remove_cdata(SubSubEls), - Name == "x"], - case ElsList of - [XData] -> - case set_profile(LUser, LServer, XData) of - ok -> - IQ#iq{type = result, sub_el = []}; - {error, Error} -> - IQ#iq{type = error, sub_el = [SubEl, Error]} - end; - _ -> - IQ#iq{type = error, - sub_el = [SubEl, ?ERR_BAD_REQUEST]} - end; - false -> - IQ#iq{type = error, - sub_el = [SubEl, ?ERR_NOT_ALLOWED]} + true -> + #xmlel{children = SubSubEls} = SubEl, + ElsList = [El + || #xmlel{name = Name} = El + <- xml:remove_cdata(SubSubEls), + Name == <<"x">>], + case ElsList of + [XData] -> + case set_profile(LUser, LServer, XData) of + ok -> IQ#iq{type = result, sub_el = []}; + {error, Error} -> + IQ#iq{type = error, sub_el = [SubEl, Error]} + end; + _ -> + IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]} + end; + false -> + IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]} end. process_sm_iq_get(LUser, LServer, SubEl, IQ) -> ReqFields = get_requested_fields(SubEl), case get_profile(LUser, LServer, ReqFields) of - {ok, Fields} -> - XEl = {xmlelement, "x", [{"xmlns", "jabber:x:data"}, - {"type", "result"}], Fields}, - ProfileEl = {xmlelement, "profile", - [{"xmlns", ?NS_PROFILE}], [XEl]}, - IQ#iq{type = result, - sub_el = - [ProfileEl] - }; - {error, user_not_found} -> - IQ#iq{type = error, - sub_el = [SubEl, ?ERR_SERVICE_UNAVAILABLE]}; - {error, OtherError} -> - ?ERROR_MSG("Problem found when getting profile of ~p@~p:~n~p", - [LUser, LServer, OtherError]), - IQ#iq{type = error, - sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]} + {ok, Fields} -> + XEl = #xmlel{name = <<"x">>, + attrs = + [{<<"xmlns">>, <<"jabber:x:data">>}, + {<<"type">>, <<"result">>}], + children = Fields}, + ProfileEl = #xmlel{name = <<"profile">>, + attrs = [{<<"xmlns">>, ?NS_PROFILE}], + children = [XEl]}, + IQ#iq{type = result, sub_el = [ProfileEl]}; + {error, user_not_found} -> + IQ#iq{type = error, + sub_el = [SubEl, ?ERR_SERVICE_UNAVAILABLE]}; + {error, OtherError} -> + ?ERROR_MSG("Problem found when getting profile of " + "~p@~p:~n~p", + [LUser, LServer, OtherError]), + IQ#iq{type = error, + sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]} end. %%%======================= %%%% Set profile -set_profile(LUser, LServer, {xmlelement, "x", _Attrs, Els}) -> +set_profile(LUser, LServer, + #xmlel{name = <<"x">>, children = Els}) -> US = {LUser, LServer}, - F = fun() -> + F = fun () -> mnesia:write(#profile{us = US, fields = Els}) end, case mnesia:transaction(F) of - {atomic, _} -> - ok; - _ -> - {error, ?ERR_INTERNAL_SERVER_ERROR} + {atomic, _} -> ok; + _ -> {error, ?ERR_INTERNAL_SERVER_ERROR} end. %%%======================= @@ -218,87 +220,89 @@ set_profile(LUser, LServer, {xmlelement, "x", _Attrs, Els}) -> get_profile(LUser, LServer, []) -> US = {LUser, LServer}, - F = fun() -> - mnesia:read({profile, US}) - end, + F = fun () -> mnesia:read({profile, US}) end, case mnesia:transaction(F) of - {atomic, [#profile{fields = Fields}]} -> - {ok, Fields}; - {atomic, []} -> - {error, user_not_found}; - OtherResult -> - {error, OtherResult} + {atomic, [#profile{fields = Fields}]} -> {ok, Fields}; + {atomic, []} -> {error, user_not_found}; + OtherResult -> {error, OtherResult} end; - get_profile(LUser, LServer, ReqFields) -> case get_profile(LUser, LServer, []) of - {ok, Fields} -> - filter_profile_fields(Fields, ReqFields); - Other -> Other + {ok, Fields} -> + filter_profile_fields(Fields, ReqFields); + Other -> Other end. filter_profile_fields(Fields, ReqFields) -> filter_profile_fields(Fields, ReqFields, []). -%% Probably there are many stored fields and the requested fields are few, -%% so it's optimal to traverse the requested fields instead of stored fields -filter_profile_fields(StoredFields, [{xmlelement, "field", Attrs, []} | ReqFields], ResFields) -> +filter_profile_fields(StoredFields, + [#xmlel{name = <<"field">>, attrs = Attrs, + children = []} + | ReqFields], + ResFields) -> case lists:keysearch(Attrs, 3, StoredFields) of - {value, StoredField} -> - filter_profile_fields(StoredFields, ReqFields, [StoredField | ResFields]); - false -> - filter_profile_fields(StoredFields, ReqFields, ResFields) + {value, StoredField} -> + filter_profile_fields(StoredFields, ReqFields, + [StoredField | ResFields]); + false -> + filter_profile_fields(StoredFields, ReqFields, + ResFields) end; -filter_profile_fields(StoredFields, [_FieldForm | ReqFields], ResFields) -> - filter_profile_fields(StoredFields, ReqFields, ResFields); +filter_profile_fields(StoredFields, + [_FieldForm | ReqFields], ResFields) -> + filter_profile_fields(StoredFields, ReqFields, + ResFields); filter_profile_fields(_StoredFields, [], ResFields) -> - FieldFormType = {xmlelement, "field", [{"var", "FORM_TYPE"}, {"type", "hidden"}], - [{xmlelement, "value",[], [{xmlcdata, <<"urn:xmpp:tmp:profile">>}]}]}, - %% NOTE: This list reverse is not necessary, it just ensure the returned fields are in the same order than the request + FieldFormType = #xmlel{name = <<"field">>, + attrs = + [{<<"var">>, <<"FORM_TYPE">>}, + {<<"type">>, <<"hidden">>}], + children = + [#xmlel{name = <<"value">>, attrs = [], + children = + [{xmlcdata, + <<"urn:xmpp:tmp:profile">>}]}]}, {ok, [FieldFormType | lists:reverse(ResFields)]}. - %% TODO: la respuesta a una iq query de fields especificos ha de incluir %%%======================= %%%% Mnesia storage - %%%======================= %%%% PubSub storage - %%%======================= %%%% XML processing %% Copied from exmpp_xml.erl, then customized get_requested_fields(SubEl) -> - case xml:get_subtag(SubEl, "x") of - false -> []; - XEl -> get_elements(XEl, "field") + case xml:get_subtag(SubEl, <<"x">>) of + false -> []; + XEl -> get_elements(XEl, <<"field">>) end. -get_elements({xmlelement, "x", _, Children}, Name) -> +get_elements(#xmlel{name = <<"x">>, + children = Children}, + Name) -> get_elements2(Children, Name); -get_elements(_, _Name) -> - []. +get_elements(_, _Name) -> []. -get_elements2([], _Name) -> - []; +get_elements2([], _Name) -> []; get_elements2(Children, Name) -> lists:filter(filter_by_name(Name), Children). filter_by_name(Searched_Name) -> - fun(XML_Element) -> + fun (XML_Element) -> element_matches(XML_Element, Searched_Name) end. -element_matches({xmlelement, Name, _, _}, Name) -> - true; -element_matches(_XML_Element, _Name) -> - false. +element_matches(#xmlel{name = Name}, Name) -> true; +element_matches(_XML_Element, _Name) -> false. %%%================ %%% vim: set foldmethod=marker foldmarker=%%%%,%%%=: +