imapflow 1.0.91 → 1.0.92

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/.gitattributes ADDED
@@ -0,0 +1 @@
1
+ *.js text eol=lf
@@ -0,0 +1,21 @@
1
+ name: Run tests
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+
7
+ jobs:
8
+ test:
9
+ strategy:
10
+ matrix:
11
+ node: [16.x, 18.x]
12
+ os: [ubuntu-latest]
13
+ runs-on: ${{ matrix.os }}
14
+ steps:
15
+ - uses: actions/checkout@v2
16
+ - name: Use Node.js ${{ matrix.node }}
17
+ uses: actions/setup-node@v1
18
+ with:
19
+ node-version: ${{ matrix.node }}
20
+ - run: npm install
21
+ - run: npm test
package/CHANGELOG.md CHANGED
@@ -1,10 +1,15 @@
1
1
  # CHANGELOG
2
2
 
3
+ - 1.0.92 - 2022-05-03
4
+
5
+ - Added support for IMAP BINARY extension (rfc3516)
6
+ - Logging improvements
7
+
3
8
  - 1.0.91 - 2022-05-02
4
9
 
5
10
  - Do not throw if literal includes a null byte
6
11
  - Use UTF8 path names by default
7
- - Overrides '\*' range query with then EXISTIS value
12
+ - Overrides '\*' range query with the EXISTS value
8
13
 
9
14
  - 1.0.90 - 2022-04-04
10
15
 
@@ -38,7 +38,17 @@ module.exports = async (connection, destination, content, flags, idate) => {
38
38
  attributes.push({ type: 'STRING', value: idate }); // force quotes as required by date-time
39
39
  }
40
40
 
41
- attributes.push({ type: 'LITERAL', value: content });
41
+ let isLiteral8 = false;
42
+ if (connection.capabilities.has('BINARY') && !connection.disableBinary) {
43
+ if (typeof content === 'string') {
44
+ content = Buffer.from(content);
45
+ }
46
+ // Value is literal8 if it contains NULL bytes. The server must support the BINARY extension
47
+ // and if it does not then send the value as a regular literal and hope for the best
48
+ isLiteral8 = content.indexOf(Buffer.from([0])) >= 0;
49
+ }
50
+
51
+ attributes.push({ type: 'LITERAL', value: content, isLiteral8 });
42
52
 
43
53
  let map = { destination };
