• 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

56.6
/src/mod_private.erl
1
%%%----------------------------------------------------------------------
2
%%% File    : mod_private.erl
3
%%% Author  : Alexey Shchepin <alexey@process-one.net>
4
%%% Purpose : Support for private storage.
5
%%% Created : 16 Jan 2003 by Alexey Shchepin <alexey@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
-module(mod_private).
27

28
-author('alexey@process-one.net').
29

30
-protocol({xep, 49, '1.2', '0.1.0', "complete", ""}).
31
-protocol({xep, 402, '1.2.0', '23.10', "complete", ""}).
32
-protocol({xep, 411, '1.1.0', '18.12', "complete", ""}).
33

34
-behaviour(gen_mod).
35

36
-export([start/2, stop/1, reload/3, process_sm_iq/1, import_info/0,
37
         remove_user/2, get_data/2, get_data/3, export/1, mod_doc/0,
38
         import/5, import_start/2, mod_opt_type/1, set_data/2,
39
         mod_options/1, depends/2, get_sm_features/5, pubsub_publish_item/6,
40
         pubsub_delete_item/5, pubsub_tree_call/4,
41
         del_data/3, get_users_with_data/2, count_users_with_data/2]).
42

43
-export([get_commands_spec/0, bookmarks_to_pep/2]).
44

45
-export([webadmin_menu_hostuser/4, webadmin_page_hostuser/4]).
46

47
-import(ejabberd_web_admin, [make_command/4, make_command/2]).
48

49
-include("logger.hrl").
50
-include_lib("xmpp/include/xmpp.hrl").
51
-include("mod_private.hrl").
52
-include("ejabberd_commands.hrl").
53
-include("ejabberd_http.hrl").
54
-include("ejabberd_web_admin.hrl").
55
-include("translate.hrl").
56
-include("pubsub.hrl").
57

58
-define(PRIVATE_CACHE, private_cache).
59

60
-callback init(binary(), gen_mod:opts()) -> any().
61
-callback import(binary(), binary(), [binary()]) -> ok.
62
-callback set_data(binary(), binary(), [{binary(), xmlel()}]) -> ok | {error, any()}.
63
-callback get_data(binary(), binary(), binary()) -> {ok, xmlel()} | error | {error, any()}.
64
-callback get_all_data(binary(), binary()) -> {ok, [xmlel()]} | error | {error, any()}.
65
-callback del_data(binary(), binary()) -> ok | {error, any()}.
66
-callback use_cache(binary()) -> boolean().
67
-callback cache_nodes(binary()) -> [node()].
68
-callback del_data(binary(), binary(), binary()) -> ok | {error, any()}.
69
-callback get_users_with_data(binary(), binary()) -> {ok, [binary()]} | {error, any()}.
70
-callback count_users_with_data(binary(), binary()) -> {ok, integer()} | {error, any()}.
71

72
-optional_callbacks([use_cache/1, cache_nodes/1]).
73

74
start(Host, Opts) ->
UNCOV
75
    Mod = gen_mod:db_mod(Opts, ?MODULE),
8✔
UNCOV
76
    Mod:init(Host, Opts),
8✔
UNCOV
77
    init_cache(Mod, Host, Opts),
8✔
UNCOV
78
    {ok, [{commands, get_commands_spec()},
8✔
79
          {hook, remove_user, remove_user, 50},
80
          {hook, disco_sm_features, get_sm_features, 50},
81
          {hook, pubsub_publish_item, pubsub_publish_item, 50},
82
          {hook, pubsub_delete_item, pubsub_delete_item, 50},
83
          {hook, pubsub_tree_call, pubsub_tree_call, 50},
84
          {hook, webadmin_menu_hostuser, webadmin_menu_hostuser, 50},
85
          {hook, webadmin_page_hostuser, webadmin_page_hostuser, 50},
86
          {iq_handler, ejabberd_sm, ?NS_PRIVATE, process_sm_iq}]}.
87

88
stop(_Host) ->
UNCOV
89
    ok.
8✔
90

91
reload(Host, NewOpts, OldOpts) ->
92
    NewMod = gen_mod:db_mod(NewOpts, ?MODULE),
×
93
    OldMod = gen_mod:db_mod(OldOpts, ?MODULE),
×
94
    if NewMod /= OldMod ->
×
95
            NewMod:init(Host, NewOpts);
×
96
       true ->
97
            ok
×
98
    end,
99
    init_cache(NewMod, Host, NewOpts).
×
100

101
depends(_Host, _Opts) ->
UNCOV
102
    [{mod_pubsub, soft}].
