imapflow 1.7.8 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/README.md +8 -2
- package/dist/cjs/charsets.d.ts +1 -0
- package/dist/cjs/charsets.js +294 -0
- package/dist/cjs/commands/append.d.ts +22 -0
- package/dist/cjs/commands/append.js +151 -0
- package/dist/cjs/commands/authenticate.d.ts +24 -0
- package/dist/cjs/commands/authenticate.js +223 -0
- package/dist/cjs/commands/capability.d.ts +8 -0
- package/dist/cjs/commands/capability.js +32 -0
- package/dist/cjs/commands/close.d.ts +8 -0
- package/dist/cjs/commands/close.js +39 -0
- package/dist/cjs/commands/compress.d.ts +8 -0
- package/dist/cjs/commands/compress.js +56 -0
- package/dist/cjs/commands/copy.d.ts +13 -0
- package/dist/cjs/commands/copy.js +44 -0
- package/dist/cjs/commands/copyuid-parser.d.ts +11 -0
- package/dist/cjs/commands/copyuid-parser.js +32 -0
- package/dist/cjs/commands/create.d.ts +11 -0
- package/dist/cjs/commands/create.js +80 -0
- package/dist/cjs/commands/delete.d.ts +11 -0
- package/dist/cjs/commands/delete.js +40 -0
- package/dist/cjs/commands/enable.d.ts +9 -0
- package/dist/cjs/commands/enable.js +61 -0
- package/dist/cjs/commands/esearch-parser.d.ts +17 -0
- package/dist/cjs/commands/esearch-parser.js +91 -0
- package/dist/cjs/commands/expunge.d.ts +12 -0
- package/dist/cjs/commands/expunge.js +60 -0
- package/dist/cjs/commands/fetch.d.ts +30 -0
- package/dist/cjs/commands/fetch.js +241 -0
- package/dist/cjs/commands/id.d.ts +10 -0
- package/dist/cjs/commands/id.js +80 -0
- package/dist/cjs/commands/idle.d.ts +9 -0
- package/dist/cjs/commands/idle.js +347 -0
- package/dist/cjs/commands/list.d.ts +16 -0
- package/dist/cjs/commands/list.js +518 -0
- package/dist/cjs/commands/login.d.ts +11 -0
- package/dist/cjs/commands/login.js +42 -0
- package/dist/cjs/commands/logout.d.ts +8 -0
- package/dist/cjs/commands/logout.js +47 -0
- package/dist/cjs/commands/move.d.ts +13 -0
- package/dist/cjs/commands/move.js +57 -0
- package/dist/cjs/commands/namespace.d.ts +25 -0
- package/dist/cjs/commands/namespace.js +139 -0
- package/dist/cjs/commands/noop.d.ts +8 -0
- package/dist/cjs/commands/noop.js +22 -0
- package/dist/cjs/commands/quota.d.ts +10 -0
- package/dist/cjs/commands/quota.js +119 -0
- package/dist/cjs/commands/rename.d.ts +12 -0
- package/dist/cjs/commands/rename.js +48 -0
- package/dist/cjs/commands/search.d.ts +15 -0
- package/dist/cjs/commands/search.js +228 -0
- package/dist/cjs/commands/select.d.ts +25 -0
- package/dist/cjs/commands/select.js +250 -0
- package/dist/cjs/commands/starttls.d.ts +8 -0
- package/dist/cjs/commands/starttls.js +30 -0
- package/dist/cjs/commands/status-fields.d.ts +14 -0
- package/dist/cjs/commands/status-fields.js +61 -0
- package/dist/cjs/commands/status.d.ts +12 -0
- package/dist/cjs/commands/status.js +108 -0
- package/dist/cjs/commands/store.d.ts +19 -0
- package/dist/cjs/commands/store.js +93 -0
- package/dist/cjs/commands/subscribe.d.ts +9 -0
- package/dist/cjs/commands/subscribe.js +31 -0
- package/dist/cjs/commands/unsubscribe.d.ts +9 -0
- package/dist/cjs/commands/unsubscribe.js +31 -0
- package/dist/cjs/connection-deadline.d.ts +49 -0
- package/dist/cjs/connection-deadline.js +91 -0
- package/dist/cjs/errors.d.ts +83 -0
- package/dist/cjs/errors.js +13 -0
- package/dist/cjs/handler/imap-compiler.d.ts +24 -0
- package/dist/cjs/handler/imap-compiler.js +285 -0
- package/dist/cjs/handler/imap-formal-syntax.d.ts +28 -0
- package/dist/cjs/handler/imap-formal-syntax.js +121 -0
- package/dist/cjs/handler/imap-handler.d.ts +9 -0
- package/dist/cjs/handler/imap-handler.js +10 -0
- package/dist/cjs/handler/imap-parser.d.ts +16 -0
- package/dist/cjs/handler/imap-parser.js +90 -0
- package/dist/cjs/handler/imap-stream.d.ts +181 -0
- package/dist/cjs/handler/imap-stream.js +446 -0
- package/dist/cjs/handler/limits.d.ts +25 -0
- package/dist/cjs/handler/limits.js +51 -0
- package/dist/cjs/handler/parser-instance.d.ts +68 -0
- package/dist/cjs/handler/parser-instance.js +223 -0
- package/dist/cjs/handler/token-parser.d.ts +91 -0
- package/dist/cjs/handler/token-parser.js +673 -0
- package/dist/cjs/handler/types.d.ts +91 -0
- package/dist/cjs/handler/types.js +4 -0
- package/dist/cjs/imap-commands.d.ts +16 -0
- package/dist/cjs/imap-commands.js +74 -0
- package/dist/cjs/imap-flow.d.ts +676 -0
- package/dist/cjs/imap-flow.js +3949 -0
- package/dist/cjs/jp-decoder.d.ts +12 -0
- package/dist/cjs/jp-decoder.js +79 -0
- package/dist/cjs/limited-passthrough.d.ts +25 -0
- package/dist/cjs/limited-passthrough.js +54 -0
- package/dist/cjs/logger.d.ts +3 -0
- package/dist/cjs/logger.js +11 -0
- package/dist/cjs/package-info.d.ts +3 -0
- package/dist/cjs/package-info.js +7 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/proxy-connection.d.ts +33 -0
- package/dist/cjs/proxy-connection.js +392 -0
- package/dist/cjs/search-compiler.d.ts +34 -0
- package/dist/cjs/search-compiler.js +476 -0
- package/dist/cjs/special-use.d.ts +22 -0
- package/dist/cjs/special-use.js +911 -0
- package/dist/cjs/tools.d.ts +427 -0
- package/dist/cjs/tools.js +1496 -0
- package/{lib/imap-flow.d.ts → dist/cjs/types.d.ts} +386 -516
- package/dist/cjs/types.js +5 -0
- package/dist/esm/charsets.d.ts +1 -0
- package/{lib → dist/esm}/charsets.js +1 -6
- package/dist/esm/commands/append.d.ts +22 -0
- package/{lib → dist/esm}/commands/append.js +22 -52
- package/dist/esm/commands/authenticate.d.ts +24 -0
- package/{lib → dist/esm}/commands/authenticate.js +62 -87
- package/dist/esm/commands/capability.d.ts +8 -0
- package/{lib → dist/esm}/commands/capability.js +6 -9
- package/dist/esm/commands/close.d.ts +8 -0
- package/{lib → dist/esm}/commands/close.js +6 -10
- package/dist/esm/commands/compress.d.ts +8 -0
- package/{lib → dist/esm}/commands/compress.js +7 -11
- package/dist/esm/commands/copy.d.ts +13 -0
- package/{lib → dist/esm}/commands/copy.js +12 -20
- package/dist/esm/commands/copyuid-parser.d.ts +11 -0
- package/{lib → dist/esm}/commands/copyuid-parser.js +9 -15
- package/dist/esm/commands/create.d.ts +11 -0
- package/{lib → dist/esm}/commands/create.js +13 -27
- package/dist/esm/commands/delete.d.ts +11 -0
- package/{lib → dist/esm}/commands/delete.js +9 -14
- package/dist/esm/commands/enable.d.ts +9 -0
- package/{lib → dist/esm}/commands/enable.js +23 -30
- package/dist/esm/commands/esearch-parser.d.ts +17 -0
- package/dist/esm/commands/esearch-parser.js +88 -0
- package/dist/esm/commands/expunge.d.ts +12 -0
- package/{lib → dist/esm}/commands/expunge.js +17 -22
- package/dist/esm/commands/fetch.d.ts +30 -0
- package/{lib → dist/esm}/commands/fetch.js +32 -64
- package/dist/esm/commands/id.d.ts +10 -0
- package/{lib → dist/esm}/commands/id.js +17 -23
- package/dist/esm/commands/idle.d.ts +9 -0
- package/{lib → dist/esm}/commands/idle.js +47 -81
- package/dist/esm/commands/list.d.ts +16 -0
- package/{lib → dist/esm}/commands/list.js +56 -121
- package/dist/esm/commands/login.d.ts +11 -0
- package/{lib → dist/esm}/commands/login.js +10 -15
- package/dist/esm/commands/logout.d.ts +8 -0
- package/{lib → dist/esm}/commands/logout.js +9 -11
- package/dist/esm/commands/move.d.ts +13 -0
- package/{lib → dist/esm}/commands/move.js +13 -21
- package/dist/esm/commands/namespace.d.ts +25 -0
- package/{lib → dist/esm}/commands/namespace.js +34 -44
- package/dist/esm/commands/noop.d.ts +8 -0
- package/{lib → dist/esm}/commands/noop.js +6 -7
- package/dist/esm/commands/quota.d.ts +10 -0
- package/{lib → dist/esm}/commands/quota.js +18 -36
- package/dist/esm/commands/rename.d.ts +12 -0
- package/{lib → dist/esm}/commands/rename.js +10 -15
- package/dist/esm/commands/search.d.ts +15 -0
- package/{lib → dist/esm}/commands/search.js +36 -135
- package/dist/esm/commands/select.d.ts +25 -0
- package/{lib → dist/esm}/commands/select.js +33 -64
- package/dist/esm/commands/starttls.d.ts +8 -0
- package/{lib → dist/esm}/commands/starttls.js +6 -8
- package/dist/esm/commands/status-fields.d.ts +14 -0
- package/{lib → dist/esm}/commands/status-fields.js +5 -16
- package/dist/esm/commands/status.d.ts +12 -0
- package/{lib → dist/esm}/commands/status.js +18 -29
- package/dist/esm/commands/store.d.ts +19 -0
- package/{lib → dist/esm}/commands/store.js +24 -37
- package/dist/esm/commands/subscribe.d.ts +9 -0
- package/{lib → dist/esm}/commands/subscribe.js +8 -12
- package/dist/esm/commands/unsubscribe.d.ts +9 -0
- package/{lib → dist/esm}/commands/unsubscribe.js +8 -12
- package/dist/esm/connection-deadline.d.ts +49 -0
- package/{lib → dist/esm}/connection-deadline.js +14 -25
- package/dist/esm/errors.d.ts +83 -0
- package/dist/esm/errors.js +9 -0
- package/dist/esm/handler/imap-compiler.d.ts +24 -0
- package/{lib → dist/esm}/handler/imap-compiler.js +22 -80
- package/dist/esm/handler/imap-formal-syntax.d.ts +28 -0
- package/dist/esm/handler/imap-formal-syntax.js +117 -0
- package/dist/esm/handler/imap-handler.d.ts +9 -0
- package/dist/esm/handler/imap-handler.js +9 -0
- package/dist/esm/handler/imap-parser.d.ts +16 -0
- package/{lib → dist/esm}/handler/imap-parser.js +31 -44
- package/dist/esm/handler/imap-stream.d.ts +181 -0
- package/{lib → dist/esm}/handler/imap-stream.js +29 -121
- package/dist/esm/handler/limits.d.ts +25 -0
- package/{lib → dist/esm}/handler/limits.js +13 -22
- package/dist/esm/handler/parser-instance.d.ts +68 -0
- package/{lib → dist/esm}/handler/parser-instance.js +19 -47
- package/dist/esm/handler/token-parser.d.ts +91 -0
- package/{lib → dist/esm}/handler/token-parser.js +71 -155
- package/dist/esm/handler/types.d.ts +91 -0
- package/dist/esm/handler/types.js +3 -0
- package/dist/esm/imap-commands.d.ts +16 -0
- package/dist/esm/imap-commands.js +67 -0
- package/dist/esm/imap-flow.d.ts +676 -0
- package/{lib → dist/esm}/imap-flow.js +761 -1789
- package/dist/esm/jp-decoder.d.ts +12 -0
- package/{lib → dist/esm}/jp-decoder.js +6 -21
- package/dist/esm/limited-passthrough.d.ts +25 -0
- package/{lib → dist/esm}/limited-passthrough.js +7 -20
- package/dist/esm/logger.d.ts +3 -0
- package/dist/esm/logger.js +4 -0
- package/dist/esm/package-info.d.ts +3 -0
- package/dist/esm/package-info.js +4 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/proxy-connection.d.ts +33 -0
- package/{lib → dist/esm}/proxy-connection.js +56 -127
- package/dist/esm/search-compiler.d.ts +34 -0
- package/{lib → dist/esm}/search-compiler.js +54 -110
- package/dist/esm/special-use.d.ts +22 -0
- package/dist/esm/special-use.js +907 -0
- package/dist/esm/tools.d.ts +427 -0
- package/dist/esm/tools.js +1446 -0
- package/dist/esm/types.d.ts +828 -0
- package/dist/esm/types.js +4 -0
- package/package.json +60 -20
- package/.gitattributes +0 -1
- package/.github/CODE_OF_CONDUCT.md +0 -76
- package/.github/FUNDING.yml +0 -4
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -40
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -19
- package/.github/contributing.md +0 -17
- package/.github/workflows/release.yaml +0 -36
- package/.github/workflows/stale.yml +0 -29
- package/.github/workflows/test.yml +0 -51
- package/.ncurc.js +0 -4
- package/.prettierignore +0 -4
- package/.prettierrc.js +0 -8
- package/.release-please-manifest.json +0 -3
- package/CLAUDE.md +0 -104
- package/Gruntfile.js +0 -23
- package/eslint.config.js +0 -45
- package/lib/handler/imap-formal-syntax.js +0 -189
- package/lib/handler/imap-handler.js +0 -17
- package/lib/imap-commands.js +0 -45
- package/lib/logger.js +0 -5
- package/lib/special-use.js +0 -923
- package/lib/tools.js +0 -1612
- package/release-please-config.json +0 -10
- package/test/authentication-test.js +0 -101
- package/test/auto-idle-test.js +0 -470
- package/test/bodystructure-test.js +0 -899
- package/test/charsets-test.js +0 -161
- package/test/commands-branches-test.js +0 -1095
- package/test/commands-integration-test.js +0 -11124
- package/test/commands-test.js +0 -73
- package/test/connection-edge-cases-test.js +0 -1828
- package/test/connection-test.js +0 -162
- package/test/copyuid-parser-test.js +0 -173
- package/test/fetch-generator-test.js +0 -218
- package/test/fixtures/fake-timers.js +0 -115
- package/test/fixtures/serialized-mimetorture.js +0 -2738
- package/test/fixtures/test-client.js +0 -101
- package/test/fixtures/test-tls.js +0 -8
- package/test/handler-branches-test.js +0 -310
- package/test/idle-polling-test.js +0 -518
- package/test/imap-compiler-test.js +0 -809
- package/test/imap-flow-compress-test.js +0 -166
- package/test/imap-flow-coverage-test.js +0 -612
- package/test/imap-flow-fetch-download-test.js +0 -909
- package/test/imap-flow-internals-test.js +0 -725
- package/test/imap-flow-methods-test.js +0 -889
- package/test/imap-flow-proxy-paths-test.js +0 -366
- package/test/imap-flow-secure-test.js +0 -573
- package/test/imap-flow-server-test.js +0 -1474
- package/test/imap-formal-syntax-test.js +0 -293
- package/test/imap-parser-test.js +0 -1474
- package/test/imap-stream-edge-cases-test.js +0 -666
- package/test/imap-stream-test.js +0 -177
- package/test/imapflow-test.js +0 -258
- package/test/integration/README.md +0 -52
- package/test/integration/dovecot-test.conf +0 -27
- package/test/integration/rev2-live-test.js +0 -431
- package/test/integration/run-rev2-tests.sh +0 -75
- package/test/integration-test.js +0 -83
- package/test/jp-decoder-test.js +0 -304
- package/test/limited-passthrough-test.js +0 -299
- package/test/memory-cleanup-test.js +0 -144
- package/test/memory-leak-test.js +0 -667
- package/test/parser-limits-test.js +0 -292
- package/test/proxy-connection-test.js +0 -738
- package/test/reliability-improvements-test.js +0 -548
- package/test/search-compiler-test.js +0 -1300
- package/test/search-test.js +0 -329
- package/test/special-use-test.js +0 -418
- package/test/starttls-injection-test.js +0 -181
- package/test/tag-correlation-test.js +0 -333
- package/test/timer-policy-test.js +0 -227
- package/test/token-parser-test.js +0 -456
- package/test/tools-test.js +0 -2013
- package/test/unhandled-rejection-test.js +0 -661
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/* eslint no-console: 0, new-cap: 0 */
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const imap_formal_syntax_js_1 = __importDefault(require("./imap-formal-syntax.js"));
|
|
8
|
+
// A single element of a sequence-set as defined by the RFC 9051 grammar: a number
|
|
9
|
+
// or a range, where "*" stands for the largest number in use. Digit strings are not
|
|
10
|
+
// range-checked here (a server rejects "0" or an overlong number on its own); the
|
|
11
|
+
// point of the check is that nothing outside this alphabet can reach the wire.
|
|
12
|
+
const SEQ_RANGE = /^(\d+|\*)(:(\d+|\*))?$/;
|
|
13
|
+
// Validates a full sequence-set: comma-separated SEQ_RANGE elements, or "$"
|
|
14
|
+
// (RFC 5182 SEARCHRES), which references the previous SEARCH result and is only
|
|
15
|
+
// valid as the entire set. Split into per-element tests on purpose - a whole-set
|
|
16
|
+
// regex with an unbounded repeat group overflows the regex engine's backtrack
|
|
17
|
+
// stack with an uncoded RangeError on valid sets in the million-element range,
|
|
18
|
+
// while the per-element regex is bounded.
|
|
19
|
+
const isValidSequenceSet = (value) => value === '$' || value.split(',').every(part => SEQ_RANGE.test(part));
|
|
20
|
+
// Numeric tokens may only put the digit alphabet on the wire. Anything that does
|
|
21
|
+
// not round to a bounded non-negative integer (NaN, Infinity, negatives, unsafe
|
|
22
|
+
// magnitudes) degrades to 0 - the fallback the NaN coercion has always used.
|
|
23
|
+
const safeNumber = (value) => {
|
|
24
|
+
let num = Math.round(Number(value));
|
|
25
|
+
return Number.isSafeInteger(num) && num >= 0 ? num : 0;
|
|
26
|
+
};
|
|
27
|
+
// Characters that cannot appear in an IMAP quoted string: CR and LF terminate a
|
|
28
|
+
// command line, and NUL is outside the CHAR production entirely. A value carrying
|
|
29
|
+
// any of them has to be sent as a literal, so quoting it is never correct.
|
|
30
|
+
const NOT_QUOTABLE = /[\r\n\0]/;
|
|
31
|
+
// A line terminator ends an IMAP command, so no token may carry one to the wire.
|
|
32
|
+
const CRLF = /[\r\n]/;
|
|
33
|
+
/**
|
|
34
|
+
* Quotes a value as an IMAP quoted string. Only DQUOTE and backslash are escaped -
|
|
35
|
+
* the IMAP grammar defines no other escape sequence, so JSON-style escaping (which
|
|
36
|
+
* turns a tab into a literal backslash-t and a control character into \\uXXXX) would
|
|
37
|
+
* silently change the value the server receives.
|
|
38
|
+
*
|
|
39
|
+
* @param value - The value to quote.
|
|
40
|
+
* @returns The quoted string, ready to be written to the wire.
|
|
41
|
+
* @throws {Error} If the value contains CR, LF or NUL, which a quoted string cannot carry.
|
|
42
|
+
*/
|
|
43
|
+
const quoteString = (value) => {
|
|
44
|
+
if (NOT_QUOTABLE.test(value)) {
|
|
45
|
+
let error = new Error('Unquotable character in IMAP string value');
|
|
46
|
+
error.code = 'InvalidStringValue';
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
return '"' + value.replace(/["\\]/g, char => '\\' + char) + '"';
|
|
50
|
+
};
|
|
51
|
+
async function compiler(response, options) {
|
|
52
|
+
let { asArray, isLogging, literalPlus, literalMinus } = options || {};
|
|
53
|
+
const respParts = [];
|
|
54
|
+
// Formats an entry (string, number or Buffer) into the Buffer that is written to
|
|
55
|
+
// the wire, and is the choke point every emission passes through: a line
|
|
56
|
+
// terminator ends an IMAP command, so no token - the tag and command name
|
|
57
|
+
// included - may put one on the wire. The literal size marker and literal data
|
|
58
|
+
// are the only emissions where CRLF is legitimate; those call sites opt out with
|
|
59
|
+
// `raw`. Never enforced when logging: re-encoding an incoming server response for
|
|
60
|
+
// the log or for error text must not throw, whatever the server sent. With
|
|
61
|
+
// `returnEmpty`, an unrecognized entry type yields null instead of an empty
|
|
62
|
+
// Buffer.
|
|
63
|
+
const emitEntry = (entry, opts) => {
|
|
64
|
+
let { returnEmpty, raw } = opts || {};
|
|
65
|
+
if (!raw && !isLogging && (typeof entry === 'string' || Buffer.isBuffer(entry)) && CRLF.test(entry.toString('latin1'))) {
|
|
66
|
+
let error = new Error('Line terminator in IMAP token');
|
|
67
|
+
error.code = 'InvalidTokenValue';
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
if (typeof entry === 'string') {
|
|
71
|
+
return Buffer.from(entry);
|
|
72
|
+
}
|
|
73
|
+
if (typeof entry === 'number') {
|
|
74
|
+
return Buffer.from(entry.toString());
|
|
75
|
+
}
|
|
76
|
+
if (Buffer.isBuffer(entry)) {
|
|
77
|
+
return entry;
|
|
78
|
+
}
|
|
79
|
+
if (returnEmpty) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
return Buffer.alloc(0);
|
|
83
|
+
};
|
|
84
|
+
let resp = []
|
|
85
|
+
.concat(emitEntry(response.tag, { returnEmpty: true }) || [])
|
|
86
|
+
.concat(response.command ? emitEntry(' ' + response.command) : []);
|
|
87
|
+
let val;
|
|
88
|
+
let lastType;
|
|
89
|
+
let walk = async (node, options) => {
|
|
90
|
+
options = options || {};
|
|
91
|
+
// Determine whether a space separator is needed before this node.
|
|
92
|
+
// Inspect the last byte written to decide context.
|
|
93
|
+
let lastRespEntry = resp.length && resp[resp.length - 1];
|
|
94
|
+
let lastRespByte = (lastRespEntry && lastRespEntry.length && lastRespEntry[lastRespEntry.length - 1]) || '';
|
|
95
|
+
if (typeof lastRespByte === 'number') {
|
|
96
|
+
lastRespByte = String.fromCharCode(lastRespByte);
|
|
97
|
+
}
|
|
98
|
+
// Add a space separator when:
|
|
99
|
+
// - The previous token was a LITERAL. Literal data ends exactly at its declared length, so
|
|
100
|
+
// a following token always needs an explicit separator, even though the last written byte
|
|
101
|
+
// is arbitrary literal content.
|
|
102
|
+
// - Otherwise: there is something written already (resp is not empty) and the last byte is
|
|
103
|
+
// not an opening delimiter ('(', '<' or '['), which suppresses the space.
|
|
104
|
+
// A sub-array element in a consecutive-list context never gets one (no space between
|
|
105
|
+
// adjacent lists).
|
|
106
|
+
if (lastType === 'LITERAL' || (!['(', '<', '['].includes(lastRespByte) && resp.length)) {
|
|
107
|
+
if (!options.subArray) {
|
|
108
|
+
resp.push(emitEntry(' '));
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (node && node.buffer && !Buffer.isBuffer(node)) {
|
|
112
|
+
// mongodb binary
|
|
113
|
+
node = node.buffer;
|
|
114
|
+
}
|
|
115
|
+
if (Array.isArray(node)) {
|
|
116
|
+
lastType = 'LIST';
|
|
117
|
+
resp.push(emitEntry('('));
|
|
118
|
+
// check if we need to skip separator WS between two arrays
|
|
119
|
+
let subArray = node.length > 1 && Array.isArray(node[0]);
|
|
120
|
+
for (let child of node) {
|
|
121
|
+
if (subArray && !Array.isArray(child)) {
|
|
122
|
+
subArray = false;
|
|
123
|
+
}
|
|
124
|
+
await walk(child, { subArray });
|
|
125
|
+
}
|
|
126
|
+
resp.push(emitEntry(')'));
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (!node && typeof node !== 'string' && typeof node !== 'number' && !Buffer.isBuffer(node)) {
|
|
130
|
+
resp.push(emitEntry('NIL'));
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (typeof node === 'string' || Buffer.isBuffer(node)) {
|
|
134
|
+
if (isLogging && node.length > 100) {
|
|
135
|
+
resp.push(emitEntry('"(* ' + node.length + 'B string *)"'));
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
resp.push(emitEntry(isLogging ? JSON.stringify(node.toString()) : quoteString(node.toString())));
|
|
139
|
+
}
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (typeof node === 'number') {
|
|
143
|
+
resp.push(emitEntry(safeNumber(node))); // Only bounded non-negative integers allowed
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
lastType = node.type;
|
|
147
|
+
if (isLogging && node.sensitive) {
|
|
148
|
+
resp.push(emitEntry('"(* value hidden *)"'));
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
switch (node.type.toUpperCase()) {
|
|
152
|
+
case 'LITERAL':
|
|
153
|
+
if (isLogging) {
|
|
154
|
+
resp.push(emitEntry('"(* ' + node.value.length + 'B literal *)"'));
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
// The literal size marker counts octets - string values are written as
|
|
158
|
+
// UTF-8, so their UTF-16 .length would undercount multi-byte characters
|
|
159
|
+
let literalLength = !node.value ? 0 : Buffer.isBuffer(node.value) ? node.value.length : Buffer.byteLength(node.value.toString());
|
|
160
|
+
// Append '+' to the size marker only when the extension actually permits a
|
|
161
|
+
// non-synchronizing literal of this size (RFC 7888): LITERAL+ always,
|
|
162
|
+
// LITERAL- only up to 4096 bytes
|
|
163
|
+
let usePlus = literalPlus || (literalMinus && literalLength <= 4096);
|
|
164
|
+
// canAppend: whether the literal data can be sent in the same buffer segment -
|
|
165
|
+
// non-synchronizing literals always, and everything in single-buffer mode
|
|
166
|
+
// (asArray false), which has no continuation flow
|
|
167
|
+
let canAppend = !asArray || usePlus;
|
|
168
|
+
// Emit the literal header: optional '~' prefix for literal8, then {size[+]}\r\n
|
|
169
|
+
resp.push(emitEntry(`${node.isLiteral8 ? '~' : ''}{${literalLength}${usePlus ? '+' : ''}}\r\n`, { raw: true }));
|
|
170
|
+
if (canAppend) {
|
|
171
|
+
// Literal data follows immediately in the same buffer segment
|
|
172
|
+
if (node.value && node.value.length) {
|
|
173
|
+
resp.push(emitEntry(node.value, { raw: true }));
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
// For synchronizing literals in asArray mode, split output into separate
|
|
178
|
+
// parts. The caller must send each part and wait for a continuation
|
|
179
|
+
// response from the server before sending the next.
|
|
180
|
+
respParts.push(resp);
|
|
181
|
+
resp = [].concat(emitEntry(node.value, { returnEmpty: true, raw: true }) || []);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
break;
|
|
185
|
+
case 'STRING':
|
|
186
|
+
if (isLogging && node.value.length > 100) {
|
|
187
|
+
resp.push(emitEntry('"(* ' + node.value.length + 'B string *)"'));
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
val = (node.value || '').toString();
|
|
191
|
+
resp.push(emitEntry(isLogging ? JSON.stringify(val) : quoteString(val)));
|
|
192
|
+
}
|
|
193
|
+
break;
|
|
194
|
+
case 'SEQUENCE':
|
|
195
|
+
// Sequence sets are written verbatim - they are the one token type with
|
|
196
|
+
// no quoting to fall back on. Callers build them from user-supplied
|
|
197
|
+
// ranges, so validate here, at the single point every outgoing sequence
|
|
198
|
+
// set passes through, rather than trusting each command module. Skipped
|
|
199
|
+
// when logging: the incoming token parser accepts sequence-shaped tokens
|
|
200
|
+
// this strict grammar rejects (an ESEARCH set like "1:2:3", a folder
|
|
201
|
+
// name like "12:30:00"), and re-compiling a server response for the log
|
|
202
|
+
// or for error text must never throw.
|
|
203
|
+
if (!isLogging && (typeof node.value === 'string' || typeof node.value === 'number' || Buffer.isBuffer(node.value))) {
|
|
204
|
+
val = node.value.toString();
|
|
205
|
+
if (val && !isValidSequenceSet(val)) {
|
|
206
|
+
let error = new Error('Invalid sequence set value');
|
|
207
|
+
error.code = 'InvalidSequenceSet';
|
|
208
|
+
throw error;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
if (node.value) {
|
|
212
|
+
// raw: the validated alphabet cannot contain a line terminator, and
|
|
213
|
+
// re-scanning a potentially multi-megabyte set in the choke point
|
|
214
|
+
// would double the cost of exactly the sets this branch exists for
|
|
215
|
+
resp.push(emitEntry(node.value, { raw: true }));
|
|
216
|
+
}
|
|
217
|
+
break;
|
|
218
|
+
case 'TEXT':
|
|
219
|
+
// Response text is written verbatim. Only the parser produces it today, for
|
|
220
|
+
// incoming lines, so this is a re-encoding path rather than a command-building
|
|
221
|
+
// one. The emitEntry choke point would refuse a line terminator here too;
|
|
222
|
+
// this check runs first only to raise the more specific InvalidTextValue code.
|
|
223
|
+
if (node.value) {
|
|
224
|
+
if (!isLogging && CRLF.test(node.value.toString())) {
|
|
225
|
+
let error = new Error('Line terminator in IMAP text value');
|
|
226
|
+
error.code = 'InvalidTextValue';
|
|
227
|
+
throw error;
|
|
228
|
+
}
|
|
229
|
+
resp.push(emitEntry(node.value));
|
|
230
|
+
}
|
|
231
|
+
break;
|
|
232
|
+
case 'NUMBER':
|
|
233
|
+
// Coerced rather than written through: formatRespEntry passes a string or
|
|
234
|
+
// Buffer straight to the wire, so a numeric token carrying a string value
|
|
235
|
+
// would be another verbatim channel
|
|
236
|
+
resp.push(emitEntry(safeNumber(node.value)));
|
|
237
|
+
break;
|
|
238
|
+
case 'ATOM':
|
|
239
|
+
case 'SECTION':
|
|
240
|
+
val = (node.value || '').toString();
|
|
241
|
+
if (!node.section || val) {
|
|
242
|
+
// Verify the value contains only valid ATOM-CHAR characters.
|
|
243
|
+
// Strip a leading backslash before checking (system flags like \Seen start with '\').
|
|
244
|
+
// If any character fails verification, fall back to an IMAP quoted string
|
|
245
|
+
// (JSON.stringify is used only for log output, where values are display-escaped).
|
|
246
|
+
if (node.value === '' || imap_formal_syntax_js_1.default.verify(val.charAt(0) === '\\' ? val.substr(1) : val, imap_formal_syntax_js_1.default['ATOM-CHAR']()) >= 0) {
|
|
247
|
+
val = isLogging ? JSON.stringify(val) : quoteString(val);
|
|
248
|
+
}
|
|
249
|
+
resp.push(emitEntry(val));
|
|
250
|
+
}
|
|
251
|
+
// Section bracket handling: emit [section-contents] after the ATOM value
|
|
252
|
+
// e.g., BODY[HEADER.FIELDS (Subject)] or BODY[1.MIME]
|
|
253
|
+
if (node.section) {
|
|
254
|
+
resp.push(emitEntry('['));
|
|
255
|
+
for (let child of node.section) {
|
|
256
|
+
await walk(child);
|
|
257
|
+
}
|
|
258
|
+
resp.push(emitEntry(']'));
|
|
259
|
+
}
|
|
260
|
+
// Partial range: emit <origin.length> after the section brackets. Coerced
|
|
261
|
+
// rather than joined as-is: this is the last token component written
|
|
262
|
+
// verbatim, and the choke point is only worth relying on if it holds for
|
|
263
|
+
// all of them. Every producer already passes numbers, so nothing changes
|
|
264
|
+
// for them.
|
|
265
|
+
if (node.partial) {
|
|
266
|
+
resp.push(emitEntry(`<${node.partial.map(safeNumber).join('.')}>`));
|
|
267
|
+
}
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
if (response.attributes) {
|
|
272
|
+
let attributes = Array.isArray(response.attributes) ? response.attributes : [].concat(response.attributes);
|
|
273
|
+
for (let child of attributes) {
|
|
274
|
+
await walk(child);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
if (resp.length) {
|
|
278
|
+
respParts.push(resp);
|
|
279
|
+
}
|
|
280
|
+
const compiled = respParts.map(part => Buffer.concat(part));
|
|
281
|
+
return asArray ? compiled : compiled.flatMap(entry => entry);
|
|
282
|
+
}
|
|
283
|
+
exports.default = compiler;
|
|
284
|
+
module.exports = exports.default;
|
|
285
|
+
Object.defineProperty(module.exports, 'default', { value: exports.default, enumerable: false, writable: true, configurable: true });
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verifies that every character in the given string is within the set of allowed characters.
|
|
3
|
+
*
|
|
4
|
+
* @param str - The string to validate.
|
|
5
|
+
* @param allowedChars - A string containing all allowed characters.
|
|
6
|
+
* @returns The index of the first disallowed character, or -1 if all characters are valid.
|
|
7
|
+
*/
|
|
8
|
+
declare function verify(str: string, allowedChars: string): number;
|
|
9
|
+
declare const imapFormalSyntax: {
|
|
10
|
+
CHAR: () => string;
|
|
11
|
+
CHAR8: () => string;
|
|
12
|
+
SP: () => string;
|
|
13
|
+
CTL: () => string;
|
|
14
|
+
DQUOTE: () => string;
|
|
15
|
+
ALPHA: () => string;
|
|
16
|
+
DIGIT: () => string;
|
|
17
|
+
'ATOM-CHAR': () => string;
|
|
18
|
+
'ASTRING-CHAR': () => string;
|
|
19
|
+
'TEXT-CHAR': () => string;
|
|
20
|
+
'atom-specials': () => string;
|
|
21
|
+
'list-wildcards': () => string;
|
|
22
|
+
'quoted-specials': () => string;
|
|
23
|
+
'resp-specials': () => string;
|
|
24
|
+
tag: () => string;
|
|
25
|
+
command: () => string;
|
|
26
|
+
verify: typeof verify;
|
|
27
|
+
};
|
|
28
|
+
export default imapFormalSyntax;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/* eslint new-cap: 0, no-useless-concat: 0 */
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
/**
|
|
5
|
+
* Defines the IMAP formal syntax character classes and validation rules as specified
|
|
6
|
+
* in RFC 3501 Section 9 (http://tools.ietf.org/html/rfc3501#section-9).
|
|
7
|
+
*
|
|
8
|
+
* Each exported method returns a string of allowed characters for a given IMAP grammar
|
|
9
|
+
* production rule (e.g., ATOM-CHAR, ASTRING-CHAR, TEXT-CHAR). Results are computed once,
|
|
10
|
+
* on first use, and cached at module level.
|
|
11
|
+
*
|
|
12
|
+
* Also exports a `verify` function for validating strings against a set of allowed characters.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Generates a string containing all characters in the given Unicode code point range (inclusive).
|
|
16
|
+
*
|
|
17
|
+
* @param start - The starting character code point.
|
|
18
|
+
* @param end - The ending character code point.
|
|
19
|
+
* @returns A string containing all characters from start to end.
|
|
20
|
+
*/
|
|
21
|
+
function expandRange(start, end) {
|
|
22
|
+
let chars = [];
|
|
23
|
+
for (let i = start; i <= end; i++) {
|
|
24
|
+
chars.push(i);
|
|
25
|
+
}
|
|
26
|
+
return String.fromCharCode(...chars);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Returns a new string with all characters from the exclude string removed from the source string.
|
|
30
|
+
*
|
|
31
|
+
* @param source - The source string to filter.
|
|
32
|
+
* @param exclude - A string of characters to exclude from the source.
|
|
33
|
+
* @returns The source string with excluded characters removed.
|
|
34
|
+
*/
|
|
35
|
+
function excludeChars(source, exclude) {
|
|
36
|
+
return Array.prototype.filter.call(source, (ch) => exclude.indexOf(ch) < 0).join('');
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Wraps a computation so that it runs once, on first call, and the result is reused afterwards.
|
|
40
|
+
*
|
|
41
|
+
* @param compute - Produces the character set.
|
|
42
|
+
* @returns A zero-argument function returning the cached character set.
|
|
43
|
+
*/
|
|
44
|
+
function memo(compute) {
|
|
45
|
+
let value = null;
|
|
46
|
+
return () => {
|
|
47
|
+
if (value === null) {
|
|
48
|
+
value = compute();
|
|
49
|
+
}
|
|
50
|
+
return value;
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/** All 7-bit US-ASCII characters excluding NUL (0x01-0x7F). */
|
|
54
|
+
const CHAR = memo(() => expandRange(0x01, 0x7f));
|
|
55
|
+
/** All 8-bit characters excluding NUL (0x01-0xFF). */
|
|
56
|
+
const CHAR8 = memo(() => expandRange(0x01, 0xff));
|
|
57
|
+
/** The space character (0x20). */
|
|
58
|
+
const SP = () => ' ';
|
|
59
|
+
/** All control characters (0x00-0x1F and 0x7F). */
|
|
60
|
+
const CTL = memo(() => expandRange(0x00, 0x1f) + '\x7F');
|
|
61
|
+
/** The double-quote character. */
|
|
62
|
+
const DQUOTE = () => '"';
|
|
63
|
+
/** All uppercase and lowercase ASCII alphabetic characters (A-Z, a-z). */
|
|
64
|
+
const ALPHA = memo(() => expandRange(0x41, 0x5a) + expandRange(0x61, 0x7a));
|
|
65
|
+
/** All ASCII digit characters (0-9). */
|
|
66
|
+
const DIGIT = memo(() => expandRange(0x30, 0x39));
|
|
67
|
+
/** The LIST wildcard characters ("%" and "*"). */
|
|
68
|
+
const listWildcards = () => '%' + '*';
|
|
69
|
+
/** Characters that are special inside quoted strings (DQUOTE and backslash). */
|
|
70
|
+
const quotedSpecials = memo(() => DQUOTE() + '\\');
|
|
71
|
+
/** The response-special character ("]"). */
|
|
72
|
+
const respSpecials = () => ']';
|
|
73
|
+
/** Characters that are special in ATOMs and must be excluded: "(", ")", "{", SP, CTL, list-wildcards, quoted-specials, resp-specials. */
|
|
74
|
+
const atomSpecials = memo(() => '(' + ')' + '{' + SP() + CTL() + listWildcards() + quotedSpecials() + respSpecials());
|
|
75
|
+
/** Characters allowed in an IMAP ATOM (CHAR minus atom-specials). */
|
|
76
|
+
const atomChar = memo(() => excludeChars(CHAR(), atomSpecials()));
|
|
77
|
+
/** Characters allowed in an IMAP ASTRING (ATOM-CHAR plus resp-specials). */
|
|
78
|
+
const astringChar = memo(() => atomChar() + respSpecials());
|
|
79
|
+
/** Characters allowed in IMAP text (CHAR minus CR and LF). */
|
|
80
|
+
const textChar = memo(() => excludeChars(CHAR(), '\r\n'));
|
|
81
|
+
/** Characters allowed in an IMAP tag (ASTRING-CHAR minus "+"). */
|
|
82
|
+
const tag = memo(() => excludeChars(astringChar(), '+'));
|
|
83
|
+
/** Characters allowed in an IMAP command name (ALPHA, DIGIT, and hyphen). */
|
|
84
|
+
const command = memo(() => ALPHA() + DIGIT() + '-');
|
|
85
|
+
/**
|
|
86
|
+
* Verifies that every character in the given string is within the set of allowed characters.
|
|
87
|
+
*
|
|
88
|
+
* @param str - The string to validate.
|
|
89
|
+
* @param allowedChars - A string containing all allowed characters.
|
|
90
|
+
* @returns The index of the first disallowed character, or -1 if all characters are valid.
|
|
91
|
+
*/
|
|
92
|
+
function verify(str, allowedChars) {
|
|
93
|
+
for (let i = 0, len = str.length; i < len; i++) {
|
|
94
|
+
if (allowedChars.indexOf(str.charAt(i)) < 0) {
|
|
95
|
+
return i;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return -1;
|
|
99
|
+
}
|
|
100
|
+
const imapFormalSyntax = {
|
|
101
|
+
CHAR,
|
|
102
|
+
CHAR8,
|
|
103
|
+
SP,
|
|
104
|
+
CTL,
|
|
105
|
+
DQUOTE,
|
|
106
|
+
ALPHA,
|
|
107
|
+
DIGIT,
|
|
108
|
+
'ATOM-CHAR': atomChar,
|
|
109
|
+
'ASTRING-CHAR': astringChar,
|
|
110
|
+
'TEXT-CHAR': textChar,
|
|
111
|
+
'atom-specials': atomSpecials,
|
|
112
|
+
'list-wildcards': listWildcards,
|
|
113
|
+
'quoted-specials': quotedSpecials,
|
|
114
|
+
'resp-specials': respSpecials,
|
|
115
|
+
tag,
|
|
116
|
+
command,
|
|
117
|
+
verify
|
|
118
|
+
};
|
|
119
|
+
exports.default = imapFormalSyntax;
|
|
120
|
+
module.exports = exports.default;
|
|
121
|
+
Object.defineProperty(module.exports, 'default', { value: exports.default, enumerable: false, writable: true, configurable: true });
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import parser from './imap-parser.js';
|
|
2
|
+
import compiler from './imap-compiler.js';
|
|
3
|
+
/**
|
|
4
|
+
* Re-exports the IMAP protocol parser and compiler as a single module.
|
|
5
|
+
*
|
|
6
|
+
* - `parser` parses raw IMAP command/response buffers into structured objects.
|
|
7
|
+
* - `compiler` compiles structured response objects into IMAP protocol Buffers.
|
|
8
|
+
*/
|
|
9
|
+
export { parser, compiler };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.compiler = exports.parser = void 0;
|
|
7
|
+
const imap_parser_js_1 = __importDefault(require("./imap-parser.js"));
|
|
8
|
+
exports.parser = imap_parser_js_1.default;
|
|
9
|
+
const imap_compiler_js_1 = __importDefault(require("./imap-compiler.js"));
|
|
10
|
+
exports.compiler = imap_compiler_js_1.default;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ImapResponse, ParserOptions } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Parses a raw IMAP command or response buffer into a structured object.
|
|
4
|
+
* Handles edge cases such as null-byte-padded responses from buggy servers and
|
|
5
|
+
* multi-word commands like UID and AUTHENTICATE.
|
|
6
|
+
*
|
|
7
|
+
* @param command - The raw IMAP command or response data to parse.
|
|
8
|
+
* @param options - Parser options passed through to the underlying ParserInstance and TokenParser.
|
|
9
|
+
* @param options.literalPlus - Whether the LITERAL+ extension is in use.
|
|
10
|
+
* @param options.literals - Pre-parsed literal values extracted from the input stream.
|
|
11
|
+
* @returns A promise that resolves to a parsed response object with `tag` (the IMAP tag, e.g. "*",
|
|
12
|
+
* "+", or a command tag like "A1"), `command` (the IMAP command or response name, e.g. "OK",
|
|
13
|
+
* "FETCH"), `attributes` (parsed attributes of the response) and `nullBytesRemoved` (number of
|
|
14
|
+
* leading null bytes removed, if any).
|
|
15
|
+
*/
|
|
16
|
+
export default function parser(command: Buffer | string, options?: ParserOptions | undefined): Promise<ImapResponse>;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.default = parser;
|
|
7
|
+
const imap_formal_syntax_js_1 = __importDefault(require("./imap-formal-syntax.js"));
|
|
8
|
+
const parser_instance_js_1 = require("./parser-instance.js");
|
|
9
|
+
/**
|
|
10
|
+
* Parses a raw IMAP command or response buffer into a structured object.
|
|
11
|
+
* Handles edge cases such as null-byte-padded responses from buggy servers and
|
|
12
|
+
* multi-word commands like UID and AUTHENTICATE.
|
|
13
|
+
*
|
|
14
|
+
* @param command - The raw IMAP command or response data to parse.
|
|
15
|
+
* @param options - Parser options passed through to the underlying ParserInstance and TokenParser.
|
|
16
|
+
* @param options.literalPlus - Whether the LITERAL+ extension is in use.
|
|
17
|
+
* @param options.literals - Pre-parsed literal values extracted from the input stream.
|
|
18
|
+
* @returns A promise that resolves to a parsed response object with `tag` (the IMAP tag, e.g. "*",
|
|
19
|
+
* "+", or a command tag like "A1"), `command` (the IMAP command or response name, e.g. "OK",
|
|
20
|
+
* "FETCH"), `attributes` (parsed attributes of the response) and `nullBytesRemoved` (number of
|
|
21
|
+
* leading null bytes removed, if any).
|
|
22
|
+
*/
|
|
23
|
+
async function parser(command, options) {
|
|
24
|
+
options = options || {};
|
|
25
|
+
let nullBytesRemoved = 0;
|
|
26
|
+
// Workaround for buggy IMAP servers that pad responses with leading NUL (\x00) bytes.
|
|
27
|
+
// Some servers (observed in the wild) prepend null bytes to their output, which would
|
|
28
|
+
// cause parsing to fail. We strip them and note how many were removed for diagnostics.
|
|
29
|
+
if (command[0] === 0) {
|
|
30
|
+
// find the first non null byte and trim
|
|
31
|
+
let firstNonNull = -1;
|
|
32
|
+
for (let i = 0; i < command.length; i++) {
|
|
33
|
+
if (command[i] !== 0) {
|
|
34
|
+
firstNonNull = i;
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (firstNonNull === -1) {
|
|
39
|
+
// All bytes are null, treat as a BAD response
|
|
40
|
+
return { tag: '*', command: 'BAD', attributes: [] };
|
|
41
|
+
}
|
|
42
|
+
command = command.slice(firstNonNull);
|
|
43
|
+
nullBytesRemoved = firstNonNull;
|
|
44
|
+
}
|
|
45
|
+
const parserInstance = new parser_instance_js_1.ParserInstance(command, options);
|
|
46
|
+
const response = {};
|
|
47
|
+
try {
|
|
48
|
+
response.tag = await parserInstance.getTag();
|
|
49
|
+
await parserInstance.getSpace();
|
|
50
|
+
response.command = await parserInstance.getCommand();
|
|
51
|
+
if (nullBytesRemoved) {
|
|
52
|
+
response.nullBytesRemoved = nullBytesRemoved;
|
|
53
|
+
}
|
|
54
|
+
// Some IMAP commands are multi-word: "UID FETCH", "UID STORE", "UID COPY",
|
|
55
|
+
// "UID MOVE", "UID SEARCH", "UID EXPUNGE", and "AUTHENTICATE PLAIN", etc.
|
|
56
|
+
// For these, the first word is consumed as the command, then we read the
|
|
57
|
+
// subcommand and concatenate them (e.g., "UID" + " " + "FETCH" -> "UID FETCH").
|
|
58
|
+
if (['UID', 'AUTHENTICATE'].includes((response.command || '').toUpperCase())) {
|
|
59
|
+
await parserInstance.getSpace();
|
|
60
|
+
response.command += ' ' + (await parserInstance.getElement(imap_formal_syntax_js_1.default.command()));
|
|
61
|
+
}
|
|
62
|
+
if (parserInstance.remainder.trim().length) {
|
|
63
|
+
await parserInstance.getSpace();
|
|
64
|
+
response.attributes = await parserInstance.getAttributes();
|
|
65
|
+
}
|
|
66
|
+
if (parserInstance.humanReadable) {
|
|
67
|
+
response.attributes = (response.attributes || []).concat({
|
|
68
|
+
type: 'TEXT',
|
|
69
|
+
value: parserInstance.humanReadable
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
let error = err;
|
|
75
|
+
if (error.code === 'ParserErrorExchange' && error.parserContext && error.parserContext.value) {
|
|
76
|
+
return error.parserContext.value;
|
|
77
|
+
}
|
|
78
|
+
if (response.tag) {
|
|
79
|
+
// The tag had already been parsed when the rest of the line failed. Expose it
|
|
80
|
+
// so the connection can settle the command this line was addressed to - unlike
|
|
81
|
+
// re-deriving the tag from the raw bytes, this inherits the leading-NUL
|
|
82
|
+
// workaround above.
|
|
83
|
+
error.parsedTag = response.tag;
|
|
84
|
+
}
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
|
87
|
+
return response;
|
|
88
|
+
}
|
|
89
|
+
module.exports = exports.default;
|
|
90
|
+
Object.defineProperty(module.exports, 'default', { value: exports.default, enumerable: false, writable: true, configurable: true });
|