From 7111845f3471fe3a7c227a666572c96aeb78166c Mon Sep 17 00:00:00 2001 From: colinfruit <17092461+colinfruit@users.noreply.github.com> Date: Sun, 6 Oct 2019 14:00:29 -0400 Subject: [PATCH 1/9] recover dead public gateways to public gateway url --- add-on/src/lib/ipfs-companion.js | 5 ++ add-on/src/lib/ipfs-request.js | 82 +++++++++++++++++++++++++++----- 2 files changed, 74 insertions(+), 13 deletions(-) diff --git a/add-on/src/lib/ipfs-companion.js b/add-on/src/lib/ipfs-companion.js index ffc6f455b..bc390bfcf 100644 --- a/add-on/src/lib/ipfs-companion.js +++ b/add-on/src/lib/ipfs-companion.js @@ -105,6 +105,7 @@ module.exports = async function init () { browser.webRequest.onBeforeRequest.addListener(onBeforeRequest, { urls: [''] }, ['blocking']) browser.webRequest.onHeadersReceived.addListener(onHeadersReceived, { urls: [''] }, ['blocking', 'responseHeaders']) browser.webRequest.onErrorOccurred.addListener(onErrorOccurred, { urls: [''] }) + browser.webRequest.onCompleted.addListener(onCompleted, { urls: [''] }) browser.storage.onChanged.addListener(onStorageChange) browser.webNavigation.onCommitted.addListener(onNavigationCommitted) browser.webNavigation.onDOMContentLoaded.addListener(onDOMContentLoaded) @@ -169,6 +170,10 @@ module.exports = async function init () { return modifyRequest.onErrorOccurred(request) } + function onCompleted (request) { + return modifyRequest.onCompleted(request) + } + // RUNTIME MESSAGES (one-off messaging) // =================================================================== // https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/runtime/sendMessage diff --git a/add-on/src/lib/ipfs-request.js b/add-on/src/lib/ipfs-request.js index 03d179e60..2cb9dd04c 100644 --- a/add-on/src/lib/ipfs-request.js +++ b/add-on/src/lib/ipfs-request.js @@ -20,6 +20,26 @@ const recoverableErrors = new Set([ 'net::ERR_INTERNET_DISCONNECTED' // no network ]) +const recoverableErrorCodes = new Set([ + 404, + 408, + 410, + 415, + 451, + 500, + 502, + 503, + 504, + 509, + 520, + 521, + 522, + 523, + 524, + 525, + 526 +]) + // Request modifier provides event listeners for the various stages of making an HTTP request // API Details: https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/webRequest function createRequestModifier (getState, dnslinkResolver, ipfsPathValidator, runtime) { @@ -380,6 +400,7 @@ function createRequestModifier (getState, dnslinkResolver, ipfsPathValidator, ru // console.log('onErrorOccurred:' + request.error) // console.log('onErrorOccurred', request) // Check if error is final and can be recovered via DNSLink + let redirect const recoverableViaDnslink = state.dnslinkPolicy && request.type === 'main_frame' && @@ -387,22 +408,40 @@ function createRequestModifier (getState, dnslinkResolver, ipfsPathValidator, ru if (recoverableViaDnslink && dnslinkResolver.canLookupURL(request.url)) { // Explicit call to ignore global DNSLink policy and force DNS TXT lookup const cachedDnslink = dnslinkResolver.readAndCacheDnslink(new URL(request.url).hostname) - const dnslinkRedirect = dnslinkResolver.dnslinkRedirect(request.url, cachedDnslink) - // We can't redirect in onErrorOccurred, so if DNSLink is present - // recover by opening IPNS version in a new tab - // TODO: add tests and demo - if (dnslinkRedirect) { - log(`onErrorOccurred: recovering using dnslink for ${request.url}`, dnslinkRedirect) - const currentTabId = await browser.tabs.query({ active: true, currentWindow: true }).then(tabs => tabs[0].id) - await browser.tabs.create({ - active: true, - openerTabId: currentTabId, - url: dnslinkRedirect.redirectUrl - }) + redirect = dnslinkResolver.dnslinkRedirect(request.url, cachedDnslink) + log(`onErrorOccurred: attempting to recover using dnslink for ${request.url}`, redirect) + } + // if error cannot be recovered via DNSLink + // direct the request to the public gateway + const recoverableViaPubGw = isRecoverableViaPubGw(request, state, ipfsPathValidator) + if (!redirect && recoverableViaPubGw) { + const redirectUrl = ipfsPathValidator.resolveToPublicUrl(request.url, state.pubGwURLString) + redirect = { redirectUrl } + log(`onErrorOccurred: attempting to recover using public gateway for ${request.url}`, redirect) + } + // We can't redirect in onErrorOccurred, so if DNSLink is present + // recover by opening IPNS version in a new tab + // TODO: add tests and demo + if (redirect) { + createTabWithURL(redirect, browser) + } + }, + + async onCompleted (request) { + const state = getState() + + const recoverableViaPubGw = + isRecoverableViaPubGw(request, state, ipfsPathValidator) && + recoverableErrorCodes.has(request.statusCode) + if (recoverableViaPubGw) { + const redirectUrl = ipfsPathValidator.resolveToPublicUrl(request.url, state.pubGwURLString) + const redirect = { redirectUrl } + if (redirect) { + log(`onErrorOccurred: attempting to recover using public gateway for ${request.url}`, redirect) + createTabWithURL(redirect, browser) } } } - } } @@ -508,3 +547,20 @@ function normalizedUnhandledIpfsProtocol (request, pubGwUrl) { function findHeaderIndex (name, headers) { return headers.findIndex(x => x.name && x.name.toLowerCase() === name.toLowerCase()) } + +// utility functions for handling redirects +// from onErrorOccurred and onCompleted +function isRecoverableViaPubGw (request, state, ipfsPathValidator) { + return ipfsPathValidator.publicIpfsOrIpnsResource(request.url) && + !request.url.startsWith(state.pubGwURLString) && + request.type === 'main_frame' +} + +async function createTabWithURL (redirect, browser) { + const currentTabId = await browser.tabs.query({ active: true, currentWindow: true }).then(tabs => tabs[0].id) + await browser.tabs.create({ + active: true, + openerTabId: currentTabId, + url: redirect.redirectUrl + }) +} From 3420a555c53fadba94e5f71c025ca16af638d937 Mon Sep 17 00:00:00 2001 From: colinfruit <17092461+colinfruit@users.noreply.github.com> Date: Sun, 6 Oct 2019 14:40:16 -0400 Subject: [PATCH 2/9] add experimental option to enable recover of broken requests to third-party gateways --- add-on/_locales/en/messages.json | 8 ++++++++ add-on/src/lib/ipfs-companion.js | 3 +++ add-on/src/lib/ipfs-request.js | 3 ++- add-on/src/lib/options.js | 1 + add-on/src/options/forms/experiments-form.js | 11 +++++++++++ add-on/src/options/page.js | 1 + 6 files changed, 26 insertions(+), 1 deletion(-) diff --git a/add-on/_locales/en/messages.json b/add-on/_locales/en/messages.json index c4bf21393..720e19def 100644 --- a/add-on/_locales/en/messages.json +++ b/add-on/_locales/en/messages.json @@ -375,6 +375,14 @@ "message": "Check before HTTP request", "description": "A select field option description on the Preferences screen (option_dnslinkPolicy_enabled)" }, + "option_recoverViaPublicGateway_title": { + "message": "Recover links via public gateway", + "description": "An option title on the Preferences screen (option_recoverViaPublicGateway_title)" + }, + "option_recoverViaPublicGateway_description": { + "message": "Redirect broken public gateway requests to the default public gateway", + "description": "An option description on the Preferences screen (option_recoverViaPublicGateway_description)" + }, "option_detectIpfsPathHeader_title": { "message": "Detect X-Ipfs-Path Header", "description": "An option title on the Preferences screen (option_detectIpfsPathHeader_title)" diff --git a/add-on/src/lib/ipfs-companion.js b/add-on/src/lib/ipfs-companion.js index bc390bfcf..49f960cd3 100644 --- a/add-on/src/lib/ipfs-companion.js +++ b/add-on/src/lib/ipfs-companion.js @@ -675,6 +675,9 @@ module.exports = async function init () { await browser.storage.local.set({ detectIpfsPathHeader: true }) } break + case 'recoverViaPublicGateway': + state[key] = change.newValue + break case 'logNamespaces': shouldReloadExtension = true state[key] = localStorage.debug = change.newValue diff --git a/add-on/src/lib/ipfs-request.js b/add-on/src/lib/ipfs-request.js index 2cb9dd04c..6cffb652b 100644 --- a/add-on/src/lib/ipfs-request.js +++ b/add-on/src/lib/ipfs-request.js @@ -551,7 +551,8 @@ function findHeaderIndex (name, headers) { // utility functions for handling redirects // from onErrorOccurred and onCompleted function isRecoverableViaPubGw (request, state, ipfsPathValidator) { - return ipfsPathValidator.publicIpfsOrIpnsResource(request.url) && + return state.recoverViaPublicGateway && + ipfsPathValidator.publicIpfsOrIpnsResource(request.url) && !request.url.startsWith(state.pubGwURLString) && request.type === 'main_frame' } diff --git a/add-on/src/lib/options.js b/add-on/src/lib/options.js index 1bb01861b..16036b8a9 100644 --- a/add-on/src/lib/options.js +++ b/add-on/src/lib/options.js @@ -16,6 +16,7 @@ exports.optionDefaults = Object.freeze({ automaticMode: true, linkify: false, dnslinkPolicy: 'best-effort', + recoverViaPublicGateway: false, detectIpfsPathHeader: true, preloadAtPublicGateway: true, catchUnhandledProtocols: true, diff --git a/add-on/src/options/forms/experiments-form.js b/add-on/src/options/forms/experiments-form.js index ccd03a9a9..7158c9511 100644 --- a/add-on/src/options/forms/experiments-form.js +++ b/add-on/src/options/forms/experiments-form.js @@ -11,6 +11,7 @@ function experimentsForm ({ catchUnhandledProtocols, linkify, dnslinkPolicy, + recoverViaPublicGateway, detectIpfsPathHeader, ipfsProxy, logNamespaces, @@ -22,6 +23,7 @@ function experimentsForm ({ const onCatchUnhandledProtocolsChange = onOptionChange('catchUnhandledProtocols') const onLinkifyChange = onOptionChange('linkify') const onDnslinkPolicyChange = onOptionChange('dnslinkPolicy') + const onrecoverViaPublicGatewayChange = onOptionChange('recoverViaPublicGateway') const onDetectIpfsPathHeaderChange = onOptionChange('detectIpfsPathHeader') const onIpfsProxyChange = onOptionChange('ipfsProxy') @@ -96,6 +98,15 @@ function experimentsForm ({ +
+ +
${switchToggle({ id: 'recoverViaPublicGateway', checked: recoverViaPublicGateway, onchange: onrecoverViaPublicGatewayChange })}
+
-