use asynchronous search matching

This commit is contained in:
Andris Reinman 2017-03-10 16:59:04 +02:00
parent 9314708e1f
commit cae2ad9e95
5 changed files with 2427 additions and 227 deletions

View file

@ -6,36 +6,32 @@ let indexer = new Indexer();
module.exports.matchSearchQuery = matchSearchQuery; module.exports.matchSearchQuery = matchSearchQuery;
let queryHandlers = { let queryHandlers = {
// atom beautify uses invalid indentation that messes up shorthand methods
/*eslint-disable object-shorthand */
// always matches // always matches
all: function () { all(message, query, callback) {
return true; return callback(null, true);
}, },
// matches if the message object includes (exists:true) or does not include (exists:false) specifiec flag // matches if the message object includes (exists:true) or does not include (exists:false) specifiec flag
flag: function (message, query) { flag(message, query, callback) {
let pos = [].concat(message.flags || []).indexOf(query.value); let pos = [].concat(message.flags || []).indexOf(query.value);
return query.exists ? pos >= 0 : pos < 0; return callback(null, query.exists ? pos >= 0 : pos < 0);
}, },
// matches message receive date // matches message receive date
internaldate: function (message, query) { internaldate(message, query, callback) {
switch (query.operator) { switch (query.operator) {
case '<': case '<':
return getShortDate(message.internaldate) < getShortDate(query.value); return callback(null, getShortDate(message.internaldate) < getShortDate(query.value));
case '=': case '=':
return getShortDate(message.internaldate) === getShortDate(query.value); return callback(null, getShortDate(message.internaldate) === getShortDate(query.value));
case '>=': case '>=':
return getShortDate(message.internaldate) >= getShortDate(query.value); return callback(null, getShortDate(message.internaldate) >= getShortDate(query.value));
} }
return false; return callback(null, false);
}, },
// matches message header date // matches message header date
date: function (message, query) { date(message, query, callback) {
let date; let date;
if (message.headerdate) { if (message.headerdate) {
date = message.headerdate; date = message.headerdate;
@ -49,39 +45,97 @@ let queryHandlers = {
switch (query.operator) { switch (query.operator) {
case '<': case '<':
return getShortDate(date) < getShortDate(query.value); return callback(null, getShortDate(date) < getShortDate(query.value));
case '=': case '=':
return getShortDate(date) === getShortDate(query.value); return callback(null, getShortDate(date) === getShortDate(query.value));
case '>=': case '>=':
return getShortDate(date) >= getShortDate(query.value); return callback(null, getShortDate(date) >= getShortDate(query.value));
} }
return false; return callback(null, false);
}, },
// matches message body // matches message body
body: function (message, query) { body(message, query, callback) {
let body = indexer.getContents(message.mimeTree).toString(); let data = indexer.getContents(message.mimeTree, {
let bodyStart = body.match(/\r?\r?\n/); type: 'text'
if (!bodyStart) { });
return false;
} let resolveData = next => {
return body.substr(bodyStart.index + bodyStart[0].length).toLowerCase().indexOf((query.value || '').toString().toLowerCase()) >= 0; 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 // matches message source
text: function (message, query) { text(message, query, callback) {
let body = indexer.getContents(message.mimeTree).toString(); let data = indexer.getContents(message.mimeTree, {
return body.toLowerCase().indexOf((query.value || '').toString().toLowerCase()) >= 0; type: 'content'
});
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 // matches message UID number. Sequence queries are also converted to UID queries
uid: function (message, query) { uid(message, query, callback) {
return query.value.indexOf(message.uid) >= 0; return callback(null, query.value.indexOf(message.uid) >= 0);
}, },
// matches message source size // matches message source size
size: function (message, query) { size(message, query, callback) {
let size = message.size; let size = message.size;
if (!size) { if (!size) {
size = (message.raw || '').length; size = (message.raw || '').length;
@ -89,18 +143,18 @@ let queryHandlers = {
switch (query.operator) { switch (query.operator) {
case '<': case '<':
return size < query.value; return callback(null, size < query.value);
case '=': case '=':
return size === query.value; return callback(null, size === query.value);
case '>': case '>':
return size > query.value; return callback(null, size > query.value);
} }
return false; return callback(null, false);
}, },
// matches message headers // matches message headers
header: function (message, query) { header(message, query, callback) {
let mimeTree = message.mimeTree; let mimeTree = message.mimeTree;
if (!mimeTree) { if (!mimeTree) {
mimeTree = indexer.parseMimeTree(message.raw || ''); mimeTree = indexer.parseMimeTree(message.raw || '');
@ -123,24 +177,22 @@ let queryHandlers = {
value = (parts.join(':') || ''); value = (parts.join(':') || '');
if (key === header && (!term || value.toLowerCase().indexOf(term) >= 0)) { if (key === header && (!term || value.toLowerCase().indexOf(term) >= 0)) {
return true; return callback(null, true);
} }
} }
return false; return callback(null, false);
}, },
// matches messages with modifyIndex exual or greater than criteria // matches messages with modifyIndex exual or greater than criteria
modseq: function (message, query) { modseq(message, query, callback) {
return message.modseq >= query.value; return callback(null, message.modseq >= query.value);
}, },
// charset argument is ignored // charset argument is ignored
charset: function () { charset(message, query, callback) {
return true; return callback(null, true);
} }
/*eslint-enable object-shorthand */
}; };
/** /**
@ -164,20 +216,41 @@ function getShortDate(date) {
* @param {Object} query Query term object * @param {Object} query Query term object
* @returns {Boolean} Term matched (true) or not (false) * @returns {Boolean} Term matched (true) or not (false)
*/ */
function matchSearchTerm(message, query) { function matchSearchTerm(message, query, callback) {
if (Array.isArray(query)) { if (Array.isArray(query)) {
// AND, all terms need to match // AND, all terms need to match
return matchSearchQuery(message, query); return matchSearchQuery(message, query, callback);
} }
if (!query || typeof query !== 'object') { if (!query || typeof query !== 'object') {
// unknown query term // unknown query term
return false; return setImmediate(() => callback(null, false));
} }
switch (query.key) { switch (query.key) {
case 'or': 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 // OR, only single match needed
for (let i = query.value.length - 1; i >= 0; i--) { for (let i = query.value.length - 1; i >= 0; i--) {
if (matchSearchTerm(message, query.value[i])) { if (matchSearchTerm(message, query.value[i])) {
@ -185,15 +258,21 @@ function matchSearchTerm(message, query) {
} }
} }
return false; return false;
*/
case 'not': case 'not':
// return reverse match // return reverse match
return !matchSearchTerm(message, query.value); return matchSearchTerm(message, query.value, (err, match) => {
if (err) {
return callback(err);
}
callback(null, !match);
});
default: default:
// check if there is a handler for the term and use it // check if there is a handler for the term and use it
if (queryHandlers.hasOwnProperty(query.key)) { if (queryHandlers.hasOwnProperty(query.key)) {
return queryHandlers[query.key](message, query); return setImmediate(() => queryHandlers[query.key](message, query, callback));
} }
return false; return setImmediate(() => callback(null, false));
} }
} }
@ -204,16 +283,35 @@ function matchSearchTerm(message, query) {
* @param {Object} query Query term object * @param {Object} query Query term object
* @returns {Boolean} Term matched (true) or not (false) * @returns {Boolean} Term matched (true) or not (false)
*/ */
function matchSearchQuery(message, query) { function matchSearchQuery(message, query, callback) {
if (!Array.isArray(query)) { if (!Array.isArray(query)) {
query = [].concat(query || []); query = [].concat(query || []);
} }
for (let i = 0, len = query.length; i < len; i++) { let checked = 0;
if (!matchSearchTerm(message, query[i])) { let checkNext = () => {
return false; 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; return true;
*/
} }

View file

@ -4,6 +4,9 @@
let parseQueryTerms = require('../lib/commands/search').parseQueryTerms; let parseQueryTerms = require('../lib/commands/search').parseQueryTerms;
let matchSearchQuery = require('../lib/search').matchSearchQuery; let matchSearchQuery = require('../lib/search').matchSearchQuery;
let Indexer = require('../lib/indexer/indexer');
let indexer = new Indexer();
let chai = require('chai'); let chai = require('chai');
let expect = chai.expect; let expect = chai.expect;
chai.config.includeStack = true; chai.config.includeStack = true;
@ -400,8 +403,8 @@ describe('#parseQueryTerms', function () {
describe('Search term match tests', function () { describe('Search term match tests', function () {
describe('AND', function () { describe('AND', function () {
it('should find all matches', function () { it('should find all matches', function (done) {
expect(matchSearchQuery({}, [{ matchSearchQuery({}, [{
key: 'all', key: 'all',
value: true value: true
}, { }, {
@ -413,11 +416,15 @@ describe('Search term match tests', function () {
}, { }, {
key: 'all', key: 'all',
value: true value: true
}])).to.be.true; }], (err, match) => {
expect(err).to.not.exist;
expect(match).to.be.true;
done();
});
}); });
it('should fail on single error', function () { it('should fail on single error', function (done) {
expect(matchSearchQuery({}, [{ matchSearchQuery({}, [{
key: 'all', key: 'all',
value: true value: true
}, { }, {
@ -430,13 +437,17 @@ describe('Search term match tests', function () {
key: 'flag', key: 'flag',
value: 'zzzzzzz', value: 'zzzzzzz',
exists: true exists: true
}])).to.be.false; }], (err, match) => {
expect(err).to.not.exist;
expect(match).to.be.false;
done();
});
}); });
}); });
describe('OR', function () { describe('OR', function () {
it('should succeed with at least one match', function () { it('should succeed with at least one match', function (done) {
expect(matchSearchQuery({}, { matchSearchQuery({}, {
key: 'or', key: 'or',
value: [{ value: [{
key: 'flag', key: 'flag',
@ -450,11 +461,15 @@ describe('Search term match tests', function () {
value: 'zzzzzzz', value: 'zzzzzzz',
exists: true exists: true
}] }]
})).to.be.true; }, (err, match) => {
expect(err).to.not.exist;
expect(match).to.be.true;
done();
});
}); });
it('should fail with no matches', function () { it('should fail with no matches', function (done) {
expect(matchSearchQuery({}, { matchSearchQuery({}, {
key: 'or', key: 'or',
value: [{ value: [{
key: 'flag', key: 'flag',
@ -465,419 +480,581 @@ describe('Search term match tests', function () {
value: 'zzzzzzz', value: 'zzzzzzz',
exists: true exists: true
}] }]
})).to.be.false; }, (err, match) => {
expect(err).to.not.exist;
expect(match).to.be.false;
done();
});
}); });
}); });
describe('NOT', function () { describe('NOT', function () {
it('should succeed with false value', function () { it('should succeed with false value', function (done) {
expect(matchSearchQuery({}, { matchSearchQuery({}, {
key: 'not', key: 'not',
value: { value: {
key: 'flag', key: 'flag',
value: 'zzzzzzz', value: 'zzzzzzz',
exists: true exists: true
} }
})).to.be.true; }, (err, match) => {
expect(err).to.not.exist;
expect(match).to.be.true;
done();
});
}); });
it('should fail with thruthy value', function () { it('should fail with thruthy value', function (done) {
expect(matchSearchQuery({}, { matchSearchQuery({}, {
key: 'not', key: 'not',
value: { value: {
key: 'all', key: 'all',
value: true value: true
} }
})).to.be.false; }, (err, match) => {
expect(err).to.not.exist;
expect(match).to.be.false;
done();
});
}); });
}); });
describe('ALL', function () { describe('ALL', function () {
it('should match ALL', function () { it('should match ALL', function (done) {
expect(matchSearchQuery({}, { matchSearchQuery({}, {
key: 'all', key: 'all',
value: true value: true
})).to.be.true; }, (err, match) => {
expect(err).to.not.exist;
expect(match).to.be.true;
done();
});
}); });
}); });
describe('FLAG', function () { describe('FLAG', function () {
it('should match existing flag', function () { it('should match existing flag', function (done) {
expect(matchSearchQuery({ matchSearchQuery({
flags: ['abc', 'def', 'ghi'] flags: ['abc', 'def', 'ghi']
}, { }, {
key: 'flag', key: 'flag',
value: 'def', value: 'def',
exists: true exists: true
})).to.be.true; }, (err, match) => {
expect(err).to.not.exist;
expect(match).to.be.true;
done();
});
}); });
it('should match non-existing flag', function () { it('should match non-existing flag', function (done) {
expect(matchSearchQuery({ matchSearchQuery({
flags: ['abc', 'def', 'ghi'] flags: ['abc', 'def', 'ghi']
}, { }, {
key: 'flag', key: 'flag',
value: 'zzzzz', value: 'zzzzz',
exists: false exists: false
})).to.be.true; }, (err, match) => {
expect(err).to.not.exist;
expect(match).to.be.true;
done();
});
}); });
it('should fail non-existing flag', function () { it('should fail non-existing flag', function (done) {
expect(matchSearchQuery({ matchSearchQuery({
flags: ['abc', 'def', 'ghi'] flags: ['abc', 'def', 'ghi']
}, { }, {
key: 'flag', key: 'flag',
value: 'zzzzz', value: 'zzzzz',
exists: true exists: true
})).to.be.false; }, (err, match) => {
expect(err).to.not.exist;
expect(match).to.be.false;
done();
});
}); });
it('should fail existing flag', function () { it('should fail existing flag', function (done) {
expect(matchSearchQuery({ matchSearchQuery({
flags: ['abc', 'def', 'ghi'] flags: ['abc', 'def', 'ghi']
}, { }, {
key: 'flag', key: 'flag',
value: 'abc', value: 'abc',
exists: false exists: false
})).to.be.false; }, (err, match) => {
expect(err).to.not.exist;
expect(match).to.be.false;
done();
});
}); });
}); });
describe('INTERNALDATE', function () { describe('INTERNALDATE', function () {
it('should match <', function () { it('should match <', function (done) {
expect(matchSearchQuery({ matchSearchQuery({
internaldate: new Date('1999-01-01') internaldate: new Date('1999-01-01')
}, { }, {
key: 'internaldate', key: 'internaldate',
value: '2001-01-01', value: '2001-01-01',
operator: '<' operator: '<'
})).to.be.true; }, (err, match) => {
expect(err).to.not.exist;
expect(match).to.be.true;
done();
});
}); });
it('should not match <', function () { it('should not match <', function (done) {
expect(matchSearchQuery({ matchSearchQuery({
internaldate: new Date('1999-01-01') internaldate: new Date('1999-01-01')
}, { }, {
key: 'internaldate', key: 'internaldate',
value: '1998-01-01', value: '1998-01-01',
operator: '<' operator: '<'
})).to.be.false; }, (err, match) => {
expect(err).to.not.exist;
expect(match).to.be.false;
done();
});
}); });
it('should match =', function () { it('should match =', function (done) {
expect(matchSearchQuery({ matchSearchQuery({
internaldate: new Date('1999-01-01') internaldate: new Date('1999-01-01')
}, { }, {
key: 'internaldate', key: 'internaldate',
value: '1999-01-01', value: '1999-01-01',
operator: '=' operator: '='
})).to.be.true; }, (err, match) => {
expect(err).to.not.exist;
expect(match).to.be.true;
done();
});
}); });
it('should not match <', function () { it('should not match <', function (done) {
expect(matchSearchQuery({ matchSearchQuery({
internaldate: new Date('1999-01-01') internaldate: new Date('1999-01-01')
}, { }, {
key: 'internaldate', key: 'internaldate',
value: '1999-01-02', value: '1999-01-02',
operator: '=' operator: '='
})).to.be.false; }, (err, match) => {
expect(err).to.not.exist;
expect(match).to.be.false;
done();
});
}); });
it('should match >=', function () { it('should match >=', function (done) {
expect(matchSearchQuery({ matchSearchQuery({
internaldate: new Date('1999-01-01') internaldate: new Date('1999-01-01')
}, { }, {
key: 'internaldate', key: 'internaldate',
value: '1999-01-01', value: '1999-01-01',
operator: '>=' operator: '>='
})).to.be.true; }, (err, match) => {
expect(err).to.not.exist;
expect(match).to.be.true;
expect(matchSearchQuery({ matchSearchQuery({
internaldate: new Date('1999-01-02') internaldate: new Date('1999-01-02')
}, { }, {
key: 'internaldate', key: 'internaldate',
value: '1999-01-01', value: '1999-01-01',
operator: '>=' operator: '>='
})).to.be.true; }, (err, match) => {
expect(err).to.not.exist;
expect(match).to.be.true;
done();
});
});
}); });
it('should not match >=', function () { it('should not match >=', function (done) {
expect(matchSearchQuery({ matchSearchQuery({
internaldate: new Date('1999-01-01') internaldate: new Date('1999-01-01')
}, { }, {
key: 'internaldate', key: 'internaldate',
value: '1999-01-02', value: '1999-01-02',
operator: '>=' operator: '>='
})).to.be.false; }, (err, match) => {
expect(err).to.not.exist;
expect(match).to.be.false;
done();
});
}); });
}); });
describe('DATE', function () { describe('DATE', function () {
let raw = 'Subject: test\r\nDate: 1999-01-01\r\n\r\nHello world!'; let raw = 'Subject: test\r\nDate: 1999-01-01\r\n\r\nHello world!';
it('should match <', function () { it('should match <', function (done) {
expect(matchSearchQuery({ matchSearchQuery({
raw mimeTree: indexer.parseMimeTree(raw)
}, { }, {
key: 'date', key: 'date',
value: '2001-01-01', value: '2001-01-01',
operator: '<' operator: '<'
})).to.be.true; }, (err, match) => {
expect(err).to.not.exist;
expect(match).to.be.true;
done();
});
}); });
it('should not match <', function () { it('should not match <', function (done) {
expect(matchSearchQuery({ matchSearchQuery({
raw mimeTree: indexer.parseMimeTree(raw)
}, { }, {
key: 'date', key: 'date',
value: '1998-01-01', value: '1998-01-01',
operator: '<' operator: '<'
})).to.be.false; }, (err, match) => {
expect(err).to.not.exist;
expect(match).to.be.false;
done();
});
}); });
it('should match =', function () { it('should match =', function (done) {
expect(matchSearchQuery({ matchSearchQuery({
raw mimeTree: indexer.parseMimeTree(raw)
}, { }, {
key: 'date', key: 'date',
value: '1999-01-01', value: '1999-01-01',
operator: '=' operator: '='
})).to.be.true; }, (err, match) => {
expect(err).to.not.exist;
expect(match).to.be.true;
done();
});
}); });
it('should not match <', function () { it('should not match <', function (done) {
expect(matchSearchQuery({ matchSearchQuery({
raw mimeTree: indexer.parseMimeTree(raw)
}, { }, {
key: 'date', key: 'date',
value: '1999-01-02', value: '1999-01-02',
operator: '=' operator: '='
})).to.be.false; }, (err, match) => {
expect(err).to.not.exist;
expect(match).to.be.false;
done();
});
}); });
it('should match >=', function () { it('should match >=', function (done) {
expect(matchSearchQuery({ matchSearchQuery({
raw mimeTree: indexer.parseMimeTree(raw)
}, { }, {
key: 'date', key: 'date',
value: '1999-01-01', value: '1999-01-01',
operator: '>=' operator: '>='
})).to.be.true; }, (err, match) => {
expect(err).to.not.exist;
expect(match).to.be.true;
expect(matchSearchQuery({
raw matchSearchQuery({
}, { mimeTree: indexer.parseMimeTree(raw)
key: 'date', }, {
value: '1998-01-01', key: 'date',
operator: '>=' value: '1998-01-01',
})).to.be.true; operator: '>='
}, (err, match) => {
expect(err).to.not.exist;
expect(match).to.be.true;
done();
});
});
}); });
it('should not match >=', function () { it('should not match >=', function (done) {
expect(matchSearchQuery({ matchSearchQuery({
raw mimeTree: indexer.parseMimeTree(raw)
}, { }, {
key: 'date', key: 'date',
value: '1999-01-02', value: '1999-01-02',
operator: '>=' operator: '>='
})).to.be.false; }, (err, match) => {
expect(err).to.not.exist;
expect(match).to.be.false;
done();
});
}); });
}); });
describe('BODY', function () { describe('BODY', function () {
let raw = 'Subject: test\r\n\r\nHello world!'; let raw = 'Subject: test\r\n\r\nHello world!';
it('should match a string', function () { it('should match a string', function (done) {
expect(matchSearchQuery({ matchSearchQuery({
raw mimeTree: indexer.parseMimeTree(raw)
}, { }, {
key: 'body', key: 'body',
value: 'hello' value: 'hello'
})).to.be.true; }, (err, match) => {
expect(err).to.not.exist;
expect(match).to.be.true;
done();
});
}); });
it('should not match a string', function () { it('should not match a string', function (done) {
expect(matchSearchQuery({ matchSearchQuery({
raw mimeTree: indexer.parseMimeTree(raw)
}, { }, {
key: 'body', key: 'body',
value: 'test' value: 'test'
})).to.be.false; }, (err, match) => {
expect(err).to.not.exist;
expect(match).to.be.false;
done();
});
}); });
}); });
describe('TEXT', function () { describe('TEXT', function () {
let raw = 'Subject: test\r\n\r\nHello world!'; let raw = 'Subject: test\r\n\r\nHello world!';
it('should match a string', function () { it('should match a string', function (done) {
expect(matchSearchQuery({ matchSearchQuery({
raw mimeTree: indexer.parseMimeTree(raw)
}, { }, {
key: 'text', key: 'text',
value: 'hello' value: 'hello'
})).to.be.true; }, (err, match) => {
expect(err).to.not.exist;
expect(match).to.be.true;
expect(matchSearchQuery({ matchSearchQuery({
raw mimeTree: indexer.parseMimeTree(raw)
}, { }, {
key: 'text', key: 'text',
value: 'test' value: 'test'
})).to.be.true; }, (err, match) => {
expect(err).to.not.exist;
expect(match).to.be.true;
done();
});
});
}); });
it('should not match a string', function () { it('should not match a string', function (done) {
expect(matchSearchQuery({ matchSearchQuery({
raw mimeTree: indexer.parseMimeTree(raw)
}, { }, {
key: 'text', key: 'text',
value: 'zzzzz' value: 'zzzzz'
})).to.be.false; }, (err, match) => {
expect(err).to.not.exist;
expect(match).to.be.false;
done();
});
}); });
}); });
describe('UID', function () { describe('UID', function () {
it('should match message uid', function () { it('should match message uid', function (done) {
expect(matchSearchQuery({ matchSearchQuery({
uid: 123 uid: 123
}, { }, {
key: 'uid', key: 'uid',
value: [11, 123, 134] value: [11, 123, 134]
})).to.be.true; }, (err, match) => {
expect(err).to.not.exist;
expect(match).to.be.true;
done();
});
}); });
it('should not match message uid', function () { it('should not match message uid', function (done) {
expect(matchSearchQuery({ matchSearchQuery({
uid: 124 uid: 124
}, { }, {
key: 'uid', key: 'uid',
value: [11, 123, 134] value: [11, 123, 134]
})).to.be.false; }, (err, match) => {
expect(err).to.not.exist;
expect(match).to.be.false;
done();
});
}); });
}); });
describe('SIZE', function () { describe('SIZE', function () {
it('should match <', function () { it('should match <', function (done) {
expect(matchSearchQuery({ matchSearchQuery({
raw: new Buffer(10) size: 10
}, { }, {
key: 'size', key: 'size',
value: 11, value: 11,
operator: '<' operator: '<'
})).to.be.true; }, (err, match) => {
expect(err).to.not.exist;
expect(match).to.be.true;
done();
});
}); });
it('should not match <', function () { it('should not match <', function (done) {
expect(matchSearchQuery({ matchSearchQuery({
raw: new Buffer(10) size: 10
}, { }, {
key: 'size', key: 'size',
value: 9, value: 9,
operator: '<' operator: '<'
})).to.be.false; }, (err, match) => {
expect(err).to.not.exist;
expect(match).to.be.false;
done();
});
}); });
it('should match =', function () { it('should match =', function (done) {
expect(matchSearchQuery({ matchSearchQuery({
raw: new Buffer(10) size: 10
}, { }, {
key: 'size', key: 'size',
value: 10, value: 10,
operator: '=' operator: '='
})).to.be.true; }, (err, match) => {
expect(err).to.not.exist;
expect(match).to.be.true;
done();
});
}); });
it('should not match =', function () { it('should not match =', function (done) {
expect(matchSearchQuery({ matchSearchQuery({
raw: new Buffer(10) size: 10
}, { }, {
key: 'size', key: 'size',
value: 11, value: 11,
operator: '=' operator: '='
})).to.be.false; }, (err, match) => {
expect(err).to.not.exist;
expect(match).to.be.false;
done();
});
}); });
it('should match >', function () { it('should match >', function (done) {
expect(matchSearchQuery({ matchSearchQuery({
raw: new Buffer(10) size: 10
}, { }, {
key: 'size', key: 'size',
value: 9, value: 9,
operator: '>' operator: '>'
})).to.be.true; }, (err, match) => {
expect(err).to.not.exist;
expect(match).to.be.true;
done();
});
}); });
it('should not match <', function () { it('should not match <', function (done) {
expect(matchSearchQuery({ matchSearchQuery({
raw: new Buffer(10) size: 10
}, { }, {
key: 'size', key: 'size',
value: 11, value: 11,
operator: '>' operator: '>'
})).to.be.false; }, (err, match) => {
expect(err).to.not.exist;
expect(match).to.be.false;
done();
});
}); });
}); });
describe('header', function () { describe('header', function () {
let raw = 'Subject: test\r\n\r\nHello world!'; let raw = 'Subject: test\r\n\r\nHello world!';
it('should match header value', function () { it('should match header value', function (done) {
expect(matchSearchQuery({ matchSearchQuery({
raw mimeTree: indexer.parseMimeTree(raw)
}, { }, {
key: 'header', key: 'header',
value: 'test', value: 'test',
header: 'subject' header: 'subject'
})).to.be.true; }, (err, match) => {
expect(err).to.not.exist;
expect(match).to.be.true;
done();
});
}); });
it('should match empty header value', function () { it('should match empty header value', function (done) {
expect(matchSearchQuery({ matchSearchQuery({
raw mimeTree: indexer.parseMimeTree(raw)
}, { }, {
key: 'header', key: 'header',
value: '', value: '',
header: 'subject' header: 'subject'
})).to.be.true; }, (err, match) => {
expect(err).to.not.exist;
expect(match).to.be.true;
done();
});
}); });
it('should not match header value', function () { it('should not match header value', function (done) {
expect(matchSearchQuery({ matchSearchQuery({
raw mimeTree: indexer.parseMimeTree(raw)
}, { }, {
key: 'header', key: 'header',
value: 'tests', value: 'tests',
header: 'subject' header: 'subject'
})).to.be.false; }, (err, match) => {
expect(err).to.not.exist;
expect(match).to.be.false;
done();
});
}); });
}); });
describe('MODSEQ', function () { describe('MODSEQ', function () {
it('should match equal modseq', function () { it('should match equal modseq', function (done) {
expect(matchSearchQuery({ matchSearchQuery({
modseq: 500 modseq: 500
}, { }, {
key: 'modseq', key: 'modseq',
value: [500] value: [500]
})).to.be.true; }, (err, match) => {
expect(err).to.not.exist;
expect(match).to.be.true;
done();
});
}); });
it('should match greater modseq', function () { it('should match greater modseq', function (done) {
expect(matchSearchQuery({ matchSearchQuery({
modseq: 1000 modseq: 1000
}, { }, {
key: 'modseq', key: 'modseq',
value: [500] value: [500]
})).to.be.true; }, (err, match) => {
expect(err).to.not.exist;
expect(match).to.be.true;
done();
});
}); });
it('should not match lesser modseq', function () { it('should not match lesser modseq', function (done) {
expect(matchSearchQuery({ matchSearchQuery({
modseq: 500 modseq: 500
}, { }, {
key: 'modseq', key: 'modseq',
value: [100] value: [100]
})).to.be.true; }, (err, match) => {
expect(err).to.not.exist;
expect(match).to.be.true;
done();
});
}); });
}); });
}); });

22
imap.js
View file

@ -1029,15 +1029,21 @@ server.onSearch = function (path, options, session, callback) {
message.raw = message.raw.toString(); message.raw = message.raw.toString();
} }
let match = session.matchSearchQuery(message, options.query); session.matchSearchQuery(message, options.query, (err, match) => {
if (match && highestModseq < message.modseq) { if (err) {
highestModseq = message.modseq; return cursor.close(() => callback(err));
} }
if (match) {
uidList.push(message.uid);
}
processNext(); if (match && highestModseq < message.modseq) {
highestModseq = message.modseq;
}
if (match) {
uidList.push(message.uid);
}
processNext();
});
}); });
}; };

View file

@ -22,7 +22,7 @@
"dependencies": { "dependencies": {
"addressparser": "^1.0.1", "addressparser": "^1.0.1",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"clone": "^2.1.0", "clone": "^2.1.1",
"config": "^1.25.1", "config": "^1.25.1",
"joi": "^10.2.2", "joi": "^10.2.2",
"libbase64": "^0.1.0", "libbase64": "^0.1.0",

1919
yarn.lock Normal file

File diff suppressed because it is too large Load diff