3
3
4
4
const IsIpfs = require ( 'is-ipfs' )
5
5
6
- function safeIpfsPath ( urlOrPath ) {
6
+ function normalizedIpfsPath ( urlOrPath ) {
7
+ let result = urlOrPath
8
+ // Convert CID-in-subdomain URL to /ipns/<fqdn>/ path
7
9
if ( IsIpfs . subdomain ( urlOrPath ) ) {
8
- urlOrPath = subdomainToIpfsPath ( urlOrPath )
10
+ result = subdomainToIpfsPath ( urlOrPath )
9
11
}
10
- // better safe than sorry: https://github.com/ipfs/ipfs-companion/issues/303
11
- return decodeURIComponent ( urlOrPath . replace ( / ^ .* ( \/ i p ( f | n ) s \/ .+ ) $ / , '$1' ) )
12
+ // Drop everything before the IPFS path
13
+ result = result . replace ( / ^ .* ( \/ i p ( f | n ) s \/ .+ ) $ / , '$1' )
14
+ // Remove Unescape special characters
15
+ // https://github.com/ipfs/ipfs-companion/issues/303
16
+ result = decodeURIComponent ( result )
17
+ // Return a valid IPFS path or null otherwise
18
+ return IsIpfs . path ( result ) ? result : null
12
19
}
13
- exports . safeIpfsPath = safeIpfsPath
20
+ exports . normalizedIpfsPath = normalizedIpfsPath
14
21
15
22
function subdomainToIpfsPath ( url ) {
16
23
if ( typeof url === 'string' ) {
17
24
url = new URL ( url )
18
25
}
19
26
const fqdn = url . hostname . split ( '.' )
27
+ // TODO: support CID split with commas
20
28
const cid = fqdn [ 0 ]
29
+ // TODO: support .ip(f|n)s. being at deeper levels
21
30
const protocol = fqdn [ 1 ]
22
- return `/${ protocol } /${ cid } ${ url . pathname } `
31
+ return `/${ protocol } /${ cid } ${ url . pathname } ${ url . search } ${ url . hash } `
23
32
}
24
33
25
34
function pathAtHttpGateway ( path , gatewayUrl ) {
@@ -39,34 +48,38 @@ function trimHashAndSearch (urlString) {
39
48
}
40
49
exports . trimHashAndSearch = trimHashAndSearch
41
50
42
- function createIpfsPathValidator ( getState , dnsLink ) {
51
+ function createIpfsPathValidator ( getState , getIpfs , dnslinkResolver ) {
43
52
const ipfsPathValidator = {
44
53
// Test if URL is a Public IPFS resource
45
54
// (pass validIpfsOrIpnsUrl(url) and not at the local gateway or API)
46
55
publicIpfsOrIpnsResource ( url ) {
47
56
// exclude custom gateway and api, otherwise we have infinite loops
48
57
if ( ! url . startsWith ( getState ( ) . gwURLString ) && ! url . startsWith ( getState ( ) . apiURLString ) ) {
49
- return validIpfsOrIpnsUrl ( url , dnsLink )
58
+ return validIpfsOrIpnsUrl ( url , dnslinkResolver )
50
59
}
51
60
return false
52
61
} ,
53
62
54
63
// Test if URL is a valid IPFS or IPNS
55
64
// (IPFS needs to be a CID, IPNS can be PeerId or have dnslink entry)
56
65
validIpfsOrIpnsUrl ( url ) {
57
- return validIpfsOrIpnsUrl ( url , dnsLink )
66
+ return validIpfsOrIpnsUrl ( url , dnslinkResolver )
58
67
} ,
59
68
60
69
// Same as validIpfsOrIpnsUrl (url) but for paths
61
70
// (we have separate methods to avoid 'new URL' where possible)
62
71
validIpfsOrIpnsPath ( path ) {
63
- return validIpfsOrIpnsPath ( path , dnsLink )
72
+ return validIpfsOrIpnsPath ( path , dnslinkResolver )
64
73
} ,
65
74
66
75
// Test if actions such as 'copy URL', 'pin/unpin' should be enabled for the URL
67
- // TODO: include hostname check for DNSLink and display option to copy CID even if no redirect
68
76
isIpfsPageActionsContext ( url ) {
69
- return ( IsIpfs . url ( url ) && ! url . startsWith ( getState ( ) . apiURLString ) ) || IsIpfs . subdomain ( url )
77
+ console . log ( url )
78
+ return Boolean ( url && ! url . startsWith ( getState ( ) . apiURLString ) && (
79
+ IsIpfs . url ( url ) ||
80
+ IsIpfs . subdomain ( url ) ||
81
+ dnslinkResolver . cachedDnslink ( new URL ( url ) . hostname )
82
+ ) )
70
83
} ,
71
84
72
85
// Test if actions such as 'per site redirect toggle' should be enabled for the URL
@@ -77,7 +90,89 @@ function createIpfsPathValidator (getState, dnsLink) {
77
90
( url . startsWith ( 'http' ) && // hide on non-HTTP pages
78
91
! url . startsWith ( state . gwURLString ) && // hide on /ipfs/*
79
92
! url . startsWith ( state . apiURLString ) ) ) // hide on api port
93
+ } ,
94
+
95
+ // Resolve URL or path to HTTP URL:
96
+ // - IPFS paths are attached to HTTP Gateway root
97
+ // - URL of DNSLinked websites are returned as-is
98
+ // The purpose of this resolver is to always return a meaningful, publicly
99
+ // accessible URL that can be accessed without the need of IPFS client.
100
+ resolveToPublicUrl ( urlOrPath , optionalGatewayUrl ) {
101
+ const input = urlOrPath
102
+ // CID-in-subdomain is good as-is
103
+ if ( IsIpfs . subdomain ( input ) ) return input
104
+ // IPFS Paths should be attached to the public gateway
105
+ const ipfsPath = normalizedIpfsPath ( input )
106
+ const gateway = optionalGatewayUrl || getState ( ) . pubGwURLString
107
+ if ( ipfsPath ) return pathAtHttpGateway ( ipfsPath , gateway )
108
+ // Return original URL (eg. DNSLink domains) or null if not an URL
109
+ return input . startsWith ( 'http' ) ? input : null
110
+ } ,
111
+
112
+ // Resolve URL or path to IPFS Path:
113
+ // - The path can be /ipfs/ or /ipns/
114
+ // - Keeps pathname + ?search + #hash from original URL
115
+ // - Returns null if no valid path can be produced
116
+ // The purpose of this resolver is to return a valid IPFS path
117
+ // that can be accessed with IPFS client.
118
+ resolveToIpfsPath ( urlOrPath ) {
119
+ const input = urlOrPath
120
+ // Try to normalize to IPFS path (gateway path or CID-in-subdomain)
121
+ const ipfsPath = normalizedIpfsPath ( input )
122
+ if ( ipfsPath ) return ipfsPath
123
+ // Check URL for DNSLink
124
+ if ( ! input . startsWith ( 'http' ) ) return null
125
+ const { hostname } = new URL ( input )
126
+ const dnslink = dnslinkResolver . cachedDnslink ( hostname )
127
+ if ( dnslink ) {
128
+ // Return full IPNS path (keeps pathname + ?search + #hash)
129
+ return dnslinkResolver . convertToIpnsPath ( input )
130
+ }
131
+ // No IPFS path by this point
132
+ return null
133
+ } ,
134
+
135
+ // Resolve URL or path to Immutable IPFS Path:
136
+ // - Same as resolveToIpfsPath, but the path is always immutable /ipfs/
137
+ // - Keeps pathname + ?search + #hash from original URL
138
+ // - Returns null if no valid path can be produced
139
+ // The purpose of this resolver is to return immutable /ipfs/ address
140
+ // even if /ipns/ is present in its input.
141
+ async resolveToImmutableIpfsPath ( urlOrPath ) {
142
+ const path = ipfsPathValidator . resolveToIpfsPath ( urlOrPath )
143
+ // Fail fast if no IPFS Path
144
+ if ( ! path ) return null
145
+ // Resolve /ipns/ → /ipfs/
146
+ if ( IsIpfs . ipnsPath ( path ) ) {
147
+ const labels = path . split ( '/' )
148
+ // We resolve /ipns/<fqdn> as value in DNSLink cache may be out of date
149
+ const ipnsRoot = `/ipns/${ labels [ 2 ] } `
150
+ const result = await getIpfs ( ) . name . resolve ( ipnsRoot , { recursive : true , nocache : false } )
151
+ // Old API returned object, latest one returns string ¯\_(ツ)_/¯
152
+ const ipfsRoot = result . Path ? result . Path : result
153
+ // Return original path with swapped root (keeps pathname + ?search + #hash)
154
+ return path . replace ( ipnsRoot , ipfsRoot )
155
+ }
156
+ // Return /ipfs/ path
157
+ return path
158
+ } ,
159
+
160
+ // TODO: add description and tests
161
+ // Resolve URL or path to a raw CID:
162
+ // - Result is the direct CID
163
+ // - Ignores ?search and #hash from original URL
164
+ // - Returns null if no CID can be produced
165
+ async resolveToCid ( urlOrPath ) {
166
+ const path = ipfsPathValidator . resolveToIpfsPath ( urlOrPath )
167
+ // Fail fast if no IPFS Path
168
+ if ( ! path ) return null
169
+ // Resolve to raw CID
170
+ const rawPath = trimHashAndSearch ( path )
171
+ const result = await getIpfs ( ) . resolve ( rawPath , { recursive : true , dhtt : '5s' , dhtrc : 1 } )
172
+ const directCid = result . split ( '/' ) [ 2 ]
173
+ return directCid
80
174
}
175
+
81
176
}
82
177
83
178
return ipfsPathValidator
@@ -122,6 +217,7 @@ function validIpnsPath (path, dnsLink) {
122
217
return true
123
218
}
124
219
// then see if there is an DNSLink entry for 'ipnsRoot' hostname
220
+ // TODO: use dnslink cache only
125
221
if ( dnsLink . readAndCacheDnslink ( ipnsRoot ) ) {
126
222
// console.log('==> IPNS for FQDN with valid dnslink: ', ipnsRoot)
127
223
return true
0 commit comments