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

haraka / Haraka / 27448084861

12 Jun 2026 11:03PM UTC coverage: 72.177% (-0.6%) from 72.806%
27448084861

Pull #3602

github

web-flow
Merge cdc635d37 into f44b0bbef
Pull Request #3602: npm ignore, remove tests and clutter

1732 of 2290 branches covered (75.63%)

7567 of 10484 relevant lines covered (72.18%)

26.58 hits per line

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

80.33
/plugins.js
1
'use strict'
16✔
2
// load all defined plugins
16✔
3

16✔
4
// node built-ins
16✔
5
const fs = require('node:fs')
16✔
6
const path = require('node:path')
16✔
7
const vm = require('node:vm')
16✔
8

16✔
9
// npm modules
16✔
10
exports.config = require('haraka-config')
16✔
11
const constants = require('haraka-constants')
16✔
12

16✔
13
// local modules
16✔
14
const logger = require('./logger')
16✔
15

16✔
16
exports.registered_hooks = {}
16✔
17
exports.registered_plugins = {}
16✔
18
exports.plugin_list = []
16✔
19

16✔
20
let order = 0
16✔
21

16✔
22
class Plugin {
16✔
23
    constructor(name) {
16✔
24
        this.name = name
52✔
25
        this.base = {}
52✔
26
        this.timeout = get_timeout(name)
52✔
27
        this._get_plugin_path()
52✔
28
        this.config = this._get_config()
52✔
29
        this.hooks = {}
52✔
30
    }
52✔
31

16✔
32
    haraka_require(name) {
16✔
33
        return require(`./${name}`)
×
34
    }
×
35

16✔
36
    _get_plugin_path() {
16✔
37
        /* From https://github.com/haraka/Haraka/pull/1278#issuecomment-168856528
52✔
38
        In Development mode, or install via a plain "git clone":
52✔
39

52✔
40
            Plain plugin in plugins/ folder
52✔
41
            Plugin in a folder in plugins/<name>/ folder. Contains a package.json.
52✔
42
            Plugin in node_modules. Contains a package.json file.
52✔
43

52✔
44
        In "installed" mode (via haraka -i <path>):
52✔
45

52✔
46
            Plain plugin in <path>/plugins/ folder
52✔
47
            Plugin in a folder in <path>/plugins/<name>/folder. (same concept as above)
52✔
48
            Plugin in <path>/node_modules. Contains a package.json file.
52✔
49
            Core plugin in <core_haraka_dir>/plugins/ folder
52✔
50
            Plugin in a folder in <core_haraka_dir>/plugins/<name>/ folder. (same concept as above)
52✔
51
            Plugin in <core_haraka_dir>/node_modules.
52✔
52
        */
52✔
53

52✔
54
        this.hasPackageJson = false
52✔
55
        const name = this.name.startsWith('haraka-plugin-') ? this.name.slice(14) : this.name
52✔
56
        if (this.name !== name) this.name = name
52✔
57

52✔
58
        let paths = []
52✔
59
        if (process.env.HARAKA) {
52✔
60
            // Installed mode - started via bin/haraka
8✔
61
            paths = [...paths, ...plugin_search_paths(process.env.HARAKA, name)]
8✔
62

8✔
63
            // permit local "folder" plugins (/$name/package.json) (see #1649)
8✔
64
            paths.push(
8✔
65
                path.resolve(process.env.HARAKA, 'plugins', name, 'package.json'),
8✔
66
                path.resolve(process.env.HARAKA, 'node_modules', name, 'package.json'),
8✔
67
            )
8✔
68
        }
8✔
69

52✔
70
        // development mode
52✔
71
        paths = [...paths, ...plugin_search_paths(__dirname, name)]
52✔
72
        for (const pp of paths) {
52✔
73
            if (this.plugin_path) continue
196✔
74
            try {
74✔
75
                fs.statSync(pp)
74✔
76
                this.plugin_path = pp
74✔
77
                if (path.basename(pp) === 'package.json') {
196✔
78
                    this.hasPackageJson = true
4✔
79
                }
4✔
80
            } catch (ignore) {}
196✔
81
        }
196✔
82
    }
52✔
83

16✔
84
    _get_config() {
16✔
85
        if (this.hasPackageJson) {
52✔
86
            // It's a package/folder plugin - look in plugin folder for defaults,
4✔
87
            // haraka/config folder for overrides
4✔
88
            return exports.config.module_config(path.dirname(this.plugin_path), process.env.HARAKA || __dirname)
4✔
89
        }
4✔
90

48✔
91
        if (process.env.HARAKA) {
52✔
92
            // Plain .js file, installed mode - look in core folder for defaults,
5✔
93
            // install dir for overrides
5✔
94
            return exports.config.module_config(__dirname, process.env.HARAKA)
5✔
95
        }
5✔
96

43✔
97
        if (process.env.HARAKA_TEST_DIR) {
52✔
98
            return exports.config.module_config(process.env.HARAKA_TEST_DIR)
36✔
99
        }
36✔
100

7✔
101
        // Plain .js file, git mode - just look in this folder
7✔
102
        return exports.config.module_config(__dirname)
7✔
103
    }
52✔
104

16✔
105
    register_hook(hook_name, method_name, priority) {
16✔
106
        priority = parseInt(priority)
60✔
107
        if (!priority) priority = 0
60✔
108
        if (priority > 100) priority = 100
60!
109
        if (priority < -100) priority = -100
60!
110

60✔
111
        if (!Array.isArray(exports.registered_hooks[hook_name])) {
60✔
112
            exports.registered_hooks[hook_name] = []
42✔
113
        }
42✔
114
        exports.registered_hooks[hook_name].push({
60✔
115
            plugin: this.name,
60✔
116
            method: method_name,
60✔
117
            priority,
60✔
118
            timeout: this.timeout,
60✔
119
            order: order++,
60✔
120
        })
60✔
121
        this.hooks[hook_name] = this.hooks[hook_name] || []
60✔
122
        this.hooks[hook_name].push(method_name)
60✔
123

60✔
124
        plugins.logdebug(`registered hook ${hook_name} to ${this.name}.${method_name} priority ${priority}`)
60✔
125
    }
60✔
126

16✔
127
    register() {} // noop
16✔
128

16✔
129
    inherits(parent_name) {
16✔
130
        const parent_plugin = plugins._load_and_compile_plugin(parent_name)
13✔
131
        for (const method in parent_plugin) {
13✔
132
            if (!this[method]) {
324✔
133
                this[method] = parent_plugin[method]
104✔
134
            }
104✔
135
        }
324✔
136
        if (parent_plugin.register) {
13✔
137
            parent_plugin.register.call(this)
13✔
138
        }
13✔
139
        this.base[parent_name] = parent_plugin
13✔
140
    }
13✔
141

16✔
142
    _make_custom_require() {
16✔
143
        return (module) => {
41✔
144
            if (this.hasPackageJson) {
32✔
145
                const mod = require(module)
2✔
146
                constants.import(global)
2✔
147
                global.server = plugins.server
2✔
148
                return mod
2✔
149
            }
2✔
150

30✔
151
            if (module === './config') {
30!
152
                return this.config
×
153
            }
×
154

30✔
155
            if (!/^\./.test(module)) {
30✔
156
                return require(module)
24✔
157
            }
24✔
158

6✔
159
            for (const ext of [`${module}.js`, module]) {
30✔
160
                if (fs.existsSync(path.join(__dirname, ext))) {
12!
161
                    return require(module)
×
162
                }
×
163
            }
12✔
164

6✔
165
            return require(path.join(path.dirname(this.plugin_path), module))
6✔
166
        }
32✔
167
    }
41✔
168

16✔
169
    _get_code(pi_path) {
16✔
170
        if (this.hasPackageJson) {
41✔
171
            let packageDir = path.dirname(pi_path)
2✔
172
            if (/^win(32|64)/.test(process.platform)) {
2!
173
                // escape the c:\path\back\slashes else they disappear
×
174
                packageDir = packageDir.replace(/\\/g, '\\\\')
×
175
            }
×
176
            return `var _p = require("${packageDir}"); for (var k in _p) { exports[k] = _p[k] }`
2✔
177
        }
2✔
178

39✔
179
        try {
39✔
180
            return `"use strict";${fs.readFileSync(pi_path)}`
39✔
181
        } catch (err) {
41!
182
            if (exports.config.get('smtp.ini').main.ignore_bad_plugins) {
×
183
                plugins.logcrit(`Loading ${this.name} failed: ${err}`)
×
184
                return
×
185
            }
×
186
            throw `Loading plugin ${this.name} failed: ${err}`
×
187
        }
×
188
    }
41✔
189

16✔
190
    _compile() {
16✔
191
        const pp = this.plugin_path
41✔
192
        const code = this._get_code(pp)
41✔
193
        if (!code) return
41!
194

41✔
195
        const sandbox = {
41✔
196
            require: this._make_custom_require(),
41✔
197
            __filename: pp,
41✔
198
            __dirname: path.dirname(pp),
41✔
199
            exports: this,
41✔
200
            fetch,
41✔
201
            clearTimeout,
41✔
202
            clearInterval,
41✔
203
            process,
41✔
204
            setInterval,
41✔
205
            setTimeout,
41✔
206
            Buffer,
41✔
207
            Math,
41✔
208
            server: plugins.server,
41✔
209
            setImmediate,
41✔
210
        }
41✔
211
        if (this.hasPackageJson) {
41✔
212
            delete sandbox.__filename
2✔
213
        }
2✔
214
        constants.import(sandbox)
41✔
215
        try {
41✔
216
            vm.runInNewContext(code, sandbox, pp)
41✔
217
        } catch (err) {
41!
218
            plugins.logcrit(`compiling '${this.name}' failed`)
×
219
            if (exports.config.get('smtp.ini').main.ignore_bad_plugins) {
×
220
                plugins.logcrit(`Loading '${this.name}' failed: ${err.message} - skipping`)
×
221
                return
×
222
            }
×
223
            throw err // default is to re-throw and stop Haraka
×
224
        }
×
225
    }
41✔
226
}
16✔
227

