From a0c0ca81ad76f666cdfd8adbd3493befd29e6bb3 Mon Sep 17 00:00:00 2001 From: Marcin Rataj Date: Mon, 23 Jul 2018 15:45:15 +0200 Subject: [PATCH] feat: support page actions for CID subdomains --- add-on/src/lib/ipfs-companion.js | 2 +- add-on/src/lib/ipfs-path.js | 14 ++++++++++-- add-on/src/popup/browser-action/store.js | 10 ++++----- package.json | 2 +- test/functional/lib/ipfs-path.test.js | 20 ++++++++++++++++++ test/functional/lib/ipfs-request.test.js | 15 +++++++++++++ yarn.lock | 27 ++++++++++++++++-------- 7 files changed, 72 insertions(+), 18 deletions(-) diff --git a/add-on/src/lib/ipfs-companion.js b/add-on/src/lib/ipfs-companion.js index e8866083d..895ca7a18 100644 --- a/add-on/src/lib/ipfs-companion.js +++ b/add-on/src/lib/ipfs-companion.js @@ -342,7 +342,7 @@ module.exports = async function init () { async function updatePageActionIndicator (tabId, url) { // Chrome does not permit for both pageAction and browserAction to be enabled at the same time // https://github.com/ipfs-shipyard/ipfs-companion/issues/398 - if (runtime.isFirefox && ipfsPathValidator.validIpfsOrIpnsUrl(url)) { + if (runtime.isFirefox && ipfsPathValidator.isIpfsPageActionsContext(url)) { if (url.startsWith(state.gwURLString) || url.startsWith(state.apiURLString)) { await browser.pageAction.setIcon({ tabId: tabId, path: '/icons/ipfs-logo-on.svg' }) await browser.pageAction.setTitle({ tabId: tabId, title: browser.i18n.getMessage('pageAction_titleIpfsAtCustomGateway') }) diff --git a/add-on/src/lib/ipfs-path.js b/add-on/src/lib/ipfs-path.js index 33966d2d4..919d94022 100644 --- a/add-on/src/lib/ipfs-path.js +++ b/add-on/src/lib/ipfs-path.js @@ -4,10 +4,21 @@ const IsIpfs = require('is-ipfs') function safeIpfsPath (urlOrPath) { + if (IsIpfs.subdomain(urlOrPath)) { + urlOrPath = subdomainToIpfsPath(urlOrPath) + } // better safe than sorry: https://github.com/ipfs/ipfs-companion/issues/303 return decodeURIComponent(urlOrPath.replace(/^.*(\/ip(f|n)s\/.+)$/, '$1')) } +function subdomainToIpfsPath (urlString) { + const url = new URL(urlString) + const fqdn = url.hostname.split('.') + const cid = fqdn[0] + const protocol = fqdn[1] + return `/${protocol}/${cid}${url.pathname}` +} + exports.safeIpfsPath = safeIpfsPath function urlAtPublicGw (path, pubGwUrl) { @@ -41,9 +52,8 @@ function createIpfsPathValidator (getState, dnsLink) { }, // Test if actions such as 'copy URL', 'pin/unpin' should be enabled for the URL - // (we explicitly disable IPNS for now as there is no support for pins) isIpfsPageActionsContext (url) { - return IsIpfs.url(url) && !url.startsWith(getState().apiURLString) + return (IsIpfs.url(url) && !url.startsWith(getState().apiURLString)) || IsIpfs.subdomain(url) } } diff --git a/add-on/src/popup/browser-action/store.js b/add-on/src/popup/browser-action/store.js index b529e83d4..7af5a1259 100644 --- a/add-on/src/popup/browser-action/store.js +++ b/add-on/src/popup/browser-action/store.js @@ -71,7 +71,7 @@ module.exports = (state, emitter) => { try { const ipfs = await getIpfsApi() - const currentPath = await resolveToIPFS(ipfs, new URL(state.currentTab.url).pathname) + const currentPath = await resolveToIPFS(ipfs, state.currentTab.url) const pinResult = await ipfs.pin.add(currentPath, { recursive: true }) console.log('ipfs.pin.add result', pinResult) state.isPinned = true @@ -90,7 +90,7 @@ module.exports = (state, emitter) => { try { const ipfs = await getIpfsApi() - const currentPath = await resolveToIPFS(ipfs, new URL(state.currentTab.url).pathname) + const currentPath = await resolveToIPFS(ipfs, state.currentTab.url) const result = await ipfs.pin.rm(currentPath, {recursive: true}) state.isPinned = false console.log('ipfs.pin.rm result', result) @@ -244,7 +244,7 @@ module.exports = (state, emitter) => { // skip update if there is an ongoing pin or unpin if (state.isPinning || state.isUnPinning) return try { - const currentPath = await resolveToIPFS(ipfs, new URL(status.currentTab.url).pathname) + const currentPath = await resolveToIPFS(ipfs, status.currentTab.url) const response = await ipfs.pin.ls(currentPath, {quiet: true}) console.log(`positive ipfs.pin.ls for ${currentPath}: ${JSON.stringify(response)}`) state.isPinned = true @@ -273,8 +273,8 @@ async function getIpfsApi () { return (bg && bg.ipfsCompanion) ? bg.ipfsCompanion.ipfs : null } -async function resolveToIPFS (ipfs, path) { - path = safeIpfsPath(path) // https://github.com/ipfs/ipfs-companion/issues/303 +async function resolveToIPFS (ipfs, urlOrPath) { + let path = safeIpfsPath(urlOrPath) // https://github.com/ipfs/ipfs-companion/issues/303 if (/^\/ipns/.test(path)) { const response = await ipfs.name.resolve(path, {recursive: true, nocache: false}) return response.Path diff --git a/package.json b/package.json index 0e3ec5e02..cdb84b151 100644 --- a/package.json +++ b/package.json @@ -93,7 +93,7 @@ "ipfs-css": "0.5.2", "ipfs-http-response": "0.1.2", "ipfs-postmsg-proxy": "3.0.0", - "is-ipfs": "0.3.2", + "is-ipfs": "0.4.2", "is-svg": "3.0.0", "lru_map": "0.3.3", "mime-types": "2.1.18", diff --git a/test/functional/lib/ipfs-path.test.js b/test/functional/lib/ipfs-path.test.js index 3be965a02..e5610baed 100644 --- a/test/functional/lib/ipfs-path.test.js +++ b/test/functional/lib/ipfs-path.test.js @@ -99,4 +99,24 @@ describe('ipfs-path.js', function () { }) }) }) + describe('isIpfsPageActionsContext', function () { + it('should return true for URL at IPFS Gateway', function () { + const url = 'https://ipfs.io/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest' + expect(ipfsPathValidator.isIpfsPageActionsContext(url)).to.equal(true) + }) + it('should return true for URL at IPFS Gateway with Base32 CIDv1 in subdomain', function () { + // context-actions are shown on publick gateways that use CID in subdomain as well + const url = 'http://bafkreigh2akiscaildcqabsyg3dfr6chu3fgpregiymsck7e7aqa4s52zy.ipfs.dweb.link/' + expect(ipfsPathValidator.isIpfsPageActionsContext(url)).to.equal(true) + }) + it('should return false for URL at IPFS Gateway with Base58 CIDv0 in subdomain', function () { + // context-actions are shown on publick gateways that use CID in subdomain as well + const url = 'http://QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR.ipfs.dweb.link/' + expect(ipfsPathValidator.isIpfsPageActionsContext(url)).to.equal(false) + }) + it('should return false for non-IPFS URL', function () { + const url = 'https://ipfs.io/ipfs/NotACid?argTest#hashTest' + expect(ipfsPathValidator.isIpfsPageActionsContext(url)).to.equal(false) + }) + }) }) diff --git a/test/functional/lib/ipfs-request.test.js b/test/functional/lib/ipfs-request.test.js index 46a755927..730a9b15e 100644 --- a/test/functional/lib/ipfs-request.test.js +++ b/test/functional/lib/ipfs-request.test.js @@ -433,6 +433,21 @@ describe('modifyRequest.onBeforeRequest', function () { }) }) + describe('request to FQDN with valid CID in subdomain', function () { + // we do not touch such requests for now, as HTTP-based local node usually can't provide the same origin-based guarantees + // we will redirect subdomains to ipfs:// when native handler is available + it('should be left untouched for IPFS', function () { + state.redirect = true + const request = url2request('http://bafybeigxjv2o4jse2lajbd5c7xxl5rluhyqg5yupln42252e5tcao7hbge.ipfs.dweb.link/') + expect(modifyRequest.onBeforeRequest(request)).to.equal(undefined) + }) + it('should be left untouched for IPNS', function () { + state.redirect = true + const request = url2request('http://bafybeigxjv2o4jse2lajbd5c7xxl5rluhyqg5yupln42252e5tcao7hbge.ipns.dweb.link/') + expect(modifyRequest.onBeforeRequest(request)).to.equal(undefined) + }) + }) + describe('request to FQDN with dnslink experiment enabled', function () { let activeGateway beforeEach(function () { diff --git a/yarn.lock b/yarn.lock index aa9205cb0..f0bfb3299 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1602,16 +1602,16 @@ browserslist@^3.2.6: caniuse-lite "^1.0.30000844" electron-to-chromium "^1.3.47" -bs58@=2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/bs58/-/bs58-2.0.0.tgz#72b713bed223a0ac518bbda0e3ce3f4817f39eb5" - -bs58@^4.0.0, bs58@^4.0.1: +bs58@4.0.1, bs58@^4.0.0, bs58@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a" dependencies: base-x "^3.0.2" +bs58@=2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/bs58/-/bs58-2.0.0.tgz#72b713bed223a0ac518bbda0e3ce3f4817f39eb5" + bs58check@<3.0.0, bs58check@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/bs58check/-/bs58check-2.1.1.tgz#8a5d0e587af97b784bf9cbf1b29f454d82bc0222" @@ -1994,7 +1994,7 @@ ci-info@^1.0.0: version "1.1.3" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.1.3.tgz#710193264bb05c77b8c90d02f5aaf22216a667b2" -cids@^0.5.2, cids@^0.5.3, cids@~0.5.1, cids@~0.5.2, cids@~0.5.3: +cids@0.5.3, cids@^0.5.2, cids@^0.5.3, cids@~0.5.1, cids@~0.5.2, cids@~0.5.3: version "0.5.3" resolved "https://registry.yarnpkg.com/cids/-/cids-0.5.3.tgz#9a25b697eb76faf807afcec35c4ab936edfbd0a4" dependencies: @@ -5329,7 +5329,16 @@ is-installed-globally@^0.1.0: global-dirs "^0.1.0" is-path-inside "^1.0.0" -is-ipfs@0.3.2, is-ipfs@~0.3.2: +is-ipfs@0.4.2: + version "0.4.2" + resolved "https://registry.yarnpkg.com/is-ipfs/-/is-ipfs-0.4.2.tgz#06a858769cbfdac39a3e0ed1ca157e3f3fcc84fc" + dependencies: + bs58 "4.0.1" + cids "0.5.3" + multibase "0.4.0" + multihashes "0.4.13" + +is-ipfs@~0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/is-ipfs/-/is-ipfs-0.3.2.tgz#c4650b838e36fd0151de5896b2ff319fe8936182" dependencies: @@ -7016,7 +7025,7 @@ multiaddr@^5.0.0: varint "^5.0.0" xtend "^4.0.1" -multibase@~0.4.0: +multibase@0.4.0, multibase@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/multibase/-/multibase-0.4.0.tgz#1bdb62c82de0114f822a1d8751bcbee91cd2efba" dependencies: @@ -7041,7 +7050,7 @@ multicodec@~0.2.7: dependencies: varint "^5.0.0" -multihashes@^0.4.13, multihashes@~0.4.12, multihashes@~0.4.13, multihashes@~0.4.9: +multihashes@0.4.13, multihashes@^0.4.13, multihashes@~0.4.12, multihashes@~0.4.13, multihashes@~0.4.9: version "0.4.13" resolved "https://registry.yarnpkg.com/multihashes/-/multihashes-0.4.13.tgz#d10bd71bd51d24aa894e2a6f1457146bb7bac125" dependencies: