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

processone / ejabberd / 1212

18 Nov 2025 12:37PM UTC coverage: 33.784% (+0.003%) from 33.781%
1212

push

github

badlop
mod_conversejs: Improve link to conversejs in WebAdmin (#4495)

Until now, the WebAdmin menu included a link to the first request handler
with mod_conversejs that the admin configured in ejabberd.yml
That link included the authentication credentials hashed as URI arguments
if using HTTPS. Then process/2 extracted those arguments and passed them
as autologin options to Converse.

From now, mod_conversejs automatically adds a request_handler nested in
webadmin subpath. The webadmin menu links to that converse URI; this allows
to access the HTTP auth credentials, no need to explicitly pass them.
process/2 extracts this HTTP auth and passes autologin options to Converse.
Now scram password storage is supported too.

This minimum configuration allows WebAdmin to access Converse:

listen:
  -
    port: 5443
    module: ejabberd_http
    tls: true
    request_handlers:
      /admin: ejabberd_web_admin
      /ws: ejabberd_http_ws
modules:
  mod_conversejs:
    conversejs_resources: "/home/conversejs/12.0.0/dist"

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

11290 existing lines in 174 files now uncovered.

15515 of 45924 relevant lines covered (33.78%)

1277.8 hits per line

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

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

25
-module(mod_offline_sql).
26

27

28
-behaviour(mod_offline).
29

30
-export([init/2, store_message/1, pop_messages/2, remove_expired_messages/1,
31
         remove_old_messages/2, remove_user/2, read_message_headers/2,
32
         read_message/3, remove_message/3, read_all_messages/2,
33
         remove_all_messages/2, count_messages/2, import/1, export/1, remove_old_messages_batch/3]).
34
-export([sql_schemas/0]).
35

36
-include_lib("xmpp/include/xmpp.hrl").
37
-include("mod_offline.hrl").
38
-include("logger.hrl").
39
-include("ejabberd_sql_pt.hrl").
40

41
%%%===================================================================
42
%%% API
43
%%%===================================================================
44
init(Host, _Opts) ->
UNCOV
45
    ejabberd_sql_schema:update_schema(Host, ?MODULE, sql_schemas()),
7✔
UNCOV
46
    ok.
7✔
47

48
sql_schemas() ->
UNCOV
49
    [#sql_schema{
7✔
50
        version = 1,
51
        tables =
52
            [#sql_table{
53
                name = <<"spool">>,
54
                columns =
55
                    [#sql_column{name = <<"username">>, type = text},
56
                     #sql_column{name = <<"server_host">>, type = text},
57
                     #sql_column{name = <<"xml">>, type = {text, big}},
58
                     #sql_column{name = <<"seq">>, type = bigserial},
59
                     #sql_column{name = <<"created_at">>, type = timestamp,
60
                                 default = true}],
61
                indices = [#sql_index{
62
                              columns = [<<"server_host">>, <<"username">>]},
63
                           #sql_index{
64
                              columns = [<<"created_at">>]}]}]}].
65