16✔
228
exports.shutdown_plugins = () => {
16✔
229
    for (const i in exports.registered_plugins) {
×
230
        if (exports.registered_plugins[i].shutdown) {
×
231
            exports.registered_plugins[i].shutdown()
×
232
        }
×
233
    }
×
234
}
×
235

16✔
236
process.on('message', (msg) => {
16✔
237
    if (msg.event && msg.event == 'plugins.shutdown') {
×
238
        plugins.loginfo('Shutting down')
×
239
        exports.shutdown_plugins()
×
240
    }
×
241
})
16✔
242

16✔
243
function plugin_search_paths(prefix, name) {
60✔
244
    return [
60✔
245
        // Haraka/plugins/*.js
60✔
246
        path.resolve(prefix, 'plugins', `${name}.js`),
60✔
247

60✔
248
        // Haraka/node_modules/haraka-plugin-*/package.json
60✔
249
        path.resolve(prefix, 'node_modules', `haraka-plugin-${name}`, 'package.json'),
60✔
250

60✔
251
        // global node_modules/haraka-plugin-*/package.json
60✔
252
        path.resolve(prefix, '..', `haraka-plugin-${name}`, 'package.json'),
60✔
253
    ]
60✔
254
}
60✔
255

16✔
256
function get_timeout(name) {
52✔
257
    let timeout = parseFloat(exports.config.get(`${name}.timeout`))
52✔
258
    if (isNaN(timeout)) {
52✔
259
        plugins.logdebug(`no timeout in ${name}.timeout`)
49✔
260
        timeout = parseFloat(exports.config.get('plugin_timeout'))
49✔
261
    }
49✔
262
    if (isNaN(timeout)) {
52✔
263
        plugins.logdebug('no timeout in plugin_timeout')
49✔
264
        timeout = 30
49✔
265
    }
49✔
266

52✔
267
    plugins.logdebug(`plugin ${name} timeout is: ${timeout}s`)
52✔
268
    return timeout
52✔
269
}
52✔
270

