/** * GNU LibreJS - A browser add-on to block nonfree nontrivial JavaScript. * * * Copyright (C) 2017 Nathan Nichols, Loic J. Duros, Nik Nyby * Copyright (C) 2018 Giorgio Maone * * This file is part of GNU LibreJS. * * GNU LibreJS is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * GNU LibreJS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU LibreJS. If not, see . */ // TO TEST THE CONTACT FINDER: // - open the manifest.json // - add a comma after the closing bracket of the key "background" // - Copy and paste this after it: /* "content_scripts": [{ "matches": [""], "js": ["/content/contactFinder.js"], "css": ["/content/contactFinder.css"] }] */ // Now, the contact finder will load on every page and you can test it where ever you want. //********************************************************************************************* //Regexes taken from "contact_regex.js" in the current LibreJS //Copyright (C) 2011, 2012, 2014 Loic J. Duros //Copyright (C) 2014, 2015 Nik Nyby function debug(format, ...args) { console.debug(`LibreJS - ${format}`, ...args); } var myPort; debug("Injecting contact finder in %s", document.URL); // email address regexp var reEmail = /^mailto\:(admin|feedback|webmaster|info|contact|support|comments|team|help)\@[a-z0-9.\-]+\.[a-z]{2,4}$/i; var reAnyEmail = /^mailto\:.*?\@[a-z0-9\.\-]+\.[a-z]{2,4}$/i; // twitter address regexp var reTwitter = /twitter\.com\/(\!?#\/)?[a-z0-9]*/i; // identi.ca address regexp var reIdentiCa = /identi\.ca\/(?!notice\/)[a-z0-9]*/i; /** * contactSearchStrings * Contains arrays of strings classified by language * and by degree of certainty. */ var contactStr = { 'da': { 'certain': [ '^[\\s]*Kontakt os[\\s]*$', '^[\\s]*Email Os[\\s]*$', '^[\\s]*Kontakt[\\s]*$' ], 'probable': ['^[\\s]Kontakt', '^[\\s]*Email'], 'uncertain': [ '^[\\s]*Om Us', '^[\\s]*Om', 'Hvem vi er' ] }, 'en': { 'certain': [ '^[\\s]*Contact Us[\\s]*$', '^[\\s]*Email Us[\\s]*$', '^[\\s]*Contact[\\s]*$', '^[\\s]*Feedback[\\s]*$', '^[\\s]*Web.?site Feedback[\\s]*$' ], 'probable': ['^[\\s]Contact', '^[\\s]*Email'], 'uncertain': [ '^[\\s]*About Us', '^[\\s]*About', 'Who we are', 'Who I am', 'Company Info', 'Customer Service' ] }, 'es': { 'certain': [ '^[\\s]*contáctenos[\\s]*$', '^[\\s]*Email[\\s]*$' ], 'probable': ['^[\\s]contáctenos', '^[\\s]*Email'], 'uncertain': [ 'Acerca de nosotros' ] }, 'fr': { 'certain': [ '^[\\s]*Contactez nous[\\s]*$', '^[\\s]*(Nous )?contacter[\\s]*$', '^[\\s]*Email[\\s]*$', '^[\\s]*Contact[\\s]*$', '^[\\s]*Commentaires[\\s]*$' ], 'probable': ['^[\\s]Contact', '^[\\s]*Email'], 'uncertain': [ '^[\\s]*(A|À) propos', 'Qui nous sommes', 'Qui suis(-| )?je', 'Info', 'Service Client(e|è)le' ] } }; var usaPhoneNumber = new RegExp(/(?:\+ ?1 ?)?\(?[2-9]{1}[0-9]{2}\)?(?:\-|\.| )?[0-9]{3}(?:\-|\.| )[0-9]{4}(?:[^0-9])/mg); // Taken from http://emailregex.com/ var email_regex = new RegExp(/(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/g); //********************************************************************************************* var prefs; /** * returns input with all elements not of type string removed */ function remove_not_str(a) { var new_a = []; for (var i in a) { if (typeof (a[i]) == "string") { new_a.push(a[i]) } } return new_a; } /** * Tests all links on the page for regexes under a certain certainty level. * * Will return either the first regex match from the selected certainty level or all regexes that * match on that certainty level. * * certainty_lvl can be "certain" > "probable" > "uncertain" */ function attempt(certainty_lvl, first = true) { // There needs to be some kind of max so that people can't troll by for example leaving a comment with a bunch of emails // to cause LibreJS users to slow down. var fail_flag = true; var flag; var matches = []; var result = []; var str_under_test = ""; for (var i in document.links) { if (typeof (document.links[i].innerText) != "string" || typeof (document.links[i].href) != "string") { continue; } str_under_test = document.links[i].innerText + " " + document.links[i].href; flag = true; for (var j in contactStr) { for (var k in contactStr[j][certainty_lvl]) { if (flag) { result = []; result = str_under_test.match(new RegExp(contactStr[j][certainty_lvl][k], "g")); result = remove_not_str(result); if (result !== undefined && typeof (result[0]) == "string") { if (first) { return { "fail": false, "result": document.links[i] }; } else { //console.log(document.links[i].href + " matched " + contactStr[j][certainty_lvl][k]); matches.push(document.links[i]); fail_flag = false; flag = false; } } } } } } return { "fail": fail_flag, "result": matches }; } /** * "LibreJS detects contact pages, email addresses that are likely to be owned by the * maintainer of the site, Twitter and identi.ca links, and phone numbers." */ function find_contacts() { var all = document.documentElement.innerText; var phone_num = []; var twitlinks = []; var identi = []; var contact_pages = []; var res = attempt("certain"); var flag = true; var type = ""; if (res["fail"] == false) { type = "certain"; res = res["result"]; flag = false; } if (flag) { res = attempt("probable"); if (res["fail"] == false) { type = "probable"; res = res["result"]; flag = false; } } if (flag) { res = attempt("uncertain"); if (res["fail"] == false) { type = "uncertain"; res = res["result"]; flag = false; } } if (flag) { return res; } return [type, res]; } function createWidget(id, tag, parent = document.body) { let widget = document.getElementById(id); if (widget) widget.remove(); widget = parent.appendChild(document.createElement(tag)); widget.id = id; return widget; } /** * * Creates the contact finder / complain UI as a semi-transparent overlay * */ function main() { let overlay = createWidget("_LibreJS_overlay", "div"); let frame = createWidget("_LibreJS_frame", "iframe"); let close = () => { frame.remove(); overlay.remove(); }; let closeListener = e => { let t = e.currentTarget; if (t.href) { // link navigation if (t.href !== document.URL) { if (t.href.includes("#")) { window.addEventListener("hashchange", close); } return; } } close(); }; let makeCloser = clickable => clickable.addEventListener("click", closeListener); makeCloser(overlay); let initFrame = () => { debug("initFrame"); let res = find_contacts(); let contentDoc = frame.contentWindow.document; let { body } = contentDoc; body.id = "_LibreJS_dialog"; body.innerHTML = `

