Haraka 3.1.3 → 3.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.prettierignore +2 -0
- package/CONTRIBUTORS.md +23 -1
- package/Changes.md +52 -0
- package/Plugins.md +81 -64
- package/README.md +1 -1
- package/bin/haraka +7 -5
- package/connection.js +15 -19
- package/docs/Plugins.md +1 -1
- package/docs/plugins/aliases.md +0 -2
- package/docs/plugins/queue/qmail-queue.md +0 -1
- package/logger.js +2 -2
- package/outbound/hmail.js +76 -83
- package/outbound/index.js +36 -34
- package/outbound/queue.js +231 -176
- package/package.json +26 -29
- package/plugins/prevent_credential_leaks.js +2 -2
- package/plugins/process_title.js +1 -1
- package/plugins/queue/smtp_forward.js +5 -5
- package/plugins/status.js +8 -5
- package/plugins/tls.js +1 -1
- package/plugins.js +19 -14
- package/rfc1869.js +10 -10
- package/run_tests +8 -2
- package/server.js +15 -10
- package/smtp_client.js +10 -15
- package/test/config/tls/haraka.local.pem +47 -47
- package/test/connection.js +286 -147
- package/test/endpoint.js +5 -4
- package/test/fixtures/line_socket.js +1 -0
- package/test/fixtures/util_hmailitem.js +1 -1
- package/test/host_pool.js +57 -31
- package/test/logger.js +75 -135
- package/test/outbound/bounce_net_errors.js +132 -0
- package/test/outbound/bounce_rfc3464.js +226 -0
- package/test/outbound/hmail.js +140 -104
- package/test/outbound/index.js +61 -101
- package/test/outbound/qfile.js +25 -25
- package/test/outbound/queue.js +233 -0
- package/test/plugins/auth/auth_base.js +39 -44
- package/test/plugins/auth/auth_vpopmaild.js +8 -9
- package/test/plugins/queue/smtp_forward.js +953 -183
- package/test/plugins/rcpt_to.host_list_base.js +58 -93
- package/test/plugins/rcpt_to.in_host_list.js +126 -175
- package/test/plugins/record_envelope_addresses.js +93 -0
- package/test/plugins/status.js +10 -10
- package/test/plugins/tls.js +11 -21
- package/test/plugins/xclient.js +102 -0
- package/test/plugins.js +10 -13
- package/test/rfc1869.js +71 -48
- package/test/server.js +281 -436
- package/test/smtp_client.js +1194 -220
- package/test/tls_socket.js +74 -243
- package/test/transaction.js +486 -201
- package/tls_socket.js +19 -23
- package/transaction.js +33 -10
- package/config/rabbitmq.ini +0 -10
- package/config/rabbitmq_amqplib.ini +0 -19
- package/docs/plugins/queue/rabbitmq.md +0 -34
- package/docs/plugins/queue/rabbitmq_amqplib.md +0 -51
- package/plugins/queue/rabbitmq.js +0 -141
- package/plugins/queue/rabbitmq_amqplib.js +0 -96
- package/test/config/tls/ec.pem +0 -23
- package/test/config/tls/mismatched.pem +0 -49
- package/test/outbound_bounce_net_errors.js +0 -157
- package/test/outbound_bounce_rfc3464.js +0 -366
package/test/plugins/status.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const assert = require('node:assert')
|
|
3
|
+
const assert = require('node:assert/strict')
|
|
4
|
+
const { describe, it, beforeEach } = require('node:test')
|
|
4
5
|
|
|
5
6
|
const fixtures = require('haraka-test-fixtures')
|
|
6
7
|
const outbound = require('../../outbound')
|
|
@@ -8,13 +9,12 @@ const TimerQueue = require('../../outbound/timer_queue')
|
|
|
8
9
|
|
|
9
10
|
const Connection = fixtures.connection
|
|
10
11
|
|
|
11
|
-
const _set_up = (
|
|
12
|
+
const _set_up = () => {
|
|
12
13
|
this.plugin = new fixtures.plugin('status')
|
|
13
14
|
this.plugin.outbound = outbound
|
|
14
15
|
|
|
15
16
|
this.connection = Connection.createConnection()
|
|
16
17
|
this.connection.remote.is_local = true
|
|
17
|
-
done()
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
describe('status', () => {
|
|
@@ -29,7 +29,7 @@ describe('status', () => {
|
|
|
29
29
|
describe('access', () => {
|
|
30
30
|
beforeEach(_set_up)
|
|
31
31
|
|
|
32
|
-
it('remote', (done) => {
|
|
32
|
+
it('remote', (t, done) => {
|
|
33
33
|
this.connection.remote.is_local = false
|
|
34
34
|
this.plugin.hook_unrecognized_command(
|
|
35
35
|
(code) => {
|
|
@@ -45,7 +45,7 @@ describe('status', () => {
|
|
|
45
45
|
describe('pools', () => {
|
|
46
46
|
beforeEach(_set_up)
|
|
47
47
|
|
|
48
|
-
it('list_pools', (done) => {
|
|
48
|
+
it('list_pools', (t, done) => {
|
|
49
49
|
this.connection.respond = (code, message) => {
|
|
50
50
|
const data = JSON.parse(message)
|
|
51
51
|
assert.equal('object', typeof data) // there should be one pools array for noncluster and more for cluster
|
|
@@ -58,7 +58,7 @@ describe('status', () => {
|
|
|
58
58
|
describe('queues', () => {
|
|
59
59
|
beforeEach(_set_up)
|
|
60
60
|
|
|
61
|
-
it('inspect_queue', (done) => {
|
|
61
|
+
it('inspect_queue', (t, done) => {
|
|
62
62
|
// should list delivery_queue and temp_fail_queue per cluster children
|
|
63
63
|
outbound.temp_fail_queue = new TimerQueue(10)
|
|
64
64
|
outbound.temp_fail_queue.add('file1', 100, () => {})
|
|
@@ -73,7 +73,7 @@ describe('status', () => {
|
|
|
73
73
|
this.plugin.hook_unrecognized_command(() => {}, this.connection, ['STATUS', 'QUEUE INSPECT'])
|
|
74
74
|
})
|
|
75
75
|
|
|
76
|
-
it('stat_queue', (done) => {
|
|
76
|
+
it('stat_queue', (t, done) => {
|
|
77
77
|
// should list files only
|
|
78
78
|
this.connection.respond = (code, message) => {
|
|
79
79
|
const data = JSON.parse(message)
|
|
@@ -83,7 +83,7 @@ describe('status', () => {
|
|
|
83
83
|
this.plugin.hook_unrecognized_command(() => {}, this.connection, ['STATUS', 'QUEUE STATS'])
|
|
84
84
|
})
|
|
85
85
|
|
|
86
|
-
it('list_queue', (done) => {
|
|
86
|
+
it('list_queue', (t, done) => {
|
|
87
87
|
// should list files only
|
|
88
88
|
this.connection.respond = (code, message) => {
|
|
89
89
|
const data = JSON.parse(message)
|
|
@@ -93,7 +93,7 @@ describe('status', () => {
|
|
|
93
93
|
this.plugin.hook_unrecognized_command(() => {}, this.connection, ['STATUS', 'QUEUE LIST'])
|
|
94
94
|
})
|
|
95
95
|
|
|
96
|
-
it('discard_from_queue', (done) => {
|
|
96
|
+
it('discard_from_queue', (t, done) => {
|
|
97
97
|
const self = this
|
|
98
98
|
|
|
99
99
|
outbound.temp_fail_queue = new TimerQueue(10)
|
|
@@ -118,7 +118,7 @@ describe('status', () => {
|
|
|
118
118
|
)
|
|
119
119
|
})
|
|
120
120
|
|
|
121
|
-
it('push_email_at_queue', (done) => {
|
|
121
|
+
it('push_email_at_queue', (t, done) => {
|
|
122
122
|
const timeout = setTimeout(() => {
|
|
123
123
|
assert.ok(false, 'Timeout')
|
|
124
124
|
done()
|
package/test/plugins/tls.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const assert = require('node:assert')
|
|
3
|
+
const assert = require('node:assert/strict')
|
|
4
4
|
const path = require('node:path')
|
|
5
|
+
const { describe, it, beforeEach } = require('node:test')
|
|
5
6
|
|
|
6
7
|
const fixtures = require('haraka-test-fixtures')
|
|
7
8
|
const Plugin = fixtures.plugin
|
|
8
9
|
|
|
9
|
-
const _set_up = (
|
|
10
|
+
const _set_up = () => {
|
|
10
11
|
this.plugin = new Plugin('tls')
|
|
11
12
|
this.connection = new fixtures.connection.createConnection()
|
|
12
13
|
|
|
@@ -15,33 +16,22 @@ const _set_up = (done) => {
|
|
|
15
16
|
this.plugin.net_utils.config = this.plugin.net_utils.config.module_config(path.resolve('test'))
|
|
16
17
|
|
|
17
18
|
this.plugin.tls_opts = {}
|
|
18
|
-
done()
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
describe('tls', () => {
|
|
22
22
|
beforeEach(_set_up)
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
assert.equal('function', typeof this.plugin.upgrade_connection)
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
it('has function advertise_starttls', () => {
|
|
34
|
-
assert.equal('function', typeof this.plugin.advertise_starttls)
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
it('has function emit_upgrade_msg', () => {
|
|
38
|
-
assert.equal('function', typeof this.plugin.emit_upgrade_msg)
|
|
39
|
-
})
|
|
24
|
+
const methods = ['register', 'upgrade_connection', 'advertise_starttls', 'emit_upgrade_msg']
|
|
25
|
+
for (const method of methods) {
|
|
26
|
+
it(`has function ${method}`, () => {
|
|
27
|
+
assert.equal(typeof this.plugin[method], 'function')
|
|
28
|
+
})
|
|
29
|
+
}
|
|
40
30
|
|
|
41
31
|
describe('register', () => {
|
|
42
|
-
it('with certs, should
|
|
32
|
+
it('with certs, should register hooks', () => {
|
|
43
33
|
this.plugin.register()
|
|
44
|
-
assert.ok(this.plugin.
|
|
34
|
+
assert.ok(Object.keys(this.plugin.hooks).length)
|
|
45
35
|
})
|
|
46
36
|
})
|
|
47
37
|
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const assert = require('node:assert/strict')
|
|
4
|
+
const { describe, it, beforeEach } = require('node:test')
|
|
5
|
+
|
|
6
|
+
const fixtures = require('haraka-test-fixtures')
|
|
7
|
+
|
|
8
|
+
const _set_up = () => {
|
|
9
|
+
this.plugin = new fixtures.plugin('xclient')
|
|
10
|
+
this.connection = fixtures.connection.createConnection()
|
|
11
|
+
this.connection.capabilities = []
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
describe('xclient', () => {
|
|
15
|
+
beforeEach(_set_up)
|
|
16
|
+
|
|
17
|
+
describe('hook_capabilities', () => {
|
|
18
|
+
const cases = [
|
|
19
|
+
{ desc: 'adds XCLIENT for loopback IPv4 (127.0.0.1)', ip: '127.0.0.1', expected: true },
|
|
20
|
+
{ desc: 'adds XCLIENT for loopback IPv6 (::1)', ip: '::1', expected: true },
|
|
21
|
+
{ desc: 'does not add XCLIENT for non-loopback IP', ip: '10.0.0.1', expected: false },
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
for (const { desc, ip, expected } of cases) {
|
|
25
|
+
it(desc, async () => {
|
|
26
|
+
this.connection.remote.ip = ip
|
|
27
|
+
await new Promise((resolve) => this.plugin.hook_capabilities(resolve, this.connection))
|
|
28
|
+
const hasXclient = this.connection.capabilities.some((c) => c.startsWith('XCLIENT'))
|
|
29
|
+
assert.equal(hasXclient, expected)
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
describe('hook_unrecognized_command', () => {
|
|
35
|
+
const callHook = (params) =>
|
|
36
|
+
new Promise((resolve) => {
|
|
37
|
+
this.plugin.hook_unrecognized_command((code) => resolve(code), this.connection, params)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
const cases = [
|
|
41
|
+
{
|
|
42
|
+
desc: 'ignores non-XCLIENT commands',
|
|
43
|
+
params: ['EHLO', 'example.com'],
|
|
44
|
+
check: (code) => assert.equal(code, undefined),
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
desc: 'denies XCLIENT when transaction is in progress',
|
|
48
|
+
setup: () => this.connection.init_transaction(),
|
|
49
|
+
params: ['XCLIENT', 'ADDR=127.0.0.1'],
|
|
50
|
+
check: (code) => assert.equal(code, DENY),
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
desc: 'denies XCLIENT from disallowed IP',
|
|
54
|
+
setup: () => {
|
|
55
|
+
this.connection.remote.ip = '10.0.0.1'
|
|
56
|
+
},
|
|
57
|
+
params: ['XCLIENT', 'ADDR=127.0.0.2'],
|
|
58
|
+
check: (code) => assert.equal(code, DENY),
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
desc: 'denies XCLIENT with no valid IP address',
|
|
62
|
+
setup: () => {
|
|
63
|
+
this.connection.remote.ip = '127.0.0.1'
|
|
64
|
+
},
|
|
65
|
+
params: ['XCLIENT', 'NAME=example.com'],
|
|
66
|
+
check: (code) => assert.equal(code, DENY),
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
desc: 'accepts XCLIENT with valid IPv4 ADDR from allowed host',
|
|
70
|
+
setup: () => {
|
|
71
|
+
this.connection.remote.ip = '127.0.0.1'
|
|
72
|
+
},
|
|
73
|
+
params: ['XCLIENT', 'ADDR=1.2.3.4'],
|
|
74
|
+
check: (code) => assert.ok(code === NEXT_HOOK || code === undefined),
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
desc: 'accepts XCLIENT with valid IPv6 ADDR from allowed host',
|
|
78
|
+
setup: () => {
|
|
79
|
+
this.connection.remote.ip = '127.0.0.1'
|
|
80
|
+
},
|
|
81
|
+
params: ['XCLIENT', 'ADDR=IPV6:2001:db8::1'],
|
|
82
|
+
check: (code) => assert.ok(code === NEXT_HOOK || code === undefined),
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
desc: 'accepts XCLIENT with ADDR and NAME, skipping rdns lookup',
|
|
86
|
+
setup: () => {
|
|
87
|
+
this.connection.remote.ip = '127.0.0.1'
|
|
88
|
+
},
|
|
89
|
+
params: ['XCLIENT', 'ADDR=1.2.3.4 NAME=example.com'],
|
|
90
|
+
check: (code) => assert.equal(code, NEXT_HOOK),
|
|
91
|
+
},
|
|
92
|
+
]
|
|
93
|
+
|
|
94
|
+
for (const { desc, setup, params, check } of cases) {
|
|
95
|
+
it(desc, async () => {
|
|
96
|
+
if (setup) setup()
|
|
97
|
+
const code = await callHook(params)
|
|
98
|
+
check(code)
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
})
|
|
102
|
+
})
|
package/test/plugins.js
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
process.env.WITHOUT_CONFIG_CACHE = true
|
|
4
4
|
|
|
5
|
-
const
|
|
5
|
+
const { describe, it, beforeEach, afterEach } = require('node:test')
|
|
6
|
+
const assert = require('node:assert/strict')
|
|
6
7
|
const fs = require('node:fs')
|
|
7
8
|
const path = require('node:path')
|
|
8
9
|
|
|
@@ -49,7 +50,7 @@ describe('plugin', () => {
|
|
|
49
50
|
|
|
50
51
|
describe('get_timeout', () => {
|
|
51
52
|
const toPath = path.resolve('config', `${piName}.timeout`)
|
|
52
|
-
it('0s', (done) => {
|
|
53
|
+
it('0s', (t, done) => {
|
|
53
54
|
fs.writeFile(toPath, '0', () => {
|
|
54
55
|
this.plugin = new plugin.Plugin(piName)
|
|
55
56
|
assert.equal(this.plugin.timeout, 0)
|
|
@@ -57,7 +58,7 @@ describe('plugin', () => {
|
|
|
57
58
|
})
|
|
58
59
|
})
|
|
59
60
|
|
|
60
|
-
it('3s', (done) => {
|
|
61
|
+
it('3s', (t, done) => {
|
|
61
62
|
fs.writeFile(toPath, '3', () => {
|
|
62
63
|
this.plugin = new plugin.Plugin(piName)
|
|
63
64
|
assert.equal(this.plugin.timeout, 3)
|
|
@@ -65,7 +66,7 @@ describe('plugin', () => {
|
|
|
65
66
|
})
|
|
66
67
|
})
|
|
67
68
|
|
|
68
|
-
it('60s', (done) => {
|
|
69
|
+
it('60s', (t, done) => {
|
|
69
70
|
fs.writeFile(toPath, '60', () => {
|
|
70
71
|
this.plugin = new plugin.Plugin(piName)
|
|
71
72
|
assert.equal(this.plugin.timeout, 60)
|
|
@@ -73,7 +74,7 @@ describe('plugin', () => {
|
|
|
73
74
|
})
|
|
74
75
|
})
|
|
75
76
|
|
|
76
|
-
it('30s default (overrides NaN)', (done) => {
|
|
77
|
+
it('30s default (overrides NaN)', (t, done) => {
|
|
77
78
|
fs.writeFile(toPath, 'apple', () => {
|
|
78
79
|
this.plugin = new plugin.Plugin(piName)
|
|
79
80
|
assert.equal(this.plugin.timeout, 30)
|
|
@@ -83,14 +84,12 @@ describe('plugin', () => {
|
|
|
83
84
|
})
|
|
84
85
|
|
|
85
86
|
describe('plugin_paths', () => {
|
|
86
|
-
beforeEach((
|
|
87
|
+
beforeEach(() => {
|
|
87
88
|
delete process.env.HARAKA
|
|
88
|
-
done()
|
|
89
89
|
})
|
|
90
90
|
|
|
91
|
-
afterEach((
|
|
91
|
+
afterEach(() => {
|
|
92
92
|
delete process.env.HARAKA
|
|
93
|
-
done()
|
|
94
93
|
})
|
|
95
94
|
|
|
96
95
|
it('CORE plugin: (tls)', () => {
|
|
@@ -190,14 +189,12 @@ describe('plugin', () => {
|
|
|
190
189
|
})
|
|
191
190
|
|
|
192
191
|
describe('plugin_config', () => {
|
|
193
|
-
beforeEach((
|
|
192
|
+
beforeEach(() => {
|
|
194
193
|
delete process.env.HARAKA
|
|
195
|
-
done()
|
|
196
194
|
})
|
|
197
195
|
|
|
198
|
-
afterEach((
|
|
196
|
+
afterEach(() => {
|
|
199
197
|
delete process.env.HARAKA
|
|
200
|
-
done()
|
|
201
198
|
})
|
|
202
199
|
|
|
203
200
|
it('CORE plugin: (tls)', () => {
|
package/test/rfc1869.js
CHANGED
|
@@ -1,66 +1,89 @@
|
|
|
1
|
-
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { describe, it } = require('node:test')
|
|
4
|
+
const assert = require('node:assert/strict')
|
|
2
5
|
|
|
3
6
|
const { parse } = require('../rfc1869')
|
|
4
7
|
|
|
5
8
|
function _check(line, expected) {
|
|
6
9
|
const match = /^(MAIL|RCPT)\s+(.*)$/.exec(line)
|
|
7
10
|
const parsed = parse(match[1].toLowerCase(), match[2])
|
|
8
|
-
assert.
|
|
9
|
-
for (let x = 0; x < expected.length; x++) {
|
|
10
|
-
assert.equal(parsed[x], expected[x])
|
|
11
|
-
}
|
|
11
|
+
assert.deepEqual(parsed, expected)
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
describe('rfc1869', () => {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
15
|
+
describe('valid parse cases', () => {
|
|
16
|
+
const validCases = [
|
|
17
|
+
// MAIL FROM variants
|
|
18
|
+
['MAIL FROM:<>', ['<>']],
|
|
19
|
+
['MAIL FROM:', ['<>']],
|
|
20
|
+
['MAIL FROM:<postmaster>', ['<postmaster>']],
|
|
21
|
+
['MAIL FROM:user', ['user']],
|
|
22
|
+
['MAIL FROM:user size=1234', ['user', 'size=1234']],
|
|
23
|
+
['MAIL FROM:user@domain size=1234', ['user@domain', 'size=1234']],
|
|
24
|
+
['MAIL FROM:<user@domain> size=1234', ['<user@domain>', 'size=1234']],
|
|
25
|
+
['MAIL FROM:<user@domain> somekey', ['<user@domain>', 'somekey']],
|
|
26
|
+
['MAIL FROM:<user@domain> somekey other=foo', ['<user@domain>', 'somekey', 'other=foo']],
|
|
27
|
+
// RFC 1652 BODY extension keyword
|
|
28
|
+
['MAIL FROM:<user@domain> BODY=8BITMIME', ['<user@domain>', 'BODY=8BITMIME']],
|
|
29
|
+
// RFC 6531 SMTPUTF8 keyword (no value)
|
|
30
|
+
['MAIL FROM:<user@domain> SMTPUTF8', ['<user@domain>', 'SMTPUTF8']],
|
|
31
|
+
// RCPT TO variants
|
|
32
|
+
['RCPT TO: 0@mailblog.biz 0=9 1=9', ['<0@mailblog.biz>', '0=9', '1=9']],
|
|
33
|
+
['RCPT TO:<r86x-ray@emailitin.com> state=1', ['<r86x-ray@emailitin.com>', 'state=1']],
|
|
34
|
+
['RCPT TO:<user=name@domain.com> foo=bar', ['<user=name@domain.com>', 'foo=bar']],
|
|
35
|
+
['RCPT TO:<postmaster>', ['<postmaster>']],
|
|
36
|
+
['RCPT TO:<abuse>', ['<abuse>']],
|
|
37
|
+
]
|
|
34
38
|
|
|
35
|
-
|
|
36
|
-
|
|
39
|
+
for (const [line, expected] of validCases) {
|
|
40
|
+
it(line, () => _check(line, expected))
|
|
41
|
+
}
|
|
37
42
|
})
|
|
38
43
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
44
|
+
describe('error cases', () => {
|
|
45
|
+
const throwCases = [
|
|
46
|
+
{
|
|
47
|
+
desc: 'MAIL FROM with space inside angle-bracket address',
|
|
48
|
+
args: ['mail', 'FROM:<user@dom ain>'],
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
desc: 'RCPT TO with syntax error in address (space in address)',
|
|
52
|
+
args: ['rcpt', 'TO: user @domain bad'],
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
desc: 'RCPT TO unknown address (no @ and not postmaster/abuse)',
|
|
56
|
+
args: ['rcpt', 'TO:unknown'],
|
|
57
|
+
},
|
|
58
|
+
]
|
|
42
59
|
|
|
43
|
-
|
|
44
|
-
|
|
60
|
+
for (const { desc, args } of throwCases) {
|
|
61
|
+
it(`throws: ${desc}`, () => {
|
|
62
|
+
assert.throws(() => parse(...args), Error)
|
|
63
|
+
})
|
|
64
|
+
}
|
|
45
65
|
})
|
|
46
66
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
it('RCPT TO:<user=name@domain.com> foo=bar', () => {
|
|
60
|
-
_check('RCPT TO:<user=name@domain.com> foo=bar', ['<user=name@domain.com>', 'foo=bar'])
|
|
61
|
-
})
|
|
67
|
+
describe('strict mode', () => {
|
|
68
|
+
const strictValidCases = [
|
|
69
|
+
['mail', 'FROM:<user@domain.com>', '<user@domain.com>'],
|
|
70
|
+
['rcpt', 'TO:<user@domain.com>', '<user@domain.com>'],
|
|
71
|
+
]
|
|
72
|
+
for (const [type, line, expected] of strictValidCases) {
|
|
73
|
+
it(`strict ${type.toUpperCase()} with angle brackets accepts address`, () => {
|
|
74
|
+
const result = parse(type, line, true)
|
|
75
|
+
assert.equal(result[0], expected)
|
|
76
|
+
})
|
|
77
|
+
}
|
|
62
78
|
|
|
63
|
-
|
|
64
|
-
|
|
79
|
+
const strictThrowCases = [
|
|
80
|
+
['mail', 'FROM:user@domain.com'],
|
|
81
|
+
['rcpt', 'TO:user@domain.com'],
|
|
82
|
+
]
|
|
83
|
+
for (const [type, line] of strictThrowCases) {
|
|
84
|
+
it(`strict ${type.toUpperCase()} without angle brackets throws`, () => {
|
|
85
|
+
assert.throws(() => parse(type, line, true), Error)
|
|
86
|
+
})
|
|
87
|
+
}
|
|
65
88
|
})
|
|
66
89
|
})
|