mirror of
https://github.com/nodemailer/wildduck.git
synced 2024-12-29 11:46:30 +08:00
693 lines
22 KiB
JavaScript
693 lines
22 KiB
JavaScript
'use strict';
|
|
|
|
const Joi = require('joi');
|
|
const ObjectID = require('mongodb').ObjectID;
|
|
const urllib = require('url');
|
|
|
|
module.exports = (db, server) => {
|
|
server.get('/users/:user/filters', (req, res, next) => {
|
|
res.charSet('utf-8');
|
|
|
|
const schema = Joi.object().keys({
|
|
user: Joi.string().hex().lowercase().length(24).required()
|
|
});
|
|
|
|
const result = Joi.validate(req.params, schema, {
|
|
abortEarly: false,
|
|
convert: true
|
|
});
|
|
|
|
if (result.error) {
|
|
res.json({
|
|
error: result.error.message
|
|
});
|
|
return next();
|
|
}
|
|
|
|
let user = new ObjectID(result.value.user);
|
|
|
|
db.users.collection('users').findOne({
|
|
_id: user
|
|
}, {
|
|
fields: {
|
|
address: true
|
|
}
|
|
}, (err, userData) => {
|
|
if (err) {
|
|
res.json({
|
|
error: 'MongoDB Error: ' + err.message
|
|
});
|
|
return next();
|
|
}
|
|
if (!userData) {
|
|
res.json({
|
|
error: 'This user does not exist'
|
|
});
|
|
return next();
|
|
}
|
|
|
|
db.database
|
|
.collection('mailboxes')
|
|
.find({
|
|
user
|
|
})
|
|
.project({ _id: 1, path: 1 })
|
|
.sort({ _id: 1 })
|
|
.toArray((err, mailboxes) => {
|
|
if (err) {
|
|
res.json({
|
|
error: 'MongoDB Error: ' + err.message
|
|
});
|
|
return next();
|
|
}
|
|
|
|
if (!mailboxes) {
|
|
mailboxes = [];
|
|
}
|
|
|
|
db.database
|
|
.collection('filters')
|
|
.find({
|
|
user
|
|
})
|
|
.sort({
|
|
_id: 1
|
|
})
|
|
.toArray((err, filters) => {
|
|
if (err) {
|
|
res.json({
|
|
error: 'MongoDB Error: ' + err.message
|
|
});
|
|
return next();
|
|
}
|
|
|
|
if (!filters) {
|
|
filters = [];
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
|
|
results: filters.map(filter => {
|
|
let descriptions = getFilterStrings(filter, mailboxes);
|
|
return {
|
|
id: filter._id,
|
|
name: filter.name,
|
|
query: descriptions.query,
|
|
action: descriptions.action,
|
|
created: filter.created
|
|
};
|
|
})
|
|
});
|
|
|
|
return next();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
server.get('/users/:user/filters/:filter', (req, res, next) => {
|
|
res.charSet('utf-8');
|
|
|
|
const schema = Joi.object().keys({
|
|
user: Joi.string().hex().lowercase().length(24).required(),
|
|
filter: Joi.string().hex().lowercase().length(24).required()
|
|
});
|
|
|
|
const result = Joi.validate(req.params, schema, {
|
|
abortEarly: false,
|
|
convert: true
|
|
});
|
|
|
|
if (result.error) {
|
|
res.json({
|
|
error: result.error.message
|
|
});
|
|
return next();
|
|
}
|
|
|
|
let user = new ObjectID(result.value.user);
|
|
let filter = new ObjectID(result.value.filter);
|
|
|
|
db.database.collection('filters').findOne({
|
|
_id: filter,
|
|
user
|
|
}, (err, filterData) => {
|
|
if (err) {
|
|
res.json({
|
|
error: 'MongoDB Error: ' + err.message
|
|
});
|
|
return next();
|
|
}
|
|
if (!filterData) {
|
|
res.json({
|
|
error: 'This filter does not exist'
|
|
});
|
|
return next();
|
|
}
|
|
|
|
db.database
|
|
.collection('mailboxes')
|
|
.find({
|
|
user
|
|
})
|
|
.project({ _id: 1, path: 1 })
|
|
.sort({ _id: 1 })
|
|
.toArray((err, mailboxes) => {
|
|
if (err) {
|
|
res.json({
|
|
error: 'MongoDB Error: ' + err.message
|
|
});
|
|
return next();
|
|
}
|
|
|
|
if (!mailboxes) {
|
|
mailboxes = [];
|
|
}
|
|
|
|
let result = {
|
|
success: true,
|
|
id: filterData._id,
|
|
name: filterData.name,
|
|
created: filterData.created
|
|
};
|
|
|
|
Object.keys((filterData.query && filterData.query.headers) || {}).forEach(key => {
|
|
result['query_' + key] = filterData.query.headers[key];
|
|
});
|
|
|
|
Object.keys(filterData.query || {}).forEach(key => {
|
|
if (key !== 'headers') {
|
|
result['query_' + key] = filterData.query[key];
|
|
}
|
|
});
|
|
|
|
Object.keys(filterData.action || {}).forEach(key => {
|
|
result['action_' + key] = filterData.action[key];
|
|
});
|
|
|
|
res.json(result);
|
|
|
|
return next();
|
|
});
|
|
});
|
|
});
|
|
|
|
server.del('/users/:user/filters/:filter', (req, res, next) => {
|
|
res.charSet('utf-8');
|
|
|
|
const schema = Joi.object().keys({
|
|
user: Joi.string().hex().lowercase().length(24).required(),
|
|
filter: Joi.string().hex().lowercase().length(24).required()
|
|
});
|
|
|
|
const result = Joi.validate(req.params, schema, {
|
|
abortEarly: false,
|
|
convert: true
|
|
});
|
|
|
|
if (result.error) {
|
|
res.json({
|
|
error: result.error.message
|
|
});
|
|
return next();
|
|
}
|
|
|
|
let user = new ObjectID(result.value.user);
|
|
let filter = new ObjectID(result.value.filter);
|
|
|
|
db.database.collection('filters').deleteOne({
|
|
_id: filter,
|
|
user
|
|
}, (err, r) => {
|
|
if (err) {
|
|
res.json({
|
|
error: 'MongoDB Error: ' + err.message
|
|
});
|
|
return next();
|
|
}
|
|
|
|
if (!r.deletedCount) {
|
|
res.status(404);
|
|
res.json({
|
|
error: 'Filter was not found'
|
|
});
|
|
return next();
|
|
}
|
|
|
|
res.json({
|
|
success: true
|
|
});
|
|
return next();
|
|
});
|
|
});
|
|
|
|
server.post('/users/:user/filters', (req, res, next) => {
|
|
res.charSet('utf-8');
|
|
|
|
const schema = Joi.object().keys({
|
|
user: Joi.string().hex().lowercase().length(24).required(),
|
|
|
|
name: Joi.string().trim().max(255).empty(''),
|
|
|
|
query_from: Joi.string().trim().max(255).empty(''),
|
|
query_to: Joi.string().trim().max(255).empty(''),
|
|
query_subject: Joi.string().trim().max(255).empty(''),
|
|
query_text: Joi.string().trim().max(255).empty(''),
|
|
query_ha: Joi.boolean().truthy(['Y', 'true', 'yes', 1]).empty(''),
|
|
query_size: Joi.number().empty(''),
|
|
|
|
action_seen: Joi.boolean().truthy(['Y', 'true', 'yes', 1]).empty(''),
|
|
action_flag: Joi.boolean().truthy(['Y', 'true', 'yes', 1]).empty(''),
|
|
action_delete: Joi.boolean().truthy(['Y', 'true', 'yes', 1]).empty(''),
|
|
action_spam: Joi.boolean().truthy(['Y', 'true', 'yes', 1]).empty(''),
|
|
|
|
action_mailbox: Joi.string().hex().lowercase().length(24).empty(''),
|
|
action_forward: Joi.string().email().empty(''),
|
|
action_targetUrl: Joi.string()
|
|
.uri({
|
|
scheme: ['http', 'https'],
|
|
allowRelative: false,
|
|
relativeOnly: false
|
|
})
|
|
.empty('')
|
|
});
|
|
|
|
const result = Joi.validate(req.params, schema, {
|
|
abortEarly: false,
|
|
convert: true
|
|
});
|
|
|
|
if (result.error) {
|
|
res.json({
|
|
error: result.error.message
|
|
});
|
|
return next();
|
|
}
|
|
|
|
let user = new ObjectID(result.value.user);
|
|
let filterData = {
|
|
_id: new ObjectID(),
|
|
user,
|
|
query: {
|
|
headers: {}
|
|
},
|
|
action: {},
|
|
created: new Date()
|
|
};
|
|
|
|
if (result.value.name) {
|
|
filterData.name = result.value.name;
|
|
}
|
|
|
|
let hasQuery = false;
|
|
let hasAction = false;
|
|
|
|
['from', 'to', 'subject'].forEach(key => {
|
|
if (result.value['query_' + key]) {
|
|
filterData.query.headers[key] = result.value['query_' + key].replace(/\s+/g, ' ');
|
|
hasQuery = true;
|
|
}
|
|
});
|
|
|
|
if (result.value.query_text) {
|
|
filterData.query.text = result.value.query_text.replace(/\s+/g, ' ');
|
|
hasQuery = true;
|
|
}
|
|
|
|
if (typeof result.value.query_ha === 'boolean') {
|
|
filterData.query.ha = result.value.query_ha;
|
|
hasQuery = true;
|
|
}
|
|
|
|
if (result.value.query_size) {
|
|
filterData.query.size = result.value.query_size;
|
|
hasQuery = true;
|
|
}
|
|
|
|
['seen', 'flag', 'delete', 'spam'].forEach(key => {
|
|
if (typeof result.value['action_' + key] === 'boolean') {
|
|
filterData.action[key] = result.value['action_' + key];
|
|
hasAction = true;
|
|
}
|
|
});
|
|
|
|
['forward', 'targetUrl'].forEach(key => {
|
|
if (result.value['action_' + key]) {
|
|
filterData.action[key] = result.value['action_' + key];
|
|
hasAction = true;
|
|
}
|
|
});
|
|
|
|
let checkFilterMailbox = done => {
|
|
if (!result.value.action_mailbox) {
|
|
return done();
|
|
}
|
|
db.database.collection('mailboxes').findOne({
|
|
_id: new ObjectID(result.value.action_mailbox),
|
|
user
|
|
}, (err, mailboxData) => {
|
|
if (err) {
|
|
res.json({
|
|
error: 'MongoDB Error: ' + err.message
|
|
});
|
|
return next();
|
|
}
|
|
if (!mailboxData) {
|
|
res.json({
|
|
error: 'This mailbox does not exist'
|
|
});
|
|
return next();
|
|
}
|
|
filterData.action.mailbox = mailboxData._id;
|
|
hasAction = true;
|
|
done();
|
|
});
|
|
};
|
|
|
|
checkFilterMailbox(() => {
|
|
if (!hasQuery) {
|
|
res.json({
|
|
error: 'Empty filter query'
|
|
});
|
|
return next();
|
|
}
|
|
|
|
if (!hasAction) {
|
|
res.json({
|
|
error: 'Empty filter action'
|
|
});
|
|
return next();
|
|
}
|
|
|
|
db.users.collection('users').findOne({
|
|
_id: user
|
|
}, {
|
|
fields: {
|
|
address: true
|
|
}
|
|
}, (err, userData) => {
|
|
if (err) {
|
|
res.json({
|
|
error: 'MongoDB Error: ' + err.message
|
|
});
|
|
return next();
|
|
}
|
|
if (!userData) {
|
|
res.json({
|
|
error: 'This user does not exist'
|
|
});
|
|
return next();
|
|
}
|
|
|
|
db.database.collection('filters').insertOne(filterData, (err, r) => {
|
|
if (err) {
|
|
res.json({
|
|
error: 'MongoDB Error: ' + err.message
|
|
});
|
|
return next();
|
|
}
|
|
|
|
res.json({
|
|
success: !!r.insertedCount,
|
|
id: filterData._id
|
|
});
|
|
return next();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
server.put('/users/:user/filters/:filter', (req, res, next) => {
|
|
res.charSet('utf-8');
|
|
|
|
const schema = Joi.object().keys({
|
|
user: Joi.string().hex().lowercase().length(24).required(),
|
|
filter: Joi.string().hex().lowercase().length(24).required(),
|
|
|
|
name: Joi.string().trim().max(255).empty(''),
|
|
|
|
query_from: Joi.string().trim().max(255).empty(''),
|
|
query_to: Joi.string().trim().max(255).empty(''),
|
|
query_subject: Joi.string().trim().max(255).empty(''),
|
|
query_text: Joi.string().trim().max(255).empty(''),
|
|
query_ha: Joi.boolean().truthy(['Y', 'true', 'yes', 1]).empty(''),
|
|
query_size: Joi.number().empty(''),
|
|
|
|
action_seen: Joi.boolean().truthy(['Y', 'true', 'yes', 1]).empty(''),
|
|
action_flag: Joi.boolean().truthy(['Y', 'true', 'yes', 1]).empty(''),
|
|
action_delete: Joi.boolean().truthy(['Y', 'true', 'yes', 1]).empty(''),
|
|
action_spam: Joi.boolean().truthy(['Y', 'true', 'yes', 1]).empty(''),
|
|
|
|
action_mailbox: Joi.string().hex().lowercase().length(24).empty(''),
|
|
action_forward: Joi.string().email().empty(''),
|
|
action_targetUrl: Joi.string()
|
|
.uri({
|
|
scheme: ['http', 'https'],
|
|
allowRelative: false,
|
|
relativeOnly: false
|
|
})
|
|
.empty('')
|
|
});
|
|
|
|
const result = Joi.validate(req.params, schema, {
|
|
abortEarly: false,
|
|
convert: true
|
|
});
|
|
|
|
if (result.error) {
|
|
res.json({
|
|
error: result.error.message
|
|
});
|
|
return next();
|
|
}
|
|
|
|
let user = new ObjectID(result.value.user);
|
|
let filter = new ObjectID(result.value.filter);
|
|
|
|
let $set = {};
|
|
let $unset = {};
|
|
|
|
if (result.value.name) {
|
|
$set.name = result.value.name;
|
|
}
|
|
|
|
let hasQuery = false;
|
|
let hasAction = false;
|
|
|
|
['from', 'to', 'subject'].forEach(key => {
|
|
if (result.value['query_' + key]) {
|
|
$set['query.headers.' + key] = result.value['query_' + key].replace(/\s+/g, ' ');
|
|
hasQuery = true;
|
|
} else if ('query_' + key in req.params) {
|
|
$unset['query.headers.' + key] = true;
|
|
hasQuery = true;
|
|
}
|
|
});
|
|
|
|
if (result.value.query_text) {
|
|
$set['query.text'] = result.value.query_text.replace(/\s+/g, ' ');
|
|
hasQuery = true;
|
|
} else if ('query_text' in req.params) {
|
|
$unset['query.text'] = true;
|
|
hasQuery = true;
|
|
}
|
|
|
|
if (typeof result.value.query_ha === 'boolean') {
|
|
$set['query.ha'] = result.value.query_ha;
|
|
hasQuery = true;
|
|
} else if ('query_ha' in req.params) {
|
|
$unset['query.ha'] = true;
|
|
hasQuery = true;
|
|
}
|
|
|
|
if (result.value.query_size) {
|
|
$set['query.size'] = result.value.query_size;
|
|
hasQuery = true;
|
|
} else if ('query_size' in req.params) {
|
|
$unset['query.size'] = true;
|
|
hasQuery = true;
|
|
}
|
|
|
|
['seen', 'flag', 'delete', 'spam'].forEach(key => {
|
|
if (typeof result.value['action_' + key] === 'boolean') {
|
|
$set['action.' + key] = result.value['action_' + key];
|
|
hasAction = true;
|
|
} else if ('action_' + key in req.params) {
|
|
$unset['action.' + key] = true;
|
|
hasAction = true;
|
|
}
|
|
});
|
|
|
|
['forward', 'targetUrl'].forEach(key => {
|
|
if (result.value['action_' + key]) {
|
|
$set['action.' + key] = result.value['action_' + key];
|
|
hasAction = true;
|
|
} else if ('action_' + key in req.params) {
|
|
$unset['action.' + key] = true;
|
|
hasAction = true;
|
|
}
|
|
});
|
|
|
|
let checkFilterMailbox = done => {
|
|
if (!result.value.action_mailbox) {
|
|
if ('action_mailbox' in req.params) {
|
|
$unset['action.mailbox'] = true;
|
|
hasAction = true;
|
|
}
|
|
return done();
|
|
}
|
|
db.database.collection('mailboxes').findOne({
|
|
_id: new ObjectID(result.value.action_mailbox),
|
|
user
|
|
}, (err, mailboxData) => {
|
|
if (err) {
|
|
res.json({
|
|
error: 'MongoDB Error: ' + err.message
|
|
});
|
|
return next();
|
|
}
|
|
if (!mailboxData) {
|
|
res.json({
|
|
error: 'This mailbox does not exist'
|
|
});
|
|
return next();
|
|
}
|
|
$set['action.mailbox'] = mailboxData._id;
|
|
hasAction = true;
|
|
done();
|
|
});
|
|
};
|
|
|
|
checkFilterMailbox(() => {
|
|
if (!hasQuery && !hasAction) {
|
|
res.json({
|
|
error: 'No changes'
|
|
});
|
|
return next();
|
|
}
|
|
|
|
let update = {};
|
|
|
|
if (Object.keys($set).length) {
|
|
update.$set = $set;
|
|
}
|
|
|
|
if (Object.keys($unset).length) {
|
|
update.$unset = $unset;
|
|
}
|
|
|
|
db.database.collection('filters').findOneAndUpdate({ _id: filter, user }, update, (err, r) => {
|
|
if (err) {
|
|
res.json({
|
|
error: 'MongoDB Error: ' + err.message
|
|
});
|
|
return next();
|
|
}
|
|
|
|
if (!r || !r.value || !r.value._id) {
|
|
res.status(404);
|
|
res.json({
|
|
error: 'Filter was not found'
|
|
});
|
|
return next();
|
|
}
|
|
|
|
res.json({
|
|
success: true
|
|
});
|
|
return next();
|
|
});
|
|
});
|
|
});
|
|
};
|
|
|
|
function getFilterStrings(filter, mailboxes) {
|
|
let query = Object.keys(filter.query.headers || {}).map(key => [key, '(' + filter.query.headers[key] + ')']);
|
|
|
|
if (filter.query.ha && filter.query.ha > 0) {
|
|
query.push(['has attachment']);
|
|
} else if (filter.query.ha && filter.query.ha < 0) {
|
|
query.push(['no attachments']);
|
|
}
|
|
|
|
if (filter.query.text) {
|
|
query.push([false, '"' + filter.query.text + '"']);
|
|
}
|
|
|
|
if (filter.query.size) {
|
|
let unit = 'B';
|
|
let size = Math.abs(filter.query.size || 0);
|
|
if (size) {
|
|
if (filter.query.size % (1024 * 1024) === 0) {
|
|
unit = 'MB';
|
|
size = Math.round(size / (1024 * 1024));
|
|
} else if (filter.query.size % 1024 === 0) {
|
|
unit = 'kB';
|
|
size = Math.round(size / 1024);
|
|
}
|
|
}
|
|
if (filter.query.size > 0) {
|
|
query.push(['larger', size + unit]);
|
|
} else if (filter.query.size < 0) {
|
|
query.push(['smaller', size + unit]);
|
|
}
|
|
}
|
|
|
|
// process actions
|
|
let action = Object.keys(filter.action || {})
|
|
.map(key => {
|
|
switch (key) {
|
|
case 'seen':
|
|
if (filter.action[key]) {
|
|
return ['mark as read'];
|
|
} else {
|
|
return ['do not mark as read'];
|
|
}
|
|
case 'flag':
|
|
if (filter.action[key]) {
|
|
return ['flag it'];
|
|
} else {
|
|
return ['do not flag it'];
|
|
}
|
|
case 'mailbox':
|
|
if (filter.action[key]) {
|
|
let target = mailboxes.find(mailbox => mailbox._id.toString() === filter.action[key].toString());
|
|
return ['move to folder', target ? '"' + target.path + '"' : '?'];
|
|
} else {
|
|
return ['keep in INBOX'];
|
|
}
|
|
case 'forward':
|
|
if (filter.action[key]) {
|
|
return ['forward to', filter.action[key]];
|
|
}
|
|
break;
|
|
case 'targetUrl':
|
|
if (filter.action[key]) {
|
|
let url = filter.action[key];
|
|
let parsed = urllib.parse(url);
|
|
return ['upload to', parsed.hostname || parsed.host];
|
|
}
|
|
break;
|
|
case 'spam':
|
|
if (filter.action[key] > 0) {
|
|
return ['mark it as spam'];
|
|
} else if (filter.action[key] < 0) {
|
|
return ['do not mark it as spam'];
|
|
}
|
|
break;
|
|
case 'delete':
|
|
if (filter.action[key]) {
|
|
return ['delete it'];
|
|
} else {
|
|
return ['do not delete it'];
|
|
}
|
|
}
|
|
return false;
|
|
})
|
|
.filter(str => str);
|
|
return {
|
|
query,
|
|
action
|
|
};
|
|
}
|