imapflow 1.7.8 → 2.0.0
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/CHANGELOG.md +20 -0
- package/README.md +8 -2
- package/dist/cjs/charsets.d.ts +1 -0
- package/dist/cjs/charsets.js +294 -0
- package/dist/cjs/commands/append.d.ts +22 -0
- package/dist/cjs/commands/append.js +151 -0
- package/dist/cjs/commands/authenticate.d.ts +24 -0
- package/dist/cjs/commands/authenticate.js +223 -0
- package/dist/cjs/commands/capability.d.ts +8 -0
- package/dist/cjs/commands/capability.js +32 -0
- package/dist/cjs/commands/close.d.ts +8 -0
- package/dist/cjs/commands/close.js +39 -0
- package/dist/cjs/commands/compress.d.ts +8 -0
- package/dist/cjs/commands/compress.js +56 -0
- package/dist/cjs/commands/copy.d.ts +13 -0
- package/dist/cjs/commands/copy.js +44 -0
- package/dist/cjs/commands/copyuid-parser.d.ts +11 -0
- package/dist/cjs/commands/copyuid-parser.js +32 -0
- package/dist/cjs/commands/create.d.ts +11 -0
- package/dist/cjs/commands/create.js +80 -0
- package/dist/cjs/commands/delete.d.ts +11 -0
- package/dist/cjs/commands/delete.js +40 -0
- package/dist/cjs/commands/enable.d.ts +9 -0
- package/dist/cjs/commands/enable.js +61 -0
- package/dist/cjs/commands/esearch-parser.d.ts +17 -0
- package/dist/cjs/commands/esearch-parser.js +91 -0
- package/dist/cjs/commands/expunge.d.ts +12 -0
- package/dist/cjs/commands/expunge.js +60 -0
- package/dist/cjs/commands/fetch.d.ts +30 -0
- package/dist/cjs/commands/fetch.js +241 -0
- package/dist/cjs/commands/id.d.ts +10 -0
- package/dist/cjs/commands/id.js +80 -0
- package/dist/cjs/commands/idle.d.ts +9 -0
- package/dist/cjs/commands/idle.js +347 -0
- package/dist/cjs/commands/list.d.ts +16 -0
- package/dist/cjs/commands/list.js +518 -0
- package/dist/cjs/commands/login.d.ts +11 -0
- package/dist/cjs/commands/login.js +42 -0
- package/dist/cjs/commands/logout.d.ts +8 -0
- package/dist/cjs/commands/logout.js +47 -0
- package/dist/cjs/commands/move.d.ts +13 -0
- package/dist/cjs/commands/move.js +57 -0
- package/dist/cjs/commands/namespace.d.ts +25 -0
- package/dist/cjs/commands/namespace.js +139 -0
- package/dist/cjs/commands/noop.d.ts +8 -0
- package/dist/cjs/commands/noop.js +22 -0
- package/dist/cjs/commands/quota.d.ts +10 -0
- package/dist/cjs/commands/quota.js +119 -0
- package/dist/cjs/commands/rename.d.ts +12 -0
- package/dist/cjs/commands/rename.js +48 -0
- package/dist/cjs/commands/search.d.ts +15 -0
- package/dist/cjs/commands/search.js +228 -0
- package/dist/cjs/commands/select.d.ts +25 -0
- package/dist/cjs/commands/select.js +250 -0
- package/dist/cjs/commands/starttls.d.ts +8 -0
- package/dist/cjs/commands/starttls.js +30 -0
- package/dist/cjs/commands/status-fields.d.ts +14 -0
- package/dist/cjs/commands/status-fields.js +61 -0
- package/dist/cjs/commands/status.d.ts +12 -0
- package/dist/cjs/commands/status.js +108 -0
- package/dist/cjs/commands/store.d.ts +19 -0
- package/dist/cjs/commands/store.js +93 -0
- package/dist/cjs/commands/subscribe.d.ts +9 -0
- package/dist/cjs/commands/subscribe.js +31 -0
- package/dist/cjs/commands/unsubscribe.d.ts +9 -0
- package/dist/cjs/commands/unsubscribe.js +31 -0
- package/dist/cjs/connection-deadline.d.ts +49 -0
- package/dist/cjs/connection-deadline.js +91 -0
- package/dist/cjs/errors.d.ts +83 -0
- package/dist/cjs/errors.js +13 -0
- package/dist/cjs/handler/imap-compiler.d.ts +24 -0
- package/dist/cjs/handler/imap-compiler.js +285 -0
- package/dist/cjs/handler/imap-formal-syntax.d.ts +28 -0
- package/dist/cjs/handler/imap-formal-syntax.js +121 -0
- package/dist/cjs/handler/imap-handler.d.ts +9 -0
- package/dist/cjs/handler/imap-handler.js +10 -0
- package/dist/cjs/handler/imap-parser.d.ts +16 -0
- package/dist/cjs/handler/imap-parser.js +90 -0
- package/dist/cjs/handler/imap-stream.d.ts +181 -0
- package/dist/cjs/handler/imap-stream.js +446 -0
- package/dist/cjs/handler/limits.d.ts +25 -0
- package/dist/cjs/handler/limits.js +51 -0
- package/dist/cjs/handler/parser-instance.d.ts +68 -0
- package/dist/cjs/handler/parser-instance.js +223 -0
- package/dist/cjs/handler/token-parser.d.ts +91 -0
- package/dist/cjs/handler/token-parser.js +673 -0
- package/dist/cjs/handler/types.d.ts +91 -0
- package/dist/cjs/handler/types.js +4 -0
- package/dist/cjs/imap-commands.d.ts +16 -0
- package/dist/cjs/imap-commands.js +74 -0
- package/dist/cjs/imap-flow.d.ts +676 -0
- package/dist/cjs/imap-flow.js +3949 -0
- package/dist/cjs/jp-decoder.d.ts +12 -0
- package/dist/cjs/jp-decoder.js +79 -0
- package/dist/cjs/limited-passthrough.d.ts +25 -0
- package/dist/cjs/limited-passthrough.js +54 -0
- package/dist/cjs/logger.d.ts +3 -0
- package/dist/cjs/logger.js +11 -0
- package/dist/cjs/package-info.d.ts +3 -0
- package/dist/cjs/package-info.js +7 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/proxy-connection.d.ts +33 -0
- package/dist/cjs/proxy-connection.js +392 -0
- package/dist/cjs/search-compiler.d.ts +34 -0
- package/dist/cjs/search-compiler.js +476 -0
- package/dist/cjs/special-use.d.ts +22 -0
- package/dist/cjs/special-use.js +911 -0
- package/dist/cjs/tools.d.ts +427 -0
- package/dist/cjs/tools.js +1496 -0
- package/{lib/imap-flow.d.ts → dist/cjs/types.d.ts} +386 -516
- package/dist/cjs/types.js +5 -0
- package/dist/esm/charsets.d.ts +1 -0
- package/{lib → dist/esm}/charsets.js +1 -6
- package/dist/esm/commands/append.d.ts +22 -0
- package/{lib → dist/esm}/commands/append.js +22 -52
- package/dist/esm/commands/authenticate.d.ts +24 -0
- package/{lib → dist/esm}/commands/authenticate.js +62 -87
- package/dist/esm/commands/capability.d.ts +8 -0
- package/{lib → dist/esm}/commands/capability.js +6 -9
- package/dist/esm/commands/close.d.ts +8 -0
- package/{lib → dist/esm}/commands/close.js +6 -10
- package/dist/esm/commands/compress.d.ts +8 -0
- package/{lib → dist/esm}/commands/compress.js +7 -11
- package/dist/esm/commands/copy.d.ts +13 -0
- package/{lib → dist/esm}/commands/copy.js +12 -20
- package/dist/esm/commands/copyuid-parser.d.ts +11 -0
- package/{lib → dist/esm}/commands/copyuid-parser.js +9 -15
- package/dist/esm/commands/create.d.ts +11 -0
- package/{lib → dist/esm}/commands/create.js +13 -27
- package/dist/esm/commands/delete.d.ts +11 -0
- package/{lib → dist/esm}/commands/delete.js +9 -14
- package/dist/esm/commands/enable.d.ts +9 -0
- package/{lib → dist/esm}/commands/enable.js +23 -30
- package/dist/esm/commands/esearch-parser.d.ts +17 -0
- package/dist/esm/commands/esearch-parser.js +88 -0
- package/dist/esm/commands/expunge.d.ts +12 -0
- package/{lib → dist/esm}/commands/expunge.js +17 -22
- package/dist/esm/commands/fetch.d.ts +30 -0
- package/{lib → dist/esm}/commands/fetch.js +32 -64
- package/dist/esm/commands/id.d.ts +10 -0
- package/{lib → dist/esm}/commands/id.js +17 -23
- package/dist/esm/commands/idle.d.ts +9 -0
- package/{lib → dist/esm}/commands/idle.js +47 -81
- package/dist/esm/commands/list.d.ts +16 -0
- package/{lib → dist/esm}/commands/list.js +56 -121
- package/dist/esm/commands/login.d.ts +11 -0
- package/{lib → dist/esm}/commands/login.js +10 -15
- package/dist/esm/commands/logout.d.ts +8 -0
- package/{lib → dist/esm}/commands/logout.js +9 -11
- package/dist/esm/commands/move.d.ts +13 -0
- package/{lib → dist/esm}/commands/move.js +13 -21
- package/dist/esm/commands/namespace.d.ts +25 -0
- package/{lib → dist/esm}/commands/namespace.js +34 -44
- package/dist/esm/commands/noop.d.ts +8 -0
- package/{lib → dist/esm}/commands/noop.js +6 -7
- package/dist/esm/commands/quota.d.ts +10 -0
- package/{lib → dist/esm}/commands/quota.js +18 -36
- package/dist/esm/commands/rename.d.ts +12 -0
- package/{lib → dist/esm}/commands/rename.js +10 -15
- package/dist/esm/commands/search.d.ts +15 -0
- package/{lib → dist/esm}/commands/search.js +36 -135
- package/dist/esm/commands/select.d.ts +25 -0
- package/{lib → dist/esm}/commands/select.js +33 -64
- package/dist/esm/commands/starttls.d.ts +8 -0
- package/{lib → dist/esm}/commands/starttls.js +6 -8
- package/dist/esm/commands/status-fields.d.ts +14 -0
- package/{lib → dist/esm}/commands/status-fields.js +5 -16
- package/dist/esm/commands/status.d.ts +12 -0
- package/{lib → dist/esm}/commands/status.js +18 -29
- package/dist/esm/commands/store.d.ts +19 -0
- package/{lib → dist/esm}/commands/store.js +24 -37
- package/dist/esm/commands/subscribe.d.ts +9 -0
- package/{lib → dist/esm}/commands/subscribe.js +8 -12
- package/dist/esm/commands/unsubscribe.d.ts +9 -0
- package/{lib → dist/esm}/commands/unsubscribe.js +8 -12
- package/dist/esm/connection-deadline.d.ts +49 -0
- package/{lib → dist/esm}/connection-deadline.js +14 -25
- package/dist/esm/errors.d.ts +83 -0
- package/dist/esm/errors.js +9 -0
- package/dist/esm/handler/imap-compiler.d.ts +24 -0
- package/{lib → dist/esm}/handler/imap-compiler.js +22 -80
- package/dist/esm/handler/imap-formal-syntax.d.ts +28 -0
- package/dist/esm/handler/imap-formal-syntax.js +117 -0
- package/dist/esm/handler/imap-handler.d.ts +9 -0
- package/dist/esm/handler/imap-handler.js +9 -0
- package/dist/esm/handler/imap-parser.d.ts +16 -0
- package/{lib → dist/esm}/handler/imap-parser.js +31 -44
- package/dist/esm/handler/imap-stream.d.ts +181 -0
- package/{lib → dist/esm}/handler/imap-stream.js +29 -121
- package/dist/esm/handler/limits.d.ts +25 -0
- package/{lib → dist/esm}/handler/limits.js +13 -22
- package/dist/esm/handler/parser-instance.d.ts +68 -0
- package/{lib → dist/esm}/handler/parser-instance.js +19 -47
- package/dist/esm/handler/token-parser.d.ts +91 -0
- package/{lib → dist/esm}/handler/token-parser.js +71 -155
- package/dist/esm/handler/types.d.ts +91 -0
- package/dist/esm/handler/types.js +3 -0
- package/dist/esm/imap-commands.d.ts +16 -0
- package/dist/esm/imap-commands.js +67 -0
- package/dist/esm/imap-flow.d.ts +676 -0
- package/{lib → dist/esm}/imap-flow.js +761 -1789
- package/dist/esm/jp-decoder.d.ts +12 -0
- package/{lib → dist/esm}/jp-decoder.js +6 -21
- package/dist/esm/limited-passthrough.d.ts +25 -0
- package/{lib → dist/esm}/limited-passthrough.js +7 -20
- package/dist/esm/logger.d.ts +3 -0
- package/dist/esm/logger.js +4 -0
- package/dist/esm/package-info.d.ts +3 -0
- package/dist/esm/package-info.js +4 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/proxy-connection.d.ts +33 -0
- package/{lib → dist/esm}/proxy-connection.js +56 -127
- package/dist/esm/search-compiler.d.ts +34 -0
- package/{lib → dist/esm}/search-compiler.js +54 -110
- package/dist/esm/special-use.d.ts +22 -0
- package/dist/esm/special-use.js +907 -0
- package/dist/esm/tools.d.ts +427 -0
- package/dist/esm/tools.js +1446 -0
- package/dist/esm/types.d.ts +828 -0
- package/dist/esm/types.js +4 -0
- package/package.json +60 -20
- package/.gitattributes +0 -1
- package/.github/CODE_OF_CONDUCT.md +0 -76
- package/.github/FUNDING.yml +0 -4
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -40
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -19
- package/.github/contributing.md +0 -17
- package/.github/workflows/release.yaml +0 -36
- package/.github/workflows/stale.yml +0 -29
- package/.github/workflows/test.yml +0 -51
- package/.ncurc.js +0 -4
- package/.prettierignore +0 -4
- package/.prettierrc.js +0 -8
- package/.release-please-manifest.json +0 -3
- package/CLAUDE.md +0 -104
- package/Gruntfile.js +0 -23
- package/eslint.config.js +0 -45
- package/lib/handler/imap-formal-syntax.js +0 -189
- package/lib/handler/imap-handler.js +0 -17
- package/lib/imap-commands.js +0 -45
- package/lib/logger.js +0 -5
- package/lib/special-use.js +0 -923
- package/lib/tools.js +0 -1612
- package/release-please-config.json +0 -10
- package/test/authentication-test.js +0 -101
- package/test/auto-idle-test.js +0 -470
- package/test/bodystructure-test.js +0 -899
- package/test/charsets-test.js +0 -161
- package/test/commands-branches-test.js +0 -1095
- package/test/commands-integration-test.js +0 -11124
- package/test/commands-test.js +0 -73
- package/test/connection-edge-cases-test.js +0 -1828
- package/test/connection-test.js +0 -162
- package/test/copyuid-parser-test.js +0 -173
- package/test/fetch-generator-test.js +0 -218
- package/test/fixtures/fake-timers.js +0 -115
- package/test/fixtures/serialized-mimetorture.js +0 -2738
- package/test/fixtures/test-client.js +0 -101
- package/test/fixtures/test-tls.js +0 -8
- package/test/handler-branches-test.js +0 -310
- package/test/idle-polling-test.js +0 -518
- package/test/imap-compiler-test.js +0 -809
- package/test/imap-flow-compress-test.js +0 -166
- package/test/imap-flow-coverage-test.js +0 -612
- package/test/imap-flow-fetch-download-test.js +0 -909
- package/test/imap-flow-internals-test.js +0 -725
- package/test/imap-flow-methods-test.js +0 -889
- package/test/imap-flow-proxy-paths-test.js +0 -366
- package/test/imap-flow-secure-test.js +0 -573
- package/test/imap-flow-server-test.js +0 -1474
- package/test/imap-formal-syntax-test.js +0 -293
- package/test/imap-parser-test.js +0 -1474
- package/test/imap-stream-edge-cases-test.js +0 -666
- package/test/imap-stream-test.js +0 -177
- package/test/imapflow-test.js +0 -258
- package/test/integration/README.md +0 -52
- package/test/integration/dovecot-test.conf +0 -27
- package/test/integration/rev2-live-test.js +0 -431
- package/test/integration/run-rev2-tests.sh +0 -75
- package/test/integration-test.js +0 -83
- package/test/jp-decoder-test.js +0 -304
- package/test/limited-passthrough-test.js +0 -299
- package/test/memory-cleanup-test.js +0 -144
- package/test/memory-leak-test.js +0 -667
- package/test/parser-limits-test.js +0 -292
- package/test/proxy-connection-test.js +0 -738
- package/test/reliability-improvements-test.js +0 -548
- package/test/search-compiler-test.js +0 -1300
- package/test/search-test.js +0 -329
- package/test/special-use-test.js +0 -418
- package/test/starttls-injection-test.js +0 -181
- package/test/tag-correlation-test.js +0 -333
- package/test/timer-policy-test.js +0 -227
- package/test/token-parser-test.js +0 -456
- package/test/tools-test.js +0 -2013
- package/test/unhandled-rejection-test.js +0 -661
|
@@ -1,292 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
// Response line and literal limits must be terminal, complete, and non-parsing:
|
|
4
|
-
// * every line is measured, including the one whose LF arrives in the same chunk,
|
|
5
|
-
// so the cap cannot be bypassed by TCP chunk boundaries
|
|
6
|
-
// * the line terminator counts toward maxLineLength and a line exactly at the cap passes
|
|
7
|
-
// * a limit violation destroys the stream, so no byte of the rejected payload is ever
|
|
8
|
-
// emitted as protocol (an oversized literal body is attacker-chosen message content)
|
|
9
|
-
|
|
10
|
-
const { ImapStream } = require('../lib/handler/imap-stream');
|
|
11
|
-
const { parser } = require('../lib/handler/imap-handler');
|
|
12
|
-
const { MAX_LINE_SIZE, MAX_LITERAL_SIZE, MAX_RESPONSE_SIZE } = require('../lib/handler/limits');
|
|
13
|
-
|
|
14
|
-
// Collects every emitted command payload and the first stream error. `error` resolves once the
|
|
15
|
-
// stream fails; `settled` resolves once processing has quiesced (error or writable end).
|
|
16
|
-
const runStream = (options, writer) => {
|
|
17
|
-
const stream = new ImapStream(Object.assign({ cid: 'test' }, options));
|
|
18
|
-
const payloads = [];
|
|
19
|
-
let resolveDone;
|
|
20
|
-
const done = new Promise(resolve => (resolveDone = resolve));
|
|
21
|
-
let error = null;
|
|
22
|
-
|
|
23
|
-
stream.on('readable', () => {
|
|
24
|
-
let cmd;
|
|
25
|
-
while ((cmd = stream.read()) !== null) {
|
|
26
|
-
payloads.push(cmd.payload.toString());
|
|
27
|
-
cmd.next();
|
|
28
|
-
}
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
stream.on('error', err => {
|
|
32
|
-
error = err;
|
|
33
|
-
resolveDone();
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
stream.on('end', () => resolveDone());
|
|
37
|
-
|
|
38
|
-
writer(stream);
|
|
39
|
-
|
|
40
|
-
// Settle one more tick after the failure so anything the stream might still emit
|
|
41
|
-
// (which it must not) would be visible in `payloads`.
|
|
42
|
-
return done.then(() => new Promise(resolve => setImmediate(() => resolve({ payloads, error }))));
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
module.exports['Parser limits: line exactly at the cap is accepted (terminator included)'] = async test => {
|
|
46
|
-
// "A NOOP\r\n" is 8 bytes with the terminator, so a cap of 8 must accept it.
|
|
47
|
-
const { payloads, error } = await runStream({ maxLineLength: 8 }, stream => stream.end(Buffer.from('A NOOP\r\n')));
|
|
48
|
-
|
|
49
|
-
test.equal(error, null, 'a line at exactly the cap must not fail');
|
|
50
|
-
test.deepEqual(payloads, ['A NOOP'], 'the line is emitted');
|
|
51
|
-
test.done();
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
module.exports['Parser limits: line one byte over the cap is rejected'] = async test => {
|
|
55
|
-
// Same line with a cap of 7: the terminator pushes it over the limit.
|
|
56
|
-
const { payloads, error } = await runStream({ maxLineLength: 7 }, stream => stream.end(Buffer.from('A NOOP\r\n')));
|
|
57
|
-
|
|
58
|
-
test.ok(error, 'the oversized line fails the stream');
|
|
59
|
-
test.equal(error.code, 'LineTooLarge');
|
|
60
|
-
test.equal(error.lineLength, 8, 'the reported length includes the terminator');
|
|
61
|
-
test.equal(error.maxSize, 7);
|
|
62
|
-
test.deepEqual(payloads, [], 'nothing is emitted');
|
|
63
|
-
test.done();
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
module.exports['Parser limits: oversized line delivered in a single chunk is rejected'] = async test => {
|
|
67
|
-
// Regression: the cap used to be checked only on the stored tail, so a complete line whose
|
|
68
|
-
// LF arrived in the same chunk was never measured at all.
|
|
69
|
-
const { payloads, error } = await runStream({ maxLineLength: 16 }, stream => stream.end(Buffer.from('* OK ' + 'A'.repeat(64) + '\r\n')));
|
|
70
|
-
|
|
71
|
-
test.ok(error, 'a single-chunk oversized line fails the stream');
|
|
72
|
-
test.equal(error.code, 'LineTooLarge');
|
|
73
|
-
test.deepEqual(payloads, [], 'nothing is emitted');
|
|
74
|
-
test.done();
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
module.exports['Parser limits: oversized line accumulated across chunks is rejected'] = async test => {
|
|
78
|
-
const { payloads, error } = await runStream({ maxLineLength: 16 }, stream => {
|
|
79
|
-
stream.write(Buffer.from('AAAAAAAA'));
|
|
80
|
-
stream.write(Buffer.from('BBBBBBBB'));
|
|
81
|
-
stream.end(Buffer.from('CCCCCCCC'));
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
test.ok(error, 'accumulated bytes over the cap fail the stream');
|
|
85
|
-
test.equal(error.code, 'LineTooLarge');
|
|
86
|
-
test.deepEqual(payloads, [], 'nothing is emitted');
|
|
87
|
-
test.done();
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
module.exports['Parser limits: oversized line whose final chunk carries the LF is rejected'] = async test => {
|
|
91
|
-
// The final chunk completes the line, so it is the LF-terminated path that must measure it.
|
|
92
|
-
const { payloads, error } = await runStream({ maxLineLength: 16 }, stream => {
|
|
93
|
-
stream.write(Buffer.from('AAAAAAAA'));
|
|
94
|
-
stream.write(Buffer.from('BBBBBBBB'));
|
|
95
|
-
stream.end(Buffer.from('CC\r\n'));
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
test.ok(error, 'the mixed final chunk is measured too');
|
|
99
|
-
test.equal(error.code, 'LineTooLarge');
|
|
100
|
-
test.equal(error.lineLength, 20, 'length covers every byte of the line plus the terminator');
|
|
101
|
-
test.deepEqual(payloads, [], 'nothing is emitted');
|
|
102
|
-
test.done();
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
module.exports['Parser limits: nothing is parsed after LineTooLarge'] = async test => {
|
|
106
|
-
// Both the remaining bytes of the offending chunk and any later chunk must be dropped:
|
|
107
|
-
// resynchronizing at the next LF would turn rejected payload back into protocol.
|
|
108
|
-
const settledWrites = [];
|
|
109
|
-
const { payloads, error } = await runStream({ maxLineLength: 16 }, stream => {
|
|
110
|
-
stream.write(Buffer.from('* OK ' + 'A'.repeat(64) + '\r\n* 9999 EXISTS\r\n'), () => settledWrites.push('first'));
|
|
111
|
-
stream.write(Buffer.from('* CAPABILITY INJECTED\r\n'), () => settledWrites.push('second'));
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
test.ok(error, 'the stream failed');
|
|
115
|
-
test.equal(error.code, 'LineTooLarge');
|
|
116
|
-
test.deepEqual(payloads, [], 'no command from the offending chunk or from the queued chunk');
|
|
117
|
-
test.equal(settledWrites.length, 2, 'every pending write callback settles, so the writer cannot hang');
|
|
118
|
-
test.done();
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
module.exports['Parser limits: oversized literal emits no command and no literal body'] = async test => {
|
|
122
|
-
// The reproduction from the remediation plan. With a lowered cap, the literal body is
|
|
123
|
-
// ordinary message content chosen by whoever sent the mail, so none of it may be parsed.
|
|
124
|
-
const { payloads, error } = await runStream({ maxLiteralSize: 1024 }, stream => {
|
|
125
|
-
stream.write(
|
|
126
|
-
Buffer.from(
|
|
127
|
-
'* 1 FETCH (BODY[] {5000}\r\n' + //
|
|
128
|
-
'INNOCENT MESSAGE TEXT\r\n' +
|
|
129
|
-
'* 9999 EXISTS\r\n' +
|
|
130
|
-
'3 OK forged completion\r\n'
|
|
131
|
-
)
|
|
132
|
-
);
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
test.ok(error, 'the oversized literal fails the stream');
|
|
136
|
-
test.equal(error.code, 'LiteralTooLarge');
|
|
137
|
-
test.equal(error.literalSize, 5000);
|
|
138
|
-
test.equal(error.maxSize, 1024);
|
|
139
|
-
test.deepEqual(payloads, [], 'not even the marker line is emitted');
|
|
140
|
-
test.done();
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
module.exports['Parser limits: literal exactly at the cap is accepted'] = async test => {
|
|
144
|
-
const { payloads, error } = await runStream({ maxLiteralSize: 5 }, stream => stream.end(Buffer.from('* 1 FETCH (BODY[] {5}\r\nHELLO)\r\n')));
|
|
145
|
-
|
|
146
|
-
test.equal(error, null, 'a literal at exactly the cap must not fail');
|
|
147
|
-
test.equal(payloads.length, 1, 'the command is emitted');
|
|
148
|
-
test.done();
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
module.exports['Parser limits: valid commands before the failure are still delivered'] = async test => {
|
|
152
|
-
// A limit violation is terminal, but responses that were already complete and legitimate
|
|
153
|
-
// when it happened stay delivered - the guarantee is that nothing *after* it is parsed.
|
|
154
|
-
const { payloads, error } = await runStream({ maxLineLength: 24 }, stream => {
|
|
155
|
-
stream.write(Buffer.from('* OK before\r\n'));
|
|
156
|
-
stream.write(Buffer.from('* OK ' + 'A'.repeat(64) + '\r\n* OK after\r\n'));
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
test.ok(error, 'the stream failed on the oversized line');
|
|
160
|
-
test.deepEqual(payloads, ['* OK before'], 'only the response that completed before the failure');
|
|
161
|
-
test.done();
|
|
162
|
-
};
|
|
163
|
-
|
|
164
|
-
module.exports['Parser limits: a failed stream refuses further input'] = async test => {
|
|
165
|
-
const stream = new ImapStream({ cid: 'test', maxLineLength: 8 });
|
|
166
|
-
const payloads = [];
|
|
167
|
-
|
|
168
|
-
stream.on('readable', () => {
|
|
169
|
-
let cmd;
|
|
170
|
-
while ((cmd = stream.read()) !== null) {
|
|
171
|
-
payloads.push(cmd.payload.toString());
|
|
172
|
-
cmd.next();
|
|
173
|
-
}
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
await new Promise(resolve => {
|
|
177
|
-
stream.on('error', () => resolve());
|
|
178
|
-
stream.write(Buffer.from('A' + 'B'.repeat(32) + '\r\n'));
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
test.ok(stream.destroyed, 'the stream is destroyed, so parsing cannot continue');
|
|
182
|
-
|
|
183
|
-
// Writing again must not resurrect parsing. The write is rejected by the destroyed
|
|
184
|
-
// stream; the callback settles either way so nothing is left pending.
|
|
185
|
-
await new Promise(resolve => stream.write(Buffer.from('* OK late\r\n'), () => resolve()));
|
|
186
|
-
|
|
187
|
-
await new Promise(resolve => setImmediate(resolve));
|
|
188
|
-
test.deepEqual(payloads, [], 'no command is emitted after the terminal failure');
|
|
189
|
-
test.done();
|
|
190
|
-
};
|
|
191
|
-
|
|
192
|
-
module.exports['Parser limits: destroy settles an in-flight backpressure wait'] = async test => {
|
|
193
|
-
// The consumer stops reading (never calls next()), then the stream is destroyed - as
|
|
194
|
-
// ImapFlow.close() does. The pending push() wait and the pending transform callback must
|
|
195
|
-
// both settle so nothing keeps the process alive.
|
|
196
|
-
const stream = new ImapStream({ cid: 'test' });
|
|
197
|
-
let writeSettled = false;
|
|
198
|
-
|
|
199
|
-
stream.on('error', () => {});
|
|
200
|
-
|
|
201
|
-
const gotCommand = new Promise(resolve => {
|
|
202
|
-
stream.on('readable', () => {
|
|
203
|
-
// read the command but deliberately do not call cmd.next()
|
|
204
|
-
if (stream.read() !== null) {
|
|
205
|
-
resolve();
|
|
206
|
-
}
|
|
207
|
-
});
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
stream.write(Buffer.from('* OK first\r\n* OK second\r\n'), () => (writeSettled = true));
|
|
211
|
-
|
|
212
|
-
await gotCommand;
|
|
213
|
-
test.ok(!writeSettled, 'the transform callback is still pending while the consumer stalls');
|
|
214
|
-
|
|
215
|
-
stream.destroy();
|
|
216
|
-
await new Promise(resolve => setImmediate(resolve));
|
|
217
|
-
|
|
218
|
-
test.ok(writeSettled, 'destruction released the pending transform callback');
|
|
219
|
-
test.equal(stream.pendingPush, null, 'the in-flight backpressure wait was settled');
|
|
220
|
-
test.done();
|
|
221
|
-
};
|
|
222
|
-
|
|
223
|
-
// ---------------------------------------------------------------------------
|
|
224
|
-
// Standalone parser: inline literal allocation
|
|
225
|
-
// ---------------------------------------------------------------------------
|
|
226
|
-
// ImapStream always supplies pre-parsed literal buffers (and enforces its own cap first), so
|
|
227
|
-
// these cases only arise when the parser is used directly - where the declared literal length is
|
|
228
|
-
// untrusted input that must not decide an allocation.
|
|
229
|
-
|
|
230
|
-
module.exports['Parser limits: inline literal beyond the available input is rejected'] = async test => {
|
|
231
|
-
let err = await parser('* 1 FETCH {1073741824}\r\nshort').then(
|
|
232
|
-
() => null,
|
|
233
|
-
e => e
|
|
234
|
-
);
|
|
235
|
-
|
|
236
|
-
test.ok(err, 'a declared literal larger than the input cannot be satisfied');
|
|
237
|
-
test.equal(err.code, 'LiteralTooLarge', 'the ImapStream error shape is reused');
|
|
238
|
-
test.equal(err.literalSize, 1073741824);
|
|
239
|
-
test.ok(err.maxSize < 1073741824, 'the reported bound is the input actually available');
|
|
240
|
-
test.done();
|
|
241
|
-
};
|
|
242
|
-
|
|
243
|
-
module.exports['Parser limits: inline literal honors a configured maxLiteralSize'] = async test => {
|
|
244
|
-
let err = await parser('* 1 FETCH {5}\r\nHELLO', { maxLiteralSize: 4 }).then(
|
|
245
|
-
() => null,
|
|
246
|
-
e => e
|
|
247
|
-
);
|
|
248
|
-
|
|
249
|
-
test.ok(err, 'the configured cap applies to inline literals too');
|
|
250
|
-
test.equal(err.code, 'LiteralTooLarge');
|
|
251
|
-
test.equal(err.maxSize, 4);
|
|
252
|
-
test.equal(err.literalSize, 5);
|
|
253
|
-
test.done();
|
|
254
|
-
};
|
|
255
|
-
|
|
256
|
-
module.exports['Parser limits: inline literal within both bounds still parses'] = async test => {
|
|
257
|
-
let parsed = await parser('* 1 FETCH {5}\r\nHELLO', { maxLiteralSize: 1024 });
|
|
258
|
-
let literal = parsed.attributes[parsed.attributes.length - 1];
|
|
259
|
-
|
|
260
|
-
test.equal(literal.type, 'LITERAL');
|
|
261
|
-
test.equal(literal.value.toString(), 'HELLO');
|
|
262
|
-
test.done();
|
|
263
|
-
};
|
|
264
|
-
|
|
265
|
-
module.exports['Parser limits: maxLiteralSize 0 rejects any non-empty inline literal'] = async test => {
|
|
266
|
-
let err = await parser('* 1 FETCH {1}\r\nA', { maxLiteralSize: 0 }).then(
|
|
267
|
-
() => null,
|
|
268
|
-
e => e
|
|
269
|
-
);
|
|
270
|
-
|
|
271
|
-
test.ok(err, 'an explicit 0 cap is honored, not swallowed into the default');
|
|
272
|
-
test.equal(err.code, 'LiteralTooLarge');
|
|
273
|
-
test.equal(err.maxSize, 0);
|
|
274
|
-
test.done();
|
|
275
|
-
};
|
|
276
|
-
|
|
277
|
-
module.exports['Parser limits: a junk limit falls back to the default'] = test => {
|
|
278
|
-
// Without validation an unusable value is honored as-is, and every comparison against it is
|
|
279
|
-
// false - so a caller who mistypes a limit silently gets no bound at all, or (for a negative
|
|
280
|
-
// value) a bound that rejects everything
|
|
281
|
-
for (let junk of ['4096', 'abc', 1.5, -1, -Infinity, null, {}, [], NaN, true]) {
|
|
282
|
-
const stream = new ImapStream({ cid: 'test', maxLineLength: junk, maxLiteralSize: junk, maxResponseSize: junk });
|
|
283
|
-
test.equal(stream.maxLineLength, MAX_LINE_SIZE, `maxLineLength ${JSON.stringify(junk)} must fall back`);
|
|
284
|
-
test.equal(stream.maxLiteralSize, MAX_LITERAL_SIZE, `maxLiteralSize ${JSON.stringify(junk)} must fall back`);
|
|
285
|
-
test.equal(stream.maxResponseSize, MAX_RESPONSE_SIZE, `maxResponseSize ${JSON.stringify(junk)} must fall back`);
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
// an explicit 0 is a real limit, not a missing one, and must not be swallowed
|
|
289
|
-
const zero = new ImapStream({ cid: 'test', maxLiteralSize: 0 });
|
|
290
|
-
test.equal(zero.maxLiteralSize, 0);
|
|
291
|
-
test.done();
|
|
292
|
-
};
|