~ubuntu-branches/ubuntu/trusty/ejabberd/trusty-proposed

« back to all changes in this revision

Viewing changes to src/mod_caps.erl

  • Committer: Bazaar Package Importer
  • Author(s): Konstantin Khomoutov, Konstantin Khomoutov, Gerfried Fuchs
  • Date: 2010-04-06 13:00:03 UTC
  • mfrom: (1.1.14 upstream) (13.1.1 sid)
  • Revision ID: james.westby@ubuntu.com-20100406130003-ub626hx26kgazp79
Tags: 2.1.3-1
[ Konstantin Khomoutov ]
* New upstream release.
* Remove obsolete c2s-p1-fsm.patch
* Remove obsolete ejabberdctl-help-dashes.patch
* Update mod_admin_extra to revision 1078
* Refresh shared_roster_recent.patch
* Refresh shared_roster_online.patch
* Clarify how to do mixed IPv4/IPv6 setup of ejabberd listeners
  as suggested by Marc Dequènes in the discussion of #573801.

[ Gerfried Fuchs ]
* Remove Torsten Werner from maintainer field on his own wish - thanks for
  your work so far! (closes: #578722)
* Promote Konstantin to Maintainer to best fit reality. Enjoy. :)

Show diffs side-by-side

added added

removed removed

Lines of Context:
33
33
-behaviour(gen_mod).
34
34
 
35
35
-export([read_caps/1,
36
 
         get_caps/1,
37
 
         note_caps/3,
38
 
         wait_caps/2,
39
 
         clear_caps/1,
40
 
         get_features/2,
41
 
         get_user_resources/2,
42
 
         handle_disco_response/3]).
 
36
         get_features/1]).
43
37
 
44
38
%% gen_mod callbacks
45
39
-export([start/2, start_link/2,
55
49
        ]).
56
50
 
57
51
%% hook handlers
58
 
-export([receive_packet/3,
59
 
         receive_packet/4,
60
 
         presence_probe/3,
61
 
         remove_connection/3]).
 
52
-export([user_send_packet/3]).
62
53
 
63
54
-include("ejabberd.hrl").
64
55
-include("jlib.hrl").
65
56
 
66
57
-define(PROCNAME, ejabberd_mod_caps).
67
 
-define(DICT, dict).
68
 
-define(CAPS_QUERY_TIMEOUT, 60000). % 1mn without answer, consider client never answer
69
58
 
70
59
-record(caps, {node, version, exts}).
71
60
-record(caps_features, {node_pair, features = []}).
72
 
-record(user_caps, {jid, caps}).
73
 
-record(user_caps_resources, {uid, resource}).
74
 
-record(state, {host,
75
 
                disco_requests = ?DICT:new(),
76
 
                feature_queries = []}).
 
61
 
 
62
-record(state, {host}).
 
63
 
 
64
%%====================================================================
 
65
%% API
 
66
%%====================================================================
 
67
start_link(Host, Opts) ->
 
68
    Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
 
69
    gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
 
70
 
 
71
start(Host, Opts) ->
 
72
    Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
 
73
    ChildSpec =
 
74
        {Proc,
 
75
         {?MODULE, start_link, [Host, Opts]},
 
76
         transient,
 
77
         1000,
 
78
         worker,
 
79
         [?MODULE]},
 
80
    supervisor:start_child(ejabberd_sup, ChildSpec).
 
81
 
 
82
stop(Host) ->
 
83
    Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
 
84
    gen_server:call(Proc, stop),
 
85
    supervisor:terminate_child(ejabberd_sup, Proc),
 
86
    supervisor:delete_child(ejabberd_sup, Proc).
 
87
 
 
88
%% get_features returns a list of features implied by the given caps
 
89
%% record (as extracted by read_caps) or 'unknown' if features are
 
90
%% not completely collected at the moment.
 
91
get_features(nothing) ->
 
92
    [];
 
