diff --git a/add-on/src/lib/ipfs-request.js b/add-on/src/lib/ipfs-request.js index 5e5871b2a..e68117f4f 100644 --- a/add-on/src/lib/ipfs-request.js +++ b/add-on/src/lib/ipfs-request.js @@ -7,6 +7,7 @@ log.error = debug('ipfs-companion:request:error') const LRU = require('lru-cache') const IsIpfs = require('is-ipfs') +const isFQDN = require('is-fqdn') const { pathAtHttpGateway } = require('./ipfs-path') const redirectOptOutHint = 'x-ipfs-companion-no-redirect' const recoverableErrors = new Set([ @@ -74,7 +75,7 @@ function createRequestModifier (getState, dnslinkResolver, ipfsPathValidator, ru 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 => - fqdn.endsWith(optout) || (parentFqdn && parentFqdn.endsWith(optout) + fqdn !== 'gateway.ipfs.io' && (fqdn.endsWith(optout) || (parentFqdn && parentFqdn.endsWith(optout)) ))) { ignore(request.requestId) } @@ -492,13 +493,24 @@ function normalizedRedirectingProtocolRequest (request, pubGwUrl) { path = path.replace(/^#dweb:\//i, '/') // dweb:/ipfs/Qm → /ipfs/Qm path = path.replace(/^#ipfs:\/\//i, '/ipfs/') // ipfs://Qm → /ipfs/Qm path = path.replace(/^#ipns:\/\//i, '/ipns/') // ipns://Qm → /ipns/Qm - // console.log(`oldPath: '${oldPath}' new: '${path}'`) + // additional fixups of the final path + path = fixupDnslinkPath(path) // /ipfs/example.com → /ipns/example.com if (oldPath !== path && IsIpfs.path(path)) { return { redirectUrl: pathAtHttpGateway(path, pubGwUrl) } } return null } +// idempotent /ipfs/example.com → /ipns/example.com +function fixupDnslinkPath (path) { + if (!(path && path.startsWith('/ipfs/'))) return path + const [, root] = path.match(/^\/ipfs\/([^/?#]+)/) + if (root && !IsIpfs.cid(root) && isFQDN(root)) { + return path.replace(/^\/ipfs\//, '/ipns/') + } + return path +} + // SEARCH-HIJACK HANDLERS: UNIVERSAL FALLBACK FOR UNHANDLED PROTOCOLS // (Used in Chrome and other browsers that do not provide better alternatives) // Background: https://github.com/ipfs-shipyard/ipfs-companion/issues/164#issuecomment-328374052 @@ -521,9 +533,10 @@ function unhandledIpfsPath (requestUrl) { } function normalizedUnhandledIpfsProtocol (request, pubGwUrl) { - const path = unhandledIpfsPath(request.url) + let path = unhandledIpfsPath(request.url) + path = fixupDnslinkPath(path) // /ipfs/example.com → /ipns/example.com if (IsIpfs.path(path)) { - // replace search query with fake request to the public gateway + // replace search query with a request to a public gateway // (will be redirected later, if needed) return { redirectUrl: pathAtHttpGateway(path, pubGwUrl) } } diff --git a/test/functional/lib/ipfs-request-protocol-handlers.js b/test/functional/lib/ipfs-request-protocol-handlers.test.js similarity index 94% rename from test/functional/lib/ipfs-request-protocol-handlers.js rename to test/functional/lib/ipfs-request-protocol-handlers.test.js index 7a3e384d5..6beb2aff3 100644 --- a/test/functional/lib/ipfs-request-protocol-handlers.js +++ b/test/functional/lib/ipfs-request-protocol-handlers.test.js @@ -1,6 +1,5 @@ 'use strict' const { describe, it, before, beforeEach, after } = require('mocha') -// const sinon = require('sinon') const { expect } = require('chai') const { URL } = require('url') const browser = require('sinon-chrome') @@ -30,6 +29,7 @@ describe('modifyRequest.onBeforeRequest:', function () { ipfsNodeType: 'external', peerCount: 1, redirect: true, + dnslinkPolicy: false, // dnslink test suite is in ipfs-request-dnslink.test.js catchUnhandledProtocols: true, gwURLString: 'http://127.0.0.1:8080', pubGwURLString: 'https://ipfs.io' @@ -66,14 +66,18 @@ describe('modifyRequest.onBeforeRequest:', function () { const request = url2request('https://gateway.ipfs.io/ipfs/QmXQY7mKr28B964Uj4ouq3fPgkNLqzaKiajTA7surAiQuD#ipfs%3A%2F%2FQmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR%3FargTest%23hashTest') expect(modifyRequest.onBeforeRequest(request).redirectUrl).to.equal('https://ipfs.io/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest') }) - it('should not be normalized if ipns:/{foo}', function () { + it('should not be normalized if ipns:/{fqdn}', function () { const request = url2request('https://gateway.ipfs.io/ipfs/QmXQY7mKr28B964Uj4ouq3fPgkNLqzaKiajTA7surAiQuD#ipns%3A%2Fipfs.io%3FargTest%23hashTest') expect(modifyRequest.onBeforeRequest(request)).to.equal(undefined) }) - it('should be normalized if ipns://{foo}', function () { + it('should be normalized if ipns://{fqdn}', function () { const request = url2request('https://gateway.ipfs.io/ipfs/QmXQY7mKr28B964Uj4ouq3fPgkNLqzaKiajTA7surAiQuD#ipns%3A%2F%2Fipfs.io%3FargTest%23hashTest') expect(modifyRequest.onBeforeRequest(request).redirectUrl).to.equal('https://ipfs.io/ipns/ipfs.io?argTest#hashTest') }) + it('should be normalized if ipfs://{fqdn}', function () { + const request = url2request('https://gateway.ipfs.io/ipfs/QmXQY7mKr28B964Uj4ouq3fPgkNLqzaKiajTA7surAiQuD#ipfs%3A%2F%2Fipfs.io%3FargTest%23hashTest') + expect(modifyRequest.onBeforeRequest(request).redirectUrl).to.equal('https://ipfs.io/ipns/ipfs.io?argTest#hashTest') + }) it('should be normalized if dweb:/ipfs/{CID}', function () { const request = url2request('https://gateway.ipfs.io/ipfs/QmXQY7mKr28B964Uj4ouq3fPgkNLqzaKiajTA7surAiQuD#dweb%3A%2Fipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR%3FargTest%23hashTest') expect(modifyRequest.onBeforeRequest(request).redirectUrl).to.equal('https://ipfs.io/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest') @@ -151,6 +155,10 @@ describe('modifyRequest.onBeforeRequest:', function () { const request = url2request('https://duckduckgo.com/?q=ipns%3A%2F%2Fipns.io%2Findex.html%3Farg%3Dfoo%26bar%3Dbuzz%23hashTest') expect(modifyRequest.onBeforeRequest(request).redirectUrl).to.equal('https://ipfs.io/ipns/ipns.io/index.html?arg=foo&bar=buzz#hashTest') }) + it('should be normalized if ipfs://{fqdn}', function () { + const request = url2request('https://duckduckgo.com/?q=ipfs%3A%2F%2Fipns.io%2Findex.html%3Farg%3Dfoo%26bar%3Dbuzz%23hashTest') + expect(modifyRequest.onBeforeRequest(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.onBeforeRequest(request).redirectUrl).to.equal('https://ipfs.io/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?arg=foo&bar=buzz#hash')