Skip to content

Commit

Permalink
feat: Poor Man's Protocol Handlers
Browse files Browse the repository at this point in the history
This is an implementation of ridiculous idea from:
#164 (comment)

I am really sorry.
  • Loading branch information
lidel committed Sep 17, 2017
1 parent fa673ea commit b49d5f7
Show file tree
Hide file tree
Showing 5 changed files with 309 additions and 18 deletions.
87 changes: 71 additions & 16 deletions add-on/src/lib/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand All @@ -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
// ===================================================================

Expand All @@ -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)) {
Expand All @@ -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
// ===================================================================

Expand Down Expand Up @@ -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') {
Expand Down
1 change: 1 addition & 0 deletions add-on/src/lib/option-defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 9 additions & 0 deletions add-on/src/options/options.html
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,15 @@
<fieldset>
<legend>Experiments</legend>
<div><span><strong>Note:</strong> these features are work-in-progress and may degrade browser performance. <abbr title="Your Mileage May Vary">YMMV</abbr>.</span></div>
<div>
<label for="catchUnhandledProtocols">
<dl>
<dt>Catch Unhandled IPFS Protocols</dt>
<dd>Enables support for <code>ipfs://</code>, <code>ipns://</code> and <code>dweb://</code> by catching and normalizing requests done with unhandled protocols</dd>
</dl>
</label>
<input type="checkbox" id="catchUnhandledProtocols" />
</div>
<div>
<label for="linkify">
<dl>
Expand Down
3 changes: 1 addition & 2 deletions test/unit/00-init.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ describe('init.js', function () {
})

describe('onStorageChange()', function () {
it('should update ipfs API instance on IPFS API URL change', done => {
it('should update ipfs API instance on IPFS API URL change', function () {
const oldIpfsApiUrl = 'http://127.0.0.1:5001'
const newIpfsApiUrl = 'http://1.2.3.4:8080'
const changes = {ipfsApiUrl: {oldValue: oldIpfsApiUrl, newValue: newIpfsApiUrl}}
Expand All @@ -59,7 +59,6 @@ describe('init.js', function () {
onStorageChange(changes, area)
sinon.assert.calledOnce(IpfsApi)
sinon.assert.calledWith(IpfsApi, newCfg)
done()
})
})

Expand Down
Loading

0 comments on commit b49d5f7

Please sign in to comment.