imapflow 1.0.116 → 1.0.118

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 CHANGED
@@ -1,5 +1,13 @@
1
1
  # CHANGELOG
2
2
 
3
+ - 1.0.118 - 2022-12-22
4
+
5
+ - Refactored detecting special use folders
6
+
7
+ - 1.0.117 - 2022-12-05
8
+
9
+ - Updated command compiling. The command compiler returns a Buffer, not a "binary" string.
10
+
3
11
  - 1.0.116 - 2022-11-30
4
12
 
5
13
  - Added `SPECIAL-USE` flag by default when given parameters to `Imap#list`
@@ -7,11 +7,13 @@ const { specialUse } = require('../special-use');
7
7
  module.exports = async (connection, reference, mailbox, options) => {
8
8
  options = options || {};
9
9
 
10
+ const FLAG_SORT_ORDER = ['\\Inbox', '\\Flagged', '\\Sent', '\\Drafts', '\\All', '\\Archive', '\\Junk', '\\Trash'];
11
+ const SOURCE_SORT_ORDER = ['user', 'extension', 'name'];
12
+
10
13
  let listCommand = connection.capabilities.has('XLIST') && !connection.capabilities.has('SPECIAL-USE') ? 'XLIST' : 'LIST';
11
14
 
12
15
  let response;
13
16
  try {
14
- let flagsSeen = new Set();
15
17
  let entries = [];
16
18
 
17
19
  let statusMap = new Map();
@@ -49,6 +51,23 @@ module.exports = async (connection, reference, mailbox, options) => {
49
51
  }
50
52
  }
51
53
 
54
+ let specialUseMatches = {};
55
+ let addSpecialUseMatch = (entry, type, source) => {
56
+ if (!specialUseMatches[type]) {
57
+ specialUseMatches[type] = [];
58
+ }
59
+ specialUseMatches[type].push({ entry, source });
60
+ };
61
+
62
+ let specialUseHints = {};
63
+ if (options.specialUseHints && typeof options.specialUseHints === 'object') {
64
+ for (let type of Object.keys(options.specialUseHints)) {
65
+ if (['sent', 'junk', 'trash', 'drafts'].includes(type)) {
66
+ specialUseHints[normalizePath(connection, options.specialUseHints[type])] = `\\${type.replace(/^./, c => c.toUpperCase())}`;
67
+ }
68
+ }
69
+ }
70
+
52
71
  let runList = async (reference, mailbox) => {
53
72
  const cmdArgs = [encodePath(connection, reference), encodePath(connection, mailbox)];
54
73
 
@@ -71,17 +90,21 @@ module.exports = async (connection, reference, mailbox, options) => {
71
90
  listed: true
72
91
  };
73
92
 
93
+ if (specialUseHints[entry.path]) {
94
+ addSpecialUseMatch(entry, specialUseHints[entry.path], 'user');
95
+ }
96
+
74
97
  if (listCommand === 'XLIST' && entry.flags.has('\\Inbox')) {
75
98
  // XLIST specific flag, ignore
76
99
  entry.flags.delete('\\Inbox');
77
100
  if (entry.path !== 'INBOX') {
78
101
  // XLIST may use localised inbox name
79
- entry.specialUse = '\\Inbox';
102
+ addSpecialUseMatch(entry, '\\Inbox', 'extension');
80
103
  }
81
104
  }
82
105
 
83
106
  if (entry.path.toUpperCase() === 'INBOX') {
84
- entry.specialUse = '\\Inbox';
107
+ addSpecialUseMatch(entry, '\\Inbox', 'name');
85
108
  }
86
109
 
87
110
  if (entry.delimiter && entry.path.charAt(0) === entry.delimiter) {
@@ -92,9 +115,13 @@ module.exports = async (connection, reference, mailbox, options) => {
92
115
  entry.parent = entry.delimiter ? entry.path.split(entry.delimiter) : [entry.path];
93
116
  entry.name = entry.parent.pop();
94
117
 
95
- let specialUseFlag = specialUse(connection.capabilities.has('XLIST') || connection.capabilities.has('SPECIAL-USE'), entry);
96
- if (specialUseFlag && !flagsSeen.has(specialUseFlag)) {
97
- entry.specialUse = specialUseFlag;
118
+ let { flag: specialUseFlag, source: flagSource } = specialUse(
119
+ connection.capabilities.has('XLIST') || connection.capabilities.has('SPECIAL-USE'),
120
+ entry
121
+ );
122
+
123
+ if (specialUseFlag) {
124
+ addSpecialUseMatch(entry, specialUseFlag, flagSource);
98
125
  }
99
126
 
100
127
  entries.push(entry);
@@ -172,7 +199,7 @@ module.exports = async (connection, reference, mailbox, options) => {
172
199
  return entries;
173
200
  }
174
201
 
175
- if (normalizedReference && !entries.find(entry => entry.specialUse === '\\Inbox')) {
202
+ if (normalizedReference && !specialUseMatches['\\Inbox']) {
176
203
  // INBOX was most probably not included in the listing if namespace was used
177
204
  await runList('', 'INBOX');
178
205
  }
@@ -213,7 +240,7 @@ module.exports = async (connection, reference, mailbox, options) => {
213
240
  };
214
241
 
215
242
  if (entry.path.toUpperCase() === 'INBOX') {
216
- entry.specialUse = '\\Inbox';
243
+ addSpecialUseMatch(entry, '\\Inbox', 'name');
217
244
  }
218
245
 
219
246
  if (entry.delimiter && entry.path.charAt(0) === entry.delimiter) {
@@ -244,26 +271,52 @@ module.exports = async (connection, reference, mailbox, options) => {
244
271
  );
245
272
  response.next();
246
273
 
274
+ for (let type of Object.keys(specialUseMatches)) {
275
+ let sortedEntries = specialUseMatches[type].sort((a, b) => {
276
+ let aSource = SOURCE_SORT_ORDER.indexOf(a.source);
277
+ let bSource = SOURCE_SORT_ORDER.indexOf(b.source);
278
+ if (aSource === bSource) {
279
+ return a.entry.path.localeCompare(b.entry.path);
280
+ }
281
+ return aSource - bSource;
282
+ });
283
+
284
+ if (!sortedEntries[0].entry.specialUse) {
285
+ sortedEntries[0].entry.specialUse = type;
286
+ sortedEntries[0].entry.specialUseSource = sortedEntries[0].source;
287
+ }
288
+ }
289
+
247
290
  let inboxEntry = entries.find(entry => entry.specialUse === '\\Inbox');
248
291
  if (inboxEntry && !inboxEntry.subscribed) {
249
292
  // override server settings and make INBOX always as subscribed
250
293
  inboxEntry.subscribed = true;
251
294
  }
252
295
 
253
- ['\\Drafts', '\\Junk', '\\Sent', '\\Trash'].forEach(specialUseFlag => {
254
- // try to ensure that we have most needed special use mailboxes detected
255
- if (entries.find(entry => entry.specialUse === specialUseFlag)) {
256
- return;
296
+ return entries.sort((a, b) => {
297
+ if (a.specialUse && !b.specialUse) {
298
+ return -1;
299
+ }
300
+ if (!a.specialUse && b.specialUse) {
301
+ return 1;
257
302
  }
303
+ if (a.specialUse && b.specialUse) {
304
+ return FLAG_SORT_ORDER.indexOf(a.specialUse) - FLAG_SORT_ORDER.indexOf(b.specialUse);
305
+ }
306
+
307
+ let aList = [].concat(a.parent).concat(a.name);
308
+ let bList = [].concat(b.parent).concat(b.name);
258
309
 
259
- // special use mailbox not found? try again
260
- let match = entries.find(entry => !entry.specialUse && specialUse(false, entry) === specialUseFlag);
261
- if (match) {
262
- match.specialUse = specialUseFlag;
310
+ for (let i = 0; i < aList.length; i++) {
311
+ let aPart = aList[i];
312
+ let bPart = bList[i];
313
+ if (aPart !== bPart) {
314
+ return aPart.localeCompare(bPart || '');
315
+ }
263
316
  }
264
- });
265
317
 
266
- return entries;
318
+ return a.path.localeCompare(b.path);
319
+ });
267
320
  } catch (err) {
268
321
  connection.log.warn({ msg: 'Failed to list folders', err, cid: connection.id });
269
322
  throw err;
@@ -4,6 +4,26 @@
4
4
 
5
5
  const imapFormalSyntax = require('./imap-formal-syntax');
6
6
 
7
+ const formatRespEntry = (entry, returnEmpty) => {
8
+ if (typeof entry === 'string') {
9
+ return Buffer.from(entry);
10
+ }
11
+
12
+ if (typeof entry === 'number') {
13
+ return Buffer.from(entry.toString());
14
+ }
15
+
16
+ if (Buffer.isBuffer(entry)) {
17
+ return entry;
18
+ }
19
+
20
+ if (returnEmpty) {
21
+ return null;
22
+ }
23
+
24
+ return Buffer.alloc(0);
25
+ };
26
+
7
27
  /**
8
28
  * Compiles an input object into
9
29
  */
@@ -11,18 +31,24 @@ module.exports = async (response, options) => {
11
31
  let { asArray, isLogging, literalPlus, literalMinus } = options || {};
12
32
  const respParts = [];
13
33
 
14
- let resp = (response.tag || '') + (response.command ? ' ' + response.command : '');
34
+ let resp = [].concat(formatRespEntry(response.tag, true) || []).concat(response.command ? formatRespEntry(' ' + response.command) : []);
15
35
  let val;
16
36
  let lastType;
17
37
 
18
38
  let walk = async (node, options) => {
19
39
  options = options || {};
20
40
 
21
- if (lastType === 'LITERAL' || (!['(', '<', '['].includes(resp.substr(-1)) && resp.length)) {
41
+ let lastRespEntry = resp.length && resp[resp.length - 1];
42
+ let lastRespByte = (lastRespEntry && lastRespEntry.length && lastRespEntry[lastRespEntry.length - 1]) || '';
43
+ if (typeof lastRespByte === 'number') {
44
+ lastRespByte = String.fromCharCode(lastRespByte);
45
+ }
46
+
47
+ if (lastType === 'LITERAL' || (!['(', '<', '['].includes(lastRespByte) && resp.length)) {
22
48
  if (options.subArray) {
23
49
  // ignore separator
24
50
  } else {
25
- resp += ' ';
51
+ resp.push(formatRespEntry(' '));
26
52
  }
27
53
  }
28
54
 
@@ -33,7 +59,7 @@ module.exports = async (response, options) => {
33
59
 
34
60
  if (Array.isArray(node)) {
35
61
  lastType = 'LIST';
36
- resp += '(';
62
+ resp.push(formatRespEntry('('));
37
63
 
38
64
  // check if we need to skip separtor WS between two arrays
39
65
  let subArray = node.length > 1 && Array.isArray(node[0]);
@@ -45,97 +71,101 @@ module.exports = async (response, options) => {
45
71
  await walk(child, { subArray });
46
72
  }
47
73
 
48
- resp += ')';
74
+ resp.push(formatRespEntry(')'));
49
75
  return;
50
76
  }
51
77
 
52
78
  if (!node && typeof node !== 'string' && typeof node !== 'number' && !Buffer.isBuffer(node)) {
53
- resp += 'NIL';
79
+ resp.push(formatRespEntry('NIL'));
54
80
  return;
55
81
  }
56
82
 
57
83
  if (typeof node === 'string' || Buffer.isBuffer(node)) {
58
84
  if (isLogging && node.length > 100) {
59
- resp += '"(* ' + node.length + 'B string *)"';
85
+ resp.push(formatRespEntry('"(* ' + node.length + 'B string *)"'));
60
86
  } else {
61
- resp += JSON.stringify(node.toString('binary'));
87
+ resp.push(formatRespEntry(JSON.stringify(node.toString())));
62
88
  }
63
89
  return;
64
90
  }
65
91
 
66
92
  if (typeof node === 'number') {
67
- resp += Math.round(node) || 0; // Only integers allowed
93
+ resp.push(formatRespEntry(Math.round(node) || 0)); // Only integers allowed
68
94
  return;
69
95
  }
70
96
 
71
97
  lastType = node.type;
72
98
 
73
99
  if (isLogging && node.sensitive) {
74
- resp += '"(* value hidden *)"';
100
+ resp.push(formatRespEntry('"(* value hidden *)"'));
75
101
  return;
76
102
  }
77
103
 
78
104
  switch (node.type.toUpperCase()) {
79
105
  case 'LITERAL':
80
106
  if (isLogging) {
81
- resp += '"(* ' + node.value.length + 'B literal *)"';
107
+ resp.push(formatRespEntry('"(* ' + node.value.length + 'B literal *)"'));
82
108
  } else {
83
109
  let literalLength = !node.value ? 0 : Math.max(node.value.length, 0);
84
110
 
85
111
  let canAppend = !asArray || literalPlus || (literalMinus && literalLength <= 4096);
86
112
  let usePlus = canAppend && (literalMinus || literalPlus);
87
113
 
88
- resp += `${node.isLiteral8 ? '~' : ''}{${literalLength}${usePlus ? '+' : ''}}\r\n`;
114
+ resp.push(formatRespEntry(`${node.isLiteral8 ? '~' : ''}{${literalLength}${usePlus ? '+' : ''}}\r\n`));
89
115
 
90
116
  if (canAppend) {
91
- resp += (node.value || '').toString('binary');
117
+ if (node.value && node.value.length) {
118
+ resp.push(formatRespEntry(node.value));
119
+ }
92
120
  } else {
93
121
  respParts.push(resp);
94
- resp = (node.value || '').toString('binary');
122
+ resp = [].concat(formatRespEntry(node.value, true) || []);
95
123
  }
96
124
  }
97
125
  break;
98
126
 
99
127
  case 'STRING':
100
128
  if (isLogging && node.value.length > 100) {
101
- resp += '"(* ' + node.value.length + 'B string *)"';
129
+ resp.push(formatRespEntry('"(* ' + node.value.length + 'B string *)"'));
102
130
  } else {
103
- resp += JSON.stringify(node.value || '');
131
+ resp.push(formatRespEntry(JSON.stringify((node.value || '').toString())));
104
132
  }
105
133
  break;
106
134
 
107
135
  case 'TEXT':
108
136
  case 'SEQUENCE':
109
- resp += (node.value || '').toString('binary');
137
+ if (node.value) {
138
+ resp.push(formatRespEntry(node.value));
139
+ }
110
140
  break;
111
141
 
112
142
  case 'NUMBER':
113
- resp += node.value || 0;
143
+ resp.push(formatRespEntry(node.value || 0));
114
144
  break;
115
145
 
116
146
  case 'ATOM':
117
147
  case 'SECTION':
118
- val = (node.value || '').toString('binary');
148
+ val = (node.value || '').toString();
119
149
 
120
150
  if (!node.section || val) {
121
151
  if (node.value === '' || imapFormalSyntax.verify(val.charAt(0) === '\\' ? val.substr(1) : val, imapFormalSyntax['ATOM-CHAR']()) >= 0) {
122
152
  val = JSON.stringify(val);
123
153
  }
124
154
 
125
- resp += val;
155
+ resp.push(formatRespEntry(val));
126
156
  }
127
157
 
128
158
  if (node.section) {
129
- resp += '[';
159
+ resp.push(formatRespEntry('['));
130
160
 
131
161
  for (let child of node.section) {
132
162
  await walk(child);
133
163
  }
134
164
 
135
- resp += ']';
165
+ resp.push(formatRespEntry(']'));
136
166
  }
137
167
  if (node.partial) {
138
- resp += '<' + node.partial.join('.') + '>';
168
+ resp.push(formatRespEntry(`<${node.partial.join('.')}>`));
139
169
  }
140
170
  break;
141
171
  }
@@ -152,5 +182,9 @@ module.exports = async (response, options) => {
152
182
  respParts.push(resp);
153
183
  }
154
184
 
155
- return asArray ? respParts : respParts.join('');
185
+ for (let i = 0; i < respParts.length; i++) {
186
+ respParts[i] = Buffer.concat(respParts[i]);
187
+ }
188
+
189
+ return asArray ? respParts : respParts.flatMap(entry => entry);
156
190
  };
package/lib/imap-flow.js CHANGED
@@ -380,7 +380,7 @@ class ImapFlow extends EventEmitter {
380
380
  let options = data.options || {};
381
381
 
382
382
  this.log.debug({ src: 's', msg: logCompiled.toString(), cid: this.id, comment: options.comment });
383
- this.write(Buffer.from(this.commandParts.shift()));
383
+ this.write(this.commandParts.shift());
384
384
 
385
385
  if (typeof options.onSend === 'function') {
386
386
  options.onSend();
@@ -491,7 +491,7 @@ class ImapFlow extends EventEmitter {
491
491
  }
492
492
 
493
493
  if (parsed.tag === '+' && this.commandParts.length) {
494
- let content = Buffer.from(this.commandParts.shift(), 'binary');
494
+ let content = this.commandParts.shift();
495
495
  this.write(content);
496
496
  this.log.debug({ src: 'c', msg: `(* ${content.length}B continuation *)`, cid: this.id });
497
497
  data.next();
@@ -1452,6 +1452,11 @@ class ImapFlow extends EventEmitter {
1452
1452
  * @param {Boolean} [options.statusQuery.uidValidity] if `true` request mailbox `UIDVALIDITY` value
1453
1453
  * @param {Boolean} [options.statusQuery.unseen] if `true` request count of unseen messages
1454
1454
  * @param {Boolean} [options.statusQuery.highestModseq] if `true` request last known modseq value
1455
+ * @param {Object} [options.specialUseHints] set specific paths as special use folders, this would override special use flags provided from the server
1456
+ * @param {String} [options.specialUseHints.sent] Path to "Sent Mail" folder
1457
+ * @param {String} [options.specialUseHints.trash] Path to "Trash" folder
1458
+ * @param {String} [options.specialUseHints.junk] Path to "Junk Mail" folder
1459
+ * @param {String} [options.specialUseHints.drafts] Path to "Drafts" folder
1455
1460
  * @returns {Promise<ListResponse[]>} An array of ListResponse objects
1456
1461
  *
1457
1462
  * @example
@@ -269,11 +269,23 @@ module.exports = {
269
269
  },
270
270
 
271
271
  specialUse(hasSpecialUseExtension, folder) {
272
+ let result;
273
+
272
274
  if (hasSpecialUseExtension) {
273
- return module.exports.flags.find(flag => folder.flags.has(flag));
275
+ result = {
276
+ flag: module.exports.flags.find(flag => folder.flags.has(flag)),
277
+ source: 'extension'
278
+ };
279
+ }
280
+
281
+ if (!result || !result.flag) {
282
+ let name = folder.name.toLowerCase().trim();
283
+ result = {
284
+ flag: Object.keys(module.exports.names).find(flag => module.exports.names[flag].includes(name)),
285
+ source: 'name'
286
+ };
274
287
  }
275
288
 
276
- let name = folder.name.toLowerCase().trim();
277
- return Object.keys(module.exports.names).find(flag => module.exports.names[flag].includes(name));
289
+ return result && result.flag ? result : { flag: null };
278
290
  }
279
291
  };
package/lib/tools.js CHANGED
@@ -103,7 +103,7 @@ module.exports = {
103
103
  return false;
104
104
  }
105
105
 
106
- return await compiler(response);
106
+ return (await compiler(response)).toString();
107
107
  },
108
108
 
109
109
  getFolderTree(folders) {
@@ -202,6 +202,7 @@ module.exports = {
202
202
  attributes: [attribute]
203
203
  })
204
204
  )
205
+ .toString()
205
206
  .toLowerCase()
206
207
  .replace(/<\d+>$/, '');
207
208
  continue;
package/lib/types.d.ts CHANGED
@@ -167,6 +167,11 @@ declare module "imapflow" {
167
167
  * @param [options.statusQuery.uidValidity] - if `true` request mailbox `UIDVALIDITY` value
168
168
  * @param [options.statusQuery.unseen] - if `true` request count of unseen messages
169
169
  * @param [options.statusQuery.highestModseq] - if `true` request last known modseq value
170
+ * @param [options.specialUseHints] - set specific paths as special use folders, this would override special use flags provided from the server
171
+ * @param [options.specialUseHints.sent] - Path to "Sent Mail" folder
172
+ * @param [options.specialUseHints.trash] - Path to "Trash" folder
173
+ * @param [options.specialUseHints.junk] - Path to "Junk Mail" folder
174
+ * @param [options.specialUseHints.drafts] - Path to "Drafts" folder
170
175
  * @returns An array of ListResponse objects
171
176
  */
172
177
  list(options?: {
@@ -178,6 +183,12 @@ declare module "imapflow" {
178
183
  unseen?: boolean;
179
184
  highestModseq?: boolean;
180
185
  };
186
+ specialUseHints?: {
187
+ sent?: string;
188
+ trash?: string;
189
+ junk?: string;
190
+ drafts?: string;
191
+ };
181
192
  }): Promise<ListResponse[]>;
182
193
  /**
183
194
  * Lists available mailboxes as a tree structured object
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "imapflow",
3
- "version": "1.0.116",
3
+ "version": "1.0.118",
4
4
  "description": "IMAP Client for Node",
5
5
  "main": "./lib/imap-flow.js",
6
6
  "scripts": {
@@ -31,15 +31,15 @@
31
31
  "@babel/eslint-plugin": "7.19.1",
32
32
  "@babel/plugin-syntax-class-properties": "7.12.13",
33
33
  "@babel/preset-env": "7.20.2",
34
- "@types/node": "18.11.10",
34
+ "@types/node": "18.11.17",
35
35
  "braintree-jsdoc-template": "3.3.0",
36
- "eslint": "8.28.0",
36
+ "eslint": "8.30.0",
37
37
  "eslint-config-nodemailer": "1.2.0",
38
38
  "eslint-config-prettier": "8.5.0",
39
39
  "grunt": "1.5.3",
40
40
  "grunt-cli": "1.4.3",
41
41
  "grunt-contrib-nodeunit": "4.0.0",
42
- "grunt-eslint": "24.0.0",
42
+ "grunt-eslint": "24.0.1",
43
43
  "jsdoc": "3.6.11",
44
44
  "st": "3.0.0",
45
45
  "tsd-jsdoc": "2.5.0"
@@ -48,11 +48,11 @@
48
48
  "encoding-japanese": "2.0.0",
49
49
  "iconv-lite": "0.6.3",
50
50
  "libbase64": "1.2.1",
51
- "libmime": "5.1.0",
51
+ "libmime": "5.2.0",
52
52
  "libqp": "2.0.1",
53
- "mailsplit": "5.3.2",
53
+ "mailsplit": "5.4.0",
54
54
  "nodemailer": "6.8.0",
55
- "pino": "8.7.0",
55
+ "pino": "8.8.0",
56
56
  "socks": "2.7.1"
57
57
  }
58
58
  }
@@ -20,17 +20,19 @@ module.exports['IMAP Compiler: mixed'] = test =>
20
20
  const parsed = await parser(command, {
21
21
  allowUntagged: true
22
22
  });
23
- const compiled = await compiler(parsed);
23
+ const compiled = (await compiler(parsed)).toString();
24
24
  test.equal(compiled, command);
25
25
  });
26
26
 
27
27
  module.exports['IMAP Compiler: no attributes'] = test =>
28
28
  asyncWrapper(test, async test =>
29
29
  test.equal(
30
- await compiler({
31
- tag: '*',
32
- command: 'CMD'
33
- }),
30
+ (
31
+ await compiler({
32
+ tag: '*',
33
+ command: 'CMD'
34
+ })
35
+ ).toString(),
34
36
  '* CMD'
35
37
  )
36
38
  );
@@ -38,16 +40,18 @@ module.exports['IMAP Compiler: no attributes'] = test =>
38
40
  module.exports['IMAP Compiler: TEXT'] = test =>
39
41
  asyncWrapper(test, async test =>
40
42
  test.equal(
41
- await compiler({
42
- tag: '*',
43
- command: 'CMD',
44
- attributes: [
45
- {
46
- type: 'TEXT',
47
- value: 'Tere tere!'
48
- }
49
- ]
50
- }),
43
+ (
44
+ await compiler({
45
+ tag: '*',
46
+ command: 'CMD',
47
+ attributes: [
48
+ {
49
+ type: 'TEXT',
50
+ value: 'Tere tere!'
51
+ }
52
+ ]
53
+ })
54
+ ).toString(),
51
55
  '* CMD Tere tere!'
52
56
  )
53
57
  );
@@ -55,21 +59,23 @@ module.exports['IMAP Compiler: TEXT'] = test =>
55
59
  module.exports['IMAP Compiler: SECTION'] = test =>
56
60
  asyncWrapper(test, async test =>
57
61
  test.equal(
58
- await compiler({
59
- tag: '*',
60
- command: 'CMD',
61
- attributes: [
62
- {
63
- type: 'SECTION',
64
- section: [
65
- {
66
- type: 'ATOM',
67
- value: 'ALERT'
68
- }
69
- ]
70
- }
71
- ]
72
- }),
62
+ (
63
+ await compiler({
64
+ tag: '*',
65
+ command: 'CMD',
66
+ attributes: [
67
+ {
68
+ type: 'SECTION',
69
+ section: [
70
+ {
71
+ type: 'ATOM',
72
+ value: 'ALERT'
73
+ }
74
+ ]
75
+ }
76
+ ]
77
+ })
78
+ ).toString(),
73
79
  '* CMD [ALERT]'
74
80
  )
75
81
  );
@@ -77,24 +83,26 @@ module.exports['IMAP Compiler: SECTION'] = test =>
77
83
  module.exports['IMAP Compiler: escaped ATOM'] = test =>
78
84
  asyncWrapper(test, async test =>
79
85
  test.equal(
80
- await compiler({
81
- tag: '*',
82
- command: 'CMD',
83
- attributes: [
84
- {
85
- type: 'ATOM',
86
- value: 'ALERT'
87
- },
88
- {
89
- type: 'ATOM',
90
- value: '\\ALERT'
91
- },
92
- {
93
- type: 'ATOM',
94
- value: 'NO ALERT'
95
- }
96
- ]
97
- }),
86
+ (
87
+ await compiler({
88
+ tag: '*',
89
+ command: 'CMD',
90
+ attributes: [
91
+ {
92
+ type: 'ATOM',
93
+ value: 'ALERT'
94
+ },
95
+ {
96
+ type: 'ATOM',
97
+ value: '\\ALERT'
98
+ },
99
+ {
100
+ type: 'ATOM',
101
+ value: 'NO ALERT'
102
+ }
103
+ ]
104
+ })
105
+ ).toString(),
98
106
  '* CMD ALERT \\ALERT "NO ALERT"'
99
107
  )
100
108
  );
@@ -102,16 +110,18 @@ module.exports['IMAP Compiler: escaped ATOM'] = test =>
102
110
  module.exports['IMAP Compiler: SEQUENCE'] = test =>
103
111
  asyncWrapper(test, async test =>
104
112
  test.equal(
105
- await compiler({
106
- tag: '*',
107
- command: 'CMD',
108
- attributes: [
109
- {
110
- type: 'SEQUENCE',
111
- value: '*:4,5,6'
112
- }
113
- ]
114
- }),
113
+ (
114
+ await compiler({
115
+ tag: '*',
116
+ command: 'CMD',
117
+ attributes: [
118
+ {
119
+ type: 'SEQUENCE',
120
+ value: '*:4,5,6'
121
+ }
122
+ ]
123
+ })
124
+ ).toString(),
115
125
  '* CMD *:4,5,6'
116
126
  )
117
127
  );
@@ -119,11 +129,13 @@ module.exports['IMAP Compiler: SEQUENCE'] = test =>
119
129
  module.exports['IMAP Compiler: NIL'] = test =>
120
130
  asyncWrapper(test, async test =>
121
131
  test.equal(
122
- await compiler({
123
- tag: '*',
124
- command: 'CMD',
125
- attributes: [null, null]
126
- }),
132
+ (
133
+ await compiler({
134
+ tag: '*',
135
+ command: 'CMD',
136
+ attributes: [null, null]
137
+ })
138
+ ).toString(),
127
139
  '* CMD NIL NIL'
128
140
  )
129
141
  );
@@ -131,39 +143,43 @@ module.exports['IMAP Compiler: NIL'] = test =>
131
143
  module.exports['IMAP Compiler: quoted TEXT'] = test =>
132
144
  asyncWrapper(test, async test =>
133
145
  test.equal(
134
- await compiler({
135
- tag: '*',
136
- command: 'CMD',
137
- attributes: [
138
- {
139
- type: 'String',
140
- value: 'Tere tere!',
141
- sensitive: true
142
- },
143
- 'Vana kere'
144
- ]
145
- }),
146
- '* CMD "Tere tere!" "Vana kere"'
147
- )
148
- );
149
-
150
- module.exports['IMAP Compiler: keep short strings'] = test =>
151
- asyncWrapper(test, async test =>
152
- test.equal(
153
- await compiler(
154
- {
146
+ (
147
+ await compiler({
155
148
  tag: '*',
156
149
  command: 'CMD',
157
150
  attributes: [
158
151
  {
159
152
  type: 'String',
160
- value: 'Tere tere!'
153
+ value: 'Tere tere!',
154
+ sensitive: true
161
155
  },
162
156
  'Vana kere'
163
157
  ]
164
- },
165
- { asArray: false, isLogging: true }
166
- ),
158
+ })
159
+ ).toString(),
160
+ '* CMD "Tere tere!" "Vana kere"'
161
+ )
162
+ );
163
+
164
+ module.exports['IMAP Compiler: keep short strings'] = test =>
165
+ asyncWrapper(test, async test =>
166
+ test.equal(
167
+ (
168
+ await compiler(
169
+ {
170
+ tag: '*',
171
+ command: 'CMD',
172
+ attributes: [
173
+ {
174
+ type: 'String',
175
+ value: 'Tere tere!'
176
+ },
177
+ 'Vana kere'
178
+ ]
179
+ },
180
+ { asArray: false, isLogging: true }
181
+ )
182
+ ).toString(),
167
183
  '* CMD "Tere tere!" "Vana kere"'
168
184
  )
169
185
  );
@@ -171,21 +187,23 @@ module.exports['IMAP Compiler: keep short strings'] = test =>
171
187
  module.exports['IMAP Compiler: hide sensitive strings'] = test =>
172
188
  asyncWrapper(test, async test =>
173
189
  test.equal(
174
- await compiler(
175
- {
176
- tag: '*',
177
- command: 'CMD',
178
- attributes: [
179
- {
180
- type: 'String',
181
- value: 'Tere tere!',
182
- sensitive: true
183
- },
184
- 'Vana kere'
185
- ]
186
- },
187
- { asArray: false, isLogging: true }
188
- ),
190
+ (
191
+ await compiler(
192
+ {
193
+ tag: '*',
194
+ command: 'CMD',
195
+ attributes: [
196
+ {
197
+ type: 'String',
198
+ value: 'Tere tere!',
199
+ sensitive: true
200
+ },
201
+ 'Vana kere'
202
+ ]
203
+ },
204
+ { asArray: false, isLogging: true }
205
+ )
206
+ ).toString(),
189
207
  '* CMD "(* value hidden *)" "Vana kere"'
190
208
  )
191
209
  );
@@ -193,20 +211,22 @@ module.exports['IMAP Compiler: hide sensitive strings'] = test =>
193
211
  module.exports['IMAP Compiler: hide long strings'] = test =>
194
212
  asyncWrapper(test, async test =>
195
213
  test.equal(
196
- await compiler(
197
- {
198
- tag: '*',
199
- command: 'CMD',
200
- attributes: [
201
- {
202
- type: 'String',
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!'
204
- },
205
- 'Vana kere'
206
- ]
207
- },
208
- { asArray: false, isLogging: true }
209
- ),
214
+ (
215
+ await compiler(
216
+ {
217
+ tag: '*',
218
+ command: 'CMD',
219
+ attributes: [
220
+ {
221
+ type: 'String',
222
+ 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!'
223
+ },
224
+ 'Vana kere'
225
+ ]
226
+ },
227
+ { asArray: false, isLogging: true }
228
+ )
229
+ ).toString(),
210
230
  '* CMD "(* 219B string *)" "Vana kere"'
211
231
  )
212
232
  );
@@ -214,16 +234,18 @@ module.exports['IMAP Compiler: hide long strings'] = test =>
214
234
  module.exports['IMAP Compiler: no command'] = test =>
215
235
  asyncWrapper(test, async test =>
216
236
  test.equal(
217
- await compiler({
218
- tag: '*',
219
- attributes: [
220
- 1,
221
- {
222
- type: 'ATOM',
223
- value: 'EXPUNGE'
224
- }
225
- ]
226
- }),
237
+ (
238
+ await compiler({
239
+ tag: '*',
240
+ attributes: [
241
+ 1,
242
+ {
243
+ type: 'ATOM',
244
+ value: 'EXPUNGE'
245
+ }
246
+ ]
247
+ })
248
+ ).toString(),
227
249
  '* 1 EXPUNGE'
228
250
  )
229
251
  );
@@ -231,18 +253,20 @@ module.exports['IMAP Compiler: no command'] = test =>
231
253
  module.exports['IMAP Compiler: LITERAL text'] = test =>
232
254
  asyncWrapper(test, async test =>
233
255
  test.equal(
234
- await compiler({
235
- tag: '*',
236
- command: 'CMD',
237
- attributes: [
238
- // keep indentation
239
- {
240
- type: 'LITERAL',
241
- value: 'Tere tere!'
242
- },
243
- 'Vana kere'
244
- ]
245
- }),
256
+ (
257
+ await compiler({
258
+ tag: '*',
259
+ command: 'CMD',
260
+ attributes: [
261
+ // keep indentation
262
+ {
263
+ type: 'LITERAL',
264
+ value: 'Tere tere!'
265
+ },
266
+ 'Vana kere'
267
+ ]
268
+ })
269
+ ).toString(),
246
270
  '* CMD {10}\r\nTere tere! "Vana kere"'
247
271
  )
248
272
  );
@@ -250,19 +274,21 @@ module.exports['IMAP Compiler: LITERAL text'] = test =>
250
274
  module.exports['IMAP Compiler: LITERAL literal'] = test =>
251
275
  asyncWrapper(test, async test =>
252
276
  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
- }),
277
+ (
278
+ await compiler({
279
+ tag: '*',
280
+ command: 'CMD',
281
+ attributes: [
282
+ // keep indentation
283
+ {
284
+ type: 'LITERAL',
285
+ value: 'Tere\x00 tere!',
286
+ isLiteral8: false
287
+ },
288
+ 'Vana kere'
289
+ ]
290
+ })
291
+ ).toString(),
266
292
  '* CMD {11}\r\nTere\x00 tere! "Vana kere"'
267
293
  )
268
294
  );
@@ -270,43 +296,47 @@ module.exports['IMAP Compiler: LITERAL literal'] = test =>
270
296
  module.exports['IMAP Compiler: LITERAL literal8'] = test =>
271
297
  asyncWrapper(test, async test =>
272
298
  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
-
290
- module.exports['IMAP Compiler: LITERAL array 1'] = test =>
291
- asyncWrapper(test, async test =>
292
- test.deepEqual(
293
- await compiler(
294
- {
299
+ (
300
+ await compiler({
295
301
  tag: '*',
296
302
  command: 'CMD',
297
303
  attributes: [
304
+ // keep indentation
298
305
  {
299
306
  type: 'LITERAL',
300
- value: 'Tere tere!'
307
+ value: 'Tere\x00 tere!',
308
+ isLiteral8: true
301
309
  },
302
- {
303
- type: 'LITERAL',
304
- value: 'Vana kere'
305
- }
310
+ 'Vana kere'
306
311
  ]
307
- },
308
- { asArray: true }
309
- ),
312
+ })
313
+ ).toString(),
314
+ '* CMD ~{11}\r\nTere\x00 tere! "Vana kere"'
315
+ )
316
+ );
317
+
318
+ module.exports['IMAP Compiler: LITERAL array 1'] = test =>
319
+ asyncWrapper(test, async test =>
320
+ test.deepEqual(
321
+ (
322
+ await compiler(
323
+ {
324
+ tag: '*',
325
+ command: 'CMD',
326
+ attributes: [
327
+ {
328
+ type: 'LITERAL',
329
+ value: 'Tere tere!'
330
+ },
331
+ {
332
+ type: 'LITERAL',
333
+ value: 'Vana kere'
334
+ }
335
+ ]
336
+ },
337
+ { asArray: true }
338
+ )
339
+ ).map(entry => entry.toString()),
310
340
  ['* CMD {10}\r\n', 'Tere tere! {9}\r\n', 'Vana kere']
311
341
  )
312
342
  );
@@ -314,24 +344,26 @@ module.exports['IMAP Compiler: LITERAL array 1'] = test =>
314
344
  module.exports['IMAP Compiler: LITERAL array 2'] = test =>
315
345
  asyncWrapper(test, async test =>
316
346
  test.deepEqual(
317
- await compiler(
318
- {
319
- tag: '*',
320
- command: 'CMD',
321
- attributes: [
322
- {
323
- type: 'LITERAL',
324
- value: 'Tere tere!'
325
- },
326
- {
327
- type: 'LITERAL',
328
- value: 'Vana kere'
329
- },
330
- 'zzz'
331
- ]
332
- },
333
- { asArray: true }
334
- ),
347
+ (
348
+ await compiler(
349
+ {
350
+ tag: '*',
351
+ command: 'CMD',
352
+ attributes: [
353
+ {
354
+ type: 'LITERAL',
355
+ value: 'Tere tere!'
356
+ },
357
+ {
358
+ type: 'LITERAL',
359
+ value: 'Vana kere'
360
+ },
361
+ 'zzz'
362
+ ]
363
+ },
364
+ { asArray: true }
365
+ )
366
+ ).map(entry => entry.toString()),
335
367
  ['* CMD {10}\r\n', 'Tere tere! {9}\r\n', 'Vana kere "zzz"']
336
368
  )
337
369
  );
@@ -339,24 +371,26 @@ module.exports['IMAP Compiler: LITERAL array 2'] = test =>
339
371
  module.exports['IMAP Compiler: LITERALPLUS array'] = test =>
340
372
  asyncWrapper(test, async test =>
341
373
  test.deepEqual(
342
- await compiler(
343
- {
344
- tag: '*',
345
- command: 'CMD',
346
- attributes: [
347
- {
348
- type: 'LITERAL',
349
- value: 'Tere tere!'
350
- },
351
- {
352
- type: 'LITERAL',
353
- value: 'Vana kere'
354
- },
355
- 'zzz'
356
- ]
357
- },
358
- { asArray: true, literalPlus: true }
359
- ),
374
+ (
375
+ await compiler(
376
+ {
377
+ tag: '*',
378
+ command: 'CMD',
379
+ attributes: [
380
+ {
381
+ type: 'LITERAL',
382
+ value: 'Tere tere!'
383
+ },
384
+ {
385
+ type: 'LITERAL',
386
+ value: 'Vana kere'
387
+ },
388
+ 'zzz'
389
+ ]
390
+ },
391
+ { asArray: true, literalPlus: true }
392
+ )
393
+ ).map(entry => entry.toString()),
360
394
  ['* CMD {10+}\r\nTere tere! {9+}\r\nVana kere "zzz"']
361
395
  )
362
396
  );
@@ -364,21 +398,23 @@ module.exports['IMAP Compiler: LITERALPLUS array'] = test =>
364
398
  module.exports['IMAP Compiler: LITERAL array without tag/command'] = test =>
365
399
  asyncWrapper(test, async test =>
366
400
  test.deepEqual(
367
- await compiler(
368
- {
369
- attributes: [
370
- {
371
- type: 'LITERAL',
372
- value: 'Tere tere!'
373
- },
374
- {
375
- type: 'LITERAL',
376
- value: 'Vana kere'
377
- }
378
- ]
379
- },
380
- { asArray: true }
381
- ),
401
+ (
402
+ await compiler(
403
+ {
404
+ attributes: [
405
+ {
406
+ type: 'LITERAL',
407
+ value: 'Tere tere!'
408
+ },
409
+ {
410
+ type: 'LITERAL',
411
+ value: 'Vana kere'
412
+ }
413
+ ]
414
+ },
415
+ { asArray: true }
416
+ )
417
+ ).map(entry => entry.toString()),
382
418
  ['{10}\r\n', 'Tere tere! {9}\r\n', 'Vana kere']
383
419
  )
384
420
  );
@@ -386,20 +422,22 @@ module.exports['IMAP Compiler: LITERAL array without tag/command'] = test =>
386
422
  module.exports['IMAP Compiler: LITERAL byte length'] = test =>
387
423
  asyncWrapper(test, async test =>
388
424
  test.deepEqual(
389
- await compiler(
390
- {
391
- tag: '*',
392
- command: 'CMD',
393
- attributes: [
394
- {
395
- type: 'LITERAL',
396
- value: 'Tere tere!'
397
- },
398
- 'Vana kere'
399
- ]
400
- },
401
- { asArray: false, isLogging: true }
402
- ),
425
+ (
426
+ await compiler(
427
+ {
428
+ tag: '*',
429
+ command: 'CMD',
430
+ attributes: [
431
+ {
432
+ type: 'LITERAL',
433
+ value: 'Tere tere!'
434
+ },
435
+ 'Vana kere'
436
+ ]
437
+ },
438
+ { asArray: false, isLogging: true }
439
+ )
440
+ ).toString(),
403
441
  '* CMD "(* 10B literal *)" "Vana kere"'
404
442
  )
405
443
  );