@helia/verified-fetch
Version:
A fetch-like API for obtaining verified & trustless IPFS content on the web
84 lines • 4.24 kB
JavaScript
import { SubdomainNotSupportedError } from '../errors.js';
import { matchURLString } from './parse-url-string.js';
import { movedPermanentlyResponse } from './responses.js';
function maybeAddTrailingSlash(path) {
// if it has an extension-like ending, don't add a trailing slash
if (path.match(/\.[a-zA-Z0-9]{1,4}$/) != null) {
return path;
}
return path.endsWith('/') ? path : `${path}/`;
}
// See https://specs.ipfs.tech/http-gateways/path-gateway/#location-response-header
export async function getRedirectResponse({ resource, options, logger, cid, fetch = globalThis.fetch }) {
const log = logger.forComponent('helia:verified-fetch:get-redirect-response');
if (typeof resource !== 'string' || options == null || ['ipfs://', 'ipns://'].some((prefix) => resource.startsWith(prefix))) {
return null;
}
const headers = new Headers(options?.headers);
const forwardedHost = headers.get('x-forwarded-host');
const headerHost = headers.get('host');
const forwardedFor = headers.get('x-forwarded-for');
if (forwardedFor == null && forwardedHost == null && headerHost == null) {
log.trace('no redirect info found in headers');
return null;
}
log.trace('checking for redirect info');
// if x-forwarded-host is passed, we need to set the location header to the subdomain
// so that the browser can redirect to the correct subdomain
try {
const urlParts = matchURLString(resource);
const reqUrl = new URL(resource);
const actualHost = forwardedHost ?? reqUrl.host;
const subdomainUrl = new URL(reqUrl);
if (urlParts.protocol === 'ipfs' && cid.version === 0) {
subdomainUrl.host = `${cid.toV1()}.ipfs.${actualHost}`;
}
else {
subdomainUrl.host = `${urlParts.cidOrPeerIdOrDnsLink}.${urlParts.protocol}.${actualHost}`;
}
if (headerHost?.includes(urlParts.protocol) === true && subdomainUrl.host.includes(headerHost)) {
log.trace('request was for a subdomain already, not setting location header');
return null;
}
if (headerHost != null && !subdomainUrl.host.includes(headerHost)) {
log.trace('host header is not the same as the subdomain url host, not setting location header');
return null;
}
if (reqUrl.host === subdomainUrl.host) {
log.trace('req url is the same as the subdomain url, not setting location header');
return null;
}
subdomainUrl.pathname = maybeAddTrailingSlash(reqUrl.pathname.replace(`/${urlParts.cidOrPeerIdOrDnsLink}`, '').replace(`/${urlParts.protocol}`, ''));
log.trace('subdomain url %s', subdomainUrl.href);
const pathUrl = new URL(reqUrl, `${reqUrl.protocol}//${actualHost}`);
pathUrl.pathname = maybeAddTrailingSlash(reqUrl.pathname);
log.trace('path url %s', pathUrl.href);
// try to query subdomain with HEAD request to see if it's supported
try {
const subdomainTest = await fetch(subdomainUrl, { method: 'HEAD' });
if (subdomainTest.ok) {
log('subdomain supported, redirecting to subdomain');
return movedPermanentlyResponse(resource.toString(), subdomainUrl.href);
}
else {
log('subdomain not supported, subdomain failed with status %s %s', subdomainTest.status, subdomainTest.statusText);
throw new SubdomainNotSupportedError('subdomain not supported');
}
}
catch (err) {
log('subdomain not supported', err);
if (pathUrl.href === reqUrl.href) {
log('path url is the same as the request url, not setting location header');
return null;
}
// pathUrl is different from request URL (maybe even with just a trailing slash)
return movedPermanentlyResponse(resource.toString(), pathUrl.href);
}
}
catch (e) {
// if it's not a full URL, we have nothing left to do.
log.error('error setting location header for x-forwarded-host', e);
}
return null;
}
//# sourceMappingURL=handle-redirects.js.map