Added support for MOVE

This commit is contained in:
Andris Reinman 2017-03-29 21:22:26 +03:00
parent 2aa4f79142
commit a671df6ed4
6 changed files with 139 additions and 23 deletions

View file

@ -32,6 +32,7 @@ Wild Duck IMAP server supports the following IMAP standards:
- **UIDPLUS**
- **SPECIAL-USE**
- **ID**
- **MOVE** (RFC6851)
- **AUTHENTICATE PLAIN** and **SASL-IR**
- **APPENDLIMIT** (RFC7889) maximum global allowed message size is advertised in CAPABILITY listing
- **UTF8=ACCEPT** (RFC6855) this also means that Wild Duck natively supports unicode email usernames. For example <андрис@уайлддак.орг> is a valid email address that is hosted by a test instance of Wild Duck

View file

@ -22,7 +22,7 @@ const transporter = nodemailer.createTransport({
});
let sent = 0;
let total = 10000;
let total = 5;
let startTime = Date.now();
function send() {

View file

@ -30,7 +30,7 @@ module.exports = {
capabilities.push('UTF8=ACCEPT');
capabilities.push('QUOTA');
// capabilities.push('MOVE');
capabilities.push('MOVE');
if (this._server.options.maxMessage) {
capabilities.push('APPENDLIMIT=' + this._server.options.maxMessage);

View file

@ -1,7 +1,6 @@
'use strict';
let imapTools = require('../imap-tools');
let imapHandler = require('../handler/imap-handler');
module.exports = {
state: 'Selected',
@ -48,27 +47,12 @@ module.exports = {
return callback(err);
}
let code = typeof success === 'string' ? success.toUpperCase() : false;
if (success === true) {
this.send(imapHandler.compiler({
tag: '*',
command: 'OK',
attributes: [{
type: 'SECTION',
section: [{
type: 'TEXT',
value: 'COPYUID ' + info.uidValidity + ' ' + imapTools.packMessageRange(info.sourceUid) + ' ' + imapTools.packMessageRange(info.destinationUid)
}]
}]
}));
}
let code = typeof success === 'string' ? success.toUpperCase() : 'COPYUID ' + info.uidValidity + ' ' + imapTools.packMessageRange(info.sourceUid) + ' ' + imapTools.packMessageRange(info.destinationUid);
callback(null, {
response: success === true ? 'OK' : 'NO',
code
});
});
}
};

135
imap.js
View file

@ -548,7 +548,6 @@ server.onStore = function (path, update, session, callback) {
});
};
// EXPUNGE deletes all messages in selected mailbox marked with \Delete
// EXPUNGE deletes all messages in selected mailbox marked with \Delete
server.onExpunge = function (path, update, session, callback) {
this.logger.debug('[%s] Deleting messages from "%s"', session.id, path);
@ -690,7 +689,9 @@ server.onCopy = function (path, update, session, callback) {
uid: {
$in: update.messages
}
}); // no projection as we need to copy the entire message
}).sort([
['uid', 1]
]); // no projection as we need to copy the entire message
let copiedMessages = 0;
let copiedStorage = 0;
@ -800,6 +801,136 @@ server.onCopy = function (path, update, session, callback) {
});
};
// MOVE / UID MOVE sequence mailbox
server.onMove = function (path, update, session, callback) {
this.logger.debug('[%s] Moving messages from "%s" to "%s"', session.id, path, update.destination);
db.database.collection('mailboxes').findOne({
user: session.user.id,
path
}, (err, mailbox) => {
if (err) {
return callback(err);
}
if (!mailbox) {
return callback(null, 'NONEXISTENT');
}
db.database.collection('mailboxes').findOne({
user: session.user.id,
path: update.destination
}, (err, target) => {
if (err) {
return callback(err);
}
if (!target) {
return callback(null, 'TRYCREATE');
}
let cursor = db.database.collection('messages').find({
mailbox: mailbox._id,
uid: {
$in: update.messages
}
}).project({
uid: 1
}).sort([
['uid', 1]
]);
let sourceUid = [];
let destinationUid = [];
let processNext = () => {
cursor.next((err, message) => {
if (err) {
return callback(err);
}
if (!message) {
return cursor.close(() => {
db.database.collection('mailboxes').findOneAndUpdate({
_id: mailbox._id
}, {
$inc: {
// increase the mailbox modification index
// to indicate that something happened
modifyIndex: 1
}
}, {
uidNext: true
}, () => {
this.notifier.fire(session.user.id, target.path);
return callback(null, true, {
uidValidity: target.uidValidity,
sourceUid,
destinationUid
});
});
});
}
sourceUid.unshift(message.uid);
db.database.collection('mailboxes').findOneAndUpdate({
_id: target._id
}, {
$inc: {
uidNext: 1
}
}, {
uidNext: true
}, (err, item) => {
if (err) {
return callback(err);
}
if (!item || !item.value) {
// was not able to acquire a lock
return callback(null, 'TRYCREATE');
}
let uidNext = item.value.uidNext;
destinationUid.unshift(uidNext);
// update message, change mailbox from old to new one
db.database.collection('messages').findOneAndUpdate({
_id: message._id
}, {
$set: {
mailbox: target._id,
// new mailbox means new UID
uid: uidNext,
// this will be changed later by the notification system
modseq: 0
}
}, err => {
if (err) {
return callback(err);
}
session.writeStream.write(session.formatResponse('EXPUNGE', message.uid));
// mark messages as deleted from old mailbox
this.notifier.addEntries(session.user.id, path, {
command: 'EXPUNGE',
ignore: session.id,
uid: message.uid
}, () => {
// mark messages as added to old mailbox
this.notifier.addEntries(session.user.id, target.path, {
command: 'EXISTS',
uid: uidNext,
message: message._id
}, processNext);
});
});
});
});
};
processNext();
});
});
};
// sends results to socket
server.onFetch = function (path, options, session, callback) {
this.logger.debug('[%s] Requested FETCH for "%s"', session.id, path);

View file

@ -210,10 +210,10 @@ class ImapNotifier extends EventEmitter {
}
let entry = entries[updated++];
let setModseq = !!entry.modseq;
let setModseq = !entry.modseq;
entry.mailbox = mailbox._id;
if (!setModseq) {
if (setModseq) {
entry.modseq = ++startIndex;
}