mirror of
https://github.com/nodemailer/wildduck.git
synced 2024-12-27 02:10:52 +08:00
Copy similar message on APPEND, do not overwrite
This commit is contained in:
parent
cc80fdb5e9
commit
7d1cff7818
4 changed files with 114 additions and 81 deletions
|
@ -1,7 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
const imapHandler = require('./handler/imap-handler');
|
||||
|
||||
const errors = require('../../lib/errors.js');
|
||||
const MAX_MESSAGE_SIZE = 1 * 1024 * 1024;
|
||||
|
||||
const commands = new Map([
|
||||
|
@ -88,6 +88,11 @@ class IMAPCommand {
|
|||
if (command.literal) {
|
||||
// check if the literal size is in acceptable bounds
|
||||
if (isNaN(command.expecting) || isNaN(command.expecting) < 0 || command.expecting > Number.MAX_SAFE_INTEGER) {
|
||||
errors.notify(new Error('Invalid literal size'), {
|
||||
command: {
|
||||
expecting: command.expecting
|
||||
}
|
||||
});
|
||||
this.connection.send(this.tag + ' BAD Invalid literal size');
|
||||
return callback(new Error('Literal too big'));
|
||||
}
|
||||
|
|
|
@ -16,3 +16,21 @@ module.exports.notify = (...args) => {
|
|||
console.error(...args);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports.intercept = (...args) => {
|
||||
if (bugsnag) {
|
||||
return bugsnag.intercept(...args);
|
||||
}
|
||||
let cb;
|
||||
if (args.length) {
|
||||
cb = args[args.length - 1];
|
||||
if (typeof cb === 'function') {
|
||||
args[args.length - 1] = function(...rArgs) {
|
||||
if (rArgs.length > 1 && rArgs[0]) {
|
||||
console.error(rArgs[0]);
|
||||
}
|
||||
return cb(...rArgs);
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -105,6 +105,7 @@ class MessageHandler {
|
|||
this.checkExistingMessage(
|
||||
mailboxData,
|
||||
{
|
||||
id,
|
||||
hdate,
|
||||
msgid,
|
||||
flags
|
||||
|
@ -331,31 +332,44 @@ class MessageHandler {
|
|||
});
|
||||
}
|
||||
|
||||
checkExistingMessage(mailboxData, message, options, callback) {
|
||||
checkExistingMessage(mailboxData, messageOpts, options, callback) {
|
||||
// if a similar message already exists then update existing one
|
||||
|
||||
let queryOpts = {};
|
||||
if (options.skipExisting) {
|
||||
// no need to load extra data when we only need to know the basics
|
||||
queryOpts.fields = {
|
||||
_id: true,
|
||||
uid: true
|
||||
};
|
||||
}
|
||||
|
||||
this.database.collection('messages').findOne({
|
||||
mailbox: mailboxData._id,
|
||||
hdate: message.hdate,
|
||||
msgid: message.msgid,
|
||||
hdate: messageOpts.hdate,
|
||||
msgid: messageOpts.msgid,
|
||||
uid: {
|
||||
$gt: 0,
|
||||
$lt: mailboxData.uidNext
|
||||
}
|
||||
}, (err, existing) => {
|
||||
}, queryOpts, (err, messageData) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if (!existing) {
|
||||
if (!messageData) {
|
||||
// nothing to do here, continue adding message
|
||||
return callback();
|
||||
}
|
||||
|
||||
let existingId = messageData._id;
|
||||
let existingUid = messageData.uid;
|
||||
|
||||
if (options.skipExisting) {
|
||||
// message already exists, just skip it
|
||||
return callback(null, true, {
|
||||
uid: existing.uid,
|
||||
id: existing._id,
|
||||
uid: existingUid,
|
||||
id: existingId,
|
||||
mailbox: mailboxData._id,
|
||||
status: 'skip'
|
||||
});
|
||||
|
@ -368,7 +382,7 @@ class MessageHandler {
|
|||
_id: mailboxData._id
|
||||
}, {
|
||||
$inc: {
|
||||
// allocate bot UID and MODSEQ values so when journal is later sorted by
|
||||
// allocate both UID and MODSEQ values so when journal is later sorted by
|
||||
// modseq then UIDs are always in ascending order
|
||||
uidNext: 1,
|
||||
modifyIndex: 1
|
||||
|
@ -388,76 +402,75 @@ class MessageHandler {
|
|||
}
|
||||
|
||||
let mailboxData = item.value;
|
||||
let uid = mailboxData.uidNext;
|
||||
let modseq = mailboxData.modifyIndex + 1;
|
||||
let newUid = mailboxData.uidNext;
|
||||
let newModseq = mailboxData.modifyIndex + 1;
|
||||
|
||||
this.database.collection('messages').findOneAndUpdate({
|
||||
_id: existing._id,
|
||||
// hash key
|
||||
mailbox: mailboxData._id,
|
||||
uid: existing.uid
|
||||
}, {
|
||||
$set: {
|
||||
uid,
|
||||
modseq,
|
||||
flags: message.flags
|
||||
}
|
||||
}, {
|
||||
returnOriginal: false
|
||||
}, (err, item) => {
|
||||
// UID is immutable, so if we want to change it, we need to copy the message
|
||||
|
||||
messageData._id = messageOpts.id;
|
||||
messageData.uid = newUid;
|
||||
messageOpts.modseq = newModseq;
|
||||
messageData.flags = messageOpts.flags;
|
||||
|
||||
this.database.collection('messages').insertOne(messageData, err => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if (!item || !item.value) {
|
||||
// message was not found for whatever reason
|
||||
return callback();
|
||||
}
|
||||
|
||||
let updated = item.value;
|
||||
|
||||
if (options.session && options.session.selected && options.session.selected.mailbox === mailboxData.path) {
|
||||
options.session.writeStream.write(options.session.formatResponse('EXPUNGE', existing.uid));
|
||||
}
|
||||
|
||||
if (options.session && options.session.selected && options.session.selected.mailbox === mailboxData.path) {
|
||||
options.session.writeStream.write(options.session.formatResponse('EXISTS', updated.uid));
|
||||
}
|
||||
this.notifier.addEntries(
|
||||
mailboxData,
|
||||
false,
|
||||
{
|
||||
command: 'EXPUNGE',
|
||||
ignore: options.session && options.session.id,
|
||||
uid: existing.uid,
|
||||
message: existing._id,
|
||||
unseen: existing.unseen
|
||||
},
|
||||
() => {
|
||||
this.notifier.addEntries(
|
||||
mailboxData,
|
||||
false,
|
||||
{
|
||||
command: 'EXISTS',
|
||||
uid: updated.uid,
|
||||
ignore: options.session && options.session.id,
|
||||
message: updated._id,
|
||||
modseq: updated.modseq,
|
||||
unseen: updated.unseen
|
||||
},
|
||||
() => {
|
||||
this.notifier.fire(mailboxData.user, mailboxData.path);
|
||||
return callback(null, true, {
|
||||
uidValidity: mailboxData.uidValidity,
|
||||
uid,
|
||||
id: existing._id,
|
||||
mailbox: mailboxData._id,
|
||||
status: 'update'
|
||||
});
|
||||
}
|
||||
);
|
||||
this.database.collection('messages').deleteOne({
|
||||
_id: existingId,
|
||||
// hash key
|
||||
mailbox: mailboxData._id,
|
||||
uid: existingUid
|
||||
}, err => {
|
||||
if (err) {
|
||||
// TODO: how to resolve this? we might end up with two copies of the same message :S
|
||||
return callback(err);
|
||||
}
|
||||
);
|
||||
|
||||
if (options.session && options.session.selected && options.session.selected.mailbox === mailboxData.path) {
|
||||
options.session.writeStream.write(options.session.formatResponse('EXPUNGE', existingUid));
|
||||
}
|
||||
|
||||
if (options.session && options.session.selected && options.session.selected.mailbox === mailboxData.path) {
|
||||
options.session.writeStream.write(options.session.formatResponse('EXISTS', messageData.uid));
|
||||
}
|
||||
|
||||
this.notifier.addEntries(
|
||||
mailboxData,
|
||||
false,
|
||||
{
|
||||
command: 'EXPUNGE',
|
||||
ignore: options.session && options.session.id,
|
||||
uid: existingUid,
|
||||
message: existingId,
|
||||
unseen: messageData.unseen
|
||||
},
|
||||
() => {
|
||||
this.notifier.addEntries(
|
||||
mailboxData,
|
||||
false,
|
||||
{
|
||||
command: 'EXISTS',
|
||||
uid: messageData.uid,
|
||||
ignore: options.session && options.session.id,
|
||||
message: messageData._id,
|
||||
modseq: messageData.modseq,
|
||||
unseen: messageData.unseen
|
||||
},
|
||||
() => {
|
||||
this.notifier.fire(mailboxData.user, mailboxData.path);
|
||||
return callback(null, true, {
|
||||
uidValidity: mailboxData.uidValidity,
|
||||
uid: newUid,
|
||||
id: existingId,
|
||||
mailbox: mailboxData._id,
|
||||
status: 'update'
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
11
lmtp.js
11
lmtp.js
|
@ -384,7 +384,7 @@ const serverOptions = {
|
|||
}
|
||||
});
|
||||
|
||||
let messageOptions = {
|
||||
let messageOpts = {
|
||||
user: userData._id,
|
||||
[mailboxQueryKey]: mailboxQueryValue,
|
||||
|
||||
|
@ -404,17 +404,14 @@ const serverOptions = {
|
|||
|
||||
messageHandler.encryptMessage(userData.encryptMessages ? userData.pubKey : false, raw, (err, encrypted) => {
|
||||
if (!err && encrypted) {
|
||||
messageOptions.prepared = messageHandler.prepareMessage({
|
||||
messageOpts.prepared = messageHandler.prepareMessage({
|
||||
raw: encrypted,
|
||||
indexedHeaders: spamHeaderKeys
|
||||
});
|
||||
messageOptions.maildata = messageHandler.indexer.getMaildata(
|
||||
messageOptions.prepared.id,
|
||||
messageOptions.prepared.mimeTree
|
||||
);
|
||||
messageOpts.maildata = messageHandler.indexer.getMaildata(messageOpts.prepared.id, messageOpts.prepared.mimeTree);
|
||||
}
|
||||
|
||||
messageHandler.add(messageOptions, (err, inserted, info) => {
|
||||
messageHandler.add(messageOpts, (err, inserted, info) => {
|
||||
// remove Delivered-To
|
||||
chunks.shift();
|
||||
chunklen -= header.length;
|
||||
|
|
Loading…
Reference in a new issue