@helia/verified-fetch
Version:
A fetch-like API for obtaining verified & trustless IPFS content on the web
120 lines • 4.95 kB
JavaScript
import { InvalidParametersError } from '@libp2p/interface';
import { peerIdFromCID, peerIdFromString } from '@libp2p/peer-id';
import { CID } from 'multiformats/cid';
import { decodeDNSLinkLabel, isInlinedDnsLink } from "./dnslink-label.js";
import { ipfsPathToUrl, ipfsUrlToUrl } from "./ipfs-path-to-url.js";
export const SUBDOMAIN_GATEWAY_REGEX = /^(?<cidOrPeerIdOrDnsLink>[^/?]+)\.(?<protocol>ip[fn]s)\.([^/?]+)$/;
const CODEC_LIBP2P_KEY = 0x72;
function matchSubdomainGroupsGuard(groups) {
const protocol = groups?.protocol;
if (protocol !== 'ipfs' && protocol !== 'ipns') {
return false;
}
const cidOrPeerIdOrDnsLink = groups?.cidOrPeerIdOrDnsLink;
if (cidOrPeerIdOrDnsLink == null) {
return false;
}
return true;
}
/**
* If the caller has passed a case-sensitive identifier (like a base58btc
* encoded CID or PeerId) in a case-insensitive location (like a subdomain),
* be nice and return the original identifier from the passed string
*/
function findOriginalCidOrPeer(needle, haystack) {
const start = haystack.toLowerCase().indexOf(needle);
if (start === -1) {
return needle;
}
return haystack.substring(start, start + needle.length);
}
function stringToUrl(urlString) {
if (urlString instanceof URL) {
return urlString;
}
// turn IPFS/IPNS path into gateway URL string
if (urlString.startsWith('/ipfs/') || urlString.startsWith('/ipns/')) {
urlString = ipfsPathToUrl(urlString);
}
// turn IPFS/IPNS URL into gateway URL string
if (urlString.startsWith('ipfs://') || urlString.startsWith('ipns://')) {
urlString = ipfsUrlToUrl(urlString);
}
if (urlString.startsWith('http://') || urlString.startsWith('https://')) {
return new URL(urlString);
}
throw new InvalidParametersError(`Invalid URL: ${urlString}`);
}
function toURL(urlString) {
// validate url
const url = stringToUrl(urlString);
// test for subdomain gateway URL
const subdomainMatch = url.hostname.match(SUBDOMAIN_GATEWAY_REGEX);
if (matchSubdomainGroupsGuard(subdomainMatch?.groups)) {
const groups = subdomainMatch.groups;
if (groups.protocol === 'ipns' && isInlinedDnsLink(groups.cidOrPeerIdOrDnsLink)) {
// decode inline dnslink domain if present
groups.cidOrPeerIdOrDnsLink = decodeDNSLinkLabel(groups.cidOrPeerIdOrDnsLink);
}
const cidOrPeerIdOrDnsLink = findOriginalCidOrPeer(groups.cidOrPeerIdOrDnsLink, urlString);
// parse url as not http(s):// - this is necessary because URL makes
// `.pathname` default to `/` for http URLs, even if no trailing slash was
// present in the string URL and we need to be able to round-trip the user's
// input while also maintaining a sane canonical URL for the resource. Phew.
const wat = new URL(`not-${urlString}`);
return new URL(`${groups.protocol}://${cidOrPeerIdOrDnsLink}${wat.pathname}${url.search}${url.hash}`);
}
// test for IPFS path gateway URL
if (url.pathname.startsWith('/ipfs/')) {
const parts = url.pathname.substring(6).split('/');
const cid = parts.shift();
if (cid == null) {
throw new InvalidParametersError(`Path gateway URL ${urlString} had no CID`);
}
return new URL(`ipfs://${cid}${url.pathname.replace(`/ipfs/${cid}`, '')}${url.search}${url.hash}`);
}
// test for IPNS path gateway URL
if (url.pathname.startsWith('/ipns/')) {
const parts = url.pathname.substring(6).split('/');
const name = parts.shift();
if (name == null) {
throw new InvalidParametersError(`Path gateway URL ${urlString} had no name`);
}
return new URL(`ipns://${name}${url.pathname.replace(`/ipns/${name}`, '')}${url.search}${url.hash}`);
}
throw new TypeError(`Invalid URL: ${urlString}, please use ipfs://, ipns://, or gateway URLs only`);
}
/**
* Accepts the following url strings:
*
* - /ipfs/Qmfoo/path
* - /ipns/Qmfoo/path
* - ipfs://cid/path
* - ipns://name/path
* - http://cid.ipfs.example.com/path
* - http://name.ipns.example.com/path
* - http://example.com/ipfs/cid/path
* - http://example.com/ipns/name/path
*/
export function parseURLString(urlString) {
// validate url
const url = toURL(urlString);
// treat IPNS keys that do not parse as PeerIds as DNSLink
if (url.protocol === 'ipns:') {
try {
peerIdFromString(url.hostname);
}
catch {
url.protocol = 'dnslink:';
}
}
if (url.protocol === 'ipfs:') {
const cid = CID.parse(url.hostname);
// special case - peer id encoded as a CID
if (cid.code === CODEC_LIBP2P_KEY) {
return new URL(`ipns://${peerIdFromCID(cid)}${url.pathname}${url.search}${url.hash}`);
}
}
return url;
}
//# sourceMappingURL=parse-url-string.js.map