diff --git a/add-on/_locales/en/messages.json b/add-on/_locales/en/messages.json index 7ff048cab..faa83773b 100644 --- a/add-on/_locales/en/messages.json +++ b/add-on/_locales/en/messages.json @@ -263,6 +263,14 @@ "message": "Redirect requests for IPFS resources to the Custom gateway", "description": "An option description on the Preferences screen (option_useCustomGateway_description)" }, + "option_noRedirectHostnames_title": { + "message": "Redirect Opt-Outs", + "description": "An option title on the Preferences screen (option_noRedirectHostnames_title)" + }, + "option_noRedirectHostnames_description": { + "message": "List of websites that should not be redirected to the Custom Gateway (includes subresources from other domains). One hostname per line.", + "description": "An option description on the Preferences screen (option_noRedirectHostnames_description)" + }, "option_publicGatewayUrl_title": { "message": "Default Public Gateway", "description": "An option title on the Preferences screen (option_publicGatewayUrl_title)" diff --git a/add-on/src/lib/options.js b/add-on/src/lib/options.js index 0b5f85139..1eaa4d257 100644 --- a/add-on/src/lib/options.js +++ b/add-on/src/lib/options.js @@ -1,5 +1,7 @@ 'use strict' +const isFQDN = require('is-fqdn') + exports.optionDefaults = Object.freeze({ active: true, // global ON/OFF switch, overrides everything else ipfsNodeType: 'external', // or 'embedded' @@ -65,6 +67,25 @@ function normalizeGatewayURL (url) { exports.normalizeGatewayURL = normalizeGatewayURL exports.safeURL = (url) => new URL(normalizeGatewayURL(url)) +// convert JS array to multiline textarea +function hostArrayToText (array) { + array = array.map(host => host.trim().toLowerCase()) + array = [...new Set(array)] // dedup + array = array.filter(Boolean).filter(isFQDN) + array.sort() + return array.join('\n') +} +// convert JS array to multiline textarea +function hostTextToArray (text) { + let array = text.split('\n').map(host => host.trim().toLowerCase()) + array = [...new Set(array)] // dedup + array = array.filter(Boolean).filter(isFQDN) + array.sort() + return array +} +exports.hostArrayToText = hostArrayToText +exports.hostTextToArray = hostTextToArray + exports.migrateOptions = async (storage) => { // <= v2.4.4 // DNSLINK: convert old on/off 'dnslink' flag to text-based 'dnslinkPolicy' diff --git a/add-on/src/options/forms/gateways-form.js b/add-on/src/options/forms/gateways-form.js index dddc87a41..df993b9cd 100644 --- a/add-on/src/options/forms/gateways-form.js +++ b/add-on/src/options/forms/gateways-form.js @@ -3,7 +3,7 @@ const browser = require('webextension-polyfill') const html = require('choo/html') -const { normalizeGatewayURL } = require('../../lib/options') +const { normalizeGatewayURL, hostTextToArray, hostArrayToText } = require('../../lib/options') // Warn about mixed content issues when changing the gateway // https://github.com/ipfs-shipyard/ipfs-companion/issues/648 @@ -13,19 +13,40 @@ function gatewaysForm ({ ipfsNodeType, customGatewayUrl, useCustomGateway, + noRedirectHostnames, publicGatewayUrl, onOptionChange }) { const onCustomGatewayUrlChange = onOptionChange('customGatewayUrl', normalizeGatewayURL) const onUseCustomGatewayChange = onOptionChange('useCustomGateway') const onPublicGatewayUrlChange = onOptionChange('publicGatewayUrl', normalizeGatewayURL) + const onNoRedirectHostnamesChange = onOptionChange('noRedirectHostnames', hostTextToArray) const mixedContentWarning = !secureContextUrl.test(customGatewayUrl) + const supportRedirectToCustomGateway = ipfsNodeType === 'external' return html`
${browser.i18n.getMessage('option_header_gateways')} - ${ipfsNodeType === 'external' ? html` +
+ + +
+ ${supportRedirectToCustomGateway ? html`
` : null} - ${ipfsNodeType === 'external' ? html` + ${supportRedirectToCustomGateway ? html`
` : null} -
- - -
+ ${supportRedirectToCustomGateway ? html` +
+ + +
+ ` : null}
` diff --git a/add-on/src/options/forms/ipfs-node-form.js b/add-on/src/options/forms/ipfs-node-form.js index 3a86b36f6..3ae72e8ce 100644 --- a/add-on/src/options/forms/ipfs-node-form.js +++ b/add-on/src/options/forms/ipfs-node-form.js @@ -46,7 +46,7 @@ function ipfsNodeForm ({ ipfsNodeType, ipfsNodeConfig, onOptionChange }) {
${browser.i18n.getMessage('option_ipfsNodeConfig_description')}
- + ` : null} diff --git a/add-on/src/options/page.js b/add-on/src/options/page.js index df0cb3ce0..ecba7bcda 100644 --- a/add-on/src/options/page.js +++ b/add-on/src/options/page.js @@ -60,6 +60,7 @@ module.exports = function optionsPage (state, emit) { customGatewayUrl: state.options.customGatewayUrl, useCustomGateway: state.options.useCustomGateway, publicGatewayUrl: state.options.publicGatewayUrl, + noRedirectHostnames: state.options.noRedirectHostnames, onOptionChange })} ${state.options.ipfsNodeType === 'external' ? apiForm({ diff --git a/add-on/src/popup/browser-action/store.js b/add-on/src/popup/browser-action/store.js index c0a1835e2..e38ca367e 100644 --- a/add-on/src/popup/browser-action/store.js +++ b/add-on/src/popup/browser-action/store.js @@ -184,7 +184,7 @@ module.exports = (state, emitter) => { } else { noRedirectHostnames.push(fqdn) } - console.dir('toggleSiteRedirect', state) + // console.dir('toggleSiteRedirect', state) await browser.storage.local.set({ noRedirectHostnames }) // Reload the current tab to apply updated redirect preference diff --git a/package.json b/package.json index 43b648e66..27f38e396 100644 --- a/package.json +++ b/package.json @@ -118,6 +118,7 @@ "ipfs-http-response": "0.2.2", "ipfs-postmsg-proxy": "3.1.1", "ipfsx": "0.17.0", + "is-fqdn": "1.0.1", "is-ipfs": "0.4.8", "is-svg": "3.0.0", "lru-cache": "5.1.1", diff --git a/test/functional/lib/options.test.js b/test/functional/lib/options.test.js index 1410924e9..e9d7c4f1b 100644 --- a/test/functional/lib/options.test.js +++ b/test/functional/lib/options.test.js @@ -1,8 +1,9 @@ 'use strict' const { describe, it, beforeEach, after } = require('mocha') +const { expect } = require('chai') const sinon = require('sinon') const browser = require('sinon-chrome') -const { storeMissingOptions, optionDefaults } = require('../../../add-on/src/lib/options') +const { storeMissingOptions, optionDefaults, hostTextToArray, hostArrayToText } = require('../../../add-on/src/lib/options') describe('storeMissingOptions()', function () { beforeEach(() => { @@ -66,3 +67,19 @@ describe('storeMissingOptions()', function () { browser.flush() }) }) + +describe('hostTextToArray()', function () { + it('should sort, dedup hostnames, drop non-FQDNs and produce an array', () => { + const text = `zombo.com\n two.com \n totally not a FQDN \none.pl \nTWO.com\n\n` + const array = ['one.pl', 'two.com', 'zombo.com'] + expect(hostTextToArray(text)).to.be.an('array').to.have.ordered.members(array) + }) +}) + +describe('hostArrayToText()', function () { + it('should sort, deduplicate, drop non-FQDNs and produce multiline string', () => { + const array = ['zombo.com ', 'two.com ', 'ONE.pl ', 'one.pl', 'totall not a FQDN', 'zombo.com'] + const text = `one.pl\ntwo.com\nzombo.com` + expect(hostArrayToText(array)).to.be.a('string').equal(text) + }) +}) diff --git a/yarn.lock b/yarn.lock index c0979d3d6..1e00a4058 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6664,6 +6664,11 @@ is-finite@^1.0.0: dependencies: number-is-nan "^1.0.0" +is-fqdn@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-fqdn/-/is-fqdn-1.0.1.tgz#f3ed9cd5a20238449ae510e10d81258dafca9b70" + integrity sha1-8+2c1aICOESa5RDhDYElja/Km3A= + is-fullwidth-code-point@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb"