UNPKG

@helia/verified-fetch

Version:

A fetch-like API for obtaining verified & trustless IPFS content on the web

83 lines 4.2 kB
import { code as rawCode } from 'multiformats/codecs/raw'; import { identity } from 'multiformats/hashes/identity'; import { getContentType } from '../utils/get-content-type.js'; import { notFoundResponse, okRangeResponse } from '../utils/responses.js'; import { PluginFatalError } from './errors.js'; import { BasePlugin } from './plugin-base.js'; /** * These are Accept header values that will cause content type sniffing to be * skipped and set to these values. */ const RAW_HEADERS = [ 'application/vnd.ipld.dag-json', 'application/vnd.ipld.raw', 'application/octet-stream' ]; /** * if the user has specified an `Accept` header, and it's in our list of * allowable "raw" format headers, use that instead of detecting the content * type. This avoids the user from receiving something different when they * signal that they want to `Accept` a specific mime type. */ function getOverriddenRawContentType({ headers, accept }) { // accept has already been resolved by getResolvedAcceptHeader, if we have it, use it. const acceptHeader = accept ?? new Headers(headers).get('accept') ?? ''; // e.g. "Accept: text/html, application/xhtml+xml, application/xml;q=0.9, image/webp, */*;q=0.8" const acceptHeaders = acceptHeader.split(',') .map(s => s.split(';')[0]) .map(s => s.trim()); for (const mimeType of acceptHeaders) { if (mimeType === '*/*') { return; } if (RAW_HEADERS.includes(mimeType ?? '')) { return mimeType; } } } export class RawPlugin extends BasePlugin { id = 'raw-plugin'; codes = [rawCode, identity.code]; canHandle({ cid, accept, query, byteRangeContext }) { this.log('checking if we can handle %c with accept %s', cid, accept); if (byteRangeContext == null) { return false; } return accept === 'application/vnd.ipld.raw' || query.format === 'raw'; } async handle(context) { const { path, resource, cid, accept, query, options } = context; const { getBlockstore, contentTypeParser } = this.pluginOptions; const session = options?.session ?? true; const log = this.log; if (accept === 'application/vnd.ipld.raw' || query.format === 'raw') { context.reqFormat = 'raw'; context.query.download = true; context.query.filename = context.query.filename ?? `${cid.toString()}.bin`; log.trace('Set content disposition...'); } else { log.trace('Did NOT set content disposition...'); } if (path !== '' && cid.code === rawCode) { log.trace('404-ing raw codec request for %c/%s', cid, path); // throw new PluginError('ERR_RAW_PATHS_NOT_SUPPORTED', 'Raw codec does not support paths') // return notFoundResponse(resource, 'Raw codec does not support paths') throw new PluginFatalError('ERR_RAW_PATHS_NOT_SUPPORTED', 'Raw codec does not support paths', { response: notFoundResponse(resource, 'Raw codec does not support paths') }); } const terminalCid = context.pathDetails?.terminalElement.cid ?? context.cid; const blockstore = getBlockstore(terminalCid, resource, session, options); const result = await blockstore.get(terminalCid, options); context.byteRangeContext.setBody(result); // if the user has specified an `Accept` header that corresponds to a raw // type, honour that header, so for example they don't request // `application/vnd.ipld.raw` but get `application/octet-stream` const contentType = await getContentType({ filename: query.filename, bytes: result, path, defaultContentType: getOverriddenRawContentType({ headers: options?.headers, accept }), contentTypeParser, log }); const response = okRangeResponse(resource, context.byteRangeContext.getBody(contentType), { byteRangeContext: context.byteRangeContext, log }, { redirected: false }); response.headers.set('content-type', context.byteRangeContext.getContentType() ?? contentType); return response; } } //# sourceMappingURL=plugin-handle-raw.js.map