Skip to content

Commit

Permalink
feat: support redirect opt-out hint in URLs
Browse files Browse the repository at this point in the history
- cleanup of existing opt-out rules
- detect `x-ipfs-no-redirect` in the URL and skip redirects
  • Loading branch information
lidel committed Jun 27, 2018
1 parent 87d7dbc commit 6ea409d
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 26 deletions.
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,13 @@ Learn more at [ipfs.io](https://ipfs.io) (it is really cool, we promise!)

#### Automagical Detection of IPFS Resources

Requests for IPFS-like paths (`/ipfs/$cid` or `/ipns/$peerid_or_fqdn-with-dnslink`) are detected on any website.
If tested path is a valid IPFS address it gets redirected and loaded from a local gateway, e.g:
`https://ipfs.io/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR`
`http://127.0.0.1:8080/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR`
Requests for IPFS-like paths (`/ipfs/$cid` or `/ipns/$peerid_or_fqdn-with-dnslink`) are detected on any website.
If tested path is a valid IPFS address it gets redirected and loaded from a local gateway, e.g:
> `https://ipfs.io/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR`
> `http://127.0.0.1:8080/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR`
It is possible to temporarily opt-out from redirect by suspending extension via global toggle,
or by including `x-ipfs-no-redirect` in the URL ([as a hash or query parameter](https://ipfs.io/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?x-ipfs-no-redirect#x-ipfs-no-redirect)).

#### IPFS API as `window.ipfs`

Expand Down
69 changes: 48 additions & 21 deletions add-on/src/lib/ipfs-request.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,14 @@

const IsIpfs = require('is-ipfs')
const { urlAtPublicGw } = require('./ipfs-path')
const redirectOptOutHint = 'x-ipfs-no-redirect'

function createRequestModifier (getState, dnsLink, ipfsPathValidator, runtime) {
return function modifyRequest (request) {
const state = getState()

// skip requests to the custom gateway or API (otherwise we have too much recursion)
if (request.url.startsWith(state.gwURLString) || request.url.startsWith(state.apiURLString)) {
return
}

// skip websocket handshake (not supported by HTTP2IPFS gateways)
if (request.type === 'websocket') {
return
}

// skip all local requests
if (request.url.startsWith('http://127.0.0.1:') || request.url.startsWith('http://localhost:') || request.url.startsWith('http://[::1]:')) {
// early sanity checks
if (preNormalizationSkip(state, request)) {
return
}

Expand All @@ -40,32 +31,63 @@ function createRequestModifier (getState, dnsLink, ipfsPathValidator, runtime) {
}
}

// skip requests to the public gateway if embedded node is running (otherwise we have too much recursion)
if (state.ipfsNodeType === 'embedded' && request.url.startsWith(state.pubGwURLString)) {
return
// TODO: do not skip and redirect to `ipfs://` and `ipns://` if hasNativeProtocolHandler === true
}

// handle redirects to custom gateway
if (state.active && state.redirect) {
// Ignore preload requests
if (request.method === 'HEAD') {
// late sanity checks
if (postNormalizationSkip(state, request)) {
return
}
// Detect valid /ipfs/ and /ipns/ on any site
if (ipfsPathValidator.publicIpfsOrIpnsResource(request.url) && isSafeToRedirect(request, runtime)) {
return redirectToGateway(request.url, state)
}
// Look for dnslink in TXT records of visited sites
if (state.dnslink && dnsLink.isDnslookupSafeForURL(request.url)) {
if (state.dnslink && dnsLink.isDnslookupSafeForURL(request.url) && isSafeToRedirect(request, runtime)) {
return dnsLink.dnslinkLookupAndOptionalRedirect(request.url)
}
}
}
}

exports.redirectOptOutHint = redirectOptOutHint
exports.createRequestModifier = createRequestModifier

// types of requests to be skipped before any normalization happens
function preNormalizationSkip (state, request) {
// skip requests to the custom gateway or API (otherwise we have too much recursion)
if (request.url.startsWith(state.gwURLString) || request.url.startsWith(state.apiURLString)) {
return true
}

// skip websocket handshake (not supported by HTTP2IPFS gateways)
if (request.type === 'websocket') {
return true
}

// skip all local requests
if (request.url.startsWith('http://127.0.0.1:') || request.url.startsWith('http://localhost:') || request.url.startsWith('http://[::1]:')) {
return true
}

return false
}

// types of requests to be skipped after expensive normalization happens
function postNormalizationSkip (state, request) {
// Ignore preload requests
if (request.method === 'HEAD') {
return true
}

// skip requests to the public gateway if embedded node is running (otherwise we have too much recursion)
if (state.ipfsNodeType === 'embedded' && request.url.startsWith(state.pubGwURLString)) {
return true
// TODO: do not skip and redirect to `ipfs://` and `ipns://` if hasNativeProtocolHandler === true
}

return false
}

function redirectToGateway (requestUrl, state) {
// TODO: redirect to `ipfs://` if hasNativeProtocolHandler === true
const gwUrl = state.ipfsNodeType === 'embedded' ? state.pubGwURL : state.gwURL
Expand All @@ -77,6 +99,11 @@ function redirectToGateway (requestUrl, state) {
}

function isSafeToRedirect (request, runtime) {
// Do not redirect if URL includes opt-out hint
if (request.url.includes('x-ipfs-no-redirect')) {
return false
}

// Ignore XHR requests for which redirect would fail due to CORS bug in Firefox
// See: https://github.com/ipfs-shipyard/ipfs-companion/issues/436
// TODO: revisit when upstream bug is addressed
Expand Down
7 changes: 6 additions & 1 deletion test/functional/lib/ipfs-request.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const { URL } = require('url')
const browser = require('sinon-chrome')
const { initState } = require('../../../add-on/src/lib/state')
const createRuntimeChecks = require('../../../add-on/src/lib/runtime-checks')
const { createRequestModifier } = require('../../../add-on/src/lib/ipfs-request')
const { createRequestModifier, redirectOptOutHint } = 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')
Expand Down Expand Up @@ -77,6 +77,11 @@ describe('modifyRequest', function () {
const request = url2request('https://google.com/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest')
expect(modifyRequest(request)).to.equal(undefined)
})
it(`should be left untouched if URL includes opt-out hint (${nodeType} node)`, function () {
const request = url2request('https://google.com/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?x-ipfs-no-redirect#hashTest')
expect(modifyRequest(request)).to.equal(undefined)
expect(redirectOptOutHint).to.equal('x-ipfs-no-redirect')
})
it(`should be left untouched if CID is invalid (${nodeType} node)`, function () {
const request = url2request('https://google.com/ipfs/notacid?argTest#hashTest')
expect(modifyRequest(request)).to.equal(undefined)
Expand Down

0 comments on commit 6ea409d

Please sign in to comment.