Haraka 3.1.4 → 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/CONTRIBUTORS.md +1 -1
- package/Changes.md +14 -0
- package/package.json +8 -10
- package/plugins/queue/smtp_forward.js +4 -4
- package/run_tests +3 -15
- package/smtp_client.js +8 -6
- package/test/endpoint.js +5 -4
- package/test/host_pool.js +57 -31
- package/test/logger.js +75 -135
- package/test/outbound/bounce_net_errors.js +87 -131
- package/test/outbound/bounce_rfc3464.js +177 -254
- 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 +8 -8
- package/test/plugins/status.js +10 -10
- package/test/plugins/tls.js +9 -19
- package/test/plugins/xclient.js +75 -110
- package/test/plugins.js +10 -13
- package/test/rfc1869.js +50 -70
- package/test/server.js +281 -436
- package/test/smtp_client.js +1192 -218
- package/test/tls_socket.js +104 -0
- package/tls_socket.js +16 -20
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,28 +16,17 @@ 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
32
|
it('with certs, should register hooks', () => {
|
package/test/plugins/xclient.js
CHANGED
|
@@ -1,137 +1,102 @@
|
|
|
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
|
|
|
7
|
-
const _set_up = (
|
|
8
|
+
const _set_up = () => {
|
|
8
9
|
this.plugin = new fixtures.plugin('xclient')
|
|
9
10
|
this.connection = fixtures.connection.createConnection()
|
|
10
11
|
this.connection.capabilities = []
|
|
11
|
-
done()
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
describe('xclient', () => {
|
|
15
15
|
beforeEach(_set_up)
|
|
16
16
|
|
|
17
17
|
describe('hook_capabilities', () => {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
'XCLIENT capability added',
|
|
24
|
-
)
|
|
25
|
-
done()
|
|
26
|
-
}, this.connection)
|
|
27
|
-
})
|
|
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
|
+
]
|
|
28
23
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}, this.connection)
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
it('does not add XCLIENT capability for disallowed IP', (done) => {
|
|
41
|
-
this.connection.remote.ip = '10.0.0.1'
|
|
42
|
-
this.plugin.hook_capabilities(() => {
|
|
43
|
-
assert.ok(
|
|
44
|
-
!this.connection.capabilities.some((c) => c.startsWith('XCLIENT')),
|
|
45
|
-
'XCLIENT capability not added',
|
|
46
|
-
)
|
|
47
|
-
done()
|
|
48
|
-
}, this.connection)
|
|
49
|
-
})
|
|
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
|
+
}
|
|
50
32
|
})
|
|
51
33
|
|
|
52
34
|
describe('hook_unrecognized_command', () => {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
(code) =>
|
|
56
|
-
|
|
57
|
-
done()
|
|
58
|
-
},
|
|
59
|
-
this.connection,
|
|
60
|
-
['EHLO', 'example.com'],
|
|
61
|
-
)
|
|
62
|
-
})
|
|
35
|
+
const callHook = (params) =>
|
|
36
|
+
new Promise((resolve) => {
|
|
37
|
+
this.plugin.hook_unrecognized_command((code) => resolve(code), this.connection, params)
|
|
38
|
+
})
|
|
63
39
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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'
|
|
70
56
|
},
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
this.plugin.hook_unrecognized_command(
|
|
79
|
-
(code) => {
|
|
80
|
-
assert.equal(code, DENY, 'denied from non-allowed IP')
|
|
81
|
-
done()
|
|
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'
|
|
82
64
|
},
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
this.plugin.hook_unrecognized_command(
|
|
91
|
-
(code) => {
|
|
92
|
-
assert.equal(code, DENY, 'denied when no valid ADDR')
|
|
93
|
-
done()
|
|
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'
|
|
94
72
|
},
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
this.plugin.hook_unrecognized_command(
|
|
103
|
-
(code) => {
|
|
104
|
-
// NEXT_HOOK or undefined (next called) means accepted
|
|
105
|
-
assert.ok(code === NEXT_HOOK || code === undefined, 'accepted valid XCLIENT')
|
|
106
|
-
done()
|
|
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'
|
|
107
80
|
},
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
this.plugin.hook_unrecognized_command(
|
|
116
|
-
(code) => {
|
|
117
|
-
assert.ok(code === NEXT_HOOK || code === undefined, 'accepted valid IPv6 XCLIENT')
|
|
118
|
-
done()
|
|
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'
|
|
119
88
|
},
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
89
|
+
params: ['XCLIENT', 'ADDR=1.2.3.4 NAME=example.com'],
|
|
90
|
+
check: (code) => assert.equal(code, NEXT_HOOK),
|
|
91
|
+
},
|
|
92
|
+
]
|
|
124
93
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
this.connection,
|
|
133
|
-
['XCLIENT', 'ADDR=1.2.3.4 NAME=example.com'],
|
|
134
|
-
)
|
|
135
|
-
})
|
|
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
|
+
}
|
|
136
101
|
})
|
|
137
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,67 +1,44 @@
|
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
it('MAIL FROM:<user@domain> size=1234', () => {
|
|
40
|
-
_check('MAIL FROM:<user@domain> size=1234', ['<user@domain>', 'size=1234'])
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
it('MAIL FROM:<user@domain> somekey', () => {
|
|
44
|
-
_check('MAIL FROM:<user@domain> somekey', ['<user@domain>', 'somekey'])
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
it('MAIL FROM:<user@domain> somekey other=foo', () => {
|
|
48
|
-
_check('MAIL FROM:<user@domain> somekey other=foo', ['<user@domain>', 'somekey', 'other=foo'])
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
it('RCPT TO ugly', () => {
|
|
52
|
-
_check('RCPT TO: 0@mailblog.biz 0=9 1=9', ['<0@mailblog.biz>', '0=9', '1=9'])
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
it('RCPT TO:<r86x-ray@emailitin.com> state=1', () => {
|
|
56
|
-
_check('RCPT TO:<r86x-ray@emailitin.com> state=1', ['<r86x-ray@emailitin.com>', 'state=1'])
|
|
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
|
-
})
|
|
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
|
+
]
|
|
62
38
|
|
|
63
|
-
|
|
64
|
-
|
|
39
|
+
for (const [line, expected] of validCases) {
|
|
40
|
+
it(line, () => _check(line, expected))
|
|
41
|
+
}
|
|
65
42
|
})
|
|
66
43
|
|
|
67
44
|
describe('error cases', () => {
|
|
@@ -88,22 +65,25 @@ describe('rfc1869', () => {
|
|
|
88
65
|
})
|
|
89
66
|
|
|
90
67
|
describe('strict mode', () => {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
const result = parse('rcpt', 'TO:<user@domain.com>', true)
|
|
102
|
-
assert.equal(result[0], '<user@domain.com>')
|
|
103
|
-
})
|
|
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
|
+
}
|
|
104
78
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
+
}
|
|
108
88
|
})
|
|
109
89
|
})
|