imapkit 0.0.0-stage → 4.0.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/LICENSE +16 -0
- package/README.md +608 -2
- package/bin/help.txt +98 -0
- package/bin/imapkit.js +108 -0
- package/cert/server.crt +20 -0
- package/cert/server.key +28 -0
- package/lib/addressparser.js +283 -0
- package/lib/arguments.js +112 -0
- package/lib/bodystructure.js +149 -0
- package/lib/command-states.js +109 -0
- package/lib/commands/append.js +313 -0
- package/lib/commands/capability.js +47 -0
- package/lib/commands/check.js +21 -0
- package/lib/commands/close.js +30 -0
- package/lib/commands/copy.js +115 -0
- package/lib/commands/create.js +52 -0
- package/lib/commands/delete.js +64 -0
- package/lib/commands/examine.js +7 -0
- package/lib/commands/expunge.js +27 -0
- package/lib/commands/fetch.js +229 -0
- package/lib/commands/handlers/fetch.js +209 -0
- package/lib/commands/handlers/flags.js +42 -0
- package/lib/commands/handlers/search.js +519 -0
- package/lib/commands/handlers/status.js +85 -0
- package/lib/commands/handlers/store.js +127 -0
- package/lib/commands/list.js +100 -0
- package/lib/commands/login.js +67 -0
- package/lib/commands/logout.js +41 -0
- package/lib/commands/lsub.js +87 -0
- package/lib/commands/noop.js +21 -0
- package/lib/commands/rename.js +102 -0
- package/lib/commands/search.js +76 -0
- package/lib/commands/select.js +289 -0
- package/lib/commands/status.js +63 -0
- package/lib/commands/store.js +151 -0
- package/lib/commands/subscribe.js +53 -0
- package/lib/commands/uid copy.js +7 -0
- package/lib/commands/uid fetch.js +5 -0
- package/lib/commands/uid search.js +5 -0
- package/lib/commands/uid store.js +5 -0
- package/lib/commands/unsubscribe.js +50 -0
- package/lib/dates.js +123 -0
- package/lib/deflate-layer.js +232 -0
- package/lib/envelope.js +82 -0
- package/lib/esearch.js +208 -0
- package/lib/framing.js +102 -0
- package/lib/list-extensions.js +36 -0
- package/lib/load-plugins.js +109 -0
- package/lib/mailbox-name.js +133 -0
- package/lib/mimeparser.js +778 -0
- package/lib/mock-client.js +233 -0
- package/lib/numbers.js +52 -0
- package/lib/plugins/acl.js +964 -0
- package/lib/plugins/appendlimit.js +83 -0
- package/lib/plugins/auth-plain.js +94 -0
- package/lib/plugins/binary.js +256 -0
- package/lib/plugins/catenate.js +253 -0
- package/lib/plugins/compress.js +76 -0
- package/lib/plugins/condstore.js +563 -0
- package/lib/plugins/context-search.js +321 -0
- package/lib/plugins/context-sort.js +19 -0
- package/lib/plugins/create-special-use.js +108 -0
- package/lib/plugins/enable.js +155 -0
- package/lib/plugins/esearch.js +156 -0
- package/lib/plugins/esort.js +60 -0
- package/lib/plugins/id.js +138 -0
- package/lib/plugins/idle.js +105 -0
- package/lib/plugins/imap4rev2.js +202 -0
- package/lib/plugins/list-extended.js +258 -0
- package/lib/plugins/list-status.js +31 -0
- package/lib/plugins/literalminus.js +20 -0
- package/lib/plugins/literalplus.js +18 -0
- package/lib/plugins/logindisabled.js +50 -0
- package/lib/plugins/messagelimit.js +234 -0
- package/lib/plugins/metadata-server.js +13 -0
- package/lib/plugins/metadata.js +475 -0
- package/lib/plugins/move.js +110 -0
- package/lib/plugins/multiappend.js +26 -0
- package/lib/plugins/multisearch.js +269 -0
- package/lib/plugins/namespace.js +67 -0
- package/lib/plugins/notify.js +654 -0
- package/lib/plugins/oauthbearer.js +217 -0
- package/lib/plugins/objectid.js +243 -0
- package/lib/plugins/partial.js +68 -0
- package/lib/plugins/preview.js +400 -0
- package/lib/plugins/qresync.js +525 -0
- package/lib/plugins/quota.js +285 -0
- package/lib/plugins/replace.js +145 -0
- package/lib/plugins/sasl-ir.js +12 -0
- package/lib/plugins/savedate.js +59 -0
- package/lib/plugins/savelimit.js +18 -0
- package/lib/plugins/searchres.js +82 -0
- package/lib/plugins/sort-display.js +23 -0
- package/lib/plugins/sort.js +132 -0
- package/lib/plugins/special-use.js +95 -0
- package/lib/plugins/starttls.js +57 -0
- package/lib/plugins/status-size.js +19 -0
- package/lib/plugins/thread-orderedsubject.js +16 -0
- package/lib/plugins/thread-references.js +16 -0
- package/lib/plugins/uidonly.js +135 -0
- package/lib/plugins/uidplus.js +124 -0
- package/lib/plugins/unauthenticate.js +28 -0
- package/lib/plugins/unselect.js +36 -0
- package/lib/plugins/utf8-accept.js +68 -0
- package/lib/plugins/x-gm-ext-1.js +456 -0
- package/lib/plugins/xoauth2.js +188 -0
- package/lib/plugins/xtoybird.js +282 -0
- package/lib/server.js +2880 -0
- package/lib/smtp-listener.js +51 -0
- package/lib/sorting.js +373 -0
- package/lib/threading.js +357 -0
- package/lib/utf8-session.js +123 -0
- package/lib/vanished.js +57 -0
- package/package.json +61 -5
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
module.exports = function (connection, parsed, data, callback) {
|
|
4
|
+
return copyMessages(connection, parsed, data, callback, false);
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Shared implementation of COPY and UID COPY
|
|
9
|
+
*
|
|
10
|
+
* @param {Boolean} isUid If true, the sequence set holds UID values
|
|
11
|
+
*/
|
|
12
|
+
function copyMessages(connection, parsed, data, callback, isUid) {
|
|
13
|
+
const command = isUid ? 'UID COPY' : 'COPY';
|
|
14
|
+
|
|
15
|
+
if (
|
|
16
|
+
!parsed.attributes ||
|
|
17
|
+
parsed.attributes.length !== 2 ||
|
|
18
|
+
!parsed.attributes[0] ||
|
|
19
|
+
['ATOM', 'SEQUENCE'].indexOf(parsed.attributes[0].type) < 0 ||
|
|
20
|
+
!parsed.attributes[1] ||
|
|
21
|
+
['ATOM', 'STRING', 'LITERAL'].indexOf(parsed.attributes[1].type) < 0
|
|
22
|
+
) {
|
|
23
|
+
connection.send(
|
|
24
|
+
{
|
|
25
|
+
tag: parsed.tag,
|
|
26
|
+
command: 'BAD',
|
|
27
|
+
attributes: [
|
|
28
|
+
{
|
|
29
|
+
type: 'TEXT',
|
|
30
|
+
value: command + ' expects sequence set and a mailbox name'
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
},
|
|
34
|
+
'INVALID COMMAND',
|
|
35
|
+
parsed,
|
|
36
|
+
data
|
|
37
|
+
);
|
|
38
|
+
return callback();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const result = copyToMailbox(connection, parsed, data, isUid, command + ' FAIL');
|
|
42
|
+
if (!result) {
|
|
43
|
+
return callback();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// the result is the extra context info for UIDPLUS
|
|
47
|
+
connection.send(
|
|
48
|
+
{
|
|
49
|
+
tag: parsed.tag,
|
|
50
|
+
command: 'OK',
|
|
51
|
+
attributes: [
|
|
52
|
+
{
|
|
53
|
+
type: 'TEXT',
|
|
54
|
+
value: command + ' Completed'
|
|
55
|
+
}
|
|
56
|
+
]
|
|
57
|
+
},
|
|
58
|
+
command,
|
|
59
|
+
parsed,
|
|
60
|
+
data,
|
|
61
|
+
result
|
|
62
|
+
);
|
|
63
|
+
callback();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Copies messages from the selected mailbox to another one, the copy step of COPY and MOVE.
|
|
68
|
+
* Sends a tagged NO and returns false if the target mailbox can not be used.
|
|
69
|
+
*
|
|
70
|
+
* @param {Object} connection IMAP connection
|
|
71
|
+
* @param {Object} parsed Parsed command, with the sequence set and the mailbox name as arguments
|
|
72
|
+
* @param {String} data Raw command
|
|
73
|
+
* @param {Boolean} isUid If true, the sequence set holds UID values
|
|
74
|
+
* @param {String} failDescription Description for the failure response
|
|
75
|
+
* @param {Boolean} [isMove] If true, the messages are moved, the caller expunges them afterwards
|
|
76
|
+
* @return {Object|false} `{ mailbox, sourceUids, targetUids, messages }`, where messages lists the copied source messages
|
|
77
|
+
*/
|
|
78
|
+
function copyToMailbox(connection, parsed, data, isUid, failDescription, isMove) {
|
|
79
|
+
const range = connection.limitRange(parsed, connection.getCommandRange(parsed.attributes[0].value, isUid));
|
|
80
|
+
const mailbox = connection.getTargetMailbox(parsed.attributes[1].value, parsed, data, failDescription);
|
|
81
|
+
if (!mailbox) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const messages = range.map(rangeMessage => rangeMessage[1]);
|
|
86
|
+
if (messages.some(message => message.ghost)) {
|
|
87
|
+
// RFC 2180 section 4.4.1: nothing is copied when the set includes a message that another session expunged,
|
|
88
|
+
// the pending EXPUNGE responses go out before the NO
|
|
89
|
+
connection.sendStatus(parsed, data, 'NO', 'Some of the requested messages no longer exist', 'EXPUNGEISSUED', failDescription);
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (!connection.checkAppend(mailbox, messages, parsed, data, failDescription, { move: !!isMove, source: connection.selectedMailbox })) {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const sourceUids = [];
|
|
98
|
+
const targetUids = [];
|
|
99
|
+
// a range that is processed from the highest UID down (parsed.highestFirst) is still copied in UID order,
|
|
100
|
+
// so the new UIDs follow the order of the source UIDs (RFC 9738 section 3.1 MOVE example)
|
|
101
|
+
(parsed.highestFirst ? messages.slice().reverse() : messages).forEach(message => {
|
|
102
|
+
sourceUids.push(message.uid);
|
|
103
|
+
targetUids.push(connection.server.copyMessage(mailbox, message).message.uid);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
mailbox,
|
|
108
|
+
sourceUids,
|
|
109
|
+
targetUids,
|
|
110
|
+
messages
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
module.exports.copyMessages = copyMessages;
|
|
115
|
+
module.exports.copyToMailbox = copyToMailbox;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
module.exports = function (connection, parsed, data, callback) {
|
|
4
|
+
if (!parsed.attributes || parsed.attributes.length !== 1 || !parsed.attributes[0] || ['STRING', 'LITERAL', 'ATOM'].indexOf(parsed.attributes[0].type) < 0) {
|
|
5
|
+
connection.send(
|
|
6
|
+
{
|
|
7
|
+
tag: parsed.tag,
|
|
8
|
+
command: 'BAD',
|
|
9
|
+
attributes: [
|
|
10
|
+
{
|
|
11
|
+
type: 'TEXT',
|
|
12
|
+
value: 'CREATE expects mailbox name'
|
|
13
|
+
}
|
|
14
|
+
]
|
|
15
|
+
},
|
|
16
|
+
'INVALID COMMAND',
|
|
17
|
+
parsed,
|
|
18
|
+
data
|
|
19
|
+
);
|
|
20
|
+
return callback();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const path = parsed.attributes[0].value;
|
|
24
|
+
|
|
25
|
+
let mailbox;
|
|
26
|
+
try {
|
|
27
|
+
mailbox = connection.server.createMailbox(path);
|
|
28
|
+
} catch (E) {
|
|
29
|
+
// RFC 5530 response codes, e.g. [ALREADYEXISTS]
|
|
30
|
+
connection.sendStatus(parsed, data, 'NO', E.message, E.code, 'CREATE FAILED');
|
|
31
|
+
return callback();
|
|
32
|
+
}
|
|
33
|
+
connection.server.mailboxChanged('create', mailbox.path);
|
|
34
|
+
|
|
35
|
+
connection.send(
|
|
36
|
+
{
|
|
37
|
+
tag: parsed.tag,
|
|
38
|
+
command: 'OK',
|
|
39
|
+
attributes: [
|
|
40
|
+
{
|
|
41
|
+
type: 'TEXT',
|
|
42
|
+
value: 'CREATE completed'
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
},
|
|
46
|
+
'CREATE',
|
|
47
|
+
parsed,
|
|
48
|
+
data,
|
|
49
|
+
mailbox
|
|
50
|
+
);
|
|
51
|
+
return callback();
|
|
52
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
module.exports = function (connection, parsed, data, callback) {
|
|
4
|
+
if (!parsed.attributes || parsed.attributes.length !== 1 || !parsed.attributes[0] || ['STRING', 'LITERAL', 'ATOM'].indexOf(parsed.attributes[0].type) < 0) {
|
|
5
|
+
connection.send(
|
|
6
|
+
{
|
|
7
|
+
tag: parsed.tag,
|
|
8
|
+
command: 'BAD',
|
|
9
|
+
attributes: [
|
|
10
|
+
{
|
|
11
|
+
type: 'TEXT',
|
|
12
|
+
value: 'DELETE expects mailbox name'
|
|
13
|
+
}
|
|
14
|
+
]
|
|
15
|
+
},
|
|
16
|
+
'INVALID COMMAND',
|
|
17
|
+
parsed,
|
|
18
|
+
data
|
|
19
|
+
);
|
|
20
|
+
return callback();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const path = parsed.attributes[0].value;
|
|
24
|
+
|
|
25
|
+
const mailbox = connection.server.getMailbox(path);
|
|
26
|
+
|
|
27
|
+
if (!mailbox) {
|
|
28
|
+
connection.sendStatus(parsed, data, 'NO', 'Mailbox does not exist', 'NONEXISTENT', 'DELETE FAILED');
|
|
29
|
+
return callback();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
connection.server.deleteMailbox(path);
|
|
34
|
+
} catch (E) {
|
|
35
|
+
connection.sendStatus(parsed, data, 'NO', E.message, E.code, 'DELETE FAILED');
|
|
36
|
+
return callback();
|
|
37
|
+
}
|
|
38
|
+
connection.server.mailboxChanged('delete', mailbox.path, { mailbox });
|
|
39
|
+
|
|
40
|
+
// RFC 2180 section 3.3: the other sessions that have the mailbox selected are disconnected with an untagged
|
|
41
|
+
// BYE (RFC 2683 section 3.1.2), as they can not be told about the deletion in any other way
|
|
42
|
+
connection.server.connections.forEach(other => {
|
|
43
|
+
if (other !== connection && other.selectedMailbox === mailbox) {
|
|
44
|
+
other.bye('Selected mailbox was deleted', 'MAILBOX DELETED');
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
connection.send(
|
|
49
|
+
{
|
|
50
|
+
tag: parsed.tag,
|
|
51
|
+
command: 'OK',
|
|
52
|
+
attributes: [
|
|
53
|
+
{
|
|
54
|
+
type: 'TEXT',
|
|
55
|
+
value: 'DELETE completed'
|
|
56
|
+
}
|
|
57
|
+
]
|
|
58
|
+
},
|
|
59
|
+
'DELETE',
|
|
60
|
+
parsed,
|
|
61
|
+
data
|
|
62
|
+
);
|
|
63
|
+
return callback();
|
|
64
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
module.exports = function (connection, parsed, data, callback) {
|
|
4
|
+
if (connection.refuseReadOnly(parsed, data, 'EXPUNGE FAILED')) {
|
|
5
|
+
return callback();
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
connection.expungeDeleted(connection.selectedMailbox, false, true);
|
|
9
|
+
|
|
10
|
+
connection.send(
|
|
11
|
+
{
|
|
12
|
+
tag: parsed.tag,
|
|
13
|
+
command: 'OK',
|
|
14
|
+
attributes: [
|
|
15
|
+
{
|
|
16
|
+
type: 'TEXT',
|
|
17
|
+
value: 'EXPUNGE Completed'
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
},
|
|
21
|
+
'EXPUNGE',
|
|
22
|
+
parsed,
|
|
23
|
+
data
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
callback();
|
|
27
|
+
};
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fetchHandlers = require('./handlers/fetch');
|
|
4
|
+
|
|
5
|
+
const macros = {
|
|
6
|
+
ALL: ['FLAGS', 'INTERNALDATE', 'RFC822.SIZE', 'ENVELOPE'],
|
|
7
|
+
FAST: ['FLAGS', 'INTERNALDATE', 'RFC822.SIZE'],
|
|
8
|
+
FULL: ['FLAGS', 'INTERNALDATE', 'RFC822.SIZE', 'ENVELOPE', 'BODY']
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Returns the item names as they appear in a FETCH response, without .PEEK and only with the origin
|
|
13
|
+
* octet of a partial range (RFC 3501 section 7.4.2)
|
|
14
|
+
*
|
|
15
|
+
* @param {Array} params Fetch items
|
|
16
|
+
* @return {Array} names
|
|
17
|
+
*/
|
|
18
|
+
function responseNames(params) {
|
|
19
|
+
return params.map(param => {
|
|
20
|
+
const name = Object.assign({}, param, { value: param.value.replace(/\.PEEK\b/i, '') });
|
|
21
|
+
if (name.partial) {
|
|
22
|
+
name.partial = name.partial.slice(0, 1);
|
|
23
|
+
}
|
|
24
|
+
return name;
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Returns the handlers of fetch items
|
|
30
|
+
*
|
|
31
|
+
* @param {Object} connection IMAP connection
|
|
32
|
+
* @param {Array} params Fetch items
|
|
33
|
+
* @return {Array} handlers
|
|
34
|
+
* @throws {Error} for an unknown item
|
|
35
|
+
*/
|
|
36
|
+
function getHandlers(connection, params) {
|
|
37
|
+
return params.map((param, i) => {
|
|
38
|
+
const key = (param.value || '').toUpperCase();
|
|
39
|
+
const handler = connection.server.fetchHandlers[key] || fetchHandlers[key];
|
|
40
|
+
if (!handler) {
|
|
41
|
+
throw new Error('Invalid FETCH argument ' + (key ? ' ' + key : '#' + (i + 1)));
|
|
42
|
+
}
|
|
43
|
+
return handler;
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Builds the data list of a FETCH response for a message, without any side effects like setting \Seen
|
|
49
|
+
*
|
|
50
|
+
* @param {Object} connection IMAP connection
|
|
51
|
+
* @param {Object} message Message object
|
|
52
|
+
* @param {Array} params Fetch items
|
|
53
|
+
* @param {Array} [handlers] Handlers from getHandlers
|
|
54
|
+
* @param {Array} [names] Names from responseNames
|
|
55
|
+
* @return {Array} FETCH data items
|
|
56
|
+
* @throws {Error} for an unknown item or invalid item arguments
|
|
57
|
+
*/
|
|
58
|
+
function fetchResponse(connection, message, params, handlers, names) {
|
|
59
|
+
handlers = handlers || getHandlers(connection, params);
|
|
60
|
+
names = names || responseNames(params);
|
|
61
|
+
const response = [];
|
|
62
|
+
params.forEach((param, i) => response.push(names[i], handlers[i](connection, message, param)));
|
|
63
|
+
return response;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Shared implementation of FETCH and UID FETCH
|
|
68
|
+
*
|
|
69
|
+
* @param {Boolean} isUid If true, the sequence set lists UIDs and UID is always included in the response
|
|
70
|
+
*/
|
|
71
|
+
function processFetch(isUid, connection, parsed, data, callback) {
|
|
72
|
+
const command = isUid ? 'UID FETCH' : 'FETCH';
|
|
73
|
+
|
|
74
|
+
if (
|
|
75
|
+
!parsed.attributes ||
|
|
76
|
+
parsed.attributes.length !== 2 ||
|
|
77
|
+
!parsed.attributes[0] ||
|
|
78
|
+
['ATOM', 'SEQUENCE'].indexOf(parsed.attributes[0].type) < 0 ||
|
|
79
|
+
!parsed.attributes[1] ||
|
|
80
|
+
(['ATOM'].indexOf(parsed.attributes[1].type) < 0 && !Array.isArray(parsed.attributes[1]))
|
|
81
|
+
) {
|
|
82
|
+
connection.send(
|
|
83
|
+
{
|
|
84
|
+
tag: parsed.tag,
|
|
85
|
+
command: 'BAD',
|
|
86
|
+
attributes: [
|
|
87
|
+
{
|
|
88
|
+
type: 'TEXT',
|
|
89
|
+
value: command + ' expects sequence set and message item names'
|
|
90
|
+
}
|
|
91
|
+
]
|
|
92
|
+
},
|
|
93
|
+
'INVALID COMMAND',
|
|
94
|
+
parsed,
|
|
95
|
+
data
|
|
96
|
+
);
|
|
97
|
+
return callback();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const range = connection.limitRange(parsed, connection.getCommandRange(parsed.attributes[0].value, isUid));
|
|
101
|
+
let params = [].concat(parsed.attributes[1] || []);
|
|
102
|
+
|
|
103
|
+
if (parsed.attributes[1].type === 'ATOM' && Object.prototype.hasOwnProperty.call(macros, parsed.attributes[1].value.toUpperCase())) {
|
|
104
|
+
params = macros[parsed.attributes[1].value.toUpperCase()].slice();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// messages that get \Seen set by this FETCH
|
|
108
|
+
const seen = [];
|
|
109
|
+
// untagged FETCH responses and their messages
|
|
110
|
+
const responses = [];
|
|
111
|
+
const messages = [];
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
let uidExist = false;
|
|
115
|
+
let flagsExist = false;
|
|
116
|
+
let forceSeen = false;
|
|
117
|
+
|
|
118
|
+
params.forEach((param, i) => {
|
|
119
|
+
if (!param || (typeof param !== 'string' && param.type !== 'ATOM')) {
|
|
120
|
+
throw new Error('Invalid FETCH argument #' + (i + 1));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (typeof param === 'string') {
|
|
124
|
+
param = params[i] = {
|
|
125
|
+
type: 'ATOM',
|
|
126
|
+
value: param
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const key = param.value.toUpperCase();
|
|
131
|
+
|
|
132
|
+
if (key === 'FLAGS') {
|
|
133
|
+
flagsExist = true;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (key === 'UID') {
|
|
137
|
+
uidExist = true;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// RFC 3501 6.4.5: BODY[<section>] and items whose handler is marked with setsSeen set \Seen
|
|
141
|
+
const handler = connection.server.fetchHandlers[key] || fetchHandlers[key];
|
|
142
|
+
if (connection.canSetSeen() && ((key === 'BODY' && param.section) || (handler && handler.setsSeen))) {
|
|
143
|
+
forceSeen = true;
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
if (forceSeen && !flagsExist) {
|
|
148
|
+
params.push({
|
|
149
|
+
type: 'ATOM',
|
|
150
|
+
value: 'FLAGS'
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (isUid && !uidExist) {
|
|
155
|
+
params.push({
|
|
156
|
+
type: 'ATOM',
|
|
157
|
+
value: 'UID'
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const handlers = getHandlers(connection, params);
|
|
162
|
+
|
|
163
|
+
// item names as they appear in the response
|
|
164
|
+
const names = responseNames(params);
|
|
165
|
+
|
|
166
|
+
// nothing is sent before every message is done, so a failing item leaves no partial output
|
|
167
|
+
range.forEach(rangeMessage => {
|
|
168
|
+
for (let i = 0, len = connection.server.fetchFilters.length; i < len; i++) {
|
|
169
|
+
if (!connection.server.fetchFilters[i](connection, rangeMessage[1], parsed, rangeMessage[0])) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (forceSeen && rangeMessage[1].flags.indexOf('\\Seen') < 0) {
|
|
175
|
+
rangeMessage[1].flags.push('\\Seen');
|
|
176
|
+
seen.push(rangeMessage[1]);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const response = fetchResponse(connection, rangeMessage[1], params, handlers, names);
|
|
180
|
+
|
|
181
|
+
messages.push(rangeMessage[1]);
|
|
182
|
+
responses.push({
|
|
183
|
+
tag: '*',
|
|
184
|
+
attributes: [
|
|
185
|
+
rangeMessage[0],
|
|
186
|
+
{
|
|
187
|
+
type: 'ATOM',
|
|
188
|
+
value: 'FETCH'
|
|
189
|
+
},
|
|
190
|
+
response
|
|
191
|
+
]
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
} catch (E) {
|
|
195
|
+
// the failed FETCH did not set \Seen after all
|
|
196
|
+
seen.forEach(message => connection.server.removeFlag(message.flags, '\\Seen'));
|
|
197
|
+
// a handler can fail with NO and a response code, e.g. NO [UNKNOWN-CTE] (RFC 3516 section 4.3)
|
|
198
|
+
connection.sendStatus(parsed, data, E.imapResponse === 'NO' ? 'NO' : 'BAD', E.message, E.code, command + ' FAILED');
|
|
199
|
+
return callback();
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// the message goes with its response, for output handlers
|
|
203
|
+
responses.forEach((response, i) => connection.send(response, command, parsed, data, messages[i]));
|
|
204
|
+
|
|
205
|
+
connection.send(
|
|
206
|
+
{
|
|
207
|
+
tag: parsed.tag,
|
|
208
|
+
command: 'OK',
|
|
209
|
+
attributes: [
|
|
210
|
+
{
|
|
211
|
+
type: 'TEXT',
|
|
212
|
+
value: command + ' Completed'
|
|
213
|
+
}
|
|
214
|
+
]
|
|
215
|
+
},
|
|
216
|
+
command,
|
|
217
|
+
parsed,
|
|
218
|
+
data
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
// other sessions that have the mailbox selected learn about the new flags (RFC 3501 section 5.2)
|
|
222
|
+
connection.notifyFlagChanges(seen);
|
|
223
|
+
return callback();
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
module.exports = (connection, parsed, data, callback) => processFetch(false, connection, parsed, data, callback);
|
|
227
|
+
module.exports.processFetch = processFetch;
|
|
228
|
+
module.exports.macros = macros;
|
|
229
|
+
module.exports.fetchResponse = fetchResponse;
|