imapkit 0.0.0-stage → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/LICENSE +16 -0
  2. package/README.md +608 -2
  3. package/bin/help.txt +98 -0
  4. package/bin/imapkit.js +108 -0
  5. package/cert/server.crt +20 -0
  6. package/cert/server.key +28 -0
  7. package/lib/addressparser.js +283 -0
  8. package/lib/arguments.js +112 -0
  9. package/lib/bodystructure.js +149 -0
  10. package/lib/command-states.js +109 -0
  11. package/lib/commands/append.js +313 -0
  12. package/lib/commands/capability.js +47 -0
  13. package/lib/commands/check.js +21 -0
  14. package/lib/commands/close.js +30 -0
  15. package/lib/commands/copy.js +115 -0
  16. package/lib/commands/create.js +52 -0
  17. package/lib/commands/delete.js +64 -0
  18. package/lib/commands/examine.js +7 -0
  19. package/lib/commands/expunge.js +27 -0
  20. package/lib/commands/fetch.js +229 -0
  21. package/lib/commands/handlers/fetch.js +209 -0
  22. package/lib/commands/handlers/flags.js +42 -0
  23. package/lib/commands/handlers/search.js +519 -0
  24. package/lib/commands/handlers/status.js +85 -0
  25. package/lib/commands/handlers/store.js +127 -0
  26. package/lib/commands/list.js +100 -0
  27. package/lib/commands/login.js +67 -0
  28. package/lib/commands/logout.js +41 -0
  29. package/lib/commands/lsub.js +87 -0
  30. package/lib/commands/noop.js +21 -0
  31. package/lib/commands/rename.js +102 -0
  32. package/lib/commands/search.js +76 -0
  33. package/lib/commands/select.js +289 -0
  34. package/lib/commands/status.js +63 -0
  35. package/lib/commands/store.js +151 -0
  36. package/lib/commands/subscribe.js +53 -0
  37. package/lib/commands/uid copy.js +7 -0
  38. package/lib/commands/uid fetch.js +5 -0
  39. package/lib/commands/uid search.js +5 -0
  40. package/lib/commands/uid store.js +5 -0
  41. package/lib/commands/unsubscribe.js +50 -0
  42. package/lib/dates.js +123 -0
  43. package/lib/deflate-layer.js +232 -0
  44. package/lib/envelope.js +82 -0
  45. package/lib/esearch.js +208 -0
  46. package/lib/framing.js +102 -0
  47. package/lib/list-extensions.js +36 -0
  48. package/lib/load-plugins.js +109 -0
  49. package/lib/mailbox-name.js +133 -0
  50. package/lib/mimeparser.js +778 -0
  51. package/lib/mock-client.js +233 -0
  52. package/lib/numbers.js +52 -0
  53. package/lib/plugins/acl.js +964 -0
  54. package/lib/plugins/appendlimit.js +83 -0
  55. package/lib/plugins/auth-plain.js +94 -0
  56. package/lib/plugins/binary.js +256 -0
  57. package/lib/plugins/catenate.js +253 -0
  58. package/lib/plugins/compress.js +76 -0
  59. package/lib/plugins/condstore.js +563 -0
  60. package/lib/plugins/context-search.js +321 -0
  61. package/lib/plugins/context-sort.js +19 -0
  62. package/lib/plugins/create-special-use.js +108 -0
  63. package/lib/plugins/enable.js +155 -0
  64. package/lib/plugins/esearch.js +156 -0
  65. package/lib/plugins/esort.js +60 -0
  66. package/lib/plugins/id.js +138 -0
  67. package/lib/plugins/idle.js +105 -0
  68. package/lib/plugins/imap4rev2.js +202 -0
  69. package/lib/plugins/list-extended.js +258 -0
  70. package/lib/plugins/list-status.js +31 -0
  71. package/lib/plugins/literalminus.js +20 -0
  72. package/lib/plugins/literalplus.js +18 -0
  73. package/lib/plugins/logindisabled.js +50 -0
  74. package/lib/plugins/messagelimit.js +234 -0
  75. package/lib/plugins/metadata-server.js +13 -0
  76. package/lib/plugins/metadata.js +475 -0
  77. package/lib/plugins/move.js +110 -0
  78. package/lib/plugins/multiappend.js +26 -0
  79. package/lib/plugins/multisearch.js +269 -0
  80. package/lib/plugins/namespace.js +67 -0
  81. package/lib/plugins/notify.js +654 -0
  82. package/lib/plugins/oauthbearer.js +217 -0
  83. package/lib/plugins/objectid.js +243 -0
  84. package/lib/plugins/partial.js +68 -0
  85. package/lib/plugins/preview.js +400 -0
  86. package/lib/plugins/qresync.js +525 -0
  87. package/lib/plugins/quota.js +285 -0
  88. package/lib/plugins/replace.js +145 -0
  89. package/lib/plugins/sasl-ir.js +12 -0
  90. package/lib/plugins/savedate.js +59 -0
  91. package/lib/plugins/savelimit.js +18 -0
  92. package/lib/plugins/searchres.js +82 -0
  93. package/lib/plugins/sort-display.js +23 -0
  94. package/lib/plugins/sort.js +132 -0
  95. package/lib/plugins/special-use.js +95 -0
  96. package/lib/plugins/starttls.js +57 -0
  97. package/lib/plugins/status-size.js +19 -0
  98. package/lib/plugins/thread-orderedsubject.js +16 -0
  99. package/lib/plugins/thread-references.js +16 -0
  100. package/lib/plugins/uidonly.js +135 -0
  101. package/lib/plugins/uidplus.js +124 -0
  102. package/lib/plugins/unauthenticate.js +28 -0
  103. package/lib/plugins/unselect.js +36 -0
  104. package/lib/plugins/utf8-accept.js +68 -0
  105. package/lib/plugins/x-gm-ext-1.js +456 -0
  106. package/lib/plugins/xoauth2.js +188 -0
  107. package/lib/plugins/xtoybird.js +282 -0
  108. package/lib/server.js +2880 -0
  109. package/lib/smtp-listener.js +51 -0
  110. package/lib/sorting.js +373 -0
  111. package/lib/threading.js +357 -0
  112. package/lib/utf8-session.js +123 -0
  113. package/lib/vanished.js +57 -0
  114. package/package.json +61 -5
