diff --git a/lib/FromMimeHeader.js b/lib/FromMimeHeader.js new file mode 100644 index 0000000..497e6a4 --- /dev/null +++ b/lib/FromMimeHeader.js @@ -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 + 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; \ No newline at end of file diff --git a/lib/InReplyToMimeHeader.js b/lib/InReplyToMimeHeader.js index 1d96e22..97a64a4 100644 --- a/lib/InReplyToMimeHeader.js +++ b/lib/InReplyToMimeHeader.js @@ -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; } } diff --git a/lib/MessageIdMimeHeader.js b/lib/MessageIdMimeHeader.js index 2133cc9..7b51179 100644 --- a/lib/MessageIdMimeHeader.js +++ b/lib/MessageIdMimeHeader.js @@ -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}`; } } diff --git a/lib/ReferencesMimeHeader.js b/lib/ReferencesMimeHeader.js new file mode 100644 index 0000000..837c2e6 --- /dev/null +++ b/lib/ReferencesMimeHeader.js @@ -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; \ No newline at end of file diff --git a/mimetest.js b/mimetest.js new file mode 100644 index 0000000..9421222 --- /dev/null +++ b/mimetest.js @@ -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); +} \ No newline at end of file diff --git a/server.js b/server.js index 2659dc4..767cf2a 100644 --- a/server.js +++ b/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",