@@ -28,7 +28,7 @@ function initIpfsApi (ipfsApiUrl) {
28
28
return window . IpfsApi ( { host : url . hostname , port : url . port , procotol : url . protocol } )
29
29
}
30
30
31
- async function initStates ( options ) {
31
+ function initStates ( options ) {
32
32
state . redirect = options . useCustomGateway
33
33
state . apiURL = new URL ( options . ipfsApiUrl )
34
34
state . apiURLString = state . apiURL . toString ( )
@@ -54,8 +54,30 @@ function registerListeners () {
54
54
// REDIRECT
55
55
// ===================================================================
56
56
57
- function publicIpfsResource ( url ) {
58
- return window . IsIpfs . url ( url ) && ! url . startsWith ( state . gwURLString ) && ! url . startsWith ( state . apiURLString )
57
+ function publicIpfsOrIpnsResource ( url ) {
58
+ // first, exclude gateway and api, otherwise we have infinite loop
59
+ if ( ! url . startsWith ( state . gwURLString ) && ! url . startsWith ( state . apiURLString ) ) {
60
+ // /ipfs/ is easy to validate, we just check if CID is correct and return if true
61
+ if ( window . IsIpfs . ipfsUrl ( url ) ) {
62
+ return true
63
+ }
64
+ // /ipns/ requires multiple stages/branches, as it can be FQDN with dnslink or CID
65
+ if ( window . IsIpfs . ipnsUrl ( url ) ) {
66
+ const ipnsRoot = new URL ( url ) . pathname . match ( / ^ \/ i p n s \/ ( [ ^ / ] + ) / ) [ 1 ]
67
+ // console.log('=====> IPNS root', ipnsRoot)
68
+ // first check if root is a regular CID
69
+ if ( window . IsIpfs . cid ( ipnsRoot ) ) {
70
+ // console.log('=====> IPNS is a valid CID', ipnsRoot)
71
+ return true
72
+ }
73
+ if ( isDnslookupSafe ( url ) && cachedDnslinkLookup ( ipnsRoot ) ) {
74
+ // console.log('=====> IPNS for FQDN with valid dnslink: ', ipnsRoot)
75
+ return true
76
+ }
77
+ }
78
+ }
79
+ // everything else is not ipfs-related
80
+ return false
59
81
}
60
82
61
83
function redirectToCustomGateway ( requestUrl ) {
@@ -107,13 +129,13 @@ function onBeforeRequest (request) {
107
129
108
130
// handle redirects to custom gateway
109
131
if ( state . redirect ) {
110
- // IPFS resources
111
- if ( publicIpfsResource ( request . url ) ) {
132
+ // Detect valid /ipfs/ and /ipns/ on any site
133
+ if ( publicIpfsOrIpnsResource ( request . url ) ) {
112
134
return redirectToCustomGateway ( request . url )
113
135
}
114
136
// Look for dnslink in TXT records of visited sites
115
- if ( isDnslookupEnabled ( request ) ) {
116
- return dnslinkLookup ( request )
137
+ if ( state . dnslink && isDnslookupSafe ( request . url ) ) {
138
+ return dnslinkLookupAndOptionalRedirect ( request . url )
117
139
}
118
140
}
119
141
}
@@ -174,54 +196,43 @@ function normalizedUnhandledIpfsProtocol (request) {
174
196
// DNSLINK
175
197
// ===================================================================
176
198
177
- function isDnslookupEnabled ( request ) {
178
- return state . dnslink &&
179
- state . peerCount > 0 &&
180
- request . url . startsWith ( 'http' ) &&
181
- ! request . url . startsWith ( state . apiURLString ) &&
182
- ! request . url . startsWith ( state . gwURLString )
199
+ function isDnslookupSafe ( requestUrl ) {
200
+ return state . peerCount > 0 &&
201
+ requestUrl . startsWith ( 'http' ) &&
202
+ ! requestUrl . startsWith ( state . apiURLString ) &&
203
+ ! requestUrl . startsWith ( state . gwURLString )
183
204
}
184
205
185
- function dnslinkLookup ( request ) {
186
- // TODO: benchmark and improve performance
187
- const requestUrl = new URL ( request . url )
188
- const fqdn = requestUrl . hostname
189
- let dnslink = state . dnslinkCache . get ( fqdn )
190
- if ( typeof dnslink === 'undefined' ) {
191
- // fetching fresh dnslink is expensive, so we switch to async
192
- console . log ( 'dnslink cache miss for: ' + fqdn )
193
- /* According to https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/webRequest/onBeforeRequest
194
- * "From Firefox 52 onwards, instead of returning BlockingResponse, the listener can return a Promise
195
- * which is resolved with a BlockingResponse. This enables the listener to process the request asynchronously."
196
- *
197
- * Seems that this does not work yet, and even tho promise is executed, request is not blocked but resolves to regular URL.
198
- * TODO: This should be revisited after Firefox 52 is released. If does not work by then, we need to fill a bug.
199
- */
200
- return asyncDnslookupResponse ( fqdn , requestUrl )
201
- }
206
+ function dnslinkLookupAndOptionalRedirect ( requestUrl ) {
207
+ const url = new URL ( requestUrl )
208
+ const fqdn = url . hostname
209
+ const dnslink = cachedDnslinkLookup ( fqdn )
202
210
if ( dnslink ) {
203
- console . log ( 'SYNC resolving to Cached dnslink redirect:' + fqdn )
204
- return redirectToDnslinkPath ( requestUrl , dnslink )
211
+ return redirectToDnslinkPath ( url , dnslink )
205
212
}
206
213
}
207
214
208
- async function asyncDnslookupResponse ( fqdn , requestUrl ) {
209
- try {
210
- const dnslink = await readDnslinkTxtRecordFromApi ( fqdn )
211
- if ( dnslink ) {
212
- state . dnslinkCache . set ( fqdn , dnslink )
213
- console . log ( 'ASYNC Resolved dnslink for:' + fqdn + ' is: ' + dnslink )
214
- return redirectToDnslinkPath ( requestUrl , dnslink )
215
- } else {
216
- state . dnslinkCache . set ( fqdn , false )
217
- console . log ( 'ASYNC NO dnslink for:' + fqdn )
218
- return { }
215
+ function cachedDnslinkLookup ( fqdn ) {
216
+ let dnslink = state . dnslinkCache . get ( fqdn )
217
+ if ( typeof dnslink === 'undefined' ) {
218
+ try {
219
+ console . log ( 'dnslink cache miss for: ' + fqdn )
220
+ dnslink = readDnslinkFromTxtRecord ( fqdn )
221
+ if ( dnslink ) {
222
+ state . dnslinkCache . set ( fqdn , dnslink )
223
+ console . log ( `Resolved dnslink: '${ fqdn } ' -> '${ dnslink } '` )
224
+ } else {
225
+ state . dnslinkCache . set ( fqdn , false )
226
+ console . log ( `Resolved NO dnslink for '${ fqdn } '` )
227
+ }
228
+ } catch ( error ) {
229
+ console . error ( `Error in dnslinkLookupAndOptionalRedirect for '${ fqdn } '` )
230
+ console . error ( error )
219
231
}
220
- } catch ( error ) {
221
- console . error ( `ASYNC Error in asyncDnslookupResponse for '${ fqdn } ': ${ error } ` )
222
- console . error ( error )
223
- return { }
232
+ } else {
233
+ console . log ( `Resolved via cached dnslink: '${ fqdn } ' -> '${ dnslink } '` )
224
234
}
235
+ return dnslink
225
236
}
226
237
227
238
function redirectToDnslinkPath ( url , dnslink ) {
@@ -232,31 +243,30 @@ function redirectToDnslinkPath (url, dnslink) {
232
243
return { redirectUrl : url . toString ( ) }
233
244
}
234
245
235
- function readDnslinkTxtRecordFromApi ( fqdn ) {
246
+ function readDnslinkFromTxtRecord ( fqdn ) {
236
247
// js-ipfs-api does not provide method for fetching this
237
248
// TODO: revisit after https://github.com/ipfs/js-ipfs-api/issues/501 is addressed
238
- return new Promise ( ( resolve , reject ) => {
239
- const apiCall = state . apiURLString + '/api/v0/dns/' + fqdn
240
- const xhr = new XMLHttpRequest ( ) // older XHR API us used because window.fetch appends Origin which causes error 403 in go-ipfs
241
- xhr . open ( 'GET' , apiCall )
242
- xhr . setRequestHeader ( 'Accept' , 'application/json' )
243
- xhr . onload = function ( ) {
244
- if ( this . status === 200 ) {
245
- const dnslink = JSON . parse ( xhr . responseText ) . Path
246
- resolve ( dnslink )
247
- } else if ( this . status === 500 ) {
248
- // go-ipfs returns 500 if host has no dnslink
249
- // TODO: find/fill an upstream bug to make this more intuitive
250
- resolve ( false )
251
- } else {
252
- reject ( new Error ( xhr . statusText ) )
253
- }
254
- }
255
- xhr . onerror = function ( ) {
256
- reject ( new Error ( xhr . statusText ) )
249
+ const apiCall = state . apiURLString + '/api/v0/dns/' + fqdn
250
+ const xhr = new XMLHttpRequest ( ) // older XHR API us used because window.fetch appends Origin which causes error 403 in go-ipfs
251
+ // synchronous mode with small timeout
252
+ // (it is okay, because we do it only once, then it is cached and read via cachedDnslinkLookup)
253
+ xhr . open ( 'GET' , apiCall , false )
254
+ xhr . setRequestHeader ( 'Accept' , 'application/json' )
255
+ xhr . send ( null )
256
+ if ( xhr . status === 200 ) {
257
+ const dnslink = JSON . parse ( xhr . responseText ) . Path
258
+ // console.log('readDnslinkFromTxtRecord', readDnslinkFromTxtRecord)
259
+ if ( ! window . IsIpfs . path ( dnslink ) ) {
260
+ throw new Error ( `dnslink for '${ fqdn } ' is not a valid IPFS path: '${ dnslink } '` )
257
261
}
258
- xhr . send ( )
259
- } )
262
+ return dnslink
263
+ } else if ( xhr . status === 500 ) {
264
+ // go-ipfs returns 500 if host has no dnslink
265
+ // TODO: find/fill an upstream bug to make this more intuitive
266
+ return false
267
+ } else {
268
+ throw new Error ( xhr . statusText )
269
+ }
260
270
}
261
271
262
272
// RUNTIME MESSAGES (one-off messaging)
0 commit comments