@helia/verified-fetch 4.1.1 → 5.0.0
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/README.md +6 -40
- package/dist/index.min.js +73 -534
- package/dist/index.min.js.map +4 -4
- package/dist/src/constants.d.ts +2 -0
- package/dist/src/constants.d.ts.map +1 -1
- package/dist/src/constants.js +2 -0
- package/dist/src/constants.js.map +1 -1
- package/dist/src/index.d.ts +162 -68
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +7 -40
- package/dist/src/index.js.map +1 -1
- package/dist/src/plugins/index.d.ts +0 -5
- package/dist/src/plugins/index.d.ts.map +1 -1
- package/dist/src/plugins/index.js +0 -4
- package/dist/src/plugins/index.js.map +1 -1
- package/dist/src/plugins/plugin-base.d.ts +8 -9
- package/dist/src/plugins/plugin-base.d.ts.map +1 -1
- package/dist/src/plugins/plugin-base.js +5 -6
- package/dist/src/plugins/plugin-base.js.map +1 -1
- package/dist/src/plugins/plugin-handle-car.d.ts +3 -3
- package/dist/src/plugins/plugin-handle-car.d.ts.map +1 -1
- package/dist/src/plugins/plugin-handle-car.js +38 -39
- package/dist/src/plugins/plugin-handle-car.js.map +1 -1
- package/dist/src/plugins/plugin-handle-ipld.d.ts +12 -0
- package/dist/src/plugins/plugin-handle-ipld.d.ts.map +1 -0
- package/dist/src/plugins/plugin-handle-ipld.js +83 -0
- package/dist/src/plugins/plugin-handle-ipld.js.map +1 -0
- package/dist/src/plugins/plugin-handle-ipns-record.d.ts +3 -3
- package/dist/src/plugins/plugin-handle-ipns-record.d.ts.map +1 -1
- package/dist/src/plugins/plugin-handle-ipns-record.js +25 -34
- package/dist/src/plugins/plugin-handle-ipns-record.js.map +1 -1
- package/dist/src/plugins/plugin-handle-tar.d.ts +3 -3
- package/dist/src/plugins/plugin-handle-tar.d.ts.map +1 -1
- package/dist/src/plugins/plugin-handle-tar.js +20 -22
- package/dist/src/plugins/plugin-handle-tar.js.map +1 -1
- package/dist/src/plugins/plugin-handle-unixfs.d.ts +14 -0
- package/dist/src/plugins/plugin-handle-unixfs.d.ts.map +1 -0
- package/dist/src/plugins/plugin-handle-unixfs.js +180 -0
- package/dist/src/plugins/plugin-handle-unixfs.js.map +1 -0
- package/dist/src/plugins/types.d.ts +1 -77
- package/dist/src/plugins/types.d.ts.map +1 -1
- package/dist/src/url-resolver.d.ts +29 -11
- package/dist/src/url-resolver.d.ts.map +1 -1
- package/dist/src/url-resolver.js +152 -74
- package/dist/src/url-resolver.js.map +1 -1
- package/dist/src/utils/content-type-parser.d.ts.map +1 -1
- package/dist/src/utils/content-type-parser.js +4 -3
- package/dist/src/utils/content-type-parser.js.map +1 -1
- package/dist/src/utils/content-types.d.ts +26 -0
- package/dist/src/utils/content-types.d.ts.map +1 -0
- package/dist/src/utils/content-types.js +137 -0
- package/dist/src/utils/content-types.js.map +1 -0
- package/dist/src/utils/convert-output.d.ts +17 -0
- package/dist/src/utils/convert-output.d.ts.map +1 -0
- package/dist/src/utils/convert-output.js +176 -0
- package/dist/src/utils/convert-output.js.map +1 -0
- package/dist/src/utils/error-to-response.d.ts +3 -0
- package/dist/src/utils/error-to-response.d.ts.map +1 -0
- package/dist/src/utils/error-to-response.js +40 -0
- package/dist/src/utils/error-to-response.js.map +1 -0
- package/dist/src/utils/get-content-disposition-filename.d.ts +1 -1
- package/dist/src/utils/get-content-disposition-filename.d.ts.map +1 -1
- package/dist/src/utils/get-content-disposition-filename.js +4 -0
- package/dist/src/utils/get-content-disposition-filename.js.map +1 -1
- package/dist/src/utils/get-e-tag.d.ts +20 -15
- package/dist/src/utils/get-e-tag.d.ts.map +1 -1
- package/dist/src/utils/get-e-tag.js +8 -22
- package/dist/src/utils/get-e-tag.js.map +1 -1
- package/dist/src/utils/get-offset-and-length.d.ts +12 -2
- package/dist/src/utils/get-offset-and-length.d.ts.map +1 -1
- package/dist/src/utils/get-offset-and-length.js +63 -21
- package/dist/src/utils/get-offset-and-length.js.map +1 -1
- package/dist/src/utils/get-range-header.d.ts +22 -0
- package/dist/src/utils/get-range-header.d.ts.map +1 -0
- package/dist/src/utils/get-range-header.js +69 -0
- package/dist/src/utils/get-range-header.js.map +1 -0
- package/dist/src/utils/parse-url-string.d.ts +2 -1
- package/dist/src/utils/parse-url-string.d.ts.map +1 -1
- package/dist/src/utils/parse-url-string.js +46 -71
- package/dist/src/utils/parse-url-string.js.map +1 -1
- package/dist/src/utils/resource-to-cache-key.d.ts +3 -3
- package/dist/src/utils/resource-to-cache-key.js +5 -5
- package/dist/src/utils/resource-to-cache-key.js.map +1 -1
- package/dist/src/utils/response-headers.d.ts +4 -14
- package/dist/src/utils/response-headers.d.ts.map +1 -1
- package/dist/src/utils/response-headers.js +36 -36
- package/dist/src/utils/response-headers.js.map +1 -1
- package/dist/src/utils/responses.d.ts +30 -11
- package/dist/src/utils/responses.d.ts.map +1 -1
- package/dist/src/utils/responses.js +146 -39
- package/dist/src/utils/responses.js.map +1 -1
- package/dist/src/verified-fetch.d.ts +16 -15
- package/dist/src/verified-fetch.d.ts.map +1 -1
- package/dist/src/verified-fetch.js +302 -238
- package/dist/src/verified-fetch.js.map +1 -1
- package/dist/typedoc-urls.json +64 -45
- package/package.json +4 -3
- package/src/constants.ts +3 -0
- package/src/index.ts +199 -68
- package/src/plugins/index.ts +0 -6
- package/src/plugins/plugin-base.ts +8 -10
- package/src/plugins/plugin-handle-car.ts +48 -46
- package/src/plugins/plugin-handle-ipld.ts +93 -0
- package/src/plugins/plugin-handle-ipns-record.ts +31 -41
- package/src/plugins/plugin-handle-tar.ts +25 -29
- package/src/plugins/plugin-handle-unixfs.ts +217 -0
- package/src/plugins/types.ts +0 -86
- package/src/url-resolver.ts +197 -83
- package/src/utils/content-type-parser.ts +4 -3
- package/src/utils/content-types.ts +159 -0
- package/src/utils/convert-output.ts +187 -0
- package/src/utils/error-to-response.ts +49 -0
- package/src/utils/get-content-disposition-filename.ts +7 -1
- package/src/utils/get-e-tag.ts +26 -35
- package/src/utils/get-offset-and-length.ts +75 -21
- package/src/utils/get-range-header.ts +107 -0
- package/src/utils/parse-url-string.ts +51 -80
- package/src/utils/resource-to-cache-key.ts +5 -5
- package/src/utils/response-headers.ts +40 -41
- package/src/utils/responses.ts +186 -45
- package/src/verified-fetch.ts +353 -270
- package/dist/src/plugins/plugin-handle-byte-range-context.d.ts +0 -14
- package/dist/src/plugins/plugin-handle-byte-range-context.d.ts.map +0 -1
- package/dist/src/plugins/plugin-handle-byte-range-context.js +0 -25
- package/dist/src/plugins/plugin-handle-byte-range-context.js.map +0 -1
- package/dist/src/plugins/plugin-handle-cbor.d.ts +0 -17
- package/dist/src/plugins/plugin-handle-cbor.d.ts.map +0 -1
- package/dist/src/plugins/plugin-handle-cbor.js +0 -94
- package/dist/src/plugins/plugin-handle-cbor.js.map +0 -1
- package/dist/src/plugins/plugin-handle-dag-cbor-html-preview.d.ts +0 -27
- package/dist/src/plugins/plugin-handle-dag-cbor-html-preview.d.ts.map +0 -1
- package/dist/src/plugins/plugin-handle-dag-cbor-html-preview.js +0 -279
- package/dist/src/plugins/plugin-handle-dag-cbor-html-preview.js.map +0 -1
- package/dist/src/plugins/plugin-handle-dag-cbor.d.ts +0 -17
- package/dist/src/plugins/plugin-handle-dag-cbor.d.ts.map +0 -1
- package/dist/src/plugins/plugin-handle-dag-cbor.js +0 -66
- package/dist/src/plugins/plugin-handle-dag-cbor.js.map +0 -1
- package/dist/src/plugins/plugin-handle-dag-pb.d.ts +0 -17
- package/dist/src/plugins/plugin-handle-dag-pb.d.ts.map +0 -1
- package/dist/src/plugins/plugin-handle-dag-pb.js +0 -209
- package/dist/src/plugins/plugin-handle-dag-pb.js.map +0 -1
- package/dist/src/plugins/plugin-handle-dag-walk.d.ts +0 -21
- package/dist/src/plugins/plugin-handle-dag-walk.d.ts.map +0 -1
- package/dist/src/plugins/plugin-handle-dag-walk.js +0 -95
- package/dist/src/plugins/plugin-handle-dag-walk.js.map +0 -1
- package/dist/src/plugins/plugin-handle-dir-index-html.d.ts +0 -10
- package/dist/src/plugins/plugin-handle-dir-index-html.d.ts.map +0 -1
- package/dist/src/plugins/plugin-handle-dir-index-html.js +0 -59
- package/dist/src/plugins/plugin-handle-dir-index-html.js.map +0 -1
- package/dist/src/plugins/plugin-handle-json.d.ts +0 -12
- package/dist/src/plugins/plugin-handle-json.d.ts.map +0 -1
- package/dist/src/plugins/plugin-handle-json.js +0 -73
- package/dist/src/plugins/plugin-handle-json.js.map +0 -1
- package/dist/src/plugins/plugin-handle-raw.d.ts +0 -9
- package/dist/src/plugins/plugin-handle-raw.d.ts.map +0 -1
- package/dist/src/plugins/plugin-handle-raw.js +0 -92
- package/dist/src/plugins/plugin-handle-raw.js.map +0 -1
- package/dist/src/plugins/plugins.d.ts +0 -6
- package/dist/src/plugins/plugins.d.ts.map +0 -1
- package/dist/src/plugins/plugins.js +0 -6
- package/dist/src/plugins/plugins.js.map +0 -1
- package/dist/src/utils/byte-range-context.d.ts +0 -103
- package/dist/src/utils/byte-range-context.d.ts.map +0 -1
- package/dist/src/utils/byte-range-context.js +0 -504
- package/dist/src/utils/byte-range-context.js.map +0 -1
- package/dist/src/utils/dag-cbor-to-safe-json.d.ts +0 -15
- package/dist/src/utils/dag-cbor-to-safe-json.d.ts.map +0 -1
- package/dist/src/utils/dag-cbor-to-safe-json.js +0 -54
- package/dist/src/utils/dag-cbor-to-safe-json.js.map +0 -1
- package/dist/src/utils/dir-index-html.d.ts +0 -19
- package/dist/src/utils/dir-index-html.d.ts.map +0 -1
- package/dist/src/utils/dir-index-html.js +0 -438
- package/dist/src/utils/dir-index-html.js.map +0 -1
- package/dist/src/utils/get-peer-id-from-string.d.ts +0 -3
- package/dist/src/utils/get-peer-id-from-string.d.ts.map +0 -1
- package/dist/src/utils/get-peer-id-from-string.js +0 -10
- package/dist/src/utils/get-peer-id-from-string.js.map +0 -1
- package/dist/src/utils/get-resolved-accept-header.d.ts +0 -9
- package/dist/src/utils/get-resolved-accept-header.d.ts.map +0 -1
- package/dist/src/utils/get-resolved-accept-header.js +0 -27
- package/dist/src/utils/get-resolved-accept-header.js.map +0 -1
- package/dist/src/utils/get-stream-from-async-iterable.d.ts +0 -9
- package/dist/src/utils/get-stream-from-async-iterable.d.ts.map +0 -1
- package/dist/src/utils/get-stream-from-async-iterable.js +0 -43
- package/dist/src/utils/get-stream-from-async-iterable.js.map +0 -1
- package/dist/src/utils/handle-redirects.d.ts +0 -16
- package/dist/src/utils/handle-redirects.d.ts.map +0 -1
- package/dist/src/utils/handle-redirects.js +0 -84
- package/dist/src/utils/handle-redirects.js.map +0 -1
- package/dist/src/utils/is-accept-explicit.d.ts +0 -15
- package/dist/src/utils/is-accept-explicit.d.ts.map +0 -1
- package/dist/src/utils/is-accept-explicit.js +0 -26
- package/dist/src/utils/is-accept-explicit.js.map +0 -1
- package/dist/src/utils/request-headers.d.ts +0 -13
- package/dist/src/utils/request-headers.d.ts.map +0 -1
- package/dist/src/utils/request-headers.js +0 -63
- package/dist/src/utils/request-headers.js.map +0 -1
- package/dist/src/utils/select-output-type.d.ts +0 -17
- package/dist/src/utils/select-output-type.d.ts.map +0 -1
- package/dist/src/utils/select-output-type.js +0 -153
- package/dist/src/utils/select-output-type.js.map +0 -1
- package/dist/src/utils/tlru.d.ts +0 -15
- package/dist/src/utils/tlru.d.ts.map +0 -1
- package/dist/src/utils/tlru.js +0 -34
- package/dist/src/utils/tlru.js.map +0 -1
- package/dist/src/utils/walk-path.d.ts +0 -27
- package/dist/src/utils/walk-path.d.ts.map +0 -1
- package/dist/src/utils/walk-path.js +0 -45
- package/dist/src/utils/walk-path.js.map +0 -1
- package/src/plugins/plugin-handle-byte-range-context.ts +0 -30
- package/src/plugins/plugin-handle-cbor.ts +0 -107
- package/src/plugins/plugin-handle-dag-cbor-html-preview.ts +0 -295
- package/src/plugins/plugin-handle-dag-cbor.ts +0 -83
- package/src/plugins/plugin-handle-dag-pb.ts +0 -248
- package/src/plugins/plugin-handle-dag-walk.ts +0 -110
- package/src/plugins/plugin-handle-dir-index-html.ts +0 -72
- package/src/plugins/plugin-handle-json.ts +0 -80
- package/src/plugins/plugin-handle-raw.ts +0 -110
- package/src/plugins/plugins.ts +0 -5
- package/src/utils/byte-range-context.ts +0 -597
- package/src/utils/dag-cbor-to-safe-json.ts +0 -63
- package/src/utils/dir-index-html.ts +0 -505
- package/src/utils/get-peer-id-from-string.ts +0 -12
- package/src/utils/get-resolved-accept-header.ts +0 -42
- package/src/utils/get-stream-from-async-iterable.ts +0 -49
- package/src/utils/handle-redirects.ts +0 -109
- package/src/utils/is-accept-explicit.ts +0 -38
- package/src/utils/request-headers.ts +0 -65
- package/src/utils/select-output-type.ts +0 -175
- package/src/utils/tlru.ts +0 -42
- package/src/utils/walk-path.ts +0 -69
|
@@ -1,19 +1,17 @@
|
|
|
1
|
-
import type { VerifiedFetchPlugin, PluginContext, PluginOptions } from '
|
|
1
|
+
import type { VerifiedFetchPlugin, PluginContext, PluginOptions } from '../index.js'
|
|
2
2
|
import type { Logger } from '@libp2p/interface'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Base class for verified-fetch plugins. This class provides a basic
|
|
6
|
-
* interface.
|
|
5
|
+
* Base class for verified-fetch plugins. This class provides a basic
|
|
6
|
+
* implementation of the `VerifiedFetchPlugin` interface.
|
|
7
7
|
*
|
|
8
|
-
* Subclasses must implement the `id` property
|
|
9
|
-
*
|
|
8
|
+
* Subclasses must implement the `id` property, the `canHandle`, and `handle`
|
|
9
|
+
* methods.
|
|
10
10
|
*
|
|
11
|
-
*
|
|
11
|
+
* Subclasses may override the `codes` and `log` properties.
|
|
12
12
|
*/
|
|
13
13
|
export abstract class BasePlugin implements VerifiedFetchPlugin {
|
|
14
|
-
readonly codes: number[] = []
|
|
15
14
|
readonly pluginOptions: PluginOptions
|
|
16
|
-
abstract readonly id: string
|
|
17
15
|
protected _log?: Logger
|
|
18
16
|
|
|
19
17
|
get log (): Logger {
|
|
@@ -29,7 +27,7 @@ export abstract class BasePlugin implements VerifiedFetchPlugin {
|
|
|
29
27
|
this.pluginOptions = options
|
|
30
28
|
}
|
|
31
29
|
|
|
30
|
+
abstract readonly id: string
|
|
32
31
|
abstract canHandle (context: PluginContext): boolean
|
|
33
|
-
|
|
34
|
-
abstract handle (context: PluginContext): Promise<Response | null>
|
|
32
|
+
abstract handle (context: PluginContext): Promise<Response>
|
|
35
33
|
}
|
|
@@ -2,17 +2,15 @@ import { BlockExporter, car, CIDPath, depthFirstWalker, naturalOrderWalker, Subg
|
|
|
2
2
|
import { code as dagPbCode } from '@ipld/dag-pb'
|
|
3
3
|
import { createScalableCuckooFilter } from '@libp2p/utils'
|
|
4
4
|
import toBrowserReadableStream from 'it-to-browser-readablestream'
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { CONTENT_TYPE_CAR, MEDIA_TYPE_CAR } from '../utils/content-types.ts'
|
|
6
|
+
import { getContentDispositionFilename } from '../utils/get-content-disposition-filename.ts'
|
|
7
|
+
import { entityBytesToOffsetAndLength } from '../utils/get-offset-and-length.ts'
|
|
8
|
+
import { badRequestResponse, notAcceptableResponse, okResponse } from '../utils/responses.js'
|
|
7
9
|
import { BasePlugin } from './plugin-base.js'
|
|
8
|
-
import type { PluginContext } from '
|
|
10
|
+
import type { PluginContext } from '../index.js'
|
|
9
11
|
import type { ExportCarOptions, UnixFSExporterOptions } from '@helia/car'
|
|
10
12
|
|
|
11
|
-
function getFilename (
|
|
12
|
-
if (query.filename != null) {
|
|
13
|
-
return query.filename
|
|
14
|
-
}
|
|
15
|
-
|
|
13
|
+
function getFilename (ipfsPath: string): string {
|
|
16
14
|
// convert context.ipfsPath to a filename. replace all / with _, replace prefix protocol with empty string
|
|
17
15
|
const filename = ipfsPath
|
|
18
16
|
.replace(/\/ipfs\//, '')
|
|
@@ -23,17 +21,20 @@ function getFilename ({ cid, ipfsPath, query }: Pick<PluginContext, 'query' | 'c
|
|
|
23
21
|
return `${filename}.car`
|
|
24
22
|
}
|
|
25
23
|
|
|
26
|
-
|
|
24
|
+
/**
|
|
25
|
+
* @see https://specs.ipfs.tech/http-gateways/trustless-gateway/#dag-scope-request-query-parameter
|
|
26
|
+
*/
|
|
27
27
|
type DagScope = 'all' | 'entity' | 'block'
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
|
|
29
|
+
function getDagScope ({ url }: PluginContext): DagScope | null {
|
|
30
|
+
const dagScope = url.searchParams.get('dag-scope')
|
|
30
31
|
|
|
31
32
|
if (dagScope === 'all' || dagScope === 'entity' || dagScope === 'block') {
|
|
32
33
|
return dagScope
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
// entity-bytes implies entity scope
|
|
36
|
-
if (
|
|
37
|
+
if (url.searchParams.has('entity-bytes')) {
|
|
37
38
|
return 'entity'
|
|
38
39
|
}
|
|
39
40
|
|
|
@@ -47,38 +48,36 @@ function getDagScope ({ query }: Pick<PluginContext, 'query'>): DagScope | null
|
|
|
47
48
|
export class CarPlugin extends BasePlugin {
|
|
48
49
|
readonly id = 'car-plugin'
|
|
49
50
|
|
|
50
|
-
canHandle (
|
|
51
|
-
|
|
52
|
-
return false
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (context.pathDetails == null) {
|
|
56
|
-
return false
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return context.accept?.mimeType.startsWith('application/vnd.ipld.car') === true || context.query.format === 'car' // application/vnd.ipld.car
|
|
51
|
+
canHandle ({ accept }: PluginContext): boolean {
|
|
52
|
+
return accept.some(header => header.contentType.mediaType === MEDIA_TYPE_CAR)
|
|
60
53
|
}
|
|
61
54
|
|
|
62
|
-
async handle (context: PluginContext
|
|
63
|
-
const { options,
|
|
55
|
+
async handle (context: PluginContext): Promise<Response> {
|
|
56
|
+
const { options, url, accept, resource, blockstore, range, ipfsRoots, terminalElement } = context
|
|
57
|
+
|
|
58
|
+
if (range != null) {
|
|
59
|
+
return badRequestResponse(resource, new Error('Range requests are not supported for CAR files'))
|
|
60
|
+
}
|
|
64
61
|
|
|
65
|
-
const
|
|
66
|
-
const duplicates = accept?.options.dups !== 'n' && context.query['car-dups'] !== 'n'
|
|
62
|
+
const acceptCar = accept.filter(header => header.contentType.mediaType === MEDIA_TYPE_CAR).pop()
|
|
67
63
|
|
|
68
|
-
|
|
69
|
-
|
|
64
|
+
// we have already asserted that the CAR media type is present so this
|
|
65
|
+
// branch should never be hit
|
|
66
|
+
if (acceptCar == null) {
|
|
67
|
+
return badRequestResponse(resource, new Error('Could not find CAR media type in accept header'))
|
|
70
68
|
}
|
|
71
69
|
|
|
70
|
+
const order = acceptCar.options.order === 'dfs' || url.searchParams.get('car-order') === 'dfs' ? 'dfs' : 'unk'
|
|
71
|
+
const duplicates = acceptCar.options.dups !== 'n' && url.searchParams.get('car-dups') !== 'n'
|
|
72
|
+
|
|
72
73
|
// TODO: `@ipld/car` only supports CARv1
|
|
73
|
-
if (
|
|
74
|
-
return notAcceptableResponse(resource,
|
|
74
|
+
if (acceptCar.options.version === '2' || url.searchParams.get('car-version') === '2') {
|
|
75
|
+
return notAcceptableResponse(resource, [
|
|
76
|
+
CONTENT_TYPE_CAR
|
|
77
|
+
])
|
|
75
78
|
}
|
|
76
79
|
|
|
77
|
-
const
|
|
78
|
-
context.reqFormat = 'car'
|
|
79
|
-
context.query.download = true
|
|
80
|
-
context.query.filename = getFilename(context)
|
|
81
|
-
const blockstore = getBlockstore(cid, context.resource, options?.session ?? true, options)
|
|
80
|
+
const helia = this.pluginOptions.helia
|
|
82
81
|
|
|
83
82
|
const c = car({
|
|
84
83
|
blockstore,
|
|
@@ -95,12 +94,12 @@ export class CarPlugin extends BasePlugin {
|
|
|
95
94
|
carExportOptions.blockFilter = createScalableCuckooFilter(1024)
|
|
96
95
|
}
|
|
97
96
|
|
|
98
|
-
if (
|
|
99
|
-
carExportOptions.traversal = new CIDPath(
|
|
97
|
+
if (ipfsRoots.length > 1) {
|
|
98
|
+
carExportOptions.traversal = new CIDPath(ipfsRoots)
|
|
100
99
|
}
|
|
101
100
|
|
|
102
101
|
const dagScope = getDagScope(context)
|
|
103
|
-
const target =
|
|
102
|
+
const target = terminalElement.cid
|
|
104
103
|
|
|
105
104
|
if (dagScope === 'block') {
|
|
106
105
|
carExportOptions.exporter = new BlockExporter()
|
|
@@ -112,7 +111,7 @@ export class CarPlugin extends BasePlugin {
|
|
|
112
111
|
listingOnly: true
|
|
113
112
|
}
|
|
114
113
|
|
|
115
|
-
const slice =
|
|
114
|
+
const slice = entityBytesToOffsetAndLength(terminalElement.size, url.searchParams.get('entity-bytes'))
|
|
116
115
|
options.offset = slice.offset
|
|
117
116
|
options.length = slice.length
|
|
118
117
|
|
|
@@ -126,14 +125,17 @@ export class CarPlugin extends BasePlugin {
|
|
|
126
125
|
})
|
|
127
126
|
}
|
|
128
127
|
|
|
129
|
-
|
|
128
|
+
const stream = toBrowserReadableStream(c.export(target, carExportOptions))
|
|
130
129
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
130
|
+
return okResponse(resource, stream, {
|
|
131
|
+
headers: {
|
|
132
|
+
'content-type': `${MEDIA_TYPE_CAR}; version=1; order=${order}; dups=${duplicates ? 'y' : 'n'}`,
|
|
133
|
+
'content-disposition': `attachment; ${
|
|
134
|
+
getContentDispositionFilename(url.searchParams.get('filename') ?? getFilename(`/ipfs/${url.hostname}${url.pathname}`))
|
|
135
|
+
}`,
|
|
136
|
+
'x-content-type-options': 'nosniff',
|
|
137
|
+
'accept-ranges': 'none'
|
|
138
|
+
}
|
|
134
139
|
})
|
|
135
|
-
response.headers.set('content-type', context.byteRangeContext.getContentType() ?? `application/vnd.ipld.car; version=1; order=${order}; dups=${duplicates ? 'y' : 'n'}`)
|
|
136
|
-
|
|
137
|
-
return response
|
|
138
140
|
}
|
|
139
141
|
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import * as ipldDagCbor from '@ipld/dag-cbor'
|
|
2
|
+
import * as ipldDagJson from '@ipld/dag-json'
|
|
3
|
+
import * as dagPb from '@ipld/dag-pb'
|
|
4
|
+
import toBuffer from 'it-to-buffer'
|
|
5
|
+
import * as json from 'multiformats/codecs/json'
|
|
6
|
+
import * as raw from 'multiformats/codecs/raw'
|
|
7
|
+
import { identity } from 'multiformats/hashes/identity'
|
|
8
|
+
import { CODEC_CBOR } from '../constants.ts'
|
|
9
|
+
import { getContentTypesForCid, MEDIA_TYPE_CBOR, MEDIA_TYPE_DAG_CBOR, MEDIA_TYPE_DAG_JSON, MEDIA_TYPE_JSON, MEDIA_TYPE_OCTET_STREAM, MEDIA_TYPE_RAW } from '../utils/content-types.ts'
|
|
10
|
+
import { convertOutput } from '../utils/convert-output.ts'
|
|
11
|
+
import { getContentDispositionFilename } from '../utils/get-content-disposition-filename.ts'
|
|
12
|
+
import { notAcceptableResponse, okResponse, partialContentResponse } from '../utils/responses.js'
|
|
13
|
+
import { BasePlugin } from './plugin-base.js'
|
|
14
|
+
import type { ContentType, PluginContext } from '../index.ts'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Handles loading JSON and CBOR content
|
|
18
|
+
*/
|
|
19
|
+
export class IpldPlugin extends BasePlugin {
|
|
20
|
+
readonly id = 'ipld-plugin'
|
|
21
|
+
readonly codes = [
|
|
22
|
+
CODEC_CBOR,
|
|
23
|
+
ipldDagCbor.code,
|
|
24
|
+
ipldDagJson.code,
|
|
25
|
+
json.code,
|
|
26
|
+
raw.code,
|
|
27
|
+
dagPb.code,
|
|
28
|
+
identity.code
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
canHandle ({ terminalElement, accept }: PluginContext): boolean {
|
|
32
|
+
const supportsCid = this.codes.includes(terminalElement.cid.code)
|
|
33
|
+
const supportsAccept = accept.length === 0 ||
|
|
34
|
+
accept.some(header => header.contentType.mediaType === MEDIA_TYPE_CBOR ||
|
|
35
|
+
header.contentType.mediaType === MEDIA_TYPE_DAG_CBOR ||
|
|
36
|
+
header.contentType.mediaType === MEDIA_TYPE_JSON ||
|
|
37
|
+
header.contentType.mediaType === MEDIA_TYPE_DAG_JSON ||
|
|
38
|
+
header.contentType.mediaType === MEDIA_TYPE_RAW ||
|
|
39
|
+
header.contentType.mediaType === MEDIA_TYPE_OCTET_STREAM
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
return supportsCid && supportsAccept
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async handle (context: PluginContext): Promise<Response> {
|
|
46
|
+
const { url, resource, accept, ipfsRoots, terminalElement, blockstore, options } = context
|
|
47
|
+
|
|
48
|
+
this.log.trace('fetching %c/%s', terminalElement.cid, url.pathname)
|
|
49
|
+
let block: Uint8Array
|
|
50
|
+
if (terminalElement.node == null) {
|
|
51
|
+
block = await toBuffer(blockstore.get(terminalElement.cid, options))
|
|
52
|
+
} else if (terminalElement.type === 'object' || terminalElement.type === 'raw' || terminalElement.type === 'identity') {
|
|
53
|
+
block = terminalElement.node
|
|
54
|
+
} else {
|
|
55
|
+
block = dagPb.encode(terminalElement.node)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let contentType: ContentType
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
// maybe convert output to different binary format
|
|
62
|
+
const result = convertOutput(terminalElement.cid, block, accept)
|
|
63
|
+
block = result.output
|
|
64
|
+
contentType = result.contentType
|
|
65
|
+
} catch (err) {
|
|
66
|
+
this.log.error('could not decode object from block - %e', err)
|
|
67
|
+
return notAcceptableResponse(resource, getContentTypesForCid(terminalElement.cid))
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const headers = {
|
|
71
|
+
'content-length': `${block.byteLength}`,
|
|
72
|
+
'content-type': contentType.mediaType,
|
|
73
|
+
'content-disposition': `${url.searchParams.get('download') === 'true' ? 'attachment' : contentType.disposition}; ${
|
|
74
|
+
getContentDispositionFilename(url.searchParams.get('filename') ?? `${terminalElement.cid}${contentType.extension}`)
|
|
75
|
+
}`,
|
|
76
|
+
'x-ipfs-roots': ipfsRoots.map(cid => cid.toV1()).join(','),
|
|
77
|
+
'x-content-type-options': 'nosniff',
|
|
78
|
+
'accept-ranges': 'bytes'
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (context.range != null) {
|
|
82
|
+
return partialContentResponse(resource, async function * (offset, length) {
|
|
83
|
+
yield block.subarray(offset, offset + length)
|
|
84
|
+
}, context.range, block.byteLength, {
|
|
85
|
+
headers
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return okResponse(resource, block, {
|
|
90
|
+
headers
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { peerIdFromString } from '@libp2p/peer-id'
|
|
1
2
|
import { marshalIPNSRecord } from 'ipns'
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
3
|
+
import { CONTENT_TYPE_IPNS, MEDIA_TYPE_IPNS_RECORD } from '../utils/content-types.ts'
|
|
4
|
+
import { getContentDispositionFilename } from '../utils/get-content-disposition-filename.ts'
|
|
5
|
+
import { badRequestResponse, okResponse } from '../utils/responses.js'
|
|
4
6
|
import { BasePlugin } from './plugin-base.js'
|
|
5
|
-
import type { PluginContext } from '
|
|
7
|
+
import type { PluginContext } from '../index.js'
|
|
6
8
|
import type { PeerId } from '@libp2p/interface'
|
|
7
9
|
|
|
8
10
|
/**
|
|
@@ -13,60 +15,48 @@ export class IpnsRecordPlugin extends BasePlugin {
|
|
|
13
15
|
readonly id = 'ipns-record-plugin'
|
|
14
16
|
readonly codes = []
|
|
15
17
|
|
|
16
|
-
canHandle ({ accept
|
|
17
|
-
|
|
18
|
-
return false
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
return accept?.mimeType === 'application/vnd.ipfs.ipns-record' || query.format === 'ipns-record'
|
|
18
|
+
canHandle ({ accept }: PluginContext): boolean {
|
|
19
|
+
return accept.some(header => header.contentType.mediaType === MEDIA_TYPE_IPNS_RECORD)
|
|
22
20
|
}
|
|
23
21
|
|
|
24
|
-
async handle (context:
|
|
25
|
-
const { resource,
|
|
22
|
+
async handle (context: Pick<PluginContext, 'resource' | 'url' | 'options' | 'range'>): Promise<Response> {
|
|
23
|
+
const { resource, url, options, range } = context
|
|
26
24
|
const { ipnsResolver } = this.pluginOptions
|
|
27
|
-
context.reqFormat = 'ipns-record'
|
|
28
25
|
|
|
29
|
-
if (
|
|
30
|
-
this.log.error('invalid request for IPNS name "%s" and path "%s"', resource,
|
|
26
|
+
if (url.pathname !== '' || url.protocol !== 'ipns:') {
|
|
27
|
+
this.log.error('invalid request for IPNS name "%s" and path "%s"', resource, url.pathname)
|
|
31
28
|
return badRequestResponse(resource, new Error('Invalid IPNS name'))
|
|
32
29
|
}
|
|
33
30
|
|
|
31
|
+
if (range != null) {
|
|
32
|
+
return badRequestResponse(resource, new Error('Range requests are not supported for IPNS records'))
|
|
33
|
+
}
|
|
34
|
+
|
|
34
35
|
let peerId: PeerId
|
|
35
36
|
|
|
36
37
|
try {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
if (resource.startsWith('ipns://')) {
|
|
40
|
-
peerIdString = resource.replace('ipns://', '')
|
|
41
|
-
} else if (resource.includes('/ipns/')) {
|
|
42
|
-
peerIdString = resource.split('/ipns/')[1].split('/')[0].split('?')[0]
|
|
43
|
-
} else {
|
|
44
|
-
peerIdString = resource.split('.ipns.')[0].split('://')[1]
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
this.log.trace('trying to parse peer id from "%s"', peerIdString)
|
|
48
|
-
peerId = getPeerIdFromString(peerIdString)
|
|
38
|
+
this.log.trace('trying to parse peer id from "%s"', url.hostname)
|
|
39
|
+
peerId = peerIdFromString(url.hostname)
|
|
49
40
|
} catch (err: any) {
|
|
50
41
|
this.log.error('could not parse peer id from IPNS url %s', resource, err)
|
|
51
42
|
|
|
52
43
|
return badRequestResponse(resource, err)
|
|
53
44
|
}
|
|
54
45
|
|
|
55
|
-
// force download in handleFinalResponse
|
|
56
|
-
query.filename = query.filename ?? `${peerId}.bin`
|
|
57
|
-
query.download = true
|
|
58
|
-
|
|
59
|
-
// @ts-expect-error progress handler types are incompatible
|
|
60
46
|
const result = await ipnsResolver.resolve(peerId, options)
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
47
|
+
const block = marshalIPNSRecord(result.record)
|
|
48
|
+
|
|
49
|
+
return okResponse(resource, block, {
|
|
50
|
+
headers: {
|
|
51
|
+
'content-length': `${block.byteLength}`,
|
|
52
|
+
'content-type': CONTENT_TYPE_IPNS.mediaType,
|
|
53
|
+
'content-disposition': `attachment; ${
|
|
54
|
+
getContentDispositionFilename(url.searchParams.get('filename') ?? `${peerId}${CONTENT_TYPE_IPNS.extension}`)
|
|
55
|
+
}`,
|
|
56
|
+
'x-ipfs-roots': result.cid.toV1().toString(),
|
|
57
|
+
'cache-control': `public, max-age=${Number((result.record.ttl ?? 0n) / BigInt(1e9))}`,
|
|
58
|
+
'accept-ranges': 'none'
|
|
59
|
+
}
|
|
60
|
+
})
|
|
71
61
|
}
|
|
72
62
|
}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
+
import { NotUnixFSError } from '@helia/unixfs/errors'
|
|
1
2
|
import { code as dagPbCode } from '@ipld/dag-pb'
|
|
2
3
|
import toBrowserReadableStream from 'it-to-browser-readablestream'
|
|
3
4
|
import { code as rawCode } from 'multiformats/codecs/raw'
|
|
4
|
-
import {
|
|
5
|
+
import { MEDIA_TYPE_TAR } from '../utils/content-types.ts'
|
|
6
|
+
import { getContentDispositionFilename } from '../utils/get-content-disposition-filename.ts'
|
|
5
7
|
import { tarStream } from '../utils/get-tar-stream.js'
|
|
6
|
-
import {
|
|
8
|
+
import { badRequestResponse, okResponse } from '../utils/responses.js'
|
|
7
9
|
import { BasePlugin } from './plugin-base.js'
|
|
8
|
-
import type { PluginContext } from '
|
|
10
|
+
import type { PluginContext } from '../index.js'
|
|
9
11
|
|
|
10
12
|
/**
|
|
11
13
|
* Accepts a UnixFS `CID` and returns a `.tar` file containing the file or
|
|
@@ -15,37 +17,31 @@ export class TarPlugin extends BasePlugin {
|
|
|
15
17
|
readonly id = 'tar-plugin'
|
|
16
18
|
readonly codes = []
|
|
17
19
|
|
|
18
|
-
canHandle ({
|
|
19
|
-
|
|
20
|
-
return false
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
return accept?.mimeType === 'application/x-tar' || query.format === 'tar'
|
|
20
|
+
canHandle ({ accept }: PluginContext): boolean {
|
|
21
|
+
return accept.some(header => header.contentType.mediaType === MEDIA_TYPE_TAR)
|
|
24
22
|
}
|
|
25
23
|
|
|
26
|
-
async handle (context: PluginContext
|
|
27
|
-
const {
|
|
28
|
-
const { getBlockstore } = this.pluginOptions
|
|
24
|
+
async handle (context: PluginContext): Promise<Response> {
|
|
25
|
+
const { terminalElement, url, resource, options, blockstore, range } = context
|
|
29
26
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
return notAcceptableResponse('only UnixFS data can be returned in a TAR file')
|
|
27
|
+
if (terminalElement.cid.code !== dagPbCode && terminalElement.cid.code !== rawCode) {
|
|
28
|
+
return badRequestResponse(resource, new NotUnixFSError('Only UnixFS data can be returned in a TAR file'))
|
|
33
29
|
}
|
|
34
30
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const blockstore = getBlockstore(terminusElement, resource, options?.session, options)
|
|
40
|
-
const stream = toBrowserReadableStream<Uint8Array>(tarStream(`/ipfs/${cid}/${path}`, blockstore, options))
|
|
41
|
-
|
|
42
|
-
context.byteRangeContext.setBody(stream)
|
|
43
|
-
|
|
44
|
-
const response = okRangeResponse(resource, context.byteRangeContext.getBody('application/x-tar'), { byteRangeContext: context.byteRangeContext, log: this.log })
|
|
45
|
-
response.headers.set('content-type', context.byteRangeContext.getContentType() ?? 'application/x-tar')
|
|
46
|
-
|
|
47
|
-
response.headers.set('etag', getETag({ cid: terminusElement, reqFormat: context.reqFormat, weak: true }))
|
|
31
|
+
if (range != null) {
|
|
32
|
+
return badRequestResponse(resource, new Error('Range requests are not supported for TAR files'))
|
|
33
|
+
}
|
|
48
34
|
|
|
49
|
-
|
|
35
|
+
const stream = toBrowserReadableStream<Uint8Array>(tarStream(`/ipfs/${terminalElement.cid}${url.pathname}`, blockstore, options))
|
|
36
|
+
|
|
37
|
+
return okResponse(resource, stream, {
|
|
38
|
+
headers: {
|
|
39
|
+
'content-type': MEDIA_TYPE_TAR,
|
|
40
|
+
'content-disposition': `attachment; ${
|
|
41
|
+
getContentDispositionFilename(url.searchParams.get('filename') ?? `${terminalElement.cid.toString()}.tar`)
|
|
42
|
+
}`,
|
|
43
|
+
'accept-ranges': 'none'
|
|
44
|
+
}
|
|
45
|
+
})
|
|
50
46
|
}
|
|
51
47
|
}
|