• 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

4.72
/src/pubsub_subscription.erl
1
%%%----------------------------------------------------------------------
2
%%% File    : pubsub_subscription.erl
3
%%% Author  : Brian Cully <bjc@kublai.com>
4
%%% Purpose : Handle pubsub subscriptions options
5
%%% Created : 29 May 2009 by Brian Cully <bjc@kublai.com>
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
-module(pubsub_subscription).
27

28
-author("bjc@kublai.com").
29

30
%% API
31
-export([init/3, subscribe_node/3, unsubscribe_node/3,
32
    get_subscription/3, set_subscription/4,
33
    make_subid/0,
34
    get_options_xform/2, parse_options_xform/1]).
35

36
% Internal function also exported for use in transactional bloc from pubsub plugins
37
-export([add_subscription/3, delete_subscription/3,
38
    read_subscription/3, write_subscription/4]).
39

40
-include("pubsub.hrl").
41
-include_lib("xmpp/include/xmpp.hrl").
42
-include("translate.hrl").
43

44
-define(PUBSUB_DELIVER, <<"pubsub#deliver">>).
45
-define(PUBSUB_DIGEST, <<"pubsub#digest">>).
46
-define(PUBSUB_DIGEST_FREQUENCY, <<"pubsub#digest_frequency">>).
47
-define(PUBSUB_EXPIRE, <<"pubsub#expire">>).
48
-define(PUBSUB_INCLUDE_BODY, <<"pubsub#include_body">>).
49
-define(PUBSUB_SHOW_VALUES, <<"pubsub#show-values">>).
50
-define(PUBSUB_SUBSCRIPTION_TYPE, <<"pubsub#subscription_type">>).
51
-define(PUBSUB_SUBSCRIPTION_DEPTH, <<"pubsub#subscription_depth">>).
52
-define(DELIVER_LABEL, <<"Whether an entity wants to receive or disable notifications">>).
53
-define(DIGEST_LABEL, <<"Whether an entity wants to receive digests "
54
        "(aggregations) of notifications or all notifications individually">>).
55
-define(DIGEST_FREQUENCY_LABEL, <<"The minimum number of milliseconds between "
56
        "sending any two notification digests">>).
57
-define(EXPIRE_LABEL, <<"The DateTime at which a leased subscription will end or has ended">>).
58
-define(INCLUDE_BODY_LABEL, <<"Whether an entity wants to receive an "
59
        "XMPP message body in addition to the payload format">>).
60
-define(SHOW_VALUES_LABEL, <<"The presence states for which an entity wants to receive notifications">>).
61
-define(SUBSCRIPTION_TYPE_LABEL, <<"Type of notification to receive">>).
62
-define(SUBSCRIPTION_DEPTH_LABEL, <<"Depth from subscription for which to receive notifications">>).
63
-define(SHOW_VALUE_AWAY_LABEL, <<"XMPP Show Value of Away">>).
64
-define(SHOW_VALUE_CHAT_LABEL, <<"XMPP Show Value of Chat">>).
65
-define(SHOW_VALUE_DND_LABEL, <<"XMPP Show Value of DND (Do Not Disturb)">>).
66
-define(SHOW_VALUE_ONLINE_LABEL, <<"Mere Availability in XMPP (No Show Value)">>).
67
-define(SHOW_VALUE_XA_LABEL, <<"XMPP Show Value of XA (Extended Away)">>).
68
-define(SUBSCRIPTION_TYPE_VALUE_ITEMS_LABEL, <<"Receive notification of new items only">>).
69
-define(SUBSCRIPTION_TYPE_VALUE_NODES_LABEL, <<"Receive notification of new nodes only">>).
70
-define(SUBSCRIPTION_DEPTH_VALUE_ONE_LABEL, <<"Receive notification from direct child nodes only">>).
71
-define(SUBSCRIPTION_DEPTH_VALUE_ALL_LABEL, <<"Receive notification from all descendent nodes">>).
72

73
%%====================================================================
74
%% API
75
%%====================================================================
76
init(_Host, _ServerHost, _Opts) -> ok = create_table().
×
77

78
subscribe_node(JID, NodeId, Options) ->
79
    case catch mnesia:sync_dirty(fun add_subscription/3, [JID, NodeId, Options])
×
80
    of
81
        {'EXIT', {aborted, Error}} -> Error;
×
82
        {error, Error} -> {error, Error};
×
83
        Result -> {result, Result}
