domain.ts 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. import { IOptions } from './options';
  2. /**
  3. * Check if `vhost` is a valid suffix of `hostname` (top-domain)
  4. *
  5. * It means that `vhost` needs to be a suffix of `hostname` and we then need to
  6. * make sure that: either they are equal, or the character preceding `vhost` in
  7. * `hostname` is a '.' (it should not be a partial label).
  8. *
  9. * * hostname = 'not.evil.com' and vhost = 'vil.com' => not ok
  10. * * hostname = 'not.evil.com' and vhost = 'evil.com' => ok
  11. * * hostname = 'not.evil.com' and vhost = 'not.evil.com' => ok
  12. */
  13. function shareSameDomainSuffix(hostname: string, vhost: string): boolean {
  14. if (hostname.endsWith(vhost)) {
  15. return (
  16. hostname.length === vhost.length ||
  17. hostname[hostname.length - vhost.length - 1] === '.'
  18. );
  19. }
  20. return false;
  21. }
  22. /**
  23. * Given a hostname and its public suffix, extract the general domain.
  24. */
  25. function extractDomainWithSuffix(
  26. hostname: string,
  27. publicSuffix: string,
  28. ): string {
  29. // Locate the index of the last '.' in the part of the `hostname` preceding
  30. // the public suffix.
  31. //
  32. // examples:
  33. // 1. not.evil.co.uk => evil.co.uk
  34. // ^ ^
  35. // | | start of public suffix
  36. // | index of the last dot
  37. //
  38. // 2. example.co.uk => example.co.uk
  39. // ^ ^
  40. // | | start of public suffix
  41. // |
  42. // | (-1) no dot found before the public suffix
  43. const publicSuffixIndex = hostname.length - publicSuffix.length - 2;
  44. const lastDotBeforeSuffixIndex = hostname.lastIndexOf('.', publicSuffixIndex);
  45. // No '.' found, then `hostname` is the general domain (no sub-domain)
  46. if (lastDotBeforeSuffixIndex === -1) {
  47. return hostname;
  48. }
  49. // Extract the part between the last '.'
  50. return hostname.slice(lastDotBeforeSuffixIndex + 1);
  51. }
  52. /**
  53. * Detects the domain based on rules and upon and a host string
  54. */
  55. export default function getDomain(
  56. suffix: string,
  57. hostname: string,
  58. options: IOptions,
  59. ): string | null {
  60. // Check if `hostname` ends with a member of `validHosts`.
  61. if (options.validHosts !== null) {
  62. const validHosts = options.validHosts;
  63. for (const vhost of validHosts) {
  64. if (/*@__INLINE__*/ shareSameDomainSuffix(hostname, vhost)) {
  65. return vhost;
  66. }
  67. }
  68. }
  69. let numberOfLeadingDots = 0;
  70. if (hostname.startsWith('.')) {
  71. while (
  72. numberOfLeadingDots < hostname.length &&
  73. hostname[numberOfLeadingDots] === '.'
  74. ) {
  75. numberOfLeadingDots += 1;
  76. }
  77. }
  78. // If `hostname` is a valid public suffix, then there is no domain to return.
  79. // Since we already know that `getPublicSuffix` returns a suffix of `hostname`
  80. // there is no need to perform a string comparison and we only compare the
  81. // size.
  82. if (suffix.length === hostname.length - numberOfLeadingDots) {
  83. return null;
  84. }
  85. // To extract the general domain, we start by identifying the public suffix
  86. // (if any), then consider the domain to be the public suffix with one added
  87. // level of depth. (e.g.: if hostname is `not.evil.co.uk` and public suffix:
  88. // `co.uk`, then we take one more level: `evil`, giving the final result:
  89. // `evil.co.uk`).
  90. return /*@__INLINE__*/ extractDomainWithSuffix(hostname, suffix);
  91. }