imapflow 1.0.108 → 1.0.110
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 +8 -0
- package/lib/handler/token-parser.js +3 -1
- package/lib/imap-flow.js +132 -0
- package/lib/types.d.ts +18 -0
- package/package.json +7 -7
- package/test/imap-parser-test.js +29 -0
package/CHANGELOG.md
CHANGED
|
@@ -271,7 +271,8 @@ class TokenParser {
|
|
|
271
271
|
// Any ATOM supported char starts a new Atom sequence, otherwise throw an error
|
|
272
272
|
// Allow \ as the first char for atom to support system flags
|
|
273
273
|
// Allow % to support LIST '' %
|
|
274
|
-
|
|
274
|
+
// Allow 8bit characters (presumably unicode)
|
|
275
|
+
if (imapFormalSyntax['ATOM-CHAR']().indexOf(chr) < 0 && chr !== '\\' && chr !== '%' && chr.charCodeAt(0) < 0x80) {
|
|
275
276
|
throw new Error(`Unexpected char at position ${this.pos + i} [E10: ${JSON.stringify(chr)}]`);
|
|
276
277
|
}
|
|
277
278
|
|
|
@@ -336,6 +337,7 @@ class TokenParser {
|
|
|
336
337
|
// if the char is not ATOM compatible, throw. Allow \* as an exception
|
|
337
338
|
if (
|
|
338
339
|
imapFormalSyntax['ATOM-CHAR']().indexOf(chr) < 0 &&
|
|
340
|
+
chr.charCodeAt(0) < 0x80 && // allow 8bit (presumably unicode) bytes
|
|
339
341
|
chr !== ']' &&
|
|
340
342
|
!(chr === '*' && this.currentNode.value === '\\') &&
|
|
341
343
|
(!this.parent || !this.parent.command || !['NO', 'BAD', 'OK'].includes(this.parent.command))
|
package/lib/imap-flow.js
CHANGED
|
@@ -2163,6 +2163,7 @@ class ImapFlow extends EventEmitter {
|
|
|
2163
2163
|
* @typedef {Object} DownloadObject
|
|
2164
2164
|
* @global
|
|
2165
2165
|
* @property {Object} meta content metadata
|
|
2166
|
+
* @property {number} meta.expectedSize The fetch response size
|
|
2166
2167
|
* @property {String} meta.contentType Content-Type of the streamed file. If part was not set then this value is "message/rfc822"
|
|
2167
2168
|
* @property {String} [meta.charset] Charset of the body part. Text parts are automaticaly converted to UTF-8, attachments are kept as is
|
|
2168
2169
|
* @property {String} [meta.disposition] Content-Disposition of the streamed file
|
|
@@ -2451,6 +2452,137 @@ class ImapFlow extends EventEmitter {
|
|
|
2451
2452
|
};
|
|
2452
2453
|
}
|
|
2453
2454
|
|
|
2455
|
+
/**
|
|
2456
|
+
* Fetch multiple attachments as Buffer values
|
|
2457
|
+
*
|
|
2458
|
+
* @param {SequenceString} range UID or sequence number for the message to fetch
|
|
2459
|
+
* @param {String} parts A list of bodystructure parts
|
|
2460
|
+
* @param {Object} [options]
|
|
2461
|
+
* @param {Boolean} [options.uid] If `true` then uses UID number instead of sequence number for `range`
|
|
2462
|
+
* @returns {Promise<Object>} Download data object
|
|
2463
|
+
*
|
|
2464
|
+
* @example
|
|
2465
|
+
* let mailbox = await client.mailboxOpen('INBOX');
|
|
2466
|
+
* // download body part nr '1.2' from latest message
|
|
2467
|
+
* let response = await client.downloadMany('*', ['2', '3]);
|
|
2468
|
+
* process.stdout.write(response[2].content)
|
|
2469
|
+
* process.stdout.write(response[3].content)
|
|
2470
|
+
*/
|
|
2471
|
+
async downloadMany(range, parts, options) {
|
|
2472
|
+
if (!this.mailbox) {
|
|
2473
|
+
// no mailbox selected, nothing to do
|
|
2474
|
+
return {};
|
|
2475
|
+
}
|
|
2476
|
+
|
|
2477
|
+
options = Object.assign(
|
|
2478
|
+
{
|
|
2479
|
+
chunkSize: 64 * 1024,
|
|
2480
|
+
maxBytes: Infinity
|
|
2481
|
+
},
|
|
2482
|
+
options || {}
|
|
2483
|
+
);
|
|
2484
|
+
|
|
2485
|
+
let query = { bodyParts: [] };
|
|
2486
|
+
|
|
2487
|
+
for (let part of parts) {
|
|
2488
|
+
query.bodyParts.push(part + '.mime');
|
|
2489
|
+
query.bodyParts.push(part);
|
|
2490
|
+
}
|
|
2491
|
+
|
|
2492
|
+
let response = await this.fetchOne(range, query, options);
|
|
2493
|
+
|
|
2494
|
+
if (!response || !response.bodyParts) {
|
|
2495
|
+
return { response: false };
|
|
2496
|
+
}
|
|
2497
|
+
|
|
2498
|
+
let data = {};
|
|
2499
|
+
|
|
2500
|
+
for (let [part, content] of response.bodyParts) {
|
|
2501
|
+
let keyParts = part.split('.');
|
|
2502
|
+
if (keyParts.length === 1) {
|
|
2503
|
+
// content
|
|
2504
|
+
let key = keyParts[0];
|
|
2505
|
+
if (!data[key]) {
|
|
2506
|
+
data[key] = { content };
|
|
2507
|
+
} else {
|
|
2508
|
+
data[key].content = content;
|
|
2509
|
+
}
|
|
2510
|
+
} else if (keyParts.length === 2) {
|
|
2511
|
+
// header
|
|
2512
|
+
let key = keyParts[0];
|
|
2513
|
+
if (!data[key]) {
|
|
2514
|
+
data[key] = {};
|
|
2515
|
+
}
|
|
2516
|
+
if (!data[key].meta) {
|
|
2517
|
+
data[key].meta = {};
|
|
2518
|
+
}
|
|
2519
|
+
|
|
2520
|
+
let headers = new Headers(content);
|
|
2521
|
+
let contentType = libmime.parseHeaderValue(headers.getFirst('Content-Type'));
|
|
2522
|
+
let transferEncoding = libmime.parseHeaderValue(headers.getFirst('Content-Transfer-Encoding'));
|
|
2523
|
+
let disposition = libmime.parseHeaderValue(headers.getFirst('Content-Disposition'));
|
|
2524
|
+
|
|
2525
|
+
if (contentType.value.toLowerCase().trim()) {
|
|
2526
|
+
data[key].meta.contentType = contentType.value.toLowerCase().trim();
|
|
2527
|
+
}
|
|
2528
|
+
|
|
2529
|
+
if (contentType.params.charset) {
|
|
2530
|
+
data[key].meta.charset = contentType.params.charset.toLowerCase().trim();
|
|
2531
|
+
}
|
|
2532
|
+
|
|
2533
|
+
if (transferEncoding.value) {
|
|
2534
|
+
data[key].meta.encoding = transferEncoding.value
|
|
2535
|
+
.replace(/\(.*\)/g, '')
|
|
2536
|
+
.toLowerCase()
|
|
2537
|
+
.trim();
|
|
2538
|
+
}
|
|
2539
|
+
|
|
2540
|
+
if (disposition.value) {
|
|
2541
|
+
data[key].meta.disposition = disposition.value.toLowerCase().trim() || false;
|
|
2542
|
+
try {
|
|
2543
|
+
data[key].meta.disposition = libmime.decodeWords(data[key].meta.disposition);
|
|
2544
|
+
} catch (err) {
|
|
2545
|
+
// failed to parse disposition, keep as is (most probably an unknown charset is used)
|
|
2546
|
+
}
|
|
2547
|
+
}
|
|
2548
|
+
|
|
2549
|
+
if (contentType.params.format && contentType.params.format.toLowerCase().trim() === 'flowed') {
|
|
2550
|
+
data[key].meta.flowed = true;
|
|
2551
|
+
if (contentType.params.delsp && contentType.params.delsp.toLowerCase().trim() === 'yes') {
|
|
2552
|
+
data[key].meta.delSp = true;
|
|
2553
|
+
}
|
|
2554
|
+
}
|
|
2555
|
+
|
|
2556
|
+
let filename = disposition.params.filename || contentType.params.name || false;
|
|
2557
|
+
if (filename) {
|
|
2558
|
+
try {
|
|
2559
|
+
filename = this.libmime.decodeWords(filename);
|
|
2560
|
+
} catch (err) {
|
|
2561
|
+
// failed to parse filename, keep as is (most probably an unknown charset is used)
|
|
2562
|
+
}
|
|
2563
|
+
data[key].meta.filename = filename;
|
|
2564
|
+
}
|
|
2565
|
+
}
|
|
2566
|
+
}
|
|
2567
|
+
|
|
2568
|
+
for (let part of Object.keys(data)) {
|
|
2569
|
+
let meta = data[part].meta;
|
|
2570
|
+
|
|
2571
|
+
switch (meta.encoding) {
|
|
2572
|
+
case 'base64':
|
|
2573
|
+
data[part].content = data[part].content ? libbase64.decode(data[part].content.toString()) : null;
|
|
2574
|
+
break;
|
|
2575
|
+
case 'quoted-printable':
|
|
2576
|
+
data[part].content = data[part].content ? libqp.decode(data[part].content.toString()) : null;
|
|
2577
|
+
break;
|
|
2578
|
+
default:
|
|
2579
|
+
// keep as is, already a buffer
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2582
|
+
|
|
2583
|
+
return data;
|
|
2584
|
+
}
|
|
2585
|
+
|
|
2454
2586
|
async run(command, ...args) {
|
|
2455
2587
|
command = command.toUpperCase();
|
|
2456
2588
|
if (!this.commands.has(command)) {
|
package/lib/types.d.ts
CHANGED
|
@@ -478,6 +478,22 @@ declare module "imapflow" {
|
|
|
478
478
|
maxBytes?: number;
|
|
479
479
|
chunkSize?: number;
|
|
480
480
|
}): Promise<DownloadObject>;
|
|
481
|
+
/**
|
|
482
|
+
* Fetch multiple attachments as Buffer values
|
|
483
|
+
* @example
|
|
484
|
+
* let mailbox = await client.mailboxOpen('INBOX');
|
|
485
|
+
* // download body part nr '1.2' from latest message
|
|
486
|
+
* let response = await client.downloadMany('*', ['2', '3]);
|
|
487
|
+
* process.stdout.write(response[2].content)
|
|
488
|
+
* process.stdout.write(response[3].content)
|
|
489
|
+
* @param range - UID or sequence number for the message to fetch
|
|
490
|
+
* @param parts - A list of bodystructure parts
|
|
491
|
+
* @param [options.uid] - If `true` then uses UID number instead of sequence number for `range`
|
|
492
|
+
* @returns Download data object
|
|
493
|
+
*/
|
|
494
|
+
downloadMany(range: SequenceString, parts: string, options?: {
|
|
495
|
+
uid?: boolean;
|
|
496
|
+
}): Promise<object>;
|
|
481
497
|
/**
|
|
482
498
|
* Opens a mailbox if not already open and returns a lock. Next call to `getMailboxLock()` is queued
|
|
483
499
|
* until previous lock is released. This is suggested over {@link module:imapflow~ImapFlow#mailboxOpen|mailboxOpen()} as
|
|
@@ -925,6 +941,7 @@ declare type FetchMessageObject = {
|
|
|
925
941
|
|
|
926
942
|
/**
|
|
927
943
|
* @property meta - content metadata
|
|
944
|
+
* @property meta.expectedSize - The fetch response size
|
|
928
945
|
* @property meta.contentType - Content-Type of the streamed file. If part was not set then this value is "message/rfc822"
|
|
929
946
|
* @property [meta.charset] - Charset of the body part. Text parts are automaticaly converted to UTF-8, attachments are kept as is
|
|
930
947
|
* @property [meta.disposition] - Content-Disposition of the streamed file
|
|
@@ -933,6 +950,7 @@ declare type FetchMessageObject = {
|
|
|
933
950
|
*/
|
|
934
951
|
declare type DownloadObject = {
|
|
935
952
|
meta: {
|
|
953
|
+
expectedSize: number;
|
|
936
954
|
contentType: string;
|
|
937
955
|
charset?: string;
|
|
938
956
|
disposition?: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "imapflow",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.110",
|
|
4
4
|
"description": "IMAP Client for Node",
|
|
5
5
|
"main": "./lib/imap-flow.js",
|
|
6
6
|
"scripts": {
|
|
@@ -30,10 +30,10 @@
|
|
|
30
30
|
"@babel/eslint-parser": "7.19.1",
|
|
31
31
|
"@babel/eslint-plugin": "7.19.1",
|
|
32
32
|
"@babel/plugin-syntax-class-properties": "7.12.13",
|
|
33
|
-
"@babel/preset-env": "7.19.
|
|
34
|
-
"@types/node": "18.
|
|
33
|
+
"@babel/preset-env": "7.19.3",
|
|
34
|
+
"@types/node": "18.8.3",
|
|
35
35
|
"braintree-jsdoc-template": "3.3.0",
|
|
36
|
-
"eslint": "8.
|
|
36
|
+
"eslint": "8.25.0",
|
|
37
37
|
"eslint-config-nodemailer": "1.2.0",
|
|
38
38
|
"eslint-config-prettier": "8.5.0",
|
|
39
39
|
"grunt": "1.5.3",
|
|
@@ -51,8 +51,8 @@
|
|
|
51
51
|
"libmime": "5.1.0",
|
|
52
52
|
"libqp": "1.1.0",
|
|
53
53
|
"mailsplit": "5.3.2",
|
|
54
|
-
"nodemailer": "6.
|
|
55
|
-
"pino": "8.6.
|
|
56
|
-
"socks": "2.7.
|
|
54
|
+
"nodemailer": "6.8.0",
|
|
55
|
+
"pino": "8.6.1",
|
|
56
|
+
"socks": "2.7.1"
|
|
57
57
|
}
|
|
58
58
|
}
|
package/test/imap-parser-test.js
CHANGED
|
@@ -941,3 +941,32 @@ module.exports['IMAP Parser, single quote in atom'] = test =>
|
|
|
941
941
|
{ type: 'ATOM', value: "'a" }
|
|
942
942
|
])
|
|
943
943
|
);
|
|
944
|
+
|
|
945
|
+
module.exports['IMAP Parser, unicode status 1'] = test =>
|
|
946
|
+
asyncWrapper(test, async test =>
|
|
947
|
+
test.deepEqual((await parser('* STATUS Segregator/Społeczności (MESSAGES 0 UIDNEXT 1 UIDVALIDITY 1)')).attributes, [
|
|
948
|
+
{ type: 'ATOM', value: 'Segregator/Społeczności' },
|
|
949
|
+
[
|
|
950
|
+
{ type: 'ATOM', value: 'MESSAGES' },
|
|
951
|
+
{ type: 'ATOM', value: '0' },
|
|
952
|
+
{ type: 'ATOM', value: 'UIDNEXT' },
|
|
953
|
+
{ type: 'ATOM', value: '1' },
|
|
954
|
+
{ type: 'ATOM', value: 'UIDVALIDITY' },
|
|
955
|
+
{ type: 'ATOM', value: '1' }
|
|
956
|
+
]
|
|
957
|
+
])
|
|
958
|
+
);
|
|
959
|
+
module.exports['IMAP Parser, unicode status 12'] = test =>
|
|
960
|
+
asyncWrapper(test, async test =>
|
|
961
|
+
test.deepEqual((await parser('* STATUS Šegregator/Społeczności (MESSAGES 0 UIDNEXT 1 UIDVALIDITY 1)')).attributes, [
|
|
962
|
+
{ type: 'ATOM', value: 'Šegregator/Społeczności' },
|
|
963
|
+
[
|
|
964
|
+
{ type: 'ATOM', value: 'MESSAGES' },
|
|
965
|
+
{ type: 'ATOM', value: '0' },
|
|
966
|
+
{ type: 'ATOM', value: 'UIDNEXT' },
|
|
967
|
+
{ type: 'ATOM', value: '1' },
|
|
968
|
+
{ type: 'ATOM', value: 'UIDVALIDITY' },
|
|
969
|
+
{ type: 'ATOM', value: '1' }
|
|
970
|
+
]
|
|
971
|
+
])
|
|
972
|
+
);
|