×
84
    end.
85

86
unsubscribe_node(JID, NodeId, SubID) ->
87
    case catch mnesia:sync_dirty(fun delete_subscription/3, [JID, NodeId, SubID])
×
88
    of
89
        {'EXIT', {aborted, Error}} -> Error;
×
90
        {error, Error} -> {error, Error};
×
91
        Result -> {result, Result}
×
92
    end.
93

94
get_subscription(JID, NodeId, SubID) ->
95
    case catch mnesia:sync_dirty(fun read_subscription/3, [JID, NodeId, SubID])
×
96
    of
97
        {'EXIT', {aborted, Error}} -> Error;
×
98
        {error, Error} -> {error, Error};
×
99
        Result -> {result, Result}
×
100
    end.
101

102
set_subscription(JID, NodeId, SubID, Options) ->
103
    case catch mnesia:sync_dirty(fun write_subscription/4, [JID, NodeId, SubID, Options])
×
104
    of
105
        {'EXIT', {aborted, Error}} -> Error;
×
106
        {error, Error} -> {error, Error};
×
107
        Result -> {result, Result}
×
108
    end.
109

110

111
get_options_xform(Lang, Options) ->
112
    Keys = [deliver, show_values, subscription_type, subscription_depth],
×
113
    XFields = [get_option_xfield(Lang, Key, Options) || Key <- Keys],
×
114
    {result,
×
115
     #xdata{type = form,
116
            fields = [#xdata_field{type = hidden,
117
                                   var = <<"FORM_TYPE">>,
118
                                   values = [?NS_PUBSUB_SUB_OPTIONS]}|
119
                      XFields]}}.
120

121
parse_options_xform(XFields) ->
UNCOV
122
    Opts = set_xoption(XFields, []),
38✔
UNCOV
123
    {result, Opts}.
38✔
124

125
%%====================================================================
126
%% Internal functions
127
%%====================================================================
128
create_table() ->
129
    case ejabberd_mnesia:create(?MODULE, pubsub_subscription,
×
130
            [{disc_copies, [node()]},
131
                {attributes,
132
                    record_info(fields, pubsub_subscription)},
133
                {type, set}])
134
    of
135
        {atomic, ok} -> ok;
×
136
        {aborted, {already_exists, _}} -> ok;
×
137
        Other -> Other
×
138
    end.
139

140
-spec add_subscription(_JID :: ljid(), _NodeId :: mod_pubsub:nodeIdx(),
141
                       Options :: [] | mod_pubsub:subOptions()) ->
142
                              SubId :: mod_pubsub:subId().
143

144
add_subscription(_JID, _NodeId, []) -> make_subid();
×
145
add_subscription(_JID, _NodeId, Options) ->
146
    SubID = make_subid(),
