imapflow 1.0.90 → 1.0.91
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 +6 -0
- package/lib/commands/create.js +2 -2
- package/lib/commands/delete.js +2 -2
- package/lib/commands/fetch.js +4 -2
- package/lib/commands/list.js +44 -60
- package/lib/commands/quota.js +2 -2
- package/lib/commands/rename.js +3 -3
- package/lib/commands/select.js +93 -89
- package/lib/commands/status.js +2 -2
- package/lib/commands/subscribe.js +2 -2
- package/lib/commands/unsubscribe.js +2 -2
- package/lib/handler/imap-formal-syntax.js +13 -13
- package/lib/handler/token-parser.js +5 -1
- package/lib/imap-flow.js +24 -4
- package/lib/tools.js +29 -11
- package/lib/types.d.ts +9 -3
- package/package.json +12 -10
package/CHANGELOG.md
CHANGED
package/lib/commands/create.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { normalizePath, getStatusCode, getErrorText } = require('../tools.js');
|
|
3
|
+
const { encodePath, normalizePath, getStatusCode, getErrorText } = require('../tools.js');
|
|
4
4
|
|
|
5
5
|
// Creates a new mailbox
|
|
6
6
|
module.exports = async (connection, path) => {
|
|
@@ -16,7 +16,7 @@ module.exports = async (connection, path) => {
|
|
|
16
16
|
let map = {
|
|
17
17
|
path
|
|
18
18
|
};
|
|
19
|
-
response = await connection.exec('CREATE', [{ type: 'ATOM', value: path }]);
|
|
19
|
+
response = await connection.exec('CREATE', [{ type: 'ATOM', value: encodePath(connection, path) }]);
|
|
20
20
|
|
|
21
21
|
let section =
|
|
22
22
|
response.response.attributes &&
|
package/lib/commands/delete.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { normalizePath, getStatusCode, getErrorText } = require('../tools.js');
|
|
3
|
+
const { encodePath, normalizePath, getStatusCode, getErrorText } = require('../tools.js');
|
|
4
4
|
|
|
5
5
|
// Deletes an existing mailbox
|
|
6
6
|
module.exports = async (connection, path) => {
|
|
@@ -20,7 +20,7 @@ module.exports = async (connection, path) => {
|
|
|
20
20
|
let map = {
|
|
21
21
|
path
|
|
22
22
|
};
|
|
23
|
-
response = await connection.exec('DELETE', [{ type: 'ATOM', value: path }]);
|
|
23
|
+
response = await connection.exec('DELETE', [{ type: 'ATOM', value: encodePath(connection, path) }]);
|
|
24
24
|
response.next();
|
|
25
25
|
return map;
|
|
26
26
|
} catch (err) {
|
package/lib/commands/fetch.js
CHANGED
|
@@ -18,6 +18,8 @@ module.exports = async (connection, range, query, options) => {
|
|
|
18
18
|
list: []
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
+
const commandKey = 'BODY';
|
|
22
|
+
|
|
21
23
|
let response;
|
|
22
24
|
try {
|
|
23
25
|
let attributes = [{ type: 'SEQUENCE', value: (range || '*').toString() }];
|
|
@@ -27,7 +29,7 @@ module.exports = async (connection, range, query, options) => {
|
|
|
27
29
|
let setBodyPeek = (attributes, partial) => {
|
|
28
30
|
let bodyPeek = {
|
|
29
31
|
type: 'ATOM',
|
|
30
|
-
value:
|
|
32
|
+
value: `${commandKey}.PEEK`,
|
|
31
33
|
section: [],
|
|
32
34
|
partial
|
|
33
35
|
};
|
|
@@ -61,7 +63,7 @@ module.exports = async (connection, range, query, options) => {
|
|
|
61
63
|
partial.push(Number(query.source.maxLength));
|
|
62
64
|
}
|
|
63
65
|
}
|
|
64
|
-
queryStructure.push({ type: 'ATOM', value:
|
|
66
|
+
queryStructure.push({ type: 'ATOM', value: `${commandKey}.PEEK`, section: [], partial });
|
|
65
67
|
}
|
|
66
68
|
|
|
67
69
|
// if possible, always request for unique email id
|
package/lib/commands/list.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { normalizePath } = require('../tools.js');
|
|
3
|
+
const { decodePath, encodePath, normalizePath } = require('../tools.js');
|
|
4
4
|
const { specialUse } = require('../special-use');
|
|
5
|
-
const iconv = require('iconv-lite');
|
|
6
5
|
|
|
7
6
|
// Lists mailboxes from server
|
|
8
7
|
module.exports = async (connection, reference, mailbox) => {
|
|
@@ -14,7 +13,7 @@ module.exports = async (connection, reference, mailbox) => {
|
|
|
14
13
|
let entries = [];
|
|
15
14
|
|
|
16
15
|
let runList = async (reference, mailbox) => {
|
|
17
|
-
response = await connection.exec(listCommand, [reference, mailbox], {
|
|
16
|
+
response = await connection.exec(listCommand, [encodePath(connection, reference), encodePath(connection, mailbox)], {
|
|
18
17
|
untagged: {
|
|
19
18
|
[listCommand]: async untagged => {
|
|
20
19
|
if (!untagged.attributes || !untagged.attributes.length) {
|
|
@@ -22,7 +21,8 @@ module.exports = async (connection, reference, mailbox) => {
|
|
|
22
21
|
}
|
|
23
22
|
|
|
24
23
|
let entry = {
|
|
25
|
-
path: (untagged.attributes[2] && untagged.attributes[2].value) || '',
|
|
24
|
+
path: normalizePath(connection, decodePath(connection, (untagged.attributes[2] && untagged.attributes[2].value) || '')),
|
|
25
|
+
pathAsListed: (untagged.attributes[2] && untagged.attributes[2].value) || '',
|
|
26
26
|
flags: new Set(untagged.attributes[0].map(entry => entry.value)),
|
|
27
27
|
delimiter: untagged.attributes[1] && untagged.attributes[1].value,
|
|
28
28
|
listed: true
|
|
@@ -45,19 +45,8 @@ module.exports = async (connection, reference, mailbox) => {
|
|
|
45
45
|
entry.path = entry.path.slice(1);
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
entry.
|
|
49
|
-
|
|
50
|
-
try {
|
|
51
|
-
return iconv.decode(Buffer.from(folder), 'utf-7-imap').toString();
|
|
52
|
-
} catch (err) {
|
|
53
|
-
return folder; // keep as is
|
|
54
|
-
}
|
|
55
|
-
} else {
|
|
56
|
-
return folder; // keep as is
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
entry.path = normalizePath(connection, entry.path);
|
|
48
|
+
entry.parentPath = entry.delimiter && entry.path ? entry.path.substr(0, entry.path.lastIndexOf(entry.delimiter)) : '';
|
|
49
|
+
entry.parent = entry.delimiter ? entry.path.split(entry.delimiter) : [entry.path];
|
|
61
50
|
entry.name = entry.parent.pop();
|
|
62
51
|
|
|
63
52
|
let specialUseFlag = specialUse(connection.capabilities.has('XLIST') || connection.capabilities.has('SPECIAL-USE'), entry);
|
|
@@ -80,59 +69,54 @@ module.exports = async (connection, reference, mailbox) => {
|
|
|
80
69
|
await runList('', 'INBOX');
|
|
81
70
|
}
|
|
82
71
|
|
|
83
|
-
response = await connection.exec(
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
delimiter: untagged.attributes[1] && untagged.attributes[1].value,
|
|
93
|
-
path: (untagged.attributes[2] && untagged.attributes[2].value) || '',
|
|
94
|
-
subscribed: true
|
|
95
|
-
};
|
|
72
|
+
response = await connection.exec(
|
|
73
|
+
'LSUB',
|
|
74
|
+
[encodePath(connection, normalizePath(connection, reference || '')), encodePath(connection, normalizePath(connection, mailbox || '', true))],
|
|
75
|
+
{
|
|
76
|
+
untagged: {
|
|
77
|
+
LSUB: async untagged => {
|
|
78
|
+
if (!untagged.attributes || !untagged.attributes.length) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
96
81
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
82
|
+
let entry = {
|
|
83
|
+
path: normalizePath(connection, decodePath(connection, (untagged.attributes[2] && untagged.attributes[2].value) || '')),
|
|
84
|
+
pathAsListed: (untagged.attributes[2] && untagged.attributes[2].value) || '',
|
|
85
|
+
flags: new Set(untagged.attributes[0].map(entry => entry.value)),
|
|
86
|
+
delimiter: untagged.attributes[1] && untagged.attributes[1].value,
|
|
87
|
+
subscribed: true
|
|
88
|
+
};
|
|
100
89
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
90
|
+
if (entry.path.toUpperCase() === 'INBOX') {
|
|
91
|
+
entry.specialUse = '\\Inbox';
|
|
92
|
+
}
|
|
104
93
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
try {
|
|
108
|
-
return iconv.decode(Buffer.from(folder), 'utf-7-imap').toString();
|
|
109
|
-
} catch (err) {
|
|
110
|
-
return folder; // keep as is
|
|
111
|
-
}
|
|
112
|
-
} else {
|
|
113
|
-
return folder; // keep as is
|
|
94
|
+
if (entry.delimiter && entry.path.charAt(0) === entry.delimiter) {
|
|
95
|
+
entry.path = entry.path.slice(1);
|
|
114
96
|
}
|
|
115
|
-
});
|
|
116
97
|
|
|
117
|
-
|
|
98
|
+
entry.parentPath = entry.delimiter && entry.path ? entry.path.substr(0, entry.path.lastIndexOf(entry.delimiter)) : '';
|
|
99
|
+
entry.parent = entry.delimiter ? entry.path.split(entry.delimiter) : [entry.path];
|
|
100
|
+
entry.name = entry.parent.pop();
|
|
118
101
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
102
|
+
let existing = entries.find(existing => existing.path === entry.path);
|
|
103
|
+
if (existing) {
|
|
104
|
+
existing.subscribed = true;
|
|
105
|
+
entry.flags.forEach(flag => existing.flags.add(flag));
|
|
106
|
+
} else {
|
|
107
|
+
// ignore non-listed folders
|
|
108
|
+
/*
|
|
109
|
+
let specialUseFlag = specialUse(connection.capabilities.has('XLIST') || connection.capabilities.has('SPECIAL-USE'), entry);
|
|
110
|
+
if (specialUseFlag && !flagsSeen.has(specialUseFlag)) {
|
|
111
|
+
entry.specialUse = specialUseFlag;
|
|
112
|
+
}
|
|
113
|
+
entries.push(entry);
|
|
114
|
+
*/
|
|
129
115
|
}
|
|
130
|
-
entries.push(entry);
|
|
131
|
-
*/
|
|
132
116
|
}
|
|
133
117
|
}
|
|
134
118
|
}
|
|
135
|
-
|
|
119
|
+
);
|
|
136
120
|
response.next();
|
|
137
121
|
|
|
138
122
|
let inboxEntry = entries.find(entry => entry.specialUse === '\\Inbox');
|
package/lib/commands/quota.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { getStatusCode, normalizePath, getErrorText } = require('../tools.js');
|
|
3
|
+
const { encodePath, getStatusCode, normalizePath, getErrorText } = require('../tools.js');
|
|
4
4
|
|
|
5
5
|
// Requests quota information for a mailbox
|
|
6
6
|
module.exports = async (connection, path) => {
|
|
@@ -63,7 +63,7 @@ module.exports = async (connection, path) => {
|
|
|
63
63
|
let quotaFound = false;
|
|
64
64
|
let response;
|
|
65
65
|
try {
|
|
66
|
-
response = await connection.exec('GETQUOTAROOT', [{ type: 'ATOM', value: path }], {
|
|
66
|
+
response = await connection.exec('GETQUOTAROOT', [{ type: 'ATOM', value: encodePath(connection, path) }], {
|
|
67
67
|
untagged: {
|
|
68
68
|
QUOTAROOT: async untagged => {
|
|
69
69
|
let quotaRoot =
|
package/lib/commands/rename.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { normalizePath, getStatusCode, getErrorText } = require('../tools.js');
|
|
3
|
+
const { encodePath, normalizePath, getStatusCode, getErrorText } = require('../tools.js');
|
|
4
4
|
|
|
5
5
|
// Renames existing mailbox
|
|
6
6
|
module.exports = async (connection, path, newPath) => {
|
|
@@ -23,8 +23,8 @@ module.exports = async (connection, path, newPath) => {
|
|
|
23
23
|
newPath
|
|
24
24
|
};
|
|
25
25
|
response = await connection.exec('RENAME', [
|
|
26
|
-
{ type: 'ATOM', value: path },
|
|
27
|
-
{ type: 'ATOM', value: newPath }
|
|
26
|
+
{ type: 'ATOM', value: encodePath(connection, path) },
|
|
27
|
+
{ type: 'ATOM', value: encodePath(connection, newPath) }
|
|
28
28
|
]);
|
|
29
29
|
response.next();
|
|
30
30
|
return map;
|
package/lib/commands/select.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { normalizePath, getStatusCode, getErrorText } = require('../tools.js');
|
|
3
|
+
const { encodePath, normalizePath, getStatusCode, getErrorText } = require('../tools.js');
|
|
4
4
|
|
|
5
5
|
// Selects a mailbox
|
|
6
6
|
module.exports = async (connection, path, options) => {
|
|
@@ -47,101 +47,105 @@ module.exports = async (connection, path, options) => {
|
|
|
47
47
|
map.qresync = true;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
response = await connection.exec(
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
let key = section[0].value.toLowerCase();
|
|
59
|
-
let value;
|
|
60
|
-
|
|
61
|
-
if (typeof section[1].value === 'string') {
|
|
62
|
-
value = section[1].value;
|
|
63
|
-
} else if (Array.isArray(section[1])) {
|
|
64
|
-
value = section[1].map(entry => (typeof entry.value === 'string' ? entry.value : false)).filter(entry => entry);
|
|
50
|
+
response = await connection.exec(
|
|
51
|
+
!options.readOnly ? 'SELECT' : 'EXAMINE',
|
|
52
|
+
[{ type: 'ATOM', value: encodePath(connection, path) }].concat(extraArgs || []),
|
|
53
|
+
{
|
|
54
|
+
untagged: {
|
|
55
|
+
OK: async untagged => {
|
|
56
|
+
if (!untagged.attributes || !untagged.attributes.length) {
|
|
57
|
+
return;
|
|
65
58
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
59
|
+
let section = !untagged.attributes[0].value && untagged.attributes[0].section;
|
|
60
|
+
if (section && section.length > 1 && section[0].type === 'ATOM' && typeof section[0].value === 'string') {
|
|
61
|
+
let key = section[0].value.toLowerCase();
|
|
62
|
+
let value;
|
|
63
|
+
|
|
64
|
+
if (typeof section[1].value === 'string') {
|
|
65
|
+
value = section[1].value;
|
|
66
|
+
} else if (Array.isArray(section[1])) {
|
|
67
|
+
value = section[1].map(entry => (typeof entry.value === 'string' ? entry.value : false)).filter(entry => entry);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
switch (key) {
|
|
71
|
+
case 'highestmodseq':
|
|
72
|
+
key = 'highestModseq';
|
|
73
|
+
if (/^[0-9]+$/.test(value)) {
|
|
74
|
+
value = BigInt(value);
|
|
75
|
+
}
|
|
76
|
+
break;
|
|
77
|
+
|
|
78
|
+
case 'mailboxid':
|
|
79
|
+
key = 'mailboxId';
|
|
80
|
+
if (Array.isArray(value) && value.length) {
|
|
81
|
+
value = value[0];
|
|
82
|
+
}
|
|
83
|
+
break;
|
|
84
|
+
|
|
85
|
+
case 'permanentflags':
|
|
86
|
+
key = 'permanentFlags';
|
|
87
|
+
value = new Set(value);
|
|
88
|
+
break;
|
|
89
|
+
|
|
90
|
+
case 'uidnext':
|
|
91
|
+
key = 'uidNext';
|
|
92
|
+
value = Number(value);
|
|
93
|
+
break;
|
|
94
|
+
|
|
95
|
+
case 'uidvalidity':
|
|
96
|
+
key = 'uidValidity';
|
|
97
|
+
if (/^[0-9]+$/.test(value)) {
|
|
98
|
+
value = BigInt(value);
|
|
99
|
+
}
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
map[key] = value;
|
|
98
104
|
}
|
|
99
105
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
106
|
+
if (section && section.length === 1 && section[0].type === 'ATOM' && typeof section[0].value === 'string') {
|
|
107
|
+
let key = section[0].value.toLowerCase();
|
|
108
|
+
switch (key) {
|
|
109
|
+
case 'nomodseq':
|
|
110
|
+
key = 'noModseq';
|
|
111
|
+
map[key] = true;
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
FLAGS: async untagged => {
|
|
117
|
+
if (!untagged.attributes || (!untagged.attributes.length && Array.isArray(untagged.attributes[0]))) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
let flags = untagged.attributes[0].map(flag => (typeof flag.value === 'string' ? flag.value : false)).filter(flag => flag);
|
|
121
|
+
map.flags = new Set(flags);
|
|
122
|
+
},
|
|
123
|
+
EXISTS: async untagged => {
|
|
124
|
+
let num = Number(untagged.command);
|
|
125
|
+
if (isNaN(num)) {
|
|
126
|
+
return false;
|
|
110
127
|
}
|
|
111
|
-
}
|
|
112
|
-
},
|
|
113
|
-
FLAGS: async untagged => {
|
|
114
|
-
if (!untagged.attributes || (!untagged.attributes.length && Array.isArray(untagged.attributes[0]))) {
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
let flags = untagged.attributes[0].map(flag => (typeof flag.value === 'string' ? flag.value : false)).filter(flag => flag);
|
|
118
|
-
map.flags = new Set(flags);
|
|
119
|
-
},
|
|
120
|
-
EXISTS: async untagged => {
|
|
121
|
-
let num = Number(untagged.command);
|
|
122
|
-
if (isNaN(num)) {
|
|
123
|
-
return false;
|
|
124
|
-
}
|
|
125
128
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
129
|
+
map.exists = num;
|
|
130
|
+
},
|
|
131
|
+
VANISHED: async untagged => {
|
|
132
|
+
await connection.untaggedVanished(
|
|
133
|
+
untagged,
|
|
134
|
+
// mailbox is not yet open, so use a dummy mailbox object
|
|
135
|
+
{ path, uidNext: false, uidValidity: false }
|
|
136
|
+
);
|
|
137
|
+
},
|
|
138
|
+
// we should only get an untagged FETCH for a SELECT/EXAMINE if QRESYNC was asked for
|
|
139
|
+
FETCH: async untagged => {
|
|
140
|
+
await connection.untaggedFetch(
|
|
141
|
+
untagged,
|
|
142
|
+
// mailbox is not yet open, so use a dummy mailbox object
|
|
143
|
+
{ path, uidNext: false, uidValidity: false }
|
|
144
|
+
);
|
|
145
|
+
}
|
|
142
146
|
}
|
|
143
147
|
}
|
|
144
|
-
|
|
148
|
+
);
|
|
145
149
|
|
|
146
150
|
let section = !response.response.attributes[0].value && response.response.attributes[0].section;
|
|
147
151
|
if (section && section.length && section[0].type === 'ATOM' && typeof section[0].value === 'string') {
|
package/lib/commands/status.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { normalizePath } = require('../tools.js');
|
|
3
|
+
const { encodePath, normalizePath } = require('../tools.js');
|
|
4
4
|
|
|
5
5
|
// Requests info about a mailbox
|
|
6
6
|
module.exports = async (connection, path, query) => {
|
|
@@ -11,7 +11,7 @@ module.exports = async (connection, path, query) => {
|
|
|
11
11
|
|
|
12
12
|
path = normalizePath(connection, path);
|
|
13
13
|
|
|
14
|
-
let attributes = [{ type: 'ATOM', value: path }];
|
|
14
|
+
let attributes = [{ type: 'ATOM', value: encodePath(connection, path) }];
|
|
15
15
|
|
|
16
16
|
let queryAttributes = [];
|
|
17
17
|
Object.keys(query || {}).forEach(key => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { normalizePath, getStatusCode, getErrorText } = require('../tools.js');
|
|
3
|
+
const { encodePath, normalizePath, getStatusCode, getErrorText } = require('../tools.js');
|
|
4
4
|
|
|
5
5
|
// Subscribes to a mailbox
|
|
6
6
|
module.exports = async (connection, path) => {
|
|
@@ -13,7 +13,7 @@ module.exports = async (connection, path) => {
|
|
|
13
13
|
|
|
14
14
|
let response;
|
|
15
15
|
try {
|
|
16
|
-
response = await connection.exec('SUBSCRIBE', [{ type: 'ATOM', value: path }]);
|
|
16
|
+
response = await connection.exec('SUBSCRIBE', [{ type: 'ATOM', value: encodePath(connection, path) }]);
|
|
17
17
|
response.next();
|
|
18
18
|
return true;
|
|
19
19
|
} catch (err) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { normalizePath, getStatusCode, getErrorText } = require('../tools.js');
|
|
3
|
+
const { encodePath, normalizePath, getStatusCode, getErrorText } = require('../tools.js');
|
|
4
4
|
|
|
5
5
|
// Unsubscribes from a mailbox
|
|
6
6
|
module.exports = async (connection, path) => {
|
|
@@ -13,7 +13,7 @@ module.exports = async (connection, path) => {
|
|
|
13
13
|
|
|
14
14
|
let response;
|
|
15
15
|
try {
|
|
16
|
-
response = await connection.exec('UNSUBSCRIBE', [{ type: 'ATOM', value: path }]);
|
|
16
|
+
response = await connection.exec('UNSUBSCRIBE', [{ type: 'ATOM', value: encodePath(connection, path) }]);
|
|
17
17
|
response.next();
|
|
18
18
|
return true;
|
|
19
19
|
} catch (err) {
|
|
@@ -26,7 +26,7 @@ function excludeChars(source, exclude) {
|
|
|
26
26
|
module.exports = {
|
|
27
27
|
CHAR() {
|
|
28
28
|
let value = expandRange(0x01, 0x7f);
|
|
29
|
-
this.CHAR = function() {
|
|
29
|
+
this.CHAR = function () {
|
|
30
30
|
return value;
|
|
31
31
|
};
|
|
32
32
|
return value;
|
|
@@ -34,7 +34,7 @@ module.exports = {
|
|
|
34
34
|
|
|
35
35
|
CHAR8() {
|
|
36
36
|
let value = expandRange(0x01, 0xff);
|
|
37
|
-
this.CHAR8 = function() {
|
|
37
|
+
this.CHAR8 = function () {
|
|
38
38
|
return value;
|
|
39
39
|
};
|
|
40
40
|
return value;
|
|
@@ -46,7 +46,7 @@ module.exports = {
|
|
|
46
46
|
|
|
47
47
|
CTL() {
|
|
48
48
|
let value = expandRange(0x00, 0x1f) + '\x7F';
|
|
49
|
-
this.CTL = function() {
|
|
49
|
+
this.CTL = function () {
|
|
50
50
|
return value;
|
|
51
51
|
};
|
|
52
52
|
return value;
|
|
@@ -58,7 +58,7 @@ module.exports = {
|
|
|
58
58
|
|
|
59
59
|
ALPHA() {
|
|
60
60
|
let value = expandRange(0x41, 0x5a) + expandRange(0x61, 0x7a);
|
|
61
|
-
this.ALPHA = function() {
|
|
61
|
+
this.ALPHA = function () {
|
|
62
62
|
return value;
|
|
63
63
|
};
|
|
64
64
|
return value;
|
|
@@ -66,7 +66,7 @@ module.exports = {
|
|
|
66
66
|
|
|
67
67
|
DIGIT() {
|
|
68
68
|
let value = expandRange(0x30, 0x39) + expandRange(0x61, 0x7a);
|
|
69
|
-
this.DIGIT = function() {
|
|
69
|
+
this.DIGIT = function () {
|
|
70
70
|
return value;
|
|
71
71
|
};
|
|
72
72
|
return value;
|
|
@@ -74,7 +74,7 @@ module.exports = {
|
|
|
74
74
|
|
|
75
75
|
'ATOM-CHAR'() {
|
|
76
76
|
let value = excludeChars(this.CHAR(), this['atom-specials']());
|
|
77
|
-
this['ATOM-CHAR'] = function() {
|
|
77
|
+
this['ATOM-CHAR'] = function () {
|
|
78
78
|
return value;
|
|
79
79
|
};
|
|
80
80
|
return value;
|
|
@@ -82,7 +82,7 @@ module.exports = {
|
|
|
82
82
|
|
|
83
83
|
'ASTRING-CHAR'() {
|
|
84
84
|
let value = this['ATOM-CHAR']() + this['resp-specials']();
|
|
85
|
-
this['ASTRING-CHAR'] = function() {
|
|
85
|
+
this['ASTRING-CHAR'] = function () {
|
|
86
86
|
return value;
|
|
87
87
|
};
|
|
88
88
|
return value;
|
|
@@ -90,7 +90,7 @@ module.exports = {
|
|
|
90
90
|
|
|
91
91
|
'TEXT-CHAR'() {
|
|
92
92
|
let value = excludeChars(this.CHAR(), '\r\n');
|
|
93
|
-
this['TEXT-CHAR'] = function() {
|
|
93
|
+
this['TEXT-CHAR'] = function () {
|
|
94
94
|
return value;
|
|
95
95
|
};
|
|
96
96
|
return value;
|
|
@@ -98,7 +98,7 @@ module.exports = {
|
|
|
98
98
|
|
|
99
99
|
'atom-specials'() {
|
|
100
100
|
let value = '(' + ')' + '{' + this.SP() + this.CTL() + this['list-wildcards']() + this['quoted-specials']() + this['resp-specials']();
|
|
101
|
-
this['atom-specials'] = function() {
|
|
101
|
+
this['atom-specials'] = function () {
|
|
102
102
|
return value;
|
|
103
103
|
};
|
|
104
104
|
return value;
|
|
@@ -109,8 +109,8 @@ module.exports = {
|
|
|
109
109
|
},
|
|
110
110
|
|
|
111
111
|
'quoted-specials'() {
|
|
112
|
-
let value = this.DQUOTE() + '\\
|
|
113
|
-
this['quoted-specials'] = function() {
|
|
112
|
+
let value = this.DQUOTE() + "'\\";
|
|
113
|
+
this['quoted-specials'] = function () {
|
|
114
114
|
return value;
|
|
115
115
|
};
|
|
116
116
|
return value;
|
|
@@ -122,7 +122,7 @@ module.exports = {
|
|
|
122
122
|
|
|
123
123
|
tag() {
|
|
124
124
|
let value = excludeChars(this['ASTRING-CHAR'](), '+');
|
|
125
|
-
this.tag = function() {
|
|
125
|
+
this.tag = function () {
|
|
126
126
|
return value;
|
|
127
127
|
};
|
|
128
128
|
return value;
|
|
@@ -130,7 +130,7 @@ module.exports = {
|
|
|
130
130
|
|
|
131
131
|
command() {
|
|
132
132
|
let value = this.ALPHA() + this.DIGIT() + '-';
|
|
133
|
-
this.command = function() {
|
|
133
|
+
this.command = function () {
|
|
134
134
|
return value;
|
|
135
135
|
};
|
|
136
136
|
return value;
|
|
@@ -189,6 +189,7 @@ class TokenParser {
|
|
|
189
189
|
case '{':
|
|
190
190
|
this.currentNode = this.createNode(this.currentNode, this.pos + i);
|
|
191
191
|
this.currentNode.type = 'LITERAL';
|
|
192
|
+
this.currentNode.literalType = 'literal';
|
|
192
193
|
this.state = 'LITERAL';
|
|
193
194
|
this.currentNode.closed = false;
|
|
194
195
|
break;
|
|
@@ -385,10 +386,13 @@ class TokenParser {
|
|
|
385
386
|
if (this.currentNode.started) {
|
|
386
387
|
// only relevant if literals are not already parsed out from input
|
|
387
388
|
|
|
388
|
-
//
|
|
389
|
+
// Disabled NUL check
|
|
390
|
+
// See https://github.com/emailjs/emailjs-imap-handler/commit/f11b2822bedabe492236e8263afc630134a3c41c
|
|
391
|
+
/*
|
|
389
392
|
if (chr === '\u0000') {
|
|
390
393
|
throw new Error('Unexpected \\x00 at position ' + (this.pos + i));
|
|
391
394
|
}
|
|
395
|
+
*/
|
|
392
396
|
|
|
393
397
|
this.currentNode.chBuffer[this.currentNode.chPos++] = chr.charCodeAt(0);
|
|
394
398
|
|
package/lib/imap-flow.js
CHANGED
|
@@ -261,7 +261,7 @@ class ImapFlow extends EventEmitter {
|
|
|
261
261
|
this.currentLock = false;
|
|
262
262
|
this.locks = [];
|
|
263
263
|
|
|
264
|
-
this.maxIdleTime = options.maxIdleTime || false;
|
|
264
|
+
this.maxIdleTime = this.options.maxIdleTime || false;
|
|
265
265
|
|
|
266
266
|
this.streamer.on('error', err => {
|
|
267
267
|
if (['Z_BUF_ERROR', 'ECONNRESET', 'EPIPE', 'ETIMEDOUT', 'EHOSTUNREACH'].includes(err.code)) {
|
|
@@ -1063,6 +1063,15 @@ class ImapFlow extends EventEmitter {
|
|
|
1063
1063
|
range = range.toString();
|
|
1064
1064
|
}
|
|
1065
1065
|
|
|
1066
|
+
// special case, some servers allow this, some do not, so replace it with the last known EXISTS value
|
|
1067
|
+
if (range === '*') {
|
|
1068
|
+
if (!this.mailbox.exists) {
|
|
1069
|
+
return false;
|
|
1070
|
+
}
|
|
1071
|
+
range = this.mailbox.exists.toString();
|
|
1072
|
+
options.uid = false; // sequence query
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1066
1075
|
if (range && typeof range === 'object' && !Array.isArray(range)) {
|
|
1067
1076
|
if (range.all && Object.keys(range) === 1) {
|
|
1068
1077
|
range = '1:*';
|
|
@@ -1357,9 +1366,12 @@ class ImapFlow extends EventEmitter {
|
|
|
1357
1366
|
/**
|
|
1358
1367
|
* @typedef {Object} ListResponse
|
|
1359
1368
|
* @global
|
|
1360
|
-
* @property {String} path mailbox path
|
|
1369
|
+
* @property {String} path mailbox path (unicode string)
|
|
1370
|
+
* @property {String} pathAsListed mailbox path as listed in the LIST/LSUB response
|
|
1361
1371
|
* @property {String} name mailbox name (last part of path after delimiter)
|
|
1362
1372
|
* @property {String} delimiter mailbox path delimiter, usually "." or "/"
|
|
1373
|
+
* @property {Array<string>} parent An array of parent folder names. All names are in unicode
|
|
1374
|
+
* @property {String} parentPath Same as `parent`, but as a complete string path (unicode string)
|
|
1363
1375
|
* @property {Set<string>} flags a set of flags for this mailbox
|
|
1364
1376
|
* @property {String} specialUse one of special-use flags (if applicable): "\All", "\Archive", "\Drafts", "\Flagged", "\Junk", "\Sent", "\Trash". Additionally INBOX has non-standard "\Inbox" flag set
|
|
1365
1377
|
* @property {Boolean} listed `true` if mailbox was found from the output of LIST command
|
|
@@ -1563,7 +1575,7 @@ class ImapFlow extends EventEmitter {
|
|
|
1563
1575
|
/**
|
|
1564
1576
|
* Requests the status of the indicated mailbox. Only requested status values will be returned.
|
|
1565
1577
|
*
|
|
1566
|
-
* @param {String} path mailbox path to check for
|
|
1578
|
+
* @param {String} path mailbox path to check for (unicode string)
|
|
1567
1579
|
* @param {Object} query defines requested status items
|
|
1568
1580
|
* @param {Boolean} query.messages if `true` request count of messages
|
|
1569
1581
|
* @param {Boolean} query.recent if `true` request count of messages with \\Recent tag
|
|
@@ -1786,7 +1798,7 @@ class ImapFlow extends EventEmitter {
|
|
|
1786
1798
|
/**
|
|
1787
1799
|
* Appends a new message to a mailbox
|
|
1788
1800
|
*
|
|
1789
|
-
* @param {String} path Mailbox path to upload the message to
|
|
1801
|
+
* @param {String} path Mailbox path to upload the message to (unicode string)
|
|
1790
1802
|
* @param {string|Buffer} content RFC822 formatted email message
|
|
1791
1803
|
* @param {string[]} [flags] an array of flags to be set for the uploaded message
|
|
1792
1804
|
* @param {Date|string} [idate=now] internal date to be set for the message
|
|
@@ -2096,6 +2108,14 @@ class ImapFlow extends EventEmitter {
|
|
|
2096
2108
|
return;
|
|
2097
2109
|
}
|
|
2098
2110
|
|
|
2111
|
+
if (seq === '*') {
|
|
2112
|
+
if (!this.mailbox.exists) {
|
|
2113
|
+
return false;
|
|
2114
|
+
}
|
|
2115
|
+
seq = this.mailbox.exists.toString();
|
|
2116
|
+
options = Object.assign({}, options || {}, { uid: false }); // force into a sequence query
|
|
2117
|
+
}
|
|
2118
|
+
|
|
2099
2119
|
let response = await this.run('FETCH', (seq || '').toString(), query, options);
|
|
2100
2120
|
|
|
2101
2121
|
if (!response || !response.list || !response.list.length) {
|
package/lib/tools.js
CHANGED
|
@@ -9,6 +9,30 @@ const { JPDecoder } = require('./jp-decoder');
|
|
|
9
9
|
const iconv = require('iconv-lite');
|
|
10
10
|
|
|
11
11
|
module.exports = {
|
|
12
|
+
encodePath(connection, path) {
|
|
13
|
+
path = (path || '').toString();
|
|
14
|
+
if (!connection.enabled.has('UTF8=ACCEPT') && /[&\x00-\x08\x0b-\x0c\x0e-\x1f\u0080-\uffff]/.test(path)) {
|
|
15
|
+
try {
|
|
16
|
+
path = iconv.encode(path, 'utf-7-imap').toString();
|
|
17
|
+
} catch (err) {
|
|
18
|
+
// ignore, keep name as is
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return path;
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
decodePath(connection, path) {
|
|
25
|
+
path = (path || '').toString();
|
|
26
|
+
if (!connection.enabled.has('UTF8=ACCEPT') && /[&]/.test(path)) {
|
|
27
|
+
try {
|
|
28
|
+
path = iconv.decode(Buffer.from(path), 'utf-7-imap').toString();
|
|
29
|
+
} catch (err) {
|
|
30
|
+
// ignore, keep name as is
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return path;
|
|
34
|
+
},
|
|
35
|
+
|
|
12
36
|
normalizePath(connection, path, skipNamespace) {
|
|
13
37
|
if (Array.isArray(path)) {
|
|
14
38
|
path = path.join((connection.namespace && connection.namespace.delimiter) || '');
|
|
@@ -19,14 +43,6 @@ module.exports = {
|
|
|
19
43
|
return 'INBOX';
|
|
20
44
|
}
|
|
21
45
|
|
|
22
|
-
if (!connection.enabled.has('UTF8=ACCEPT') && /[\x00-\x08\x0b-\x0c\x0e-\x1f\u0080-\uffff]/.test(path)) {
|
|
23
|
-
try {
|
|
24
|
-
path = iconv.encode(path, 'utf-7-imap').toString();
|
|
25
|
-
} catch (err) {
|
|
26
|
-
// ignore, keep name as is
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
46
|
// ensure namespace prefix if needed
|
|
31
47
|
if (!skipNamespace && connection.namespace && connection.namespace.prefix && path.indexOf(connection.namespace.prefix) !== 0) {
|
|
32
48
|
path = connection.namespace.prefix + path;
|
|
@@ -290,9 +306,10 @@ module.exports = {
|
|
|
290
306
|
break;
|
|
291
307
|
}
|
|
292
308
|
|
|
293
|
-
default:
|
|
294
|
-
|
|
295
|
-
|
|
309
|
+
default: {
|
|
310
|
+
let match = key.match(/(body)\[/i);
|
|
311
|
+
if (match) {
|
|
312
|
+
let partKey = key.replace(/^(body)\[|]$/gi, '');
|
|
296
313
|
partKey = partKey.replace(/\.fields.*$/g, '');
|
|
297
314
|
|
|
298
315
|
let value = getBuffer(attribute);
|
|
@@ -308,6 +325,7 @@ module.exports = {
|
|
|
308
325
|
break;
|
|
309
326
|
}
|
|
310
327
|
break;
|
|
328
|
+
}
|
|
311
329
|
}
|
|
312
330
|
}
|
|
313
331
|
|
package/lib/types.d.ts
CHANGED
|
@@ -241,7 +241,7 @@ declare module "imapflow" {
|
|
|
241
241
|
* let status = await client.status('INBOX', {unseen: true});
|
|
242
242
|
* console.log(status.unseen);
|
|
243
243
|
* // 123
|
|
244
|
-
* @param path - mailbox path to check for
|
|
244
|
+
* @param path - mailbox path to check for (unicode string)
|
|
245
245
|
* @param query - defines requested status items
|
|
246
246
|
* @param query.messages - if `true` request count of messages
|
|
247
247
|
* @param query.recent - if `true` request count of messages with \\Recent tag
|
|
@@ -342,7 +342,7 @@ declare module "imapflow" {
|
|
|
342
342
|
* Appends a new message to a mailbox
|
|
343
343
|
* @example
|
|
344
344
|
* await client.append('INBOX', rawMessageBuffer, ['\\Seen'], new Date(2000, 1, 1));
|
|
345
|
-
* @param path - Mailbox path to upload the message to
|
|
345
|
+
* @param path - Mailbox path to upload the message to (unicode string)
|
|
346
346
|
* @param content - RFC822 formatted email message
|
|
347
347
|
* @param [flags] - an array of flags to be set for the uploaded message
|
|
348
348
|
* @param [idate = now] - internal date to be set for the message
|
|
@@ -565,9 +565,12 @@ declare type QuotaResponse = {
|
|
|
565
565
|
};
|
|
566
566
|
|
|
567
567
|
/**
|
|
568
|
-
* @property path - mailbox path
|
|
568
|
+
* @property path - mailbox path (unicode string)
|
|
569
|
+
* @property pathAsListed - mailbox path as listed in the LIST/LSUB response
|
|
569
570
|
* @property name - mailbox name (last part of path after delimiter)
|
|
570
571
|
* @property delimiter - mailbox path delimiter, usually "." or "/"
|
|
572
|
+
* @property parent - An array of parent folder names. All names are in unicode
|
|
573
|
+
* @property parentPath - Same as `parent`, but as a complete string path (unicode string)
|
|
571
574
|
* @property flags - a set of flags for this mailbox
|
|
572
575
|
* @property specialUse - one of special-use flags (if applicable): "\All", "\Archive", "\Drafts", "\Flagged", "\Junk", "\Sent", "\Trash". Additionally INBOX has non-standard "\Inbox" flag set
|
|
573
576
|
* @property listed - `true` if mailbox was found from the output of LIST command
|
|
@@ -575,8 +578,11 @@ declare type QuotaResponse = {
|
|
|
575
578
|
*/
|
|
576
579
|
declare type ListResponse = {
|
|
577
580
|
path: string;
|
|
581
|
+
pathAsListed: string;
|
|
578
582
|
name: string;
|
|
579
583
|
delimiter: string;
|
|
584
|
+
parent: string[];
|
|
585
|
+
parentPath: string;
|
|
580
586
|
flags: Set<string>;
|
|
581
587
|
specialUse: string;
|
|
582
588
|
listed: boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "imapflow",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.91",
|
|
4
4
|
"description": "IMAP Client for Node",
|
|
5
5
|
"main": "./lib/imap-flow.js",
|
|
6
6
|
"scripts": {
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
"prepare": "npm run build",
|
|
9
9
|
"docs": "rm -rf docs && mkdir -p docs && jsdoc lib/imap-flow.js -c jsdoc.json -R README.md --destination docs/ && cp assets/favicon.ico docs",
|
|
10
10
|
"dst": "node types.js",
|
|
11
|
-
"build": "npm run docs && npm run dst"
|
|
11
|
+
"build": "npm run docs && npm run dst",
|
|
12
|
+
"st": "npm run docs && st -d docs -i index.html"
|
|
12
13
|
},
|
|
13
14
|
"repository": {
|
|
14
15
|
"type": "git",
|
|
@@ -29,28 +30,29 @@
|
|
|
29
30
|
"@babel/eslint-parser": "7.17.0",
|
|
30
31
|
"@babel/eslint-plugin": "7.17.7",
|
|
31
32
|
"@babel/plugin-syntax-class-properties": "7.12.13",
|
|
32
|
-
"@babel/preset-env": "7.
|
|
33
|
-
"@types/node": "17.0.
|
|
33
|
+
"@babel/preset-env": "7.17.10",
|
|
34
|
+
"@types/node": "17.0.31",
|
|
34
35
|
"braintree-jsdoc-template": "3.3.0",
|
|
35
|
-
"eslint": "8.
|
|
36
|
+
"eslint": "8.14.0",
|
|
36
37
|
"eslint-config-nodemailer": "1.2.0",
|
|
37
38
|
"eslint-config-prettier": "8.5.0",
|
|
38
|
-
"grunt": "1.
|
|
39
|
+
"grunt": "1.5.2",
|
|
39
40
|
"grunt-cli": "1.4.3",
|
|
40
41
|
"grunt-contrib-nodeunit": "4.0.0",
|
|
41
42
|
"grunt-eslint": "24.0.0",
|
|
42
43
|
"jsdoc": "3.6.10",
|
|
44
|
+
"st": "3.0.0",
|
|
43
45
|
"tsd-jsdoc": "2.5.0"
|
|
44
46
|
},
|
|
45
47
|
"dependencies": {
|
|
46
48
|
"encoding-japanese": "2.0.0",
|
|
47
49
|
"iconv-lite": "0.6.3",
|
|
48
50
|
"libbase64": "1.2.1",
|
|
49
|
-
"libmime": "5.
|
|
51
|
+
"libmime": "5.1.0",
|
|
50
52
|
"libqp": "1.1.0",
|
|
51
|
-
"mailsplit": "5.3.
|
|
52
|
-
"nodemailer": "6.7.
|
|
53
|
-
"pino": "7.
|
|
53
|
+
"mailsplit": "5.3.2",
|
|
54
|
+
"nodemailer": "6.7.4",
|
|
55
|
+
"pino": "7.11.0",
|
|
54
56
|
"socks": "2.6.2"
|
|
55
57
|
}
|
|
56
58
|
}
|