Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Per-site Redirect Opt-out #687

Merged
merged 11 commits into from
Mar 5, 2019
Prev Previous commit
Next Next commit
feat(options): UI for editing redirect opt-outs
This adds a textarea for editing per-site opt-outs.
Array is converted into multi-line text.
Entries that are not valid FQDNs are dropped.
The list is sorted lexicographically.
  • Loading branch information
lidel committed Feb 24, 2019
commit 86f5fcf33efd0a7c5422415b358b551863af0c0a
8 changes: 8 additions & 0 deletions add-on/_locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -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)"
Expand Down
21 changes: 21 additions & 0 deletions add-on/src/lib/options.js
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -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'
Expand Down
61 changes: 40 additions & 21 deletions add-on/src/options/forms/gateways-form.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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`
<form>
<fieldset>
<legend>${browser.i18n.getMessage('option_header_gateways')}</legend>
${ipfsNodeType === 'external' ? html`
<div>
<label for="publicGatewayUrl">
<dl>
<dt>${browser.i18n.getMessage('option_publicGatewayUrl_title')}</dt>
<dd>${browser.i18n.getMessage('option_publicGatewayUrl_description')}</dd>
</dl>
</label>
<input
id="publicGatewayUrl"
type="url"
inputmode="url"
required
pattern="^https?://[^/]+/?$"
spellcheck="false"
title="Enter URL without any sub-path"
onchange=${onPublicGatewayUrlChange}
value=${publicGatewayUrl} />
</div>
${supportRedirectToCustomGateway ? html`
<div>
<label for="customGatewayUrl">
<dl>
Expand All @@ -48,7 +69,7 @@ function gatewaysForm ({

</div>
` : null}
${ipfsNodeType === 'external' ? html`
${supportRedirectToCustomGateway ? html`
<div>
<label for="useCustomGateway">
<dl>
Expand All @@ -63,24 +84,22 @@ function gatewaysForm ({
checked=${useCustomGateway} />
</div>
` : null}
<div>
<label for="publicGatewayUrl">
<dl>
<dt>${browser.i18n.getMessage('option_publicGatewayUrl_title')}</dt>
<dd>${browser.i18n.getMessage('option_publicGatewayUrl_description')}</dd>
</dl>
</label>
<input
id="publicGatewayUrl"
type="url"
inputmode="url"
required
pattern="^https?://[^/]+/?$"
spellcheck="false"
title="Enter URL without any sub-path"
onchange=${onPublicGatewayUrlChange}
value=${publicGatewayUrl} />
</div>
${supportRedirectToCustomGateway ? html`
<div>
<label for="noRedirectHostnames">
<dl>
<dt>${browser.i18n.getMessage('option_noRedirectHostnames_title')}</dt>
<dd>${browser.i18n.getMessage('option_noRedirectHostnames_description')}</dd>
</dl>
</label>
<textarea
id="noRedirectHostnames"
spellcheck="false"
onchange=${onNoRedirectHostnamesChange}
rows="4"
>${hostArrayToText(noRedirectHostnames)}</textarea>
</div>
` : null}
</fieldset>
</form>
`
Expand Down
2 changes: 1 addition & 1 deletion add-on/src/options/forms/ipfs-node-form.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ function ipfsNodeForm ({ ipfsNodeType, ipfsNodeConfig, onOptionChange }) {
<dd>${browser.i18n.getMessage('option_ipfsNodeConfig_description')}</dd>
</dl>
</label>
<textarea id="ipfsNodeConfig" rows="4" onchange=${onIpfsNodeConfigChange}>${ipfsNodeConfig}</textarea>
<textarea id="ipfsNodeConfig" rows="7" onchange=${onIpfsNodeConfigChange}>${ipfsNodeConfig}</textarea>
</div>
` : null}
</fieldset>
Expand Down
1 change: 1 addition & 0 deletions add-on/src/options/page.js
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
2 changes: 1 addition & 1 deletion add-on/src/popup/browser-action/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
19 changes: 18 additions & 1 deletion test/functional/lib/options.test.js
Original file line number Diff line number Diff line change
@@ -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(() => {
Expand Down Expand Up @@ -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)
})
})
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down