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

processone / ejabberd / 603

17 Oct 2023 01:57PM UTC coverage: 32.654% (-0.4%) from 33.021%
603

push

github

badlop
Fixing minor typos in CHANGELOG

13497 of 41333 relevant lines covered (32.65%)

646.75 hits per line

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

66.3
/src/node_flat_sql.erl
1
%%%----------------------------------------------------------------------
2
%%% File    : node_flat_sql.erl
3
%%% Author  : Christophe Romain <christophe.romain@process-one.net>
4
%%% Purpose : Standard PubSub node plugin with ODBC backend
5
%%% Created :  1 Dec 2007 by Christophe Romain <christophe.romain@process-one.net>
6
%%%
7
%%%
8
%%% ejabberd, Copyright (C) 2002-2023   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
%%% @doc The module <strong>{@module}</strong> is the default PubSub plugin.
27
%%% <p>It is used as a default for all unknown PubSub node type.  It can serve
28
%%% as a developer basis and reference to build its own custom pubsub node
29
%%% types.</p>
30
%%% <p>PubSub plugin nodes are using the {@link gen_node} behaviour.</p>
31

32
-module(node_flat_sql).
33
-behaviour(gen_pubsub_node).
34
-author('christophe.romain@process-one.net').
35

36

37
-include("pubsub.hrl").
38
-include_lib("xmpp/include/xmpp.hrl").
39
-include("ejabberd_sql_pt.hrl").
40
-include("translate.hrl").
41

42
-export([init/3, terminate/2, options/0, features/0,
43
    create_node_permission/6, create_node/2, delete_node/1, purge_node/2,
44
    subscribe_node/8, unsubscribe_node/4,
45
    publish_item/7, delete_item/4,
46
    remove_extra_items/2, remove_extra_items/3, remove_expired_items/2,
47
    get_entity_affiliations/2, get_node_affiliations/1,
48
    get_affiliation/2, set_affiliation/3,
49
    get_entity_subscriptions/2, get_node_subscriptions/1,
50
    get_subscriptions/2, set_subscriptions/4,
51
    get_pending_nodes/2, get_states/1, get_state/2,
52
    set_state/1, get_items/7, get_items/3, get_item/7,
53
    get_item/2, set_item/1, get_item_name/3, node_to_path/1,
54
    path_to_node/1,
55
    get_entity_subscriptions_for_send_last/2, get_last_items/3,
56
    get_only_item/2]).
57

58
-export([decode_jid/1, encode_jid/1, encode_jid_like/1,
59
         decode_affiliation/1, decode_subscriptions/1,
60
         encode_affiliation/1, encode_subscriptions/1,
61
         encode_host/1, encode_host_like/1]).
62

63
init(_Host, _ServerHost, _Opts) ->
64
    %%pubsub_subscription_sql:init(Host, ServerHost, Opts),
65
    ok.
6✔
66

67
terminate(_Host, _ServerHost) ->
68
    ok.
6✔
69

70
options() ->
71
    [{sql, true}, {rsm, true} | node_flat:options()].
1,080✔
72

73
features() ->
74
    [<<"rsm">> | node_flat:features()].
2,652✔
75

76
create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) ->
77
    node_flat:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access).
90✔
78

79
create_node(Nidx, Owner) ->
80
    {_U, _S, _R} = OwnerKey = jid:tolower(jid:remove_resource(Owner)),
96✔
81
    J = encode_jid(OwnerKey),
96✔
82
    A = encode_affiliation(owner),
96✔
83
    S = encode_subscriptions([]),
96✔
84
    ejabberd_sql:sql_query_t(
96✔
85
      ?SQL("insert into pubsub_state("
130✔
86
           "nodeid, jid, affiliation, subscriptions) "
87
           "values (%(Nidx)d, %(J)s, %(A)s, %(S)s)")),
88
    {result, {default, broadcast}}.
96✔
89

