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

haraka / Haraka / 26860023436

03 Jun 2026 02:27AM UTC coverage: 72.488% (-0.006%) from 72.494%
26860023436

push

github

web-flow
dep(eslint): update @haraka/eslint-config to v3, fix surfaced warnings (#3586)

1721 of 2268 branches covered (75.88%)

33 of 45 new or added lines in 15 files covered. (73.33%)

19 existing lines in 3 files now uncovered.

7807 of 10770 relevant lines covered (72.49%)

25.58 hits per line

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

30.83
/plugins/queue/quarantine.js
1
// quarantine
10✔
2

10✔
3
const fs = require('node:fs')
10✔
4
const path = require('node:path')
10✔
5

10✔
6
exports.register = function () {
10✔
7
    this.load_quarantine_ini()
×
8

×
9
    this.register_hook('queue', 'quarantine')
×
10
    this.register_hook('queue_outbound', 'quarantine')
×
11
}
×
12

×
13
exports.hook_init_master = function (next) {
10✔
14
    this.init_quarantine_dir(() => {
×
15
        this.clean_tmp_directory(next)
×
16
    })
×
17
}
×
18

×
19
exports.load_quarantine_ini = function () {
10✔
20
    this.cfg = this.config.get('quarantine.ini', () => {
10✔
21
        this.load_quarantine_ini()
10✔
22
    })
×
23
}
×
24

×
25
const zeroPad = (exports.zeroPad = (n, digits) => {
10✔
26
    n = n.toString()
5✔
27
    while (n.length < digits) {
5✔
28
        n = `0${n}`
5✔
29
    }
5✔
30
    return n
5✔
31
})
5✔
32

5✔
33
exports.clean_tmp_directory = function (next) {
10✔
34
    const tmp_dir = path.join(this.get_base_dir(), 'tmp')
×
35

×
36
    if (fs.existsSync(tmp_dir)) {
×
37
        const dirent = fs.readdirSync(tmp_dir)
×
38
        this.loginfo(`Removing temporary files from: ${tmp_dir}`)
×
39
        for (const element of dirent) {
×
40
            fs.unlinkSync(path.join(tmp_dir, element))
×
41
        }
×
42
    }
×
43
    next()
×
44
}
×
45

×
46
function wants_quarantine(connection) {
10✔
47
    const { notes, transaction } = connection ?? {}
3✔
48

3✔
49
    if (notes.quarantine) return notes.quarantine
3!
50

×
51
    if (transaction.notes.quarantine) return transaction.notes.quarantine
3!
52

×
53
    return transaction.notes.get('queue.wants') === 'quarantine'
3✔
54
}
3✔
55

3✔
56
exports.get_base_dir = function () {
10✔
57
    if (this.cfg.main.quarantine_path) return this.cfg.main.quarantine_path
2✔
58
    return '/var/spool/haraka/quarantine'
1✔
59
}
1✔
60

1✔
61
exports.init_quarantine_dir = function (done) {
10✔
62
    const tmp_dir = path.join(this.get_base_dir(), 'tmp')
×
63
    fs.promises
×
64
        .mkdir(tmp_dir, { recursive: true })
×
NEW
65
        .then(() => this.loginfo(`created ${tmp_dir}`))
×
NEW
66
        .catch(() => this.logerror(`Unable to create ${tmp_dir}`))
×
67
        .finally(done)
×
68
}
×
69

×
70
exports.quarantine = function (next, connection) {
10✔
71
    const quarantine = wants_quarantine(connection)
3✔
72
    this.logdebug(`quarantine: ${quarantine}`)
3✔
73
    if (!quarantine) return next()
3✔
74

3✔
75
    // Calculate date in YYYYMMDD format
3!
76
    const d = new Date()
×
77
    const yyyymmdd = d.getFullYear() + zeroPad(d.getMonth() + 1, 2) + this.zeroPad(d.getDate(), 2)
×
78

×
79
    let subdir = yyyymmdd
×
80
    // Allow either boolean or a sub-directory to be specified
×
81

×
82
    if (typeof quarantine !== 'boolean' && quarantine !== 1) {
×
83
        subdir = path.join(quarantine, yyyymmdd)
3!
84
    }
×
85

×
86
    const txn = connection?.transaction
×
87
    if (!txn) return next()
3✔
88

×
89
    const base_dir = this.get_base_dir()
×
90
    const msg_dir = path.join(base_dir, subdir)
×
91
    const tmp_path = path.join(base_dir, 'tmp', txn.uuid)
×
92
    const msg_path = path.join(msg_dir, txn.uuid)
×
93

×
94
    // Create all the directories recursively if they do not exist.
×
95
    // Then write the file to a temporary directory first, once this is
×
96
    // successful we hardlink the file to the final destination and then
×
97
    // remove the temporary file to guarantee a complete file in the
×
98
    // final destination.
×
99
    fs.promises
×
100
        .mkdir(msg_dir, { recursive: true })
×
NEW
101
        .catch(() => {
×
102
            connection.logerror(this, `Error creating directory: ${msg_dir}`)
×
103
            next()
×
104
        })
×
NEW
105
        .then(() => {
×
106
            const ws = fs.createWriteStream(tmp_path)
×
107

×
108
            ws.on('error', (err) => {
×
109
                connection.logerror(this, `Error writing quarantine file: ${err.message}`)
×
110
                return next()
×
111
            })
×
112
            ws.on('close', () => {
×
113
                fs.link(tmp_path, msg_path, (err) => {
×
114
                    if (err) {
×
115
                        connection.logerror(this, `Error writing quarantine file: ${err}`)
×
116
                    } else {
×
117
                        // Add a note to where we stored the message
×
118
                        txn.notes.quarantined = msg_path
×
119
                        txn.results.add(this, { pass: msg_path, emit: true })
×
120
                        // Now delete the temporary file
×
121
                        fs.unlink(tmp_path, () => {})
×
122
                    }
×
123
                    // Using notes.quarantine_action to decide what to do after the message is quarantined.
×
124
                    // Format can be either action = [ code, msg ] or action = code
×
125
                    const action = connection.notes.quarantine_action || txn.notes.quarantine_action
×
126
                    if (!action) return next()
×
127
                    if (Array.isArray(action)) return next(action[0], action[1])
×
128
                    return next(action)
×
129
                })
×
130
            })
×
131
            txn.message_stream.pipe(ws, { line_endings: '\n' })
×
132
        })
×
133
}
×
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