LibreJS Complaint

`; contentDoc.documentElement.appendChild(contentDoc.createElement("base")).target = "_top"; let content = body.appendChild(contentDoc.createElement("div")); content.id = "content"; let addHTML = s => content.insertAdjacentHTML("beforeend", s); if ("fail" in res) { content.classList.toggle("_LibreJS_fail", true) addHTML("
Could not guess any contact page for this site.
"); } else { addHTML("

Contact info guessed for this site

"); if (typeof (res[1]) === "string") { let a = contentDoc.createElement("a"); a.href = a.textContent = res[1]; content.appendChild(a); } else if (typeof (res[1]) === "object") { addHTML(`${res[0]}: ${res[1].outerHTML}`); } } let emails = document.documentElement.textContent.match(email_regex); if (emails && (emails = Array.filter(emails, e => !!e)).length) { addHTML("
Possible email addresses:
"); let list = contentDoc.createElement("ul"); for (let i = 0, max = Math.min(emails.length, 10); i < max; i++) { let recipient = emails[i]; let a = contentDoc.createElement("a"); a.href = `mailto:${recipient}?subject${encodeURIComponent(prefs["pref_subject"]) }&body=${encodeURIComponent(prefs["pref_body"]) }`; a.textContent = recipient; list.appendChild(contentDoc.createElement("li")).appendChild(a); } content.appendChild(list); } contentDoc.querySelectorAll(".close, a").forEach(makeCloser); debug("frame initialized"); } frame.addEventListener("load", e => { debug("frame loaded"); myPort = browser.runtime.connect({ name: "contact_finder" }).onMessage.addListener(m => { prefs = m; initFrame(); }); }); } main();