@@ -0,0 +1,127 @@
1
+ 'use strict';
2
+
3
+ const { normalizeSystemFlag, checkSystemFlags } = require('./flags');
4
+
5
+ const storeHandlers = {};
6
+
7
+ module.exports = storeHandlers;
8
+
9
+ function setFlags(connection, message, flags) {
10
+ const messageFlags = [];
11
+ [].concat(flags).forEach(flag => {
12
+ flag = normalizeSystemFlag(typeof flag === 'string' ? flag : String(flag.value || ''));
13
+ checkSystemFlags(connection, flag);
14
+
15
+ // Ignore if it is not in allowed list and only permament flags are allowed to use
16
+ if (connection.selectedMailbox.permanentFlags.indexOf(flag) < 0 && !connection.selectedMailbox.allowPermanentFlags) {
17
+ return;
18
+ }
19
+
20
+ if (messageFlags.indexOf(flag) < 0) {
21
+ messageFlags.push(flag);
22
+ }
23
+ });
24
+ message.flags = messageFlags;
25
+ connection.server.rememberFlags(connection.selectedMailbox, messageFlags);
26
+ }
27
+
28
+ function addFlags(connection, message, flags) {
29
+ [].concat(flags).forEach(flag => {
30
+ flag = normalizeSystemFlag(typeof flag === 'string' ? flag : String(flag.value || ''));
31
+ checkSystemFlags(connection, flag);
32
+
33
+ // Ignore if it is not in allowed list and only permament flags are allowed to use
34
+ if (connection.selectedMailbox.permanentFlags.indexOf(flag) < 0 && !connection.selectedMailbox.allowPermanentFlags) {
35
+ return;
36
+ }
37
+
38
+ if (message.flags.indexOf(flag) < 0) {
39
+ message.flags.push(flag);
40
+ }
41
+ });
42
+ connection.server.rememberFlags(connection.selectedMailbox, message.flags);
43
+ }
44
+
45
+ function removeFlags(connection, message, flags) {
46
+ [].concat(flags).forEach(flag => {
47
+ flag = normalizeSystemFlag(typeof flag === 'string' ? flag : String(flag.value || ''));
48
+ checkSystemFlags(connection, flag);
49
+
50
+ if (message.flags.indexOf(flag) >= 0) {
51
+ for (let i = 0; i < message.flags.length; i++) {
52
+ if (message.flags[i] === flag) {
53
+ message.flags.splice(i, 1);
54
+ break;
55
+ }
56
+ }
57
+ }
58
+ });
59
+ }
60
+
61
+ function sendUpdate(connection, parsed, data, index, message) {
62
+ const resp = [
63
+ {
64
+ type: 'ATOM',
65
+ value: 'FLAGS'
66
+ },
67
+ connection.getFlags(message).map(flag => {
68
+ return {
69
+ type: 'ATOM',
70
+ value: flag
71
+ };
72
+ })
73
+ ];
74
+
75
+ if ((parsed.command || '').toUpperCase() === 'UID STORE') {
76
+ resp.push({
77
+ type: 'ATOM',
78
+ value: 'UID'
79
+ });
80
+ resp.push(message.uid);
81
+ }
82
+
83
+ connection.send(
84
+ {
85
+ tag: '*',
86
+ attributes: [
87
+ index,
88
+ {
89
+ type: 'ATOM',
90
+ value: 'FETCH'
91
+ },
92
+ resp
93
+ ]
94
+ },
95
+ 'FLAG UPDATE',
96
+ parsed,
97
+ data,
98
+ message
99
+ );
100
+ }
101
+
102
+ storeHandlers.FLAGS = function (connection, message, flags, index, parsed, data) {
103
+ setFlags(connection, message, flags);
104
+ sendUpdate(connection, parsed, data, index, message);
105
+ };
106
+
107
+ storeHandlers['+FLAGS'] = function (connection, message, flags, index, parsed, data) {
108
+ addFlags(connection, message, flags);
109
+ sendUpdate(connection, parsed, data, index, message);
110
+ };
111
+
112
+ storeHandlers['-FLAGS'] = function (connection, message, flags, index, parsed, data) {
113
+ removeFlags(connection, message, flags);
114
+ sendUpdate(connection, parsed, data, index, message);
115
+ };
116
+
117
+ storeHandlers['FLAGS.SILENT'] = function (connection, message, flags) {
118
+ setFlags(connection, message, flags);
119
+ };
120
+
121
+ storeHandlers['+FLAGS.SILENT'] = function (connection, message, flags) {
122
+ addFlags(connection, message, flags);
123
+ };
124
+
125
+ storeHandlers['-FLAGS.SILENT'] = function (connection, message, flags) {
126
+ removeFlags(connection, message, flags);
127
+ };
@@ -0,0 +1,100 @@
1
+ 'use strict';
2
+
3
+ module.exports = function (connection, parsed, data, callback) {
4
+ let folders;
5
+
6
+ if (
7
+ !parsed.attributes ||
8
+ parsed.attributes.length !== 2 ||
9
+ !parsed.attributes[0] ||
10
+ ['STRING', 'LITERAL', 'ATOM'].indexOf(parsed.attributes[0].type) < 0 ||
11
+ !parsed.attributes[1] ||
12
+ ['STRING', 'LITERAL', 'ATOM'].indexOf(parsed.attributes[1].type) < 0
13
+ ) {
14
+ connection.send(
15
+ {
16
+ tag: parsed.tag,
17
+ command: 'BAD',
18
+ attributes: [
19
+ {
20
+ type: 'TEXT',
21
+ value: 'LIST expects 2 string arguments'
22
+ }
23
+ ]
24
+ },
25
+ 'INVALID COMMAND',
26
+ parsed,
27
+ data
28
+ );
29
+ return callback();
30
+ }
31
+
32
+ if (!parsed.attributes[1].value) {
33
+ // empty reference lists separator only
34
+ const namespace = connection.server.storage[parsed.attributes[1].value || connection.server.referenceNamespace];
35
+ if (namespace) {
36
+ connection.send(
37
+ {
38
+ tag: '*',
39
+ command: 'LIST',
40
+ attributes: [
41
+ [
42
+ {
43
+ type: 'ATOM',
44
+ value: '\\Noselect'
45
+ }
46
+ ],
47
+ namespace.separator,
48
+ ''
49
+ ]
50
+ },
51
+ 'LIST ITEM',
52
+ parsed,
53
+ data
54
+ );
55
+ }
56
+ } else {
57
+ folders = connection.server.matchFolders(parsed.attributes[0].value, parsed.attributes[1].value, path => connection.exportMailboxName(path));
58
+
59
+ folders.forEach(folder => {
60
+ connection.send(
61
+ {
62
+ tag: '*',
63
+ command: 'LIST',
64
+ attributes: [
65
+ folder.flags.map(flag => {
66
+ return {
67
+ type: 'ATOM',
68
+ value: flag
69
+ };
70
+ }),
71
+ connection.server.getSeparator(folder),
72
+ connection.exportMailboxName(folder.path)
73
+ ]
74
+ },
75
+ 'LIST ITEM',
76
+ parsed,
77
+ data,
78
+ folder
79
+ );
80
+ });
81
+ }
82
+
83
+ connection.send(
84
+ {
85
+ tag: parsed.tag,
86
+ command: 'OK',
87
+ attributes: [
88
+ {
89
+ type: 'TEXT',
90
+ value: 'Completed'
91
+ }
92
+ ]
93
+ },
94
+ 'LIST',
95
+ parsed,
96
+ data
97
+ );
98
+
99
+ return callback();
100
+ };
@@ -0,0 +1,67 @@
1
+ 'use strict';
2
+
3
+ module.exports = function (connection, parsed, data, callback) {
4
+ // LOGIN expects 2 string params - username and password
5
+ if (
6
+ !parsed.attributes ||
7
+ parsed.attributes.length !== 2 ||
8
+ !parsed.attributes[0] ||
9
+ !parsed.attributes[1] ||
10
+ ['STRING', 'LITERAL', 'ATOM'].indexOf(parsed.attributes[0].type) < 0 ||
11
+ ['STRING', 'LITERAL', 'ATOM'].indexOf(parsed.attributes[1].type) < 0
12
+ ) {
13
+ connection.send(
14
+ {
15
+ tag: parsed.tag,
16
+ command: 'BAD',
17
+ attributes: [
18
+ {
19
+ type: 'TEXT',
20
+ value: 'LOGIN takes 2 string arguments'
21
+ }
22
+ ]
23
+ },
24
+ 'INVALID COMMAND',
25
+ parsed,
26
+ data
27
+ );
28
+ return callback();
29
+ }
30
+
31
+ // User names are unicode strings everywhere (LOGIN, SASL, ACL identifiers). LOGIN does not take UTF-8
32
+ // user names or passwords, a client MUST use AUTHENTICATE for these (RFC 6855 and RFC 9755 section 5)
33
+ if (parsed.attributes.some(attr => /[\x80-\xff]/.test(attr.value))) {
34
+ connection.sendStatus(parsed, data, 'BAD', 'LOGIN does not take UTF-8 user names or passwords, use AUTHENTICATE (RFC 9755 section 5)');
35
+ return callback();
36
+ }
37
+
38
+ const user = connection.server.getUser(parsed.attributes[0].value);
39
+
40
+ if (!user || user.password !== parsed.attributes[1].value) {
41
+ // RFC 5530 section 3: unknown user or bad password
42
+ connection.sendStatus(parsed, data, 'NO', 'Login failed: authentication failure', 'AUTHENTICATIONFAILED', 'LOGIN FAILED');
43
+ return callback();
44
+ }
45
+
46
+ connection.state = 'Authenticated';
47
+ // the authenticated user, for plugins that tell users apart (e.g. ACL)
48
+ connection.username = parsed.attributes[0].value;
49
+
50
+ connection.send(
51
+ {
52
+ tag: parsed.tag,
53
+ command: 'OK',
54
+ attributes: [
55
+ {
56
+ type: 'TEXT',
57
+ value: 'User logged in'
58
+ }
59
+ ]
60
+ },
61
+ 'LOGIN SUCCESS',
62
+ parsed,
63
+ data
64
+ );
65
+
66
+ callback();
67
+ };
@@ -0,0 +1,41 @@
1
+ 'use strict';
2
+
3
+ module.exports = function (connection, parsed, data, callback) {
4
+ connection.state = 'Logout';
5
+
6
+ connection.send(
7
+ {
8
+ tag: '*',
9
+ command: 'BYE',
10
+ attributes: [
11
+ {
12
+ type: 'TEXT',
13
+ value: 'LOGOUT received'
14
+ }
15
+ ]
16
+ },
17
+ 'LOGOUT UNTAGGED',
18
+ parsed,
19
+ data
20
+ );
21
+
22
+ connection.send(
23
+ {
24
+ tag: parsed.tag,
25
+ command: 'OK',
26
+ attributes: [
27
+ {
28
+ type: 'TEXT',
29
+ value: 'Completed'
30
+ }
31
+ ]
32
+ },
33
+ 'LOGOUT COMPLETED',
34
+ parsed,
35
+ data
36
+ );
37
+
38
+ connection.end();
39
+
40
+ callback();
41
+ };
@@ -0,0 +1,87 @@
1
+ 'use strict';
2
+
3
+ module.exports = function (connection, parsed, data, callback) {
4
+ if (
5
+ !parsed.attributes ||
6
+ parsed.attributes.length !== 2 ||
7
+ !parsed.attributes[0] ||
8
+ ['STRING', 'LITERAL', 'ATOM'].indexOf(parsed.attributes[0].type) < 0 ||
9
+ !parsed.attributes[1] ||
10
+ ['STRING', 'LITERAL', 'ATOM'].indexOf(parsed.attributes[1].type) < 0
11
+ ) {
12
+ connection.send(
13
+ {
14
+ tag: parsed.tag,
15
+ command: 'BAD',
16
+ attributes: [
17
+ {
18
+ type: 'TEXT',
19
+ value: 'LSUB expects 2 string arguments'
20
+ }
21
+ ]
22
+ },
23
+ 'INVALID COMMAND',
24
+ parsed,
25
+ data
26
+ );
27
+ return callback();
28
+ }
29
+
30
+ const server = connection.server;
31
+ const match = parsed.attributes[1].value;
32
+ const folders = server.matchFolders(parsed.attributes[0].value, match, path => connection.exportMailboxName(path), server.getSubscriptionTree());
33
+
34
+ folders.forEach(folder => {
35
+ let flags;
36
+ if (folder.subscribed) {
37
+ // the subscription list holds names, a subscribed name is listed even if the mailbox does not exist.
38
+ // \Noselect has a special meaning in LSUB (RFC 5258 section 3.1), so it is not listed for these
39
+ flags = folder.flags.filter(flag => flag !== '\\Noselect');
40
+ } else if (match.substr(-1) === '%') {
41
+ // RFC 3501 section 6.3.9: with "%" a level that is not subscribed but has subscribed names
42
+ // below it is listed with \Noselect
43
+ flags = ['\\Noselect'];
44
+ } else {
45
+ return;
46
+ }
47
+
48
+ connection.send(
49
+ {
50
+ tag: '*',
51
+ command: 'LSUB',
52
+ attributes: [
53
+ flags.map(flag => {
54
+ return {
55
+ type: 'ATOM',
56
+ value: flag
57
+ };
58
+ }),
59
+ server.getSeparator(folder),
60
+ connection.exportMailboxName(folder.path)
61
+ ]
62
+ },
63
+ 'LSUB ITEM',
64
+ parsed,
65
+ data,
66
+ folder
67
+ );
68
+ });
69
+
70
+ connection.send(
71
+ {
72
+ tag: parsed.tag,
73
+ command: 'OK',
74
+ attributes: [
75
+ {
76
+ type: 'TEXT',
77
+ value: 'Completed'
78
+ }
79
+ ]
80
+ },
81
+ 'LSUB',
82
+ parsed,
83
+ data
84
+ );
85
+
86
+ return callback();
87
+ };
@@ -0,0 +1,21 @@
1
+ 'use strict';
2
+
3
+ module.exports = function (connection, parsed, data, callback) {
4
+ connection.send(
5
+ {
6
+ tag: parsed.tag,
7
+ command: 'OK',
8
+ attributes: [
9
+ {
10
+ type: 'TEXT',
11
+ value: 'Completed'
12
+ }
13
+ ]
14
+ },
15
+ 'NOOP completed',
16
+ parsed,
17
+ data
18
+ );
19
+
20
+ callback();
21
+ };
@@ -0,0 +1,102 @@
1
+ 'use strict';
2
+
3
+ module.exports = function (connection, parsed, data, callback) {
4
+ if (
5
+ !parsed.attributes ||
6
+ parsed.attributes.length !== 2 ||
7
+ !parsed.attributes[0] ||
8
+ ['STRING', 'LITERAL', 'ATOM'].indexOf(parsed.attributes[0].type) < 0 ||
9
+ !parsed.attributes[1] ||
10
+ ['STRING', 'LITERAL', 'ATOM'].indexOf(parsed.attributes[1].type) < 0
11
+ ) {
12
+ connection.send(
13
+ {
14
+ tag: parsed.tag,
15
+ command: 'BAD',
16
+ attributes: [
17
+ {
18
+ type: 'TEXT',
19
+ value: 'RENAME expects mailbox source and destination names'
20
+ }
21
+ ]
22
+ },
23
+ 'INVALID COMMAND',
24
+ parsed,
25
+ data
26
+ );
27
+ return callback();
28
+ }
29
+
30
+ const source = parsed.attributes[0].value;
31
+ const destination = parsed.attributes[1].value;
32
+
33
+ const server = connection.server;
34
+ const mailbox = server.getMailbox(source);
35
+ const target = server.getMailbox(destination);
36
+
37
+ const sendError = (code, message) => {
38
+ connection.sendStatus(parsed, data, 'NO', message, code, 'RENAME FAILED');
39
+ return callback();
40
+ };
41
+
42
+ // check everything that can be checked up front, so that a failed RENAME never loses the source
43
+ if (!mailbox || mailbox.flags.indexOf('\\Noselect') >= 0) {
44
+ return sendError('NONEXISTENT', 'Mailbox does not exist');
45
+ }
46
+
47
+ if (target && target.flags.indexOf('\\Noselect') < 0) {
48
+ return sendError('ALREADYEXISTS', 'Mailbox already exists');
49
+ }
50
+
51
+ const separator = server.getSeparator(mailbox);
52
+ if (destination.substr(0, mailbox.path.length + separator.length) === mailbox.path + separator) {
53
+ return sendError('CANNOT', 'Can not move a mailbox into itself');
54
+ }
55
+
56
+ const oldPath = mailbox.path;
57
+ let newPath;
58
+ try {
59
+ if (source.toUpperCase() === 'INBOX') {
60
+ // Renaming INBOX moves its messages to the new mailbox and leaves INBOX empty (RFC 3501 section 6.3.5)
61
+ const newMailbox = server.createMailbox(destination);
62
+ newPath = newMailbox.path;
63
+ mailbox.messages.forEach(message => {
64
+ server.copyMessage(newMailbox, message);
65
+ });
66
+ connection.expungeSpecificMessages(mailbox, () => true);
67
+ } else {
68
+ server.deleteMailbox(source, true);
69
+ try {
70
+ server.createMailbox(destination, mailbox);
71
+ } catch (E) {
72
+ // put the source mailbox back where it was
73
+ server.createMailbox(source, mailbox);
74
+ server.indexFolders();
75
+ throw E;
76
+ }
77
+ server.indexFolders();
78
+ newPath = mailbox.path;
79
+ }
80
+ } catch (E) {
81
+ return sendError(E.code, E.message);
82
+ }
83
+ server.mailboxChanged('rename', newPath, { oldPath });
84
+
85
+ connection.send(
86
+ {
87
+ tag: parsed.tag,
88
+ command: 'OK',
89
+ attributes: [
90
+ {
91
+ type: 'TEXT',
92
+ value: 'RENAME completed'
93
+ }
94
+ ]
95
+ },
96
+ 'RENAME',
97
+ parsed,
98
+ data,
99
+ mailbox
100
+ );
101
+ return callback();
102
+ };
@@ -0,0 +1,76 @@
1
+ 'use strict';
2
+
3
+ const makeSearch = require('./handlers/search');
4
+ const { criteriaValues, sendSearchError } = makeSearch;
5
+
6
+ /**
7
+ * Shared implementation of SEARCH and UID SEARCH
8
+ *
9
+ * @param {Boolean} isUid If true, the response lists UIDs instead of sequence numbers
10
+ */
11
+ function processSearch(isUid, connection, parsed, data, callback) {
12
+ const command = isUid ? 'UID SEARCH' : 'SEARCH';
13
+
14
+ if (!parsed.attributes || !parsed.attributes.length) {
15
+ connection.send(
16
+ {
17
+ tag: parsed.tag,
18
+ command: 'BAD',
19
+ attributes: [
20
+ {
21
+ type: 'TEXT',
22
+ value: command + ' expects search criteria, empty query given'
23
+ }
24
+ ]
25
+ },
26
+ command + ' FAILED',
27
+ parsed,
28
+ data
29
+ );
30
+ return callback();
31
+ }
32
+
33
+ let searchResult;
34
+ try {
35
+ searchResult = makeSearch(connection, connection.getSessionMessages(), criteriaValues(parsed.attributes));
36
+ } catch (E) {
37
+ sendSearchError(connection, parsed, data, E, command + ' FAILED');
38
+ return callback();
39
+ }
40
+
41
+ // RFC 3501 7.2.5: the SEARCH response is sent even when nothing matched. Plugins get the
42
+ // search result, so they can extend the response or replace it (e.g. ESEARCH)
43
+ connection.send(
44
+ {
45
+ tag: '*',
46
+ command: 'SEARCH',
47
+ attributes: searchResult.list.map(item => {
48
+ return isUid ? item.uid : searchResult.numbers[item.uid];
49
+ })
50
+ },
51
+ command,
52
+ parsed,
53
+ data,
54
+ searchResult
55
+ );
56
+
57
+ connection.send(
58
+ {
59
+ tag: parsed.tag,
60
+ command: 'OK',
61
+ attributes: [
62
+ {
63
+ type: 'TEXT',
64
+ value: command + ' completed'
65
+ }
66
+ ]
67
+ },
68
+ command,
69
+ parsed,
70
+ data
71
+ );
72
+ return callback();
73
+ }
74
+
75
+ module.exports = (connection, parsed, data, callback) => processSearch(false, connection, parsed, data, callback);
76
+ module.exports.processSearch = processSearch;