×
147
    mnesia:write(#pubsub_subscription{subid = SubID, options = Options}),
×
148
    SubID.
×
149

150
-spec delete_subscription(_JID :: _, _NodeId :: _, SubId :: mod_pubsub:subId()) -> ok.
151

152
delete_subscription(_JID, _NodeId, SubID) ->
153
    mnesia:delete({pubsub_subscription, SubID}).
×
154

155
-spec read_subscription(_JID :: ljid(), _NodeId :: _, SubID :: mod_pubsub:subId()) ->
156
                               mod_pubsub:pubsubSubscription() | {error, notfound}.
157

158
read_subscription(_JID, _NodeId, SubID) ->
159
    case mnesia:read({pubsub_subscription, SubID}) of
×
160
        [Sub] -> Sub;
×
161
        _ -> {error, notfound}
×
162
    end.
163

164
-spec write_subscription(_JID :: ljid(), _NodeId :: _, SubID :: mod_pubsub:subId(),
165
                         Options :: mod_pubsub:subOptions()) -> ok.
166

167
write_subscription(_JID, _NodeId, SubID, Options) ->
168
    mnesia:write(#pubsub_subscription{subid = SubID, options = Options}).
×
169

170
-spec make_subid() -> SubId::mod_pubsub:subId().
171
make_subid() ->
UNCOV
172
    {T1, T2, T3} = erlang:timestamp(),
36✔
UNCOV
173
    (str:format("~.16B~.16B~.16B", [T1, T2, T3])).
36✔
174

175
%%
176
%% Subscription XForm processing.
177
%%
178

179
%% Return processed options, with types converted and so forth, using
180
%% Opts as defaults.
UNCOV
181
set_xoption([], Opts) -> Opts;
38✔
182
set_xoption([{Var, Value} | T], Opts) ->
183
    NewOpts = case var_xfield(Var) of
×
184
        {error, _} -> Opts;
×
185
        Key ->
186
            Val = val_xfield(Key, Value),
×
187
            lists:keystore(Key, 1, Opts, {Key, Val})
×
188
    end,
189
    set_xoption(T, NewOpts).
×
190

191
%% Return the options list's key for an XForm var.
192
%% Convert Values for option list's Key.
193
var_xfield(?PUBSUB_DELIVER) -> deliver;
×
194
var_xfield(?PUBSUB_DIGEST) -> digest;
×
195
var_xfield(?PUBSUB_DIGEST_FREQUENCY) -> digest_frequency;
×
196
var_xfield(?PUBSUB_EXPIRE) -> expire;
×
197
var_xfield(?PUBSUB_INCLUDE_BODY) -> include_body;
×
198
var_xfield(?PUBSUB_SHOW_VALUES) -> show_values;
×
199
var_xfield(?PUBSUB_SUBSCRIPTION_TYPE) -> subscription_type;
×
200
var_xfield(?PUBSUB_SUBSCRIPTION_DEPTH) -> subscription_depth;
×
201
var_xfield(_) -> {error, badarg}.
×
202

203
val_xfield(deliver = Opt, [Val]) -> xopt_to_bool(Opt, Val);
×
204
val_xfield(digest = Opt, [Val]) -> xopt_to_bool(Opt, Val);
×
205
val_xfield(digest_frequency = Opt, [Val]) ->
206
    case catch binary_to_integer(Val) of
×
207
        N when is_integer(N) -> N;
×
208
        _ ->
209
            Txt = {?T("Value of '~s' should be integer"), [Opt]},
×
210
            {error, xmpp:err_not_acceptable(Txt, ejabberd_option:language())}
×
211
    end;
212
val_xfield(expire = Opt, [Val]) ->
213
    try xmpp_util:decode_timestamp(Val)
×
214
    catch _:{bad_timestamp, _} ->
215
            Txt = {?T("Value of '~s' should be datetime string"), [Opt]},
×
216
            {error, xmpp:err_not_acceptable(Txt, ejabberd_option:language())}
×
217
    end;
218
val_xfield(include_body = Opt, [Val]) -> xopt_to_bool(Opt, Val);
×
219
val_xfield(show_values, Vals) -> Vals;
×
220
val_xfield(subscription_type, [<<"items">>]) -> items;
×
221
val_xfield(subscription_type, [<<"nodes">>]) -> nodes;
×
222
val_xfield(subscription_depth, [<<"all">>]) -> all;
×
223
val_xfield(subscription_depth = Opt, [Depth]) ->
224
    case catch binary_to_integer(Depth) of
×
225
        N when is_integer(N) -> N;
×
226
        _ ->
227
            Txt = {?T("Value of '~s' should be integer"), [Opt]},
×
228
            {error, xmpp:err_not_acceptable(Txt, ejabberd_option:language())}
×
229
    end.
230

231
%% Convert XForm booleans to Erlang booleans.
232
xopt_to_bool(_, <<"0">>) -> false;
×
233
xopt_to_bool(_, <<"1">>) -> true;
×
234
xopt_to_bool(_, <<"false">>) -> false;
×
235
xopt_to_bool(_, <<"true">>) -> true;
×
236
xopt_to_bool(Option, _) ->
237
    Txt = {?T("Value of '~s' should be boolean"), [Option]},
×
238
    {error, xmpp:err_not_acceptable(Txt, ejabberd_option:language())}.
×
239

240
%% Return a field for an XForm for Key, with data filled in, if
241
%% applicable, from Options.
242
get_option_xfield(Lang, Key, Options) ->
243
    Var = xfield_var(Key),
×
244
    Label = xfield_label(Key),
×
245
    {Type, OptEls} = type_and_options(xfield_type(Key), Lang),
×
246
    Vals = case lists:keysearch(Key, 1, Options) of
×
247
        {value, {_, Val}} ->
248
                   [xfield_val(Key, Val)];
×
249
               false ->
250
                   []
×
251
    end,
252
    #xdata_field{type = Type, var = Var,
×
253
                 label = translate:translate(Lang, Label),
254
                 values = Vals,
255
                 options = OptEls}.
256

257
type_and_options({Type, Options}, Lang) ->
258
    {Type, [tr_xfield_options(O, Lang) || O <- Options]};
×
259
type_and_options(Type, _Lang) -> {Type, []}.
×
260

261
tr_xfield_options({Value, Label}, Lang) ->
262
    #xdata_option{label = translate:translate(Lang, Label),
×
263
                  value = Value}.
