friendship ended with social-app. php is my new best friend
1<?php 2 3namespace React\Dns\Resolver; 4 5use React\Cache\ArrayCache; 6use React\Cache\CacheInterface; 7use React\Dns\Config\Config; 8use React\Dns\Config\HostsFile; 9use React\Dns\Query\CachingExecutor; 10use React\Dns\Query\CoopExecutor; 11use React\Dns\Query\ExecutorInterface; 12use React\Dns\Query\FallbackExecutor; 13use React\Dns\Query\HostsFileExecutor; 14use React\Dns\Query\RetryExecutor; 15use React\Dns\Query\SelectiveTransportExecutor; 16use React\Dns\Query\TcpTransportExecutor; 17use React\Dns\Query\TimeoutExecutor; 18use React\Dns\Query\UdpTransportExecutor; 19use React\EventLoop\Loop; 20use React\EventLoop\LoopInterface; 21 22final class Factory 23{ 24 /** 25 * Creates a DNS resolver instance for the given DNS config 26 * 27 * As of v1.7.0 it's recommended to pass a `Config` object instead of a 28 * single nameserver address. If the given config contains more than one DNS 29 * nameserver, all DNS nameservers will be used in order. The primary DNS 30 * server will always be used first before falling back to the secondary or 31 * tertiary DNS server. 32 * 33 * @param Config|string $config DNS Config object (recommended) or single nameserver address 34 * @param ?LoopInterface $loop 35 * @return \React\Dns\Resolver\ResolverInterface 36 * @throws \InvalidArgumentException for invalid DNS server address 37 * @throws \UnderflowException when given DNS Config object has an empty list of nameservers 38 */ 39 public function create($config, $loop = null) 40 { 41 if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1 42 throw new \InvalidArgumentException('Argument #2 ($loop) expected null|React\EventLoop\LoopInterface'); 43 } 44 45 $executor = $this->decorateHostsFileExecutor($this->createExecutor($config, $loop ?: Loop::get())); 46 47 return new Resolver($executor); 48 } 49 50 /** 51 * Creates a cached DNS resolver instance for the given DNS config and cache 52 * 53 * As of v1.7.0 it's recommended to pass a `Config` object instead of a 54 * single nameserver address. If the given config contains more than one DNS 55 * nameserver, all DNS nameservers will be used in order. The primary DNS 56 * server will always be used first before falling back to the secondary or 57 * tertiary DNS server. 58 * 59 * @param Config|string $config DNS Config object (recommended) or single nameserver address 60 * @param ?LoopInterface $loop 61 * @param ?CacheInterface $cache 62 * @return \React\Dns\Resolver\ResolverInterface 63 * @throws \InvalidArgumentException for invalid DNS server address 64 * @throws \UnderflowException when given DNS Config object has an empty list of nameservers 65 */ 66 public function createCached($config, $loop = null, $cache = null) 67 { 68 if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1 69 throw new \InvalidArgumentException('Argument #2 ($loop) expected null|React\EventLoop\LoopInterface'); 70 } 71 72 if ($cache !== null && !$cache instanceof CacheInterface) { // manual type check to support legacy PHP < 7.1 73 throw new \InvalidArgumentException('Argument #3 ($cache) expected null|React\Cache\CacheInterface'); 74 } 75 76 // default to keeping maximum of 256 responses in cache unless explicitly given 77 if (!($cache instanceof CacheInterface)) { 78 $cache = new ArrayCache(256); 79 } 80 81 $executor = $this->createExecutor($config, $loop ?: Loop::get()); 82 $executor = new CachingExecutor($executor, $cache); 83 $executor = $this->decorateHostsFileExecutor($executor); 84 85 return new Resolver($executor); 86 } 87 88 /** 89 * Tries to load the hosts file and decorates the given executor on success 90 * 91 * @param ExecutorInterface $executor 92 * @return ExecutorInterface 93 * @codeCoverageIgnore 94 */ 95 private function decorateHostsFileExecutor(ExecutorInterface $executor) 96 { 97 try { 98 $executor = new HostsFileExecutor( 99 HostsFile::loadFromPathBlocking(), 100 $executor 101 ); 102 } catch (\RuntimeException $e) { 103 // ignore this file if it can not be loaded 104 } 105 106 // Windows does not store localhost in hosts file by default but handles this internally 107 // To compensate for this, we explicitly use hard-coded defaults for localhost 108 if (DIRECTORY_SEPARATOR === '\\') { 109 $executor = new HostsFileExecutor( 110 new HostsFile("127.0.0.1 localhost\n::1 localhost"), 111 $executor 112 ); 113 } 114 115 return $executor; 116 } 117 118 /** 119 * @param Config|string $nameserver 120 * @param LoopInterface $loop 121 * @return CoopExecutor 122 * @throws \InvalidArgumentException for invalid DNS server address 123 * @throws \UnderflowException when given DNS Config object has an empty list of nameservers 124 */ 125 private function createExecutor($nameserver, LoopInterface $loop) 126 { 127 if ($nameserver instanceof Config) { 128 if (!$nameserver->nameservers) { 129 throw new \UnderflowException('Empty config with no DNS servers'); 130 } 131 132 // Hard-coded to check up to 3 DNS servers to match default limits in place in most systems (see MAXNS config). 133 // Note to future self: Recursion isn't too hard, but how deep do we really want to go? 134 $primary = reset($nameserver->nameservers); 135 $secondary = next($nameserver->nameservers); 136 $tertiary = next($nameserver->nameservers); 137 138 if ($tertiary !== false) { 139 // 3 DNS servers given => nest first with fallback for second and third 140 return new CoopExecutor( 141 new RetryExecutor( 142 new FallbackExecutor( 143 $this->createSingleExecutor($primary, $loop), 144 new FallbackExecutor( 145 $this->createSingleExecutor($secondary, $loop), 146 $this->createSingleExecutor($tertiary, $loop) 147 ) 148 ) 149 ) 150 ); 151 } elseif ($secondary !== false) { 152 // 2 DNS servers given => fallback from first to second 153 return new CoopExecutor( 154 new RetryExecutor( 155 new FallbackExecutor( 156 $this->createSingleExecutor($primary, $loop), 157 $this->createSingleExecutor($secondary, $loop) 158 ) 159 ) 160 ); 161 } else { 162 // 1 DNS server given => use single executor 163 $nameserver = $primary; 164 } 165 } 166 167 return new CoopExecutor(new RetryExecutor($this->createSingleExecutor($nameserver, $loop))); 168 } 169 170 /** 171 * @param string $nameserver 172 * @param LoopInterface $loop 173 * @return ExecutorInterface 174 * @throws \InvalidArgumentException for invalid DNS server address 175 */ 176 private function createSingleExecutor($nameserver, LoopInterface $loop) 177 { 178 $parts = \parse_url($nameserver); 179 180 if (isset($parts['scheme']) && $parts['scheme'] === 'tcp') { 181 $executor = $this->createTcpExecutor($nameserver, $loop); 182 } elseif (isset($parts['scheme']) && $parts['scheme'] === 'udp') { 183 $executor = $this->createUdpExecutor($nameserver, $loop); 184 } else { 185 $executor = new SelectiveTransportExecutor( 186 $this->createUdpExecutor($nameserver, $loop), 187 $this->createTcpExecutor($nameserver, $loop) 188 ); 189 } 190 191 return $executor; 192 } 193 194 /** 195 * @param string $nameserver 196 * @param LoopInterface $loop 197 * @return TimeoutExecutor 198 * @throws \InvalidArgumentException for invalid DNS server address 199 */ 200 private function createTcpExecutor($nameserver, LoopInterface $loop) 201 { 202 return new TimeoutExecutor( 203 new TcpTransportExecutor($nameserver, $loop), 204 5.0, 205 $loop 206 ); 207 } 208 209 /** 210 * @param string $nameserver 211 * @param LoopInterface $loop 212 * @return TimeoutExecutor 213 * @throws \InvalidArgumentException for invalid DNS server address 214 */ 215 private function createUdpExecutor($nameserver, LoopInterface $loop) 216 { 217 return new TimeoutExecutor( 218 new UdpTransportExecutor( 219 $nameserver, 220 $loop 221 ), 222 5.0, 223 $loop 224 ); 225 } 226}