diff --git a/add-on/src/lib/common.js b/add-on/src/lib/common.js
index fdf449559..6d9a3236b 100644
--- a/add-on/src/lib/common.js
+++ b/add-on/src/lib/common.js
@@ -37,6 +37,7 @@ async function initStates (options) {
state.automaticMode = options.automaticMode
state.linkify = options.linkify
state.dnslink = options.dnslink
+ state.catchUnhandledProtocols = options.catchUnhandledProtocols
state.dnslinkCache = /* global LRUMap */ new LRUMap(1000)
}
@@ -54,25 +55,14 @@ function publicIpfsResource (url) {
return window.IsIpfs.url(url) && !url.startsWith(state.gwURLString) && !url.startsWith(state.apiURLString)
}
-function redirectToCustomGateway (request) {
- const url = new URL(request.url)
+function redirectToCustomGateway (requestUrl) {
+ const url = new URL(requestUrl)
url.protocol = state.gwURL.protocol
url.host = state.gwURL.host
url.port = state.gwURL.port
return { redirectUrl: url.toString() }
}
-function redirectToNormalizedPath (request) {
- const url = new URL(request.url)
- let path = decodeURIComponent(url.pathname)
- path = path.replace(/^\/web\+fs:[/]*/i, '/') // web+fs://ipfs/Qm → /ipfs/Qm
- path = path.replace(/^\/web\+dweb:[/]*/i, '/') // web+dweb://ipfs/Qm → /ipfs/Qm
- path = path.replace(/^\/web\+([^:]+):[/]*/i, '/$1/') // web+foo://Qm → /foo/Qm
- path = path.replace(/^\/ip([^/]+)\/ip[^/]+\//, '/ip$1/') // /ipfs/ipfs/Qm → /ipfs/Qm
- url.pathname = path
- return { redirectUrl: url.toString() }
-}
-
// HTTP Request Hooks
// ===================================================================
@@ -95,14 +85,25 @@ function onBeforeSendHeaders (request) {
}
function onBeforeRequest (request) {
- if (request.url.startsWith('https://ipfs.io/web%2B')) {
+ // poor-mans protocol handlers - https://github.com/ipfs/ipfs-companion/issues/164#issuecomment-328374052
+ if (state.catchUnhandledProtocols && mayContainUnhandledIpfsProtocol(request)) {
+ const fix = normalizedUnhandledIpfsProtocol(request)
+ if (fix) {
+ return fix
+ }
+ }
+
+ // handler for protocol_handlers from manifest.json
+ if (webPlusProtocolRequest(request)) {
// fix path passed via custom protocol
- return redirectToNormalizedPath(request)
+ return normalizedWebPlusRequest(request)
}
+
+ // handle redirects to custom gateway
if (state.redirect) {
// IPFS resources
if (publicIpfsResource(request.url)) {
- return redirectToCustomGateway(request)
+ return redirectToCustomGateway(request.url)
}
// Look for dnslink in TXT records of visited sites
if (isDnslookupEnabled(request)) {
@@ -111,6 +112,58 @@ function onBeforeRequest (request) {
}
}
+// PROTOCOL HANDLERS: web+ in Firefox (protocol_handlers from manifest.json)
+// ===================================================================
+
+const webPlusProtocolHandler = 'https://ipfs.io/web%2B'
+
+function webPlusProtocolRequest (request) {
+ return request.url.startsWith(webPlusProtocolHandler)
+}
+
+function pathAtPublicGw (path) {
+ return new URL(`https://ipfs.io${path}`).toString()
+}
+
+function normalizedWebPlusRequest (request) {
+ let path = decodeURIComponent(new URL(request.url).pathname)
+ path = path.replace(/^\/web\+fs:[/]*/i, '/') // web+fs://ipfs/Qm → /ipfs/Qm
+ path = path.replace(/^\/web\+dweb:[/]*/i, '/') // web+dweb://ipfs/Qm → /ipfs/Qm
+ path = path.replace(/^\/web\+([^:]+):[/]*/i, '/$1/') // web+foo://Qm → /foo/Qm
+ path = path.replace(/^\/ip([^/]+)\/ip[^/]+\//, '/ip$1/') // /ipfs/ipfs/Qm → /ipfs/Qm
+ return { redirectUrl: pathAtPublicGw(path) }
+}
+
+// PROTOCOL HANDLERS: UNIVERSAL FALLBACK FOR UNHANDLED PROTOCOLS
+// ===================================================================
+
+const unhandledIpfsRE = /=(?:web%2B|)(ipfs|ipns|fs|dweb)%3A(?:%2F|)(%2F[^&]+)/
+
+function mayContainUnhandledIpfsProtocol (request) {
+ // TODO: run only for google, bing, duckduckgo etc
+ // TODO: add checkbox under experiments
+ return request.url.includes('%3A%2F')
+}
+
+function unhandledIpfsPath (requestUrl) {
+ const unhandled = requestUrl.match(unhandledIpfsRE)
+ if (unhandled && unhandled.length > 1) {
+ const unhandledProtocol = decodeURIComponent(unhandled[1])
+ const unhandledPath = decodeURIComponent(unhandled[2])
+ return window.IsIpfs.path(unhandledPath) ? unhandledPath : `/${unhandledProtocol}${unhandledPath}`
+ }
+ return null
+}
+
+function normalizedUnhandledIpfsProtocol (request) {
+ const path = unhandledIpfsPath(request.url)
+ if (window.IsIpfs.path(path)) {
+ // replace search query with fake request to the public gateway
+ // (will be redirected later, if needed)
+ return { redirectUrl: pathAtPublicGw(path) }
+ }
+}
+
// DNSLINK
// ===================================================================
@@ -548,6 +601,8 @@ function onStorageChange (changes, area) { // eslint-disable-line no-unused-vars
state.redirect = change.newValue
} else if (key === 'linkify') {
state.linkify = change.newValue
+ } else if (key === 'catchUnhandledProtocols') {
+ state.catchUnhandledProtocols = change.newValue
} else if (key === 'automaticMode') {
state.automaticMode = change.newValue
} else if (key === 'dnslink') {
diff --git a/add-on/src/lib/option-defaults.js b/add-on/src/lib/option-defaults.js
index 8e7bf16c5..9fdf6c614 100644
--- a/add-on/src/lib/option-defaults.js
+++ b/add-on/src/lib/option-defaults.js
@@ -7,6 +7,7 @@ const optionDefaults = Object.freeze({ // eslint-disable-line no-unused-vars
automaticMode: true,
linkify: false,
dnslink: false,
+ catchUnhandledProtocols: true,
customGatewayUrl: 'http://127.0.0.1:8080',
ipfsApiUrl: 'http://127.0.0.1:5001',
ipfsApiPollMs: 3000
diff --git a/add-on/src/options/options.html b/add-on/src/options/options.html
index 6f96a0062..5de504f2b 100644
--- a/add-on/src/options/options.html
+++ b/add-on/src/options/options.html
@@ -138,6 +138,15 @@