andrewpietiladotcom/mimetest.js

442 lines
No EOL
13 KiB
JavaScript

import fs from 'node:fs';
const validMime = fs.readFileSync("/home/rallias/Dev/andrewpietiladotcom/messageStore/1.eml", "utf-8");
// TODO: Does this actually need to throw internally?
class ABNFNoMatchError extends Error {}
class ABNFResult {
constructor(value, subTokens, type, startChar, endChar) {
this._value = value;
this._subTokens = subTokens;
this._type = type;
this._startChar = startChar;
this._endChar = endChar;
}
}
function parseCRLF(chars, ptr) {
// RFC 5234 B.1
// CRLF = CR LF
// ; Internet standard newline
if ( chars[ptr] === String.fromCharCode(0x0D) && chars[ptr+1] === String.fromCharCode(0x0A) ) {
return new ABNFResult(chars[ptr]+chars[ptr+1], null, "CRLF", ptr, ptr+1);
} else {
throw new ABNFNoMatchError();
}
}
function parseWSP(chars, ptr) {
// RFC 5234 B.1
// WSP = SP / HTAB
// ; white space
if ( chars[ptr] === String.fromCharCode(0x20) ) {
return new ABNFResult(chars[ptr], null, "WSP", ptr, ptr);
} else if ( chars[ptr] === String.fromCharCode(0x09)) {
return new ABNFResult(chars[ptr], null, "WSP", ptr, ptr);
} else {
throw new ABNFNoMatchError();
}
}
function parseQuotedPair(chars, ptr) {
// RFC 5322 3.2.1
// quoted-pair = ("\" (VCHAR / WSP)) / obs-qp
// TODO: Handle obs-qp
if ( chars[ptr] === "\\" && chars[ptr+1] === "\"" ) {
return new ABNFResult("\\\"", null, "quoted-pair", ptr, ptr+1);
} else {
throw new ABNFNoMatchError();
}
}
function parseFWS(chars, ptr) {
// RFC 5322 3.2.2
// FWS = ([*WSP CRLF] 1*WSP) / obs-FWS
// ; Folding white space
// TODO: Handle obs-FWS
var startChar = ptr;
var value = "";
var subTokens = [];
var inFWS = true;
while(inFWS) {
try {
var wsp = parseWSP(chars, ptr);
subTokens.push(wsp);
value += wsp._value;
ptr = wsp._endChar + 1;
} catch(e) {
try {
var crlf = parseCRLF(chars, ptr);
subTokens.push(crlf);
value += crlf._value;
ptr = crlf._endChar + 1;
} catch(e) {
inFWS = false;
}
}
if ( inFWS && subTokens.length > 1 && !(subTokens[subTokens.length-1]._type === "CRLF" && subTokens[subTokens.length-2]._type === "CRLF")) {
throw new ABNFNoMatchError();
}
}
if ( subTokens[subTokens.length-1]._type === "CRLF" ) {
throw new ABNFNoMatchError();
}
return new ABNFResult(value, subTokens, "FWS", startChar, ptr);
}
const ctextChars = "!\"#$%&'*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~".split("");
function parseCtext(chars, ptr) {
// RFC 5322 3.2.2
// ctext = %d33-39 / ; Printable US-ASCII
// %d42-91 / ; characters not including
// %d93-126 / ; "(", ")", or "\"
// obs-ctext
// TODO: handle obs-ctext
if ( ctextChars.includes(chars[ptr]) ) {
return new ABNFResult(chars[ptr], null, "ctext", ptr, ptr);
} else {
throw new ABNFNoMatchError();
}
}
// Help the typescript parser understand heads from tails.
/**
* @param {String[]} chars
* @param {Number} ptr
* @returns {ABNFResult}
* @throws {ABNFNoMatchError}
*/
function parseCcontent(chars, ptr) {
// RFC 5322 3.2.2
// ccontent = ctext / quoted-pair / comment
try {
var ctext = parseCtext(chars, ptr);
return new ABNFResult(ctext._value, ctext, "ccontent", ctext._startChar, ctext._endChar);
} catch(e) {
try {
var quotedPair = parseQuotedPair(chars, ptr);
return new ABNFResult(quotedPair._value, quotedPair, "ccontent", quotedPair._startChar, quotedPair._endChar);
} catch(e) {
try {
var comment = parseComment(chars, ptr);
return new ABNFResult(comment._value, comment, "ccontent", comment._startChar, comment._endChar);
} catch(e) {
}
}
}
throw new ABNFNoMatchError();
}
function parseComment(chars, ptr) {
// RFC 5322 3.2.2
// comment = "(" *([FWS] ccontent) [FWS] ")"
var startChar = ptr;
var value = "";
var subTokens = [];
if ( chars[ptr] === "(" ) {
value += chars[ptr];
ptr++;
var inLoop = true;
while(inLoop) {
try {
var fws = parseFWS(chars, ptr);
subTokens.push(fws);
value += fws._value;
ptr = fws._endChar + 1;
} catch(e) {
try {
var ccontent = parseCcontent(chars, ptr);
subTokens.push(ccontent);
value += ccontent._value;
ptr = ccontent._endChar + 1;
} catch(e) {
inLoop = false;
}
}
}
try {
var fws = parseFWS(chars, ptr);
subTokens.push(fws);
value += fws._value;
ptr = fws._endChar + 1;
} catch(e) {
// FWS is optional.
}
if ( chars[ptr] === ")" ) {
value += chars[ptr];
return new ABNFResult(value, subTokens, "comment", startChar, ptr);
}
}
throw new ABNFNoMatchError();
}
function parseCFWS(chars, ptr) {
// RFC 5322 3.2.2
// CFWS = (1*([FWS] comment) [FWS]) / FWS
var startChar = ptr;
var value = "";
var subTokens = [];
var inCFWS = true;
while(inCFWS) {
try {
var fws = parseFWS(chars, ptr);
subTokens.push(fws);
value += fws._value;
ptr = fws._endChar + 1;
} catch(e) {
try {
var comment = parseComment(chars, ptr);
subTokens.push(comment);
value += comment._value;
ptr = comment._endChar + 1;
} catch(e) {
inCFWS = false;
}
}
}
if ( subTokens.length >= 1 ) {
return new ABNFResult(value, subTokens, "CFWS", startChar, ptr);
}
throw new ABNFNoMatchError();
}
const atextChars = "ABCDEFGHIJKLMNOPQRSTUVWXYabcdefghijklmnopqrstuvwxy012345678!#$%&'*+-/=?^_`{|}~".split('');
function parseAtext(chars, ptr) {
// RFC 5322 3.2.3
// atext = ALPHA / DIGIT / ; Printable US-ASCII
// "!" / "#" / ; characters not including
// "$" / "%" / ; specials. Used for atoms.
// "&" / "'" /
// "*" / "+" /
// "-" / "/" /
// "=" / "?" /
// "^" / "_" /
// "`" / "{" /
// "|" / "}" /
// "~"
if ( atextChars.includes(chars[ptr]) ) {
return new ABNFResult(chars[ptr], null, "atext", ptr, ptr);
} else {
throw new ABNFNoMatchError();
}
}
function parseAtom(chars, ptr) {
// RFC 5322 3.2.3
// atom = [CFWS] 1*atext [CFWS]
var startChar = ptr;
var value = "";
var subTokens = [];
var inAtom = true;
try {
var cfws = parseCFWS(chars, ptr);
subTokens.push(cfws);
value += cfws._value;
ptr = cfws._endChar + 1;
} catch(e) {
// Optional.
}
while (inAtom) {
try {
var atext = parseAtext(chars, ptr);
subTokens.push(atext);
value += atext._value;
ptr = atext._endChar + 1;
} catch(e) {
inAtom = false;
}
}
if ( subTokens[subTokens.length-1]._type != "atext" ) {
throw new ABNFNoMatchError();
}
try {
var cfws = parseCFWS(chars, ptr);
subTokens.push(cfws);
value += cfws._value;
ptr = cfws._endChar + 1;
} catch(e) {
// Optional.
}
return new ABNFResult(value, subTokens, "atom", startChar, ptr);
}
function parseDotAtomText(chars, ptr) {
// RFC 5322 3.2.3
// dot-atom-text = 1*atext *("." 1*atext)
var startChar = ptr;
var value = "";
var subTokens = [];
var inDAT = true;
while (inDAT) {
try {
var atext = parseAtext(chars, ptr);
subTokens.push(atext);
value += atext._value;
ptr = atext._endChar + 1;
} catch(e) {
// We fish later.
}
if ( chars[ptr] === "." && value === "" ) {
throw new ABNFNoMatchError();
}
if ( chars[ptr] === "." ) {
value += ".";
ptr++;
}
}
if ( value.endsWith(".") ) {
throw new ABNFNoMatchError();
}
// TODO: Do we care with granularity about the dots and their positions?
return new ABNFResult(value, subTokens, "dot-atom-text", startChar, ptr);
}
function parseDotAtom(chars, ptr) {
// RFC 5322 3.2.3
// dot-atom = [CFWS] dot-atom-text [CFWS]
var startChar = ptr;
var value = "";
var subTokens = [];
try {
var cfws = parseCFWS(chars, ptr);
subTokens.push(cfws);
value += cfws._value;
ptr = cfws._endChar + 1;
} catch(e) {
}
try {
var dat = parseDotAtomText(chars, ptr);
subTokens.push(dat);
value += dat._value;
ptr = dat._endChar + 1;
} catch(e) {
throw new ABNFNoMatchError();
}
try {
const cfws = parseCFWS(chars, ptr);
subTokens.push(cfws);
value += cfws._value;
ptr = cfws._endChar + 1;
} catch(e) {
}
return new ABNFResult(value, subTokens, "dot-atom", startChar, ptr);
}
const qtextChars = "!#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~".split('');
function parseQtext(chars, ptr) {
// RFC 5322 3.2.4
// qtext = %d33 / ; Printable US-ASCII
// %d35-91 / ; characters not including
// %d93-126 / ; "\" or the quote character
// obs-qtext
if ( qtextChars.includes(chars[ptr]) ) {
return new ABNFResult(chars[ptr], null, "qtext", ptr, ptr);
} else {
throw new ABNFNoMatchError();
}
}
function parseQcontent(chars, ptr) {
// RFC 5322 3.2.4
try {
const qtext = parseQtext(chars, ptr);
return new ABNFResult(qtext._value, qtext, "qcontent", qtext._startChar, qtext._endChar);
} catch(e) {
try {
const quotedPair = parseQuotedPair(chars, ptr);
return new ABNFResult(quotedPair._value, quotedPair, "qcontent", quotedPair._startChar, quotedPair._endChar);
} catch(e) {
throw new ABNFNoMatchError();
}
}
}
function parseDQUOTE(chars, ptr) {
// RFC 5234 B.1
// DQUOTE = %x22
// ; " (Double Quote)
if ( chars[ptr] === String.fromCharCode(0x22) ) {
return new ABNFResult(chars[ptr], null, "DQUOTE", ptr, ptr);
} else {
throw new ABNFNoMatchError();
}
}
function parseQuotedString(chars, ptr) {
// RFC 5322 3.2.4
// quoted-string = [CFWS]
// DQUOTE *([FWS] qcontent) [FWS] DQUOTE
// [CFWS]
var startChar = ptr;
var value = "";
var subTokens = [];
try {
const cfws = parseCFWS(chars, ptr);
subTokens.push(cfws);
value += cfws._value;
ptr = cfws._endChar + 1;
} catch(e) {
}
try {
const dquote = parseDQUOTE(chars, ptr);
subTokens.push(dquote);
value += dquote._value;
ptr = dquote._endChar + 1;
} catch(e) {
throw new ABNFNoMatchError();
}
var inDquot = true;
while (inDquot) {
try {
const fws = parseFWS(chars, ptr);
subTokens.push(fws);
value += fws._value;
ptr = fws._endChar + 1;
} catch(e) {
try {
const qcontent = parseQcontent(chars, ptr);
subTokens.push(qcontent);
value += qcontent._value;
ptr = qcontent._endChar + 1;
} catch(e) {
inDquot = false;
}
}
}
try {
const dquote = parseDQUOTE(chars, ptr);
subTokens.push(dquote);
value += dquote._value;
ptr = dquote._endChar + 1;
} catch(e) {
throw new ABNFNoMatchError();
}
try {
const cfws = parseCFWS(chars, ptr);
subTokens.push(cfws);
value += cfws._value;
ptr = cfws._endChar + 1;
} catch(e) {
}
return new ABNFResult(value, subTokens, "quoted-string", startChar, ptr);
}