diff --git a/imap-core/lib/indexer/body-structure.js b/imap-core/lib/indexer/body-structure.js
index 4b7110b1..d9aab464 100644
--- a/imap-core/lib/indexer/body-structure.js
+++ b/imap-core/lib/indexer/body-structure.js
@@ -4,7 +4,6 @@ const libmime = require('libmime');
const createEnvelope = require('./create-envelope');
class BodyStructure {
-
constructor(tree, options) {
this.tree = tree;
this.options = options || {};
@@ -38,13 +37,10 @@ class BodyStructure {
case 'text':
return this.processTextNode(node, options);
case 'message':
- if (node.parsedHeader['content-type'].subtype === 'rfc822') {
- if (!options.attachmentRFC822) {
- return this.processRFC822Node(node, options);
- }
- return this.processAttachmentNode(node, options);
+ if (node.parsedHeader['content-type'].subtype === 'rfc822' && node.message && !options.attachmentRFC822) {
+ return this.processRFC822Node(node, options);
}
- // fall through
+ // fall through
default:
return this.processAttachmentNode(node, options);
}
@@ -60,32 +56,32 @@ class BodyStructure {
* @return {Array} A list of basic fields
*/
getBasicFields(node, options) {
- let bodyType = node.parsedHeader['content-type'] && node.parsedHeader['content-type'].type || null;
- let bodySubtype = node.parsedHeader['content-type'] && node.parsedHeader['content-type'].subtype || null;
+ let bodyType = (node.parsedHeader['content-type'] && node.parsedHeader['content-type'].type) || null;
+ let bodySubtype = (node.parsedHeader['content-type'] && node.parsedHeader['content-type'].subtype) || null;
let contentTransfer = node.parsedHeader['content-transfer-encoding'] || '7bit';
return [
// body type
- options.upperCaseKeys ? bodyType && bodyType.toUpperCase() || null : bodyType,
+ options.upperCaseKeys ? (bodyType && bodyType.toUpperCase()) || null : bodyType,
// body subtype
- options.upperCaseKeys ? bodySubtype && bodySubtype.toUpperCase() || null : bodySubtype,
+ options.upperCaseKeys ? (bodySubtype && bodySubtype.toUpperCase()) || null : bodySubtype,
// body parameter parenthesized list
- node.parsedHeader['content-type'] &&
- node.parsedHeader['content-type'].hasParams &&
- this.flatten(Object.keys(node.parsedHeader['content-type'].params).map(key => {
- let value = node.parsedHeader['content-type'].params[key];
- try {
- value = Buffer.from(libmime.decodeWords(value).trim());
- } catch (E) {
- // failed to parse value
- }
- return [
- options.upperCaseKeys ? key.toUpperCase() : key,
- value
- ];
- })) || null,
+ (node.parsedHeader['content-type'] &&
+ node.parsedHeader['content-type'].hasParams &&
+ this.flatten(
+ Object.keys(node.parsedHeader['content-type'].params).map(key => {
+ let value = node.parsedHeader['content-type'].params[key];
+ try {
+ value = Buffer.from(libmime.decodeWords(value).trim());
+ } catch (E) {
+ // failed to parse value
+ }
+ return [options.upperCaseKeys ? key.toUpperCase() : key, value];
+ })
+ )) ||
+ null,
// body id
node.parsedHeader['content-id'] || null,
@@ -94,7 +90,7 @@ class BodyStructure {
node.parsedHeader['content-description'] || null,
// body encoding
- options.upperCaseKeys ? contentTransfer && contentTransfer.toUpperCase() || '7bit' : contentTransfer,
+ options.upperCaseKeys ? (contentTransfer && contentTransfer.toUpperCase()) || '7bit' : contentTransfer,
// body size
node.size
@@ -111,9 +107,8 @@ class BodyStructure {
getExtensionFields(node, options) {
options = options || {};
- let languageString = node.parsedHeader['content-language'] &&
- node.parsedHeader['content-language'].replace(/[ ,]+/g, ',').replace(/^,+|,+$/g, '');
- let language = languageString && languageString.split(',') || null;
+ let languageString = node.parsedHeader['content-language'] && node.parsedHeader['content-language'].replace(/[ ,]+/g, ',').replace(/^,+|,+$/g, '');
+ let language = (languageString && languageString.split(',')) || null;
let data;
// if `contentLanguageString` is true, then use a string instead of single element array
@@ -126,25 +121,24 @@ class BodyStructure {
node.parsedHeader['content-md5'] || null,
// body disposition
- node.parsedHeader['content-disposition'] && [
- options.upperCaseKeys ?
- node.parsedHeader['content-disposition'].value.toUpperCase() :
- node.parsedHeader['content-disposition'].value,
- node.parsedHeader['content-disposition'].params &&
- node.parsedHeader['content-disposition'].hasParams &&
- this.flatten(Object.keys(node.parsedHeader['content-disposition'].params).map(key => {
- let value = node.parsedHeader['content-disposition'].params[key];
- try {
- value = Buffer.from(libmime.decodeWords(value).trim());
- } catch (E) {
- // failed to parse value
- }
- return [
- options.upperCaseKeys ? key.toUpperCase() : key,
- value
- ];
- })) || null
- ] || null,
+ (node.parsedHeader['content-disposition'] && [
+ options.upperCaseKeys ? node.parsedHeader['content-disposition'].value.toUpperCase() : node.parsedHeader['content-disposition'].value,
+ (node.parsedHeader['content-disposition'].params &&
+ node.parsedHeader['content-disposition'].hasParams &&
+ this.flatten(
+ Object.keys(node.parsedHeader['content-disposition'].params).map(key => {
+ let value = node.parsedHeader['content-disposition'].params[key];
+ try {
+ value = Buffer.from(libmime.decodeWords(value).trim());
+ } catch (E) {
+ // failed to parse value
+ }
+ return [options.upperCaseKeys ? key.toUpperCase() : key, value];
+ })
+ )) ||
+ null
+ ]) ||
+ null,
// body language
language
@@ -174,36 +168,35 @@ class BodyStructure {
processMultipartNode(node, options) {
options = options || {};
- let data = (node.childNodes && node.childNodes.map(tree => this.createBodystructure(tree, options)) || [
- []
- ]).
- concat([
+ let data = ((node.childNodes && node.childNodes.map(tree => this.createBodystructure(tree, options))) || [[]]).concat([
// body subtype
- options.upperCaseKeys ? node.multipart && node.multipart.toUpperCase() || null : node.multipart,
+ options.upperCaseKeys ? (node.multipart && node.multipart.toUpperCase()) || null : node.multipart,
// body parameter parenthesized list
- node.parsedHeader['content-type'] &&
- node.parsedHeader['content-type'].hasParams &&
- this.flatten(Object.keys(node.parsedHeader['content-type'].params).map(key => {
- let value = node.parsedHeader['content-type'].params[key];
- try {
- value = Buffer.from(libmime.decodeWords(value).trim());
- } catch (E) {
- // failed to parse value
- }
- return [
- options.upperCaseKeys ? key.toUpperCase() : key,
- value
- ];
- })) || null
+ (node.parsedHeader['content-type'] &&
+ node.parsedHeader['content-type'].hasParams &&
+ this.flatten(
+ Object.keys(node.parsedHeader['content-type'].params).map(key => {
+ let value = node.parsedHeader['content-type'].params[key];
+ try {
+ value = Buffer.from(libmime.decodeWords(value).trim());
+ } catch (E) {
+ // failed to parse value
+ }
+ return [options.upperCaseKeys ? key.toUpperCase() : key, value];
+ })
+ )) ||
+ null
]);
if (options.body) {
return data;
} else {
- return data.
- // skip body MD5 from extension fields
- concat(this.getExtensionFields(node, options).slice(1));
+ return (
+ data
+ // skip body MD5 from extension fields
+ .concat(this.getExtensionFields(node, options).slice(1))
+ );
}
}
@@ -217,9 +210,7 @@ class BodyStructure {
processTextNode(node, options) {
options = options || {};
- let data = [].concat(this.getBasicFields(node, options)).concat([
- node.lineCount
- ]);
+ let data = [].concat(this.getBasicFields(node, options)).concat([node.lineCount]);
if (!options.body) {
data = data.concat(this.getExtensionFields(node, options));
@@ -261,10 +252,7 @@ class BodyStructure {
data.push(createEnvelope(node.message.parsedHeader));
data.push(this.createBodystructure(node.message, options));
- data = data.concat(
- node.lineCount
- ).
- concat(this.getExtensionFields(node, options));
+ data = data.concat(node.lineCount).concat(this.getExtensionFields(node, options));
return data;
}
@@ -291,7 +279,6 @@ class BodyStructure {
}
return result;
}
-
}
// Expose to the world
diff --git a/imap-core/lib/indexer/create-envelope.js b/imap-core/lib/indexer/create-envelope.js
index 13dd818d..69d8fa50 100644
--- a/imap-core/lib/indexer/create-envelope.js
+++ b/imap-core/lib/indexer/create-envelope.js
@@ -11,8 +11,7 @@ const punycode = require('punycode');
* @param {Object} message A parsed mime tree node
* @return {Object} ENVELOPE compatible object
*/
-module.exports = function (header) {
-
+module.exports = function(header) {
let subject = Array.isArray(header.subject) ? header.subject.reverse().filter(line => line.trim()) : header.subject;
subject = Buffer.from(subject || '', 'binary').toString();
@@ -78,9 +77,7 @@ function processAddress(arr, defaults) {
domain = Buffer.from(punycode.toUnicode(domain));
}
- result.push([
- name, null, user, domain
- ]);
+ result.push([name, null, user, domain]);
} else {
// Handle group syntax
let name = addr.name || '';
diff --git a/imap-core/lib/indexer/indexer.js b/imap-core/lib/indexer/indexer.js
index d2c85441..b8507148 100644
--- a/imap-core/lib/indexer/indexer.js
+++ b/imap-core/lib/indexer/indexer.js
@@ -15,6 +15,7 @@ const iconv = require('iconv-lite');
const he = require('he');
const htmlToText = require('html-to-text');
const crypto = require('crypto');
+
let cryptoAsync;
try {
cryptoAsync = require('@ronomon/crypto-async'); // eslint-disable-line global-require
@@ -23,7 +24,6 @@ try {
}
class Indexer {
-
constructor(options) {
this.options = options || {};
this.fetchOptions = this.options.fetchOptions || {};
@@ -67,7 +67,6 @@ class Indexer {
};
let walk = (node, next) => {
-
if (!textOnly || !root) {
append(formatHeaders(node.header).join('\r\n') + '\r\n');
}
@@ -157,7 +156,6 @@ class Indexer {
};
let walk = (node, next) => {
-
if (aborted) {
return next();
}
@@ -234,9 +232,11 @@ class Indexer {
}
};
- setImmediate(walk.bind(null, mimeTree, () => {
- res.end();
- }));
+ setImmediate(
+ walk.bind(null, mimeTree, () => {
+ res.end();
+ })
+ );
// if called then stops resolving rest of the message
res.abort = () => {
@@ -290,7 +290,9 @@ class Indexer {
let parsedDisposition = node.parsedHeader['content-disposition'];
let transferEncoding = (node.parsedHeader['content-transfer-encoding'] || '7bit').toLowerCase().trim();
- let contentType = (parsedContentType && parsedContentType.value || (node.rootNode ? 'text/plain' : 'application/octet-stream')).toLowerCase().trim();
+ let contentType = ((parsedContentType && parsedContentType.value) || (node.rootNode ? 'text/plain' : 'application/octet-stream'))
+ .toLowerCase()
+ .trim();
alternative = alternative || contentType === 'multipart/alternative';
related = related || contentType === 'multipart/related';
@@ -302,13 +304,16 @@ class Indexer {
}
}
- let disposition = (parsedDisposition && parsedDisposition.value || '').toLowerCase().trim() || false;
+ let disposition = ((parsedDisposition && parsedDisposition.value) || '').toLowerCase().trim() || false;
let isInlineText = false;
let isMultipart = contentType.split('/')[0] === 'multipart';
// If the current node is HTML or Plaintext then allow larger content included in the mime tree
// Also decode text/html value
- if (['text/plain', 'text/html', 'text/rfc822-headers', 'message/delivery-status'].includes(contentType) && (!disposition || disposition === 'inline')) {
+ if (
+ ['text/plain', 'text/html', 'text/rfc822-headers', 'message/delivery-status'].includes(contentType) &&
+ (!disposition || disposition === 'inline')
+ ) {
isInlineText = true;
if (node.body && node.body.length) {
let charset = parsedContentType.params.charset || 'windows-1257';
@@ -353,7 +358,12 @@ class Indexer {
let attachmentId = 'ATT' + leftPad(++idcount, '0', 5);
map[attachmentId] = new ObjectID();
- let fileName = (node.parsedHeader['content-disposition'] && node.parsedHeader['content-disposition'].params && node.parsedHeader['content-disposition'].params.filename) || (node.parsedHeader['content-type'] && node.parsedHeader['content-type'].params && node.parsedHeader['content-type'].params.name) || false;
+ let fileName =
+ (node.parsedHeader['content-disposition'] &&
+ node.parsedHeader['content-disposition'].params &&
+ node.parsedHeader['content-disposition'].params.filename) ||
+ (node.parsedHeader['content-type'] && node.parsedHeader['content-type'].params && node.parsedHeader['content-type'].params.name) ||
+ false;
let contentId = (node.parsedHeader['content-id'] || '').toString().replace(/<|>/g, '').trim();
if (fileName) {
@@ -363,7 +373,7 @@ class Indexer {
// failed to parse filename, keep as is (most probably an unknown charset is used)
}
} else {
- fileName = (crypto.randomBytes(4).toString('hex') + '.' + libmime.detectExtension(contentType));
+ fileName = crypto.randomBytes(4).toString('hex') + '.' + libmime.detectExtension(contentType);
}
cidMap.set(contentId, {
@@ -425,13 +435,14 @@ class Indexer {
walk(mimeTree, false, false);
- let updateCidLinks = str => str.replace(/\bcid:([^\s"']+)/g, (match, cid) => {
- if (cidMap.has(cid)) {
- let attachment = cidMap.get(cid);
- return 'attachment:' + messageId + '/' + attachment.id.toString();
- }
- return match;
- });
+ let updateCidLinks = str =>
+ str.replace(/\bcid:([^\s"']+)/g, (match, cid) => {
+ if (cidMap.has(cid)) {
+ let attachment = cidMap.get(cid);
+ return 'attachment:' + messageId + '/' + attachment.id.toString();
+ }
+ return match;
+ });
maildata.html = htmlContent.filter(str => str.trim()).map(updateCidLinks);
maildata.text = textContent.filter(str => str.trim()).map(updateCidLinks).join('\n').trim();
@@ -447,10 +458,8 @@ class Indexer {
let nodes = maildata.nodes;
let storeNode = () => {
if (pos >= nodes.length) {
-
// replace attachment IDs with ObjectIDs in the mimeTree
let walk = (node, next) => {
-
if (node.attachmentId && maildata.map[node.attachmentId]) {
node.attachmentId = maildata.map[node.attachmentId];
}
@@ -546,7 +555,6 @@ class Indexer {
* @return {Array} BODY object as a structured Array
*/
getBody(mimeTree) {
-
// BODY – BODYSTRUCTURE without extension data
let body = new BodyStructure(mimeTree, {
upperCaseKeys: true,
@@ -563,7 +571,6 @@ class Indexer {
* @return {Array} BODYSTRUCTURE object as a structured Array
*/
getBodyStructure(mimeTree) {
-
// full BODYSTRUCTURE
let bodystructure = new BodyStructure(mimeTree, {
upperCaseKeys: true,
@@ -647,7 +654,6 @@ class Indexer {
sent = true;
return callback(null, Buffer.concat(buffers, buflen));
});
-
} else {
return setImmediate(() => callback(null, Buffer.from((data || '').toString(), 'binary')));
}
@@ -711,20 +717,28 @@ class Indexer {
if (!selector.headers || !selector.headers.length) {
return '\r\n\r\n';
}
- return formatHeaders(node.header).filter(line => {
- let key = line.split(':').shift().toLowerCase().trim();
- return selector.headers.indexOf(key) >= 0;
- }).join('\r\n') + '\r\n\r\n';
+ return (
+ formatHeaders(node.header)
+ .filter(line => {
+ let key = line.split(':').shift().toLowerCase().trim();
+ return selector.headers.indexOf(key) >= 0;
+ })
+ .join('\r\n') + '\r\n\r\n'
+ );
case 'header.fields.not':
// BODY[HEADER.FIELDS.NOT (Key1 Key2 KeyN)] all but selected header keys
if (!selector.headers || !selector.headers.length) {
return formatHeaders(node.header).join('\r\n') + '\r\n\r\n';
}
- return formatHeaders(node.header).filter(line => {
- let key = line.split(':').shift().toLowerCase().trim();
- return selector.headers.indexOf(key) < 0;
- }).join('\r\n') + '\r\n\r\n';
+ return (
+ formatHeaders(node.header)
+ .filter(line => {
+ let key = line.split(':').shift().toLowerCase().trim();
+ return selector.headers.indexOf(key) < 0;
+ })
+ .join('\r\n') + '\r\n\r\n'
+ );
case 'mime':
// BODY[1.2.3.MIME] mime node header
@@ -754,19 +768,21 @@ function formatHeaders(headers) {
return headers;
}
-
function textToHtml(str) {
-
- let text = '
' + he.
- // encode special chars
- encode(
- str, {
- useNamedReferences: true
- }).
- replace(/\r?\n/g, '\n').trim(). // normalize line endings
- replace(/[ \t]+$/mg, '').trim(). // trim empty line endings
- replace(/\n\n+/g, '
').trim(). // insert
to multiple linebreaks
- replace(/\n/g, '
') + // insert
to single linebreaks
+ let text =
+ '
' +
+ he
+ // encode special chars
+ .encode(str, {
+ useNamedReferences: true
+ })
+ .replace(/\r?\n/g, '\n')
+ .trim() // normalize line endings
+ .replace(/[ \t]+$/gm, '')
+ .trim() // trim empty line endings
+ .replace(/\n\n+/g, '
')
+ .trim() // insert
to multiple linebreaks
+ .replace(/\n/g, '
') + // insert
to single linebreaks
'
';
return text;
diff --git a/imap-core/lib/indexer/parse-mime-tree.js b/imap-core/lib/indexer/parse-mime-tree.js
index 24afb44a..ed5b61dc 100644
--- a/imap-core/lib/indexer/parse-mime-tree.js
+++ b/imap-core/lib/indexer/parse-mime-tree.js
@@ -1,6 +1,6 @@
'use strict';
-let addressparser = require('nodemailer/lib/addressparser');
+const addressparser = require('nodemailer/lib/addressparser');
/**
* Parses a RFC822 message into a structured object (JSON compatible)
@@ -9,9 +9,7 @@ let addressparser = require('nodemailer/lib/addressparser');
* @param {String|Buffer} rfc822 Raw body of the message
*/
class MIMEParser {
-
constructor(rfc822) {
-
// ensure the input is a binary string
this.rfc822 = (rfc822 || '').toString('binary');
@@ -38,7 +36,6 @@ class MIMEParser {
line = this.readLine();
switch (this._node.state) {
-
case 'header': // process header section
if (this.rawBody) {
this.rawBody += prevBr + line;
@@ -58,8 +55,11 @@ class MIMEParser {
this.rawBody += prevBr + line;
if (this._node.parentBoundary && (line === '--' + this._node.parentBoundary || line === '--' + this._node.parentBoundary + '--')) {
-
- if (this._node.parsedHeader['content-type'].value === 'message/rfc822') {
+ if (
+ this._node.parsedHeader['content-type'].value === 'message/rfc822' &&
+ (!this._node.parsedHeader['content-transfer-encoding'] ||
+ ['7bit', '8bit', 'binary'].includes(this._node.parsedHeader['content-transfer-encoding']))
+ ) {
this._node.message = parse(this._node.body.join(''));
}
@@ -78,7 +78,8 @@ class MIMEParser {
}
break;
- default: // never should be reached
+ default:
+ // never should be reached
throw new Error('Unexpected state');
}
@@ -107,7 +108,6 @@ class MIMEParser {
* from the tree (circular references prohibit conversion to JSON)
*/
finalizeTree() {
-
if (this._node.state === 'header') {
this.processNodeHeader();
this.processContentType();
@@ -125,9 +125,12 @@ class MIMEParser {
node.lineCount = node.body.length;
node.body = Buffer.from(
- node.body.join('').
- // ensure proper line endings
- replace(/\r?\n/g, '\r\n'), 'binary');
+ node.body
+ .join('')
+ // ensure proper line endings
+ .replace(/\r?\n/g, '\r\n'),
+ 'binary'
+ );
node.size = node.body.length;
}
node.childNodes.forEach(walker);
@@ -243,7 +246,8 @@ class MIMEParser {
subtype: '',
params: {}
},
- match, processEncodedWords = {};
+ match,
+ processEncodedWords = {};
(headerValue || '').split(';').forEach((part, i) => {
let key, value;
diff --git a/imap.js b/imap.js
index 0f0a771f..69712fef 100644
--- a/imap.js
+++ b/imap.js
@@ -69,67 +69,93 @@ let userHandler;
let gcTimeout;
let gcLock;
-server.onAuth = function (login, session, callback) {
+server.onAuth = function(login, session, callback) {
let username = (login.username || '').toString().trim();
- userHandler.authenticate(username, login.password, {
- protocol: 'IMAP',
- ip: session.remoteAddress
- }, (err, result) => {
- if (err) {
- return callback(err);
- }
- if (!result) {
- return callback();
- }
-
- if (result.scope === 'master' && result.enabled2fa) {
- // master password not allowed if 2fa is enabled!
- return callback();
- }
-
- callback(null, {
- user: {
- id: result.user,
- username: result.username
+ userHandler.authenticate(
+ username,
+ login.password,
+ {
+ protocol: 'IMAP',
+ ip: session.remoteAddress
+ },
+ (err, result) => {
+ if (err) {
+ return callback(err);
}
- });
- });
+ if (!result) {
+ return callback();
+ }
+
+ if (result.scope === 'master' && result.enabled2fa) {
+ // master password not allowed if 2fa is enabled!
+ return callback();
+ }
+
+ callback(null, {
+ user: {
+ id: result.user,
+ username: result.username
+ }
+ });
+ }
+ );
};
// LIST "" "*"
// Returns all folders, query is informational
// folders is either an Array or a Map
-server.onList = function (query, session, callback) {
- this.logger.debug({
- tnx: 'list',
- cid: session.id
- }, '[%s] LIST for "%s"', session.id, query);
- db.database.collection('mailboxes').find({
- user: session.user.id
- }).toArray(callback);
+server.onList = function(query, session, callback) {
+ this.logger.debug(
+ {
+ tnx: 'list',
+ cid: session.id
+ },
+ '[%s] LIST for "%s"',
+ session.id,
+ query
+ );
+ db.database
+ .collection('mailboxes')
+ .find({
+ user: session.user.id
+ })
+ .toArray(callback);
};
// LSUB "" "*"
// Returns all subscribed folders, query is informational
// folders is either an Array or a Map
-server.onLsub = function (query, session, callback) {
- this.logger.debug({
- tnx: 'lsub',
- cid: session.id
- }, '[%s] LSUB for "%s"', session.id, query);
- db.database.collection('mailboxes').find({
- user: session.user.id,
- subscribed: true
- }).toArray(callback);
+server.onLsub = function(query, session, callback) {
+ this.logger.debug(
+ {
+ tnx: 'lsub',
+ cid: session.id
+ },
+ '[%s] LSUB for "%s"',
+ session.id,
+ query
+ );
+ db.database
+ .collection('mailboxes')
+ .find({
+ user: session.user.id,
+ subscribed: true
+ })
+ .toArray(callback);
};
// SUBSCRIBE "path/to/mailbox"
-server.onSubscribe = function (path, session, callback) {
- this.logger.debug({
- tnx: 'subscribe',
- cid: session.id
- }, '[%s] SUBSCRIBE to "%s"', session.id, path);
+server.onSubscribe = function(path, session, callback) {
+ this.logger.debug(
+ {
+ tnx: 'subscribe',
+ cid: session.id
+ },
+ '[%s] SUBSCRIBE to "%s"',
+ session.id,
+ path
+ );
db.database.collection('mailboxes').findOneAndUpdate({
user: session.user.id,
path
@@ -152,11 +178,16 @@ server.onSubscribe = function (path, session, callback) {
};
// UNSUBSCRIBE "path/to/mailbox"
-server.onUnsubscribe = function (path, session, callback) {
- this.logger.debug({
- tnx: 'unsubscribe',
- cid: session.id
- }, '[%s] UNSUBSCRIBE from "%s"', session.id, path);
+server.onUnsubscribe = function(path, session, callback) {
+ this.logger.debug(
+ {
+ tnx: 'unsubscribe',
+ cid: session.id
+ },
+ '[%s] UNSUBSCRIBE from "%s"',
+ session.id,
+ path
+ );
db.database.collection('mailboxes').findOneAndUpdate({
user: session.user.id,
path
@@ -179,11 +210,16 @@ server.onUnsubscribe = function (path, session, callback) {
};
// CREATE "path/to/mailbox"
-server.onCreate = function (path, session, callback) {
- this.logger.debug({
- tnx: 'create',
- cid: session.id
- }, '[%s] CREATE "%s"', session.id, path);
+server.onCreate = function(path, session, callback) {
+ this.logger.debug(
+ {
+ tnx: 'create',
+ cid: session.id
+ },
+ '[%s] CREATE "%s"',
+ session.id,
+ path
+ );
db.database.collection('mailboxes').findOne({
user: session.user.id,
path
@@ -229,11 +265,17 @@ server.onCreate = function (path, session, callback) {
// RENAME "path/to/mailbox" "new/path"
// NB! RENAME affects child and hierarchy mailboxes as well, this example does not do this
-server.onRename = function (path, newname, session, callback) {
- this.logger.debug({
- tnx: 'rename',
- cid: session.id
- }, '[%s] RENAME "%s" to "%s"', session.id, path, newname);
+server.onRename = function(path, newname, session, callback) {
+ this.logger.debug(
+ {
+ tnx: 'rename',
+ cid: session.id
+ },
+ '[%s] RENAME "%s" to "%s"',
+ session.id,
+ path,
+ newname
+ );
db.database.collection('mailboxes').findOne({
user: session.user.id,
path: newname
@@ -268,11 +310,16 @@ server.onRename = function (path, newname, session, callback) {
};
// DELETE "path/to/mailbox"
-server.onDelete = function (path, session, callback) {
- this.logger.debug({
- tnx: 'delete',
- cid: session.id
- }, '[%s] DELETE "%s"', session.id, path);
+server.onDelete = function(path, session, callback) {
+ this.logger.debug(
+ {
+ tnx: 'delete',
+ cid: session.id
+ },
+ '[%s] DELETE "%s"',
+ session.id,
+ path
+ );
db.database.collection('mailboxes').findOne({
user: session.user.id,
path
@@ -295,72 +342,90 @@ server.onDelete = function (path, session, callback) {
}
// calculate mailbox size by aggregating the size's of all messages
- db.database.collection('messages').aggregate([{
- $match: {
- mailbox: mailbox._id
- }
- }, {
- $group: {
- _id: {
- mailbox: '$mailbox'
+ db.database
+ .collection('messages')
+ .aggregate(
+ [
+ {
+ $match: {
+ mailbox: mailbox._id
+ }
},
- storageUsed: {
- $sum: '$size'
+ {
+ $group: {
+ _id: {
+ mailbox: '$mailbox'
+ },
+ storageUsed: {
+ $sum: '$size'
+ }
+ }
+ }
+ ],
+ {
+ cursor: {
+ batchSize: 1
}
}
- }], {
- cursor: {
- batchSize: 1
- }
- }).toArray((err, res) => {
- if (err) {
- return callback(err);
- }
-
- let storageUsed = res && res[0] && res[0].storageUsed || 0;
-
- db.database.collection('messages').deleteMany({
- mailbox: mailbox._id
- }, err => {
+ )
+ .toArray((err, res) => {
if (err) {
return callback(err);
}
- let done = () => {
- db.database.collection('journal').deleteMany({
- mailbox: mailbox._id
- }, err => {
- if (err) {
- return callback(err);
- }
- callback(null, true);
- });
- };
+ let storageUsed = (res && res[0] && res[0].storageUsed) || 0;
- if (!storageUsed) {
- return done();
- }
-
- // decrement quota counters
- db.database.collection('users').findOneAndUpdate({
- _id: mailbox.user
- }, {
- $inc: {
- storageUsed: -Number(storageUsed) || 0
+ db.database.collection('messages').deleteMany({
+ mailbox: mailbox._id
+ }, err => {
+ if (err) {
+ return callback(err);
}
- }, done);
+
+ let done = () => {
+ db.database.collection('journal').deleteMany({
+ mailbox: mailbox._id
+ }, err => {
+ if (err) {
+ return callback(err);
+ }
+ callback(null, true);
+ });
+ };
+
+ if (!storageUsed) {
+ return done();
+ }
+
+ // decrement quota counters
+ db.database.collection('users').findOneAndUpdate(
+ {
+ _id: mailbox.user
+ },
+ {
+ $inc: {
+ storageUsed: -Number(storageUsed) || 0
+ }
+ },
+ done
+ );
+ });
});
- });
});
});
};
// SELECT/EXAMINE
-server.onOpen = function (path, session, callback) {
- this.logger.debug({
- tnx: 'open',
- cid: session.id
- }, '[%s] Opening "%s"', session.id, path);
+server.onOpen = function(path, session, callback) {
+ this.logger.debug(
+ {
+ tnx: 'open',
+ cid: session.id
+ },
+ '[%s] Opening "%s"',
+ session.id,
+ path
+ );
db.database.collection('mailboxes').findOne({
user: session.user.id,
path
@@ -372,28 +437,36 @@ server.onOpen = function (path, session, callback) {
return callback(null, 'NONEXISTENT');
}
- db.database.collection('messages').find({
- mailbox: mailbox._id
- }).project({
- uid: true
- }).sort([
- ['uid', 1]
- ]).toArray((err, messages) => {
- if (err) {
- return callback(err);
- }
- mailbox.uidList = messages.map(message => message.uid);
- callback(null, mailbox);
- });
+ db.database
+ .collection('messages')
+ .find({
+ mailbox: mailbox._id
+ })
+ .project({
+ uid: true
+ })
+ .sort([['uid', 1]])
+ .toArray((err, messages) => {
+ if (err) {
+ return callback(err);
+ }
+ mailbox.uidList = messages.map(message => message.uid);
+ callback(null, mailbox);
+ });
});
};
// STATUS (X Y X)
-server.onStatus = function (path, session, callback) {
- this.logger.debug({
- tnx: 'status',
- cid: session.id
- }, '[%s] Requested status for "%s"', session.id, path);
+server.onStatus = function(path, session, callback) {
+ this.logger.debug(
+ {
+ tnx: 'status',
+ cid: session.id
+ },
+ '[%s] Requested status for "%s"',
+ session.id,
+ path
+ );
db.database.collection('mailboxes').findOne({
user: session.user.id,
path
@@ -405,38 +478,48 @@ server.onStatus = function (path, session, callback) {
return callback(null, 'NONEXISTENT');
}
- db.database.collection('messages').find({
- mailbox: mailbox._id
- }).count((err, total) => {
- if (err) {
- return callback(err);
- }
- db.database.collection('messages').find({
- mailbox: mailbox._id,
- seen: false
- }).count((err, unseen) => {
+ db.database
+ .collection('messages')
+ .find({
+ mailbox: mailbox._id
+ })
+ .count((err, total) => {
if (err) {
return callback(err);
}
+ db.database
+ .collection('messages')
+ .find({
+ mailbox: mailbox._id,
+ seen: false
+ })
+ .count((err, unseen) => {
+ if (err) {
+ return callback(err);
+ }
- return callback(null, {
- messages: total,
- uidNext: mailbox.uidNext,
- uidValidity: mailbox.uidValidity,
- unseen
- });
+ return callback(null, {
+ messages: total,
+ uidNext: mailbox.uidNext,
+ uidValidity: mailbox.uidValidity,
+ unseen
+ });
+ });
});
- });
-
});
};
// APPEND mailbox (flags) date message
-server.onAppend = function (path, flags, date, raw, session, callback) {
- this.logger.debug({
- tnx: 'append',
- cid: session.id
- }, '[%s] Appending message to "%s"', session.id, path);
+server.onAppend = function(path, flags, date, raw, session, callback) {
+ this.logger.debug(
+ {
+ tnx: 'append',
+ cid: session.id
+ },
+ '[%s] Appending message to "%s"',
+ session.id,
+ path
+ );
db.database.collection('users').findOne({
_id: session.user.id
@@ -452,31 +535,34 @@ server.onAppend = function (path, flags, date, raw, session, callback) {
return callback(false, 'OVERQUOTA');
}
- messageHandler.add({
- user: session.user.id,
- path,
- meta: {
- source: 'IMAP',
- to: session.user.username,
- time: Date.now()
+ messageHandler.add(
+ {
+ user: session.user.id,
+ path,
+ meta: {
+ source: 'IMAP',
+ to: session.user.username,
+ time: Date.now()
+ },
+ session,
+ date,
+ flags,
+ raw
},
- session,
- date,
- flags,
- raw
- }, (err, status, data) => {
- if (err) {
- if (err.imapResponse) {
- return callback(null, err.imapResponse);
+ (err, status, data) => {
+ if (err) {
+ if (err.imapResponse) {
+ return callback(null, err.imapResponse);
+ }
+ return callback(err);
}
- return callback(err);
+ callback(null, status, data);
}
- callback(null, status, data);
- });
+ );
});
};
-server.updateMailboxFlags = function (mailbox, update, callback) {
+server.updateMailboxFlags = function(mailbox, update, callback) {
if (update.action === 'remove') {
// we didn't add any new flags, so there's nothing to update
return callback();
@@ -504,23 +590,33 @@ server.updateMailboxFlags = function (mailbox, update, callback) {
// found some new flags not yet set for mailbox
// FIXME: Should we send unsolicited FLAGS and PERMANENTFLAGS notifications? Probably not
- return db.database.collection('mailboxes').findOneAndUpdate({
- _id: mailbox._id
- }, {
- $addToSet: {
- flags: {
- $each: newFlags
+ return db.database.collection('mailboxes').findOneAndUpdate(
+ {
+ _id: mailbox._id
+ },
+ {
+ $addToSet: {
+ flags: {
+ $each: newFlags
+ }
}
- }
- }, {}, callback);
+ },
+ {},
+ callback
+ );
};
// STORE / UID STORE, updates flags for selected UIDs
-server.onStore = function (path, update, session, callback) {
- this.logger.debug({
- tnx: 'store',
- cid: session.id
- }, '[%s] Updating messages in "%s"', session.id, path);
+server.onStore = function(path, update, session, callback) {
+ this.logger.debug(
+ {
+ tnx: 'store',
+ cid: session.id
+ },
+ '[%s] Updating messages in "%s"',
+ session.id,
+ path
+ );
db.database.collection('mailboxes').findOne({
user: session.user.id,
path
@@ -557,15 +653,15 @@ server.onStore = function (path, update, session, callback) {
queryAll = true;
}
- let cursor = db.database.collection('messages').
- find(query).
- project({
- _id: true,
- uid: true,
- flags: true
- }).sort([
- ['uid', 1]
- ]);
+ let cursor = db.database
+ .collection('messages')
+ .find(query)
+ .project({
+ _id: true,
+ uid: true,
+ flags: true
+ })
+ .sort([['uid', 1]]);
let updateEntries = [];
let notifyEntries = [];
@@ -580,7 +676,8 @@ server.onStore = function (path, update, session, callback) {
this.notifier.addEntries(session.user.id, path, notifyEntries, () => {
notifyEntries = [];
this.notifier.fire(session.user.id, path);
- if (args[0]) { // first argument is an error
+ if (args[0]) {
+ // first argument is an error
return callback(...args);
} else {
server.updateMailboxFlags(mailbox, update, () => callback(...args));
@@ -589,7 +686,8 @@ server.onStore = function (path, update, session, callback) {
});
}
this.notifier.fire(session.user.id, path);
- if (args[0]) { // first argument is an error
+ if (args[0]) {
+ // first argument is an error
return callback(...args);
} else {
server.updateMailboxFlags(mailbox, update, () => callback(...args));
@@ -644,112 +742,124 @@ server.onStore = function (path, update, session, callback) {
}
break;
- case 'add':
- {
- let newFlags = [];
- message.flags = message.flags.concat(update.value.filter(flag => {
+ case 'add': {
+ let newFlags = [];
+ message.flags = message.flags.concat(
+ update.value.filter(flag => {
if (!existingFlags.includes(flag.toLowerCase().trim())) {
updated = true;
newFlags.push(flag);
return true;
}
return false;
- }));
+ })
+ );
- // add flags
- if (updated) {
- flagsupdate = {
- $addToSet: {
- flags: {
- $each: newFlags
- }
+ // add flags
+ if (updated) {
+ flagsupdate = {
+ $addToSet: {
+ flags: {
+ $each: newFlags
}
- };
+ }
+ };
- if (newFlags.includes('\\Seen') || newFlags.includes('\\Flagged') || newFlags.includes('\\Deleted') || newFlags.includes('\\Draft')) {
- flagsupdate.$set = {};
- if (newFlags.includes('\\Seen')) {
- flagsupdate.$set = {
- seen: true
- };
- }
- if (newFlags.includes('\\Flagged')) {
- flagsupdate.$set = {
- flagged: true
- };
- }
- if (newFlags.includes('\\Deleted')) {
- flagsupdate.$set = {
- deleted: true
- };
- }
- if (newFlags.includes('\\Draft')) {
- flagsupdate.$set = {
- draft: true
- };
- }
+ if (
+ newFlags.includes('\\Seen') ||
+ newFlags.includes('\\Flagged') ||
+ newFlags.includes('\\Deleted') ||
+ newFlags.includes('\\Draft')
+ ) {
+ flagsupdate.$set = {};
+ if (newFlags.includes('\\Seen')) {
+ flagsupdate.$set = {
+ seen: true
+ };
+ }
+ if (newFlags.includes('\\Flagged')) {
+ flagsupdate.$set = {
+ flagged: true
+ };
+ }
+ if (newFlags.includes('\\Deleted')) {
+ flagsupdate.$set = {
+ deleted: true
+ };
+ }
+ if (newFlags.includes('\\Draft')) {
+ flagsupdate.$set = {
+ draft: true
+ };
}
}
- break;
}
+ break;
+ }
- case 'remove':
- {
- // We need to use the case of existing flags when removing
- let oldFlags = [];
- let flagsUpdates = update.value.map(flag => flag.toLowerCase().trim());
- message.flags = message.flags.filter(flag => {
- if (!flagsUpdates.includes(flag.toLowerCase().trim())) {
- return true;
+ case 'remove': {
+ // We need to use the case of existing flags when removing
+ let oldFlags = [];
+ let flagsUpdates = update.value.map(flag => flag.toLowerCase().trim());
+ message.flags = message.flags.filter(flag => {
+ if (!flagsUpdates.includes(flag.toLowerCase().trim())) {
+ return true;
+ }
+ oldFlags.push(flag);
+ updated = true;
+ return false;
+ });
+
+ // remove flags
+ if (updated) {
+ flagsupdate = {
+ $pull: {
+ flags: {
+ $in: oldFlags
+ }
}
- oldFlags.push(flag);
- updated = true;
- return false;
- });
-
- // remove flags
- if (updated) {
- flagsupdate = {
- $pull: {
- flags: {
- $in: oldFlags
- }
- }
- };
- if (oldFlags.includes('\\Seen') || oldFlags.includes('\\Flagged') || oldFlags.includes('\\Deleted') || oldFlags.includes('\\Draft')) {
- flagsupdate.$set = {};
- if (oldFlags.includes('\\Seen')) {
- flagsupdate.$set = {
- seen: false
- };
- }
- if (oldFlags.includes('\\Flagged')) {
- flagsupdate.$set = {
- flagged: false
- };
- }
- if (oldFlags.includes('\\Deleted')) {
- flagsupdate.$set = {
- deleted: false
- };
- }
- if (oldFlags.includes('\\Draft')) {
- flagsupdate.$set = {
- draft: false
- };
- }
+ };
+ if (
+ oldFlags.includes('\\Seen') ||
+ oldFlags.includes('\\Flagged') ||
+ oldFlags.includes('\\Deleted') ||
+ oldFlags.includes('\\Draft')
+ ) {
+ flagsupdate.$set = {};
+ if (oldFlags.includes('\\Seen')) {
+ flagsupdate.$set = {
+ seen: false
+ };
+ }
+ if (oldFlags.includes('\\Flagged')) {
+ flagsupdate.$set = {
+ flagged: false
+ };
+ }
+ if (oldFlags.includes('\\Deleted')) {
+ flagsupdate.$set = {
+ deleted: false
+ };
+ }
+ if (oldFlags.includes('\\Draft')) {
+ flagsupdate.$set = {
+ draft: false
+ };
}
}
- break;
}
+ break;
+ }
}
if (!update.silent) {
// print updated state of the message
- session.writeStream.write(session.formatResponse('FETCH', message.uid, {
- uid: update.isUid ? message.uid : false,
- flags: message.flags
- }));
+ session.writeStream.write(
+ session.formatResponse('FETCH', message.uid, {
+ uid: update.isUid ? message.uid : false,
+ flags: message.flags
+ })
+ );
}
if (updated) {
@@ -800,11 +910,16 @@ server.onStore = function (path, update, session, callback) {
};
// EXPUNGE deletes all messages in selected mailbox marked with \Delete
-server.onExpunge = function (path, update, session, callback) {
- this.logger.debug({
- tnx: 'expunge',
- cid: session.id
- }, '[%s] Deleting messages from "%s"', session.id, path);
+server.onExpunge = function(path, update, session, callback) {
+ this.logger.debug(
+ {
+ tnx: 'expunge',
+ cid: session.id
+ },
+ '[%s] Deleting messages from "%s"',
+ session.id,
+ path
+ );
db.database.collection('mailboxes').findOne({
user: session.user.id,
path
@@ -816,18 +931,20 @@ server.onExpunge = function (path, update, session, callback) {
return callback(null, 'NONEXISTENT');
}
- let cursor = db.database.collection('messages').find({
- mailbox: mailbox._id,
- deleted: true
- }).project({
- _id: true,
- uid: true,
- size: true,
- map: true,
- magic: true
- }).sort([
- ['uid', 1]
- ]);
+ let cursor = db.database
+ .collection('messages')
+ .find({
+ mailbox: mailbox._id,
+ deleted: true
+ })
+ .project({
+ _id: true,
+ uid: true,
+ size: true,
+ map: true,
+ magic: true
+ })
+ .sort([['uid', 1]]);
let deletedMessages = 0;
let deletedStorage = 0;
@@ -837,13 +954,17 @@ server.onExpunge = function (path, update, session, callback) {
return next();
}
- db.database.collection('users').findOneAndUpdate({
- _id: mailbox.user
- }, {
- $inc: {
- storageUsed: -deletedStorage
- }
- }, next);
+ db.database.collection('users').findOneAndUpdate(
+ {
+ _id: mailbox.user
+ },
+ {
+ $inc: {
+ storageUsed: -deletedStorage
+ }
+ },
+ next
+ );
};
let processNext = () => {
@@ -878,12 +999,17 @@ server.onExpunge = function (path, update, session, callback) {
if (!attachments.length) {
// not stored attachments
- return this.notifier.addEntries(session.user.id, path, {
- command: 'EXPUNGE',
- ignore: session.id,
- uid: message.uid,
- message: message._id
- }, processNext);
+ return this.notifier.addEntries(
+ session.user.id,
+ path,
+ {
+ command: 'EXPUNGE',
+ ignore: session.id,
+ uid: message.uid,
+ message: message._id
+ },
+ processNext
+ );
}
// remove references to attachments (if any exist)
@@ -903,12 +1029,17 @@ server.onExpunge = function (path, update, session, callback) {
if (err) {
// ignore as we don't really care if we have orphans or not
}
- this.notifier.addEntries(session.user.id, path, {
- command: 'EXPUNGE',
- ignore: session.id,
- uid: message.uid,
- message: message._id
- }, processNext);
+ this.notifier.addEntries(
+ session.user.id,
+ path,
+ {
+ command: 'EXPUNGE',
+ ignore: session.id,
+ uid: message.uid,
+ message: message._id
+ },
+ processNext
+ );
});
});
});
@@ -919,11 +1050,17 @@ server.onExpunge = function (path, update, session, callback) {
};
// COPY / UID COPY sequence mailbox
-server.onCopy = function (path, update, session, callback) {
- this.logger.debug({
- tnx: 'copy',
- cid: session.id
- }, '[%s] Copying messages from "%s" to "%s"', session.id, path, update.destination);
+server.onCopy = function(path, update, session, callback) {
+ this.logger.debug(
+ {
+ tnx: 'copy',
+ cid: session.id
+ },
+ '[%s] Copying messages from "%s" to "%s"',
+ session.id,
+ path,
+ update.destination
+ );
db.database.collection('mailboxes').findOne({
user: session.user.id,
path
@@ -946,14 +1083,15 @@ server.onCopy = function (path, update, session, callback) {
return callback(null, 'TRYCREATE');
}
- let cursor = db.database.collection('messages').find({
- mailbox: mailbox._id,
- uid: {
- $in: update.messages
- }
- }).sort([
- ['uid', 1]
- ]); // no projection as we need to copy the entire message
+ let cursor = db.database
+ .collection('messages')
+ .find({
+ mailbox: mailbox._id,
+ uid: {
+ $in: update.messages
+ }
+ })
+ .sort([['uid', 1]]); // no projection as we need to copy the entire message
let copiedMessages = 0;
let copiedStorage = 0;
@@ -962,13 +1100,17 @@ server.onCopy = function (path, update, session, callback) {
if (!copiedMessages) {
return next();
}
- db.database.collection('users').findOneAndUpdate({
- _id: mailbox.user
- }, {
- $inc: {
- storageUsed: copiedStorage
- }
- }, next);
+ db.database.collection('users').findOneAndUpdate(
+ {
+ _id: mailbox.user
+ },
+ {
+ $inc: {
+ storageUsed: copiedStorage
+ }
+ },
+ next
+ );
};
let sourceUid = [];
@@ -1043,11 +1185,16 @@ server.onCopy = function (path, update, session, callback) {
let attachments = Object.keys(message.map || {}).map(key => message.map[key]);
if (!attachments.length) {
- return this.notifier.addEntries(session.user.id, target.path, {
- command: 'EXISTS',
- uid: message.uid,
- message: message._id
- }, processNext);
+ return this.notifier.addEntries(
+ session.user.id,
+ target.path,
+ {
+ command: 'EXISTS',
+ uid: message.uid,
+ message: message._id
+ },
+ processNext
+ );
}
// update attachments
@@ -1067,11 +1214,16 @@ server.onCopy = function (path, update, session, callback) {
if (err) {
// should we care about this error?
}
- this.notifier.addEntries(session.user.id, target.path, {
- command: 'EXISTS',
- uid: message.uid,
- message: message._id
- }, processNext);
+ this.notifier.addEntries(
+ session.user.id,
+ target.path,
+ {
+ command: 'EXISTS',
+ uid: message.uid,
+ message: message._id
+ },
+ processNext
+ );
});
});
});
@@ -1083,44 +1235,58 @@ server.onCopy = function (path, update, session, callback) {
};
// MOVE / UID MOVE sequence mailbox
-server.onMove = function (path, update, session, callback) {
- this.logger.debug({
- tnx: 'move',
- cid: session.id
- }, '[%s] Moving messages from "%s" to "%s"', session.id, path, update.destination);
+server.onMove = function(path, update, session, callback) {
+ this.logger.debug(
+ {
+ tnx: 'move',
+ cid: session.id
+ },
+ '[%s] Moving messages from "%s" to "%s"',
+ session.id,
+ path,
+ update.destination
+ );
- messageHandler.move({
- user: session.user.id,
- // folder to move messages from
- source: {
+ messageHandler.move(
+ {
user: session.user.id,
- path
+ // folder to move messages from
+ source: {
+ user: session.user.id,
+ path
+ },
+ // folder to move messages to
+ destination: {
+ user: session.user.id,
+ path: update.destination
+ },
+ session,
+ // list of UIDs to move
+ messages: update.messages
},
- // folder to move messages to
- destination: {
- user: session.user.id,
- path: update.destination
- },
- session,
- // list of UIDs to move
- messages: update.messages
- }, (...args) => {
- if (args[0]) {
- if (args[0].imapResponse) {
- return callback(null, args[0].imapResponse);
+ (...args) => {
+ if (args[0]) {
+ if (args[0].imapResponse) {
+ return callback(null, args[0].imapResponse);
+ }
+ return callback(args[0]);
}
- return callback(args[0]);
+ callback(...args);
}
- callback(...args);
- });
+ );
};
// sends results to socket
-server.onFetch = function (path, options, session, callback) {
- this.logger.debug({
- tnx: 'fetch',
- cid: session.id
- }, '[%s] Requested FETCH for "%s"', session.id, path);
+server.onFetch = function(path, options, session, callback) {
+ this.logger.debug(
+ {
+ tnx: 'fetch',
+ cid: session.id
+ },
+ '[%s] Requested FETCH for "%s"',
+ session.id,
+ path
+ );
db.database.collection('mailboxes').findOne({
user: session.user.id,
path
@@ -1194,12 +1360,7 @@ server.onFetch = function (path, options, session, callback) {
return callback(...args);
};
- let cursor = db.database.collection('messages').
- find(query).
- project(projection).
- sort([
- ['uid', 1]
- ]);
+ let cursor = db.database.collection('messages').find(query).project(projection).sort([['uid', 1]]);
let rowCount = 0;
let processNext = () => {
@@ -1223,15 +1384,17 @@ server.onFetch = function (path, options, session, callback) {
message.flags.unshift('\\Seen');
}
- let stream = imapHandler.compileStream(session.formatResponse('FETCH', message.uid, {
- query: options.query,
- values: session.getQueryResponse(options.query, message, {
- logger: this.logger,
- fetchOptions: {},
- database: db.database,
- acceptUTF8Enabled: session.isUTF8Enabled()
+ let stream = imapHandler.compileStream(
+ session.formatResponse('FETCH', message.uid, {
+ query: options.query,
+ values: session.getQueryResponse(options.query, message, {
+ logger: this.logger,
+ fetchOptions: {},
+ database: db.database,
+ acceptUTF8Enabled: session.isUTF8Enabled()
+ })
})
- }));
+ );
stream.description = util.format('* FETCH #%s uid=%s size=%sB ', ++rowCount, message.uid, message.size);
@@ -1247,10 +1410,15 @@ server.onFetch = function (path, options, session, callback) {
return processNext();
}
- this.logger.debug({
- tnx: 'flags',
- cid: session.id
- }, '[%s] UPDATE FLAGS for "%s"', session.id, message.uid);
+ this.logger.debug(
+ {
+ tnx: 'flags',
+ cid: session.id
+ },
+ '[%s] UPDATE FLAGS for "%s"',
+ session.id,
+ message.uid
+ );
isUpdated = true;
@@ -1311,7 +1479,7 @@ server.onFetch = function (path, options, session, callback) {
* IMAP search can be quite complex, so we optimize here for most common queries to be handled
* by MongoDB and then do the final filtering on the client side. This allows
*/
-server.onSearch = function (path, options, session, callback) {
+server.onSearch = function(path, options, session, callback) {
db.database.collection('mailboxes').findOne({
user: session.user.id,
path
@@ -1345,23 +1513,22 @@ server.onSearch = function (path, options, session, callback) {
walkQuery(parent, !ne, [].concat(term.value || []));
break;
- case 'or':
- {
- let $or = [];
+ case 'or': {
+ let $or = [];
- [].concat(term.value || []).forEach(entry => {
- walkQuery($or, false, [].concat(entry || []));
+ [].concat(term.value || []).forEach(entry => {
+ walkQuery($or, false, [].concat(entry || []));
+ });
+
+ if ($or.length) {
+ parent.push({
+ $or
});
-
- if ($or.length) {
- parent.push({
- $or
- });
- }
-
- break;
}
+ break;
+ }
+
case 'text': // search over entire email
case 'body': // search over email body
if (term.value && !ne) {
@@ -1457,26 +1624,32 @@ server.onSearch = function (path, options, session, callback) {
{
// FIXME: this does not match unicode symbols for whatever reason
let regex = Buffer.from(term.value, 'binary').toString().replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
- let entry = term.value ? {
- headers: {
- $elemMatch: {
- key: term.header,
- value: !ne ? {
- $regex: regex,
- $options: 'i'
- } : {
- $not: {
- $regex: regex,
- $options: 'i'
- }
+ let entry = term.value
+ ? {
+ headers: {
+ $elemMatch: {
+ key: term.header,
+ value: !ne
+ ? {
+ $regex: regex,
+ $options: 'i'
+ }
+ : {
+ $not: {
+ $regex: regex,
+ $options: 'i'
+ }
+ }
}
}
}
- } : {
- 'headers.key': !ne ? term.header : {
- $ne: term.header
- }
- };
+ : {
+ 'headers.key': !ne
+ ? term.header
+ : {
+ $ne: term.header
+ }
+ };
parent.push(entry);
}
break;
@@ -1499,18 +1672,25 @@ server.onSearch = function (path, options, session, callback) {
op = '$gte';
break;
}
- let entry = !op ? [{
- $gte: value
- }, {
- $lt: new Date(value.getTime() + 24 * 3600 * 1000)
- }] : {
- [op]: value
- };
+ let entry = !op
+ ? [
+ {
+ $gte: value
+ },
+ {
+ $lt: new Date(value.getTime() + 24 * 3600 * 1000)
+ }
+ ]
+ : {
+ [op]: value
+ };
entry = {
- idate: !ne ? entry : {
- $not: entry
- }
+ idate: !ne
+ ? entry
+ : {
+ $not: entry
+ }
};
parent.push(entry);
@@ -1535,18 +1715,25 @@ server.onSearch = function (path, options, session, callback) {
op = '$gte';
break;
}
- let entry = !op ? [{
- $gte: value
- }, {
- $lt: new Date(value.getTime() + 24 * 3600 * 1000)
- }] : {
- [op]: value
- };
+ let entry = !op
+ ? [
+ {
+ $gte: value
+ },
+ {
+ $lt: new Date(value.getTime() + 24 * 3600 * 1000)
+ }
+ ]
+ : {
+ [op]: value
+ };
entry = {
- hdate: !ne ? entry : {
- $not: entry
- }
+ hdate: !ne
+ ? entry
+ : {
+ $not: entry
+ }
};
parent.push(entry);
@@ -1577,9 +1764,11 @@ server.onSearch = function (path, options, session, callback) {
};
entry = {
- size: !ne ? entry : {
- $not: entry
- }
+ size: !ne
+ ? entry
+ : {
+ $not: entry
+ }
};
parent.push(entry);
@@ -1595,14 +1784,17 @@ server.onSearch = function (path, options, session, callback) {
query.$and = $and;
}
- this.logger.info({
- tnx: 'search',
- cid: session.id
- }, '[%s] SEARCH %s', session.id, JSON.stringify(query));
+ this.logger.info(
+ {
+ tnx: 'search',
+ cid: session.id
+ },
+ '[%s] SEARCH %s',
+ session.id,
+ JSON.stringify(query)
+ );
- let cursor = db.database.collection('messages').
- find(query).
- project({
+ let cursor = db.database.collection('messages').find(query).project({
uid: true,
modseq: true
});
@@ -1613,17 +1805,25 @@ server.onSearch = function (path, options, session, callback) {
let processNext = () => {
cursor.next((err, message) => {
if (err) {
- this.logger.error({
- tnx: 'search',
- cid: session.id
- }, '[%s] SEARCHFAIL %s error="%s"', session.id, JSON.stringify(query), err.message);
+ this.logger.error(
+ {
+ tnx: 'search',
+ cid: session.id
+ },
+ '[%s] SEARCHFAIL %s error="%s"',
+ session.id,
+ JSON.stringify(query),
+ err.message
+ );
return callback(new Error('Can not make requested search query'));
}
if (!message) {
- return cursor.close(() => callback(null, {
- uidList,
- highestModseq
- }));
+ return cursor.close(() =>
+ callback(null, {
+ uidList,
+ highestModseq
+ })
+ );
}
if (highestModseq < message.modseq) {
@@ -1639,11 +1839,16 @@ server.onSearch = function (path, options, session, callback) {
});
};
-server.onGetQuotaRoot = function (path, session, callback) {
- this.logger.debug({
- tnx: 'quota',
- cid: session.id
- }, '[%s] Requested quota root info for "%s"', session.id, path);
+server.onGetQuotaRoot = function(path, session, callback) {
+ this.logger.debug(
+ {
+ tnx: 'quota',
+ cid: session.id
+ },
+ '[%s] Requested quota root info for "%s"',
+ session.id,
+ path
+ );
db.database.collection('mailboxes').findOne({
user: session.user.id,
@@ -1675,11 +1880,16 @@ server.onGetQuotaRoot = function (path, session, callback) {
});
};
-server.onGetQuota = function (quotaRoot, session, callback) {
- this.logger.debug({
- tnx: 'quota',
- cid: session.id
- }, '[%s] Requested quota info for "%s"', session.id, quotaRoot);
+server.onGetQuota = function(quotaRoot, session, callback) {
+ this.logger.debug(
+ {
+ tnx: 'quota',
+ cid: session.id
+ },
+ '[%s] Requested quota info for "%s"',
+ session.id,
+ quotaRoot
+ );
if (quotaRoot !== '') {
return callback(null, 'NONEXISTENT');
@@ -1760,12 +1970,16 @@ function clearExpiredMessages() {
clearTimeout(gcTimeout);
// First, acquire the lock. This prevents multiple connected clients for deleting the same messages
- gcLock.acquireLock('gc_expired', 10 * 60 * 1000 /* Lock expires after 10min if not released */ , (err, lock) => {
+ gcLock.acquireLock('gc_expired', 10 * 60 * 1000 /* Lock expires after 10min if not released */, (err, lock) => {
if (err) {
- server.logger.error({
- tnx: 'gc',
- err
- }, 'Failed to acquire lock error=%s', err.message);
+ server.logger.error(
+ {
+ tnx: 'gc',
+ err
+ },
+ 'Failed to acquire lock error=%s',
+ err.message
+ );
gcTimeout = setTimeout(clearExpiredMessages, GC_INTERVAL);
gcTimeout.unref();
return;
@@ -1778,29 +1992,36 @@ function clearExpiredMessages() {
let done = () => {
gcLock.releaseLock(lock, err => {
if (err) {
- server.logger.error({
- tnx: 'gc',
- err
- }, 'Failed to release lock error=%s', err.message);
+ server.logger.error(
+ {
+ tnx: 'gc',
+ err
+ },
+ 'Failed to release lock error=%s',
+ err.message
+ );
}
gcTimeout = setTimeout(clearExpiredMessages, GC_INTERVAL);
gcTimeout.unref();
});
};
- let cursor = db.database.collection('messages').find({
- exp: true,
- rdate: {
- $lte: Date.now()
- }
- }).project({
- _id: true,
- mailbox: true,
- uid: true,
- size: true,
- map: true,
- magic: true
- });
+ let cursor = db.database
+ .collection('messages')
+ .find({
+ exp: true,
+ rdate: {
+ $lte: Date.now()
+ }
+ })
+ .project({
+ _id: true,
+ mailbox: true,
+ uid: true,
+ size: true,
+ map: true,
+ magic: true
+ });
let deleted = 0;
let processNext = () => {
@@ -1812,36 +2033,46 @@ function clearExpiredMessages() {
return cursor.close(() => {
// delete all attachments that do not have any active links to message objects
deleteOrphanedAttachments(() => {
- server.logger.debug({
- tnx: 'gc'
- }, 'Deleted %s messages', deleted);
+ server.logger.debug(
+ {
+ tnx: 'gc'
+ },
+ 'Deleted %s messages',
+ deleted
+ );
done(null, true);
});
});
}
- server.logger.info({
- tnx: 'gc',
- err
- }, 'Deleting expired message id=%s', message._id);
+ server.logger.info(
+ {
+ tnx: 'gc',
+ err
+ },
+ 'Deleting expired message id=%s',
+ message._id
+ );
gcTimeout = setTimeout(clearExpiredMessages, GC_INTERVAL);
- messageHandler.del({
- message,
- skipAttachments: true
- }, err => {
- if (err) {
- return cursor.close(() => done(err));
+ messageHandler.del(
+ {
+ message,
+ skipAttachments: true
+ },
+ err => {
+ if (err) {
+ return cursor.close(() => done(err));
+ }
+ deleted++;
+ processNext();
}
- deleted++;
- processNext();
- });
+ );
});
};
processNext();
-
});
}
@@ -1851,7 +2082,6 @@ module.exports = done => {
}
let start = () => {
-
messageHandler = new MessageHandler(db.database, db.redisConfig);
userHandler = new UserHandler(db.database, db.redis);
@@ -1871,9 +2101,12 @@ module.exports = done => {
started = true;
return done(err);
}
- server.logger.error({
+ server.logger.error(
+ {
+ err
+ },
err
- }, err);
+ );
});
// start listening
@@ -1889,9 +2122,13 @@ module.exports = done => {
let indexpos = 0;
let ensureIndexes = () => {
if (indexpos >= setupIndexes.length) {
- server.logger.info({
- tnx: 'mongo'
- }, 'Setup indexes for %s collections', setupIndexes.length);
+ server.logger.info(
+ {
+ tnx: 'mongo'
+ },
+ 'Setup indexes for %s collections',
+ setupIndexes.length
+ );
gcLock = new RedFour({
redis: db.redisConfig,
diff --git a/indexes.json b/indexes.json
index 626e82af..e6f44b69 100644
--- a/indexes.json
+++ b/indexes.json
@@ -154,7 +154,6 @@
}
}, {
"name": "retention_time",
- "expireAfterSeconds": 0,
"key": {
"exp": 1,
"rdate": 1
@@ -184,7 +183,7 @@
}
}, {
"name": "autoexpire",
- "expireAfterSeconds": "21600",
+ "expireAfterSeconds": 21600,
"key": {
"created": 1
}
diff --git a/lib/message-handler.js b/lib/message-handler.js
index d4ddf8ce..d3ad6c94 100644
--- a/lib/message-handler.js
+++ b/lib/message-handler.js
@@ -31,7 +31,6 @@ const IGNORE_HEADERS = [
];
class MessageHandler {
-
constructor(database, redisConfig) {
this.database = database;
this.redis = redisConfig || tools.redisConfig(config.redis);
@@ -79,7 +78,6 @@ class MessageHandler {
// Monster method for inserting new messages to a mailbox
// TODO: Refactor into smaller pieces
add(options, callback) {
-
let prepared = options.prepared || this.prepareMessage(options);
let id = prepared.id;
@@ -100,208 +98,212 @@ class MessageHandler {
return callback(err);
}
- this.checkExistingMessage(mailbox._id, {
- hdate,
- msgid,
- flags
- }, options, (...args) => {
- if (args[0] || args[1]) {
- return callback(...args);
- }
-
- let cleanup = (...args) => {
-
- if (!args[0]) {
+ this.checkExistingMessage(
+ mailbox._id,
+ {
+ hdate,
+ msgid,
+ flags
+ },
+ options,
+ (...args) => {
+ if (args[0] || args[1]) {
return callback(...args);
}
- let attachments = Object.keys(maildata.map || {}).map(key => maildata.map[key]);
- if (!attachments.length) {
- return callback(...args);
- }
-
- // error occured, remove attachments
- this.database.collection('attachments.files').deleteMany({
- _id: {
- $in: attachments
- }
- }, err => {
- if (err) {
- // ignore as we don't really care if we have orphans or not
+ let cleanup = (...args) => {
+ if (!args[0]) {
+ return callback(...args);
}
- return callback(null, true);
- });
- };
+ let attachments = Object.keys(maildata.map || {}).map(key => maildata.map[key]);
+ if (!attachments.length) {
+ return callback(...args);
+ }
- this.indexer.storeNodeBodies(id, maildata, mimeTree, err => {
- if (err) {
- return cleanup(err);
- }
-
- // prepare message object
- let message = {
- _id: id,
-
- v: SCHEMA_VERSION,
-
- // if true then expirest after rdate + retention
- exp: !!mailbox.retention,
- rdate: Date.now() + (mailbox.retention || 0),
-
- idate,
- hdate,
- flags,
- size,
-
- // some custom metadata about the delivery
- meta: options.meta || {},
-
- // list filter IDs that matched this message
- filters: Array.isArray(options.filters) ? options.filters : [].concat(options.filters || []),
-
- headers,
- mimeTree,
- envelope,
- bodystructure,
- msgid,
-
- // use boolean for more commonly used (and searched for) flags
- seen: flags.includes('\\Seen'),
- flagged: flags.includes('\\Flagged'),
- deleted: flags.includes('\\Deleted'),
- draft: flags.includes('\\Draft'),
-
- magic: maildata.magic,
- map: maildata.map
+ // error occured, remove attachments
+ this.database.collection('attachments.files').deleteMany({
+ _id: {
+ $in: attachments
+ }
+ }, () => callback(...args));
};
- if (maildata.attachments && maildata.attachments.length) {
- message.attachments = maildata.attachments;
- message.ha = true;
- } else {
- message.ha = false;
- }
-
- let maxTextLength = 300 * 1024;
-
- if (maildata.text) {
- message.text = maildata.text.replace(/\r\n/g, '\n').trim();
- message.text = message.text.length <= maxTextLength ? message.text : message.text.substr(0, maxTextLength);
- message.intro = message.text.replace(/\s+/g, ' ').trim();
- if (message.intro.length > 128) {
- let intro = message.intro.substr(0, 128);
- let lastSp = intro.lastIndexOf(' ');
- if (lastSp > 0) {
- intro = intro.substr(0, lastSp);
- }
- message.intro = intro + '…';
- }
- }
-
- if (maildata.html && maildata.html.length) {
- let htmlSize = 0;
- message.html = maildata.html.map(html => {
- if (htmlSize >= maxTextLength || !html) {
- return '';
- }
-
- if (htmlSize + Buffer.byteLength(html) <= maxTextLength) {
- htmlSize += Buffer.byteLength(html);
- return html;
- }
-
- html = html.substr(0, htmlSize + Buffer.byteLength(html) - maxTextLength);
- htmlSize += Buffer.byteLength(html);
- return html;
- }).filter(html => html);
- }
-
-
- this.database.collection('users').findOneAndUpdate({
- _id: mailbox.user
- }, {
- $inc: {
- storageUsed: size
- }
- }, err => {
+ this.indexer.storeNodeBodies(id, maildata, mimeTree, err => {
if (err) {
return cleanup(err);
}
- let rollback = err => {
- this.database.collection('users').findOneAndUpdate({
- _id: mailbox.user
- }, {
- $inc: {
- storageUsed: -size
- }
- }, () => {
- cleanup(err);
- });
+ // prepare message object
+ let message = {
+ _id: id,
+
+ v: SCHEMA_VERSION,
+
+ // if true then expirest after rdate + retention
+ exp: !!mailbox.retention,
+ rdate: Date.now() + (mailbox.retention || 0),
+
+ idate,
+ hdate,
+ flags,
+ size,
+
+ // some custom metadata about the delivery
+ meta: options.meta || {},
+
+ // list filter IDs that matched this message
+ filters: Array.isArray(options.filters) ? options.filters : [].concat(options.filters || []),
+
+ headers,
+ mimeTree,
+ envelope,
+ bodystructure,
+ msgid,
+
+ // use boolean for more commonly used (and searched for) flags
+ seen: flags.includes('\\Seen'),
+ flagged: flags.includes('\\Flagged'),
+ deleted: flags.includes('\\Deleted'),
+ draft: flags.includes('\\Draft'),
+
+ magic: maildata.magic,
+ map: maildata.map
};
- // acquire new UID+MODSEQ
- this.database.collection('mailboxes').findOneAndUpdate({
- _id: mailbox._id
+ if (maildata.attachments && maildata.attachments.length) {
+ message.attachments = maildata.attachments;
+ message.ha = true;
+ } else {
+ message.ha = false;
+ }
+
+ let maxTextLength = 300 * 1024;
+
+ if (maildata.text) {
+ message.text = maildata.text.replace(/\r\n/g, '\n').trim();
+ message.text = message.text.length <= maxTextLength ? message.text : message.text.substr(0, maxTextLength);
+ message.intro = message.text.replace(/\s+/g, ' ').trim();
+ if (message.intro.length > 128) {
+ let intro = message.intro.substr(0, 128);
+ let lastSp = intro.lastIndexOf(' ');
+ if (lastSp > 0) {
+ intro = intro.substr(0, lastSp);
+ }
+ message.intro = intro + '…';
+ }
+ }
+
+ if (maildata.html && maildata.html.length) {
+ let htmlSize = 0;
+ message.html = maildata.html
+ .map(html => {
+ if (htmlSize >= maxTextLength || !html) {
+ return '';
+ }
+
+ if (htmlSize + Buffer.byteLength(html) <= maxTextLength) {
+ htmlSize += Buffer.byteLength(html);
+ return html;
+ }
+
+ html = html.substr(0, htmlSize + Buffer.byteLength(html) - maxTextLength);
+ htmlSize += Buffer.byteLength(html);
+ return html;
+ })
+ .filter(html => html);
+ }
+
+ this.database.collection('users').findOneAndUpdate({
+ _id: mailbox.user
}, {
$inc: {
- // allocate bot UID and MODSEQ values so when journal is later sorted by
- // modseq then UIDs are always in ascending order
- uidNext: 1,
- modifyIndex: 1
+ storageUsed: size
}
- }, (err, item) => {
+ }, err => {
if (err) {
- return rollback(err);
+ return cleanup(err);
}
- if (!item || !item.value) {
- // was not able to acquire a lock
- let err = new Error('Mailbox is missing');
- err.imapResponse = 'TRYCREATE';
- return rollback(err);
- }
+ let rollback = err => {
+ this.database.collection('users').findOneAndUpdate({
+ _id: mailbox.user
+ }, {
+ $inc: {
+ storageUsed: -size
+ }
+ }, () => {
+ cleanup(err);
+ });
+ };
- let mailbox = item.value;
-
- // updated message object by setting mailbox specific values
- message.mailbox = mailbox._id;
- message.user = mailbox.user;
- message.uid = mailbox.uidNext;
- message.modseq = mailbox.modifyIndex + 1;
-
- this.database.collection('messages').insertOne(message, err => {
+ // acquire new UID+MODSEQ
+ this.database.collection('mailboxes').findOneAndUpdate({
+ _id: mailbox._id
+ }, {
+ $inc: {
+ // allocate bot UID and MODSEQ values so when journal is later sorted by
+ // modseq then UIDs are always in ascending order
+ uidNext: 1,
+ modifyIndex: 1
+ }
+ }, (err, item) => {
if (err) {
return rollback(err);
}
- let uidValidity = mailbox.uidValidity;
- let uid = message.uid;
-
- if (options.session && options.session.selected && options.session.selected.mailbox === mailbox.path) {
- options.session.writeStream.write(options.session.formatResponse('EXISTS', message.uid));
+ if (!item || !item.value) {
+ // was not able to acquire a lock
+ let err = new Error('Mailbox is missing');
+ err.imapResponse = 'TRYCREATE';
+ return rollback(err);
}
- this.notifier.addEntries(mailbox, false, {
- command: 'EXISTS',
- uid: message.uid,
- ignore: options.session && options.session.id,
- message: message._id,
- modseq: message.modseq
- }, () => {
- this.notifier.fire(mailbox.user, mailbox.path);
- return cleanup(null, true, {
- uidValidity,
- uid,
- id: message._id
- });
+ let mailbox = item.value;
+
+ // updated message object by setting mailbox specific values
+ message.mailbox = mailbox._id;
+ message.user = mailbox.user;
+ message.uid = mailbox.uidNext;
+ message.modseq = mailbox.modifyIndex + 1;
+
+ this.database.collection('messages').insertOne(message, err => {
+ if (err) {
+ return rollback(err);
+ }
+
+ let uidValidity = mailbox.uidValidity;
+ let uid = message.uid;
+
+ if (options.session && options.session.selected && options.session.selected.mailbox === mailbox.path) {
+ options.session.writeStream.write(options.session.formatResponse('EXISTS', message.uid));
+ }
+
+ this.notifier.addEntries(
+ mailbox,
+ false,
+ {
+ command: 'EXISTS',
+ uid: message.uid,
+ ignore: options.session && options.session.id,
+ message: message._id,
+ modseq: message.modseq
+ },
+ () => {
+ this.notifier.fire(mailbox.user, mailbox.path);
+ return cleanup(null, true, {
+ uidValidity,
+ uid,
+ id: message._id
+ });
+ }
+ );
});
});
});
});
- });
- });
+ }
+ );
});
}
@@ -387,28 +389,37 @@ class MessageHandler {
if (options.session && options.session.selected && options.session.selected.mailbox === mailbox.path) {
options.session.writeStream.write(options.session.formatResponse('EXISTS', updated.uid));
}
- this.notifier.addEntries(mailbox, false, {
- command: 'EXPUNGE',
- ignore: options.session && options.session.id,
- uid: existing.uid,
- message: existing._id
- }, () => {
-
- this.notifier.addEntries(mailbox, false, {
- command: 'EXISTS',
- uid: updated.uid,
+ this.notifier.addEntries(
+ mailbox,
+ false,
+ {
+ command: 'EXPUNGE',
ignore: options.session && options.session.id,
- message: updated._id,
- modseq: updated.modseq
- }, () => {
- this.notifier.fire(mailbox.user, mailbox.path);
- return callback(null, true, {
- uidValidity: mailbox.uidValidity,
- uid,
- id: existing._id
- });
- });
- });
+ uid: existing.uid,
+ message: existing._id
+ },
+ () => {
+ this.notifier.addEntries(
+ mailbox,
+ false,
+ {
+ command: 'EXISTS',
+ uid: updated.uid,
+ ignore: options.session && options.session.id,
+ message: updated._id,
+ modseq: updated.modseq
+ },
+ () => {
+ this.notifier.fire(mailbox.user, mailbox.path);
+ return callback(null, true, {
+ uidValidity: mailbox.uidValidity,
+ uid,
+ id: existing._id
+ });
+ }
+ );
+ }
+ );
});
});
});
@@ -417,30 +428,37 @@ class MessageHandler {
updateQuota(mailbox, inc, callback) {
inc = inc || {};
- this.database.collection('users').findOneAndUpdate({
- _id: mailbox.user
- }, {
- $inc: {
- storageUsed: Number(inc.storageUsed) || 0
- }
- }, callback);
+ this.database.collection('users').findOneAndUpdate(
+ {
+ _id: mailbox.user
+ },
+ {
+ $inc: {
+ storageUsed: Number(inc.storageUsed) || 0
+ }
+ },
+ callback
+ );
}
del(options, callback) {
-
let getMessage = next => {
if (options.message) {
return next(null, options.message);
}
- this.database.collection('messages').findOne(options.query, {
- fields: {
- mailbox: true,
- uid: true,
- size: true,
- map: true,
- magic: true
- }
- }, next);
+ this.database.collection('messages').findOne(
+ options.query,
+ {
+ fields: {
+ mailbox: true,
+ uid: true,
+ size: true,
+ map: true,
+ magic: true
+ }
+ },
+ next
+ );
};
getMessage((err, message) => {
@@ -452,75 +470,85 @@ class MessageHandler {
return callback(new Error('Message does not exist'));
}
- this.getMailbox({
- mailbox: options.mailbox || message.mailbox
- }, (err, mailbox) => {
- if (err) {
- return callback(err);
- }
-
- this.database.collection('messages').deleteOne({
- _id: message._id
- }, err => {
+ this.getMailbox(
+ {
+ mailbox: options.mailbox || message.mailbox
+ },
+ (err, mailbox) => {
if (err) {
return callback(err);
}
- this.updateQuota(mailbox, {
- storageUsed: -message.size
- }, () => {
+ this.database.collection('messages').deleteOne({
+ _id: message._id
+ }, err => {
+ if (err) {
+ return callback(err);
+ }
- let updateAttachments = next => {
- let attachments = Object.keys(message.map || {}).map(key => message.map[key]);
- if (!attachments.length) {
- return next();
+ this.updateQuota(
+ mailbox,
+ {
+ storageUsed: -message.size
+ },
+ () => {
+ let updateAttachments = next => {
+ let attachments = Object.keys(message.map || {}).map(key => message.map[key]);
+ if (!attachments.length) {
+ return next();
+ }
+
+ // remove link to message from attachments (if any exist)
+ this.database.collection('attachments.files').updateMany({
+ _id: {
+ $in: attachments
+ }
+ }, {
+ $inc: {
+ 'metadata.c': -1,
+ 'metadata.m': -message.magic
+ }
+ }, {
+ multi: true,
+ w: 1
+ }, err => {
+ if (err) {
+ // ignore as we don't really care if we have orphans or not
+ }
+ next();
+ });
+ };
+
+ updateAttachments(() => {
+ if (options.session && options.session.selected && options.session.selected.mailbox === mailbox.path) {
+ options.session.writeStream.write(options.session.formatResponse('EXPUNGE', message.uid));
+ }
+
+ this.notifier.addEntries(
+ mailbox,
+ false,
+ {
+ command: 'EXPUNGE',
+ ignore: options.session && options.session.id,
+ uid: message.uid,
+ message: message._id
+ },
+ () => {
+ this.notifier.fire(mailbox.user, mailbox.path);
+
+ if (options.skipAttachments) {
+ return callback(null, true);
+ }
+
+ return callback(null, true);
+ }
+ );
+ });
}
-
- // remove link to message from attachments (if any exist)
- this.database.collection('attachments.files').updateMany({
- _id: {
- $in: attachments
- }
- }, {
- $inc: {
- 'metadata.c': -1,
- 'metadata.m': -message.magic
- }
- }, {
- multi: true,
- w: 1
- }, err => {
- if (err) {
- // ignore as we don't really care if we have orphans or not
- }
- next();
- });
- };
-
- updateAttachments(() => {
-
- if (options.session && options.session.selected && options.session.selected.mailbox === mailbox.path) {
- options.session.writeStream.write(options.session.formatResponse('EXPUNGE', message.uid));
- }
-
- this.notifier.addEntries(mailbox, false, {
- command: 'EXPUNGE',
- ignore: options.session && options.session.id,
- uid: message.uid,
- message: message._id
- }, () => {
- this.notifier.fire(mailbox.user, mailbox.path);
-
- if (options.skipAttachments) {
- return callback(null, true);
- }
-
- return callback(null, true);
- });
- });
+ );
});
- });
- });
+ }
+ );
});
}
@@ -546,17 +574,18 @@ class MessageHandler {
}, {
uidNext: true
}, () => {
-
- let cursor = this.database.collection('messages').find({
- mailbox: mailbox._id,
- uid: {
- $in: options.messages || []
- }
- }).project({
- uid: 1
- }).sort([
- ['uid', 1]
- ]);
+ let cursor = this.database
+ .collection('messages')
+ .find({
+ mailbox: mailbox._id,
+ uid: {
+ $in: options.messages || []
+ }
+ })
+ .project({
+ uid: 1
+ })
+ .sort([['uid', 1]]);
let sourceUid = [];
let destinationUid = [];
@@ -565,7 +594,6 @@ class MessageHandler {
let existsEntries = [];
let done = err => {
-
let next = () => {
if (err) {
return callback(err);
@@ -692,43 +720,45 @@ class MessageHandler {
}
generateIndexedHeaders(headersArray) {
- return (headersArray || []).map(line => {
- line = Buffer.from(line, 'binary').toString();
+ return (headersArray || [])
+ .map(line => {
+ line = Buffer.from(line, 'binary').toString();
- let key = line.substr(0, line.indexOf(':')).trim().toLowerCase();
+ let key = line.substr(0, line.indexOf(':')).trim().toLowerCase();
- if (IGNORE_HEADERS.find(prefix => key.indexOf(prefix) === 0)) {
- // do not index this header
- return false;
- }
+ if (IGNORE_HEADERS.find(prefix => key.indexOf(prefix) === 0)) {
+ // do not index this header
+ return false;
+ }
- let value = line.substr(line.indexOf(':') + 1).trim().toLowerCase().replace(/\s*\r?\n\s*/g, ' ');
+ let value = line.substr(line.indexOf(':') + 1).trim().toLowerCase().replace(/\s*\r?\n\s*/g, ' ');
- try {
- value = libmime.decodeWords(value);
- } catch (E) {
- // ignore
- }
+ try {
+ value = libmime.decodeWords(value);
+ } catch (E) {
+ // ignore
+ }
- // trim long values as mongodb indexed fields can not be too long
+ // trim long values as mongodb indexed fields can not be too long
- if (Buffer.byteLength(key, 'utf-8') >= 255) {
- key = Buffer.from(key).slice(0, 255).toString();
- key = key.substr(0, key.length - 4);
- }
+ if (Buffer.byteLength(key, 'utf-8') >= 255) {
+ key = Buffer.from(key).slice(0, 255).toString();
+ key = key.substr(0, key.length - 4);
+ }
- if (Buffer.byteLength(value, 'utf-8') >= 880) {
- // value exceeds MongoDB max indexed value length
- value = Buffer.from(value).slice(0, 880).toString();
- // remove last 4 chars to be sure we do not have any incomplete unicode sequences
- value = value.substr(0, value.length - 4);
- }
+ if (Buffer.byteLength(value, 'utf-8') >= 880) {
+ // value exceeds MongoDB max indexed value length
+ value = Buffer.from(value).slice(0, 880).toString();
+ // remove last 4 chars to be sure we do not have any incomplete unicode sequences
+ value = value.substr(0, value.length - 4);
+ }
- return {
- key,
- value
- };
- }).filter(line => line);
+ return {
+ key,
+ value
+ };
+ })
+ .filter(line => line);
}
prepareMessage(options) {
@@ -740,8 +770,8 @@ class MessageHandler {
let bodystructure = this.indexer.getBodyStructure(mimeTree);
let envelope = this.indexer.getEnvelope(mimeTree);
- let idate = options.date && new Date(options.date) || new Date();
- let hdate = mimeTree.parsedHeader.date && new Date(mimeTree.parsedHeader.date) || false;
+ let idate = (options.date && new Date(options.date)) || new Date();
+ let hdate = (mimeTree.parsedHeader.date && new Date(mimeTree.parsedHeader.date)) || false;
let flags = [].concat(options.flags || []);
@@ -749,7 +779,7 @@ class MessageHandler {
hdate = idate;
}
- let msgid = envelope[9] || ('<' + uuidV1() + '@wildduck.email>');
+ let msgid = envelope[9] || '<' + uuidV1() + '@wildduck.email>';
let headers = this.generateIndexedHeaders(mimeTree.header);