diff --git a/add-on/_locales/en/messages.json b/add-on/_locales/en/messages.json index a923b424a..d65ab0eb9 100644 --- a/add-on/_locales/en/messages.json +++ b/add-on/_locales/en/messages.json @@ -63,13 +63,13 @@ "message": "Active Tab", "description": "A menu item in Browser Action pop-up (panel_activeTabSiteRedirectEnable)" }, - "panel_activeTabSiteRedirectToggle": { - "message": "Redirect on $1", - "description": "A menu item in Browser Action pop-up (panel_activeTabSiteRedirectToggle)" + "panel_activeTabSiteIntegrationsToggle": { + "message": "Enable on $1", + "description": "A menu item in Browser Action pop-up (panel_activeTabSiteIntegrationsToggle)" }, - "panel_activeTabSiteRedirectToggleTooltip": { - "message": "Click to toggle gateway redirects on $1", - "description": "A menu item tooltip in Browser Action pop-up (panel_activeTabSiteRedirectToggleTooltip)" + "panel_activeTabSiteIntegrationsToggleTooltip": { + "message": "Click to toggle all IPFS integrations on $1", + "description": "A menu item tooltip in Browser Action pop-up (panel_activeTabSiteIntegrationsToggleTooltip)" }, "panel_pinCurrentIpfsAddress": { "message": "Pin IPFS Resource", @@ -280,7 +280,7 @@ "description": "An option description on the Preferences screen (option_useCustomGateway_description)" }, "option_dnslinkRedirect_title": { - "message": "Force page load from custom gateway", + "message": "Load websites from Custom Gateway", "description": "An option title on the Preferences screen (option_dnslinkRedirect_title)" }, "option_dnslinkRedirect_description": { @@ -296,15 +296,15 @@ "description": "An option description on the Preferences screen (option_dnslinkDataPreload_description)" }, "option_dnslinkRedirect_warning": { - "message": "Redirecting to a path-based gateway breaks Origin-based security isolation of DNSLink website! Please leave this disabled unless you are aware of (and ok with) related risks.", + "message": "Redirecting to a path-based gateway breaks Origin-based security isolation of DNSLink websites. Make sure you understand related risks.", "description": "A warning on the Preferences screen, displayed when URL does not belong to Secure Context (option_customGatewayUrl_warning)" }, - "option_noRedirectHostnames_title": { - "message": "Redirect Opt-Outs", - "description": "An option title on the Preferences screen (option_noRedirectHostnames_title)" + "option_noIntegrationsHostnames_title": { + "message": "IPFS Integrations Opt-Outs", + "description": "An option title on the Preferences screen (option_noIntegrationsHostnames_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.", + "option_noIntegrationsHostnames_description": { + "message": "List of websites that should not have any IPFS integrations enabled. One hostname per line.", "description": "An option description on the Preferences screen (option_noRedirectHostnames_description)" }, "option_publicGatewayUrl_title": { diff --git a/add-on/src/lib/ipfs-companion.js b/add-on/src/lib/ipfs-companion.js index 4c49cccce..63aa50be5 100644 --- a/add-on/src/lib/ipfs-companion.js +++ b/add-on/src/lib/ipfs-companion.js @@ -241,7 +241,7 @@ module.exports = async function init () { openViaWebUI: state.openViaWebUI, apiURLString: dropSlash(state.apiURLString), redirect: state.redirect, - noRedirectHostnames: state.noRedirectHostnames, + noIntegrationsHostnames: state.noIntegrationsHostnames, currentTab: await browser.tabs.query({ active: true, currentWindow: true }).then(tabs => tabs[0]) } try { @@ -257,7 +257,7 @@ module.exports = async function init () { info.isIpfsContext = ipfsPathValidator.isIpfsPageActionsContext(url) info.currentDnslinkFqdn = dnslinkResolver.findDNSLinkHostname(url) info.currentFqdn = info.currentDnslinkFqdn || new URL(url).hostname - info.currentTabRedirectOptOut = info.noRedirectHostnames && info.noRedirectHostnames.includes(info.currentFqdn) + info.currentTabIntegrationsOptOut = info.noIntegrationsHostnames && info.noIntegrationsHostnames.includes(info.currentFqdn) info.isRedirectContext = info.currentFqdn && ipfsPathValidator.isRedirectPageActionsContext(url) } // Still here? @@ -365,7 +365,8 @@ module.exports = async function init () { async function onDOMContentLoaded (details) { if (!state.active) return // skip content script injection when off - if (!details.url.startsWith('http')) return // skip special pages + if (!details.url || !details.url.startsWith('http')) return // skip empty and special pages + if (!state.activeIntegrations(details.url)) return // skip if opt-out exists // console.info(`[ipfs-companion] onDOMContentLoaded`, details) if (state.linkify) { console.info(`[ipfs-companion] Running linkfy experiment for ${details.url}`) @@ -679,7 +680,7 @@ module.exports = async function init () { case 'detectIpfsPathHeader': case 'preloadAtPublicGateway': case 'openViaWebUI': - case 'noRedirectHostnames': + case 'noIntegrationsHostnames': case 'dnslinkRedirect': state[key] = change.newValue break diff --git a/add-on/src/lib/ipfs-proxy/enable-command.js b/add-on/src/lib/ipfs-proxy/enable-command.js index 23d7a4605..ff87c0074 100644 --- a/add-on/src/lib/ipfs-proxy/enable-command.js +++ b/add-on/src/lib/ipfs-proxy/enable-command.js @@ -10,10 +10,11 @@ const { createProxyAclError } = require('./pre-acl') function createEnableCommand (getIpfs, getState, getScope, accessControl, requestAccess) { return async (opts) => { const scope = await getScope() + const state = getState() log(`received window.ipfs.enable request from ${scope}`, opts) - // Check if all access to the IPFS node is disabled - if (!getState().ipfsProxy) throw new Error('User disabled access to API proxy in IPFS Companion') + // Check if access to the IPFS node is disabled + if (!state.ipfsProxy || !state.activeIntegrations(scope)) throw new Error('User disabled access to API proxy in IPFS Companion') // NOOP if .enable() was called without any arguments if (!opts) return diff --git a/add-on/src/lib/ipfs-proxy/pre-acl.js b/add-on/src/lib/ipfs-proxy/pre-acl.js index da9bac61b..d99d14628 100644 --- a/add-on/src/lib/ipfs-proxy/pre-acl.js +++ b/add-on/src/lib/ipfs-proxy/pre-acl.js @@ -3,12 +3,13 @@ // no access decision has been made yet. function createPreAcl (permission, getState, getScope, accessControl, requestAccess) { return async (...args) => { - // Check if all access to the IPFS node is disabled - if (!getState().ipfsProxy) { + const scope = await getScope() + const state = getState() + // Check if access to the IPFS node is disabled + if (!state.ipfsProxy || !state.activeIntegrations(scope)) { throw createProxyAclError(undefined, undefined, 'User disabled access to API proxy in IPFS Companion') } - const scope = await getScope() const access = await getAccessWithPrompt(accessControl, requestAccess, scope, permission) if (!access.allow) { diff --git a/add-on/src/lib/ipfs-request.js b/add-on/src/lib/ipfs-request.js index d0e870ef2..8901ad93b 100644 --- a/add-on/src/lib/ipfs-request.js +++ b/add-on/src/lib/ipfs-request.js @@ -76,11 +76,11 @@ function createRequestModifier (getState, dnslinkResolver, ipfsPathValidator, ru if (request.url.startsWith('http://127.0.0.1') || request.url.startsWith('http://localhost') || request.url.startsWith('http://[::1]')) { ignore(request.requestId) } - // skip if a per-site redirect opt-out exists + // skip if a per-site opt-out exists const parentUrl = request.originUrl || request.initiator // FF: originUrl (Referer-like Origin URL), Chrome: initiator (just Origin) const fqdn = new URL(request.url).hostname const parentFqdn = parentUrl && parentUrl !== 'null' && request.url !== parentUrl ? new URL(parentUrl).hostname : null - if (state.noRedirectHostnames.some(optout => + if (state.noIntegrationsHostnames.some(optout => fqdn !== 'gateway.ipfs.io' && (fqdn.endsWith(optout) || (parentFqdn && parentFqdn.endsWith(optout)) ))) { ignore(request.requestId) diff --git a/add-on/src/lib/options.js b/add-on/src/lib/options.js index e747b5065..dc8ee2b00 100644 --- a/add-on/src/lib/options.js +++ b/add-on/src/lib/options.js @@ -13,7 +13,7 @@ exports.optionDefaults = Object.freeze({ publicGatewayUrl: 'https://ipfs.io', publicSubdomainGatewayUrl: 'https://dweb.link', useCustomGateway: true, - noRedirectHostnames: [], + noIntegrationsHostnames: [], automaticMode: true, linkify: false, dnslinkPolicy: 'best-effort', @@ -132,4 +132,11 @@ exports.migrateOptions = async (storage) => { ipfsNodeConfig: buildDefaultIpfsNodeConfig() }) } + // ~ v2.9.x: migrating noRedirectHostnames → noIntegrationsHostnames + // https://github.com/ipfs-shipyard/ipfs-companion/pull/830 + const { noRedirectHostnames } = await storage.get('noRedirectHostnames') + if (noRedirectHostnames) { + await storage.set({ noIntegrationsHostnames: noRedirectHostnames }) + await storage.remove('noRedirectHostnames') + } } diff --git a/add-on/src/lib/state.js b/add-on/src/lib/state.js index c422f17bd..59d703305 100644 --- a/add-on/src/lib/state.js +++ b/add-on/src/lib/state.js @@ -8,7 +8,7 @@ const offlinePeerCount = -1 // which should work without setting CORS headers const webuiCid = 'Qmexhq2sBHnXQbvyP2GfUdbnY7HCagH2Mw5vUNSBn2nxip' // v2.7.2 -function initState (options) { +function initState (options, overrides) { // we store options and some pregenerated values to avoid async storage // reads and minimize performance impact on overall browsing experience const state = Object.assign({}, options) @@ -31,6 +31,18 @@ function initState (options) { state.dnslinkPolicy = String(options.dnslinkPolicy) === 'false' ? false : options.dnslinkPolicy state.webuiCid = webuiCid state.webuiRootUrl = `${state.gwURLString}ipfs/${state.webuiCid}/` + // attach helper functions + state.activeIntegrations = (url) => { + if (!state.active) return false + try { + const fqdn = new URL(url).hostname + return !(state.noIntegrationsHostnames.find(host => fqdn.endsWith(host))) + } catch (_) { + return false + } + } + // apply optional overrides + if (overrides) Object.assign(state, overrides) return state } diff --git a/add-on/src/options/forms/gateways-form.js b/add-on/src/options/forms/gateways-form.js index 09e9f3d9d..1d6133f94 100644 --- a/add-on/src/options/forms/gateways-form.js +++ b/add-on/src/options/forms/gateways-form.js @@ -14,7 +14,7 @@ function gatewaysForm ({ ipfsNodeType, customGatewayUrl, useCustomGateway, - noRedirectHostnames, + noIntegrationsHostnames, publicGatewayUrl, publicSubdomainGatewayUrl, onOptionChange @@ -23,7 +23,7 @@ function gatewaysForm ({ const onUseCustomGatewayChange = onOptionChange('useCustomGateway') const onPublicGatewayUrlChange = onOptionChange('publicGatewayUrl', normalizeGatewayURL) const onPublicSubdomainGatewayUrlChange = onOptionChange('publicSubdomainGatewayUrl', normalizeGatewayURL) - const onNoRedirectHostnamesChange = onOptionChange('noRedirectHostnames', hostTextToArray) + const onNoIntegrationsHostnamesChange = onOptionChange('noIntegrationsHostnames', hostTextToArray) const mixedContentWarning = !secureContextUrl.test(customGatewayUrl) const supportRedirectToCustomGateway = ipfsNodeType !== 'embedded' const allowChangeOfCustomGateway = ipfsNodeType !== 'embedded:chromesockets' @@ -110,18 +110,18 @@ function gatewaysForm ({ ` : null} ${supportRedirectToCustomGateway ? html`