Progress. I've decided to properly handle it by the ABNF book, so we're rewriting the world.
This commit is contained in:
parent
c93c2fa8c8
commit
c774cb1db5
6 changed files with 653 additions and 9 deletions
167
lib/FromMimeHeader.js
Normal file
167
lib/FromMimeHeader.js
Normal file
|
@ -0,0 +1,167 @@
|
|||
import MimeHeader from "./MimeHeader.js";
|
||||
|
||||
const atext = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%&'*+-/=?^_`{|}~".split('');
|
||||
const dtext = "!\"#$%^&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ^_`abcdefghijklmnopqrstuvwxyz{|}~".split('');
|
||||
const qtext = "!#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~".split('');
|
||||
|
||||
class FromMimeHeader extends MimeHeader {
|
||||
constructor(key, value) {
|
||||
super(key, value);
|
||||
this._fromValue = [];
|
||||
}
|
||||
// From: Andrew Pietila <a.pietila@protonmail.com>
|
||||
get fromValue() {
|
||||
// ABNF:
|
||||
// from = "From:" mailbox-list CRLF
|
||||
// mailbox-list = (mailbox *("," mailbox)) / obs-mbox-list
|
||||
// mailbox = name-addr / addr-spec
|
||||
// name-addr = [display-name] angle-addr
|
||||
// display-name = phrase
|
||||
// phrase = 1*word / obs-phrase
|
||||
// word = atom / quoted-string
|
||||
// atom = [CFWS] 1*atext [CFWS]
|
||||
// addr-spec = local-part "@" domain
|
||||
// local-part = dot-atom / quoted-string / obs-local-part
|
||||
// domain = dot-atom / domain-literal / obs-domain
|
||||
// domain-literal = [CFWS] "[" *([FWS] dtext) [FWS] "]" [CFWS]
|
||||
// dtext = %d33-90 / ; Printable US-ASCII
|
||||
// %d94-126 / ; characters not including
|
||||
// obs-dtext ; "[", "]", or "\"
|
||||
// dot-atom-text = 1*atext *("." 1*atext)
|
||||
// dot-atom = [CFWS] dot-atom-text [CFWS]
|
||||
// quoted-string = [CFWS]
|
||||
// DQUOTE *([FWS] qcontent) [FWS] DQUOTE
|
||||
// [CFWS]
|
||||
// qcontent = qtext / quoted-pair
|
||||
// qtext = %d33 / ; Printable US-ASCII
|
||||
// %d35-91 / ; characters not including
|
||||
// %d93-126 / ; "\" or the quote character
|
||||
// obs-qtext
|
||||
// TODO: Implement utf-8 encoding and whatever other encodings we have to support.
|
||||
if ( this._fromValue.length !== 0 ) {
|
||||
return this._fromValue;
|
||||
}
|
||||
// TODO: Implement this state machine properly according to the above ABNF.
|
||||
var from = [];
|
||||
var state = "";
|
||||
var inCRLF = false;
|
||||
var commentDepth = 0;
|
||||
var displayName = "";
|
||||
var inDquot = false;
|
||||
var idLeft = "";
|
||||
var idRight = "";
|
||||
var idRightDatEmittedRightSquacket = false;
|
||||
for (var char of this.rawValue) {
|
||||
if ( inCRLF === true && char !== "\n" ) {
|
||||
return null; // Error state.
|
||||
}
|
||||
if (state === "") {
|
||||
// CFWS
|
||||
if ( char === " ") {
|
||||
continue;
|
||||
} else if ( char === "\t") {
|
||||
continue;
|
||||
} else if ( char === "(" && !inDquot ) {
|
||||
commentDepth++;
|
||||
} else if ( char === "\"" ) {
|
||||
inDquot = !inDquot;
|
||||
} else if ( char === ")" && !inDquot ) {
|
||||
commentDepth--;
|
||||
} else if ( char === "\r" ) {
|
||||
inCRLF = true;
|
||||
} else if ( char === "\n" ) {
|
||||
inCRLF = false;
|
||||
} else {
|
||||
state = "display-name";
|
||||
}
|
||||
} else if ( state === "display-name") {
|
||||
if ( char === " " ) {
|
||||
if ( state.endsWith(" ") ) {
|
||||
continue;
|
||||
} else {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
// else if ( char === "<" ) {
|
||||
// state = "id-left";
|
||||
// } else {
|
||||
// return null; // Error state, we couldn't produce a proper message ID from the input.
|
||||
// }
|
||||
// } else if ( state === "id-left" ) {
|
||||
// if ( atext.includes(char) ) {
|
||||
// idLeft += char;
|
||||
// } else if ( char === "." ) {
|
||||
// if ( idLeft.endsWith(".") || idLeft.length === 0 ) {
|
||||
// return null; // Error state.
|
||||
// }
|
||||
// idLeft += char;
|
||||
// } else if ( char === "@" ) {
|
||||
// if ( idLeft.endsWith(".") || idLeft.length === 0) {
|
||||
// return null; // Error state.
|
||||
// }
|
||||
// state = "id-right";
|
||||
// }
|
||||
// } else if ( state === "id-right" ) {
|
||||
// if ( char === "[" ) {
|
||||
// if ( idRight.length !== 0 ) {
|
||||
// return null; // Error state.
|
||||
// }
|
||||
// idRight += char;
|
||||
// state = "id-right-dat";
|
||||
// } else if ( char === "." ) {
|
||||
// if ( idRight.endsWith(".") || idRight.length === 0 ) {
|
||||
// return null; // Error state.
|
||||
// }
|
||||
// idRight += char;
|
||||
// } else if ( char === ">" ) {
|
||||
// if ( idRight.endsWith(".") || idRight.length === 0) {
|
||||
// return null; // Error state.
|
||||
// }
|
||||
// state = "end-cfws"
|
||||
// }
|
||||
// } else if ( state === "id-right-dat" ) {
|
||||
// if ( char === ">" && idRight.endsWith("]") ) {
|
||||
// state = "end-cfws";
|
||||
// continue;
|
||||
// }
|
||||
// if ( dtext.includes(char) ) {
|
||||
// if ( idRightDatEmittedRightSquacket ) {
|
||||
// return null; // Error state.
|
||||
// }
|
||||
// idRight += char;
|
||||
// } else if ( char === "]" ) {
|
||||
// if ( idRightDatEmittedRightSquacket ) {
|
||||
// return null; // Error state.
|
||||
// }
|
||||
// idRight += char;
|
||||
// idRightDatEmittedRightSquacket = true;
|
||||
// }
|
||||
// } else if ( state === "end-cfws" ) {
|
||||
// // CFWS
|
||||
// if ( char === " ") {
|
||||
// continue;
|
||||
// } else if ( char === "\t") {
|
||||
// continue;
|
||||
// } else if ( char === "(" && !inDquot ) {
|
||||
// commentDepth++;
|
||||
// } else if ( char === "\"" ) {
|
||||
// inDquot = !inDquot;
|
||||
// } else if ( char === ")" && !inDquot ) {
|
||||
// commentDepth--;
|
||||
// } else if ( char === "\r" ) {
|
||||
// inCRLF = true;
|
||||
// } else if ( char === "\n" ) {
|
||||
// inCRLF = false;
|
||||
// } else {
|
||||
// return null; // Error state.
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
this._fromValue = from;
|
||||
return `${idLeft}@${idRight}`;
|
||||
}
|
||||
}
|
||||
|
||||
export default FromMimeHeader;
|
|
@ -6,7 +6,7 @@ const dtext = "!\"#$%^&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ^_`ab
|
|||
class InReplyToMimeHeader extends MimeHeader {
|
||||
constructor(key, value) {
|
||||
super(key, value);
|
||||
this._messageIdValue = "";
|
||||
this._inReplyToValue = [];
|
||||
}
|
||||
// Message-ID: <0Q27kpOFnz1MK3ASM_vbjI8L_BYN3j5OSxIaGxerST74zBn7VZfqCowbhEWgJ0Yv4mV847u25YGjENqlsM7-15Zdus90qN7t_kj55FyuN_c=@protonmail.com>
|
||||
get inReplyToValues() {
|
||||
|
@ -48,8 +48,8 @@ class InReplyToMimeHeader extends MimeHeader {
|
|||
// %d14-31 / ; return, line feed, and
|
||||
// %d127 ; white space characters
|
||||
// TODO: Implement utf-8 encoding and whatever other encodings we have to support.
|
||||
if ( this._messageIdValue !== undefined ) {
|
||||
return this._messageIdValue;
|
||||
if ( this._inReplyToValue.length !== 0 ) {
|
||||
return this._inReplyToValue;
|
||||
}
|
||||
// TODO: Implement this state machine properly according to the above ABNF.
|
||||
var inReplyTo = [];
|
||||
|
@ -161,6 +161,7 @@ class InReplyToMimeHeader extends MimeHeader {
|
|||
}
|
||||
}
|
||||
|
||||
this._inReplyToValue = inReplyTo;
|
||||
return inReplyTo;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,7 +51,7 @@ class MessageIdMimeHeader extends MimeHeader {
|
|||
// %d14-31 / ; return, line feed, and
|
||||
// %d127 ; white space characters
|
||||
// TODO: Implement utf-8 encoding and whatever other encodings we have to support.
|
||||
if ( this._messageIdValue !== undefined ) {
|
||||
if ( this._messageIdValue !== "" ) {
|
||||
return this._messageIdValue;
|
||||
}
|
||||
// TODO: Implement this state machine properly according to the above ABNF.
|
||||
|
@ -158,6 +158,7 @@ class MessageIdMimeHeader extends MimeHeader {
|
|||
}
|
||||
}
|
||||
|
||||
this._messageIdValue = `${idLeft}@${idRight}`;
|
||||
return `${idLeft}@${idRight}`;
|
||||
}
|
||||
}
|
||||
|
|
168
lib/ReferencesMimeHeader.js
Normal file
168
lib/ReferencesMimeHeader.js
Normal file
|
@ -0,0 +1,168 @@
|
|||
import MimeHeader from "./MimeHeader.js";
|
||||
|
||||
const atext = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%&'*+-/=?^_`{|}~".split('');
|
||||
const dtext = "!\"#$%^&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ^_`abcdefghijklmnopqrstuvwxyz{|}~".split('');
|
||||
|
||||
class ReferencesMimeHeader extends MimeHeader {
|
||||
constructor(key, value) {
|
||||
super(key, value);
|
||||
this._referencesValue = [];
|
||||
}
|
||||
// Message-ID: <0Q27kpOFnz1MK3ASM_vbjI8L_BYN3j5OSxIaGxerST74zBn7VZfqCowbhEWgJ0Yv4mV847u25YGjENqlsM7-15Zdus90qN7t_kj55FyuN_c=@protonmail.com>
|
||||
get referencesValues() {
|
||||
// ABNF:
|
||||
// references = "References:" 1*msg-id CRLF
|
||||
// msg-id = [CFWS] "<" id-left "@" id-right ">" [CFWS]
|
||||
// id-left = dot-atom-text / obs-id-left
|
||||
// obs-id-left = local-part
|
||||
// id-right = dot-atom-text / no-fold-literal / obs-id-right
|
||||
// obs-id-right = domain
|
||||
// no-fold-literal = "[" *dtext "]"
|
||||
// dtext = %d33-90 / ; Printable US-ASCII
|
||||
// %d94-126 / ; characters not including
|
||||
// obs-dtext ; "[", "]", or "\"
|
||||
// dot-atom-text = 1*atext *("." 1*atext)
|
||||
// atext = ALPHA / DIGIT / ; Printable US-ASCII
|
||||
// "!" / "#" / ; characters not including
|
||||
// "$" / "%" / ; specials. Used for atoms.
|
||||
// "&" / "'" /
|
||||
// "*" / "+" /
|
||||
// "-" / "/" /
|
||||
// "=" / "?" /
|
||||
// "^" / "_" /
|
||||
// "`" / "{" /
|
||||
// "|" / "}" /
|
||||
// "~"
|
||||
// dtext = %d33-90 / ; Printable US-ASCII
|
||||
// %d94-126 / ; characters not including
|
||||
// obs-dtext ; "[", "]", or "\"
|
||||
// comment = "(" *([FWS] ccontent) [FWS] ")"
|
||||
// ccontent = ctext / quoted-pair / comment
|
||||
// WSP = SP / HTAB ; white space
|
||||
// obs-FWS = 1*WSP *(CRLF 1*WSP)
|
||||
// quoted-pair = ("\" (VCHAR / WSP)) / obs-qp
|
||||
// obs-qp = "\" (%d0 / obs-NO-WS-CTL / LF / CR)
|
||||
// obs-NO-WS-CTL = %d1-8 / ; US-ASCII control
|
||||
// %d11 / ; characters that do not
|
||||
// %d12 / ; include the carriage
|
||||
// %d14-31 / ; return, line feed, and
|
||||
// %d127 ; white space characters
|
||||
// TODO: Implement utf-8 encoding and whatever other encodings we have to support.
|
||||
if ( this._referencesValue.length !== 0 ) {
|
||||
return this._referencesValue;
|
||||
}
|
||||
// TODO: Implement this state machine properly according to the above ABNF.
|
||||
var references = [];
|
||||
var state = "";
|
||||
var inCRLF = false;
|
||||
var commentDepth = 0;
|
||||
var inDquot = false;
|
||||
var idLeft = "";
|
||||
var idRight = "";
|
||||
var idRightDatEmittedRightSquacket = false;
|
||||
for (var char of this.rawValue) {
|
||||
if ( inCRLF === true && char !== "\n" ) {
|
||||
return null; // Error state.
|
||||
}
|
||||
if (state === "") {
|
||||
// CFWS
|
||||
if ( char === " ") {
|
||||
continue;
|
||||
} else if ( char === "\t") {
|
||||
continue;
|
||||
} else if ( char === "(" && !inDquot ) {
|
||||
commentDepth++;
|
||||
} else if ( char === "\"" ) {
|
||||
inDquot = !inDquot;
|
||||
} else if ( char === ")" && !inDquot ) {
|
||||
commentDepth--;
|
||||
} else if ( char === "\r" ) {
|
||||
inCRLF = true;
|
||||
} else if ( char === "\n" ) {
|
||||
inCRLF = false;
|
||||
} else if ( char === "<" ) {
|
||||
state = "id-left";
|
||||
} else {
|
||||
return null; // Error state, we couldn't produce a proper message ID from the input.
|
||||
}
|
||||
} else if ( state === "id-left" ) {
|
||||
if ( atext.includes(char) ) {
|
||||
idLeft += char;
|
||||
} else if ( char === "." ) {
|
||||
if ( idLeft.endsWith(".") || idLeft.length === 0 ) {
|
||||
return null; // Error state.
|
||||
}
|
||||
idLeft += char;
|
||||
} else if ( char === "@" ) {
|
||||
if ( idLeft.endsWith(".") || idLeft.length === 0) {
|
||||
return null; // Error state.
|
||||
}
|
||||
state = "id-right";
|
||||
}
|
||||
} else if ( state === "id-right" ) {
|
||||
if ( char === "[" ) {
|
||||
if ( idRight.length !== 0 ) {
|
||||
return null; // Error state.
|
||||
}
|
||||
idRight += char;
|
||||
state = "id-right-dat";
|
||||
} else if ( char === "." ) {
|
||||
if ( idRight.endsWith(".") || idRight.length === 0 ) {
|
||||
return null; // Error state.
|
||||
}
|
||||
idRight += char;
|
||||
} else if ( char === ">" ) {
|
||||
if ( idRight.endsWith(".") || idRight.length === 0) {
|
||||
return null; // Error state.
|
||||
}
|
||||
references.push(`${idLeft}@${idRight}`);
|
||||
idLeft = "";
|
||||
idRight = "";
|
||||
state = "intermediate-or-end-cfws"
|
||||
}
|
||||
} else if ( state === "id-right-dat" ) {
|
||||
if ( char === ">" && idRight.endsWith("]") ) {
|
||||
state = "intermediate-or-end-cfws";
|
||||
continue;
|
||||
}
|
||||
if ( dtext.includes(char) ) {
|
||||
if ( idRightDatEmittedRightSquacket ) {
|
||||
return null; // Error state.
|
||||
}
|
||||
idRight += char;
|
||||
} else if ( char === "]" ) {
|
||||
if ( idRightDatEmittedRightSquacket ) {
|
||||
return null; // Error state.
|
||||
}
|
||||
idRight += char;
|
||||
idRightDatEmittedRightSquacket = true;
|
||||
}
|
||||
} else if ( state === "intermediate-or-end-cfws" ) {
|
||||
// CFWS
|
||||
if ( char === " ") {
|
||||
continue;
|
||||
} else if ( char === "\t") {
|
||||
continue;
|
||||
} else if ( char === "(" && !inDquot ) {
|
||||
commentDepth++;
|
||||
} else if ( char === "\"" ) {
|
||||
inDquot = !inDquot;
|
||||
} else if ( char === ")" && !inDquot ) {
|
||||
commentDepth--;
|
||||
} else if ( char === "\r" ) {
|
||||
inCRLF = true;
|
||||
} else if ( char === "\n" ) {
|
||||
inCRLF = false;
|
||||
} else if ( char === "<" ) {
|
||||
state = "id-left";
|
||||
} else {
|
||||
return null; // Error state.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return references;
|
||||
}
|
||||
}
|
||||
|
||||
export default ReferencesMimeHeader;
|
306
mimetest.js
Normal file
306
mimetest.js
Normal file
|
@ -0,0 +1,306 @@
|
|||
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._value + 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._value + 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);
|
||||
}
|
11
server.js
11
server.js
|
@ -56,11 +56,11 @@ app.post('/sendgrid/ingress', (req, res) => {
|
|||
|
||||
var envelope = JSON.parse(fields.envelope?.[0]??"{}");
|
||||
|
||||
dns.reverse(typeof req.headers["x-forwarded-for"] === "string" ? req.headers["x-forwarded-for"] : "192.0.2.0", (err, dnsRes) => {
|
||||
dns.reverse(typeof req.headers["x-forwarded-for"] === "string" ? req.headers["x-forwarded-for"] : "192.0.2.0", async (err, dnsRes) => {
|
||||
if (err)
|
||||
messageObj.prependHeader(new MimeHeader(`Received: from ${req.headers["x-forwarded-for"]} by andrewpietila.com with HTTPS-API id ${state} for ${envelope.to ?? "unknown"}; ${new Date().toISOString()}`));
|
||||
messageObj.prependHeader(new MimeHeader(`Received: from ${req.headers["x-forwarded-for"]} by andrewpietila.com with HTTPS-API id ${state} for <${envelope.to ?? "unknown"}>; ${new Date().toString()}`));
|
||||
else
|
||||
messageObj.prependHeader(new MimeHeader(`Received: from ${dnsRes[0]} (${dnsRes[0]} [${req.headers["x-forwarded-for"]}] by andrewpietila.com with HTTPS-API id ${state} for ${envelope.to ?? "unknown"}; ${new Date().toISOString()}`));
|
||||
messageObj.prependHeader(new MimeHeader(`Received: from ${dnsRes[0]} (${dnsRes[0]} [${req.headers["x-forwarded-for"]}] by andrewpietila.com with HTTPS-API id ${state} for <${envelope.to ?? "unknown"}>; ${new Date().toString()}`));
|
||||
messageObj.prependHeader(new MimeHeader(`X-Auth-Check: SPF ${fields.SPF?.[0] ?? "missing"}`));
|
||||
messageObj.prependHeader(new MimeHeader(`X-Auth-Check: DKIM ${fields.dkim?.[0] ?? "missing"}`));
|
||||
messageObj.prependHeader(new MimeHeader(`Return-Path: <${envelope.from ?? "unknown"}>`));
|
||||
|
@ -241,8 +241,9 @@ app.post("/api/jmap/api/", bodyParser.json(), async (req, res) => {
|
|||
// blobId: state
|
||||
// mailboxIds: {INBOX: true} // TODO: Implement more mailboxes.
|
||||
// keywords: keywords table, {keyword_column: true}
|
||||
// messageId: new MimeMessage().getFirstHeaderOf("Message-Id").messageIdValue
|
||||
// inReplyTo: new MimeMessage().getFirstHeadderOf("In-Reply-To").inReplyToValues
|
||||
// messageId: new MimeMessage().getFirstHeaderOf("Message-Id").messageIdValue()
|
||||
// inReplyTo: new MimeMessage().getFirstHeadderOf("In-Reply-To").inReplyToValues()
|
||||
// references: new MimeMessage().getFirstHeaderOf("References").referencesValues()
|
||||
|
||||
return [
|
||||
"error",
|
||||
|
|
Loading…
Add table
Reference in a new issue