From 055839e95923cda3ca1abef0b38f0c3a7423d220 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20=C5=81ukasik?= Date: Mon, 3 Jun 2019 16:15:06 +0200 Subject: [PATCH 1/6] Add eslint to the project Add .eslintrc with best practices and simply update js code to meet current standards --- .eslintrc | 183 ++++++++++++++++++++++++++++++++++++++++++ background-script.js | 186 ++++++++++++++++++++++--------------------- 2 files changed, 278 insertions(+), 91 deletions(-) create mode 100644 .eslintrc diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..a6fa254 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,183 @@ + +{ + "env": { + "browser": true, + "es6": true + }, + "parserOptions": { + "ecmaVersion": 6, + "ecmaFeatures": { + "modules": true + } + }, + "extends": [ + "eslint:recommended" + ], + "rules": { + // Possible Errors + "comma-dangle": [2, "never"], + "computed-property-spacing": [2, "never"], + "no-cond-assign": 2, + "no-console": 0, // allow console + "no-constant-condition": 2, + "no-control-regex": 2, + "no-debugger": 2, + "no-dupe-keys": 2, + "no-empty": 2, + "no-empty-character-class": 2, + "no-ex-assign": 2, + "no-extra-boolean-cast": 2, + "no-extra-parens": 0, + "no-extra-semi": 2, + "no-func-assign": 2, + "no-inner-declarations": 2, + "no-invalid-regexp": 2, + "no-irregular-whitespace": 2, + "no-negated-in-lhs": 2, + "no-obj-calls": 2, + "no-regex-spaces": 2, + "no-reserved-keys": 0, + "no-sparse-arrays": 2, + "no-unreachable": 2, + "use-isnan": 2, + "valid-jsdoc": 0, + "valid-typeof": 2, + // Best Practices + "block-scoped-var": 2, + "complexity": 0, + "consistent-return": 2, + "curly": 2, + "default-case": 2, + "dot-notation": 2, + "eqeqeq": 2, + "guard-for-in": 2, + "no-alert": 2, + "no-caller": 2, + "no-confusing-arrow": 2, + "no-div-regex": 2, + "no-else-return": 2, + "no-eq-null": 2, + "no-eval": 2, + "no-extend-native": 2, + "no-extra-bind": 2, + "no-fallthrough": 2, + "no-floating-decimal": 2, + "no-implied-eval": 2, + "no-iterator": 2, + "no-labels": 2, + "no-lone-blocks": 2, + "no-loop-func": 2, + "no-multi-spaces": 2, + "no-multi-str": 2, + "no-native-reassign": 2, + "no-new": 2, + "no-new-func": 2, + "no-new-wrappers": 2, + "no-octal": 2, + "no-octal-escape": 2, + "no-mixed-operators": 2, + "no-process-env": 2, + "no-proto": 2, + "no-redeclare": 2, + "no-return-assign": 2, + "no-script-url": 2, + "no-self-compare": 2, + "no-sequences": 2, + "no-unused-expressions": 2, + "no-void": 0, + "no-warning-comments": 2, + "no-with": 2, + "prefer-arrow-callback": 2, + "radix": 2, + "vars-on-top": 0, + "wrap-iife": 2, + "yoda": 2, + // Strict Mode + "strict": [2, "global"], + // Variables + "prefer-const": 2, + "no-catch-shadow": 2, + "no-const-assign": 2, + "no-delete-var": 2, + "no-label-var": 2, + "no-shadow": 2, + "no-shadow-restricted-names": 2, + "no-undef": 2, + "no-undef-init": 2, + "no-undefined": 2, + "no-unused-vars": 2, + "no-use-before-define": 2, + "no-var": 2, + // Stylistic Issues + "indent": [2, 2, { + "SwitchCase": 1 + }], + "arrow-body-style": [2, "as-needed"], + "arrow-parens": [2, "as-needed"], + "arrow-spacing": 2, + "brace-style": 2, + "camelcase": 0, + "comma-spacing": 2, + "comma-style": 2, + "consistent-this": 0, + "eol-last": 2, + "func-names": 0, + "func-style": 0, + "key-spacing": [2, { + "beforeColon": false, + "afterColon": true + }], + "max-nested-callbacks": 0, + "new-cap": 2, + "new-parens": 2, + "no-array-constructor": 2, + "no-inline-comments": 0, + "no-lonely-if": 2, + "no-mixed-spaces-and-tabs": 2, + "no-nested-ternary": 2, + "no-new-object": 2, + "semi-spacing": [2, { + "before": false, + "after": true + }], + "no-spaced-func": 2, + "no-ternary": 0, + "no-trailing-spaces": 2, + "no-multiple-empty-lines": 2, + "no-underscore-dangle": 0, + "one-var": 0, + "operator-assignment": [2, "always"], + "padded-blocks": [2, { "blocks": "never", "classes": "never", "switches": "never" }], + "quotes": [2, "single"], + "quote-props": [2, "as-needed"], + "semi": [2, "always"], + "sort-vars": [2, {"ignoreCase": true}], + "keyword-spacing": 2, + "space-before-blocks": 2, + "object-curly-spacing": [2, "never"], + "array-bracket-spacing": [2, "never"], + "space-in-parens": 2, + "space-infix-ops": 2, + "space-unary-ops": 2, + "spaced-comment": 2, + "wrap-regex": 0, + "space-before-function-paren": ["error", { + "anonymous": "always", + "named": "never", + "asyncArrow": "always" + }], + // Legacy + "max-depth": 0, + "max-len": [2, 80, { + "ignoreStrings": false, + "ignoreTemplateLiterals": false, + "ignoreComments": false + }], + "max-params": 0, + "max-statements": 0, + "no-plusplus": 0, + "no-prototype-builtins": 2, + "prefer-template": 2, + "template-curly-spacing": [2, "never"] + } +} \ No newline at end of file diff --git a/background-script.js b/background-script.js index 1fdb16a..dcc0af4 100644 --- a/background-script.js +++ b/background-script.js @@ -16,16 +16,44 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with DNSSEC-HSTS. If not, see . */ +/* global chrome, browser */ +'use strict'; +// Based on https://stackoverflow.com/a/45985333 +function onFirefox() { + if (typeof chrome !== 'undefined' && typeof browser !== 'undefined') { + return true; + } + return false; +} + +let compatBrowser; +// Firefox supports both browser and chrome; Chromium only supports chrome; +// Edge only supports browser. See https://stackoverflow.com/a/45985333 +if (typeof browser !== 'undefined') { + console.log('Testing for browser/chrome: browser'); + compatBrowser = browser; +} else { + console.log('Testing for browser/chrome: chrome'); + compatBrowser = chrome; +} + +// Only used with native messaging +let nativePort; +const pendingUpgradeChecks = new Map(); + +// host for match pattern for the URLs to upgrade +const matchHost = '*.bit'; +let currentRequestListener; -function queryUpgradeNative(requestDetails, resolve, reject) { +function queryUpgradeNative(requestDetails, resolve) { const url = new URL(requestDetails.url); const host = url.host; const hostname = url.hostname; const port = url.port; - if(! pendingUpgradeChecks.has(host)) { + if (!pendingUpgradeChecks.has(host)) { pendingUpgradeChecks.set(host, new Set()); - const message = {"host": host, "hostname": hostname, "port": port}; + const message = {host: host, hostname: hostname, port: port}; // Send message to the native DNSSEC app nativePort.postMessage(message); @@ -36,7 +64,7 @@ function queryUpgradeNative(requestDetails, resolve, reject) { // upgradeAsync function returns a Promise // which is resolved with the upgrade after the native DNSSEC app replies function upgradeAsync(requestDetails) { - var asyncCancel = new Promise((resolve, reject) => { + const asyncCancel = new Promise((resolve, reject) => { queryUpgradeNative(requestDetails, resolve, reject); }); @@ -46,17 +74,37 @@ function upgradeAsync(requestDetails) { // Adapted from Tagide/chrome-bit-domain-extension // Returns true if timed out, returns false if hostname showed up function sleep(milliseconds, queryFinishedRef) { - // synchronous XMLHttpRequests from Chrome extensions are not blocking event handlers. That's why we use this - // pretty little sleep function to try to get the API response before the request times out. - var start = new Date().getTime(); - for (var i = 0; i < 1e7; i++) { + // synchronous XMLHttpRequests from Chrome extensions are not blocking event + // handlers. That's why we use this + // pretty little sleep function to try to get the API response before the + // request times out. + const start = new Date().getTime(); + for (let i = 0; i < 1e7; i++) { if ((new Date().getTime() - start) > milliseconds) { return true; } - if (queryFinishedRef["val"]) { + if (queryFinishedRef.val) { return false; } } + return true; +} + +function buildBlockingResponse(url, upgrade, lookupError) { + if (lookupError) { + return {redirectUrl: + compatBrowser.runtime.getURL('/pages/lookup_error/index.html')}; + } + if (upgrade) { + if (onFirefox()) { + return {upgradeToSecure: true}; + } + url.protocol = 'https:'; + // Chromium and Edge don't support "upgradeToSecure", + // so we use "redirectUrl" instead + return {redirectUrl: url.toString()}; + } + return {}; } // Compatibility for Chromium/Edge, which don't support async onBeforeRequest @@ -65,51 +113,52 @@ function upgradeSync(requestDetails) { const url = new URL(requestDetails.url); const host = url.host; const hostname = url.hostname; - const port = url.port; + // const port = url.port; - var certResponse; - var queryFinishedRef = {"val": false}; + let certResponse; + const queryFinishedRef = {val: false}; - var upgrade = false; - var lookupError = false; + let upgrade = false; + let lookupError = false; // Adapted from Tagide/chrome-bit-domain-extension // Get the TLSA records from the API - var xhr = new XMLHttpRequest(); - var apiUrl = "http://127.0.0.1:8080/lookup?domain="+encodeURIComponent(hostname); + const xhr = new XMLHttpRequest(); + const apiUrl = + `http://127.0.0.1:8080/lookup?domain=${encodeURIComponent(hostname)}`; // synchronous XMLHttpRequest is actually asynchronous // check out https://developer.chrome.com/extensions/webRequest - xhr.open("GET", apiUrl, false); - xhr.onreadystatechange = function() { - if (xhr.readyState == 4) { - if (xhr.status != 200) { - console.log("Error received from API: status " + xhr.status); + xhr.open('GET', apiUrl, false); + xhr.onreadystatechange = function () { + if (xhr.readyState === 4) { + if (xhr.status !== 200) { + console.log(`Error received from API: status ${xhr.status}`); lookupError = true; } // Get the certs returned from the API server. certResponse = xhr.responseText; // Notify the sleep function that we're ready to proceed - queryFinishedRef["val"] = true; + queryFinishedRef.val = true; } - } + }; try { xhr.send(); } catch (e) { - console.log("Error reaching API: " + e.toString()); + console.log(`Error reaching API: ${e.toString()}`); lookupError = true; } // block the request until the API response is received. Block for up to two // seconds. if (sleep(2000, queryFinishedRef)) { - console.log("API timed out"); + console.log('API timed out'); lookupError = true; } // Check if any certs exist in the result - var result = certResponse; - if (result.trim() != "") { - console.log("Upgraded via TLSA: " + host); + const result = certResponse; + if (result.trim() !== '') { + console.log(`Upgraded via TLSA: ${host}`); upgrade = true; } @@ -119,24 +168,13 @@ function upgradeSync(requestDetails) { function upgradeCompat(requestDetails) { if (onFirefox()) { return upgradeAsync(requestDetails); - } else { - return upgradeSync(requestDetails); } + return upgradeSync(requestDetails); } -function buildBlockingResponse(url, upgrade, lookupError) { - if (lookupError) { - return {"redirectUrl": compatBrowser.runtime.getURL("/pages/lookup_error/index.html")}; - } - if (upgrade) { - if (onFirefox()) { - return {"upgradeToSecure": true}; - } - url.protocol = "https:"; - // Chromium and Edge don't support "upgradeToSecure", so we use "redirectUrl" instead - return {"redirectUrl": url.toString()}; - } - return {}; +// Builds a match pattern for all HTTP URL's for the specified host +function buildPattern(host) { + return `http://${host}/*`; } // Only use this on initial extension startup; afterwards you should use @@ -145,7 +183,7 @@ function attachRequestListener() { // This shim function is a hack so that we can add a new listener before we // remove the old one. In theory JavaScript's single-threaded nature makes // that irrelevant, but I don't trust browsers to behave sanely on this. - currentRequestListener = function(requestDetails) { + currentRequestListener = function (requestDetails) { return upgradeCompat(requestDetails); }; @@ -154,54 +192,22 @@ function attachRequestListener() { compatBrowser.webRequest.onBeforeRequest.addListener( currentRequestListener, {urls: [buildPattern(matchHost)]}, - ["blocking"] + ['blocking'] ); } // Attaches a new listener based on the current matchHost, and then removes the // old listener. The ordering is intended to prevent race conditions where the // protection is disabled. -function resetRequestListener() { - var oldListener = currentRequestListener; +// function resetRequestListener() { +// const oldListener = currentRequestListener; - attachRequestListener(); +// attachRequestListener(); - compatBrowser.webRequest.onBeforeRequest.removeListener(oldListener); -} - -// Builds a match pattern for all HTTP URL's for the specified host -function buildPattern(host) { - return "http://" + host + "/*"; -} +// compatBrowser.webRequest.onBeforeRequest.removeListener(oldListener); +// } -// Based on https://stackoverflow.com/a/45985333 -function onFirefox() { - if (typeof chrome !== "undefined" && typeof browser !== "undefined") { - return true; - } - return false; -} - -console.log("Testing for Firefox: " + onFirefox()); - -var compatBrowser; -// Firefox supports both browser and chrome; Chromium only supports chrome; -// Edge only supports browser. See https://stackoverflow.com/a/45985333 -if (typeof browser !== "undefined") { - console.log("Testing for browser/chrome: browser"); - compatBrowser = browser; -} else { - console.log("Testing for browser/chrome: chrome"); - compatBrowser = chrome; -} - -// Only used with native messaging -var nativePort; -var pendingUpgradeChecks = new Map(); - -// host for match pattern for the URLs to upgrade -var matchHost = "*.bit"; -var currentRequestListener; +console.log(`Testing for Firefox: ${onFirefox()}`); // Firefox is the only browser that supports async onBeforeRequest, and // therefore is the only browser that we can use native messaging with. @@ -209,25 +215,23 @@ if (onFirefox()) { /* On startup, connect to the Namecoin "dnssec_hsts" app. */ - nativePort = compatBrowser.runtime.connectNative("org.namecoin.dnssec_hsts"); + nativePort = compatBrowser.runtime.connectNative('org.namecoin.dnssec_hsts'); /* Listen for messages from the native DNSSEC app. */ - nativePort.onMessage.addListener((response) => { - const host = response["host"]; - const hasTLSA = response["hasTLSA"]; - const ok = response["ok"]; + nativePort.onMessage.addListener(response => { + const {host, hasTLSA, ok} = response; if (!ok) { - console.log("Native DNSSEC app error: " + host); + console.log(`Native DNSSEC app error: ${host}`); } - if(! pendingUpgradeChecks.has(host)) { + if (!pendingUpgradeChecks.has(host)) { return; } - for (let item of pendingUpgradeChecks.get(host)) { + for (const item of pendingUpgradeChecks.get(host)) { item(buildBlockingResponse(null, hasTLSA, !ok)); } From fdadd4e525a093fd537a4752744d66fa08f634c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20=C5=81ukasik?= Date: Mon, 3 Jun 2019 16:27:30 +0200 Subject: [PATCH 2/6] Remove unused code - remove unused resetRequestListener --- background-script.js | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/background-script.js b/background-script.js index dcc0af4..64e9f4a 100644 --- a/background-script.js +++ b/background-script.js @@ -196,17 +196,6 @@ function attachRequestListener() { ); } -// Attaches a new listener based on the current matchHost, and then removes the -// old listener. The ordering is intended to prevent race conditions where the -// protection is disabled. -// function resetRequestListener() { -// const oldListener = currentRequestListener; - -// attachRequestListener(); - -// compatBrowser.webRequest.onBeforeRequest.removeListener(oldListener); -// } - console.log(`Testing for Firefox: ${onFirefox()}`); // Firefox is the only browser that supports async onBeforeRequest, and From 394dd88f062c0e8e1f8471fe3b9a7f4aeb802b67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20=C5=81ukasik?= Date: Mon, 3 Jun 2019 17:11:35 +0200 Subject: [PATCH 3/6] General refactorization - Remove currentRequestListener artifact - Use consts for crucial settings - Refactor names --- background-script.js | 52 +++++++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/background-script.js b/background-script.js index 64e9f4a..192698f 100644 --- a/background-script.js +++ b/background-script.js @@ -37,13 +37,24 @@ if (typeof browser !== 'undefined') { compatBrowser = chrome; } +const pages = { + error: compatBrowser.runtime.getURL('/pages/lookup_error/index.html') +}; +const httpLookupApiUrl = 'http://127.0.0.1:8080/lookup'; +const nativeLookupAppName = 'org.namecoin.dnssec_hsts'; + // Only used with native messaging let nativePort; const pendingUpgradeChecks = new Map(); // host for match pattern for the URLs to upgrade const matchHost = '*.bit'; -let currentRequestListener; +let communicationType; +if (onFirefox()) { + communicationType = 'native'; +} else { + communicationType = 'sync_over_http'; +} function queryUpgradeNative(requestDetails, resolve) { const url = new URL(requestDetails.url); @@ -63,12 +74,10 @@ function queryUpgradeNative(requestDetails, resolve) { // upgradeAsync function returns a Promise // which is resolved with the upgrade after the native DNSSEC app replies -function upgradeAsync(requestDetails) { - const asyncCancel = new Promise((resolve, reject) => { +function upgradeAsyncNative(requestDetails) { + return new Promise((resolve, reject) => { queryUpgradeNative(requestDetails, resolve, reject); }); - - return asyncCancel; } // Adapted from Tagide/chrome-bit-domain-extension @@ -92,8 +101,7 @@ function sleep(milliseconds, queryFinishedRef) { function buildBlockingResponse(url, upgrade, lookupError) { if (lookupError) { - return {redirectUrl: - compatBrowser.runtime.getURL('/pages/lookup_error/index.html')}; + return {redirectUrl: pages.error}; } if (upgrade) { if (onFirefox()) { @@ -109,7 +117,7 @@ function buildBlockingResponse(url, upgrade, lookupError) { // Compatibility for Chromium/Edge, which don't support async onBeforeRequest // See Chromium Bug 904365 -function upgradeSync(requestDetails) { +function upgradeSyncOverHttp(requestDetails) { const url = new URL(requestDetails.url); const host = url.host; const hostname = url.hostname; @@ -124,8 +132,7 @@ function upgradeSync(requestDetails) { // Adapted from Tagide/chrome-bit-domain-extension // Get the TLSA records from the API const xhr = new XMLHttpRequest(); - const apiUrl = - `http://127.0.0.1:8080/lookup?domain=${encodeURIComponent(hostname)}`; + const apiUrl = `${httpLookupApiUrl}?domain=${encodeURIComponent(hostname)}`; // synchronous XMLHttpRequest is actually asynchronous // check out https://developer.chrome.com/extensions/webRequest xhr.open('GET', apiUrl, false); @@ -166,10 +173,12 @@ function upgradeSync(requestDetails) { } function upgradeCompat(requestDetails) { - if (onFirefox()) { - return upgradeAsync(requestDetails); + switch (communicationType) { + case 'native': + return upgradeAsyncNative(requestDetails); + default: + return upgradeSyncOverHttp(requestDetails); } - return upgradeSync(requestDetails); } // Builds a match pattern for all HTTP URL's for the specified host @@ -180,17 +189,10 @@ function buildPattern(host) { // Only use this on initial extension startup; afterwards you should use // resetRequestListener instead. function attachRequestListener() { - // This shim function is a hack so that we can add a new listener before we - // remove the old one. In theory JavaScript's single-threaded nature makes - // that irrelevant, but I don't trust browsers to behave sanely on this. - currentRequestListener = function (requestDetails) { - return upgradeCompat(requestDetails); - }; - // add the listener, // passing the filter argument and "blocking" compatBrowser.webRequest.onBeforeRequest.addListener( - currentRequestListener, + upgradeCompat, {urls: [buildPattern(matchHost)]}, ['blocking'] ); @@ -200,11 +202,11 @@ console.log(`Testing for Firefox: ${onFirefox()}`); // Firefox is the only browser that supports async onBeforeRequest, and // therefore is the only browser that we can use native messaging with. -if (onFirefox()) { +if (communicationType === 'native') { /* On startup, connect to the Namecoin "dnssec_hsts" app. */ - nativePort = compatBrowser.runtime.connectNative('org.namecoin.dnssec_hsts'); + nativePort = compatBrowser.runtime.connectNative(nativeLookupAppName); /* Listen for messages from the native DNSSEC app. @@ -220,8 +222,8 @@ if (onFirefox()) { return; } - for (const item of pendingUpgradeChecks.get(host)) { - item(buildBlockingResponse(null, hasTLSA, !ok)); + for (const callback of pendingUpgradeChecks.get(host)) { + callback(buildBlockingResponse(null, hasTLSA, !ok)); } pendingUpgradeChecks.delete(host); From e723c3e5ab2d7af692d89587657ec67e0c64e137 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20=C5=81ukasik?= Date: Mon, 3 Jun 2019 17:17:05 +0200 Subject: [PATCH 4/6] Code reorganization - change code order --- background-script.js | 49 ++++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/background-script.js b/background-script.js index 192698f..c83f052 100644 --- a/background-script.js +++ b/background-script.js @@ -56,6 +56,11 @@ if (onFirefox()) { communicationType = 'sync_over_http'; } +// Builds a match pattern for all HTTP URL's for the specified host +function buildPattern(host) { + return `http://${host}/*`; +} + function queryUpgradeNative(requestDetails, resolve) { const url = new URL(requestDetails.url); const host = url.host; @@ -72,14 +77,6 @@ function queryUpgradeNative(requestDetails, resolve) { pendingUpgradeChecks.get(host).add(resolve); } -// upgradeAsync function returns a Promise -// which is resolved with the upgrade after the native DNSSEC app replies -function upgradeAsyncNative(requestDetails) { - return new Promise((resolve, reject) => { - queryUpgradeNative(requestDetails, resolve, reject); - }); -} - // Adapted from Tagide/chrome-bit-domain-extension // Returns true if timed out, returns false if hostname showed up function sleep(milliseconds, queryFinishedRef) { @@ -172,6 +169,14 @@ function upgradeSyncOverHttp(requestDetails) { return buildBlockingResponse(url, upgrade, lookupError); } +// upgradeAsync function returns a Promise +// which is resolved with the upgrade after the native DNSSEC app replies +function upgradeAsyncNative(requestDetails) { + return new Promise((resolve, reject) => { + queryUpgradeNative(requestDetails, resolve, reject); + }); +} + function upgradeCompat(requestDetails) { switch (communicationType) { case 'native': @@ -181,23 +186,6 @@ function upgradeCompat(requestDetails) { } } -// Builds a match pattern for all HTTP URL's for the specified host -function buildPattern(host) { - return `http://${host}/*`; -} - -// Only use this on initial extension startup; afterwards you should use -// resetRequestListener instead. -function attachRequestListener() { - // add the listener, - // passing the filter argument and "blocking" - compatBrowser.webRequest.onBeforeRequest.addListener( - upgradeCompat, - {urls: [buildPattern(matchHost)]}, - ['blocking'] - ); -} - console.log(`Testing for Firefox: ${onFirefox()}`); // Firefox is the only browser that supports async onBeforeRequest, and @@ -230,4 +218,15 @@ if (communicationType === 'native') { }); } +// Only use this on initial extension startup; afterwards you should use +// resetRequestListener instead. +function attachRequestListener() { + // add the listener, + // passing the filter argument and "blocking" + compatBrowser.webRequest.onBeforeRequest.addListener( + upgradeCompat, + {urls: [buildPattern(matchHost)]}, + ['blocking'] + ); +} attachRequestListener(); From ea5bacca14675f61ff703c0cfe812c730cf1dedc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20=C5=81ukasik?= Date: Mon, 3 Jun 2019 23:14:41 +0200 Subject: [PATCH 5/6] Add support for asyncBlocking in Chromium - Add support for asyncBlocking in chromium with fallback to sync HTTP lookup - Use native communication by default --- background-script.js | 82 ++++++++++++++++++++++++++++---------------- 1 file changed, 52 insertions(+), 30 deletions(-) diff --git a/background-script.js b/background-script.js index c83f052..b9093e4 100644 --- a/background-script.js +++ b/background-script.js @@ -18,22 +18,20 @@ along with DNSSEC-HSTS. If not, see . */ /* global chrome, browser */ 'use strict'; -// Based on https://stackoverflow.com/a/45985333 -function onFirefox() { - if (typeof chrome !== 'undefined' && typeof browser !== 'undefined') { - return true; - } - return false; -} let compatBrowser; +// let isFirefox; +let isChrome; + // Firefox supports both browser and chrome; Chromium only supports chrome; // Edge only supports browser. See https://stackoverflow.com/a/45985333 if (typeof browser !== 'undefined') { console.log('Testing for browser/chrome: browser'); + // isFirefox = true; compatBrowser = browser; } else { console.log('Testing for browser/chrome: chrome'); + isChrome = true; compatBrowser = chrome; } @@ -49,12 +47,7 @@ const pendingUpgradeChecks = new Map(); // host for match pattern for the URLs to upgrade const matchHost = '*.bit'; -let communicationType; -if (onFirefox()) { - communicationType = 'native'; -} else { - communicationType = 'sync_over_http'; -} +let communicationType = 'native'; // Builds a match pattern for all HTTP URL's for the specified host function buildPattern(host) { @@ -74,7 +67,10 @@ function queryUpgradeNative(requestDetails, resolve) { // Send message to the native DNSSEC app nativePort.postMessage(message); } - pendingUpgradeChecks.get(host).add(resolve); + pendingUpgradeChecks.get(host).add({ + url: url, + callback: resolve + }); } // Adapted from Tagide/chrome-bit-domain-extension @@ -101,7 +97,7 @@ function buildBlockingResponse(url, upgrade, lookupError) { return {redirectUrl: pages.error}; } if (upgrade) { - if (onFirefox()) { + if (!isChrome) { return {upgradeToSecure: true}; } url.protocol = 'https:'; @@ -177,20 +173,24 @@ function upgradeAsyncNative(requestDetails) { }); } -function upgradeCompat(requestDetails) { +function upgradeCompat(requestDetails, chromiumAsyncResolve) { switch (communicationType) { case 'native': + if (isChrome) { + if (typeof chromiumAsyncResolve === 'function') { + upgradeAsyncNative(requestDetails).then(chromiumAsyncResolve); + return false; + } + // chromiumAsyncResolve not found, fallback to sync HTTP + return upgradeSyncOverHttp(requestDetails); + } return upgradeAsyncNative(requestDetails); default: return upgradeSyncOverHttp(requestDetails); } } -console.log(`Testing for Firefox: ${onFirefox()}`); - -// Firefox is the only browser that supports async onBeforeRequest, and -// therefore is the only browser that we can use native messaging with. -if (communicationType === 'native') { +function connectNative() { /* On startup, connect to the Namecoin "dnssec_hsts" app. */ @@ -210,23 +210,45 @@ if (communicationType === 'native') { return; } - for (const callback of pendingUpgradeChecks.get(host)) { - callback(buildBlockingResponse(null, hasTLSA, !ok)); + for (const query of pendingUpgradeChecks.get(host)) { + query.callback(buildBlockingResponse(query.url, hasTLSA, !ok)); } - pendingUpgradeChecks.delete(host); }); } -// Only use this on initial extension startup; afterwards you should use -// resetRequestListener instead. +function getExtraInfoSpecOptional() { + const extraInfoSpecOptional = ['blocking']; + if (isChrome && communicationType === 'native') { + extraInfoSpecOptional[0] = 'asyncBlocking'; + let validated = false; + extraInfoSpecOptional.__defineGetter__(0, () => { + if (!validated) { + validated = true; + return 'blocking'; + } + return 'asyncBlocking'; + }); + } + return extraInfoSpecOptional; +} + function attachRequestListener() { - // add the listener, - // passing the filter argument and "blocking" compatBrowser.webRequest.onBeforeRequest.addListener( upgradeCompat, {urls: [buildPattern(matchHost)]}, - ['blocking'] + getExtraInfoSpecOptional() ); } -attachRequestListener(); + +try { + if (communicationType === 'native') { + connectNative(); + } + attachRequestListener(); +} catch (e) { + console.warn( + 'Exception while attaching listener, fallback to sync HTTP lookup', e); + communicationType = 'sync_over_http'; + attachRequestListener(); +} From c2d64cd206dbd6ce1f5825a8ef13ab6dff105c4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20=C5=81ukasik?= Date: Mon, 3 Jun 2019 23:27:38 +0200 Subject: [PATCH 6/6] Refactorization - change names from 'compat' to 'unified' - remove unnecessary functions - try to keep vars alphabetically ordered - use compact notation - remove magic numbers - use semantic console output --- background-script.js | 77 +++++++++++++++++++------------------------- 1 file changed, 34 insertions(+), 43 deletions(-) diff --git a/background-script.js b/background-script.js index b9093e4..be6f157 100644 --- a/background-script.js +++ b/background-script.js @@ -19,7 +19,7 @@ along with DNSSEC-HSTS. If not, see . /* global chrome, browser */ 'use strict'; -let compatBrowser; +let unifiedBrowser; // let isFirefox; let isChrome; @@ -28,41 +28,31 @@ let isChrome; if (typeof browser !== 'undefined') { console.log('Testing for browser/chrome: browser'); // isFirefox = true; - compatBrowser = browser; + unifiedBrowser = browser; } else { console.log('Testing for browser/chrome: chrome'); isChrome = true; - compatBrowser = chrome; + unifiedBrowser = chrome; } -const pages = { - error: compatBrowser.runtime.getURL('/pages/lookup_error/index.html') -}; const httpLookupApiUrl = 'http://127.0.0.1:8080/lookup'; +const matchHostPattern = 'http://*.bit/*'; const nativeLookupAppName = 'org.namecoin.dnssec_hsts'; - -// Only used with native messaging -let nativePort; +const pages = { + error: unifiedBrowser.runtime.getURL('/pages/lookup_error/index.html') +}; const pendingUpgradeChecks = new Map(); - -// host for match pattern for the URLs to upgrade -const matchHost = '*.bit'; let communicationType = 'native'; +let nativePort; // Only used with native messaging -// Builds a match pattern for all HTTP URL's for the specified host -function buildPattern(host) { - return `http://${host}/*`; -} function queryUpgradeNative(requestDetails, resolve) { const url = new URL(requestDetails.url); - const host = url.host; - const hostname = url.hostname; - const port = url.port; + const {host, hostname, port} = url; if (!pendingUpgradeChecks.has(host)) { pendingUpgradeChecks.set(host, new Set()); - const message = {host: host, hostname: hostname, port: port}; + const message = {host, hostname, port}; // Send message to the native DNSSEC app nativePort.postMessage(message); @@ -77,19 +67,22 @@ function queryUpgradeNative(requestDetails, resolve) { // Returns true if timed out, returns false if hostname showed up function sleep(milliseconds, queryFinishedRef) { // synchronous XMLHttpRequests from Chrome extensions are not blocking event - // handlers. That's why we use this - // pretty little sleep function to try to get the API response before the - // request times out. - const start = new Date().getTime(); - for (let i = 0; i < 1e7; i++) { - if ((new Date().getTime() - start) > milliseconds) { - return true; + // handlers. That's why we use this pretty little sleep function to try to get + // the API response before the request times out. + const start = Date.now(); + let lock = true; + let timeout; + do { + if ((Date.now() - start) > milliseconds) { + timeout = true; + lock = false; } if (queryFinishedRef.val) { - return false; + timeout = false; + lock = false; } - } - return true; + } while (lock); + return timeout; } function buildBlockingResponse(url, upgrade, lookupError) { @@ -112,9 +105,7 @@ function buildBlockingResponse(url, upgrade, lookupError) { // See Chromium Bug 904365 function upgradeSyncOverHttp(requestDetails) { const url = new URL(requestDetails.url); - const host = url.host; - const hostname = url.hostname; - // const port = url.port; + const {host, hostname} = url; let certResponse; const queryFinishedRef = {val: false}; @@ -132,20 +123,20 @@ function upgradeSyncOverHttp(requestDetails) { xhr.onreadystatechange = function () { if (xhr.readyState === 4) { if (xhr.status !== 200) { - console.log(`Error received from API: status ${xhr.status}`); + console.error(`Error received from API: status ${xhr.status}`); lookupError = true; } - // Get the certs returned from the API server. certResponse = xhr.responseText; // Notify the sleep function that we're ready to proceed queryFinishedRef.val = true; } }; + try { xhr.send(); } catch (e) { - console.log(`Error reaching API: ${e.toString()}`); + console.error(`Error reaching API: ${e.toString()}`); lookupError = true; } // block the request until the API response is received. Block for up to two @@ -157,8 +148,8 @@ function upgradeSyncOverHttp(requestDetails) { // Check if any certs exist in the result const result = certResponse; - if (result.trim() !== '') { - console.log(`Upgraded via TLSA: ${host}`); + if (result.trim()) { + console.info(`Upgraded via TLSA: ${host}`); upgrade = true; } @@ -173,7 +164,7 @@ function upgradeAsyncNative(requestDetails) { }); } -function upgradeCompat(requestDetails, chromiumAsyncResolve) { +function upgradeUnified(requestDetails, chromiumAsyncResolve) { switch (communicationType) { case 'native': if (isChrome) { @@ -194,7 +185,7 @@ function connectNative() { /* On startup, connect to the Namecoin "dnssec_hsts" app. */ - nativePort = compatBrowser.runtime.connectNative(nativeLookupAppName); + nativePort = unifiedBrowser.runtime.connectNative(nativeLookupAppName); /* Listen for messages from the native DNSSEC app. @@ -234,9 +225,9 @@ function getExtraInfoSpecOptional() { } function attachRequestListener() { - compatBrowser.webRequest.onBeforeRequest.addListener( - upgradeCompat, - {urls: [buildPattern(matchHost)]}, + unifiedBrowser.webRequest.onBeforeRequest.addListener( + upgradeUnified, + {urls: [matchHostPattern]}, getExtraInfoSpecOptional() ); }