UNPKG

@helia/verified-fetch

Version:

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

73 lines (60 loc) 3.03 kB
import { code as dagPbCode } from '@ipld/dag-pb' import { base32 } from 'multiformats/bases/base32' import { sha256 } from 'multiformats/hashes/sha2' import { dirIndexHtml } from '../utils/dir-index-html.js' import { getETag } from '../utils/get-e-tag.js' import { getIpfsRoots } from '../utils/response-headers.js' import { okRangeResponse } from '../utils/responses.js' import { BasePlugin } from './plugin-base.js' import type { PluginContext, VerifiedFetchPluginFactory } from './types.js' import type { UnixFSEntry } from 'ipfs-unixfs-exporter' /** * Converts a list of directory entries into a small hash that can be used in the etag header. * * @see https://github.com/ipfs/boxo/blob/dc60fe747c375c631a92fcfd6c7456f44a760d24/gateway/assets/assets.go#L84 * @see https://github.com/ipfs/boxo/blob/dc60fe747c375c631a92fcfd6c7456f44a760d24/gateway/handler_unixfs_dir.go#L233-L235 */ async function getAssetHash (directoryEntries: UnixFSEntry[]): Promise<string> { const entryDetails = directoryEntries.reduce((acc, entry) => { return `${acc}${entry.name}${entry.cid.toString()}` }, '') const hashBytes = await sha256.encode(new TextEncoder().encode(entryDetails)) return base32.encode(hashBytes) } export class DirIndexHtmlPlugin extends BasePlugin { readonly id = 'dir-index-html-plugin' readonly codes = [dagPbCode] canHandle (context: PluginContext): boolean { const { cid, pathDetails, directoryEntries } = context if (pathDetails == null) { return false } if (pathDetails.terminalElement?.type !== 'directory') { return false } if (directoryEntries == null || directoryEntries.length === 0) { return false } return cid.code === dagPbCode } async handle (context: PluginContext & Required<Pick<PluginContext, 'byteRangeContext' | 'pathDetails' | 'directoryEntries'>>): Promise<Response> { const { resource, pathDetails, directoryEntries } = context const { terminalElement, ipfsRoots } = pathDetails const gatewayURL = resource const htmlResponse = dirIndexHtml(terminalElement, directoryEntries, { gatewayURL, log: this.log }) context.byteRangeContext.setBody(htmlResponse) const etagPrefix = `DirIndex-${await getAssetHash(directoryEntries)}_CID-` const response = okRangeResponse(resource, context.byteRangeContext.getBody('text/html'), { byteRangeContext: context.byteRangeContext, log: this.log }, { headers: { 'Content-Type': context.byteRangeContext.getContentType() ?? 'text/html', // see https://github.com/ipfs/gateway-conformance/pull/219 'Cache-Control': 'public, max-age=604800, stale-while-revalidate=2678400', 'X-Ipfs-Roots': getIpfsRoots(ipfsRoots), // e.g. DirIndex-<asset_hash>_CID-<cid> Etag: getETag({ cid: terminalElement.cid, reqFormat: context.reqFormat, contentPrefix: etagPrefix }) } }) return response } } export const dirIndexHtmlPluginFactory: VerifiedFetchPluginFactory = (opts) => new DirIndexHtmlPlugin(opts)