merged branch vicb/security (PR #5988)
This PR was merged into the 2.0 branch.
Commits
-------
e12bd12
[HttpFoundation] Make host & methods really case insensitive in the RequestMacther
Discussion
----------
[HttpFoundation] Make host & methods really case insensitive in the Requ...
...estMacther
and backport changes from 2.2
Details:
- does not take case into account when checking the host (the `Request` always returns a lowercase value) to protect against user typo,
- makes the constructor case proof by invoking setters rather than setting properties directly (you could then add un unreachable method i.e; `get`)
Please propagate to 2.1/2.2 if accpeted. Thanks.
This commit is contained in:
commit
d060fd4953
@ -20,19 +20,49 @@ namespace Symfony\Component\HttpFoundation;
|
|||||||
*/
|
*/
|
||||||
class RequestMatcher implements RequestMatcherInterface
|
class RequestMatcher implements RequestMatcherInterface
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
private $path;
|
private $path;
|
||||||
private $host;
|
|
||||||
private $methods;
|
|
||||||
private $ip;
|
|
||||||
private $attributes;
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $host;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $methods = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $ip;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attributes.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $attributes = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string|null $path
|
||||||
|
* @param string|null $host
|
||||||
|
* @param string|string[]|null $methods
|
||||||
|
* @param string|null $ip
|
||||||
|
* @param array $attributes
|
||||||
|
*/
|
||||||
public function __construct($path = null, $host = null, $methods = null, $ip = null, array $attributes = array())
|
public function __construct($path = null, $host = null, $methods = null, $ip = null, array $attributes = array())
|
||||||
{
|
{
|
||||||
$this->path = $path;
|
$this->matchPath($path);
|
||||||
$this->host = $host;
|
$this->matchHost($host);
|
||||||
$this->methods = $methods;
|
$this->matchMethod($methods);
|
||||||
$this->ip = $ip;
|
$this->matchIp($ip);
|
||||||
$this->attributes = $attributes;
|
foreach ($attributes as $k => $v) {
|
||||||
|
$this->matchAttribute($k, $v);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -68,11 +98,11 @@ class RequestMatcher implements RequestMatcherInterface
|
|||||||
/**
|
/**
|
||||||
* Adds a check for the HTTP method.
|
* Adds a check for the HTTP method.
|
||||||
*
|
*
|
||||||
* @param string|array $method An HTTP method or an array of HTTP methods
|
* @param string|string[]|null $method An HTTP method or an array of HTTP methods
|
||||||
*/
|
*/
|
||||||
public function matchMethod($method)
|
public function matchMethod($method)
|
||||||
{
|
{
|
||||||
$this->methods = array_map('strtoupper', is_array($method) ? $method : array($method));
|
$this->methods = array_map('strtoupper', (array) $method);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -93,7 +123,7 @@ class RequestMatcher implements RequestMatcherInterface
|
|||||||
*/
|
*/
|
||||||
public function matches(Request $request)
|
public function matches(Request $request)
|
||||||
{
|
{
|
||||||
if (null !== $this->methods && !in_array($request->getMethod(), $this->methods)) {
|
if ($this->methods && !in_array($request->getMethod(), $this->methods)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,7 +141,7 @@ class RequestMatcher implements RequestMatcherInterface
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (null !== $this->host && !preg_match('#'.str_replace('#', '\\#', $this->host).'#', $request->getHost())) {
|
if (null !== $this->host && !preg_match('#'.str_replace('#', '\\#', $this->host).'#i', $request->getHost())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,6 +152,14 @@ class RequestMatcher implements RequestMatcherInterface
|
|||||||
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)
|
protected function checkIp($requestIp, $ip)
|
||||||
{
|
{
|
||||||
// IPv6 address
|
// IPv6 address
|
||||||
@ -132,6 +170,14 @@ class RequestMatcher implements RequestMatcherInterface
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates an IPv4 address.
|
||||||
|
*
|
||||||
|
* @param string $requestIp
|
||||||
|
* @param string $ip
|
||||||
|
*
|
||||||
|
* @return boolean True valid, false if not.
|
||||||
|
*/
|
||||||
protected function checkIp4($requestIp, $ip)
|
protected function checkIp4($requestIp, $ip)
|
||||||
{
|
{
|
||||||
if (false !== strpos($ip, '/')) {
|
if (false !== strpos($ip, '/')) {
|
||||||
@ -149,8 +195,15 @@ class RequestMatcher implements RequestMatcherInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Validates an IPv6 address.
|
||||||
|
*
|
||||||
* @author David Soria Parra <dsp at php dot net>
|
* @author David Soria Parra <dsp at php dot net>
|
||||||
* @see https://github.com/dsp/v6tools
|
* @see https://github.com/dsp/v6tools
|
||||||
|
*
|
||||||
|
* @param string $requestIp
|
||||||
|
* @param string $ip
|
||||||
|
*
|
||||||
|
* @return boolean True valid, false if not.
|
||||||
*/
|
*/
|
||||||
protected function checkIp6($requestIp, $ip)
|
protected function checkIp6($requestIp, $ip)
|
||||||
{
|
{
|
||||||
@ -158,16 +211,25 @@ class RequestMatcher implements RequestMatcherInterface
|
|||||||
throw new \RuntimeException('Unable to check Ipv6. Check that PHP was not compiled with option "disable-ipv6".');
|
throw new \RuntimeException('Unable to check Ipv6. Check that PHP was not compiled with option "disable-ipv6".');
|
||||||
}
|
}
|
||||||
|
|
||||||
list($address, $netmask) = explode('/', $ip, 2);
|
if (false !== strpos($ip, '/')) {
|
||||||
|
list($address, $netmask) = explode('/', $ip, 2);
|
||||||
|
|
||||||
$bytes_addr = unpack("n*", inet_pton($address));
|
if ($netmask < 1 || $netmask > 128) {
|
||||||
$bytes_test = unpack("n*", inet_pton($requestIp));
|
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++) {
|
for ($i = 1, $ceil = ceil($netmask / 16); $i <= $ceil; $i++) {
|
||||||
$left = $netmask - 16 * ($i-1);
|
$left = $netmask - 16 * ($i-1);
|
||||||
$left = ($left <= 16) ? $left : 16;
|
$left = ($left <= 16) ? $left : 16;
|
||||||
$mask = ~(0xffff >> $left) & 0xffff;
|
$mask = ~(0xffff >> $left) & 0xffff;
|
||||||
if (($bytes_addr[$i] & $mask) != ($bytes_test[$i] & $mask)) {
|
if (($bytesAddr[$i] & $mask) != ($bytesTest[$i] & $mask)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -175,3 +237,4 @@ class RequestMatcher implements RequestMatcherInterface
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,6 +66,9 @@ class RequestMatcherTest extends \PHPUnit_Framework_TestCase
|
|||||||
return array(
|
return array(
|
||||||
array(true, '2a01:198:603:0:396e:4789:8e99:890f', '2a01:198:603:0::/65'),
|
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, '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'),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,38 +92,59 @@ class RequestMatcherTest extends \PHPUnit_Framework_TestCase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testMethod()
|
/**
|
||||||
|
* @dataProvider testMethodFixtures
|
||||||
|
*/
|
||||||
|
public function testMethod($requestMethod, $matcherMethod, $isMatch)
|
||||||
{
|
{
|
||||||
$matcher = new RequestMatcher();
|
$matcher = new RequestMatcher();
|
||||||
|
$matcher->matchMethod($matcherMethod);
|
||||||
|
$request = Request::create('', $requestMethod);
|
||||||
|
$this->assertSame($isMatch, $matcher->matches($request));
|
||||||
|
|
||||||
$matcher->matchMethod('get');
|
$matcher = new RequestMatcher(null, null, $matcherMethod);
|
||||||
$request = Request::create('', 'get');
|
$request = Request::create('', $requestMethod);
|
||||||
$this->assertTrue($matcher->matches($request));
|
$this->assertSame($isMatch, $matcher->matches($request));
|
||||||
|
|
||||||
$matcher->matchMethod('post');
|
|
||||||
$this->assertFalse($matcher->matches($request));
|
|
||||||
|
|
||||||
$matcher->matchMethod(array('get', 'post'));
|
|
||||||
$this->assertTrue($matcher->matches($request));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testHost()
|
public function testMethodFixtures()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
array('get', 'get', true),
|
||||||
|
array('get', array('get', 'post'), true),
|
||||||
|
array('get', 'post', false),
|
||||||
|
array('get', 'GET', true),
|
||||||
|
array('get', array('GET', 'POST'), true),
|
||||||
|
array('get', 'POST', false),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider testHostFixture
|
||||||
|
*/
|
||||||
|
public function testHost($pattern, $isMatch)
|
||||||
{
|
{
|
||||||
$matcher = new RequestMatcher();
|
$matcher = new RequestMatcher();
|
||||||
|
|
||||||
$request = Request::create('', 'get', array(), array(), array(), array('HTTP_HOST' => 'foo.example.com'));
|
$request = Request::create('', 'get', array(), array(), array(), array('HTTP_HOST' => 'foo.example.com'));
|
||||||
|
|
||||||
$matcher->matchHost('.*\.example\.com');
|
$matcher->matchHost($pattern);
|
||||||
$this->assertTrue($matcher->matches($request));
|
$this->assertSame($isMatch, $matcher->matches($request));
|
||||||
|
|
||||||
$matcher->matchHost('\.example\.com$');
|
$matcher= new RequestMatcher(null, $pattern);
|
||||||
$this->assertTrue($matcher->matches($request));
|
$this->assertSame($isMatch, $matcher->matches($request));
|
||||||
|
}
|
||||||
|
|
||||||
$matcher->matchHost('^.*\.example\.com$');
|
public function testHostFixture()
|
||||||
$this->assertTrue($matcher->matches($request));
|
{
|
||||||
|
return array(
|
||||||
$matcher->matchMethod('.*\.sensio\.com');
|
array('.*\.example\.com', true),
|
||||||
$this->assertFalse($matcher->matches($request));
|
array('\.example\.com$', true),
|
||||||
|
array('^.*\.example\.com$', true),
|
||||||
|
array('.*\.sensio\.com', false),
|
||||||
|
array('.*\.example\.COM', true),
|
||||||
|
array('\.example\.COM$', true),
|
||||||
|
array('^.*\.example\.COM$', true),
|
||||||
|
array('.*\.sensio\.COM', false), );
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testPath()
|
public function testPath()
|
||||||
@ -175,3 +199,4 @@ class RequestMatcherTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->assertFalse($matcher->matches($request));
|
$this->assertFalse($matcher->matches($request));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user