merged branch vicb/httputils (PR #6005)
This PR was squashed before being merged into the master branch (closes #6005).
Commits
-------
577ee80
[HttpFoundation] Move IP check methods to a HttpUtils class for reuse
Discussion
----------
[HttpFoundation] Move IP check methods to a HttpUtils class for reuse
---------------------------------------------------------------------------
by vicb at 2012-11-13T18:05:18Z
Thanks @stof ! (didn't get my copy paste error as PHP allow calling non static method w/o a warning).
---------------------------------------------------------------------------
by GromNaN at 2012-11-17T23:19:29Z
Having an `Utils` class with mixed functions doesn't seem to be a good practice. I think the class should be called something like `Symfony\Component\HttpFoundation\IpAddress`.
---------------------------------------------------------------------------
by vicb at 2012-11-27T09:37:20Z
@fabpot could this be merged if `HttpUtils` is renamed to `IpUtils` ?
---------------------------------------------------------------------------
by fabpot at 2012-12-06T13:35:28Z
Renaming the class to `IpUtils` is indeed a good idea.
---------------------------------------------------------------------------
by vicb at 2012-12-06T14:07:59Z
ready !
---------------------------------------------------------------------------
by fabpot at 2012-12-06T14:39:19Z
Can you add an entry in the CHANGELOG?
---------------------------------------------------------------------------
by vicb at 2012-12-06T14:53:09Z
done, thanks for the reminder !
This commit is contained in:
commit
0c6e145c0d
@ -4,6 +4,7 @@ CHANGELOG
|
|||||||
2.2.0
|
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)
|
* 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)
|
* 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
|
* Request::splitHttpAcceptHeader() method is deprecated and will be removed in 2.3
|
||||||
|
111
src/Symfony/Component/HttpFoundation/IpUtils.php
Normal file
111
src/Symfony/Component/HttpFoundation/IpUtils.php
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* 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 <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
class IpUtils
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* This class should not be instantiated
|
||||||
|
*/
|
||||||
|
private function __construct() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates an IPv4 or IPv6 address.
|
||||||
|
*
|
||||||
|
* @param string $requestIp
|
||||||
|
* @param string $ip
|
||||||
|
*
|
||||||
|
* @return boolean Whether the IP is valid
|
||||||
|
*/
|
||||||
|
public static function checkIp($requestIp, $ip)
|
||||||
|
{
|
||||||
|
if (false !== strpos($requestIp, ':')) {
|
||||||
|
return self::checkIp6($requestIp, $ip);
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::checkIp4($requestIp, $ip);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates an IPv4 address.
|
||||||
|
*
|
||||||
|
* @param string $requestIp
|
||||||
|
* @param string $ip
|
||||||
|
*
|
||||||
|
* @return boolean Whether the IP is valid
|
||||||
|
*/
|
||||||
|
public static 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 <dsp at php dot net>
|
||||||
|
* @see https://github.com/dsp/v6tools
|
||||||
|
*
|
||||||
|
* @param string $requestIp
|
||||||
|
* @param string $ip
|
||||||
|
*
|
||||||
|
* @return boolean Whether the IP is valid
|
||||||
|
*
|
||||||
|
* @throws \RuntimeException When IPV6 support is not enabled
|
||||||
|
*/
|
||||||
|
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".');
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
@ -143,96 +143,11 @@ class RequestMatcher implements RequestMatcherInterface
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (null !== $this->ip && !$this->checkIp($request->getClientIp(), $this->ip)) {
|
if (null !== $this->ip && !IpUtils::checkIp($request->getClientIp(), $this->ip)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
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 <dsp at php dot net>
|
|
||||||
* @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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
71
src/Symfony/Component/HttpFoundation/Tests/IpUtilsTest.php
Normal file
71
src/Symfony/Component/HttpFoundation/Tests/IpUtilsTest.php
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* 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\IpUtils;
|
||||||
|
|
||||||
|
class IpUtilsTest extends \PHPUnit_Framework_TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @dataProvider testIpv4Provider
|
||||||
|
*/
|
||||||
|
public function testIpv4($matches, $remoteAddr, $cidr)
|
||||||
|
{
|
||||||
|
$this->assertSame($matches, IpUtils::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, IpUtils::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".');
|
||||||
|
}
|
||||||
|
|
||||||
|
IpUtils::checkIp('2a01:198:603:0:396e:4789:8e99:890f', '2a01:198:603:0::/65');
|
||||||
|
}
|
||||||
|
}
|
@ -16,78 +16,6 @@ use Symfony\Component\HttpFoundation\Request;
|
|||||||
|
|
||||||
class RequestMatcherTest extends \PHPUnit_Framework_TestCase
|
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
|
* @dataProvider testMethodFixtures
|
||||||
*/
|
*/
|
||||||
@ -200,4 +128,3 @@ class RequestMatcherTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->assertFalse($matcher->matches($request));
|
$this->assertFalse($matcher->matches($request));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user