16✔
271
logger.add_log_methods(Plugin)
16✔
272

16✔
273
const plugins = exports
16✔
274

16✔
275
logger.add_log_methods(plugins, 'plugins')
16✔
276

16✔
277
plugins.Plugin = Plugin
16✔
278

16✔
279
plugins.load_plugins = (override) => {
16✔
280
    plugins.logdebug('Loading')
6✔
281
    let plugin_list
6✔
282
    if (override) {
6!
283
        if (!Array.isArray(override)) override = [override]
×
284
        plugin_list = override
×
285
    } else {
6✔
286
        plugin_list = exports.config.get('plugins', 'list')
6✔
287
    }
6✔
288

6✔
289
    for (let plugin of plugin_list) {
6✔
290
        if (plugin.startsWith('haraka-plugin-')) plugin = plugin.substring(14)
24!
291
        if (plugins.deprecated[plugin]) {
24!
292
            plugins.lognotice(
×
293
                `${plugin} has been replaced by '${plugins.deprecated[plugin]}'. Please update config/plugins`,
×
294
            )
×
295
            plugins.load_plugin(plugins.deprecated[plugin])
×
296
        } else {
24✔
297
            plugins.load_plugin(plugin)
24✔
298
        }
24✔
299
    }
24✔
300

6✔
301
    plugins.plugin_list = Object.keys(plugins.registered_plugins)
6✔
302

6✔
303
    // Sort registered_hooks by priority
6✔
304
    for (const hook of Object.keys(plugins.registered_hooks)) {
6✔
305
        plugins.registered_hooks[hook].sort((a, b) => {
42✔
306
            if (a.priority < b.priority) return -1
18!
307
            if (a.priority > b.priority) return 1
18!
308
            if (a.priority == b.priority) {
18✔
309
                if (a.order > b.order) return 1
18✔
310
                return -1
×
311
            }
×
312
            return 0
×
313
        })
42✔
314
    }
42✔
315

6✔
316
    logger.dump_logs() // now logging plugins are loaded.
6✔
317
}
6✔
318

