@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.
Files changed (68) hide show
  1. package/dist/index.min.js +41 -41
  2. package/dist/index.min.js.map +4 -4
  3. package/dist/src/index.d.ts +4 -2
  4. package/dist/src/index.d.ts.map +1 -1
  5. package/dist/src/index.js.map +1 -1
  6. package/dist/src/plugins/plugin-handle-dag-cbor-html-preview.d.ts +3 -3
  7. package/dist/src/plugins/plugin-handle-dag-cbor-html-preview.d.ts.map +1 -1
  8. package/dist/src/plugins/plugin-handle-dag-cbor-html-preview.js +5 -5
  9. package/dist/src/plugins/plugin-handle-dag-cbor-html-preview.js.map +1 -1
  10. package/dist/src/plugins/plugin-handle-dag-pb.d.ts.map +1 -1
  11. package/dist/src/plugins/plugin-handle-dag-pb.js +22 -24
  12. package/dist/src/plugins/plugin-handle-dag-pb.js.map +1 -1
  13. package/dist/src/plugins/plugin-handle-ipns-record.d.ts +1 -1
  14. package/dist/src/plugins/plugin-handle-ipns-record.d.ts.map +1 -1
  15. package/dist/src/plugins/plugin-handle-ipns-record.js +2 -2
  16. package/dist/src/plugins/plugin-handle-ipns-record.js.map +1 -1
  17. package/dist/src/plugins/plugin-handle-raw.js +1 -1
  18. package/dist/src/plugins/plugin-handle-raw.js.map +1 -1
  19. package/dist/src/plugins/types.d.ts +1 -4
  20. package/dist/src/plugins/types.d.ts.map +1 -1
  21. package/dist/src/url-resolver.d.ts +4 -3
  22. package/dist/src/url-resolver.d.ts.map +1 -1
  23. package/dist/src/url-resolver.js +35 -47
  24. package/dist/src/url-resolver.js.map +1 -1
  25. package/dist/src/utils/dnslink-label.d.ts +26 -0
  26. package/dist/src/utils/dnslink-label.d.ts.map +1 -0
  27. package/dist/src/utils/dnslink-label.js +35 -0
  28. package/dist/src/utils/dnslink-label.js.map +1 -0
  29. package/dist/src/utils/get-content-type.d.ts +1 -1
  30. package/dist/src/utils/get-content-type.d.ts.map +1 -1
  31. package/dist/src/utils/get-content-type.js +1 -1
  32. package/dist/src/utils/get-content-type.js.map +1 -1
  33. package/dist/src/utils/get-stream-from-async-iterable.d.ts +1 -2
  34. package/dist/src/utils/get-stream-from-async-iterable.d.ts.map +1 -1
  35. package/dist/src/utils/get-stream-from-async-iterable.js +1 -3
  36. package/dist/src/utils/get-stream-from-async-iterable.js.map +1 -1
  37. package/dist/src/utils/handle-redirects.js +2 -2
  38. package/dist/src/utils/ipfs-path-to-url.d.ts +16 -0
  39. package/dist/src/utils/ipfs-path-to-url.d.ts.map +1 -0
  40. package/dist/src/utils/ipfs-path-to-url.js +45 -0
  41. package/dist/src/utils/ipfs-path-to-url.js.map +1 -0
  42. package/dist/src/utils/parse-url-string.d.ts +18 -5
  43. package/dist/src/utils/parse-url-string.d.ts.map +1 -1
  44. package/dist/src/utils/parse-url-string.js +126 -44
  45. package/dist/src/utils/parse-url-string.js.map +1 -1
  46. package/dist/src/utils/resource-to-cache-key.js +2 -2
  47. package/dist/src/utils/responses.d.ts.map +1 -1
  48. package/dist/src/utils/responses.js +4 -0
  49. package/dist/src/utils/responses.js.map +1 -1
  50. package/dist/src/utils/walk-path.js +1 -1
  51. package/dist/src/utils/walk-path.js.map +1 -1
  52. package/package.json +10 -10
  53. package/src/index.ts +4 -2
  54. package/src/plugins/plugin-handle-dag-cbor-html-preview.ts +8 -8
  55. package/src/plugins/plugin-handle-dag-pb.ts +27 -23
  56. package/src/plugins/plugin-handle-ipns-record.ts +2 -2
  57. package/src/plugins/plugin-handle-raw.ts +1 -1
  58. package/src/plugins/types.ts +1 -4
  59. package/src/url-resolver.ts +37 -56
  60. package/src/utils/dnslink-label.ts +38 -0
  61. package/src/utils/get-content-type.ts +2 -2
  62. package/src/utils/get-stream-from-async-iterable.ts +1 -4
  63. package/src/utils/handle-redirects.ts +2 -2
  64. package/src/utils/ipfs-path-to-url.ts +54 -0
  65. package/src/utils/parse-url-string.ts +166 -49
  66. package/src/utils/resource-to-cache-key.ts +2 -2
  67. package/src/utils/responses.ts +6 -0
  68. package/src/utils/walk-path.ts +1 -1