66
store_message(#offline_msg{us = {LUser, LServer}} = M) ->
UNCOV
67
    From = M#offline_msg.from,
903✔
UNCOV
68
    To = M#offline_msg.to,
903✔
UNCOV
69
    Packet = xmpp:set_from_to(M#offline_msg.packet, From, To),
903✔
UNCOV
70
    NewPacket = misc:add_delay_info(
903✔
71
                  Packet, jid:make(LServer),
72
                  M#offline_msg.timestamp,
73
                  <<"Offline Storage">>),
UNCOV
74
    XML = fxml:element_to_binary(
903✔
75
            xmpp:encode(NewPacket)),
UNCOV
76
    case ejabberd_sql:sql_query(
903✔
77
           LServer,
UNCOV
78
           ?SQL_INSERT(
903✔
79
              "spool",
80
              ["username=%(LUser)s",
81
               "server_host=%(LServer)s",
82
               "xml=%(XML)s"])) of
83
        {updated, _} ->
UNCOV
84
            ok;
903✔
85
        _ ->
86
            {error, db_failure}
×
87
    end.
88

89
pop_messages(LUser, LServer) ->
UNCOV
90
    case get_and_del_spool_msg_t(LServer, LUser) of
196✔
91
        {atomic, {selected, Rs}} ->
UNCOV
92
            {ok, lists:flatmap(
196✔
93
                   fun({_, XML}) ->
UNCOV
94
                           case xml_to_offline_msg(XML) of
868✔
95
                               {ok, Msg} ->
UNCOV
96
                                   [Msg];
868✔
97
                               _Err ->
98
                                   []
×
99
                           end
100
                   end, Rs)};
101
        Err ->
102
            {error, Err}
×
103
    end.
104

105
remove_expired_messages(_LServer) ->
106
    %% TODO
107
    {atomic, ok}.
×
108

109
remove_old_messages(Days, LServer) ->
110
    case ejabberd_sql:sql_query(
×
111
           LServer,
112
           fun(pgsql, _) ->
113
                   ejabberd_sql:sql_query_t(
×
114
                     ?SQL("DELETE FROM spool"
×
115
                          " WHERE created_at <"
116
                          " NOW() - %(Days)d * INTERVAL '1 DAY'"));
117
              (sqlite, _) ->
118
                  ejabberd_sql:sql_query_t(
×
119
                      ?SQL("DELETE FROM spool"
×
120
                           " WHERE created_at <"
121
                           " DATETIME('now', '-%(Days)d days')"));
122
              (_, _) ->
123
                   ejabberd_sql:sql_query_t(
×
124
                     ?SQL("DELETE FROM spool"
×
125
                          " WHERE created_at < NOW() - INTERVAL %(Days)d DAY"))
126
              end)
127
        of
128
        {updated, N} ->
129
            ?INFO_MSG("~p message(s) deleted from offline spool", [N]);
×
130
        Error ->
131
            ?ERROR_MSG("Cannot delete message in offline spool: ~p", [Error])
×
132
    end,
133
    {atomic, ok}.
×
134

135
remove_old_messages_batch(LServer, Days, Batch) ->
136
    case ejabberd_sql:sql_query(
×
137
        LServer,
138
        fun(pgsql, _) ->
139
            ejabberd_sql:sql_query_t(
×
140
                ?SQL("DELETE FROM spool"
×
141
                     " WHERE created_at <"
142
                     " NOW() - %(Days)d * INTERVAL '1 DAY' LIMIT %(Batch)d"));
143
           (sqlite, _) ->
144
               ejabberd_sql:sql_query_t(
×
145
                   ?SQL("DELETE FROM spool"
×
146
                        " WHERE created_at <"
147
                        " DATETIME('now', '-%(Days)d days') LIMIT %(Batch)d"));
148
           (_, _) ->
149
               ejabberd_sql:sql_query_t(
×
150
                   ?SQL("DELETE FROM spool"
×
151
                        " WHERE created_at < NOW() - INTERVAL %(Days)d DAY LIMIT %(Batch)d"))
152
        end)
153
    of
154
        {updated, N} ->
155
            {ok, N};
×
156
        Error ->
157
            {error, Error}
×
158
    end.
159

160
remove_user(LUser, LServer) ->
UNCOV
161
    ejabberd_sql:sql_query(
42✔
162
      LServer,
UNCOV
163
      ?SQL("delete from spool where username=%(LUser)s and %(LServer)H")).
42✔
164

165
read_message_headers(LUser, LServer) ->
UNCOV
166
    case ejabberd_sql:sql_query(
28✔
167
           LServer,
UNCOV
168
           ?SQL("select @(xml)s, @(seq)d from spool"
28✔
169
                " where username=%(LUser)s and %(LServer)H order by seq")) of
170
        {selected, Rows} ->
UNCOV
171
            lists:flatmap(
28✔
172
              fun({XML, Seq}) ->
UNCOV
173
                      case xml_to_offline_msg(XML) of
70✔
174
                          {ok, #offline_msg{from = From,
175
                                            to = To,
176
                                            timestamp = TS,
177
                                            packet = El}} ->
UNCOV
178
                              [{Seq, From, To, TS, El}];
70✔
179
                          _ ->
180
                              []
×
181
                      end
182
              end, Rows);
183
        _Err ->
184
            error
×
185
    end.
186

187
read_message(LUser, LServer, Seq) ->
UNCOV
188
    case ejabberd_sql:sql_query(
42✔
189
           LServer,
UNCOV
190
           ?SQL("select @(xml)s from spool where username=%(LUser)s"
42✔
191
                " and %(LServer)H"
192
                " and seq=%(Seq)d")) of
193
        {selected, [{RawXML}|_]} ->
UNCOV
194
            case xml_to_offline_msg(RawXML) of
35✔
195
                {ok, Msg} ->
UNCOV
196
                    {ok, Msg};
35✔
197
                _ ->
198
                    error
×
199
            end;
200
        _ ->
UNCOV
201
            error
7✔
202
    end.
203

204
remove_message(LUser, LServer, Seq) ->
UNCOV
205
    ejabberd_sql:sql_query(
21✔
206
      LServer,
UNCOV
207
      ?SQL("delete from spool where username=%(LUser)s and %(LServer)H"
21✔
208
           " and seq=%(Seq)d")),
UNCOV
209
    ok.
21✔
210

211
read_all_messages(LUser, LServer) ->
212
    case ejabberd_sql:sql_query(
×
213
           LServer,
214
           ?SQL("select @(xml)s from spool where "
×
215
                "username=%(LUser)s and %(LServer)H order by seq")) of
216
        {selected, Rs} ->
217
            lists:flatmap(
×
218
              fun({XML}) ->
219
                      case xml_to_offline_msg(XML) of
×
220
                          {ok, Msg} -> [Msg];
×
221
                          _ -> []
×
222
                      end
223
              end, Rs);
224
        _ ->
225
            []
×
226
    end.
227

228
remove_all_messages(LUser, LServer) ->
UNCOV
229
    remove_user(LUser, LServer),
7✔
UNCOV
230
    {atomic, ok}.
7✔
231

232
count_messages(LUser, LServer) ->
UNCOV
233
    case catch ejabberd_sql:sql_query(
49✔
234
                 LServer,
UNCOV
235
                 ?SQL("select @(count(*))d from spool "
49✔
236
                      "where username=%(LUser)s and %(LServer)H")) of
237
        {selected, [{Res}]} ->
UNCOV
238
            {cache, Res};
49✔
239
        {selected, []} ->
240
            {cache, 0};
×
241
        _ ->
242
            {nocache, 0}
×
243
    end.
244

245
export(_Server) ->
246
    [{offline_msg,
×
247
      fun(Host, #offline_msg{us = {LUser, LServer}})
248
            when LServer == Host ->
249
                      [?SQL("delete from spool where username=%(LUser)s"
×
250
                            " and %(LServer)H;")];
251
         (_Host, _R) ->
252
              []
×
253
      end},
254
     {offline_msg,
255
      fun(Host, #offline_msg{us = {LUser, LServer},
256
                             timestamp = TimeStamp, from = From, to = To,
257
                             packet = El})
258
            when LServer == Host ->
259
              try xmpp:decode(El, ?NS_CLIENT, [ignore_els]) of
×
260
                  Packet ->
261
                      Packet1 = xmpp:set_from_to(Packet, From, To),
×
262
                      Packet2 = misc:add_delay_info(
×
263
                                  Packet1, jid:make(LServer),
264
                                  TimeStamp, <<"Offline Storage">>),
265
                      XML = fxml:element_to_binary(xmpp:encode(Packet2)),
×
266
                      [?SQL_INSERT(
×
267
                          "spool",
268
                          ["username=%(LUser)s",
269
                           "server_host=%(LServer)s",
270
                           "xml=%(XML)s"])]
271
              catch _:{xmpp_codec, Why} ->
272
                      ?ERROR_MSG("Failed to decode packet ~p of user ~ts@~ts: ~ts",
×
273
                                 [El, LUser, LServer, xmpp:format_error(Why)]),
×
274
                      []
×
275
              end;
276
         (_Host, _R) ->
277
              []
×
278
      end}].
279

280
import(_) ->
281
    ok.
×
282

283
%%%===================================================================
284
%%% Internal functions
285
%%%===================================================================
286
xml_to_offline_msg(XML) ->
UNCOV
287
    case fxml_stream:parse_element(XML) of
973✔
288
        #xmlel{} = El ->
UNCOV
289
            el_to_offline_msg(El);
973✔
290
        Err ->
291
            ?ERROR_MSG("Got ~p when parsing XML packet ~ts",
×
292
                       [Err, XML]),
×
293
            Err
×
294
    end.
295

296
el_to_offline_msg(El) ->
UNCOV
297
    To_s = fxml:get_tag_attr_s(<<"to">>, El),
973✔
UNCOV
298
    From_s = fxml:get_tag_attr_s(<<"from">>, El),
973✔
UNCOV
299
    try
973✔
UNCOV
300
        To = jid:decode(To_s),
973✔
UNCOV
301
        From = jid:decode(From_s),
973✔
UNCOV
302
        {ok, #offline_msg{us = {To#jid.luser, To#jid.lserver},
973✔
303
                          from = From,
304
                          to = To,
305
                          packet = El}}
