Skip to content

Commit

Permalink
Merge pull request #495 from ipfs-shipyard/feat/hide-window.ipfs
Browse files Browse the repository at this point in the history
This PR enables Firefox users to disable creation of `window.ipfs` attribute via _Preferences_ screen, solving fingerprinting issue raised in #451.

It requires webpack, so should be merged after #498 

### Background

Existing `tabs.executeScript` API with `runAt: 'document_start'` flag was executing too late and page scripts were  unable to detect `window.ipfs` correctly.  More info on the underlying issue: #368 #362 (comment)

As a workaround that worked in both Chrome and Firefox, we were forced to always load content script via manifest definition. This meant no control over when it is loaded and no easy off switch. If `window.ipfs` was disabled via _Preferences_, it was throwing an Error on access, but remained in the scope.

Mozilla added [`contentScripts`](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/contentScripts/register) API in Firefox 59. The key difference between `tabs.executeScript` and `contentScripts` API is that the latter provides guarantee to execute before anything else on the page, passing [our synchronous smoke test](https://ipfs-shipyard.github.io/ipfs-companion/docs/examples/window.ipfs-fallback.html). 

### Known Issues

Chrome does not provide `contentScripts` API so it will continue to statically load `content_script` via  manifest.  Hopefully with time, other browser vendors will adopt the new API.
  • Loading branch information
lidel authored Jun 15, 2018
2 parents 4c77644 + 3c53eb5 commit 032ebdd
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 6 deletions.
1 change: 1 addition & 0 deletions add-on/manifest.firefox.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"default_title": "__MSG_pageAction_titleNonIpfs__",
"default_popup": "dist/popup/page-action/index.html"
},
"content_scripts": [ ],
"protocol_handlers": [
{
"protocol": "web+dweb",
Expand Down
30 changes: 25 additions & 5 deletions add-on/src/contentScripts/ipfs-proxy/content.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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()
}
38 changes: 37 additions & 1 deletion add-on/src/lib/ipfs-companion.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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: ['<all_urls>'],
js: [
{file: '/dist/bundles/ipfsProxyContentScript.bundle.js'}
],
allFrames: true,
runAt: 'document_start'
})
return newHandle
}

// HTTP Request Hooks
// ===================================================================

Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -616,6 +648,10 @@ module.exports = async function init () {
contextMenus = null
ipfsProxy.destroy()
ipfsProxy = null
if (ipfsProxyContentScript) {
ipfsProxyContentScript.unregister()
ipfsProxyContentScript = null
}
await destroyIpfsClient()
}
}
Expand Down

0 comments on commit 032ebdd

Please sign in to comment.