• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

processone / ejabberd / 1296

19 Jan 2026 11:25AM UTC coverage: 33.562% (+0.09%) from 33.468%
1296

push

github

badlop
mod_conversejs: Cosmetic change: sort paths alphabetically

0 of 4 new or added lines in 1 file covered. (0.0%)

11245 existing lines in 174 files now uncovered.

15580 of 46421 relevant lines covered (33.56%)

1074.56 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

52.99
/src/mod_pubsub.erl
1
%%%----------------------------------------------------------------------
2
%%% File    : mod_pubsub.erl
3
%%% Author  : Christophe Romain <christophe.romain@process-one.net>
4
%%% Purpose : Publish Subscribe service (XEP-0060)
5
%%% Created :  1 Dec 2007 by Christophe Romain <christophe.romain@process-one.net>
6
%%%
7
%%%
8
%%% ejabberd, Copyright (C) 2002-2026   ProcessOne
9
%%%
10
%%% This program is free software; you can redistribute it and/or
11
%%% modify it under the terms of the GNU General Public License as
12
%%% published by the Free Software Foundation; either version 2 of the
13
%%% License, or (at your option) any later version.
14
%%%
15
%%% This program is distributed in the hope that it will be useful,
16
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
17
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18
%%% General Public License for more details.
19
%%%
20
%%% You should have received a copy of the GNU General Public License along
21
%%% with this program; if not, write to the Free Software Foundation, Inc.,
22
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23
%%%
24
%%%----------------------------------------------------------------------
25

26
%%% Support for subscription-options and multi-subscribe features was
27
%%% added by Brian Cully (bjc AT kublai.com). Subscriptions and options are
28
%%% stored in the pubsub_subscription table, with a link to them provided
29
%%% by the subscriptions field of pubsub_state. For information on
30
%%% subscription-options and mulit-subscribe see XEP-0060 sections 6.1.6,
31
%%% 6.2.3.1, 6.2.3.5, and 6.3. For information on subscription leases see
32
%%% XEP-0060 section 12.18.
33

34
-module(mod_pubsub).
35
-behaviour(gen_mod).
36
-behaviour(gen_server).
37
-author('christophe.romain@process-one.net').
38
-protocol({xep, 48, '1.2', '0.5.0', "complete", ""}).
39
-protocol({xep, 60, '1.14', '0.5.0', "partial", ""}).
40
-protocol({xep, 163, '1.2.2', '2.0.0', "complete", ""}).
41
-protocol({xep, 223, '1.1.1', '2.0.0', "complete", ""}).
42
-protocol({xep, 248, '0.2', '2.1.0', "complete", ""}).
43

44
-include("logger.hrl").
45
-include_lib("xmpp/include/xmpp.hrl").
46
-include("pubsub.hrl").
47
-include("mod_roster.hrl").
48
-include("translate.hrl").
49

50
-include("ejabberd_commands.hrl").
51

52
-define(STDTREE, <<"tree">>).
53
-define(STDNODE, <<"flat">>).
54
-define(PEPNODE, <<"pep">>).
55

56
%% exports for hooks
57
-export([presence_probe/3, caps_add/3, caps_update/3,
58
    in_subscription/2, out_subscription/1,
59
    on_self_presence/1, on_user_offline/2, remove_user/2,
60
    disco_local_identity/5, disco_local_features/5,
61
    disco_local_items/5, disco_sm_identity/5,
62
    disco_sm_features/5, disco_sm_items/5,
63
    c2s_handle_info/2]).
64

65
%% exported iq handlers
66
-export([iq_sm/1, process_disco_info/1, process_disco_items/1,
67
         process_pubsub/1, process_pubsub_owner/1, process_vcard/1,
68
         process_commands/1]).
69

70
%% exports for console debug manual use
71
-export([create_node/5, create_node/7, delete_node/3,
72
    subscribe_node/5, unsubscribe_node/5, publish_item/6, publish_item/8,
73
    delete_item/4, delete_item/5, send_items/7, get_items/2, get_item/3,
74
    get_cached_item/2, get_configure/5, set_configure/5,
75
    tree_action/3, node_action/4, node_call/4]).
76

77
%% general helpers for plugins
78
-export([extended_error/2, service_jid/1,
79
    tree/1, tree/2, plugin/2, plugins/1, config/3,
80
    host/1, serverhost/1]).
81

82
%% pubsub#errors
83
-export([err_closed_node/0, err_configuration_required/0,
84
         err_invalid_jid/0, err_invalid_options/0, err_invalid_payload/0,
85
         err_invalid_subid/0, err_item_forbidden/0, err_item_required/0,
86
         err_jid_required/0, err_max_items_exceeded/0, err_max_nodes_exceeded/0,
87
         err_nodeid_required/0, err_not_in_roster_group/0, err_not_subscribed/0,
88
         err_payload_too_big/0, err_payload_required/0,
89
         err_pending_subscription/0, err_precondition_not_met/0,
90
         err_presence_subscription_required/0, err_subid_required/0,
91
         err_too_many_subscriptions/0, err_unsupported/1,
92
         err_unsupported_access_model/0]).
93

94
%% API and gen_server callbacks
95
-export([start/2, stop/1, init/1,
96
    handle_call/3, handle_cast/2, handle_info/2, mod_doc/0,
97
    terminate/2, code_change/3, depends/2, mod_opt_type/1, mod_options/1]).
98

99
%% ejabberd commands
100
-export([get_commands_spec/0, delete_old_items/1, delete_expired_items/0]).
101

102
-export([route/1]).
103

104
%%====================================================================
105
%% API
106
%%====================================================================
107
%%--------------------------------------------------------------------
108
%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
109
%% Description: Starts the server
110
%%--------------------------------------------------------------------
111

112
-export_type([
113
        host/0,
114
        hostPubsub/0,
115
        hostPEP/0,
116
        %%
117
        nodeIdx/0,
118
        nodeId/0,
119
        itemId/0,
120
        subId/0,
121
        payload/0,
122
        %%
123
        nodeOption/0,
124
        nodeOptions/0,
125
        subOption/0,
126
        subOptions/0,
127
        pubOption/0,
128
        pubOptions/0,
129
        %%
130
        affiliation/0,
131
        subscription/0,
132
        accessModel/0,
133
        publishModel/0
134
        ]).
135

136
%% -type payload() defined here because the -type xmlel() is not accessible
137
%% from pubsub.hrl
138
-type(payload() :: [] | [xmlel(),...]).
139

140
-export_type([
141
        pubsubNode/0,
142
        pubsubState/0,
143
        pubsubItem/0,
144
        pubsubSubscription/0,
145
        pubsubLastItem/0
146
        ]).
147

148
-type(pubsubNode() ::
149
    #pubsub_node{
150
        nodeid  :: {Host::mod_pubsub:host(), Node::mod_pubsub:nodeId()},
151
        id      :: Nidx::mod_pubsub:nodeIdx(),
152
        parents :: [Node::mod_pubsub:nodeId()],
153
        type    :: Type::binary(),
154
        owners  :: [Owner::ljid(),...],
155
        options :: Opts::mod_pubsub:nodeOptions()
156
        }
157
    ).
158

159
-type(pubsubState() ::
160
    #pubsub_state{
161
        stateid       :: {Entity::ljid(), Nidx::mod_pubsub:nodeIdx()},
162
        nodeidx       :: Nidx::mod_pubsub:nodeIdx(),
163
        items         :: [ItemId::mod_pubsub:itemId()],
164
        affiliation   :: Affs::mod_pubsub:affiliation(),
165
        subscriptions :: [{Sub::mod_pubsub:subscription(), SubId::mod_pubsub:subId()}]
166
        }
167
    ).
168

169
-type(pubsubItem() ::
170
    #pubsub_item{
171
        itemid       :: {ItemId::mod_pubsub:itemId(), Nidx::mod_pubsub:nodeIdx()},
172
        nodeidx      :: Nidx::mod_pubsub:nodeIdx(),
173
        creation     :: {erlang:timestamp(), ljid()},
174
        modification :: {erlang:timestamp(), ljid()},
175
        payload      :: mod_pubsub:payload()
176
        }
177
    ).
178

179
-type(pubsubSubscription() ::
180
    #pubsub_subscription{
181
        subid   :: SubId::mod_pubsub:subId(),
182
        options :: [] | mod_pubsub:subOptions()
183
        }
184
    ).
185

186
-type(pubsubLastItem() ::
187
    #pubsub_last_item{
188
        nodeid   :: {binary(), mod_pubsub:nodeIdx()},
189
        itemid   :: mod_pubsub:itemId(),
190
        creation :: {erlang:timestamp(), ljid()},
191
        payload  :: mod_pubsub:payload()
192
        }
193
    ).
194

195
-record(state,
196
    {
197
        server_host,
198
        hosts,
199
        access,
200
        pep_mapping             = [],
201
        ignore_pep_from_offline = true,
202
        last_item_cache         = false,
203
        max_items_node          = ?MAXITEMS,
204
        max_subscriptions_node  = undefined,
205
        default_node_config     = [],
206
        nodetree                = <<"nodetree_", (?STDTREE)/binary>>,
207
        plugins                 = [?STDNODE],
208
        db_type
209
        }).
210

211
-type(state() ::
212
    #state{
213
        server_host             :: binary(),
214
        hosts                   :: [mod_pubsub:hostPubsub()],
215
        access                  :: atom(),
216
        pep_mapping             :: [{binary(), binary()}],
217
        ignore_pep_from_offline :: boolean(),
218
        last_item_cache         :: boolean(),
219
        max_items_node          :: non_neg_integer()|unlimited,
220
        max_subscriptions_node  :: non_neg_integer()|undefined,
221
        default_node_config     :: [{atom(), binary()|boolean()|integer()|atom()}],
222
        nodetree                :: binary(),
223
        plugins                 :: [binary(),...],
224
        db_type                 :: atom()
225
        }
226

227
    ).
228

229
-type subs_by_depth() :: [{integer(), [{#pubsub_node{}, [{ljid(), subId(), subOptions()}]}]}].
230

231
start(Host, Opts) ->
UNCOV
232
    gen_mod:start_child(?MODULE, Host, Opts).
8✔
233

234
stop(Host) ->
UNCOV
235
    gen_mod:stop_child(?MODULE, Host).
8✔
236

237
%%====================================================================
238
%% gen_server callbacks
239
%%====================================================================
240

241
%%--------------------------------------------------------------------
242
%% Function: init(Args) -> {ok, State} |
243
%%                         {ok, State, Timeout} |
244
%%                         ignore               |
245
%%                         {stop, Reason}
246
%% Description: Initiates the server
247
%%--------------------------------------------------------------------
248
-spec init([binary() | [{_,_}],...]) -> {'ok',state()}.
249

250
init([ServerHost|_]) ->
UNCOV
251
    process_flag(trap_exit, true),
8✔
UNCOV
252
    Opts = gen_mod:get_module_opts(ServerHost, ?MODULE),
8✔
UNCOV
253
    Hosts = gen_mod:get_opt_hosts(Opts),
8✔
UNCOV
254
    Access = mod_pubsub_opt:access_createnode(Opts),
8✔
UNCOV
255
    PepOffline = mod_pubsub_opt:ignore_pep_from_offline(Opts),
8✔
UNCOV
256
    LastItemCache = mod_pubsub_opt:last_item_cache(Opts),
8✔
UNCOV
257
    MaxItemsNode = mod_pubsub_opt:max_items_node(Opts),
8✔
UNCOV
258
    MaxSubsNode = mod_pubsub_opt:max_subscriptions_node(Opts),
8✔
UNCOV
259
    ejabberd_mnesia:create(?MODULE, pubsub_last_item,
8✔
260
                           [{ram_copies, [node()]},
261
                            {attributes, record_info(fields, pubsub_last_item)}]),
UNCOV
262
    DBMod = gen_mod:db_mod(Opts, ?MODULE),
8✔
UNCOV
263
    AllPlugins =
8✔
264
        lists:flatmap(
265
          fun(Host) ->
UNCOV
266
                  DBMod:init(Host, ServerHost, Opts),
8✔
UNCOV
267
                  ejabberd_router:register_route(
8✔
268
                    Host, ServerHost, {apply, ?MODULE, route}),
UNCOV
269
                  {Plugins, NodeTree, PepMapping} = init_plugins(Host, ServerHost, Opts),
8✔
UNCOV
270
                  DefaultNodeCfg = mod_pubsub_opt:default_node_config(Opts),
8✔
UNCOV
271
                  lists:foreach(
8✔
272
                    fun(H) ->
UNCOV
273
                            T = gen_mod:get_module_proc(H, config),
16✔
UNCOV
274
                            try
16✔
UNCOV
275
                                ets:new(T, [set, named_table]),
16✔
UNCOV
276
                                ets:insert(T, {nodetree, NodeTree}),
16✔
UNCOV
277
                                ets:insert(T, {plugins, Plugins}),
16✔
UNCOV
278
                                ets:insert(T, {last_item_cache, LastItemCache}),
16✔
UNCOV
279
                                ets:insert(T, {max_items_node, MaxItemsNode}),
16✔
UNCOV
280
                                ets:insert(T, {max_subscriptions_node, MaxSubsNode}),
16✔
UNCOV
281
                                ets:insert(T, {default_node_config, DefaultNodeCfg}),
16✔
UNCOV
282
                                ets:insert(T, {pep_mapping, PepMapping}),
16✔
UNCOV
283
                                ets:insert(T, {ignore_pep_from_offline, PepOffline}),
16✔
UNCOV
284
                                ets:insert(T, {host, Host}),
16✔
UNCOV
285
                                ets:insert(T, {access, Access})
16✔
286
                            catch error:badarg when H == ServerHost ->
287
                                    ok
×
288
                            end
289
                    end, [Host, ServerHost]),
UNCOV
290
                  gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO,
8✔
291
                                                ?MODULE, process_disco_info),
UNCOV
292
                  gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS,
8✔
293
                                                ?MODULE, process_disco_items),
UNCOV
294
                  gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_PUBSUB,
8✔
295
                                                ?MODULE, process_pubsub),
UNCOV
296
                  gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_PUBSUB_OWNER,
8✔
297
                                                ?MODULE, process_pubsub_owner),
UNCOV
298
                  gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_VCARD,
8✔
299
                                                ?MODULE, process_vcard),
UNCOV
300
                  gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_COMMANDS,
8✔
301
                                                ?MODULE, process_commands),
UNCOV
302
                  Plugins
8✔
303
          end, Hosts),
UNCOV
304
    ejabberd_hooks:add(c2s_self_presence, ServerHost,
8✔
305
        ?MODULE, on_self_presence, 75),
UNCOV
306
    ejabberd_hooks:add(c2s_terminated, ServerHost,
8✔
307
        ?MODULE, on_user_offline, 75),
UNCOV
308
    ejabberd_hooks:add(disco_local_identity, ServerHost,
8✔
309
        ?MODULE, disco_local_identity, 75),
UNCOV
310
    ejabberd_hooks:add(disco_local_features, ServerHost,
8✔
311
        ?MODULE, disco_local_features, 75),
UNCOV
312
    ejabberd_hooks:add(disco_local_items, ServerHost,
8✔
313
        ?MODULE, disco_local_items, 75),
UNCOV
314
    ejabberd_hooks:add(presence_probe_hook, ServerHost,
8✔
315
        ?MODULE, presence_probe, 80),
UNCOV
316
    ejabberd_hooks:add(roster_in_subscription, ServerHost,
8✔
317
        ?MODULE, in_subscription, 50),
UNCOV
318
    ejabberd_hooks:add(roster_out_subscription, ServerHost,
8✔
319
        ?MODULE, out_subscription, 50),
UNCOV
320
    ejabberd_hooks:add(remove_user, ServerHost,
8✔
321
        ?MODULE, remove_user, 50),
UNCOV
322
    ejabberd_hooks:add(c2s_handle_info, ServerHost,
8✔
323
        ?MODULE, c2s_handle_info, 50),
UNCOV
324
    case lists:member(?PEPNODE, AllPlugins) of
8✔
325
        true ->
UNCOV
326
            ejabberd_hooks:add(caps_add, ServerHost,
8✔
327
                ?MODULE, caps_add, 80),
UNCOV
328
            ejabberd_hooks:add(caps_update, ServerHost,
8✔
329
                ?MODULE, caps_update, 80),
UNCOV
330
            ejabberd_hooks:add(disco_sm_identity, ServerHost,
8✔
331
                ?MODULE, disco_sm_identity, 75),
UNCOV
332
            ejabberd_hooks:add(disco_sm_features, ServerHost,
8✔
333
                ?MODULE, disco_sm_features, 75),
UNCOV
334
            ejabberd_hooks:add(disco_sm_items, ServerHost,
8✔
335
                ?MODULE, disco_sm_items, 75),
UNCOV
336
            gen_iq_handler:add_iq_handler(ejabberd_sm, ServerHost,
8✔
337
                ?NS_PUBSUB, ?MODULE, iq_sm),
UNCOV
338
            gen_iq_handler:add_iq_handler(ejabberd_sm, ServerHost,
8✔
339
                ?NS_PUBSUB_OWNER, ?MODULE, iq_sm);
340
        false ->
341
            ok
×
342
    end,
UNCOV
343
    ejabberd_commands:register_commands(ServerHost, ?MODULE, get_commands_spec()),
8✔
UNCOV
344
    NodeTree = config(ServerHost, nodetree),
8✔
UNCOV
345
    Plugins = config(ServerHost, plugins),
8✔
UNCOV
346
    PepMapping = config(ServerHost, pep_mapping),
8✔
UNCOV
347
    DBType = mod_pubsub_opt:db_type(ServerHost),
8✔
UNCOV
348
    {ok, #state{hosts = Hosts, server_host = ServerHost,
8✔
349
                access = Access, pep_mapping = PepMapping,
350
                ignore_pep_from_offline = PepOffline,
351
                last_item_cache = LastItemCache,
352
                max_items_node = MaxItemsNode, nodetree = NodeTree,
353
                plugins = Plugins, db_type = DBType}}.
354

355
depends(ServerHost, Opts) ->
UNCOV
356
    [Host|_] = gen_mod:get_opt_hosts(Opts),
8✔
UNCOV
357
    Plugins = mod_pubsub_opt:plugins(Opts),
8✔
UNCOV
358
    Db = mod_pubsub_opt:db_type(Opts),
8✔
UNCOV
359
    lists:flatmap(
8✔
360
      fun(Name) ->
UNCOV
361
              Plugin = plugin(Db, Name),
16✔
UNCOV
362
              try apply(Plugin, depends, [Host, ServerHost, Opts])
16✔
UNCOV
363
              catch _:undef -> []
8✔
364
              end
365
      end, Plugins).
366

367
%% @doc Call the init/1 function for each plugin declared in the config file.
368
%% The default plugin module is implicit.
369
%% <p>The Erlang code for the plugin is located in a module called
370
%% <em>node_plugin</em>. The 'node_' prefix is mandatory.</p>
371
%% <p>See {@link node_hometree:init/1} for an example implementation.</p>
372
init_plugins(Host, ServerHost, Opts) ->
UNCOV
373
    TreePlugin = tree(Host, mod_pubsub_opt:nodetree(Opts)),
8✔
UNCOV
374
    TreePlugin:init(Host, ServerHost, Opts),
8✔
UNCOV
375
    Plugins = mod_pubsub_opt:plugins(Opts),
8✔
UNCOV
376
    PepMapping = mod_pubsub_opt:pep_mapping(Opts),
8✔
UNCOV
377
    PluginsOK = lists:foldl(
8✔
378
            fun (Name, Acc) ->
UNCOV
379
                    Plugin = plugin(Host, Name),
16✔
UNCOV
380
                    apply(Plugin, init, [Host, ServerHost, Opts]),
16✔
UNCOV
381
                    [Name | Acc]
16✔
382
            end,
383
            [], Plugins),
UNCOV
384
    {lists:reverse(PluginsOK), TreePlugin, PepMapping}.
8✔
385

386
terminate_plugins(Host, ServerHost, Plugins, TreePlugin) ->
UNCOV
387
    lists:foreach(
8✔
388
        fun (Name) ->
UNCOV
389
                Plugin = plugin(Host, Name),
16✔
UNCOV
390
                Plugin:terminate(Host, ServerHost)
16✔
391
        end,
392
        Plugins),
UNCOV
393
    TreePlugin:terminate(Host, ServerHost),
8✔
UNCOV
394
    ok.
8✔
395

396
%% -------
397
%% disco hooks handling functions
398
%%
399

400
-spec disco_local_identity([identity()], jid(), jid(),
401
                           binary(), binary()) -> [identity()].
402
disco_local_identity(Acc, _From, To, <<>>, _Lang) ->
UNCOV
403
    case lists:member(?PEPNODE, plugins(host(To#jid.lserver))) of
1,872✔
404
        true ->
UNCOV
405
            [#identity{category = <<"pubsub">>, type = <<"pep">>} | Acc];
1,872✔
406
        false ->
407
            Acc
×
408
    end;
409
disco_local_identity(Acc, _From, _To, _Node, _Lang) ->
410
    Acc.
×
411

412
-spec disco_local_features({error, stanza_error()} | {result, [binary()]} | empty,
413
                           jid(), jid(), binary(), binary()) ->
414
                                  {error, stanza_error()} | {result, [binary()]} | empty.
415
disco_local_features(Acc, _From, To, <<>>, _Lang) ->
UNCOV
416
    Host = host(To#jid.lserver),
1,872✔
UNCOV
417
    Feats = case Acc of
1,872✔
UNCOV
418
        {result, I} -> I;
1,872✔
419
        _ -> []
×
420
    end,
UNCOV
421
    {result, Feats ++ [?NS_PUBSUB|[feature(F) || F <- features(Host, <<>>)]]};
1,872✔
422
disco_local_features(Acc, _From, _To, _Node, _Lang) ->
423
    Acc.
×
424

425
-spec disco_local_items({error, stanza_error()} | {result, [disco_item()]} | empty,
426
                        jid(), jid(), binary(), binary()) ->
427
                               {error, stanza_error()} | {result, [disco_item()]} | empty.
428
disco_local_items(Acc, _From, _To, <<>>, _Lang) -> Acc;
×
429
disco_local_items(Acc, _From, _To, _Node, _Lang) -> Acc.
×
430

431
-spec disco_sm_identity([identity()], jid(), jid(),
432
                        binary(), binary()) -> [identity()].
433
disco_sm_identity(Acc, From, To, Node, _Lang) ->
434
    disco_identity(jid:tolower(jid:remove_resource(To)), Node, From)
UNCOV
435
    ++ Acc.
56✔
436

437
-spec disco_identity(host(), binary(), jid()) -> [identity()].
438
disco_identity(_Host, <<>>, _From) ->
UNCOV
439
    [#identity{category = <<"pubsub">>, type = <<"pep">>}];
32✔
440
disco_identity(Host, Node, From) ->
UNCOV
441
    Action =
24✔
442
        fun(#pubsub_node{id = Nidx, type = Type,
443
                         options = Options, owners = O}) ->
444
                Owners = node_owners_call(Host, Type, Nidx, O),
×
445
                case get_allowed_items_call(Host, Nidx, From, Type,
×
446
                                            Options, Owners) of
447
                    {result, _} ->
448
                        {result, [#identity{category = <<"pubsub">>, type = <<"pep">>},
×
449
                                  #identity{category = <<"pubsub">>, type = <<"leaf">>,
450
                                            name = get_option(Options, title, <<>>)}]};
451
                    _ ->
452
                        {result, []}
×
453
                end
454
        end,
UNCOV
455
    case transaction(Host, Node, Action, sync_dirty) of
24✔
456
        {result, {_, Result}} -> Result;
×
UNCOV
457
        _ -> []
24✔
458
    end.
459

460
-spec disco_sm_features({error, stanza_error()} | {result, [binary()]} | empty,
461
                        jid(), jid(), binary(), binary()) ->
462
                               {error, stanza_error()} | {result, [binary()]}.
463
disco_sm_features(empty, From, To, Node, Lang) ->
464
    disco_sm_features({result, []}, From, To, Node, Lang);
×
465
disco_sm_features({result, OtherFeatures} = _Acc, From, To, Node, _Lang) ->
UNCOV
466
    {result,
56✔
467
     OtherFeatures ++
468
         disco_features(jid:tolower(jid:remove_resource(To)), Node, From)};
469
disco_sm_features(Acc, _From, _To, _Node, _Lang) -> Acc.
×
470

471
-spec disco_features(ljid(), binary(), jid()) -> [binary()].
472
disco_features(Host, <<>>, _From) ->
UNCOV
473
    [?NS_PUBSUB | [feature(F) || F <- plugin_features(Host, <<"pep">>)]];
32✔
474
disco_features(Host, Node, From) ->
UNCOV
475
    Action =
24✔
476
        fun(#pubsub_node{id = Nidx, type = Type,
477
                         options = Options, owners = O}) ->
478
                Owners = node_owners_call(Host, Type, Nidx, O),
×
479
                case get_allowed_items_call(Host, Nidx, From,
×
480
                                            Type, Options, Owners) of
481
                    {result, _} ->
482
                        {result,
×
483
                         [?NS_PUBSUB | [feature(F) || F <- plugin_features(Host, <<"pep">>)]]};
×
484
                    _ ->
485
                        {result, []}
×
486
                end
487
        end,
UNCOV
488
    case transaction(Host, Node, Action, sync_dirty) of
24✔
489
        {result, {_, Result}} -> Result;
×
UNCOV
490
        _ -> []
24✔
491
    end.
492

493
-spec disco_sm_items({error, stanza_error()} | {result, [disco_item()]} | empty,
494
                     jid(), jid(), binary(), binary()) ->
495
                            {error, stanza_error()} | {result, [disco_item()]}.
496
disco_sm_items(empty, From, To, Node, Lang) ->
497
    disco_sm_items({result, []}, From, To, Node, Lang);
×
498
disco_sm_items({result, OtherItems}, From, To, Node, _Lang) ->
UNCOV
499
    {result, lists:usort(OtherItems ++
8✔
500
            disco_items(jid:tolower(jid:remove_resource(To)), Node, From))};
501
disco_sm_items(Acc, _From, _To, _Node, _Lang) -> Acc.
×
502

503
-spec disco_items(ljid(), binary(), jid()) -> [disco_item()].
504
disco_items(Host, <<>>, From) ->
505
    MaxNodes = mod_pubsub_opt:max_nodes_discoitems(serverhost(Host)),
×
506
    Action =
×
507
        fun(#pubsub_node{nodeid = {_, Node}, options = Options,
508
                         type = Type, id = Nidx, owners = O}, Acc) ->
509
                Owners = node_owners_call(Host, Type, Nidx, O),
×
510
                case get_allowed_items_call(Host, Nidx, From,
×
511
                                            Type, Options, Owners) of
512
                    {result, _} ->
513
                        [#disco_item{node = Node,
×
514
                                     jid = jid:make(Host),
515
                                     name = get_option(Options, title, <<>>)} | Acc];
516
                    _ ->
517
                        Acc
×
518
                end
519
        end,
520
    NodeBloc = fun() ->
×
521
                       case tree_call(Host, get_nodes, [Host, MaxNodes]) of
×
522
                           Nodes when is_list(Nodes) ->
523
                               {result, lists:foldl(Action, [], Nodes)};
×
524
                           Error ->
525
                               Error
×
526
                       end
527
               end,
528
    case transaction(Host, NodeBloc, sync_dirty) of
×
529
        {result, Items} -> Items;
×
530
        _ -> []
×
531
    end;
532
disco_items(Host, Node, From) ->
UNCOV
533
    Action =
8✔
534
        fun(#pubsub_node{id = Nidx, type = Type,
535
                         options = Options, owners = O}) ->
536
                Owners = node_owners_call(Host, Type, Nidx, O),
×
537
                case get_allowed_items_call(Host, Nidx, From,
×
538
                                            Type, Options, Owners) of
539
                    {result, Items} ->