93
get_features(#caps{node = Node, version = Version, exts = Exts}) ->
 
94
    SubNodes = [Version | Exts],
 
95
    lists:foldl(
 
96
      fun(SubNode, Acc) ->
 
97
              case mnesia:dirty_read({caps_features,
 
98
                                      node_to_binary(Node, SubNode)}) of
 
99
                  [] ->
 
100
                      Acc;
 
101
                  [#caps_features{features = Features}] ->
 
102
                      binary_to_features(Features) ++ Acc
 
103
              end
 
104
      end, [], SubNodes).
77
105
 
78
106
%% read_caps takes a list of XML elements (the child elements of a
79
107
%% <presence/> stanza) and returns an opaque value representing the
81
109
%% capabilities are advertised.
82
110
read_caps(Els) ->
83
111
    read_caps(Els, nothing).
 
112
 
84
113
read_caps([{xmlelement, "c", Attrs, _Els} | Tail], Result) ->
85
114
    case xml:get_attr_s("xmlns", Attrs) of
86
115
        ?NS_CAPS ->
103
132
read_caps([], Result) ->
104
133
    Result.
105
134
 
106
 
%% get_caps reads user caps from database
107
 
%% here we handle a simple retry loop, to avoid race condition
108
 
%% when asking caps while we still did not called note_caps
109
 
%% timeout is set to 10s
110
 
%% this is to be improved, but without altering performances.
111
 
%% if we did not get user presence 10s after getting presence_probe
112
 
%% we assume it has no caps
113
 
get_caps(LJID) ->
114
 
    get_caps(LJID, 5).
115
 
get_caps(_, 0) ->
116
 
    nothing;
117
 
get_caps(LJID, Retry) ->
118
 
    case catch mnesia:dirty_read({user_caps, jid_to_binary(LJID)}) of
119
 
        [#user_caps{caps=waiting}] ->
120
 
            timer:sleep(2000),
121
 
            get_caps(LJID, Retry-1);
122
 
        [#user_caps{caps=Caps}] ->
123
 
            Caps;
124
 
        _ ->
125
 
            nothing
126
 
    end.
127
 
 
128
 
%% clear_caps removes user caps from database
129
 
clear_caps(JID) ->
130
 
    {U, S, R} = jlib:jid_tolower(JID),
131
 
    BJID = jid_to_binary(JID),
132
 
    BUID = jid_to_binary({U, S, []}),
133
 
    catch mnesia:dirty_delete({user_caps, BJID}),
134
 
    catch mnesia:dirty_delete_object(#user_caps_resources{uid = BUID, resource = list_to_binary(R)}),
135
 
    ok.
136
 
 
137
 
%% give default user resource
138
 
get_user_resources(LUser, LServer) ->
139
 
    case catch mnesia:dirty_read({user_caps_resources, jid_to_binary({LUser, LServer, []})}) of
140
 
        {'EXIT', _} ->
141
 
            [];
142
 
        Resources ->
143
 
            lists:map(fun(#user_caps_resources{resource=R}) -> binary_to_list(R) end, Resources)
144
 
    end.
145
 
 
146
 
%% note_caps should be called to make the module request disco
147
 
%% information.  Host is the host that asks, From is the full JID that
148
 
%% sent the caps packet, and Caps is what read_caps returned.
149
 
note_caps(Host, From, Caps) ->
150
 
    Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
151
 
    gen_server:cast(Proc, {note_caps, From, Caps}).
152
 
 
153
 
%% wait_caps should be called just before note_caps
154
 
%% it allows to lock get_caps usage for code using presence_probe
155
 
%% that may run before we get any chance to note_caps.
156
 
wait_caps(Host, From) ->
157
 
    Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
158
 
    gen_server:cast(Proc, {wait_caps, From}).
159
 
 
160
 
%% get_features returns a list of features implied by the given caps
161
 
%% record (as extracted by read_caps).  It may block, and may signal a
162
 
%% timeout error.
163
 
get_features(Host, Caps) ->
164
 
    case Caps of
165
 
        nothing -> 
166
 
            [];
167
 
        #caps{} ->
168
 
            Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
169
 
            gen_server:call(Proc, {get_features, Caps})
170
 
    end.
171
 
 
172
 
start_link(Host, Opts) ->
173
 
    Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
174
 
    gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
175
 
 
176
 
start(Host, Opts) ->
177
 
    Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
178
 
    ChildSpec =
179
 
        {Proc,
180
 
         {?MODULE, start_link, [Host, Opts]},
181
 
         transient,
182
 
         1000,
183
 
         worker,
184
 
         [?MODULE]},
185
 
    supervisor:start_child(ejabberd_sup, ChildSpec).
186
 
 
187
 
stop(Host) ->
188
 
    Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
189
 
    gen_server:call(Proc, stop).
190
 
 
191
 
receive_packet(From, To, {xmlelement, "presence", Attrs, Els}) ->
192
 
    case xml:get_attr_s("type", Attrs) of
193
 
    "probe" ->
194
 
        ok;
195
 
    "error" ->
196
 
        ok;
197
 
    "invisible" ->
198
 
        ok;
199
 
    "subscribe" ->
200
 
        ok;
201
 
    "subscribed" ->
202
 
        ok;
203
 
    "unsubscribe" ->
204
 
        ok;
205
 
    "unsubscribed" ->
206
 
        ok;
207
 
    "unavailable" ->
208
 
        {_, S1, _} = jlib:jid_tolower(From),
209
 
        case jlib:jid_tolower(To) of
210
 
        {_, S1, _} -> ok;
211
 
        _ -> clear_caps(From)
212
 
        end;
213
 
        %% TODO: probe client, and clean only if no answers
214
 
        %% as far as protocol does not allow inter-server communication to
215
 
        %% let remote server handle it's own caps to decide which user is to be
216
 
        %% notified, we must keep a cache of online status of external contacts
217
 
        %% this is absolutely not scallable, but we have no choice for now
218
 
        %% we can only use unavailable presence, but if remote user just remove a local user
219
 
        %% from its roster, then it's considered as offline, so he does not receive local PEP
220
 
        %% anymore until he login again.
221
 
        %% This is tracked in EJAB-943
222
 
    _ ->
223
 
        note_caps(To#jid.lserver, From, read_caps(Els))
 
135
%%====================================================================
 
136
%% Hooks
 
137
%%====================================================================
 
138
user_send_packet(#jid{luser = User, lserver = Server} = From,
 
139
                 #jid{luser = User, lserver = Server, lresource = ""},
 
140
                 {xmlelement, "presence", Attrs, Els}) ->
 
141
    Type = xml:get_attr_s("type", Attrs),
 
142
    if Type == ""; Type == "available" ->
 
143
            case read_caps(Els) of
 
144
                nothing ->
 
145
                    ok;
 
146
                #caps{version = Version, exts = Exts} = Caps ->
 
147
                    feature_request(Server, From, Caps, [Version | Exts])
 
148
            end;
 
149
       true ->
 
150
            ok
224
151
    end;
225
 
receive_packet(_, _, _) ->
 
152
user_send_packet(_From, _To, _Packet) ->
226
153
    ok.
227
154
 
228
 
receive_packet(_JID, From, To, Packet) ->
229
 
    receive_packet(From, To, Packet).
230
 
 
231
 
presence_probe(From, To, _) ->
232
 
    wait_caps(To#jid.lserver, From).
233
 
 
234
 
remove_connection(_SID, JID, _Info) ->
235
 
    clear_caps(JID).
236
 
 
237
 
jid_to_binary(JID) ->
238
 
    {U, S, R} = jlib:jid_tolower(JID),
239
 
    list_to_binary(jlib:jid_to_string({U, S, R})).
240
 
 
241
 
caps_to_binary(#caps{node = Node, version = Version, exts = Exts}) ->
242
 
    BExts = [list_to_binary(Ext) || Ext <- Exts],
243
 
    #caps{node = list_to_binary(Node), version = list_to_binary(Version), exts = BExts}.
244
 
 
245
 
node_to_binary(Node, SubNode) ->
246
 
    {list_to_binary(Node), list_to_binary(SubNode)}.
247
 
 
248
 
features_to_binary(L) -> [list_to_binary(I) || I <- L].
249
 
binary_to_features(L) -> [binary_to_list(I) || I <- L].
250
 
 
251
155
%%====================================================================
252
156
%% gen_server callbacks
253
157
%%====================================================================
254
 
 
255
158
init([Host, _Opts]) ->
256
159
    mnesia:create_table(caps_features,
257
160
                        [{disc_copies, [node()]},
 
161
                         {local_content, true},
258
162
                         {attributes, record_info(fields, caps_features)}]),
259
 
    mnesia:create_table(user_caps,
260
 
                        [{ram_copies, [node()]},
261
 
                         {attributes, record_info(fields, user_caps)}]),
262
 
    mnesia:create_table(user_caps_resources,
263
 
                        [{ram_copies, [node()]},
264
 
                         {type, bag},
265
 
                         {attributes, record_info(fields, user_caps_resources)}]),
266
 
    mnesia:delete_table(user_caps_default),
267
 
    mnesia:clear_table(user_caps),            % clean in case of explicitely set to disc_copies
268
 
    mnesia:clear_table(user_caps_resources),  % clean in case of explicitely set to disc_copies
269
 
    ejabberd_hooks:add(user_receive_packet, Host, ?MODULE, receive_packet, 30),
270
 
    ejabberd_hooks:add(s2s_receive_packet, Host, ?MODULE, receive_packet, 30),
271
 
    ejabberd_hooks:add(presence_probe_hook, Host, ?MODULE, presence_probe, 20),
272
 
    ejabberd_hooks:add(sm_remove_connection_hook, Host, ?MODULE, remove_connection, 20),
 
163
    mnesia:add_table_copy(caps_features, node(), disc_copies),
 
164
    ejabberd_hooks:add(user_send_packet, Host,
 
165
                       ?MODULE, user_send_packet, 75),
273
166
    {ok, #state{host = Host}}.
274
167
 
275
 
maybe_get_features(#caps{node = Node, version = Version, exts = Exts}) ->
276
 
    SubNodes = [Version | Exts],
277
 
    %% Make sure that we have all nodes we need to know.
278
 
    %% If a single one is missing, we wait for more disco
279
 
    %% responses.
280
 
    case lists:foldl(fun(SubNode, Acc) ->
281
 
                        case Acc of
282
 
                            fail -> fail;
283
 
                            _ ->
284
 
                                case mnesia:dirty_read({caps_features, {Node, SubNode}}) of
285
 
                                    [] -> fail;
286
 
                                    [#caps_features{features = Features}] -> Features ++ Acc %% TODO binary
287
 
                                end
288
 
                        end
289
 
                end, [], SubNodes) of
290
 
        fail -> wait;
291
 
        Features -> {ok, Features}
292
 
    end.
293
 
 
294
 
timestamp() ->
295
 
    {MegaSecs, Secs, _MicroSecs} = now(),
296
 
    MegaSecs * 1000000 + Secs.
297
 
 
298
 
handle_call({get_features, Caps}, From, State) ->
299
 
    case maybe_get_features(Caps) of
300
 
        {ok, Features} -> 
301
 
            {reply, binary_to_features(Features), State};
302
 
        wait ->
303
 
            gen_server:cast(self(), visit_feature_queries),
304
 
            Timeout = timestamp() + 10,
305
 
            FeatureQueries = State#state.feature_queries,
306
 
            NewFeatureQueries = [{From, Caps, Timeout} | FeatureQueries],
307
 
            NewState = State#state{feature_queries = NewFeatureQueries},
308
 
            {noreply, NewState}
309
 
    end;
310
 
 
311
168
handle_call(stop, _From, State) ->
312
 
    {stop, normal, ok, State}.
313
 
 
314
 
handle_cast({note_caps, From, nothing}, State) ->
315
 
    BJID = jid_to_binary(From),
316
 
    catch mnesia:dirty_delete({user_caps, BJID}),
317
 
    {noreply, State};
318
 
handle_cast({note_caps, From, 
319
 
             #caps{node = Node, version = Version, exts = Exts} = Caps}, 
320
 
            #state{host = Host, disco_requests = Requests} = State) ->
321
 
    %% XXX: this leads to race conditions where ejabberd will send
322
 
    %% lots of caps disco requests.
323
 
    {U, S, R} = jlib:jid_tolower(From),
324
 
    BJID = jid_to_binary(From),
325
 
    mnesia:transaction(fun() ->
326
 
        mnesia:write(#user_caps{jid = BJID, caps = caps_to_binary(Caps)}),
327
 
        case ejabberd_sm:get_user_resources(U, S) of
328
 
            [] ->
329
 
                % only store resources of caps aware external contacts
330
 
                BUID = jid_to_binary({U, S, []}),
331
 
                mnesia:write(#user_caps_resources{uid = BUID, resource = list_to_binary(R)});
332
 
            _ ->
333
 
                ok
334
 
        end
335
 
    end),
336
 
    %% Now, find which of these are not already in the database.
337
 
    SubNodes = [Version | Exts],
338
 
    case lists:foldl(fun(SubNode, Acc) ->
339
 
                                case mnesia:dirty_read({caps_features, node_to_binary(Node, SubNode)}) of
340
 
                                    [] ->
341
 
                                        [SubNode | Acc];
342
 
                                    _ ->
343
 
                                        Acc
344
 
                                end
345
 
                        end, [], SubNodes) of
346
 
        [] ->
347
 
            {noreply, State};
348
 
        Missing ->
349
 
            %% For each unknown caps "subnode", we send a disco request.
350
 
            NewRequests = lists:foldl(
351
 
                fun(SubNode, Dict) ->
352
 
                          ID = randoms:get_string(),
353
 
                          Stanza =
354
 
                              {xmlelement, "iq",
355
 
                               [{"type", "get"},
356
 
                                {"id", ID}],
357
 
                               [{xmlelement, "query",
358
 
                                 [{"xmlns", ?NS_DISCO_INFO},
359
 
                                  {"node", lists:concat([Node, "#", SubNode])}],
360
 
                                 []}]},
361
 
                          ejabberd_local:register_iq_response_handler
362
 
                            (Host, ID, ?MODULE, handle_disco_response),
363
 
                          ejabberd_router:route(jlib:make_jid("", Host, ""), From, Stanza),
364
 
                          timer:send_after(?CAPS_QUERY_TIMEOUT, self(), {disco_timeout, ID, BJID}),
365
 
                          ?DICT:store(ID, node_to_binary(Node, SubNode), Dict)
366
 
                  end, Requests, Missing),
367
 
            {noreply, State#state{disco_requests = NewRequests}}
368
 
    end;
369
 
handle_cast({wait_caps, From}, State) ->
370
 
    BJID = jid_to_binary(From),
371
 
    mnesia:dirty_write(#user_caps{jid = BJID, caps = waiting}),
372
 
    {noreply, State};
373
 
handle_cast({disco_response, From, _To, 
374
 
             #iq{type = Type, id = ID,
375
 
                 sub_el = SubEls}},
376
 
            #state{disco_requests = Requests} = State) ->
377
 
    case {Type, SubEls} of
378
 
        {result, [{xmlelement, "query", _Attrs, Els}]} ->
379
 
            case ?DICT:find(ID, Requests) of
380
 
                {ok, BinaryNode} ->
381
 
                    Features =
382
 
                        lists:flatmap(fun({xmlelement, "feature", FAttrs, _}) ->
383
 
                                              [xml:get_attr_s("var", FAttrs)];
384
 
                                         (_) ->
385
 
                                              []
386
 
                                      end, Els),
387
 
                    mnesia:dirty_write(#caps_features{node_pair = BinaryNode, features = features_to_binary(Features)}),
388
 
                    gen_server:cast(self(), visit_feature_queries);
389
 
                error ->
390
 
                    ?DEBUG("ID '~s' matches no query", [ID])
391
 
            end;
392
 
        {error, _} ->
393
 
            %% XXX: if we get error, we cache empty feature not to probe the client continuously
394
 
            case ?DICT:find(ID, Requests) of
395
 
                {ok, BinaryNode} ->
396
 
                    mnesia:dirty_write(#caps_features{node_pair = BinaryNode}),
397
 
                    gen_server:cast(self(), visit_feature_queries);
398
 
                error ->
399
 
                    ?DEBUG("ID '~s' matches no query", [ID])
400
 
            end;
401
 
            %gen_server:cast(self(), visit_feature_queries),
402
 
            %?DEBUG("Error IQ reponse from ~s:~n~p", [jlib:jid_to_string(From), SubEls]);
403
 
        {result, _} ->
404
 
            ?DEBUG("Invalid IQ contents from ~s:~n~p", [jlib:jid_to_string(From), SubEls]);
405
 
        _ ->
406
 
            %% Can't do anything about errors
407
 
            ok
408
 
    end,
409
 
    NewRequests = ?DICT:erase(ID, Requests),
410
 
    {noreply, State#state{disco_requests = NewRequests}};
411
 
handle_cast({disco_timeout, ID, BJID}, #state{host = Host, disco_requests = Requests} = State) ->
412
 
    %% do not wait a response anymore for this IQ, client certainly will never answer
413
 
    NewRequests = case ?DICT:is_key(ID, Requests) of
414
 
    true ->
415
 
        catch mnesia:dirty_delete({user_caps, BJID}),
416
 
        ejabberd_local:unregister_iq_response_handler(Host, ID),
417
 
        ?DICT:erase(ID, Requests);
418
 
    false ->
419
 
        Requests
420
 
    end,
421
 
    {noreply, State#state{disco_requests = NewRequests}};
422
 
handle_cast(visit_feature_queries, #state{feature_queries = FeatureQueries} = State) ->
423
 
    Timestamp = timestamp(),
424
 
    NewFeatureQueries =
425
 
        lists:foldl(fun({From, Caps, Timeout}, Acc) ->
426
 
                            case maybe_get_features(Caps) of
427
 
                                wait when Timeout > Timestamp -> [{From, Caps, Timeout} | Acc];
428
 
                                wait -> Acc;
429
 
                                {ok, Features} ->
430
 
                                    gen_server:reply(From, Features),
431
 
                                    Acc
432
 
                            end
433
 
                    end, [], FeatureQueries),
434
 
    {noreply, State#state{feature_queries = NewFeatureQueries}}.
435
 
 
436
 
handle_disco_response(From, To, IQ) ->
437
 
    #jid{lserver = Host} = To,
438
 
    Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
439
 
    gen_server:cast(Proc, {disco_response, From, To, IQ}).
 
169
    {stop, normal, ok, State};
 
170
handle_call(_Req, _From, State) ->
 
171
    {reply, {error, badarg}, State}.
 
172
 
 
173
handle_cast(_Msg, State) ->
 
174
    {noreply, State}.
440
175
 
441
176
handle_info(_Info, State) ->
442
177
    {noreply, State}.
443
178
 
444
179
terminate(_Reason, State) ->
445
180
    Host = State#state.host,
446
 
    ejabberd_hooks:delete(user_receive_packet, Host, ?MODULE, receive_packet, 30),
447
 
    ejabberd_hooks:delete(s2s_receive_packet, Host, ?MODULE, receive_packet, 30),
448
 
    ejabberd_hooks:delete(presence_probe_hook, Host, ?MODULE, presence_probe, 20),
449
 
    ejabberd_hooks:delete(sm_remove_connection_hook, Host, ?MODULE, remove_connection, 20),
 
181
    ejabberd_hooks:delete(user_send_packet, Host,
 
182
                          ?MODULE, user_send_packet, 75),
450
183
    ok.
451
184
 
452
185
code_change(_OldVsn, State, _Extra) ->
453
186
    {ok, State}.
 
187
 
 
188
%%====================================================================
 
189
%% Aux functions
 
190
%%====================================================================
 
191
feature_request(Host, From, Caps, [SubNode | Tail] = SubNodes) ->
 
192
    Node = Caps#caps.node,
 
193
    BinaryNode = node_to_binary(Node, SubNode),
 
194
    case mnesia:dirty_read({caps_features, BinaryNode}) of
 
195
        [] ->
 
196
            IQ = #iq{type = get,
 
197
                     xmlns = ?NS_DISCO_INFO,
 
198
                     sub_el = [{xmlelement, "query",
 
199
                                [{"xmlns", ?NS_DISCO_INFO},
 
200
                                 {"node", Node ++ "#" ++ SubNode}],
 
201
                                []}]},
 
202
            F = fun(IQReply) ->
 
203
                        feature_response(
 
204
                          IQReply, Host, From, Caps, SubNodes)
 
205
                end,
 
206
            ejabberd_local:route_iq(
 
207
              jlib:make_jid("", Host, ""), From, IQ, F);
 
208
        _ ->
 
209
            feature_request(Host, From, Caps, Tail)
 
210
    end;
 
211
feature_request(_Host, _From, _Caps, []) ->
 
212
    ok.
 
213
 
 
214
feature_response(#iq{type = result,
 
215
                     sub_el = [{xmlelement, _, _, Els}]},
 
216
                 Host, From, Caps, [SubNode | SubNodes]) ->
 
217
    Features = lists:flatmap(
 
218
                 fun({xmlelement, "feature", FAttrs, _}) ->
 
219
                         [xml:get_attr_s("var", FAttrs)];
 
220
                    (_) ->
 
221
                         []
 
222
                 end, Els),
 
223
    BinaryNode = node_to_binary(Caps#caps.node, SubNode),
 
224
    mnesia:dirty_write(
 
225
      #caps_features{node_pair = BinaryNode,
 
226
                     features = features_to_binary(Features)}),
 
227
    feature_request(Host, From, Caps, SubNodes);
 
228
feature_response(timeout, _Host, _From, _Caps, _SubNodes) ->
 
229
    ok;
 
230
feature_response(_IQResult, Host, From, Caps, [SubNode | SubNodes]) ->
 
231
    %% We got type=error or invalid type=result stanza, so
 
232
    %% we cache empty feature not to probe the client permanently
 
233
    BinaryNode = node_to_binary(Caps#caps.node, SubNode),
 
234
    mnesia:dirty_write(#caps_features{node_pair = BinaryNode}),
 
235
    feature_request(Host, From, Caps, SubNodes).
 
236
 
 
237
node_to_binary(Node, SubNode) ->
 
238
    {list_to_binary(Node), list_to_binary(SubNode)}.
 
239
 
 
240
features_to_binary(L) -> [list_to_binary(I) || I <- L].
 
241
binary_to_features(L) -> [binary_to_list(I) || I <- L].