imapflow 1.2.13 → 1.2.14
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/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +8 -0
- package/lib/charsets.js +1 -4
- package/lib/commands/append.js +1 -1
- package/lib/commands/copy.js +3 -24
- package/lib/commands/copyuid-parser.js +33 -0
- package/lib/commands/idle.js +5 -7
- package/lib/commands/list.js +1 -1
- package/lib/commands/move.js +4 -27
- package/lib/commands/store.js +1 -3
- package/lib/handler/imap-compiler.js +1 -3
- package/lib/handler/imap-formal-syntax.js +1 -7
- package/lib/handler/imap-parser.js +1 -1
- package/lib/handler/imap-stream.js +7 -13
- package/lib/handler/parser-instance.js +2 -2
- package/lib/handler/token-parser.js +9 -9
- package/lib/imap-flow.js +80 -117
- package/lib/search-compiler.js +4 -10
- package/lib/tools.js +29 -36
- package/package.json +2 -2
- package/test/charsets-test.js +161 -0
- package/test/commands-integration-test.js +61 -0
- package/test/connection-edge-cases-test.js +94 -0
- package/test/copyuid-parser-test.js +153 -0
- package/test/imap-compiler-test.js +66 -0
- package/test/imap-formal-syntax-test.js +275 -0
- package/test/imap-stream-edge-cases-test.js +268 -0
- package/test/imapflow-test.js +212 -0
- package/test/search-compiler-test.js +74 -0
- package/test/token-parser-test.js +374 -0
- package/test/tools-test.js +149 -2
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
/* eslint new-cap: 0 */
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const imapFormalSyntax = require('../lib/handler/imap-formal-syntax');
|
|
5
|
+
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// CHAR
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
module.exports['Formal Syntax: CHAR() has length 127'] = test => {
|
|
11
|
+
test.equal(imapFormalSyntax.CHAR().length, 127);
|
|
12
|
+
test.done();
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
module.exports['Formal Syntax: CHAR() includes A'] = test => {
|
|
16
|
+
test.ok(imapFormalSyntax.CHAR().includes('A'));
|
|
17
|
+
test.done();
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
module.exports['Formal Syntax: CHAR() excludes NUL (0x00)'] = test => {
|
|
21
|
+
test.ok(!imapFormalSyntax.CHAR().includes('\x00'));
|
|
22
|
+
test.done();
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
module.exports['Formal Syntax: CHAR() excludes 0x80'] = test => {
|
|
26
|
+
test.ok(!imapFormalSyntax.CHAR().includes('\x80'));
|
|
27
|
+
test.done();
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
// CHAR8
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
module.exports['Formal Syntax: CHAR8() has length 255'] = test => {
|
|
35
|
+
test.equal(imapFormalSyntax.CHAR8().length, 255);
|
|
36
|
+
test.done();
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
module.exports['Formal Syntax: CHAR8() includes 0xFF'] = test => {
|
|
40
|
+
test.ok(imapFormalSyntax.CHAR8().includes('\xFF'));
|
|
41
|
+
test.done();
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// SP
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
module.exports['Formal Syntax: SP() equals a single space'] = test => {
|
|
49
|
+
test.equal(imapFormalSyntax.SP(), ' ');
|
|
50
|
+
test.done();
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
module.exports['Formal Syntax: SP() has length 1'] = test => {
|
|
54
|
+
test.equal(imapFormalSyntax.SP().length, 1);
|
|
55
|
+
test.done();
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
// CTL
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
|
|
62
|
+
module.exports['Formal Syntax: CTL() includes NUL (0x00)'] = test => {
|
|
63
|
+
test.ok(imapFormalSyntax.CTL().includes('\x00'));
|
|
64
|
+
test.done();
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
module.exports['Formal Syntax: CTL() includes 0x1F'] = test => {
|
|
68
|
+
test.ok(imapFormalSyntax.CTL().includes('\x1F'));
|
|
69
|
+
test.done();
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
module.exports['Formal Syntax: CTL() includes DEL (0x7F)'] = test => {
|
|
73
|
+
test.ok(imapFormalSyntax.CTL().includes('\x7F'));
|
|
74
|
+
test.done();
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
module.exports['Formal Syntax: CTL() excludes space (0x20)'] = test => {
|
|
78
|
+
test.ok(!imapFormalSyntax.CTL().includes(' '));
|
|
79
|
+
test.done();
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
// ALPHA
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
|
|
86
|
+
module.exports['Formal Syntax: ALPHA() has length 52'] = test => {
|
|
87
|
+
test.equal(imapFormalSyntax.ALPHA().length, 52);
|
|
88
|
+
test.done();
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
module.exports['Formal Syntax: ALPHA() includes A'] = test => {
|
|
92
|
+
test.ok(imapFormalSyntax.ALPHA().includes('A'));
|
|
93
|
+
test.done();
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
module.exports['Formal Syntax: ALPHA() includes z'] = test => {
|
|
97
|
+
test.ok(imapFormalSyntax.ALPHA().includes('z'));
|
|
98
|
+
test.done();
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
module.exports['Formal Syntax: ALPHA() excludes 0'] = test => {
|
|
102
|
+
test.ok(!imapFormalSyntax.ALPHA().includes('0'));
|
|
103
|
+
test.done();
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
// ---------------------------------------------------------------------------
|
|
107
|
+
// DIGIT
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
109
|
+
|
|
110
|
+
module.exports['Formal Syntax: DIGIT() has length 10'] = test => {
|
|
111
|
+
test.equal(imapFormalSyntax.DIGIT().length, 10);
|
|
112
|
+
test.done();
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
module.exports['Formal Syntax: DIGIT() includes 0'] = test => {
|
|
116
|
+
test.ok(imapFormalSyntax.DIGIT().includes('0'));
|
|
117
|
+
test.done();
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
module.exports['Formal Syntax: DIGIT() includes 9'] = test => {
|
|
121
|
+
test.ok(imapFormalSyntax.DIGIT().includes('9'));
|
|
122
|
+
test.done();
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
module.exports['Formal Syntax: DIGIT() excludes a'] = test => {
|
|
126
|
+
test.ok(!imapFormalSyntax.DIGIT().includes('a'));
|
|
127
|
+
test.done();
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
// ATOM-CHAR
|
|
132
|
+
// ---------------------------------------------------------------------------
|
|
133
|
+
|
|
134
|
+
module.exports['Formal Syntax: ATOM-CHAR() includes A'] = test => {
|
|
135
|
+
test.ok(imapFormalSyntax['ATOM-CHAR']().includes('A'));
|
|
136
|
+
test.done();
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
module.exports['Formal Syntax: ATOM-CHAR() excludes open paren'] = test => {
|
|
140
|
+
test.ok(!imapFormalSyntax['ATOM-CHAR']().includes('('));
|
|
141
|
+
test.done();
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
module.exports['Formal Syntax: ATOM-CHAR() excludes close paren'] = test => {
|
|
145
|
+
test.ok(!imapFormalSyntax['ATOM-CHAR']().includes(')'));
|
|
146
|
+
test.done();
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
module.exports['Formal Syntax: ATOM-CHAR() excludes open brace'] = test => {
|
|
150
|
+
test.ok(!imapFormalSyntax['ATOM-CHAR']().includes('{'));
|
|
151
|
+
test.done();
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
module.exports['Formal Syntax: ATOM-CHAR() excludes space'] = test => {
|
|
155
|
+
test.ok(!imapFormalSyntax['ATOM-CHAR']().includes(' '));
|
|
156
|
+
test.done();
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
module.exports['Formal Syntax: ATOM-CHAR() excludes double quote'] = test => {
|
|
160
|
+
test.ok(!imapFormalSyntax['ATOM-CHAR']().includes('"'));
|
|
161
|
+
test.done();
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
module.exports['Formal Syntax: ATOM-CHAR() excludes backslash'] = test => {
|
|
165
|
+
test.ok(!imapFormalSyntax['ATOM-CHAR']().includes('\\'));
|
|
166
|
+
test.done();
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
// ---------------------------------------------------------------------------
|
|
170
|
+
// ASTRING-CHAR
|
|
171
|
+
// ---------------------------------------------------------------------------
|
|
172
|
+
|
|
173
|
+
module.exports['Formal Syntax: ASTRING-CHAR() includes close bracket (resp-special)'] = test => {
|
|
174
|
+
test.ok(imapFormalSyntax['ASTRING-CHAR']().includes(']'));
|
|
175
|
+
test.done();
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
// ---------------------------------------------------------------------------
|
|
179
|
+
// TEXT-CHAR
|
|
180
|
+
// ---------------------------------------------------------------------------
|
|
181
|
+
|
|
182
|
+
module.exports['Formal Syntax: TEXT-CHAR() includes A'] = test => {
|
|
183
|
+
test.ok(imapFormalSyntax['TEXT-CHAR']().includes('A'));
|
|
184
|
+
test.done();
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
module.exports['Formal Syntax: TEXT-CHAR() includes space'] = test => {
|
|
188
|
+
test.ok(imapFormalSyntax['TEXT-CHAR']().includes(' '));
|
|
189
|
+
test.done();
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
module.exports['Formal Syntax: TEXT-CHAR() excludes CR'] = test => {
|
|
193
|
+
test.ok(!imapFormalSyntax['TEXT-CHAR']().includes('\r'));
|
|
194
|
+
test.done();
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
module.exports['Formal Syntax: TEXT-CHAR() excludes LF'] = test => {
|
|
198
|
+
test.ok(!imapFormalSyntax['TEXT-CHAR']().includes('\n'));
|
|
199
|
+
test.done();
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
// ---------------------------------------------------------------------------
|
|
203
|
+
// tag
|
|
204
|
+
// ---------------------------------------------------------------------------
|
|
205
|
+
|
|
206
|
+
module.exports['Formal Syntax: tag() excludes plus sign'] = test => {
|
|
207
|
+
test.ok(!imapFormalSyntax.tag().includes('+'));
|
|
208
|
+
test.done();
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
module.exports['Formal Syntax: tag() includes A'] = test => {
|
|
212
|
+
test.ok(imapFormalSyntax.tag().includes('A'));
|
|
213
|
+
test.done();
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
module.exports['Formal Syntax: tag() includes close bracket'] = test => {
|
|
217
|
+
test.ok(imapFormalSyntax.tag().includes(']'));
|
|
218
|
+
test.done();
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
// ---------------------------------------------------------------------------
|
|
222
|
+
// command
|
|
223
|
+
// ---------------------------------------------------------------------------
|
|
224
|
+
|
|
225
|
+
module.exports['Formal Syntax: command() includes A'] = test => {
|
|
226
|
+
test.ok(imapFormalSyntax.command().includes('A'));
|
|
227
|
+
test.done();
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
module.exports['Formal Syntax: command() includes 0'] = test => {
|
|
231
|
+
test.ok(imapFormalSyntax.command().includes('0'));
|
|
232
|
+
test.done();
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
module.exports['Formal Syntax: command() includes hyphen'] = test => {
|
|
236
|
+
test.ok(imapFormalSyntax.command().includes('-'));
|
|
237
|
+
test.done();
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
module.exports['Formal Syntax: command() excludes asterisk'] = test => {
|
|
241
|
+
test.ok(!imapFormalSyntax.command().includes('*'));
|
|
242
|
+
test.done();
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
// ---------------------------------------------------------------------------
|
|
246
|
+
// verify()
|
|
247
|
+
// ---------------------------------------------------------------------------
|
|
248
|
+
|
|
249
|
+
module.exports['Formal Syntax: verify() returns -1 when all characters are valid'] = test => {
|
|
250
|
+
test.equal(imapFormalSyntax.verify('ABC', 'ABCDEF'), -1);
|
|
251
|
+
test.done();
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
module.exports['Formal Syntax: verify() returns index of first invalid character'] = test => {
|
|
255
|
+
test.equal(imapFormalSyntax.verify('ABXC', 'ABC'), 2);
|
|
256
|
+
test.done();
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
module.exports['Formal Syntax: verify() returns 0 when first character is invalid'] = test => {
|
|
260
|
+
test.equal(imapFormalSyntax.verify('!A', 'ABC'), 0);
|
|
261
|
+
test.done();
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
// ---------------------------------------------------------------------------
|
|
265
|
+
// Memoization
|
|
266
|
+
// ---------------------------------------------------------------------------
|
|
267
|
+
|
|
268
|
+
module.exports['Formal Syntax: CHAR() returns same string reference on repeated calls (memoized)'] = test => {
|
|
269
|
+
// After the first call, the method is replaced with a closure that returns
|
|
270
|
+
// the cached value, so subsequent calls must return the exact same string object.
|
|
271
|
+
const first = imapFormalSyntax.CHAR();
|
|
272
|
+
const second = imapFormalSyntax.CHAR();
|
|
273
|
+
test.strictEqual(first, second);
|
|
274
|
+
test.done();
|
|
275
|
+
};
|
|
@@ -0,0 +1,268 @@
|
|
|
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['Incomplete line continued in next chunk'] = test => {
|
|
135
|
+
runStreamTest(
|
|
136
|
+
test,
|
|
137
|
+
cmd => {
|
|
138
|
+
test.equal(cmd.payload.toString(), 'A CAPABILITY', 'payload should be A CAPABILITY');
|
|
139
|
+
test.equal(cmd.literals.length, 0, 'should have no literals');
|
|
140
|
+
},
|
|
141
|
+
async stream => {
|
|
142
|
+
stream.write(Buffer.from('A CA'));
|
|
143
|
+
stream.end(Buffer.from('PABILITY\r\n'));
|
|
144
|
+
},
|
|
145
|
+
1
|
|
146
|
+
);
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
module.exports['Empty chunk then valid command'] = test => {
|
|
150
|
+
runStreamTest(
|
|
151
|
+
test,
|
|
152
|
+
cmd => {
|
|
153
|
+
test.equal(cmd.payload.toString(), 'A CMD', 'payload should be A CMD');
|
|
154
|
+
test.equal(cmd.literals.length, 0, 'should have no literals');
|
|
155
|
+
},
|
|
156
|
+
async stream => {
|
|
157
|
+
stream.write(Buffer.alloc(0));
|
|
158
|
+
stream.end(Buffer.from('A CMD\r\n'));
|
|
159
|
+
},
|
|
160
|
+
1
|
|
161
|
+
);
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
module.exports['String input converted to Buffer'] = test => {
|
|
165
|
+
runStreamTest(
|
|
166
|
+
test,
|
|
167
|
+
cmd => {
|
|
168
|
+
test.equal(cmd.payload.toString(), 'A CMD', 'payload should be A CMD');
|
|
169
|
+
test.ok(Buffer.isBuffer(cmd.payload), 'payload should be a Buffer');
|
|
170
|
+
test.equal(cmd.literals.length, 0, 'should have no literals');
|
|
171
|
+
},
|
|
172
|
+
async stream => {
|
|
173
|
+
stream.end('A CMD\r\n');
|
|
174
|
+
},
|
|
175
|
+
1
|
|
176
|
+
);
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
module.exports['LF-only line terminator'] = test => {
|
|
180
|
+
runStreamTest(
|
|
181
|
+
test,
|
|
182
|
+
cmd => {
|
|
183
|
+
test.equal(cmd.payload.toString(), 'A CMD', 'payload should be A CMD without CR or LF');
|
|
184
|
+
test.equal(cmd.literals.length, 0, 'should have no literals');
|
|
185
|
+
},
|
|
186
|
+
async stream => {
|
|
187
|
+
stream.end(Buffer.from('A CMD\n'));
|
|
188
|
+
},
|
|
189
|
+
1
|
|
190
|
+
);
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
module.exports['Many chunks trigger event loop yield'] = test => {
|
|
194
|
+
runStreamTest(
|
|
195
|
+
test,
|
|
196
|
+
cmd => {
|
|
197
|
+
// Just verify each command is valid
|
|
198
|
+
test.ok(cmd.payload.toString().startsWith('A'), 'command should start with tag');
|
|
199
|
+
},
|
|
200
|
+
async stream => {
|
|
201
|
+
for (let i = 0; i < 15; i++) {
|
|
202
|
+
stream.write(Buffer.from(`A CMD${i}\r\n`));
|
|
203
|
+
}
|
|
204
|
+
stream.end();
|
|
205
|
+
},
|
|
206
|
+
15
|
|
207
|
+
);
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
module.exports['Destroy with queued items does not hang'] = test => {
|
|
211
|
+
const stream = new ImapStream({ cid: 'test' });
|
|
212
|
+
let errorEmitted = false;
|
|
213
|
+
|
|
214
|
+
stream.on('error', () => {
|
|
215
|
+
errorEmitted = true;
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// Write multiple chunks rapidly then destroy
|
|
219
|
+
stream.write(Buffer.from('A CMD1\r\n'));
|
|
220
|
+
stream.write(Buffer.from('B CMD2\r\n'));
|
|
221
|
+
stream.destroy();
|
|
222
|
+
|
|
223
|
+
// Errors from destroy are emitted synchronously or on next tick
|
|
224
|
+
setImmediate(() => {
|
|
225
|
+
test.ok(!errorEmitted, 'should not emit error on destroy');
|
|
226
|
+
test.done();
|
|
227
|
+
});
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
module.exports['logRaw option triggers trace logging'] = test => {
|
|
231
|
+
let traceCalled = false;
|
|
232
|
+
let traceData = null;
|
|
233
|
+
|
|
234
|
+
const stream = new ImapStream({
|
|
235
|
+
cid: 'test',
|
|
236
|
+
logRaw: true
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// Override the log object to capture trace calls
|
|
240
|
+
stream.log = {
|
|
241
|
+
trace: data => {
|
|
242
|
+
traceCalled = true;
|
|
243
|
+
traceData = data;
|
|
244
|
+
},
|
|
245
|
+
debug: () => {},
|
|
246
|
+
info: () => {},
|
|
247
|
+
warn: () => {},
|
|
248
|
+
error: () => {}
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
// Also need to handle readable events
|
|
252
|
+
stream.on('readable', () => {
|
|
253
|
+
let cmd;
|
|
254
|
+
while ((cmd = stream.read()) !== null) {
|
|
255
|
+
cmd.next();
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
stream.on('end', () => {
|
|
260
|
+
test.ok(traceCalled, 'trace should have been called');
|
|
261
|
+
test.ok(traceData, 'trace data should exist');
|
|
262
|
+
test.equal(traceData.src, 's', 'source should be s');
|
|
263
|
+
test.ok(traceData.data, 'should have base64 data');
|
|
264
|
+
test.done();
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
stream.end(Buffer.from('A CMD\r\n'));
|
|
268
|
+
};
|