imapflow 2.3.0 → 2.4.1
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 +19 -0
- package/SECURITY.md +126 -55
- package/dist/cjs/commands/append.d.ts +1 -1
- package/dist/cjs/commands/append.js +15 -4
- package/dist/cjs/commands/compress.js +2 -2
- package/dist/cjs/commands/idle.js +2 -3
- package/dist/cjs/commands/status.js +2 -4
- package/dist/cjs/connection-deadline.d.ts +1 -1
- package/dist/cjs/connection-deadline.js +2 -4
- package/dist/cjs/download.js +22 -19
- package/dist/cjs/errors.d.ts +15 -0
- package/dist/cjs/errors.js +22 -0
- package/dist/cjs/handler/imap-compiler.d.ts +4 -4
- package/dist/cjs/handler/imap-compiler.js +18 -18
- package/dist/cjs/handler/imap-parser.js +28 -3
- package/dist/cjs/handler/imap-stream.js +9 -8
- package/dist/cjs/handler/limits.d.ts +1 -1
- package/dist/cjs/handler/limits.js +5 -7
- package/dist/cjs/handler/parser-instance.js +23 -38
- package/dist/cjs/handler/token-parser.d.ts +12 -0
- package/dist/cjs/handler/token-parser.js +44 -104
- package/dist/cjs/imap-flow.d.ts +4 -3
- package/dist/cjs/imap-flow.js +72 -72
- package/dist/cjs/package-info.d.ts +1 -1
- package/dist/cjs/package-info.js +1 -1
- package/dist/cjs/proxy-connection.js +2 -5
- package/dist/cjs/search-compiler.js +2 -3
- package/dist/cjs/tools.d.ts +2 -2
- package/dist/cjs/tools.js +8 -11
- package/dist/esm/commands/append.d.ts +1 -1
- package/dist/esm/commands/append.js +15 -4
- package/dist/esm/commands/compress.js +2 -2
- package/dist/esm/commands/idle.js +2 -3
- package/dist/esm/commands/status.js +2 -4
- package/dist/esm/connection-deadline.d.ts +1 -1
- package/dist/esm/connection-deadline.js +2 -4
- package/dist/esm/download.js +22 -19
- package/dist/esm/errors.d.ts +15 -0
- package/dist/esm/errors.js +21 -0
- package/dist/esm/handler/imap-compiler.d.ts +4 -4
- package/dist/esm/handler/imap-compiler.js +18 -18
- package/dist/esm/handler/imap-parser.js +28 -3
- package/dist/esm/handler/imap-stream.js +9 -8
- package/dist/esm/handler/limits.d.ts +1 -1
- package/dist/esm/handler/limits.js +5 -7
- package/dist/esm/handler/parser-instance.js +23 -38
- package/dist/esm/handler/token-parser.d.ts +12 -0
- package/dist/esm/handler/token-parser.js +44 -104
- package/dist/esm/imap-flow.d.ts +4 -3
- package/dist/esm/imap-flow.js +73 -73
- package/dist/esm/package-info.d.ts +1 -1
- package/dist/esm/package-info.js +1 -1
- package/dist/esm/proxy-connection.js +2 -5
- package/dist/esm/search-compiler.js +2 -3
- package/dist/esm/tools.d.ts +2 -2
- package/dist/esm/tools.js +9 -12
- package/package.json +6 -6
package/dist/esm/imap-flow.js
CHANGED
|
@@ -14,7 +14,7 @@ import { parser, compiler } from './handler/imap-handler.js';
|
|
|
14
14
|
import { proxyConnection, detachEarlyErrorHandler } from './proxy-connection.js';
|
|
15
15
|
import { ConnectionDeadline } from './connection-deadline.js';
|
|
16
16
|
import { downloadMessage, downloadMessageParts } from './download.js';
|
|
17
|
-
import { AuthenticationFailure } from './errors.js';
|
|
17
|
+
import { AuthenticationFailure, createImapError } from './errors.js';
|
|
18
18
|
import imapCommands from './imap-commands.js';
|
|
19
19
|
import { comparePaths, updateCapabilities, getFolderTree, formatMessageResponse, mergeFetchRows, packMessageRange, normalizePath, expandRange, getColorFlags, hasCapability, isRev2Active, logConnectionError, unrefTimer, clearTimer, parseUintValue, getStringList, getTextValues, emitSafe, buildConnectionError, guardedPromise, guardedReject, MAX_UINT32_DIGITS } from './tools.js';
|
|
20
20
|
export { AuthenticationFailure, ImapFlowErrorCode } from './errors.js';
|
|
@@ -44,6 +44,10 @@ const RAW_SENSITIVE_COMMANDS = new Set(['LOGIN', 'AUTHENTICATE']);
|
|
|
44
44
|
// Stand-in payload for a withheld raw client frame. Fixed width, so the entry says nothing
|
|
45
45
|
// about the length of what it replaced.
|
|
46
46
|
const RAW_HIDDEN_PLACEHOLDER = Buffer.from('(* value hidden *)\r\n').toString('base64');
|
|
47
|
+
// From this size up, a Buffer written as the last part of a command gets its CRLF as a socket
|
|
48
|
+
// write of its own instead of being copied to append it (see write())
|
|
49
|
+
const SEPARATE_LINE_BREAK_SIZE = 64 * 1024;
|
|
50
|
+
const CRLF = Buffer.from('\r\n');
|
|
47
51
|
// Servers, matched by the name in their ID response, that advertise IMAP4rev2 next to
|
|
48
52
|
// IMAP4rev1 and accept ENABLE IMAP4rev2, but misbehave once it is enabled. ENABLE cannot
|
|
49
53
|
// be undone (RFC 5161), so these are kept in IMAP4rev1 mode from the start. Strato's
|
|
@@ -344,43 +348,47 @@ export class ImapFlow extends EventEmitter {
|
|
|
344
348
|
// commandParts holds the remaining parts (literal data, continuation); the CRLF
|
|
345
349
|
// delimiter is only added when no more parts remain (the command is complete).
|
|
346
350
|
let addLineBreak = !this.commandParts.length;
|
|
347
|
-
|
|
351
|
+
// What goes to the socket, one write per entry. A large Buffer (the data of a literal)
|
|
352
|
+
// gets its CRLF as a write of its own, as concatenating would copy all of it.
|
|
353
|
+
let frames;
|
|
348
354
|
if (typeof chunk === 'string') {
|
|
349
355
|
if (addLineBreak) {
|
|
350
356
|
chunk += '\r\n';
|
|
351
357
|
}
|
|
352
|
-
|
|
358
|
+
frames = [Buffer.from(chunk, 'binary')];
|
|
353
359
|
}
|
|
354
360
|
else if (Buffer.isBuffer(chunk)) {
|
|
355
|
-
if (addLineBreak) {
|
|
356
|
-
|
|
361
|
+
if (!addLineBreak) {
|
|
362
|
+
frames = [chunk];
|
|
357
363
|
}
|
|
358
364
|
else {
|
|
359
|
-
|
|
365
|
+
frames = chunk.length >= SEPARATE_LINE_BREAK_SIZE ? [chunk, CRLF] : [Buffer.concat([chunk, CRLF])];
|
|
360
366
|
}
|
|
361
367
|
}
|
|
362
368
|
else {
|
|
363
369
|
return false;
|
|
364
370
|
}
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
371
|
+
for (let data of frames) {
|
|
372
|
+
if (this.logRaw) {
|
|
373
|
+
// Client frames of an authentication exchange carry credentials: the LOGIN
|
|
374
|
+
// arguments, and for AUTHENTICATE also the continuation writes (SASL PLAIN
|
|
375
|
+
// response, AUTH=LOGIN password, OAuth token payload) that bypass send(). The
|
|
376
|
+
// parsed command log masks these, so the raw log must withhold them too, but
|
|
377
|
+
// `data` still carries the placeholder rather than being dropped - the field is
|
|
378
|
+
// part of the documented log format and consumers decode it unconditionally.
|
|
379
|
+
this.log.trace({
|
|
380
|
+
src: 'c',
|
|
381
|
+
msg: 'write to socket',
|
|
382
|
+
data: this.rawSensitiveCommand ? RAW_HIDDEN_PLACEHOLDER : data.toString('base64'),
|
|
383
|
+
...(this.rawSensitiveCommand ? { hidden: true } : {}),
|
|
384
|
+
compress: !!this._deflate,
|
|
385
|
+
secure: !!this.secureConnection,
|
|
386
|
+
cid: this.id
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
this.writeBytesCounter += data.length;
|
|
390
|
+
this.writeSocket.write(data);
|
|
381
391
|
}
|
|
382
|
-
this.writeBytesCounter += data.length;
|
|
383
|
-
this.writeSocket.write(data);
|
|
384
392
|
}
|
|
385
393
|
/**
|
|
386
394
|
* Returns byte counters for the current connection.
|
|
@@ -406,6 +414,9 @@ export class ImapFlow extends EventEmitter {
|
|
|
406
414
|
// and once as a string (for logging, with sensitive data masked).
|
|
407
415
|
// When LITERAL- or LITERAL+ extensions are available, the compiler can use
|
|
408
416
|
// non-synchronizing literals to avoid waiting for server "+" continuation.
|
|
417
|
+
// Async although nothing in it awaits: a failure inside it rejects one microtask later, so a
|
|
418
|
+
// close() in the same tick settles the command with NoConnection first (see
|
|
419
|
+
// test/unhandled-rejection-test.ts)
|
|
409
420
|
/** @internal */
|
|
410
421
|
async send(data) {
|
|
411
422
|
if (this.state === this.states.LOGOUT) {
|
|
@@ -415,11 +426,11 @@ export class ImapFlow extends EventEmitter {
|
|
|
415
426
|
// the socket watchdog.
|
|
416
427
|
throw this.createNoConnectionError(false, { rejectedFrom: 'sendAfterLogout', command: data.command });
|
|
417
428
|
}
|
|
418
|
-
// Classify
|
|
419
|
-
//
|
|
429
|
+
// Classify once per command. Every frame of this command - the command line and any
|
|
430
|
+
// continuation write that follows it - belongs to it until the next send(), because
|
|
420
431
|
// trySend() keeps one command in flight at a time. Reading currentRequest inside write()
|
|
421
|
-
// instead would be racy: rejectCurrentRequest() can clear it
|
|
422
|
-
//
|
|
432
|
+
// instead would be racy: rejectCurrentRequest() can clear it before a continuation frame
|
|
433
|
+
// is written, and the credential frame would then be logged in the clear.
|
|
423
434
|
// Uppercased because the wire protocol is case-insensitive and exec() passes the
|
|
424
435
|
// caller's spelling through unchanged. The command list covers the mechanisms whose
|
|
425
436
|
// secret arrives in a continuation frame, which carries no attributes of its own; the
|
|
@@ -430,7 +441,7 @@ export class ImapFlow extends EventEmitter {
|
|
|
430
441
|
// Compile with asArray=true: splits output into parts for literal handling.
|
|
431
442
|
// First part is the command text up to the first literal, remaining parts
|
|
432
443
|
// are stored in this.commandParts and sent after server "+" continuations.
|
|
433
|
-
let compiled =
|
|
444
|
+
let compiled = compiler(data, {
|
|
434
445
|
asArray: true,
|
|
435
446
|
// LITERAL- is part of base IMAP4rev2
|
|
436
447
|
literalMinus: hasCapability(this, 'LITERAL-') || this.capabilities.has('LITERAL+')
|
|
@@ -441,7 +452,7 @@ export class ImapFlow extends EventEmitter {
|
|
|
441
452
|
// Compile again for logging with isLogging=true: masks sensitive values
|
|
442
453
|
// like passwords while producing a human-readable command string
|
|
443
454
|
if (this.isLogLevelEnabled('debug')) {
|
|
444
|
-
let logCompiled =
|
|
455
|
+
let logCompiled = compiler(data, {
|
|
445
456
|
isLogging: true
|
|
446
457
|
});
|
|
447
458
|
this.log.debug({ src: 'c', msg: logCompiled.toString(), cid: this.id, comment: options.comment });
|
|
@@ -698,9 +709,7 @@ export class ImapFlow extends EventEmitter {
|
|
|
698
709
|
// parser would keep waiting on its backpressure callback forever, which is a
|
|
699
710
|
// silent permanent hang. Fail closed instead.
|
|
700
711
|
keepReading = false;
|
|
701
|
-
let error =
|
|
702
|
-
error.code = 'ResponseProcessingFailed';
|
|
703
|
-
error._err = err;
|
|
712
|
+
let error = createImapError('Failed to process server response', 'ResponseProcessingFailed', { _err: err });
|
|
704
713
|
this.log.error({ msg: 'Failed to process server response', err, cid: this.id });
|
|
705
714
|
this.rejectCurrentRequest(error);
|
|
706
715
|
this.failProtocol(error);
|
|
@@ -823,7 +832,7 @@ export class ImapFlow extends EventEmitter {
|
|
|
823
832
|
// skipped when the level is off.
|
|
824
833
|
let logLevel = /^\d+$/.test(parsed.command || '') && parsed.attributes && parsed.attributes[0] && parsed.attributes[0].value === 'FETCH' ? 'trace' : 'debug';
|
|
825
834
|
if (this.isLogLevelEnabled(logLevel)) {
|
|
826
|
-
let logCompiled =
|
|
835
|
+
let logCompiled = compiler(parsed, {
|
|
827
836
|
isLogging: true
|
|
828
837
|
});
|
|
829
838
|
this.log[logLevel]({ src: 's', msg: logCompiled.toString(), cid: this.id, nullBytesRemoved: parsed.nullBytesRemoved });
|
|
@@ -929,12 +938,12 @@ export class ImapFlow extends EventEmitter {
|
|
|
929
938
|
// response ambiguous, so reject this request and fail the connection closed.
|
|
930
939
|
let request = this.requestTagMap.get(parsed.tag);
|
|
931
940
|
this.requestTagMap.delete(parsed.tag);
|
|
932
|
-
let err =
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
};
|
|
941
|
+
let err = createImapError('Server sent a tagged response for a command that was not in flight', 'UnexpectedTag', {
|
|
942
|
+
details: {
|
|
943
|
+
received: parsed.tag,
|
|
944
|
+
expected: this.currentRequest ? this.currentRequest.tag : null
|
|
945
|
+
}
|
|
946
|
+
});
|
|
938
947
|
this.log.error({ msg: 'Protocol desynchronization', err, cid: this.id });
|
|
939
948
|
request.reject(err);
|
|
940
949
|
this.failProtocol(err);
|
|
@@ -978,9 +987,9 @@ export class ImapFlow extends EventEmitter {
|
|
|
978
987
|
try {
|
|
979
988
|
err.executedCommand =
|
|
980
989
|
parsed.tag +
|
|
981
|
-
|
|
990
|
+
compiler(request, {
|
|
982
991
|
isLogging: true
|
|
983
|
-
})
|
|
992
|
+
}).toString();
|
|
984
993
|
}
|
|
985
994
|
catch {
|
|
986
995
|
// ignore
|
|
@@ -1033,9 +1042,7 @@ export class ImapFlow extends EventEmitter {
|
|
|
1033
1042
|
break;
|
|
1034
1043
|
}
|
|
1035
1044
|
default: {
|
|
1036
|
-
let err =
|
|
1037
|
-
err.code = 'InvalidResponse';
|
|
1038
|
-
err.response = parsed;
|
|
1045
|
+
let err = createImapError('Invalid server response', 'InvalidResponse', { response: parsed });
|
|
1039
1046
|
request.reject(err);
|
|
1040
1047
|
break;
|
|
1041
1048
|
}
|
|
@@ -1136,11 +1143,7 @@ export class ImapFlow extends EventEmitter {
|
|
|
1136
1143
|
this._socketTimeout =
|
|
1137
1144
|
this._socketTimeout ||
|
|
1138
1145
|
(() => {
|
|
1139
|
-
const timeoutError = () =>
|
|
1140
|
-
const err = new Error('Socket timeout');
|
|
1141
|
-
err.code = 'ETIMEOUT';
|
|
1142
|
-
return err;
|
|
1143
|
-
};
|
|
1146
|
+
const timeoutError = () => createImapError('Socket timeout', 'ETIMEOUT');
|
|
1144
1147
|
if (!this.usable || !this.socket || this.socket.destroyed) {
|
|
1145
1148
|
this.emitError(timeoutError());
|
|
1146
1149
|
return;
|
|
@@ -1324,7 +1327,10 @@ export class ImapFlow extends EventEmitter {
|
|
|
1324
1327
|
// to call deflate.flush() after each IMAP command to push all pending
|
|
1325
1328
|
// compressed bytes to the server immediately (IMAP is request-response).
|
|
1326
1329
|
const writeSocket = new PassThrough({
|
|
1327
|
-
highWaterMark: 64 * 1024 // 64KB buffer limit to prevent excessive memory usage
|
|
1330
|
+
highWaterMark: 64 * 1024, // 64KB buffer limit to prevent excessive memory usage
|
|
1331
|
+
// read() hands out each written chunk as is: in byte mode it joins whatever is
|
|
1332
|
+
// queued into a new Buffer, which copies every large literal once more
|
|
1333
|
+
readableObjectMode: true
|
|
1328
1334
|
});
|
|
1329
1335
|
this.writeSocket = writeSocket;
|
|
1330
1336
|
/* c8 ignore start */ // destroySoon override is never invoked by ImapFlow (close() calls destroy()); kept for stream API completeness
|
|
@@ -1452,9 +1458,7 @@ export class ImapFlow extends EventEmitter {
|
|
|
1452
1458
|
// socket; injection that still races in afterwards corrupts the TLS handshake and
|
|
1453
1459
|
// is rejected there instead (with a generic TLS error rather than STARTTLS_INJECTION).
|
|
1454
1460
|
const failSTARTTLSInjection = () => {
|
|
1455
|
-
let err =
|
|
1456
|
-
err.code = 'STARTTLS_INJECTION';
|
|
1457
|
-
err.tlsFailed = true;
|
|
1461
|
+
let err = createImapError('Server sent data after the STARTTLS response and before the TLS handshake; possible plaintext-injection attack', 'STARTTLS_INJECTION', { tlsFailed: true });
|
|
1458
1462
|
this.closeAfter();
|
|
1459
1463
|
return err;
|
|
1460
1464
|
};
|
|
@@ -1537,8 +1541,7 @@ export class ImapFlow extends EventEmitter {
|
|
|
1537
1541
|
socketPlain.on('error', settle);
|
|
1538
1542
|
/* c8 ignore start */ // UPGRADE_TIMEOUT is 10s; firing it deterministically would make the test suite hang
|
|
1539
1543
|
this.upgradeTimeout = setTimeout(() => {
|
|
1540
|
-
let err =
|
|
1541
|
-
err.code = 'UPGRADE_TIMEOUT';
|
|
1544
|
+
let err = createImapError('Failed to upgrade connection in required time', 'UPGRADE_TIMEOUT');
|
|
1542
1545
|
settle(err);
|
|
1543
1546
|
}, UPGRADE_TIMEOUT);
|
|
1544
1547
|
/* c8 ignore stop */
|
|
@@ -2042,9 +2045,7 @@ export class ImapFlow extends EventEmitter {
|
|
|
2042
2045
|
async connect() {
|
|
2043
2046
|
if (this._connectCalled) {
|
|
2044
2047
|
// Prevent re-using ImapFlow instances by allowing to call connect just once.
|
|
2045
|
-
|
|
2046
|
-
err.code = 'InstanceReused';
|
|
2047
|
-
throw err;
|
|
2048
|
+
throw createImapError('Can not re-use ImapFlow instance', 'InstanceReused');
|
|
2048
2049
|
}
|
|
2049
2050
|
this._connectCalled = true;
|
|
2050
2051
|
let closedError = () => this.createNoConnectionError(this.byeReason, { rejectedFrom: 'connect' });
|
|
@@ -2132,14 +2133,14 @@ export class ImapFlow extends EventEmitter {
|
|
|
2132
2133
|
detachEarlyErrorHandler(socket);
|
|
2133
2134
|
this.configureSocket(this.socket);
|
|
2134
2135
|
this.greetingTimeout = setTimeout(() => {
|
|
2135
|
-
let err =
|
|
2136
|
+
let err = createImapError(
|
|
2136
2137
|
/* c8 ignore next */ // the greeting-timeout test uses a plaintext socket; the secure-socket branch of this hint is not separately exercised
|
|
2137
|
-
`Failed to receive greeting from server in required time${!this.secureConnection ? '. Maybe should use TLS?' : ''}
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
};
|
|
2138
|
+
`Failed to receive greeting from server in required time${!this.secureConnection ? '. Maybe should use TLS?' : ''}`, 'GREETING_TIMEOUT', {
|
|
2139
|
+
details: {
|
|
2140
|
+
/* c8 ignore next */ // firing the timeout with the default (large) value would hang the suite, so only the explicit-option path is tested
|
|
2141
|
+
greetingTimeout: this.options.greetingTimeout || GREETING_TIMEOUT
|
|
2142
|
+
}
|
|
2143
|
+
});
|
|
2143
2144
|
this.log.error({ err, cid: this.id });
|
|
2144
2145
|
this.closeAfter();
|
|
2145
2146
|
reject(err);
|
|
@@ -2363,9 +2364,9 @@ export class ImapFlow extends EventEmitter {
|
|
|
2363
2364
|
let reject = this.initialReject;
|
|
2364
2365
|
this.initialResolve = false;
|
|
2365
2366
|
this.initialReject = false;
|
|
2366
|
-
let err =
|
|
2367
|
+
let err = createImapError('Unexpected close',
|
|
2367
2368
|
/* c8 ignore next */ // closing a pending connect over an already-secure socket (the TLS branch) is not separately exercised
|
|
2368
|
-
|
|
2369
|
+
this.secureConnection ? 'ClosedAfterConnectTLS' : 'ClosedAfterConnectText');
|
|
2369
2370
|
// Surface the server's BYE reason (e.g. "Too many connections") when the
|
|
2370
2371
|
// connection was closed by an untagged BYE, so the caller sees why.
|
|
2371
2372
|
if (this.byeReason) {
|
|
@@ -2876,7 +2877,7 @@ export class ImapFlow extends EventEmitter {
|
|
|
2876
2877
|
* Appends a new message to a mailbox
|
|
2877
2878
|
*
|
|
2878
2879
|
* @param path Mailbox path to upload the message to (unicode string). If value is an array then it is joined using current delimiter symbols. Namespace prefix is added automatically if required.
|
|
2879
|
-
* @param content RFC822 formatted email message
|
|
2880
|
+
* @param content RFC822 formatted email message, as a Buffer, Uint8Array or string
|
|
2880
2881
|
* @param flags an array of flags to be set for the uploaded message
|
|
2881
2882
|
* @param idate internal date to be set for the message
|
|
2882
2883
|
* @returns info about uploaded message
|
|
@@ -3178,7 +3179,8 @@ export class ImapFlow extends EventEmitter {
|
|
|
3178
3179
|
* // download body part nr '1.2' from latest message
|
|
3179
3180
|
* let download = await client.download('*', '1.2');
|
|
3180
3181
|
* if (download.content) {
|
|
3181
|
-
* download.
|
|
3182
|
+
* // pipeline() rejects when the download fails, a plain .pipe() would not report it
|
|
3183
|
+
* await stream.promises.pipeline(download.content, fs.createWriteStream(download.meta.filename));
|
|
3182
3184
|
* }
|
|
3183
3185
|
*/
|
|
3184
3186
|
async download(range, part, options) {
|
|
@@ -3515,9 +3517,7 @@ export class ImapFlow extends EventEmitter {
|
|
|
3515
3517
|
const idx = this.locks.indexOf(lockEntry);
|
|
3516
3518
|
if (idx !== -1) {
|
|
3517
3519
|
this.locks.splice(idx, 1);
|
|
3518
|
-
let err =
|
|
3519
|
-
err.code = 'LockTimeout';
|
|
3520
|
-
err.lockId = lockEntry.lockId;
|
|
3520
|
+
let err = createImapError('Timed out waiting for mailbox lock', 'LockTimeout', { lockId: lockEntry.lockId });
|
|
3521
3521
|
reject(err);
|
|
3522
3522
|
}
|
|
3523
3523
|
}, Number(lockOptions.acquireTimeout));
|
package/dist/esm/package-info.js
CHANGED
|
@@ -4,6 +4,7 @@ import net from 'node:net';
|
|
|
4
4
|
import tls from 'node:tls';
|
|
5
5
|
import { ConnectionDeadline } from './connection-deadline.js';
|
|
6
6
|
import { clearTimer } from './tools.js';
|
|
7
|
+
import { createImapError } from './errors.js';
|
|
7
8
|
// Cap the CONNECT response buffered before the header terminator, so a proxy that never sends
|
|
8
9
|
// \r\n\r\n cannot grow memory without bound.
|
|
9
10
|
const MAX_RESPONSE_HEADER_BYTES = 64 * 1024;
|
|
@@ -27,11 +28,7 @@ const redactUrl = (proxyUrl) => {
|
|
|
27
28
|
}
|
|
28
29
|
return redacted.href;
|
|
29
30
|
};
|
|
30
|
-
const proxyError = (message, code) =>
|
|
31
|
-
let err = new Error(message);
|
|
32
|
-
err.code = code || 'ProxyError';
|
|
33
|
-
return err;
|
|
34
|
-
};
|
|
31
|
+
const proxyError = (message, code) => createImapError(message, code || 'ProxyError');
|
|
35
32
|
// URL userinfo is percent-encoded, so it has to be decoded before it can be used as credentials.
|
|
36
33
|
// A password containing a bare '%' is not valid percent-encoding and makes decodeURIComponent
|
|
37
34
|
// throw, so such values are used as they came in rather than failing the connection.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/* eslint no-control-regex:0 */
|
|
2
2
|
import { formatDate, formatFlag, toValidDate, isRev2Active } from './tools.js';
|
|
3
|
+
import { createImapError } from './errors.js';
|
|
3
4
|
// Matches any character outside the ASCII range
|
|
4
5
|
const UNICODE_PATTERN = /[^\x00-\x7F]/;
|
|
5
6
|
/**
|
|
@@ -101,9 +102,7 @@ let processDateField = (attributes, term, value) => {
|
|
|
101
102
|
* @param message - Error message
|
|
102
103
|
*/
|
|
103
104
|
let fail = (code, message) => {
|
|
104
|
-
|
|
105
|
-
error.code = code;
|
|
106
|
-
throw error;
|
|
105
|
+
throw createImapError(message, code);
|
|
107
106
|
};
|
|
108
107
|
/**
|
|
109
108
|
* Checks whether any search value was compiled into a literal, which only
|
package/dist/esm/tools.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Transform } from 'node:stream';
|
|
2
2
|
import type { ImapFlow } from './imap-flow.js';
|
|
3
|
-
import { type ConnectionErrorSite, type ImapFlowError } from './errors.js';
|
|
3
|
+
import { ImapFlowErrorCode, type ConnectionErrorSite, type ImapFlowError } from './errors.js';
|
|
4
4
|
import type { ImapAttributeList, ImapAttributeNode, ImapResponse } from './handler/types.js';
|
|
5
5
|
import type { FetchMessageObject, ListResponse, ListTreeResponse, MailboxObject, MessageEnvelopeObject, MessageStructureObject, ImapFlowEvents, StatusQuery } from './types.js';
|
|
6
6
|
export { AuthenticationFailure } from './errors.js';
|
|
@@ -33,7 +33,7 @@ export type CharsetDecoder = Transform & {
|
|
|
33
33
|
* @param meta - Fields to stamp on the error
|
|
34
34
|
* @returns The stamped error
|
|
35
35
|
*/
|
|
36
|
-
export declare function buildConnectionError(cid: string, code:
|
|
36
|
+
export declare function buildConnectionError(cid: string, code: ImapFlowErrorCode, message: string, meta?: ConnectionErrorSite | undefined): ImapFlowError;
|
|
37
37
|
/**
|
|
38
38
|
* Re-stamps an existing connection error for a different rejection site.
|
|
39
39
|
*
|
package/dist/esm/tools.js
CHANGED
|
@@ -6,7 +6,7 @@ import imapFormalSyntax from './handler/imap-formal-syntax.js';
|
|
|
6
6
|
import { createHash } from 'node:crypto';
|
|
7
7
|
import { JPDecoder } from './jp-decoder.js';
|
|
8
8
|
import iconv from 'iconv-lite';
|
|
9
|
-
import { ImapFlowErrorCode } from './errors.js';
|
|
9
|
+
import { createImapError, ImapFlowErrorCode } from './errors.js';
|
|
10
10
|
export { AuthenticationFailure } from './errors.js';
|
|
11
11
|
const FLAG_COLORS = ['red', 'orange', 'yellow', 'green', 'blue', 'purple', 'grey'];
|
|
12
12
|
// Error codes that only mean the connection is no longer usable. See logConnectionError().
|
|
@@ -88,13 +88,7 @@ const CONNECTION_ERROR_SITE_KEYS = ['rejectedFrom', 'command', 'path'];
|
|
|
88
88
|
* @returns The stamped error
|
|
89
89
|
*/
|
|
90
90
|
export function buildConnectionError(cid, code, message, meta) {
|
|
91
|
-
|
|
92
|
-
error.code = code;
|
|
93
|
-
error.cid = cid;
|
|
94
|
-
if (meta) {
|
|
95
|
-
Object.assign(error, meta);
|
|
96
|
-
}
|
|
97
|
-
return error;
|
|
91
|
+
return createImapError(message, code, { cid, ...meta });
|
|
98
92
|
}
|
|
99
93
|
/**
|
|
100
94
|
* Re-stamps an existing connection error for a different rejection site.
|
|
@@ -116,6 +110,7 @@ export function buildConnectionError(cid, code, message, meta) {
|
|
|
116
110
|
* @returns A separate error describing the same failure at the new site
|
|
117
111
|
*/
|
|
118
112
|
export function restampConnectionError(err, meta) {
|
|
113
|
+
// the original code is carried over unchanged, whatever its origin
|
|
119
114
|
let error = buildConnectionError(err.cid, err.code, err.message, err);
|
|
120
115
|
for (let key of CONNECTION_ERROR_SITE_KEYS) {
|
|
121
116
|
delete error[key];
|
|
@@ -469,12 +464,13 @@ export function getTextValues(attributes) {
|
|
|
469
464
|
* @param response - Parsed IMAP server response
|
|
470
465
|
* @returns Compiled response text, or false if no response
|
|
471
466
|
*/
|
|
467
|
+
// Async although nothing in it awaits any more: `imapflow/lib/tools` deep imports may chain on the promise
|
|
472
468
|
export async function getErrorText(response) {
|
|
473
469
|
if (!response) {
|
|
474
470
|
return false;
|
|
475
471
|
}
|
|
476
472
|
try {
|
|
477
|
-
return
|
|
473
|
+
return compiler(response).toString();
|
|
478
474
|
}
|
|
479
475
|
catch {
|
|
480
476
|
// The wire encoder refuses values that cannot be expressed as a valid IMAP
|
|
@@ -483,7 +479,7 @@ export async function getErrorText(response) {
|
|
|
483
479
|
// tolerates stray bytes inside an OK/NO/BAD atom, and those bytes then have
|
|
484
480
|
// no valid re-encoding. This text is diagnostic, so fall back to the logging
|
|
485
481
|
// encoder rather than replacing the server's error with an encoding failure.
|
|
486
|
-
return
|
|
482
|
+
return compiler(response, { isLogging: true }).toString();
|
|
487
483
|
}
|
|
488
484
|
}
|
|
489
485
|
/**
|
|
@@ -697,6 +693,7 @@ export function mergeFetchRows(rows) {
|
|
|
697
693
|
* @param connection - Connection the response arrived on, decodes Gmail labels like mailbox names
|
|
698
694
|
* @returns Formatted message object with properties like seq, uid, flags, envelope, etc.
|
|
699
695
|
*/
|
|
696
|
+
// Async although nothing in it awaits any more: `imapflow/lib/tools` deep imports may chain on the promise
|
|
700
697
|
export async function formatMessageResponse(untagged, mailbox, idHashAlgorithm, connection) {
|
|
701
698
|
let map = {};
|
|
702
699
|
// The sequence number indexes into mailbox state, so an unusable one is dropped rather
|
|
@@ -719,9 +716,9 @@ export async function formatMessageResponse(untagged, mailbox, idHashAlgorithm,
|
|
|
719
716
|
let attribute = attributes[i];
|
|
720
717
|
if (i % 2 === 0) {
|
|
721
718
|
origin = false;
|
|
722
|
-
key =
|
|
719
|
+
key = compiler({
|
|
723
720
|
attributes: [attribute]
|
|
724
|
-
})
|
|
721
|
+
})
|
|
725
722
|
.toString()
|
|
726
723
|
.toLowerCase()
|
|
727
724
|
.replace(/<(\d+)(\.\d+)?>$/, (match, start) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "imapflow",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.1",
|
|
4
4
|
"description": "IMAP Client for Node",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/cjs/imap-flow.js",
|
|
@@ -75,19 +75,19 @@
|
|
|
75
75
|
"eslint": "10.12.0",
|
|
76
76
|
"eslint-config-prettier": "10.1.8",
|
|
77
77
|
"globals": "17.13.0",
|
|
78
|
-
"imapkit": "5.0.
|
|
79
|
-
"prettier": "3.9.
|
|
78
|
+
"imapkit": "5.0.1",
|
|
79
|
+
"prettier": "3.9.10",
|
|
80
80
|
"tsx": "4.23.15",
|
|
81
81
|
"types-node-legacy": "npm:@types/node@20.0.0",
|
|
82
82
|
"typescript": "6.0.3",
|
|
83
|
-
"typescript-eslint": "8.71.
|
|
84
|
-
"wrangler": "4.
|
|
83
|
+
"typescript-eslint": "8.71.2",
|
|
84
|
+
"wrangler": "4.150.0"
|
|
85
85
|
},
|
|
86
86
|
"dependencies": {
|
|
87
87
|
"@zone-eu/mailsplit": "5.4.20",
|
|
88
88
|
"encoding-japanese": "2.4.0",
|
|
89
89
|
"iconv-lite": "0.7.3",
|
|
90
|
-
"libbase64": "1.3.
|
|
90
|
+
"libbase64": "1.3.3",
|
|
91
91
|
"libmime": "5.4.7",
|
|
92
92
|
"libqp": "2.1.2",
|
|
93
93
|
"pino": "10.3.1",
|