diff --git a/packages/verified-fetch/src/utils/dag-cbor-to-safe-json.ts b/packages/verified-fetch/src/utils/dag-cbor-to-safe-json.ts index 0c52bdcc..b8d051bb 100644 --- a/packages/verified-fetch/src/utils/dag-cbor-to-safe-json.ts +++ b/packages/verified-fetch/src/utils/dag-cbor-to-safe-json.ts @@ -1,18 +1,5 @@ import { decode } from 'cborg' import { encode } from 'cborg/json' -import { CID } from 'multiformats/cid' -import type { TagDecoder } from 'cborg' - -// https://github.com/ipfs/go-ipfs/issues/3570#issuecomment-273931692 -const CID_CBOR_TAG = 0x2A - -function cidDecoder (bytes: Uint8Array): CID { - if (bytes[0] !== 0) { - throw new Error('Invalid CID for CBOR tag 42; expected leading 0x00') - } - - return CID.decode(bytes.subarray(1)) // ignore leading 0x00 -} /** * Take a `DAG-CBOR` encoded `Uint8Array`, deserialize it as an object and @@ -20,18 +7,14 @@ function cidDecoder (bytes: Uint8Array): CID { * `JSON.parse` without losing any data. */ export function dagCborToSafeJSON (buf: Uint8Array): string { - const tags: TagDecoder[] = [] - tags[CID_CBOR_TAG] = cidDecoder - const obj = decode(buf, { allowIndefinite: false, - coerceUndefinedToNull: true, + coerceUndefinedToNull: false, allowNaN: false, allowInfinity: false, strict: true, useMaps: false, rejectDuplicateMapKeys: true, - tags, // this is different to `DAG-CBOR` - the reason we disallow BigInts is // because we are about to re-encode to `JSON` which does not support diff --git a/packages/verified-fetch/test/accept-header.spec.ts b/packages/verified-fetch/test/accept-header.spec.ts index a71d9882..b21b0fd6 100644 --- a/packages/verified-fetch/test/accept-header.spec.ts +++ b/packages/verified-fetch/test/accept-header.spec.ts @@ -6,15 +6,59 @@ import * as ipldDagJson from '@ipld/dag-json' import { stop } from '@libp2p/interface' import { createEd25519PeerId } from '@libp2p/peer-id-factory' import { expect } from 'aegir/chai' +import * as cborg from 'cborg' import { marshal } from 'ipns' +import { CID } from 'multiformats/cid' +import * as raw from 'multiformats/codecs/raw' +import { sha256 } from 'multiformats/hashes/sha2' import { VerifiedFetch } from '../src/verified-fetch.js' import { createHelia } from './fixtures/create-offline-helia.js' import type { Helia } from '@helia/interface' +interface Codec { + encode(obj: any): Uint8Array + decode(obj: Uint8Array): any +} + +interface AcceptCborTestArgs { + obj: any + type: string + codec?: Codec +} + describe('accept header', () => { let helia: Helia let verifiedFetch: VerifiedFetch + function shouldNotAcceptCborWith ({ obj, type, codec = ipldDagCbor }: AcceptCborTestArgs): void { + it(`should return 406 Not Acceptable if CBOR ${type} field is encountered`, async () => { + const buf = codec.encode(obj) + const rawCid = CID.createV1(raw.code, await sha256.digest(buf)) + await helia.blockstore.put(rawCid, buf) + const dagCborCid = CID.createV1(ipldDagCbor.code, rawCid.multihash) + + const resp = await verifiedFetch.fetch(dagCborCid, { + headers: { + accept: 'application/json' + } + }) + + expect(resp.status).to.equal(406) + expect(resp.statusText).to.equal('Not Acceptable') + + const resp2 = await verifiedFetch.fetch(dagCborCid, { + headers: { + accept: 'application/octet-stream' + } + }) + + expect(resp2.status).to.equal(200) + + const out = codec.decode(new Uint8Array(await resp2.arrayBuffer())) + expect(out).to.deep.equal(obj, 'could not round-trip as application/octet-stream') + }) + } + beforeEach(async () => { helia = await createHelia() verifiedFetch = new VerifiedFetch({ @@ -246,4 +290,39 @@ describe('accept header', () => { expect(buf).to.equalBytes(marshal(record)) }) + + shouldNotAcceptCborWith({ + obj: { + hello: 'world', + invalid: undefined + }, + type: 'undefined', + // `undefined` is not supported by the IPLD data model so we have to encode + // using cborg and not @ipld/dag-cbor + codec: cborg + }) + + shouldNotAcceptCborWith({ + obj: { + hello: 'world', + invalid: BigInt(Number.MAX_SAFE_INTEGER) + 10n + }, + type: 'BigInt' + }) + + shouldNotAcceptCborWith({ + obj: { + hello: 'world', + invalid: Uint8Array.from([0, 1, 2, 3]) + }, + type: 'Uint8Array' + }) + + shouldNotAcceptCborWith({ + obj: { + hello: 'world', + invalid: CID.parse('QmbxpRxwKXxnJQjnPqm1kzDJSJ8YgkLxH23mcZURwPHjGv') + }, + type: 'CID' + }) })