mirror of
https://github.com/nodemailer/wildduck.git
synced 2025-01-02 21:52:34 +08:00
312 lines
9.4 KiB
JavaScript
312 lines
9.4 KiB
JavaScript
'use strict';
|
|
|
|
const Indexer = require('./indexer/indexer');
|
|
let indexer = new Indexer();
|
|
|
|
module.exports.matchSearchQuery = matchSearchQuery;
|
|
|
|
let queryHandlers = {
|
|
// always matches
|
|
all(message, query, callback) {
|
|
return callback(null, true);
|
|
},
|
|
|
|
// matches if the message object includes (exists:true) or does not include (exists:false) specifiec flag
|
|
flag(message, query, callback) {
|
|
let pos = [].concat(message.flags || []).indexOf(query.value);
|
|
return callback(null, query.exists ? pos >= 0 : pos < 0);
|
|
},
|
|
|
|
// matches message receive date
|
|
internaldate(message, query, callback) {
|
|
switch (query.operator) {
|
|
case '<':
|
|
return callback(null, getShortDate(message.internaldate) < getShortDate(query.value));
|
|
case '=':
|
|
return callback(null, getShortDate(message.internaldate) === getShortDate(query.value));
|
|
case '>=':
|
|
return callback(null, getShortDate(message.internaldate) >= getShortDate(query.value));
|
|
}
|
|
return callback(null, false);
|
|
},
|
|
|
|
// matches message header date
|
|
date(message, query, callback) {
|
|
let date;
|
|
if (message.headerdate) {
|
|
date = message.headerdate;
|
|
} else {
|
|
let mimeTree = message.mimeTree;
|
|
if (!mimeTree) {
|
|
mimeTree = indexer.parseMimeTree(message.raw);
|
|
}
|
|
date = mimeTree.parsedHeader.date || message.internaldate;
|
|
}
|
|
|
|
switch (query.operator) {
|
|
case '<':
|
|
return callback(null, getShortDate(date) < getShortDate(query.value));
|
|
case '=':
|
|
return callback(null, getShortDate(date) === getShortDate(query.value));
|
|
case '>=':
|
|
return callback(null, getShortDate(date) >= getShortDate(query.value));
|
|
}
|
|
|
|
return callback(null, false);
|
|
},
|
|
|
|
// matches message body
|
|
body(message, query, callback) {
|
|
let data = indexer.getContents(message.mimeTree, {
|
|
type: 'text'
|
|
}, true);
|
|
|
|
let resolveData = next => {
|
|
if (data.type !== 'stream') {
|
|
return next(null, data.value);
|
|
}
|
|
|
|
let chunks = [];
|
|
let chunklen = 0;
|
|
|
|
data.value.once('error', err => next(err));
|
|
|
|
data.value.on('readable', () => {
|
|
let chunk;
|
|
while ((chunk = data.value.read()) !== null) {
|
|
chunks.push(chunk);
|
|
chunklen += chunk.length;
|
|
}
|
|
});
|
|
|
|
data.value.on('end', () => {
|
|
next(null, Buffer.concat(chunks, chunklen));
|
|
});
|
|
};
|
|
|
|
resolveData((err, body) => {
|
|
if (err) {
|
|
return callback(err);
|
|
}
|
|
callback(null, body.toString().toLowerCase().indexOf((query.value || '').toString().toLowerCase()) >= 0);
|
|
});
|
|
},
|
|
|
|
// matches message source
|
|
text(message, query, callback) {
|
|
let data = indexer.getContents(message.mimeTree, {
|
|
type: 'content'
|
|
}, true);
|
|
|
|
let resolveData = next => {
|
|
if (data.type !== 'stream') {
|
|
return next(null, data.value);
|
|
}
|
|
|
|
let chunks = [];
|
|
let chunklen = 0;
|
|
|
|
data.value.once('error', err => next(err));
|
|
|
|
data.value.on('readable', () => {
|
|
let chunk;
|
|
while ((chunk = data.value.read()) !== null) {
|
|
chunks.push(chunk);
|
|
chunklen += chunk.length;
|
|
}
|
|
});
|
|
|
|
data.value.on('end', () => {
|
|
next(null, Buffer.concat(chunks, chunklen));
|
|
});
|
|
};
|
|
|
|
resolveData((err, text) => {
|
|
if (err) {
|
|
return callback(err);
|
|
}
|
|
callback(null, text.toString().toLowerCase().indexOf((query.value || '').toString().toLowerCase()) >= 0);
|
|
});
|
|
},
|
|
|
|
// matches message UID number. Sequence queries are also converted to UID queries
|
|
uid(message, query, callback) {
|
|
return callback(null, query.value.indexOf(message.uid) >= 0);
|
|
},
|
|
|
|
// matches message source size
|
|
size(message, query, callback) {
|
|
let size = message.size;
|
|
if (!size) {
|
|
size = (message.raw || '').length;
|
|
}
|
|
|
|
switch (query.operator) {
|
|
case '<':
|
|
return callback(null, size < query.value);
|
|
case '=':
|
|
return callback(null, size === query.value);
|
|
case '>':
|
|
return callback(null, size > query.value);
|
|
}
|
|
|
|
return callback(null, false);
|
|
},
|
|
|
|
// matches message headers
|
|
header(message, query, callback) {
|
|
let mimeTree = message.mimeTree;
|
|
if (!mimeTree) {
|
|
mimeTree = indexer.parseMimeTree(message.raw || '');
|
|
}
|
|
|
|
let headers = (mimeTree.header || []);
|
|
let header = query.header;
|
|
let term = (query.value || '').toString().toLowerCase();
|
|
let key, value, parts;
|
|
|
|
for (let i = 0, len = headers.length; i < len; i++) {
|
|
parts = headers[i].split(':');
|
|
key = (parts.shift() || '').trim().toLowerCase();
|
|
|
|
value = (parts.join(':') || '');
|
|
|
|
if (key === header && (!term || value.toLowerCase().indexOf(term) >= 0)) {
|
|
return callback(null, true);
|
|
}
|
|
}
|
|
|
|
return callback(null, false);
|
|
},
|
|
|
|
// matches messages with modifyIndex exual or greater than criteria
|
|
modseq(message, query, callback) {
|
|
return callback(null, message.modseq >= query.value);
|
|
},
|
|
|
|
// charset argument is ignored
|
|
charset(message, query, callback) {
|
|
return callback(null, true);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Returns a date object with time set to 00:00 on UTC timezone
|
|
*
|
|
* @param {String|Date} date Date to convert
|
|
* @returns {Date} Date object without time
|
|
*/
|
|
function getShortDate(date) {
|
|
date = date || new Date();
|
|
if (typeof date === 'string' || typeof date === 'number') {
|
|
date = new Date(date);
|
|
}
|
|
return date.toISOString().substr(0, 10);
|
|
}
|
|
|
|
/**
|
|
* Checks if a specific search term match the message or not
|
|
*
|
|
* @param {Object} message Stored message object
|
|
* @param {Object} query Query term object
|
|
* @returns {Boolean} Term matched (true) or not (false)
|
|
*/
|
|
function matchSearchTerm(message, query, callback) {
|
|
|
|
if (Array.isArray(query)) {
|
|
// AND, all terms need to match
|
|
return matchSearchQuery(message, query, callback);
|
|
}
|
|
|
|
if (!query || typeof query !== 'object') {
|
|
// unknown query term
|
|
return setImmediate(() => callback(null, false));
|
|
}
|
|
|
|
switch (query.key) {
|
|
case 'or':
|
|
{
|
|
// OR, only single match needed
|
|
let checked = 0;
|
|
let checkNext = () => {
|
|
if (checked >= query.value.length) {
|
|
return callback(null, false);
|
|
}
|
|
let term = query.value[checked++];
|
|
matchSearchTerm(message, term, (err, match) => {
|
|
if (err) {
|
|
return callback(err);
|
|
}
|
|
if (match) {
|
|
return callback(null, true);
|
|
}
|
|
setImmediate(checkNext);
|
|
});
|
|
};
|
|
return setImmediate(checkNext);
|
|
}
|
|
/*
|
|
// OR, only single match needed
|
|
for (let i = query.value.length - 1; i >= 0; i--) {
|
|
if (matchSearchTerm(message, query.value[i])) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
*/
|
|
case 'not':
|
|
// return reverse match
|
|
return matchSearchTerm(message, query.value, (err, match) => {
|
|
if (err) {
|
|
return callback(err);
|
|
}
|
|
callback(null, !match);
|
|
});
|
|
default:
|
|
// check if there is a handler for the term and use it
|
|
if (queryHandlers.hasOwnProperty(query.key)) {
|
|
return setImmediate(() => queryHandlers[query.key](message, query, callback));
|
|
}
|
|
return setImmediate(() => callback(null, false));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Traverses query tree and checks if all query terms match or not. Stops on first false match occurence
|
|
*
|
|
* @param {Object} message Stored message object
|
|
* @param {Object} query Query term object
|
|
* @returns {Boolean} Term matched (true) or not (false)
|
|
*/
|
|
function matchSearchQuery(message, query, callback) {
|
|
if (!Array.isArray(query)) {
|
|
query = [].concat(query || []);
|
|
}
|
|
|
|
let checked = 0;
|
|
let checkNext = () => {
|
|
if (checked >= query.length) {
|
|
return callback(null, true);
|
|
}
|
|
let term = query[checked++];
|
|
matchSearchTerm(message, term, (err, match) => {
|
|
if (err) {
|
|
return callback(err);
|
|
}
|
|
if (!match) {
|
|
return callback(null, false);
|
|
}
|
|
setImmediate(checkNext);
|
|
});
|
|
};
|
|
return setImmediate(checkNext);
|
|
/*
|
|
for (let i = 0, len = query.length; i < len; i++) {
|
|
if (!matchSearchTerm(message, query[i])) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
*/
|
|
}
|