264

265
xfield_var(deliver) -> ?PUBSUB_DELIVER;
×
266
%xfield_var(digest) -> ?PUBSUB_DIGEST;
267
%xfield_var(digest_frequency) -> ?PUBSUB_DIGEST_FREQUENCY;
268
%xfield_var(expire) -> ?PUBSUB_EXPIRE;
269
%xfield_var(include_body) -> ?PUBSUB_INCLUDE_BODY;
270
xfield_var(show_values) -> ?PUBSUB_SHOW_VALUES;
×
271
xfield_var(subscription_type) -> ?PUBSUB_SUBSCRIPTION_TYPE;
×
272
xfield_var(subscription_depth) -> ?PUBSUB_SUBSCRIPTION_DEPTH.
×
273

274
xfield_type(deliver) -> boolean;
×
275
%xfield_type(digest) -> boolean;
276
%xfield_type(digest_frequency) -> 'text-single';
277
%xfield_type(expire) -> 'text-single';
278
%xfield_type(include_body) -> boolean;
279
xfield_type(show_values) ->
280
    {'list-multi',
×
281
        [{<<"away">>, ?SHOW_VALUE_AWAY_LABEL},
282
            {<<"chat">>, ?SHOW_VALUE_CHAT_LABEL},
283
            {<<"dnd">>, ?SHOW_VALUE_DND_LABEL},
284
            {<<"online">>, ?SHOW_VALUE_ONLINE_LABEL},
285
            {<<"xa">>, ?SHOW_VALUE_XA_LABEL}]};
286
xfield_type(subscription_type) ->
287
    {'list-single',
×
288
        [{<<"items">>, ?SUBSCRIPTION_TYPE_VALUE_ITEMS_LABEL},
289
            {<<"nodes">>, ?SUBSCRIPTION_TYPE_VALUE_NODES_LABEL}]};
290
xfield_type(subscription_depth) ->
291
    {'list-single',
×
292
        [{<<"1">>, ?SUBSCRIPTION_DEPTH_VALUE_ONE_LABEL},
293
            {<<"all">>, ?SUBSCRIPTION_DEPTH_VALUE_ALL_LABEL}]}.
294

295
%% Return the XForm variable label for a subscription option key.
296
xfield_label(deliver) -> ?DELIVER_LABEL;
×
297
%xfield_label(digest) -> ?DIGEST_LABEL;
298
%xfield_label(digest_frequency) -> ?DIGEST_FREQUENCY_LABEL;
299
%xfield_label(expire) -> ?EXPIRE_LABEL;
300
%xfield_label(include_body) -> ?INCLUDE_BODY_LABEL;
301
xfield_label(show_values) -> ?SHOW_VALUES_LABEL;
×
302
%% Return the XForm value for a subscription option key.
303
%% Convert erlang booleans to XForms.
304
xfield_label(subscription_type) -> ?SUBSCRIPTION_TYPE_LABEL;
×
305
xfield_label(subscription_depth) -> ?SUBSCRIPTION_DEPTH_LABEL.
×
306

307
xfield_val(deliver, Val) -> [bool_to_xopt(Val)];
×
308
%xfield_val(digest, Val) -> [bool_to_xopt(Val)];
309
%xfield_val(digest_frequency, Val) ->
310
%    [integer_to_binary(Val))];
311
%xfield_val(expire, Val) ->
312
%    [jlib:now_to_utc_string(Val)];
313
%xfield_val(include_body, Val) -> [bool_to_xopt(Val)];
314
xfield_val(show_values, Val) -> Val;
×
315
xfield_val(subscription_type, items) -> [<<"items">>];
×
316
xfield_val(subscription_type, nodes) -> [<<"nodes">>];
×
317
xfield_val(subscription_depth, all) -> [<<"all">>];
×
318
xfield_val(subscription_depth, N) ->
319
    [integer_to_binary(N)].
×
320

321

322
bool_to_xopt(true) -> <<"true">>;
×
323
bool_to_xopt(false) -> <<"false">>.
×
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