mirror of
https://github.com/MailHops/mailhops-plugin.git
synced 2025-05-16 14:20:10 -07:00
The current code only shows the basic auth status and a description of what it means. Some users (i.e., me) would like to see more information, typically the whole header part that is responsible for the decision, so prepare to show this information. This commit does NOT hook it up just yet. This will be done in a later commit.
468 lines
17 KiB
JavaScript
468 lines
17 KiB
JavaScript
/*
|
|
* @author: Andrew Van Tassel
|
|
* @email: andrew@andrewvantassel.com
|
|
* @website: http://Mailhops.com
|
|
*/
|
|
|
|
class MailHops {
|
|
msgURI = null
|
|
isLoaded = false
|
|
loading = false
|
|
tabId = null
|
|
options = {
|
|
version: 'MailHops Plugin 4.4.0',
|
|
api_key: '',
|
|
owm_key: '',
|
|
lang: 'en',
|
|
unit: 'mi',
|
|
theme: 'light',
|
|
api_http: 'https://',
|
|
api_host: 'api.Mailhops.com',
|
|
travel_time_junk: false,
|
|
extrainfo: false,
|
|
debug: false,
|
|
country_filter: []
|
|
}
|
|
message = {
|
|
id: null
|
|
, map_url: ''
|
|
, time: null
|
|
, date: new Date().toISOString()
|
|
, hash: ''
|
|
, secure: []
|
|
, headers: []
|
|
, auth: []
|
|
, sender: {
|
|
icon: '/images/refresh.png'
|
|
, title: 'Loading...'
|
|
, description: ''
|
|
},
|
|
error: ''
|
|
}
|
|
response = {}
|
|
|
|
LOG(msg) {
|
|
if (!this.options.debug)
|
|
return;
|
|
console.log(msg);
|
|
}
|
|
|
|
async init(tabId, id, headers) {
|
|
this.tabId = tabId;
|
|
try {
|
|
var data = await browser.storage.local.get();
|
|
if (data.api_key) {
|
|
this.options.api_key = data.api_key;
|
|
}
|
|
if (data.owm_key) {
|
|
this.options.owm_key = data.owm_key;
|
|
}
|
|
if (data.lang) {
|
|
this.options.lang = data.lang;
|
|
}
|
|
if (data.unit) {
|
|
this.options.unit = data.unit;
|
|
}
|
|
if (data.theme) {
|
|
this.options.theme = data.theme;
|
|
}
|
|
if (data.travel_time_junk && data.travel_time_junk != 'off') {
|
|
this.options.travel_time_junk = Boolean(data.travel_time_junk);
|
|
}
|
|
if (data.extrainfo) {
|
|
this.options.extrainfo = Boolean(data.extrainfo);
|
|
}
|
|
if (data.debug) {
|
|
this.options.debug = Boolean(data.debug);
|
|
}
|
|
if (data.countries) {
|
|
this.options.country_filter = data.countries.split(',');
|
|
}
|
|
this.LOG('load MailHops prefs');
|
|
// reset message
|
|
this.message = {
|
|
id: id
|
|
, map_url: ''
|
|
, time: null
|
|
, date: new Date().toISOString()
|
|
, hash: ''
|
|
, secure: []
|
|
, headers: headers
|
|
, auth: []
|
|
, sender: {
|
|
icon: '/images/refresh.png'
|
|
, title: 'Loading...'
|
|
, description: ''
|
|
},
|
|
error: ''
|
|
};
|
|
this.getRoute();
|
|
} catch (e) {
|
|
this.LOG('Error loading MailHops prefs');
|
|
}
|
|
}
|
|
|
|
async getRoute() {
|
|
if (this.loading) return;
|
|
|
|
this.loading = true;
|
|
// set loading icon
|
|
browser.messageDisplayAction.setPopup({ popup: '', tabId: this.tabId });
|
|
browser.messageDisplayAction.setIcon({ path: '/images/refresh.png', tabId: this.tabId });
|
|
browser.messageDisplayAction.setTitle({ title: 'Loading...', tabId: this.tabId });
|
|
|
|
//IP regex
|
|
var regexIp = /(1\d{0,2}|2(?:[0-4]\d{0,1}|[6789]|5[0-5]?)?|[3-9]\d?|0)\.(1\d{0,2}|2(?:[0-4]\d{0,1}|[6789]|5[0-5]?)?|[3-9]\d?|0)\.(1\d{0,2}|2(?:[0-4]\d{0,1}|[6789]|5[0-5]?)?|[3-9]\d?|0)\.(1\d{0,2}|2(?:[0-4]\d{0,1}|[6789]|5[0-5]?)?|[3-9]\d?|0)(\/(?:[012]\d?|3[012]?|[456789])){0,1}$/;
|
|
var regexAllIp = /(1\d{0,2}|2(?:[0-4]\d{0,1}|[6789]|5[0-5]?)?|[3-9]\d?|0)\.(1\d{0,2}|2(?:[0-4]\d{0,1}|[6789]|5[0-5]?)?|[3-9]\d?|0)\.(1\d{0,2}|2(?:[0-4]\d{0,1}|[6789]|5[0-5]?)?|[3-9]\d?|0)\.(1\d{0,2}|2(?:[0-4]\d{0,1}|[6789]|5[0-5]?)?|[3-9]\d?|0)(\/(?:[012]\d?|3[012]?|[456789])){0,1}/g;
|
|
var regexIPV6 = /s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*/g;
|
|
|
|
var headReceived = this.message.headers['received'] || [];
|
|
var headDate = this.message.headers['date'] ? this.message.headers['date'][0] : '';
|
|
var headXReceived = this.message.headers['x-received'] ? this.message.headers['x-received'][0] : '';
|
|
var headXOrigIP = this.message.headers['x-originating-ip'] ? this.message.headers['x-originating-ip'][0] : '';
|
|
// auth box
|
|
var headXMailer = this.message.headers['x-mailer'] ? this.message.headers['x-mailer'][0] : '';
|
|
var headUserAgent = this.message.headers['user-agent'] ? this.message.headers['user-agent'][0] : '';
|
|
var headXMimeOLE = this.message.headers['x-mimeole'] ? this.message.headers['x-mimeole'][0] : '';
|
|
var headReceivedSPF = this.message.headers['received-spf'] ? this.message.headers['received-spf'][0] : '';
|
|
var headAuth = this.message.headers['authentication-results'] ? this.message.headers['authentication-results'][0] : '';
|
|
var headListUnsubscribe = this.message.headers['list-unsubscribe'] ? this.message.headers['list-unsubscribe'][0] : '';
|
|
|
|
var all_ips = new Array();
|
|
var rline = '';
|
|
var firstDate = headDate;
|
|
var lastDate;
|
|
//empty secure and time
|
|
this.message.secure = [];
|
|
this.message.time = null;
|
|
try {
|
|
this.message.date = new Date(headDate).toISOString();
|
|
} catch (error) {
|
|
headDate = headDate.substring(0, headDate.lastIndexOf(' '));
|
|
}
|
|
try {
|
|
this.message.date = new Date(headDate).toISOString();
|
|
} catch (error) {
|
|
headDate = new Date();
|
|
}
|
|
|
|
this.message.auth = this.auth(headXMailer, headUserAgent, headXMimeOLE, headAuth, headReceivedSPF, headListUnsubscribe);
|
|
|
|
//loop through the received headers and parse for IP addresses
|
|
if (Boolean(headReceived)) {
|
|
var received_ips = new Array();
|
|
for (var h = 0; h < headReceived.length; h++) {
|
|
//build the received line by concat until semi-colon ; date/time
|
|
rline += headReceived[h];
|
|
if (headReceived[h].indexOf(';') === -1)
|
|
continue;
|
|
// first and last dates are used to calculate time traveled
|
|
if (rline.indexOf(';') !== -1) {
|
|
if (!firstDate)
|
|
firstDate = rline.substring(rline.indexOf(';') + 1).trim();
|
|
if (!lastDate)
|
|
lastDate = rline.substring(rline.indexOf(';') + 1).trim();
|
|
}
|
|
|
|
// IPV6 check
|
|
rline = rline.replace(/\[IPv6\:/g, '[');
|
|
if (rline.match(regexIPV6)) {
|
|
all_ips.unshift(rline.match(regexIPV6)[0]);
|
|
//reset the line
|
|
rline = '';
|
|
continue;
|
|
}
|
|
// parse IPs out of Received line
|
|
received_ips = rline.match(regexAllIp);
|
|
//continue if no IPs found
|
|
if (!received_ips) {
|
|
//reset the line
|
|
rline = '';
|
|
continue;
|
|
}
|
|
//get unique IPs for each Received header
|
|
received_ips = received_ips.filter(function (item, pos) {
|
|
return received_ips.indexOf(item) === pos;
|
|
});
|
|
for (var r = received_ips.length; r >= 0; r--) {
|
|
if (regexIp.test(received_ips[r]) && this.testIP(received_ips[r], rline)) {
|
|
all_ips.unshift(received_ips[r]);
|
|
}
|
|
}
|
|
//reset the line
|
|
rline = '';
|
|
}
|
|
}
|
|
|
|
// parse dates
|
|
if (firstDate && firstDate.indexOf('(') !== - 1)
|
|
firstDate = firstDate.substring(0, firstDate.indexOf('(')).trim();
|
|
if (lastDate && lastDate.indexOf('(') !== -1)
|
|
lastDate = lastDate.substring(0, lastDate.indexOf('(')).trim();
|
|
if (firstDate && lastDate) {
|
|
try {
|
|
firstDate = new Date(firstDate);
|
|
lastDate = new Date(lastDate);
|
|
this.message.time = lastDate - firstDate;
|
|
} catch (e) {
|
|
this.LOG('travel dates parse Error: ' + JSON.stringify(e));
|
|
this.message.time = null;
|
|
}
|
|
} else {
|
|
this.message.time = null;
|
|
}
|
|
|
|
//get the originating IP address
|
|
if (Boolean(headXOrigIP)) {
|
|
headXOrigIP = headXOrigIP.replace(/\[IPv6\:/g, '[');
|
|
//IPV6 check
|
|
if (headXOrigIP.match(regexIPV6)) {
|
|
var ip = headXOrigIP.match(regexIPV6)
|
|
if (Boolean(ip) && ip.length && all_ips.indexOf(ip[0]) == -1)
|
|
all_ips.unshift(ip[0]);
|
|
} else {
|
|
var ip = headXOrigIP.match(regexAllIp);
|
|
if (Boolean(ip) && ip.length && all_ips.indexOf(ip[0]) == -1)
|
|
all_ips.unshift(ip[0]);
|
|
}
|
|
}
|
|
if (all_ips.length) {
|
|
// set the message hash
|
|
this.message.hash = btoa(this.message.date + '' + all_ips.join(','));
|
|
const cached = await this.getCacheResponse();
|
|
if (cached) {
|
|
this.displayRoute(cached);
|
|
this.isLoaded = true;
|
|
this.loading = false;
|
|
} else {
|
|
this.lookupRoute(all_ips.join(','));
|
|
}
|
|
} else {
|
|
this.clear();
|
|
}
|
|
};
|
|
|
|
//another ip check, dates will throw off the regex
|
|
testIP(ip, header) {
|
|
var validIP = true;
|
|
|
|
try {
|
|
var firstchar = header.substring(header.indexOf(ip) - 1);
|
|
firstchar = firstchar.substring(0, 1);
|
|
var lastchar = header.substring((header.indexOf(ip) + ip.length));
|
|
lastchar = lastchar.substring(0, 1);
|
|
|
|
if (firstchar.match(/\.|\d|\-/)
|
|
|| lastchar.match(/\.|\d|\-/)
|
|
|| (firstchar == '?' && lastchar == '?')
|
|
|| (firstchar == ':' || lastchar == ':')
|
|
|| lastchar == ';'
|
|
|| header.toLowerCase().indexOf(' id ' + ip) !== -1
|
|
|| parseInt(ip.substring(0, ip.indexOf('.'))) >= 240 //IANA-RESERVED
|
|
) {
|
|
//only if there is one instance of this IP
|
|
if (header.indexOf(ip) == header.lastIndexOf(ip))
|
|
validIP = false;
|
|
} else if (header.indexOf('using SSL') !== -1
|
|
|| header.indexOf('using TLS') !== -1
|
|
|| header.indexOf('version=TLSv1/SSLv3') !== -1
|
|
) {
|
|
//check if this IP was part of a secure transmission
|
|
this.message.secure.push(ip);
|
|
}
|
|
} catch (e) {
|
|
this.LOG('testIP Error: ' + JSON.stringify(e));
|
|
}
|
|
return validIP;
|
|
}
|
|
|
|
clear() {
|
|
this.message.sender = {
|
|
title: 'Local',
|
|
countryCode: '',
|
|
icon: '/images/local.png'
|
|
};
|
|
browser.messageDisplayAction.setIcon({ path: this.message.sender.icon, tabId: this.tabId });
|
|
browser.messageDisplayAction.setTitle({ title: this.message.sender.title, tabId: this.tabId });
|
|
this.isLoaded = true;
|
|
this.loading = false;
|
|
}
|
|
|
|
error(status, data) {
|
|
this.message.error = (data && data.error && data.error.message) ? data && data.error.message : 'Service Unavailable';
|
|
this.message.sender = {
|
|
title: (data && data.error && data.error.message) ? data && data.error.message : 'Service Unavailable',
|
|
countryCode: '',
|
|
icon: '/images/auth/error.png'
|
|
};
|
|
browser.messageDisplayAction.setIcon({ path: this.message.sender.icon, tabId: this.tabId });
|
|
browser.messageDisplayAction.setTitle({ title: this.message.sender.title, tabId: this.tabId });
|
|
}
|
|
|
|
auth(header_xmailer, header_useragent, header_xmimeole, header_auth, header_spf, header_unsubscribe) {
|
|
let auth = [];
|
|
//SPF
|
|
if (header_spf) {
|
|
header_spf = header_spf.replace(/^\s+/, "");
|
|
var headerSPFArr = header_spf.split(' ');
|
|
auth.push({
|
|
type: 'SPF',
|
|
color: 'green',
|
|
icon: '/images/auth/' + headerSPFArr[0] + '.png',
|
|
copy: header_spf + '\n' + MailHopsUtils.spf(headerSPFArr[0]).trim()
|
|
});
|
|
}
|
|
//Authentication-Results
|
|
//http://tools.ietf.org/html/rfc5451
|
|
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;
|
|
}
|
|
if (!header_spf && headerAuthArr[h].indexOf('spf=') != -1) {
|
|
spf_result = headerAuthArr[h];
|
|
if (dkim_result)
|
|
break;
|
|
}
|
|
}
|
|
if (dkim_result) {
|
|
dkim_result = dkim_result.replace(/^\s+/, "");
|
|
var dkimArr = dkim_result.split(' ');
|
|
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()
|
|
});
|
|
}
|
|
if (spf_result) {
|
|
spf_result = spf_result.replace(/^\s+/, "");
|
|
var spfArr = spf_result.split(' ');
|
|
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()
|
|
});
|
|
}
|
|
}
|
|
if (header_unsubscribe) {
|
|
auth.push({
|
|
type: 'Unsubscribe',
|
|
color: 'grey',
|
|
link: header_unsubscribe.replace('<', '').replace('>', '').trim()
|
|
});
|
|
}
|
|
return auth;
|
|
}
|
|
|
|
//mailhops lookup
|
|
lookupRoute(header_route) {
|
|
let mailHop = this;
|
|
|
|
let lookupURL = '?' + MailHopsUtils.getAPIUrlParams(this.options) + '&r=' + String(header_route) + '&l=' + this.options.lang + '&u=' + this.options.unit;
|
|
|
|
if (this.options.owm_key != '')
|
|
lookupURL += '&owm_key=' + this.options.owm_key;
|
|
if (this.message.time != null)
|
|
lookupURL += '&t=' + this.message.time;
|
|
if (this.message.date != null)
|
|
lookupURL += '&d=' + this.message.date;
|
|
|
|
this.message.map_url = MailHopsUtils.getAPIUrl() + '/map/' + lookupURL;
|
|
|
|
//call mailhops api for lookup
|
|
var xmlhttp = new XMLHttpRequest();
|
|
xmlhttp.open("GET", MailHopsUtils.getAPIUrl() + '/lookup/' + lookupURL, true);
|
|
xmlhttp.onreadystatechange = function () {
|
|
if (xmlhttp.readyState === 4) {
|
|
try {
|
|
let data = JSON.parse(xmlhttp.responseText);
|
|
if (xmlhttp.status === 200) {
|
|
mailHop.cacheResponse(data.response);
|
|
mailHop.displayRoute(data.response);
|
|
//tag the result
|
|
mailHop.tagResults(data, data.response.route);
|
|
} else if (data.error) {
|
|
mailHop.LOG(JSON.stringify(data.error));
|
|
//display the error
|
|
mailHop.error(xmlhttp.status, data);
|
|
}
|
|
} catch (e) {
|
|
mailHop.LOG(e);
|
|
mailHop.error();
|
|
}
|
|
}
|
|
mailHop.isLoaded = true;
|
|
mailHop.loading = false;
|
|
};
|
|
xmlhttp.send(null);
|
|
}
|
|
|
|
displayRoute(response) {
|
|
this.response = response;
|
|
this.message.sender = MailHopsUtils.getSender(response.route);
|
|
|
|
if (this.message.sender) {
|
|
browser.messageDisplayAction.setIcon({ path: this.message.sender.icon, tabId: this.tabId });
|
|
browser.messageDisplayAction.setTitle({ title: this.message.sender.title, tabId: this.tabId });
|
|
} else {
|
|
browser.messageDisplayAction.setIcon({ path: '/images/local.png', tabId: this.tabId });
|
|
browser.messageDisplayAction.setTitle({ title: 'Local', tabId: this.tabId });
|
|
}
|
|
}
|
|
|
|
// keep a daily cache
|
|
async cacheResponse(response) {
|
|
let data = await browser.storage.local.get('messages');
|
|
let cached_date = new Date();
|
|
let messages = {
|
|
cached: cached_date.getUTCFullYear() + '-' + cached_date.getUTCMonth() + '-' + cached_date.getUTCDate(),
|
|
list: []
|
|
};
|
|
if (data.messages && data.messages.list && data.messages.cached === messages.cached) {
|
|
messages.list = data.messages.list;
|
|
}
|
|
messages.list[this.message.hash] = response;
|
|
await browser.storage.local.set({ messages: messages });
|
|
this.LOG('Cached Message ' + this.message.id + ' hash ' + this.message.hash);
|
|
}
|
|
|
|
// get cached message
|
|
async getCacheResponse() {
|
|
let data = await browser.storage.local.get('messages');
|
|
if (data.messages && data.messages.list[this.message.hash]) {
|
|
this.LOG('Found Cached Message ' + this.message.hash);
|
|
return data.messages.list[this.message.hash];
|
|
}
|
|
return false;
|
|
};
|
|
|
|
tagResults(results, route) {
|
|
if (!results) {
|
|
return false;
|
|
}
|
|
|
|
//Add junk tag on messages taking too long to travel
|
|
try {
|
|
if (Boolean(this.options.travel_time_junk) && this.message.time != null && this.message.time > 10000) {
|
|
messenger.messages.update(this.message.id, { 'junk': true });
|
|
this.LOG("Junk: Travel time match for " + this.message.time);
|
|
}
|
|
if (this.options.country_filter.length && this.message.sender && this.message.sender.countryCode) {
|
|
if (this.options.country_filter.indexOf(this.message.sender.countryCode.toUpperCase()) != -1) {
|
|
messenger.messages.update(this.message.id, { 'junk': true });
|
|
this.LOG("Junk: Country code match for " + this.message.sender.countryCode);
|
|
}
|
|
}
|
|
} catch (e) {
|
|
this.LOG("Error tagResults: " + e);
|
|
}
|
|
}
|
|
}
|