@helia/verified-fetch
Version:
A fetch-like API for obtaining verified & trustless IPFS content on the web
83 lines • 4.2 kB
JavaScript
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