mina-attestations
Version:
Private Attestations on Mina
262 lines • 9.08 kB
JavaScript
/**
* This file was copied and modified from zk-email-verify, which copied and modified from mailauth:
* https://github.com/postalsys/mailauth
*/
// NB! fails to properly parse nested comments (should be rare enough though)
export { parseDkimHeaders };
function parseValue(str) {
let line = str.replace(/\s+/g, ' ').trim();
let parts = [];
let lastState = false;
const createPart = () => {
let part = {
key: '',
value: '',
};
parts.push(part);
return part;
};
const parse = () => {
let state = 'key';
let escaped;
let quote;
let curPart = createPart();
for (let i = 0; i < line.length; i++) {
let c = line.charAt(i);
switch (state) {
// @ts-ignore
case 'key':
if (c === '=') {
state = 'value';
break;
}
// falls through
case 'value': {
if (escaped === true) {
curPart[state] += c;
break;
}
switch (c) {
case ' ':
// start new part
curPart = createPart();
state = 'key';
break;
case '\\':
escaped = true;
break;
case '"':
case "'":
lastState = state;
state = 'quoted';
quote = c;
break;
default:
curPart[state] += c;
break;
}
break;
}
case 'quoted':
if (escaped === true && typeof lastState === 'string') {
curPart[lastState] += c;
break;
}
switch (c) {
case '\\':
escaped = true;
break;
case quote:
state = lastState;
break;
default:
if (typeof lastState === 'string') {
curPart[lastState] += c;
}
break;
}
break;
}
}
let result = {
value: parts[0].key,
};
parts.slice(1).forEach((part) => {
if (part.key || part.value) {
let path = part.key.split('.');
let curRes = result;
let final = path.pop();
for (let p of path) {
if (typeof curRes[p] !== 'object' || !curRes[p]) {
curRes[p] = {};
}
curRes = curRes[p];
}
curRes[final ?? ''] = part.value;
}
});
return result;
};
return parse();
}
function parseDkimHeaders(buf) {
let line = (buf || '').toString().trim();
let splitterPos = line.indexOf(':');
let headerKey;
if (splitterPos >= 0) {
headerKey = line.substr(0, splitterPos).trim().toLowerCase();
line = line.substr(splitterPos + 1).trim();
}
let parts = [];
let lastState = false;
const createPart = () => {
let part = {
key: '',
value: '',
comment: '',
hasValue: false,
};
parts.push(part);
return part;
};
const parse = () => {
let state = 'key';
let escaped;
let quote;
let curPart = createPart();
for (let i = 0; i < line.length; i++) {
let c = line.charAt(i);
switch (state) {
// @ts-ignore
case 'key':
if (c === '=') {
state = 'value';
curPart.hasValue = true;
break;
}
// falls through
case 'value': {
if (escaped === true) {
curPart[state] += c;
}
switch (c) {
case ';':
// start new part
curPart = createPart();
state = 'key';
break;
case '\\':
escaped = true;
break;
case '(':
lastState = state;
state = 'comment';
break;
case '"':
case "'":
lastState = state;
curPart[state] += c;
state = 'quoted';
quote = c;
break;
default:
curPart[state] += c;
break;
}
break;
}
case 'comment':
switch (c) {
case '\\':
escaped = true;
break;
case ')':
state = lastState;
break;
default:
curPart[state] += c;
break;
}
break;
case 'quoted':
switch (c) {
case '\\':
escaped = true;
break;
// @ts-ignore
case quote:
state = lastState;
// falls through
default:
if (typeof lastState === 'string') {
curPart[lastState] += c;
}
break;
}
break;
}
}
for (let i = parts.length - 1; i >= 0; i--) {
for (let key of Object.keys(parts[i])) {
if (typeof parts[i][key] === 'string') {
parts[i][key] = parts[i][key].replace(/\s+/g, ' ').trim();
}
}
parts[i].key = parts[i].key.toLowerCase();
if (!parts[i].key) {
// remove empty value
parts.splice(i, 1);
}
else if (['bh', 'b', 'p', 'h'].includes(parts[i].key)) {
// remove unneeded whitespace
parts[i].value = parts[i].value?.replace(/\s+/g, '');
}
else if (['l', 'v', 't'].includes(parts[i].key) &&
!isNaN(parts[i].value)) {
parts[i].value = Number(parts[i].value);
}
else if (parts[i].key === 'i' && /^arc-/i.test(headerKey)) {
parts[i].value = Number(parts[i].value);
}
}
let result = {
header: headerKey,
};
for (let i = 0; i < parts.length; i++) {
// find the first entry with key only and use it as the default value
if (parts[i].key && !parts[i].hasValue) {
result.value = parts[i].key;
parts.splice(i, 1);
break;
}
}
parts.forEach((part) => {
let entry = {
value: part.value,
};
if (['arc-authentication-results', 'authentication-results'].includes(headerKey) &&
typeof part.value === 'string') {
// parse value into subparts as well
entry = Object.assign(entry, parseValue(entry.value));
}
if (part.comment) {
entry.comment = part.comment;
}
if (['arc-authentication-results', 'authentication-results'].includes(headerKey) &&
part.key === 'dkim') {
if (!result[part.key]) {
result[part.key] = [];
}
if (Array.isArray(result[part.key])) {
result[part.key].push(entry);
}
}
else {
result[part.key] = entry;
}
});
return result;
};
return { parsed: parse(), original: buf };
}
//# sourceMappingURL=parse-dkim-headers.js.map