diff --git a/api.js b/api.js index ac796f26..c277e9a7 100644 --- a/api.js +++ b/api.js @@ -103,7 +103,8 @@ server.post('/user/create', (req, res, next) => { uidValidity, uidNext: 1, modifyIndex: 0, - subscribed: true + subscribed: true, + flags: [] }, { user, path: 'Sent Mail', @@ -111,7 +112,8 @@ server.post('/user/create', (req, res, next) => { uidValidity, uidNext: 1, modifyIndex: 0, - subscribed: true + subscribed: true, + flags: [] }, { user, path: 'Trash', @@ -119,7 +121,8 @@ server.post('/user/create', (req, res, next) => { uidValidity, uidNext: 1, modifyIndex: 0, - subscribed: true + subscribed: true, + flags: [] }, { user, path: 'Junk', @@ -127,7 +130,8 @@ server.post('/user/create', (req, res, next) => { uidValidity, uidNext: 1, modifyIndex: 0, - subscribed: true + subscribed: true, + flags: [] }], { w: 1, ordered: false diff --git a/imap-core/lib/commands/select.js b/imap-core/lib/commands/select.js index fb0e7bb5..86b86dbe 100644 --- a/imap-core/lib/commands/select.js +++ b/imap-core/lib/commands/select.js @@ -74,27 +74,17 @@ module.exports = { }; this.state = 'Selected'; + let flagList = imapTools.systemFlagsFormatted.concat(folder.flags || []); + // * FLAGS (\Answered \Flagged \Draft \Deleted \Seen) this.send(imapHandler.compiler({ tag: '*', command: 'FLAGS', attributes: [ - [{ + flagList.map(flag => ({ type: 'atom', - value: '\\Answered' - }, { - type: 'atom', - value: '\\Flagged' - }, { - type: 'atom', - value: '\\Draft' - }, { - type: 'atom', - value: '\\Deleted' - }, { - type: 'atom', - value: '\\Seen' - }] + value: flag + })) ] })); @@ -110,25 +100,13 @@ module.exports = { type: 'atom', value: 'PERMANENTFLAGS' }, - [{ + flagList.map(flag => ({ type: 'atom', - value: '\\Answered' - }, { - type: 'atom', - value: '\\Flagged' - }, { - type: 'atom', - value: '\\Draft' - }, { - type: 'atom', - value: '\\Deleted' - }, { - type: 'atom', - value: '\\Seen' - }, { + value: flag + })).concat({ type: 'text', value: '\\*' - }] + }) ] }, { type: 'text', diff --git a/imap-core/lib/commands/store.js b/imap-core/lib/commands/store.js index 80b8f96d..1f796739 100644 --- a/imap-core/lib/commands/store.js +++ b/imap-core/lib/commands/store.js @@ -95,7 +95,7 @@ module.exports = { for (let i = flags.length - 1; i >= 0; i--) { if (flags[i].charAt(0) === '\\') { - if (imapTools.systemFlags.indexOf(flags[i].toLowerCase()) < 0) { + if (!imapTools.systemFlags.includes(flags[i].toLowerCase())) { return callback(new Error('Invalid system flag argument for STORE')); } else { // fix flag case diff --git a/imap-core/lib/imap-connection.js b/imap-core/lib/imap-connection.js index 22af144d..1a344d7c 100644 --- a/imap-core/lib/imap-connection.js +++ b/imap-core/lib/imap-connection.js @@ -476,7 +476,7 @@ class IMAPConnection extends EventEmitter { } } - if (existsResponse) { + if (existsResponse && !changed) { // send cached EXISTS response this.writeStream.write(existsResponse); existsResponse = false; diff --git a/imap-core/lib/imap-tools.js b/imap-core/lib/imap-tools.js index 04656a54..109df6f4 100644 --- a/imap-core/lib/imap-tools.js +++ b/imap-core/lib/imap-tools.js @@ -5,6 +5,7 @@ const utf7 = require('utf7').imap; const libmime = require('libmime'); const punycode = require('punycode'); +module.exports.systemFlagsFormatted = ['\\Answered', '\\Flagged', '\\Draft', '\\Deleted', '\\Seen']; module.exports.systemFlags = ['\\answered', '\\flagged', '\\draft', '\\deleted', '\\seen']; module.exports.fetchSchema = { diff --git a/imap.js b/imap.js index a881c9db..aac32298 100644 --- a/imap.js +++ b/imap.js @@ -9,6 +9,7 @@ const imapHandler = IMAPServerModule.imapHandler; const bcrypt = require('bcryptjs'); const ObjectID = require('mongodb').ObjectID; const Indexer = require('./imap-core/lib/indexer/indexer'); +const imapTools = require('./imap-core/lib/imap-tools'); const fs = require('fs'); const setupIndexes = require('./indexes.json'); const MessageHandler = require('./lib/message-handler'); @@ -28,7 +29,7 @@ const serverOptions = { debug: log.silly.bind(log, 'IMAP'), error: log.error.bind(log, 'IMAP') }, - + maxMessage: config.imap.maxMB * 1024 * 1024, maxStorage: config.imap.maxStorage * 1024 * 1024 }; @@ -161,7 +162,8 @@ server.onCreate = function (path, session, callback) { uidValidity: Math.floor(Date.now() / 1000), uidNext: 1, modifyIndex: 0, - subscribed: true + subscribed: true, + flags: [] }; db.database.collection('mailboxes').insertOne(mailbox, err => { @@ -422,6 +424,9 @@ server.onStore = function (path, update, session, callback) { return callback(null, 'NONEXISTENT'); } + let mailboxFlags = imapTools.systemFlags.concat(mailbox.flags || []).map(flag => flag.trim().toLowerCase()); + let newFlags = []; + let cursor = db.database.collection('messages').find({ mailbox: mailbox._id, uid: { @@ -509,6 +514,13 @@ server.onStore = function (path, update, session, callback) { break; } + message.flags.forEach(flag => { + // limit mailbox flags by 100 + if (!mailboxFlags.includes(flag.trim().toLowerCase()) && mailboxFlags.length + newFlags.length < 100) { + newFlags.push(flag); + } + }); + if (!update.silent) { session.writeStream.write(session.formatResponse('FETCH', message.uid, { uid: update.isUid ? message.uid : false, @@ -516,35 +528,53 @@ server.onStore = function (path, update, session, callback) { })); } - if (updated) { - db.database.collection('messages').findOneAndUpdate({ - _id: message._id - }, flagsupdate, {}, err => { - if (err) { - return cursor.close(() => done(err)); - } + let updateMailboxFlags = next => { + if (!newFlags.length) { + return next(); + } - notifyEntries.push({ - command: 'FETCH', - ignore: session.id, - uid: message.uid, - flags: message.flags, - message: message._id + // found some new flags not yet set for mailbox + return db.database.collection('mailboxes').findOneAndUpdate({ + _id: mailbox._id + }, { + $addToSet: { + flags: { + $each: newFlags + } + } + }, {}, next); + }; + + updateMailboxFlags(() => { + if (updated) { + db.database.collection('messages').findOneAndUpdate({ + _id: message._id + }, flagsupdate, {}, err => { + if (err) { + return cursor.close(() => done(err)); + } + + notifyEntries.push({ + command: 'FETCH', + ignore: session.id, + uid: message.uid, + flags: message.flags, + message: message._id + }); + + if (notifyEntries.length > 100) { + let entries = notifyEntries; + notifyEntries = []; + setImmediate(() => this.notifier.addEntries(session.user.id, path, entries, processNext)); + return; + } else { + setImmediate(() => processNext()); + } }); - - if (notifyEntries.length > 100) { - let entries = notifyEntries; - notifyEntries = []; - setImmediate(() => this.notifier.addEntries(session.user.id, path, entries, processNext)); - return; - } else { - setImmediate(() => processNext()); - } - }); - } else { - processNext(); - } - + } else { + processNext(); + } + }); }); };