From 4cfd90f4730ef21c2c54459a6b5d55887cc725cc Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Fri, 1 Sep 2017 14:50:53 +0300 Subject: [PATCH] v1.0.77 --- lib/api/addresses.js | 83 +++++++--- lib/api/auth.js | 60 +++++--- lib/api/messages.js | 338 +++++++++++++++++++++++++++++++++-------- lib/api/users.js | 132 +++++++++++----- lib/message-handler.js | 36 ++++- package.json | 6 +- 6 files changed, 504 insertions(+), 151 deletions(-) diff --git a/lib/api/addresses.js b/lib/api/addresses.js index d28cdf31..4a023d71 100644 --- a/lib/api/addresses.js +++ b/lib/api/addresses.js @@ -10,10 +10,20 @@ module.exports = (db, server) => { res.charSet('utf-8'); const schema = Joi.object().keys({ - query: Joi.string().trim().empty('').max(255), - limit: Joi.number().default(20).min(1).max(250), - next: Joi.string().alphanum().max(100), - prev: Joi.string().alphanum().max(100), + query: Joi.string() + .trim() + .empty('') + .max(255), + limit: Joi.number() + .default(20) + .min(1) + .max(250), + next: Joi.string() + .alphanum() + .max(100), + prev: Joi.string() + .alphanum() + .max(100), page: Joi.number().default(1) }); @@ -83,18 +93,13 @@ module.exports = (db, server) => { page = 1; } - let prevUrl = result.hasPrevious - ? server.router.render('addresses', {}, { prev: result.previous, limit, query: query || '', page: Math.max(page - 1, 1) }) - : false; - let nextUrl = result.hasNext ? server.router.render('addresses', {}, { next: result.next, limit, query: query || '', page: page + 1 }) : false; - let response = { success: true, query, total, page, - prev: prevUrl, - next: nextUrl, + previousCursor: result.hasPrevious ? result.previous : false, + nextCursor: result.hasNext ? result.next : false, results: (result.results || []).map(addressData => ({ id: addressData._id.toString(), address: addressData.address, @@ -112,8 +117,14 @@ module.exports = (db, server) => { res.charSet('utf-8'); const schema = Joi.object().keys({ - user: Joi.string().hex().lowercase().length(24).required(), - address: Joi.string().email().required(), + user: Joi.string() + .hex() + .lowercase() + .length(24) + .required(), + address: Joi.string() + .email() + .required(), main: Joi.boolean().truthy(['Y', 'true', 'yes', 1]) }); @@ -233,7 +244,11 @@ module.exports = (db, server) => { res.charSet('utf-8'); const schema = Joi.object().keys({ - user: Joi.string().hex().lowercase().length(24).required() + user: Joi.string() + .hex() + .lowercase() + .length(24) + .required() }); const result = Joi.validate(req.params, schema, { @@ -310,8 +325,16 @@ module.exports = (db, server) => { res.charSet('utf-8'); const schema = Joi.object().keys({ - user: Joi.string().hex().lowercase().length(24).required(), - address: Joi.string().hex().lowercase().length(24).required() + user: Joi.string() + .hex() + .lowercase() + .length(24) + .required(), + address: Joi.string() + .hex() + .lowercase() + .length(24) + .required() }); const result = Joi.validate(req.params, schema, { @@ -384,9 +407,19 @@ module.exports = (db, server) => { res.charSet('utf-8'); const schema = Joi.object().keys({ - user: Joi.string().hex().lowercase().length(24).required(), - address: Joi.string().hex().lowercase().length(24).required(), - main: Joi.boolean().truthy(['Y', 'true', 'yes', 1]).required() + user: Joi.string() + .hex() + .lowercase() + .length(24) + .required(), + address: Joi.string() + .hex() + .lowercase() + .length(24) + .required(), + main: Joi.boolean() + .truthy(['Y', 'true', 'yes', 1]) + .required() }); const result = Joi.validate(req.params, schema, { @@ -487,8 +520,16 @@ module.exports = (db, server) => { res.charSet('utf-8'); const schema = Joi.object().keys({ - user: Joi.string().hex().lowercase().length(24).required(), - address: Joi.string().hex().lowercase().length(24).required() + user: Joi.string() + .hex() + .lowercase() + .length(24) + .required(), + address: Joi.string() + .hex() + .lowercase() + .length(24) + .required() }); const result = Joi.validate(req.params, schema, { diff --git a/lib/api/auth.js b/lib/api/auth.js index 94666057..42ead7f4 100644 --- a/lib/api/auth.js +++ b/lib/api/auth.js @@ -9,8 +9,19 @@ module.exports = (db, server, userHandler) => { res.charSet('utf-8'); const schema = Joi.object().keys({ - username: Joi.string().lowercase().regex(/^[a-z](?:\.?[a-z0-9]+)*$/, 'username').min(3).max(30).required(), - password: Joi.string().max(256).required(), + username: Joi.alternatives() + .try( + Joi.string() + .lowercase() + .regex(/^[a-z](?:\.?[a-z0-9]+)*$/, 'username') + .min(3) + .max(30), + Joi.string().email() + ) + .required(), + password: Joi.string() + .max(256) + .required(), protocol: Joi.string().default('API'), scope: Joi.string().default('master'), @@ -70,12 +81,31 @@ module.exports = (db, server, userHandler) => { res.charSet('utf-8'); const schema = Joi.object().keys({ - user: Joi.string().hex().lowercase().length(24).required(), - action: Joi.string().trim().lowercase().empty('').max(100), - limit: Joi.number().default(20).min(1).max(250), - next: Joi.string().empty('').alphanum().max(100), - previous: Joi.string().empty('').alphanum().max(100), - page: Joi.number().empty('').default(1) + user: Joi.string() + .hex() + .lowercase() + .length(24) + .required(), + action: Joi.string() + .trim() + .lowercase() + .empty('') + .max(100), + limit: Joi.number() + .default(20) + .min(1) + .max(250), + next: Joi.string() + .empty('') + .alphanum() + .max(100), + previous: Joi.string() + .empty('') + .alphanum() + .max(100), + page: Joi.number() + .empty('') + .default(1) }); req.query.user = req.params.user; @@ -161,31 +191,17 @@ module.exports = (db, server, userHandler) => { page = 1; } - let prevUrl = result.hasPrevious - ? server.router.render( - 'authlog', - { user: user.toString() }, - { prev: result.previous, action: action || '', limit, page: Math.max(page - 1, 1) } - ) - : false; - let nextUrl = result.hasNext - ? server.router.render('authlog', { user: user.toString() }, { next: result.next, action: action || '', limit, page: page + 1 }) - : false; - let response = { success: true, action, total, page, - prev: prevUrl, previousCursor: result.hasPrevious ? result.previous : false, - next: nextUrl, nextCursor: result.hasNext ? result.next : false, results: (result.results || []).map(resultData => { let response = { id: resultData._id }; - Object.keys(resultData).forEach(key => { if (!['_id', 'user'].includes(key)) { response[key] = resultData[key]; diff --git a/lib/api/messages.js b/lib/api/messages.js index bad2b891..b4e1424c 100644 --- a/lib/api/messages.js +++ b/lib/api/messages.js @@ -13,13 +13,36 @@ module.exports = (db, server, messageHandler) => { res.charSet('utf-8'); const schema = Joi.object().keys({ - user: Joi.string().hex().lowercase().length(24).required(), - mailbox: Joi.string().hex().lowercase().length(24).required(), - limit: Joi.number().empty('').default(20).min(1).max(250), - order: Joi.any().empty('').allow(['asc', 'desc']).default('desc'), - next: Joi.string().empty('').alphanum().max(100), - previous: Joi.string().empty('').alphanum().max(100), - page: Joi.number().empty('').default(1) + user: Joi.string() + .hex() + .lowercase() + .length(24) + .required(), + mailbox: Joi.string() + .hex() + .lowercase() + .length(24) + .required(), + limit: Joi.number() + .empty('') + .default(20) + .min(1) + .max(250), + order: Joi.any() + .empty('') + .allow(['asc', 'desc']) + .default('desc'), + next: Joi.string() + .empty('') + .alphanum() + .max(100), + previous: Joi.string() + .empty('') + .alphanum() + .max(100), + page: Joi.number() + .empty('') + .default(1) }); req.query.user = req.params.user; @@ -128,28 +151,11 @@ module.exports = (db, server, messageHandler) => { page = 1; } - let prevUrl = result.hasPrevious - ? server.router.render( - 'messages', - { user: user.toString(), mailbox: mailbox.toString() }, - { prev: result.previous, limit, order: sortAscending ? 'asc' : 'desc', page: Math.max(page - 1, 1) } - ) - : false; - let nextUrl = result.hasNext - ? server.router.render( - 'messages', - { user: user.toString(), mailbox: mailbox.toString() }, - { next: result.next, limit, order: sortAscending ? 'asc' : 'desc', page: page + 1 } - ) - : false; - let response = { success: true, total, page, - prev: prevUrl, previousCursor: result.hasPrevious ? result.previous : false, - next: nextUrl, nextCursor: result.hasNext ? result.next : false, specialUse: mailboxData.specialUse, results: (result.results || []).map(messageData => { @@ -207,11 +213,47 @@ module.exports = (db, server, messageHandler) => { res.charSet('utf-8'); const schema = Joi.object().keys({ - user: Joi.string().hex().lowercase().length(24).required(), - query: Joi.string().trim().max(255).required(), - limit: Joi.number().default(20).min(1).max(250), - next: Joi.string().alphanum().max(100), - prev: Joi.string().alphanum().max(100), + user: Joi.string() + .hex() + .lowercase() + .length(24) + .required(), + mailbox: Joi.string() + .hex() + .length(24) + .empty(''), + query: Joi.string() + .trim() + .max(255) + .empty(''), + datestart: Joi.number() + .label('Start time') + .empty(''), + dateend: Joi.number() + .label('End time') + .empty(''), + filterFrom: Joi.string() + .trim() + .empty(''), + filterTo: Joi.string() + .trim() + .empty(''), + filterSubject: Joi.string() + .trim() + .empty(''), + filterAttachments: Joi.boolean() + .empty('') + .truthy('true'), + limit: Joi.number() + .default(20) + .min(1) + .max(250), + next: Joi.string() + .alphanum() + .max(100), + previous: Joi.string() + .alphanum() + .max(100), page: Joi.number().default(1) }); @@ -231,11 +273,19 @@ module.exports = (db, server, messageHandler) => { } let user = new ObjectID(result.value.user); + let mailbox = result.value.mailbox ? new ObjectID(result.value.mailbox) : false; let query = result.value.query; + let datestart = result.value.datestart ? new Date(result.value.datestart) : false; + let dateend = result.value.dateend ? new Date(result.value.dateend) : false; + let filterFrom = result.value.filterFrom; + let filterTo = result.value.filterTo; + let filterSubject = result.value.filterSubject; + let filterAttachments = result.value.filterAttachments; + let limit = result.value.limit; let page = result.value.page; let pageNext = result.value.next; - let pagePrev = result.value.prev; + let pagePrev = result.value.previous; db.users.collection('users').findOne({ _id: user @@ -262,10 +312,104 @@ module.exports = (db, server, messageHandler) => { // NB! Scattered query, searches over all user mailboxes and all shards let filter = { user, - searchable: true, - $text: { $search: query, $language: 'none' } + searchable: true }; + if (query) { + filter.$text = { $search: query, $language: 'none' }; + } + + if (mailbox) { + filter.mailbox = mailbox; + } + + if (datestart) { + if (!filter.idate) { + filter.idate = {}; + } + filter.idate.$gte = datestart; + } + + if (dateend) { + if (!filter.idate) { + filter.idate = {}; + } + filter.idate.$lte = dateend; + } + + if (filterFrom) { + let regex = filterFrom.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&'); + if (!filter.$and) { + filter.$and = []; + } + filter.$and.push({ + headers: { + $elemMatch: { + key: 'from', + value: { + $regex: regex, + $options: 'i' + } + } + } + }); + } + + if (filterTo) { + let regex = filterTo.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&'); + if (!filter.$and) { + filter.$and = []; + } + filter.$and.push({ + $or: [ + { + headers: { + $elemMatch: { + key: 'to', + value: { + $regex: regex, + $options: 'i' + } + } + } + }, + { + headers: { + $elemMatch: { + key: 'cc', + value: { + $regex: regex, + $options: 'i' + } + } + } + } + ] + }); + } + + if (filterSubject) { + let regex = filterSubject.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&'); + if (!filter.$and) { + filter.$and = []; + } + filter.$and.push({ + headers: { + $elemMatch: { + key: 'subject', + value: { + $regex: regex, + $options: 'i' + } + } + } + }); + } + + if (filterAttachments) { + filter.ha = true; + } + getFilteredMessageCount(db, filter, (err, total) => { if (err) { res.json({ @@ -302,7 +446,7 @@ module.exports = (db, server, messageHandler) => { if (pageNext) { opts.next = pageNext; } else if (pagePrev) { - opts.prev = pagePrev; + opts.previous = pagePrev; } MongoPaging.find(db.database.collection('messages'), opts, (err, result) => { @@ -317,20 +461,13 @@ module.exports = (db, server, messageHandler) => { page = 1; } - let prevUrl = result.hasPrevious - ? server.router.render('search', { user: user.toString() }, { prev: result.previous, limit, query, page: Math.max(page - 1, 1) }) - : false; - let nextUrl = result.hasNext - ? server.router.render('search', { user: user.toString() }, { next: result.next, limit, query, page: page + 1 }) - : false; - let response = { success: true, query, total, page, - prev: prevUrl, - next: nextUrl, + previousCursor: result.hasPrevious ? result.previous : false, + nextCursor: result.hasNext ? result.next : false, results: (result.results || []).map(messageData => { let parsedHeader = (messageData.mimeTree && messageData.mimeTree.parsedHeader) || {}; let from = parsedHeader.from || @@ -387,11 +524,25 @@ module.exports = (db, server, messageHandler) => { res.charSet('utf-8'); const schema = Joi.object().keys({ - user: Joi.string().hex().lowercase().length(24).required(), - mailbox: Joi.string().hex().lowercase().length(24).required(), - message: Joi.number().min(1).required(), - replaceCidLinks: Joi.boolean().truthy(['Y', 'true', 'yes', 1]).default(false), - markAsSeen: Joi.boolean().truthy(['Y', 'true', 'yes', 1]).default(false) + user: Joi.string() + .hex() + .lowercase() + .length(24) + .required(), + mailbox: Joi.string() + .hex() + .lowercase() + .length(24) + .required(), + message: Joi.number() + .min(1) + .required(), + replaceCidLinks: Joi.boolean() + .truthy(['Y', 'true', 'yes', 1]) + .default(false), + markAsSeen: Joi.boolean() + .truthy(['Y', 'true', 'yes', 1]) + .default(false) }); if (req.query.replaceCidLinks) { @@ -582,9 +733,19 @@ module.exports = (db, server, messageHandler) => { server.get({ name: 'raw', path: '/users/:user/mailboxes/:mailbox/messages/:message/message.eml' }, (req, res, next) => { const schema = Joi.object().keys({ - user: Joi.string().hex().lowercase().length(24).required(), - mailbox: Joi.string().hex().lowercase().length(24).required(), - message: Joi.number().min(1).required() + user: Joi.string() + .hex() + .lowercase() + .length(24) + .required(), + mailbox: Joi.string() + .hex() + .lowercase() + .length(24) + .required(), + message: Joi.number() + .min(1) + .required() }); const result = Joi.validate(req.params, schema, { @@ -641,10 +802,23 @@ module.exports = (db, server, messageHandler) => { server.get({ name: 'attachment', path: '/users/:user/mailboxes/:mailbox/messages/:message/attachments/:attachment' }, (req, res, next) => { const schema = Joi.object().keys({ - user: Joi.string().hex().lowercase().length(24).required(), - mailbox: Joi.string().hex().lowercase().length(24).required(), - message: Joi.number().min(1).required(), - attachment: Joi.string().regex(/^ATT\d+$/i).uppercase().required() + user: Joi.string() + .hex() + .lowercase() + .length(24) + .required(), + mailbox: Joi.string() + .hex() + .lowercase() + .length(24) + .required(), + message: Joi.number() + .min(1) + .required(), + attachment: Joi.string() + .regex(/^ATT\d+$/i) + .uppercase() + .required() }); const result = Joi.validate(req.params, schema, { @@ -728,15 +902,33 @@ module.exports = (db, server, messageHandler) => { res.charSet('utf-8'); const schema = Joi.object().keys({ - user: Joi.string().hex().lowercase().length(24).required(), - mailbox: Joi.string().hex().lowercase().length(24).required(), - moveTo: Joi.string().hex().lowercase().length(24), - message: Joi.string().regex(/^\d+(,\d+)*$|^\d+:\d+$|/i).required(), + user: Joi.string() + .hex() + .lowercase() + .length(24) + .required(), + mailbox: Joi.string() + .hex() + .lowercase() + .length(24) + .required(), + moveTo: Joi.string() + .hex() + .lowercase() + .length(24), + message: Joi.string() + .regex(/^\d+(,\d+)*$|^\d+:\d+$|/i) + .required(), seen: Joi.boolean().truthy(['Y', 'true', 'yes', 1]), deleted: Joi.boolean().truthy(['Y', 'true', 'yes', 1]), flagged: Joi.boolean().truthy(['Y', 'true', 'yes', 1]), draft: Joi.boolean().truthy(['Y', 'true', 'yes', 1]), - expires: Joi.alternatives().try(Joi.date(), Joi.boolean().truthy(['Y', 'true', 'yes', 1]).allow(false)) + expires: Joi.alternatives().try( + Joi.date(), + Joi.boolean() + .truthy(['Y', 'true', 'yes', 1]) + .allow(false) + ) }); const result = Joi.validate(req.params, schema, { @@ -761,9 +953,17 @@ module.exports = (db, server, messageHandler) => { if (/^\d+$/.test(message)) { messageQuery = Number(message); } else if (/^\d+(,\d+)*$/.test(message)) { - messageQuery = { $in: message.split(',').map(uid => Number(uid)).sort((a, b) => a - b) }; + messageQuery = { + $in: message + .split(',') + .map(uid => Number(uid)) + .sort((a, b) => a - b) + }; } else if (/^\d+:\d+$/.test(message)) { - let parts = message.split(':').map(uid => Number(uid)).sort((a, b) => a - b); + let parts = message + .split(':') + .map(uid => Number(uid)) + .sort((a, b) => a - b); if (parts[0] === parts[1]) { messageQuery = parts[0]; } else { @@ -840,9 +1040,19 @@ module.exports = (db, server, messageHandler) => { res.charSet('utf-8'); const schema = Joi.object().keys({ - user: Joi.string().hex().lowercase().length(24).required(), - mailbox: Joi.string().hex().lowercase().length(24).required(), - message: Joi.number().min(1).required() + user: Joi.string() + .hex() + .lowercase() + .length(24) + .required(), + mailbox: Joi.string() + .hex() + .lowercase() + .length(24) + .required(), + message: Joi.number() + .min(1) + .required() }); const result = Joi.validate(req.params, schema, { diff --git a/lib/api/users.js b/lib/api/users.js index 5020ddc0..bbe12170 100644 --- a/lib/api/users.js +++ b/lib/api/users.js @@ -14,10 +14,21 @@ module.exports = (db, server, userHandler) => { res.charSet('utf-8'); const schema = Joi.object().keys({ - query: Joi.string().alphanum().lowercase().empty('').max(100), - limit: Joi.number().default(20).min(1).max(250), - next: Joi.string().alphanum().max(100), - prev: Joi.string().alphanum().max(100), + query: Joi.string() + .alphanum() + .lowercase() + .empty('') + .max(100), + limit: Joi.number() + .default(20) + .min(1) + .max(250), + next: Joi.string() + .alphanum() + .max(100), + previous: Joi.string() + .alphanum() + .max(100), page: Joi.number().default(1) }); @@ -38,7 +49,7 @@ module.exports = (db, server, userHandler) => { let limit = result.value.limit; let page = result.value.page; let pageNext = result.value.next; - let pagePrev = result.value.prev; + let pagePrev = result.value.previous; let filter = query ? { @@ -75,7 +86,7 @@ module.exports = (db, server, userHandler) => { if (pageNext) { opts.next = pageNext; } else if (pagePrev) { - opts.prev = pagePrev; + opts.previous = pagePrev; } MongoPaging.find(db.users.collection('users'), opts, (err, result) => { @@ -90,18 +101,13 @@ module.exports = (db, server, userHandler) => { page = 1; } - let prevUrl = result.hasPrevious - ? server.router.render('users', {}, { prev: result.previous, limit, query: query || '', page: Math.max(page - 1, 1) }) - : false; - let nextUrl = result.hasNext ? server.router.render('users', {}, { next: result.next, limit, query: query || '', page: page + 1 }) : false; - let response = { success: true, query, total, page, - prev: prevUrl, - next: nextUrl, + previousCursor: result.hasPrevious ? result.previous : false, + nextCursor: result.hasNext ? result.next : false, results: (result.results || []).map(userData => ({ id: userData._id.toString(), username: userData.username, @@ -125,24 +131,47 @@ module.exports = (db, server, userHandler) => { res.charSet('utf-8'); const schema = Joi.object().keys({ - username: Joi.string().lowercase().regex(/^[a-z](?:\.?[a-z0-9]+)*$/, 'username').min(3).max(30).required(), - password: Joi.string().max(256).required(), + username: Joi.string() + .lowercase() + .regex(/^[a-z](?:\.?[a-z0-9]+)*$/, 'username') + .min(3) + .max(30) + .required(), + password: Joi.string() + .max(256) + .required(), address: Joi.string().email(), - language: Joi.string().min(2).max(20).lowercase(), - retention: Joi.number().min(0).default(0), + language: Joi.string() + .min(2) + .max(20) + .lowercase(), + retention: Joi.number() + .min(0) + .default(0), name: Joi.string().max(256), forward: Joi.string().email(), targetUrl: Joi.string().max(256), - quota: Joi.number().min(0).default(0), - recipients: Joi.number().min(0).default(0), - forwards: Joi.number().min(0).default(0), + quota: Joi.number() + .min(0) + .default(0), + recipients: Joi.number() + .min(0) + .default(0), + forwards: Joi.number() + .min(0) + .default(0), - pubKey: Joi.string().empty('').trim().regex(/^-----BEGIN PGP PUBLIC KEY BLOCK-----/, 'PGP key format'), - encryptMessages: Joi.boolean().truthy(['Y', 'true', 'yes', 1]).default(false), + pubKey: Joi.string() + .empty('') + .trim() + .regex(/^-----BEGIN PGP PUBLIC KEY BLOCK-----/, 'PGP key format'), + encryptMessages: Joi.boolean() + .truthy(['Y', 'true', 'yes', 1]) + .default(false), ip: Joi.string().ip({ version: ['ipv4', 'ipv6'], @@ -208,7 +237,11 @@ module.exports = (db, server, userHandler) => { res.charSet('utf-8'); const schema = Joi.object().keys({ - user: Joi.string().hex().lowercase().length(24).required() + user: Joi.string() + .hex() + .lowercase() + .length(24) + .required() }); const result = Joi.validate(req.params, schema, { @@ -312,19 +345,42 @@ module.exports = (db, server, userHandler) => { res.charSet('utf-8'); const schema = Joi.object().keys({ - user: Joi.string().hex().lowercase().length(24).required(), + user: Joi.string() + .hex() + .lowercase() + .length(24) + .required(), - existingPassword: Joi.string().empty('').min(1).max(256), - password: Joi.string().min(8).max(256), + existingPassword: Joi.string() + .empty('') + .min(1) + .max(256), + password: Joi.string() + .min(8) + .max(256), - language: Joi.string().min(2).max(20).lowercase(), + language: Joi.string() + .min(2) + .max(20) + .lowercase(), - name: Joi.string().empty('').max(256), - forward: Joi.string().empty('').email(), - targetUrl: Joi.string().empty('').max(256), + name: Joi.string() + .empty('') + .max(256), + forward: Joi.string() + .empty('') + .email(), + targetUrl: Joi.string() + .empty('') + .max(256), - pubKey: Joi.string().empty('').trim().regex(/^-----BEGIN PGP PUBLIC KEY BLOCK-----/, 'PGP key format'), - encryptMessages: Joi.boolean().empty('').truthy(['Y', 'true', 'yes', 1]), + pubKey: Joi.string() + .empty('') + .trim() + .regex(/^-----BEGIN PGP PUBLIC KEY BLOCK-----/, 'PGP key format'), + encryptMessages: Joi.boolean() + .empty('') + .truthy(['Y', 'true', 'yes', 1]), retention: Joi.number().min(0), quota: Joi.number().min(0), @@ -402,7 +458,11 @@ module.exports = (db, server, userHandler) => { res.charSet('utf-8'); const schema = Joi.object().keys({ - user: Joi.string().hex().lowercase().length(24).required() + user: Joi.string() + .hex() + .lowercase() + .length(24) + .required() }); const result = Joi.validate(req.params, schema, { @@ -516,7 +576,11 @@ module.exports = (db, server, userHandler) => { res.charSet('utf-8'); const schema = Joi.object().keys({ - user: Joi.string().hex().lowercase().length(24).required() + user: Joi.string() + .hex() + .lowercase() + .length(24) + .required() }); const result = Joi.validate(req.params, schema, { diff --git a/lib/message-handler.js b/lib/message-handler.js index 7992e2c9..e20354f8 100644 --- a/lib/message-handler.js +++ b/lib/message-handler.js @@ -511,7 +511,7 @@ class MessageHandler { return next(); } - this.attachmentStorage.deleteMany(attachmentIds, next); + this.attachmentStorage.deleteMany(attachmentIds, message.magic, next); }; updateAttachments(() => { @@ -812,14 +812,20 @@ class MessageHandler { .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 (!INDEXED_HEADERS.includes(key)) { // do not index this header return false; } - let value = line.substr(line.indexOf(':') + 1).trim().replace(/\s*\r?\n\s*/g, ' '); + let value = line + .substr(line.indexOf(':') + 1) + .trim() + .replace(/\s*\r?\n\s*/g, ' '); try { value = libmime.decodeWords(value); @@ -832,13 +838,17 @@ class MessageHandler { // 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 = 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(); + 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); } @@ -909,7 +919,13 @@ class MessageHandler { .split(/\s+/) .map(id => id.replace(/[<>]/g, '').trim()) .filter(id => id) - .map(id => crypto.createHash('sha1').update(id).digest('base64').replace(/[=]+$/g, '')) + .map(id => + crypto + .createHash('sha1') + .update(id) + .digest('base64') + .replace(/[=]+$/g, '') + ) ); referenceIds = Array.from(referenceIds).slice(0, 10); @@ -1216,7 +1232,13 @@ class MessageHandler { if (/^content-type:/i.test(line)) { let parts = line.split(':'); let value = parts.slice(1).join(':'); - if (value.split(';').shift().trim().toLowerCase() === 'multipart/encrypted') { + if ( + value + .split(';') + .shift() + .trim() + .toLowerCase() === 'multipart/encrypted' + ) { // message is already encrypted, do nothing return callback(null, false); } diff --git a/package.json b/package.json index 03aba89a..d38b1bc0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wildduck", - "version": "1.0.76", + "version": "1.0.77", "description": "IMAP server built with Node.js and MongoDB", "main": "server.js", "scripts": { @@ -11,7 +11,7 @@ "license": "EUPL-1.1", "devDependencies": { "browserbox": "^0.9.1", - "chai": "^4.1.1", + "chai": "^4.1.2", "eslint-config-nodemailer": "^1.2.0", "grunt": "^1.0.1", "grunt-cli": "^1.2.0", @@ -25,7 +25,7 @@ "dependencies": { "addressparser": "^1.0.1", "bcryptjs": "^2.4.3", - "bugsnag": "^1.12.0", + "bugsnag": "^1.12.1", "generate-password": "^1.3.0", "he": "^1.1.1", "html-to-text": "^3.3.0",