From c460f66315edee68998b2d1f37557d20120cda56 Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Tue, 13 Nov 2012 17:48:13 +0100 Subject: [PATCH 1/5] [HttpFoundation] Move ip check methods to a HttpUtils class for reuse --- .../Component/HttpFoundation/HttpUtils.php | 111 ++++++++++++++++++ .../HttpFoundation/RequestMatcher.php | 87 +------------- .../HttpFoundation/Tests/HttpUtilsTest.php | 71 +++++++++++ .../Tests/RequestMatcherTest.php | 73 ------------ 4 files changed, 183 insertions(+), 159 deletions(-) create mode 100644 src/Symfony/Component/HttpFoundation/HttpUtils.php create mode 100644 src/Symfony/Component/HttpFoundation/Tests/HttpUtilsTest.php diff --git a/src/Symfony/Component/HttpFoundation/HttpUtils.php b/src/Symfony/Component/HttpFoundation/HttpUtils.php new file mode 100644 index 0000000000000..a344dcec5e044 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/HttpUtils.php @@ -0,0 +1,111 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * Http utility functions. + * + * @author Fabien Potencier + */ +class HttpUtils +{ + /** + * This class should not be instantiated + */ + private function __construct() {} + + /** + * Validates an IPv4 or IPv6 address. + * + * @param string $requestIp + * @param string $ip + * + * @return boolean True valid, false if not. + */ + public function checkIp($requestIp, $ip) + { + if (false !== strpos($requestIp, ':')) { + return self::checkIp6($requestIp, $ip); + } else { + return self::checkIp4($requestIp, $ip); + } + } + + /** + * Validates an IPv4 address. + * + * @param string $requestIp + * @param string $ip + * + * @return boolean True valid, false if not. + */ + public function checkIp4($requestIp, $ip) + { + if (false !== strpos($ip, '/')) { + list($address, $netmask) = explode('/', $ip, 2); + + if ($netmask < 1 || $netmask > 32) { + return false; + } + } else { + $address = $ip; + $netmask = 32; + } + + return 0 === substr_compare(sprintf('%032b', ip2long($requestIp)), sprintf('%032b', ip2long($address)), 0, $netmask); + } + + /** + * Validates an IPv6 address. + * + * @author David Soria Parra + * @see https://github.com/dsp/v6tools + * + * @param string $requestIp + * @param string $ip + * + * @return boolean True valid, false if not. + * + * @throws RuntimeException When IPV6 support is not enabled + */ + public function checkIp6($requestIp, $ip) + { + if (!((extension_loaded('sockets') && defined('AF_INET6')) || @inet_pton('::1'))) { + throw new \RuntimeException('Unable to check Ipv6. Check that PHP was not compiled with option "disable-ipv6".'); + } + + if (false !== strpos($ip, '/')) { + list($address, $netmask) = explode('/', $ip, 2); + + if ($netmask < 1 || $netmask > 128) { + return false; + } + } else { + $address = $ip; + $netmask = 128; + } + + $bytesAddr = unpack("n*", inet_pton($address)); + $bytesTest = unpack("n*", inet_pton($requestIp)); + + for ($i = 1, $ceil = ceil($netmask / 16); $i <= $ceil; $i++) { + $left = $netmask - 16 * ($i-1); + $left = ($left <= 16) ? $left : 16; + $mask = ~(0xffff >> $left) & 0xffff; + if (($bytesAddr[$i] & $mask) != ($bytesTest[$i] & $mask)) { + return false; + } + } + + return true; + } +} diff --git a/src/Symfony/Component/HttpFoundation/RequestMatcher.php b/src/Symfony/Component/HttpFoundation/RequestMatcher.php index 7371d17242ba6..5c9aee57eff76 100644 --- a/src/Symfony/Component/HttpFoundation/RequestMatcher.php +++ b/src/Symfony/Component/HttpFoundation/RequestMatcher.php @@ -143,96 +143,11 @@ public function matches(Request $request) return false; } - if (null !== $this->ip && !$this->checkIp($request->getClientIp(), $this->ip)) { + if (null !== $this->ip && !HttpUtils::checkIp($request->getClientIp(), $this->ip)) { return false; } return true; } - - /** - * Validates an IP address. - * - * @param string $requestIp - * @param string $ip - * - * @return boolean True valid, false if not. - */ - protected function checkIp($requestIp, $ip) - { - // IPv6 address - if (false !== strpos($requestIp, ':')) { - return $this->checkIp6($requestIp, $ip); - } else { - return $this->checkIp4($requestIp, $ip); - } - } - - /** - * Validates an IPv4 address. - * - * @param string $requestIp - * @param string $ip - * - * @return boolean True valid, false if not. - */ - protected function checkIp4($requestIp, $ip) - { - if (false !== strpos($ip, '/')) { - list($address, $netmask) = explode('/', $ip, 2); - - if ($netmask < 1 || $netmask > 32) { - return false; - } - } else { - $address = $ip; - $netmask = 32; - } - - return 0 === substr_compare(sprintf('%032b', ip2long($requestIp)), sprintf('%032b', ip2long($address)), 0, $netmask); - } - - /** - * Validates an IPv6 address. - * - * @author David Soria Parra - * @see https://github.com/dsp/v6tools - * - * @param string $requestIp - * @param string $ip - * - * @return boolean True valid, false if not. - */ - protected function checkIp6($requestIp, $ip) - { - if (!((extension_loaded('sockets') && defined('AF_INET6')) || @inet_pton('::1'))) { - throw new \RuntimeException('Unable to check Ipv6. Check that PHP was not compiled with option "disable-ipv6".'); - } - - if (false !== strpos($ip, '/')) { - list($address, $netmask) = explode('/', $ip, 2); - - if ($netmask < 1 || $netmask > 128) { - return false; - } - } else { - $address = $ip; - $netmask = 128; - } - - $bytesAddr = unpack("n*", inet_pton($address)); - $bytesTest = unpack("n*", inet_pton($requestIp)); - - for ($i = 1, $ceil = ceil($netmask / 16); $i <= $ceil; $i++) { - $left = $netmask - 16 * ($i-1); - $left = ($left <= 16) ? $left : 16; - $mask = ~(0xffff >> $left) & 0xffff; - if (($bytesAddr[$i] & $mask) != ($bytesTest[$i] & $mask)) { - return false; - } - } - - return true; - } } diff --git a/src/Symfony/Component/HttpFoundation/Tests/HttpUtilsTest.php b/src/Symfony/Component/HttpFoundation/Tests/HttpUtilsTest.php new file mode 100644 index 0000000000000..088a27c3ce84c --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Tests/HttpUtilsTest.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests; + +use Symfony\Component\HttpFoundation\HttpUtils; + +class HttpUtilsTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider testIpv4Provider + */ + public function testIpv4($matches, $remoteAddr, $cidr) + { + $this->assertSame($matches, HttpUtils::checkIp($remoteAddr, $cidr)); + } + + public function testIpv4Provider() + { + return array( + array(true, '192.168.1.1', '192.168.1.1'), + array(true, '192.168.1.1', '192.168.1.1/1'), + array(true, '192.168.1.1', '192.168.1.0/24'), + array(false, '192.168.1.1', '1.2.3.4/1'), + array(false, '192.168.1.1', '192.168.1/33'), + ); + } + + /** + * @dataProvider testIpv6Provider + */ + public function testIpv6($matches, $remoteAddr, $cidr) + { + if (!defined('AF_INET6')) { + $this->markTestSkipped('Only works when PHP is compiled without the option "disable-ipv6".'); + } + + $this->assertSame($matches, HttpUtils::checkIp($remoteAddr, $cidr)); + } + + public function testIpv6Provider() + { + return array( + array(true, '2a01:198:603:0:396e:4789:8e99:890f', '2a01:198:603:0::/65'), + array(false, '2a00:198:603:0:396e:4789:8e99:890f', '2a01:198:603:0::/65'), + array(false, '2a01:198:603:0:396e:4789:8e99:890f', '::1'), + array(true, '0:0:0:0:0:0:0:1', '::1'), + array(false, '0:0:603:0:396e:4789:8e99:0001', '::1'), + ); + } + + /** + * @expectedException \RuntimeException + */ + public function testAnIpv6WithOptionDisabledIpv6() + { + if (defined('AF_INET6')) { + $this->markTestSkipped('Only works when PHP is compiled with the option "disable-ipv6".'); + } + + HttpUtils::checkIp('2a01:198:603:0:396e:4789:8e99:890f', '2a01:198:603:0::/65'); + } +} diff --git a/src/Symfony/Component/HttpFoundation/Tests/RequestMatcherTest.php b/src/Symfony/Component/HttpFoundation/Tests/RequestMatcherTest.php index 0e7b9eff662b5..0e1a0f5caf363 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/RequestMatcherTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/RequestMatcherTest.php @@ -16,78 +16,6 @@ class RequestMatcherTest extends \PHPUnit_Framework_TestCase { - /** - * @dataProvider testIpv4Provider - */ - public function testIpv4($matches, $remoteAddr, $cidr) - { - $request = Request::create('', 'get', array(), array(), array(), array('REMOTE_ADDR' => $remoteAddr)); - - $matcher = new RequestMatcher(); - $matcher->matchIp($cidr); - - $this->assertEquals($matches, $matcher->matches($request)); - } - - public function testIpv4Provider() - { - return array( - array(true, '192.168.1.1', '192.168.1.1'), - array(true, '192.168.1.1', '192.168.1.1/1'), - array(true, '192.168.1.1', '192.168.1.0/24'), - array(false, '192.168.1.1', '1.2.3.4/1'), - array(false, '192.168.1.1', '192.168.1/33'), - ); - } - - /** - * @dataProvider testIpv6Provider - */ - public function testIpv6($matches, $remoteAddr, $cidr) - { - if (!defined('AF_INET6')) { - $this->markTestSkipped('Only works when PHP is compiled without the option "disable-ipv6".'); - } - - $request = Request::create('', 'get', array(), array(), array(), array('REMOTE_ADDR' => $remoteAddr)); - - $matcher = new RequestMatcher(); - $matcher->matchIp($cidr); - - $this->assertEquals($matches, $matcher->matches($request)); - } - - public function testIpv6Provider() - { - return array( - array(true, '2a01:198:603:0:396e:4789:8e99:890f', '2a01:198:603:0::/65'), - array(false, '2a00:198:603:0:396e:4789:8e99:890f', '2a01:198:603:0::/65'), - array(false, '2a01:198:603:0:396e:4789:8e99:890f', '::1'), - array(true, '0:0:0:0:0:0:0:1', '::1'), - array(false, '0:0:603:0:396e:4789:8e99:0001', '::1'), - ); - } - - public function testAnIpv6WithOptionDisabledIpv6() - { - if (defined('AF_INET6')) { - $this->markTestSkipped('Only works when PHP is compiled with the option "disable-ipv6".'); - } - - $request = Request::create('', 'get', array(), array(), array(), array('REMOTE_ADDR' => '2a01:198:603:0:396e:4789:8e99:890f')); - - $matcher = new RequestMatcher(); - $matcher->matchIp('2a01:198:603:0::/65'); - - try { - $matcher->matches($request); - - $this->fail('An expected RuntimeException has not been raised.'); - } catch (\Exception $e) { - $this->assertInstanceOf('\RuntimeException', $e); - } - } - /** * @dataProvider testMethodFixtures */ @@ -200,4 +128,3 @@ public function testAttributes() $this->assertFalse($matcher->matches($request)); } } - From d78114226f6daa16fe1458a5c2bd1557bf8f2a0d Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Tue, 13 Nov 2012 19:03:33 +0100 Subject: [PATCH 2/5] [HttpUtils] stof review(tm) --- src/Symfony/Component/HttpFoundation/HttpUtils.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Symfony/Component/HttpFoundation/HttpUtils.php b/src/Symfony/Component/HttpFoundation/HttpUtils.php index a344dcec5e044..e8d818fead990 100644 --- a/src/Symfony/Component/HttpFoundation/HttpUtils.php +++ b/src/Symfony/Component/HttpFoundation/HttpUtils.php @@ -31,13 +31,13 @@ private function __construct() {} * * @return boolean True valid, false if not. */ - public function checkIp($requestIp, $ip) + public static function checkIp($requestIp, $ip) { if (false !== strpos($requestIp, ':')) { return self::checkIp6($requestIp, $ip); - } else { - return self::checkIp4($requestIp, $ip); } + + return self::checkIp4($requestIp, $ip); } /** @@ -48,7 +48,7 @@ public function checkIp($requestIp, $ip) * * @return boolean True valid, false if not. */ - public function checkIp4($requestIp, $ip) + public static function checkIp4($requestIp, $ip) { if (false !== strpos($ip, '/')) { list($address, $netmask) = explode('/', $ip, 2); @@ -77,7 +77,7 @@ public function checkIp4($requestIp, $ip) * * @throws RuntimeException When IPV6 support is not enabled */ - public function checkIp6($requestIp, $ip) + public static function checkIp6($requestIp, $ip) { if (!((extension_loaded('sockets') && defined('AF_INET6')) || @inet_pton('::1'))) { throw new \RuntimeException('Unable to check Ipv6. Check that PHP was not compiled with option "disable-ipv6".'); From 91809795abab0c4a1585638cf3295b38cd26e34c Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Thu, 6 Dec 2012 14:47:04 +0100 Subject: [PATCH 3/5] [IpUtils] renamed --- .../HttpFoundation/{HttpUtils.php => IpUtils.php} | 10 +++++----- .../Component/HttpFoundation/RequestMatcher.php | 2 +- .../Component/HttpFoundation/Tests/HttpUtilsTest.php | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) rename src/Symfony/Component/HttpFoundation/{HttpUtils.php => IpUtils.php} (92%) diff --git a/src/Symfony/Component/HttpFoundation/HttpUtils.php b/src/Symfony/Component/HttpFoundation/IpUtils.php similarity index 92% rename from src/Symfony/Component/HttpFoundation/HttpUtils.php rename to src/Symfony/Component/HttpFoundation/IpUtils.php index e8d818fead990..2e3e1aa746359 100644 --- a/src/Symfony/Component/HttpFoundation/HttpUtils.php +++ b/src/Symfony/Component/HttpFoundation/IpUtils.php @@ -16,7 +16,7 @@ * * @author Fabien Potencier */ -class HttpUtils +class IpUtils { /** * This class should not be instantiated @@ -29,7 +29,7 @@ private function __construct() {} * @param string $requestIp * @param string $ip * - * @return boolean True valid, false if not. + * @return boolean Whether the IP is valid */ public static function checkIp($requestIp, $ip) { @@ -46,7 +46,7 @@ public static function checkIp($requestIp, $ip) * @param string $requestIp * @param string $ip * - * @return boolean True valid, false if not. + * @return boolean Whether the IP is valid */ public static function checkIp4($requestIp, $ip) { @@ -73,9 +73,9 @@ public static function checkIp4($requestIp, $ip) * @param string $requestIp * @param string $ip * - * @return boolean True valid, false if not. + * @return boolean Whether the IP is valid * - * @throws RuntimeException When IPV6 support is not enabled + * @throws \RuntimeException When IPV6 support is not enabled */ public static function checkIp6($requestIp, $ip) { diff --git a/src/Symfony/Component/HttpFoundation/RequestMatcher.php b/src/Symfony/Component/HttpFoundation/RequestMatcher.php index 5c9aee57eff76..536cc7b075f5b 100644 --- a/src/Symfony/Component/HttpFoundation/RequestMatcher.php +++ b/src/Symfony/Component/HttpFoundation/RequestMatcher.php @@ -143,7 +143,7 @@ public function matches(Request $request) return false; } - if (null !== $this->ip && !HttpUtils::checkIp($request->getClientIp(), $this->ip)) { + if (null !== $this->ip && !IpUtils::checkIp($request->getClientIp(), $this->ip)) { return false; } diff --git a/src/Symfony/Component/HttpFoundation/Tests/HttpUtilsTest.php b/src/Symfony/Component/HttpFoundation/Tests/HttpUtilsTest.php index 088a27c3ce84c..881acbbbd1425 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/HttpUtilsTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/HttpUtilsTest.php @@ -11,7 +11,7 @@ namespace Symfony\Component\HttpFoundation\Tests; -use Symfony\Component\HttpFoundation\HttpUtils; +use Symfony\Component\HttpFoundation\IpUtils; class HttpUtilsTest extends \PHPUnit_Framework_TestCase { @@ -20,7 +20,7 @@ class HttpUtilsTest extends \PHPUnit_Framework_TestCase */ public function testIpv4($matches, $remoteAddr, $cidr) { - $this->assertSame($matches, HttpUtils::checkIp($remoteAddr, $cidr)); + $this->assertSame($matches, IpUtils::checkIp($remoteAddr, $cidr)); } public function testIpv4Provider() @@ -43,7 +43,7 @@ public function testIpv6($matches, $remoteAddr, $cidr) $this->markTestSkipped('Only works when PHP is compiled without the option "disable-ipv6".'); } - $this->assertSame($matches, HttpUtils::checkIp($remoteAddr, $cidr)); + $this->assertSame($matches, IpUtils::checkIp($remoteAddr, $cidr)); } public function testIpv6Provider() @@ -66,6 +66,6 @@ public function testAnIpv6WithOptionDisabledIpv6() $this->markTestSkipped('Only works when PHP is compiled with the option "disable-ipv6".'); } - HttpUtils::checkIp('2a01:198:603:0:396e:4789:8e99:890f', '2a01:198:603:0::/65'); + IpUtils::checkIp('2a01:198:603:0:396e:4789:8e99:890f', '2a01:198:603:0::/65'); } } From 152af8d2dc2d09e5e9981743f5abf9dd626ec982 Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Thu, 6 Dec 2012 14:54:40 +0100 Subject: [PATCH 4/5] [IpUtils] renamed the test --- .../HttpFoundation/Tests/{HttpUtilsTest.php => IpUtilsTest.php} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/Symfony/Component/HttpFoundation/Tests/{HttpUtilsTest.php => IpUtilsTest.php} (97%) diff --git a/src/Symfony/Component/HttpFoundation/Tests/HttpUtilsTest.php b/src/Symfony/Component/HttpFoundation/Tests/IpUtilsTest.php similarity index 97% rename from src/Symfony/Component/HttpFoundation/Tests/HttpUtilsTest.php rename to src/Symfony/Component/HttpFoundation/Tests/IpUtilsTest.php index 881acbbbd1425..5b94bd2bd01a0 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/HttpUtilsTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/IpUtilsTest.php @@ -13,7 +13,7 @@ use Symfony\Component\HttpFoundation\IpUtils; -class HttpUtilsTest extends \PHPUnit_Framework_TestCase +class IpUtilsTest extends \PHPUnit_Framework_TestCase { /** * @dataProvider testIpv4Provider From 550d45d91e3052f617b6c2ad0f900049297fc3f6 Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Thu, 6 Dec 2012 15:49:55 +0100 Subject: [PATCH 5/5] [HttpFoundation] Update the changelog --- src/Symfony/Component/HttpFoundation/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Symfony/Component/HttpFoundation/CHANGELOG.md b/src/Symfony/Component/HttpFoundation/CHANGELOG.md index 4b7d0c64ace59..13ed8dd7c96c6 100644 --- a/src/Symfony/Component/HttpFoundation/CHANGELOG.md +++ b/src/Symfony/Component/HttpFoundation/CHANGELOG.md @@ -4,6 +4,7 @@ CHANGELOG 2.2.0 ----- + * added a IpUtils class to check if an IP belongs to a CIDR * added Request::getRealMethod() to get the "real" HTTP method (getMethod() returns the "intended" HTTP method) * disabled _method request parameter support by default (call Request::enableHttpMethodParameterOverride() to enable it) * Request::splitHttpAcceptHeader() method is deprecated and will be removed in 2.3