Skip to content

Commit 04fcac7

Browse files
committed
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 symfony#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
2 parents 7d76227 + 8371dea commit 04fcac7

File tree

10 files changed

+142
-106
lines changed

10 files changed

+142
-106
lines changed

src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@
6060
}
6161
#tree-menu .empty {
6262
border: 0;
63-
margin: 0;
6463
padding: 0;
6564
}
6665
#tree-details-container {

src/Symfony/Component/Console/Application.php

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -839,10 +839,6 @@ protected function doRunCommand(Command $command, InputInterface $input, OutputI
839839
// ignore invalid options/arguments for now, to allow the event listeners to customize the InputDefinition
840840
}
841841

842-
// don't bind the input again as it would override any input argument/option set from the command event in
843-
// addition to being useless
844-
$command->setInputBound(true);
845-
846842
$event = new ConsoleCommandEvent($command, $input, $output);
847843
$this->dispatcher->dispatch(ConsoleEvents::COMMAND, $event);
848844

src/Symfony/Component/Console/Command/Command.php

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ class Command
4040
private $ignoreValidationErrors = false;
4141
private $applicationDefinitionMerged = false;
4242
private $applicationDefinitionMergedWithArgs = false;
43-
private $inputBound = false;
4443
private $code;
4544
private $synopsis = array();
4645
private $usages = array();
@@ -217,13 +216,11 @@ public function run(InputInterface $input, OutputInterface $output)
217216
$this->mergeApplicationDefinition();
218217

219218
// bind the input against the command specific arguments/options
220-
if (!$this->inputBound) {
221-
try {
222-
$input->bind($this->definition);
223-
} catch (ExceptionInterface $e) {
224-
if (!$this->ignoreValidationErrors) {
225-
throw $e;
226-
}
219+
try {
220+
$input->bind($this->definition);
221+
} catch (ExceptionInterface $e) {
222+
if (!$this->ignoreValidationErrors) {
223+
throw $e;
227224
}
228225
}
229226

@@ -652,14 +649,6 @@ public function getHelper($name)
652649
return $this->helperSet->get($name);
653650
}
654651

655-
/**
656-
* @internal
657-
*/
658-
public function setInputBound($inputBound)
659-
{
660-
$this->inputBound = $inputBound;
661-
}
662-
663652
/**
664653
* Validates a command name.
665654
*

src/Symfony/Component/Console/Tests/ApplicationTest.php

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1106,31 +1106,6 @@ public function testRunWithDispatcherAddingInputOptions()
11061106
$this->assertEquals('some test value', $extraValue);
11071107
}
11081108

1109-
public function testUpdateInputFromConsoleCommandEvent()
1110-
{
1111-
$dispatcher = $this->getDispatcher();
1112-
$dispatcher->addListener('console.command', function (ConsoleCommandEvent $event) {
1113-
$event->getInput()->setOption('extra', 'overriden');
1114-
});
1115-
1116-
$application = new Application();
1117-
$application->setDispatcher($dispatcher);
1118-
$application->setAutoExit(false);
1119-
1120-
$application
1121-
->register('foo')
1122-
->addOption('extra', null, InputOption::VALUE_REQUIRED)
1123-
->setCode(function (InputInterface $input, OutputInterface $output) {
1124-
$output->write('foo.');
1125-
})
1126-
;
1127-
1128-
$tester = new ApplicationTester($application);
1129-
$tester->run(array('command' => 'foo', '--extra' => 'original'));
1130-
1131-
$this->assertEquals('overriden', $tester->getInput()->getOption('extra'));
1132-
}
1133-
11341109
/**
11351110
* @group legacy
11361111
*/

src/Symfony/Component/HttpFoundation/Request.php

Lines changed: 68 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,15 @@ class Request
206206

207207
protected static $requestFactory;
208208

209+
private $isForwardedValid = true;
210+
211+
private static $forwardedParams = array(
212+
self::HEADER_CLIENT_IP => 'for',
213+
self::HEADER_CLIENT_HOST => 'host',
214+
self::HEADER_CLIENT_PROTO => 'proto',
215+
self::HEADER_CLIENT_PORT => 'host',
216+
);
217+
209218
/**
210219
* Constructor.
211220
*
@@ -793,41 +802,13 @@ public function setSession(SessionInterface $session)
793802
*/
794803
public function getClientIps()
795804
{
796-
$clientIps = array();
797805
$ip = $this->server->get('REMOTE_ADDR');
798806

799807
if (!$this->isFromTrustedProxy()) {
800808
return array($ip);
801809
}
802810

803-
$hasTrustedForwardedHeader = self::$trustedHeaders[self::HEADER_FORWARDED] && $this->headers->has(self::$trustedHeaders[self::HEADER_FORWARDED]);
804-
$hasTrustedClientIpHeader = self::$trustedHeaders[self::HEADER_CLIENT_IP] && $this->headers->has(self::$trustedHeaders[self::HEADER_CLIENT_IP]);
805-
806-
if ($hasTrustedForwardedHeader) {
807-
$forwardedHeader = $this->headers->get(self::$trustedHeaders[self::HEADER_FORWARDED]);
808-
preg_match_all('{(for)=("?\[?)([a-z0-9\.:_\-/]*)}', $forwardedHeader, $matches);
809-
$forwardedClientIps = $matches[3];
810-
811-
$forwardedClientIps = $this->normalizeAndFilterClientIps($forwardedClientIps, $ip);
812-
$clientIps = $forwardedClientIps;
813-
}
814-
815-
if ($hasTrustedClientIpHeader) {
816-
$xForwardedForClientIps = array_map('trim', explode(',', $this->headers->get(self::$trustedHeaders[self::HEADER_CLIENT_IP])));
817-
818-
$xForwardedForClientIps = $this->normalizeAndFilterClientIps($xForwardedForClientIps, $ip);
819-
$clientIps = $xForwardedForClientIps;
820-
}
821-
822-
if ($hasTrustedForwardedHeader && $hasTrustedClientIpHeader && $forwardedClientIps !== $xForwardedForClientIps) {
823-
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.');
824-
}
825-
826-
if (!$hasTrustedForwardedHeader && !$hasTrustedClientIpHeader) {
827-
return $this->normalizeAndFilterClientIps(array(), $ip);
828-
}
829-
830-
return $clientIps;
811+
return $this->getTrustedValues(self::HEADER_CLIENT_IP, $ip) ?: array($ip);
831812
}
832813

833814
/**
@@ -953,31 +934,25 @@ public function getScheme()
953934
*/
954935
public function getPort()
955936
{
956-
if ($this->isFromTrustedProxy()) {
957-
if (self::$trustedHeaders[self::HEADER_CLIENT_PORT] && $port = $this->headers->get(self::$trustedHeaders[self::HEADER_CLIENT_PORT])) {
958-
return $port;
959-
}
960-
961-
if (self::$trustedHeaders[self::HEADER_CLIENT_PROTO] && 'https' === $this->headers->get(self::$trustedHeaders[self::HEADER_CLIENT_PROTO], 'http')) {
962-
return 443;
963-
}
937+
if ($this->isFromTrustedProxy() && $host = $this->getTrustedValues(self::HEADER_CLIENT_PORT)) {
938+
$host = $host[0];
939+
} elseif ($this->isFromTrustedProxy() && $host = $this->getTrustedValues(self::HEADER_CLIENT_HOST)) {
940+
$host = $host[0];
941+
} elseif (!$host = $this->headers->get('HOST')) {
942+
return $this->server->get('SERVER_PORT');
964943
}
965944

966-
if ($host = $this->headers->get('HOST')) {
967-
if ($host[0] === '[') {
968-
$pos = strpos($host, ':', strrpos($host, ']'));
969-
} else {
970-
$pos = strrpos($host, ':');
971-
}
972-
973-
if (false !== $pos) {
974-
return (int) substr($host, $pos + 1);
975-
}
945+
if ($host[0] === '[') {
946+
$pos = strpos($host, ':', strrpos($host, ']'));
947+
} else {
948+
$pos = strrpos($host, ':');
949+
}
976950

977-
return 'https' === $this->getScheme() ? 443 : 80;
951+
if (false !== $pos) {
952+
return (int) substr($host, $pos + 1);
978953
}
979954

980-
return $this->server->get('SERVER_PORT');
955+
return 'https' === $this->getScheme() ? 443 : 80;
981956
}
982957

983958
/**
@@ -1177,8 +1152,8 @@ public function getQueryString()
11771152
*/
11781153
public function isSecure()
11791154
{
1180-
if ($this->isFromTrustedProxy() && self::$trustedHeaders[self::HEADER_CLIENT_PROTO] && $proto = $this->headers->get(self::$trustedHeaders[self::HEADER_CLIENT_PROTO])) {
1181-
return in_array(strtolower(current(explode(',', $proto))), array('https', 'on', 'ssl', '1'));
1155+
if ($this->isFromTrustedProxy() && $proto = $this->getTrustedValues(self::HEADER_CLIENT_PROTO)) {
1156+
return in_array(strtolower($proto[0]), array('https', 'on', 'ssl', '1'), true);
11821157
}
11831158

11841159
$https = $this->server->get('HTTPS');
@@ -1203,10 +1178,8 @@ public function isSecure()
12031178
*/
12041179
public function getHost()
12051180
{
1206-
if ($this->isFromTrustedProxy() && self::$trustedHeaders[self::HEADER_CLIENT_HOST] && $host = $this->headers->get(self::$trustedHeaders[self::HEADER_CLIENT_HOST])) {
1207-
$elements = explode(',', $host);
1208-
1209-
$host = $elements[count($elements) - 1];
1181+
if ($this->isFromTrustedProxy() && $host = $this->getTrustedValues(self::HEADER_CLIENT_HOST)) {
1182+
$host = $host[0];
12101183
} elseif (!$host = $this->headers->get('HOST')) {
12111184
if (!$host = $this->server->get('SERVER_NAME')) {
12121185
$host = $this->server->get('SERVER_ADDR', '');
@@ -1977,8 +1950,48 @@ public function isFromTrustedProxy()
19771950
return self::$trustedProxies && IpUtils::checkIp($this->server->get('REMOTE_ADDR'), self::$trustedProxies);
19781951
}
19791952

1953+
private function getTrustedValues($type, $ip = null)
1954+
{
1955+
$clientValues = array();
1956+
$forwardedValues = array();
1957+
1958+
if (self::$trustedHeaders[$type] && $this->headers->has(self::$trustedHeaders[$type])) {
1959+
foreach (explode(',', $this->headers->get(self::$trustedHeaders[$type])) as $v) {
1960+
$clientValues[] = (self::HEADER_CLIENT_PORT === $type ? '0.0.0.0:' : '').trim($v);
1961+
}
1962+
}
1963+
1964+
if (self::$trustedHeaders[self::HEADER_FORWARDED] && $this->headers->has(self::$trustedHeaders[self::HEADER_FORWARDED])) {
1965+
$forwardedValues = $this->headers->get(self::$trustedHeaders[self::HEADER_FORWARDED]);
1966+
$forwardedValues = preg_match_all(sprintf('{(?:%s)=(?:"?\[?)([a-zA-Z0-9\.:_\-/]*+)}', self::$forwardedParams[$type]), $forwardedValues, $matches) ? $matches[1] : array();
1967+
}
1968+
1969+
if (null !== $ip) {
1970+
$clientValues = $this->normalizeAndFilterClientIps($clientValues, $ip);
1971+
$forwardedValues = $this->normalizeAndFilterClientIps($forwardedValues, $ip);
1972+
}
1973+
1974+
if ($forwardedValues === $clientValues || !$clientValues) {
1975+
return $forwardedValues;
1976+
}
1977+
1978+
if (!$forwardedValues) {
1979+
return $clientValues;
1980+
}
1981+
1982+
if (!$this->isForwardedValid) {
1983+
return null !== $ip ? array('0.0.0.0', $ip) : array();
1984+
}
1985+
$this->isForwardedValid = false;
1986+
1987+
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]));
1988+
}
1989+
19801990
private function normalizeAndFilterClientIps(array $clientIps, $ip)
19811991
{
1992+
if (!$clientIps) {
1993+
return array();
1994+
}
19821995
$clientIps[] = $ip; // Complete the IP chain with the IP the request actually came from
19831996
$firstTrustedIp = null;
19841997

src/Symfony/Component/HttpFoundation/Tests/RequestTest.php

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1670,12 +1670,12 @@ private function getRequestInstanceForClientIpsForwardedTests($remoteAddr, $http
16701670
return $request;
16711671
}
16721672

1673-
public function testTrustedProxies()
1673+
public function testTrustedProxiesXForwardedFor()
16741674
{
16751675
$request = Request::create('http://example.com/');
16761676
$request->server->set('REMOTE_ADDR', '3.3.3.3');
16771677
$request->headers->set('X_FORWARDED_FOR', '1.1.1.1, 2.2.2.2');
1678-
$request->headers->set('X_FORWARDED_HOST', 'foo.example.com, real.example.com:8080');
1678+
$request->headers->set('X_FORWARDED_HOST', 'foo.example.com:1234, real.example.com:8080');
16791679
$request->headers->set('X_FORWARDED_PROTO', 'https');
16801680
$request->headers->set('X_FORWARDED_PORT', 443);
16811681
$request->headers->set('X_MY_FOR', '3.3.3.3, 4.4.4.4');
@@ -1706,7 +1706,7 @@ public function testTrustedProxies()
17061706
// trusted proxy via setTrustedProxies()
17071707
Request::setTrustedProxies(array('3.3.3.3', '2.2.2.2'));
17081708
$this->assertEquals('1.1.1.1', $request->getClientIp());
1709-
$this->assertEquals('real.example.com', $request->getHost());
1709+
$this->assertEquals('foo.example.com', $request->getHost());
17101710
$this->assertEquals(443, $request->getPort());
17111711
$this->assertTrue($request->isSecure());
17121712

@@ -1753,6 +1753,55 @@ public function testTrustedProxies()
17531753
Request::setTrustedHeaderName(Request::HEADER_CLIENT_PROTO, 'X_FORWARDED_PROTO');
17541754
}
17551755

1756+
public function testTrustedProxiesForwarded()
1757+
{
1758+
$request = Request::create('http://example.com/');
1759+
$request->server->set('REMOTE_ADDR', '3.3.3.3');
1760+
$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');
1761+
1762+
// no trusted proxies
1763+
$this->assertEquals('3.3.3.3', $request->getClientIp());
1764+
$this->assertEquals('example.com', $request->getHost());
1765+
$this->assertEquals(80, $request->getPort());
1766+
$this->assertFalse($request->isSecure());
1767+
1768+
// disabling proxy trusting
1769+
Request::setTrustedProxies(array());
1770+
$this->assertEquals('3.3.3.3', $request->getClientIp());
1771+
$this->assertEquals('example.com', $request->getHost());
1772+
$this->assertEquals(80, $request->getPort());
1773+
$this->assertFalse($request->isSecure());
1774+
1775+
// request is forwarded by a non-trusted proxy
1776+
Request::setTrustedProxies(array('2.2.2.2'));
1777+
$this->assertEquals('3.3.3.3', $request->getClientIp());
1778+
$this->assertEquals('example.com', $request->getHost());
1779+
$this->assertEquals(80, $request->getPort());
1780+
$this->assertFalse($request->isSecure());
1781+
1782+
// trusted proxy via setTrustedProxies()
1783+
Request::setTrustedProxies(array('3.3.3.3', '2.2.2.2'));
1784+
$this->assertEquals('1.1.1.1', $request->getClientIp());
1785+
$this->assertEquals('foo.example.com', $request->getHost());
1786+
$this->assertEquals(8080, $request->getPort());
1787+
$this->assertTrue($request->isSecure());
1788+
1789+
// trusted proxy via setTrustedProxies()
1790+
Request::setTrustedProxies(array('3.3.3.4', '2.2.2.2'));
1791+
$this->assertEquals('3.3.3.3', $request->getClientIp());
1792+
$this->assertEquals('example.com', $request->getHost());
1793+
$this->assertEquals(80, $request->getPort());
1794+
$this->assertFalse($request->isSecure());
1795+
1796+
// check various X_FORWARDED_PROTO header values
1797+
Request::setTrustedProxies(array('3.3.3.3', '2.2.2.2'));
1798+
$request->headers->set('FORWARDED', 'proto=ssl');
1799+
$this->assertTrue($request->isSecure());
1800+
1801+
$request->headers->set('FORWARDED', 'proto=https, proto=http');
1802+
$this->assertTrue($request->isSecure());
1803+
}
1804+
17561805
/**
17571806
* @expectedException \InvalidArgumentException
17581807
*/

src/Symfony/Component/HttpKernel/Tests/EventListener/ValidateRequestListenerTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public function testListenerThrowsWhenMasterRequestHasInconsistentClientIps()
3232
$request = new Request();
3333
$request->setTrustedProxies(array('1.1.1.1'));
3434
$request->server->set('REMOTE_ADDR', '1.1.1.1');
35-
$request->headers->set('FORWARDED', '2.2.2.2');
35+
$request->headers->set('FORWARDED', 'for=2.2.2.2');
3636
$request->headers->set('X_FORWARDED_FOR', '3.3.3.3');
3737

3838
$dispatcher->addListener(KernelEvents::REQUEST, array(new ValidateRequestListener(), 'onKernelRequest'));

src/Symfony/Component/HttpKernel/Tests/HttpKernelTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,7 @@ public function testInconsistentClientIpsOnMasterRequests()
311311
$request = new Request();
312312
$request->setTrustedProxies(array('1.1.1.1'));
313313
$request->server->set('REMOTE_ADDR', '1.1.1.1');
314-
$request->headers->set('FORWARDED', '2.2.2.2');
314+
$request->headers->set('FORWARDED', 'for=2.2.2.2');
315315
$request->headers->set('X_FORWARDED_FOR', '3.3.3.3');
316316

317317
$dispatcher = new EventDispatcher();

src/Symfony/Component/Validator/Constraints/Composite.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ public function __construct($options = null)
6767

6868
foreach ($nestedConstraints as $constraint) {
6969
if (!$constraint instanceof Constraint) {
70+
if (is_object($constraint)) {
71+
$constraint = get_class($constraint);
72+
}
73+
7074
throw new ConstraintDefinitionException(sprintf('The value %s is not an instance of Constraint in constraint %s', $constraint, get_class($this)));
7175
}
7276

src/Symfony/Component/Validator/Tests/Constraints/CompositeTest.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,17 @@ public function testFailIfNoConstraint()
125125
));
126126
}
127127

128+
/**
129+
* @expectedException \Symfony\Component\Validator\Exception\ConstraintDefinitionException
130+
*/
131+
public function testFailIfNoConstraintObject()
132+
{
133+
new ConcreteComposite(array(
134+
new NotNull(array('groups' => 'Default')),
135+
new \ArrayObject(),
136+
));
137+
}
138+
128139
/**
129140
* @expectedException \Symfony\Component\Validator\Exception\ConstraintDefinitionException
130141
*/

0 commit comments

Comments
 (0)