UNPKG

parse-domain

Version:

Splits a hostname into subdomains, domain and (effective) top-level domains

1 lines 9.11 kB
{"version":3,"file":"parse-domain.js","sourceRoot":"","sources":["../src/parse-domain.ts"],"names":[],"mappings":"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","sourcesContent":["import { icannTrie, privateTrie } from \"./serialized-tries.js\";\nimport { lookUpTldsInTrie } from \"./trie/look-up.js\";\nimport {\n ValidationError,\n sanitize,\n SanitizationResultType,\n SanitizationResultValidIp,\n Validation,\n} from \"./sanitize.js\";\nimport { TrieRootNode } from \"./trie/nodes.js\";\nimport { parseTrie } from \"./trie/parse-trie.js\";\nimport { NO_HOSTNAME } from \"./from-url.js\";\n\nexport const RESERVED_TOP_LEVEL_DOMAINS = [\n \"localhost\",\n \"local\",\n \"example\",\n \"invalid\",\n \"test\",\n];\n\nexport type Label = string;\n\nexport enum ParseResultType {\n /**\n * This parse result is returned in case the given hostname does not adhere to [RFC 1034](https://tools.ietf.org/html/rfc1034).\n */\n Invalid = \"INVALID\",\n /**\n * This parse result is returned if the given hostname was an IPv4 or IPv6.\n */\n Ip = \"IP\",\n /**\n * This parse result is returned when the given hostname\n * - is the root domain (the empty string `\"\"`)\n * - belongs to the top-level domain `localhost`, `local`, `example`, `invalid` or `test`\n */\n Reserved = \"RESERVED\",\n /**\n * This parse result is returned when the given hostname is valid and does not belong to a reserved top-level domain, but is not listed in the public suffix list.\n */\n NotListed = \"NOT_LISTED\",\n /**\n * This parse result is returned when the given hostname belongs to a top-level domain that is listed in the public suffix list.\n */\n Listed = \"LISTED\",\n}\n\n// The following parse result types are organized in this complicated way\n// way because every property should only be described by a single JSDoc comment.\n// If we copy the types (hence duplicating the shared properties),\n// JSDoc comments would show up duplicated in the final return type as well.\ntype ParseResultCommon<Type extends ParseResultType> = {\n /**\n * The type of the parse result. Use switch or if to distinguish between different results.\n */\n type: Type;\n /**\n * The original hostname that was passed to parseDomain().\n */\n hostname: Type extends ParseResultType.Invalid\n ? string | typeof NO_HOSTNAME\n : string;\n};\n\nexport type ParseResultInvalid = ParseResultCommon<ParseResultType.Invalid> & {\n /**\n * An array of validation errors.\n */\n errors: Array<ValidationError>;\n};\n\ntype ParseResultCommonValidDomain = {\n /**\n * An array of labels that were separated by a dot character in the given hostname.\n */\n labels: Array<Label>;\n};\n\nexport type ParseResultIp = ParseResultCommon<ParseResultType.Ip> &\n Pick<SanitizationResultValidIp, \"ipVersion\">;\n\nexport type ParseResultReserved = ParseResultCommon<ParseResultType.Reserved> &\n ParseResultCommonValidDomain;\n\nexport type ParseResultNotListed =\n ParseResultCommon<ParseResultType.NotListed> & ParseResultCommonValidDomain;\n\ntype ParseResultListedDomains = {\n /**\n * An array of labels that belong to the subdomain. Can be empty if there was no subdomain in the given hostname.\n */\n subDomains: Array<Label>;\n /**\n * The first label that belongs to the user-controlled section of the hostname. Can be undefined if just a top-level domain was passed to parseDomain().\n */\n domain: Label | undefined;\n /**\n * An array of labels that are controlled by the domain registrar.\n */\n topLevelDomains: Array<Label>;\n};\n\nexport type ParseResultListed = ParseResultCommon<ParseResultType.Listed> &\n ParseResultCommonValidDomain &\n ParseResultListedDomains & {\n /**\n * The parse result according to ICANN only without private top-level domains.\n */\n icann: ParseResultListedDomains;\n };\n\nexport type ParseResult =\n | ParseResultInvalid\n | ParseResultIp\n | ParseResultReserved\n | ParseResultNotListed\n | ParseResultListed;\n\nconst getAtIndex = <Item>(\n array: Array<Item>,\n index: number,\n): Item | undefined => {\n return index >= 0 && index < array.length ? array[index] : undefined;\n};\n\nconst splitLabelsIntoDomains = (\n labels: Array<Label>,\n index: number,\n): ParseResultListedDomains => {\n return {\n subDomains: labels.slice(0, Math.max(0, index)),\n domain: getAtIndex(labels, index),\n topLevelDomains: labels.slice(index + 1),\n };\n};\n\nlet parsedIcannTrie: TrieRootNode | undefined;\nlet parsedPrivateTrie: TrieRootNode | undefined;\n\nexport type ParseDomainOptions = {\n /**\n * If no validation is specified, Validation.Strict will be used.\n **/\n validation?: Validation;\n};\n\n/**\n * Splits the given hostname in topLevelDomains, a domain and subDomains.\n */\nexport const parseDomain = (\n hostname: string | typeof NO_HOSTNAME,\n options?: ParseDomainOptions,\n): ParseResult => {\n const sanitizationResult = sanitize(hostname, options);\n\n if (sanitizationResult.type === SanitizationResultType.Error) {\n return {\n type: ParseResultType.Invalid,\n hostname,\n errors: sanitizationResult.errors,\n };\n }\n\n if (sanitizationResult.type === SanitizationResultType.ValidIp) {\n return {\n type: ParseResultType.Ip,\n hostname: sanitizationResult.ip,\n ipVersion: sanitizationResult.ipVersion,\n };\n }\n\n const { labels, domain } = sanitizationResult;\n\n if (\n hostname === \"\" ||\n RESERVED_TOP_LEVEL_DOMAINS.includes(labels[labels.length - 1])\n ) {\n return {\n type: ParseResultType.Reserved,\n hostname: domain,\n labels,\n };\n }\n\n // Parse the serialized trie lazily\n parsedIcannTrie = parsedIcannTrie ?? parseTrie(icannTrie);\n parsedPrivateTrie = parsedPrivateTrie ?? parseTrie(privateTrie);\n\n const icannTlds = lookUpTldsInTrie(labels, parsedIcannTrie);\n const privateTlds = lookUpTldsInTrie(labels, parsedPrivateTrie);\n\n if (icannTlds.length === 0 && privateTlds.length === 0) {\n return {\n type: ParseResultType.NotListed,\n hostname: domain,\n labels,\n };\n }\n\n const indexOfPublicSuffixDomain =\n labels.length - Math.max(privateTlds.length, icannTlds.length) - 1;\n\n const indexOfIcannDomain = labels.length - icannTlds.length - 1;\n\n return {\n type: ParseResultType.Listed,\n hostname: domain,\n labels,\n icann: splitLabelsIntoDomains(labels, indexOfIcannDomain),\n ...splitLabelsIntoDomains(labels, indexOfPublicSuffixDomain),\n };\n};\n"]}