@helia/verified-fetch 0.0.0-a04e041 → 0.0.0-dc2e7a6

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 (33) hide show
  1. package/README.md +0 -33
  2. package/dist/index.min.js +4 -4
  3. package/dist/src/index.d.ts +0 -36
  4. package/dist/src/index.d.ts.map +1 -1
  5. package/dist/src/index.js +0 -33
  6. package/dist/src/index.js.map +1 -1
  7. package/dist/src/utils/parse-url-string.d.ts +0 -2
  8. package/dist/src/utils/parse-url-string.d.ts.map +1 -1
  9. package/dist/src/utils/parse-url-string.js +0 -6
  10. package/dist/src/utils/parse-url-string.js.map +1 -1
  11. package/dist/src/verified-fetch.d.ts +15 -17
  12. package/dist/src/verified-fetch.d.ts.map +1 -1
  13. package/dist/src/verified-fetch.js +129 -211
  14. package/dist/src/verified-fetch.js.map +1 -1
  15. package/package.json +12 -18
  16. package/src/index.ts +0 -37
  17. package/src/utils/parse-url-string.ts +1 -11
  18. package/src/verified-fetch.ts +134 -237
  19. package/dist/src/utils/get-content-disposition-filename.d.ts +0 -6
  20. package/dist/src/utils/get-content-disposition-filename.d.ts.map +0 -1
  21. package/dist/src/utils/get-content-disposition-filename.js +0 -16
  22. package/dist/src/utils/get-content-disposition-filename.js.map +0 -1
  23. package/dist/src/utils/responses.d.ts +0 -4
  24. package/dist/src/utils/responses.d.ts.map +0 -1
  25. package/dist/src/utils/responses.js +0 -21
  26. package/dist/src/utils/responses.js.map +0 -1
  27. package/dist/src/utils/select-output-type.d.ts +0 -12
  28. package/dist/src/utils/select-output-type.d.ts.map +0 -1
  29. package/dist/src/utils/select-output-type.js +0 -147
  30. package/dist/src/utils/select-output-type.js.map +0 -1
  31. package/src/utils/get-content-disposition-filename.ts +0 -18
  32. package/src/utils/responses.ts +0 -22
  33. package/src/utils/select-output-type.ts +0 -166
@@ -1,23 +1,18 @@
1
- import { car } from '@helia/car'
2
1
  import { ipns as heliaIpns, type IPNS } from '@helia/ipns'
3
2
  import { dnsJsonOverHttps } from '@helia/ipns/dns-resolvers'
4
3
  import { unixfs as heliaUnixFs, type UnixFS as HeliaUnixFs, type UnixFSStats } from '@helia/unixfs'
5
- import * as ipldDagCbor from '@ipld/dag-cbor'
6
- import * as ipldDagJson from '@ipld/dag-json'
4
+ import { code as dagCborCode } from '@ipld/dag-cbor'
5
+ import { code as dagJsonCode } from '@ipld/dag-json'
7
6
  import { code as dagPbCode } from '@ipld/dag-pb'
8
- import toBrowserReadableStream from 'it-to-browser-readablestream'
9
7
  import { code as jsonCode } from 'multiformats/codecs/json'
10
8
  import { code as rawCode } from 'multiformats/codecs/raw'
11
9
  import { identity } from 'multiformats/hashes/identity'
12
10
  import { CustomProgressEvent } from 'progress-events'
13
11
  import { dagCborToSafeJSON } from './utils/dag-cbor-to-safe-json.js'
14
- import { getContentDispositionFilename } from './utils/get-content-disposition-filename.js'
15
12
  import { getETag } from './utils/get-e-tag.js'
16
13
  import { getStreamFromAsyncIterable } from './utils/get-stream-from-async-iterable.js'
17
14
  import { parseResource } from './utils/parse-resource.js'
18
- import { notAcceptableResponse, notSupportedResponse, okResponse } from './utils/responses.js'
19
- import { selectOutputType, queryFormatToAcceptHeader } from './utils/select-output-type.js'
20
- import { walkPath } from './utils/walk-path.js'
15
+ import { walkPath, type PathWalkerFn } from './utils/walk-path.js'
21
16
  import type { CIDDetail, ContentTypeParser, Resource, VerifiedFetchInit as VerifiedFetchOptions } from './index.js'
