HostsFile.php 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. <?php
  2. namespace React\Dns\Config;
  3. use RuntimeException;
  4. /**
  5. * Represents a static hosts file which maps hostnames to IPs
  6. *
  7. * Hosts files are used on most systems to avoid actually hitting the DNS for
  8. * certain common hostnames.
  9. *
  10. * Most notably, this file usually contains an entry to map "localhost" to the
  11. * local IP. Windows is a notable exception here, as Windows does not actually
  12. * include "localhost" in this file by default. To compensate for this, this
  13. * class may explicitly be wrapped in another HostsFile instance which
  14. * hard-codes these entries for Windows (see also Factory).
  15. *
  16. * This class mostly exists to abstract the parsing/extraction process so this
  17. * can be replaced with a faster alternative in the future.
  18. */
  19. class HostsFile
  20. {
  21. /**
  22. * Returns the default path for the hosts file on this system
  23. *
  24. * @return string
  25. * @codeCoverageIgnore
  26. */
  27. public static function getDefaultPath()
  28. {
  29. // use static path for all Unix-based systems
  30. if (DIRECTORY_SEPARATOR !== '\\') {
  31. return '/etc/hosts';
  32. }
  33. // Windows actually stores the path in the registry under
  34. // \HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\DataBasePath
  35. $path = '%SystemRoot%\\system32\drivers\etc\hosts';
  36. $base = getenv('SystemRoot');
  37. if ($base === false) {
  38. $base = 'C:\\Windows';
  39. }
  40. return str_replace('%SystemRoot%', $base, $path);
  41. }
  42. /**
  43. * Loads a hosts file (from the given path or default location)
  44. *
  45. * Note that this method blocks while loading the given path and should
  46. * thus be used with care! While this should be relatively fast for normal
  47. * hosts file, this may be an issue if this file is located on a slow device
  48. * or contains an excessive number of entries. In particular, this method
  49. * should only be executed before the loop starts, not while it is running.
  50. *
  51. * @param ?string $path (optional) path to hosts file or null=load default location
  52. * @return self
  53. * @throws RuntimeException if the path can not be loaded (does not exist)
  54. */
  55. public static function loadFromPathBlocking($path = null)
  56. {
  57. if ($path === null) {
  58. $path = self::getDefaultPath();
  59. }
  60. $contents = @file_get_contents($path);
  61. if ($contents === false) {
  62. throw new RuntimeException('Unable to load hosts file "' . $path . '"');
  63. }
  64. return new self($contents);
  65. }
  66. private $contents;
  67. /**
  68. * Instantiate new hosts file with the given hosts file contents
  69. *
  70. * @param string $contents
  71. */
  72. public function __construct($contents)
  73. {
  74. // remove all comments from the contents
  75. $contents = preg_replace('/[ \t]*#.*/', '', strtolower($contents));
  76. $this->contents = $contents;
  77. }
  78. /**
  79. * Returns all IPs for the given hostname
  80. *
  81. * @param string $name
  82. * @return string[]
  83. */
  84. public function getIpsForHost($name)
  85. {
  86. $name = strtolower($name);
  87. $ips = array();
  88. foreach (preg_split('/\r?\n/', $this->contents) as $line) {
  89. $parts = preg_split('/\s+/', $line);
  90. $ip = array_shift($parts);
  91. if ($parts && array_search($name, $parts) !== false) {
  92. // remove IPv6 zone ID (`fe80::1%lo0` => `fe80:1`)
  93. if (strpos($ip, ':') !== false && ($pos = strpos($ip, '%')) !== false) {
  94. $ip = substr($ip, 0, $pos);
  95. }
  96. if (@inet_pton($ip) !== false) {
  97. $ips[] = $ip;
  98. }
  99. }
  100. }
  101. return $ips;
  102. }
  103. /**
  104. * Returns all hostnames for the given IPv4 or IPv6 address
  105. *
  106. * @param string $ip
  107. * @return string[]
  108. */
  109. public function getHostsForIp($ip)
  110. {
  111. // check binary representation of IP to avoid string case and short notation
  112. $ip = @inet_pton($ip);
  113. if ($ip === false) {
  114. return array();
  115. }
  116. $names = array();
  117. foreach (preg_split('/\r?\n/', $this->contents) as $line) {
  118. $parts = preg_split('/\s+/', $line, -1, PREG_SPLIT_NO_EMPTY);
  119. $addr = (string) array_shift($parts);
  120. // remove IPv6 zone ID (`fe80::1%lo0` => `fe80:1`)
  121. if (strpos($addr, ':') !== false && ($pos = strpos($addr, '%')) !== false) {
  122. $addr = substr($addr, 0, $pos);
  123. }
  124. if (@inet_pton($addr) === $ip) {
  125. foreach ($parts as $part) {
  126. $names[] = $part;
  127. }
  128. }
  129. }
  130. return $names;
  131. }
  132. }