diff --git a/add-on/manifest.json b/add-on/manifest.json index e975253da..c6f219d8d 100644 --- a/add-on/manifest.json +++ b/add-on/manifest.json @@ -15,7 +15,7 @@ "applications": { "gecko": { "id": "ipfs-firefox-addon@lidel.org", - "strict_min_version": "57.0" + "strict_min_version": "58.0" } }, diff --git a/add-on/src/lib/dns-link.js b/add-on/src/lib/dns-link.js index c4ddf6e5f..94cbc538f 100644 --- a/add-on/src/lib/dns-link.js +++ b/add-on/src/lib/dns-link.js @@ -3,6 +3,7 @@ const IsIpfs = require('is-ipfs') const { LRUMap } = require('lru_map') +const { embeddedNodeIsActive } = require('./state') module.exports = function createDnsLink (getState) { const cache = new LRUMap(1000) @@ -84,10 +85,15 @@ module.exports = function createDnsLink (getState) { } }, - redirectToIpnsPath (url) { - const fqdn = url.hostname - url.protocol = getState().gwURL.protocol - url.host = getState().gwURL.host + redirectToIpnsPath (originalUrl) { + // TODO: redirect to `ipns://` if browserWithNativeProtocol() === true + const fqdn = originalUrl.hostname + const state = getState() + const gwUrl = embeddedNodeIsActive(state) ? state.pubGwURL : state.gwURL + const url = new URL(originalUrl) + url.protocol = gwUrl.protocol + url.host = gwUrl.host + url.port = gwUrl.port url.pathname = `/ipns/${fqdn}${url.pathname}` return { redirectUrl: url.toString() } } diff --git a/add-on/src/lib/ipfs-companion.js b/add-on/src/lib/ipfs-companion.js index 438a1b437..652d6c344 100644 --- a/add-on/src/lib/ipfs-companion.js +++ b/add-on/src/lib/ipfs-companion.js @@ -3,7 +3,7 @@ const browser = require('webextension-polyfill') const { optionDefaults, storeMissingOptions } = require('./options') -const { initState } = require('./state') +const { initState, offlinePeerCount, inFirefox, browserWithNativeProtocol, embeddedNodeIsActive } = require('./state') const { createIpfsPathValidator, urlAtPublicGw } = require('./ipfs-path') const createDnsLink = require('./dns-link') const { createRequestModifier } = require('./ipfs-request') @@ -28,7 +28,6 @@ module.exports = async function init () { var contextMenus var apiStatusUpdateInterval var ipfsProxy - const offlinePeerCount = -1 const idleInSecs = 5 * 60 const browserActionPortName = 'browser-action-port' @@ -74,11 +73,11 @@ module.exports = async function init () { browser.runtime.onMessage.addListener(onRuntimeMessage) browser.runtime.onConnect.addListener(onRuntimeConnect) // browser.protocol exists only in Brave - if (browser.protocol && browser.protocol.registerStringProtocol) { + if (browserWithNativeProtocol()) { console.log(`[ipfs-companion] registerStringProtocol available. Adding ipfs:// handler`) browser.protocol.registerStringProtocol('ipfs', createIpfsUrlProtocolHandler(() => ipfs)) } else { - console.log(`[ipfs-companion] registerStringProtocol not available, native protocol will not be registered`, browser.protocol) + console.log('[ipfs-companion] browser.protocol.registerStringProtocol not available, native protocol will not be registered') } } @@ -181,10 +180,6 @@ module.exports = async function init () { // GUI // =================================================================== - function inFirefox () { - return !!navigator.userAgent.match('Firefox') - } - function preloadAtPublicGateway (path) { // asynchronous HTTP HEAD request preloads triggers content without downloading it return new Promise((resolve, reject) => { @@ -251,23 +246,23 @@ module.exports = async function init () { } // TODO: feature detect and push to client type specific modules. - function getIpfsPathAndLocalAddress (hash) { + function getIpfsPathAndNativeAddress (hash) { const path = `/ipfs/${hash}` - if (state.ipfsNodeType === 'embedded' && browser && browser.protocol && browser.protocol.registerStringProtocol) { - return {path, localAddress: `ipfs://${hash}`} + if (embeddedNodeIsActive(state) && browserWithNativeProtocol()) { + return {path, nativeAddress: `ipfs://${hash}`} } else { - // Use the chosen gateway... local or public - const url = new URL(path, state.gwURLString).toString() - return {path, localAddress: url} + // open at public GW (will be redirected to local elsewhere, if enabled) + const url = new URL(path, state.pubGwURLString).toString() + return {path, nativeAddress: url} } } function uploadResultHandler (result) { result.forEach(function (file) { if (file && file.hash) { - const {path, localAddress} = getIpfsPathAndLocalAddress(file.hash) + const {path, nativeAddress} = getIpfsPathAndNativeAddress(file.hash) browser.tabs.create({ - 'url': localAddress + 'url': nativeAddress }) console.info('[ipfs-companion] successfully stored', path) if (state.preloadAtPublicGateway) { @@ -350,11 +345,14 @@ module.exports = async function init () { } async function apiStatusUpdate () { + // update peer count let oldPeerCount = state.peerCount state.peerCount = await getSwarmPeerCount() - state.repoStats = await getRepoStats() updatePeerCountDependentStates(oldPeerCount, state.peerCount) - sendStatusUpdateToBrowserAction() + // update repo stats + state.repoStats = await getRepoStats() + // trigger pending updates + await sendStatusUpdateToBrowserAction() } function updatePeerCountDependentStates (oldPeerCount, newPeerCount) { @@ -482,11 +480,11 @@ module.exports = async function init () { // enable/disable gw redirect based on API going online or offline // newPeerCount === -1 currently implies node is offline. // TODO: use `node.isOnline()` if available (js-ipfs) - if (state.automaticMode) { - if (oldPeerCount === -1 && newPeerCount > -1 && !state.redirect) { + if (state.automaticMode && !embeddedNodeIsActive(state)) { + if (oldPeerCount === offlinePeerCount && newPeerCount > offlinePeerCount && !state.redirect) { browser.storage.local.set({useCustomGateway: true}) .then(() => notify('notify_apiOnlineTitle', 'notify_apiOnlineAutomaticModeMsg')) - } else if (oldPeerCount > -1 && newPeerCount === -1 && state.redirect) { + } else if (newPeerCount === offlinePeerCount && state.redirect) { browser.storage.local.set({useCustomGateway: false}) .then(() => notify('notify_apiOfflineTitle', 'notify_apiOfflineAutomaticModeMsg')) } diff --git a/add-on/src/lib/ipfs-request.js b/add-on/src/lib/ipfs-request.js index 074b7006b..ed2c65b9f 100644 --- a/add-on/src/lib/ipfs-request.js +++ b/add-on/src/lib/ipfs-request.js @@ -3,6 +3,7 @@ const IsIpfs = require('is-ipfs') const { urlAtPublicGw } = require('./ipfs-path') +const { embeddedNodeIsActive } = require('./state') function createRequestModifier (getState, dnsLink, ipfsPathValidator) { return function modifyRequest (request) { @@ -35,6 +36,12 @@ function createRequestModifier (getState, dnsLink, ipfsPathValidator) { } } + // skip requests to the public gateway if embedded node is running (otherwise we have too much recursion) + if (embeddedNodeIsActive(state) && request.url.startsWith(state.pubGwURLString)) { + return + // TODO: do not skip and redirect to `ipfs://` and `ipns://` if browserWithNativeProtocol() === true + } + // handle redirects to custom gateway if (state.redirect) { // Ignore preload requests @@ -43,7 +50,7 @@ function createRequestModifier (getState, dnsLink, ipfsPathValidator) { } // Detect valid /ipfs/ and /ipns/ on any site if (ipfsPathValidator.publicIpfsOrIpnsResource(request.url)) { - return redirectToCustomGateway(request.url, state.gwURL) + return redirectToIpfsTransport(request.url, state) } // Look for dnslink in TXT records of visited sites if (state.dnslink && dnsLink.isDnslookupSafeForURL(request.url)) { @@ -55,7 +62,9 @@ function createRequestModifier (getState, dnsLink, ipfsPathValidator) { exports.createRequestModifier = createRequestModifier -function redirectToCustomGateway (requestUrl, gwUrl) { +function redirectToIpfsTransport (requestUrl, state) { + // TODO: redirect to `ipfs://` if browserWithNativeProtocol() === true + const gwUrl = embeddedNodeIsActive(state) ? state.pubGwURL : state.gwURL const url = new URL(requestUrl) url.protocol = gwUrl.protocol url.host = gwUrl.host diff --git a/add-on/src/lib/state.js b/add-on/src/lib/state.js index b802c644d..9675ffb03 100644 --- a/add-on/src/lib/state.js +++ b/add-on/src/lib/state.js @@ -1,10 +1,15 @@ 'use strict' -/* eslint-env browser */ +/* eslint-env browser, webextensions */ + +// The State +// =================================================================== +const offlinePeerCount = -1 function initState (options) { const state = {} // we store the most used values in optimized form // to minimize performance impact on overall browsing experience + state.peerCount = offlinePeerCount state.ipfsNodeType = options.ipfsNodeType state.pubGwURL = new URL(options.publicGatewayUrl) state.pubGwURLString = state.pubGwURL.toString() @@ -22,4 +27,21 @@ function initState (options) { return state } +// Browser Feature Detection +// =================================================================== + +exports.inFirefox = function () { + // TODO: we really need to change this to something more robust + return !!navigator.userAgent.match('Firefox') +} + +exports.browserWithNativeProtocol = function () { + return browser && browser.protocol && browser.protocol.registerStringProtocol +} + +exports.embeddedNodeIsActive = function (state) { + return state.ipfsNodeType === 'embedded' +} + exports.initState = initState +exports.offlinePeerCount = offlinePeerCount diff --git a/test/functional/lib/dns-link.test.js b/test/functional/lib/dns-link.test.js index 1cb243811..6e88e75f0 100644 --- a/test/functional/lib/dns-link.test.js +++ b/test/functional/lib/dns-link.test.js @@ -1,18 +1,44 @@ 'use strict' -const { describe, it } = require('mocha') +const { describe, it, before, after } = require('mocha') const { expect } = require('chai') const { URL } = require('url') const createDnsLink = require('../../../add-on/src/lib/dns-link') // https://github.com/ipfs/ipfs-companion/issues/303 describe('DNSLINK', function () { - describe('redirectToIpnsPath(url)', function () { + before(() => { + global.URL = URL + }) + + describe('redirectToIpnsPath(url) with external gateway', function () { it('should return IPNS path at a custom gateway', function () { const url = new URL('http://ipfs.git.sexy/sketches/ipld_intro.html?a=b#c=d') - const getState = () => ({ gwURL: new URL('http://127.0.0.1:8080') }) + const getState = () => ({ + gwURL: new URL('http://127.0.0.1:8080'), + pubGwURL: new URL('https://ipfs.io'), + ipfsNodeType: 'external' + }) const dnsLink = createDnsLink(getState) expect(dnsLink.redirectToIpnsPath(url).redirectUrl) .to.equal('http://127.0.0.1:8080/ipns/ipfs.git.sexy/sketches/ipld_intro.html?a=b#c=d') }) }) + + describe('redirectToIpnsPath(url) with embedded gateway', function () { + it('should return IPNS path at a public gateway', function () { + const url = new URL('http://ipfs.git.sexy/sketches/ipld_intro.html?a=b#c=d') + const getState = () => ({ + gwURL: new URL('http://127.0.0.1:8080'), + pubGwURL: new URL('https://ipfs.io'), + ipfsNodeType: 'embedded' + }) + const dnsLink = createDnsLink(getState) + expect(dnsLink.redirectToIpnsPath(url).redirectUrl) + .to.equal('https://ipfs.io/ipns/ipfs.git.sexy/sketches/ipld_intro.html?a=b#c=d') + }) + }) + + after(() => { + delete global.URL + }) }) diff --git a/test/functional/lib/ipfs-request-embedded.test.js b/test/functional/lib/ipfs-request-embedded.test.js new file mode 100644 index 000000000..a752298f7 --- /dev/null +++ b/test/functional/lib/ipfs-request-embedded.test.js @@ -0,0 +1,246 @@ +'use strict' +const { describe, it, before, beforeEach, after } = require('mocha') +const sinon = require('sinon') +const { expect } = require('chai') +const { URL } = require('url') +const { initState } = require('../../../add-on/src/lib/state') +const { createRequestModifier } = require('../../../add-on/src/lib/ipfs-request') +const createDnsLink = require('../../../add-on/src/lib/dns-link') +const { createIpfsPathValidator } = require('../../../add-on/src/lib/ipfs-path') +const { optionDefaults } = require('../../../add-on/src/lib/options') + +const url2request = (string) => { + return {url: string, type: 'main_frame'} +} + +describe('modifyRequest with embedded ipfsNodeType', function () { + let state, dnsLink, ipfsPathValidator, modifyRequest + + before(() => { + global.URL = URL + }) + + beforeEach(() => { + state = Object.assign(initState(optionDefaults), { + ipfsNodeType: 'embedded', + peerCount: 1, + redirect: true, + catchUnhandledProtocols: true, + gwURLString: 'http://127.0.0.1:8080', + pubGwURLString: 'https://ipfs.io' + }) + const getState = () => state + dnsLink = createDnsLink(getState) + ipfsPathValidator = createIpfsPathValidator(getState, dnsLink) + modifyRequest = createRequestModifier(getState, dnsLink, ipfsPathValidator) + }) + + describe('request for a path matching /ipfs/{CIDv0}', function () { + it('should be served from public gateway if redirect is enabled', function () { + const request = url2request('https://google.com/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest') + expect(modifyRequest(request).redirectUrl).to.equal('https://ipfs.io/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest') + }) + it('should be left untouched if redirect is disabled', function () { + state.redirect = false + const request = url2request('https://google.com/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest') + expect(modifyRequest(request)).to.equal(undefined) + }) + it('should be left untouched if CID is invalid', function () { + const request = url2request('https://google.com/ipfs/notacid?argTest#hashTest') + expect(modifyRequest(request)).to.equal(undefined) + }) + }) + + describe('request for a path matching /ipns/{path}', function () { + it('should be served from public gateway if {path} points to a FQDN with existing dnslink', function () { + const request = url2request('https://google.com/ipns/ipfs.git.sexy/index.html?argTest#hashTest') + // stub the existence of valid dnslink + const fqdn = 'ipfs.git.sexy' + dnsLink.readDnslinkFromTxtRecord = sinon.stub().withArgs(fqdn).returns('/ipfs/Qmazvovg6Sic3m9igZMKoAPjkiVZsvbWWc8ZvgjjK1qMss') + // pretend API is online and we can do dns lookups with it + state.peerCount = 1 + expect(modifyRequest(request).redirectUrl).to.equal('https://ipfs.io/ipns/ipfs.git.sexy/index.html?argTest#hashTest') + }) + it('should be served from public gateway if {path} starts with a valid CID', function () { + const request = url2request('https://google.com/ipns/QmSWnBwMKZ28tcgMFdihD8XS7p6QzdRSGf71cCybaETSsU/index.html?argTest#hashTest') + dnsLink.readDnslinkFromTxtRecord = sinon.stub().returns(false) + expect(modifyRequest(request).redirectUrl).to.equal('https://ipfs.io/ipns/QmSWnBwMKZ28tcgMFdihD8XS7p6QzdRSGf71cCybaETSsU/index.html?argTest#hashTest') + }) + it('should be left untouched if redirect is disabled', function () { + state.redirect = false + const request = url2request('https://google.com/ipns/ipfs.io?argTest#hashTest') + expect(modifyRequest(request)).to.equal(undefined) + }) + it('should be left untouched if FQDN is not a real domain nor a valid CID', function () { + const request = url2request('https://google.com/ipns/notafqdnorcid?argTest#hashTest') + dnsLink.readDnslinkFromTxtRecord = sinon.stub().returns(false) + expect(modifyRequest(request)).to.equal(undefined) + }) + it('should be left untouched if {path} points to a FQDN but API is offline', function () { + const request = url2request('https://google.com/ipns/ipfs.git.sexy/index.html?argTest#hashTest') + // stub the existence of valid dnslink in dnslink cache + const fqdn = 'ipfs.git.sexy' + dnsLink.readDnslinkFromTxtRecord = sinon.stub().withArgs(fqdn).returns('/ipfs/Qmazvovg6Sic3m9igZMKoAPjkiVZsvbWWc8ZvgjjK1qMss') + // pretend API is offline and we can do dns lookups with it + state.peerCount = 0 + expect(modifyRequest(request)).to.equal(undefined) + }) + }) + + describe('request made via "web+" handler from manifest.json/protocol_handlers', function () { + it('should not be normalized if web+ipfs:/{CID}', function () { + const request = url2request('https://ipfs.io/web%2Bipfs%3A%2FQmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR%3FargTest%23hashTest') + expect(modifyRequest(request)).to.equal(undefined) + }) + it('should be normalized if web+ipfs://{CID}', function () { + const request = url2request('https://ipfs.io/web%2Bipfs%3A%2F%2FQmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR%3FargTest%23hashTest') + expect(modifyRequest(request).redirectUrl).to.equal('https://ipfs.io/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest') + }) + it('should not be normalized if web+ipns:/{foo}', function () { + const request = url2request('https://ipfs.io/web%2Bipns%3A%2Fipfs.io%3FargTest%23hashTest') + expect(modifyRequest(request)).to.equal(undefined) + }) + it('should be normalized if web+ipns://{foo}', function () { + const request = url2request('https://ipfs.io/web%2Bipns%3A%2F%2Fipfs.io%3FargTest%23hashTest') + expect(modifyRequest(request).redirectUrl).to.equal('https://ipfs.io/ipns/ipfs.io?argTest#hashTest') + }) + it('should be normalized if web+dweb:/ipfs/{CID}', function () { + const request = url2request('https://ipfs.io/web%2Bdweb%3A%2Fipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR%3FargTest%23hashTest') + expect(modifyRequest(request).redirectUrl).to.equal('https://ipfs.io/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest') + }) + it('should not be normalized if web+dweb://ipfs/{CID}', function () { + const request = url2request('https://ipfs.io/web%2Bdweb%3A%2F%2Fipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR%3FargTest%23hashTest') + expect(modifyRequest(request)).to.equal(undefined) + }) + it('should be normalized if web+dweb:/ipns/{foo}', function () { + const request = url2request('https://ipfs.io/web%2Bdweb%3A%2Fipns/ipfs.io%3FargTest%23hashTest') + expect(modifyRequest(request).redirectUrl).equal('https://ipfs.io/ipns/ipfs.io?argTest#hashTest') + }) + it('should not be normalized if web+dweb://ipns/{foo}', function () { + const request = url2request('https://ipfs.io/web%2Bdweb%3A%2F%2Fipns/ipfs.io%3FargTest%23hashTest') + expect(modifyRequest(request)).to.equal(undefined) + }) + it('should not be normalized if web+{foo}:/bar', function () { + const request = url2request('https://ipfs.io/web%2Bfoo%3A%2Fbar%3FargTest%23hashTest') + expect(modifyRequest(request)).to.equal(undefined) + }) + it('should not be normalized if web+{foo}://bar', function () { + const request = url2request('https://ipfs.io/web%2Bfoo%3A%2F%2Fbar%3FargTest%23hashTest') + expect(modifyRequest(request)).to.equal(undefined) + }) + }) + + describe('catching unhandled custom protocol request', function () { + it('should not be normalized if ipfs:/{CID}', function () { + const request = url2request('https://duckduckgo.com/?q=ipfs%3A%2FQmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR%3FargTest%23hashTest&foo=bar') + expect(modifyRequest(request)).to.equal(undefined) + }) + it('should be normalized if ipfs://{CID}', function () { + const request = url2request('https://duckduckgo.com/?q=ipfs%3A%2F%2FQmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR%3FargTest%23hashTest&foo=bar') + expect(modifyRequest(request).redirectUrl).to.equal('https://ipfs.io/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest') + }) + it('should not be normalized if ipns:/{foo}', function () { + const request = url2request('https://duckduckgo.com/?q=ipns%3A%2Fipns.io%2Findex.html%3Farg%3Dfoo%26bar%3Dbuzz%23hashTest') + expect(modifyRequest(request)).to.equal(undefined) + }) + it('should be normalized if ipns://{foo}', function () { + const request = url2request('https://duckduckgo.com/?q=ipns%3A%2F%2Fipns.io%2Findex.html%3Farg%3Dfoo%26bar%3Dbuzz%23hashTest') + expect(modifyRequest(request).redirectUrl).to.equal('https://ipfs.io/ipns/ipns.io/index.html?arg=foo&bar=buzz#hashTest') + }) + it('should be normalized if dweb:/ipfs/{CID}', function () { + const request = url2request('https://duckduckgo.com/?q=dweb%3A%2Fipfs%2FQmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR%3Farg%3Dfoo%26bar%3Dbuzz%23hash&ia=software') + expect(modifyRequest(request).redirectUrl).to.equal('https://ipfs.io/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?arg=foo&bar=buzz#hash') + }) + it('should not be normalized if dweb://ipfs/{CID}', function () { + const request = url2request('https://duckduckgo.com/?q=dweb%3A%2F%2Fipfs%2FQmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR%3Farg%3Dfoo%26bar%3Dbuzz%23hash&ia=software') + expect(modifyRequest(request)).to.equal(undefined) + }) + it('should be normalized if dweb:/ipns/{foo}', function () { + const request = url2request('https://duckduckgo.com/?q=dweb%3A%2Fipns%2Fipfs.io%2Findex.html%3Farg%3Dfoo%26bar%3Dbuzz%23hash&ia=web') + expect(modifyRequest(request).redirectUrl).to.equal('https://ipfs.io/ipns/ipfs.io/index.html?arg=foo&bar=buzz#hash') + }) + it('should not be normalized if dweb://ipns/{foo}', function () { + const request = url2request('https://duckduckgo.com/?q=dweb%3A%2F%2Fipns%2Fipfs.io%2Findex.html%3Farg%3Dfoo%26bar%3Dbuzz%23hash&ia=web') + expect(modifyRequest(request)).to.equal(undefined) + }) + + it('should not be normalized if web+ipfs:/{CID}', function () { + const request = url2request('https://duckduckgo.com/?q=web%2Bipfs%3A%2FQmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR%3FargTest%23hashTest&foo=bar') + expect(modifyRequest(request)).to.equal(undefined) + }) + it('should be normalized if web+ipfs://{CID}', function () { + const request = url2request('https://duckduckgo.com/?q=web%2Bipfs%3A%2F%2FQmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR%3FargTest%23hashTest&foo=bar') + expect(modifyRequest(request).redirectUrl).to.equal('https://ipfs.io/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest') + }) + it('should not be normalized if web+ipns:/{foo}', function () { + const request = url2request('https://duckduckgo.com/?q=web%2Bipns%3A%2Fipns.io%2Findex.html%3Farg%3Dfoo%26bar%3Dbuzz%23hashTest') + expect(modifyRequest(request)).to.equal(undefined) + }) + it('should be normalized if web+ipns://{foo}', function () { + const request = url2request('https://duckduckgo.com/?q=web%2Bipns%3A%2F%2Fipns.io%2Findex.html%3Farg%3Dfoo%26bar%3Dbuzz%23hashTest') + expect(modifyRequest(request).redirectUrl).to.equal('https://ipfs.io/ipns/ipns.io/index.html?arg=foo&bar=buzz#hashTest') + }) + it('should be normalized if web+dweb:/ipfs/{CID}', function () { + const request = url2request('https://duckduckgo.com/?q=web%2Bdweb%3A%2Fipfs%2FQmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR%3Farg%3Dfoo%26bar%3Dbuzz%23hash&ia=software') + expect(modifyRequest(request).redirectUrl).to.equal('https://ipfs.io/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?arg=foo&bar=buzz#hash') + }) + it('should not be normalized if web+dweb://ipfs/{CID}', function () { + const request = url2request('https://duckduckgo.com/?q=web%2Bdweb%3A%2F%2Fipfs%2FQmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR%3Farg%3Dfoo%26bar%3Dbuzz%23hash&ia=software') + expect(modifyRequest(request)).to.equal(undefined) + }) + it('should be normalized if web+dweb:/ipns/{foo}', function () { + const request = url2request('https://duckduckgo.com/?q=web%2Bdweb%3A%2Fipns%2Fipfs.io%2Findex.html%3Farg%3Dfoo%26bar%3Dbuzz%23hash&ia=web') + expect(modifyRequest(request).redirectUrl).to.equal('https://ipfs.io/ipns/ipfs.io/index.html?arg=foo&bar=buzz#hash') + }) + it('should not be normalized if web+dweb://ipns/{foo}', function () { + const request = url2request('https://duckduckgo.com/?q=web%2Bdweb%3A%2F%2Fipns%2Fipfs.io%2Findex.html%3Farg%3Dfoo%26bar%3Dbuzz%23hash&ia=web') + expect(modifyRequest(request)).to.equal(undefined) + }) + + it('should not be normalized if disabled in Preferences', function () { + state.catchUnhandledProtocols = false + const request = url2request('https://duckduckgo.com/?q=ipfs%3A%2FQmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR%3FargTest%23hashTest&foo=bar') + expect(modifyRequest(request)).to.equal(undefined) + }) + it('should not be normalized if CID is invalid', function () { + state.catchUnhandledProtocols = false + const request = url2request('https://duckduckgo.com/?q=ipfs%3A%2FnotARealIpfsPathWithCid%3FargTest%23hashTest&foo=bar') + expect(modifyRequest(request)).to.equal(undefined) + }) + it('should not be normalized if presence of %3A%2F is a false-positive', function () { + state.catchUnhandledProtocols = false + const request = url2request('https://duckduckgo.com/?q=foo%3A%2Fbar%3FargTest%23hashTest&foo=bar') + expect(modifyRequest(request)).to.equal(undefined) + }) + it('should not be normalized if request.type != main_frame', function () { + const xhrRequest = {url: 'https://duckduckgo.com/?q=ipfs%3A%2FQmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR%3FargTest%23hashTest&foo=bar', type: 'xmlhttprequest'} + expect(modifyRequest(xhrRequest)).to.equal(undefined) + }) + }) + + describe('request for IPFS path at a localhost', function () { + // we do not touch local requests, as it may interfere with other nodes running at the same machine + // or could produce false-positives such as redirection from 127.0.0.1:5001/ipfs/path to 127.0.0.1:8080/ipfs/path + it('should be left untouched if 127.0.0.1 is used', function () { + state.redirect = true + const request = url2request('http://127.0.0.1:5001/ipfs/QmPhnvn747LqwPYMJmQVorMaGbMSgA7mRRoyyZYz3DoZRQ/') + expect(modifyRequest(request)).to.equal(undefined) + }) + it('should be left untouched if localhost is used', function () { + // https://github.com/ipfs/ipfs-companion/issues/291 + state.redirect = true + const request = url2request('http://localhost:5001/ipfs/QmPhnvn747LqwPYMJmQVorMaGbMSgA7mRRoyyZYz3DoZRQ/') + expect(modifyRequest(request)).to.equal(undefined) + }) + it('should be left untouched if [::1] is used', function () { + // https://github.com/ipfs/ipfs-companion/issues/291 + state.redirect = true + const request = url2request('http://[::1]:5001/ipfs/QmPhnvn747LqwPYMJmQVorMaGbMSgA7mRRoyyZYz3DoZRQ/') + expect(modifyRequest(request)).to.equal(undefined) + }) + }) + + after(() => { + delete global.URL + }) +}) diff --git a/test/functional/lib/ipfs-request.test.js b/test/functional/lib/ipfs-request-external.test.js similarity index 99% rename from test/functional/lib/ipfs-request.test.js rename to test/functional/lib/ipfs-request-external.test.js index 047ae9b61..c5e1a17bd 100644 --- a/test/functional/lib/ipfs-request.test.js +++ b/test/functional/lib/ipfs-request-external.test.js @@ -13,7 +13,7 @@ const url2request = (string) => { return {url: string, type: 'main_frame'} } -describe('modifyRequest', function () { +describe('modifyRequest with external ipfsNodeType', function () { let state, dnsLink, ipfsPathValidator, modifyRequest before(() => { @@ -22,6 +22,7 @@ describe('modifyRequest', function () { beforeEach(() => { state = Object.assign(initState(optionDefaults), { + ipfsNodeType: 'external', peerCount: 1, redirect: true, catchUnhandledProtocols: true, diff --git a/yarn.lock b/yarn.lock index 9ada1d579..316b82ba6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5389,7 +5389,7 @@ level-js@^2.2.4: typedarray-to-buffer "~1.0.0" xtend "~2.1.2" -"level-js@github:timkuijsten/level.js#idbunwrapper": +level-js@timkuijsten/level.js#idbunwrapper: version "2.2.3" resolved "https://codeload.github.com/timkuijsten/level.js/tar.gz/18e03adab34c49523be7d3d58fafb0c632f61303" dependencies: @@ -6279,7 +6279,7 @@ multihashing-async@~0.4.6, multihashing-async@~0.4.7: murmurhash3js "^3.0.1" nodeify "^1.0.1" -"multiplex@github:dignifiedquire/multiplex": +multiplex@dignifiedquire/multiplex: version "6.7.0" resolved "https://codeload.github.com/dignifiedquire/multiplex/tar.gz/b5d5edd30454e2c978ee8c52df86f5f4840d2eab" dependencies: