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,666 +0,0 @@
|
|
|
1
|
-
/*eslint no-unused-expressions: 0, prefer-arrow-callback: 0 */
|
|
2
|
-
|
|
3
|
-
'use strict';
|
|
4
|
-
|
|
5
|
-
const { ImapStream } = require('../lib/handler/imap-stream');
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Helper that wires up the standard readable/next consumer pattern used across tests.
|
|
9
|
-
*
|
|
10
|
-
* The reader calls onCommand synchronously for each command object, then calls cmd.next()
|
|
11
|
-
* to allow the stream to continue. test.done() is called on the 'end' event.
|
|
12
|
-
*
|
|
13
|
-
* The readable handler uses a pendingRead flag so that if a 'readable' event fires
|
|
14
|
-
* while the reader is active, a follow-up read is triggered after the reader finishes.
|
|
15
|
-
* This prevents the 'end' event from being missed when the stream ends between reader
|
|
16
|
-
* iterations.
|
|
17
|
-
*
|
|
18
|
-
* @param {Object} test - nodeunit test object
|
|
19
|
-
* @param {Function} onCommand - synchronous function(cmd) called for each parsed command
|
|
20
|
-
* @param {Function} writer - async function(stream) that writes data to the stream
|
|
21
|
-
* @param {number} [expectedCount] - if set, assert that exactly this many commands were emitted
|
|
22
|
-
*/
|
|
23
|
-
function runStreamTest(test, onCommand, writer, expectedCount) {
|
|
24
|
-
const stream = new ImapStream({ cid: 'test' });
|
|
25
|
-
let commandCount = 0;
|
|
26
|
-
|
|
27
|
-
let reading = false;
|
|
28
|
-
let pendingRead = false;
|
|
29
|
-
|
|
30
|
-
const reader = async () => {
|
|
31
|
-
let cmd;
|
|
32
|
-
while ((cmd = stream.read()) !== null) {
|
|
33
|
-
commandCount++;
|
|
34
|
-
onCommand(cmd);
|
|
35
|
-
cmd.next();
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
const drainStream = () => {
|
|
40
|
-
if (reading) {
|
|
41
|
-
pendingRead = true;
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
reading = true;
|
|
45
|
-
reader()
|
|
46
|
-
.catch(err => test.ifError(err))
|
|
47
|
-
.finally(() => {
|
|
48
|
-
reading = false;
|
|
49
|
-
if (pendingRead) {
|
|
50
|
-
pendingRead = false;
|
|
51
|
-
drainStream();
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
stream.on('readable', drainStream);
|
|
57
|
-
|
|
58
|
-
stream.on('error', err => {
|
|
59
|
-
test.ifError(err);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
stream.on('end', () => {
|
|
63
|
-
if (expectedCount !== undefined) {
|
|
64
|
-
test.equal(commandCount, expectedCount, `expected ${expectedCount} command(s), got ${commandCount}`);
|
|
65
|
-
}
|
|
66
|
-
test.done();
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
writer(stream).catch(err => test.ifError(err));
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
module.exports['Literal split across chunks'] = test => {
|
|
73
|
-
runStreamTest(
|
|
74
|
-
test,
|
|
75
|
-
cmd => {
|
|
76
|
-
test.equal(cmd.payload.toString(), 'A APPEND {5}\r\n', 'payload should include literal marker line');
|
|
77
|
-
test.equal(cmd.literals.length, 1, 'should have one literal');
|
|
78
|
-
test.ok(Buffer.isBuffer(cmd.literals[0]), 'literal should be a Buffer');
|
|
79
|
-
test.equal(cmd.literals[0].toString(), '12345', 'literal content should be 12345');
|
|
80
|
-
},
|
|
81
|
-
async stream => {
|
|
82
|
-
stream.write(Buffer.from('A APPEND {5}\r\n'));
|
|
83
|
-
stream.end(Buffer.from('12345\r\n'));
|
|
84
|
-
},
|
|
85
|
-
1
|
|
86
|
-
);
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
module.exports['Literal with zero size'] = test => {
|
|
90
|
-
runStreamTest(
|
|
91
|
-
test,
|
|
92
|
-
cmd => {
|
|
93
|
-
test.equal(cmd.payload.toString(), 'A APPEND {0}\r\n', 'payload should include zero-size literal marker');
|
|
94
|
-
test.equal(cmd.literals.length, 1, 'should have one literal');
|
|
95
|
-
test.ok(Buffer.isBuffer(cmd.literals[0]), 'literal should be a Buffer');
|
|
96
|
-
test.equal(cmd.literals[0].length, 0, 'literal should be empty (length 0)');
|
|
97
|
-
},
|
|
98
|
-
async stream => {
|
|
99
|
-
stream.end(Buffer.from('A APPEND {0}\r\n\r\n'));
|
|
100
|
-
},
|
|
101
|
-
1
|
|
102
|
-
);
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
module.exports['Multiple commands in single chunk'] = test => {
|
|
106
|
-
const expected = ['A CMD1', 'B CMD2'];
|
|
107
|
-
let index = 0;
|
|
108
|
-
|
|
109
|
-
runStreamTest(
|
|
110
|
-
test,
|
|
111
|
-
cmd => {
|
|
112
|
-
test.equal(cmd.payload.toString(), expected[index], `command ${index} payload`);
|
|
113
|
-
index++;
|
|
114
|
-
},
|
|
115
|
-
async stream => {
|
|
116
|
-
stream.end(Buffer.from('A CMD1\r\nB CMD2\r\n'));
|
|
117
|
-
},
|
|
118
|
-
2
|
|
119
|
-
);
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
module.exports['LiteralTooLarge error'] = test => {
|
|
123
|
-
const stream = new ImapStream({ cid: 'test' });
|
|
124
|
-
|
|
125
|
-
stream.on('error', err => {
|
|
126
|
-
test.equal(err.code, 'LiteralTooLarge', 'error code should be LiteralTooLarge');
|
|
127
|
-
stream.destroy();
|
|
128
|
-
test.done();
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
stream.write(Buffer.from('A APPEND {1073741825}\r\n'));
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
module.exports['LiteralTooLarge error honors configured maxLiteralSize'] = test => {
|
|
135
|
-
const cap = 1024; // 1KB cap
|
|
136
|
-
const stream = new ImapStream({ cid: 'test', maxLiteralSize: cap });
|
|
137
|
-
|
|
138
|
-
stream.on('error', err => {
|
|
139
|
-
test.equal(err.code, 'LiteralTooLarge', 'error code should be LiteralTooLarge');
|
|
140
|
-
test.equal(err.maxSize, cap, 'maxSize should reflect the configured cap');
|
|
141
|
-
test.equal(err.literalSize, 2048, 'literalSize should be the offending value');
|
|
142
|
-
stream.destroy();
|
|
143
|
-
test.done();
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
stream.write(Buffer.from('A APPEND {2048}\r\n'));
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
module.exports['maxLiteralSize: 0 is honored (not swallowed into the default)'] = test => {
|
|
150
|
-
// Regression: `this.options.maxLiteralSize || MAX_LITERAL_SIZE` turned an explicit 0 into
|
|
151
|
-
// the 1GB default. An explicit 0 must mean "reject any non-empty literal".
|
|
152
|
-
test.expect(2);
|
|
153
|
-
|
|
154
|
-
const stream = new ImapStream({ cid: 'test', maxLiteralSize: 0 });
|
|
155
|
-
test.equal(stream.maxLiteralSize, 0, 'an explicit 0 cap is preserved, not replaced by the default');
|
|
156
|
-
|
|
157
|
-
stream.on('error', err => {
|
|
158
|
-
test.equal(err.code, 'LiteralTooLarge', 'a 1-byte literal exceeds the 0 cap');
|
|
159
|
-
stream.destroy();
|
|
160
|
-
test.done();
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
stream.write(Buffer.from('A APPEND {1}\r\n'));
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
module.exports['Literal within configured maxLiteralSize parses cleanly'] = test => {
|
|
167
|
-
// Require both literal assertions to actually run: 'end' fires even if the parser never
|
|
168
|
-
// emits the command, so without expect() a dropped-literal regression would pass green.
|
|
169
|
-
test.expect(2);
|
|
170
|
-
|
|
171
|
-
const stream = new ImapStream({ cid: 'test', maxLiteralSize: 1024 });
|
|
172
|
-
const literal = Buffer.alloc(512, 0x61); // 512 * 'a'
|
|
173
|
-
|
|
174
|
-
stream.on('readable', () => {
|
|
175
|
-
let cmd;
|
|
176
|
-
while ((cmd = stream.read()) !== null) {
|
|
177
|
-
test.equal(cmd.literals.length, 1, 'should have one literal');
|
|
178
|
-
test.equal(cmd.literals[0].length, 512, 'literal length should be 512');
|
|
179
|
-
cmd.next();
|
|
180
|
-
}
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
stream.on('error', err => test.ifError(err));
|
|
184
|
-
|
|
185
|
-
stream.on('end', () => test.done());
|
|
186
|
-
|
|
187
|
-
stream.write(Buffer.from('A APPEND {512}\r\n'));
|
|
188
|
-
stream.write(literal);
|
|
189
|
-
stream.end(Buffer.from('\r\n'));
|
|
190
|
-
};
|
|
191
|
-
|
|
192
|
-
module.exports['Incomplete line continued in next chunk'] = test => {
|
|
193
|
-
runStreamTest(
|
|
194
|
-
test,
|
|
195
|
-
cmd => {
|
|
196
|
-
test.equal(cmd.payload.toString(), 'A CAPABILITY', 'payload should be A CAPABILITY');
|
|
197
|
-
test.equal(cmd.literals.length, 0, 'should have no literals');
|
|
198
|
-
},
|
|
199
|
-
async stream => {
|
|
200
|
-
stream.write(Buffer.from('A CA'));
|
|
201
|
-
stream.end(Buffer.from('PABILITY\r\n'));
|
|
202
|
-
},
|
|
203
|
-
1
|
|
204
|
-
);
|
|
205
|
-
};
|
|
206
|
-
|
|
207
|
-
module.exports['Empty chunk then valid command'] = test => {
|
|
208
|
-
runStreamTest(
|
|
209
|
-
test,
|
|
210
|
-
cmd => {
|
|
211
|
-
test.equal(cmd.payload.toString(), 'A CMD', 'payload should be A CMD');
|
|
212
|
-
test.equal(cmd.literals.length, 0, 'should have no literals');
|
|
213
|
-
},
|
|
214
|
-
async stream => {
|
|
215
|
-
stream.write(Buffer.alloc(0));
|
|
216
|
-
stream.end(Buffer.from('A CMD\r\n'));
|
|
217
|
-
},
|
|
218
|
-
1
|
|
219
|
-
);
|
|
220
|
-
};
|
|
221
|
-
|
|
222
|
-
module.exports['String input converted to Buffer'] = test => {
|
|
223
|
-
runStreamTest(
|
|
224
|
-
test,
|
|
225
|
-
cmd => {
|
|
226
|
-
test.equal(cmd.payload.toString(), 'A CMD', 'payload should be A CMD');
|
|
227
|
-
test.ok(Buffer.isBuffer(cmd.payload), 'payload should be a Buffer');
|
|
228
|
-
test.equal(cmd.literals.length, 0, 'should have no literals');
|
|
229
|
-
},
|
|
230
|
-
async stream => {
|
|
231
|
-
stream.end('A CMD\r\n');
|
|
232
|
-
},
|
|
233
|
-
1
|
|
234
|
-
);
|
|
235
|
-
};
|
|
236
|
-
|
|
237
|
-
module.exports['LF-only line terminator'] = test => {
|
|
238
|
-
runStreamTest(
|
|
239
|
-
test,
|
|
240
|
-
cmd => {
|
|
241
|
-
test.equal(cmd.payload.toString(), 'A CMD', 'payload should be A CMD without CR or LF');
|
|
242
|
-
test.equal(cmd.literals.length, 0, 'should have no literals');
|
|
243
|
-
},
|
|
244
|
-
async stream => {
|
|
245
|
-
stream.end(Buffer.from('A CMD\n'));
|
|
246
|
-
},
|
|
247
|
-
1
|
|
248
|
-
);
|
|
249
|
-
};
|
|
250
|
-
|
|
251
|
-
module.exports['Many chunks trigger event loop yield'] = test => {
|
|
252
|
-
runStreamTest(
|
|
253
|
-
test,
|
|
254
|
-
cmd => {
|
|
255
|
-
// Just verify each command is valid
|
|
256
|
-
test.ok(cmd.payload.toString().startsWith('A'), 'command should start with tag');
|
|
257
|
-
},
|
|
258
|
-
async stream => {
|
|
259
|
-
for (let i = 0; i < 15; i++) {
|
|
260
|
-
stream.write(Buffer.from(`A CMD${i}\r\n`));
|
|
261
|
-
}
|
|
262
|
-
stream.end();
|
|
263
|
-
},
|
|
264
|
-
15
|
|
265
|
-
);
|
|
266
|
-
};
|
|
267
|
-
|
|
268
|
-
module.exports['Destroy with queued items does not hang'] = test => {
|
|
269
|
-
const stream = new ImapStream({ cid: 'test' });
|
|
270
|
-
let errorEmitted = false;
|
|
271
|
-
|
|
272
|
-
stream.on('error', () => {
|
|
273
|
-
errorEmitted = true;
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
// Write multiple chunks rapidly then destroy
|
|
277
|
-
stream.write(Buffer.from('A CMD1\r\n'));
|
|
278
|
-
stream.write(Buffer.from('B CMD2\r\n'));
|
|
279
|
-
stream.destroy();
|
|
280
|
-
|
|
281
|
-
// Errors from destroy are emitted synchronously or on next tick
|
|
282
|
-
setImmediate(() => {
|
|
283
|
-
test.ok(!errorEmitted, 'should not emit error on destroy');
|
|
284
|
-
test.done();
|
|
285
|
-
});
|
|
286
|
-
};
|
|
287
|
-
|
|
288
|
-
module.exports['logRaw option triggers trace logging'] = test => {
|
|
289
|
-
let traceCalled = false;
|
|
290
|
-
let traceData = null;
|
|
291
|
-
|
|
292
|
-
const stream = new ImapStream({
|
|
293
|
-
cid: 'test',
|
|
294
|
-
logRaw: true
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
// Override the log object to capture trace calls
|
|
298
|
-
stream.log = {
|
|
299
|
-
trace: data => {
|
|
300
|
-
traceCalled = true;
|
|
301
|
-
traceData = data;
|
|
302
|
-
},
|
|
303
|
-
debug: () => {},
|
|
304
|
-
info: () => {},
|
|
305
|
-
warn: () => {},
|
|
306
|
-
error: () => {}
|
|
307
|
-
};
|
|
308
|
-
|
|
309
|
-
// Also need to handle readable events
|
|
310
|
-
stream.on('readable', () => {
|
|
311
|
-
let cmd;
|
|
312
|
-
while ((cmd = stream.read()) !== null) {
|
|
313
|
-
cmd.next();
|
|
314
|
-
}
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
stream.on('end', () => {
|
|
318
|
-
test.ok(traceCalled, 'trace should have been called');
|
|
319
|
-
test.ok(traceData, 'trace data should exist');
|
|
320
|
-
test.equal(traceData.src, 's', 'source should be s');
|
|
321
|
-
test.ok(traceData.data, 'should have base64 data');
|
|
322
|
-
test.done();
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
stream.end(Buffer.from('A CMD\r\n'));
|
|
326
|
-
};
|
|
327
|
-
|
|
328
|
-
module.exports['Adjacent literals with marker at line start'] = test => {
|
|
329
|
-
// After the first literal's data (12345) is consumed, parsing resumes at the very start of
|
|
330
|
-
// a line that is itself a literal marker ({3}). The marker begins at byte 0 of the resumed
|
|
331
|
-
// line, which the backward scan must still recognize. Previously the loop bound skipped
|
|
332
|
-
// index 0, so the second literal was silently dropped.
|
|
333
|
-
runStreamTest(
|
|
334
|
-
test,
|
|
335
|
-
cmd => {
|
|
336
|
-
test.equal(cmd.literals.length, 2, 'both adjacent literals must be extracted');
|
|
337
|
-
test.equal(cmd.literals[0].toString(), '12345', 'first literal content');
|
|
338
|
-
test.equal(cmd.literals[1].toString(), 'ABC', 'second literal content');
|
|
339
|
-
},
|
|
340
|
-
async stream => {
|
|
341
|
-
stream.end(Buffer.from('A LOGIN {5}\r\n12345{3}\r\nABC\r\n'));
|
|
342
|
-
},
|
|
343
|
-
1
|
|
344
|
-
);
|
|
345
|
-
};
|
|
346
|
-
|
|
347
|
-
module.exports['Line length cap rejects oversized line'] = test => {
|
|
348
|
-
// A server that never sends a line terminator must not grow the line buffer without bound.
|
|
349
|
-
const stream = new ImapStream({ cid: 'test', maxLineLength: 16 });
|
|
350
|
-
let errored = false;
|
|
351
|
-
|
|
352
|
-
stream.on('error', err => {
|
|
353
|
-
errored = true;
|
|
354
|
-
test.equal(err.code, 'LineTooLarge', 'error code should be LineTooLarge');
|
|
355
|
-
test.equal(err.maxSize, 16, 'error should report the configured cap');
|
|
356
|
-
stream.destroy();
|
|
357
|
-
test.done();
|
|
358
|
-
});
|
|
359
|
-
|
|
360
|
-
stream.on('end', () => {
|
|
361
|
-
if (!errored) {
|
|
362
|
-
test.ok(false, 'expected a LineTooLarge error');
|
|
363
|
-
test.done();
|
|
364
|
-
}
|
|
365
|
-
});
|
|
366
|
-
|
|
367
|
-
// 24 bytes, no LF, written across chunks -> exceeds the 16 byte cap.
|
|
368
|
-
stream.write(Buffer.from('AAAAAAAA'));
|
|
369
|
-
stream.write(Buffer.from('BBBBBBBB'));
|
|
370
|
-
stream.write(Buffer.from('CCCCCCCC'));
|
|
371
|
-
};
|
|
372
|
-
|
|
373
|
-
module.exports['Line length cap allows line within limit'] = test => {
|
|
374
|
-
// A normal line under the configured cap must still parse cleanly.
|
|
375
|
-
const stream = new ImapStream({ cid: 'test', maxLineLength: 32 });
|
|
376
|
-
let payloads = [];
|
|
377
|
-
|
|
378
|
-
stream.on('readable', () => {
|
|
379
|
-
let cmd;
|
|
380
|
-
while ((cmd = stream.read()) !== null) {
|
|
381
|
-
payloads.push(cmd.payload.toString());
|
|
382
|
-
cmd.next();
|
|
383
|
-
}
|
|
384
|
-
});
|
|
385
|
-
|
|
386
|
-
stream.on('error', err => test.ifError(err));
|
|
387
|
-
|
|
388
|
-
stream.on('end', () => {
|
|
389
|
-
test.deepEqual(payloads, ['A NOOP'], 'line under the cap should parse');
|
|
390
|
-
test.done();
|
|
391
|
-
});
|
|
392
|
-
|
|
393
|
-
stream.end(Buffer.from('A NOOP\r\n'));
|
|
394
|
-
};
|
|
395
|
-
|
|
396
|
-
// ---------------------------------------------------------------------------
|
|
397
|
-
// checkLiteralMarker direct edge cases + _transform string handling + _destroy
|
|
398
|
-
// draining (lines that the normal streaming flow does not exercise).
|
|
399
|
-
// ---------------------------------------------------------------------------
|
|
400
|
-
|
|
401
|
-
module.exports['ImapStream: checkLiteralMarker returns false for empty line'] = test => {
|
|
402
|
-
const stream = new ImapStream({ cid: 't' });
|
|
403
|
-
test.equal(stream.checkLiteralMarker(Buffer.alloc(0)), false);
|
|
404
|
-
test.equal(stream.checkLiteralMarker(null), false);
|
|
405
|
-
test.done();
|
|
406
|
-
};
|
|
407
|
-
|
|
408
|
-
module.exports['ImapStream: checkLiteralMarker returns false when no trailing LF'] = test => {
|
|
409
|
-
const stream = new ImapStream({ cid: 't' });
|
|
410
|
-
test.equal(stream.checkLiteralMarker(Buffer.from('A1 OK no newline')), false);
|
|
411
|
-
test.done();
|
|
412
|
-
};
|
|
413
|
-
|
|
414
|
-
module.exports['ImapStream: checkLiteralMarker returns false for non-numeric marker'] = test => {
|
|
415
|
-
const stream = new ImapStream({ cid: 't' });
|
|
416
|
-
// '{' present but contains a non-digit, and an empty {} marker
|
|
417
|
-
test.equal(stream.checkLiteralMarker(Buffer.from('A1 CMD {x}\r\n')), false);
|
|
418
|
-
test.equal(stream.checkLiteralMarker(Buffer.from('A1 CMD {}\r\n')), false);
|
|
419
|
-
test.done();
|
|
420
|
-
};
|
|
421
|
-
|
|
422
|
-
module.exports['ImapStream: checkLiteralMarker activates literal state for valid marker'] = test => {
|
|
423
|
-
const stream = new ImapStream({ cid: 't' });
|
|
424
|
-
test.equal(stream.checkLiteralMarker(Buffer.from('A1 CMD {5}\r\n')), true);
|
|
425
|
-
test.equal(stream.literalWaiting, 5);
|
|
426
|
-
test.done();
|
|
427
|
-
};
|
|
428
|
-
|
|
429
|
-
module.exports['ImapStream: _transform converts string chunks to Buffer'] = test => {
|
|
430
|
-
const stream = new ImapStream({ cid: 't' });
|
|
431
|
-
let commands = [];
|
|
432
|
-
stream.on('readable', () => {
|
|
433
|
-
let cmd;
|
|
434
|
-
while ((cmd = stream.read()) !== null) {
|
|
435
|
-
commands.push(cmd.payload.toString());
|
|
436
|
-
cmd.next();
|
|
437
|
-
}
|
|
438
|
-
});
|
|
439
|
-
stream.on('end', () => {
|
|
440
|
-
test.ok(commands.some(c => /A1 OK/.test(c)));
|
|
441
|
-
test.done();
|
|
442
|
-
});
|
|
443
|
-
// write a string (not a Buffer) to exercise the string->Buffer branch
|
|
444
|
-
stream.write('A1 OK done\r\n');
|
|
445
|
-
stream.end();
|
|
446
|
-
};
|
|
447
|
-
|
|
448
|
-
module.exports['ImapStream: _destroy drains pending input queue callbacks'] = test => {
|
|
449
|
-
const stream = new ImapStream({ cid: 't' });
|
|
450
|
-
let nextCalled = false;
|
|
451
|
-
// Stage a pending queue item with a next() callback, then destroy.
|
|
452
|
-
stream.inputQueue.push({ chunk: Buffer.from('x'), next: () => (nextCalled = true) });
|
|
453
|
-
stream.destroy();
|
|
454
|
-
setImmediate(() => {
|
|
455
|
-
test.ok(nextCalled, 'pending next() invoked during destroy');
|
|
456
|
-
test.done();
|
|
457
|
-
});
|
|
458
|
-
};
|
|
459
|
-
|
|
460
|
-
module.exports['Literal marker scan stays linear on a long digit run'] = test => {
|
|
461
|
-
// A backwards scan that accumulated digits one at a time cost O(n^2), so a few
|
|
462
|
-
// hundred KB of digits blocked the event loop for seconds before the size was
|
|
463
|
-
// even known - all inside the default line-length budget
|
|
464
|
-
const stream = new ImapStream({ cid: 'test' });
|
|
465
|
-
stream.on('error', () => {});
|
|
466
|
-
stream.resume();
|
|
467
|
-
|
|
468
|
-
const line = Buffer.concat([Buffer.from('* OK {'), Buffer.from('9'.repeat(200000)), Buffer.from('}\r\n')]);
|
|
469
|
-
|
|
470
|
-
const started = process.hrtime.bigint();
|
|
471
|
-
stream.checkLiteralMarker(line);
|
|
472
|
-
const elapsedMs = Number(process.hrtime.bigint() - started) / 1e6;
|
|
473
|
-
|
|
474
|
-
test.ok(elapsedMs < 250, `scan should stay cheap, took ${elapsedMs.toFixed(1)}ms`);
|
|
475
|
-
test.done();
|
|
476
|
-
};
|
|
477
|
-
|
|
478
|
-
module.exports['Literal marker fails the stream on an oversized digit run'] = test => {
|
|
479
|
-
// An impossible size is still a syntactically valid marker. Treating it as an
|
|
480
|
-
// ordinary line instead would feed the announced literal body to the line parser
|
|
481
|
-
// and desynchronize the session, so the stream must end with LiteralTooLarge.
|
|
482
|
-
const stream = new ImapStream({ cid: 'test' });
|
|
483
|
-
let streamErr = null;
|
|
484
|
-
stream.on('error', err => {
|
|
485
|
-
streamErr = err;
|
|
486
|
-
});
|
|
487
|
-
stream.resume();
|
|
488
|
-
|
|
489
|
-
const line = Buffer.concat([Buffer.from('* OK {'), Buffer.from('1'.repeat(40)), Buffer.from('}\r\n')]);
|
|
490
|
-
|
|
491
|
-
test.equal(stream.checkLiteralMarker(line), false, 'an impossible size must not start literal mode');
|
|
492
|
-
test.ok(stream.destroyed, 'the stream must fail closed instead of continuing as if the marker were text');
|
|
493
|
-
setImmediate(() => {
|
|
494
|
-
test.ok(streamErr && streamErr.code === 'LiteralTooLarge', 'must fail with LiteralTooLarge');
|
|
495
|
-
test.done();
|
|
496
|
-
});
|
|
497
|
-
};
|
|
498
|
-
|
|
499
|
-
module.exports['Literal marker accepts a zero-padded size'] = test => {
|
|
500
|
-
// The RFC "number" production is 1*DIGIT, so leading zeros are legal - a long
|
|
501
|
-
// digit run can still denote a small size and must be consumed as a literal
|
|
502
|
-
const stream = new ImapStream({ cid: 'test' });
|
|
503
|
-
stream.on('error', () => {});
|
|
504
|
-
stream.resume();
|
|
505
|
-
|
|
506
|
-
const line = Buffer.from(`* 1 FETCH (BODY[] {${'0'.repeat(21)}123}\r\n`);
|
|
507
|
-
|
|
508
|
-
test.equal(stream.checkLiteralMarker(line), true, 'a zero-padded marker is a valid literal marker');
|
|
509
|
-
test.equal(stream.literalWaiting, 123, 'the padded size must parse to its numeric value');
|
|
510
|
-
stream.destroy();
|
|
511
|
-
test.done();
|
|
512
|
-
};
|
|
513
|
-
|
|
514
|
-
module.exports['Literal marker still accepts sizes at the digit-length bound'] = test => {
|
|
515
|
-
const stream = new ImapStream({ cid: 'test', maxLiteralSize: Number.MAX_SAFE_INTEGER });
|
|
516
|
-
stream.on('error', () => {});
|
|
517
|
-
stream.resume();
|
|
518
|
-
|
|
519
|
-
// 19 digits is the widest a number64 gets, so it must still be recognized
|
|
520
|
-
const line = Buffer.from(`* OK {${'9'.repeat(19)}}\r\n`);
|
|
521
|
-
|
|
522
|
-
test.equal(stream.checkLiteralMarker(line), false, 'a size beyond the configured maximum fails the stream rather than parsing');
|
|
523
|
-
|
|
524
|
-
const ok = new ImapStream({ cid: 'test' });
|
|
525
|
-
ok.on('error', () => {});
|
|
526
|
-
ok.resume();
|
|
527
|
-
test.equal(ok.checkLiteralMarker(Buffer.from('* OK {1024}\r\n')), true);
|
|
528
|
-
test.equal(ok.literalWaiting, 1024);
|
|
529
|
-
test.done();
|
|
530
|
-
};
|
|
531
|
-
|
|
532
|
-
module.exports['Response assembly enforces the cumulative size cap'] = test => {
|
|
533
|
-
// The per-line and per-literal caps alone cannot stop a response spread across many
|
|
534
|
-
// tokens: under a 40-byte response budget, a 25-byte marker line plus a declared
|
|
535
|
-
// 10-byte literal fits (35), but the next marker line (16 bytes + 10 declared) must
|
|
536
|
-
// trip the cap - before the second literal's bytes are even read
|
|
537
|
-
const stream = new ImapStream({ cid: 'test', maxResponseSize: 40 });
|
|
538
|
-
let streamErr = null;
|
|
539
|
-
stream.on('error', err => {
|
|
540
|
-
streamErr = err;
|
|
541
|
-
});
|
|
542
|
-
stream.resume();
|
|
543
|
-
|
|
544
|
-
stream.write(Buffer.from('* 1 FETCH (BODY[1] {10}\r\n'));
|
|
545
|
-
stream.write(Buffer.from('0123456789'));
|
|
546
|
-
stream.write(Buffer.from(' BODY[2] {10}\r\n0123456789)\r\n'));
|
|
547
|
-
|
|
548
|
-
setTimeout(() => {
|
|
549
|
-
test.ok(streamErr, 'an oversized cumulative response must fail the stream');
|
|
550
|
-
test.equal(streamErr && streamErr.code, 'ResponseTooLarge');
|
|
551
|
-
test.ok(stream.destroyed, 'the stream must fail closed instead of parsing the rejected payload');
|
|
552
|
-
test.done();
|
|
553
|
-
}, 100);
|
|
554
|
-
};
|
|
555
|
-
|
|
556
|
-
// Resolves once the stream has settled - on its first 'error', or on 'end' when it is
|
|
557
|
-
// consumed to completion. Waiting for the real signal keeps these tests off fixed sleeps.
|
|
558
|
-
const settle = stream =>
|
|
559
|
-
new Promise(resolve => {
|
|
560
|
-
stream.once('error', err => resolve(err));
|
|
561
|
-
stream.once('end', () => resolve(null));
|
|
562
|
-
});
|
|
563
|
-
|
|
564
|
-
module.exports['Response size budget resets between responses'] = async test => {
|
|
565
|
-
// The counter tracks a single response, not the whole session. Each response here fits
|
|
566
|
-
// the 40-byte budget on its own but the two together do not, so a counter that failed to
|
|
567
|
-
// reset would trip the cap on the second one - which is what makes this test detect the
|
|
568
|
-
// regression rather than merely pass alongside it.
|
|
569
|
-
const line = '* OK ' + 'a'.repeat(23) + '\r\n'; // 30 bytes, two of them exceed the budget
|
|
570
|
-
const stream = new ImapStream({ cid: 'test', maxResponseSize: 40 });
|
|
571
|
-
let streamErr = null;
|
|
572
|
-
let count = 0;
|
|
573
|
-
stream.on('error', err => {
|
|
574
|
-
streamErr = err;
|
|
575
|
-
});
|
|
576
|
-
stream.on('data', cmd => {
|
|
577
|
-
count++;
|
|
578
|
-
cmd.next();
|
|
579
|
-
});
|
|
580
|
-
|
|
581
|
-
test.ok(line.length <= 40 && line.length * 2 > 40, 'each response fits the budget, the pair does not');
|
|
582
|
-
|
|
583
|
-
stream.write(Buffer.from(line + line));
|
|
584
|
-
stream.end();
|
|
585
|
-
|
|
586
|
-
await settle(stream);
|
|
587
|
-
test.ifError(streamErr);
|
|
588
|
-
test.equal(count, 2);
|
|
589
|
-
test.done();
|
|
590
|
-
};
|
|
591
|
-
|
|
592
|
-
module.exports['Response size cap defaults above the literal cap'] = test => {
|
|
593
|
-
// The response total also carries the literal marker line and the rest of the framing, so
|
|
594
|
-
// a default equal to the literal cap would make a literal of exactly the maximum permitted
|
|
595
|
-
// size impossible to receive
|
|
596
|
-
const stream = new ImapStream({ cid: 'test' });
|
|
597
|
-
test.equal(stream.maxResponseSize, 2 * 1024 * 1024 * 1024);
|
|
598
|
-
test.ok(stream.maxResponseSize > stream.maxLiteralSize, 'the response cap must leave headroom above the literal cap');
|
|
599
|
-
test.done();
|
|
600
|
-
};
|
|
601
|
-
|
|
602
|
-
module.exports['A literal of exactly maxLiteralSize is accepted when the response cap leaves headroom'] = async test => {
|
|
603
|
-
const stream = new ImapStream({ cid: 'test', maxLiteralSize: 100, maxResponseSize: 200 });
|
|
604
|
-
let streamErr = null;
|
|
605
|
-
let received = null;
|
|
606
|
-
stream.on('error', err => {
|
|
607
|
-
streamErr = err;
|
|
608
|
-
});
|
|
609
|
-
stream.on('data', cmd => {
|
|
610
|
-
received = cmd;
|
|
611
|
-
cmd.next();
|
|
612
|
-
});
|
|
613
|
-
|
|
614
|
-
stream.write(Buffer.from('* 1 FETCH (BODY[] {100}\r\n'));
|
|
615
|
-
stream.write(Buffer.from('x'.repeat(100)));
|
|
616
|
-
stream.write(Buffer.from(')\r\n'));
|
|
617
|
-
stream.end();
|
|
618
|
-
|
|
619
|
-
await settle(stream);
|
|
620
|
-
test.ifError(streamErr);
|
|
621
|
-
test.ok(received, 'a literal at exactly the configured maximum must be delivered');
|
|
622
|
-
test.equal(received.literals.length, 1);
|
|
623
|
-
test.equal(received.literals[0].length, 100);
|
|
624
|
-
test.done();
|
|
625
|
-
};
|
|
626
|
-
|
|
627
|
-
module.exports['An unterminated line is bounded by the response budget'] = async test => {
|
|
628
|
-
// maxResponseSize is only committed when a line completes, so an in-progress line has to
|
|
629
|
-
// be measured against the remaining budget separately - otherwise a response cap lowered
|
|
630
|
-
// to bound parser memory buys nothing while a server streams a line that never ends
|
|
631
|
-
const stream = new ImapStream({ cid: 'test', maxResponseSize: 64 });
|
|
632
|
-
stream.resume();
|
|
633
|
-
|
|
634
|
-
stream.write(Buffer.from('x'.repeat(1024))); // no line terminator anywhere
|
|
635
|
-
|
|
636
|
-
let err = await settle(stream);
|
|
637
|
-
test.ok(err, 'an unterminated line beyond the response budget must fail the stream');
|
|
638
|
-
test.equal(err.code, 'ResponseTooLarge');
|
|
639
|
-
test.ok(stream.lineBytes <= 64, 'no more than the budget may stay buffered');
|
|
640
|
-
test.done();
|
|
641
|
-
};
|
|
642
|
-
|
|
643
|
-
module.exports['Infinity disables a parser size cap'] = test => {
|
|
644
|
-
// A cap that cannot be disabled forces a caller who knows their server onto the default
|
|
645
|
-
const stream = new ImapStream({ cid: 'test', maxResponseSize: Infinity, maxLiteralSize: Infinity, maxLineLength: Infinity });
|
|
646
|
-
test.equal(stream.maxResponseSize, Infinity);
|
|
647
|
-
test.equal(stream.maxLiteralSize, Infinity);
|
|
648
|
-
test.equal(stream.maxLineLength, Infinity);
|
|
649
|
-
test.done();
|
|
650
|
-
};
|
|
651
|
-
|
|
652
|
-
module.exports['A marker line that fits the budget can still be refused for its literal'] = async test => {
|
|
653
|
-
// The line is measured against the budget as it is assembled, but the declared literal is
|
|
654
|
-
// only charged once the marker line completes - so the cap has to be enforced in both places
|
|
655
|
-
const stream = new ImapStream({ cid: 'test', maxResponseSize: 40 });
|
|
656
|
-
stream.resume();
|
|
657
|
-
|
|
658
|
-
// 25-byte marker line fits on its own; the 30 declared literal bytes push the total past 40
|
|
659
|
-
stream.write(Buffer.from('* 1 FETCH (BODY[1] {30}\r\n'));
|
|
660
|
-
|
|
661
|
-
let err = await settle(stream);
|
|
662
|
-
test.ok(err, 'the declared literal must be charged before its bytes arrive');
|
|
663
|
-
test.equal(err.code, 'ResponseTooLarge');
|
|
664
|
-
test.equal(err.responseSize, 55);
|
|
665
|
-
test.done();
|
|
666
|
-
};
|