22
17
  import type { RequestFormatShorthand } from './types.js'
23
18
  import type { Helia } from '@helia/interface'
@@ -29,6 +24,7 @@ interface VerifiedFetchComponents {
29
24
  helia: Helia
30
25
  ipns?: IPNS
31
26
  unixfs?: HeliaUnixFs
27
+ pathWalker?: PathWalkerFn
32
28
  }
33
29
 
34
30
  /**
@@ -41,13 +37,8 @@ interface VerifiedFetchInit {
41
37
  interface FetchHandlerFunctionArg {
42
38
  cid: CID
43
39
  path: string
40
+ terminalElement?: UnixFSEntry
44
41
  options?: Omit<VerifiedFetchOptions, 'signal'> & AbortOptions
45
-
46
- /**
47
- * If present, the user has sent an accept header with this value - if the
48
- * content cannot be represented in this format a 406 should be returned
49
- */
50
- accept?: string
51
42
  }
52
43
 
53
44
  interface FetchHandlerFunction {
@@ -69,48 +60,29 @@ function convertOptions (options?: VerifiedFetchOptions): (Omit<VerifiedFetchOpt
69
60
  }
70
61
  }
71
62
 
72
- /**
73
- * These are Accept header values that will cause content type sniffing to be
74
- * skipped and set to these values.
75
- */
76
- const RAW_HEADERS = [
77
- 'application/vnd.ipld.raw',
78
- 'application/octet-stream'
79
- ]
80
-
81
- /**
82
- * if the user has specified an `Accept` header, and it's in our list of
83
- * allowable "raw" format headers, use that instead of detecting the content
84
- * type. This avoids the user from receiving something different when they
85
- * signal that they want to `Accept` a specific mime type.
86
- */
87
- function getOverridenRawContentType (headers?: HeadersInit): string | undefined {
88
- const acceptHeader = new Headers(headers).get('accept') ?? ''
89
-
90
- // e.g. "Accept: text/html, application/xhtml+xml, application/xml;q=0.9, image/webp, */*;q=0.8"
91
- const acceptHeaders = acceptHeader.split(',')
92
- .map(s => s.split(';')[0])
93
- .map(s => s.trim())
94
-
95
- for (const mimeType of acceptHeaders) {
96
- if (mimeType === '*/*') {
97
- return
98
- }
63
+ function okResponse (body?: BodyInit | null): Response {
64
+ return new Response(body, {
65
+ status: 200,
66
+ statusText: 'OK'
67
+ })
68
+ }
99
69
 
100
- if (RAW_HEADERS.includes(mimeType ?? '')) {
101
- return mimeType
102
- }
103
- }
70
+ function notSupportedResponse (body?: BodyInit | null): Response {
71
+ return new Response(body, {
72
+ status: 501,
73
+ statusText: 'Not Implemented'
74
+ })
104
75
  }
105
76
 
106
77
  export class VerifiedFetch {
107
78
  private readonly helia: Helia
108
79
  private readonly ipns: IPNS
109
80
  private readonly unixfs: HeliaUnixFs
81
+ private readonly pathWalker: PathWalkerFn
110
82
  private readonly log: Logger
111
83
  private readonly contentTypeParser: ContentTypeParser | undefined
112
84
 
113
- constructor ({ helia, ipns, unixfs }: VerifiedFetchComponents, init?: VerifiedFetchInit) {
85
+ constructor ({ helia, ipns, unixfs, pathWalker }: VerifiedFetchComponents, init?: VerifiedFetchInit) {
114
86
  this.helia = helia
115
87
  this.log = helia.logger.forComponent('helia:verified-fetch')
116
88
  this.ipns = ipns ?? heliaIpns(helia, {
@@ -120,128 +92,60 @@ export class VerifiedFetch {
120
92
  ]
121
93
  })
122
94
  this.unixfs = unixfs ?? heliaUnixFs(helia)
95
+ this.pathWalker = pathWalker ?? walkPath
123
96
  this.contentTypeParser = init?.contentTypeParser
124
97
  this.log.trace('created VerifiedFetch instance')
125
98
  }
126
99
 
127
- /**
128
- * Accepts an `ipns://...` URL as a string and returns a `Response` containing
129
- * a raw IPNS record.
130
- */
131
- private async handleIPNSRecord (resource: string, opts?: VerifiedFetchOptions): Promise<Response> {
132
- return notSupportedResponse('vnd.ipfs.ipns-record support is not implemented')
133
- }
134
-
135
- /**
136
- * Accepts a `CID` and returns a `Response` with a body stream that is a CAR
137
- * of the `DAG` referenced by the `CID`.
138
- */
139
- private async handleCar ({ cid, options }: FetchHandlerFunctionArg): Promise<Response> {
140
- const c = car(this.helia)
141
- const stream = toBrowserReadableStream(c.stream(cid, options))
142
-
143
- const response = okResponse(stream)
144
- response.headers.set('content-type', 'application/vnd.ipld.car; version=1')
145
-
100
+ // handle vnd.ipfs.ipns-record
101
+ private async handleIPNSRecord ({ cid, path, options }: FetchHandlerFunctionArg): Promise<Response> {
102
+ const response = notSupportedResponse('vnd.ipfs.ipns-record support is not implemented')
103
+ response.headers.set('X-Content-Type-Options', 'nosniff') // see https://specs.ipfs.tech/http-gateways/path-gateway/#x-content-type-options-response-header
146
104
  return response
147
105
  }
148
106
 
149
- /**
150
- * Accepts a UnixFS `CID` and returns a `.tar` file containing the file or
151
- * directory structure referenced by the `CID`.
152
- */
153
- private async handleTar ({ cid, path, options }: FetchHandlerFunctionArg): Promise<Response> {
154
- if (cid.code !== dagPbCode) {
155
- return notAcceptableResponse('only dag-pb CIDs can be returned in TAR files')
156
- }
157
-
158
- return notSupportedResponse('application/tar support is not implemented')
107
+ // handle vnd.ipld.car
108
+ private async handleIPLDCar ({ cid, path, options }: FetchHandlerFunctionArg): Promise<Response> {
109
+ const response = notSupportedResponse('vnd.ipld.car support is not implemented')
110
+ response.headers.set('X-Content-Type-Options', 'nosniff') // see https://specs.ipfs.tech/http-gateways/path-gateway/#x-content-type-options-response-header
111
+ return response
159
112
  }
160
113
 
161
- private async handleJson ({ cid, path, accept, options }: FetchHandlerFunctionArg): Promise<Response> {
114
+ private async handleJson ({ cid, path, options }: FetchHandlerFunctionArg): Promise<Response> {
162
115
  this.log.trace('fetching %c/%s', cid, path)
163
- const block = await this.helia.blockstore.get(cid, options)
164
- let body: string | Uint8Array
165
-
166
- if (accept === 'application/vnd.ipld.dag-cbor' || accept === 'application/cbor') {
167
- try {
168
- // if vnd.ipld.dag-cbor has been specified, convert to the format - note
169
- // that this supports more data types than regular JSON, the content-type
170
- // response header is set so the user knows to process it differently
171
- const obj = ipldDagJson.decode(block)
172
- body = ipldDagCbor.encode(obj)
173
- } catch (err) {
174
- this.log.error('could not transform %c to application/vnd.ipld.dag-cbor', err)
175
- return notAcceptableResponse()
176
- }
177
- } else {
178
- // skip decoding
179
- body = block
180
- }
181
-
182
- const response = okResponse(body)
183
- response.headers.set('content-type', accept ?? 'application/json')
116
+ options?.onProgress?.(new CustomProgressEvent<CIDDetail>('verified-fetch:request:start', { cid, path }))
117
+ const result = await this.helia.blockstore.get(cid, {
118
+ signal: options?.signal,
119
+ onProgress: options?.onProgress
120
+ })
121
+ const response = okResponse(result)
122
+ response.headers.set('content-type', 'application/json')
123
+ options?.onProgress?.(new CustomProgressEvent<CIDDetail>('verified-fetch:request:end', { cid, path }))
184
124
  return response
185
125
  }
186
126
 
187
- private async handleDagCbor ({ cid, path, accept, options }: FetchHandlerFunctionArg): Promise<Response> {
127
+ private async handleDagCbor ({ cid, path, options }: FetchHandlerFunctionArg): Promise<Response> {
188
128
  this.log.trace('fetching %c/%s', cid, path)
189
-
190
- const block = await this.helia.blockstore.get(cid, options)
129
+ options?.onProgress?.(new CustomProgressEvent<CIDDetail>('verified-fetch:request:start', { cid, path }))
130
+ // return body as binary
131
+ const block = await this.helia.blockstore.get(cid)
191
132
  let body: string | Uint8Array
192
133
 
193
- if (accept === 'application/octet-stream' || accept === 'application/vnd.ipld.dag-cbor' || accept === 'application/cbor') {
194
- // skip decoding
134
+ try {
135
+ body = dagCborToSafeJSON(block)
136
+ } catch (err) {
137
+ this.log('could not decode DAG-CBOR as JSON-safe, falling back to `application/octet-stream`', err)
195
138
  body = block
196
- } else if (accept === 'application/vnd.ipld.dag-json') {
197
- try {
198
- // if vnd.ipld.dag-json has been specified, convert to the format - note
199
- // that this supports more data types than regular JSON, the content-type
200
- // response header is set so the user knows to process it differently
201
- const obj = ipldDagCbor.decode(block)
202
- body = ipldDagJson.encode(obj)
203
- } catch (err) {
204
- this.log.error('could not transform %c to application/vnd.ipld.dag-json', err)
205
- return notAcceptableResponse()
206
- }
207
- } else {
208
- try {
209
- body = dagCborToSafeJSON(block)
210
- } catch (err) {
211
- if (accept === 'application/json') {
212
- this.log('could not decode DAG-CBOR as JSON-safe, but the client sent "Accept: application/json"', err)
213
-
214
- return notAcceptableResponse()
215
- }
216
-
217
- this.log('could not decode DAG-CBOR as JSON-safe, falling back to `application/octet-stream`', err)
218
- body = block
219
- }
220
139
  }
221
140
 
222
141
  const response = okResponse(body)
223
-
224
- if (accept == null) {
225
- accept = body instanceof Uint8Array ? 'application/octet-stream' : 'application/json'
226
- }
227
-
228
- response.headers.set('content-type', accept)
229
-
142
+ response.headers.set('content-type', body instanceof Uint8Array ? 'application/octet-stream' : 'application/json')
143
+ options?.onProgress?.(new CustomProgressEvent<CIDDetail>('verified-fetch:request:end', { cid, path }))
230
144
  return response
231
145
  }
232
146
 
233
- private async handleDagPb ({ cid, path, options }: FetchHandlerFunctionArg): Promise<Response> {
234
- let terminalElement: UnixFSEntry | undefined
235
- let ipfsRoots: CID[] | undefined
236
-
237
- try {
238
- const pathDetails = await walkPath(this.helia.blockstore, `${cid.toString()}/${path}`, options)
239
- ipfsRoots = pathDetails.ipfsRoots
240
- terminalElement = pathDetails.terminalElement
241
- } catch (err) {
242
- this.log.error('Error walking path %s', path, err)
243
- }
244
-
147
+ private async handleDagPb ({ cid, path, options, terminalElement }: FetchHandlerFunctionArg): Promise<Response> {
148
+ this.log.trace('fetching %c/%s', cid, path)
245
149
  let resolvedCID = terminalElement?.cid ?? cid
246
150
  let stat: UnixFSStats
247
151
  if (terminalElement?.type === 'directory') {
@@ -250,6 +154,7 @@ export class VerifiedFetch {
250
154
  const rootFilePath = 'index.html'
251
155
  try {
252
156
  this.log.trace('found directory at %c/%s, looking for index.html', cid, path)
157
+ options?.onProgress?.(new CustomProgressEvent<CIDDetail>('verified-fetch:request:start', { cid: dirCid, path: rootFilePath }))
253
158
  stat = await this.unixfs.stat(dirCid, {
254
159
  path: rootFilePath,
255
160
  signal: options?.signal,
@@ -267,6 +172,7 @@ export class VerifiedFetch {
267
172
  }
268
173
  }
269
174
 
175
+ options?.onProgress?.(new CustomProgressEvent<CIDDetail>('verified-fetch:request:start', { cid: resolvedCID, path: '' }))
270
176
  const asyncIter = this.unixfs.cat(resolvedCID, {
271
177
  signal: options?.signal,
272
178
  onProgress: options?.onProgress
@@ -279,27 +185,19 @@ export class VerifiedFetch {
279
185
  const response = okResponse(stream)
280
186
  await this.setContentType(firstChunk, path, response)
281
187
 
282
- if (ipfsRoots != null) {
283
- response.headers.set('X-Ipfs-Roots', ipfsRoots.map(cid => cid.toV1().toString()).join(',')) // https://specs.ipfs.tech/http-gateways/path-gateway/#x-ipfs-roots-response-header
284
- }
188
+ options?.onProgress?.(new CustomProgressEvent<CIDDetail>('verified-fetch:request:end', { cid: resolvedCID, path: '' }))
285
189
 
286
190
  return response
287
191
  }
288
192
 
289
193
  private async handleRaw ({ cid, path, options }: FetchHandlerFunctionArg): Promise<Response> {
290
- const result = await this.helia.blockstore.get(cid, options)
194
+ this.log.trace('fetching %c/%s', cid, path)
195
+ options?.onProgress?.(new CustomProgressEvent<CIDDetail>('verified-fetch:request:start', { cid, path }))
196
+ const result = await this.helia.blockstore.get(cid)
291
197
  const response = okResponse(result)
198
+ await this.setContentType(result, path, response)
292
199
 
293
- // if the user has specified an `Accept` header that corresponds to a raw
294
- // type, honour that header, so for example they don't request
295
- // `application/vnd.ipld.raw` but get `application/octet-stream`
296
- const overriddenContentType = getOverridenRawContentType(options?.headers)
297
- if (overriddenContentType != null) {
298
- response.headers.set('content-type', overriddenContentType)
299
- } else {
300
- await this.setContentType(result, path, response)
301
- }
302
-
200
+ options?.onProgress?.(new CustomProgressEvent<CIDDetail>('verified-fetch:request:end', { cid, path }))
303
201
  return response
304
202
  }
305
203
 
@@ -330,112 +228,111 @@ export class VerifiedFetch {
330
228
  }
331
229
 
332
230
  /**
333
- * If the user has not specified an Accept header or format query string arg,
334
- * use the CID codec to choose an appropriate handler for the block data.
231
+ * Determines the format requested by the client, defaults to `null` if no format is requested.
232
+ *
233
+ * @see https://specs.ipfs.tech/http-gateways/path-gateway/#format-request-query-parameter
234
+ * @default 'raw'
235
+ */
236
+ private getFormat ({ headerFormat, queryFormat }: { headerFormat: string | null, queryFormat: RequestFormatShorthand | null }): RequestFormatShorthand | null {
237
+ const formatMap: Record<string, RequestFormatShorthand> = {
238
+ 'vnd.ipld.raw': 'raw',
239
+ 'vnd.ipld.car': 'car',
240
+ 'application/x-tar': 'tar',
241
+ 'application/vnd.ipld.dag-json': 'dag-json',
242
+ 'application/vnd.ipld.dag-cbor': 'dag-cbor',
243
+ 'application/json': 'json',
244
+ 'application/cbor': 'cbor',
245
+ 'vnd.ipfs.ipns-record': 'ipns-record'
246
+ }
247
+
248
+ if (headerFormat != null) {
249
+ for (const format in formatMap) {
250
+ if (headerFormat.includes(format)) {
251
+ return formatMap[format]
252
+ }
253
+ }
254
+ } else if (queryFormat != null) {
255
+ return queryFormat
256
+ }
257
+
258
+ return null
259
+ }
260
+
261
+ /**
262
+ * Map of format to specific handlers for that format.
263
+ * These format handlers should adjust the response headers as specified in https://specs.ipfs.tech/http-gateways/path-gateway/#response-headers
335
264
  */
265
+ private readonly formatHandlers: Record<string, FetchHandlerFunction> = {
266
+ raw: async () => notSupportedResponse('application/vnd.ipld.raw support is not implemented'),
267
+ car: this.handleIPLDCar,
268
+ 'ipns-record': this.handleIPNSRecord,
269
+ tar: async () => notSupportedResponse('application/x-tar support is not implemented'),
270
+ 'dag-json': async () => notSupportedResponse('application/vnd.ipld.dag-json support is not implemented'),
271
+ 'dag-cbor': async () => notSupportedResponse('application/vnd.ipld.dag-cbor support is not implemented'),
272
+ json: async () => notSupportedResponse('application/json support is not implemented'),
273
+ cbor: async () => notSupportedResponse('application/cbor support is not implemented')
274
+ }
275
+
336
276
  private readonly codecHandlers: Record<number, FetchHandlerFunction> = {
337
277
  [dagPbCode]: this.handleDagPb,
338
- [ipldDagJson.code]: this.handleJson,
278
+ [dagJsonCode]: this.handleJson,
339
279
  [jsonCode]: this.handleJson,
340
- [ipldDagCbor.code]: this.handleDagCbor,
280
+ [dagCborCode]: this.handleDagCbor,
341
281
  [rawCode]: this.handleRaw,
342
282
  [identity.code]: this.handleRaw
343
283
  }
344
284
 
345
285
  async fetch (resource: Resource, opts?: VerifiedFetchOptions): Promise<Response> {
346
- this.log('fetch %s', resource)
347
-
348
286
  const options = convertOptions(opts)
287
+ const { path, query, ...rest } = await parseResource(resource, { ipns: this.ipns, logger: this.helia.logger }, options)
288
+ const cid = rest.cid
289
+ let response: Response | undefined
349
290
 
350
- options?.onProgress?.(new CustomProgressEvent<CIDDetail>('verified-fetch:request:start', { resource }))
351
-
352
- // resolve the CID/path from the requested resource
353
- const { path, query, cid } = await parseResource(resource, { ipns: this.ipns, logger: this.helia.logger }, options)
291
+ const format = this.getFormat({ headerFormat: new Headers(options?.headers).get('accept'), queryFormat: query.format ?? null })
354
292
 
355
- options?.onProgress?.(new CustomProgressEvent<CIDDetail>('verified-fetch:request:resolve', { cid, path }))
356
-
357
- const requestHeaders = new Headers(options?.headers)
358
- const incomingAcceptHeader = requestHeaders.get('accept')
359
-
360
- if (incomingAcceptHeader != null) {
361
- this.log('incoming accept header "%s"', incomingAcceptHeader)
362
- }
293
+ if (format != null) {
294
+ // TODO: These should be handled last when they're returning something other than 501
295
+ const formatHandler = this.formatHandlers[format]
363
296
 
364
- const queryFormatMapping = queryFormatToAcceptHeader(query.format)
297
+ if (formatHandler != null) {
298
+ response = await formatHandler.call(this, { cid, path, options })
365
299
 
366
- if (query.format != null) {
367
- this.log('incoming query format "%s", mapped to %s', query.format, queryFormatMapping)
300
+ if (response.status === 501) {
301
+ return response
302
+ }
303
+ }
368
304
  }
369
305
 
370
- const acceptHeader = incomingAcceptHeader ?? queryFormatMapping
371
- const accept = selectOutputType(cid, acceptHeader)
372
- this.log('output type %s', accept)
306
+ let terminalElement: UnixFSEntry | undefined
307
+ let ipfsRoots: CID[] | undefined
373
308
 
374
- if (acceptHeader != null && accept == null) {
375
- return notAcceptableResponse()
309
+ try {
310
+ const pathDetails = await this.pathWalker(this.helia.blockstore, `${cid.toString()}/${path}`, options)
311
+ ipfsRoots = pathDetails.ipfsRoots
312
+ terminalElement = pathDetails.terminalElement
313
+ } catch (err) {
314
+ this.log.error('Error walking path %s', path, err)
315
+ // return new Response(`Error walking path: ${(err as Error).message}`, { status: 500 })
376
316
  }
377
317
 
378
- let response: Response
379
- let reqFormat: RequestFormatShorthand | undefined
380
-
381
- if (accept === 'application/vnd.ipfs.ipns-record') {
382
- // the user requested a raw IPNS record
383
- reqFormat = 'ipns-record'
384
- response = await this.handleIPNSRecord(resource.toString(), options)
385
- } else if (accept === 'application/vnd.ipld.car') {
386
- // the user requested a CAR file
387
- reqFormat = 'car'
388
- query.download = true
389
- query.filename = query.filename ?? `${cid.toString()}.car`
390
- response = await this.handleCar({ cid, path, options })
391
- } else if (accept === 'application/vnd.ipld.raw') {
392
- // the user requested a raw block
393
- reqFormat = 'raw'
394
- query.download = true
395
- query.filename = query.filename ?? `${cid.toString()}.bin`
396
- response = await this.handleRaw({ cid, path, options })
397
- } else if (accept === 'application/x-tar') {
398
- // the user requested a TAR file
399
- reqFormat = 'tar'
400
- response = await this.handleTar({ cid, path, options })
401
- } else {
402
- // derive the handler from the CID type
318
+ if (response == null) {
403
319
  const codecHandler = this.codecHandlers[cid.code]
404
320
 
405
- if (codecHandler == null) {
321
+ if (codecHandler != null) {
322
+ response = await codecHandler.call(this, { cid, path, options, terminalElement })
323
+ } else {
406
324
  return notSupportedResponse(`Support for codec with code ${cid.code} is not yet implemented. Please open an issue at https://github.com/ipfs/helia/issues/new`)
407
325
  }
408
-
409
- response = await codecHandler.call(this, { cid, path, accept, options })
410
326
  }
411
327
 
412
- response.headers.set('etag', getETag({ cid, reqFormat, weak: false }))
328
+ response.headers.set('etag', getETag({ cid, reqFormat: format ?? undefined, weak: false }))
413
329
  response.headers.set('cache-control', 'public, max-age=29030400, immutable')
414
- // https://specs.ipfs.tech/http-gateways/path-gateway/#x-ipfs-path-response-header
415
- response.headers.set('X-Ipfs-Path', resource.toString())
416
-
417
- // set Content-Disposition header
418
- let contentDisposition: string | undefined
419
-
420
- // force download if requested
421
- if (query.download === true) {
422
- contentDisposition = 'attachment'
423
- }
424
-
425
- // override filename if requested
426
- if (query.filename != null) {
427
- if (contentDisposition == null) {
428
- contentDisposition = 'inline'
429
- }
430
-
431
- contentDisposition = `${contentDisposition}; ${getContentDispositionFilename(query.filename)}`
432
- }
330
+ response.headers.set('X-Ipfs-Path', resource.toString()) // https://specs.ipfs.tech/http-gateways/path-gateway/#x-ipfs-path-response-header
433
331
 
434
- if (contentDisposition != null) {
435
- response.headers.set('Content-Disposition', contentDisposition)
332
+ if (ipfsRoots != null) {
333
+ response.headers.set('X-Ipfs-Roots', ipfsRoots.map(cid => cid.toV1().toString()).join(',')) // https://specs.ipfs.tech/http-gateways/path-gateway/#x-ipfs-roots-response-header
436
334
  }
437
-
438
- options?.onProgress?.(new CustomProgressEvent<CIDDetail>('verified-fetch:request:end', { cid, path }))
335
+ // response.headers.set('Content-Disposition', `TODO`) // https://specs.ipfs.tech/http-gateways/path-gateway/#content-disposition-response-header
439
336
 
440
337
  return response
441
338
  }
@@ -1,6 +0,0 @@
1
- /**
2
- * Takes a filename URL param and returns a string for use in a
3
- * `Content-Disposition` header
4
- */
5
- export declare function getContentDispositionFilename(filename: string): string;
6
- //# sourceMappingURL=get-content-disposition-filename.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"get-content-disposition-filename.d.ts","sourceRoot":"","sources":["../../../src/utils/get-content-disposition-filename.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAgB,6BAA6B,CAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAQvE"}
@@ -1,16 +0,0 @@
1
- /**
2
- * Takes a filename URL param and returns a string for use in a
3
- * `Content-Disposition` header
4
- */
5
- export function getContentDispositionFilename(filename) {
6
- const asciiOnly = replaceNonAsciiCharacters(filename);
7
- if (asciiOnly === filename) {
8
- return `filename="${filename}"`;
9
- }
10
- return `filename="${asciiOnly}"; filename*=UTF-8''${encodeURIComponent(filename)}`;
11
- }
12
- function replaceNonAsciiCharacters(filename) {
13
- // eslint-disable-next-line no-control-regex
14
- return filename.replace(/[^\x00-\x7F]/g, '_');
15
- }
16
- //# sourceMappingURL=get-content-disposition-filename.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"get-content-disposition-filename.js","sourceRoot":"","sources":["../../../src/utils/get-content-disposition-filename.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,UAAU,6BAA6B,CAAE,QAAgB;IAC7D,MAAM,SAAS,GAAG,yBAAyB,CAAC,QAAQ,CAAC,CAAA;IAErD,IAAI,SAAS,KAAK,QAAQ,EAAE,CAAC;QAC3B,OAAO,aAAa,QAAQ,GAAG,CAAA;IACjC,CAAC;IAED,OAAO,aAAa,SAAS,uBAAuB,kBAAkB,CAAC,QAAQ,CAAC,EAAE,CAAA;AACpF,CAAC;AAED,SAAS,yBAAyB,CAAE,QAAgB;IAClD,4CAA4C;IAC5C,OAAO,QAAQ,CAAC,OAAO,CAAC,eAAe,EAAE,GAAG,CAAC,CAAA;AAC/C,CAAC"}
@@ -1,4 +0,0 @@
1
- export declare function okResponse(body?: BodyInit | null): Response;
2
- export declare function notSupportedResponse(body?: BodyInit | null): Response;
3
- export declare function notAcceptableResponse(body?: BodyInit | null): Response;
4
- //# sourceMappingURL=responses.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"responses.d.ts","sourceRoot":"","sources":["../../../src/utils/responses.ts"],"names":[],"mappings":"AAAA,wBAAgB,UAAU,CAAE,IAAI,CAAC,EAAE,QAAQ,GAAG,IAAI,GAAG,QAAQ,CAK5D;AAED,wBAAgB,oBAAoB,CAAE,IAAI,CAAC,EAAE,QAAQ,GAAG,IAAI,GAAG,QAAQ,CAOtE;AAED,wBAAgB,qBAAqB,CAAE,IAAI,CAAC,EAAE,QAAQ,GAAG,IAAI,GAAG,QAAQ,CAKvE"}
@@ -1,21 +0,0 @@
1
- export function okResponse(body) {
2
- return new Response(body, {
3
- status: 200,
4
- statusText: 'OK'
5
- });
6
- }
7
- export function notSupportedResponse(body) {
8
- const response = new Response(body, {
9
- status: 501,
10
- statusText: 'Not Implemented'
11
- });
12
- response.headers.set('X-Content-Type-Options', 'nosniff'); // see https://specs.ipfs.tech/http-gateways/path-gateway/#x-content-type-options-response-header
13
- return response;
14
- }
15
- export function notAcceptableResponse(body) {
16
- return new Response(body, {
17
- status: 406,
18
- statusText: 'Not Acceptable'
19
- });
20
- }
21
- //# sourceMappingURL=responses.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"responses.js","sourceRoot":"","sources":["../../../src/utils/responses.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,UAAU,CAAE,IAAsB;IAChD,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE;QACxB,MAAM,EAAE,GAAG;QACX,UAAU,EAAE,IAAI;KACjB,CAAC,CAAA;AACJ,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAE,IAAsB;IAC1D,MAAM,QAAQ,GAAG,IAAI,QAAQ,CAAC,IAAI,EAAE;QAClC,MAAM,EAAE,GAAG;QACX,UAAU,EAAE,iBAAiB;KAC9B,CAAC,CAAA;IACF,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,wBAAwB,EAAE,SAAS,CAAC,CAAA,CAAC,iGAAiG;IAC3J,OAAO,QAAQ,CAAA;AACjB,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAE,IAAsB;IAC3D,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE;QACxB,MAAM,EAAE,GAAG;QACX,UAAU,EAAE,gBAAgB;KAC7B,CAAC,CAAA;AACJ,CAAC"}
@@ -1,12 +0,0 @@
1
- import type { RequestFormatShorthand } from '../types.js';
2
- import type { CID } from 'multiformats/cid';
3
- /**
4
- * Selects an output mime-type based on the CID and a passed `Accept` header
5
- */
6
- export declare function selectOutputType(cid: CID, accept?: string): string | undefined;
7
- /**
8
- * Converts a `format=...` query param to a mime type as would be found in the
9
- * `Accept` header, if a valid mapping is available
10
- */
11
- export declare function queryFormatToAcceptHeader(format?: RequestFormatShorthand): string | undefined;
12
- //# sourceMappingURL=select-output-type.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"select-output-type.d.ts","sourceRoot":"","sources":["../../../src/utils/select-output-type.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAA;AACzD,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAA;AAuD3C;;GAEG;AACH,wBAAgB,gBAAgB,CAAE,GAAG,EAAE,GAAG,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAM/E;AAuFD;;;GAGG;AACH,wBAAgB,yBAAyB,CAAE,MAAM,CAAC,EAAE,sBAAsB,GAAG,MAAM,GAAG,SAAS,CAI9F"}