44
54
  if (connection.mailbox && connection.mailbox.path) {
@@ -39,7 +39,7 @@ module.exports = async (connection, username, accessToken) => {
39
39
  try {
40
40
  errorResponse = JSON.parse(Buffer.from(resp.attributes[0].value, 'base64').toString());
41
41
  } catch (err) {
42
- connection.log.debug({ data: resp.attributes[0].value, err });
42
+ connection.log.debug({ errorResponse: resp.attributes[0].value, err });
43
43
  }
44
44
  }
45
45
 
@@ -18,7 +18,7 @@ module.exports = async (connection, range, query, options) => {
18
18
  list: []
19
19
  };
20
20
 
21
- const commandKey = 'BODY';
21
+ const commandKey = connection.capabilities.has('BINARY') && !connection.disableBinary ? 'BINARY' : 'BODY';
22
22
 
23
23
  let response;
24
24
  try {
@@ -85,7 +85,7 @@ module.exports = async (response, options) => {
85
85
  let canAppend = !asArray || literalPlus || (literalMinus && literalLength <= 4096);
86
86
  let usePlus = canAppend && (literalMinus || literalPlus);
87
87
 
88
- resp += `{${literalLength}${usePlus ? '+' : ''}}\r\n`;
88
+ resp += `${node.isLiteral8 ? '~' : ''}{${literalLength}${usePlus ? '+' : ''}}\r\n`;
89
89
 
90
90
  if (canAppend) {
91
91
  resp += (node.value || '').toString('binary');
@@ -40,6 +40,7 @@ class ImapStream extends Transform {
40
40
  this.literals = [];
41
41
 
42
42
  this.compress = false;
43
+ this.secureConnection = this.options.secureConnection;
43
44
 
44
45
  this.processingInput = false;
45
46
  this.inputQueue = []; // unprocessed input chunks
@@ -197,7 +198,14 @@ class ImapStream extends Transform {
197
198
  }
198
199
 
199
200
  if (this.options.logRaw) {
200
- this.log.trace({ src: 's', msg: 'read from socket', data: chunk.toString('base64'), compress: !!this.compress, cid: this.cid });
201
+ this.log.trace({
202
+ src: 's',
203
+ msg: 'read from socket',
204
+ data: chunk.toString('base64'),
205
+ compress: !!this.compress,
206
+ secure: !!this.secureConnection,
207
+ cid: this.cid
208
+ });
201
209
  }
202
210
 
203
211
  if (chunk && chunk.length) {
@@ -29,13 +29,13 @@ class TokenParser {
29
29
  let elm;
30
30
  let partial;
31
31
 
32
- if (!node.closed && node.type === 'SEQUENCE' && node.value === '*') {
33
- node.closed = true;
32
+ if (!node.isClosed && node.type === 'SEQUENCE' && node.value === '*') {
33
+ node.isClosed = true;
34
34
  node.type = 'ATOM';
35
35
  }
36
36
 
37
37
  // If the node was never closed, throw it
38
- if (!node.closed) {
38
+ if (!node.isClosed) {
39
39
  throw new Error('Unexpected end of input at position ' + (this.pos + this.str.length - 1));
40
40
  }
41
41
 
@@ -97,7 +97,7 @@ class TokenParser {
97
97
  childNodes: [],
98
98
  type: false,
99
99
  value: '',
100
- closed: true
100
+ isClosed: true
101
101
  };
102
102
 
103
103
  if (parentNode) {
@@ -136,14 +136,14 @@ class TokenParser {
136
136
  this.currentNode = this.createNode(this.currentNode, this.pos + i);
137
137
  this.currentNode.type = 'string';
138
138
  this.state = 'STRING';
139
- this.currentNode.closed = false;
139
+ this.currentNode.isClosed = false;
140
140
  break;
141
141
 
142
142
  // ( starts a new list
143
143
  case '(':
144
144
  this.currentNode = this.createNode(this.currentNode, this.pos + i);
145
145
  this.currentNode.type = 'LIST';
146
- this.currentNode.closed = false;
146
+ this.currentNode.isClosed = false;
147
147
  break;
148
148
 
149
149
  // ) closes a list
@@ -152,7 +152,7 @@ class TokenParser {
152
152
  throw new Error('Unexpected list terminator ) at position ' + (this.pos + i));
153
153
  }
154
154
 
155
- this.currentNode.closed = true;
155
+ this.currentNode.isClosed = true;
156
156
  this.currentNode.endPos = this.pos + i;
157
157
  this.currentNode = this.currentNode.parentNode;
158
158
 
@@ -164,7 +164,7 @@ class TokenParser {
164
164
  if (this.currentNode.type !== 'SECTION') {
165
165
  throw new Error('Unexpected section terminator ] at position ' + (this.pos + i));
166
166
  }
167
- this.currentNode.closed = true;
167
+ this.currentNode.isClosed = true;
168
168
  this.currentNode.endPos = this.pos + i;
169
169
  this.currentNode = this.currentNode.parentNode;
170
170
  checkSP();
@@ -181,25 +181,36 @@ class TokenParser {
181
181
  this.currentNode = this.createNode(this.currentNode, this.pos + i);
182
182
  this.currentNode.type = 'PARTIAL';
183
183
  this.state = 'PARTIAL';
184
- this.currentNode.closed = false;
184
+ this.currentNode.isClosed = false;
185
185
  }
186
186
  break;
187
187
 
188
+ // binary literal8
189
+ case '~': {
190
+ let nextChr = this.str.charAt(i + 1);
191
+ if (nextChr !== '{') {
192
+ throw new Error('Unexpected literal8 marker at position ' + (this.pos + i));
193
+ }
194
+ this.expectedLiteralType = 'literal8';
195
+ break;
196
+ }
197
+
188
198
  // { starts a new literal
189
199
  case '{':
190
200
  this.currentNode = this.createNode(this.currentNode, this.pos + i);
191
201
  this.currentNode.type = 'LITERAL';
192
- this.currentNode.literalType = 'literal';
202
+ this.currentNode.literalType = this.expectedLiteralType || 'literal';
203
+ this.expectedLiteralType = false;
193
204
  this.state = 'LITERAL';
194
- this.currentNode.closed = false;
205
+ this.currentNode.isClosed = false;
195
206
  break;
196
207
 
197
- // ( starts a new sequence
208
+ // * starts a new sequence
198
209
  case '*':
199
210
  this.currentNode = this.createNode(this.currentNode, this.pos + i);
200
211
  this.currentNode.type = 'SEQUENCE';
201
212
  this.currentNode.value = chr;
202
- this.currentNode.closed = false;
213
+ this.currentNode.isClosed = false;
203
214
  this.state = 'SEQUENCE';
204
215
  break;
205
216
 
@@ -219,7 +230,7 @@ class TokenParser {
219
230
 
220
231
  this.currentNode = this.createNode(this.currentNode, this.pos + i);
221
232
  this.currentNode.type = 'SECTION';
222
- this.currentNode.closed = false;
233
+ this.currentNode.isClosed = false;
223
234
  this.state = 'NORMAL';
224
235
 
225
236
  // RFC2221 defines a response code REFERRAL whose payload is an
@@ -245,7 +256,7 @@ class TokenParser {
245
256
  this.currentNode = this.currentNode.parentNode;
246
257
 
247
258
  // close out the SECTION
248
- this.currentNode.closed = true;
259
+ this.currentNode.isClosed = true;
249
260
  this.currentNode = this.currentNode.parentNode;
250
261
  checkSP();
251
262
  }
@@ -287,7 +298,7 @@ class TokenParser {
287
298
  this.currentNode.endPos = this.pos + i - 1;
288
299
  this.currentNode = this.currentNode.parentNode;
289
300
 
290
- this.currentNode.closed = true;
301
+ this.currentNode.isClosed = true;
291
302
  this.currentNode.endPos = this.pos + i;
292
303
  this.currentNode = this.currentNode.parentNode;
293
304
  this.state = 'NORMAL';
@@ -298,20 +309,20 @@ class TokenParser {
298
309
 
299
310
  if ((chr === ',' || chr === ':') && this.currentNode.value.match(/^\d+$/)) {
300
311
  this.currentNode.type = 'SEQUENCE';
301
- this.currentNode.closed = true;
312
+ this.currentNode.isClosed = true;
302
313
  this.state = 'SEQUENCE';
303
314
  }
304
315
 
305
316
  // [ starts a section group for this element
306
317
  if (chr === '[') {
307
318
  // allowed only for selected elements
308
- if (['BODY', 'BODY.PEEK'].indexOf(this.currentNode.value.toUpperCase()) < 0) {
319
+ if (['BODY', 'BODY.PEEK', 'BINARY', 'BINARY.PEEK'].indexOf(this.currentNode.value.toUpperCase()) < 0) {
309
320
  throw new Error('Unexpected section start char [ at position ' + this.pos);
310
321
  }
311
322
  this.currentNode.endPos = this.pos + i;
312
323
  this.currentNode = this.createNode(this.currentNode.parentNode, this.pos + i);
313
324
  this.currentNode.type = 'SECTION';
314
- this.currentNode.closed = false;
325
+ this.currentNode.isClosed = false;
315
326
  this.state = 'NORMAL';
316
327
  break;
317
328
  }
@@ -334,7 +345,7 @@ class TokenParser {
334
345
  // DQUOTE ends the string sequence
335
346
  if (chr === '"') {
336
347
  this.currentNode.endPos = this.pos + i;
337
- this.currentNode.closed = true;
348
+ this.currentNode.isClosed = true;
338
349
  this.currentNode = this.currentNode.parentNode;
339
350
  this.state = 'NORMAL';
340
351
 
@@ -360,7 +371,7 @@ class TokenParser {
360
371
  throw new Error('Unexpected end of partial at position ' + this.pos);
361
372
  }
362
373
  this.currentNode.endPos = this.pos + i;
363
- this.currentNode.closed = true;
374
+ this.currentNode.isClosed = true;
364
375
  this.currentNode = this.currentNode.parentNode;
365
376
  this.state = 'NORMAL';
366
377
  checkSP();
@@ -386,7 +397,7 @@ class TokenParser {
386
397
  if (this.currentNode.started) {
387
398
  // only relevant if literals are not already parsed out from input
388
399
 
389
- // Disabled NUL check
400
+ // Disabled NULL byte check
390
401
  // See https://github.com/emailjs/emailjs-imap-handler/commit/f11b2822bedabe492236e8263afc630134a3c41c
391
402
  /*
392
403
  if (chr === '\u0000') {
@@ -398,7 +409,7 @@ class TokenParser {
398
409
 
399
410
  if (this.currentNode.chPos >= this.currentNode.literalLength) {
400
411
  this.currentNode.endPos = this.pos + i;
401
- this.currentNode.closed = true;
412
+ this.currentNode.isClosed = true;
402
413
  this.currentNode.value = this.currentNode.chBuffer.toString('binary');
403
414
  this.currentNode.chBuffer = Buffer.alloc(0);
404
415
  this.currentNode = this.currentNode.parentNode;
@@ -431,7 +442,7 @@ class TokenParser {
431
442
  // special case where literal content length is 0
432
443
  // close the node right away, do not wait for additional input
433
444
  this.currentNode.endPos = this.pos + i;
434
- this.currentNode.closed = true;
445
+ this.currentNode.isClosed = true;
435
446
  this.currentNode = this.currentNode.parentNode;
436
447
  this.state = 'NORMAL';
437
448
  checkSP();
@@ -449,7 +460,7 @@ class TokenParser {
449
460
  this.currentNode.endPos = this.pos + i + this.currentNode.value.length;
450
461
 
451
462
  this.currentNode.started = false;
452
- this.currentNode.closed = true;
463
+ this.currentNode.isClosed = true;
453
464
  this.currentNode = this.currentNode.parentNode;
454
465
  this.state = 'NORMAL';
455
466
  checkSP();
@@ -482,7 +493,7 @@ class TokenParser {
482
493
  throw new Error('Unexpected whitespace at position ' + (this.pos + i));
483
494
  }
484
495
 
485
- this.currentNode.closed = true;
496
+ this.currentNode.isClosed = true;
486
497
  this.currentNode.endPos = this.pos + i - 1;
487
498
  this.currentNode = this.currentNode.parentNode;
488
499
  this.state = 'NORMAL';
@@ -491,7 +502,7 @@ class TokenParser {
491
502
  this.currentNode.endPos = this.pos + i - 1;
492
503
  this.currentNode = this.currentNode.parentNode;
493
504
 
494
- this.currentNode.closed = true;
505
+ this.currentNode.isClosed = true;
495
506
  this.currentNode.endPos = this.pos + i;
496
507
  this.currentNode = this.currentNode.parentNode;
497
508
  this.state = 'NORMAL';
package/lib/imap-flow.js CHANGED
@@ -130,6 +130,7 @@ class ImapFlow extends EventEmitter {
130
130
  * @param {Boolean} [options.proxy] Optional proxy URL. Supports HTTP CONNECT (`http://`, `https://`) and SOCKS (`socks://`, `socks4://`, `socks5://`) proxies
131
131
  * @param {Boolean} [options.qresync=false] If true, then enables QRESYNC support. EXPUNGE notifications will include `uid` property instead of `seq`
132
132
  * @param {Number} [options.maxIdleTime] If set, then breaks and restarts IDLE every maxIdleTime ms
133
+ * @param {Boolean} [options.disableBinary=false] If true, then ignores the BINARY extension when making FETCH and APPEND calls
133
134
  */
134
135
  constructor(options) {
135
136
  super({ captureRejections: true });
@@ -183,7 +184,8 @@ class ImapFlow extends EventEmitter {
183
184
  this.streamer = new ImapStream({
184
185
  logger: this.log,
185
186
  cid: this.id,
186
- logRaw: this.logRaw
187
+ logRaw: this.logRaw,
188
+ secureConnection: this.secureConnection
187
189
  });
188
190
 
189
191
  this.reading = false;
@@ -263,6 +265,8 @@ class ImapFlow extends EventEmitter {
263
265
 
264
266
  this.maxIdleTime = this.options.maxIdleTime || false;
265
267
 
268
+ this.disableBinary = !!this.options.disableBinary;
269
+
266
270
  this.streamer.on('error', err => {
267
271
  if (['Z_BUF_ERROR', 'ECONNRESET', 'EPIPE', 'ETIMEDOUT', 'EHOSTUNREACH'].includes(err.code)) {
268
272
  // just close the connection, usually nothing but noise
@@ -310,7 +314,14 @@ class ImapFlow extends EventEmitter {
310
314
  }
311
315
 
312
316
  if (this.logRaw) {
313
- this.log.trace({ src: 'c', msg: 'write to socket', data: chunk.toString('base64'), compress: !!this._deflate, cid: this.id });
317
+ this.log.trace({
318
+ src: 'c',
319
+ msg: 'write to socket',
320
+ data: chunk.toString('base64'),
321
+ compress: !!this._deflate,
322
+ secure: !!this.secureConnection,
323
+ cid: this.id
324
+ });
314
325
  }
315
326
 
316
327
  this.writeSocket.write(chunk);
@@ -750,13 +761,14 @@ class ImapFlow extends EventEmitter {
750
761
  this.upgrading = true;
751
762
  this.socket = tls.connect(opts, () => {
752
763
  clearTimeout(this.upgradeTimeout);
753
- if (this.closed) {
764
+ if (this.isClosed) {
754
765
  // not sure if this is possible?
755
766
  return this.close();
756
767
  }
757
768
 
758
769
  this.secureConnection = true;
759
770
  this.upgrading = false;
771
+ this.streamer.secureConnection = true;
760
772
  this.socket.pipe(this.streamer);
761
773
  this.tls = typeof this.socket.getCipher === 'function' ? this.socket.getCipher() : false;
762
774
  if (this.tls) {
@@ -838,7 +850,7 @@ class ImapFlow extends EventEmitter {
838
850
  this.untaggedHandlers.OK = null;
839
851
  this.untaggedHandlers.PREAUTH = null;
840
852
 
841
- if (this.closed) {
853
+ if (this.isClosed) {
842
854
  return;
843
855
  }
844
856
 
@@ -871,7 +883,7 @@ class ImapFlow extends EventEmitter {
871
883
  this.untaggedHandlers.OK = null;
872
884
  this.untaggedHandlers.PREAUTH = null;
873
885
 
874
- if (this.closed) {
886
+ if (this.isClosed) {
875
887
  return;
876
888
  }
877
889
 
@@ -1281,7 +1293,9 @@ class ImapFlow extends EventEmitter {
1281
1293
  let reject = this.initialReject;
1282
1294
  this.initialResolve = false;
1283
1295
  this.initialReject = false;
1284
- reject(new Error('Unexpected close'));
1296
+ let err = new Error('Unexpected close');
1297
+ err.code = `ClosedAfterConnect${this.secureConnection ? 'TLS' : 'Text'}`;
1298
+ reject(err);
1285
1299
  }
1286
1300
 
1287
1301
  if (typeof this.preCheck === 'function') {
@@ -1311,10 +1325,10 @@ class ImapFlow extends EventEmitter {
1311
1325
  }
1312
1326
 
1313
1327
  this.state = this.states.LOGOUT;
1314
- if (this.closed) {
1328
+ if (this.isClosed) {
1315
1329
  return;
1316
1330
  }
1317
- this.closed = true;
1331
+ this.isClosed = true;
1318
1332
 
1319
1333
  if (this.writeSocket && !this.writeSocket.destroyed) {
1320
1334
  try {
package/lib/tools.js CHANGED
@@ -307,9 +307,9 @@ module.exports = {
307
307
  }
308
308
 
309
309
  default: {
310
- let match = key.match(/(body)\[/i);
310
+ let match = key.match(/(body|binary)\[/i);
311
311
  if (match) {
312
- let partKey = key.replace(/^(body)\[|]$/gi, '');
312
+ let partKey = key.replace(/^(body|binary)\[|]$/gi, '');
313
313
  partKey = partKey.replace(/\.fields.*$/g, '');
314
314
 
315
315
  let value = getBuffer(attribute);
package/lib/types.d.ts CHANGED
@@ -29,6 +29,7 @@ declare module "imapflow" {
29
29
  * @param [options.proxy] - Optional proxy URL. Supports HTTP CONNECT (`http://`, `https://`) and SOCKS (`socks://`, `socks4://`, `socks5://`) proxies
30
30
  * @param [options.qresync = false] - If true, then enables QRESYNC support. EXPUNGE notifications will include `uid` property instead of `seq`
31
31
  * @param [options.maxIdleTime] - If set, then breaks and restarts IDLE every maxIdleTime ms
32
+ * @param [options.disableBinary = false] - If true, then ignores the BINARY extension when making FETCH and APPEND calls
32
33
  */
33
34
  class ImapFlow extends EventEmitter {
34
35
  constructor(options: {
@@ -56,6 +57,7 @@ declare module "imapflow" {
56
57
  proxy?: boolean;
57
58
  qresync?: boolean;
58
59
  maxIdleTime?: number;
60
+ disableBinary?: boolean;
59
61
  });
60
62
  /**
61
63
  * Current module version as a static class property
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "imapflow",
3
- "version": "1.0.91",
3
+ "version": "1.0.92",
4
4
  "description": "IMAP Client for Node",
5
5
  "main": "./lib/imap-flow.js",
6
6
  "scripts": {
@@ -200,8 +200,7 @@ module.exports['IMAP Compiler: hide long strings'] = test =>
200
200
  attributes: [
201
201
  {
202
202
  type: 'String',
203
- value:
204
- 'Tere tere! Tere tere! Tere tere! Tere tere! Tere tere! Tere tere! Tere tere! Tere tere! Tere tere! Tere tere! Tere tere! Tere tere! Tere tere! Tere tere! Tere tere! Tere tere! Tere tere! Tere tere! Tere tere! Tere tere!'
203
+ value: 'Tere tere! Tere tere! Tere tere! Tere tere! Tere tere! Tere tere! Tere tere! Tere tere! Tere tere! Tere tere! Tere tere! Tere tere! Tere tere! Tere tere! Tere tere! Tere tere! Tere tere! Tere tere! Tere tere! Tere tere!'
205
204
  },
206
205
  'Vana kere'
207
206
  ]
@@ -248,6 +247,46 @@ module.exports['IMAP Compiler: LITERAL text'] = test =>
248
247
  )
249
248
  );
250
249
 
250
+ module.exports['IMAP Compiler: LITERAL literal'] = test =>
251
+ asyncWrapper(test, async test =>
252
+ test.equal(
253
+ await compiler({
254
+ tag: '*',
255
+ command: 'CMD',
256
+ attributes: [
257
+ // keep indentation
258
+ {
259
+ type: 'LITERAL',
260
+ value: 'Tere\x00 tere!',
261
+ isLiteral8: false
262
+ },
263
+ 'Vana kere'
264
+ ]
265
+ }),
266
+ '* CMD {11}\r\nTere\x00 tere! "Vana kere"'
267
+ )
268
+ );
269
+
270
+ module.exports['IMAP Compiler: LITERAL literal8'] = test =>
271
+ asyncWrapper(test, async test =>
272
+ test.equal(
273
+ await compiler({
274
+ tag: '*',
275
+ command: 'CMD',
276
+ attributes: [
277
+ // keep indentation
278
+ {
279
+ type: 'LITERAL',
280
+ value: 'Tere\x00 tere!',
281
+ isLiteral8: true
282
+ },
283
+ 'Vana kere'
284
+ ]
285
+ }),
286
+ '* CMD ~{11}\r\nTere\x00 tere! "Vana kere"'
287
+ )
288
+ );
289
+
251
290
  module.exports['IMAP Compiler: LITERAL array 1'] = test =>
252
291
  asyncWrapper(test, async test =>
253
292
  test.deepEqual(
@@ -309,6 +309,36 @@ module.exports['IMAP Parser: Literals: single literal'] = test =>
309
309
  ])
310
310
  );
311
311
 
312
+ module.exports['IMAP Parser: Literals: literal with NULL'] = test =>
313
+ asyncWrapper(test, async test =>
314
+ test.deepEqual((await parser('TAG1 CMD {4}\r\n', { literals: [Buffer.from('ab\x00d')] })).attributes, [
315
+ {
316
+ type: 'LITERAL',
317
+ value: Buffer.from('ab\x00d')
318
+ }
319
+ ])
320
+ );
321
+
322
+ module.exports['IMAP Parser: Literals: literal8'] = test =>
323
+ asyncWrapper(test, async test =>
324
+ test.deepEqual((await parser('TAG1 CMD ~{4}\r\n', { literals: [Buffer.from('ab\x00d')] })).attributes, [
325
+ {
326
+ type: 'LITERAL',
327
+ value: Buffer.from('ab\x00d')
328
+ }
329
+ ])
330
+ );
331
+
332
+ module.exports['IMAP Parser: Literals: unexpected literal8 prefix'] = test =>
333
+ asyncWrapper(test, async test => {
334
+ try {
335
+ await parser('TAG1 CMD ~\r\n');
336
+ test.ok(false, 'Must throw');
337
+ } catch (err) {
338
+ test.ok(err, 'Error must exist');
339
+ }
340
+ });
341
+
312
342
  module.exports['IMAP Parser: Literals: multiple literals'] = test =>
313
343
  asyncWrapper(test, async test =>
314
344
  test.deepEqual((await parser('TAG1 CMD {4}\r\n {4}\r\n', { literals: [Buffer.from('abcd'), Buffer.from('kere')] })).attributes, [