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
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.2.14](https://github.com/postalsys/imapflow/compare/v1.2.13...v1.2.14) (2026-03-15)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* correct getColorFlags treating red (index 0) as falsy and use nullish coalescing in resolveCharset ([268629c](https://github.com/postalsys/imapflow/commit/268629cf799027bf802c0231675cdc2c94a227d4))
|
|
9
|
+
* use nullish coalescing in getFlagColor, match euc-jp in getDecoder, and handle BigInt(0) in uidValidity checks ([713e100](https://github.com/postalsys/imapflow/commit/713e100093edff21df020942a2c17e7b9939b42b))
|
|
10
|
+
|
|
3
11
|
## [1.2.13](https://github.com/postalsys/imapflow/compare/v1.2.12...v1.2.13) (2026-03-09)
|
|
4
12
|
|
|
5
13
|
|
package/lib/charsets.js
CHANGED
|
@@ -291,8 +291,5 @@ CHARACTER_SETS.forEach(entry => {
|
|
|
291
291
|
// Returns null if the charset is not recognized.
|
|
292
292
|
module.exports.resolveCharset = charset => {
|
|
293
293
|
let key = charset.replace(/[_-\s]/g, '').toLowerCase();
|
|
294
|
-
|
|
295
|
-
return CHARSET_MAP.get(key);
|
|
296
|
-
}
|
|
297
|
-
return null;
|
|
294
|
+
return CHARSET_MAP.get(key) ?? null;
|
|
298
295
|
};
|
package/lib/commands/append.js
CHANGED
|
@@ -111,7 +111,7 @@ module.exports = async (connection, destination, content, flags, idate) => {
|
|
|
111
111
|
if (responseCode.toUpperCase() === 'APPENDUID') {
|
|
112
112
|
let uidValidity = section[1] && typeof section[1].value === 'string' && !isNaN(section[1].value) ? BigInt(section[1].value) : false;
|
|
113
113
|
let uid = section[2] && typeof section[2].value === 'string' && !isNaN(section[2].value) ? Number(section[2].value) : false;
|
|
114
|
-
if (uidValidity) {
|
|
114
|
+
if (uidValidity !== false) {
|
|
115
115
|
map.uidValidity = uidValidity;
|
|
116
116
|
}
|
|
117
117
|
if (uid) {
|
package/lib/commands/copy.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { normalizePath, encodePath,
|
|
3
|
+
const { normalizePath, encodePath, enhanceCommandError } = require('../tools.js');
|
|
4
|
+
const { parseCopyUid } = require('./copyuid-parser.js');
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Copies messages from the current mailbox to another mailbox.
|
|
@@ -35,29 +36,7 @@ module.exports = async (connection, range, destination, options) => {
|
|
|
35
36
|
|
|
36
37
|
// UIDPLUS (RFC 4315): the server may include a COPYUID response code in the
|
|
37
38
|
// tagged OK response, providing a mapping from source UIDs to destination UIDs.
|
|
38
|
-
|
|
39
|
-
let section = response.response.attributes && response.response.attributes[0] && response.response.attributes[0].section;
|
|
40
|
-
let responseCode = section && section.length && section[0] && typeof section[0].value === 'string' ? section[0].value : '';
|
|
41
|
-
switch (responseCode) {
|
|
42
|
-
case 'COPYUID':
|
|
43
|
-
{
|
|
44
|
-
// section[1] = destination mailbox UIDVALIDITY
|
|
45
|
-
let uidValidity = section[1] && typeof section[1].value === 'string' && !isNaN(section[1].value) ? BigInt(section[1].value) : false;
|
|
46
|
-
if (uidValidity) {
|
|
47
|
-
map.uidValidity = uidValidity;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// section[2] = source UID set, section[3] = destination UID set
|
|
51
|
-
// Both can be ranges (e.g., "1:3") which expandRange() converts to arrays
|
|
52
|
-
let sourceUids = section[2] && typeof section[2].value === 'string' ? expandRange(section[2].value) : false;
|
|
53
|
-
let destinationUids = section[3] && typeof section[3].value === 'string' ? expandRange(section[3].value) : false;
|
|
54
|
-
// Build a source->destination UID map for the caller to track where messages went
|
|
55
|
-
if (sourceUids && destinationUids && sourceUids.length === destinationUids.length) {
|
|
56
|
-
map.uidMap = new Map(sourceUids.map((uid, i) => [uid, destinationUids[i]]));
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
break;
|
|
60
|
-
}
|
|
39
|
+
parseCopyUid(response.response, map);
|
|
61
40
|
|
|
62
41
|
return map;
|
|
63
42
|
} catch (err) {
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { expandRange } = require('../tools.js');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Parses COPYUID response code from an IMAP response (RFC 4315).
|
|
7
|
+
* Used by both COPY and MOVE commands to extract the UID mapping
|
|
8
|
+
* from source mailbox to destination mailbox.
|
|
9
|
+
*
|
|
10
|
+
* @param {Object} response - IMAP response object with attributes
|
|
11
|
+
* @param {Object} map - Result map to populate with uidValidity and uidMap
|
|
12
|
+
*/
|
|
13
|
+
function parseCopyUid(response, map) {
|
|
14
|
+
let section = response.attributes && response.attributes[0] && response.attributes[0].section;
|
|
15
|
+
let responseCode = section && section.length && section[0] && typeof section[0].value === 'string' ? section[0].value : '';
|
|
16
|
+
|
|
17
|
+
if (responseCode !== 'COPYUID') {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let uidValidity = section[1] && typeof section[1].value === 'string' && !isNaN(section[1].value) ? BigInt(section[1].value) : false;
|
|
22
|
+
if (uidValidity !== false) {
|
|
23
|
+
map.uidValidity = uidValidity;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let sourceUids = section[2] && typeof section[2].value === 'string' ? expandRange(section[2].value) : false;
|
|
27
|
+
let destinationUids = section[3] && typeof section[3].value === 'string' ? expandRange(section[3].value) : false;
|
|
28
|
+
if (sourceUids && destinationUids && sourceUids.length === destinationUids.length) {
|
|
29
|
+
map.uidMap = new Map(sourceUids.map((uid, i) => [uid, destinationUids[i]]));
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
module.exports = { parseCopyUid };
|
package/lib/commands/idle.js
CHANGED
|
@@ -90,9 +90,7 @@ async function runIdle(connection) {
|
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
},
|
|
93
|
-
onSend: () => {
|
|
94
|
-
//idleSent = true;
|
|
95
|
-
}
|
|
93
|
+
onSend: () => {}
|
|
96
94
|
});
|
|
97
95
|
|
|
98
96
|
// Clean up: unset preCheck and resolve any remaining waiters before processing the response.
|
|
@@ -206,10 +204,10 @@ module.exports = async (connection, maxIdleTime) => {
|
|
|
206
204
|
|
|
207
205
|
case 'STATUS':
|
|
208
206
|
{
|
|
209
|
-
let statusArgs = [
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
207
|
+
let statusArgs = [
|
|
208
|
+
selectCommand.arguments[0],
|
|
209
|
+
['MESSAGES', 'UIDNEXT', 'UIDVALIDITY', 'UNSEEN'].map(key => ({ type: 'ATOM', value: key }))
|
|
210
|
+
];
|
|
213
211
|
connection.log.debug({ src: 'c', msg: `Running STATUS to detect changes in folder` });
|
|
214
212
|
response = await connection.exec('STATUS', statusArgs);
|
|
215
213
|
}
|
package/lib/commands/list.js
CHANGED
|
@@ -43,7 +43,7 @@ module.exports = async (connection, reference, mailbox, options) => {
|
|
|
43
43
|
|
|
44
44
|
// Build the list of STATUS data items to request (MESSAGES, UIDNEXT, etc.)
|
|
45
45
|
if (options.statusQuery) {
|
|
46
|
-
Object.keys(options.statusQuery
|
|
46
|
+
Object.keys(options.statusQuery).forEach(key => {
|
|
47
47
|
if (!options.statusQuery[key]) {
|
|
48
48
|
return;
|
|
49
49
|
}
|
package/lib/commands/move.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { normalizePath, encodePath,
|
|
3
|
+
const { normalizePath, encodePath, enhanceCommandError } = require('../tools.js');
|
|
4
|
+
const { parseCopyUid } = require('./copyuid-parser.js');
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Moves messages from the current mailbox to another mailbox.
|
|
@@ -36,30 +37,6 @@ module.exports = async (connection, range, destination, options) => {
|
|
|
36
37
|
return result;
|
|
37
38
|
}
|
|
38
39
|
|
|
39
|
-
// Extract COPYUID response code (UIDPLUS, RFC 4315) from either an untagged
|
|
40
|
-
// OK response or the final tagged OK. MOVE uses the same COPYUID format as COPY
|
|
41
|
-
// to report the source-to-destination UID mapping.
|
|
42
|
-
let checkMoveInfo = response => {
|
|
43
|
-
let section = response.attributes && response.attributes[0] && response.attributes[0].section;
|
|
44
|
-
let responseCode = section && section.length && section[0] && typeof section[0].value === 'string' ? section[0].value : '';
|
|
45
|
-
switch (responseCode) {
|
|
46
|
-
case 'COPYUID':
|
|
47
|
-
{
|
|
48
|
-
let uidValidity = section[1] && typeof section[1].value === 'string' && !isNaN(section[1].value) ? BigInt(section[1].value) : false;
|
|
49
|
-
if (uidValidity) {
|
|
50
|
-
map.uidValidity = uidValidity;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
let sourceUids = section[2] && typeof section[2].value === 'string' ? expandRange(section[2].value) : false;
|
|
54
|
-
let destinationUids = section[3] && typeof section[3].value === 'string' ? expandRange(section[3].value) : false;
|
|
55
|
-
if (sourceUids && destinationUids && sourceUids.length === destinationUids.length) {
|
|
56
|
-
map.uidMap = new Map(sourceUids.map((uid, i) => [uid, destinationUids[i]]));
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
break;
|
|
60
|
-
}
|
|
61
|
-
};
|
|
62
|
-
|
|
63
40
|
let response;
|
|
64
41
|
try {
|
|
65
42
|
// Some servers send COPYUID in an untagged OK before the tagged response,
|
|
@@ -67,13 +44,13 @@ module.exports = async (connection, range, destination, options) => {
|
|
|
67
44
|
response = await connection.exec(options.uid ? 'UID MOVE' : 'MOVE', attributes, {
|
|
68
45
|
untagged: {
|
|
69
46
|
OK: async untagged => {
|
|
70
|
-
|
|
47
|
+
parseCopyUid(untagged, map);
|
|
71
48
|
}
|
|
72
49
|
}
|
|
73
50
|
});
|
|
74
51
|
response.next();
|
|
75
52
|
|
|
76
|
-
|
|
53
|
+
parseCopyUid(response.response, map);
|
|
77
54
|
return map;
|
|
78
55
|
} catch (err) {
|
|
79
56
|
await enhanceCommandError(err);
|
package/lib/commands/store.js
CHANGED
|
@@ -28,9 +28,7 @@ module.exports = async (connection, range, flags, options) => {
|
|
|
28
28
|
// [+|-]FLAGS[.SILENT] or [+|-]X-GM-LABELS
|
|
29
29
|
// Where: no prefix = replace all, + = add, - = remove
|
|
30
30
|
// .SILENT suppresses the server from sending back updated flags (saves bandwidth).
|
|
31
|
-
let operation;
|
|
32
|
-
|
|
33
|
-
operation = 'FLAGS';
|
|
31
|
+
let operation = 'FLAGS';
|
|
34
32
|
|
|
35
33
|
if (options.useLabels) {
|
|
36
34
|
// Gmail labels (X-GM-EXT-1 extension): operates on labels instead of IMAP flags
|
|
@@ -71,9 +71,7 @@ module.exports = async (response, options) => {
|
|
|
71
71
|
// - This is the first token (resp is empty)
|
|
72
72
|
// - This is a sub-array element in a consecutive-list context (no space between adjacent lists)
|
|
73
73
|
if (lastType === 'LITERAL' || (!['(', '<', '['].includes(lastRespByte) && resp.length)) {
|
|
74
|
-
if (options.subArray) {
|
|
75
|
-
// ignore separator between consecutive sub-arrays in a list
|
|
76
|
-
} else {
|
|
74
|
+
if (!options.subArray) {
|
|
77
75
|
resp.push(formatRespEntry(' '));
|
|
78
76
|
}
|
|
79
77
|
}
|
|
@@ -39,13 +39,7 @@ function expandRange(start, end) {
|
|
|
39
39
|
* @returns {string} The source string with excluded characters removed.
|
|
40
40
|
*/
|
|
41
41
|
function excludeChars(source, exclude) {
|
|
42
|
-
|
|
43
|
-
for (let i = sourceArr.length - 1; i >= 0; i--) {
|
|
44
|
-
if (exclude.indexOf(sourceArr[i]) >= 0) {
|
|
45
|
-
sourceArr.splice(i, 1);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
return sourceArr.join('');
|
|
42
|
+
return Array.prototype.filter.call(source, ch => exclude.indexOf(ch) < 0).join('');
|
|
49
43
|
}
|
|
50
44
|
|
|
51
45
|
module.exports = {
|
|
@@ -61,7 +61,7 @@ module.exports = async (command, options) => {
|
|
|
61
61
|
// "UID MOVE", "UID SEARCH", "UID EXPUNGE", and "AUTHENTICATE PLAIN", etc.
|
|
62
62
|
// For these, the first word is consumed as the command, then we read the
|
|
63
63
|
// subcommand and concatenate them (e.g., "UID" + " " + "FETCH" -> "UID FETCH").
|
|
64
|
-
if (['UID', 'AUTHENTICATE'].
|
|
64
|
+
if (['UID', 'AUTHENTICATE'].includes((response.command || '').toUpperCase())) {
|
|
65
65
|
await parser.getSpace();
|
|
66
66
|
response.command += ' ' + (await parser.getElement(imapFormalSyntax.command()));
|
|
67
67
|
}
|
|
@@ -172,17 +172,13 @@ class ImapStream extends Transform {
|
|
|
172
172
|
this.literals = [];
|
|
173
173
|
|
|
174
174
|
if (payload.length) {
|
|
175
|
-
// remove final line terminator
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
skipBytes++;
|
|
175
|
+
// remove final line terminator (\n or \r\n)
|
|
176
|
+
if (payload[payload.length - 1] === LF) {
|
|
177
|
+
let end = payload.length - 1;
|
|
178
|
+
if (end > 0 && payload[end - 1] === CR) {
|
|
179
|
+
end--;
|
|
181
180
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
if (skipBytes) {
|
|
185
|
-
payload = payload.slice(0, payload.length - skipBytes);
|
|
181
|
+
payload = payload.slice(0, end);
|
|
186
182
|
}
|
|
187
183
|
|
|
188
184
|
if (payload.length) {
|
|
@@ -278,9 +274,7 @@ class ImapStream extends Transform {
|
|
|
278
274
|
// Queue the chunk for async processing. The 'next' callback serves as
|
|
279
275
|
// backpressure: it is called only after this chunk is fully processed,
|
|
280
276
|
// which signals the writable side that more data can be accepted.
|
|
281
|
-
|
|
282
|
-
this.inputQueue.push({ chunk, next });
|
|
283
|
-
}
|
|
277
|
+
this.inputQueue.push({ chunk, next });
|
|
284
278
|
|
|
285
279
|
if (!this.processingInput) {
|
|
286
280
|
this.processingInput = true;
|
|
@@ -114,7 +114,7 @@ class ParserInstance {
|
|
|
114
114
|
async getElement(syntax) {
|
|
115
115
|
let match, element, errPos;
|
|
116
116
|
|
|
117
|
-
if (this.remainder
|
|
117
|
+
if (/^\s/.test(this.remainder)) {
|
|
118
118
|
let error = new Error(`Unexpected whitespace at position ${this.pos} [E1]`);
|
|
119
119
|
error.code = 'ParserError1';
|
|
120
120
|
error.parserContext = { input: this.input, pos: this.pos };
|
|
@@ -209,7 +209,7 @@ class ParserInstance {
|
|
|
209
209
|
throw error;
|
|
210
210
|
}
|
|
211
211
|
|
|
212
|
-
if (this.remainder
|
|
212
|
+
if (/^\s/.test(this.remainder)) {
|
|
213
213
|
let error = new Error(`Unexpected whitespace at position ${this.pos} [E7]`);
|
|
214
214
|
error.code = 'ParserError7';
|
|
215
215
|
error.parserContext = { input: this.input, element: this.remainder, pos: this.pos };
|
|
@@ -272,7 +272,7 @@ class TokenParser {
|
|
|
272
272
|
case '~': {
|
|
273
273
|
let nextChr = this.str.charAt(i + 1);
|
|
274
274
|
if (nextChr !== '{') {
|
|
275
|
-
if (imapFormalSyntax['ATOM-CHAR']().
|
|
275
|
+
if (imapFormalSyntax['ATOM-CHAR']().includes(nextChr)) {
|
|
276
276
|
// '~' not followed by '{' but followed by an ATOM char: treat as ATOM
|
|
277
277
|
this.currentNode = this.createNode(this.currentNode, this.pos + i);
|
|
278
278
|
this.currentNode.type = 'ATOM';
|
|
@@ -369,7 +369,7 @@ class TokenParser {
|
|
|
369
369
|
// Allow \ as the first char for atom to support system flags
|
|
370
370
|
// Allow % to support LIST '' %
|
|
371
371
|
// Allow 8bit characters (presumably unicode)
|
|
372
|
-
if (imapFormalSyntax['ATOM-CHAR']().
|
|
372
|
+
if (!imapFormalSyntax['ATOM-CHAR']().includes(chr) && chr !== '\\' && chr !== '%' && chr.charCodeAt(0) < 0x80) {
|
|
373
373
|
let error = new Error(`Unexpected char at position ${this.pos + i} [E13: ${JSON.stringify(chr)}]`);
|
|
374
374
|
error.code = 'ParserError13';
|
|
375
375
|
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|
|
@@ -422,7 +422,7 @@ class TokenParser {
|
|
|
422
422
|
|
|
423
423
|
// [ starts a section group for this element
|
|
424
424
|
// Allowed only for selected elements, otherwise falls through to regular ATOM processing
|
|
425
|
-
if (chr === '[' && ['BODY', 'BODY.PEEK', 'BINARY', 'BINARY.PEEK'].
|
|
425
|
+
if (chr === '[' && ['BODY', 'BODY.PEEK', 'BINARY', 'BINARY.PEEK'].includes(this.currentNode.value.toUpperCase())) {
|
|
426
426
|
this.currentNode.endPos = this.pos + i;
|
|
427
427
|
this.currentNode = this.createNode(this.currentNode.parentNode, this.pos + i);
|
|
428
428
|
this.currentNode.type = 'SECTION';
|
|
@@ -433,7 +433,7 @@ class TokenParser {
|
|
|
433
433
|
|
|
434
434
|
// if the char is not ATOM compatible, throw. Allow \* as an exception
|
|
435
435
|
if (
|
|
436
|
-
imapFormalSyntax['ATOM-CHAR']().
|
|
436
|
+
!imapFormalSyntax['ATOM-CHAR']().includes(chr) &&
|
|
437
437
|
chr.charCodeAt(0) < 0x80 && // allow 8bit (presumably unicode) bytes
|
|
438
438
|
chr !== ']' &&
|
|
439
439
|
!(chr === '*' && this.currentNode.value === '\\') &&
|
|
@@ -496,21 +496,21 @@ class TokenParser {
|
|
|
496
496
|
break;
|
|
497
497
|
}
|
|
498
498
|
|
|
499
|
-
if (chr === '.' && (
|
|
499
|
+
if (chr === '.' && (this.currentNode.value === '' || this.currentNode.value.includes('.'))) {
|
|
500
500
|
let error = new Error(`Unexpected partial separator . at position ${this.pos + i} [E20]`);
|
|
501
501
|
error.code = 'ParserError20';
|
|
502
502
|
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|
|
503
503
|
throw error;
|
|
504
504
|
}
|
|
505
505
|
|
|
506
|
-
if (imapFormalSyntax.DIGIT().
|
|
506
|
+
if (!imapFormalSyntax.DIGIT().includes(chr) && chr !== '.') {
|
|
507
507
|
let error = new Error(`Unexpected char at position ${this.pos + i} [E21: ${JSON.stringify(chr)}]`);
|
|
508
508
|
error.code = 'ParserError21';
|
|
509
509
|
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|
|
510
510
|
throw error;
|
|
511
511
|
}
|
|
512
512
|
|
|
513
|
-
if (this.currentNode.value.
|
|
513
|
+
if ((this.currentNode.value === '0' || this.currentNode.value.endsWith('.0')) && chr !== '.') {
|
|
514
514
|
let error = new Error(`Invalid partial at position ${this.pos + i} [E22: ${JSON.stringify(chr)}]`);
|
|
515
515
|
error.code = 'ParserError22';
|
|
516
516
|
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|
|
@@ -606,7 +606,7 @@ class TokenParser {
|
|
|
606
606
|
}
|
|
607
607
|
break;
|
|
608
608
|
}
|
|
609
|
-
if (imapFormalSyntax.DIGIT().
|
|
609
|
+
if (!imapFormalSyntax.DIGIT().includes(chr)) {
|
|
610
610
|
let error = new Error(`Unexpected char at position ${this.pos + i} [E25: ${JSON.stringify(chr)}]`);
|
|
611
611
|
error.code = 'ParserError25';
|
|
612
612
|
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|
|
@@ -664,7 +664,7 @@ class TokenParser {
|
|
|
664
664
|
throw error;
|
|
665
665
|
}
|
|
666
666
|
} else if (chr === '*') {
|
|
667
|
-
if ([',', ':'].
|
|
667
|
+
if (![',', ':'].includes(this.currentNode.value.at(-1))) {
|
|
668
668
|
let error = new Error(`Unexpected range wildcard at position ${this.pos + i} [E30]`);
|
|
669
669
|
error.code = 'ParserError30';
|
|
670
670
|
error.parserContext = { input: this.str, pos: this.pos + i, chr };
|