From 4482b4055b1016eac7788492fff50eff8976bc0f Mon Sep 17 00:00:00 2001 From: Marcin Rataj Date: Fri, 8 Jun 2018 16:28:39 +0200 Subject: [PATCH 1/4] fix: avoid window.ipfs in non-HTML context Injecting proxy into application/xml produced malformed XML and SVG files. This adds some sanity-checks and ensures injection happens only in HTML contexts. It also improves performance by avoiding unnecessary port creation. --- .../src/contentScripts/ipfs-proxy/content.js | 30 +++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/add-on/src/contentScripts/ipfs-proxy/content.js b/add-on/src/contentScripts/ipfs-proxy/content.js index b98536d46..d43f0b5ee 100644 --- a/add-on/src/contentScripts/ipfs-proxy/content.js +++ b/add-on/src/contentScripts/ipfs-proxy/content.js @@ -7,14 +7,12 @@ const injectScript = require('./inject-script') function init () { const port = browser.runtime.connect({ name: 'ipfs-proxy' }) - // Forward on messages from background to the page and vice versa port.onMessage.addListener((data) => { if (data && data.sender && data.sender.startsWith('postmsg-rpc/')) { window.postMessage(data, '*') } }) - window.addEventListener('message', (msg) => { if (msg.data && msg.data.sender && msg.data.sender.startsWith('postmsg-rpc/')) { port.postMessage(msg.data) @@ -24,8 +22,30 @@ function init () { injectScript(rawCode) } -// Restricting window.ipfs to Secure Context -// See: https://github.com/ipfs-shipyard/ipfs-companion/issues/476 -if (window.isSecureContext) { +function injectIpfsProxy () { + // Skip if proxy is already present + if (window.ipfs) { + return false + } + // Restricting window.ipfs to Secure Context + // See: https://github.com/ipfs-shipyard/ipfs-companion/issues/476 + if (!window.isSecureContext) { + return false + } + // Skip if not in HTML context + // Check 1/2 + const doctype = window.document.doctype + if (doctype && doctype.name !== 'html') { + return false + } + // Check 2/2 + if (document.documentElement.nodeName !== 'HTML') { + return false + } + // Should be ok by now + return true +} + +if (injectIpfsProxy()) { init() } From 52b0190356f719ff228b1e5dd6ca4cc92e99714b Mon Sep 17 00:00:00 2001 From: Marcin Rataj Date: Fri, 8 Jun 2018 17:35:32 +0200 Subject: [PATCH 2/4] feat: use browser.contentScripts API in Firefox This change enables Firefox users to disable creation of window.ipfs, solving fingerprinting issue raised in: https://github.com/ipfs-shipyard/ipfs-companion/issues/451 The key difference between tabs.executeScript and contentScripts API is that the latter provides guarantee to execute before anything else. Chrome does not provide contentScripts API and we need to statically load content_script via manifest. More info on the underlying issue: https://github.com/ipfs-shipyard/ipfs-companion/pull/368 https://github.com/ipfs-shipyard/ipfs-companion/issues/362#issuecomment-362231167 --- add-on/manifest.firefox.json | 1 + add-on/src/lib/ipfs-companion.js | 38 +++++++++++++++++++++++++++++++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/add-on/manifest.firefox.json b/add-on/manifest.firefox.json index 0865fb696..30d74bb1d 100644 --- a/add-on/manifest.firefox.json +++ b/add-on/manifest.firefox.json @@ -18,6 +18,7 @@ "default_title": "__MSG_pageAction_titleNonIpfs__", "default_popup": "dist/popup/page-action/index.html" }, + "content_scripts": null, "protocol_handlers": [ { "protocol": "web+dweb", diff --git a/add-on/src/lib/ipfs-companion.js b/add-on/src/lib/ipfs-companion.js index 64b04d769..bd30dffd7 100644 --- a/add-on/src/lib/ipfs-companion.js +++ b/add-on/src/lib/ipfs-companion.js @@ -30,6 +30,7 @@ module.exports = async function init () { var contextMenus var apiStatusUpdateInterval var ipfsProxy + var ipfsProxyContentScript const idleInSecs = 5 * 60 const browserActionPortName = 'browser-action-port' @@ -60,6 +61,7 @@ module.exports = async function init () { }) modifyRequest = createRequestModifier(getState, dnsLink, ipfsPathValidator, runtime) ipfsProxy = createIpfsProxy(() => ipfs, getState) + ipfsProxyContentScript = await registerIpfsProxyContentScript() registerListeners() await setApiStatusUpdateInterval(options.ipfsApiPollMs) await storeMissingOptions( @@ -95,6 +97,33 @@ module.exports = async function init () { } } + // Register Content Script responsible for loading window.ipfs (ipfsProxy) + // + // The key difference between tabs.executeScript and contentScripts API + // is the latter provides guarantee to execute before anything else. + // https://github.com/ipfs-shipyard/ipfs-companion/issues/451#issuecomment-382669093 + async function registerIpfsProxyContentScript (previousHandle) { + previousHandle = previousHandle || ipfsProxyContentScript + if (previousHandle) { + previousHandle.unregister() + } + if (!state.ipfsProxy || !browser.contentScripts) { + // no-op if window.ipfs is disabled in Preferences + // or if runtime has no contentScript API + // (Chrome loads content script via manifest) + return + } + const newHandle = await browser.contentScripts.register({ + matches: [''], + js: [ + {file: '/dist/contentScripts/ipfs-proxy/content.js'} + ], + allFrames: true, + runAt: 'document_start' + }) + return newHandle + } + // HTTP Request Hooks // =================================================================== @@ -544,13 +573,16 @@ module.exports = async function init () { case 'useCustomGateway': state.redirect = change.newValue break + case 'ipfsProxy': + state[key] = change.newValue + ipfsProxyContentScript = await registerIpfsProxyContentScript() + break case 'linkify': case 'catchUnhandledProtocols': case 'displayNotifications': case 'automaticMode': case 'dnslink': case 'preloadAtPublicGateway': - case 'ipfsProxy': state[key] = change.newValue break } @@ -616,6 +648,10 @@ module.exports = async function init () { contextMenus = null ipfsProxy.destroy() ipfsProxy = null + if (ipfsProxyContentScript) { + ipfsProxyContentScript.unregister() + ipfsProxyContentScript = null + } await destroyIpfsClient() } } From f3e362839bc03b066164eef194fec33fde397fee Mon Sep 17 00:00:00 2001 From: Marcin Rataj Date: Fri, 8 Jun 2018 17:43:28 +0200 Subject: [PATCH 3/4] fix: Fix addon-linter error for /content_scripts MANIFEST_FIELD_INVALID "/content_scripts" should be array --- add-on/manifest.firefox.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/add-on/manifest.firefox.json b/add-on/manifest.firefox.json index 30d74bb1d..a57e25cc9 100644 --- a/add-on/manifest.firefox.json +++ b/add-on/manifest.firefox.json @@ -18,7 +18,7 @@ "default_title": "__MSG_pageAction_titleNonIpfs__", "default_popup": "dist/popup/page-action/index.html" }, - "content_scripts": null, + "content_scripts": [ ], "protocol_handlers": [ { "protocol": "web+dweb", From 3c53eb5f0fa481d77eac2948e134cc280823f465 Mon Sep 17 00:00:00 2001 From: Marcin Rataj Date: Wed, 13 Jun 2018 14:08:12 +0200 Subject: [PATCH 4/4] fix: change script name to webpack bundle --- add-on/src/lib/ipfs-companion.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/add-on/src/lib/ipfs-companion.js b/add-on/src/lib/ipfs-companion.js index bd30dffd7..439e853b2 100644 --- a/add-on/src/lib/ipfs-companion.js +++ b/add-on/src/lib/ipfs-companion.js @@ -116,7 +116,7 @@ module.exports = async function init () { const newHandle = await browser.contentScripts.register({ matches: [''], js: [ - {file: '/dist/contentScripts/ipfs-proxy/content.js'} + {file: '/dist/bundles/ipfsProxyContentScript.bundle.js'} ], allFrames: true, runAt: 'document_start'