16✔
319
plugins.deprecated = {
16✔
320
    'auth/auth_ldap': 'auth-ldap',
16✔
321
    backscatterer: 'dns-list',
16✔
322
    'connect.asn': 'asn',
16✔
323
    'connect.fcrdns': 'fcrdns',
16✔
324
    'connect.geoip': 'geoip',
16✔
325
    'connect.p0f': 'p0f',
16✔
326
    'connect.rdns_access': 'access',
16✔
327
    'data.nomsgid': 'headers',
16✔
328
    'data.noreceived': 'headers',
16✔
329
    'data.rfc5322_header_checks': 'headers',
16✔
330
    'data.headers': 'headers',
16✔
331
    dkim_sign: 'dkim',
16✔
332
    dkim_verify: 'dkim',
16✔
333
    'data.uribl': 'uribl',
16✔
334
    dnsbl: 'dns-list',
16✔
335
    dnswl: 'dns-list',
16✔
336
    'log.syslog': 'syslog',
16✔
337
    'mail_from.access': 'access',
16✔
338
    'mail_from.blocklist': 'access',
16✔
339
    'mail_from.nobounces': 'bounce',
16✔
340
    max_unrecognized_commands: 'limit',
16✔
341
    rate_limit: 'limit',
16✔
342
    'rcpt_to.access': 'access',
16✔
343
    'rcpt_to.blocklist': 'access',
16✔
344
    'rcpt_to.ldap': 'rcpt-ldap',
16✔
345
    'rcpt_to.max_count': 'limit',
16✔
346
    'rcpt_to.qmail_deliverable': 'qmail-deliverable',
16✔
347
    'rdns.regexp': 'access',
16✔
348
    relay_acl: 'relay',
16✔
349
    relay_all: 'relay',
16✔
350
    relay_force_routing: 'relay',
16✔
351
}
16✔
352

