learning-website-nextjs1-2 Exercise 1: Decide the Site From the Host Name, Strictly ==================================================================================== When one program answers for several sites, everything depends on one small question: which site is this request for? It is answered from the Host header, in the shared package so that every app (and every test) uses the same answer. Add to packages/sites/src/index.ts: /** The front page of the whole domain is its own app, not one of the eight sites. */ export type HostTarget = SiteName | "portfolio"; /** * Which site a request is for, from its Host header; null when the host is none of ours. * * Only the exact names are accepted: languages.localhost, never evillanguages.localhost or * languages.localhost.evil.com. Case, a port and a trailing dot are ignored. */ export function siteForHost(host: string | null | undefined, env: Environment): HostTarget | null { if (!host) return null; const name = host.toLowerCase().replace(/:\d+$/, "").replace(/\.$/, ""); const base = env === "prod" ? DOMAIN : "localhost"; if (name === base) return "portfolio"; for (const site of SITE_NAMES) { if (name === `${site}.${base}`) return site; } return null; } Add to packages/sites/src/index.test.ts (and import siteForHost): test("a host name picks its site, ignoring case, port and a trailing dot", () => { assert.equal(siteForHost("languages.localhost", "dev"), "languages"); assert.equal(siteForHost("Languages.LOCALHOST:3010", "dev"), "languages"); assert.equal(siteForHost("systems.osztromok.com.", "prod"), "systems"); assert.equal(siteForHost("localhost:3010", "dev"), "portfolio"); assert.equal(siteForHost("osztromok.com", "prod"), "portfolio"); }); test("only exact names are accepted", () => { for (const host of [ "evillanguages.localhost", "languages.localhost.evil.com", "x.languages.localhost", "languages.osztromok.com", "languages", "", " ", null, undefined, "127.0.0.1:3010", "languages.localhost:abc", "languages.localhost:80:80", "languages.localhost\n", "languages%2elocalhost", ]) { assert.equal(siteForHost(host, "dev"), null, JSON.stringify(host)); } assert.equal(siteForHost("languages.localhost", "prod"), null); // the wrong environment's names do not work }); Strict on purpose: the name must be EXACTLY languages.localhost (or languages.osztromok.com in production) after lower-casing and removing a port and one trailing dot. So evillanguages.localhost, languages.localhost.evil.com, x.languages.localhost, an IP address and an empty header are all "not ours" (null), and the production names do not work in development or the other way round. What went wrong first, and how to avoid it: my first version also trimmed whitespace, and the test "languages.localhost\n" came back as "languages". A Host header with a newline or space in it is malformed, so it should be refused, not tidied up. The tidying made the function more forgiving than the rule it implements. The takeaway: normalise only what the rule says is equivalent (case, port, trailing dot) and nothing more, and put the awkward inputs in the test BEFORE trusting the function. npm test ✔ every folder belongs to exactly one site (0.8438ms) ✔ a path belongs to the site that owns its first folder (0.2095ms) ✔ sidebar pages follow their subject (0.3853ms) ✔ special prefixes win over their parent folder (0.1175ms) ✔ a path that no site owns fails loudly instead of guessing (0.1126ms) ✔ hosts and addresses (0.1025ms) ✔ every site has its own development port (0.1337ms) ✔ a host name picks its site, ignoring case, port and a trailing dot (0.1678ms) ✔ only exact names are accepted (0.8191ms) ℹ tests 9 ℹ pass 9 ℹ fail 0 ℹ tests 9 (7 from Chapter 1 and 2 new) WHY THIS WORKS AS AN ANSWER --------------------------- The risky decision is made in one tested function with a list of hostile inputs, not scattered through the apps.