8✔
103

104
mod_opt_type(db_type) ->
UNCOV
105
    econf:db_type(?MODULE);
8✔
106
mod_opt_type(use_cache) ->
UNCOV
107
    econf:bool();
8✔
108
mod_opt_type(cache_size) ->
UNCOV
109
    econf:pos_int(infinity);
8✔
110
mod_opt_type(cache_missed) ->
UNCOV
111
    econf:bool();
8✔
112
mod_opt_type(cache_life_time) ->
UNCOV
113
    econf:timeout(second, infinity).
8✔
114

115
mod_options(Host) ->
UNCOV
116
    [{db_type, ejabberd_config:default_db(Host, ?MODULE)},
8✔
117
     {use_cache, ejabberd_option:use_cache(Host)},
118
     {cache_size, ejabberd_option:cache_size(Host)},
119
     {cache_missed, ejabberd_option:cache_missed(Host)},
120
     {cache_life_time, ejabberd_option:cache_life_time(Host)}].
121

122
mod_doc() ->
123
    #{desc =>
×
124
          [?T("This module adds support for "
125
              "https://xmpp.org/extensions/xep-0049.html"
126
              "[XEP-0049: Private XML Storage]."), "",
127
           ?T("Using this method, XMPP entities can store "
128
              "private data on the server, retrieve it "
129
              "whenever necessary and share it between multiple "
130
              "connected clients of the same user. The data stored "
131
              "might be anything, as long as it is a valid XML. "
132
              "One typical usage is storing a bookmark of all user's conferences "
133
              "(https://xmpp.org/extensions/xep-0048.html"
134
              "[XEP-0048: Bookmarks])."), "",
135
           ?T("It also implements the bookmark conversion described in "
136
              "https://xmpp.org/extensions/xep-0402.html[XEP-0402: PEP Native Bookmarks]"
137
              ", see _`bookmarks_to_pep`_ API.")],
138
      opts =>
139
          [{db_type,
140
            #{value => "mnesia | sql",
141
              desc =>
142
                  ?T("Same as top-level _`default_db`_ option, but applied to this module only.")}},
143
           {use_cache,
144
            #{value => "true | false",
145
              desc =>
146
                  ?T("Same as top-level _`use_cache`_ option, but applied to this module only.")}},
147
           {cache_size,
148
            #{value => "pos_integer() | infinity",
149
              desc =>
150
                  ?T("Same as top-level _`cache_size`_ option, but applied to this module only.")}},
151
           {cache_missed,
152
            #{value => "true | false",
153
              desc =>
154
                  ?T("Same as top-level _`cache_missed`_ option, but applied to this module only.")}},
155
           {cache_life_time,
156
            #{value => "timeout()",
157
              desc =>
158
                  ?T("Same as top-level _`cache_life_time`_ option, but applied to this module only.")}}]}.
159

160
-spec get_sm_features({error, stanza_error()} | empty | {result, [binary()]},
161
                      jid(), jid(), binary(), binary()) ->
162
                             {error, stanza_error()} | empty | {result, [binary()]}.
163
get_sm_features({error, _Error} = Acc, _From, _To, _Node, _Lang) ->
164
    Acc;
