diff --git a/packages/verified-fetch/src/utils/select-output-type.ts b/packages/verified-fetch/src/utils/select-output-type.ts index 0f7c40c9..53d76326 100644 --- a/packages/verified-fetch/src/utils/select-output-type.ts +++ b/packages/verified-fetch/src/utils/select-output-type.ts @@ -55,6 +55,7 @@ const CID_TYPE_MAP: Record<number, string[]> = { 'application/octet-stream', 'application/vnd.ipld.raw', 'application/vnd.ipfs.ipns-record', + 'application/vnd.ipld.dag-json', 'application/vnd.ipld.car', 'application/x-tar' ] @@ -145,7 +146,7 @@ function parseQFactor (str?: string): number { return factor } -const FORMAT_TO_MIME_TYPE: Record<RequestFormatShorthand, string> = { +export const FORMAT_TO_MIME_TYPE: Record<RequestFormatShorthand, string> = { raw: 'application/vnd.ipld.raw', car: 'application/vnd.ipld.car', 'dag-json': 'application/vnd.ipld.dag-json', diff --git a/packages/verified-fetch/src/verified-fetch.ts b/packages/verified-fetch/src/verified-fetch.ts index 304ad6f6..b9edf8a2 100644 --- a/packages/verified-fetch/src/verified-fetch.ts +++ b/packages/verified-fetch/src/verified-fetch.ts @@ -25,7 +25,7 @@ import { tarStream } from './utils/get-tar-stream.js' import { parseResource } from './utils/parse-resource.js' import { setCacheControlHeader } from './utils/response-headers.js' import { badRequestResponse, movedPermanentlyResponse, notAcceptableResponse, notSupportedResponse, okResponse, badRangeResponse, okRangeResponse, badGatewayResponse } from './utils/responses.js' -import { selectOutputType, queryFormatToAcceptHeader } from './utils/select-output-type.js' +import { selectOutputType, queryFormatToAcceptHeader, FORMAT_TO_MIME_TYPE } from './utils/select-output-type.js' import { walkPath } from './utils/walk-path.js' import type { CIDDetail, ContentTypeParser, Resource, VerifiedFetchInit as VerifiedFetchOptions } from './index.js' import type { RequestFormatShorthand } from './types.js' @@ -512,7 +512,16 @@ export class VerifiedFetch { this.log('incoming query format "%s", mapped to %s', query.format, queryFormatMapping) } - const acceptHeader = incomingAcceptHeader ?? queryFormatMapping + let acceptHeader: string | undefined + // if the incomingAcceptHeader is autogenerated by the requesting client (browser/curl/fetch/etc) then we may need to override it if query.format is specified + if (queryFormatMapping != null && (incomingAcceptHeader == null || !Object.values(FORMAT_TO_MIME_TYPE).includes(incomingAcceptHeader))) { + this.log('accept header not recognized, but query format provided, setting accept header to %s', queryFormatMapping) + acceptHeader = queryFormatMapping + } else { + acceptHeader = incomingAcceptHeader ?? queryFormatMapping + } + this.log('determined accept header "%s"', acceptHeader) + const accept = selectOutputType(cid, acceptHeader) this.log('output type %s', accept) diff --git a/packages/verified-fetch/test/verified-fetch.spec.ts b/packages/verified-fetch/test/verified-fetch.spec.ts index 64cbc84e..bdd0af98 100644 --- a/packages/verified-fetch/test/verified-fetch.spec.ts +++ b/packages/verified-fetch/test/verified-fetch.spec.ts @@ -710,4 +710,62 @@ describe('@helia/verifed-fetch', () => { expect(output).to.deep.equal(obj) }) }) + + describe('?format', () => { + let helia: Helia + let verifiedFetch: VerifiedFetch + let contentTypeParser: Sinon.SinonStub + + beforeEach(async () => { + contentTypeParser = Sinon.stub() + helia = await createHelia() + verifiedFetch = new VerifiedFetch({ + helia + }, { + contentTypeParser + }) + }) + + afterEach(async () => { + await stop(helia, verifiedFetch) + }) + + it('cbor?format=dag-json should be able to override curl/browser default accept header when query parameter is provided', async () => { + const obj = { + hello: 'world' + } + const c = dagCbor(helia) + const cid = await c.add(obj) + + const resp = await verifiedFetch.fetch(`http://example.com/ipfs/${cid}?format=dag-json`, { + headers: { + // see https://github.com/ipfs/helia-verified-fetch/issues/35 + // accept: '*/*' + } + }) + expect(resp.headers.get('content-type')).to.equal('application/vnd.ipld.dag-json') + const data = ipldDagJson.decode(await resp.arrayBuffer()) + expect(data).to.deep.equal(obj) + }) + + it('raw?format=dag-json should be able to override curl/browser default accept header when query parameter is provided', async () => { + const finalRootFileContent = uint8ArrayFromString(JSON.stringify({ + hello: 'world' + })) + const cid = CID.createV1(raw.code, await sha256.digest(finalRootFileContent)) + await helia.blockstore.put(cid, finalRootFileContent) + + const resp = await verifiedFetch.fetch(`http://example.com/ipfs/${cid}?format=dag-json`, { + headers: { + // see https://github.com/ipfs/helia-verified-fetch/issues/35 + accept: '*/*' + } + }) + expect(resp).to.be.ok() + expect(resp.status).to.equal(200) + expect(resp.statusText).to.equal('OK') + const data = await resp.arrayBuffer() + expect(new Uint8Array(data)).to.equalBytes(finalRootFileContent) + }) + }) })