Functional Weave
Code in TypeScript

net.hostname-validate@1.0.0

impl/typescript.ts

1,769 bytes · the TypeScript implementation · view raw

import { type HostnameValidation } from "./net_hostname_validate_types.ts";

const ALLOWED = /^[A-Za-z0-9.-]*$/;
const DIGITS = /^[0-9]+$/;

/**
 * Check a host name against the RFC 1123 / RFC 952 rules and normalise it:
 * lower-case, one trailing dot (a fully qualified name) removed.
 *
 * Checks run in a fixed order and the first failure is the reason: empty,
 * characters, total length, then each label in turn, then the top-level label.
 */
export function validateHostname(text: string): HostnameValidation {
  if (typeof text !== "string") return { valid: false, hostname: null, reason: "not text", labels: 0 };
  const name = text.endsWith(".") ? text.slice(0, -1) : text;
  const labels = name.length === 0 ? [] : name.split(".");
  const fail = (reason: string): HostnameValidation => ({ valid: false, hostname: null, reason, labels: labels.length });

  if (name.length === 0) return fail("empty");
  // Characters first: once the text is known to be ASCII, every language
  // counts its length the same way.
  if (!ALLOWED.test(name)) return fail("character other than a letter, digit, hyphen or dot");
  if (name.length > 253) return fail("longer than 253 characters");
  for (const label of labels) {
    if (label.length === 0) return fail("empty label");
    if (label.length > 63) return fail("label longer than 63 characters");
    if (label.startsWith("-") || label.endsWith("-")) return fail("label starts or ends with a hyphen");
  }
  // RFC 3696 section 2: a top-level domain is never all-numeric, which keeps
  // 1.2.3.4 an address rather than a name.
  if (DIGITS.test(labels[labels.length - 1])) return fail("top-level label is all numeric");
  return { valid: true, hostname: name.toLowerCase(), reason: null, labels: labels.length };
}