@@ -5,24 +5,30 @@ import { unixfs as heliaUnixFs, type UnixFS as HeliaUnixFs, type UnixFSStats } f
5
5
import * as ipldDagCbor from '@ipld/dag-cbor'
6
6
import * as ipldDagJson from '@ipld/dag-json'
7
7
import { code as dagPbCode } from '@ipld/dag-pb'
8
+ import { Record as DHTRecord } from '@libp2p/kad-dht'
9
+ import { peerIdFromString } from '@libp2p/peer-id'
10
+ import { Key } from 'interface-datastore'
8
11
import toBrowserReadableStream from 'it-to-browser-readablestream'
9
12
import { code as jsonCode } from 'multiformats/codecs/json'
10
13
import { code as rawCode } from 'multiformats/codecs/raw'
11
14
import { identity } from 'multiformats/hashes/identity'
12
15
import { CustomProgressEvent } from 'progress-events'
16
+ import { concat as uint8ArrayConcat } from 'uint8arrays/concat'
17
+ import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
18
+ import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
13
19
import { dagCborToSafeJSON } from './utils/dag-cbor-to-safe-json.js'
14
20
import { getContentDispositionFilename } from './utils/get-content-disposition-filename.js'
15
21
import { getETag } from './utils/get-e-tag.js'
16
22
import { getStreamFromAsyncIterable } from './utils/get-stream-from-async-iterable.js'
17
23
import { tarStream } from './utils/get-tar-stream.js'
18
24
import { parseResource } from './utils/parse-resource.js'
19
- import { notAcceptableResponse , notSupportedResponse , okResponse } from './utils/responses.js'
25
+ import { badRequestResponse , notAcceptableResponse , notSupportedResponse , okResponse } from './utils/responses.js'
20
26
import { selectOutputType , queryFormatToAcceptHeader } from './utils/select-output-type.js'
21
27
import { walkPath } from './utils/walk-path.js'
22
28
import type { CIDDetail , ContentTypeParser , Resource , VerifiedFetchInit as VerifiedFetchOptions } from './index.js'
23
29
import type { RequestFormatShorthand } from './types.js'
24
30
import type { Helia } from '@helia/interface'
25
- import type { AbortOptions , Logger } from '@libp2p/interface'
31
+ import type { AbortOptions , Logger , PeerId } from '@libp2p/interface'
26
32
import type { UnixFSEntry } from 'ipfs-unixfs-exporter'
27
33
import type { CID } from 'multiformats/cid'
28
34
@@ -49,6 +55,11 @@ interface FetchHandlerFunctionArg {
49
55
* content cannot be represented in this format a 406 should be returned
50
56
*/
51
57
accept ?: string
58
+
59
+ /**
60
+ * The originally requested resource
61
+ */
62
+ resource : string
52
63
}
53
64
54
65
interface FetchHandlerFunction {
@@ -129,8 +140,36 @@ export class VerifiedFetch {
129
140
* Accepts an `ipns://...` URL as a string and returns a `Response` containing
130
141
* a raw IPNS record.
131
142
*/
132
- private async handleIPNSRecord ( resource : string , opts ?: VerifiedFetchOptions ) : Promise < Response > {
133
- return notSupportedResponse ( 'vnd.ipfs.ipns-record support is not implemented' )
143
+ private async handleIPNSRecord ( { resource, cid, path, options } : FetchHandlerFunctionArg ) : Promise < Response > {
144
+ if ( path !== '' || ! resource . startsWith ( 'ipns://' ) ) {
145
+ return badRequestResponse ( 'Invalid IPNS name' )
146
+ }
147
+
148
+ let peerId : PeerId
149
+
150
+ try {
151
+ peerId = peerIdFromString ( resource . replace ( 'ipns://' , '' ) )
152
+ } catch ( err ) {
153
+ this . log . error ( 'could not parse peer id from IPNS url %s' , resource )
154
+
155
+ return badRequestResponse ( 'Invalid IPNS name' )
156
+ }
157
+
158
+ // since this call happens after parseResource, we've already resolved the
159
+ // IPNS name so a local copy should be in the helia datastore, so we can
160
+ // just read it out..
161
+ const routingKey = uint8ArrayConcat ( [
162
+ uint8ArrayFromString ( '/ipns/' ) ,
163
+ peerId . toBytes ( )
164
+ ] )
165
+ const datastoreKey = new Key ( '/dht/record/' + uint8ArrayToString ( routingKey , 'base32' ) , false )
166
+ const buf = await this . helia . datastore . get ( datastoreKey , options )
167
+ const record = DHTRecord . deserialize ( buf )
168
+
169
+ const response = okResponse ( record . value )
170
+ response . headers . set ( 'content-type' , 'application/vnd.ipfs.ipns-record' )
171
+
172
+ return response
134
173
}
135
174
136
175
/**
@@ -384,28 +423,30 @@ export class VerifiedFetch {
384
423
let response : Response
385
424
let reqFormat : RequestFormatShorthand | undefined
386
425
426
+ const handlerArgs = { resource : resource . toString ( ) , cid, path, accept, options }
427
+
387
428
if ( accept === 'application/vnd.ipfs.ipns-record' ) {
388
429
// the user requested a raw IPNS record
389
430
reqFormat = 'ipns-record'
390
- response = await this . handleIPNSRecord ( resource . toString ( ) , options )
431
+ response = await this . handleIPNSRecord ( handlerArgs )
391
432
} else if ( accept === 'application/vnd.ipld.car' ) {
392
433
// the user requested a CAR file
393
434
reqFormat = 'car'
394
435
query . download = true
395
436
query . filename = query . filename ?? `${ cid . toString ( ) } .car`
396
- response = await this . handleCar ( { cid , path , options } )
437
+ response = await this . handleCar ( handlerArgs )
397
438
} else if ( accept === 'application/vnd.ipld.raw' ) {
398
439
// the user requested a raw block
399
440
reqFormat = 'raw'
400
441
query . download = true
401
442
query . filename = query . filename ?? `${ cid . toString ( ) } .bin`
402
- response = await this . handleRaw ( { cid , path , options } )
443
+ response = await this . handleRaw ( handlerArgs )
403
444
} else if ( accept === 'application/x-tar' ) {
404
445
// the user requested a TAR file
405
446
reqFormat = 'tar'
406
447
query . download = true
407
448
query . filename = query . filename ?? `${ cid . toString ( ) } .tar`
408
- response = await this . handleTar ( { cid , path , options } )
449
+ response = await this . handleTar ( handlerArgs )
409
450
} else {
410
451
// derive the handler from the CID type
411
452
const codecHandler = this . codecHandlers [ cid . code ]
@@ -414,7 +455,7 @@ export class VerifiedFetch {
414
455
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` )
415
456
}
416
457
417
- response = await codecHandler . call ( this , { cid , path , accept , options } )
458
+ response = await codecHandler . call ( this , handlerArgs )
418
459
}
419
460
420
461
response . headers . set ( 'etag' , getETag ( { cid, reqFormat, weak : false } ) )
0 commit comments