From 0bfff97029d1f88dfd17f6c1f9c593c954d0c2e2 Mon Sep 17 00:00:00 2001 From: Russell Dempsey <1173416+SgtPooki@users.noreply.github.com> Date: Mon, 11 Mar 2024 14:14:45 -0700 Subject: [PATCH] fix: redirect to proper encoded dnslink subdomain --- src/dns-link-labels.ts | 32 +++++++++++++++++++++++++++----- src/heliaServer.ts | 7 ++++++- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/src/dns-link-labels.ts b/src/dns-link-labels.ts index 55666b8..1e65edc 100644 --- a/src/dns-link-labels.ts +++ b/src/dns-link-labels.ts @@ -1,13 +1,35 @@ +import { CID } from 'multiformats/cid' + /** * For dnslinks see https://specs.ipfs.tech/http-gateways/subdomain-gateway/#host-request-header * DNSLink names include . which means they must be inlined into a single DNS label to provide unique origin and work with wildcard TLS certificates. */ +// DNS label can have up to 63 characters, consisting of alphanumeric +// characters or hyphens -, but it must not start or end with a hyphen. +const dnsLabelRegex = /^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$/ + +/** + * We can receive either IPNS Name string or DNSLink label string here. + * IPNS Names do not have dots or dashes. + */ +export function isValidDnsLabel (label: string): boolean { + // If string is not a valid IPNS Name (CID) + // then we assume it may be a valid DNSLabel. + try { + CID.parse(label) + return false + } catch { + return dnsLabelRegex.test(label) + } +} + /** - * We can receive either a peerId string or dnsLink label string here. PeerId strings do not have dots or dashes. + * Checks if label looks like inlined DNSLink. + * (https://specs.ipfs.tech/http-gateways/subdomain-gateway/#host-request-header) */ -export function isDnsLabel (label: string): boolean { - return ['-', '.'].some((char) => label.includes(char)) +export function isInlinedDnsLink (label: string): boolean { + return isValidDnsLabel(label) && label.includes('-') && !label.includes('.') } /** @@ -18,7 +40,7 @@ export function isDnsLabel (label: string): boolean { * @example en-wikipedia--on--ipfs-org.ipns.example.net -> example.net/ipns/en.wikipedia-on-ipfs.org */ export function dnsLinkLabelDecoder (linkLabel: string): string { - return linkLabel.replace(/--/g, '-').replace(/-/g, '.') + return linkLabel.replace(/--/g, '%').replace(/-/g, '.').replace(/%/g, '-') } /** @@ -29,5 +51,5 @@ export function dnsLinkLabelDecoder (linkLabel: string): string { * @example example.net/ipns/en.wikipedia-on-ipfs.org → Host: en-wikipedia--on--ipfs-org.ipns.example.net */ export function dnsLinkLabelEncoder (linkLabel: string): string { - return linkLabel.replace(/\./g, '-').replace(/-/g, '--') + return linkLabel.replace(/-/g, '--').replace(/\./g, '-') } diff --git a/src/heliaServer.ts b/src/heliaServer.ts index 333e7af..7d1c32f 100644 --- a/src/heliaServer.ts +++ b/src/heliaServer.ts @@ -3,6 +3,7 @@ import { createVerifiedFetch, type VerifiedFetch } from '@helia/verified-fetch' import { type FastifyReply, type FastifyRequest, type RouteGenericInterface } from 'fastify' import { USE_SUBDOMAINS } from './constants.js' import { contentTypeParser } from './content-type-parser.js' +import { dnsLinkLabelEncoder, isInlinedDnsLink } from './dns-link-labels.js' import { getCustomHelia } from './getCustomHelia.js' import { getIpnsAddressDetails } from './ipns-address-utils.js' import type { ComponentLogger, Logger } from '@libp2p/interface' @@ -152,8 +153,12 @@ export class HeliaServer { // } // finalUrl += encodeURIComponent(`?${new URLSearchParams(request.query).toString()}`) } + let encodedDnsLink = address + if (!isInlinedDnsLink(address)) { + encodedDnsLink = dnsLinkLabelEncoder(address) + } - const finalUrl = `//${cidv1Address ?? address}.${namespace}.${request.hostname}/${relativePath ?? ''}` + const finalUrl = `//${cidv1Address ?? encodedDnsLink}.${namespace}.${request.hostname}/${relativePath ?? ''}` this.log('redirecting to final URL:', finalUrl) await reply .headers({