@@ -6,16 +6,21 @@ import { HttpError } from '../util/errors.js'
6
6
/**
7
7
* @typedef {import('../bindings').IpfsUrlContext & import('../bindings').DagulaContext & { timeoutController?: import('../bindings').TimeoutControllerContext['timeoutController'] } } CarHandlerContext
8
8
* @typedef {import('multiformats').CID } CID
9
+ * @typedef {{ version: 1|2, order: import('dagula').BlockOrder, dups: boolean } } CarParams
9
10
*/
10
11
12
+ /** @type {CarParams } */
13
+ const DefaultCarParams = { version : 1 , order : 'unk' , dups : true }
14
+
11
15
/** @type {import('../bindings').Handler<CarHandlerContext> } */
12
16
export async function handleCar ( request , env , ctx ) {
13
17
const { dataCid, path, timeoutController : controller , dagula, searchParams } = ctx
14
18
if ( ! dataCid ) throw new Error ( 'missing IPFS path' )
15
19
if ( path == null ) throw new Error ( 'missing URL path' )
16
20
if ( ! dagula ) throw new Error ( 'missing dagula instance' )
17
21
18
- const carScope = getCarScope ( searchParams )
22
+ const dagScope = getDagScope ( searchParams )
23
+ const { version, order, dups } = getAcceptParams ( request . headers )
19
24
20
25
// Use root CID for etag even tho we may resolve a different root for the terminus of the path
21
26
// as etags are only relevant per path. If the caller has an etag for this path already, and
@@ -34,7 +39,8 @@ export async function handleCar (request, env, ctx) {
34
39
const { writer, out } = CarWriter . create ( dataCid )
35
40
; ( async ( ) => {
36
41
try {
37
- for await ( const block of dagula . getPath ( `${ dataCid } ${ path } ` , { carScope, signal : controller ?. signal } ) ) {
42
+ for await ( const block of dagula . getPath ( `${ dataCid } ${ path } ` , { dagScope, order, signal : controller ?. signal } ) ) {
43
+ // @ts -expect-error
38
44
await writer . put ( block )
39
45
}
40
46
} catch ( /** @type {any } */ err ) {
@@ -52,7 +58,7 @@ export async function handleCar (request, env, ctx) {
52
58
const headers = {
53
59
// Make it clear we don't support range-requests over a car stream
54
60
'Accept-Ranges' : 'none' ,
55
- 'Content-Type' : ' application/vnd.ipld.car; version=1' ,
61
+ 'Content-Type' : ` application/vnd.ipld.car; version=${ version } ; order= ${ order } ; dups= ${ dups ? 'y' : 'n' } ` ,
56
62
'X-Content-Type-Options' : 'nosniff' ,
57
63
Etag : etag ,
58
64
'Cache-Control' : 'public, max-age=29030400, immutable' ,
@@ -62,11 +68,45 @@ export async function handleCar (request, env, ctx) {
62
68
return new Response ( toReadableStream ( out ) , { headers } )
63
69
}
64
70
65
- /** @param {URLSearchParams } searchParams */
66
- function getCarScope ( searchParams ) {
67
- const carScope = searchParams . get ( 'car-scope' ) ?? 'all'
68
- if ( carScope === 'all' || carScope === 'file' || carScope === 'block' ) {
69
- return carScope
71
+ /**
72
+ * @param {URLSearchParams } searchParams
73
+ * @returns {import('dagula').DagScope }
74
+ */
75
+ function getDagScope ( searchParams ) {
76
+ const scope = searchParams . get ( 'dag-scope' ) ?? 'all'
77
+ if ( scope === 'all' || scope === 'entity' || scope === 'block' ) {
78
+ return scope
79
+ }
80
+ throw new HttpError ( `unsupported dag-scope: ${ scope } ` , { status : 400 } )
81
+ }
82
+
83
+ /**
84
+ * @param {Headers } headers
85
+ * @returns {CarParams }
86
+ */
87
+ function getAcceptParams ( headers ) {
88
+ const accept = headers . get ( 'accept' )
89
+ if ( ! accept ) return DefaultCarParams
90
+
91
+ const types = accept . split ( ',' ) . map ( s => s . trim ( ) )
92
+ const carType = types . find ( t => t . startsWith ( 'application/vnd.ipld.car' ) )
93
+ if ( ! carType ) return DefaultCarParams
94
+
95
+ const paramPairs = carType . split ( ';' ) . slice ( 1 ) . map ( s => s . trim ( ) )
96
+ const { version, order, dups } = Object . fromEntries ( paramPairs . map ( p => p . split ( '=' ) . map ( s => s . trim ( ) ) ) )
97
+
98
+ // only CARv1
99
+ if ( version != null && version !== '1' ) {
100
+ throw new HttpError ( `unsupported accept parameter: version=${ version } ` , { status : 400 } )
70
101
}
71
- throw new HttpError ( `unsupported car-scope: ${ carScope } ` , { status : 400 } )
102
+ // only yes duplicates
103
+ if ( dups && dups !== 'y' ) {
104
+ throw new HttpError ( `unsupported accept parameter: dups=${ dups } ` , { status : 400 } )
105
+ }
106
+ // only dfs or unk ordering
107
+ if ( order && order !== 'dfs' && order !== 'unk' ) {
108
+ throw new HttpError ( `unsupported accept parameter: order=${ order } ` , { status : 400 } )
109
+ }
110
+
111
+ return { version : 1 , order, dups : true }
72
112
}
0 commit comments