90
delete_node(Nodes) ->
91
    Reply = lists:map(
96✔
92
              fun(#pubsub_node{id = Nidx} = PubsubNode) ->
93
                      Subscriptions =
96✔
94
                          case ejabberd_sql:sql_query_t(
95
                                 ?SQL("select @(jid)s, @(subscriptions)s "
128✔
96
                                      "from pubsub_state where nodeid=%(Nidx)d")) of
97
                              {selected, RItems} ->
98
                                  [{decode_jid(SJID), decode_subscriptions(Subs)}
96✔
99
                                   || {SJID, Subs} <- RItems];
96✔
100
                              _ ->
101
                                  []
×
102
                          end,
103
                      {PubsubNode, Subscriptions}
96✔
104
              end, Nodes),
105
    {result, {default, broadcast, Reply}}.
96✔
106

107
subscribe_node(Nidx, Sender, Subscriber, AccessModel,
108
            SendLast, PresenceSubscription, RosterGroup, _Options) ->
109
    SubKey = jid:tolower(Subscriber),
51✔
110
    GenKey = jid:remove_resource(SubKey),
51✔
111
    Authorized = jid:tolower(jid:remove_resource(Sender)) == GenKey,
51✔
112
    {Affiliation, Subscriptions} = select_affiliation_subscriptions(Nidx, GenKey, SubKey),
51✔
113
    Whitelisted = lists:member(Affiliation, [member, publisher, owner]),
51✔
114
    PendingSubscription = lists:any(fun
51✔
115
                ({pending, _}) -> true;
×
116
                (_) -> false
×
117
            end,
118
            Subscriptions),
119
    Owner = Affiliation == owner,
51✔
120
    if not Authorized ->
51✔
121
            {error, mod_pubsub:extended_error(
×
122
                      xmpp:err_bad_request(), mod_pubsub:err_invalid_jid())};
123
        (Affiliation == outcast) or (Affiliation == publish_only) ->
124
            {error, xmpp:err_forbidden()};
6✔
125
        PendingSubscription ->
126
            {error, mod_pubsub:extended_error(
×
127
                      xmpp:err_not_authorized(),
128
                      mod_pubsub:err_pending_subscription())};
129
        (AccessModel == presence) and (not PresenceSubscription) and (not Owner) ->
130
            {error, mod_pubsub:extended_error(
×
131
                      xmpp:err_not_authorized(),
132
                      mod_pubsub:err_presence_subscription_required())};
133
        (AccessModel == roster) and (not RosterGroup) and (not Owner) ->
134
            {error, mod_pubsub:extended_error(
×
135
                      xmpp:err_not_authorized(),
136
                      mod_pubsub:err_not_in_roster_group())};
137
        (AccessModel == whitelist) and (not Whitelisted) and (not Owner) ->
138
            {error, mod_pubsub:extended_error(
×
139
                      xmpp:err_not_allowed(), mod_pubsub:err_closed_node())};
140
        %%MustPay ->
141
        %%        % Payment is required for a subscription
142
        %%        {error, ?ERR_PAYMENT_REQUIRED};
143
        %%ForbiddenAnonymous ->
144
        %%        % Requesting entity is anonymous
145
        %%        {error, ?ERR_FORBIDDEN};
146
        true ->
147
            %%{result, SubId} = pubsub_subscription_sql:subscribe_node(Subscriber, Nidx, Options),
148
            {NewSub, SubId} = case Subscriptions of
45✔
149
                [{subscribed, Id}|_] ->
150
                    {subscribed, Id};
×
151
                [] ->
152
                    Id = pubsub_subscription_sql:make_subid(),
45✔
153
                    Sub = case AccessModel of
45✔
154
                        authorize -> pending;
6✔
155
                        _ -> subscribed
39✔
156
                    end,
157
                    update_subscription(Nidx, SubKey, [{Sub, Id} | Subscriptions]),
45✔
158
                    {Sub, Id}
45✔
159
            end,
160
            case {NewSub, SendLast} of
45✔
161
                {subscribed, never} ->
162
                    {result, {default, subscribed, SubId}};
12✔
163
                {subscribed, _} ->
164
                    {result, {default, subscribed, SubId, send_last}};
27✔
165
                {_, _} ->
166
                    {result, {default, pending, SubId}}
6✔
167
            end
168
    end.
169

170
unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
171
    SubKey = jid:tolower(Subscriber),
21✔
172
    GenKey = jid:remove_resource(SubKey),
21✔
173
    Authorized = jid:tolower(jid:remove_resource(Sender)) == GenKey,
21✔
174
    {Affiliation, Subscriptions} = select_affiliation_subscriptions(Nidx, SubKey),
21✔
175
    SubIdExists = case SubId of
21✔
176
        <<>> -> false;
21✔
177
        Binary when is_binary(Binary) -> true;
×
178
        _ -> false
×
179
    end,
180
    if
21✔
181
        %% Requesting entity is prohibited from unsubscribing entity
182
        not Authorized ->
183
            {error, xmpp:err_forbidden()};
×
184
        %% Entity did not specify SubId
185
        %%SubId == "", ?? ->
186
        %%        {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
187
        %% Invalid subscription identifier
188
        %%InvalidSubId ->
189
        %%        {error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
190
        %% Requesting entity is not a subscriber
191
        Subscriptions == [] ->
192
            {error, mod_pubsub:extended_error(
6✔
193
                      xmpp:err_unexpected_request(),
194
                      mod_pubsub:err_not_subscribed())};
195
        %% Subid supplied, so use that.
196
        SubIdExists ->
197
            Sub = first_in_list(fun
×
198
                        ({_, S}) when S == SubId -> true;
×
199
                        (_) -> false
×
200
                    end,
201
                    Subscriptions),
202
            case Sub of
×
203
                {value, S} ->
204
                    delete_subscription(SubKey, Nidx, S, Affiliation, Subscriptions),
×
205
                    {result, default};
×
206
                false ->
207
                    {error, mod_pubsub:extended_error(
×
208
                              xmpp:err_unexpected_request(),
209
                              mod_pubsub:err_not_subscribed())}
210
            end;
211
        %% Asking to remove all subscriptions to the given node
212
        SubId == all ->
213
            [delete_subscription(SubKey, Nidx, S, Affiliation, Subscriptions)
×
214
                || S <- Subscriptions],
×
215
            {result, default};
×
216
        %% No subid supplied, but there's only one matching subscription
217
        length(Subscriptions) == 1 ->
218
            delete_subscription(SubKey, Nidx, hd(Subscriptions), Affiliation, Subscriptions),
15✔
219
            {result, default};
15✔
220
        %% No subid and more than one possible subscription match.
221
        true ->
222
            {error, mod_pubsub:extended_error(
×
223
                      xmpp:err_bad_request(), mod_pubsub:err_subid_required())}
224
    end.
225

226
delete_subscription(SubKey, Nidx, {Subscription, SubId}, Affiliation, Subscriptions) ->
227
    NewSubs = Subscriptions -- [{Subscription, SubId}],
15✔
228
    %%pubsub_subscription_sql:unsubscribe_node(SubKey, Nidx, SubId),
229
    case {Affiliation, NewSubs} of
15✔
230
        {none, []} -> del_state(Nidx, SubKey);
15✔
231
        _ -> update_subscription(Nidx, SubKey, NewSubs)
×
232
    end.
233

234
publish_item(Nidx, Publisher, PublishModel, MaxItems, ItemId, Payload,
235
             _PubOpts) ->
236
    SubKey = jid:tolower(Publisher),
99✔
237
    GenKey = jid:remove_resource(SubKey),
99✔
238
    {Affiliation, Subscriptions} = select_affiliation_subscriptions(Nidx, GenKey, SubKey),
99✔
239
    Subscribed = case PublishModel of
99✔
240
        subscribers -> node_flat:is_subscribed(Subscriptions);
×
241
        _ -> undefined
99✔
242
    end,
243
    if not ((PublishModel == open) or
99✔
244
                    (PublishModel == publishers) and
245
                    ((Affiliation == owner)
246
                        or (Affiliation == publisher)
247
                        or (Affiliation == publish_only))
248
                    or (Subscribed == true)) ->
249
            {error, xmpp:err_forbidden()};
9✔
250
        true ->
251
            if MaxItems > 0;
90✔
252
               MaxItems == unlimited ->
253
                    Now = erlang:timestamp(),
90✔
254
                    case get_item(Nidx, ItemId) of
90✔
255
                        {result, #pubsub_item{creation = {_, GenKey}} = OldItem} ->
256
                            set_item(OldItem#pubsub_item{
3✔
257
                                        modification = {Now, SubKey},
258
                                        payload = Payload}),
259
                            {result, {default, broadcast, []}};
3✔
260
                        % Allow node owner to modify any item, he can also delete it and recreate
261
                        {result, #pubsub_item{creation = {CreationTime, _}} = OldItem} when Affiliation == owner->
262
                            set_item(OldItem#pubsub_item{
×
263
                                creation = {CreationTime, GenKey},
264
                                modification = {Now, SubKey},
265
                                payload = Payload}),
266
                            {result, {default, broadcast, []}};
×
267
                        {result, _} ->
268
                            {error, xmpp:err_forbidden()};
×
269
                        _ ->
270
                            OldIds = maybe_remove_extra_items(Nidx, MaxItems,
87✔
271
                                                              GenKey, ItemId),
272
                            set_item(#pubsub_item{
87✔
273
                                        itemid = {ItemId, Nidx},
274
                                        creation = {Now, GenKey},
275
                                        modification = {Now, SubKey},
276
                                        payload = Payload}),
277
                            {result, {default, broadcast, OldIds}}
87✔
278
                    end;
279
                true ->
280
                    {result, {default, broadcast, []}}
×
281
            end
282
    end.
283

284
remove_extra_items(Nidx, MaxItems) ->
285
    remove_extra_items(Nidx, MaxItems, itemids(Nidx)).
×
286

287
remove_extra_items(_Nidx, unlimited, ItemIds) ->
288
    {result, {ItemIds, []}};
×
289
remove_extra_items(Nidx, MaxItems, ItemIds) ->
290
    NewItems = lists:sublist(ItemIds, MaxItems),
87✔
291
    OldItems = lists:nthtail(length(NewItems), ItemIds),
87✔
292
    del_items(Nidx, OldItems),
87✔
293
    {result, {NewItems, OldItems}}.
87✔
294

295
remove_expired_items(_Nidx, infinity) ->
296
    {result, []};
×
297
remove_expired_items(Nidx, Seconds) ->
298
    ExpT = encode_now(
×
299
             misc:usec_to_now(
300
               erlang:system_time(microsecond) - (Seconds * 1000000))),
301
    case ejabberd_sql:sql_query_t(
×
302
           ?SQL("select @(itemid)s from pubsub_item where nodeid=%(Nidx)d "
×
303
                "and creation < %(ExpT)s")) of
304
        {selected, RItems} ->
305
            ItemIds = [ItemId || {ItemId} <- RItems],
×
306
            del_items(Nidx, ItemIds),
×
307
            {result, ItemIds};
×
308
        _ ->
309
            {result, []}
×
310
    end.
311

312
delete_item(Nidx, Publisher, PublishModel, ItemId) ->
313
    SubKey = jid:tolower(Publisher),
24✔
314
    GenKey = jid:remove_resource(SubKey),
24✔
315
    {result, Affiliation} = get_affiliation(Nidx, GenKey),
24✔
316
    Allowed = Affiliation == publisher orelse
24✔
317
        Affiliation == owner orelse
21✔
318
        (PublishModel == open andalso
12✔
319
          case get_item(Nidx, ItemId) of
×
320
            {result, #pubsub_item{creation = {_, GenKey}}} -> true;
×
321
            _ -> false
×
322
          end),
323
    if not Allowed ->
24✔
324
            {error, xmpp:err_forbidden()};
12✔
325
        true ->
326
            Items = itemids(Nidx, GenKey),
12✔
327
            case lists:member(ItemId, Items) of
12✔
328
                true ->
329
                    case del_item(Nidx, ItemId) of
9✔
330
                        {updated, 1} -> {result, {default, broadcast}};
9✔
331
                        _ -> {error, xmpp:err_item_not_found()}
×
332
                    end;
333
                false ->
334
                    case Affiliation of
3✔
335
                        owner ->
336
                            case del_item(Nidx, ItemId) of
3✔
337
                                {updated, 1} -> {result, {default, broadcast}};
3✔
338
                                _ -> {error, xmpp:err_item_not_found()}
×
339
                            end;
340
                        _ ->
341
                            {error, xmpp:err_forbidden()}
×
342
                    end
343
            end
344
    end.
345

346
purge_node(Nidx, Owner) ->
347
    SubKey = jid:tolower(Owner),
21✔
348
    GenKey = jid:remove_resource(SubKey),
21✔
349
    GenState = get_state(Nidx, GenKey),
21✔
350
    case GenState of
21✔
351
        #pubsub_state{affiliation = owner} ->
352
            {result, States} = get_states(Nidx),
6✔
353
            lists:foreach(fun
6✔
354
                    (#pubsub_state{items = []}) -> ok;
6✔
355
                    (#pubsub_state{items = Items}) -> del_items(Nidx, Items)
3✔
356
                end,
357
                States),
358
            {result, {default, broadcast}};
6✔
359
        _ ->
360
            {error, xmpp:err_forbidden()}
15✔
361
    end.
362

363
get_entity_affiliations(Host, Owner) ->
364
    SubKey = jid:tolower(Owner),
1,338✔
365
    GenKey = jid:remove_resource(SubKey),
1,338✔
366
    H = encode_host(Host),
1,338✔
367
    J = encode_jid(GenKey),
1,338✔
368
    {result,
1,338✔
369
     case ejabberd_sql:sql_query_t(
370
            ?SQL("select @(node)s, @(plugin)s, @(i.nodeid)d, @(affiliation)s "
1,786✔
371
                 "from pubsub_state i, pubsub_node n where "
372
                 "i.nodeid = n.nodeid and jid=%(J)s and host=%(H)s")) of
373
         {selected, RItems} ->
374
             [{nodetree_tree_sql:raw_to_node(Host, {N, <<"">>, T, I}),
1,338✔
375
               decode_affiliation(A)} || {N, T, I, A} <- RItems];
1,338✔
376
         _ ->
377
             []
×
378
     end}.
379

380
get_node_affiliations(Nidx) ->
381
    {result,
186✔
382
     case ejabberd_sql:sql_query_t(
383
            ?SQL("select @(jid)s, @(affiliation)s from pubsub_state "
250✔
384
                 "where nodeid=%(Nidx)d")) of
385
         {selected, RItems} ->
386
             [{decode_jid(J), decode_affiliation(A)} || {J, A} <- RItems];
186✔
387
         _ ->
388
             []
×
389
     end}.
390

391
get_affiliation(Nidx, Owner) ->
392
    SubKey = jid:tolower(Owner),
255✔
393
    GenKey = jid:remove_resource(SubKey),
255✔
394
    J = encode_jid(GenKey),
255✔
395
    {result,
255✔
396
     case ejabberd_sql:sql_query_t(
397
            ?SQL("select @(affiliation)s from pubsub_state "
342✔
398
                 "where nodeid=%(Nidx)d and jid=%(J)s")) of
399
         {selected, [{A}]} ->
400
             decode_affiliation(A);
237✔
401
         _ ->
402
             none
18✔
403
     end}.
404

405
set_affiliation(Nidx, Owner, Affiliation) ->
406
    SubKey = jid:tolower(Owner),
24✔
407
    GenKey = jid:remove_resource(SubKey),
24✔
408
    {_, Subscriptions} = select_affiliation_subscriptions(Nidx, GenKey),
24✔
409
    case {Affiliation, Subscriptions} of
24✔
410
        {none, []} -> {result, del_state(Nidx, GenKey)};
3✔
411
        _ -> {result, update_affiliation(Nidx, GenKey, Affiliation)}
21✔
412
    end.
413

414
get_entity_subscriptions(Host, Owner) ->
415
    SubKey = jid:tolower(Owner),
291✔
416
    GenKey = jid:remove_resource(SubKey),
291✔
417
    H = encode_host(Host),
291✔
418
    GJ = encode_jid(GenKey),
291✔
419
    Query = case SubKey of
291✔
420
              GenKey ->
421
                GJLike = <<(encode_jid_like(GenKey))/binary, "/%">>,
204✔
422
                ?SQL("select @(node)s, @(plugin)s, @(i.nodeid)d, @(jid)s, @(subscriptions)s "
204✔
423
                     "from pubsub_state i, pubsub_node n "
424
                     "where i.nodeid = n.nodeid and "
425
                     "(jid=%(GJ)s or jid like %(GJLike)s %ESCAPE) and host=%(H)s");
426
              _ ->
427
                SJ = encode_jid(SubKey),
87✔
428
                ?SQL("select @(node)s, @(plugin)s, @(i.nodeid)d, @(jid)s, @(subscriptions)s "
87✔
429
                     "from pubsub_state i, pubsub_node n "
430
                     "where i.nodeid = n.nodeid and "
431
                     "jid in (%(SJ)s, %(GJ)s) and host=%(H)s")
432
            end,
433
    {result,
291✔
434
     case ejabberd_sql:sql_query_t(Query) of
435
         {selected, RItems} ->
436
             lists:foldl(
291✔
437
               fun({N, T, I, J, S}, Acc) ->
438
                       Node = nodetree_tree_sql:raw_to_node(Host, {N, <<"">>, T, I}),
51✔
439
                       Jid = decode_jid(J),
51✔
440
                       lists:foldl(
51✔
441
                         fun({Sub, SubId}, Acc2) ->
442
                             [{Node, Sub, SubId, Jid} | Acc2]
24✔
443
                         end, Acc, decode_subscriptions(S))
444
               end, [], RItems);
445
         _ ->
446
             []
×
447
     end}.
448

449
-spec get_entity_subscriptions_for_send_last(Host :: mod_pubsub:hostPubsub(),
450
                                             Owner :: jid()) ->
451
                                                    {result, [{mod_pubsub:pubsubNode(),
452
                                                               mod_pubsub:subscription(),
453
                                                               mod_pubsub:subId(),
454
                                                               ljid()}]}.
455

456
get_entity_subscriptions_for_send_last(Host, Owner) ->
457
    SubKey = jid:tolower(Owner),
×
458
    GenKey = jid:remove_resource(SubKey),
×
459
    H = encode_host(Host),
×
460
    GJ = encode_jid(GenKey),
×
461
    Query = case SubKey of
×
462
              GenKey ->
463
                GJLike = <<(encode_jid_like(GenKey))/binary, "/%">>,
×
464
                ?SQL("select @(node)s, @(plugin)s, @(i.nodeid)d, @(jid)s, @(subscriptions)s "
×
465
                     "from pubsub_state i, pubsub_node n, pubsub_node_option o "
466
                     "where i.nodeid = n.nodeid and n.nodeid = o.nodeid and "
467
                     "name='send_last_published_item' and val='on_sub_and_presence' and "
468
                     "(jid=%(GJ)s or jid like %(GJLike)s %ESCAPE) and host=%(H)s");
469
              _ ->
470
                SJ = encode_jid(SubKey),
×
471
                ?SQL("select @(node)s, @(plugin)s, @(i.nodeid)d, @(jid)s, @(subscriptions)s "
×
472
                     "from pubsub_state i, pubsub_node n, pubsub_node_option o "
473
                     "where i.nodeid = n.nodeid and n.nodeid = o.nodeid and "
474
                     "name='send_last_published_item' and val='on_sub_and_presence' and "
475
                     "jid in (%(SJ)s, %(GJ)s) and host=%(H)s")
476
            end,
477
    {result,
×
478
     case ejabberd_sql:sql_query_t(Query) of
479
         {selected, RItems} ->
480
             lists:foldl(
×
481
               fun ({N, T, I, J, S}, Acc) ->
482
                       Node = nodetree_tree_sql:raw_to_node(Host, {N, <<"">>, T, I}),
×
483
                       Jid = decode_jid(J),
×
484
                       lists:foldl(
×
485
                         fun ({Sub, SubId}, Acc2) ->
486
                             [{Node, Sub, SubId, Jid}| Acc2]
×
487
                         end, Acc, decode_subscriptions(S))
488
               end, [], RItems);
489
         _ ->
490
             []
×
491
     end}.
492

493
get_node_subscriptions(Nidx) ->
494
    {result,
321✔
495
     case ejabberd_sql:sql_query_t(
496
            ?SQL("select @(jid)s, @(subscriptions)s from pubsub_state "
430✔
497
                 "where nodeid=%(Nidx)d")) of
498
         {selected, RItems} ->
499
             lists:foldl(
321✔
500
               fun ({J, S}, Acc) ->
501
                       Jid = decode_jid(J),
312✔
502
                       lists:foldl(
312✔
503
                         fun ({Sub, SubId}, Acc2) ->
504
                             [{Jid, Sub, SubId} | Acc2]
48✔
505
                         end, Acc, decode_subscriptions(S))
506
               end, [], RItems);
507
         _ ->
508
             []
×
509
     end}.
510

511
get_subscriptions(Nidx, Owner) ->
512
    SubKey = jid:tolower(Owner),
6✔
513
    J = encode_jid(SubKey),
6✔
514
    {result,
6✔
515
     case ejabberd_sql:sql_query_t(
516
            ?SQL("select @(subscriptions)s from pubsub_state"
10✔
517
                 " where nodeid=%(Nidx)d and jid=%(J)s")) of
518
         {selected, [{S}]} ->
519
             decode_subscriptions(S);
6✔
520
         _ ->
521
             []
×
522
     end}.
523

524
set_subscriptions(Nidx, Owner, Subscription, SubId) ->
525
    SubKey = jid:tolower(Owner),
18✔
526
    SubState = get_state_without_itemids(Nidx, SubKey),
18✔
527
    case {SubId, SubState#pubsub_state.subscriptions} of
18✔
528
        {_, []} ->
529
            case Subscription of
3✔
530
                none ->
531
                    {error, mod_pubsub:extended_error(
×
532
                              xmpp:err_bad_request(),
533
                              mod_pubsub:err_not_subscribed())};
534
                _ ->
535
                    new_subscription(Nidx, Owner, Subscription, SubState)
3✔
536
            end;
537
        {<<>>, [{_, SID}]} ->
538
            case Subscription of
9✔
539
                none -> unsub_with_subid(Nidx, SID, SubState);
3✔
540
                _ -> replace_subscription({Subscription, SID}, SubState)
6✔
541
            end;
542
        {<<>>, [_ | _]} ->
543
            {error, mod_pubsub:extended_error(
×
544
                      xmpp:err_bad_request(),
545
                      mod_pubsub:err_subid_required())};
546
        _ ->
547
            case Subscription of
6✔
548
                none -> unsub_with_subid(Nidx, SubId, SubState);
3✔
549
                _ -> replace_subscription({Subscription, SubId}, SubState)
3✔
550
            end
551
    end.
552

553
replace_subscription(NewSub, SubState) ->
554
    NewSubs = replace_subscription(NewSub, SubState#pubsub_state.subscriptions, []),
9✔
555
    {result, set_state(SubState#pubsub_state{subscriptions = NewSubs})}.
9✔
556

557
replace_subscription(_, [], Acc) -> Acc;
9✔
558
replace_subscription({Sub, SubId}, [{_, SubId} | T], Acc) ->
559
    replace_subscription({Sub, SubId}, T, [{Sub, SubId} | Acc]).
9✔
560

561
new_subscription(_Nidx, _Owner, Subscription, SubState) ->
562
    %%{result, SubId} = pubsub_subscription_sql:subscribe_node(Owner, Nidx, []),
563
    SubId = pubsub_subscription_sql:make_subid(),
3✔
564
    Subscriptions = [{Subscription, SubId} | SubState#pubsub_state.subscriptions],
3✔
565
    set_state(SubState#pubsub_state{subscriptions = Subscriptions}),
3✔
566
    {result, {Subscription, SubId}}.
3✔
567

568
unsub_with_subid(Nidx, SubId, SubState) ->
569
    %%pubsub_subscription_sql:unsubscribe_node(SubState#pubsub_state.stateid, Nidx, SubId),
570
    NewSubs = [{S, Sid}
6✔
571
            || {S, Sid} <- SubState#pubsub_state.subscriptions,
6✔
572
                SubId =/= Sid],
6✔
573
    case {NewSubs, SubState#pubsub_state.affiliation} of
6✔
574
        {[], none} -> {result, del_state(Nidx, element(1, SubState#pubsub_state.stateid))};
6✔
575
        _ -> {result, set_state(SubState#pubsub_state{subscriptions = NewSubs})}
×
576
    end.
577

578
get_pending_nodes(Host, Owner) ->
579
    GenKey = encode_jid(jid:remove_resource(jid:tolower(Owner))),
×
580
    PendingIdxs = case ejabberd_sql:sql_query_t(
×
581
                         ?SQL("select @(nodeid)d from pubsub_state "
×
582
                              "where subscriptions like '%p%' and affiliation='o'"
583
                              "and jid=%(GenKey)s")) of
584
        {selected, RItems} ->
585
            [Nidx || {Nidx} <- RItems];
×
586
        _ ->
587
            []
×
588
        end,
589
    NodeTree = mod_pubsub:tree(Host),
×
590
    Reply = lists:foldl(fun(Nidx, Acc) ->
×
591
                            case NodeTree:get_node(Nidx) of
×
592
                                #pubsub_node{nodeid = {_, Node}} -> [Node | Acc];
×
593
                                _ -> Acc
×
594
                            end
595
                        end,
596
                        [], PendingIdxs),
597
    {result, Reply}.
×
598

599
get_states(Nidx) ->
600
    case ejabberd_sql:sql_query_t(
6✔
601
           ?SQL("select @(jid)s, @(affiliation)s, @(subscriptions)s "
10✔
602
                "from pubsub_state where nodeid=%(Nidx)d")) of
603
        {selected, RItems} ->
604
            {result,
6✔
605
             lists:map(
606
               fun({SJID, Aff, Subs}) ->
607
                       JID = decode_jid(SJID),
9✔
608
                       #pubsub_state{stateid = {JID, Nidx},
9✔
609
                                     nodeidx = Nidx,
610
                                     items = itemids(Nidx, JID),
611
                                     affiliation = decode_affiliation(Aff),
612
                                     subscriptions = decode_subscriptions(Subs)}
613
               end, RItems)};
614
        _ ->
615
            {result, []}
×
616
    end.
617

618
get_state(Nidx, JID) ->
619
    State = get_state_without_itemids(Nidx, JID),
21✔
620
    {SJID, _} = State#pubsub_state.stateid,
21✔
621
    State#pubsub_state{items = itemids(Nidx, SJID)}.
21✔
622

623
-spec get_state_without_itemids(Nidx :: mod_pubsub:nodeIdx(), Key :: ljid()) ->
624
                                       mod_pubsub:pubsubState().
625

626
get_state_without_itemids(Nidx, JID) ->
627
    J = encode_jid(JID),
39✔
628
    case ejabberd_sql:sql_query_t(
39✔
629
           ?SQL("select @(jid)s, @(affiliation)s, @(subscriptions)s "
54✔
630
                "from pubsub_state "
631
                "where nodeid=%(Nidx)d and jid=%(J)s")) of
632
        {selected, [{SJID, Aff, Subs}]} ->
633
            #pubsub_state{stateid = {decode_jid(SJID), Nidx},
33✔
634
                          nodeidx = Nidx,
635
                          affiliation = decode_affiliation(Aff),
636
                          subscriptions = decode_subscriptions(Subs)};
637
        _ ->
638
            #pubsub_state{stateid = {JID, Nidx}, nodeidx = Nidx}
6✔
639
    end.
640

641
set_state(State) ->
642
    {_, Nidx} = State#pubsub_state.stateid,
12✔
643
    set_state(Nidx, State).
12✔
644

645
set_state(Nidx, State) ->
646
    {JID, _} = State#pubsub_state.stateid,
12✔
647
    J = encode_jid(JID),
12✔
648
    S = encode_subscriptions(State#pubsub_state.subscriptions),
12✔
649
    A = encode_affiliation(State#pubsub_state.affiliation),
12✔
650
    ?SQL_UPSERT_T(
12✔
651
       "pubsub_state",
652
       ["!nodeid=%(Nidx)d",
653
        "!jid=%(J)s",
654
        "affiliation=%(A)s",
655
        "subscriptions=%(S)s"
656
       ]),
657
    ok.
12✔
658

659
del_state(Nidx, JID) ->
660
    J = encode_jid(JID),
24✔
661
    catch ejabberd_sql:sql_query_t(
24✔
662
            ?SQL("delete from pubsub_state"
34✔
663
                 " where jid=%(J)s and nodeid=%(Nidx)d")),
664
    ok.
24✔
665

666
get_items(Nidx, _From, undefined) ->
667
    SNidx = misc:i2l(Nidx),
42✔
668
    case ejabberd_sql:sql_query_t(
42✔
669
           [<<"select itemid, publisher, creation, modification, payload",
670
              " from pubsub_item where nodeid='", SNidx/binary, "'",
671
              " order by creation asc">>]) of
672
        {selected, _, AllItems} ->
673
            {result, {[raw_to_item(Nidx, RItem) || RItem <- AllItems], undefined}};
42✔
674
        _ ->
675
            {result, {[], undefined}}
×
676
    end;
677
get_items(Nidx, _From, #rsm_set{max = Max, index = IncIndex,
678
                                'after' = After, before = Before}) ->
679
    Count = case catch ejabberd_sql:sql_query_t(
×
680
                    ?SQL("select @(count(itemid))d from pubsub_item"
×
681
                         " where nodeid=%(Nidx)d")) of
682
                {selected, [{C}]} -> C;
×
683
                _ -> 0
×
684
            end,
685
    Offset = case {IncIndex, Before, After} of
×
686
                {I, undefined, undefined} when is_integer(I) -> I;
×
687
                _ -> 0
×
688
             end,
689
    Limit = case Max of
×
690
                undefined -> ?MAXITEMS;
×
691
                _ -> Max
×
692
            end,
693
    Filters = rsm_filters(misc:i2l(Nidx), Before, After),
×
694
    Query = fun(mssql, _) ->
×
695
                    ejabberd_sql:sql_query_t(
×
696
                      [<<"select top ", (integer_to_binary(Limit))/binary,
697
                         " itemid, publisher, creation, modification, payload",
698
                         " from pubsub_item", Filters/binary>>]);
699
                         %OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY;
700
               (_, _) ->
701
                    ejabberd_sql:sql_query_t(
×
702
                      [<<"select itemid, publisher, creation, modification, payload",
703
                         " from pubsub_item", Filters/binary,
704
                         " limit ", (integer_to_binary(Limit))/binary,
705
                         " offset ", (integer_to_binary(Offset))/binary>>])
706
            end,
707
    case ejabberd_sql:sql_query_t(Query) of
×
708
        {selected, _, []} ->
709
            {result, {[], #rsm_set{count = Count}}};
×
710
        {selected, [<<"itemid">>, <<"publisher">>, <<"creation">>,
711
                    <<"modification">>, <<"payload">>], RItems} ->
712
            Rsm = rsm_page(Count, IncIndex, Offset, RItems),
×
713
            {result, {[raw_to_item(Nidx, RItem) || RItem <- RItems], Rsm}};
×
714
        _ ->
715
            {result, {[], undefined}}
×
716
    end.
717

718
get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId, RSM) ->
719
    SubKey = jid:tolower(JID),
36✔
720
    GenKey = jid:remove_resource(SubKey),
36✔
721
    {Affiliation, Subscriptions} = select_affiliation_subscriptions(Nidx, GenKey, SubKey),
36✔
722
    Whitelisted = node_flat:can_fetch_item(Affiliation, Subscriptions),
36✔
723
    if %%SubId == "", ?? ->
36✔
724
        %% Entity has multiple subscriptions to the node but does not specify a subscription ID
725
        %{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
726
        %%InvalidSubId ->
727
        %% Entity is subscribed but specifies an invalid subscription ID
728
        %{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
729
        (Affiliation == outcast) or (Affiliation == publish_only) ->
730
            {error, xmpp:err_forbidden()};
6✔
731
        (AccessModel == presence) and not PresenceSubscription ->
732
            {error, mod_pubsub:extended_error(
×
733
                      xmpp:err_not_authorized(),
734
                      mod_pubsub:err_presence_subscription_required())};
735
        (AccessModel == roster) and not RosterGroup ->
736
            {error, mod_pubsub:extended_error(
×
737
                      xmpp:err_not_authorized(),
738
                      mod_pubsub:err_not_in_roster_group())};
739
        (AccessModel == whitelist) and not Whitelisted ->
740
            {error, mod_pubsub:extended_error(
×
741
                      xmpp:err_not_allowed(), mod_pubsub:err_closed_node())};
742
        (AccessModel == authorize) and not Whitelisted ->
743
            {error, xmpp:err_forbidden()};
×
744
        %%MustPay ->
745
        %%        % Payment is required for a subscription
746
        %%        {error, ?ERR_PAYMENT_REQUIRED};
747
        true ->
748
            get_items(Nidx, JID, RSM)
30✔
749
    end.
750

751
get_last_items(Nidx, _From, Limit) ->
752
    SNidx = misc:i2l(Nidx),
24✔
753
    Query = fun(mssql, _) ->
24✔
754
                    ejabberd_sql:sql_query_t(
×
755
                      [<<"select top ", (integer_to_binary(Limit))/binary,
756
                         " itemid, publisher, creation, modification, payload",
757
                         " from pubsub_item where nodeid='", SNidx/binary,
758
                         "' order by modification desc">>]);
759
               (_, _) ->
760
                    ejabberd_sql:sql_query_t(
24✔
761
                      [<<"select itemid, publisher, creation, modification, payload",
762
                         " from pubsub_item where nodeid='", SNidx/binary,
763
                         "' order by modification desc ",
764
                         " limit ", (integer_to_binary(Limit))/binary>>])
765
            end,
766
    case catch ejabberd_sql:sql_query_t(Query) of
24✔
767
        {selected, [<<"itemid">>, <<"publisher">>, <<"creation">>,
768
                    <<"modification">>, <<"payload">>], RItems} ->
769
            {result, [raw_to_item(Nidx, RItem) || RItem <- RItems]};
24✔
770
        _ ->
771
            {result, []}
×
772
    end.
773

774
get_only_item(Nidx, _From) ->
775
    SNidx = misc:i2l(Nidx),
3✔
776
    Query = fun(mssql, _) ->
3✔
777
        ejabberd_sql:sql_query_t(
×
778
            [<<"select  itemid, publisher, creation, modification, payload",
779
               " from pubsub_item where nodeid='", SNidx/binary, "'">>]);
780
               (_, _) ->
781
                   ejabberd_sql:sql_query_t(
3✔
782
                       [<<"select itemid, publisher, creation, modification, payload",
783
                          " from pubsub_item where nodeid='", SNidx/binary, "'">>])
784
            end,
785
    case catch ejabberd_sql:sql_query_t(Query) of
3✔
786
        {selected, [<<"itemid">>, <<"publisher">>, <<"creation">>,
787
                    <<"modification">>, <<"payload">>], RItems} ->
788
            {result, [raw_to_item(Nidx, RItem) || RItem <- RItems]};
3✔
789
        _ ->
790
            {result, []}
×
791
    end.
792

793
get_item(Nidx, ItemId) ->
794
    case catch ejabberd_sql:sql_query_t(
90✔
795
                 ?SQL("select @(itemid)s, @(publisher)s, @(creation)s,"
122✔
796
                      " @(modification)s, @(payload)s from pubsub_item"
797
                      " where nodeid=%(Nidx)d and itemid=%(ItemId)s"))
798
    of
799
        {selected, [RItem]} ->
800
            {result, raw_to_item(Nidx, RItem)};
3✔
801
        {selected, []} ->
802
            {error, xmpp:err_item_not_found()};
87✔
803
        {'EXIT', _} ->
804
            {error, xmpp:err_internal_server_error(?T("Database failure"), ejabberd_option:language())}
×
805
    end.
806

807
get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId) ->
808
    SubKey = jid:tolower(JID),
×
809
    GenKey = jid:remove_resource(SubKey),
×
810
    {Affiliation, Subscriptions} = select_affiliation_subscriptions(Nidx, GenKey, SubKey),
×
811
    Whitelisted = node_flat:can_fetch_item(Affiliation, Subscriptions),
×
812
    if %%SubId == "", ?? ->
×
813
        %% Entity has multiple subscriptions to the node but does not specify a subscription ID
814
        %{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
815
        %%InvalidSubId ->
816
        %% Entity is subscribed but specifies an invalid subscription ID
817
        %{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
818
        (Affiliation == outcast) or (Affiliation == publish_only) ->
819
            {error, xmpp:err_forbidden()};
×
820
        (AccessModel == presence) and not PresenceSubscription ->
821
            {error, mod_pubsub:extended_error(
×
822
                      xmpp:err_not_authorized(),
823
                      mod_pubsub:err_presence_subscription_required())};
824
        (AccessModel == roster) and not RosterGroup ->
825
            {error, mod_pubsub:extended_error(
×
826
                      xmpp:err_not_authorized(),
827
                      mod_pubsub:err_not_in_roster_group())};
828
        (AccessModel == whitelist) and not Whitelisted ->
829
            {error, mod_pubsub:extended_error(
×
830
                      xmpp:err_not_allowed(), mod_pubsub:err_closed_node())};
831
        (AccessModel == authorize) and not Whitelisted ->
832
            {error, xmpp:err_forbidden()};
×
833
        %%MustPay ->
834
        %%        % Payment is required for a subscription
835
        %%        {error, ?ERR_PAYMENT_REQUIRED};
836
        true ->
837
            get_item(Nidx, ItemId)
×
838
    end.
839

840
set_item(Item) ->
841
    {ItemId, Nidx} = Item#pubsub_item.itemid,
90✔
842
    {C, _} = Item#pubsub_item.creation,
90✔
843
    {M, JID} = Item#pubsub_item.modification,
90✔
844
    P = encode_jid(JID),
90✔
845
    Payload = Item#pubsub_item.payload,
90✔
846
    XML = str:join([fxml:element_to_binary(X) || X<-Payload], <<>>),
90✔
847
    SM = encode_now(M),
90✔
848
    SC = encode_now(C),
90✔
849
    ?SQL_UPSERT_T(
90✔
850
       "pubsub_item",
851
       ["!nodeid=%(Nidx)d",
852
        "!itemid=%(ItemId)s",
853
        "publisher=%(P)s",
854
        "modification=%(SM)s",
855
        "payload=%(XML)s",
856
        "-creation=%(SC)s"
857
       ]),
858
    ok.
90✔
859

860
del_item(Nidx, ItemId) ->
861
    catch ejabberd_sql:sql_query_t(
12✔
862
            ?SQL("delete from pubsub_item where itemid=%(ItemId)s"
18✔
863
                 " and nodeid=%(Nidx)d")).
864

865
del_items(_, []) ->
866
    ok;
87✔
867
del_items(Nidx, [ItemId]) ->
868
    del_item(Nidx, ItemId);
×
869
del_items(Nidx, ItemIds) ->
870
    I = str:join([ejabberd_sql:to_string_literal_t(X) || X <- ItemIds], <<",">>),
3✔
871
    SNidx = misc:i2l(Nidx),
3✔
872
    catch
3✔
873
    ejabberd_sql:sql_query_t([<<"delete from pubsub_item where itemid in (">>,
874
            I, <<") and nodeid='">>, SNidx, <<"';">>]).
875

876
get_item_name(_Host, _Node, Id) ->
877
    {result, Id}.
×
878

879
node_to_path(Node) ->
880
    node_flat:node_to_path(Node).
96✔
881

882
path_to_node(Path) ->
883
    node_flat:path_to_node(Path).
×
884

885

886
first_in_list(_Pred, []) ->
887
    false;
×
888
first_in_list(Pred, [H | T]) ->
889
    case Pred(H) of
×
890
        true -> {value, H};
×
891
        _ -> first_in_list(Pred, T)
×
892
    end.
893

894
itemids(Nidx) ->
895
    case catch
×
896
        ejabberd_sql:sql_query_t(
897
          ?SQL("select @(itemid)s from pubsub_item where "
×
898
               "nodeid=%(Nidx)d order by modification desc"))
899
    of
900
        {selected, RItems} ->
901
            [ItemId || {ItemId} <- RItems];
×
902
        _ ->
903
            []
×
904
    end.
905

906
itemids(Nidx, {_U, _S, _R} = JID) ->
907
    SJID = encode_jid(JID),
129✔
908
    SJIDLike = <<(encode_jid_like(JID))/binary, "/%">>,
129✔
909
    case catch
129✔
910
        ejabberd_sql:sql_query_t(
911
          ?SQL("select @(itemid)s from pubsub_item where "
174✔
912
               "nodeid=%(Nidx)d and (publisher=%(SJID)s"
913
               " or publisher like %(SJIDLike)s %ESCAPE) "
914
               "order by modification desc"))
915
    of
916
        {selected, RItems} ->
917
            [ItemId || {ItemId} <- RItems];
129✔
918
        _ ->
919
            []
×
920
    end.
921

922
select_affiliation_subscriptions(Nidx, JID) ->
923
    J = encode_jid(JID),
51✔
924
    case catch
51✔
925
        ejabberd_sql:sql_query_t(
926
          ?SQL("select @(affiliation)s, @(subscriptions)s from "
70✔
927
               " pubsub_state where nodeid=%(Nidx)d and jid=%(J)s"))
928
    of
929
        {selected, [{A, S}]} ->
930
            {decode_affiliation(A), decode_subscriptions(S)};
27✔
931
        _ ->
932
            {none, []}
24✔
933
    end.
934

935
select_affiliation_subscriptions(Nidx, JID, JID) ->
936
    select_affiliation_subscriptions(Nidx, JID);
6✔
937
select_affiliation_subscriptions(Nidx, GenKey, SubKey) ->
938
    GJ = encode_jid(GenKey),
180✔
939
    SJ = encode_jid(SubKey),
180✔
940
    case catch ejabberd_sql:sql_query_t(
180✔
941
        ?SQL("select @(jid)s, @(affiliation)s, @(subscriptions)s from "
242✔
942
             " pubsub_state where nodeid=%(Nidx)d and jid in (%(GJ)s, %(SJ)s)"))
943
    of
944
        {selected, Res} ->
945
            lists:foldr(
180✔
946
                fun({Jid, A, S}, {_, Subs}) when Jid == GJ ->
947
                    {decode_affiliation(A), Subs ++ decode_subscriptions(S)};
162✔
948
                   ({_, _, S}, {Aff, Subs}) ->
949
                       {Aff, Subs ++ decode_subscriptions(S)}
×
950
                end, {none, []}, Res);
951
        _ ->
952
            {none, []}
×
953
    end.
954

955
update_affiliation(Nidx, JID, Affiliation) ->
956
    J = encode_jid(JID),
21✔
957
    A = encode_affiliation(Affiliation),
21✔
958
    ?SQL_UPSERT_T(
21✔
959
       "pubsub_state",
960
       ["!nodeid=%(Nidx)d",
961
        "!jid=%(J)s",
962
        "affiliation=%(A)s",
963
        "-subscriptions=''"
964
       ]).
965

966
update_subscription(Nidx, JID, Subscription) ->
967
    J = encode_jid(JID),
45✔
968
    S = encode_subscriptions(Subscription),
45✔
969
    ?SQL_UPSERT_T(
45✔
970
       "pubsub_state",
971
       ["!nodeid=%(Nidx)d",
972
        "!jid=%(J)s",
973
        "subscriptions=%(S)s",
974
        "-affiliation='n'"
975
       ]).
976

977
-spec maybe_remove_extra_items(mod_pubsub:nodeIdx(),
978
                               non_neg_integer() | unlimited, ljid(),
979
                               mod_pubsub:itemId()) -> [mod_pubsub:itemId()].
980
maybe_remove_extra_items(_Nidx, unlimited, _GenKey, _ItemId) ->
981
    [];
×
982
maybe_remove_extra_items(Nidx, MaxItems, GenKey, ItemId) ->
983
    ItemIds = [ItemId | itemids(Nidx, GenKey)],
87✔
984
    {result, {_NewIds, OldIds}} = remove_extra_items(Nidx, MaxItems, ItemIds),
87✔
985
    OldIds.
87✔
986

987
-spec decode_jid(SJID :: binary()) -> ljid().
988
decode_jid(SJID) ->
989
    jid:tolower(jid:decode(SJID)).
753✔
990

991
-spec decode_affiliation(Arg :: binary()) -> atom().
992
decode_affiliation(<<"o">>) -> owner;
546✔
993
decode_affiliation(<<"p">>) -> publisher;
45✔
994
decode_affiliation(<<"u">>) -> publish_only;
45✔
995
decode_affiliation(<<"m">>) -> member;
45✔
996
decode_affiliation(<<"c">>) -> outcast;
48✔
997
decode_affiliation(_) -> none.
51✔
998

999
-spec decode_subscription(Arg :: binary()) -> atom().
1000
decode_subscription(<<"s">>) -> subscribed;
78✔
1001
decode_subscription(<<"p">>) -> pending;
21✔
1002
decode_subscription(<<"u">>) -> unconfigured;
9✔
1003
decode_subscription(_) -> none.
×
1004

1005
-spec decode_subscriptions(Subscriptions :: binary()) -> [] | [{atom(), binary()},...].
1006
decode_subscriptions(Subscriptions) ->
1007
    lists:foldl(fun (Subscription, Acc) ->
600✔
1008
                case str:tokens(Subscription, <<":">>) of
108✔
1009
                    [S, SubId] -> [{decode_subscription(S), SubId} | Acc];
108✔
1010
                    _ -> Acc
×
1011
                end
1012
        end,
1013
        [], str:tokens(Subscriptions, <<",">>)).
1014

1015
-spec encode_jid(JID :: ljid()) -> binary().
1016
encode_jid(JID) ->
1017
    jid:encode(JID).
4,161✔
1018

1019
-spec encode_jid_like(JID :: ljid()) -> binary().
1020
encode_jid_like(JID) ->
1021
    ejabberd_sql:escape_like_arg(jid:encode(JID)).
537✔
1022

1023
-spec encode_host(Host :: host()) -> binary().
1024
encode_host({_U, _S, _R} = LJID) -> encode_jid(LJID);
939✔
1025
encode_host(Host) -> Host.
2,346✔
1026

1027
-spec encode_host_like(Host :: host()) -> binary().
1028
encode_host_like({_U, _S, _R} = LJID) -> encode_jid_like(LJID);
×
1029
encode_host_like(Host) ->
1030
    ejabberd_sql:escape_like_arg(Host).
291✔
1031

1032
-spec encode_affiliation(Arg :: atom()) -> binary().
1033
encode_affiliation(owner) -> <<"o">>;
102✔
1034
encode_affiliation(publisher) -> <<"p">>;
3✔
1035
encode_affiliation(publish_only) -> <<"u">>;
3✔
1036
encode_affiliation(member) -> <<"m">>;
3✔
1037
encode_affiliation(outcast) -> <<"c">>;
6✔
1038
encode_affiliation(_) -> <<"n">>.
12✔
1039

1040
-spec encode_subscription(Arg :: atom()) -> binary().
1041
encode_subscription(subscribed) -> <<"s">>;
45✔
1042
encode_subscription(pending) -> <<"p">>;
9✔
1043
encode_subscription(unconfigured) -> <<"u">>;
3✔
1044
encode_subscription(_) -> <<"n">>.
×
1045

1046
-spec encode_subscriptions(Subscriptions :: [] | [{atom(), binary()},...]) -> binary().
1047
encode_subscriptions(Subscriptions) ->
1048
    str:join([<<(encode_subscription(S))/binary, ":", SubId/binary>>
153✔
1049
            || {S, SubId} <- Subscriptions], <<",">>).
153✔
1050

1051
%%% record getter/setter
1052

1053
raw_to_item(Nidx, [ItemId, SJID, Creation, Modification, XML]) ->
1054
    raw_to_item(Nidx, {ItemId, SJID, Creation, Modification, XML});
60✔
1055
raw_to_item(Nidx, {ItemId, SJID, Creation, Modification, XML}) ->
1056
    JID = decode_jid(SJID),
63✔
1057
    Payload = case fxml_stream:parse_element(XML) of
63✔
1058
        {error, _Reason} -> [];
×
1059
        El -> [El]
63✔
1060
    end,
1061
    #pubsub_item{itemid = {ItemId, Nidx},
63✔
1062
        nodeidx = Nidx,
1063
        creation = {decode_now(Creation), jid:remove_resource(JID)},
1064
        modification = {decode_now(Modification), JID},
1065
        payload = Payload}.
1066

1067
rsm_filters(SNidx, undefined, undefined) ->
1068
    <<" where nodeid='", SNidx/binary, "'",
×
1069
      " order by creation asc">>;
1070
rsm_filters(SNidx, undefined, After)  ->
1071
    <<" where nodeid='", SNidx/binary, "'",
×
1072
      " and creation>'", (encode_stamp(After))/binary, "'",
1073
      " order by creation asc">>;
1074
rsm_filters(SNidx, <<>>, undefined) ->
1075
    %% 2.5 Requesting the Last Page in a Result Set
1076
    <<" where nodeid='", SNidx/binary, "'",
×
1077
      " order by creation desc">>;
1078
rsm_filters(SNidx, Before, undefined) ->
1079
    <<" where nodeid='", SNidx/binary, "'",
×
1080
      " and creation<'", (encode_stamp(Before))/binary, "'",
1081
      " order by creation desc">>.
1082

1083
rsm_page(Count, Index, Offset, Items) ->
1084
    First = decode_stamp(lists:nth(3, hd(Items))),
×
1085
    Last = decode_stamp(lists:nth(3, lists:last(Items))),
×
1086
    #rsm_set{count = Count, index = Index,
×
1087
             first = #rsm_first{index = Offset, data = First},
1088
             last = Last}.
1089

1090
encode_stamp(Stamp) ->
1091
    try xmpp_util:decode_timestamp(Stamp) of
×
1092
        Now ->
1093
            encode_now(Now)
×
1094
    catch _:{bad_timestamp, _} ->
1095
            Stamp % We should return a proper error to the client instead.
×
1096
    end.
1097
decode_stamp(Stamp) ->
1098
    xmpp_util:encode_timestamp(decode_now(Stamp)).
×
1099

1100
encode_now({T1, T2, T3}) ->
1101
    <<(misc:i2l(T1, 6))/binary, ":",
180✔
1102
      (misc:i2l(T2, 6))/binary, ":",
1103
      (misc:i2l(T3, 6))/binary>>.
1104
decode_now(NowStr) ->
1105
    [MS, S, US] = binary:split(NowStr, <<":">>, [global]),
126✔
1106
    {binary_to_integer(MS), binary_to_integer(S), binary_to_integer(US)}.
126✔
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

© 2025 Coveralls, Inc