16✔
353
plugins.load_plugin = (name) => {
16✔
354
    plugins.loginfo(`loading ${name}`)
24✔
355

24✔
356
    const plugin = plugins._load_and_compile_plugin(name)
24✔
357
    if (plugin) {
24✔
358
        plugins._register_plugin(plugin)
24✔
359
    }
24✔
360

24✔
361
    plugins.registered_plugins[name] = plugin
24✔
362
}
24✔
363

16✔
364
// Set in server.js; initialized to empty object
16✔
365
// to prevent it from blowing up any unit tests.
16✔
366
plugins.server = { notes: {} }
16✔
367

16✔
368
plugins._load_and_compile_plugin = (name) => {
16✔
369
    const plugin = new Plugin(name)
37✔
370
    if (!plugin.plugin_path) {
37!
371
        const err = `Loading plugin ${plugin.name} failed: No plugin with this name found`
×
372
        if (exports.config.get('smtp.ini').main.ignore_bad_plugins) {
×
373
            plugins.logcrit(err)
×
374
            return
×
375
        }
×
376
        throw err
×
377
    }
×
378
    plugin._compile()
37✔
379
    return plugin
37✔
380
}
37✔
381

16✔
382
plugins._register_plugin = (plugin) => {
16✔
383
    plugin.register()
24✔
384

24✔
385
    // register any hook_blah methods.
24✔
386
    for (const method in plugin) {
24✔
387
        const result = method.match(/^hook_(\w+)\b/)
618✔
388
        if (result) {
618✔
389
            plugin.register_hook(result[1], method)
30✔
390
        }
30✔
391
    }
618✔
392
}
24✔
393

16✔
394
plugins.run_hooks = (hook, object, params) => {
16✔
395
    if (client_disconnected(object) && !is_required_hook(hook)) {
321!
396
        object.logdebug(`aborting ${hook} hook`)
×
397
        return
×
398
    }
×
399

321✔
400
    if (hook !== 'log') object.logdebug(`running ${hook} hooks`)
321✔
401

321✔
402
    if (is_required_hook(hook) && object.current_hook) {
321✔
403
        object.current_hook[2]() // call cancel function
1✔
404
    }
1✔
405

321✔
406
    if (!is_required_hook(hook) && hook !== 'deny' && object.hooks_to_run && object.hooks_to_run.length) {
321!
407
        throw new Error('We are already running hooks! Fatal error!')
×
408
    }
×
409

321✔
410
    if (hook === 'deny') {
321!
411
        // Save the hooks_to_run list so that we can run any remaining
×
412
        // plugins on the previous hook once this hook is complete.
×
413
        object.saved_hooks_to_run = object.hooks_to_run
×
414
    }
×
415
    object.hooks_to_run = []
321✔
416

321✔
417
    if (plugins.registered_hooks[hook]) {
321✔
418
        for (const item of plugins.registered_hooks[hook]) {
20✔
419
            const plugin = plugins.registered_plugins[item.plugin]
33✔
420
            object.hooks_to_run.push([plugin, item.method])
33✔
421
        }
33✔
422
    }
20✔
423

321✔
424
    plugins.run_next_hook(hook, object, params)
321✔
425
}
321✔
426

