mailauth
Version:
Email authentication library for Node.js
179 lines (156 loc) • 5.12 kB
JavaScript
'use strict';
const parseReceived = buf => {
let header = (buf || '').toString();
let splitPos = header.indexOf(':');
if (splitPos < 0) {
return false;
}
let headerValue = header.substr(splitPos + 1).trim();
let state = 'none';
let values = [];
let expect = false;
let quoted = false;
let escaped = false;
let curKey;
let timestamp = '';
let commentLevel = 0;
let nextValue = () => {
curKey = '';
let val = { key: '', value: '', comment: '' };
values.push(val);
return val;
};
let curValue = nextValue();
for (let i = 0; i < headerValue.length; i++) {
let c = headerValue.charAt(i);
if (state === 'timestamp') {
timestamp += c;
continue;
}
if (escaped) {
curValue[curKey] += c;
escaped = false;
continue;
}
if (quoted) {
if (c === quoted) {
quoted = false;
state = 'none';
continue;
}
curValue[curKey] += c;
continue;
}
if (expect) {
if (c === expect) {
if (commentLevel) {
commentLevel--;
if (commentLevel) {
// still in nested comment
curValue[curKey] += c;
continue;
}
}
expect = false;
state = 'none';
curValue = nextValue();
continue;
}
if (c === '(') {
commentLevel++;
}
curValue[curKey] += c;
continue;
}
switch (c) {
case ' ':
case '\t':
case '\n':
case '\r':
state = 'none';
break;
case '"':
case "'":
// start quoting
quoted = c;
break;
case '(':
// start comment block
expect = ')';
commentLevel++;
curKey = 'comment';
break;
case ';':
state = 'timestamp';
break;
case '\\':
escaped = true;
break;
default:
if (state === 'none') {
state = 'val';
switch (curKey) {
case '':
curKey = 'key';
curValue[curKey] += c;
break;
case 'key':
curKey = 'value';
curValue[curKey] += c;
break;
case 'value':
case 'comment':
curValue = nextValue();
curKey = 'key';
curValue[curKey] += c;
break;
}
} else {
if (curKey === 'comment' && c === '(') {
commentLevel++;
}
curValue[curKey] += c;
}
}
}
timestamp = timestamp.split(';').shift().trim();
let result = {};
// join non key values into strings
for (let i = values.length - 1; i > 1; i--) {
let val = values[i];
let prev = values[i - 1];
let key = val.key.toLowerCase();
if (!['from', 'by', 'with', 'id', 'for', 'envelope-from', ''].includes(key) && prev.key) {
prev.value = [prev.value || []]
.concat(val.key || [])
.concat(val.value || [])
.join(' ');
prev.comment = [prev.comment || []].concat(val.comment || []).join(' ');
values.splice(i, 1);
}
}
for (let val of values) {
if (val.comment) {
val.comment = val.comment.replace(/\s+/g, ' ').trim();
}
if (val.key) {
let key = val.key.toLowerCase();
if (key !== 'from' && !result.tls && /tls|cipher=|Google Transport Security/i.test(val.comment)) {
result.tls = { value: '', ...(val.comment && { comment: val.comment }) };
val.comment = '';
}
result[key] = { value: val.value, ...(val.comment && { comment: val.comment }) };
} else if (!result.tls && /tls|cipher=|Google Transport Security/i.test(val.comment)) {
result.tls = { value: val.value, ...(val.comment && { comment: val.comment }) };
}
}
if (!result.tls && /SMTPS/.test(result?.with?.value)) {
result.tls = { value: '', comment: result?.with?.value };
}
if (timestamp) {
result.timestamp = timestamp;
}
result.full = (buf || '').toString().replace(/\s+/g, ' ').trim();
return result;
};
module.exports = { parseReceived };