@@ -1,77 +1,194 @@
1
- const URL_REGEX = /^(?<protocol>ip[fn]s):\/\/(?<cidOrPeerIdOrDnsLink>[^/?]+)\/?(?<path>[^?]*)\??(?<query>.*)$/
2
- const PATH_REGEX = /^\/(?<protocol>ip[fn]s)\/(?<cidOrPeerIdOrDnsLink>[^/?]+)\/?(?<path>[^?]*)\??(?<query>.*)$/
3
- const PATH_GATEWAY_REGEX = /^https?:\/\/(.*[^/])\/(?<protocol>ip[fn]s)\/(?<cidOrPeerIdOrDnsLink>[^/?]+)\/?(?<path>[^?]*)\??(?<query>.*)$/
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 MatchUrlGroups {
5
+ interface SubdomainMatchGroups {
7
6
  protocol: 'ipfs' | 'ipns'
8
7
  cidOrPeerIdOrDnsLink: string
9
- path?: string
10
- query?: string
11
8
  }
12
9
 
13
- function matchUrlGroupsGuard (groups?: null | { [key in string]: string; } | MatchUrlGroups): groups is MatchUrlGroups {
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
- if (protocol == null) { return false }
14
+
15
+ if (protocol !== 'ipfs' && protocol !== 'ipns') {
16
+ return false
17
+ }
18
+
16
19
  const cidOrPeerIdOrDnsLink = groups?.cidOrPeerIdOrDnsLink
17
- if (cidOrPeerIdOrDnsLink == null) { return false }
18
- const path = groups?.path
19
- const query = groups?.query
20
-
21
- return ['ipns', 'ipfs'].includes(protocol) &&
22
- typeof cidOrPeerIdOrDnsLink === 'string' &&
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
- // TODO: can this be replaced with `new URL`?
28
- export function matchURLString (urlString: string): MatchUrlGroups {
29
- for (const pattern of [SUBDOMAIN_GATEWAY_REGEX, URL_REGEX, PATH_GATEWAY_REGEX, PATH_REGEX]) {
30
- const match = urlString.match(pattern)
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
- if (matchUrlGroupsGuard(match?.groups)) {
33
- const groups = match.groups satisfies MatchUrlGroups
37
+ function toQuery (query?: URLSearchParams): Record<string, any> {
38
+ if (query == null) {
39
+ return {}
40
+ }
34
41
 
35
- if (groups.path != null) {
36
- groups.path = decodeURIComponent(groups.path)
37
- }
42
+ const output: Record<string, any> = {}
38
43
 
39
- // decode inline dnslink domain if present
40
- if (pattern === SUBDOMAIN_GATEWAY_REGEX && groups.protocol === 'ipns' && isInlinedDnsLink(groups.cidOrPeerIdOrDnsLink)) {
41
- groups.cidOrPeerIdOrDnsLink = dnsLinkLabelDecoder(groups.cidOrPeerIdOrDnsLink)
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
- return groups
51
+ if (value === 'false') {
52
+ output[key] = false
45
53
  }
46
54
  }
47
55
 
48
- throw new TypeError(`Invalid URL: ${urlString}, please use ipfs://, ipns://, or gateway URLs only`)
56
+ return output
49
57
  }
50
58
 
51
- /**
52
- * For DNSLink see https://specs.ipfs.tech/http-gateways/subdomain-gateway/#host-request-header
53
- * DNSLink names include . which means they must be inlined into a single DNS label to provide unique origin and work with wildcard TLS certificates.
54
- */
59
+ function stripLeadingHash (pathname: string): string {
60
+ return stripLeading(pathname, '#')
61
+ }
55
62
 
56
- // DNS label can have up to 63 characters, consisting of alphanumeric
57
- // characters or hyphens -, but it must not start or end with a hyphen.
58
- const dnsLabelRegex = /^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$/
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
- * Checks if label looks like inlined DNSLink.
62
- * (https://specs.ipfs.tech/http-gateways/subdomain-gateway/#host-request-header)
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 isInlinedDnsLink (label: string): boolean {
65
- return dnsLabelRegex.test(label) && label.includes('-') && !label.includes('.')
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
- * DNSLink label decoding
70
- * - Every standalone - is replaced with .
71
- * - Every remaining -- is replaced with -
105
+ * Accepts the following url strings:
72
106
  *
73
- * @example en-wikipedia--on--ipfs-org.ipns.example.net -> example.net/ipns/en.wikipedia-on-ipfs.org
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 dnsLinkLabelDecoder (linkLabel: string): string {
76
- return linkLabel.replace(/--/g, '%').replace(/-/g, '.').replace(/%/g, '-')
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 { matchURLString } from './parse-url-string.js'
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 } = matchURLString(url.toString())
27
+ const { protocol, cidOrPeerIdOrDnsLink } = parseURLString(url.toString())
28
28
 
29
29
  return `${protocol}://${cidOrPeerIdOrDnsLink}`
30
30
  }
@@ -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
 
@@ -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)