Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: do not coerce undefined to null for JSON serialization #2

Merged
merged 1 commit into from
Feb 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 1 addition & 18 deletions packages/verified-fetch/src/utils/dag-cbor-to-safe-json.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,20 @@
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
* re-serialize it in a form that can be passed to `JSON.serialize` and then
* `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
Expand Down
79 changes: 79 additions & 0 deletions packages/verified-fetch/test/accept-header.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down Expand Up @@ -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'
})
})
Loading