diff --git a/src/Query/TcpTransportExecutor.php b/src/Query/TcpTransportExecutor.php index 822f145f..1333e06a 100644 --- a/src/Query/TcpTransportExecutor.php +++ b/src/Query/TcpTransportExecutor.php @@ -84,13 +84,13 @@ class TcpTransportExecutor implements ExecutorInterface */ public function __construct($nameserver, LoopInterface $loop) { - if (\strpos($nameserver, '[') === false && \substr_count($nameserver, ':') >= 2) { + if (\strpos($nameserver, '[') === false && \substr_count($nameserver, ':') >= 2 && \strpos($nameserver, '://') === false) { // several colons, but not enclosed in square brackets => enclose IPv6 address in square brackets $nameserver = '[' . $nameserver . ']'; } - $parts = \parse_url('tcp://' . $nameserver); - if (!isset($parts['scheme'], $parts['host']) || !\filter_var(\trim($parts['host'], '[]'), \FILTER_VALIDATE_IP)) { + $parts = \parse_url((\strpos($nameserver, '://') === false ? 'tcp://' : '') . $nameserver); + if (!isset($parts['scheme'], $parts['host']) || $parts['scheme'] !== 'tcp' || !\filter_var(\trim($parts['host'], '[]'), \FILTER_VALIDATE_IP)) { throw new \InvalidArgumentException('Invalid nameserver address given'); } diff --git a/src/Query/UdpTransportExecutor.php b/src/Query/UdpTransportExecutor.php index 64de88a6..d1b4d215 100644 --- a/src/Query/UdpTransportExecutor.php +++ b/src/Query/UdpTransportExecutor.php @@ -98,13 +98,13 @@ final class UdpTransportExecutor implements ExecutorInterface */ public function __construct($nameserver, LoopInterface $loop) { - if (strpos($nameserver, '[') === false && substr_count($nameserver, ':') >= 2) { + if (\strpos($nameserver, '[') === false && \substr_count($nameserver, ':') >= 2 && \strpos($nameserver, '://') === false) { // several colons, but not enclosed in square brackets => enclose IPv6 address in square brackets $nameserver = '[' . $nameserver . ']'; } - $parts = parse_url('udp://' . $nameserver); - if (!isset($parts['scheme'], $parts['host']) || $parts['scheme'] !== 'udp') { + $parts = \parse_url((\strpos($nameserver, '://') === false ? 'udp://' : '') . $nameserver); + if (!isset($parts['scheme'], $parts['host']) || $parts['scheme'] !== 'udp' || !\filter_var(\trim($parts['host'], '[]'), \FILTER_VALIDATE_IP)) { throw new \InvalidArgumentException('Invalid nameserver address given'); } diff --git a/src/Resolver/Factory.php b/src/Resolver/Factory.php index 4223f080..2cec9492 100644 --- a/src/Resolver/Factory.php +++ b/src/Resolver/Factory.php @@ -10,6 +10,7 @@ use React\Dns\Query\ExecutorInterface; use React\Dns\Query\HostsFileExecutor; use React\Dns\Query\RetryExecutor; +use React\Dns\Query\TcpTransportExecutor; use React\Dns\Query\TimeoutExecutor; use React\Dns\Query\UdpTransportExecutor; use React\EventLoop\LoopInterface; @@ -23,7 +24,7 @@ final class Factory */ public function create($nameserver, LoopInterface $loop) { - $executor = $this->decorateHostsFileExecutor($this->createRetryExecutor($nameserver, $loop)); + $executor = $this->decorateHostsFileExecutor($this->createExecutor($nameserver, $loop)); return new Resolver($executor); } @@ -41,7 +42,9 @@ public function createCached($nameserver, LoopInterface $loop, CacheInterface $c $cache = new ArrayCache(256); } - $executor = $this->decorateHostsFileExecutor($this->createCachedExecutor($nameserver, $loop, $cache)); + $executor = $this->createExecutor($nameserver, $loop); + $executor = new CachingExecutor($executor, $cache); + $executor = $this->decorateHostsFileExecutor($executor); return new Resolver($executor); } @@ -78,23 +81,27 @@ private function decorateHostsFileExecutor(ExecutorInterface $executor) private function createExecutor($nameserver, LoopInterface $loop) { - return new TimeoutExecutor( - new UdpTransportExecutor( - $nameserver, - $loop - ), - 5.0, - $loop - ); - } + $parts = \parse_url($nameserver); - private function createRetryExecutor($namserver, LoopInterface $loop) - { - return new CoopExecutor(new RetryExecutor($this->createExecutor($namserver, $loop))); - } + if (isset($parts['scheme']) && $parts['scheme'] === 'tcp') { + $executor = new TimeoutExecutor( + new TcpTransportExecutor($nameserver, $loop), + 5.0, + $loop + ); + } else { + $executor = new RetryExecutor( + new TimeoutExecutor( + new UdpTransportExecutor( + $nameserver, + $loop + ), + 5.0, + $loop + ) + ); + } - private function createCachedExecutor($namserver, LoopInterface $loop, CacheInterface $cache) - { - return new CachingExecutor($this->createRetryExecutor($namserver, $loop), $cache); + return new CoopExecutor($executor); } } diff --git a/tests/FunctionalResolverTest.php b/tests/FunctionalResolverTest.php index 5abc2d7a..d4d437cd 100644 --- a/tests/FunctionalResolverTest.php +++ b/tests/FunctionalResolverTest.php @@ -44,6 +44,34 @@ public function testResolveGoogleResolves() $this->loop->run(); } + /** + * @group internet + */ + public function testResolveGoogleOverUdpResolves() + { + $factory = new Factory($this->loop); + $this->resolver = $factory->create('udp://8.8.8.8', $this->loop); + + $promise = $this->resolver->resolve('google.com'); + $promise->then($this->expectCallableOnce(), $this->expectCallableNever()); + + $this->loop->run(); + } + + /** + * @group internet + */ + public function testResolveGoogleOverTcpResolves() + { + $factory = new Factory($this->loop); + $this->resolver = $factory->create('tcp://8.8.8.8', $this->loop); + + $promise = $this->resolver->resolve('google.com'); + $promise->then($this->expectCallableOnce(), $this->expectCallableNever()); + + $this->loop->run(); + } + /** * @group internet */ diff --git a/tests/Query/TcpTransportExecutorTest.php b/tests/Query/TcpTransportExecutorTest.php index deb3853f..9d1508ba 100644 --- a/tests/Query/TcpTransportExecutorTest.php +++ b/tests/Query/TcpTransportExecutorTest.php @@ -33,10 +33,30 @@ public function testCtorShouldAcceptNameserverAddresses($input, $expected) public static function provideDefaultPortProvider() { return array( - array('8.8.8.8', '8.8.8.8:53'), - array('1.2.3.4:5', '1.2.3.4:5'), - array('::1', '[::1]:53'), - array('[::1]:53', '[::1]:53') + array( + '8.8.8.8', + '8.8.8.8:53' + ), + array( + '1.2.3.4:5', + '1.2.3.4:5' + ), + array( + 'tcp://1.2.3.4', + '1.2.3.4:53' + ), + array( + 'tcp://1.2.3.4:53', + '1.2.3.4:53' + ), + array( + '::1', + '[::1]:53' + ), + array( + '[::1]:53', + '[::1]:53' + ) ); } @@ -60,6 +80,16 @@ public function testCtorShouldThrowWhenNameserverAddressContainsHostname() new TcpTransportExecutor('localhost', $loop); } + /** + * @expectedException InvalidArgumentException + */ + public function testCtorShouldThrowWhenNameserverSchemeIsInvalid() + { + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + + new TcpTransportExecutor('udp://1.2.3.4', $loop); + } + public function testQueryRejectsIfMessageExceedsMaximumMessageSize() { $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); diff --git a/tests/Query/UdpTransportExecutorTest.php b/tests/Query/UdpTransportExecutorTest.php index 6d9baa5d..44ff7f4e 100644 --- a/tests/Query/UdpTransportExecutorTest.php +++ b/tests/Query/UdpTransportExecutorTest.php @@ -33,12 +33,30 @@ public function testCtorShouldAcceptNameserverAddresses($input, $expected) public static function provideDefaultPortProvider() { return array( - array('8.8.8.8', 'udp://8.8.8.8:53'), - array('1.2.3.4:5', 'udp://1.2.3.4:5'), - array('localhost', 'udp://localhost:53'), - array('localhost:1234', 'udp://localhost:1234'), - array('::1', 'udp://[::1]:53'), - array('[::1]:53', 'udp://[::1]:53') + array( + '8.8.8.8', + 'udp://8.8.8.8:53' + ), + array( + '1.2.3.4:5', + 'udp://1.2.3.4:5' + ), + array( + 'udp://1.2.3.4', + 'udp://1.2.3.4:53' + ), + array( + 'udp://1.2.3.4:53', + 'udp://1.2.3.4:53' + ), + array( + '::1', + 'udp://[::1]:53' + ), + array( + '[::1]:53', + 'udp://[::1]:53' + ) ); } @@ -52,6 +70,26 @@ public function testCtorShouldThrowWhenNameserverAddressIsInvalid() new UdpTransportExecutor('///', $loop); } + /** + * @expectedException InvalidArgumentException + */ + public function testCtorShouldThrowWhenNameserverAddressContainsHostname() + { + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + + new UdpTransportExecutor('localhost', $loop); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testCtorShouldThrowWhenNameserverSchemeIsInvalid() + { + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + + new UdpTransportExecutor('tcp://1.2.3.4', $loop); + } + public function testQueryRejectsIfMessageExceedsUdpSize() { $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); diff --git a/tests/Resolver/FactoryTest.php b/tests/Resolver/FactoryTest.php index df736d83..b7027827 100644 --- a/tests/Resolver/FactoryTest.php +++ b/tests/Resolver/FactoryTest.php @@ -19,6 +19,100 @@ public function createShouldCreateResolver() $this->assertInstanceOf('React\Dns\Resolver\Resolver', $resolver); } + + /** @test */ + public function createWithoutSchemeShouldCreateResolverWithUdpExecutorStack() + { + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + + $factory = new Factory(); + $resolver = $factory->create('8.8.8.8:53', $loop); + + $this->assertInstanceOf('React\Dns\Resolver\Resolver', $resolver); + + $coopExecutor = $this->getResolverPrivateExecutor($resolver); + + $this->assertInstanceOf('React\Dns\Query\CoopExecutor', $coopExecutor); + + $ref = new \ReflectionProperty($coopExecutor, 'executor'); + $ref->setAccessible(true); + $retryExecutor = $ref->getValue($coopExecutor); + + $this->assertInstanceOf('React\Dns\Query\RetryExecutor', $retryExecutor); + + $ref = new \ReflectionProperty($retryExecutor, 'executor'); + $ref->setAccessible(true); + $timeoutExecutor = $ref->getValue($retryExecutor); + + $this->assertInstanceOf('React\Dns\Query\TimeoutExecutor', $timeoutExecutor); + + $ref = new \ReflectionProperty($timeoutExecutor, 'executor'); + $ref->setAccessible(true); + $udpExecutor = $ref->getValue($timeoutExecutor); + + $this->assertInstanceOf('React\Dns\Query\UdpTransportExecutor', $udpExecutor); + } + + /** @test */ + public function createWithUdpSchemeShouldCreateResolverWithUdpExecutorStack() + { + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + + $factory = new Factory(); + $resolver = $factory->create('udp://8.8.8.8:53', $loop); + + $this->assertInstanceOf('React\Dns\Resolver\Resolver', $resolver); + + $coopExecutor = $this->getResolverPrivateExecutor($resolver); + + $this->assertInstanceOf('React\Dns\Query\CoopExecutor', $coopExecutor); + + $ref = new \ReflectionProperty($coopExecutor, 'executor'); + $ref->setAccessible(true); + $retryExecutor = $ref->getValue($coopExecutor); + + $this->assertInstanceOf('React\Dns\Query\RetryExecutor', $retryExecutor); + + $ref = new \ReflectionProperty($retryExecutor, 'executor'); + $ref->setAccessible(true); + $timeoutExecutor = $ref->getValue($retryExecutor); + + $this->assertInstanceOf('React\Dns\Query\TimeoutExecutor', $timeoutExecutor); + + $ref = new \ReflectionProperty($timeoutExecutor, 'executor'); + $ref->setAccessible(true); + $udpExecutor = $ref->getValue($timeoutExecutor); + + $this->assertInstanceOf('React\Dns\Query\UdpTransportExecutor', $udpExecutor); + } + + /** @test */ + public function createWithTcpSchemeShouldCreateResolverWithTcpExecutorStack() + { + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + + $factory = new Factory(); + $resolver = $factory->create('tcp://8.8.8.8:53', $loop); + + $this->assertInstanceOf('React\Dns\Resolver\Resolver', $resolver); + + $coopExecutor = $this->getResolverPrivateExecutor($resolver); + + $this->assertInstanceOf('React\Dns\Query\CoopExecutor', $coopExecutor); + + $ref = new \ReflectionProperty($coopExecutor, 'executor'); + $ref->setAccessible(true); + $timeoutExecutor = $ref->getValue($coopExecutor); + + $this->assertInstanceOf('React\Dns\Query\TimeoutExecutor', $timeoutExecutor); + + $ref = new \ReflectionProperty($timeoutExecutor, 'executor'); + $ref->setAccessible(true); + $tcpExecutor = $ref->getValue($timeoutExecutor); + + $this->assertInstanceOf('React\Dns\Query\TcpTransportExecutor', $tcpExecutor); + } + /** * @test * @expectedException InvalidArgumentException