540
                        {result, [#disco_item{jid = jid:make(Host),
×
541
                                              name = ItemId}
542
                                  || #pubsub_item{itemid = {ItemId, _}} <- Items]};
×
543
                    _ ->
544
                        {result, []}
×
545
                end
546
        end,
UNCOV
547
    case transaction(Host, Node, Action, sync_dirty) of
8✔
548
        {result, {_, Result}} -> Result;
×
UNCOV
549
        _ -> []
8✔
550
    end.
551

552
%% -------
553
%% presence and session hooks handling functions
554
%%
555

556
-spec caps_add(jid(), jid(), [binary()]) -> ok.
557
caps_add(JID, JID, Features) ->
558
    %% Send the owner his last PEP items.
UNCOV
559
    send_last_pep(JID, JID, Features);
8✔
560
caps_add(#jid{lserver = S1} = From, #jid{lserver = S2} = To, Features)
561
  when S1 =/= S2 ->
562
    %% When a remote contact goes online while the local user is offline, the
563
    %% remote contact won't receive last items from the local user even if
564
    %% ignore_pep_from_offline is set to false. To work around this issue a bit,
565
    %% we'll also send the last items to remote contacts when the local user
566
    %% connects. That's the reason to use the caps_add hook instead of the
567
    %% presence_probe_hook for remote contacts: The latter is only called when a
568
    %% contact becomes available; the former is also executed when the local
569
    %% user goes online (because that triggers the contact to send a presence
570
    %% packet with CAPS).
571
    send_last_pep(To, From, Features);
×
572
caps_add(_From, _To, _Features) ->
573
    ok.
×
574

575
-spec caps_update(jid(), jid(), [binary()]) -> ok.
576
caps_update(From, To, Features) ->
577
    send_last_pep(To, From, Features).
×
578

579
-spec presence_probe(jid(), jid(), pid()) -> ok.
580
presence_probe(#jid{luser = U, lserver = S}, #jid{luser = U, lserver = S}, _Pid) ->
581
    %% ignore presence_probe from my other resources
582
    ok;
×
583
presence_probe(#jid{lserver = S} = From, #jid{lserver = S} = To, _Pid) ->
584
    send_last_pep(To, From, unknown);
×
585
presence_probe(_From, _To, _Pid) ->
586
    %% ignore presence_probe from remote contacts, those are handled via caps_add
587
    ok.
×
588

589
-spec on_self_presence({presence(), ejabberd_c2s:state()})
590
                    -> {presence(), ejabberd_c2s:state()}.
591
on_self_presence({_, #{pres_last := _}} = Acc) -> % Just a presence update.
UNCOV
592
    Acc;
24✔
593
on_self_presence({#presence{type = available}, #{jid := JID}} = Acc) ->
UNCOV
594
    send_last_items(JID),
232✔
UNCOV
595
    Acc;
232✔
596
on_self_presence(Acc) ->
597
    Acc.
×
598

599
-spec on_user_offline(ejabberd_c2s:state(), atom()) -> ejabberd_c2s:state().
600
on_user_offline(#{jid := JID} = C2SState, _Reason) ->
UNCOV
601
    purge_offline(JID),
1,824✔
UNCOV
602
    C2SState;
1,824✔
603
on_user_offline(C2SState, _Reason) ->
UNCOV
604
    C2SState.
56✔
605

606
%% -------
607
%% subscription hooks handling functions
608
%%
609

610
-spec out_subscription(presence()) -> any().
611
out_subscription(#presence{type = subscribed, from = From, to = To}) ->
UNCOV
612
    if From#jid.lserver == To#jid.lserver ->
392✔
UNCOV
613
            send_last_pep(jid:remove_resource(From), To, unknown);
392✔
614
       true ->
615
           ok
×
616
    end;
617
out_subscription(_) ->
UNCOV
618
    ok.
1,176✔
619

620
-spec in_subscription(boolean(), presence()) -> true.
621
in_subscription(_, #presence{to = To, from = Owner, type = unsubscribed}) ->
UNCOV
622
    unsubscribe_user(jid:remove_resource(To), Owner),
488✔
UNCOV
623
    true;
488✔
624
in_subscription(_, _) ->
UNCOV
625
    true.
984✔
626

627
-spec unsubscribe_user(jid(), jid()) -> ok.
628
unsubscribe_user(Entity, Owner) ->
UNCOV
629
    lists:foreach(
488✔
630
      fun(ServerHost) ->
UNCOV
631
              unsubscribe_user(ServerHost, Entity, Owner)
488✔
632
      end,
633
      lists:usort(
634
        lists:foldl(
635
          fun(UserHost, Acc) ->
UNCOV
636
                  case gen_mod:is_loaded(UserHost, mod_pubsub) of
976✔
UNCOV
637
                      true -> [UserHost|Acc];
976✔
638
                      false -> Acc
×
639
                  end
640
          end, [], [Entity#jid.lserver, Owner#jid.lserver]))).
641

642
-spec unsubscribe_user(binary(), jid(), jid()) -> ok.
643
unsubscribe_user(Host, Entity, Owner) ->
UNCOV
644
    BJID = jid:tolower(jid:remove_resource(Owner)),
488✔
UNCOV
645
    lists:foreach(
488✔
646
      fun (PType) ->
UNCOV
647
              case node_action(Host, PType,
976✔
648
                               get_entity_subscriptions,
649
                               [Host, Entity]) of
650
                  {result, Subs} ->
UNCOV
651
                      lists:foreach(
976✔
652
                        fun({#pubsub_node{options = Options,
653
                                          owners = O,
654
                                          id = Nidx},
655
                             subscribed, _, JID}) ->
656
                                Unsubscribe = match_option(Options, access_model, presence)
×
657
                                    andalso lists:member(BJID, node_owners_action(Host, PType, Nidx, O)),
×
658
                                case Unsubscribe of
×
659
                                    true ->
660
                                        node_action(Host, PType,
×
661
                                                    unsubscribe_node, [Nidx, Entity, JID, all]);
662
                                    false ->
663
                                        ok
×
664
                                end;
665
                           (_) ->
666
                                ok
×
667
                        end, Subs);
668
                  _ ->
669
                      ok
×
670
              end
671
      end, plugins(Host)).
672

673
%% -------
674
%% user remove hook handling function
675
%%
676

677
-spec remove_user(binary(), binary()) -> ok.
678
remove_user(User, Server) ->
UNCOV
679
    LUser = jid:nodeprep(User),
16✔
UNCOV
680
    LServer = jid:nameprep(Server),
16✔
UNCOV
681
    Entity = jid:make(LUser, LServer),
16✔
UNCOV
682
    Host = host(LServer),
16✔
UNCOV
683
    HomeTreeBase = <<"/home/", LServer/binary, "/", LUser/binary>>,
16✔
UNCOV
684
    lists:foreach(
16✔
685
      fun(PType) ->
UNCOV
686
              case node_action(Host, PType,
32✔
687
                               get_entity_subscriptions,
688
                               [Host, Entity]) of
689
                  {result, Subs} ->
UNCOV
690
                      lists:foreach(
32✔
691
                        fun({#pubsub_node{id = Nidx}, _, _, JID}) ->
692
                                node_action(Host, PType,
×
693
                                            unsubscribe_node,
694
                                            [Nidx, Entity, JID, all]);
695
                           (_) ->
696
                                ok
×
697
                        end, Subs),
UNCOV
698
                      case node_action(Host, PType,
32✔
699
                                       get_entity_affiliations,
700
                                       [Host, Entity]) of
701
                          {result, Affs} ->
UNCOV
702
                              lists:foreach(
32✔
703
                                fun({#pubsub_node{nodeid = {H, N}, parents = []}, owner}) ->
704
                                        delete_node(H, N, Entity);
×
705
                                   ({#pubsub_node{nodeid = {H, N}, type = Type}, owner})
706
                                      when N == HomeTreeBase, Type == <<"hometree">> ->
707
                                        delete_node(H, N, Entity);
×
708
                                   ({#pubsub_node{id = Nidx}, _}) ->
709
                                        case node_action(Host, PType,
×
710
                                                         get_state,
711
                                                         [Nidx, jid:tolower(Entity)]) of
712
                                            {result, #pubsub_state{items = ItemIds}} ->
713
                                                node_action(Host, PType,
×
714
                                                            remove_extra_items,
715
                                                            [Nidx, 0, ItemIds]),
716
                                                node_action(Host, PType,
×
717
                                                            set_affiliation,
718
                                                            [Nidx, Entity, none]);
719
                                            _ ->
720
                                                ok
×
721
                                        end
722
                                end, Affs);
723
                          _ ->
724
                              ok
×
725
                      end;
726
                  _ ->
727
                      ok
×
728
              end
729
      end, plugins(Host)).
730

731
handle_call(server_host, _From, State) ->
732
    {reply, State#state.server_host, State};
×
733
handle_call(plugins, _From, State) ->
734
    {reply, State#state.plugins, State};
×
735
handle_call(pep_mapping, _From, State) ->
736
    {reply, State#state.pep_mapping, State};
×
737
handle_call(nodetree, _From, State) ->
738
    {reply, State#state.nodetree, State};
×
739
handle_call(stop, _From, State) ->
740
    {stop, normal, ok, State};
×
741
handle_call(Request, From, State) ->
742
    ?WARNING_MSG("Unexpected call from ~p: ~p", [From, Request]),
×
743
    {noreply, State}.
×
744

745
handle_cast(Msg, State) ->
746
    ?WARNING_MSG("Unexpected cast: ~p", [Msg]),
×
747
    {noreply, State}.
×
748

749
handle_info({route, Packet}, State) ->
750
    try route(Packet)
×
751
    catch
752
        Class:Reason:StackTrace ->
753
            ?ERROR_MSG("Failed to route packet:~n~ts~n** ~ts",
×
754
                       [xmpp:pp(Packet),
755
                        misc:format_exception(2, Class, Reason, StackTrace)])
×
756
    end,
757
    {noreply, State};
×
758
handle_info(Info, State) ->
759
    ?WARNING_MSG("Unexpected info: ~p", [Info]),
×
760
    {noreply, State}.
×
761

762
terminate(_Reason,
763
            #state{hosts = Hosts, server_host = ServerHost, nodetree = TreePlugin, plugins = Plugins}) ->
UNCOV
764
    case lists:member(?PEPNODE, Plugins) of
8✔
765
        true ->
UNCOV
766
            ejabberd_hooks:delete(caps_add, ServerHost,
8✔
767
                ?MODULE, caps_add, 80),
UNCOV
768
            ejabberd_hooks:delete(caps_update, ServerHost,
8✔
769
                ?MODULE, caps_update, 80),
UNCOV
770
            ejabberd_hooks:delete(disco_sm_identity, ServerHost,
8✔
771
                ?MODULE, disco_sm_identity, 75),
UNCOV
772
            ejabberd_hooks:delete(disco_sm_features, ServerHost,
8✔
773
                ?MODULE, disco_sm_features, 75),
UNCOV
774
            ejabberd_hooks:delete(disco_sm_items, ServerHost,
8✔
775
                ?MODULE, disco_sm_items, 75),
UNCOV
776
            gen_iq_handler:remove_iq_handler(ejabberd_sm,
8✔
777
                ServerHost, ?NS_PUBSUB),
UNCOV
778
            gen_iq_handler:remove_iq_handler(ejabberd_sm,
8✔
779
                ServerHost, ?NS_PUBSUB_OWNER);
780
        false ->
781
            ok
×
782
    end,
UNCOV
783
    ejabberd_hooks:delete(c2s_self_presence, ServerHost,
8✔
784
        ?MODULE, on_self_presence, 75),
UNCOV
785
    ejabberd_hooks:delete(c2s_terminated, ServerHost,
8✔
786
        ?MODULE, on_user_offline, 75),
UNCOV
787
    ejabberd_hooks:delete(disco_local_identity, ServerHost,
8✔
788
        ?MODULE, disco_local_identity, 75),
UNCOV
789
    ejabberd_hooks:delete(disco_local_features, ServerHost,
8✔
790
        ?MODULE, disco_local_features, 75),
UNCOV
791
    ejabberd_hooks:delete(disco_local_items, ServerHost,
8✔
792
        ?MODULE, disco_local_items, 75),
UNCOV
793
    ejabberd_hooks:delete(presence_probe_hook, ServerHost,
8✔
794
        ?MODULE, presence_probe, 80),
UNCOV
795
    ejabberd_hooks:delete(roster_in_subscription, ServerHost,
8✔
796
        ?MODULE, in_subscription, 50),
UNCOV
797
    ejabberd_hooks:delete(roster_out_subscription, ServerHost,
8✔
798
        ?MODULE, out_subscription, 50),
UNCOV
799
    ejabberd_hooks:delete(remove_user, ServerHost,
8✔
800
        ?MODULE, remove_user, 50),
UNCOV
801
    ejabberd_hooks:delete(c2s_handle_info, ServerHost,
8✔
802
        ?MODULE, c2s_handle_info, 50),
UNCOV
803
    lists:foreach(
8✔
804
      fun(Host) ->
UNCOV
805
              gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO),
8✔
UNCOV
806
              gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS),
8✔
UNCOV
807
              gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_PUBSUB),
8✔
UNCOV
808
              gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_PUBSUB_OWNER),
8✔
UNCOV
809
              gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_VCARD),
8✔
UNCOV
810
              gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_COMMANDS),
8✔
UNCOV
811
              terminate_plugins(Host, ServerHost, Plugins, TreePlugin),
8✔
UNCOV
812
              ejabberd_router:unregister_route(Host)
8✔
813
      end, Hosts),
UNCOV
814
    ejabberd_commands:unregister_commands(ServerHost, ?MODULE, get_commands_spec()).
8✔
815

816
%%--------------------------------------------------------------------
817
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
818
%% Description: Convert process state when code is changed
819
%%--------------------------------------------------------------------
820
%% @private
821
code_change(_OldVsn, State, _Extra) -> {ok, State}.
×
822

823
%%--------------------------------------------------------------------
824
%%% Internal functions
825
%%--------------------------------------------------------------------
826
-spec process_disco_info(iq()) -> iq().
827
process_disco_info(#iq{type = set, lang = Lang} = IQ) ->
828
    Txt = ?T("Value 'set' of 'type' attribute is not allowed"),
×
829
    xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
×
830
process_disco_info(#iq{from = From, to = To, lang = Lang, type = get,
831
                       sub_els = [#disco_info{node = Node}]} = IQ) ->
UNCOV
832
    Host = To#jid.lserver,
8✔
UNCOV
833
    ServerHost = ejabberd_router:host_of_route(Host),
8✔
UNCOV
834
    Info = ejabberd_hooks:run_fold(disco_info, ServerHost,
8✔
835
                                   [],
836
                                   [ServerHost, ?MODULE, <<>>, <<>>]),
UNCOV
837
    case iq_disco_info(ServerHost, Host, Node, From, Lang) of
8✔
838
        {result, IQRes} ->
UNCOV
839
            XData = IQRes#disco_info.xdata ++ Info,
8✔
UNCOV
840
            xmpp:make_iq_result(IQ, IQRes#disco_info{node = Node, xdata = XData});
8✔
841
        {error, Error} ->
842
            xmpp:make_error(IQ, Error)
×
843
    end.
844

845
-spec process_disco_items(iq()) -> iq().
846
process_disco_items(#iq{type = set, lang = Lang} = IQ) ->
847
    Txt = ?T("Value 'set' of 'type' attribute is not allowed"),
×
848
    xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
×
849
process_disco_items(#iq{type = get, from = From, to = To,
850
                        sub_els = [#disco_items{node = Node} = SubEl]} = IQ) ->
851
    Host = To#jid.lserver,
×
852
    case iq_disco_items(Host, Node, From, SubEl#disco_items.rsm) of
×
853
        {result, IQRes} ->
854
            xmpp:make_iq_result(IQ, IQRes#disco_items{node = Node});
×
855
        {error, Error} ->
856
            xmpp:make_error(IQ, Error)
×
857
    end.
858

859
-spec process_pubsub(iq()) -> iq().
860
process_pubsub(#iq{to = To} = IQ) ->
UNCOV
861
    Host = To#jid.lserver,
882✔
UNCOV
862
    ServerHost = ejabberd_router:host_of_route(Host),
882✔
UNCOV
863
    Access = config(ServerHost, access),
882✔
UNCOV
864
    case iq_pubsub(Host, Access, IQ) of
882✔
865
        {result, IQRes} ->
UNCOV
866
            xmpp:make_iq_result(IQ, IQRes);
768✔
867
        {error, Error} ->
UNCOV
868
            xmpp:make_error(IQ, Error)
114✔
869
    end.
870

871
-spec process_pubsub_owner(iq()) -> iq().
872
process_pubsub_owner(#iq{to = To} = IQ) ->
UNCOV
873
    Host = To#jid.lserver,
864✔
UNCOV
874
    case iq_pubsub_owner(Host, IQ) of
864✔
875
        {result, IQRes} ->
UNCOV
876
            xmpp:make_iq_result(IQ, IQRes);
536✔
877
        {error, Error} ->
UNCOV
878
            xmpp:make_error(IQ, Error)
328✔
879
    end.
880

881
-spec process_vcard(iq()) -> iq().
882
process_vcard(#iq{type = get, to = To, lang = Lang} = IQ) ->
UNCOV
883
    ServerHost = ejabberd_router:host_of_route(To#jid.lserver),
8✔
UNCOV
884
    xmpp:make_iq_result(IQ, iq_get_vcard(ServerHost, Lang));
8✔
885
process_vcard(#iq{type = set, lang = Lang} = IQ) ->
886
    Txt = ?T("Value 'set' of 'type' attribute is not allowed"),
×
887
    xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)).
×
888

889
-spec process_commands(iq()) -> iq().
890
process_commands(#iq{type = set, to = To, from = From,
891
                     sub_els = [#adhoc_command{} = Request]} = IQ) ->
892
    Host = To#jid.lserver,
×
893
    ServerHost = ejabberd_router:host_of_route(Host),
×
894
    Plugins = config(ServerHost, plugins),
×
895
    Access = config(ServerHost, access),
×
896
    case adhoc_request(Host, ServerHost, From, Request, Access, Plugins) of
×
897
        {error, Error} ->
898
            xmpp:make_error(IQ, Error);
×
899
        Response ->
900
            xmpp:make_iq_result(
×
901
              IQ, xmpp_util:make_adhoc_response(Request, Response))
902
    end;
903
process_commands(#iq{type = get, lang = Lang} = IQ) ->
904
    Txt = ?T("Value 'get' of 'type' attribute is not allowed"),
×
905
    xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)).
×
906

907
-spec route(stanza()) -> ok.
908
route(#iq{to = To} = IQ) when To#jid.lresource == <<"">> ->
UNCOV
909
    ejabberd_router:process_iq(IQ);
1,762✔
910
route(Packet) ->
UNCOV
911
    To = xmpp:get_to(Packet),
24✔
UNCOV
912
    case To of
24✔
913
        #jid{luser = <<>>, lresource = <<>>} ->
UNCOV
914
            case Packet of
24✔
915
                #message{type = T} when T /= error ->
UNCOV
916
                    case find_authorization_response(Packet) of
16✔
917
                        undefined ->
918
                            ok;
×
919
                        {error, Err} ->
920
                            ejabberd_router:route_error(Packet, Err);
×
921
                        AuthResponse ->
UNCOV
922
                            handle_authorization_response(
16✔
923
                              To#jid.lserver, Packet, AuthResponse)
924
                    end;
925
                _ ->
UNCOV
926
                    Err = xmpp:err_service_unavailable(),
8✔
UNCOV
927
                    ejabberd_router:route_error(Packet, Err)
8✔
928
            end;
929
        _ ->
930
            Err = xmpp:err_item_not_found(),
×
931
            ejabberd_router:route_error(Packet, Err)
×
932
    end.
933

934
-spec command_disco_info(binary(), binary(), jid()) -> {result, disco_info()}.
935
command_disco_info(_Host, ?NS_COMMANDS, _From) ->
936
    {result, #disco_info{identities = [#identity{category = <<"automation">>,
×
937
                                                 type = <<"command-list">>}]}};
938
command_disco_info(_Host, ?NS_PUBSUB_GET_PENDING, _From) ->
939
    {result, #disco_info{identities = [#identity{category = <<"automation">>,
×
940
                                                 type = <<"command-node">>}],
941
                         features = [?NS_COMMANDS]}}.
942

943
-spec node_disco_info(binary(), binary(), jid()) -> {result, disco_info()} |
944
                                                    {error, stanza_error()}.
945
node_disco_info(Host, Node, From) ->
946
    node_disco_info(Host, Node, From, true, true).
×
947

948
-spec node_disco_info(binary(), binary(), jid(), boolean(), boolean()) ->
949
                             {result, disco_info()} | {error, stanza_error()}.
950
node_disco_info(Host, Node, _From, _Identity, _Features) ->
951
    Action =
×
952
        fun(#pubsub_node{id = Nidx, type = Type, options = Options}) ->
953
                NodeType = case get_option(Options, node_type) of
×
954
                               collection -> <<"collection">>;
×
955
                               _ -> <<"leaf">>
×
956
                           end,
957
                Affs = case node_call(Host, Type, get_node_affiliations, [Nidx]) of
×
958
                          {result, As} -> As;
×
959
                          _ -> []
×
960
                       end,
961
                Subs = case node_call(Host, Type, get_node_subscriptions, [Nidx]) of
×
962
                          {result, Ss} -> Ss;
×
963
                          _ -> []
×
964
                       end,
965
                Meta = [{title, get_option(Options, title, <<>>)},
×
966
                        {type, get_option(Options, type, <<>>)},
967
                        {description, get_option(Options, description, <<>>)},
968
                        {owner, [jid:make(LJID) || {LJID, Aff} <- Affs, Aff =:= owner]},
×
969
                        {publisher, [jid:make(LJID) || {LJID, Aff} <- Affs, Aff =:= publisher]},
×
970
                        {access_model, get_option(Options, access_model, open)},
971
                        {publish_model, get_option(Options, publish_model, publishers)},
972
                        {num_subscribers, length(Subs)}],
973
                XData = #xdata{type = result,
×
974
                               fields = pubsub_meta_data:encode(Meta)},
975
                Is = [#identity{category = <<"pubsub">>, type = NodeType}],
×
976
                Fs = [?NS_PUBSUB | [feature(F) || F <- plugin_features(Host, Type)]],
×
977
                {result, #disco_info{identities = Is, features = Fs, xdata = [XData]}}
×
978
        end,
979
    case transaction(Host, Node, Action, sync_dirty) of
×
980
        {result, {_, Result}} -> {result, Result};
×
981
        Other -> Other
×
982
    end.
983

984
-spec iq_disco_info(binary(), binary(), binary(), jid(), binary())
985
                   -> {result, disco_info()} | {error, stanza_error()}.
986
iq_disco_info(ServerHost, Host, SNode, From, Lang) ->
UNCOV
987
    [Node | _] = case SNode of
8✔
UNCOV
988
                     <<>> -> [<<>>];
8✔
989
                     _ -> str:tokens(SNode, <<"!">>)
×
990
                 end,
UNCOV
991
    case Node of
8✔
992
        <<>> ->
UNCOV
993
            Name = mod_pubsub_opt:name(ServerHost),
8✔
UNCOV
994
            {result,
8✔
995
             #disco_info{
996
                identities = [#identity{
997
                                 category = <<"pubsub">>,
998
                                 type = <<"service">>,
999
                                 name = translate:translate(Lang, Name)}],
1000
                features = [?NS_DISCO_INFO,
1001
                            ?NS_DISCO_ITEMS,
1002
                            ?NS_PUBSUB,
1003
                            ?NS_COMMANDS,
1004
                            ?NS_VCARD |
UNCOV
1005
                            [feature(F) || F <- features(Host, Node)]]}};
318✔
1006
        ?NS_COMMANDS ->
1007
            command_disco_info(Host, Node, From);
×
1008
        ?NS_PUBSUB_GET_PENDING ->
1009
            command_disco_info(Host, Node, From);
×
1010
        _ ->
1011
            node_disco_info(Host, Node, From)
×
1012
    end.
1013

1014
-spec iq_disco_items(host(), binary(), jid(), undefined | rsm_set()) ->
1015
                            {result, disco_items()} | {error, stanza_error()}.
1016
iq_disco_items(Host, <<>>, _From, _RSM) ->
1017
    MaxNodes = mod_pubsub_opt:max_nodes_discoitems(serverhost(Host)),
×
1018
    case tree_action(Host, get_subnodes, [Host, <<>>, MaxNodes]) of
×
1019
        {error, #stanza_error{}} = Err ->
1020
            Err;
×
1021
        Nodes when is_list(Nodes) ->
1022
            Items =
×
1023
                lists:map(
1024
                  fun(#pubsub_node{nodeid = {_, SubNode}, options = Options}) ->
1025
                          case get_option(Options, title) of
×
1026
                              false ->
1027
                                  #disco_item{jid = jid:make(Host),
×
1028
                                              node = SubNode};
1029
                              Title ->
1030
                                  #disco_item{jid = jid:make(Host),
×
1031
                                              name = Title,
1032
                                              node = SubNode}
1033
                          end
1034
                  end, Nodes),
1035
            {result, #disco_items{items = Items}}
×
1036
    end;
1037
iq_disco_items(Host, ?NS_COMMANDS, _From, _RSM) ->
1038
    {result,
×
1039
     #disco_items{items = [#disco_item{jid = jid:make(Host),
1040
                                       node = ?NS_PUBSUB_GET_PENDING,
1041
                                       name = ?T("Get Pending")}]}};
1042
iq_disco_items(_Host, ?NS_PUBSUB_GET_PENDING, _From, _RSM) ->
1043
    {result, #disco_items{}};
×
1044
iq_disco_items(Host, Item, From, RSM) ->
1045
    case str:tokens(Item, <<"!">>) of
×
1046
        [_Node, _ItemId] ->
1047
            {result, #disco_items{}};
×
1048
        [Node] ->
1049
            MaxNodes = mod_pubsub_opt:max_nodes_discoitems(serverhost(Host)),
×
1050
            Action = fun(#pubsub_node{id = Nidx, type = Type, options = Options, owners = O}) ->
×
1051
                             Owners = node_owners_call(Host, Type, Nidx, O),
×
1052
                             {NodeItems, RsmOut} = case get_allowed_items_call(
×
1053
                                                          Host, Nidx, From, Type, Options, Owners, RSM) of
1054
                                                       {result, R} -> R;
×
1055
                                                       _ -> {[], undefined}
×
1056
                                                   end,
1057
                             case tree_call(Host, get_subnodes, [Host, Node, MaxNodes]) of
×
1058
                                 SubNodes when is_list(SubNodes) ->
1059
                                     Nodes = lists:map(
×
1060
                                               fun(#pubsub_node{nodeid = {_, SubNode}, options = SubOptions}) ->
1061
                                                       case get_option(SubOptions, title) of
×
1062
                                                           false ->
1063
                                                               #disco_item{jid = jid:make(Host),
×
1064
                                                                           node = SubNode};
1065
                                                           Title ->
1066
                                                               #disco_item{jid = jid:make(Host),
×
1067
                                                                           name = Title,
1068
                                                                           node = SubNode}
1069
                                                       end
1070
                                               end, SubNodes),
1071
                                     Items = lists:flatmap(
×
1072
                                               fun(#pubsub_item{itemid = {RN, _}}) ->
1073
                                                       case node_call(Host, Type, get_item_name, [Host, Node, RN]) of
×
1074
                                                           {result, Name} ->
1075
                                                               [#disco_item{jid = jid:make(Host), name = Name}];
×
1076
                                                           _ ->
1077
                                                               []
×
1078
                                                       end
1079
                                               end, NodeItems),
1080
                                     {result, #disco_items{items = Nodes ++ Items,
×
1081
                                                           rsm = RsmOut}};
1082
                                 Error ->
1083
                                     Error
×
1084
                             end
1085
                     end,
1086
            case transaction(Host, Node, Action, sync_dirty) of
×
1087
                {result, {_, Result}} -> {result, Result};
×
1088
                Other -> Other
×
1089
            end
1090
    end.
1091

1092
-spec iq_sm(iq()) -> iq().
1093
iq_sm(#iq{to = To, sub_els = [SubEl]} = IQ) ->
UNCOV
1094
    LOwner = jid:tolower(jid:remove_resource(To)),
24✔
UNCOV
1095
    Res = case xmpp:get_ns(SubEl) of
24✔
1096
              ?NS_PUBSUB ->
UNCOV
1097
                  iq_pubsub(LOwner, all, IQ);
8✔
1098
              ?NS_PUBSUB_OWNER ->
UNCOV
1099
                  iq_pubsub_owner(LOwner, IQ)
16✔
1100
          end,
UNCOV
1101
    case Res of
24✔
1102
        {result, IQRes} ->
UNCOV
1103
            xmpp:make_iq_result(IQ, IQRes);
24✔
1104
        {error, Error} ->
1105
            xmpp:make_error(IQ, Error)
×
1106
    end.
1107

1108
-spec iq_get_vcard(binary(), binary()) -> vcard_temp().
1109
iq_get_vcard(ServerHost, Lang) ->
UNCOV
1110
    case mod_pubsub_opt:vcard(ServerHost) of
8✔
1111
        undefined ->
1112
            Desc = misc:get_descr(Lang, ?T("ejabberd Publish-Subscribe module")),
×
1113
            #vcard_temp{fn = <<"ejabberd/mod_pubsub">>,
×
1114
                        url = ejabberd_config:get_uri(),
1115
                        desc = Desc};
1116
        VCard ->
UNCOV
1117
            VCard
8✔
1118
    end.
1119

1120
-spec iq_pubsub(binary() | ljid(), atom(), iq()) ->
1121
                       {result, pubsub()} | {error, stanza_error()}.
1122
iq_pubsub(Host, Access, #iq{from = From, type = IQType, lang = Lang,
1123
                            sub_els = [SubEl]}) ->
UNCOV
1124
    case {IQType, SubEl} of
890✔
1125
        {set, #pubsub{create = Node, configure = Configure,
1126
                      _ = undefined}} when is_binary(Node) ->
UNCOV
1127
            ServerHost = serverhost(Host),
232✔
UNCOV
1128
            Plugins = config(ServerHost, plugins),
232✔
UNCOV
1129
            Config = case Configure of
232✔
UNCOV
1130
                         {_, XData} -> decode_node_config(XData, Host, Lang);
232✔
1131
                         undefined -> []
×
1132
                     end,
UNCOV
1133
            Type = hd(Plugins),
232✔
UNCOV
1134
            case Config of
232✔
1135
                {error, _} = Err ->
1136
                    Err;
×
1137
                _ ->
UNCOV
1138
                    create_node(Host, ServerHost, Node, From, Type, Access, Config)
232✔
1139
            end;
1140
        {set, #pubsub{publish = #ps_publish{node = Node, items = Items},
1141
                      publish_options = XData, configure = _, _ = undefined}} ->
UNCOV
1142
            ServerHost = serverhost(Host),
240✔
UNCOV
1143
            case Items of
240✔
1144
                [#ps_item{id = ItemId, sub_els = Payload}] ->
UNCOV
1145
                    case decode_publish_options(XData, Lang) of
240✔
1146
                        {error, _} = Err ->
1147
                            Err;
×
1148
                        PubOpts ->
UNCOV
1149
                            publish_item(Host, ServerHost, Node, From, ItemId,
240✔
1150
                                         Payload, PubOpts, Access)
1151
                    end;
1152
                [] ->
1153
                    publish_item(Host, ServerHost, Node, From, <<>>, [], [], Access);
×
1154
                _ ->
1155
                    {error, extended_error(xmpp:err_bad_request(), err_invalid_payload())}
×
1156
            end;
1157
        {set, #pubsub{retract = #ps_retract{node = Node, notify = Notify, items = Items},
1158
                      _ = undefined}} ->
UNCOV
1159
            case Items of
64✔
1160
                [#ps_item{id = ItemId}] ->
UNCOV
1161
                    if ItemId /= <<>> ->
64✔
UNCOV
1162
                            delete_item(Host, Node, From, ItemId, Notify);
64✔
1163
                       true ->
1164
                            {error, extended_error(xmpp:err_bad_request(),
×
1165
                                                   err_item_required())}
1166
                    end;
1167
                [] ->
1168
                    {error, extended_error(xmpp:err_bad_request(), err_item_required())};
×
1169
                _ ->
1170
                    {error, extended_error(xmpp:err_bad_request(), err_invalid_payload())}
×
1171
            end;
1172
        {set, #pubsub{subscribe = #ps_subscribe{node = Node, jid = JID},
1173
                      options = Options, _ = undefined}} ->
UNCOV
1174
            Config = case Options of
140✔
1175
                         #ps_options{xdata = XData, jid = undefined, node = <<>>} ->
1176
                             decode_subscribe_options(XData, Lang);
×
1177
                         #ps_options{xdata = _XData, jid = #jid{}} ->
1178
                             Txt = ?T("Attribute 'jid' is not allowed here"),
×
1179
                             {error, xmpp:err_bad_request(Txt, Lang)};
×
1180
                         #ps_options{xdata = _XData} ->
1181
                             Txt = ?T("Attribute 'node' is not allowed here"),
×
1182
                             {error, xmpp:err_bad_request(Txt, Lang)};
×
1183
                         _ ->
UNCOV
1184
                             []
140✔
1185
                     end,
UNCOV
1186
            case Config of
140✔
1187
                {error, _} = Err ->
1188
                    Err;
×
1189
                _ ->
UNCOV
1190
                    subscribe_node(Host, Node, From, JID, Config)
140✔
1191
            end;
1192
        {set, #pubsub{unsubscribe = #ps_unsubscribe{node = Node, jid = JID, subid = SubId},
1193
                      _ = undefined}} ->
UNCOV
1194
            unsubscribe_node(Host, Node, From, JID, SubId);
64✔
1195
        {get, #pubsub{items = #ps_items{node = Node,
1196
                                        max_items = MaxItems,
1197
                                        subid = SubId,
1198
                                        items = Items},
1199
                      rsm = RSM, _ = undefined}} ->
UNCOV
1200
            ItemIds = [ItemId || #ps_item{id = ItemId} <- Items, ItemId /= <<>>],
102✔
UNCOV
1201
            get_items(Host, Node, From, SubId, MaxItems, ItemIds, RSM);
102✔
1202
        {get, #pubsub{subscriptions = {Node, _}, _ = undefined}} ->
UNCOV
1203
            Plugins = config(serverhost(Host), plugins),
40✔
UNCOV
1204
            get_subscriptions(Host, Node, From, Plugins);
40✔
1205
        {get, #pubsub{affiliations = {Node, _}, _ = undefined}} ->
UNCOV
1206
            Plugins = config(serverhost(Host), plugins),
8✔
UNCOV
1207
            get_affiliations(Host, Node, From, Plugins);
8✔
1208
        {_, #pubsub{options = #ps_options{jid = undefined}, _ = undefined}} ->
1209
            {error, extended_error(xmpp:err_bad_request(), err_jid_required())};
×
1210
        {_, #pubsub{options = #ps_options{node = <<>>}, _ = undefined}} ->
1211
            {error, extended_error(xmpp:err_bad_request(), err_nodeid_required())};
×
1212
        {get, #pubsub{options = #ps_options{node = Node, subid = SubId, jid = JID},
1213
                      _ = undefined}} ->
1214
            get_options(Host, Node, JID, SubId, Lang);
×
1215
        {set, #pubsub{options = #ps_options{node = Node, subid = SubId,
1216
                                            jid = JID, xdata = XData},
1217
                      _ = undefined}} ->
1218
            case decode_subscribe_options(XData, Lang) of
×
1219
                {error, _} = Err ->
1220
                    Err;
×
1221
                Config ->
1222
                    set_options(Host, Node, JID, SubId, Config)
×
1223
            end;
1224
        {set, #pubsub{}} ->
1225
            {error, xmpp:err_bad_request()};
×
1226
        _ ->
1227
            {error, xmpp:err_feature_not_implemented()}
×
1228
    end.
1229

1230
-spec iq_pubsub_owner(binary() | ljid(), iq()) -> {result, pubsub_owner() | undefined} |
1231
                                                  {error, stanza_error()}.
1232
iq_pubsub_owner(Host, #iq{type = IQType, from = From,
1233
                          lang = Lang, sub_els = [SubEl]}) ->
UNCOV
1234
    case {IQType, SubEl} of
880✔
1235
        {get, #pubsub_owner{configure = {Node, undefined}, _ = undefined}} ->
UNCOV
1236
            ServerHost = serverhost(Host),
72✔
UNCOV
1237
            get_configure(Host, ServerHost, Node, From, Lang);
72✔
1238
        {set, #pubsub_owner{configure = {Node, XData}, _ = undefined}} ->
UNCOV
1239
            case XData of
56✔
1240
                undefined ->
1241
                    {error, xmpp:err_bad_request(?T("No data form found"), Lang)};
×
1242
                #xdata{type = cancel} ->
1243
                    {result, #pubsub_owner{}};
×
1244
                #xdata{type = submit} ->
UNCOV
1245
                    case decode_node_config(XData, Host, Lang) of
56✔
1246
                        {error, _} = Err ->
1247
                            Err;
×
1248
                        Config ->
UNCOV
1249
                            set_configure(Host, Node, From, Config, Lang)
56✔
1250
                    end;
1251
                #xdata{} ->
1252
                    {error, xmpp:err_bad_request(?T("Incorrect data form"), Lang)}
×
1253
            end;
1254
        {get, #pubsub_owner{default = {Node, undefined}, _ = undefined}} ->
UNCOV
1255
            get_default(Host, Node, From, Lang);
24✔
1256
        {set, #pubsub_owner{delete = {Node, _}, _ = undefined}} ->
UNCOV
1257
            delete_node(Host, Node, From);
304✔
1258
        {set, #pubsub_owner{purge = Node, _ = undefined}} when Node /= undefined ->
UNCOV
1259
            purge_node(Host, Node, From);
56✔
1260
        {get, #pubsub_owner{subscriptions = {Node, []}, _ = undefined}} ->
UNCOV
1261
            get_subscriptions(Host, Node, From);
96✔
1262
        {set, #pubsub_owner{subscriptions = {Node, Subs}, _ = undefined}} ->
UNCOV
1263
            set_subscriptions(Host, Node, From, Subs);
80✔
1264
        {get, #pubsub_owner{affiliations = {Node, []}, _ = undefined}} ->
UNCOV
1265
            get_affiliations(Host, Node, From);
96✔
1266
        {set, #pubsub_owner{affiliations = {Node, Affs}, _ = undefined}} ->
UNCOV
1267
            set_affiliations(Host, Node, From, Affs);
96✔
1268
        {_, #pubsub_owner{}} ->
1269
            {error, xmpp:err_bad_request()};
×
1270
        _ ->
1271
            {error, xmpp:err_feature_not_implemented()}
×
1272
    end.
1273

1274
-spec adhoc_request(binary(), binary(), jid(), adhoc_command(),
1275
                    atom(), [binary()]) -> adhoc_command() | {error, stanza_error()}.
1276
adhoc_request(Host, _ServerHost, Owner,
1277
              #adhoc_command{node = ?NS_PUBSUB_GET_PENDING, lang = Lang,
1278
                             action = execute, xdata = undefined},
1279
              _Access, Plugins) ->
1280
    send_pending_node_form(Host, Owner, Lang, Plugins);
×
1281
adhoc_request(Host, _ServerHost, Owner,
1282
              #adhoc_command{node = ?NS_PUBSUB_GET_PENDING, lang = Lang,
1283
                             action = execute, xdata = #xdata{} = XData} = Request,
1284
              _Access, _Plugins) ->
1285
    case decode_get_pending(XData, Lang) of
×
1286
        {error, _} = Err ->
1287
            Err;
×
1288
        Config ->
1289
            Node = proplists:get_value(node, Config),
×
1290
            case send_pending_auth_events(Host, Node, Owner, Lang) of
×
1291
                ok ->
1292
                    xmpp_util:make_adhoc_response(
×
1293
                      Request, #adhoc_command{status = completed});
1294
                Err ->
1295
                    Err
×
1296
            end
1297
    end;
1298
adhoc_request(_Host, _ServerHost, _Owner,
1299
              #adhoc_command{action = cancel}, _Access, _Plugins) ->
1300
    #adhoc_command{status = canceled};
×
1301
adhoc_request(_Host, _ServerHost, _Owner, Other, _Access, _Plugins) ->
1302
    ?DEBUG("Couldn't process ad hoc command:~n~p", [Other]),
×
1303
    {error, xmpp:err_item_not_found()}.
×
1304

1305
-spec send_pending_node_form(binary(), jid(), binary(),
1306
                             [binary()]) -> adhoc_command() | {error, stanza_error()}.
1307
send_pending_node_form(Host, Owner, Lang, Plugins) ->
1308
    Filter = fun (Type) ->
×
1309
            lists:member(<<"get-pending">>, plugin_features(Host, Type))
×
1310
    end,
1311
    case lists:filter(Filter, Plugins) of
×
1312
        [] ->
1313
            Err = extended_error(xmpp:err_feature_not_implemented(),
×
1314
                                 err_unsupported('get-pending')),
1315
            {error, Err};
×
1316
        Ps ->
1317
            case get_pending_nodes(Host, Owner, Ps) of
×
1318
                {ok, Nodes} ->
1319
                    Form = [{node, <<>>, lists:zip(Nodes, Nodes)}],
×
1320
                    XForm = #xdata{type = form,
×
1321
                                   fields = pubsub_get_pending:encode(Form, Lang)},
1322
                    #adhoc_command{status = executing, action = execute,
×
1323
                                   xdata = XForm};
1324
                Err ->
1325
                    Err
×
1326
            end
1327
    end.
1328

1329
-spec get_pending_nodes(binary(), jid(), [binary()]) -> {ok, [binary()]} |
1330
                                                        {error, stanza_error()}.
1331
get_pending_nodes(Host, Owner, Plugins) ->
1332
    Tr = fun (Type) ->
×
1333
            case node_call(Host, Type, get_pending_nodes, [Host, Owner]) of
×
1334
                {result, Nodes} -> Nodes;
×
1335
                _ -> []
×
1336
            end
1337
         end,
1338
    Action = fun() -> {result, lists:flatmap(Tr, Plugins)} end,
×
1339
    case transaction(Host, Action, sync_dirty) of
×
1340
        {result, Res} -> {ok, Res};
×
1341
        Err -> Err
×
1342
    end.
1343

1344
%% @doc <p>Send a subscription approval form to Owner for all pending
1345
%% subscriptions on Host and Node.</p>
1346
-spec send_pending_auth_events(binary(), binary(), jid(),
1347
                               binary()) -> ok | {error, stanza_error()}.
1348
send_pending_auth_events(Host, Node, Owner, Lang) ->
1349
    ?DEBUG("Sending pending auth events for ~ts on ~ts:~ts",
×
1350
           [jid:encode(Owner), Host, Node]),
×
1351
    Action =
×
1352
        fun(#pubsub_node{id = Nidx, type = Type}) ->
1353
                case lists:member(<<"get-pending">>, plugin_features(Host, Type)) of
×
1354
                    true ->
1355
                        case node_call(Host, Type, get_affiliation, [Nidx, Owner]) of
×
1356
                            {result, owner} ->
1357
                                node_call(Host, Type, get_node_subscriptions, [Nidx]);
×
1358
                            _ ->
1359
                                {error, xmpp:err_forbidden(
×
1360
                                          ?T("Owner privileges required"), Lang)}
1361
                        end;
1362
                    false ->
1363
                        {error, extended_error(xmpp:err_feature_not_implemented(),
×
1364
                                               err_unsupported('get-pending'))}
1365
                end
1366
        end,
1367
    case transaction(Host, Node, Action, sync_dirty) of
×
1368
        {result, {N, Subs}} ->
1369
            lists:foreach(
×
1370
              fun({J, pending, _SubId}) -> send_authorization_request(N, jid:make(J));
×
1371
                 ({J, pending}) -> send_authorization_request(N, jid:make(J));
×
1372
                 (_) -> ok
×
1373
              end, Subs);
1374
        Err ->
1375
            Err
×
1376
    end.
1377

1378
%%% authorization handling
1379
-spec send_authorization_request(#pubsub_node{}, jid()) -> ok.
1380
send_authorization_request(#pubsub_node{nodeid = {Host, Node},
1381
                                        type = Type, id = Nidx, owners = O},
1382
                           Subscriber) ->
1383
    %% TODO: pass lang to this function
UNCOV
1384
    Lang = <<"en">>,
16✔
UNCOV
1385
    Fs = pubsub_subscribe_authorization:encode(
16✔
1386
           [{node, Node},
1387
            {subscriber_jid, Subscriber},
1388
            {allow, false}],
1389
           Lang),
UNCOV
1390
    X = #xdata{type = form,
16✔
1391
               title = translate:translate(
1392
                         Lang, ?T("PubSub subscriber request")),
1393
               instructions = [translate:translate(
1394
                                 Lang,
1395
                                 ?T("Choose whether to approve this entity's "
1396
                                    "subscription."))],
1397
               fields = Fs},
UNCOV
1398
    Stanza = #message{from = service_jid(Host), sub_els = [X]},
16✔
UNCOV
1399
    lists:foreach(
16✔
1400
      fun (Owner) ->
UNCOV
1401
              ejabberd_router:route(xmpp:set_to(Stanza, jid:make(Owner)))
16✔
1402
      end, node_owners_action(Host, Type, Nidx, O)).
1403

1404
-spec find_authorization_response(message()) -> undefined |
1405
                                                pubsub_subscribe_authorization:result() |
1406
                                                {error, stanza_error()}.
1407
find_authorization_response(Packet) ->
UNCOV
1408
    case xmpp:get_subtag(Packet, #xdata{type = form}) of
16✔
1409
        #xdata{type = cancel} ->
1410
            undefined;
×
1411
        #xdata{type = submit, fields = Fs} ->
UNCOV
1412
            try pubsub_subscribe_authorization:decode(Fs) of
16✔
UNCOV
1413
                Result -> Result
16✔
1414
            catch _:{pubsub_subscribe_authorization, Why} ->
1415
                    Lang = xmpp:get_lang(Packet),
×
1416
                    Txt = pubsub_subscribe_authorization:format_error(Why),
×
1417
                    {error, xmpp:err_bad_request(Txt, Lang)}
×
1418
            end;
1419
        #xdata{} ->
1420
            {error, xmpp:err_bad_request()};
×
1421
        false ->
1422
            undefined
×
1423
    end.
1424

1425
%% @doc Send a message to JID with the supplied Subscription
1426
-spec send_authorization_approval(binary(), jid(), binary(), subscribed | none) -> ok.
1427
send_authorization_approval(Host, JID, SNode, Subscription) ->
UNCOV
1428
    Event = #ps_event{subscription =
16✔
1429
                          #ps_subscription{jid = JID,
1430
                                           node = SNode,
1431
                                           type = Subscription}},
UNCOV
1432
    Stanza = #message{from = service_jid(Host), to = JID, sub_els = [Event]},
16✔
UNCOV
1433
    ejabberd_router:route(Stanza).
16✔
1434

1435
-spec handle_authorization_response(binary(), message(),
1436
                                    pubsub_subscribe_authorization:result()) -> ok.
1437
handle_authorization_response(Host, #message{from = From} = Packet, Response) ->
UNCOV
1438
    Node = proplists:get_value(node, Response),
16✔
UNCOV
1439
    Subscriber = proplists:get_value(subscriber_jid, Response),
16✔
UNCOV
1440
    Allow = proplists:get_value(allow, Response),
16✔
UNCOV
1441
    Lang = xmpp:get_lang(Packet),
16✔
UNCOV
1442
    FromLJID = jid:tolower(jid:remove_resource(From)),
16✔
UNCOV
1443
    Action =
16✔
1444
        fun(#pubsub_node{type = Type, id = Nidx, owners = O}) ->
UNCOV
1445
                Owners = node_owners_call(Host, Type, Nidx, O),
16✔
UNCOV
1446
                case lists:member(FromLJID, Owners) of
16✔
1447
                    true ->
UNCOV
1448
                        case node_call(Host, Type, get_subscriptions, [Nidx, Subscriber]) of
16✔
1449
                            {result, Subs} ->
UNCOV
1450
                                update_auth(Host, Node, Type, Nidx, Subscriber, Allow, Subs);
16✔
1451
                            {error, _} = Err ->
1452
                                Err
×
1453
                        end;
1454
                    false ->
1455
                        {error, xmpp:err_forbidden(?T("Owner privileges required"), Lang)}
×
1456
                end
1457
        end,
UNCOV
1458
    case transaction(Host, Node, Action, sync_dirty) of
16✔
1459
        {error, Error} ->
1460
            ejabberd_router:route_error(Packet, Error);
×
1461
        {result, {_, _NewSubscription}} ->
1462
            %% XXX: notify about subscription state change, section 12.11
UNCOV
1463
            ok
16✔
1464
    end.
1465

1466
-spec update_auth(binary(), binary(), _, _, jid() | error, boolean(), _) ->
1467
                         {result, ok} | {error, stanza_error()}.
1468
update_auth(Host, Node, Type, Nidx, Subscriber, Allow, Subs) ->
UNCOV
1469
    Sub= lists:filter(fun
16✔
UNCOV
1470
                ({pending, _}) -> true;
16✔
1471
                (_) -> false
×
1472
            end,
1473
            Subs),
UNCOV
1474
    case Sub of
16✔
1475
        [{pending, SubId}|_] ->
UNCOV
1476
            NewSub = case Allow of
16✔
UNCOV
1477
                true -> subscribed;
8✔
UNCOV
1478
                false -> none
8✔
1479
            end,
UNCOV
1480
            node_call(Host, Type, set_subscriptions, [Nidx, Subscriber, NewSub, SubId]),
16✔
UNCOV
1481
            send_authorization_approval(Host, Subscriber, Node, NewSub),
16✔
UNCOV
1482
            {result, ok};
16✔
1483
        _ ->
1484
            Txt = ?T("No pending subscriptions found"),
×
1485
            {error, xmpp:err_unexpected_request(Txt, ejabberd_option:language())}
×
1486
    end.
1487

1488
%% @doc <p>Create new pubsub nodes</p>
1489
%%<p>In addition to method-specific error conditions, there are several general reasons why the node creation request might fail:</p>
1490
%%<ul>
1491
%%<li>The service does not support node creation.</li>
1492
%%<li>Only entities that are registered with the service are allowed to create nodes but the requesting entity is not registered.</li>
1493
%%<li>The requesting entity does not have sufficient privileges to create nodes.</li>
1494
%%<li>The requested Node already exists.</li>
1495
%%<li>The request did not include a Node and "instant nodes" are not supported.</li>
1496
%%</ul>
1497
%%<p>ote: node creation is a particular case, error return code is evaluated at many places:</p>
1498
%%<ul>
1499
%%<li>iq_pubsub checks if service supports node creation (type exists)</li>
1500
%%<li>create_node checks if instant nodes are supported</li>
1501
%%<li>create_node asks node plugin if entity have sufficient privilege</li>
1502
%%<li>nodetree create_node checks if nodeid already exists</li>
1503
%%<li>node plugin create_node just sets default affiliation/subscription</li>
1504
%%</ul>
1505
-spec create_node(host(), binary(), binary(), jid(),
1506
                  binary()) -> {result, pubsub()} | {error, stanza_error()}.
1507
create_node(Host, ServerHost, Node, Owner, Type) ->
UNCOV
1508
    create_node(Host, ServerHost, Node, Owner, Type, all, []).
4✔
1509

1510
-spec create_node(host(), binary(), binary(), jid(), binary(),
1511
                  atom(), [{binary(), [binary()]}]) -> {result, pubsub()} | {error, stanza_error()}.
1512
create_node(Host, ServerHost, <<>>, Owner, Type, Access, Configuration) ->
UNCOV
1513
    case lists:member(<<"instant-nodes">>, plugin_features(Host, Type)) of
160✔
1514
        true ->
UNCOV
1515
            Node = p1_rand:get_string(),
160✔
UNCOV
1516
            case create_node(Host, ServerHost, Node, Owner, Type, Access, Configuration) of
160✔
1517
                {result, _} ->
UNCOV
1518
                    {result, #pubsub{create = Node}};
160✔
1519
                Error ->
1520
                    Error
×
1521
            end;
1522
        false ->
1523
            {error, extended_error(xmpp:err_not_acceptable(), err_nodeid_required())}
×
1524
    end;
1525
create_node(Host, ServerHost, Node, Owner, GivenType, Access, Configuration) ->
UNCOV
1526
    Type = select_type(ServerHost, Host, Node, GivenType),
260✔
UNCOV
1527
    NodeOptions = merge_config(
260✔
1528
                    [node_config(Node, ServerHost),
1529
                     Configuration, node_options(Host, Type)]),
UNCOV
1530
    CreateNode =
260✔
1531
        fun() ->
UNCOV
1532
                Parent = case node_call(Host, Type, node_to_path, [Node]) of
260✔
1533
                             {result, [Node]} ->
UNCOV
1534
                                 <<>>;
260✔
1535
                             {result, Path} ->
1536
                                 element(2, node_call(Host, Type, path_to_node,
×
1537
                                                      [lists:sublist(Path, length(Path)-1)]))
1538
                         end,
UNCOV
1539
                Parents = case Parent of
260✔
UNCOV
1540
                              <<>> -> [];
260✔
1541
                              _ -> [Parent]
×
1542
                          end,
UNCOV
1543
                case node_call(Host, Type, create_node_permission,
260✔
1544
                               [Host, ServerHost, Node, Parent, Owner, Access]) of
1545
                    {result, true} ->
UNCOV
1546
                        case tree_call(Host, create_node,
260✔
1547
                                       [Host, Node, Type, Owner, NodeOptions, Parents])
1548
                        of
1549
                            {ok, Nidx} ->
UNCOV
1550
                                case get_node_subs_by_depth(Host, Node, Owner) of
260✔
1551
                                    {result, SubsByDepth} ->
UNCOV
1552
                                        case node_call(Host, Type, create_node, [Nidx, Owner]) of
260✔
UNCOV
1553
                                            {result, Result} -> {result, {Nidx, SubsByDepth, Result}};
260✔
1554
                                            Error -> Error
×
1555
                                        end;
1556
                                    Error ->
1557
                                        Error
×
1558
                                end;
1559
                            {error, {virtual, Nidx}} ->
1560
                                case node_call(Host, Type, create_node, [Nidx, Owner]) of
×
1561
                                    {result, Result} -> {result, {Nidx, [], Result}};
×
1562
                                    Error -> Error
×
1563
                                end;
1564
                            Error ->
1565
                                Error
×
1566
                        end;
1567
                    {result, _} ->
1568
                        Txt = ?T("You're not allowed to create nodes"),
×
1569
                        {error, xmpp:err_forbidden(Txt, ejabberd_option:language())};
×
1570
                    Err ->
1571
                        Err
×
1572
                end
1573
        end,
UNCOV
1574
    Reply = #pubsub{create = Node},
260✔
UNCOV
1575
    case transaction(Host, CreateNode, transaction) of
260✔
1576
        {result, {Nidx, SubsByDepth, {Result, broadcast}}} ->
UNCOV
1577
            broadcast_created_node(Host, Node, Nidx, Type, NodeOptions, SubsByDepth),
260✔
UNCOV
1578
            ejabberd_hooks:run(pubsub_create_node, ServerHost,
260✔
1579
                               [ServerHost, Host, Node, Nidx, NodeOptions]),
UNCOV
1580
            case Result of
260✔
UNCOV
1581
                default -> {result, Reply};
260✔
1582
                _ -> {result, Result}
×
1583
            end;
1584
        {result, {Nidx, _SubsByDepth, Result}} ->
1585
            ejabberd_hooks:run(pubsub_create_node, ServerHost,
×
1586
                               [ServerHost, Host, Node, Nidx, NodeOptions]),
1587
            case Result of
×
1588
                default -> {result, Reply};
×
1589
                _ -> {result, Result}
×
1590
            end;
1591
        Error ->
1592
            %% in case we change transaction to sync_dirty...
1593
            %%  node_call(Host, Type, delete_node, [Host, Node]),
1594
            %%  tree_call(Host, delete_node, [Host, Node]),
1595
            Error
×
1596
    end.
1597

1598
%% @doc <p>Delete specified node and all children.</p>
1599
%%<p>There are several reasons why the node deletion request might fail:</p>
1600
%%<ul>
1601
%%<li>The requesting entity does not have sufficient privileges to delete the node.</li>
1602
%%<li>The node is the root collection node, which cannot be deleted.</li>
1603
%%<li>The specified node does not exist.</li>
1604
%%</ul>
1605
-spec delete_node(host(), binary(), jid()) -> {result, pubsub_owner()} | {error, stanza_error()}.
1606
delete_node(_Host, <<>>, _Owner) ->
1607
    {error, xmpp:err_not_allowed(?T("No node specified"), ejabberd_option:language())};
×
1608
delete_node(Host, Node, Owner) ->
UNCOV
1609
    Action =
308✔
1610
        fun(#pubsub_node{type = Type, id = Nidx}) ->
UNCOV
1611
                case node_call(Host, Type, get_affiliation, [Nidx, Owner]) of
300✔
1612
                    {result, owner} ->
UNCOV
1613
                        case get_node_subs_by_depth(Host, Node, service_jid(Host)) of
260✔
1614
                            {result, SubsByDepth} ->
UNCOV
1615
                                case tree_call(Host, delete_node, [Host, Node]) of
260✔
1616
                                    Removed when is_list(Removed) ->
UNCOV
1617
                                        case node_call(Host, Type, delete_node, [Removed]) of
260✔
UNCOV
1618
                                            {result, Res} -> {result, {SubsByDepth, Res}};
260✔
1619
                                            Error -> Error
×
1620
                                        end;
1621
                                    Error ->
1622
                                        Error
×
1623
                                end;
1624
                            Error ->
1625
                                Error
×
1626
                        end;
1627
                    {result, _} ->
UNCOV
1628
                        Lang = ejabberd_option:language(),
40✔
UNCOV
1629
                        {error, xmpp:err_forbidden(?T("Owner privileges required"), Lang)};
40✔
1630
                    Error ->
1631
                        Error
×
1632
                end
1633
        end,
UNCOV
1634
    Reply = undefined,
308✔
UNCOV
1635
    ServerHost = serverhost(Host),
308✔
UNCOV
1636
    case transaction(Host, Node, Action, transaction) of
308✔
1637
        {result, {_, {SubsByDepth, {Result, broadcast, Removed}}}} ->
UNCOV
1638
            lists:foreach(fun ({RNode, _RSubs}) ->
260✔
UNCOV
1639
                        {RH, RN} = RNode#pubsub_node.nodeid,
260✔
UNCOV
1640
                        RNidx = RNode#pubsub_node.id,
260✔
UNCOV
1641
                        RType = RNode#pubsub_node.type,
260✔
UNCOV
1642
                        ROptions = RNode#pubsub_node.options,
260✔
UNCOV
1643
                        unset_cached_item(RH, RNidx),
260✔
UNCOV
1644
                        broadcast_removed_node(RH, RN, RNidx, RType, ROptions, SubsByDepth),
260✔
UNCOV
1645
                        ejabberd_hooks:run(pubsub_delete_node,
260✔
1646
                            ServerHost,
1647
                            [ServerHost, RH, RN, RNidx])
1648
                end,
1649
                Removed),
UNCOV
1650
            case Result of
260✔
UNCOV
1651
                default -> {result, Reply};
260✔
1652
                _ -> {result, Result}
×
1653
            end;
1654
        {result, {_, {_, {Result, Removed}}}} ->
1655
            lists:foreach(fun ({RNode, _RSubs}) ->
×
1656
                        {RH, RN} = RNode#pubsub_node.nodeid,
×
1657
                        RNidx = RNode#pubsub_node.id,
×
1658
                        unset_cached_item(RH, RNidx),
×
1659
                        ejabberd_hooks:run(pubsub_delete_node,
×
1660
                            ServerHost,
1661
                            [ServerHost, RH, RN, RNidx])
1662
                end,
1663
                Removed),
1664
            case Result of
×
1665
                default -> {result, Reply};
×
1666
                _ -> {result, Result}
×
1667
            end;
1668
        {result, {TNode, {_, Result}}} ->
1669
            Nidx = TNode#pubsub_node.id,
×
1670
            unset_cached_item(Host, Nidx),
×
1671
            ejabberd_hooks:run(pubsub_delete_node, ServerHost,
×
1672
                [ServerHost, Host, Node, Nidx]),
1673
            case Result of
×
1674
                default -> {result, Reply};
×
1675
                _ -> {result, Result}
×
1676
            end;
1677
        Error ->
UNCOV
1678
            Error
48✔
1679
    end.
1680

1681
%% @see node_hometree:subscribe_node/5
1682
%% @doc <p>Accepts or rejects subcription requests on a PubSub node.</p>
1683
%%<p>There are several reasons why the subscription request might fail:</p>
1684
%%<ul>
1685
%%<li>The bare JID portions of the JIDs do not match.</li>
1686
%%<li>The node has an access model of "presence" and the requesting entity is not subscribed to the owner's presence.</li>
1687
%%<li>The node has an access model of "roster" and the requesting entity is not in one of the authorized roster groups.</li>
1688
%%<li>The node has an access model of "whitelist" and the requesting entity is not on the whitelist.</li>
1689
%%<li>The service requires payment for subscriptions to the node.</li>
1690
%%<li>The requesting entity is anonymous and the service does not allow anonymous entities to subscribe.</li>
1691
%%<li>The requesting entity has a pending subscription.</li>
1692
%%<li>The requesting entity is blocked from subscribing (e.g., because having an affiliation of outcast).</li>
1693
%%<li>The node does not support subscriptions.</li>
1694
%%<li>The node does not exist.</li>
1695
%%</ul>
1696
-spec subscribe_node(host(), binary(), jid(), jid(), [{binary(), [binary()]}]) ->
1697
                            {result, pubsub()} | {error, stanza_error()}.
1698
subscribe_node(Host, Node, From, JID, Configuration) ->
UNCOV
1699
    SubModule = subscription_plugin(Host),
140✔
UNCOV
1700
    SubOpts = case SubModule:parse_options_xform(Configuration) of
140✔
UNCOV
1701
        {result, GoodSubOpts} -> GoodSubOpts;
140✔
1702
        _ -> invalid
×
1703
    end,
UNCOV
1704
    Subscriber = jid:tolower(JID),
140✔
UNCOV
1705
    Action = fun (#pubsub_node{options = Options, type = Type, id = Nidx, owners = O}) ->
140✔
UNCOV
1706
            Features = plugin_features(Host, Type),
140✔
UNCOV
1707
            SubscribeFeature = lists:member(<<"subscribe">>, Features),
140✔
UNCOV
1708
            OptionsFeature = lists:member(<<"subscription-options">>, Features),
140✔
UNCOV
1709
            HasOptions = not (SubOpts == []),
140✔
UNCOV
1710
            SubscribeConfig = get_option(Options, subscribe),
140✔
UNCOV
1711
            AccessModel = get_option(Options, access_model),
140✔
UNCOV
1712
            SendLast = get_option(Options, send_last_published_item),
140✔
UNCOV
1713
            AllowedGroups = get_option(Options, roster_groups_allowed, []),
140✔
UNCOV
1714
            CanSubscribe = case get_max_subscriptions_node(Host) of
140✔
1715
                Max when is_integer(Max) ->
1716
                    case node_call(Host, Type, get_node_subscriptions, [Nidx]) of
×
1717
                        {result, NodeSubs} ->
1718
                            SubsNum = lists:foldl(
×
1719
                                    fun ({_, subscribed, _}, Acc) -> Acc+1;
×
1720
                                        (_, Acc) -> Acc
×
1721
                                    end, 0, NodeSubs),
1722
                            SubsNum < Max;
×
1723
                        _ ->
1724
                            true
×
1725
                    end;
1726
                _ ->
UNCOV
1727
                    true
140✔
1728
            end,
UNCOV
1729
            if not SubscribeFeature ->
140✔
1730
                    {error, extended_error(xmpp:err_feature_not_implemented(),
×
1731
                                           err_unsupported('subscribe'))};
1732
                not SubscribeConfig ->
1733
                    {error, extended_error(xmpp:err_feature_not_implemented(),
×
1734
                                           err_unsupported('subscribe'))};
1735
                HasOptions andalso not OptionsFeature ->
1736
                    {error, extended_error(xmpp:err_feature_not_implemented(),
×
1737
                                           err_unsupported('subscription-options'))};
1738
                SubOpts == invalid ->
1739
                    {error, extended_error(xmpp:err_bad_request(),
×
1740
                                           err_invalid_options())};
1741
                not CanSubscribe ->
1742
                    %% fallback to closest XEP compatible result, assume we are not allowed to subscribe
1743
                    {error, extended_error(xmpp:err_not_allowed(),
×
1744
                                           err_closed_node())};
1745
                true ->
UNCOV
1746
                    Owners = node_owners_call(Host, Type, Nidx, O),
140✔
UNCOV
1747
                    {PS, RG} = get_presence_and_roster_permissions(Host, JID,
140✔
1748
                            Owners, AccessModel, AllowedGroups),
UNCOV
1749
                    node_call(Host, Type, subscribe_node,
140✔
1750
                        [Nidx, From, Subscriber, AccessModel,
1751
                            SendLast, PS, RG, SubOpts])
1752
            end
1753
    end,
UNCOV
1754
    Reply = fun (Subscription) ->
140✔
UNCOV
1755
            Sub = case Subscription of
124✔
1756
                      {subscribed, SubId} ->
UNCOV
1757
                          #ps_subscription{jid = JID, type = subscribed, subid = SubId};
108✔
1758
                      Other ->
UNCOV
1759
                          #ps_subscription{jid = JID, type = Other}
16✔
1760
                  end,
UNCOV
1761
            #pubsub{subscription = Sub#ps_subscription{node = Node}}
124✔
1762
    end,
UNCOV
1763
    case transaction(Host, Node, Action, sync_dirty) of
140✔
1764
        {result, {TNode, {Result, subscribed, SubId, send_last}}} ->
UNCOV
1765
            Nidx = TNode#pubsub_node.id,
76✔
UNCOV
1766
            Type = TNode#pubsub_node.type,
76✔
UNCOV
1767
            Options = TNode#pubsub_node.options,
76✔
UNCOV
1768
            send_items(Host, Node, Nidx, Type, Options, Subscriber, last),
76✔
UNCOV
1769
            ServerHost = serverhost(Host),
76✔
UNCOV
1770
            ejabberd_hooks:run(pubsub_subscribe_node, ServerHost,
76✔
1771
                [ServerHost, Host, Node, Subscriber, SubId]),
UNCOV
1772
            case Result of
76✔
UNCOV
1773
                default -> {result, Reply({subscribed, SubId})};
76✔
1774
                _ -> {result, Result}
×
1775
            end;
1776
        {result, {_TNode, {default, subscribed, SubId}}} ->
UNCOV
1777
            {result, Reply({subscribed, SubId})};
32✔
1778
        {result, {_TNode, {Result, subscribed, _SubId}}} ->
1779
            {result, Result};
×
1780
        {result, {TNode, {default, pending, _SubId}}} ->
UNCOV
1781
            send_authorization_request(TNode, JID),
16✔
UNCOV
1782
            {result, Reply(pending)};
16✔
1783
        {result, {TNode, {Result, pending}}} ->
1784
            send_authorization_request(TNode, JID),
×
1785
            {result, Result};
×
1786
        {result, {_, Result}} ->
1787
            {result, Result};
×
UNCOV
1788
        Error -> Error
16✔
1789
    end.
1790

1791
%% @doc <p>Unsubscribe <tt>JID</tt> from the <tt>Node</tt>.</p>
1792
%%<p>There are several reasons why the unsubscribe request might fail:</p>
1793
%%<ul>
1794
%%<li>The requesting entity has multiple subscriptions to the node but does not specify a subscription ID.</li>
1795
%%<li>The request does not specify an existing subscriber.</li>
1796
%%<li>The requesting entity does not have sufficient privileges to unsubscribe the specified JID.</li>
1797
%%<li>The node does not exist.</li>
1798
%%<li>The request specifies a subscription ID that is not valid or current.</li>
1799
%%</ul>
1800
-spec unsubscribe_node(host(), binary(), jid(), jid(), binary()) ->
1801
                              {result, undefined} | {error, stanza_error()}.
1802
unsubscribe_node(Host, Node, From, JID, SubId) ->
UNCOV
1803
    Subscriber = jid:tolower(JID),
64✔
UNCOV
1804
    Action = fun (#pubsub_node{type = Type, id = Nidx}) ->
64✔
UNCOV
1805
            node_call(Host, Type, unsubscribe_node, [Nidx, From, Subscriber, SubId])
60✔
1806
    end,
UNCOV
1807
    case transaction(Host, Node, Action, sync_dirty) of
64✔
1808
        {result, {_, default}} ->
UNCOV
1809
            ServerHost = serverhost(Host),
40✔
UNCOV
1810
            ejabberd_hooks:run(pubsub_unsubscribe_node, ServerHost,
40✔
1811
                               [ServerHost, Host, Node, Subscriber, SubId]),
UNCOV
1812
            {result, undefined};
40✔
UNCOV
1813
        Error -> Error
24✔
1814
    end.
1815

1816
%% @doc <p>Publish item to a PubSub node.</p>
1817
%% <p>The permission to publish an item must be verified by the plugin implementation.</p>
1818
%%<p>There are several reasons why the publish request might fail:</p>
1819
%%<ul>
1820
%%<li>The requesting entity does not have sufficient privileges to publish.</li>
1821
%%<li>The node does not support item publication.</li>
1822
%%<li>The node does not exist.</li>
1823
%%<li>The payload size exceeds a service-defined limit.</li>
1824
%%<li>The item contains more than one payload element or the namespace of the root payload element does not match the configured namespace for the node.</li>
1825
%%<li>The request does not match the node configuration.</li>
1826
%%</ul>
1827
-spec publish_item(host(), binary(), binary(), jid(), binary(),
1828
                   [xmlel()]) -> {result, pubsub()} | {error, stanza_error()}.
1829
publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload) ->
UNCOV
1830
    publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload, [], all).
8✔
1831
publish_item(Host, ServerHost, Node, Publisher, <<>>, Payload, PubOpts, Access) ->
1832
    publish_item(Host, ServerHost, Node, Publisher, uniqid(), Payload, PubOpts, Access);
×
1833
publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload, PubOpts, Access) ->
UNCOV
1834
    Action = fun (#pubsub_node{options = Options, type = Type, id = Nidx}) ->
296✔
UNCOV
1835
            Features = plugin_features(Host, Type),
272✔
UNCOV
1836
            PublishFeature = lists:member(<<"publish">>, Features),
272✔
UNCOV
1837
            PublishModel = get_option(Options, publish_model),
272✔
UNCOV
1838
            DeliverPayloads = get_option(Options, deliver_payloads),
272✔
UNCOV
1839
            PersistItems = get_option(Options, persist_items),
272✔
UNCOV
1840
            MaxItems = max_items(Host, Options),
272✔
UNCOV
1841
            PayloadCount = payload_xmlelements(Payload),
272✔
UNCOV
1842
            PayloadSize = byte_size(term_to_binary(Payload)) - 2,
272✔
UNCOV
1843
            PayloadMaxSize = get_option(Options, max_payload_size),
272✔
UNCOV
1844
            PreconditionsMet = preconditions_met(PubOpts, Options),
272✔
UNCOV
1845
            if not PublishFeature ->
272✔
1846
                    {error, extended_error(xmpp:err_feature_not_implemented(),
×
1847
                                           err_unsupported(publish))};
1848
                not PreconditionsMet ->
1849
                    {error, extended_error(xmpp:err_conflict(),
×
1850
                                           err_precondition_not_met())};
1851
                PayloadSize > PayloadMaxSize ->
1852
                    {error, extended_error(xmpp:err_not_acceptable(),
×
1853
                                           err_payload_too_big())};
1854
                (DeliverPayloads or PersistItems) and (PayloadCount == 0) ->
1855
                    {error, extended_error(xmpp:err_bad_request(),
×
1856
                                           err_item_required())};
1857
                (DeliverPayloads or PersistItems) and (PayloadCount > 1) ->
1858
                    {error, extended_error(xmpp:err_bad_request(),
×
1859
                                           err_invalid_payload())};
1860
                (not (DeliverPayloads or PersistItems)) and (PayloadCount > 0) ->
1861
                    {error, extended_error(xmpp:err_bad_request(),
×
1862
                                           err_item_forbidden())};
1863
                true ->
UNCOV
1864
                    node_call(Host, Type, publish_item,
272✔
1865
                        [Nidx, Publisher, PublishModel, MaxItems, ItemId, Payload, PubOpts])
1866
            end
1867
    end,
UNCOV
1868
    Reply = #pubsub{publish = #ps_publish{node = Node,
296✔
1869
                                          items = [#ps_item{id = ItemId}]}},
UNCOV
1870
    case transaction(Host, Node, Action, sync_dirty) of
296✔
1871
        {result, {TNode, {Result, Broadcast, Removed}}} ->
UNCOV
1872
            Nidx = TNode#pubsub_node.id,
248✔
UNCOV
1873
            Type = TNode#pubsub_node.type,
248✔
UNCOV
1874
            Options = TNode#pubsub_node.options,
248✔
UNCOV
1875
            BrPayload = case Broadcast of
248✔
UNCOV
1876
                broadcast -> Payload;
248✔
1877
                PluginPayload -> PluginPayload
×
1878
            end,
UNCOV
1879
            set_cached_item(Host, Nidx, ItemId, Publisher, BrPayload),
248✔
UNCOV
1880
            case get_option(Options, deliver_notifications) of
248✔
1881
                true ->
UNCOV
1882
                    broadcast_publish_item(Host, Node, Nidx, Type, Options, ItemId,
248✔
1883
                        Publisher, BrPayload, Removed);
1884
                false ->
1885
                    ok
×
1886
            end,
UNCOV
1887
            ejabberd_hooks:run(pubsub_publish_item, ServerHost,
248✔
1888
                [ServerHost, Node, Publisher, service_jid(Host), ItemId, BrPayload]),
UNCOV
1889
            case Result of
248✔
UNCOV
1890
                default -> {result, Reply};
248✔
1891
                _ -> {result, Result}
×
1892
            end;
1893
        {result, {TNode, {default, Removed}}} ->
1894
            Nidx = TNode#pubsub_node.id,
×
1895
            Type = TNode#pubsub_node.type,
×
1896
            Options = TNode#pubsub_node.options,
×
1897
            broadcast_retract_items(Host, Publisher, Node, Nidx, Type, Options, Removed),
×
1898
            set_cached_item(Host, Nidx, ItemId, Publisher, Payload),
×
1899
            {result, Reply};
×
1900
        {result, {TNode, {Result, Removed}}} ->
1901
            Nidx = TNode#pubsub_node.id,
×
1902
            Type = TNode#pubsub_node.type,
×
1903
            Options = TNode#pubsub_node.options,
×
1904
            broadcast_retract_items(Host, Publisher, Node, Nidx, Type, Options, Removed),
×
1905
            set_cached_item(Host, Nidx, ItemId, Publisher, Payload),
×
1906
            {result, Result};
×
1907
        {result, {_, default}} ->
1908
            {result, Reply};
×
1909
        {result, {_, Result}} ->
1910
            {result, Result};
×
1911
        {error, #stanza_error{reason = 'item-not-found'}} ->
UNCOV
1912
            Type = select_type(ServerHost, Host, Node),
24✔
UNCOV
1913
            case lists:member(<<"auto-create">>, plugin_features(Host, Type)) of
24✔
1914
                true ->
UNCOV
1915
                    case create_node(Host, ServerHost, Node, Publisher, Type, Access, PubOpts) of
24✔
1916
                        {result, #pubsub{create = NewNode}} ->
UNCOV
1917
                            publish_item(Host, ServerHost, NewNode, Publisher, ItemId,
24✔
1918
                                         Payload, PubOpts, Access);
1919
                        _ ->
1920
                            {error, xmpp:err_item_not_found()}
×
1921
                    end;
1922
                false ->
1923
                    Txt = ?T("Automatic node creation is not enabled"),
×
1924
                    {error, xmpp:err_item_not_found(Txt, ejabberd_option:language())}
×
1925
            end;
1926
        Error ->
UNCOV
1927
            Error
24✔
1928
    end.
1929

1930
%% @doc <p>Delete item from a PubSub node.</p>
1931
%% <p>The permission to delete an item must be verified by the plugin implementation.</p>
1932
%%<p>There are several reasons why the item retraction request might fail:</p>
1933
%%<ul>
1934
%%<li>The publisher does not have sufficient privileges to delete the requested item.</li>
1935
%%<li>The node or item does not exist.</li>
1936
%%<li>The request does not specify a node.</li>
1937
%%<li>The request does not include an <item/> element or the <item/> element does not specify an ItemId.</li>
1938
%%<li>The node does not support persistent items.</li>
1939
%%<li>The service does not support the deletion of items.</li>
1940
%%</ul>
1941
-spec delete_item(host(), binary(), jid(), binary()) -> {result, undefined} |
1942
                                                        {error, stanza_error()}.
1943
delete_item(Host, Node, Publisher, ItemId) ->
1944
    delete_item(Host, Node, Publisher, ItemId, false).
×
1945
delete_item(_, <<>>, _, _, _) ->
1946
    {error, extended_error(xmpp:err_bad_request(), err_nodeid_required())};
×
1947
delete_item(Host, Node, Publisher, ItemId, ForceNotify) ->
UNCOV
1948
    Action = fun (#pubsub_node{options = Options, type = Type, id = Nidx}) ->
66✔
UNCOV
1949
            Features = plugin_features(Host, Type),
66✔
UNCOV
1950
            PersistentFeature = lists:member(<<"persistent-items">>, Features),
66✔
UNCOV
1951
            DeleteFeature = lists:member(<<"delete-items">>, Features),
66✔
UNCOV
1952
            PublishModel = get_option(Options, publish_model),
66✔
UNCOV
1953
            if %%->   iq_pubsub just does that matches
66✔
1954
                %%        %% Request does not specify an item
1955
                %%        {error, extended_error(?ERR_BAD_REQUEST, "item-required")};
1956
                not PersistentFeature ->
1957
                    {error, extended_error(xmpp:err_feature_not_implemented(),
×
1958
                                           err_unsupported('persistent-items'))};
1959
                not DeleteFeature ->
1960
                    {error, extended_error(xmpp:err_feature_not_implemented(),
×
1961
                                           err_unsupported('delete-items'))};
1962
                true ->
UNCOV
1963
                    node_call(Host, Type, delete_item, [Nidx, Publisher, PublishModel, ItemId])
66✔
1964
            end
1965
    end,
UNCOV
1966
    Reply = undefined,
66✔
UNCOV
1967
    case transaction(Host, Node, Action, sync_dirty) of
66✔
1968
        {result, {TNode, {Result, broadcast}}} ->
UNCOV
1969
            Nidx = TNode#pubsub_node.id,
34✔
UNCOV
1970
            Type = TNode#pubsub_node.type,
34✔
UNCOV
1971
            Options = TNode#pubsub_node.options,
34✔
UNCOV
1972
            ServerHost = serverhost(Host),
34✔
UNCOV
1973
            ejabberd_hooks:run(pubsub_delete_item, ServerHost,
34✔
1974
                               [ServerHost, Node, Publisher, service_jid(Host), ItemId]),
UNCOV
1975
            broadcast_retract_items(Host, Publisher, Node, Nidx, Type, Options, [ItemId], ForceNotify),
34✔
UNCOV
1976
            case get_cached_item(Host, Nidx) of
34✔
1977
                #pubsub_item{itemid = {ItemId, Nidx}} -> unset_cached_item(Host, Nidx);
×
UNCOV
1978
                _ -> ok
34✔
1979
            end,
UNCOV
1980
            case Result of
34✔
UNCOV
1981
                default -> {result, Reply};
34✔
1982
                _ -> {result, Result}
×
1983
            end;
1984
        {result, {_, default}} ->
1985
            {result, Reply};
×
1986
        {result, {_, Result}} ->
1987
            {result, Result};
×
1988
        Error ->
UNCOV
1989
            Error
32✔
1990
    end.
1991

1992
%% @doc <p>Delete all items of specified node owned by JID.</p>
1993
%%<p>There are several reasons why the node purge request might fail:</p>
1994
%%<ul>
1995
%%<li>The node or service does not support node purging.</li>
1996
%%<li>The requesting entity does not have sufficient privileges to purge the node.</li>
1997
%%<li>The node is not configured to persist items.</li>
1998
%%<li>The specified node does not exist.</li>
1999
%%</ul>
2000
-spec purge_node(mod_pubsub:host(), binary(), jid()) -> {result, undefined} |
2001
                                                        {error, stanza_error()}.
2002
purge_node(Host, Node, Owner) ->
UNCOV
2003
    Action = fun (#pubsub_node{options = Options, type = Type, id = Nidx}) ->
56✔
UNCOV
2004
            Features = plugin_features(Host, Type),
56✔
UNCOV
2005
            PurgeFeature = lists:member(<<"purge-nodes">>, Features),
56✔
UNCOV
2006
            PersistentFeature = lists:member(<<"persistent-items">>, Features),
56✔
UNCOV
2007
            PersistentConfig = get_option(Options, persist_items),
56✔
UNCOV
2008
            if not PurgeFeature ->
56✔
2009
                    {error, extended_error(xmpp:err_feature_not_implemented(),
×
2010
                                           err_unsupported('purge-nodes'))};
2011
                not PersistentFeature ->
2012
                    {error, extended_error(xmpp:err_feature_not_implemented(),
×
2013
                                           err_unsupported('persistent-items'))};
2014
                not PersistentConfig ->
2015
                    {error, extended_error(xmpp:err_feature_not_implemented(),
×
2016
                                           err_unsupported('persistent-items'))};
UNCOV
2017
                true -> node_call(Host, Type, purge_node, [Nidx, Owner])
56✔
2018
            end
2019
    end,
UNCOV
2020
    Reply = undefined,
56✔
UNCOV
2021
    case transaction(Host, Node, Action, transaction) of
56✔
2022
        {result, {TNode, {Result, broadcast}}} ->
UNCOV
2023
            Nidx = TNode#pubsub_node.id,
16✔
UNCOV
2024
            Type = TNode#pubsub_node.type,
16✔
UNCOV
2025
            Options = TNode#pubsub_node.options,
16✔
UNCOV
2026
            broadcast_purge_node(Host, Node, Nidx, Type, Options),
16✔
UNCOV
2027
            unset_cached_item(Host, Nidx),
16✔
UNCOV
2028
            case Result of
16✔
UNCOV
2029
                default -> {result, Reply};
16✔
2030
                _ -> {result, Result}
×
2031
            end;
2032
        {result, {_, default}} ->
2033
            {result, Reply};
×
2034
        {result, {_, Result}} ->
2035
            {result, Result};
×
2036
        Error ->
UNCOV
2037
            Error
40✔
2038
    end.
2039

2040
%% @doc <p>Return the items of a given node.</p>
2041
%% <p>The number of items to return is limited by MaxItems.</p>
2042
%% <p>The permission are not checked in this function.</p>
2043
-spec get_items(host(), binary(), jid(), binary(),
2044
                undefined | non_neg_integer(), [binary()], undefined | rsm_set()) ->
2045
                       {result, pubsub()} | {error, stanza_error()}.
2046
get_items(Host, Node, From, SubId, MaxItems, ItemIds, undefined)
2047
  when MaxItems =/= undefined ->
2048
    get_items(Host, Node, From, SubId, MaxItems, ItemIds,
×
2049
              #rsm_set{max = MaxItems, before = <<>>});
2050
get_items(Host, Node, From, SubId, _MaxItems, ItemIds, RSM) ->
UNCOV
2051
    Action =
102✔
2052
        fun(#pubsub_node{options = Options, type = Type,
2053
                         id = Nidx, owners = O}) ->
UNCOV
2054
                Features = plugin_features(Host, Type),
100✔
UNCOV
2055
                RetreiveFeature = lists:member(<<"retrieve-items">>, Features),
100✔
UNCOV
2056
                PersistentFeature = lists:member(<<"persistent-items">>, Features),
100✔
UNCOV
2057
                AccessModel = get_option(Options, access_model),
100✔
UNCOV
2058
                AllowedGroups = get_option(Options, roster_groups_allowed, []),
100✔
UNCOV
2059
                if not RetreiveFeature ->
100✔
2060
                        {error, extended_error(xmpp:err_feature_not_implemented(),
×
2061
                                               err_unsupported('retrieve-items'))};
2062
                   not PersistentFeature ->
2063
                        {error, extended_error(xmpp:err_feature_not_implemented(),
×
2064
                                               err_unsupported('persistent-items'))};
2065
                   true ->
UNCOV
2066
                        Owners = node_owners_call(Host, Type, Nidx, O),
100✔
UNCOV
2067
                        {PS, RG} = get_presence_and_roster_permissions(
100✔
2068
                                     Host, From, Owners, AccessModel, AllowedGroups),
UNCOV
2069
                        case ItemIds of
100✔
2070
                            [ItemId] ->
2071
                                NotFound = xmpp:err_item_not_found(),
×
2072
                                case node_call(Host, Type, get_item,
×
2073
                                               [Nidx, ItemId, From, AccessModel, PS, RG, undefined])
2074
                                of
2075
                                    {error, NotFound} -> {result, {[], undefined}};
×
2076
                                    Result -> Result
×
2077
                                end;
2078
                            _ ->
UNCOV
2079
                                node_call(Host, Type, get_items,
100✔
2080
                                          [Nidx, From, AccessModel, PS, RG, SubId, RSM])
2081
                        end
2082
                end
2083
        end,
UNCOV
2084
    case transaction(Host, Node, Action, sync_dirty) of
102✔
2085
        {result, {TNode, {Items, RsmOut}}} ->
UNCOV
2086
            SendItems = case ItemIds of
84✔
2087
                            [] ->
UNCOV
2088
                                Items;
84✔
2089
                            _ ->
2090
                                lists:filter(
×
2091
                                  fun(#pubsub_item{itemid = {ItemId, _}}) ->
2092
                                          lists:member(ItemId, ItemIds)
×
2093
                                  end, Items)
2094
                        end,
UNCOV
2095
            Options = TNode#pubsub_node.options,
84✔
UNCOV
2096
            {result, #pubsub{items = items_els(Node, Options, SendItems),
84✔
2097
                             rsm = RsmOut}};
2098
        {result, {TNode, Item}} ->
2099
            Options = TNode#pubsub_node.options,
×
2100
            {result, #pubsub{items = items_els(Node, Options, [Item])}};
×
2101
        Error ->
UNCOV
2102
            Error
18✔
2103
    end.
2104

2105
%% Seems like this function broken
2106
get_items(Host, Node) ->
UNCOV
2107
    Action = fun (#pubsub_node{type = Type, id = Nidx}) ->
56✔
UNCOV
2108
            node_call(Host, Type, get_items, [Nidx, service_jid(Host), undefined])
40✔
2109
    end,
UNCOV
2110
    case transaction(Host, Node, Action, sync_dirty) of
56✔
UNCOV
2111
        {result, {_, {Items, _}}} -> Items;
40✔
UNCOV
2112
        Error -> Error
16✔
2113
    end.
2114

2115
%% This function is broken too?
2116
get_item(Host, Node, ItemId) ->
2117
    Action = fun (#pubsub_node{type = Type, id = Nidx}) ->
×
2118
            node_call(Host, Type, get_item, [Nidx, ItemId])
×
2119
    end,
2120
    case transaction(Host, Node, Action, sync_dirty) of
×
2121
        {result, {_, Items}} -> Items;
×
2122
        Error -> Error
×
2123
    end.
2124

2125
-spec get_allowed_items_call(host(), nodeIdx(), jid(),
2126
                             binary(), nodeOptions(), [ljid()]) -> {result, [#pubsub_item{}]} |
2127
                                                                   {error, stanza_error()}.
2128
get_allowed_items_call(Host, Nidx, From, Type, Options, Owners) ->
2129
    case get_allowed_items_call(Host, Nidx, From, Type, Options, Owners, undefined) of
×
2130
        {result, {Items, _RSM}} -> {result, Items};
×
2131
        Error -> Error
×
2132
    end.
2133

2134
-spec get_allowed_items_call(host(), nodeIdx(), jid(),
2135
                             binary(), nodeOptions(), [ljid()],
2136
                             undefined | rsm_set()) ->
2137
                                    {result, {[#pubsub_item{}], undefined | rsm_set()}} |
2138
                                    {error, stanza_error()}.
2139
get_allowed_items_call(Host, Nidx, From, Type, Options, Owners, RSM) ->
2140
    AccessModel = get_option(Options, access_model),
×
2141
    AllowedGroups = get_option(Options, roster_groups_allowed, []),
×
2142
    {PS, RG} = get_presence_and_roster_permissions(Host, From, Owners, AccessModel, AllowedGroups),
×
2143
    node_call(Host, Type, get_items, [Nidx, From, AccessModel, PS, RG, undefined, RSM]).
×
2144

2145
-spec get_last_items(host(), binary(), nodeIdx(), ljid(), last | integer()) -> [#pubsub_item{}].
2146
get_last_items(Host, Type, Nidx, LJID, last) ->
2147
    % hack to handle section 6.1.7 of XEP-0060
UNCOV
2148
    get_last_items(Host, Type, Nidx, LJID, 1);
68✔
2149
get_last_items(Host, Type, Nidx, LJID, 1) ->
UNCOV
2150
    case get_cached_item(Host, Nidx) of
68✔
2151
        undefined ->
UNCOV
2152
            case node_action(Host, Type, get_last_items, [Nidx, LJID, 1]) of
68✔
UNCOV
2153
                {result, Items} -> Items;
68✔
2154
                _ -> []
×
2155
            end;
2156
        LastItem ->
2157
            [LastItem]
×
2158
    end;
2159
get_last_items(Host, Type, Nidx, LJID, Count) when Count > 1 ->
2160
    case node_action(Host, Type, get_last_items, [Nidx, LJID, Count]) of
×
2161
        {result, Items} -> Items;
×
2162
        _ -> []
×
2163
    end;
2164
get_last_items(_Host, _Type, _Nidx, _LJID, _Count) ->
2165
    [].
×
2166

2167
-spec get_only_item(host(), binary(), nodeIdx(), ljid()) -> [#pubsub_item{}].
2168
get_only_item(Host, Type, Nidx, LJID) ->
UNCOV
2169
    case get_cached_item(Host, Nidx) of
8✔
2170
        undefined ->
UNCOV
2171
            case node_action(Host, Type, get_only_item, [Nidx, LJID]) of
8✔
2172
                {result, Items} when length(Items) < 2 ->
UNCOV
2173
                    Items;
8✔
2174
                {result, Items} ->
2175
                    [hd(lists:keysort(#pubsub_item.modification, Items))];
×
2176
                _ -> []
×
2177
            end;
2178
        LastItem ->
2179
            [LastItem]
×
2180
    end.
2181

2182
%% @doc <p>Return the list of affiliations as an XMPP response.</p>
2183
-spec get_affiliations(host(), binary(), jid(), [binary()]) ->
2184
                              {result, pubsub()} | {error, stanza_error()}.
2185
get_affiliations(Host, Node, JID, Plugins) when is_list(Plugins) ->
UNCOV
2186
    Result =
8✔
2187
        lists:foldl(
2188
          fun(Type, {Status, Acc}) ->
UNCOV
2189
                  Features = plugin_features(Host, Type),
16✔
UNCOV
2190
                  RetrieveFeature = lists:member(<<"retrieve-affiliations">>, Features),
16✔
UNCOV
2191
                  if not RetrieveFeature ->
16✔
2192
                          {{error, extended_error(xmpp:err_feature_not_implemented(),
×
2193
                                                  err_unsupported('retrieve-affiliations'))},
2194
                           Acc};
2195
                     true ->
UNCOV
2196
                          case node_action(Host, Type,
16✔
2197
                                           get_entity_affiliations,
2198
                                           [Host, JID]) of
2199
                              {result, Affs} ->
UNCOV
2200
                                  {Status, [Affs | Acc]};
16✔
2201
                              {error, _} = Err ->
2202
                                  {Err, Acc}
×
2203
                          end
2204
                  end
2205
          end, {ok, []}, Plugins),
UNCOV
2206
    case Result of
8✔
2207
        {ok, Affs} ->
UNCOV
2208
            Entities = lists:flatmap(
8✔
2209
                         fun({_, none}) ->
2210
                                 [];
×
2211
                            ({#pubsub_node{nodeid = {_, NodeId}}, Aff}) ->
UNCOV
2212
                                 if (Node == <<>>) or (Node == NodeId) ->
40✔
UNCOV
2213
                                         [#ps_affiliation{node = NodeId,
40✔
2214
                                                          type = Aff}];
2215
                                    true ->
2216
                                         []
×
2217
                                 end;
2218
                            (_) ->
2219
                                 []
×
2220
                         end, lists:usort(lists:flatten(Affs))),
UNCOV
2221
            {result, #pubsub{affiliations = {<<>>, Entities}}};
8✔
2222
        {Error, _} ->
2223
            Error
×
2224
    end.
2225

2226
-spec get_affiliations(host(), binary(), jid()) ->
2227
                              {result, pubsub_owner()} | {error, stanza_error()}.
2228
get_affiliations(Host, Node, JID) ->
UNCOV
2229
    Action =
96✔
2230
        fun(#pubsub_node{type = Type, id = Nidx}) ->
UNCOV
2231
                Features = plugin_features(Host, Type),
96✔
UNCOV
2232
                RetrieveFeature = lists:member(<<"modify-affiliations">>, Features),
96✔
UNCOV
2233
                {result, Affiliation} = node_call(Host, Type, get_affiliation, [Nidx, JID]),
96✔
UNCOV
2234
                if not RetrieveFeature ->
96✔
2235
                        {error, extended_error(xmpp:err_feature_not_implemented(),
×
2236
                                               err_unsupported('modify-affiliations'))};
2237
                   Affiliation /= owner ->
UNCOV
2238
                        {error, xmpp:err_forbidden(?T("Owner privileges required"), ejabberd_option:language())};
40✔
2239
                   true ->
UNCOV
2240
                        node_call(Host, Type, get_node_affiliations, [Nidx])
56✔
2241
                end
2242
        end,
UNCOV
2243
    case transaction(Host, Node, Action, sync_dirty) of
96✔
2244
        {result, {_, []}} ->
2245
            {error, xmpp:err_item_not_found()};
×
2246
        {result, {_, Affs}} ->
UNCOV
2247
            Entities = lists:flatmap(
56✔
2248
                         fun({_, none}) ->
2249
                                 [];
×
2250
                            ({AJID, Aff}) ->
UNCOV
2251
                                 [#ps_affiliation{jid = AJID, type = Aff}]
104✔
2252
                         end, Affs),
UNCOV
2253
            {result, #pubsub_owner{affiliations = {Node, Entities}}};
56✔
2254
        Error ->
UNCOV
2255
            Error
40✔
2256
    end.
2257

2258
-spec set_affiliations(host(), binary(), jid(), [ps_affiliation()]) ->
2259
                              {result, undefined} | {error, stanza_error()}.
2260
set_affiliations(Host, Node, From, Affs) ->
UNCOV
2261
    Owner = jid:tolower(jid:remove_resource(From)),
96✔
UNCOV
2262
    Action =
96✔
2263
        fun(#pubsub_node{type = Type, id = Nidx, owners = O, options = Options} = N) ->
UNCOV
2264
                Owners = node_owners_call(Host, Type, Nidx, O),
96✔
UNCOV
2265
                case lists:member(Owner, Owners) of
96✔
2266
                    true ->
UNCOV
2267
                        AccessModel = get_option(Options, access_model),
56✔
UNCOV
2268
                        OwnerJID = jid:make(Owner),
56✔
UNCOV
2269
                        FilteredAffs =
56✔
2270
                            case Owners of
2271
                                [Owner] ->
UNCOV
2272
                                    [Aff || Aff <- Affs,
48✔
UNCOV
2273
                                            Aff#ps_affiliation.jid /= OwnerJID];
48✔
2274
                                _ ->
UNCOV
2275
                                    Affs
8✔
2276
                            end,
UNCOV
2277
                        lists:foreach(
56✔
2278
                          fun(#ps_affiliation{jid = JID, type = Affiliation}) ->
UNCOV
2279
                                  node_call(Host, Type, set_affiliation, [Nidx, JID, Affiliation]),
64✔
UNCOV
2280
                                  case Affiliation of
64✔
2281
                                      owner ->
UNCOV
2282
                                          NewOwner = jid:tolower(jid:remove_resource(JID)),
16✔
UNCOV
2283
                                          NewOwners = [NewOwner | Owners],
16✔
UNCOV
2284
                                          tree_call(Host,
16✔
2285
                                                    set_node,
2286
                                                    [N#pubsub_node{owners = NewOwners}]);
2287
                                      none ->
UNCOV
2288
                                          OldOwner = jid:tolower(jid:remove_resource(JID)),
8✔
UNCOV
2289
                                          case lists:member(OldOwner, Owners) of
8✔
2290
                                              true ->
2291
                                                  NewOwners = Owners -- [OldOwner],
×
2292
                                                  tree_call(Host,
×
2293
                                                            set_node,
2294
                                                            [N#pubsub_node{owners = NewOwners}]);
2295
                                              _ ->
UNCOV
2296
                                                  ok
8✔
2297
                                          end;
2298
                                      _ ->
UNCOV
2299
                                          ok
40✔
2300
                                  end,
UNCOV
2301
                                  case AccessModel of
64✔
2302
                                      whitelist when Affiliation /= owner,
2303
                                                     Affiliation /= publisher,
2304
                                                     Affiliation /= member ->
2305
                                          node_action(Host, Type,
×
2306
                                                      unsubscribe_node,
2307
                                                      [Nidx, OwnerJID, JID,
2308
                                                       all]);
2309
                                      _ ->
UNCOV
2310
                                          ok
64✔
2311
                                  end
2312
                          end, FilteredAffs),
UNCOV
2313
                        {result, undefined};
56✔
2314
                    _ ->
UNCOV
2315
                        {error, xmpp:err_forbidden(
40✔
2316
                                  ?T("Owner privileges required"), ejabberd_option:language())}
2317
                end
2318
        end,
UNCOV
2319
    case transaction(Host, Node, Action, sync_dirty) of
96✔
UNCOV
2320
        {result, {_, Result}} -> {result, Result};
56✔
UNCOV
2321
        Other -> Other
40✔
2322
    end.
2323

2324
-spec get_options(binary(), binary(), jid(), binary(), binary()) ->
2325
                         {result, xdata()} | {error, stanza_error()}.
2326
get_options(Host, Node, JID, SubId, Lang) ->
2327
    Action = fun (#pubsub_node{type = Type, id = Nidx}) ->
×
2328
            case lists:member(<<"subscription-options">>, plugin_features(Host, Type)) of
×
2329
                true ->
2330
                    get_options_helper(Host, JID, Lang, Node, Nidx, SubId, Type);
×
2331
                false ->
2332
                    {error, extended_error(xmpp:err_feature_not_implemented(),
×
2333
                                           err_unsupported('subscription-options'))}
2334
            end
2335
    end,
2336
    case transaction(Host, Node, Action, sync_dirty) of
×
2337
        {result, {_Node, XForm}} -> {result, XForm};
×
2338
        Error -> Error
×
2339
    end.
2340

2341
-spec get_options_helper(binary(), jid(), binary(), binary(), _, binary(),
2342
                         binary()) -> {result, pubsub()} | {error, stanza_error()}.
2343
get_options_helper(Host, JID, Lang, Node, Nidx, SubId, Type) ->
2344
    Subscriber = jid:tolower(JID),
×
2345
    case node_call(Host, Type, get_subscriptions, [Nidx, Subscriber]) of
×
2346
        {result, Subs} ->
2347
            SubIds = [Id || {Sub, Id} <- Subs, Sub == subscribed],
×
2348
            case {SubId, SubIds} of
×
2349
                {_, []} ->
2350
                    {error, extended_error(xmpp:err_not_acceptable(),
×
2351
                                           err_not_subscribed())};
2352
                {<<>>, [SID]} ->
2353
                    read_sub(Host, Node, Nidx, Subscriber, SID, Lang);
×
2354
                {<<>>, _} ->
2355
                    {error, extended_error(xmpp:err_not_acceptable(),
×
2356
                                           err_subid_required())};
2357
                {_, _} ->
2358
                    ValidSubId = lists:member(SubId, SubIds),
×
2359
                    if ValidSubId ->
×
2360
                            read_sub(Host, Node, Nidx, Subscriber, SubId, Lang);
×
2361
                       true ->
2362
                            {error, extended_error(xmpp:err_not_acceptable(),
×
2363
                                                   err_invalid_subid())}
2364
                    end
2365
            end;
2366
        {error, _} = Error ->
2367
            Error
×
2368
    end.
2369

2370
-spec read_sub(binary(), binary(), nodeIdx(), ljid(), binary(), binary()) -> {result, pubsub()}.
2371
read_sub(Host, Node, Nidx, Subscriber, SubId, Lang) ->
2372
    SubModule = subscription_plugin(Host),
×
2373
    XData = case SubModule:get_subscription(Subscriber, Nidx, SubId) of
×
2374
                {error, notfound} ->
2375
                    undefined;
×
2376
                {result, #pubsub_subscription{options = Options}} ->
2377
                    {result, X} = SubModule:get_options_xform(Lang, Options),
×
2378
                    X
×
2379
            end,
2380
    {result, #pubsub{options = #ps_options{jid = jid:make(Subscriber),
×
2381
                                           subid = SubId,
2382
                                           node = Node,
2383
                                           xdata = XData}}}.
2384

2385
-spec set_options(binary(), binary(), jid(), binary(),
2386
                  [{binary(), [binary()]}]) ->
2387
                         {result, undefined} | {error, stanza_error()}.
2388
set_options(Host, Node, JID, SubId, Configuration) ->
2389
    Action = fun (#pubsub_node{type = Type, id = Nidx}) ->
×
2390
            case lists:member(<<"subscription-options">>, plugin_features(Host, Type)) of
×
2391
                true ->
2392
                    set_options_helper(Host, Configuration, JID, Nidx, SubId, Type);
×
2393
                false ->
2394
                    {error, extended_error(xmpp:err_feature_not_implemented(),
×
2395
                                           err_unsupported('subscription-options'))}
2396
            end
2397
    end,
2398
    case transaction(Host, Node, Action, sync_dirty) of
×
2399
        {result, {_Node, Result}} -> {result, Result};
×
2400
        Error -> Error
×
2401
    end.
2402

2403
-spec set_options_helper(binary(), [{binary(), [binary()]}], jid(),
2404
                         nodeIdx(), binary(), binary()) ->
2405
                                {result, undefined} | {error, stanza_error()}.
2406
set_options_helper(Host, Configuration, JID, Nidx, SubId, Type) ->
2407
    SubModule = subscription_plugin(Host),
×
2408
    SubOpts = case SubModule:parse_options_xform(Configuration) of
×
2409
        {result, GoodSubOpts} -> GoodSubOpts;
×
2410
        _ -> invalid
×
2411
    end,
2412
    Subscriber = jid:tolower(JID),
×
2413
    case node_call(Host, Type, get_subscriptions, [Nidx, Subscriber]) of
×
2414
        {result, Subs} ->
2415
            SubIds = [Id || {Sub, Id} <- Subs, Sub == subscribed],
×
2416
            case {SubId, SubIds} of
×
2417
                {_, []} ->
2418
                    {error, extended_error(xmpp:err_not_acceptable(), err_not_subscribed())};
×
2419
                {<<>>, [SID]} ->
2420
                    write_sub(Host, Nidx, Subscriber, SID, SubOpts);
×
2421
                {<<>>, _} ->
2422
                    {error, extended_error(xmpp:err_not_acceptable(), err_subid_required())};
×
2423
                {_, _} ->
2424
                    write_sub(Host, Nidx, Subscriber, SubId, SubOpts)
×
2425
            end;
2426
        {error, _} = Err ->
2427
            Err
×
2428
    end.
2429

2430
-spec write_sub(binary(), nodeIdx(), ljid(), binary(), _) -> {result, undefined} |
2431
                                                             {error, stanza_error()}.
2432
write_sub(_Host, _Nidx, _Subscriber, _SubId, invalid) ->
2433
    {error, extended_error(xmpp:err_bad_request(), err_invalid_options())};
×
2434
write_sub(_Host, _Nidx, _Subscriber, _SubId, []) ->
2435
    {result, undefined};
×
2436
write_sub(Host, Nidx, Subscriber, SubId, Options) ->
2437
    SubModule = subscription_plugin(Host),
×
2438
    case SubModule:set_subscription(Subscriber, Nidx, SubId, Options) of
×
2439
        {result, _} -> {result, undefined};
×
2440
        {error, _} -> {error, extended_error(xmpp:err_not_acceptable(),
×
2441
                                             err_invalid_subid())}
2442
    end.
2443

2444
%% @doc <p>Return the list of subscriptions as an XMPP response.</p>
2445
-spec get_subscriptions(host(), binary(), jid(), [binary()]) ->
2446
                               {result, pubsub()} | {error, stanza_error()}.
2447
get_subscriptions(Host, Node, JID, Plugins) when is_list(Plugins) ->
UNCOV
2448
    Result = lists:foldl(fun (Type, {Status, Acc}) ->
40✔
UNCOV
2449
                    Features = plugin_features(Host, Type),
80✔
UNCOV
2450
                    RetrieveFeature = lists:member(<<"retrieve-subscriptions">>, Features),
80✔
UNCOV
2451
                    if not RetrieveFeature ->
80✔
2452
                            {{error, extended_error(xmpp:err_feature_not_implemented(),
×
2453
                                                    err_unsupported('retrieve-subscriptions'))},
2454
                                Acc};
2455
                        true ->
UNCOV
2456
                            Subscriber = jid:remove_resource(JID),
80✔
UNCOV
2457
                            case node_action(Host, Type,
80✔
2458
                                             get_entity_subscriptions,
2459
                                             [Host, Subscriber]) of
2460
                                {result, Subs} ->
UNCOV
2461
                                    {Status, [Subs | Acc]};
80✔
2462
                                {error, _} = Err ->
2463
                                    {Err, Acc}
×
2464
                            end
2465
                    end
2466
            end, {ok, []}, Plugins),
UNCOV
2467
    case Result of
40✔
2468
        {ok, Subs} ->
UNCOV
2469
            Entities = lists:flatmap(fun
40✔
2470
                        ({#pubsub_node{nodeid = {_, SubsNode}}, Sub}) ->
2471
                            case Node of
×
2472
                                <<>> ->
2473
                                    [#ps_subscription{jid = jid:remove_resource(JID),
×
2474
                                                      node = SubsNode, type = Sub}];
2475
                                SubsNode ->
2476
                                    [#ps_subscription{jid = jid:remove_resource(JID),
×
2477
                                                      type = Sub}];
2478
                                _ ->
2479
                                    []
×
2480
                            end;
2481
                        ({#pubsub_node{nodeid = {_, SubsNode}}, Sub, SubId, SubJID}) ->
UNCOV
2482
                            case Node of
64✔
2483
                                <<>> ->
UNCOV
2484
                                    [#ps_subscription{jid = SubJID,
64✔
2485
                                                      subid = SubId,
2486
                                                      type = Sub,
2487
                                                      node = SubsNode}];
2488
                                SubsNode ->
2489
                                    [#ps_subscription{jid = SubJID,
×
2490
                                                      subid = SubId,
2491
                                                      type = Sub}];
2492
                                _ ->
2493
                                    []
×
2494
                            end;
2495
                        ({#pubsub_node{nodeid = {_, SubsNode}}, Sub, SubJID}) ->
2496
                            case Node of
×
2497
                                <<>> ->
2498
                                    [#ps_subscription{jid = SubJID,
×
2499
                                                      type = Sub,
2500
                                                      node = SubsNode}];
2501
                                SubsNode ->
2502
                                    [#ps_subscription{jid = SubJID, type = Sub}];
×
2503
                                _ ->
2504
                                    []
×
2505
                            end
2506
                    end,
2507
                    lists:usort(lists:flatten(Subs))),
UNCOV
2508
            {result, #pubsub{subscriptions = {<<>>, Entities}}};
40✔
2509
        {Error, _} ->
2510
            Error
×
2511
    end.
2512

2513
-spec get_subscriptions(host(), binary(), jid()) -> {result, pubsub_owner()} |
2514
                                                    {error, stanza_error()}.
2515
get_subscriptions(Host, Node, JID) ->
UNCOV
2516
    Action = fun(#pubsub_node{type = Type, id = Nidx}) ->
96✔
UNCOV
2517
                     Features = plugin_features(Host, Type),
96✔
UNCOV
2518
                     RetrieveFeature = lists:member(<<"manage-subscriptions">>, Features),
96✔
UNCOV
2519
                     case node_call(Host, Type, get_affiliation, [Nidx, JID]) of
96✔
2520
                         {result, Affiliation} ->
UNCOV
2521
                             if not RetrieveFeature ->
96✔
2522
                                     {error, extended_error(xmpp:err_feature_not_implemented(),
×
2523
                                                            err_unsupported('manage-subscriptions'))};
2524
                                Affiliation /= owner ->
UNCOV
2525
                                     Lang = ejabberd_option:language(),
40✔
UNCOV
2526
                                     {error, xmpp:err_forbidden(?T("Owner privileges required"), Lang)};
40✔
2527
                                true ->
UNCOV
2528
                                     node_call(Host, Type, get_node_subscriptions, [Nidx])
56✔
2529
                             end;
2530
                         Error ->
2531
                             Error
×
2532
                     end
2533
             end,
UNCOV
2534
    case transaction(Host, Node, Action, sync_dirty) of
96✔
2535
        {result, {_, Subs}} ->
UNCOV
2536
            Entities =
56✔
2537
                lists:flatmap(
2538
                  fun({_, none}) ->
2539
                          [];
×
2540
                     ({_, pending, _}) ->
UNCOV
2541
                          [];
8✔
2542
                     ({AJID, Sub}) ->
2543
                          [#ps_subscription{jid = AJID, type = Sub}];
×
2544
                        ({AJID, Sub, SubId}) ->
UNCOV
2545
                          [#ps_subscription{jid = AJID, type = Sub, subid = SubId}]
16✔
2546
                  end, Subs),
UNCOV
2547
            {result, #pubsub_owner{subscriptions = {Node, Entities}}};
56✔
2548
        Error ->
UNCOV
2549
            Error
40✔
2550
    end.
2551

2552
-spec get_subscriptions_for_send_last(host(), binary(), atom(), jid(), ljid(), ljid()) ->
2553
                                      [{#pubsub_node{}, subId(), ljid()}].
2554
get_subscriptions_for_send_last(Host, PType, sql, JID, LJID, BJID) ->
2555
    case node_action(Host, PType,
×
2556
                     get_entity_subscriptions_for_send_last,
2557
                     [Host, JID]) of
2558
        {result, Subs} ->
2559
            [{Node, SubId, SubJID}
×
2560
             || {Node, Sub, SubId, SubJID} <- Subs,
×
2561
                Sub =:= subscribed, (SubJID == LJID) or (SubJID == BJID)];
×
2562
        _ ->
2563
            []
×
2564
    end;
2565
%% sql version already filter result by on_sub_and_presence
2566
get_subscriptions_for_send_last(Host, PType, _, JID, LJID, BJID) ->
UNCOV
2567
    case node_action(Host, PType,
464✔
2568
                     get_entity_subscriptions,
2569
                     [Host, JID]) of
2570
        {result, Subs} ->
UNCOV
2571
            [{Node, SubId, SubJID}
464✔
UNCOV
2572
             || {Node, Sub, SubId, SubJID} <- Subs,
464✔
2573
                Sub =:= subscribed, (SubJID == LJID) or (SubJID == BJID),
×
2574
                match_option(Node, send_last_published_item, on_sub_and_presence)];
×
2575
        _ ->
2576
            []
×
2577
    end.
2578

2579
-spec set_subscriptions(host(), binary(), jid(), [ps_subscription()]) ->
2580
                               {result, undefined} | {error, stanza_error()}.
2581
set_subscriptions(Host, Node, From, Entities) ->
UNCOV
2582
    Owner = jid:tolower(jid:remove_resource(From)),
80✔
UNCOV
2583
    Notify = fun(#ps_subscription{jid = JID, type = Sub}) ->
80✔
UNCOV
2584
                     Stanza = #message{
32✔
2585
                                 from = service_jid(Host),
2586
                                 to = JID,
2587
                                 sub_els = [#ps_event{
2588
                                               subscription = #ps_subscription{
2589
                                                                 jid = JID,
2590
                                                                 type = Sub,
2591
                                                                 node = Node}}]},
UNCOV
2592
                     ejabberd_router:route(Stanza)
32✔
2593
             end,
UNCOV
2594
    Action =
80✔
2595
        fun(#pubsub_node{type = Type, id = Nidx, owners = O}) ->
UNCOV
2596
                Owners = node_owners_call(Host, Type, Nidx, O),
80✔
UNCOV
2597
                case lists:member(Owner, Owners) of
80✔
2598
                    true ->
UNCOV
2599
                        Result =
40✔
2600
                            lists:foldl(
2601
                              fun(_, {error, _} = Err) ->
2602
                                      Err;
×
2603
                                 (#ps_subscription{jid = JID, type = Sub,
2604
                                                   subid = SubId} = Entity, _) ->
UNCOV
2605
                                      case node_call(Host, Type,
32✔
2606
                                                     set_subscriptions,
2607
                                                     [Nidx, JID, Sub, SubId]) of
2608
                                          {error, _} = Err ->
2609
                                              Err;
×
2610
                                          _ ->
UNCOV
2611
                                              Notify(Entity)
32✔
2612
                                      end
2613
                              end, ok, Entities),
UNCOV
2614
                        case Result of
40✔
UNCOV
2615
                            ok -> {result, undefined};
40✔
2616
                            {error, _} = Err -> Err
×
2617
                        end;
2618
                    _ ->
UNCOV
2619
                        {error, xmpp:err_forbidden(
40✔
2620
                                  ?T("Owner privileges required"), ejabberd_option:language())}
2621

2622
                end
2623
        end,
UNCOV
2624
    case transaction(Host, Node, Action, sync_dirty) of
80✔
UNCOV
2625
        {result, {_, Result}} -> {result, Result};
40✔
UNCOV
2626
        Other -> Other
40✔
2627
    end.
2628

2629
-spec get_presence_and_roster_permissions(
2630
        host(), jid(), [ljid()], accessModel(),
2631
        [binary()]) -> {boolean(), boolean()}.
2632
get_presence_and_roster_permissions(Host, From, Owners, AccessModel, AllowedGroups) ->
UNCOV
2633
    if (AccessModel == presence) or (AccessModel == roster) ->
240✔
2634
            case Host of
×
2635
                {User, Server, _} ->
2636
                    get_roster_info(User, Server, From, AllowedGroups);
×
2637
                _ ->
2638
                    [{OUser, OServer, _} | _] = Owners,
×
2639
                    get_roster_info(OUser, OServer, From, AllowedGroups)
×
2640
            end;
2641
        true ->
UNCOV
2642
            {true, true}
240✔
2643
    end.
2644

2645
-spec get_roster_info(binary(), binary(), ljid() | jid(), [binary()]) -> {boolean(), boolean()}.
2646
get_roster_info(_, _, {<<>>, <<>>, _}, _) ->
2647
    {false, false};
×
2648
get_roster_info(OwnerUser, OwnerServer, {SubscriberUser, SubscriberServer, _}, AllowedGroups) ->
2649
    LJID = {SubscriberUser, SubscriberServer, <<>>},
×
2650
    {Subscription, _Ask, Groups} = ejabberd_hooks:run_fold(roster_get_jid_info,
×
2651
            OwnerServer, {none, none, []},
2652
            [OwnerUser, OwnerServer, LJID]),
2653
    PresenceSubscription = Subscription == both orelse
×
2654
        Subscription == from orelse
×
2655
        {OwnerUser, OwnerServer} == {SubscriberUser, SubscriberServer},
×
2656
    RosterGroup = lists:any(fun (Group) ->
×
2657
                    lists:member(Group, AllowedGroups)
×
2658
            end,
2659
            Groups),
2660
    {PresenceSubscription, RosterGroup};
×
2661
get_roster_info(OwnerUser, OwnerServer, JID, AllowedGroups) ->
2662
    get_roster_info(OwnerUser, OwnerServer, jid:tolower(JID), AllowedGroups).
×
2663

2664
-spec preconditions_met(pubsub_publish_options:result(),
2665
                        pubsub_node_config:result()) -> boolean().
2666
preconditions_met(PubOpts, NodeOpts) ->
UNCOV
2667
    lists:all(fun(Opt) -> lists:member(Opt, NodeOpts) end, PubOpts).
272✔
2668

2669
-spec service_jid(jid() | ljid() | binary()) -> jid().
2670
service_jid(#jid{} = Jid) -> Jid;
×
UNCOV
2671
service_jid({U, S, R}) -> jid:make(U, S, R);
152✔
UNCOV
2672
service_jid(Host) -> jid:make(Host).
1,362✔
2673

2674
%% @doc <p>Check if a notification must be delivered or not based on
2675
%% node and subscription options.</p>
2676
-spec is_to_deliver(ljid(), items | nodes, integer(), nodeOptions(), subOptions()) -> boolean().
2677
is_to_deliver(LJID, NotifyType, Depth, NodeOptions, SubOptions) ->
UNCOV
2678
    sub_to_deliver(LJID, NotifyType, Depth, SubOptions)
20✔
UNCOV
2679
    andalso node_to_deliver(LJID, NodeOptions).
20✔
2680

2681
-spec sub_to_deliver(ljid(), items | nodes, integer(), subOptions()) -> boolean().
2682
sub_to_deliver(_LJID, NotifyType, Depth, SubOptions) ->
UNCOV
2683
    lists:all(fun (Option) ->
20✔
2684
                sub_option_can_deliver(NotifyType, Depth, Option)
×
2685
        end,
2686
        SubOptions).
2687

2688
-spec node_to_deliver(ljid(), nodeOptions()) -> boolean().
2689
node_to_deliver(LJID, NodeOptions) ->
UNCOV
2690
    presence_can_deliver(LJID, get_option(NodeOptions, presence_based_delivery)).
20✔
2691

2692
-spec sub_option_can_deliver(items | nodes, integer(), _) -> boolean().
2693
sub_option_can_deliver(items, _, {subscription_type, nodes}) -> false;
×
2694
sub_option_can_deliver(nodes, _, {subscription_type, items}) -> false;
×
2695
sub_option_can_deliver(_, _, {subscription_depth, all}) -> true;
×
2696
sub_option_can_deliver(_, Depth, {subscription_depth, D}) -> Depth =< D;
×
2697
sub_option_can_deliver(_, _, {deliver, false}) -> false;
×
2698
sub_option_can_deliver(_, _, {expire, When}) -> erlang:timestamp() < When;
×
2699
sub_option_can_deliver(_, _, _) -> true.
×
2700

2701
-spec presence_can_deliver(ljid(), boolean()) -> boolean().
2702
presence_can_deliver(_, false) ->
UNCOV
2703
    true;
20✔
2704
presence_can_deliver({User, Server, Resource}, true) ->
2705
    case ejabberd_router:is_my_route(Server) of
×
2706
        % We don't know presence of users on remote server, so always deliver
2707
        false -> true;
×
2708
        _ ->
2709
            case ejabberd_sm:get_user_present_resources(User, Server) of
×
2710
                [] ->
2711
                    false;
×
2712
                Ss ->
2713
                    lists:foldl(fun
×
2714
                                    (_, true) ->
2715
                                        true;
×
2716
                                    ({_, R}, _Acc) ->
2717
                                        case Resource of
×
2718
                                            <<>> -> true;
×
2719
                                            R -> true;
×
2720
                                            _ -> false
×
2721
                                        end
2722
                                end,
2723
                                false, Ss)
2724
            end
2725
    end.
2726

2727
-spec state_can_deliver(ljid(), subOptions()) -> [ljid()].
UNCOV
2728
state_can_deliver({U, S, R}, []) -> [{U, S, R}];
20✔
2729
state_can_deliver({U, S, R}, SubOptions) ->
2730
    case lists:keysearch(show_values, 1, SubOptions) of
×
2731
        %% If not in suboptions, item can be delivered, case doesn't apply
2732
        false -> [{U, S, R}];
×
2733
        %% If in a suboptions ...
2734
        {_, {_, ShowValues}} ->
2735
            Resources = case R of
×
2736
                %% If the subscriber JID is a bare one, get all its resources
2737
                <<>> -> user_resources(U, S);
×
2738
                %% If the subscriber JID is a full one, use its resource
2739
                R -> [R]
×
2740
            end,
2741
            lists:foldl(fun (Resource, Acc) ->
×
2742
                        get_resource_state({U, S, Resource}, ShowValues, Acc)
×
2743
                end,
2744
                [], Resources)
2745
    end.
2746

2747
-spec get_resource_state(ljid(), [binary()], [ljid()]) -> [ljid()].
2748
get_resource_state({U, S, R}, ShowValues, JIDs) ->
2749
    case ejabberd_sm:get_session_pid(U, S, R) of
×
2750
        none ->
2751
            %% If no PID, item can be delivered
2752
            lists:append([{U, S, R}], JIDs);
×
2753
        Pid ->
2754
            Show = case ejabberd_c2s:get_presence(Pid) of
×
2755
                       #presence{type = unavailable} -> <<"unavailable">>;
×
2756
                       #presence{show = undefined} -> <<"online">>;
×
2757
                       #presence{show = Sh} -> atom_to_binary(Sh, latin1)
×
2758
            end,
2759
            case lists:member(Show, ShowValues) of
×
2760
                %% If yes, item can be delivered
2761
                true -> lists:append([{U, S, R}], JIDs);
×
2762
                %% If no, item can't be delivered
2763
                false -> JIDs
×
2764
            end
2765
    end.
2766

2767
-spec payload_xmlelements([xmlel()]) -> non_neg_integer().
2768
payload_xmlelements(Payload) ->
UNCOV
2769
    payload_xmlelements(Payload, 0).
272✔
2770

2771
-spec payload_xmlelements([xmlel()], non_neg_integer()) -> non_neg_integer().
UNCOV
2772
payload_xmlelements([], Count) -> Count;
272✔
2773
payload_xmlelements([#xmlel{} | Tail], Count) ->
UNCOV
2774
    payload_xmlelements(Tail, Count + 1);
272✔
2775
payload_xmlelements([_ | Tail], Count) ->
2776
    payload_xmlelements(Tail, Count).
×
2777

2778
-spec items_els(binary(), nodeOptions(), [#pubsub_item{}]) -> ps_items().
2779
items_els(Node, Options, Items) ->
UNCOV
2780
    Els = case get_option(Options, itemreply) of
88✔
2781
        publisher ->
2782
            [#ps_item{id = ItemId, sub_els = Payload, publisher = jid:encode(USR)}
×
2783
             || #pubsub_item{itemid = {ItemId, _}, payload = Payload, modification = {_, USR}}
2784
                <- Items];
×
2785
        _ ->
UNCOV
2786
            [#ps_item{id = ItemId, sub_els = Payload}
88✔
2787
             || #pubsub_item{itemid = {ItemId, _}, payload = Payload}
UNCOV
2788
                <- Items]
88✔
2789
    end,
UNCOV
2790
    #ps_items{node = Node, items = Els}.
88✔
2791

2792
%%%%%% broadcast functions
2793

2794
-spec broadcast_publish_item(host(), binary(), nodeIdx(), binary(),
2795
                             nodeOptions(), binary(), jid(), [xmlel()], _) ->
2796
                                    {result, boolean()}.
2797
broadcast_publish_item(Host, Node, Nidx, Type, NodeOptions, ItemId, From, Payload, Removed) ->
UNCOV
2798
    case get_collection_subscriptions(Host, Node) of
248✔
2799
        {result, SubsByDepth} ->
UNCOV
2800
            ItemPublisher = case get_option(NodeOptions, itemreply) of
248✔
2801
                                publisher -> jid:encode(From);
×
UNCOV
2802
                                _ -> <<>>
248✔
2803
                            end,
UNCOV
2804
            ItemPayload = case get_option(NodeOptions, deliver_payloads) of
248✔
UNCOV
2805
                              true -> Payload;
248✔
2806
                              false -> []
×
2807
                          end,
UNCOV
2808
            ItemsEls = #ps_items{node = Node,
248✔
2809
                                 items = [#ps_item{id = ItemId,
2810
                                                   publisher = ItemPublisher,
2811
                                                   sub_els = ItemPayload}]},
UNCOV
2812
            Stanza = #message{ sub_els = [#ps_event{items = ItemsEls}]},
248✔
UNCOV
2813
            broadcast_stanza(Host, From, Node, Nidx, Type,
248✔
2814
                             NodeOptions, SubsByDepth, items, Stanza, true),
UNCOV
2815
            case Removed of
248✔
2816
                [] ->
UNCOV
2817
                    ok;
248✔
2818
                _ ->
2819
                    case get_option(NodeOptions, notify_retract) of
×
2820
                        true ->
2821
                            RetractStanza = #message{
×
2822
                                               sub_els =
2823
                                                   [#ps_event{
2824
                                                       items = #ps_items{
2825
                                                                  node = Node,
2826
                                                                  retract = Removed}}]},
2827
                            broadcast_stanza(Host, Node, Nidx, Type,
×
2828
                                NodeOptions, SubsByDepth,
2829
                                items, RetractStanza, true);
2830
                        _ ->
2831
                            ok
×
2832
                    end
2833
            end,
UNCOV
2834
            {result, true};
248✔
2835
        _ ->
2836
            {result, false}
×
2837
    end.
2838

2839
-spec broadcast_retract_items(host(), jid(), binary(), nodeIdx(), binary(),
2840
                              nodeOptions(), [itemId()]) -> {result, boolean()}.
2841
broadcast_retract_items(Host, Publisher, Node, Nidx, Type, NodeOptions, ItemIds) ->
2842
    broadcast_retract_items(Host, Publisher, Node, Nidx, Type, NodeOptions, ItemIds, false).
×
2843

2844
-spec broadcast_retract_items(host(), jid(), binary(), nodeIdx(), binary(),
2845
                              nodeOptions(), [itemId()], boolean()) -> {result, boolean()}.
2846
broadcast_retract_items(_Host, _Publisher, _Node, _Nidx, _Type, _NodeOptions, [], _ForceNotify) ->
2847
    {result, false};
×
2848
broadcast_retract_items(Host, Publisher, Node, Nidx, Type, NodeOptions, ItemIds, ForceNotify) ->
UNCOV
2849
    case (get_option(NodeOptions, notify_retract) or ForceNotify) of
34✔
2850
        true ->
UNCOV
2851
            case get_collection_subscriptions(Host, Node) of
34✔
2852
                {result, SubsByDepth} ->
UNCOV
2853
                    Stanza = #message{
34✔
2854
                                sub_els =
2855
                                    [#ps_event{
2856
                                        items = #ps_items{
2857
                                                   node = Node,
2858
                                                   retract = ItemIds}}]},
UNCOV
2859
                    broadcast_stanza(Host, Publisher, Node, Nidx, Type,
34✔
2860
                        NodeOptions, SubsByDepth, items, Stanza, true),
UNCOV
2861
                    {result, true};
34✔
2862
                _ ->
2863
                    {result, false}
×
2864
            end;
2865
        _ ->
2866
            {result, false}
×
2867
    end.
2868

2869
-spec broadcast_purge_node(host(), binary(), nodeIdx(), binary(), nodeOptions()) -> {result, boolean()}.
2870
broadcast_purge_node(Host, Node, Nidx, Type, NodeOptions) ->
UNCOV
2871
    case get_option(NodeOptions, notify_retract) of
16✔
2872
        true ->
UNCOV
2873
            case get_collection_subscriptions(Host, Node) of
16✔
2874
                {result, SubsByDepth} ->
UNCOV
2875
                    Stanza = #message{sub_els = [#ps_event{purge = Node}]},
16✔
UNCOV
2876
                    broadcast_stanza(Host, Node, Nidx, Type,
16✔
2877
                        NodeOptions, SubsByDepth, nodes, Stanza, false),
UNCOV
2878
                    {result, true};
16✔
2879
                _ ->
2880
                    {result, false}
×
2881
            end;
2882
        _ ->
2883
            {result, false}
×
2884
    end.
2885

2886
-spec broadcast_removed_node(host(), binary(), nodeIdx(), binary(),
2887
                             nodeOptions(), subs_by_depth()) -> {result, boolean()}.
2888
broadcast_removed_node(Host, Node, Nidx, Type, NodeOptions, SubsByDepth) ->
UNCOV
2889
    case get_option(NodeOptions, notify_delete) of
260✔
2890
        true ->
UNCOV
2891
            case SubsByDepth of
8✔
2892
                [] ->
2893
                    {result, false};
×
2894
                _ ->
UNCOV
2895
                    Stanza = #message{sub_els = [#ps_event{delete = {Node, <<>>}}]},
8✔
UNCOV
2896
                    broadcast_stanza(Host, Node, Nidx, Type,
8✔
2897
                        NodeOptions, SubsByDepth, nodes, Stanza, false),
UNCOV
2898
                    {result, true}
8✔
2899
            end;
2900
        _ ->
UNCOV
2901
            {result, false}
252✔
2902
    end.
2903

2904
-spec broadcast_created_node(host(), binary(), nodeIdx(), binary(),
2905
                             nodeOptions(), subs_by_depth()) -> {result, boolean()}.
2906
broadcast_created_node(_, _, _, _, _, []) ->
2907
    {result, false};
×
2908
broadcast_created_node(Host, Node, Nidx, Type, NodeOptions, SubsByDepth) ->
UNCOV
2909
    Stanza = #message{sub_els = [#ps_event{create = Node}]},
260✔
UNCOV
2910
    broadcast_stanza(Host, Node, Nidx, Type, NodeOptions, SubsByDepth, nodes, Stanza, true),
260✔
UNCOV
2911
    {result, true}.
260✔
2912

2913
-spec broadcast_config_notification(host(), binary(), nodeIdx(), binary(),
2914
                                    nodeOptions(), binary()) -> {result, boolean()}.
2915
broadcast_config_notification(Host, Node, Nidx, Type, NodeOptions, Lang) ->
UNCOV
2916
    case get_option(NodeOptions, notify_config) of
16✔
2917
        true ->
2918
            case get_collection_subscriptions(Host, Node) of
×
2919
                {result, SubsByDepth} ->
2920
                    Content = case get_option(NodeOptions, deliver_payloads) of
×
2921
                        true ->
2922
                            #xdata{type = result,
×
2923
                                   fields = get_configure_xfields(
2924
                                              Type, NodeOptions, Lang, [])};
2925
                        false ->
2926
                            undefined
×
2927
                    end,
2928
                    Stanza = #message{
×
2929
                                sub_els = [#ps_event{
2930
                                              configuration = {Node, Content}}]},
2931
                    broadcast_stanza(Host, Node, Nidx, Type,
×
2932
                        NodeOptions, SubsByDepth, nodes, Stanza, false),
2933
                    {result, true};
×
2934
                _ ->
2935
                    {result, false}
×
2936
            end;
2937
        _ ->
UNCOV
2938
            {result, false}
16✔
2939
    end.
2940

2941
-spec get_collection_subscriptions(host(), nodeId()) -> {result, subs_by_depth()} |
2942
                                                        {error, stanza_error()}.
2943
get_collection_subscriptions(Host, Node) ->
UNCOV
2944
    Action = fun() -> get_node_subs_by_depth(Host, Node, service_jid(Host)) end,
298✔
UNCOV
2945
    transaction(Host, Action, sync_dirty).
298✔
2946

2947
-spec get_node_subs_by_depth(host(), nodeId(), jid()) -> {result, subs_by_depth()} |
2948
                                                         {error, stanza_error()}.
2949
get_node_subs_by_depth(Host, Node, From) ->
UNCOV
2950
    case tree_call(Host, get_parentnodes_tree, [Host, Node, From]) of
818✔
2951
        ParentTree when is_list(ParentTree) ->
UNCOV
2952
            {result,
818✔
2953
             lists:filtermap(
2954
               fun({Depth, Nodes}) ->
UNCOV
2955
                       case lists:filtermap(
818✔
2956
                              fun(N) ->
UNCOV
2957
                                      case get_node_subs(Host, N) of
818✔
UNCOV
2958
                                          {result, Result} -> {true, {N, Result}};
818✔
2959
                                          _ -> false
×
2960
                                      end
2961
                              end, Nodes) of
2962
                           [] -> false;
×
UNCOV
2963
                           Subs -> {true, {Depth, Subs}}
818✔
2964
                       end
2965
               end, ParentTree)};
2966
        Error ->
2967
            Error
×
2968
    end.
2969

2970
-spec get_node_subs(host(), #pubsub_node{}) -> {result, [{ljid(), subId(), subOptions()}]} |
2971
                                               {error, stanza_error()}.
2972
get_node_subs(Host, #pubsub_node{type = Type, id = Nidx}) ->
UNCOV
2973
    WithOptions =  lists:member(<<"subscription-options">>, plugin_features(Host, Type)),
818✔
UNCOV
2974
    case node_call(Host, Type, get_node_subscriptions, [Nidx]) of
818✔
UNCOV
2975
        {result, Subs} -> {result, get_options_for_subs(Host, Nidx, Subs, WithOptions)};
818✔
2976
        Other -> Other
×
2977
    end.
2978

2979
-spec get_options_for_subs(host(), nodeIdx(),
2980
                           [{ljid(), subscription(), subId()}],
2981
                           boolean()) ->
2982
                                  [{ljid(), subId(), subOptions()}].
2983
get_options_for_subs(_Host, _Nidx, Subs, false) ->
UNCOV
2984
    lists:foldl(fun({JID, subscribed, SubID}, Acc) ->
818✔
UNCOV
2985
                    [{JID, SubID, []} | Acc];
96✔
2986
                   (_, Acc) ->
UNCOV
2987
                    Acc
16✔
2988
        end, [], Subs);
2989
get_options_for_subs(Host, Nidx, Subs, true) ->
2990
    SubModule = subscription_plugin(Host),
×
2991
    lists:foldl(fun({JID, subscribed, SubID}, Acc) ->
×
2992
                case SubModule:get_subscription(JID, Nidx, SubID) of
×
2993
                    #pubsub_subscription{options = Options} -> [{JID, SubID, Options} | Acc];
×
2994
                    {error, notfound} -> [{JID, SubID, []} | Acc]
×
2995
                end;
2996
            (_, Acc) ->
2997
                Acc
×
2998
        end, [], Subs).
2999

3000
-spec broadcast_stanza(host(), nodeId(), nodeIdx(), binary(),
3001
                       nodeOptions(), subs_by_depth(),
3002
                       items | nodes, stanza(), boolean()) -> ok.
3003
broadcast_stanza(Host, _Node, _Nidx, _Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM) ->
UNCOV
3004
    NotificationType = get_option(NodeOptions, notification_type, headline),
566✔
UNCOV
3005
    BroadcastAll = get_option(NodeOptions, broadcast_all_resources), %% XXX this is not standard, but useful
566✔
UNCOV
3006
    Stanza = add_message_type(
566✔
3007
               xmpp:set_from(BaseStanza, service_jid(Host)),
3008
               NotificationType),
3009
    %% Handles explicit subscriptions
UNCOV
3010
    SubIDsByJID = subscribed_nodes_by_jid(NotifyType, SubsByDepth),
566✔
UNCOV
3011
    lists:foreach(fun ({LJID, _NodeName, SubIDs}) ->
566✔
UNCOV
3012
                LJIDs = case BroadcastAll of
20✔
3013
                    true ->
3014
                        {U, S, _} = LJID,
×
3015
                        [{U, S, R} || R <- user_resources(U, S)];
×
3016
                    false ->
UNCOV
3017
                        [LJID]
20✔
3018
                end,
3019
                %% Determine if the stanza should have SHIM ('SubID' and 'name') headers
UNCOV
3020
                StanzaToSend = case {SHIM, SubIDs} of
20✔
3021
                    {false, _} ->
3022
                        Stanza;
×
3023
                    %% If there's only one SubID, don't add it
3024
                    {true, [_]} ->
UNCOV
3025
                        Stanza;
20✔
3026
                    {true, SubIDs} ->
3027
                        add_shim_headers(Stanza, subid_shim(SubIDs))
×
3028
                end,
UNCOV
3029
                lists:foreach(fun(To) ->
20✔
UNCOV
3030
                            ejabberd_router:route(
20✔
3031
                              xmpp:set_to(xmpp:put_meta(StanzaToSend, ignore_sm_bounce, true),
3032
                                          jid:make(To)))
3033
                    end, LJIDs)
3034
        end, SubIDsByJID).
3035

3036
-spec broadcast_stanza(host(), jid(), nodeId(), nodeIdx(), binary(),
3037
                       nodeOptions(), subs_by_depth(), items | nodes,
3038
                       stanza(), boolean()) -> ok.
3039
broadcast_stanza({LUser, LServer, LResource}, Publisher, Node, Nidx, Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM) ->
UNCOV
3040
    broadcast_stanza({LUser, LServer, <<>>}, Node, Nidx, Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM),
24✔
3041
    %% Handles implicit presence subscriptions
UNCOV
3042
    SenderResource = user_resource(LUser, LServer, LResource),
24✔
UNCOV
3043
    NotificationType = get_option(NodeOptions, notification_type, headline),
24✔
3044
    %% set the from address on the notification to the bare JID of the account owner
3045
    %% Also, add "replyto" if entity has presence subscription to the account owner
3046
    %% See XEP-0163 1.1 section 4.3.1
UNCOV
3047
    Owner = jid:make(LUser, LServer),
24✔
UNCOV
3048
    FromBareJid = xmpp:set_from(BaseStanza, Owner),
24✔
UNCOV
3049
    Stanza = add_extended_headers(
24✔
3050
               add_message_type(FromBareJid, NotificationType),
3051
               extended_headers([Publisher])),
UNCOV
3052
    Pred = fun(To) -> delivery_permitted(Owner, To, NodeOptions) end,
24✔
UNCOV
3053
    ejabberd_sm:route(jid:make(LUser, LServer, SenderResource),
24✔
3054
                      {pep_message, <<((Node))/binary, "+notify">>, Stanza, Pred});
3055
broadcast_stanza(Host, _Publisher, Node, Nidx, Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM) ->
UNCOV
3056
    broadcast_stanza(Host, Node, Nidx, Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM).
258✔
3057

3058
-spec c2s_handle_info(ejabberd_c2s:state(), term()) -> ejabberd_c2s:state().
3059
c2s_handle_info(#{lserver := LServer} = C2SState,
3060
                {pep_message, Feature, Packet, Pred}) when is_function(Pred) ->
UNCOV
3061
    [maybe_send_pep_stanza(LServer, USR, Caps, Feature, Packet)
24✔
UNCOV
3062
     || {USR, Caps} <- mod_caps:list_features(C2SState), Pred(USR)],
24✔
UNCOV
3063
    {stop, C2SState};
24✔
3064
c2s_handle_info(#{lserver := LServer} = C2SState,
3065
                {pep_message, Feature, Packet, {_, _, _} = USR}) ->
3066
    case mod_caps:get_user_caps(USR, C2SState) of
×
3067
        {ok, Caps} -> maybe_send_pep_stanza(LServer, USR, Caps, Feature, Packet);
×
3068
        error -> ok
×
3069
    end,
3070
    {stop, C2SState};
×
3071
c2s_handle_info(C2SState, _) ->
UNCOV
3072
    C2SState.
48,808✔
3073

3074
-spec send_items(host(), nodeId(), nodeIdx(), binary(),
3075
                 nodeOptions(), ljid(), last | integer()) -> ok.
3076
send_items(Host, Node, Nidx, Type, Options, LJID, Number) ->
UNCOV
3077
    send_items(Host, Node, Nidx, Type, Options, Host, LJID, LJID, Number).
76✔
3078
send_items(Host, Node, Nidx, Type, Options, Publisher, SubLJID, ToLJID, Number) ->
UNCOV
3079
    Items = case max_items(Host, Options) of
76✔
3080
                1 ->
UNCOV
3081
                    get_only_item(Host, Type, Nidx, SubLJID);
8✔
3082
                _ ->
UNCOV
3083
                    get_last_items(Host, Type, Nidx, SubLJID, Number)
68✔
3084
            end,
UNCOV
3085
    case Items of
76✔
3086
        [] ->
UNCOV
3087
            ok;
72✔
3088
        Items ->
UNCOV
3089
            Delay = case Number of
4✔
3090
                last -> % handle section 6.1.7 of XEP-0060
UNCOV
3091
                    [Last] = Items,
4✔
UNCOV
3092
                    {Stamp, _USR} = Last#pubsub_item.modification,
4✔
UNCOV
3093
                    [#delay{stamp = Stamp}];
4✔
3094
                _ ->
3095
                    []
×
3096
            end,
UNCOV
3097
            Stanza = #message{
4✔
3098
                        sub_els = [#ps_event{items = items_els(Node, Options, Items)}
3099
                                   | Delay]},
UNCOV
3100
            NotificationType = get_option(Options, notification_type, headline),
4✔
UNCOV
3101
            send_stanza(Publisher, ToLJID, Node,
4✔
3102
                        add_message_type(Stanza, NotificationType))
3103
    end.
3104

3105
-spec send_stanza(host(), ljid(), binary(), stanza()) -> ok.
3106
send_stanza({LUser, LServer, _} = Publisher, USR, Node, BaseStanza) ->
3107
    Stanza = xmpp:set_from(BaseStanza, jid:make(LUser, LServer)),
×
3108
    USRs = case USR of
×
3109
               {PUser, PServer, <<>>} ->
3110
                   [{PUser, PServer, PRessource}
×
3111
                    || PRessource <- user_resources(PUser, PServer)];
×
3112
               _ ->
3113
                   [USR]
×
3114
           end,
3115
    lists:foreach(
×
3116
      fun(To) ->
3117
              ejabberd_sm:route(
×
3118
                jid:make(Publisher),
3119
                {pep_message, <<((Node))/binary, "+notify">>,
3120
                 add_extended_headers(
3121
                   Stanza, extended_headers([jid:make(Publisher)])),
3122
                 To})
3123
      end, USRs);
3124
send_stanza(Host, USR, _Node, Stanza) ->
UNCOV
3125
    ejabberd_router:route(
4✔
3126
      xmpp:set_from_to(Stanza, service_jid(Host), jid:make(USR))).
3127

3128
-spec maybe_send_pep_stanza(binary(), ljid(), caps(), binary(), stanza()) -> ok.
3129
maybe_send_pep_stanza(LServer, USR, Caps, Feature, Packet) ->
3130
    Features = mod_caps:get_features(LServer, Caps),
×
3131
    case lists:member(Feature, Features) of
×
3132
        true ->
3133
            ejabberd_router:route(xmpp:set_to(Packet, jid:make(USR)));
×
3134
        false ->
3135
            ok
×
3136
    end.
3137

3138
-spec send_last_items(jid()) -> ok.
3139
send_last_items(JID) ->
UNCOV
3140
    ServerHost = JID#jid.lserver,
232✔
UNCOV
3141
    Host = host(ServerHost),
232✔
UNCOV
3142
    DBType = config(ServerHost, db_type),
232✔
UNCOV
3143
    LJID = jid:tolower(JID),
232✔
UNCOV
3144
    BJID = jid:remove_resource(LJID),
232✔
UNCOV
3145
    lists:foreach(
232✔
3146
      fun(PType) ->
UNCOV
3147
              Subs = get_subscriptions_for_send_last(Host, PType, DBType, JID, LJID, BJID),
464✔
UNCOV
3148
              lists:foreach(
464✔
3149
                fun({#pubsub_node{nodeid = {_, Node}, type = Type, id = Nidx,
3150
                                  options = Options}, _, SubJID})
3151
                      when Type == PType->
3152
                        send_items(Host, Node, Nidx, PType, Options, Host, SubJID, LJID, 1);
×
3153
                   (_) ->
3154
                        ok
×
3155
                end,
3156
                lists:usort(Subs))
3157
      end, config(ServerHost, plugins)).
3158
% pep_from_offline hack can not work anymore, as sender c2s does not
3159
% exists when sender is offline, so we can't get match receiver caps
3160
% does it make sens to send PEP from an offline contact anyway ?
3161
%    case config(ServerHost, ignore_pep_from_offline) of
3162
%        false ->
3163
%            Roster = ejabberd_hooks:run_fold(roster_get, ServerHost, [],
3164
%                                             [{JID#jid.luser, ServerHost}]),
3165
%            lists:foreach(
3166
%              fun(#roster{jid = {U, S, R}, subscription = Sub})
3167
%                    when Sub == both orelse Sub == from,
3168
%                         S == ServerHost ->
3169
%                      case user_resources(U, S) of
3170
%                          [] -> send_last_pep(jid:make(U, S, R), JID);
3171
%                          _ -> ok %% this is already handled by presence probe
3172
%                      end;
3173
%                 (_) ->
3174
%                      ok %% we can not do anything in any cases
3175
%              end, Roster);
3176
%        true ->
3177
%            ok
3178
%    end.
3179
send_last_pep(From, To, Features) ->
UNCOV
3180
    ServerHost = From#jid.lserver,
400✔
UNCOV
3181
    Host = host(ServerHost),
400✔
UNCOV
3182
    Publisher = jid:tolower(From),
400✔
UNCOV
3183
    Owner = jid:remove_resource(Publisher),
400✔
UNCOV
3184
    NotifyNodes =
400✔
3185
    case Features of
3186
        _ when is_list(Features) ->
UNCOV
3187
            lists:filtermap(
8✔
3188
                fun(V) ->
3189
                    Vs = byte_size(V) - 7,
×
3190
                    case V of
×
3191
                        <<NotNode:Vs/binary, "+notify">> ->
3192
                            {true, NotNode};
×
3193
                        _ ->
3194
                            false
×
3195
                    end
3196
                end, Features);
3197
        _ ->
UNCOV
3198
            unknown
392✔
3199
    end,
UNCOV
3200
    case tree_action(Host, get_nodes, [Owner, infinity]) of
400✔
3201
        Nodes when is_list(Nodes) ->
UNCOV
3202
            lists:foreach(
400✔
3203
                fun(#pubsub_node{nodeid = {_, Node}, type = Type, id = Nidx, options = Options}) ->
3204
                    MaybeNotify =
×
3205
                    case NotifyNodes of
3206
                        unknown -> true;
×
3207
                        _ -> lists:member(Node, NotifyNodes)
×
3208
                    end,
3209
                    case MaybeNotify andalso match_option(Options, send_last_published_item, on_sub_and_presence) of
×
3210
                        true ->
3211
                            case delivery_permitted(From, To, Options) of
×
3212
                                true ->
3213
                                    LJID = jid:tolower(To),
×
3214
                                    send_items(Owner, Node, Nidx, Type, Options,
×
3215
                                               Publisher, LJID, LJID, 1);
3216
                                false ->
3217
                                    ok
×
3218
                            end;
3219
                        _ ->
3220
                            ok
×
3221
                    end
3222
                end, Nodes);
3223
        _ ->
3224
            ok
×
3225
    end.
3226

3227
-spec subscribed_nodes_by_jid(items | nodes, subs_by_depth()) -> [{ljid(), binary(), subId()}].
3228
subscribed_nodes_by_jid(NotifyType, SubsByDepth) ->
UNCOV
3229
    NodesToDeliver = fun (Depth, Node, Subs, Acc) ->
566✔
UNCOV
3230
        NodeName = case Node#pubsub_node.nodeid of
566✔
UNCOV
3231
                       {_, N} -> N;
566✔
3232
                       Other -> Other
×
3233
                   end,
UNCOV
3234
        NodeOptions = Node#pubsub_node.options,
566✔
UNCOV
3235
        lists:foldl(fun({LJID, SubID, SubOptions}, {JIDs, Recipients}) ->
566✔
UNCOV
3236
            case is_to_deliver(LJID, NotifyType, Depth, NodeOptions, SubOptions) of
20✔
3237
                true ->
UNCOV
3238
                    case state_can_deliver(LJID, SubOptions) of
20✔
3239
                        [] -> {JIDs, Recipients};
×
UNCOV
3240
                        [LJID] -> {JIDs, [{LJID, NodeName, [SubID]} | Recipients]};
20✔
3241
                        JIDsToDeliver ->
3242
                            lists:foldl(
×
3243
                                fun(JIDToDeliver, {JIDsAcc, RecipientsAcc}) ->
3244
                                        case lists:member(JIDToDeliver, JIDs) of
×
3245
                                            %% check if the JIDs co-accumulator contains the Subscription Jid,
3246
                                            false ->
3247
                                                %%  - if not,
3248
                                                %%  - add the Jid to JIDs list co-accumulator ;
3249
                                                %%  - create a tuple of the Jid, Nidx, and SubID (as list),
3250
                                                %%    and add the tuple to the Recipients list co-accumulator
3251
                                                {[JIDToDeliver | JIDsAcc],
×
3252
                                                    [{JIDToDeliver, NodeName, [SubID]}
3253
                                                        | RecipientsAcc]};
3254
                                            true ->
3255
                                                %% - if the JIDs co-accumulator contains the Jid
3256
                                                %%   get the tuple containing the Jid from the Recipient list co-accumulator
3257
                                                {_, {JIDToDeliver, NodeName1, SubIDs}} =
×
3258
                                                    lists:keysearch(JIDToDeliver, 1, RecipientsAcc),
3259
                                                %%   delete the tuple from the Recipients list
3260
                                                % v1 : Recipients1 = lists:keydelete(LJID, 1, Recipients),
3261
                                                % v2 : Recipients1 = lists:keyreplace(LJID, 1, Recipients, {LJID, Nidx1, [SubID | SubIDs]}),
3262
                                                %%   add the SubID to the SubIDs list in the tuple,
3263
                                                %%   and add the tuple back to the Recipients list co-accumulator
3264
                                                % v1.1 : {JIDs, lists:append(Recipients1, [{LJID, Nidx1, lists:append(SubIDs, [SubID])}])}
3265
                                                % v1.2 : {JIDs, [{LJID, Nidx1, [SubID | SubIDs]} | Recipients1]}
3266
                                                % v2: {JIDs, Recipients1}
3267
                                                {JIDsAcc,
×
3268
                                                    lists:keyreplace(JIDToDeliver, 1,
3269
                                                        RecipientsAcc,
3270
                                                        {JIDToDeliver, NodeName1,
3271
                                                            [SubID | SubIDs]})}
3272
                                        end
3273
                                end, {JIDs, Recipients}, JIDsToDeliver)
3274
                    end;
3275
                false ->
3276
                    {JIDs, Recipients}
×
3277
            end
3278
        end, Acc, Subs)
3279
        end,
UNCOV
3280
    DepthsToDeliver = fun({Depth, SubsByNode}, Acc1) ->
566✔
UNCOV
3281
        lists:foldl(fun({Node, Subs}, Acc2) ->
566✔
UNCOV
3282
                            NodesToDeliver(Depth, Node, Subs, Acc2)
566✔
3283
                    end, Acc1, SubsByNode)
3284
        end,
UNCOV
3285
    {_, JIDSubs} = lists:foldl(DepthsToDeliver, {[], []}, SubsByDepth),
566✔
UNCOV
3286
    JIDSubs.
566✔
3287

3288
-spec delivery_permitted(jid() | ljid(), jid() | ljid(), nodeOptions()) -> boolean().
3289
delivery_permitted(From, To, Options) ->
3290
    LFrom = jid:tolower(From),
×
3291
    LTo = jid:tolower(To),
×
3292
    RecipientIsOwner = jid:remove_resource(LFrom) == jid:remove_resource(LTo),
×
3293
    %% TODO: Fix the 'whitelist'/'authorize' cases for last PEP notifications.
3294
    %% Currently, only node owners receive those.
3295
    case get_option(Options, access_model) of
×
3296
        open -> true;
×
3297
        presence -> true;
×
3298
        whitelist -> RecipientIsOwner;
×
3299
        authorize -> RecipientIsOwner;
×
3300
        roster ->
3301
           Grps = get_option(Options, roster_groups_allowed, []),
×
3302
           {LUser, LServer, _} = LFrom,
×
3303
           {_, IsInGrp} = get_roster_info(LUser, LServer, LTo, Grps),
×
3304
           IsInGrp
×
3305
    end.
3306

3307
-spec user_resources(binary(), binary()) -> [binary()].
3308
user_resources(User, Server) ->
UNCOV
3309
    ejabberd_sm:get_user_resources(User, Server).
24✔
3310

3311
-spec user_resource(binary(), binary(), binary()) -> binary().
3312
user_resource(User, Server, <<>>) ->
UNCOV
3313
    case user_resources(User, Server) of
24✔
UNCOV
3314
        [R | _] -> R;
24✔
3315
        _ -> <<>>
×
3316
    end;
3317
user_resource(_, _, Resource) ->
3318
    Resource.
×
3319

3320
%%%%%%% Configuration handling
3321
-spec get_configure(host(), binary(), binary(), jid(),
3322
                    binary()) -> {error, stanza_error()} | {result, pubsub_owner()}.
3323
get_configure(Host, ServerHost, Node, From, Lang) ->
UNCOV
3324
    Action = fun (#pubsub_node{options = Options, type = Type, id = Nidx}) ->
72✔
UNCOV
3325
            case node_call(Host, Type, get_affiliation, [Nidx, From]) of
72✔
3326
                {result, owner} ->
UNCOV
3327
                    Groups = ejabberd_hooks:run_fold(roster_groups, ServerHost, [], [ServerHost]),
32✔
UNCOV
3328
                    Fs = get_configure_xfields(Type, Options, Lang, Groups),
32✔
UNCOV
3329
                    {result, #pubsub_owner{
32✔
3330
                                configure =
3331
                                    {Node, #xdata{type = form, fields = Fs}}}};
3332
                {result, _} ->
UNCOV
3333
                    {error, xmpp:err_forbidden(?T("Owner privileges required"), Lang)};
40✔
3334
                Error ->
3335
                    Error
×
3336
            end
3337
    end,
UNCOV
3338
    case transaction(Host, Node, Action, sync_dirty) of
72✔
UNCOV
3339
        {result, {_, Result}} -> {result, Result};
32✔
UNCOV
3340
        Other -> Other
40✔
3341
    end.
3342

3343
-spec get_default(host(), binary(), jid(), binary()) -> {result, pubsub_owner()}.
3344
get_default(Host, Node, _From, Lang) ->
UNCOV
3345
    Type = select_type(serverhost(Host), Host, Node),
24✔
UNCOV
3346
    Options = node_options(Host, Type),
24✔
UNCOV
3347
    Fs = get_configure_xfields(Type, Options, Lang, []),
24✔
UNCOV
3348
    {result, #pubsub_owner{default = {<<>>, #xdata{type = form, fields = Fs}}}}.
24✔
3349

3350
-spec match_option(#pubsub_node{} | [{atom(), any()}], atom(), any()) -> boolean().
3351
match_option(Node, Var, Val) when is_record(Node, pubsub_node) ->
3352
    match_option(Node#pubsub_node.options, Var, Val);
×
3353
match_option(Options, Var, Val) when is_list(Options) ->
3354
    get_option(Options, Var) == Val;
×
3355
match_option(_, _, _) ->
3356
    false.
×
3357

3358
-spec get_option([{atom(), any()}], atom()) -> any().
3359
get_option([], _) -> false;
×
UNCOV
3360
get_option(Options, Var) -> get_option(Options, Var, false).
4,258✔
3361

3362
-spec get_option([{atom(), any()}], atom(), any()) -> any().
3363
get_option(Options, Var, Def) ->
UNCOV
3364
    case lists:keysearch(Var, 1, Options) of
5,092✔
UNCOV
3365
        {value, {_Val, Ret}} -> Ret;
4,526✔
UNCOV
3366
        _ -> Def
566✔
3367
    end.
3368

3369
-spec node_options(host(), binary()) -> [{atom(), any()}].
3370
node_options(Host, Type) ->
UNCOV
3371
    ConfigOpts = config(Host, default_node_config),
284✔
UNCOV
3372
    PluginOpts = node_plugin_options(Host, Type),
284✔
UNCOV
3373
    merge_config([ConfigOpts, PluginOpts]).
284✔
3374

3375
-spec node_plugin_options(host(), binary()) -> [{atom(), any()}].
3376
node_plugin_options(Host, Type) ->
UNCOV
3377
    Module = plugin(Host, Type),
284✔
UNCOV
3378
    case {lists:member(Type, config(Host, plugins)), catch Module:options()} of
284✔
3379
        {true, Opts} when is_list(Opts) ->
UNCOV
3380
            Opts;
284✔
3381
        {_, _} ->
3382
            DefaultModule = plugin(Host, ?STDNODE),
×
3383
            DefaultModule:options()
×
3384
    end.
3385

3386
-spec node_owners_action(host(), binary(), nodeIdx(), [ljid()]) -> [ljid()].
3387
node_owners_action(Host, Type, Nidx, []) ->
UNCOV
3388
    case node_action(Host, Type, get_node_affiliations, [Nidx]) of
12✔
UNCOV
3389
        {result, Affs} -> [LJID || {LJID, Aff} <- Affs, Aff =:= owner];
12✔
3390
        _ -> []
×
3391
    end;
3392
node_owners_action(_Host, _Type, _Nidx, Owners) ->
UNCOV
3393
    Owners.
4✔
3394

3395
-spec node_owners_call(host(), binary(), nodeIdx(), [ljid()]) -> [ljid()].
3396
node_owners_call(Host, Type, Nidx, []) ->
UNCOV
3397
    case node_call(Host, Type, get_node_affiliations, [Nidx]) of
318✔
UNCOV
3398
        {result, Affs} -> [LJID || {LJID, Aff} <- Affs, Aff =:= owner];
318✔
3399
        _ -> []
×
3400
    end;
3401
node_owners_call(_Host, _Type, _Nidx, Owners) ->
UNCOV
3402
    Owners.
114✔
3403

3404
node_config(Node, ServerHost) ->
UNCOV
3405
    Opts = mod_pubsub_opt:force_node_config(ServerHost),
276✔
UNCOV
3406
    node_config(Node, ServerHost, Opts).
276✔
3407

3408
node_config(Node, ServerHost, [{RE, Opts}|NodeOpts]) ->
3409
    case re:run(Node, RE) of
×
3410
        {match, _} ->
3411
            Opts;
×
3412
        nomatch ->
3413
            node_config(Node, ServerHost, NodeOpts)
×
3414
    end;
3415
node_config(_, _, []) ->
UNCOV
3416
    [].
276✔
3417

3418
%% @doc <p>Return the maximum number of items for a given node.</p>
3419
%% <p>Unlimited means that there is no limit in the number of items that can
3420
%% be stored.</p>
3421
-spec max_items(host(), [{atom(), any()}]) -> non_neg_integer() | unlimited.
3422
max_items(Host, Options) ->
UNCOV
3423
    case get_option(Options, persist_items) of
348✔
3424
        true ->
UNCOV
3425
            case get_option(Options, max_items) of
348✔
3426
                I when is_integer(I), I < 0 -> 0;
×
UNCOV
3427
                I when is_integer(I) -> I;
340✔
UNCOV
3428
                _ -> get_max_items_node(Host)
8✔
3429
            end;
3430
        false ->
3431
            case get_option(Options, send_last_published_item) of
×
3432
                never ->
3433
                    0;
×
3434
                _ ->
3435
                    case is_last_item_cache_enabled(Host) of
×
3436
                        true -> 0;
×
3437
                        false -> 1
×
3438
                    end
3439
            end
3440
    end.
3441

3442
-spec item_expire(host(), [{atom(), any()}]) -> non_neg_integer() | infinity.
3443
item_expire(Host, Options) ->
3444
    case get_option(Options, item_expire) of
×
3445
        I when is_integer(I), I < 0 -> 0;
×
3446
        I when is_integer(I) -> I;
×
3447
        _ -> get_max_item_expire_node(Host)
×
3448
    end.
3449

3450
-spec get_configure_xfields(_, pubsub_node_config:result(),
3451
                            binary(), [binary()]) -> [xdata_field()].
3452
get_configure_xfields(_Type, Options, Lang, Groups) ->
UNCOV
3453
    pubsub_node_config:encode(
56✔
3454
      lists:filtermap(
3455
        fun({roster_groups_allowed, Value}) ->
UNCOV
3456
                {true, {roster_groups_allowed, Value, Groups}};
56✔
UNCOV
3457
           ({sql, _}) -> false;
42✔
UNCOV
3458
           ({rsm, _}) -> false;
42✔
3459
           ({Item, infinity}) when Item == max_items;
3460
                                   Item == item_expire;
3461
                                   Item == children_max ->
3462
               {true, {Item, max}};
×
UNCOV
3463
           (_) -> true
920✔
3464
        end, Options),
3465
      Lang).
3466

3467
%%<p>There are several reasons why the node configuration request might fail:</p>
3468
%%<ul>
3469
%%<li>The service does not support node configuration.</li>
3470
%%<li>The requesting entity does not have sufficient privileges to configure the node.</li>
3471
%%<li>The request did not specify a node.</li>
3472
%%<li>The node has no configuration options.</li>
3473
%%<li>The specified node does not exist.</li>
3474
%%</ul>
3475
-spec set_configure(host(), binary(), jid(), [{binary(), [binary()]}],
3476
                    binary()) -> {result, undefined} | {error, stanza_error()}.
3477
set_configure(_Host, <<>>, _From, _Config, _Lang) ->
3478
    {error, extended_error(xmpp:err_bad_request(), err_nodeid_required())};
×
3479
set_configure(Host, Node, From, Config, Lang) ->
UNCOV
3480
    Action =
56✔
3481
        fun(#pubsub_node{options = Options, type = Type, id = Nidx} = N) ->
UNCOV
3482
                case node_call(Host, Type, get_affiliation, [Nidx, From]) of
56✔
3483
                    {result, owner} ->
UNCOV
3484
                        OldOpts = case Options of
16✔
3485
                                      [] -> node_options(Host, Type);
×
UNCOV
3486
                                      _ -> Options
16✔
3487
                                  end,
UNCOV
3488
                        NewOpts = merge_config(
16✔
3489
                                    [node_config(Node, serverhost(Host)),
3490
                                     Config, OldOpts]),
UNCOV
3491
                        case tree_call(Host,
16✔
3492
                                       set_node,
3493
                                       [N#pubsub_node{options = NewOpts}]) of
UNCOV
3494
                            {result, Nidx} -> {result, NewOpts};
12✔
UNCOV
3495
                            ok -> {result, NewOpts};
4✔
3496
                            Err -> Err
×
3497
                        end;
3498
                    {result, _} ->
UNCOV
3499
                        {error, xmpp:err_forbidden(
40✔
3500
                                  ?T("Owner privileges required"), Lang)};
3501
                    Error ->
3502
                        Error
×
3503
                end
3504
        end,
UNCOV
3505
    case transaction(Host, Node, Action, transaction) of
56✔
3506
        {result, {TNode, Options}} ->
UNCOV
3507
            Nidx = TNode#pubsub_node.id,
16✔
UNCOV
3508
            Type = TNode#pubsub_node.type,
16✔
UNCOV
3509
            broadcast_config_notification(Host, Node, Nidx, Type, Options, Lang),
16✔
UNCOV
3510
            {result, undefined};
16✔
3511
        Other ->
UNCOV
3512
            Other
40✔
3513
    end.
3514

3515
-spec merge_config([[proplists:property()]]) -> [proplists:property()].
3516
merge_config(ListOfConfigs) ->
UNCOV
3517
    lists:ukeysort(1, lists:flatten(ListOfConfigs)).
560✔
3518

3519
-spec decode_node_config(undefined | xdata(), binary(), binary()) ->
3520
                                pubsub_node_config:result() |
3521
                                {error, stanza_error()}.
3522
decode_node_config(undefined, _, _) ->
UNCOV
3523
    [];
160✔
3524
decode_node_config(#xdata{fields = Fs}, Host, Lang) ->
UNCOV
3525
    try
128✔
UNCOV
3526
        Config = pubsub_node_config:decode(Fs),
128✔
UNCOV
3527
        MaxItems = get_max_items_node(Host),
128✔
UNCOV
3528
        MaxExpiry = get_max_item_expire_node(Host),
128✔
UNCOV
3529
        case {check_opt_range(max_items, Config, MaxItems),
128✔
3530
              check_opt_range(item_expire, Config, MaxExpiry),
3531
              check_opt_range(max_payload_size, Config, ?MAX_PAYLOAD_SIZE)} of
3532
            {true, true, true} ->
UNCOV
3533
                Config;
128✔
3534
            {true, true, false} ->
3535
                erlang:error(
×
3536
                  {pubsub_node_config,
3537
                   {bad_var_value, <<"pubsub#max_payload_size">>,
3538
                    ?NS_PUBSUB_NODE_CONFIG}});
3539
            {true, false, _} ->
3540
                erlang:error(
×
3541
                  {pubsub_node_config,
3542
                   {bad_var_value, <<"pubsub#item_expire">>,
3543
                    ?NS_PUBSUB_NODE_CONFIG}});
3544
            {false, _, _} ->
3545
                erlang:error(
×
3546
                  {pubsub_node_config,
3547
                   {bad_var_value, <<"pubsub#max_items">>,
3548
                    ?NS_PUBSUB_NODE_CONFIG}})
3549
        end
3550
    catch _:{pubsub_node_config, Why} ->
3551
            Txt = pubsub_node_config:format_error(Why),
×
3552
            {error, xmpp:err_resource_constraint(Txt, Lang)}
×
3553
    end.
3554

3555
-spec decode_subscribe_options(undefined | xdata(), binary()) ->
3556
                                      pubsub_subscribe_options:result() |
3557
                                      {error, stanza_error()}.
3558
decode_subscribe_options(undefined, _) ->
3559
    [];
×
3560
decode_subscribe_options(#xdata{fields = Fs}, Lang) ->
3561
    try pubsub_subscribe_options:decode(Fs)
×
3562
    catch _:{pubsub_subscribe_options, Why} ->
3563
            Txt = pubsub_subscribe_options:format_error(Why),
×
3564
            {error, xmpp:err_resource_constraint(Txt, Lang)}
×
3565
    end.
3566

3567
-spec decode_publish_options(undefined | xdata(), binary()) ->
3568
                                    pubsub_publish_options:result() |
3569
                                    {error, stanza_error()}.
3570
decode_publish_options(undefined, _) ->
UNCOV
3571
    [];
240✔
3572
decode_publish_options(#xdata{fields = Fs}, Lang) ->
3573
    try pubsub_publish_options:decode(Fs)
×
3574
    catch _:{pubsub_publish_options, Why} ->
3575
            Txt = pubsub_publish_options:format_error(Why),
×
3576
            {error, xmpp:err_resource_constraint(Txt, Lang)}
×
3577
    end.
3578

3579
-spec decode_get_pending(xdata(), binary()) ->
3580
                                pubsub_get_pending:result() |
3581
                                {error, stanza_error()}.
3582
decode_get_pending(#xdata{fields = Fs}, Lang) ->
3583
    try pubsub_get_pending:decode(Fs)
×
3584
    catch _:{pubsub_get_pending, Why} ->
3585
            Txt = pubsub_get_pending:format_error(Why),
×
3586
            {error, xmpp:err_resource_constraint(Txt, Lang)}
×
3587
    end.
3588

3589
-spec check_opt_range(atom(), [proplists:property()],
3590
                      non_neg_integer() | unlimited | infinity) -> boolean().
3591
check_opt_range(_Opt, _Opts, unlimited) ->
3592
    true;
×
3593
check_opt_range(_Opt, _Opts, infinity) ->
UNCOV
3594
    true;
128✔
3595
check_opt_range(Opt, Opts, Max) ->
UNCOV
3596
    case proplists:get_value(Opt, Opts, Max) of
256✔
3597
        max -> true;
×
UNCOV
3598
        Val -> Val =< Max
256✔
3599
    end.
3600

3601
-spec get_max_items_node(host()) -> unlimited | non_neg_integer().
3602
get_max_items_node(Host) ->
UNCOV
3603
    config(Host, max_items_node, ?MAXITEMS).
136✔
3604

3605
-spec get_max_item_expire_node(host()) -> infinity | non_neg_integer().
3606
get_max_item_expire_node(Host) ->
UNCOV
3607
    config(Host, max_item_expire_node, infinity).
128✔
3608

3609
-spec get_max_subscriptions_node(host()) -> undefined | non_neg_integer().
3610
get_max_subscriptions_node(Host) ->
UNCOV
3611
    config(Host, max_subscriptions_node, undefined).
140✔
3612

3613
%%%% last item cache handling
3614
-spec is_last_item_cache_enabled(host()) -> boolean().
3615
is_last_item_cache_enabled(Host) ->
UNCOV
3616
    config(Host, last_item_cache, false).
634✔
3617

3618
-spec set_cached_item(host(), nodeIdx(), binary(), jid(), [xmlel()]) -> ok.
3619
set_cached_item({_, ServerHost, _}, Nidx, ItemId, Publisher, Payload) ->
UNCOV
3620
    set_cached_item(ServerHost, Nidx, ItemId, Publisher, Payload);
24✔
3621
set_cached_item(Host, Nidx, ItemId, Publisher, Payload) ->
UNCOV
3622
    case is_last_item_cache_enabled(Host) of
248✔
3623
        true ->
3624
            Stamp = {erlang:timestamp(), jid:tolower(jid:remove_resource(Publisher))},
×
3625
            Item = #pubsub_last_item{nodeid = {Host, Nidx},
×
3626
                                     itemid = ItemId,
3627
                                     creation = Stamp,
3628
                                     payload = Payload},
3629
            mnesia:dirty_write(Item);
×
3630
        _ ->
UNCOV
3631
            ok
248✔
3632
    end.
3633

3634
-spec unset_cached_item(host(), nodeIdx()) -> ok.
3635
unset_cached_item({_, ServerHost, _}, Nidx) ->
UNCOV
3636
    unset_cached_item(ServerHost, Nidx);
16✔
3637
unset_cached_item(Host, Nidx) ->
UNCOV
3638
    case is_last_item_cache_enabled(Host) of
276✔
3639
        true -> mnesia:dirty_delete({pubsub_last_item, {Host, Nidx}});
×
UNCOV
3640
        _ -> ok
276✔
3641
    end.
3642

3643
-spec get_cached_item(host(), nodeIdx()) -> undefined | #pubsub_item{}.
3644
get_cached_item({_, ServerHost, _}, Nidx) ->
3645
    get_cached_item(ServerHost, Nidx);
×
3646
get_cached_item(Host, Nidx) ->
UNCOV
3647
    case is_last_item_cache_enabled(Host) of
110✔
3648
        true ->
3649
            case mnesia:dirty_read({pubsub_last_item, {Host, Nidx}}) of
×
3650
                [#pubsub_last_item{itemid = ItemId, creation = Creation, payload = Payload}] ->
3651
                    #pubsub_item{itemid = {ItemId, Nidx},
×
3652
                        payload = Payload, creation = Creation,
3653
                        modification = Creation};
3654
                _ ->
3655
                    undefined
×
3656
            end;
3657
        _ ->
UNCOV
3658
            undefined
110✔
3659
    end.
3660

3661
%%%% plugin handling
3662
-spec host(binary()) -> binary().
3663
host(ServerHost) ->
UNCOV
3664
    config(ServerHost, host, <<"pubsub.", ServerHost/binary>>).
4,392✔
3665

3666
-spec serverhost(host()) -> binary().
3667
serverhost({_U, ServerHost, _R})->
UNCOV
3668
    serverhost(ServerHost);
598✔
3669
serverhost(Host) ->
UNCOV
3670
    ejabberd_router:host_of_route(Host).
31,622✔
3671

3672
-spec tree(host()) -> atom().
3673
tree(Host) ->
UNCOV
3674
    case config(Host, nodetree) of
4,798✔
3675
        undefined -> tree(Host, ?STDTREE);
×
UNCOV
3676
        Tree -> Tree
4,798✔
3677
    end.
3678

3679
-spec tree(host() | atom(), binary()) -> atom().
3680
tree(_Host, <<"virtual">>) ->
3681
    nodetree_virtual;   % special case, virtual does not use any backend
×
3682
tree(Host, Name) ->
UNCOV
3683
    submodule(Host, <<"nodetree">>, Name).
8✔
3684

3685
-spec plugin(host() | atom(), binary()) -> atom().
3686
plugin(Host, Name) ->
UNCOV
3687
    submodule(Host, <<"node">>, Name).
22,450✔
3688

3689
-spec plugins(host()) -> [binary()].
3690
plugins(Host) ->
UNCOV
3691
    case config(Host, plugins) of
6,128✔
3692
        undefined -> [?STDNODE];
×
3693
        [] -> [?STDNODE];
×
UNCOV
3694
        Plugins -> Plugins
6,128✔
3695
    end.
3696

3697
-spec subscription_plugin(host() | atom()) -> atom().
3698
subscription_plugin(Host) ->
UNCOV
3699
    submodule(Host, <<"pubsub">>, <<"subscription">>).
140✔
3700

3701
-spec submodule(host() | atom(), binary(), binary()) -> atom().
3702
submodule(Db, Type, Name) when is_atom(Db) ->
UNCOV
3703
    case Db of
22,598✔
UNCOV
3704
        mnesia -> ejabberd:module_name([<<"pubsub">>, Type, Name]);
5,888✔
UNCOV
3705
        _ -> ejabberd:module_name([<<"pubsub">>, Type, Name, misc:atom_to_binary(Db)])
16,710✔
3706
    end;
3707
submodule(Host, Type, Name) ->
UNCOV
3708
    Db = mod_pubsub_opt:db_type(serverhost(Host)),
22,582✔
UNCOV
3709
    submodule(Db, Type, Name).
22,582✔
3710

3711
-spec config(binary(), any()) -> any().
3712
config(ServerHost, Key) ->
UNCOV
3713
    config(ServerHost, Key, undefined).
13,452✔
3714

3715
-spec config(host(), any(), any()) -> any().
3716
config({_User, Host, _Resource}, Key, Default) ->
UNCOV
3717
    config(Host, Key, Default);
56✔
3718
config(ServerHost, Key, Default) ->
UNCOV
3719
    case catch ets:lookup(gen_mod:get_module_proc(ServerHost, config), Key) of
18,882✔
UNCOV
3720
        [{Key, Value}] -> Value;
18,522✔
UNCOV
3721
        _ -> Default
360✔
3722
    end.
3723

3724
-spec select_type(binary(), host(), binary(), binary()) -> binary().
3725
select_type(ServerHost, {_User, _Server, _Resource}, Node, _Type) ->
UNCOV
3726
    case config(ServerHost, pep_mapping) of
32✔
3727
        undefined -> ?PEPNODE;
×
UNCOV
3728
        Mapping -> proplists:get_value(Node, Mapping, ?PEPNODE)
32✔
3729
    end;
3730
select_type(ServerHost, _Host, _Node, Type) ->
UNCOV
3731
    case config(ServerHost, plugins) of
276✔
3732
        undefined ->
3733
            Type;
×
3734
        Plugins ->
UNCOV
3735
            case lists:member(Type, Plugins) of
276✔
UNCOV
3736
                true -> Type;
276✔
3737
                false -> hd(Plugins)
×
3738
            end
3739
    end.
3740

3741
-spec select_type(binary(), host(), binary()) -> binary().
3742
select_type(ServerHost, Host, Node) ->
UNCOV
3743
    select_type(ServerHost, Host, Node, hd(plugins(Host))).
48✔
3744

3745
-spec feature(binary()) -> binary().
UNCOV
3746
feature(<<"rsm">>) -> ?NS_RSM;
1,404✔
UNCOV
3747
feature(Feature) -> <<(?NS_PUBSUB)/binary, "#", Feature/binary>>.
73,992✔
3748

3749
-spec features() -> [binary()].
3750
features() ->
3751
    [% see plugin "access-authorize",   % OPTIONAL
UNCOV
3752
     <<"access-open">>,   % OPTIONAL this relates to access_model option in node_hometree
1,880✔
3753
     <<"access-presence">>,   % OPTIONAL this relates to access_model option in node_pep
3754
     <<"access-whitelist">>,   % OPTIONAL
3755
     <<"collections">>,   % RECOMMENDED
3756
     <<"config-node">>,   % RECOMMENDED
3757
     <<"config-node-max">>,
3758
     <<"create-and-configure">>,   % RECOMMENDED
3759
     <<"item-ids">>,   % RECOMMENDED
3760
     <<"last-published">>,   % RECOMMENDED
3761
     <<"member-affiliation">>,   % RECOMMENDED
3762
     <<"presence-notifications">>,   % OPTIONAL
3763
     <<"presence-subscribe">>,   % RECOMMENDED
3764
     <<"publisher-affiliation">>,   % RECOMMENDED
3765
     <<"publish-only-affiliation">>,   % OPTIONAL
3766
     <<"publish-options">>,   % OPTIONAL
3767
     <<"retrieve-default">>,
3768
     <<"shim">>].   % RECOMMENDED
3769

3770
% see plugin "retrieve-items",   % RECOMMENDED
3771
% see plugin "retrieve-subscriptions",   % RECOMMENDED
3772
% see plugin "subscribe",   % REQUIRED
3773
% see plugin "subscription-options",   % OPTIONAL
3774
% see plugin "subscription-notifications"   % OPTIONAL
3775
-spec plugin_features(host(), binary()) -> [binary()].
3776
plugin_features(Host, Type) ->
UNCOV
3777
    Module = plugin(Host, Type),
13,012✔
UNCOV
3778
    case catch Module:features() of
13,012✔
3779
        {'EXIT', {undef, _}} -> [];
×
UNCOV
3780
        Result -> Result
13,012✔
3781
    end.
3782

3783
-spec features(binary(), binary()) -> [binary()].
3784
features(Host, <<>>) ->
UNCOV
3785
    lists:usort(lists:foldl(fun (Plugin, Acc) ->
1,880✔
UNCOV
3786
                    Acc ++ plugin_features(Host, Plugin)
3,760✔
3787
            end,
3788
            features(), plugins(Host)));
3789
features(Host, Node) when is_binary(Node) ->
3790
    Action = fun (#pubsub_node{type = Type}) ->
×
3791
            {result, plugin_features(Host, Type)}
×
3792
    end,
3793
    case transaction(Host, Node, Action, sync_dirty) of
×
3794
        {result, Features} -> lists:usort(features() ++ Features);
×
3795
        _ -> features()
×
3796
    end.
3797

3798
%% @doc <p>node tree plugin call.</p>
3799
-spec tree_call(host(), atom(), list()) -> {error, stanza_error() | {virtual, nodeIdx()}} | any().
3800
tree_call({_User, Server, _Resource}, Function, Args) ->
UNCOV
3801
    tree_call(Server, Function, Args);
264✔
3802
tree_call(Host, Function, Args) ->
UNCOV
3803
    Tree = tree(Host),
3,426✔
UNCOV
3804
    ?DEBUG("Tree_call apply(~ts, ~ts, ~p) @ ~ts", [Tree, Function, Args, Host]),
3,426✔
UNCOV
3805
    Res = apply(Tree, Function, Args),
3,426✔
UNCOV
3806
    Res2 = ejabberd_hooks:run_fold(pubsub_tree_call, Host, Res, [Tree, Function, Args]),
3,426✔
UNCOV
3807
    case Res2 of
3,426✔
3808
        {error, #stanza_error{}} = Err ->
UNCOV
3809
            Err;
110✔
3810
        {error, {virtual, _}} = Err ->
3811
            Err;
×
3812
        {error, _} ->
3813
            ErrTxt = ?T("Database failure"),
×
3814
            Lang = ejabberd_option:language(),
×
3815
            {error, xmpp:err_internal_server_error(ErrTxt, Lang)};
×
3816
        Other ->
UNCOV
3817
            Other
3,316✔
3818
    end.
3819

3820
-spec tree_action(host(), atom(), list()) -> {error, stanza_error() | {virtual, nodeIdx()}} | any().
3821
tree_action(Host, Function, Args) ->
UNCOV
3822
    ?DEBUG("Tree_action ~p ~p ~p", [Host, Function, Args]),
400✔
UNCOV
3823
    ServerHost = serverhost(Host),
400✔
UNCOV
3824
    DBType = mod_pubsub_opt:db_type(ServerHost),
400✔
UNCOV
3825
    Fun = fun () ->
400✔
UNCOV
3826
                  try tree_call(Host, Function, Args)
400✔
3827
                  catch
3828
                      Class:Reason:StackTrace when DBType == sql ->
3829
                          ejabberd_sql:abort({exception, Class, Reason, StackTrace})
×
3830
                  end
3831
          end,
UNCOV
3832
    Ret = case DBType of
400✔
3833
              mnesia ->
UNCOV
3834
                  mnesia:sync_dirty(Fun);
100✔
3835
              sql ->
UNCOV
3836
                  ejabberd_sql:sql_bloc(ServerHost, Fun);
300✔
3837
              _ ->
3838
                  Fun()
×
3839
          end,
UNCOV
3840
    get_tree_action_result(Ret).
400✔
3841

3842
-spec get_tree_action_result(any()) -> {error, stanza_error() | {virtual, nodeIdx()}} | any().
3843
get_tree_action_result({atomic, Result}) ->
UNCOV
3844
    Result;
300✔
3845
get_tree_action_result({aborted, {exception, Class, Reason, StackTrace}}) ->
3846
    ?ERROR_MSG("Transaction aborted:~n** ~ts",
×
3847
               [misc:format_exception(2, Class, Reason, StackTrace)]),
×
3848
    get_tree_action_result({error, db_failure});
×
3849
get_tree_action_result({aborted, Reason}) ->
3850
    ?ERROR_MSG("Transaction aborted: ~p~n", [Reason]),
×
3851
    get_tree_action_result({error, db_failure});
×
3852
get_tree_action_result({error, #stanza_error{}} = Err) ->
3853
    Err;
×
3854
get_tree_action_result({error, {virtual, _}} = Err) ->
3855
    Err;
×
3856
get_tree_action_result({error, _}) ->
3857
    ErrTxt = ?T("Database failure"),
×
3858
    Lang = ejabberd_option:language(),
×
3859
    {error, xmpp:err_internal_server_error(ErrTxt, Lang)};
×
3860
get_tree_action_result(Other) ->
3861
    %% This is very risky, but tree plugins design is really bad
UNCOV
3862
    Other.
100✔
3863

3864
%% @doc <p>node plugin call.</p>
3865
-spec node_call(host(), binary(), atom(), list()) -> {result, any()} | {error, stanza_error()}.
3866
node_call(Host, Type, Function, Args) ->
UNCOV
3867
    ?DEBUG("Node_call ~p ~p ~p", [Type, Function, Args]),
9,106✔
UNCOV
3868
    Module = plugin(Host, Type),
9,106✔
UNCOV
3869
    case erlang:function_exported(Module, Function, length(Args)) of
9,106✔
3870
        true ->
UNCOV
3871
            case apply(Module, Function, Args) of
9,106✔
3872
                {result, Result} ->
UNCOV
3873
                    {result, Result};
8,958✔
3874
                #pubsub_state{} = Result ->
3875
                    {result, Result};
×
3876
                {error, #stanza_error{}} = Err ->
UNCOV
3877
                    Err;
148✔
3878
                {error, _} ->
3879
                    ErrTxt = ?T("Database failure"),
×
3880
                    Lang = ejabberd_option:language(),
×
3881
                    {error, xmpp:err_internal_server_error(ErrTxt, Lang)}
×
3882
            end;
3883
        false when Type /= ?STDNODE ->
3884
            node_call(Host, ?STDNODE, Function, Args);
×
3885
        false ->
3886
            %% Let it crash with the stacktrace
3887
            apply(Module, Function, Args)
×
3888
    end.
3889

3890
-spec node_action(host(), binary(), atom(), list()) -> {result, any()} | {error, stanza_error()}.
3891
node_action(Host, Type, Function, Args) ->
UNCOV
3892
    ?DEBUG("Node_action ~p ~p ~p ~p", [Host, Type, Function, Args]),
5,336✔
UNCOV
3893
    transaction(Host, fun() -> node_call(Host, Type, Function, Args) end, sync_dirty).
5,336✔
3894

3895
%% @doc <p>plugin transaction handling.</p>
3896
-spec transaction(host(), binary(), fun((#pubsub_node{}) -> _), transaction | sync_dirty) ->
3897
                         {result, any()} | {error, stanza_error()}.
3898
transaction(Host, Node, Action, Trans) ->
UNCOV
3899
    transaction(
1,656✔
3900
      Host,
3901
      fun() ->
UNCOV
3902
              case tree_call(Host, get_node, [Host, Node]) of
1,656✔
3903
                  N when is_record(N, pubsub_node) ->
UNCOV
3904
                      case Action(N) of
1,546✔
UNCOV
3905
                          {result, Result} -> {result, {N, Result}};
1,118✔
3906
                          {atomic, {result, Result}} -> {result, {N, Result}};
×
UNCOV
3907
                          Other -> Other
428✔
3908
                      end;
3909
                  Error ->
UNCOV
3910
                      Error
110✔
3911
              end
3912
      end,
3913
      Trans).
3914

3915
-spec transaction(host(), fun(), transaction | sync_dirty) ->
3916
                         {result, any()} | {error, stanza_error()}.
3917
transaction(Host, Fun, Trans) ->
UNCOV
3918
    ServerHost = serverhost(Host),
7,550✔
UNCOV
3919
    DBType = mod_pubsub_opt:db_type(ServerHost),
7,550✔
UNCOV
3920
    do_transaction(ServerHost, Fun, Trans, DBType).
7,550✔
3921

3922
-spec do_transaction(binary(), fun(), transaction | sync_dirty, atom()) ->
3923
                            {result, any()} | {error, stanza_error()}.
3924
do_transaction(ServerHost, Fun, Trans, DBType) ->
UNCOV
3925
    F = fun() ->
7,550✔
UNCOV
3926
                try Fun()
7,550✔
3927
                catch
3928
                                        exit:{aborted, _} = Err when DBType == mnesia ->
3929
                                                exit(Err);
×
3930
                    Class:Reason:StackTrace when (DBType == mnesia andalso
3931
                                                  Trans == transaction) orelse
3932
                                                 DBType == sql ->
3933
                        Ex = {exception, Class, Reason, StackTrace},
×
3934
                        case DBType of
×
3935
                            mnesia -> mnesia:abort(Ex);
×
3936
                            sql -> ejabberd_sql:abort(Ex)
×
3937
                        end
3938
                end
3939
        end,
UNCOV
3940
    Res = case DBType of
7,550✔
3941
              mnesia ->
UNCOV
3942
                  mnesia:Trans(F);
1,982✔
3943
              sql ->
UNCOV
3944
                  SqlFun = case Trans of
5,568✔
UNCOV
3945
                               transaction -> sql_transaction;
504✔
UNCOV
3946
                               _ -> sql_bloc
5,064✔
3947
                           end,
UNCOV
3948
                  ejabberd_sql:SqlFun(ServerHost, F);
5,568✔
3949
              _ ->
3950
                  F()
×
3951
          end,
UNCOV
3952
    get_transaction_response(Res).
7,550✔
3953

3954
-spec get_transaction_response(any()) -> {result, any()} | {error, stanza_error()}.
3955
get_transaction_response({result, _} = Result) ->
UNCOV
3956
    Result;
7,012✔
3957
get_transaction_response({error, #stanza_error{}} = Err) ->
UNCOV
3958
    Err;
538✔
3959
get_transaction_response({atomic, Result}) ->
UNCOV
3960
    get_transaction_response(Result);
5,744✔
3961
get_transaction_response({aborted, Err}) ->
3962
    get_transaction_response(Err);
×
3963
get_transaction_response({error, _}) ->
3964
    Lang = ejabberd_option:language(),
×
3965
    {error, xmpp:err_internal_server_error(?T("Database failure"), Lang)};
×
3966
get_transaction_response({exception, Class, Reason, StackTrace}) ->
3967
    ?ERROR_MSG("Transaction aborted:~n** ~ts",
×
3968
               [misc:format_exception(2, Class, Reason, StackTrace)]),
×
3969
    get_transaction_response({error, db_failure});
×
3970
get_transaction_response(Err) ->
3971
    ?ERROR_MSG("Transaction error: ~p", [Err]),
×
3972
    get_transaction_response({error, db_failure}).
×
3973

3974
%%%% helpers
3975

3976
%% Add pubsub-specific error element
3977
-spec extended_error(stanza_error(), ps_error()) -> stanza_error().
3978
extended_error(StanzaErr, PubSubErr) ->
UNCOV
3979
    StanzaErr#stanza_error{sub_els = [PubSubErr]}.
20✔
3980

3981
-spec err_closed_node() -> ps_error().
3982
err_closed_node() ->
3983
    #ps_error{type = 'closed-node'}.
×
3984

3985
-spec err_configuration_required() -> ps_error().
3986
err_configuration_required() ->
3987
    #ps_error{type = 'configuration-required'}.
×
3988

3989
-spec err_invalid_jid() -> ps_error().
3990
err_invalid_jid() ->
3991
    #ps_error{type = 'invalid-jid'}.
×
3992

3993
-spec err_invalid_options() -> ps_error().
3994
err_invalid_options() ->
3995
    #ps_error{type = 'invalid-options'}.
×
3996

3997
-spec err_invalid_payload() -> ps_error().
3998
err_invalid_payload() ->
3999
    #ps_error{type = 'invalid-payload'}.
×
4000

4001
-spec err_invalid_subid() -> ps_error().
4002
err_invalid_subid() ->
4003
    #ps_error{type = 'invalid-subid'}.
×
4004

4005
-spec err_item_forbidden() -> ps_error().
4006
err_item_forbidden() ->
4007
    #ps_error{type = 'item-forbidden'}.
×
4008

4009
-spec err_item_required() -> ps_error().
4010
err_item_required() ->
4011
    #ps_error{type = 'item-required'}.
×
4012

4013
-spec err_jid_required() -> ps_error().
4014
err_jid_required() ->
4015
    #ps_error{type = 'jid-required'}.
×
4016

4017
-spec err_max_items_exceeded() -> ps_error().
4018
err_max_items_exceeded() ->
4019
    #ps_error{type = 'max-items-exceeded'}.
×
4020

4021
-spec err_max_nodes_exceeded() -> ps_error().
4022
err_max_nodes_exceeded() ->
4023
    #ps_error{type = 'max-nodes-exceeded'}.
×
4024

4025
-spec err_nodeid_required() -> ps_error().
4026
err_nodeid_required() ->
4027
    #ps_error{type = 'nodeid-required'}.
×
4028

4029
-spec err_not_in_roster_group() -> ps_error().
4030
err_not_in_roster_group() ->
4031
    #ps_error{type = 'not-in-roster-group'}.
×
4032

4033
-spec err_not_subscribed() -> ps_error().
4034
err_not_subscribed() ->
UNCOV
4035
    #ps_error{type = 'not-subscribed'}.
20✔
4036

4037
-spec err_payload_too_big() -> ps_error().
4038
err_payload_too_big() ->
4039
    #ps_error{type = 'payload-too-big'}.
×
4040

4041
-spec err_payload_required() -> ps_error().
4042
err_payload_required() ->
4043
    #ps_error{type = 'payload-required'}.
×
4044

4045
-spec err_pending_subscription() -> ps_error().
4046
err_pending_subscription() ->
4047
    #ps_error{type = 'pending-subscription'}.
×
4048

4049
-spec err_precondition_not_met() -> ps_error().
4050
err_precondition_not_met() ->
4051
    #ps_error{type = 'precondition-not-met'}.
×
4052

4053
-spec err_presence_subscription_required() -> ps_error().
4054
err_presence_subscription_required() ->
4055
    #ps_error{type = 'presence-subscription-required'}.
×
4056

4057
-spec err_subid_required() -> ps_error().
4058
err_subid_required() ->
4059
    #ps_error{type = 'subid-required'}.
×
4060

4061
-spec err_too_many_subscriptions() -> ps_error().
4062
err_too_many_subscriptions() ->
4063
    #ps_error{type = 'too-many-subscriptions'}.
×
4064

4065
-spec err_unsupported(ps_feature()) -> ps_error().
4066
err_unsupported(Feature) ->
4067
    #ps_error{type = 'unsupported', feature = Feature}.
×
4068

4069
-spec err_unsupported_access_model() -> ps_error().
4070
err_unsupported_access_model() ->
4071
    #ps_error{type = 'unsupported-access-model'}.
×
4072

4073
-spec uniqid() -> mod_pubsub:itemId().
4074
uniqid() ->
4075
    {T1, T2, T3} = erlang:timestamp(),
×
4076
    (str:format("~.16B~.16B~.16B", [T1, T2, T3])).
×
4077

4078
-spec add_message_type(message(), message_type()) -> message().
4079
add_message_type(#message{} = Message, Type) ->
UNCOV
4080
    Message#message{type = Type}.
594✔
4081

4082
%% Place of <headers/> changed at the bottom of the stanza
4083
%% cf. http://xmpp.org/extensions/xep-0060.html#publisher-publish-success-subid
4084
%%
4085
%% "[SHIM Headers] SHOULD be included after the event notification information
4086
%% (i.e., as the last child of the <message/> stanza)".
4087

4088
-spec add_shim_headers(stanza(), [{binary(), binary()}]) -> stanza().
4089
add_shim_headers(Stanza, Headers) ->
4090
    xmpp:set_subtag(Stanza, #shim{headers = Headers}).
×
4091

4092
-spec add_extended_headers(stanza(), [address()]) -> stanza().
4093
add_extended_headers(Stanza, Addrs) ->
UNCOV
4094
    xmpp:set_subtag(Stanza, #addresses{list = Addrs}).
24✔
4095

4096
-spec subid_shim([binary()]) -> [{binary(), binary()}].
4097
subid_shim(SubIds) ->
4098
    [{<<"SubId">>, SubId} || SubId <- SubIds].
×
4099

4100
%% The argument is a list of Jids because this function could be used
4101
%% with the 'pubsub#replyto' (type=jid-multi) node configuration.
4102

4103
-spec extended_headers([jid()]) -> [address()].
4104
extended_headers(Jids) ->
UNCOV
4105
    [#address{type = replyto, jid = Jid} || Jid <- Jids].
24✔
4106

4107
-spec purge_offline(jid()) -> ok.
4108
purge_offline(#jid{lserver = Host} = JID) ->
UNCOV
4109
    Plugins = plugins(Host),
1,824✔
UNCOV
4110
    Result = lists:foldl(
1,824✔
4111
               fun(Type, {Status, Acc}) ->
UNCOV
4112
                       Features = plugin_features(Host, Type),
3,648✔
UNCOV
4113
                       case lists:member(<<"retrieve-affiliations">>, plugin_features(Host, Type)) of
3,648✔
4114
                           false ->
4115
                               {{error, extended_error(xmpp:err_feature_not_implemented(),
×
4116
                                                       err_unsupported('retrieve-affiliations'))},
4117
                                Acc};
4118
                           true ->
UNCOV
4119
                               Items = lists:member(<<"retract-items">>, Features)
3,648✔
UNCOV
4120
                                   andalso lists:member(<<"persistent-items">>, Features),
3,648✔
UNCOV
4121
                               if Items ->
3,648✔
UNCOV
4122
                                       case node_action(Host, Type,
3,648✔
4123
                                                        get_entity_affiliations, [Host, JID]) of
4124
                                           {result, Affs} ->
UNCOV
4125
                                               {Status, [Affs | Acc]};
3,648✔
4126
                                           {error, _} = Err ->
4127
                                               {Err, Acc}
×
4128
                                       end;
4129
                                  true ->
4130
                                       {Status, Acc}
×
4131
                               end
4132
                       end
4133
               end, {ok, []}, Plugins),
UNCOV
4134
    case Result of
1,824✔
4135
        {ok, Affs} ->
UNCOV
4136
            lists:foreach(
1,824✔
4137
                    fun ({Node, Affiliation}) ->
UNCOV
4138
                            Options = Node#pubsub_node.options,
16✔
UNCOV
4139
                            Publisher = lists:member(Affiliation, [owner,publisher,publish_only]),
16✔
UNCOV
4140
                            Open = (get_option(Options, publish_model) == open),
16✔
UNCOV
4141
                            Purge = (get_option(Options, purge_offline)
16✔
4142
                                andalso get_option(Options, persist_items)),
×
UNCOV
4143
                            if (Publisher or Open) and Purge ->
16✔
4144
                                purge_offline(Host, JID, Node);
×
4145
                            true ->
UNCOV
4146
                                ok
16✔
4147
                            end
4148
                    end, lists:usort(lists:flatten(Affs)));
4149
        _ ->
4150
            ok
×
4151
    end.
4152

4153
-spec purge_offline(host(), jid(), #pubsub_node{}) -> ok | {error, stanza_error()}.
4154
purge_offline(Host, #jid{luser = User, lserver = Server, lresource = Resource} = JID, Node) ->
4155
    Nidx = Node#pubsub_node.id,
×
4156
    Type = Node#pubsub_node.type,
×
4157
    Options = Node#pubsub_node.options,
×
4158
    case node_action(Host, Type, get_items, [Nidx, service_jid(Host), undefined]) of
×
4159
        {result, {[], _}} ->
4160
            ok;
×
4161
        {result, {Items, _}} ->
4162
            PublishModel = get_option(Options, publish_model),
×
4163
            ForceNotify = get_option(Options, notify_retract),
×
4164
            {_, NodeId} = Node#pubsub_node.nodeid,
×
4165
            lists:foreach(
×
4166
              fun(#pubsub_item{itemid = {ItemId, _}, modification = {_, {U, S, R}}})
4167
                    when (U == User) and (S == Server) and (R == Resource) ->
4168
                      case node_action(Host, Type, delete_item, [Nidx, {U, S, <<>>}, PublishModel, ItemId]) of
×
4169
                          {result, {_, broadcast}} ->
4170
                              broadcast_retract_items(Host, JID, NodeId, Nidx, Type, Options, [ItemId], ForceNotify),
×
4171
                              case get_cached_item(Host, Nidx) of
×
4172
                                  #pubsub_item{itemid = {ItemId, Nidx}} -> unset_cached_item(Host, Nidx);
×
4173
                                  _ -> ok
×
4174
                              end;
4175
                          _ ->
4176
                              ok
×
4177
                      end;
4178
                 (_) ->
4179
                      true
×
4180
              end, Items);
4181
        {error, #stanza_error{}} = Err ->
4182
            Err;
×
4183
        _ ->
4184
            Txt = ?T("Database failure"),
×
4185
            Lang = ejabberd_option:language(),
×
4186
            {error, xmpp:err_internal_server_error(Txt, Lang)}
×
4187
    end.
4188

4189
-spec delete_old_items(non_neg_integer()) -> ok | error.
4190
delete_old_items(N) ->
4191
    Results = lists:flatmap(
×
4192
                fun(Host) ->
4193
                        case tree_action(Host, get_all_nodes, [Host]) of
×
4194
                            Nodes when is_list(Nodes) ->
4195
                                lists:map(
×
4196
                                  fun(#pubsub_node{id = Nidx, type = Type}) ->
4197
                                          case node_action(Host, Type,
×
4198
                                                           remove_extra_items,
4199
                                                           [Nidx, N]) of
4200
                                              {result, _} ->
4201
                                                  ok;
×
4202
                                              {error, _} ->
4203
                                                  error
×
4204
                                          end
4205
                                  end, Nodes);
4206
                            _ ->
4207
                                [error]
×
4208
                        end
4209
                end, ejabberd_option:hosts()),
4210
    case lists:member(error, Results) of
×
4211
        true ->
4212
            error;
×
4213
        false ->
4214
            ok
×
4215
    end.
4216

4217
-spec delete_expired_items() -> ok | error.
4218
delete_expired_items() ->
4219
    Results = lists:flatmap(
×
4220
                fun(Host) ->
4221
                        case tree_action(Host, get_all_nodes, [Host]) of
×
4222
                            Nodes when is_list(Nodes) ->
4223
                                lists:map(
×
4224
                                  fun(#pubsub_node{id = Nidx, type = Type,
4225
                                                   options = Options}) ->
4226
                                          case item_expire(Host, Options) of
×
4227
                                              infinity ->
4228
                                                  ok;
×
4229
                                              Seconds ->
4230
                                                  case node_action(
×
4231
                                                         Host, Type,
4232
                                                         remove_expired_items,
4233
                                                         [Nidx, Seconds]) of
4234
                                                      {result, []} ->
4235
                                                          ok;
×
4236
                                                      {result, [_|_]} ->
4237
                                                          unset_cached_item(
×
4238
                                                            Host, Nidx);
4239
                                                      {error, _} ->
4240
                                                          error
×
4241
                                                  end
4242
                                          end
4243
                                  end, Nodes);
4244
                            _ ->
4245
                                [error]
×
4246
                        end
4247
                end, ejabberd_option:hosts()),
4248
    case lists:member(error, Results) of
×
4249
        true ->
4250
            error;
×
4251
        false ->
4252
            ok
×
4253
    end.
4254

4255
-spec get_commands_spec() -> [ejabberd_commands()].
4256
get_commands_spec() ->
UNCOV
4257
    [#ejabberd_commands{name = delete_old_pubsub_items, tags = [purge],
16✔
4258
                        desc = "Keep only NUMBER of PubSub items per node",
4259
                        note = "added in 21.12",
4260
                        module = ?MODULE, function = delete_old_items,
4261
                        args_desc = ["Number of items to keep per node"],
4262
                        args = [{number, integer}],
4263
                        result = {res, rescode},
4264
                        result_desc = "0 if command failed, 1 when succeeded",
4265
                        args_example = [1000],
4266
                        result_example = ok},
4267
     #ejabberd_commands{name = delete_expired_pubsub_items, tags = [purge],
4268
                        desc = "Delete expired PubSub items",
4269
                        note = "added in 21.12",
4270
                        module = ?MODULE, function = delete_expired_items,
4271
                        args = [],
4272
                        result = {res, rescode},
4273
                        result_desc = "0 if command failed, 1 when succeeded",
4274
                        result_example = ok}].
4275

4276
-spec mod_opt_type(atom()) -> econf:validator().
4277
mod_opt_type(access_createnode) ->
UNCOV
4278
    econf:acl();
8✔
4279
mod_opt_type(name) ->
UNCOV
4280
    econf:binary();
8✔
4281
mod_opt_type(ignore_pep_from_offline) ->
UNCOV
4282
    econf:bool();
8✔
4283
mod_opt_type(last_item_cache) ->
UNCOV
4284
    econf:bool();
8✔
4285
mod_opt_type(max_items_node) ->
UNCOV
4286
    econf:non_neg_int(unlimited);
8✔
4287
mod_opt_type(max_item_expire_node) ->
UNCOV
4288
    econf:timeout(second, infinity);
8✔
4289
mod_opt_type(max_nodes_discoitems) ->
UNCOV
4290
    econf:non_neg_int(infinity);
8✔
4291
mod_opt_type(max_subscriptions_node) ->
UNCOV
4292
    econf:non_neg_int();
8✔
4293
mod_opt_type(force_node_config) ->
UNCOV
4294
    econf:map(
8✔
4295
      econf:glob(),
4296
      econf:map(
4297
        econf:atom(),
4298
        econf:either(
4299
          econf:int(),
4300
          econf:atom()),
4301
        [{return, orddict}, unique]));
4302
mod_opt_type(default_node_config) ->
UNCOV
4303
    econf:map(
8✔
4304
      econf:atom(),
4305
      econf:either(
4306
        econf:int(),
4307
        econf:atom()),
4308
      [unique]);
4309
mod_opt_type(nodetree) ->
UNCOV
4310
    econf:binary();
8✔
4311
mod_opt_type(pep_mapping) ->
UNCOV
4312
    econf:map(econf:binary(), econf:binary());
8✔
4313
mod_opt_type(plugins) ->
UNCOV
4314
    econf:list(
8✔
4315
      econf:enum([<<"flat">>, <<"pep">>]),
4316
      [unique]);
4317
mod_opt_type(host) ->
UNCOV
4318
    econf:host();
8✔
4319
mod_opt_type(hosts) ->
UNCOV
4320
    econf:hosts();
8✔
4321
mod_opt_type(db_type) ->
UNCOV
4322
    econf:db_type(?MODULE);
8✔
4323
mod_opt_type(vcard) ->
UNCOV
4324
    econf:vcard_temp().
8✔
4325

4326
mod_options(Host) ->
UNCOV
4327
    [{access_createnode, all},
8✔
4328
     {db_type, ejabberd_config:default_db(Host, ?MODULE)},
4329
     {host, <<"pubsub.", Host/binary>>},
4330
     {hosts, []},
4331
     {name, ?T("Publish-Subscribe")},
4332
     {vcard, undefined},
4333
     {ignore_pep_from_offline, true},
4334
     {last_item_cache, false},
4335
     {max_items_node, ?MAXITEMS},
4336
     {max_item_expire_node, infinity},
4337
     {max_nodes_discoitems, 100},
4338
     {nodetree, ?STDTREE},
4339
     {pep_mapping, []},
4340
     {plugins, [?STDNODE]},
4341
     {max_subscriptions_node, undefined},
4342
     {default_node_config, []},
4343
     {force_node_config, []}].
4344

4345
mod_doc() ->
4346
    #{desc =>
×
4347
          [?T("This module offers a service for "
4348
              "https://xmpp.org/extensions/xep-0060.html"
4349
              "[XEP-0060: Publish-Subscribe]. The functionality in "
4350
              "'mod_pubsub' can be extended using plugins. "
4351
              "The plugin that implements PEP "
4352
              "(https://xmpp.org/extensions/xep-0163.html"
4353
              "[XEP-0163: Personal Eventing via Pubsub]) "
4354
              "is enabled in the default ejabberd configuration file, "
4355
              "and it requires _`mod_caps`_.")],
4356
      opts =>
4357
          [{access_createnode,
4358
            #{value => "AccessName",
4359
              desc =>
4360
                  ?T("This option restricts which users are allowed to "
4361
                     "create pubsub nodes using 'acl' and 'access'. "
4362
                     "By default any account in the local ejabberd server "
4363
                     "is allowed to create pubsub nodes. "
4364
                     "The default value is: 'all'.")}},
4365
           {db_type,
4366
            #{value => "mnesia | sql",
4367
              desc =>
4368
                  ?T("Same as top-level _`default_db`_ option, but applied to "
4369
                     "this module only.")}},
4370
           {default_node_config,
4371
            #{value => "List of Key:Value",
4372
              desc =>
4373
                  ?T("To override default node configuration, regardless "
4374
                     "of node plugin. Value is a list of key-value "
4375
                     "definition. Node configuration still uses default "
4376
                     "configuration defined by node plugin, and overrides "
4377
                     "any items by value defined in this configurable list.")}},
4378
           {force_node_config,
4379
            #{value => "List of Node and the list of its Key:Value",
4380
              desc =>
4381
                  ?T("Define the configuration for given nodes. "
4382
                     "The default value is: '[]'."),
4383
              example =>
4384
                  ["force_node_config:",
4385
                   "  ## Avoid buggy clients to make their bookmarks public",
4386
                   "  storage:bookmarks:",
4387
                   "    access_model: whitelist"]}},
4388
           {host,
4389
            #{desc => ?T("Deprecated. Use 'hosts' instead.")}},
4390
           {hosts,
4391
            #{value => ?T("[Host, ...]"),
4392
              desc =>
4393
                  ?T("This option defines the Jabber IDs of the service. "
4394
                     "If the 'hosts' option is not specified, the only Jabber "
4395
                     "ID will be the hostname of the virtual host with the "
4396
                     "prefix \"pubsub.\". The keyword '@HOST@' is replaced "
4397
                     "with the real virtual host name.")}},
4398
           {ignore_pep_from_offline,
4399
            #{value => "false | true",
4400
              desc =>
4401
                  ?T("To specify whether or not we should get last "
4402
                     "published PEP items from users in our roster which "
4403
                     "are offline when we connect. Value is 'true' or "
4404
                     "'false'. If not defined, pubsub assumes true so we "
4405
                     "only get last items of online contacts.")}},
4406
           {last_item_cache,
4407
            #{value => "false | true",
4408
              desc =>
4409
                  ?T("To specify whether or not pubsub should cache last "
4410
                     "items. Value is 'true' or 'false'. If not defined, "
4411
                     "pubsub does not cache last items. On systems with not"
4412
                     " so many nodes, caching last items speeds up pubsub "
4413
                     "and allows you to raise the user connection rate. The cost "
4414
                     "is memory usage, as every item is stored in memory.")}},
4415
           {max_item_expire_node,
4416
            #{value => "timeout() | infinity",
4417
              note => "added in 21.12",
4418
              desc =>
4419
                  ?T("Specify the maximum item epiry time. Default value "
4420
                     "is: 'infinity'.")}},
4421
           {max_items_node,
4422
            #{value => "non_neg_integer() | infinity",
4423
              desc =>
4424
                  ?T("Define the maximum number of items that can be "
4425
                     "stored in a node. Default value is: '1000'.")}},
4426
           {max_nodes_discoitems,
4427
            #{value => "pos_integer() | infinity",
4428
              desc =>
4429
                  ?T("The maximum number of nodes to return in a "
4430
                     "discoitem response. The default value is: '100'.")}},
4431
           {max_subscriptions_node,
4432
            #{value => "MaxSubs",
4433
              desc =>
4434
                  ?T("Define the maximum number of subscriptions managed "
4435
                     "by a node. "
4436
                     "Default value is no limitation: 'undefined'.")}},
4437
           {name,
4438
            #{value => ?T("Name"),
4439
              desc =>
4440
                  ?T("The value of the service name. This name is only visible "
4441
                     "in some clients that support "
4442
                     "https://xmpp.org/extensions/xep-0030.html"
4443
                     "[XEP-0030: Service Discovery]. "
4444
                     "The default is 'vCard User Search'.")}},
4445
           {nodetree,
4446
            #{value => "Nodetree",
4447
              desc =>
4448
                  [?T("To specify which nodetree to use. If not defined, the "
4449
                      "default pubsub nodetree is used: 'tree'. Only one "
4450
                      "nodetree can be used per host, and is shared by all "
4451
                      "node plugins."),
4452
                   ?T("- 'tree' nodetree store node configuration and "
4453
                      "relations on the database. 'flat' nodes are stored "
4454
                      "without any relationship, and 'hometree' nodes can "
4455
                      "have child nodes."),
4456
                   ?T("- 'virtual' nodetree does not store nodes on database. "
4457
                      "This saves resources on systems with tons of nodes. "
4458
                      "If using the 'virtual' nodetree, you can only enable "
4459
                      "those node plugins: '[flat, pep]' or '[flat]'; any "
4460
                      "other plugins configuration will not work. Also, all "
4461
                      "nodes will have the default configuration, and this "
4462
                      "can not be changed. Using 'virtual' nodetree requires "
4463
                      "to start from a clean database, it will not work if "
4464
                      "you used the default 'tree' nodetree before.")]}},
4465
           {pep_mapping,
4466
            #{value => "List of Key:Value",
4467
              desc =>
4468
                  ?T("In this option you can provide a list of key-value to choose "
4469
                     "defined node plugins on given PEP namespace. "
4470
                     "The following example will use 'node_tune' instead of "
4471
                     "'node_pep' for every PEP node with the tune namespace:"),
4472
              example =>
4473
                    ["modules:",
4474
                     "  ...",
4475
                     "  mod_pubsub:",
4476
                     "    pep_mapping:",
4477
                     "      http://jabber.org/protocol/tune: tune",
4478
                     "  ..."]
4479
                   }},
4480
           {plugins,
4481
            #{value => "[Plugin, ...]",
4482
              desc => [?T("To specify which pubsub node plugins to use. "
4483
                          "The first one in the list is used by default. "
4484
                          "If this option is not defined, the default plugins "
4485
                          "list is: '[flat]'. PubSub clients can define which "
4486
                          "plugin to use when creating a node: "
4487
                          "add 'type=\'plugin-name\'' attribute "
4488
                          "to the 'create' stanza element."),
4489
                       ?T("- 'flat' plugin handles the default behaviour and "
4490
                          "follows standard XEP-0060 implementation."),
4491
                       ?T("- 'pep' plugin adds extension to handle Personal "
4492
                          "Eventing Protocol (XEP-0163) to the PubSub engine. "
4493
                          "When enabled, PEP is handled automatically.")]}},
4494
           {vcard,
4495
            #{value => ?T("vCard"),
4496
              desc =>
4497
                  ?T("A custom vCard of the server that will be displayed by "
4498
                     "some XMPP clients in Service Discovery. The value of "
4499
                     "'vCard' is a YAML map constructed from an XML "
4500
                     "representation of vCard. Since the representation has "
4501
                     "no attributes, the mapping is straightforward."),
4502
              example =>
4503
                  ["# This XML representation of vCard:",
4504
                   "#   <vCard xmlns='vcard-temp'>",
4505
                   "#     <FN>Conferences</FN>",
4506
                   "#     <ADR>",
4507
                   "#       <WORK/>",
4508
                   "#       <STREET>Elm Street</STREET>",
4509
                   "#     </ADR>",
4510
                   "#   </vCard>",
4511
                   "# ",
4512
                   "# is translated to:",
4513
                   "vcard:",
4514
                   "  fn: Conferences",
4515
                   "  adr:",
4516
                   "    -",
4517
                   "      work: true",
4518
                   "      street: Elm Street"]}}
4519
          ],
4520
      example =>
4521
          [{?T("Example of configuration that uses flat nodes as default, "
4522
               "and allows use of flat, hometree and pep nodes:"),
4523
            ["modules:",
4524
             "  mod_pubsub:",
4525
             "    access_createnode: pubsub_createnode",
4526
             "    max_subscriptions_node: 100",
4527
             "    default_node_config:",
4528
             "      notification_type: normal",
4529
             "      notify_retract: false",
4530
             "      max_items: 4",
4531
             "    plugins:",
4532
             "      - flat",
4533
             "      - pep"]},
4534
           {?T("Using relational database requires using mod_pubsub with "
4535
               "db_type 'sql'. Only flat, hometree and pep plugins supports "
4536
               "SQL. The following example shows previous configuration "
4537
               "with SQL usage:"),
4538
            ["modules:",
4539
             "  mod_pubsub:",
4540
             "    db_type: sql",
4541
             "    access_createnode: pubsub_createnode",
4542
             "    ignore_pep_from_offline: true",
4543
             "    last_item_cache: false",
4544
             "    plugins:",
4545
             "      - flat",
4546
             "      - pep"]}
4547
          ]}.
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc