Merge branch '2.8' into 3.2
* 2.8: [HttpFoundation] Fix missing handling of for/host/proto info from "Forwarded" header [Validator] Add object handling of invalid constraints in Composite [WebProfilerBundle] Remove uneeded directive in the form collector styles Revert "bug #21841 [Console] Do not squash input changes made from console.command event (chalasr)" [HttpFoundation] Fix Request::getHost() when having several hosts in X_FORWARDED_HOST
This commit is contained in:
commit
04fcac74b1
@ -60,7 +60,6 @@
|
||||
}
|
||||
#tree-menu .empty {
|
||||
border: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
#tree-details-container {
|
||||
|
@ -839,10 +839,6 @@ class Application
|
||||
// ignore invalid options/arguments for now, to allow the event listeners to customize the InputDefinition
|
||||
}
|
||||
|
||||
// don't bind the input again as it would override any input argument/option set from the command event in
|
||||
// addition to being useless
|
||||
$command->setInputBound(true);
|
||||
|
||||
$event = new ConsoleCommandEvent($command, $input, $output);
|
||||
$this->dispatcher->dispatch(ConsoleEvents::COMMAND, $event);
|
||||
|
||||
|
@ -40,7 +40,6 @@ class Command
|
||||
private $ignoreValidationErrors = false;
|
||||
private $applicationDefinitionMerged = false;
|
||||
private $applicationDefinitionMergedWithArgs = false;
|
||||
private $inputBound = false;
|
||||
private $code;
|
||||
private $synopsis = array();
|
||||
private $usages = array();
|
||||
@ -217,13 +216,11 @@ class Command
|
||||
$this->mergeApplicationDefinition();
|
||||
|
||||
// bind the input against the command specific arguments/options
|
||||
if (!$this->inputBound) {
|
||||
try {
|
||||
$input->bind($this->definition);
|
||||
} catch (ExceptionInterface $e) {
|
||||
if (!$this->ignoreValidationErrors) {
|
||||
throw $e;
|
||||
}
|
||||
try {
|
||||
$input->bind($this->definition);
|
||||
} catch (ExceptionInterface $e) {
|
||||
if (!$this->ignoreValidationErrors) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
@ -652,14 +649,6 @@ class Command
|
||||
return $this->helperSet->get($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function setInputBound($inputBound)
|
||||
{
|
||||
$this->inputBound = $inputBound;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a command name.
|
||||
*
|
||||
|
@ -1106,31 +1106,6 @@ class ApplicationTest extends TestCase
|
||||
$this->assertEquals('some test value', $extraValue);
|
||||
}
|
||||
|
||||
public function testUpdateInputFromConsoleCommandEvent()
|
||||
{
|
||||
$dispatcher = $this->getDispatcher();
|
||||
$dispatcher->addListener('console.command', function (ConsoleCommandEvent $event) {
|
||||
$event->getInput()->setOption('extra', 'overriden');
|
||||
});
|
||||
|
||||
$application = new Application();
|
||||
$application->setDispatcher($dispatcher);
|
||||
$application->setAutoExit(false);
|
||||
|
||||
$application
|
||||
->register('foo')
|
||||
->addOption('extra', null, InputOption::VALUE_REQUIRED)
|
||||
->setCode(function (InputInterface $input, OutputInterface $output) {
|
||||
$output->write('foo.');
|
||||
})
|
||||
;
|
||||
|
||||
$tester = new ApplicationTester($application);
|
||||
$tester->run(array('command' => 'foo', '--extra' => 'original'));
|
||||
|
||||
$this->assertEquals('overriden', $tester->getInput()->getOption('extra'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group legacy
|
||||
*/
|
||||
|
@ -206,6 +206,15 @@ class Request
|
||||
|
||||
protected static $requestFactory;
|
||||
|
||||
private $isForwardedValid = true;
|
||||
|
||||
private static $forwardedParams = array(
|
||||
self::HEADER_CLIENT_IP => 'for',
|
||||
self::HEADER_CLIENT_HOST => 'host',
|
||||
self::HEADER_CLIENT_PROTO => 'proto',
|
||||
self::HEADER_CLIENT_PORT => 'host',
|
||||
);
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
@ -793,41 +802,13 @@ class Request
|
||||
*/
|
||||
public function getClientIps()
|
||||
{
|
||||
$clientIps = array();
|
||||
$ip = $this->server->get('REMOTE_ADDR');
|
||||
|
||||
if (!$this->isFromTrustedProxy()) {
|
||||
return array($ip);
|
||||
}
|
||||
|
||||
$hasTrustedForwardedHeader = self::$trustedHeaders[self::HEADER_FORWARDED] && $this->headers->has(self::$trustedHeaders[self::HEADER_FORWARDED]);
|
||||
$hasTrustedClientIpHeader = self::$trustedHeaders[self::HEADER_CLIENT_IP] && $this->headers->has(self::$trustedHeaders[self::HEADER_CLIENT_IP]);
|
||||
|
||||
if ($hasTrustedForwardedHeader) {
|
||||
$forwardedHeader = $this->headers->get(self::$trustedHeaders[self::HEADER_FORWARDED]);
|
||||
preg_match_all('{(for)=("?\[?)([a-z0-9\.:_\-/]*)}', $forwardedHeader, $matches);
|
||||
$forwardedClientIps = $matches[3];
|
||||
|
||||
$forwardedClientIps = $this->normalizeAndFilterClientIps($forwardedClientIps, $ip);
|
||||
$clientIps = $forwardedClientIps;
|
||||
}
|
||||
|
||||
if ($hasTrustedClientIpHeader) {
|
||||
$xForwardedForClientIps = array_map('trim', explode(',', $this->headers->get(self::$trustedHeaders[self::HEADER_CLIENT_IP])));
|
||||
|
||||
$xForwardedForClientIps = $this->normalizeAndFilterClientIps($xForwardedForClientIps, $ip);
|
||||
$clientIps = $xForwardedForClientIps;
|
||||
}
|
||||
|
||||
if ($hasTrustedForwardedHeader && $hasTrustedClientIpHeader && $forwardedClientIps !== $xForwardedForClientIps) {
|
||||
throw new ConflictingHeadersException('The request has both a trusted Forwarded header and a trusted Client IP header, conflicting with each other with regards to the originating IP addresses of the request. This is the result of a misconfiguration. You should either configure your proxy only to send one of these headers, or configure Symfony to distrust one of them.');
|
||||
}
|
||||
|
||||
if (!$hasTrustedForwardedHeader && !$hasTrustedClientIpHeader) {
|
||||
return $this->normalizeAndFilterClientIps(array(), $ip);
|
||||
}
|
||||
|
||||
return $clientIps;
|
||||
return $this->getTrustedValues(self::HEADER_CLIENT_IP, $ip) ?: array($ip);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -953,31 +934,25 @@ class Request
|
||||
*/
|
||||
public function getPort()
|
||||
{
|
||||
if ($this->isFromTrustedProxy()) {
|
||||
if (self::$trustedHeaders[self::HEADER_CLIENT_PORT] && $port = $this->headers->get(self::$trustedHeaders[self::HEADER_CLIENT_PORT])) {
|
||||
return $port;
|
||||
}
|
||||
|
||||
if (self::$trustedHeaders[self::HEADER_CLIENT_PROTO] && 'https' === $this->headers->get(self::$trustedHeaders[self::HEADER_CLIENT_PROTO], 'http')) {
|
||||
return 443;
|
||||
}
|
||||
if ($this->isFromTrustedProxy() && $host = $this->getTrustedValues(self::HEADER_CLIENT_PORT)) {
|
||||
$host = $host[0];
|
||||
} elseif ($this->isFromTrustedProxy() && $host = $this->getTrustedValues(self::HEADER_CLIENT_HOST)) {
|
||||
$host = $host[0];
|
||||
} elseif (!$host = $this->headers->get('HOST')) {
|
||||
return $this->server->get('SERVER_PORT');
|
||||
}
|
||||
|
||||
if ($host = $this->headers->get('HOST')) {
|
||||
if ($host[0] === '[') {
|
||||
$pos = strpos($host, ':', strrpos($host, ']'));
|
||||
} else {
|
||||
$pos = strrpos($host, ':');
|
||||
}
|
||||
|
||||
if (false !== $pos) {
|
||||
return (int) substr($host, $pos + 1);
|
||||
}
|
||||
|
||||
return 'https' === $this->getScheme() ? 443 : 80;
|
||||
if ($host[0] === '[') {
|
||||
$pos = strpos($host, ':', strrpos($host, ']'));
|
||||
} else {
|
||||
$pos = strrpos($host, ':');
|
||||
}
|
||||
|
||||
return $this->server->get('SERVER_PORT');
|
||||
if (false !== $pos) {
|
||||
return (int) substr($host, $pos + 1);
|
||||
}
|
||||
|
||||
return 'https' === $this->getScheme() ? 443 : 80;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1177,8 +1152,8 @@ class Request
|
||||
*/
|
||||
public function isSecure()
|
||||
{
|
||||
if ($this->isFromTrustedProxy() && self::$trustedHeaders[self::HEADER_CLIENT_PROTO] && $proto = $this->headers->get(self::$trustedHeaders[self::HEADER_CLIENT_PROTO])) {
|
||||
return in_array(strtolower(current(explode(',', $proto))), array('https', 'on', 'ssl', '1'));
|
||||
if ($this->isFromTrustedProxy() && $proto = $this->getTrustedValues(self::HEADER_CLIENT_PROTO)) {
|
||||
return in_array(strtolower($proto[0]), array('https', 'on', 'ssl', '1'), true);
|
||||
}
|
||||
|
||||
$https = $this->server->get('HTTPS');
|
||||
@ -1203,10 +1178,8 @@ class Request
|
||||
*/
|
||||
public function getHost()
|
||||
{
|
||||
if ($this->isFromTrustedProxy() && self::$trustedHeaders[self::HEADER_CLIENT_HOST] && $host = $this->headers->get(self::$trustedHeaders[self::HEADER_CLIENT_HOST])) {
|
||||
$elements = explode(',', $host);
|
||||
|
||||
$host = $elements[count($elements) - 1];
|
||||
if ($this->isFromTrustedProxy() && $host = $this->getTrustedValues(self::HEADER_CLIENT_HOST)) {
|
||||
$host = $host[0];
|
||||
} elseif (!$host = $this->headers->get('HOST')) {
|
||||
if (!$host = $this->server->get('SERVER_NAME')) {
|
||||
$host = $this->server->get('SERVER_ADDR', '');
|
||||
@ -1977,8 +1950,48 @@ class Request
|
||||
return self::$trustedProxies && IpUtils::checkIp($this->server->get('REMOTE_ADDR'), self::$trustedProxies);
|
||||
}
|
||||
|
||||
private function getTrustedValues($type, $ip = null)
|
||||
{
|
||||
$clientValues = array();
|
||||
$forwardedValues = array();
|
||||
|
||||
if (self::$trustedHeaders[$type] && $this->headers->has(self::$trustedHeaders[$type])) {
|
||||
foreach (explode(',', $this->headers->get(self::$trustedHeaders[$type])) as $v) {
|
||||
$clientValues[] = (self::HEADER_CLIENT_PORT === $type ? '0.0.0.0:' : '').trim($v);
|
||||
}
|
||||
}
|
||||
|
||||
if (self::$trustedHeaders[self::HEADER_FORWARDED] && $this->headers->has(self::$trustedHeaders[self::HEADER_FORWARDED])) {
|
||||
$forwardedValues = $this->headers->get(self::$trustedHeaders[self::HEADER_FORWARDED]);
|
||||
$forwardedValues = preg_match_all(sprintf('{(?:%s)=(?:"?\[?)([a-zA-Z0-9\.:_\-/]*+)}', self::$forwardedParams[$type]), $forwardedValues, $matches) ? $matches[1] : array();
|
||||
}
|
||||
|
||||
if (null !== $ip) {
|
||||
$clientValues = $this->normalizeAndFilterClientIps($clientValues, $ip);
|
||||
$forwardedValues = $this->normalizeAndFilterClientIps($forwardedValues, $ip);
|
||||
}
|
||||
|
||||
if ($forwardedValues === $clientValues || !$clientValues) {
|
||||
return $forwardedValues;
|
||||
}
|
||||
|
||||
if (!$forwardedValues) {
|
||||
return $clientValues;
|
||||
}
|
||||
|
||||
if (!$this->isForwardedValid) {
|
||||
return null !== $ip ? array('0.0.0.0', $ip) : array();
|
||||
}
|
||||
$this->isForwardedValid = false;
|
||||
|
||||
throw new ConflictingHeadersException(sprintf('The request has both a trusted "%s" header and a trusted "%s" header, conflicting with each other. You should either configure your proxy to remove one of them, or configure your project to distrust the offending one.', self::$trustedHeaders[self::HEADER_FORWARDED], self::$trustedHeaders[$type]));
|
||||
}
|
||||
|
||||
private function normalizeAndFilterClientIps(array $clientIps, $ip)
|
||||
{
|
||||
if (!$clientIps) {
|
||||
return array();
|
||||
}
|
||||
$clientIps[] = $ip; // Complete the IP chain with the IP the request actually came from
|
||||
$firstTrustedIp = null;
|
||||
|
||||
|
@ -1670,12 +1670,12 @@ class RequestTest extends TestCase
|
||||
return $request;
|
||||
}
|
||||
|
||||
public function testTrustedProxies()
|
||||
public function testTrustedProxiesXForwardedFor()
|
||||
{
|
||||
$request = Request::create('http://example.com/');
|
||||
$request->server->set('REMOTE_ADDR', '3.3.3.3');
|
||||
$request->headers->set('X_FORWARDED_FOR', '1.1.1.1, 2.2.2.2');
|
||||
$request->headers->set('X_FORWARDED_HOST', 'foo.example.com, real.example.com:8080');
|
||||
$request->headers->set('X_FORWARDED_HOST', 'foo.example.com:1234, real.example.com:8080');
|
||||
$request->headers->set('X_FORWARDED_PROTO', 'https');
|
||||
$request->headers->set('X_FORWARDED_PORT', 443);
|
||||
$request->headers->set('X_MY_FOR', '3.3.3.3, 4.4.4.4');
|
||||
@ -1706,7 +1706,7 @@ class RequestTest extends TestCase
|
||||
// trusted proxy via setTrustedProxies()
|
||||
Request::setTrustedProxies(array('3.3.3.3', '2.2.2.2'));
|
||||
$this->assertEquals('1.1.1.1', $request->getClientIp());
|
||||
$this->assertEquals('real.example.com', $request->getHost());
|
||||
$this->assertEquals('foo.example.com', $request->getHost());
|
||||
$this->assertEquals(443, $request->getPort());
|
||||
$this->assertTrue($request->isSecure());
|
||||
|
||||
@ -1753,6 +1753,55 @@ class RequestTest extends TestCase
|
||||
Request::setTrustedHeaderName(Request::HEADER_CLIENT_PROTO, 'X_FORWARDED_PROTO');
|
||||
}
|
||||
|
||||
public function testTrustedProxiesForwarded()
|
||||
{
|
||||
$request = Request::create('http://example.com/');
|
||||
$request->server->set('REMOTE_ADDR', '3.3.3.3');
|
||||
$request->headers->set('FORWARDED', 'for=1.1.1.1, host=foo.example.com:8080, proto=https, for=2.2.2.2, host=real.example.com:8080');
|
||||
|
||||
// no trusted proxies
|
||||
$this->assertEquals('3.3.3.3', $request->getClientIp());
|
||||
$this->assertEquals('example.com', $request->getHost());
|
||||
$this->assertEquals(80, $request->getPort());
|
||||
$this->assertFalse($request->isSecure());
|
||||
|
||||
// disabling proxy trusting
|
||||
Request::setTrustedProxies(array());
|
||||
$this->assertEquals('3.3.3.3', $request->getClientIp());
|
||||
$this->assertEquals('example.com', $request->getHost());
|
||||
$this->assertEquals(80, $request->getPort());
|
||||
$this->assertFalse($request->isSecure());
|
||||
|
||||
// request is forwarded by a non-trusted proxy
|
||||
Request::setTrustedProxies(array('2.2.2.2'));
|
||||
$this->assertEquals('3.3.3.3', $request->getClientIp());
|
||||
$this->assertEquals('example.com', $request->getHost());
|
||||
$this->assertEquals(80, $request->getPort());
|
||||
$this->assertFalse($request->isSecure());
|
||||
|
||||
// trusted proxy via setTrustedProxies()
|
||||
Request::setTrustedProxies(array('3.3.3.3', '2.2.2.2'));
|
||||
$this->assertEquals('1.1.1.1', $request->getClientIp());
|
||||
$this->assertEquals('foo.example.com', $request->getHost());
|
||||
$this->assertEquals(8080, $request->getPort());
|
||||
$this->assertTrue($request->isSecure());
|
||||
|
||||
// trusted proxy via setTrustedProxies()
|
||||
Request::setTrustedProxies(array('3.3.3.4', '2.2.2.2'));
|
||||
$this->assertEquals('3.3.3.3', $request->getClientIp());
|
||||
$this->assertEquals('example.com', $request->getHost());
|
||||
$this->assertEquals(80, $request->getPort());
|
||||
$this->assertFalse($request->isSecure());
|
||||
|
||||
// check various X_FORWARDED_PROTO header values
|
||||
Request::setTrustedProxies(array('3.3.3.3', '2.2.2.2'));
|
||||
$request->headers->set('FORWARDED', 'proto=ssl');
|
||||
$this->assertTrue($request->isSecure());
|
||||
|
||||
$request->headers->set('FORWARDED', 'proto=https, proto=http');
|
||||
$this->assertTrue($request->isSecure());
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \InvalidArgumentException
|
||||
*/
|
||||
|
@ -32,7 +32,7 @@ class ValidateRequestListenerTest extends TestCase
|
||||
$request = new Request();
|
||||
$request->setTrustedProxies(array('1.1.1.1'));
|
||||
$request->server->set('REMOTE_ADDR', '1.1.1.1');
|
||||
$request->headers->set('FORWARDED', '2.2.2.2');
|
||||
$request->headers->set('FORWARDED', 'for=2.2.2.2');
|
||||
$request->headers->set('X_FORWARDED_FOR', '3.3.3.3');
|
||||
|
||||
$dispatcher->addListener(KernelEvents::REQUEST, array(new ValidateRequestListener(), 'onKernelRequest'));
|
||||
|
@ -311,7 +311,7 @@ class HttpKernelTest extends TestCase
|
||||
$request = new Request();
|
||||
$request->setTrustedProxies(array('1.1.1.1'));
|
||||
$request->server->set('REMOTE_ADDR', '1.1.1.1');
|
||||
$request->headers->set('FORWARDED', '2.2.2.2');
|
||||
$request->headers->set('FORWARDED', 'for=2.2.2.2');
|
||||
$request->headers->set('X_FORWARDED_FOR', '3.3.3.3');
|
||||
|
||||
$dispatcher = new EventDispatcher();
|
||||
|
@ -67,6 +67,10 @@ abstract class Composite extends Constraint
|
||||
|
||||
foreach ($nestedConstraints as $constraint) {
|
||||
if (!$constraint instanceof Constraint) {
|
||||
if (is_object($constraint)) {
|
||||
$constraint = get_class($constraint);
|
||||
}
|
||||
|
||||
throw new ConstraintDefinitionException(sprintf('The value %s is not an instance of Constraint in constraint %s', $constraint, get_class($this)));
|
||||
}
|
||||
|
||||
|
@ -125,6 +125,17 @@ class CompositeTest extends TestCase
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Validator\Exception\ConstraintDefinitionException
|
||||
*/
|
||||
public function testFailIfNoConstraintObject()
|
||||
{
|
||||
new ConcreteComposite(array(
|
||||
new NotNull(array('groups' => 'Default')),
|
||||
new \ArrayObject(),
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Validator\Exception\ConstraintDefinitionException
|
||||
*/
|
||||
|
Reference in New Issue
Block a user