@helia/verified-fetch 4.0.1 → 4.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.min.js +41 -41
- package/dist/index.min.js.map +4 -4
- package/dist/src/index.d.ts +4 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/plugins/plugin-handle-dag-cbor-html-preview.d.ts +3 -3
- package/dist/src/plugins/plugin-handle-dag-cbor-html-preview.d.ts.map +1 -1
- package/dist/src/plugins/plugin-handle-dag-cbor-html-preview.js +5 -5
- package/dist/src/plugins/plugin-handle-dag-cbor-html-preview.js.map +1 -1
- package/dist/src/plugins/plugin-handle-dag-pb.d.ts.map +1 -1
- package/dist/src/plugins/plugin-handle-dag-pb.js +22 -24
- package/dist/src/plugins/plugin-handle-dag-pb.js.map +1 -1
- package/dist/src/plugins/plugin-handle-ipns-record.d.ts +1 -1
- package/dist/src/plugins/plugin-handle-ipns-record.d.ts.map +1 -1
- package/dist/src/plugins/plugin-handle-ipns-record.js +2 -2
- package/dist/src/plugins/plugin-handle-ipns-record.js.map +1 -1
- package/dist/src/plugins/plugin-handle-raw.js +1 -1
- package/dist/src/plugins/plugin-handle-raw.js.map +1 -1
- package/dist/src/plugins/types.d.ts +1 -4
- package/dist/src/plugins/types.d.ts.map +1 -1
- package/dist/src/url-resolver.d.ts +4 -3
- package/dist/src/url-resolver.d.ts.map +1 -1
- package/dist/src/url-resolver.js +35 -47
- package/dist/src/url-resolver.js.map +1 -1
- package/dist/src/utils/dnslink-label.d.ts +26 -0
- package/dist/src/utils/dnslink-label.d.ts.map +1 -0
- package/dist/src/utils/dnslink-label.js +35 -0
- package/dist/src/utils/dnslink-label.js.map +1 -0
- package/dist/src/utils/get-content-type.d.ts +1 -1
- package/dist/src/utils/get-content-type.d.ts.map +1 -1
- package/dist/src/utils/get-content-type.js +1 -1
- package/dist/src/utils/get-content-type.js.map +1 -1
- package/dist/src/utils/get-stream-from-async-iterable.d.ts +1 -2
- package/dist/src/utils/get-stream-from-async-iterable.d.ts.map +1 -1
- package/dist/src/utils/get-stream-from-async-iterable.js +1 -3
- package/dist/src/utils/get-stream-from-async-iterable.js.map +1 -1
- package/dist/src/utils/handle-redirects.js +2 -2
- package/dist/src/utils/ipfs-path-to-url.d.ts +16 -0
- package/dist/src/utils/ipfs-path-to-url.d.ts.map +1 -0
- package/dist/src/utils/ipfs-path-to-url.js +45 -0
- package/dist/src/utils/ipfs-path-to-url.js.map +1 -0
- package/dist/src/utils/parse-url-string.d.ts +18 -5
- package/dist/src/utils/parse-url-string.d.ts.map +1 -1
- package/dist/src/utils/parse-url-string.js +126 -44
- package/dist/src/utils/parse-url-string.js.map +1 -1
- package/dist/src/utils/resource-to-cache-key.js +2 -2
- package/dist/src/utils/responses.d.ts.map +1 -1
- package/dist/src/utils/responses.js +4 -0
- package/dist/src/utils/responses.js.map +1 -1
- package/dist/src/utils/walk-path.js +1 -1
- package/dist/src/utils/walk-path.js.map +1 -1
- package/package.json +10 -10
- package/src/index.ts +4 -2
- package/src/plugins/plugin-handle-dag-cbor-html-preview.ts +8 -8
- package/src/plugins/plugin-handle-dag-pb.ts +27 -23
- package/src/plugins/plugin-handle-ipns-record.ts +2 -2
- package/src/plugins/plugin-handle-raw.ts +1 -1
- package/src/plugins/types.ts +1 -4
- package/src/url-resolver.ts +37 -56
- package/src/utils/dnslink-label.ts +38 -0
- package/src/utils/get-content-type.ts +2 -2
- package/src/utils/get-stream-from-async-iterable.ts +1 -4
- package/src/utils/handle-redirects.ts +2 -2
- package/src/utils/ipfs-path-to-url.ts +54 -0
- package/src/utils/parse-url-string.ts +166 -49
- package/src/utils/resource-to-cache-key.ts +2 -2
- package/src/utils/responses.ts +6 -0
- package/src/utils/walk-path.ts +1 -1
|
@@ -1,77 +1,194 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const SUBDOMAIN_GATEWAY_REGEX = /^https?:\/\/(?<cidOrPeerIdOrDnsLink>[^/?]+)\.(?<protocol>ip[fn]s)\.([^/?]+)\/?(?<path>[^?]*)\??(?<query>.*)$/
|
|
1
|
+
import { InvalidParametersError } from '@libp2p/interface'
|
|
2
|
+
import { decodeDNSLinkLabel, isInlinedDnsLink } from './dnslink-label.ts'
|
|
3
|
+
import { ipfsPathToUrl, ipfsUrlToUrl } from './ipfs-path-to-url.ts'
|
|
5
4
|
|
|
6
|
-
interface
|
|
5
|
+
interface SubdomainMatchGroups {
|
|
7
6
|
protocol: 'ipfs' | 'ipns'
|
|
8
7
|
cidOrPeerIdOrDnsLink: string
|
|
9
|
-
path?: string
|
|
10
|
-
query?: string
|
|
11
8
|
}
|
|
12
9
|
|
|
13
|
-
|
|
10
|
+
const SUBDOMAIN_GATEWAY_REGEX = /^(?<cidOrPeerIdOrDnsLink>[^/?]+)\.(?<protocol>ip[fn]s)\.([^/?]+)$/
|
|
11
|
+
|
|
12
|
+
function matchSubdomainGroupsGuard (groups?: null | { [key in string]: string; } | SubdomainMatchGroups): groups is SubdomainMatchGroups {
|
|
14
13
|
const protocol = groups?.protocol
|
|
15
|
-
|
|
14
|
+
|
|
15
|
+
if (protocol !== 'ipfs' && protocol !== 'ipns') {
|
|
16
|
+
return false
|
|
17
|
+
}
|
|
18
|
+
|
|
16
19
|
const cidOrPeerIdOrDnsLink = groups?.cidOrPeerIdOrDnsLink
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
(path == null || typeof path === 'string') &&
|
|
24
|
-
(query == null || typeof query === 'string')
|
|
20
|
+
|
|
21
|
+
if (cidOrPeerIdOrDnsLink == null) {
|
|
22
|
+
return false
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return true
|
|
25
26
|
}
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
export interface ParsedURL {
|
|
29
|
+
url: URL,
|
|
30
|
+
protocol: 'ipfs' | 'ipns'
|
|
31
|
+
cidOrPeerIdOrDnsLink: string
|
|
32
|
+
path: string[]
|
|
33
|
+
query: Record<string, any>
|
|
34
|
+
fragment: string
|
|
35
|
+
}
|
|
31
36
|
|
|
32
|
-
|
|
33
|
-
|
|
37
|
+
function toQuery (query?: URLSearchParams): Record<string, any> {
|
|
38
|
+
if (query == null) {
|
|
39
|
+
return {}
|
|
40
|
+
}
|
|
34
41
|
|
|
35
|
-
|
|
36
|
-
groups.path = decodeURIComponent(groups.path)
|
|
37
|
-
}
|
|
42
|
+
const output: Record<string, any> = {}
|
|
38
43
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
44
|
+
for (const [key, value] of query.entries()) {
|
|
45
|
+
output[key] = value
|
|
46
|
+
|
|
47
|
+
if (value === 'true') {
|
|
48
|
+
output[key] = true
|
|
49
|
+
}
|
|
43
50
|
|
|
44
|
-
|
|
51
|
+
if (value === 'false') {
|
|
52
|
+
output[key] = false
|
|
45
53
|
}
|
|
46
54
|
}
|
|
47
55
|
|
|
48
|
-
|
|
56
|
+
return output
|
|
49
57
|
}
|
|
50
58
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
*/
|
|
59
|
+
function stripLeadingHash (pathname: string): string {
|
|
60
|
+
return stripLeading(pathname, '#')
|
|
61
|
+
}
|
|
55
62
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
63
|
+
function stripLeading (str: string, char: string): string {
|
|
64
|
+
while (str.startsWith(char)) {
|
|
65
|
+
str = str.substring(1)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return str
|
|
69
|
+
}
|
|
59
70
|
|
|
60
71
|
/**
|
|
61
|
-
*
|
|
62
|
-
* (
|
|
72
|
+
* If the caller has passed a case-sensitive identifier (like a base58btc
|
|
73
|
+
* encoded CID or PeerId) in a case-insensitive location (like a subdomain),
|
|
74
|
+
* be nice and return the original identifier from the passed string
|
|
63
75
|
*/
|
|
64
|
-
function
|
|
65
|
-
|
|
76
|
+
function findOriginalCidOrPeer (needle: string, haystack: string): string {
|
|
77
|
+
const start = haystack.toLowerCase().indexOf(needle)
|
|
78
|
+
|
|
79
|
+
if (start === -1) {
|
|
80
|
+
return needle
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return haystack.substring(start, start + needle.length)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function toUrl (urlString: string): URL {
|
|
87
|
+
// turn IPFS/IPNS path into gateway URL string
|
|
88
|
+
if (urlString.startsWith('/ipfs/') || urlString.startsWith('/ipns/')) {
|
|
89
|
+
urlString = ipfsPathToUrl(urlString)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// turn IPFS/IPNS URL into gateway URL string
|
|
93
|
+
if (urlString.startsWith('ipfs://') || urlString.startsWith('ipns://')) {
|
|
94
|
+
urlString = ipfsUrlToUrl(urlString)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (urlString.startsWith('http://') || urlString.startsWith('https://')) {
|
|
98
|
+
return new URL(urlString)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
throw new InvalidParametersError(`Invalid URL: ${urlString}`)
|
|
66
102
|
}
|
|
67
103
|
|
|
68
104
|
/**
|
|
69
|
-
*
|
|
70
|
-
* - Every standalone - is replaced with .
|
|
71
|
-
* - Every remaining -- is replaced with -
|
|
105
|
+
* Accepts the following url strings:
|
|
72
106
|
*
|
|
73
|
-
*
|
|
107
|
+
* - /ipfs/Qmfoo/path
|
|
108
|
+
* - /ipns/Qmfoo/path
|
|
109
|
+
* - ipfs://cid/path
|
|
110
|
+
* - ipns://name/path
|
|
111
|
+
* - http://cid.ipfs.example.com/path
|
|
112
|
+
* - http://name.ipns.example.com/path
|
|
113
|
+
* - http://example.com/ipfs/cid/path
|
|
114
|
+
* - http://example.com/ipns/name/path
|
|
74
115
|
*/
|
|
75
|
-
function
|
|
76
|
-
|
|
116
|
+
export function parseURLString (urlString: string): ParsedURL {
|
|
117
|
+
// validate url
|
|
118
|
+
const url = toUrl(urlString)
|
|
119
|
+
|
|
120
|
+
// test for subdomain gateway URL
|
|
121
|
+
const subdomainMatch = url.hostname.match(SUBDOMAIN_GATEWAY_REGEX)
|
|
122
|
+
|
|
123
|
+
if (matchSubdomainGroupsGuard(subdomainMatch?.groups)) {
|
|
124
|
+
const groups = subdomainMatch.groups
|
|
125
|
+
|
|
126
|
+
if (groups.protocol === 'ipns' && isInlinedDnsLink(groups.cidOrPeerIdOrDnsLink)) {
|
|
127
|
+
// decode inline dnslink domain if present
|
|
128
|
+
groups.cidOrPeerIdOrDnsLink = decodeDNSLinkLabel(groups.cidOrPeerIdOrDnsLink)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const cidOrPeerIdOrDnsLink = findOriginalCidOrPeer(groups.cidOrPeerIdOrDnsLink, urlString)
|
|
132
|
+
|
|
133
|
+
// parse url as not http(s):// - this is necessary because URL makes
|
|
134
|
+
// `.pathname` default to `/` for http URLs, even if no trailing slash was
|
|
135
|
+
// present in the string URL and we need to be able to round-trip the user's
|
|
136
|
+
// input while also maintaining a sane canonical URL for the resource. Phew.
|
|
137
|
+
const wat = new URL(`not-${urlString}`)
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
url: new URL(`${groups.protocol}://${cidOrPeerIdOrDnsLink}${wat.pathname}${url.search}${url.hash}`),
|
|
141
|
+
protocol: groups.protocol,
|
|
142
|
+
cidOrPeerIdOrDnsLink,
|
|
143
|
+
path: url.pathname.split('/')
|
|
144
|
+
.filter(str => str !== '')
|
|
145
|
+
.map(str => decodeURIComponent(str)),
|
|
146
|
+
query: toQuery(url.searchParams),
|
|
147
|
+
fragment: stripLeadingHash(url.hash)
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// test for IPFS path gateway URL
|
|
152
|
+
if (url.pathname.startsWith('/ipfs/')) {
|
|
153
|
+
const parts = url.pathname.substring(6).split('/')
|
|
154
|
+
const cid = parts.shift()
|
|
155
|
+
|
|
156
|
+
if (cid == null) {
|
|
157
|
+
throw new InvalidParametersError(`Path gateway URL ${urlString} had no CID`)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
url: new URL(`ipfs://${cid}${url.pathname.replace(`/ipfs/${cid}`, '')}${url.search}${url.hash}`),
|
|
162
|
+
protocol: 'ipfs',
|
|
163
|
+
cidOrPeerIdOrDnsLink: cid,
|
|
164
|
+
path: parts
|
|
165
|
+
.filter(str => str !== '')
|
|
166
|
+
.map(str => decodeURIComponent(str)),
|
|
167
|
+
query: toQuery(url.searchParams),
|
|
168
|
+
fragment: stripLeadingHash(url.hash)
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// test for IPNS path gateway URL
|
|
173
|
+
if (url.pathname.startsWith('/ipns/')) {
|
|
174
|
+
const parts = url.pathname.substring(6).split('/')
|
|
175
|
+
const name = parts.shift()
|
|
176
|
+
|
|
177
|
+
if (name == null) {
|
|
178
|
+
throw new InvalidParametersError(`Path gateway URL ${urlString} had no name`)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
url: new URL(`ipns://${name}${url.pathname.replace(`/ipns/${name}`, '')}${url.search}${url.hash}`),
|
|
183
|
+
protocol: 'ipns',
|
|
184
|
+
cidOrPeerIdOrDnsLink: name,
|
|
185
|
+
path: parts
|
|
186
|
+
.filter(str => str !== '')
|
|
187
|
+
.map(str => decodeURIComponent(str)),
|
|
188
|
+
query: toQuery(url.searchParams),
|
|
189
|
+
fragment: stripLeadingHash(url.hash)
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
throw new TypeError(`Invalid URL: ${urlString}, please use ipfs://, ipns://, or gateway URLs only`)
|
|
77
194
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CID } from 'multiformats/cid'
|
|
2
|
-
import {
|
|
2
|
+
import { parseURLString } from './parse-url-string.js'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Takes a resource and returns a session cache key as an IPFS or IPNS path with
|
|
@@ -24,7 +24,7 @@ export function resourceToSessionCacheKey (url: string | CID): string {
|
|
|
24
24
|
return `ipfs://${CID.parse(url.toString())}`
|
|
25
25
|
} catch {}
|
|
26
26
|
|
|
27
|
-
const { protocol, cidOrPeerIdOrDnsLink } =
|
|
27
|
+
const { protocol, cidOrPeerIdOrDnsLink } = parseURLString(url.toString())
|
|
28
28
|
|
|
29
29
|
return `${protocol}://${cidOrPeerIdOrDnsLink}`
|
|
30
30
|
}
|
package/src/utils/responses.ts
CHANGED
|
@@ -16,6 +16,12 @@ function setType (response: Response, value: 'basic' | 'cors' | 'error' | 'opaqu
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
function setUrl (response: Response, value: string): void {
|
|
19
|
+
const fragmentStart = value.indexOf('#')
|
|
20
|
+
|
|
21
|
+
if (fragmentStart > -1) {
|
|
22
|
+
value = value.substring(0, fragmentStart)
|
|
23
|
+
}
|
|
24
|
+
|
|
19
25
|
setField(response, 'url', value)
|
|
20
26
|
}
|
|
21
27
|
|
package/src/utils/walk-path.ts
CHANGED
|
@@ -52,7 +52,7 @@ export function isObjectNode (node: UnixFSEntry): node is ObjectNode {
|
|
|
52
52
|
*/
|
|
53
53
|
export async function handlePathWalking ({ cid, path, resource, options, blockstore, log }: PluginContext & { blockstore: Blockstore, log: Logger }): Promise<PathWalkerResponse | Response> {
|
|
54
54
|
try {
|
|
55
|
-
return await walkPath(blockstore, `${cid}/${path}`, options)
|
|
55
|
+
return await walkPath(blockstore, `${cid}/${path.join('/')}`, options)
|
|
56
56
|
} catch (err: any) {
|
|
57
57
|
if (options?.signal?.aborted) {
|
|
58
58
|
throw new AbortError(options?.signal?.reason)
|