16✔
427
plugins.run_next_hook = (hook, object, params) => {
16✔
428
    if (client_disconnected(object) && !is_required_hook(hook)) {
333!
429
        object.logdebug(`aborting ${hook} hook`)
×
430
        return
×
431
    }
×
432
    let called_once = false
333✔
433
    let timeout_id
333✔
434
    let timed_out = false
333✔
435
    let cancelled = false
333✔
436
    let item
333✔
437

333✔
438
    function cancel() {
333✔
439
        if (timeout_id) clearTimeout(timeout_id)
1✔
440
        cancelled = true
1✔
441
    }
1✔
442
    function callback(retval, msg) {
333✔
443
        if (timeout_id) clearTimeout(timeout_id)
333✔
444
        object.current_hook = null
333✔
445
        if (cancelled) return // This hook has been cancelled
333✔
446

332✔
447
        // Bail if client has disconnected
332✔
448
        if (client_disconnected(object) && !is_required_hook(hook)) {
333!
449
            object.logdebug(`ignoring ${item[0].name} plugin callback`)
×
450
            return
×
451
        }
×
452
        if (called_once && hook !== 'log') {
333!
453
            if (!timed_out) {
×
454
                object.logerror(`${item[0].name} plugin ran callback ` + `multiple times - ignoring subsequent calls`)
×
455
                // Write a stack trace to the log to aid debugging
×
456
                object.logerror(new Error().stack)
×
457
            }
×
458
            return
×
459
        }
×
460
        called_once = true
332✔
461
        if (!retval) retval = constants.cont
333✔
462

332✔
463
        log_run_item(item, hook, retval, object, params, msg)
332✔
464

332✔
465
        if (object.hooks_to_run.length !== 0) {
333✔
466
            if (retval === constants.cont) {
12✔
467
                return plugins.run_next_hook(hook, object, params)
12✔
468
            }
12✔
469
            if (hook === 'connect_init' || hook === 'disconnect') {
12!
470
                // these hooks ignore retval and always run for every plugin
×
471
                return plugins.run_next_hook(hook, object, params)
×
472
            }
×
473
        }
12✔
474

320✔
475
        const respond_method = `${hook}_respond`
320✔
476
        if (item && is_deny_retval(retval) && hook.substring(0, 5) !== 'init_') {
333!
477
            object.deny_respond = get_denyfn(object, hook, params, retval, msg, respond_method)
×
478
            plugins.run_hooks('deny', object, [retval, msg, item[0].name, item[1], params, hook])
×
479
        } else {
333✔
480
            object.hooks_to_run = []
320✔
481
            object[respond_method](retval, msg, params)
320✔
482
        }
320✔
483
    }
333✔
484

333✔
485
    if (!object.hooks_to_run.length) return callback()
333✔
486

32✔
487
    // shift the next one off the stack and run it.
32✔
488
    item = object.hooks_to_run.shift()
32✔
489
    item.push(cancel)
32✔
490

32✔
491
    if (hook !== 'log' && item[0].timeout) {
333✔
492
        timeout_id = setTimeout(() => {
32✔
493
            timed_out = true
×
494
            object.logcrit(`Plugin ${item[0].name} timed out on hook ${hook} - make sure it calls the callback`)
×
495
            callback(constants.denysoft, 'plugin timeout')
×
496
        }, item[0].timeout * 1000)
32✔
497
    }
32✔
498

32✔
499
    if (hook !== 'log') {
32✔
500
        object.logdebug(`running ${hook} hook in ${item[0].name} plugin`)
32✔
501
    }
32✔
502

32✔
503
    if (object.transaction?.notes.skip_plugins.includes(item[0].name)) {
333!
504
        object.logdebug(`skipping ${item[0].name}_${hook} by request in notes`)
×
505
        return callback()
×
506
    }
×
507

32✔
508
    try {
32✔
509
        object.current_hook = item
32✔
510
        object.hook = hook
32✔
511
        item[0][item[1]].call(item[0], callback, object, params)
32✔
512
    } catch (err) {
311!
513
        if (hook !== 'log') {
×
514
            object.logcrit(`Plugin ${item[0].name} failed: ${err.stack || err}`)
×
515
        }
×
516
        callback()
×
517
    }
×
518
}
333✔
519

