Use header parser from libmime, removed local implementation

This commit is contained in:
Andris Reinman 2023-05-15 15:07:21 +03:00
parent 83b7353f3f
commit e983eae623
No known key found for this signature in database
GPG key ID: DC6C83F4D584D364
3 changed files with 254 additions and 55 deletions

View file

@ -1,6 +1,7 @@
'use strict';
const addressparser = require('nodemailer/lib/addressparser');
const libmime = require('libmime');
/**
* Parses a RFC822 message into a structured object (JSON compatible)
@ -254,61 +255,19 @@ class MIMEParser {
* @return {Object} Parsed value
*/
parseValueParams(headerValue) {
let data = {
value: '',
type: '',
subtype: '',
params: {}
let parsed = libmime.parseHeaderValue(headerValue) || {};
let subtype = (parsed.value || '').split('/');
let type = (subtype.shift() || '').toLowerCase();
subtype = subtype.join('/');
return {
value: parsed.value || '',
type,
subtype,
params: parsed.params || {},
hasParams: !!Object.keys(parsed.params || {}).length
};
let match;
let processEncodedWords = {};
(headerValue || '').split(';').forEach((part, i) => {
let key, value;
if (!i) {
data.value = part.trim();
data.subtype = data.value.split('/');
data.type = (data.subtype.shift() || '').toLowerCase();
data.subtype = data.subtype.join('/');
return;
}
value = part.split('=');
key = (value.shift() || '').trim().toLowerCase();
value = value.join('=').replace(/^['"\s]*|['"\s]*$/g, '');
// Do not touch headers that have strange looking keys, keep these
// only in the unparsed array
if (/[^a-zA-Z0-9\-*]/.test(key) || key.length >= 100) {
return;
}
// This regex allows for an optional trailing asterisk, for headers
// which are encoded with lang/charset info as well as a continuation.
// See https://tools.ietf.org/html/rfc2231 section 4.1.
if ((match = key.match(/^([^*]+)\*(\d)?\*?$/))) {
if (!processEncodedWords[match[1]]) {
processEncodedWords[match[1]] = [];
}
processEncodedWords[match[1]][Number(match[2]) || 0] = value;
} else {
data.params[key] = value;
}
data.hasParams = true;
});
// convert extended mime word into a regular one
Object.keys(processEncodedWords).forEach(key => {
let charset = '';
let value = '';
processEncodedWords[key].forEach(val => {
let parts = val.split("'"); // eslint-disable-line quotes
charset = charset || parts.shift();
value += (parts.pop() || '').replace(/%/g, '=');
});
data.params[key] = '=?' + (charset || 'ISO-8859-1').toUpperCase() + '?Q?' + value + '?=';
});
return data;
}
/**
@ -337,4 +296,6 @@ function parse(rfc822) {
return response;
}
parse.MIMEParser = MIMEParser;
module.exports = parse;

View file

@ -0,0 +1,33 @@
/*eslint no-unused-expressions: 0, prefer-arrow-callback: 0 */
'use strict';
const MIMEParser = require('../lib/indexer/parse-mime-tree').MIMEParser;
const chai = require('chai');
const expect = chai.expect;
chai.config.includeStack = true;
describe('#parseValueParams', function () {
it.only('should return as is', function () {
let parser = new MIMEParser();
const parsed = parser.parseValueParams(
'text/plain;\n' +
'\tname*0=emailengine_uuendamise_kasud_ja_muud_asjad_ja_veelgi_pikem_pealk;\n' +
'\tname*1=iri.txt;\n' +
'\tx-apple-part-url=99AFDE83-8953-43B4-BE59-F59D6160AFAB'
);
console.log('PARSED', parsed);
expect(parsed).to.equal({
value: 'text/plain',
type: 'text',
subtype: 'plain',
params: {
'x-apple-part-url': '99AFDE83-8953-43B4-BE59-F59D6160AFAB',
name: 'emailengine_uuendamise_kasud_ja_muud_asjad_ja_veelgi_pikem_pealkiri.txt'
},
hasParams: true
});
});
});

View file

@ -314,8 +314,213 @@ const getMongoDBQuery = async (db, user, queryStr) => {
return { user: false };
};
/*
const getElasticSearchQuery = async (db, user, queryStr) => {
const parsed = parseSearchQuery(queryStr);
module.exports = { parseSearchQuery, getMongoDBQuery };
let searchQuery = {
bool: {
must: [
{
term: {
user: (user || '').toString().trim()
}
}
]
}
};
let curNode = searchQuery;
let walkTree = async (node, curNode) => {
if (Array.isArray(node)) {
let branches = [];
for (let entry of node) {
branches.push(await walkTree(entry));
}
return branches;
}
if (node.$and && node.$and.length) {
let branch = {
bool: { must: [] }
};
for (let entry of node.$and) {
let subBranch = await walkTree(entry);
branch.bool.must = branch.bool.must.concat(subBranch || []);
}
return branch;
} else if (node.$or && node.$or.length) {
let branch = {
bool: { should: [], minimum_should_match: 1 }
};
for (let entry of node.$or) {
let subBranch = await walkTree(entry);
branch.bool.should = branch.bool.should.concat(subBranch || []);
}
return branch;
} else if (node.text) {
let branch = {
bool: {
should: [
{
match: {
'text.plain': {
query: node.text.value,
operator: 'and'
}
}
},
{
match: {
'text.html': {
query: node.text.value,
operator: 'and'
}
}
}
],
minimum_should_match: 1
}
};
if (node.text.negated) {
// FIXME: negation support!
}
return branch;
} else if (node.keywords) {
let branches = [];
let keyword = Object.keys(node.keywords || {}).find(key => key && key !== 'negated');
if (keyword) {
let { value, negated } = node.keywords[keyword];
switch (keyword) {
case 'from':
case 'subject':
{
let regex = escapeRegexStr(value);
let branch = {
headers: {
$elemMatch: {
key: keyword,
value: {
$regex: regex,
$options: 'i'
}
}
}
};
if (negated) {
branch = { $not: branch };
}
branches.push(branch);
}
break;
case 'to':
{
let regex = escapeRegexStr(value);
for (let toKey of ['to', 'cc', 'bcc']) {
let branch = {
headers: {
$elemMatch: {
key: toKey,
value: {
$regex: regex,
$options: 'i'
}
}
}
};
if (negated) {
branch = { $not: branch };
}
branches.push(branch);
}
}
break;
case 'in': {
value = (value || '').toString().trim();
let resolveQuery = { user, $or: [] };
if (/^[0-9a-f]{24}$/i.test(value)) {
resolveQuery.$or.push({ _id: new ObjectId(value) });
} else if (/^Inbox$/i.test(value)) {
resolveQuery.$or.push({ path: 'INBOX' });
} else {
resolveQuery.$or.push({ path: value });
if (/^\/?(spam|junk)/i.test(value)) {
resolveQuery.$or.push({ specialUse: '\\Junk' });
} else if (/^\/?(sent)/i.test(value)) {
resolveQuery.$or.push({ specialUse: '\\Sent' });
} else if (/^\/?(trash|deleted)/i.test(value)) {
resolveQuery.$or.push({ specialUse: '\\Trash' });
} else if (/^\/?(drafts)/i.test(value)) {
resolveQuery.$or.push({ specialUse: '\\Drafts' });
}
}
let mailboxEntry = await db.database.collection('mailboxes').findOne(resolveQuery, { project: { _id: -1 } });
let branch = { mailbox: mailboxEntry ? mailboxEntry._id : new ObjectId('0'.repeat(24)) };
if (negated) {
branch = { $not: branch };
}
branches.push(branch);
break;
}
case 'thread':
{
value = (value || '').toString().trim();
if (/^[0-9a-f]{24}$/i.test(value)) {
let branch = { thread: new ObjectId(value) };
if (negated) {
branch = { $not: branch };
}
branches.push(branch);
}
}
break;
case 'has': {
switch (value) {
case 'attachment': {
branches.push({ ha: true });
break;
}
}
}
}
}
return branches;
}
};
if (parsed && parsed.length) {
let filter = await walkTree(Array.isArray(parsed) ? { $and: parsed } : parsed);
let extras = { user };
if (hasTextFilter) {
extras.searchable = true;
}
return Object.assign({ user: null }, filter, extras);
}
return { user: false };
};
*/
module.exports = { parseSearchQuery, getMongoDBQuery /*, getElasticSearchQuery*/ };
/*
const util = require('util');