diff --git a/js/mailhops.js b/js/mailhops.js
index a6792bb..398f5f0 100644
--- a/js/mailhops.js
+++ b/js/mailhops.js
@@ -299,6 +299,53 @@ class MailHops {
browser.messageDisplayAction.setTitle({ title: this.message.sender.title, tabId: this.tabId });
}
+ extractMaybeQuotedValue(line, key) {
+ let ret = [ ];
+ let new_line = line;
+
+ console.assert (key.length);
+
+ // The possible combinations we might be interested in.
+ let dQuote = new RegExp (key + '="(.*?)"');
+ let sQuote = new RegExp (key + "='(.*?)'");
+ let nQuote = new RegExp (key + '=(.*?)(?:\\s|$)');
+
+ let match = line.match (dQuote);
+ if (null !== match) {
+ ret.push (match[1]);
+ }
+
+ if (!ret.length) {
+ match = line.match (sQuote);
+ if (null !== match) {
+ ret.push (match[1]);
+ }
+ }
+
+ if (!ret.length) {
+ match = line.match (nQuote);
+ if (null !== match) {
+ ret.push (match[1]);
+ }
+ }
+
+ if (ret.length) {
+ // Match length must be non-zero.
+ console.assert (match[0].length);
+
+ // Drop data leading up to the match.
+ new_line = new_line.substring (match.index);
+
+ // Drop the match itself.
+ new_line = new_line.substring (match[0].length);
+
+ // Add to return value.
+ ret.push (new_line);
+ }
+
+ return ret;
+ }
+
sanitizeString(str) {
return str.replace (/\t/g, ' ').replace (/\s+/g, ' ').replace (//g, '>').trim ();
}
@@ -342,39 +389,192 @@ class MailHops {
//Authentication-Results
//http://tools.ietf.org/html/rfc8601
if (header_auth) {
- var headerAuthArr = header_auth.split(';');
- var dkim_result;
- var spf_result;
- for (var h = 0; h < headerAuthArr.length; h++) {
- if (headerAuthArr[h].indexOf('dkim=') != -1) {
- dkim_result = headerAuthArr[h];
- if (header_spf)
- break;
+ this.LOG ('Authentication-Results header found, parsing...\n');
+ var headerAuthArr = [];
+
+ // The header might contain multiple lines, so iterate above them.
+ var headerAuthArr_ = header_auth.split('\n');
+ var dkimState = '';
+ var spfState = '';
+ var dkimReason = '';
+ var spfReason = '';
+ var dkimAux = '';
+ var spfAux = '';
+ var gotDkimState = false;
+ var gotSpfState = false;
+ for (var i = 0; i < headerAuthArr_.length; ++i) {
+ var curLine = headerAuthArr_[i];
+ this.LOG ('current header line: ' + curLine + '\n');
+
+ // This is the actual fun part.
+ // The AH can very funky and naïve field splitting often leads to
+ // wrong results.
+ // We'll heave to tread carefully and parse data in a more
+ // sophisticated way, relying on arcane knowledge.
+ //
+ // It might be temping to try to merge the dkim and spf sections below,
+ // but doing so is not really possible, since the formats are
+ // different.
+ let statePos = 0;
+ let stateWork = curLine;
+ while ((!gotDkimState) &&
+ (-1 !== (statePos = stateWork.indexOf ('dkim=')))) {
+ // Fetch "dkim=" value, potentially quoted.
+ // While the RFC doesn't mention anything about quote-enclosing
+ // values, it probably doesn't hurt to support it.
+ let extract = this.extractMaybeQuotedValue (stateWork, 'dkim');
+
+ if (extract.length) {
+ dkimState = extract[0];
+ stateWork = extract[1];
+ gotDkimState = true;
+ this.LOG ("extracted DKIM state: " + dkimState + "; continuing with new line: " + stateWork);
+ }
+ else {
+ // Nothing found on this line, continue.
+ this.LOG ("nothing found on current line (leftover) '" + truncatedWork + "', continuing...");
+ continue;
+ }
+
+ // Look ahead to find the next "dkim=" section. This will mark the
+ // end of our current chunk (if it exists).
+ let stateEnd = -1;
+ let truncatedWork = stateWork;
+ if (-1 !== (stateEnd = stateWork.indexOf ('dkim=', 5))) {
+ truncatedWork = truncatedWork.substring (0, stateEnd);
+ }
+
+ // Now fetch additional data.
+ // Typically, that should be "reason=".
+ extract = this.extractMaybeQuotedValue (truncatedWork, 'reason');
+
+ if (extract.length) {
+ dkimReason = extract[0];
+ truncatedWork = extract[1];
+ this.LOG ("extracted DKIM reason: " + dkimReason + "; continuing with new line: " + truncatedWork);
+ }
+
+ // Same MTA append a string such as "(1024-bit key; unprotected)",
+ // add this to the reason.
+ const key_data = new RegExp ('\\(\\d+-bit key(?:; .+?)?\\)');
+ let match = truncatedWork.match (key_data);
+ if (null !== match) {
+ if (dkimReason.length) {
+ dkimReason += ' ';
+ }
+ dkimReason += match[0];
+ truncatedWork = truncatedWork.substring (match.index);
+ truncatedWork = truncatedWork.substring (match[0].length);
+ }
+
+ // Others enclose the reason in parentheses.
+ const paren_reason = new RegExp ('\\(.+?\\)');
+ match = truncatedWork.match (paren_reason);
+ if (null !== match) {
+ if (dkimReason.length) {
+ dkimReason += ' ';
+ }
+ dkimReason += match[0];
+ truncatedWork = truncatedWork.substring (match.index);
+ truncatedWork = truncatedWork.substring (match[0].length);
+ }
+
+ // Everything else should be additional data, up until the first
+ // semicolon or end of the line.
+ const aux_data = new RegExp ('(.+?)(?:;|$)');
+ match = truncatedWork.match (aux_data);
+ if (null !== match) {
+ dkimAux = match[1];
+ }
}
- if (!header_spf && headerAuthArr[h].indexOf('spf=') != -1) {
- spf_result = headerAuthArr[h];
- if (dkim_result)
- break;
+
+ // Do the same for SPF data.
+ statePos = 0;
+ stateWork = curLine;
+ while ((!gotSpfState) &&
+ (-1 !== (statePos = stateWork.indexOf ('spf=')))) {
+ // Refer to the DKIM section above for more information.
+ let extract = this.extractMaybeQuotedValue (stateWork, 'spf');
+
+ if (extract.length) {
+ spfState = extract[0];
+ stateWork = extract[1];
+ gotSpfState = true;
+ }
+ else {
+ // Nothing found on this line, continue.
+ this.LOG ("nothing found on current line (leftover) '" + stateWork + "', continuing...");
+ continue;
+ }
+
+ let stateEnd = -1;
+ let truncatedWork = stateWork;
+ if (-1 !== (stateEnd = stateWork.indexOf ('spf=', 4))) {
+ truncatedWork = truncatedWork.substring (0, stateEnd);
+ }
+
+ // Now fetch additional data.
+ // Reason is usually enclosed in parentheses.
+ const paren_reason = new RegExp ('\\(.+?\\)');
+ let match = truncatedWork.match (paren_reason);
+ if (null !== match) {
+ spfReason = match[0];
+ truncatedWork = truncatedWork.substring (match.index);
+ truncatedWork = truncatedWork.substring (match[0].length);
+ }
+
+ // Everything else is additional data.
+ const aux_data = new RegExp ('(.+?)[;|$]');
+ match = truncatedWork.match (aux_data);
+ if (null !== match) {
+ spfAux = match[1];
+ }
}
}
- if (dkim_result) {
- dkim_result = dkim_result.replace(/^\s+/, "");
- var dkimArr = dkim_result.split(' ');
+
+ if (gotDkimState) {
+ // Just sanitize our data.
+ dkimState = this.sanitizeString (dkimState);
+ dkimReason = this.sanitizeString (dkimReason);
+ dkimAux = this.sanitizeString (dkimAux);
+
+ var copy = dkimState;
+ if (dkimReason.length) {
+ copy += ' - ' + dkimReason;
+ }
+ if ((this.options.extrainfo) && (dkimAux.length)) {
+ copy += '\n
' + dkimAux;
+ }
+ copy += '\n
\n
' + MailHopsUtils.dkim(dkimState).trim();
+
+ this.LOG ("DKIM state and data: " + copy);
auth.push({
type: 'DKIM',
color: 'green',
- icon: '/images/auth/' + dkimArr[0].replace('dkim=', '') + '.png',
- copy: dkim_result + '\n' + MailHopsUtils.dkim(dkimArr[0].replace('dkim=', '')).trim()
+ icon: '/images/auth/' + dkimState.toLowerCase () + '.png',
+ copy: copy
});
}
- if (spf_result) {
- spf_result = spf_result.replace(/^\s+/, "");
- var spfArr = spf_result.split(' ');
+ if (gotSpfState) {
+ spfState = this.sanitizeString (spfState);
+ spfReason = this.sanitizeString (spfReason);
+ spfAux = this.sanitizeString (spfAux);
+
+ var copy = spfState;
+ if (spfReason.length) {
+ copy += ' ' + spfReason;
+ }
+ if ((this.options.extrainfo) && (spfAux.length)) {
+ copy += '\n
' + spfAux;
+ }
+ copy += '\n
\n
' + MailHopsUtils.spf(spfState).trim();
+
+ this.LOG ("SPF state and data: " + copy);
auth.push({
type: 'SPF',
color: 'green',
- icon: '/images/auth/' + spfArr[0].replace('spf=', '') + '.png',
- copy: spf_result + '\n' + MailHopsUtils.spf(spfArr[0].replace('spf=', '')).trim()
+ icon: '/images/auth/' + spfState.toLowerCase () + '.png',
+ copy: copy
});
}
}