×
165
get_sm_features(Acc, _From, To, <<"">>, _Lang) ->
UNCOV
166
    case gen_mod:is_loaded(To#jid.lserver, mod_pubsub) of
32✔
167
        true ->
UNCOV
168
            {result, [?NS_BOOKMARKS_CONVERSION_0, ?NS_PEP_BOOKMARKS_COMPAT, ?NS_PEP_BOOKMARKS_COMPAT_PEP |
32✔
169
                      case Acc of
UNCOV
170
                          {result, Features} -> Features;
32✔
171
                          empty -> []
×
172
                      end]};
173
        false ->
174
            Acc
×
175
    end;
176
get_sm_features(Acc, _From, _To, _Node, _Lang) ->
UNCOV
177
    Acc.
24✔
178

179
-spec process_sm_iq(iq()) -> iq().
180
process_sm_iq(#iq{type = Type, lang = Lang,
181
                  from = #jid{luser = LUser, lserver = LServer} = From,
182
                  to = #jid{luser = LUser, lserver = LServer},
183
                  sub_els = [#private{sub_els = Els0}]} = IQ) ->
UNCOV
184
    case filter_xmlels(Els0) of
24✔
185
        [] ->
UNCOV
186
            Txt = ?T("No private data found in this query"),
8✔
UNCOV
187
            xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang));
8✔
188
        Data when Type == set ->
UNCOV
189
            case set_data(From, Data) of
8✔
190
                ok ->
UNCOV
191
                    xmpp:make_iq_result(IQ);
8✔
192
                {error, #stanza_error{} = Err} ->
193
                    xmpp:make_error(IQ, Err);
×
194
                {error, _} ->
195
                    Txt = ?T("Database failure"),
×
196
                    Err = xmpp:err_internal_server_error(Txt, Lang),
×
197
                    xmpp:make_error(IQ, Err)
×
198
            end;
199
        Data when Type == get ->
UNCOV
200
            case get_data(LUser, LServer, Data) of
8✔
201
                {error, _} ->
202
                    Txt = ?T("Database failure"),
×
203
                    Err = xmpp:err_internal_server_error(Txt, Lang),
×
204
                    xmpp:make_error(IQ, Err);
×
205
                Els ->
UNCOV
206
                    xmpp:make_iq_result(IQ, #private{sub_els = Els})
8✔
207
            end
208
    end;
209
process_sm_iq(#iq{lang = Lang} = IQ) ->
210
    Txt = ?T("Query to another users is forbidden"),
×
211
    xmpp:make_error(IQ, xmpp:err_forbidden(Txt, Lang)).
×
212

213
-spec filter_xmlels([xmlel()]) -> [{binary(), xmlel()}].
214
filter_xmlels(Els) ->
UNCOV
215
    lists:flatmap(
32✔
216
      fun(#xmlel{} = El) ->
UNCOV
217
              case fxml:get_tag_attr_s(<<"xmlns">>, El) of
32✔
UNCOV
218
                  <<"">> -> [];
8✔
UNCOV
219
                  NS -> [{NS, El}]
24✔
220
              end
221
      end, Els).
222

223
-spec set_data(jid(), [{binary(), xmlel()}]) -> ok | {error, _}.
224
set_data(JID, Data) ->
UNCOV
225
    set_data(JID, Data, true, true).
8✔
226

227
-spec set_data(jid(), [{binary(), xmlel()}], boolean(), boolean()) -> ok | {error, _}.
228
set_data(JID, Data, PublishPepStorageBookmarks, PublishPepXmppBookmarks) ->
UNCOV
229
    {LUser, LServer, _} = jid:tolower(JID),
24✔
UNCOV
230
    Mod = gen_mod:db_mod(LServer, ?MODULE),
24✔
UNCOV
231
    case Mod:set_data(LUser, LServer, Data) of
24✔
232
        ok ->
UNCOV
233
            delete_cache(Mod, LUser, LServer, Data),
24✔
UNCOV
234
            case PublishPepStorageBookmarks of
24✔
UNCOV
235
                true -> publish_pep_storage_bookmarks(JID, Data);
8✔
UNCOV
236
                false -> ok
16✔
237
            end,
UNCOV
238
            case PublishPepXmppBookmarks of
24✔
UNCOV
239
                true -> publish_pep_native_bookmarks(JID, Data);
24✔
240
                false -> ok
×
241
            end;
242
        {error, _} = Err ->
243
            Err
×
244
    end.
245

246
-spec get_data(binary(), binary(), [{binary(), xmlel()}]) -> [xmlel()] | {error, _}.
247
get_data(LUser, LServer, Data) ->
UNCOV
248
    Mod = gen_mod:db_mod(LServer, ?MODULE),
1,960✔
UNCOV
249
    lists:foldr(
1,960✔
250
      fun(_, {error, _} = Err) ->
251
              Err;
×
252
         ({NS, El}, Els) ->
UNCOV
253
              Res = case use_cache(Mod, LServer) of
1,960✔
254
                        true ->
UNCOV
255
                            ets_cache:lookup(
1,960✔
256
                              ?PRIVATE_CACHE, {LUser, LServer, NS},
UNCOV
257
                              fun() -> Mod:get_data(LUser, LServer, NS) end);
58✔
258
                        false ->
259
                            Mod:get_data(LUser, LServer, NS)
×
260
                    end,
UNCOV
261
              case Res of
1,960✔
262
                  {ok, StorageEl} ->
UNCOV
263
                      [StorageEl|Els];
8✔
264
                  error ->
UNCOV
265
                      [El|Els];
1,952✔
266
                  {error, _} = Err ->
267
                      Err
×
268
              end
269
      end, [], Data).
270

271
-spec get_data(binary(), binary()) -> [xmlel()] | {error, _}.
272
get_data(LUser, LServer) ->
273
    Mod = gen_mod:db_mod(LServer, ?MODULE),
×
274
    case Mod:get_all_data(LUser, LServer) of
×
275
        {ok, Els} -> Els;
×
276
        error -> [];
×
277
        {error, _} = Err -> Err
×
278
    end.
279

280
-spec del_data(binary(), binary(), binary()) -> ok | {error, _}.
281
del_data(LUser, LServer, NS) ->
282
    Mod = gen_mod:db_mod(LServer, ?MODULE),
×
283
        case Mod:del_data(LUser, LServer, NS) of
×
284
                ok ->
285
                        case use_cache(Mod, LServer) of
×
286
                                true ->
287
                                        delete_cache(Mod, LUser, LServer, [{NS, #xmlel{}}]);
×
288
                                _ ->
289
                                        ok
×
290
                        end;
291
                Err -> Err
×
292
        end.
293

294
-spec get_users_with_data(binary(), binary()) -> [jid()] | {error, any()}.
295
get_users_with_data(LServer, NS) ->
296
    Mod = gen_mod:db_mod(LServer, ?MODULE),
×
297
        case Mod:get_users_with_data(LServer, NS) of
×
298
                {ok, Users} ->
299
                        [jid:make(User, LServer) || User <- Users];
×
300
                Err -> Err
×
301
        end.
302

303
-spec count_users_with_data(binary(), binary()) -> integer() | {error, any()}.
304
count_users_with_data(LServer, NS) ->
305
    Mod = gen_mod:db_mod(LServer, ?MODULE),
×
306
        case Mod:count_users_with_data(LServer, NS) of
×
307
                {ok, Num} -> Num;
×
308
                Err -> Err
×
309
        end.
310

311
-spec remove_user(binary(), binary()) -> ok.
312
remove_user(User, Server) ->
UNCOV
313
    LUser = jid:nodeprep(User),
16✔
UNCOV
314
    LServer = jid:nameprep(Server),
16✔
UNCOV
315
    Mod = gen_mod:db_mod(Server, ?MODULE),
16✔
UNCOV
316
    Data = case use_cache(Mod, LServer) of
16✔
317
               true ->
UNCOV
318
                   case Mod:get_all_data(LUser, LServer) of
16✔
UNCOV
319
                       {ok, Els} -> filter_xmlels(Els);
8✔
UNCOV
320
                       _ -> []
8✔
321
                   end;
322
               false ->
323
                   []
×
324
           end,
UNCOV
325
    Mod:del_data(LUser, LServer),
16✔
UNCOV
326
    delete_cache(Mod, LUser, LServer, Data).
16✔
327

328
%%%===================================================================
329
%%% Pubsub
330
%%%===================================================================
331
-spec publish_pep_storage_bookmarks(jid(), [{binary(), xmlel()}]) -> ok | {error, stanza_error()}.
332
publish_pep_storage_bookmarks(JID, Data) ->
UNCOV
333
    {_, LServer, _} = LBJID = jid:remove_resource(jid:tolower(JID)),
16✔
UNCOV
334
    case gen_mod:is_loaded(LServer, mod_pubsub) of
16✔
335
        true ->
UNCOV
336
            case lists:keyfind(?NS_STORAGE_BOOKMARKS, 1, Data) of
16✔
337
                false -> ok;
×
338
                {_, El} ->
UNCOV
339
                    case mod_pubsub:get_items(LBJID, ?NS_STORAGE_BOOKMARKS) of
16✔
340
                        {error, #stanza_error{reason = 'item-not-found'}} ->
UNCOV
341
                            PubOpts = [{persist_items, true},
8✔
342
                                       {access_model, whitelist}],
UNCOV
343
                            case mod_pubsub:publish_item(
8✔
344
                                LBJID, LServer, ?NS_STORAGE_BOOKMARKS, JID,
345
                                <<"current">>, [El], PubOpts, all) of
UNCOV
346
                                {result, _} -> ok;
8✔
347
                                {error, _} = Err -> Err
×
348
                            end;
349
                        _ ->
UNCOV
350
                            case mod_pubsub:publish_item(
8✔
351
                                LBJID, LServer, ?NS_STORAGE_BOOKMARKS, JID,
352
                                <<"current">>, [El], [], all) of
UNCOV
353
                                {result, _} -> ok;
8✔
354
                                {error, _} = Err -> Err
×
355
                            end
356
                    end
357
            end;
358
        false ->
359
            ok
×
360
    end.
361

362
-spec publish_pep_native_bookmarks(jid(), [{binary(), xmlel()}]) -> ok | {error, stanza_error()}.
363
publish_pep_native_bookmarks(JID, Data) ->
UNCOV
364
    {_, LServer, _} = LBJID = jid:remove_resource(jid:tolower(JID)),
32✔
UNCOV
365
    case gen_mod:is_loaded(LServer, mod_pubsub) of
32✔
366
        true ->
UNCOV
367
            case lists:keyfind(?NS_STORAGE_BOOKMARKS, 1, Data) of
32✔
368
                {_, Bookmarks0} ->
UNCOV
369
                    Bookmarks = try xmpp:decode(Bookmarks0) of
32✔
UNCOV
370
                                    #bookmark_storage{conference = C} -> C;
32✔
371
                                    _ -> []
×
372
                                catch _:{xmpp_codec, Why} ->
373
                                          ?DEBUG("Failed to decode bookmarks of ~ts: ~ts",
×
374
                                                 [jid:encode(JID), xmpp:format_error(Why)]),
×
375
                                          []
×
376
                                end,
UNCOV
377
                    PubOpts = [{persist_items, true}, {access_model, whitelist}, {max_items, max}, {notify_retract,true}, {notify_delete,true}, {send_last_published_item, never}],
32✔
UNCOV
378
                    case mod_pubsub:get_items(LBJID, ?NS_PEP_BOOKMARKS) of
32✔
379
                        PepBookmarks when is_list(PepBookmarks) ->
UNCOV
380
                            put(mod_private_pep_update, true),
24✔
UNCOV
381
                            PepBookmarksMap = lists:foldl(fun pubsub_item_to_map/2, #{}, PepBookmarks),
24✔
UNCOV
382
                            {ToDelete, Ret} =
24✔
383
                            lists:foldl(
384
                                fun(#bookmark_conference{jid = BookmarkJID} = Bookmark, {Map2, Ret2}) ->
UNCOV
385
                                    PB = storage_bookmark_to_xmpp_bookmark(Bookmark),
24✔
UNCOV
386
                                    case maps:take(jid:tolower(BookmarkJID), Map2) of
24✔
387
                                        {StoredBookmark, Map3} when StoredBookmark == PB ->
UNCOV
388
                                            {Map3, Ret2};
24✔
389
                                        {_, Map4} ->
390
                                            {Map4,
×
391
                                             err_ret(Ret2, mod_pubsub:publish_item(
392
                                                 LBJID, LServer, ?NS_PEP_BOOKMARKS, JID,
393
                                                 jid:encode(BookmarkJID), [xmpp:encode(PB)], [], all))};
394
                                        _ ->
395
                                            {Map2,
×
396
                                             err_ret(Ret2, mod_pubsub:publish_item(
397
                                                 LBJID, LServer, ?NS_PEP_BOOKMARKS, JID,
398
                                                 jid:encode(BookmarkJID), [xmpp:encode(PB)], [], all))}
399
                                    end
400
                                end, {PepBookmarksMap, ok}, Bookmarks),
UNCOV
401
                            Ret4 =
24✔
402
                            maps:fold(
403
                                fun(DeleteJid, _, Ret3) ->
404
                                    err_ret(Ret3, mod_pubsub:delete_item(LBJID, ?NS_PEP_BOOKMARKS,
×
405
                                                                         JID, jid:encode(DeleteJid)))
406
                                end, Ret, ToDelete),
UNCOV
407
                            erase(mod_private_pep_update),
24✔
UNCOV
408
                            Ret4;
24✔
409
                        {error, #stanza_error{reason = 'item-not-found'}} ->
UNCOV
410
                            put(mod_private_pep_update, true),
8✔
UNCOV
411
                            Ret7 =
8✔
412
                            lists:foldl(
413
                                fun(#bookmark_conference{jid = BookmarkJID} = Bookmark, Ret5) ->
UNCOV
414
                                    PB = storage_bookmark_to_xmpp_bookmark(Bookmark),
8✔
UNCOV
415
                                    err_ret(Ret5, mod_pubsub:publish_item(
8✔
416
                                        LBJID, LServer, ?NS_PEP_BOOKMARKS, JID,
417
                                        jid:encode(BookmarkJID), [xmpp:encode(PB)], PubOpts, all))
418
                                end, ok, Bookmarks),
UNCOV
419
                            erase(mod_private_pep_update),
8✔
UNCOV
420
                            Ret7;
8✔
421
                        _ ->
422
                            ok
×
423
                    end;
424
                _ ->
425
                    ok
×
426
            end;
427
        false ->
428
            ok
×
429
    end.
430

431
err_ret({error, _} = E, _) ->
432
    E;
×
433
err_ret(ok, {error, _} = E) ->
434
    E;
×
435
err_ret(_, _) ->
UNCOV
436
    ok.
8✔
437

438
-spec pubsub_publish_item(binary(), binary(), jid(), jid(),
439
                          binary(), [xmlel()]) -> any().
440
pubsub_publish_item(LServer, ?NS_STORAGE_BOOKMARKS,
441
                    #jid{luser = LUser, lserver = LServer} = From,
442
                    #jid{luser = LUser, lserver = LServer},
443
                    _ItemId, [Payload|_]) ->
UNCOV
444
    set_data(From, [{?NS_STORAGE_BOOKMARKS, Payload}], false, true);
16✔
445
pubsub_publish_item(LServer, ?NS_PEP_BOOKMARKS,
446
                    #jid{luser = LUser, lserver = LServer} = From,
447
                    #jid{luser = LUser, lserver = LServer},
448
                    _ItemId, _Payload) ->
UNCOV
449
    NotRecursion = get(mod_private_pep_update) == undefined,
8✔
UNCOV
450
    case mod_pubsub:get_items({LUser, LServer, <<>>}, ?NS_PEP_BOOKMARKS) of
8✔
451
        Bookmarks when is_list(Bookmarks), NotRecursion ->
452
            Bookmarks2 = lists:filtermap(fun pubsub_item_to_storage_bookmark/1, Bookmarks),
×
453
            Payload = xmpp:encode(#bookmark_storage{conference = Bookmarks2}),
×
454
            set_data(From, [{?NS_STORAGE_BOOKMARKS, Payload}], true, false);
×
455
        _ ->
UNCOV
456
            ok
8✔
457
    end;
458
pubsub_publish_item(_, _, _, _, _, _) ->
UNCOV
459
    ok.
224✔
460

461
-spec pubsub_delete_item(binary(), binary(), jid(), jid(), binary()) -> any().
462
pubsub_delete_item(LServer, ?NS_PEP_BOOKMARKS,
463
                    #jid{luser = LUser, lserver = LServer} = From,
464
                    #jid{luser = LUser, lserver = LServer},
465
                    _ItemId) ->
466
    NotRecursion = get(mod_private_pep_update) == undefined,
×
467
    case mod_pubsub:get_items({LUser, LServer, <<>>}, ?NS_PEP_BOOKMARKS) of
×
468
        Bookmarks when is_list(Bookmarks), NotRecursion ->
469
            Bookmarks2 = lists:filtermap(fun pubsub_item_to_storage_bookmark/1, Bookmarks),
×
470
            Payload = xmpp:encode(#bookmark_storage{conference = Bookmarks2}),
×
471
            set_data(From, [{?NS_STORAGE_BOOKMARKS, Payload}], true, false);
×
472
        _ ->
473
            ok
×
474
    end;
475
pubsub_delete_item(_, _, _, _, _) ->
UNCOV
476
    ok.
34✔
477

478
-spec pubsub_item_to_storage_bookmark(#pubsub_item{}) -> {true, bookmark_conference()} | false.
479
pubsub_item_to_storage_bookmark(#pubsub_item{itemid = {Id, _}, payload = [#xmlel{} = B | _]}) ->
480
    try {xmpp:decode(B), jid:decode(Id)} of
×
481
        {#pep_bookmarks_conference{name = Name, autojoin = AutoJoin,
482
                                   nick = Nick, password = Password},
483
         #jid{} = Jid} ->
484
            {true, #bookmark_conference{jid = Jid, name = Name,
×
485
                                        autojoin = AutoJoin, nick = Nick,
486
                                        password = Password}};
487
        {_, _} ->
488
            false
×
489
    catch
490
        _:{xmpp_codec, Why} ->
491
            ?DEBUG("Failed to decode bookmark element (~ts): ~ts",
×
492
                   [Id, xmpp:format_error(Why)]),
×
493
            false;
×
494
        _:{bad_jid, _} ->
495
            ?DEBUG("Failed to decode bookmark ID (~ts)", [Id]),
×
496
            false
×
497
    end;
498
pubsub_item_to_storage_bookmark(_) ->
499
    false.
×
500

501
-spec pubsub_tree_call(Res :: any(), _Tree::any(), atom(), any()) -> any().
502
pubsub_tree_call({error, #stanza_error{reason = 'item-not-found'}} = Res, Tree, get_node,
503
                 [{User, Server, _}, ?NS_PEP_BOOKMARKS] = Args) ->
UNCOV
504
    case get(mod_private_in_pubsub_tree_call) of
24✔
505
        undefined ->
UNCOV
506
            put(mod_private_in_pubsub_tree_call, true),
8✔
UNCOV
507
            bookmarks_to_pep(User, Server),
8✔
UNCOV
508
            Res2 = apply(Tree, get_node, Args),
8✔
UNCOV
509
            erase(mod_private_in_pubsub_tree_call),
8✔
UNCOV
510
            Res2;
8✔
511
        _ ->
UNCOV
512
            Res
16✔
513
    end;
514
pubsub_tree_call(Res, _Tree, _Function, _Args) ->
UNCOV
515
    Res.
240✔
516

517
-spec storage_bookmark_to_xmpp_bookmark(bookmark_conference()) -> pep_bookmarks_conference().
518
storage_bookmark_to_xmpp_bookmark(#bookmark_conference{name = Name, autojoin = AutoJoin, nick = Nick,
519
                                                       password = Password}) ->
UNCOV
520
    #pep_bookmarks_conference{name = Name, autojoin = AutoJoin, nick = Nick,
32✔
521
                              password = Password}.
522

523
-spec pubsub_item_to_map(#pubsub_item{}, map()) -> map().
524
pubsub_item_to_map(#pubsub_item{itemid = {Id, _}, payload = [#xmlel{} = B | _]}, Map) ->
UNCOV
525
    try {xmpp:decode(B), jid:decode(Id)} of
24✔
526
        {#pep_bookmarks_conference{} = B1, #jid{} = Jid} ->
UNCOV
527
            B2 = B1#pep_bookmarks_conference{extensions = undefined},
24✔
UNCOV
528
            maps:put(jid:tolower(Jid), B2, Map);
24✔
529
        {_, _} ->
530
            Map
×
531
    catch
532
        _:{xmpp_codec, Why} ->
533
            ?DEBUG("Failed to decode bookmark element (~ts): ~ts",
×
534
                   [Id, xmpp:format_error(Why)]),
×
535
            Map;
×
536
        _:{bad_jid, _} ->
537
            ?DEBUG("Failed to decode bookmark ID (~ts)", [Id]),
×
538
            Map
×
539
    end;
540
pubsub_item_to_map(_, Map) ->
541
    Map.
×
542

543
%%%===================================================================
544
%%% Commands
545
%%%===================================================================
546
-spec get_commands_spec() -> [ejabberd_commands()].
547
get_commands_spec() ->
UNCOV
548
    [#ejabberd_commands{name = bookmarks_to_pep, tags = [private],
8✔
549
                        desc = "Export private XML storage bookmarks to PEP",
550
                        module = ?MODULE, function = bookmarks_to_pep,
551
                        args = [{user, binary}, {host, binary}],
552
                        args_rename = [{server, host}],
553
                        args_desc = ["Username", "Server"],
554
                        args_example = [<<"bob">>, <<"example.com">>],
555
                        result = {res, restuple},
556
                        result_desc = "Result tuple",
557
                        result_example = {ok, <<"Bookmarks exported">>}}].
558

559
-spec bookmarks_to_pep(binary(), binary())
560
      -> {ok, binary()} | {error, binary()}.
561
bookmarks_to_pep(User, Server) ->
UNCOV
562
    LUser = jid:nodeprep(User),
8✔
UNCOV
563
    LServer = jid:nameprep(Server),
8✔
UNCOV
564
    Mod = gen_mod:db_mod(LServer, ?MODULE),
8✔
UNCOV
565
    Res = case use_cache(Mod, LServer) of
8✔
566
              true ->
UNCOV
567
                  ets_cache:lookup(
8✔
568
                    ?PRIVATE_CACHE, {LUser, LServer, ?NS_STORAGE_BOOKMARKS},
569
                    fun() ->
UNCOV
570
                            Mod:get_data(LUser, LServer, ?NS_STORAGE_BOOKMARKS)
8✔
571
                    end);
572
              false ->
573
                  Mod:get_data(LUser, LServer, ?NS_STORAGE_BOOKMARKS)
×
574
        end,
UNCOV
575
    case Res of
8✔
576
        {ok, El} ->
UNCOV
577
            Data = [{?NS_STORAGE_BOOKMARKS, El}],
8✔
UNCOV
578
            case publish_pep_storage_bookmarks(jid:make(User, Server), Data) of
8✔
579
                ok ->
UNCOV
580
                    case publish_pep_native_bookmarks(jid:make(User, Server), Data) of
8✔
581
                        ok ->
UNCOV
582
                            {ok, <<"Bookmarks exported to PEP node">>};
8✔
583
                        {error, Err} ->
584
                            {error, xmpp:format_stanza_error(Err)}
×
585
                    end;
586
                {error, Err} ->
587
                    {error, xmpp:format_stanza_error(Err)}
×
588

589
            end;
590
        _ ->
591
            {error, <<"Cannot retrieve bookmarks from private XML storage">>}
×
592
    end.
593

594
%%%===================================================================
595
%%% WebAdmin
596
%%%===================================================================
597

598
webadmin_menu_hostuser(Acc, _Host, _Username, _Lang) ->
UNCOV
599
    Acc ++ [{<<"private">>, <<"Private XML Storage">>}].
24✔
600

601
webadmin_page_hostuser(_, Host, User,
602
              #request{path = [<<"private">>]} = R) ->
603
    Res = ?H1GL(<<"Private XML Storage">>, <<"modules/#mod_private">>, <<"mod_private">>)
×
604
          ++ [make_command(private_set, R, [{<<"user">>, User}, {<<"host">>, Host}], []),
605
              make_command(private_get, R, [{<<"user">>, User}, {<<"host">>, Host}], [])],
606
    {stop, Res};
×
607
webadmin_page_hostuser(Acc, _, _, _) -> Acc.
×
608

609
%%%===================================================================
610
%%% Cache
611
%%%===================================================================
612
-spec delete_cache(module(), binary(), binary(), [{binary(), xmlel()}]) -> ok.
613
delete_cache(Mod, LUser, LServer, Data) ->
UNCOV
614
    case use_cache(Mod, LServer) of
40✔
615
        true ->
UNCOV
616
            Nodes = cache_nodes(Mod, LServer),
40✔
UNCOV
617
            lists:foreach(
40✔
618
              fun({NS, _}) ->
UNCOV
619
                      ets_cache:delete(?PRIVATE_CACHE,
32✔
620
                                       {LUser, LServer, NS},
621
                                       Nodes)
622
              end, Data);
623
        false ->
624
            ok
×
625
    end.
626

627
-spec init_cache(module(), binary(), gen_mod:opts()) -> ok.
628
init_cache(Mod, Host, Opts) ->
UNCOV
629
    case use_cache(Mod, Host) of
8✔
630
        true ->
UNCOV
631
            CacheOpts = cache_opts(Opts),
8✔
UNCOV
632
            ets_cache:new(?PRIVATE_CACHE, CacheOpts);
8✔
633
        false ->
634
            ets_cache:delete(?PRIVATE_CACHE)
×
635
    end.
636

637
-spec cache_opts(gen_mod:opts()) -> [proplists:property()].
638
cache_opts(Opts) ->
UNCOV
639
    MaxSize = mod_private_opt:cache_size(Opts),
8✔
UNCOV
640
    CacheMissed = mod_private_opt:cache_missed(Opts),
8✔
UNCOV
641
    LifeTime = mod_private_opt:cache_life_time(Opts),
8✔
UNCOV
642
    [{max_size, MaxSize}, {cache_missed, CacheMissed}, {life_time, LifeTime}].
8✔
643

644
-spec use_cache(module(), binary()) -> boolean().
645
use_cache(Mod, Host) ->
UNCOV
646
    case erlang:function_exported(Mod, use_cache, 1) of
2,032✔
UNCOV
647
        true -> Mod:use_cache(Host);
538✔
UNCOV
648
        false -> mod_private_opt:use_cache(Host)
1,494✔
649
    end.
650

651
-spec cache_nodes(module(), binary()) -> [node()].
652
cache_nodes(Mod, Host) ->
UNCOV
653
    case erlang:function_exported(Mod, cache_nodes, 1) of
40✔
654
        true -> Mod:cache_nodes(Host);
×
UNCOV
655
        false -> ejabberd_cluster:get_nodes()
40✔
656
    end.
657

658
%%%===================================================================
659
%%% Import/Export
660
%%%===================================================================
661
import_info() ->
662
    [{<<"private_storage">>, 4}].
×
663

664
import_start(LServer, DBType) ->
665
    Mod = gen_mod:db_mod(DBType, ?MODULE),
×
666
    Mod:init(LServer, []).
×
667

668
export(LServer) ->
669
    Mod = gen_mod:db_mod(LServer, ?MODULE),
×
670
    Mod:export(LServer).
×
671

672
import(LServer, {sql, _}, DBType, Tab, L) ->
673
    Mod = gen_mod:db_mod(DBType, ?MODULE),
×
674
    Mod:import(LServer, Tab, L).
×
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