1 |
|
%%%---------------------------------------------------------------------- |
2 |
|
%%% File : mod_vcard.erl |
3 |
|
%%% Author : Alexey Shchepin <alexey@process-one.net> |
4 |
|
%%% Purpose : Vcard management in Mnesia |
5 |
|
%%% Created : 2 Jan 2003 by Alexey Shchepin <alexey@process-one.net> |
6 |
|
%%% |
7 |
|
%%% Store vCards in mnesia to provide "XEP-0054: vcard-temp" |
8 |
|
%%% and "XEP-0055: Jabber Search" |
9 |
|
%%% |
10 |
|
%%% Most of this is now using binaries. The search fields l* in vcard_search |
11 |
|
%%% are still stored as lists to allow string prefix search using the match |
12 |
|
%%% spec with a trailing element String ++ '_'. |
13 |
|
%%% |
14 |
|
%%%---------------------------------------------------------------------- |
15 |
|
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne |
16 |
|
%%% |
17 |
|
%%% This program is free software; you can redistribute it and/or |
18 |
|
%%% modify it under the terms of the GNU General Public License as |
19 |
|
%%% published by the Free Software Foundation; either version 2 of the |
20 |
|
%%% License, or (at your option) any later version. |
21 |
|
%%% |
22 |
|
%%% This program is distributed in the hope that it will be useful, |
23 |
|
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of |
24 |
|
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
25 |
|
%%% General Public License for more details. |
26 |
|
%%% |
27 |
|
%%% You should have received a copy of the GNU General Public License |
28 |
|
%%% along with this program; if not, write to the Free Software |
29 |
|
%%% Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
30 |
|
%%% |
31 |
|
%%%---------------------------------------------------------------------- |
32 |
|
|
33 |
|
-module(mod_vcard). |
34 |
|
-author('alexey@process-one.net'). |
35 |
|
-xep([{xep, 54}, {version, "1.2"}]). |
36 |
|
-xep([{xep, 55}, {version, "1.3"}]). |
37 |
|
-behaviour(gen_mod). |
38 |
|
-behaviour(gen_server). |
39 |
|
-behaviour(mongoose_module_metrics). |
40 |
|
|
41 |
|
-include("mongoose.hrl"). |
42 |
|
-include("jlib.hrl"). |
43 |
|
-include("mod_vcard.hrl"). |
44 |
|
-include("mongoose_rsm.hrl"). |
45 |
|
-include("mongoose_config_spec.hrl"). |
46 |
|
|
47 |
|
%% gen_mod handlers |
48 |
|
-export([start/2, stop/1, |
49 |
|
supported_features/0]). |
50 |
|
|
51 |
|
%% config_spec |
52 |
|
-export([config_spec/0, |
53 |
|
process_map_spec/1, |
54 |
|
process_search_spec/1, |
55 |
|
process_search_reported_spec/1]). |
56 |
|
|
57 |
|
%% gen_server handlers |
58 |
|
-export([init/1, |
59 |
|
handle_info/2, |
60 |
|
handle_call/3, |
61 |
|
handle_cast/2, |
62 |
|
terminate/2, |
63 |
|
code_change/3]). |
64 |
|
|
65 |
|
%% mongoose_packet_handler export |
66 |
|
-export([process_packet/5]). |
67 |
|
|
68 |
|
%% Hook handlers |
69 |
|
-export([process_local_iq/5, |
70 |
|
process_sm_iq/5, |
71 |
|
remove_user/3, |
72 |
|
remove_domain/3, |
73 |
|
set_vcard/4]). |
74 |
|
|
75 |
|
-export([start_link/2]). |
76 |
|
-export([default_search_fields/0]). |
77 |
|
-export([get_results_limit/1]). |
78 |
|
-export([get_default_reported_fields/1]). |
79 |
|
|
80 |
|
%% GDPR related |
81 |
|
-export([get_personal_data/3]). |
82 |
|
|
83 |
|
-export([config_metrics/1]). |
84 |
|
|
85 |
|
-ignore_xref([ |
86 |
|
process_packet/5, |
87 |
|
get_personal_data/3, remove_user/3, remove_domain/3, set_vcard/4, start_link/2 |
88 |
|
]). |
89 |
|
|
90 |
|
-define(PROCNAME, ejabberd_mod_vcard). |
91 |
|
|
92 |
|
-record(state, {search :: boolean(), |
93 |
|
host_type :: mongooseim:host_type()}). |
94 |
|
|
95 |
|
-type error() :: error | {error, any()}. |
96 |
|
|
97 |
|
%%-------------------------------------------------------------------- |
98 |
|
%% gdpr callback |
99 |
|
%%-------------------------------------------------------------------- |
100 |
|
|
101 |
|
-spec get_personal_data(gdpr:personal_data(), mongooseim:host_type(), jid:jid()) -> gdpr:personal_data(). |
102 |
|
get_personal_data(Acc, HostType, #jid{luser = LUser, lserver = LServer}) -> |
103 |
33 |
Jid = jid:to_binary({LUser, LServer}), |
104 |
33 |
Schema = ["jid", "vcard"], |
105 |
33 |
Entries = case mod_vcard_backend:get_vcard(HostType, LUser, LServer) of |
106 |
|
{ok, Record} -> |
107 |
33 |
SerializedRecords = exml:to_binary(Record), |
108 |
23 |
[{Jid, SerializedRecords}]; |
109 |
:-( |
_ -> [] |
110 |
|
end, |
111 |
23 |
[{vcard, Schema, Entries} | Acc]. |
112 |
|
|
113 |
|
-spec default_search_fields() -> list(). |
114 |
|
default_search_fields() -> |
115 |
:-( |
[{<<"User">>, <<"user">>}, |
116 |
|
{<<"Full Name">>, <<"fn">>}, |
117 |
|
{<<"Given Name">>, <<"first">>}, |
118 |
|
{<<"Middle Name">>, <<"middle">>}, |
119 |
|
{<<"Family Name">>, <<"last">>}, |
120 |
|
{<<"Nickname">>, <<"nick">>}, |
121 |
|
{<<"Birthday">>, <<"bday">>}, |
122 |
|
{<<"Country">>, <<"ctry">>}, |
123 |
|
{<<"City">>, <<"locality">>}, |
124 |
|
{<<"Email">>, <<"email">>}, |
125 |
|
{<<"Organization Name">>, <<"orgname">>}, |
126 |
|
{<<"Organization Unit">>, <<"orgunit">>}]. |
127 |
|
|
128 |
|
-spec get_results_limit(mongooseim:host_type()) -> non_neg_integer() | infinity. |
129 |
|
get_results_limit(HostType) -> |
130 |
:-( |
case gen_mod:get_module_opt(HostType, mod_vcard, matches) of |
131 |
|
infinity -> |
132 |
:-( |
infinity; |
133 |
|
Val when is_integer(Val) and (Val > 0) -> |
134 |
:-( |
Val |
135 |
|
end. |
136 |
|
|
137 |
|
%%-------------------------------------------------------------------- |
138 |
|
%% gen_mod callbacks |
139 |
|
%%-------------------------------------------------------------------- |
140 |
|
|
141 |
|
start(HostType, Opts) -> |
142 |
334 |
mod_vcard_backend:init(HostType, Opts), |
143 |
334 |
start_hooks(HostType), |
144 |
334 |
start_iq_handlers(HostType, Opts), |
145 |
334 |
Proc = gen_mod:get_module_proc(HostType, ?PROCNAME), |
146 |
334 |
ChildSpec = {Proc, {?MODULE, start_link, [HostType, Opts]}, |
147 |
|
transient, 1000, worker, [?MODULE]}, |
148 |
334 |
ejabberd_sup:start_child(ChildSpec). |
149 |
|
|
150 |
|
-spec stop(mongooseim:host_type()) -> ok. |
151 |
|
stop(HostType) -> |
152 |
334 |
Proc = gen_mod:get_module_proc(HostType, ?PROCNAME), |
153 |
334 |
stop_hooks(HostType), |
154 |
334 |
stop_iq_handlers(HostType), |
155 |
334 |
stop_backend(HostType), |
156 |
334 |
gen_server:call(Proc, stop), |
157 |
334 |
ejabberd_sup:stop_child(Proc), |
158 |
334 |
ok. |
159 |
|
|
160 |
146 |
supported_features() -> [dynamic_domains]. |
161 |
|
|
162 |
|
start_hooks(HostType) -> |
163 |
334 |
ejabberd_hooks:add(hooks(HostType)). |
164 |
|
|
165 |
|
stop_hooks(HostType) -> |
166 |
334 |
ejabberd_hooks:delete(hooks(HostType)). |
167 |
|
|
168 |
|
hooks(HostType) -> |
169 |
668 |
[{Hook, HostType, ?MODULE, Function, Priority} |
170 |
668 |
|| {Hook, Function, Priority} <- hooks2()]. |
171 |
|
|
172 |
|
hooks2() -> |
173 |
668 |
[{remove_user, remove_user, 50}, |
174 |
|
{anonymous_purge_hook, remove_user, 50}, |
175 |
|
{remove_domain, remove_domain, 50}, |
176 |
|
{set_vcard, set_vcard, 50}, |
177 |
|
{get_personal_data, get_personal_data, 50}]. |
178 |
|
|
179 |
|
start_iq_handlers(HostType, #{iqdisc := IQDisc}) -> |
180 |
334 |
gen_iq_handler:add_iq_handler_for_domain(HostType, ?NS_VCARD, ejabberd_sm, |
181 |
|
fun ?MODULE:process_sm_iq/5, #{}, IQDisc), |
182 |
334 |
gen_iq_handler:add_iq_handler_for_domain(HostType, ?NS_VCARD, ejabberd_local, |
183 |
|
fun ?MODULE:process_local_iq/5, #{}, IQDisc). |
184 |
|
|
185 |
|
stop_iq_handlers(HostType) -> |
186 |
334 |
gen_iq_handler:remove_iq_handler_for_domain(HostType, ?NS_VCARD, ejabberd_local), |
187 |
334 |
gen_iq_handler:remove_iq_handler_for_domain(HostType, ?NS_VCARD, ejabberd_sm). |
188 |
|
|
189 |
|
stop_backend(HostType) -> |
190 |
334 |
mod_vcard_backend:tear_down(HostType). |
191 |
|
|
192 |
|
%% Domain registration |
193 |
|
maybe_register_search(false, _HostType, _Opts) -> |
194 |
2 |
ok; |
195 |
|
maybe_register_search(true, HostType, Opts) -> |
196 |
332 |
SubdomainPattern = gen_mod:get_opt(host, Opts), |
197 |
332 |
PacketHandler = mongoose_packet_handler:new(?MODULE, #{pid => self()}), |
198 |
|
%% Always register, even if search functionality is disabled. |
199 |
|
%% So, we can send 503 error, instead of 404 error. |
200 |
332 |
mongoose_domain_api:register_subdomain(HostType, SubdomainPattern, PacketHandler). |
201 |
|
|
202 |
|
maybe_unregister_search(false, _HostType) -> |
203 |
2 |
ok; |
204 |
|
maybe_unregister_search(true, HostType) -> |
205 |
332 |
SubdomainPattern = gen_mod:get_module_opt(HostType, ?MODULE, host), |
206 |
332 |
mongoose_domain_api:unregister_subdomain(HostType, SubdomainPattern). |
207 |
|
|
208 |
|
%%-------------------------------------------------------------------- |
209 |
|
%% config_spec |
210 |
|
%%-------------------------------------------------------------------- |
211 |
|
|
212 |
|
-spec config_spec() -> mongoose_config_spec:config_section(). |
213 |
|
config_spec() -> |
214 |
164 |
#section{ |
215 |
|
items = #{<<"iqdisc">> => mongoose_config_spec:iqdisc(), |
216 |
|
<<"host">> => #option{type = string, |
217 |
|
validate = subdomain_template, |
218 |
|
process = fun mongoose_subdomain_utils:make_subdomain_pattern/1}, |
219 |
|
<<"search">> => #option{type = boolean}, |
220 |
|
<<"backend">> => #option{type = atom, |
221 |
|
validate = {module, mod_vcard}}, |
222 |
|
<<"matches">> => #option{type = int_or_infinity, |
223 |
|
validate = non_negative}, |
224 |
|
<<"ldap">> => ldap_section(), |
225 |
|
<<"riak">> => riak_config_spec() |
226 |
|
}, |
227 |
|
defaults = #{<<"iqdisc">> => parallel, |
228 |
|
<<"host">> => mongoose_subdomain_utils:make_subdomain_pattern("vjud.@HOST@"), |
229 |
|
<<"search">> => true, |
230 |
|
<<"backend">> => mnesia, |
231 |
|
<<"matches">> => 30 |
232 |
|
}, |
233 |
|
format_items = map, |
234 |
|
process = fun remove_unused_backend_opts/1 |
235 |
|
}. |
236 |
|
|
237 |
|
ldap_section() -> |
238 |
164 |
CommonLDAPSpec = mongoose_ldap_config:spec(), |
239 |
164 |
Items = #{ |
240 |
|
<<"uids">> => #list{items = mongoose_ldap_config:uids()}, |
241 |
|
<<"vcard_map">> => #list{items = ldap_vcard_map_spec()}, |
242 |
|
<<"search_fields">> => #list{items = ldap_search_fields_spec()}, |
243 |
|
<<"search_reported">> => #list{items = ldap_search_reported_spec()}, |
244 |
|
<<"search_operator">> => #option{type = atom, |
245 |
|
validate = {enum, ['or', 'and']}}, |
246 |
|
<<"binary_search_fields">> => #list{items = #option{type = binary, |
247 |
|
validate = non_empty}}}, |
248 |
164 |
Defaults = #{<<"uids">> => [{<<"uid">>, <<"%u">>}], |
249 |
|
<<"vcard_map">> => mod_vcard_ldap:default_vcard_map(), |
250 |
|
<<"search_fields">> => mod_vcard_ldap:default_search_fields(), |
251 |
|
<<"search_reported">> => mod_vcard_ldap:default_search_reported(), |
252 |
|
<<"search_operator">> => 'and', |
253 |
|
<<"binary_search_fields">> => []}, |
254 |
164 |
CommonLDAPSpec#section{items = maps:merge(CommonLDAPSpec#section.items, Items), |
255 |
|
defaults = maps:merge(CommonLDAPSpec#section.defaults, Defaults), |
256 |
|
include = always}. |
257 |
|
|
258 |
|
ldap_vcard_map_spec() -> |
259 |
164 |
#section{ |
260 |
|
items = #{<<"vcard_field">> => #option{type = binary, |
261 |
|
validate = non_empty}, |
262 |
|
<<"ldap_pattern">> => #option{type = binary, |
263 |
|
validate = non_empty}, |
264 |
|
<<"ldap_field">> => #option{type = binary, |
265 |
|
validate = non_empty} |
266 |
|
}, |
267 |
|
required = all, |
268 |
|
process = fun ?MODULE:process_map_spec/1 |
269 |
|
}. |
270 |
|
|
271 |
|
ldap_search_fields_spec() -> |
272 |
164 |
#section{ |
273 |
|
items = #{<<"search_field">> => #option{type = binary, |
274 |
|
validate = non_empty}, |
275 |
|
<<"ldap_field">> => #option{type = binary, |
276 |
|
validate = non_empty} |
277 |
|
}, |
278 |
|
required = all, |
279 |
|
process = fun ?MODULE:process_search_spec/1 |
280 |
|
}. |
281 |
|
|
282 |
|
ldap_search_reported_spec() -> |
283 |
164 |
#section{ |
284 |
|
items = #{<<"search_field">> => #option{type = binary, |
285 |
|
validate = non_empty}, |
286 |
|
<<"vcard_field">> => #option{type = binary, |
287 |
|
validate = non_empty} |
288 |
|
}, |
289 |
|
required = all, |
290 |
|
process = fun ?MODULE:process_search_reported_spec/1 |
291 |
|
}. |
292 |
|
|
293 |
|
riak_config_spec() -> |
294 |
164 |
#section{ |
295 |
|
items = #{<<"bucket_type">> => #option{type = binary, |
296 |
|
validate = non_empty}, |
297 |
|
<<"search_index">> => #option{type = binary, |
298 |
|
validate = non_empty} |
299 |
|
}, |
300 |
|
include = always, |
301 |
|
format_items = map, |
302 |
|
defaults = #{<<"bucket_type">> => <<"vcard">>, |
303 |
|
<<"search_index">> => <<"vcard">>} |
304 |
|
}. |
305 |
|
|
306 |
|
process_map_spec(KVs) -> |
307 |
:-( |
{[[{vcard_field, VF}], [{ldap_pattern, LP}], [{ldap_field, LF}]], []} = |
308 |
|
proplists:split(KVs, [vcard_field, ldap_pattern, ldap_field]), |
309 |
:-( |
{VF, LP, [LF]}. |
310 |
|
|
311 |
|
process_search_spec(KVs) -> |
312 |
:-( |
{[[{search_field, SF}], [{ldap_field, LF}]], []} = |
313 |
|
proplists:split(KVs, [search_field, ldap_field]), |
314 |
:-( |
{SF, LF}. |
315 |
|
|
316 |
|
process_search_reported_spec(KVs) -> |
317 |
:-( |
{[[{search_field, SF}], [{vcard_field, VF}]], []} = |
318 |
|
proplists:split(KVs, [search_field, vcard_field]), |
319 |
:-( |
{SF, VF}. |
320 |
|
|
321 |
:-( |
remove_unused_backend_opts(Opts = #{backend := riak}) -> maps:remove(ldap, Opts); |
322 |
82 |
remove_unused_backend_opts(Opts = #{backend := ldap}) -> maps:remove(riak, Opts); |
323 |
|
remove_unused_backend_opts(Opts) -> |
324 |
:-( |
M = maps:remove(riak, Opts), |
325 |
:-( |
maps:remove(ldap, M). |
326 |
|
|
327 |
|
%%-------------------------------------------------------------------- |
328 |
|
%% mongoose_packet_handler callbacks for search |
329 |
|
%%-------------------------------------------------------------------- |
330 |
|
|
331 |
|
-spec process_packet(Acc :: mongoose_acc:t(), From ::jid:jid(), To ::jid:jid(), |
332 |
|
Packet :: exml:element(), #{}) -> mongoose_acc:t(). |
333 |
|
process_packet(Acc, From, To, _Packet, _Extra) -> |
334 |
25 |
handle_route(Acc, From, To), |
335 |
25 |
Acc. |
336 |
|
|
337 |
|
handle_route(Acc, From, To) -> |
338 |
25 |
HostType = mongoose_acc:host_type(Acc), |
339 |
25 |
{IQ, Acc1} = mongoose_iq:info(Acc), |
340 |
25 |
LServer = directory_jid_to_server_host(To), |
341 |
25 |
try do_route(HostType, LServer, From, To, Acc1, IQ) |
342 |
|
catch |
343 |
|
Class:Reason:Stacktrace -> |
344 |
:-( |
?LOG_ERROR(#{what => vcard_route_failed, acc => Acc, |
345 |
:-( |
class => Class, reason => Reason, stacktrace => Stacktrace}) |
346 |
|
end. |
347 |
|
|
348 |
|
%%-------------------------------------------------------------------- |
349 |
|
%% gen_server callbacks for search |
350 |
|
%%-------------------------------------------------------------------- |
351 |
|
start_link(HostType, Opts) -> |
352 |
334 |
Proc = gen_mod:get_module_proc(HostType, ?PROCNAME), |
353 |
334 |
gen_server:start_link({local, Proc}, ?MODULE, [HostType, Opts], []). |
354 |
|
|
355 |
|
init([HostType, Opts]) -> |
356 |
334 |
process_flag(trap_exit, true), |
357 |
334 |
Search = gen_mod:get_opt(search, Opts), |
358 |
334 |
maybe_register_search(Search, HostType, Opts), |
359 |
334 |
{ok, #state{host_type = HostType, search = Search}}. |
360 |
|
|
361 |
|
handle_call(get_state, _From, State) -> |
362 |
:-( |
{reply, {ok, State}, State}; |
363 |
|
handle_call(stop, _From, State) -> |
364 |
334 |
{stop, normal, ok, State}; |
365 |
|
handle_call(_Request, _From, State) -> |
366 |
:-( |
{reply, bad_request, State}. |
367 |
|
|
368 |
|
handle_info(_, State) -> |
369 |
:-( |
{noreply, State}. |
370 |
|
|
371 |
|
handle_cast(_Request, State) -> |
372 |
:-( |
{noreply, State}. |
373 |
|
|
374 |
|
code_change(_OldVsn, State, _Extra) -> |
375 |
:-( |
{ok, State}. |
376 |
|
|
377 |
|
terminate(_Reason, #state{host_type = HostType, search = Search}) -> |
378 |
334 |
maybe_unregister_search(Search, HostType). |
379 |
|
|
380 |
|
%%-------------------------------------------------------------------- |
381 |
|
%% Hook handlers |
382 |
|
%%-------------------------------------------------------------------- |
383 |
|
process_local_iq(Acc, _From, _To, IQ = #iq{type = set, sub_el = SubEl}, _Extra) -> |
384 |
:-( |
{Acc, IQ#iq{type = error, sub_el = [SubEl, mongoose_xmpp_errors:not_allowed()]}}; |
385 |
|
process_local_iq(Acc, _From, _To, IQ = #iq{type = get}, _Extra) -> |
386 |
1 |
DescCData = #xmlcdata{content = [<<"MongooseIM XMPP Server">>, |
387 |
|
<<"\nCopyright (c) Erlang Solutions Ltd.">>]}, |
388 |
1 |
{Acc, IQ#iq{type = result, |
389 |
|
sub_el = [#xmlel{name = <<"vCard">>, attrs = [{<<"xmlns">>, ?NS_VCARD}], |
390 |
|
children = [#xmlel{name = <<"FN">>, |
391 |
|
children = [#xmlcdata{content = <<"MongooseIM">>}]}, |
392 |
|
#xmlel{name = <<"URL">>, |
393 |
|
children = [#xmlcdata{content = ?MONGOOSE_URI}]}, |
394 |
|
#xmlel{name = <<"DESC">>, |
395 |
|
children = [DescCData]} |
396 |
|
]}]}}. |
397 |
|
|
398 |
|
-spec process_sm_iq(Acc :: mongoose_acc:t(), |
399 |
|
From :: jid:jid(), |
400 |
|
To :: jid:jid(), |
401 |
|
IQ :: jlib:iq(), |
402 |
|
Extra :: map()) -> |
403 |
|
{stop, mongoose_acc:t()} | {mongoose_acc:t(), jlib:iq()}. |
404 |
|
process_sm_iq(Acc, From, To, IQ = #iq{type = set, sub_el = VCARD}, _Extra) -> |
405 |
2 |
HostType = mongoose_acc:host_type(Acc), |
406 |
2 |
process_sm_iq_set(HostType, From, To, Acc, IQ, VCARD); |
407 |
|
process_sm_iq(Acc, From, To, IQ = #iq{type = get, sub_el = VCARD}, _Extra) -> |
408 |
11 |
HostType = mongoose_acc:host_type(Acc), |
409 |
11 |
process_sm_iq_get(HostType, From, To, Acc, IQ, VCARD). |
410 |
|
|
411 |
|
process_sm_iq_set(HostType, From, To, Acc, IQ, VCARD) -> |
412 |
2 |
#jid{user = FromUser, lserver = FromVHost} = From, |
413 |
2 |
#jid{user = ToUser, lserver = ToVHost, resource = ToResource} = To, |
414 |
2 |
Local = ((FromUser == ToUser) andalso (FromVHost == ToVHost) andalso (ToResource == <<>>)) |
415 |
2 |
orelse ((ToUser == <<>>) andalso (ToVHost == <<>>)), |
416 |
2 |
Res = case Local of |
417 |
|
true -> |
418 |
:-( |
try unsafe_set_vcard(HostType, From, VCARD) of |
419 |
|
ok -> |
420 |
:-( |
IQ#iq{type = result, sub_el = []}; |
421 |
|
{error, {invalid_input, {Field, Value}}} -> |
422 |
:-( |
?LOG_WARNING(#{what => vcard_sm_iq_set_failed, value => Value, |
423 |
:-( |
reason => invalid_input, field => Field, acc => Acc}), |
424 |
:-( |
Text = io_lib:format("Invalid input for vcard field ~s", [Field]), |
425 |
:-( |
ReasonEl = mongoose_xmpp_errors:bad_request(<<"en">>, erlang:iolist_to_binary(Text)), |
426 |
:-( |
vcard_error(IQ, ReasonEl); |
427 |
|
{error, Reason} -> |
428 |
:-( |
?LOG_WARNING(#{what => vcard_sm_iq_set_failed, |
429 |
:-( |
reason => Reason, acc => Acc}), |
430 |
:-( |
vcard_error(IQ, mongoose_xmpp_errors:unexpected_request_cancel()) |
431 |
|
catch |
432 |
|
E:R:Stack -> |
433 |
:-( |
?LOG_ERROR(#{what => vcard_sm_iq_set_failed, |
434 |
:-( |
class => E, reason => R, stacktrace => Stack, acc => Acc}), |
435 |
:-( |
vcard_error(IQ, mongoose_xmpp_errors:internal_server_error()) |
436 |
|
end; |
437 |
|
_ -> |
438 |
2 |
?LOG_WARNING(#{what => vcard_sm_iq_get_failed, |
439 |
:-( |
reason => not_allowed, acc => Acc}), |
440 |
2 |
vcard_error(IQ, mongoose_xmpp_errors:not_allowed()) |
441 |
|
end, |
442 |
2 |
{Acc, Res}. |
443 |
|
|
444 |
|
process_sm_iq_get(HostType, _From, To, Acc, IQ, SubEl) -> |
445 |
11 |
#jid{luser = LUser, lserver = LServer} = To, |
446 |
11 |
Res = try mod_vcard_backend:get_vcard(HostType, LUser, LServer) of |
447 |
|
{ok, VCARD} -> |
448 |
11 |
IQ#iq{type = result, sub_el = VCARD}; |
449 |
|
{error, Reason} -> |
450 |
:-( |
IQ#iq{type = error, sub_el = [SubEl, Reason]} |
451 |
|
catch E:R:Stack -> |
452 |
:-( |
?LOG_ERROR(#{what => vcard_sm_iq_get_failed, |
453 |
:-( |
class => E, reason => R, stacktrace => Stack, acc => Acc}), |
454 |
:-( |
vcard_error(IQ, mongoose_xmpp_errors:internal_server_error()) |
455 |
|
end, |
456 |
11 |
{Acc, Res}. |
457 |
|
|
458 |
|
unsafe_set_vcard(HostType, From, VCARD) -> |
459 |
:-( |
#jid{user = FromUser, lserver = FromVHost} = From, |
460 |
:-( |
case parse_vcard(FromUser, FromVHost, VCARD) of |
461 |
|
{ok, VcardSearch} -> |
462 |
:-( |
mod_vcard_backend:set_vcard(HostType, FromUser, FromVHost, VCARD, VcardSearch); |
463 |
|
{error, Reason} -> |
464 |
:-( |
{error, Reason} |
465 |
|
end. |
466 |
|
|
467 |
|
|
468 |
|
-spec set_vcard(HandlerAcc, HostType, From, VCARD) -> Result when |
469 |
|
HandlerAcc :: ok | error(), |
470 |
|
HostType :: mongooseim:host_type(), |
471 |
|
From ::jid:jid(), |
472 |
|
VCARD :: exml:element(), |
473 |
|
Result :: ok | error(). |
474 |
|
set_vcard(ok, _HostType, _From, _VCARD) -> |
475 |
:-( |
?LOG_DEBUG(#{what => hook_call_already_handled}), |
476 |
:-( |
ok; |
477 |
|
set_vcard({error, no_handler_defined}, HostType, From, VCARD) -> |
478 |
:-( |
try unsafe_set_vcard(HostType, From, VCARD) of |
479 |
:-( |
ok -> ok; |
480 |
|
{error, Reason} -> |
481 |
:-( |
?LOG_ERROR(#{what => unsafe_set_vcard_failed, reason => Reason}), |
482 |
:-( |
{error, Reason} |
483 |
|
catch |
484 |
:-( |
E:R:S -> ?LOG_ERROR(#{what => unsafe_set_vcard_failed, class => E, |
485 |
:-( |
reason => R, stacktrace => S}), |
486 |
:-( |
{error, {E, R}} |
487 |
|
end; |
488 |
:-( |
set_vcard({error, _} = E, _HostType, _From, _VCARD) -> E. |
489 |
|
|
490 |
|
-spec remove_domain(mongoose_hooks:simple_acc(), |
491 |
|
mongooseim:host_type(), jid:lserver()) -> |
492 |
|
mongoose_hooks:simple_acc(). |
493 |
|
remove_domain(Acc, HostType, Domain) -> |
494 |
:-( |
mod_vcard_backend:remove_domain(HostType, Domain), |
495 |
:-( |
Acc. |
496 |
|
|
497 |
|
%% #rh |
498 |
|
remove_user(Acc, User, Server) -> |
499 |
2320 |
HostType = mongoose_acc:host_type(Acc), |
500 |
2320 |
LUser = jid:nodeprep(User), |
501 |
2320 |
LServer = jid:nodeprep(Server), |
502 |
2320 |
mod_vcard_backend:remove_user(HostType, LUser, LServer), |
503 |
2320 |
Acc. |
504 |
|
|
505 |
|
%% ------------------------------------------------------------------ |
506 |
|
%% Internal |
507 |
|
%% ------------------------------------------------------------------ |
508 |
|
do_route(_HostType, _LServer, From, |
509 |
|
#jid{user = User, resource = Resource} = To, Acc, _IQ) |
510 |
|
when (User /= <<>>) or (Resource /= <<>>) -> |
511 |
:-( |
{Acc1, Err} = jlib:make_error_reply(Acc, mongoose_xmpp_errors:service_unavailable()), |
512 |
:-( |
ejabberd_router:route(To, From, Acc1, Err); |
513 |
|
do_route(HostType, LServer, From, To, Acc, |
514 |
|
#iq{type = set, xmlns = ?NS_SEARCH, lang = Lang, sub_el = SubEl} = IQ) -> |
515 |
21 |
route_search_iq_set(HostType, LServer, From, To, Acc, Lang, SubEl, IQ); |
516 |
|
do_route(HostType, LServer, From, To, Acc, |
517 |
|
#iq{type = get, xmlns = ?NS_SEARCH, lang = Lang} = IQ) -> |
518 |
2 |
Form = ?FORM(To, mod_vcard_backend:search_fields(HostType, LServer), Lang), |
519 |
2 |
ResIQ = make_search_form_result_iq(IQ, Form), |
520 |
2 |
ejabberd_router:route(To, From, Acc, jlib:iq_to_xml(ResIQ)); |
521 |
|
do_route(_HostType, _LServer, From, To, Acc, |
522 |
|
#iq{type = set, xmlns = ?NS_DISCO_INFO}) -> |
523 |
:-( |
{Acc1, Err} = jlib:make_error_reply(Acc, mongoose_xmpp_errors:not_allowed()), |
524 |
:-( |
ejabberd_router:route(To, From, Acc1, Err); |
525 |
|
do_route(HostType, _LServer, From, To, Acc, |
526 |
|
#iq{type = get, xmlns = ?NS_DISCO_INFO, lang = Lang} = IQ) -> |
527 |
1 |
IdentityXML = mongoose_disco:identities_to_xml([identity(Lang)]), |
528 |
1 |
FeatureXML = mongoose_disco:features_to_xml(features()), |
529 |
1 |
InfoXML = mongoose_disco:get_info(HostType, ?MODULE, <<>>, <<>>), |
530 |
1 |
ResIQ = IQ#iq{type = result, |
531 |
|
sub_el = [#xmlel{name = <<"query">>, |
532 |
|
attrs = [{<<"xmlns">>, ?NS_DISCO_INFO}], |
533 |
|
children = IdentityXML ++ FeatureXML ++ InfoXML}]}, |
534 |
1 |
ejabberd_router:route(To, From, Acc, jlib:iq_to_xml(ResIQ)); |
535 |
|
do_route(_HostType, _LServer, From, To, Acc, |
536 |
|
#iq{type = set, xmlns = ?NS_DISCO_ITEMS}) -> |
537 |
:-( |
{Acc1, Err} = jlib:make_error_reply(Acc, mongoose_xmpp_errors:not_allowed()), |
538 |
:-( |
ejabberd_router:route(To, From, Acc1, Err); |
539 |
|
do_route(_HostType, _LServer, From, To, Acc, |
540 |
|
#iq{type = get, xmlns = ?NS_DISCO_ITEMS} = IQ) -> |
541 |
:-( |
ResIQ = |
542 |
|
IQ#iq{type = result, |
543 |
|
sub_el = [#xmlel{name = <<"query">>, |
544 |
|
attrs = [{<<"xmlns">>, ?NS_DISCO_ITEMS}]}]}, |
545 |
:-( |
ejabberd_router:route(To, From, Acc, jlib:iq_to_xml(ResIQ)); |
546 |
|
do_route(_HostType, _LServer, From, To, Acc, |
547 |
|
#iq{type = get, xmlns = ?NS_VCARD} = IQ) -> |
548 |
1 |
ResIQ = |
549 |
|
IQ#iq{type = result, |
550 |
|
sub_el = [#xmlel{name = <<"vCard">>, |
551 |
|
attrs = [{<<"xmlns">>, ?NS_VCARD}], |
552 |
|
children = iq_get_vcard()}]}, |
553 |
1 |
ejabberd_router:route(To, From, Acc, jlib:iq_to_xml(ResIQ)); |
554 |
|
do_route(_HostType, _LServer, From, To, Acc, _IQ) -> |
555 |
:-( |
{Acc1, Err} = jlib:make_error_reply(Acc, mongoose_xmpp_errors:service_unavailable()), |
556 |
:-( |
ejabberd_router:route(To, From, Acc1, Err). |
557 |
|
|
558 |
|
make_search_form_result_iq(IQ, Form) -> |
559 |
2 |
IQ#iq{type = result, |
560 |
|
sub_el = [#xmlel{name = <<"query">>, |
561 |
|
attrs = [{<<"xmlns">>, ?NS_SEARCH}], |
562 |
|
children = Form |
563 |
|
}]}. |
564 |
|
|
565 |
|
route_search_iq_set(HostType, LServer, From, To, Acc, Lang, SubEl, IQ) -> |
566 |
21 |
XDataEl = find_xdata_el(SubEl), |
567 |
21 |
RSMIn = jlib:rsm_decode(IQ), |
568 |
21 |
case XDataEl of |
569 |
|
false -> |
570 |
:-( |
{Acc1, Err} = jlib:make_error_reply(Acc, mongoose_xmpp_errors:bad_request()), |
571 |
:-( |
ejabberd_router:route(To, From, Acc1, Err); |
572 |
|
_ -> |
573 |
21 |
XData = jlib:parse_xdata_submit(XDataEl), |
574 |
21 |
case XData of |
575 |
|
invalid -> |
576 |
:-( |
{Acc1, Err} = jlib:make_error_reply(Acc, mongoose_xmpp_errors:bad_request()), |
577 |
:-( |
ejabberd_router:route(To, From, Acc1, Err); |
578 |
|
_ -> |
579 |
21 |
{SearchResult, RSMOutEls} = search_result(HostType, LServer, Lang, To, XData, RSMIn), |
580 |
21 |
ResIQ = make_search_result_iq(IQ, SearchResult, RSMOutEls), |
581 |
21 |
ejabberd_router:route(To, From, Acc, jlib:iq_to_xml(ResIQ)) |
582 |
|
end |
583 |
|
end. |
584 |
|
|
585 |
|
make_search_result_iq(IQ, SearchResult, RSMOutEls) -> |
586 |
21 |
IQ#iq{ |
587 |
|
type = result, |
588 |
|
sub_el = [#xmlel{name = <<"query">>, |
589 |
|
attrs = [{<<"xmlns">>, ?NS_SEARCH}], |
590 |
|
children = [#xmlel{name = <<"x">>, |
591 |
|
attrs = [{<<"xmlns">>, ?NS_XDATA}, |
592 |
|
{<<"type">>, <<"result">>}], |
593 |
|
children = SearchResult} |
594 |
|
] ++ RSMOutEls} |
595 |
|
]}. |
596 |
|
|
597 |
|
iq_get_vcard() -> |
598 |
1 |
[#xmlel{name = <<"FN">>, |
599 |
|
children = [#xmlcdata{content = <<"MongooseIM/mod_vcard">>}]}, |
600 |
|
#xmlel{name = <<"URL">>, children = [#xmlcdata{content = ?MONGOOSE_URI}]}, |
601 |
|
#xmlel{name = <<"DESC">>, |
602 |
|
children = [#xmlcdata{content = [<<"MongooseIM vCard module">>, |
603 |
|
<<"\nCopyright (c) Erlang Solutions Ltd.">>]}]}]. |
604 |
|
find_xdata_el(#xmlel{children = SubEls}) -> |
605 |
21 |
find_xdata_el1(SubEls). |
606 |
|
|
607 |
|
find_xdata_el1([]) -> |
608 |
:-( |
false; |
609 |
|
find_xdata_el1([XE = #xmlel{attrs = Attrs} | Els]) -> |
610 |
21 |
case xml:get_attr_s(<<"xmlns">>, Attrs) of |
611 |
|
?NS_XDATA -> |
612 |
21 |
XE; |
613 |
|
_ -> |
614 |
:-( |
find_xdata_el1(Els) |
615 |
|
end; |
616 |
|
find_xdata_el1([_ | Els]) -> |
617 |
:-( |
find_xdata_el1(Els). |
618 |
|
|
619 |
|
features() -> |
620 |
1 |
[?NS_DISCO_INFO, ?NS_SEARCH, ?NS_VCARD]. |
621 |
|
|
622 |
|
identity(Lang) -> |
623 |
1 |
#{category => <<"directory">>, |
624 |
|
type => <<"user">>, |
625 |
|
name => translate:translate(Lang, <<"vCard User Search">>)}. |
626 |
|
|
627 |
|
search_result(HostType, LServer, Lang, JID, Data, RSMIn) -> |
628 |
21 |
Text = translate:translate(Lang, <<"Search Results for ">>), |
629 |
21 |
TitleEl = #xmlel{name = <<"title">>, |
630 |
|
children = [#xmlcdata{content = [Text, jid:to_binary(JID)]}]}, |
631 |
21 |
ReportedFields = mod_vcard_backend:search_reported_fields(HostType, LServer, Lang), |
632 |
21 |
Results1 = mod_vcard_backend:search(HostType, LServer, Data), |
633 |
21 |
Results2 = lists:filtermap( |
634 |
|
fun(Result) -> |
635 |
39 |
case search_result_get_jid(Result) of |
636 |
|
{ok, ResultJID} -> |
637 |
39 |
{true, {ResultJID, Result}}; |
638 |
|
undefined -> |
639 |
:-( |
false |
640 |
|
end |
641 |
|
end, |
642 |
|
Results1), |
643 |
|
%% mnesia does not guarantee sorting order |
644 |
21 |
Results3 = lists:sort(Results2), |
645 |
21 |
{Results4, RSMOutEls} = |
646 |
|
apply_rsm_to_search_results(Results3, RSMIn, none), |
647 |
21 |
Results5 = [Result || {_, Result} <- Results4], |
648 |
21 |
{[TitleEl, ReportedFields | Results5], RSMOutEls}. |
649 |
|
|
650 |
|
%% No RSM input, create empty |
651 |
|
apply_rsm_to_search_results(Results, none, RSMOut) -> |
652 |
12 |
apply_rsm_to_search_results(Results, #rsm_in{}, RSMOut); |
653 |
|
|
654 |
|
%% Create RSM output |
655 |
|
apply_rsm_to_search_results(Results, #rsm_in{} = RSMIn, none) -> |
656 |
21 |
RSMOut = #rsm_out{count = length(Results)}, |
657 |
21 |
apply_rsm_to_search_results(Results, RSMIn, RSMOut); |
658 |
|
|
659 |
|
%% Skip by <after>$id</after> |
660 |
|
apply_rsm_to_search_results(Results1, #rsm_in{direction = aft, |
661 |
|
id = After} = RSMIn, RSMOut) |
662 |
|
when is_binary(After) -> |
663 |
2 |
Results2 = lists:dropwhile( |
664 |
|
fun({JID, _Result}) -> |
665 |
4 |
JID == After |
666 |
|
end, |
667 |
|
lists:dropwhile( |
668 |
|
fun({JID, _Result}) -> |
669 |
3 |
JID =/= After |
670 |
|
end, |
671 |
|
Results1 |
672 |
|
)), |
673 |
2 |
Index = length(Results1) - length(Results2), |
674 |
2 |
apply_rsm_to_search_results( |
675 |
|
Results2, |
676 |
|
RSMIn#rsm_in{direction = undefined, id = undefined}, |
677 |
|
RSMOut#rsm_out{index = Index} |
678 |
|
); |
679 |
|
|
680 |
|
%% Seek by <before>$id</before> |
681 |
|
apply_rsm_to_search_results(Results1, #rsm_in{max = Max, |
682 |
|
direction = before, |
683 |
|
id = Before} = RSMIn, RSMOut) |
684 |
|
when is_binary(Before) -> |
685 |
3 |
Results2 = lists:takewhile( |
686 |
|
fun({JID, _Result}) -> |
687 |
8 |
JID =/= Before |
688 |
|
end, Results1), |
689 |
3 |
if |
690 |
|
is_integer(Max) -> |
691 |
3 |
Index = max(0, length(Results2) - Max), |
692 |
3 |
Results3 = lists:nthtail(Index, Results2); |
693 |
|
true -> |
694 |
:-( |
Index = 0, |
695 |
:-( |
Results3 = Results2 |
696 |
|
end, |
697 |
3 |
apply_rsm_to_search_results( |
698 |
|
Results3, |
699 |
|
RSMIn#rsm_in{direction = undefined, id = undefined}, |
700 |
|
RSMOut#rsm_out{index = Index} |
701 |
|
); |
702 |
|
|
703 |
|
%% Skip by page number <index>371</index> |
704 |
|
apply_rsm_to_search_results(Results1, |
705 |
|
#rsm_in{max = Max, |
706 |
|
index = Index} = RSMIn1, |
707 |
|
RSMOut) |
708 |
|
when is_integer(Max), is_integer(Index) -> |
709 |
1 |
Results2 = lists:nthtail(min(Index, length(Results1)), Results1), |
710 |
1 |
RSMIn2 = RSMIn1#rsm_in{index = undefined}, |
711 |
1 |
apply_rsm_to_search_results( |
712 |
|
Results2, |
713 |
|
RSMIn2, |
714 |
|
RSMOut#rsm_out{index = Index} |
715 |
|
); |
716 |
|
|
717 |
|
%% Limit to <max>10</max> items |
718 |
|
apply_rsm_to_search_results(Results1, #rsm_in{max = Max} = RSMIn, RSMOut) |
719 |
|
when is_integer(Max) -> |
720 |
9 |
Results2 = lists:sublist(Results1, Max), |
721 |
9 |
apply_rsm_to_search_results(Results2, |
722 |
|
RSMIn#rsm_in{max = undefined}, RSMOut); |
723 |
|
|
724 |
|
%% Encode RSM output |
725 |
|
apply_rsm_to_search_results([_ | _] = Results, _, #rsm_out{} = RSMOut1) -> |
726 |
16 |
{FirstJID, _} = hd(Results), |
727 |
16 |
{LastJID, _} = lists:last(Results), |
728 |
16 |
RSMOut2 = RSMOut1#rsm_out{first = FirstJID, |
729 |
|
last = LastJID}, |
730 |
16 |
{Results, jlib:rsm_encode(RSMOut2)}; |
731 |
|
|
732 |
|
apply_rsm_to_search_results([], _, #rsm_out{} = RSMOut1) -> |
733 |
|
%% clear `index' without `after' |
734 |
5 |
RSMOut2 = RSMOut1#rsm_out{index = undefined}, |
735 |
5 |
{[], jlib:rsm_encode(RSMOut2)}. |
736 |
|
|
737 |
|
search_result_get_jid(#xmlel{name = <<"item">>, |
738 |
|
children = Children}) -> |
739 |
39 |
Fields = jlib:parse_xdata_fields(Children), |
740 |
39 |
case lists:keysearch(<<"jid">>, 1, Fields) of |
741 |
|
{value, {<<"jid">>, JID}} -> |
742 |
39 |
{ok, list_to_binary(JID)}; |
743 |
|
false -> |
744 |
:-( |
undefined |
745 |
|
end. |
746 |
|
|
747 |
|
parse_vcard(User, VHost, VCARD) -> |
748 |
:-( |
FN = xml:get_path_s(VCARD, [{elem, <<"FN">>}, cdata]), |
749 |
:-( |
Family = xml:get_path_s(VCARD, [{elem, <<"N">>}, |
750 |
|
{elem, <<"FAMILY">>}, cdata]), |
751 |
:-( |
Given = xml:get_path_s(VCARD, [{elem, <<"N">>}, |
752 |
|
{elem, <<"GIVEN">>}, cdata]), |
753 |
:-( |
Middle = xml:get_path_s(VCARD, [{elem, <<"N">>}, |
754 |
|
{elem, <<"MIDDLE">>}, cdata]), |
755 |
:-( |
Nickname = xml:get_path_s(VCARD, [{elem, <<"NICKNAME">>}, cdata]), |
756 |
:-( |
BDay = xml:get_path_s(VCARD, [{elem, <<"BDAY">>}, cdata]), |
757 |
:-( |
CTRY = xml:get_path_s(VCARD, [{elem, <<"ADR">>}, |
758 |
|
{elem, <<"CTRY">>}, cdata]), |
759 |
:-( |
Locality = xml:get_path_s(VCARD, [{elem, <<"ADR">>}, |
760 |
|
{elem, <<"LOCALITY">>}, cdata]), |
761 |
:-( |
EMail1 = xml:get_path_s(VCARD, [{elem, <<"EMAIL">>}, |
762 |
|
{elem, <<"USERID">>}, cdata]), |
763 |
:-( |
EMail2 = xml:get_path_s(VCARD, [{elem, <<"EMAIL">>}, cdata]), |
764 |
:-( |
OrgName = xml:get_path_s(VCARD, [{elem, <<"ORG">>}, |
765 |
|
{elem, <<"ORGNAME">>}, cdata]), |
766 |
:-( |
OrgUnit = xml:get_path_s(VCARD, [{elem, <<"ORG">>}, |
767 |
|
{elem, <<"ORGUNIT">>}, cdata]), |
768 |
:-( |
EMail = case EMail1 of |
769 |
:-( |
<<"">> -> EMail2; |
770 |
:-( |
_ -> EMail1 |
771 |
|
end, |
772 |
:-( |
try |
773 |
:-( |
LUser = jid:nodeprep(User), |
774 |
:-( |
LFN = prepare_index(<<"FN">>, FN), |
775 |
:-( |
LFamily = prepare_index(<<"FAMILY">>, Family), |
776 |
:-( |
LGiven = prepare_index(<<"GIVEN">>, Given), |
777 |
:-( |
LMiddle = prepare_index(<<"MIDDLE">>, Middle), |
778 |
:-( |
LNickname = prepare_index_allow_emoji(<<"NICKNAME">>, Nickname), |
779 |
:-( |
LBDay = prepare_index(<<"BDAY">>, BDay), |
780 |
:-( |
LCTRY = prepare_index(<<"CTRY">>, CTRY), |
781 |
:-( |
LLocality = prepare_index(<<"LOCALITY">>, Locality), |
782 |
:-( |
LEMail = prepare_index(<<"EMAIL">>, EMail), |
783 |
:-( |
LOrgName = prepare_index(<<"ORGNAME">>, OrgName), |
784 |
:-( |
LOrgUnit = prepare_index(<<"ORGUNIT">>, OrgUnit), |
785 |
|
|
786 |
:-( |
US = {LUser, VHost}, |
787 |
|
|
788 |
:-( |
{ok, #vcard_search{us = US, |
789 |
|
user = {User, VHost}, |
790 |
|
luser = LUser, |
791 |
|
fn = FN, lfn = LFN, |
792 |
|
family = Family, lfamily = LFamily, |
793 |
|
given = Given, lgiven = LGiven, |
794 |
|
middle = Middle, lmiddle = LMiddle, |
795 |
|
nickname = Nickname, lnickname = LNickname, |
796 |
|
bday = BDay, lbday = LBDay, |
797 |
|
ctry = CTRY, lctry = LCTRY, |
798 |
|
locality = Locality, llocality = LLocality, |
799 |
|
email = EMail, lemail = LEMail, |
800 |
|
orgname = OrgName, lorgname = LOrgName, |
801 |
|
orgunit = OrgUnit, lorgunit = LOrgUnit |
802 |
|
}} |
803 |
|
catch |
804 |
|
throw:{invalid_input, Info} -> |
805 |
:-( |
{error, {invalid_input, Info}} |
806 |
|
end. |
807 |
|
|
808 |
|
prepare_index(FieldName, Value) -> |
809 |
:-( |
case jid:str_tolower(Value) of |
810 |
|
error -> |
811 |
:-( |
throw({invalid_input, {FieldName, Value}}); |
812 |
|
LValue -> |
813 |
:-( |
LValue |
814 |
|
end. |
815 |
|
|
816 |
|
prepare_index_allow_emoji(FieldName, Value) -> |
817 |
:-( |
{ok, Re} = re:compile(<<"[^[:alnum:][:space:][:punct:]]">>, [unicode, ucp]), |
818 |
:-( |
Sanitized = re:replace(Value, Re, <<"">>, [global]), |
819 |
:-( |
prepare_index(FieldName, Sanitized). |
820 |
|
|
821 |
|
|
822 |
|
-spec get_default_reported_fields(binary()) -> exml:element(). |
823 |
|
get_default_reported_fields(Lang) -> |
824 |
:-( |
#xmlel{name = <<"reported">>, |
825 |
|
children = [ |
826 |
|
?TLFIELD(<<"jid-single">>, <<"Jabber ID">>, <<"jid">>), |
827 |
|
?TLFIELD(<<"text-single">>, <<"Full Name">>, <<"fn">>), |
828 |
|
?TLFIELD(<<"text-single">>, <<"Name">>, <<"first">>), |
829 |
|
?TLFIELD(<<"text-single">>, <<"Middle Name">>, <<"middle">>), |
830 |
|
?TLFIELD(<<"text-single">>, <<"Family Name">>, <<"last">>), |
831 |
|
?TLFIELD(<<"text-single">>, <<"Nickname">>, <<"nick">>), |
832 |
|
?TLFIELD(<<"text-single">>, <<"Birthday">>, <<"bday">>), |
833 |
|
?TLFIELD(<<"text-single">>, <<"Country">>, <<"ctry">>), |
834 |
|
?TLFIELD(<<"text-single">>, <<"City">>, <<"locality">>), |
835 |
|
?TLFIELD(<<"text-single">>, <<"Email">>, <<"email">>), |
836 |
|
?TLFIELD(<<"text-single">>, <<"Organization Name">>, <<"orgname">>), |
837 |
|
?TLFIELD(<<"text-single">>, <<"Organization Unit">>, <<"orgunit">>) |
838 |
|
]}. |
839 |
|
|
840 |
|
config_metrics(Host) -> |
841 |
228 |
mongoose_module_metrics:opts_for_module(Host, ?MODULE, [backend]). |
842 |
|
|
843 |
|
vcard_error(IQ = #iq{sub_el = VCARD}, ReasonEl) -> |
844 |
2 |
IQ#iq{type = error, sub_el = [VCARD, ReasonEl]}. |
845 |
|
|
846 |
|
directory_jid_to_server_host(#jid{lserver = DirHost}) -> |
847 |
25 |
case mongoose_domain_api:get_subdomain_info(DirHost) of |
848 |
|
{ok, #{parent_domain := ServerHost}} when is_binary(ServerHost) -> |
849 |
25 |
ServerHost; |
850 |
|
Other -> |
851 |
:-( |
error({dir_jid_to_server_host_failed, DirHost, Other}) |
852 |
|
end. |