306
    catch _:{bad_jid, To_s} ->
307
            ?ERROR_MSG("Failed to get 'to' JID from offline XML ~p", [El]),
×
308
            {error, bad_jid_to};
×
309
          _:{bad_jid, From_s} ->
310
            ?ERROR_MSG("Failed to get 'from' JID from offline XML ~p", [El]),
×
311
            {error, bad_jid_from}
×
312
    end.
313

314
get_and_del_spool_msg_t(LServer, LUser) ->
UNCOV
315
    F = fun () ->
196✔
UNCOV
316
                Result =
196✔
317
                    ejabberd_sql:sql_query_t(
UNCOV
318
                      ?SQL("select @(username)s, @(xml)s from spool where "
196✔
319
                           "username=%(LUser)s and %(LServer)H order by seq;")),
UNCOV
320
                DResult =
196✔
321
                    ejabberd_sql:sql_query_t(
UNCOV
322
                      ?SQL("delete from spool where"
196✔
323
                           " username=%(LUser)s and %(LServer)H;")),
UNCOV
324
                case {Result, DResult} of
196✔
325
                    {{selected, Rs}, {updated, DC}} when length(Rs) /= DC ->
326
                        ejabberd_sql:restart(concurent_insert);
×
327
                    _ ->
UNCOV
328
                        Result
196✔
329
                end
330
        end,
UNCOV
331
    ejabberd_sql:sql_transaction(LServer, F).
196✔
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