// Depends on: data/tlds.js (REAL_TLDS, REAL_SLD_TLDS) /** * Validate a virtual host hostname. * Rules: * 1. Must have at least 2 dots (3 labels minimum: host.second.tld) * 2. Each label: only [a-z0-9-], no leading/trailing hyphen, non-empty * 3. The last two labels (base domain, e.g. "hole.sail") must not be a * real public TLD or second-level public suffix * Returns { ok: true } or { ok: false, error: string } */ function isValidVhostHostname(hostname) { if (!hostname) return { ok: false, error: 'Hostname is required' }; const labels = hostname.split('.'); if (labels.length < 3) { return { ok: false, error: 'Hostname must have the form host.second.tld (e.g. myapp.hole.sail) — single-dot names are not allowed' }; } for (const label of labels) { if (!label) return { ok: false, error: 'Hostname contains empty labels' }; if (!/^[a-z0-9-]+$/.test(label)) return { ok: false, error: 'Hostname contains invalid characters — only letters, digits and hyphens allowed' }; if (label.startsWith('-') || label.endsWith('-')) return { ok: false, error: 'Hostname labels must not start or end with a hyphen' }; } const tld = labels[labels.length - 1]; const baseDomain = labels.slice(-2).join('.'); if (REAL_TLDS.has(tld) || REAL_SLD_TLDS.has(baseDomain)) { return { ok: false, error: 'The TLD ".' + baseDomain + '" is a real registered domain — use a private TLD like .hole.sail or .my.internal' }; } return { ok: true }; } /** Extract the two-label base domain from a hostname (e.g. "hole.sail" from "myapp.hole.sail") */ function extractBaseDomain(hostname) { const parts = hostname.split('.'); return parts.slice(-2).join('.'); } /** Extract unique two-label base domains from a list of virtual host objects */ function extractActiveTlds(virtualHosts) { const seen = new Set(); seen.add('hole.sail'); // always include baseline for (const v of (virtualHosts || [])) { if (v.hostname) seen.add(extractBaseDomain(v.hostname)); } return Array.from(seen).map(b => '.' + b); }