16✔
520
function client_disconnected(object) {
986✔
521
    if (object.constructor.name === 'Connection' && object.state >= constants.connection.state.DISCONNECTING) {
986✔
522
        object.logdebug('client has disconnected')
27✔
523
        return true
27✔
524
    }
27✔
525
    return false
959✔
526
}
986✔
527

16✔
528
function is_required_hook(hook) {
669✔
529
    // Hooks that must always run
669✔
530
    switch (hook) {
669✔
531
        case 'reset_transaction':
669✔
532
        case 'disconnect':
669✔
533
        case 'log':
669✔
534
            return true
515✔
535
        default:
669✔
536
            return false
154✔
537
    }
669✔
538
}
669✔
539

16✔
540
function log_run_item(item, hook, retval, object, params, msg) {
332✔
541
    if (!item) return
332✔
542
    if (hook === 'log') return
310!
543

31✔
544
    let log = 'logdebug'
31✔
545
    const is_not_cont = retval !== constants.cont && logger.would_log(logger.LOGINFO)
310✔
546
    if (is_not_cont) log = 'loginfo'
332✔
547
    if (is_not_cont || logger.would_log(logger.LOGDEBUG)) {
332✔
548
        object[log]({
10✔
549
            hook,
10✔
550
            plugin: item[0].name,
10✔
551
            function: item[1],
10✔
552
            params: params ? (typeof params === 'string' ? params : params[0]) : '',
10!
553
            retval: constants.translate(retval),
10✔
554
            msg: sanitize(msg),
10✔
555
        })
10✔
556
    }
10✔
557
}
332✔
558

16✔
559
function sanitize(msg) {
10✔
560
    if (!msg) return ''
10✔
561
    if (typeof msg === 'string') return msg
×
562
    if (typeof msg === 'object') {
×
563
        if (msg.constructor.name === 'DSN') return msg.reply
×
564
        const sanitized = { ...msg } // copy the message
×
565
        for (const priv of ['password', 'auth_pass']) {
×
566
            delete sanitized[priv]
×
567
        }
×
568
        return JSON.stringify(sanitized)
×
569
    }
×
570
    logger.logerror(`what is ${msg} (typeof ${typeof msg})?`)
×
571
}
10✔
572

16✔
573
function is_deny_retval(val) {
19✔
574
    switch (val) {
19✔
575
        case constants.deny:
19!
576
        case constants.denysoft:
19!
577
        case constants.denydisconnect:
19!
578
        case constants.denysoftdisconnect:
19!
579
            return true
×
580
    }
19✔
581
    return false
19✔
582
}
19✔
583

16✔
584
function get_denyfn(object, hook, params, retval, msg, respond_method) {
×
585
    return (deny_retval, deny_msg) => {
×
586
        switch (deny_retval) {
×
587
            case constants.ok:
×
588
                // Override rejection
×
589
                object.loginfo(`deny(soft?) overridden by deny hook${deny_msg ? ': deny_msg' : ''}`)
×
590
                // Restore hooks_to_run with saved copy so that
×
591
                // any other plugins on this hook can also run.
×
592
                if (object.saved_hooks_to_run.length > 0) {
×
593
                    object.hooks_to_run = object.saved_hooks_to_run
×
594
                    plugins.run_next_hook(hook, object, params)
×
595
                } else {
×
596
                    object[respond_method](constants.cont, deny_msg, params)
×
597
                }
×
598
                break
×
599
            default:
×
600
                object.saved_hooks_to_run = []
×
601
                object.hooks_to_run = []
×
602
                object[respond_method](retval, msg, params)
×
603
        }
×
604
    }
×
605
}
×
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