From 548f1cbf73f5b1d50b999dd12d11e7b423a43da2 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Fri, 25 May 2018 13:45:30 +0200 Subject: [PATCH 01/20] updated CHANGELOG for 2.7.48 --- CHANGELOG-2.7.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG-2.7.md b/CHANGELOG-2.7.md index 6903fb4ed4..c343a87407 100644 --- a/CHANGELOG-2.7.md +++ b/CHANGELOG-2.7.md @@ -7,6 +7,14 @@ in 2.7 minor versions. To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v2.7.0...v2.7.1 +* 2.7.48 (2018-05-25) + + * bug #27359 [HttpFoundation] Fix perf issue during MimeTypeGuesser intialization (nicolas-grekas) + * security #cve-2018-11408 [SecurityBundle] Fail if security.http_utils cannot be configured + * security #cve-2018-11406 clear CSRF tokens when the user is logged out + * security #cve-2018-11385 Adding session strategy to ALL listeners to avoid *any* possible fixation + * security #cve-2018-11386 [HttpFoundation] Break infinite loop in PdoSessionHandler when MySQL is in loose mode + * 2.7.47 (2018-05-21) * bug #26781 [Form] Fix precision of MoneyToLocalizedStringTransformer's divisions on transform() (syastrebov) From fb79294e7625a73a6e9d19f97c8977fb8a436abe Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Fri, 25 May 2018 13:45:39 +0200 Subject: [PATCH 02/20] update CONTRIBUTORS for 2.7.48 --- CONTRIBUTORS.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 672246e7f9..73af1c6a1c 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -127,6 +127,7 @@ Symfony is the result of the work of many people who made the code better - Tugdual Saunier (tucksaun) - Javier Spagnoletti (phansys) - Théo FIDRY (theofidry) + - gadelat (gadelat) - Robert Schönthal (digitalkaoz) - Florian Lonqueu-Brochard (florianlb) - Sebastiaan Stok (sstok) @@ -141,7 +142,6 @@ Symfony is the result of the work of many people who made the code better - Hidenori Goto (hidenorigoto) - Jérôme Vasseur (jvasseur) - Valentin Udaltsov (vudaltsov) - - gadelat (gadelat) - Guilherme Blanco (guilhermeblanco) - Pablo Godel (pgodel) - Jérémie Augustin (jaugustin) @@ -511,6 +511,7 @@ Symfony is the result of the work of many people who made the code better - Andrew Udvare (audvare) - alexpods - Arjen van der Meijden + - Adam Szaraniec (mimol) - Dariusz Ruminski - Erik Trapman (eriktrapman) - De Cock Xavier (xdecock) @@ -588,6 +589,7 @@ Symfony is the result of the work of many people who made the code better - Nahuel Cuesta (ncuesta) - Chris Boden (cboden) - Christophe Villeger (seragan) + - Bob van de Vijver (bobvandevijver) - Stefan Gehrig (sgehrig) - Hany el-Kerdany - Wang Jingyu @@ -691,6 +693,7 @@ Symfony is the result of the work of many people who made the code better - Alex Xandra Albert Sim - Craig Duncan (duncan3dc) - Carson Full + - Sergey Yastrebov - Trent Steel (trsteel88) - Yuen-Chi Lian - Besnik Br @@ -717,11 +720,11 @@ Symfony is the result of the work of many people who made the code better - Joschi Kuphal - John Bohn (jbohn) - Marc Morera (mmoreram) + - Smaine Milianni (ismail1432) - Andrew Hilobok (hilobok) - Noah Heck (myesain) - Christian Soronellas (theunic) - Johann Pardanaud - - Adam Szaraniec (mimol) - Yosmany Garcia (yosmanyga) - Wouter de Wild - Antoine M (amakdessi) @@ -934,7 +937,6 @@ Symfony is the result of the work of many people who made the code better - Máximo Cuadros (mcuadros) - tamirvs - julien.galenski - - Bob van de Vijver - Christian Neff - Oliver Hoff - Ole Rößner (basster) @@ -1017,6 +1019,7 @@ Symfony is the result of the work of many people who made the code better - Alex Bowers - Jeremy Bush - wizhippo + - Thomason, James - Viacheslav Sychov - Helmut Hummel (helhum) - Matt Brunt @@ -1158,7 +1161,6 @@ Symfony is the result of the work of many people who made the code better - Berat Doğan - Guillaume LECERF - Juanmi Rodriguez Cerón - - Sergey Yastrebov - Andy Raines - Anthony Ferrara - Klaas Cuvelier (kcuvelier) @@ -1209,6 +1211,7 @@ Symfony is the result of the work of many people who made the code better - Romain Geissler - Adrien Moiruad - Tomaz Ahlin + - Philip Ardery - Marcus Stöhr (dafish) - Emmanuel Vella (emmanuel.vella) - Jonathan Johnson (jrjohnson) @@ -1254,7 +1257,6 @@ Symfony is the result of the work of many people who made the code better - Ergie Gonzaga - Matthew J Mucklo - AnrDaemon - - Smaine Milianni (ismail1432) - fdgdfg (psampaz) - Stéphane Seng - Maxwell Vandervelde @@ -1765,6 +1767,7 @@ Symfony is the result of the work of many people who made the code better - Norman Soetbeer - zorn - Yuriy Potemkin + - Emilie Lorenzo - Benjamin Long - Matt Janssen - Ben Miller @@ -1929,6 +1932,7 @@ Symfony is the result of the work of many people who made the code better - fh-github@fholzhauer.de - AbdElKader Bouadjadja - DSeemiller + - Kyle - Jan Emrich - Mark Topper - Xavier REN From 81564555d81e4955b12b41837ae7cbae6fed907d Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Fri, 25 May 2018 13:45:58 +0200 Subject: [PATCH 03/20] updated VERSION for 2.7.48 --- src/Symfony/Component/HttpKernel/Kernel.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index 158099cd31..9ec779c8ce 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -58,12 +58,12 @@ abstract class Kernel implements KernelInterface, TerminableInterface protected $startTime; protected $loadClassCache; - const VERSION = '2.7.48-DEV'; + const VERSION = '2.7.48'; const VERSION_ID = 20748; const MAJOR_VERSION = 2; const MINOR_VERSION = 7; const RELEASE_VERSION = 48; - const EXTRA_VERSION = 'DEV'; + const EXTRA_VERSION = ''; const END_OF_MAINTENANCE = '05/2018'; const END_OF_LIFE = '05/2019'; From eda2b20df5a9fc7bff420a6cd60e06d4b44d9de0 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sat, 14 Jul 2018 21:56:04 +0200 Subject: [PATCH 04/20] [HttpFoundation] Remove support for legacy and risky HTTP headers --- .../Component/HttpFoundation/CHANGELOG.md | 6 +++ .../Component/HttpFoundation/Request.php | 13 +----- .../HttpFoundation/Tests/RequestTest.php | 44 ------------------- 3 files changed, 7 insertions(+), 56 deletions(-) diff --git a/src/Symfony/Component/HttpFoundation/CHANGELOG.md b/src/Symfony/Component/HttpFoundation/CHANGELOG.md index dcdeb4ebf9..d881cf01b7 100644 --- a/src/Symfony/Component/HttpFoundation/CHANGELOG.md +++ b/src/Symfony/Component/HttpFoundation/CHANGELOG.md @@ -1,6 +1,12 @@ CHANGELOG ========= +2.7.49 +------ + + * [BC BREAK] Support for the IIS-only `X_ORIGINAL_URL` and `X_REWRITE_URL` + HTTP headers has been dropped for security reasons. + 2.6.0 ----- diff --git a/src/Symfony/Component/HttpFoundation/Request.php b/src/Symfony/Component/HttpFoundation/Request.php index 7e8e075a83..f4ea78bbe1 100644 --- a/src/Symfony/Component/HttpFoundation/Request.php +++ b/src/Symfony/Component/HttpFoundation/Request.php @@ -1712,18 +1712,7 @@ class Request { $requestUri = ''; - if ($this->headers->has('X_ORIGINAL_URL')) { - // IIS with Microsoft Rewrite Module - $requestUri = $this->headers->get('X_ORIGINAL_URL'); - $this->headers->remove('X_ORIGINAL_URL'); - $this->server->remove('HTTP_X_ORIGINAL_URL'); - $this->server->remove('UNENCODED_URL'); - $this->server->remove('IIS_WasUrlRewritten'); - } elseif ($this->headers->has('X_REWRITE_URL')) { - // IIS with ISAPI_Rewrite - $requestUri = $this->headers->get('X_REWRITE_URL'); - $this->headers->remove('X_REWRITE_URL'); - } elseif ('1' == $this->server->get('IIS_WasUrlRewritten') && '' != $this->server->get('UNENCODED_URL')) { + if ('1' == $this->server->get('IIS_WasUrlRewritten') && '' != $this->server->get('UNENCODED_URL')) { // IIS7 with URL Rewrite: make sure we get the unencoded URL (double slash problem) $requestUri = $this->server->get('UNENCODED_URL'); $this->server->remove('UNENCODED_URL'); diff --git a/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php b/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php index 688a7c714a..eb49947788 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php @@ -1768,20 +1768,6 @@ class RequestTest extends TestCase public function iisRequestUriProvider() { return array( - array( - array( - 'X_ORIGINAL_URL' => '/foo/bar', - ), - array(), - '/foo/bar', - ), - array( - array( - 'X_REWRITE_URL' => '/foo/bar', - ), - array(), - '/foo/bar', - ), array( array(), array( @@ -1790,36 +1776,6 @@ class RequestTest extends TestCase ), '/foo/bar', ), - array( - array( - 'X_ORIGINAL_URL' => '/foo/bar', - ), - array( - 'HTTP_X_ORIGINAL_URL' => '/foo/bar', - ), - '/foo/bar', - ), - array( - array( - 'X_ORIGINAL_URL' => '/foo/bar', - ), - array( - 'IIS_WasUrlRewritten' => '1', - 'UNENCODED_URL' => '/foo/bar', - ), - '/foo/bar', - ), - array( - array( - 'X_ORIGINAL_URL' => '/foo/bar', - ), - array( - 'HTTP_X_ORIGINAL_URL' => '/foo/bar', - 'IIS_WasUrlRewritten' => '1', - 'UNENCODED_URL' => '/foo/bar', - ), - '/foo/bar', - ), array( array(), array( From 08a32d44b62275b3c6499493dd95e099c84daf60 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 31 Jul 2018 19:00:56 +0200 Subject: [PATCH 05/20] [HttpKernel] fix trusted headers management in HttpCache and InlineFragmentRenderer --- .../Fragment/InlineFragmentRenderer.php | 17 +- .../HttpKernel/HttpCache/HttpCache.php | 21 +-- .../HttpCache/SubRequestHandler.php | 100 +++++++++++ .../Fragment/InlineFragmentRendererTest.php | 63 +++++-- .../Tests/HttpCache/HttpCacheTest.php | 56 +++--- .../Tests/HttpCache/SubRequestHandlerTest.php | 163 ++++++++++++++++++ .../Tests/HttpCache/TestHttpKernel.php | 27 ++- 7 files changed, 375 insertions(+), 72 deletions(-) create mode 100644 src/Symfony/Component/HttpKernel/HttpCache/SubRequestHandler.php create mode 100644 src/Symfony/Component/HttpKernel/Tests/HttpCache/SubRequestHandlerTest.php diff --git a/src/Symfony/Component/HttpKernel/Fragment/InlineFragmentRenderer.php b/src/Symfony/Component/HttpKernel/Fragment/InlineFragmentRenderer.php index 17ed967fb5..6dee3aa1e0 100644 --- a/src/Symfony/Component/HttpKernel/Fragment/InlineFragmentRenderer.php +++ b/src/Symfony/Component/HttpKernel/Fragment/InlineFragmentRenderer.php @@ -13,6 +13,7 @@ namespace Symfony\Component\HttpKernel\Fragment; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpCache\SubRequestHandler; use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\HttpKernel\Controller\ControllerReference; use Symfony\Component\HttpKernel\KernelEvents; @@ -76,7 +77,7 @@ class InlineFragmentRenderer extends RoutableFragmentRenderer $level = ob_get_level(); try { - return $this->kernel->handle($subRequest, HttpKernelInterface::SUB_REQUEST, false); + return SubRequestHandler::handle($this->kernel, $subRequest, HttpKernelInterface::SUB_REQUEST, false); } catch (\Exception $e) { // we dispatch the exception event to trigger the logging // the response that comes back is simply ignored @@ -109,20 +110,6 @@ class InlineFragmentRenderer extends RoutableFragmentRenderer $cookies = $request->cookies->all(); $server = $request->server->all(); - // Override the arguments to emulate a sub-request. - // Sub-request object will point to localhost as client ip and real client ip - // will be included into trusted header for client ip - try { - if ($trustedHeaderName = Request::getTrustedHeaderName(Request::HEADER_CLIENT_IP)) { - $currentXForwardedFor = $request->headers->get($trustedHeaderName, ''); - - $server['HTTP_'.$trustedHeaderName] = ($currentXForwardedFor ? $currentXForwardedFor.', ' : '').$request->getClientIp(); - } - } catch (\InvalidArgumentException $e) { - // Do nothing - } - - $server['REMOTE_ADDR'] = '127.0.0.1'; unset($server['HTTP_IF_MODIFIED_SINCE']); unset($server['HTTP_IF_NONE_MATCH']); diff --git a/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php b/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php index 81c75ff740..3fb3f04624 100644 --- a/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php +++ b/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php @@ -464,27 +464,8 @@ class HttpCache implements HttpKernelInterface, TerminableInterface $this->surrogate->addSurrogateCapability($request); } - // modify the X-Forwarded-For header if needed - $forwardedFor = $request->headers->get('X-Forwarded-For'); - if ($forwardedFor) { - $request->headers->set('X-Forwarded-For', $forwardedFor.', '.$request->server->get('REMOTE_ADDR')); - } else { - $request->headers->set('X-Forwarded-For', $request->server->get('REMOTE_ADDR')); - } - - // fix the client IP address by setting it to 127.0.0.1 as HttpCache - // is always called from the same process as the backend. - $request->server->set('REMOTE_ADDR', '127.0.0.1'); - - // make sure HttpCache is a trusted proxy - if (!in_array('127.0.0.1', $trustedProxies = Request::getTrustedProxies())) { - $trustedProxies[] = '127.0.0.1'; - Request::setTrustedProxies($trustedProxies); - } - // always a "master" request (as the real master request can be in cache) - $response = $this->kernel->handle($request, HttpKernelInterface::MASTER_REQUEST, $catch); - // FIXME: we probably need to also catch exceptions if raw === true + $response = SubRequestHandler::handle($this->kernel, $request, HttpKernelInterface::MASTER_REQUEST, $catch); // we don't implement the stale-if-error on Requests, which is nonetheless part of the RFC if (null !== $entry && in_array($response->getStatusCode(), array(500, 502, 503, 504))) { diff --git a/src/Symfony/Component/HttpKernel/HttpCache/SubRequestHandler.php b/src/Symfony/Component/HttpKernel/HttpCache/SubRequestHandler.php new file mode 100644 index 0000000000..c050256025 --- /dev/null +++ b/src/Symfony/Component/HttpKernel/HttpCache/SubRequestHandler.php @@ -0,0 +1,100 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\HttpCache; + +use Symfony\Component\HttpFoundation\IpUtils; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpKernelInterface; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class SubRequestHandler +{ + /** + * @return Response + */ + public static function handle(HttpKernelInterface $kernel, Request $request, $type, $catch) + { + // save global state related to trusted headers and proxies + $trustedProxies = Request::getTrustedProxies(); + $trustedHeaders = array( + Request::HEADER_FORWARDED => Request::getTrustedHeaderName(Request::HEADER_FORWARDED), + Request::HEADER_CLIENT_IP => Request::getTrustedHeaderName(Request::HEADER_CLIENT_IP), + Request::HEADER_CLIENT_HOST => Request::getTrustedHeaderName(Request::HEADER_CLIENT_HOST), + Request::HEADER_CLIENT_PROTO => Request::getTrustedHeaderName(Request::HEADER_CLIENT_PROTO), + Request::HEADER_CLIENT_PORT => Request::getTrustedHeaderName(Request::HEADER_CLIENT_PORT), + ); + + // remove untrusted values + $remoteAddr = $request->server->get('REMOTE_ADDR'); + if (!IpUtils::checkIp($remoteAddr, $trustedProxies)) { + foreach (array_filter($trustedHeaders) as $name) { + $request->headers->remove($name); + } + } + + // compute trusted values, taking any trusted proxies into account + $trustedIps = array(); + $trustedValues = array(); + foreach (array_reverse($request->getClientIps()) as $ip) { + $trustedIps[] = $ip; + $trustedValues[] = sprintf('for="%s"', $ip); + } + if ($ip !== $remoteAddr) { + $trustedIps[] = $remoteAddr; + $trustedValues[] = sprintf('for="%s"', $remoteAddr); + } + + // set trusted values, reusing as much as possible the global trusted settings + if ($name = $trustedHeaders[Request::HEADER_FORWARDED]) { + $trustedValues[0] .= sprintf(';host="%s";proto=%s', $request->getHttpHost(), $request->getScheme()); + $request->headers->set($name, implode(', ', $trustedValues)); + } + if ($name = $trustedHeaders[Request::HEADER_CLIENT_IP]) { + $request->headers->set($name, implode(', ', $trustedIps)); + } + if (!$name && !$trustedHeaders[Request::HEADER_FORWARDED]) { + $request->headers->set('X-Forwarded-For', implode(', ', $trustedIps)); + Request::setTrustedHeaderName(Request::HEADER_CLIENT_IP, 'X_FORWARDED_FOR'); + } + + // fix the client IP address by setting it to 127.0.0.1, + // which is the core responsibility of this method + $request->server->set('REMOTE_ADDR', '127.0.0.1'); + + // ensure 127.0.0.1 is set as trusted proxy + if (!IpUtils::checkIp('127.0.0.1', $trustedProxies)) { + Request::setTrustedProxies(array_merge($trustedProxies, array('127.0.0.1'))); + } + + try { + $e = null; + $response = $kernel->handle($request, $type, $catch); + } catch (\Throwable $e) { + } catch (\Exception $e) { + } + + // restore global state + Request::setTrustedHeaderName(Request::HEADER_CLIENT_IP, $trustedHeaders[Request::HEADER_CLIENT_IP]); + Request::setTrustedProxies($trustedProxies); + + if (null !== $e) { + throw $e; + } + + return $response; + } +} diff --git a/src/Symfony/Component/HttpKernel/Tests/Fragment/InlineFragmentRendererTest.php b/src/Symfony/Component/HttpKernel/Tests/Fragment/InlineFragmentRendererTest.php index 4c1d6a00c4..a3eea96fb0 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Fragment/InlineFragmentRendererTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Fragment/InlineFragmentRendererTest.php @@ -26,12 +26,16 @@ class InlineFragmentRendererTest extends TestCase protected function setUp() { - $this->originalTrustedHeaderName = Request::getTrustedHeaderName(Request::HEADER_CLIENT_IP); + $this->originalTrustedHeaderNames = array( + Request::getTrustedHeaderName(Request::HEADER_CLIENT_IP), + Request::getTrustedHeaderName(Request::HEADER_FORWARDED), + ); } protected function tearDown() { - Request::setTrustedHeaderName(Request::HEADER_CLIENT_IP, $this->originalTrustedHeaderName); + Request::setTrustedHeaderName(Request::HEADER_CLIENT_IP, $this->originalTrustedHeaderNames[0]); + Request::setTrustedHeaderName(Request::HEADER_FORWARDED, $this->originalTrustedHeaderNames[1]); } public function testRender() @@ -55,7 +59,7 @@ class InlineFragmentRendererTest extends TestCase $subRequest = Request::create('/_fragment?_path=_format%3Dhtml%26_locale%3Den%26_controller%3Dmain_controller'); $subRequest->attributes->replace(array('object' => $object, '_format' => 'html', '_controller' => 'main_controller', '_locale' => 'en')); $subRequest->headers->set('x-forwarded-for', array('127.0.0.1')); - $subRequest->server->set('HTTP_X_FORWARDED_FOR', '127.0.0.1'); + $subRequest->headers->set('forwarded', array('for="127.0.0.1";host="localhost";proto=http')); $strategy = new InlineFragmentRenderer($this->getKernelExpectingRequest($subRequest)); @@ -83,8 +87,12 @@ class InlineFragmentRendererTest extends TestCase public function testRenderWithTrustedHeaderDisabled() { Request::setTrustedHeaderName(Request::HEADER_CLIENT_IP, ''); + Request::setTrustedHeaderName(Request::HEADER_FORWARDED, ''); - $strategy = new InlineFragmentRenderer($this->getKernelExpectingRequest(Request::create('/'))); + $expectedSubRequest = Request::create('/'); + $expectedSubRequest->headers->set('x-forwarded-for', array('127.0.0.1')); + + $strategy = new InlineFragmentRenderer($this->getKernelExpectingRequest($expectedSubRequest)); $this->assertSame('foo', $strategy->render('/', Request::create('/'))->getContent()); } @@ -168,11 +176,10 @@ class InlineFragmentRendererTest extends TestCase { $expectedSubRequest = Request::create('/'); $expectedSubRequest->headers->set('Surrogate-Capability', 'abc="ESI/1.0"'); - if (Request::getTrustedHeaderName(Request::HEADER_CLIENT_IP)) { $expectedSubRequest->headers->set('x-forwarded-for', array('127.0.0.1')); - $expectedSubRequest->server->set('HTTP_X_FORWARDED_FOR', '127.0.0.1'); } + $expectedSubRequest->headers->set('forwarded', array('for="127.0.0.1";host="localhost";proto=http')); $strategy = new InlineFragmentRenderer($this->getKernelExpectingRequest($expectedSubRequest)); @@ -194,16 +201,52 @@ class InlineFragmentRendererTest extends TestCase public function testHeadersPossiblyResultingIn304AreNotAssignedToSubrequest() { $expectedSubRequest = Request::create('/'); - if (Request::getTrustedHeaderName(Request::HEADER_CLIENT_IP)) { - $expectedSubRequest->headers->set('x-forwarded-for', array('127.0.0.1')); - $expectedSubRequest->server->set('HTTP_X_FORWARDED_FOR', '127.0.0.1'); - } + $expectedSubRequest->headers->set('x-forwarded-for', array('127.0.0.1')); + $expectedSubRequest->headers->set('forwarded', array('for="127.0.0.1";host="localhost";proto=http')); $strategy = new InlineFragmentRenderer($this->getKernelExpectingRequest($expectedSubRequest)); $request = Request::create('/', 'GET', array(), array(), array(), array('HTTP_IF_MODIFIED_SINCE' => 'Fri, 01 Jan 2016 00:00:00 GMT', 'HTTP_IF_NONE_MATCH' => '*')); $strategy->render('/', $request); } + public function testFirstTrustedProxyIsSetAsRemote() + { + $expectedSubRequest = Request::create('/'); + $expectedSubRequest->headers->set('Surrogate-Capability', 'abc="ESI/1.0"'); + $expectedSubRequest->server->set('REMOTE_ADDR', '127.0.0.1'); + $expectedSubRequest->headers->set('x-forwarded-for', array('127.0.0.1')); + $expectedSubRequest->headers->set('forwarded', array('for="127.0.0.1";host="localhost";proto=http')); + + Request::setTrustedProxies(array('1.1.1.1')); + + $strategy = new InlineFragmentRenderer($this->getKernelExpectingRequest($expectedSubRequest)); + + $request = Request::create('/'); + $request->headers->set('Surrogate-Capability', 'abc="ESI/1.0"'); + $strategy->render('/', $request); + + Request::setTrustedProxies(array()); + } + + public function testIpAddressOfRangedTrustedProxyIsSetAsRemote() + { + $expectedSubRequest = Request::create('/'); + $expectedSubRequest->headers->set('Surrogate-Capability', 'abc="ESI/1.0"'); + $expectedSubRequest->server->set('REMOTE_ADDR', '127.0.0.1'); + $expectedSubRequest->headers->set('x-forwarded-for', array('127.0.0.1')); + $expectedSubRequest->headers->set('forwarded', array('for="127.0.0.1";host="localhost";proto=http')); + + Request::setTrustedProxies(array('1.1.1.1/24')); + + $strategy = new InlineFragmentRenderer($this->getKernelExpectingRequest($expectedSubRequest)); + + $request = Request::create('/'); + $request->headers->set('Surrogate-Capability', 'abc="ESI/1.0"'); + $strategy->render('/', $request); + + Request::setTrustedProxies(array()); + } + /** * Creates a Kernel expecting a request equals to $request * Allows delta in comparison in case REQUEST_TIME changed by 1 second. diff --git a/src/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTest.php b/src/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTest.php index d6902f4880..ca94230652 100644 --- a/src/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTest.php @@ -1301,64 +1301,72 @@ class HttpCacheTest extends HttpCacheTestCase $this->setNextResponse(); $this->request('GET', '/', array('REMOTE_ADDR' => '10.0.0.1')); - $this->assertEquals('127.0.0.1', $this->kernel->getBackendRequest()->server->get('REMOTE_ADDR')); + $that = $this; + $this->kernel->assert(function ($backendRequest) use ($that) { + $that->assertSame('127.0.0.1', $backendRequest->server->get('REMOTE_ADDR')); + }); } /** * @dataProvider getTrustedProxyData */ - public function testHttpCacheIsSetAsATrustedProxy(array $existing, array $expected) + public function testHttpCacheIsSetAsATrustedProxy(array $existing) { Request::setTrustedProxies($existing); $this->setNextResponse(); $this->request('GET', '/', array('REMOTE_ADDR' => '10.0.0.1')); + $this->assertSame($existing, Request::getTrustedProxies()); - $this->assertEquals($expected, Request::getTrustedProxies()); + $that = $this; + $existing = array_unique(array_merge($existing, array('127.0.0.1'))); + $this->kernel->assert(function ($backendRequest) use ($existing, $that) { + $that->assertSame($existing, Request::getTrustedProxies()); + $that->assertsame('10.0.0.1', $backendRequest->getClientIp()); + }); + + Request::setTrustedProxies(array()); } public function getTrustedProxyData() { return array( - array(array(), array('127.0.0.1')), - array(array('10.0.0.2'), array('10.0.0.2', '127.0.0.1')), - array(array('10.0.0.2', '127.0.0.1'), array('10.0.0.2', '127.0.0.1')), + array(array()), + array(array('10.0.0.2')), + array(array('10.0.0.2', '127.0.0.1')), ); } /** - * @dataProvider getXForwardedForData + * @dataProvider getForwardedData */ - public function testXForwarderForHeaderForForwardedRequests($xForwardedFor, $expected) + public function testForwarderHeaderForForwardedRequests($forwarded, $expected) { $this->setNextResponse(); $server = array('REMOTE_ADDR' => '10.0.0.1'); - if (false !== $xForwardedFor) { - $server['HTTP_X_FORWARDED_FOR'] = $xForwardedFor; + if (null !== $forwarded) { + Request::setTrustedProxies($server); + $server['HTTP_FORWARDED'] = $forwarded; } $this->request('GET', '/', $server); - $this->assertEquals($expected, $this->kernel->getBackendRequest()->headers->get('X-Forwarded-For')); + $that = $this; + $this->kernel->assert(function ($backendRequest) use ($expected, $that) { + $that->assertSame($expected, $backendRequest->headers->get('Forwarded')); + }); + + Request::setTrustedProxies(array()); } - public function getXForwardedForData() + public function getForwardedData() { return array( - array(false, '10.0.0.1'), - array('10.0.0.2', '10.0.0.2, 10.0.0.1'), - array('10.0.0.2, 10.0.0.3', '10.0.0.2, 10.0.0.3, 10.0.0.1'), + array(null, 'for="10.0.0.1";host="localhost";proto=http'), + array('for=10.0.0.2', 'for="10.0.0.2";host="localhost";proto=http, for="10.0.0.1"'), + array('for=10.0.0.2, for=10.0.0.3', 'for="10.0.0.2";host="localhost";proto=http, for="10.0.0.3", for="10.0.0.1"'), ); } - public function testXForwarderForHeaderForPassRequests() - { - $this->setNextResponse(); - $server = array('REMOTE_ADDR' => '10.0.0.1'); - $this->request('POST', '/', $server); - - $this->assertEquals('10.0.0.1', $this->kernel->getBackendRequest()->headers->get('X-Forwarded-For')); - } - public function testEsiCacheRemoveValidationHeadersIfEmbeddedResponses() { $time = \DateTime::createFromFormat('U', time()); diff --git a/src/Symfony/Component/HttpKernel/Tests/HttpCache/SubRequestHandlerTest.php b/src/Symfony/Component/HttpKernel/Tests/HttpCache/SubRequestHandlerTest.php new file mode 100644 index 0000000000..3f558878c6 --- /dev/null +++ b/src/Symfony/Component/HttpKernel/Tests/HttpCache/SubRequestHandlerTest.php @@ -0,0 +1,163 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\HttpCache; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpCache\SubRequestHandler; +use Symfony\Component\HttpKernel\HttpKernelInterface; + +class SubRequestHandlerTest extends TestCase +{ + private static $globalState; + + protected function setUp() + { + self::$globalState = $this->getGlobalState(); + } + + protected function tearDown() + { + foreach (self::$globalState[1] as $key => $name) { + Request::setTrustedHeaderName($key, $name); + } + Request::setTrustedProxies(self::$globalState[0]); + } + + public function testTrustedHeadersAreKept() + { + Request::setTrustedProxies(array('10.0.0.1')); + $globalState = $this->getGlobalState(); + + $request = Request::create('/'); + $request->server->set('REMOTE_ADDR', '10.0.0.1'); + $request->headers->set('X-Forwarded-For', '10.0.0.2'); + $request->headers->set('X-Forwarded-Host', 'Good'); + $request->headers->set('X-Forwarded-Port', '1234'); + $request->headers->set('X-Forwarded-Proto', 'https'); + + $that = $this; + $kernel = new TestSubRequestHandlerKernel(function ($request, $type, $catch) use ($that) { + $that->assertSame('127.0.0.1', $request->server->get('REMOTE_ADDR')); + $that->assertSame('10.0.0.2', $request->getClientIp()); + $that->assertSame('Good', $request->headers->get('X-Forwarded-Host')); + $that->assertSame('1234', $request->headers->get('X-Forwarded-Port')); + $that->assertSame('https', $request->headers->get('X-Forwarded-Proto')); + }); + + SubRequestHandler::handle($kernel, $request, HttpKernelInterface::MASTER_REQUEST, true); + + $this->assertSame($globalState, $this->getGlobalState()); + } + + public function testUntrustedHeadersAreRemoved() + { + $request = Request::create('/'); + $request->server->set('REMOTE_ADDR', '10.0.0.1'); + $request->headers->set('X-Forwarded-For', '10.0.0.2'); + $request->headers->set('X-Forwarded-Host', 'Evil'); + $request->headers->set('X-Forwarded-Port', '1234'); + $request->headers->set('X-Forwarded-Proto', 'http'); + $request->headers->set('Forwarded', 'Evil2'); + + $that = $this; + $kernel = new TestSubRequestHandlerKernel(function ($request, $type, $catch) use ($that) { + $that->assertSame('127.0.0.1', $request->server->get('REMOTE_ADDR')); + $that->assertSame('10.0.0.1', $request->getClientIp()); + $that->assertFalse($request->headers->has('X-Forwarded-Host')); + $that->assertFalse($request->headers->has('X-Forwarded-Port')); + $that->assertFalse($request->headers->has('X-Forwarded-Proto')); + $that->assertSame('for="10.0.0.1";host="localhost";proto=http', $request->headers->get('Forwarded')); + }); + + SubRequestHandler::handle($kernel, $request, HttpKernelInterface::MASTER_REQUEST, true); + + $this->assertSame(self::$globalState, $this->getGlobalState()); + } + + public function testTrustedForwardedHeader() + { + Request::setTrustedProxies(array('10.0.0.1')); + $globalState = $this->getGlobalState(); + + $request = Request::create('/'); + $request->server->set('REMOTE_ADDR', '10.0.0.1'); + $request->headers->set('Forwarded', 'for="10.0.0.2";host="foo.bar:1234";proto=https'); + + $that = $this; + $kernel = new TestSubRequestHandlerKernel(function ($request, $type, $catch) use ($that) { + $that->assertSame('127.0.0.1', $request->server->get('REMOTE_ADDR')); + $that->assertSame('10.0.0.2', $request->getClientIp()); + }); + + SubRequestHandler::handle($kernel, $request, HttpKernelInterface::MASTER_REQUEST, true); + + $this->assertSame($globalState, $this->getGlobalState()); + } + + public function testTrustedXForwardedForHeader() + { + Request::setTrustedProxies(array('10.0.0.1')); + $globalState = $this->getGlobalState(); + + $request = Request::create('/'); + $request->server->set('REMOTE_ADDR', '10.0.0.1'); + $request->headers->set('X-Forwarded-For', '10.0.0.2'); + $request->headers->set('X-Forwarded-Host', 'foo.bar'); + $request->headers->set('X-Forwarded-Proto', 'https'); + + $that = $this; + $kernel = new TestSubRequestHandlerKernel(function ($request, $type, $catch) use ($that) { + $that->assertSame('127.0.0.1', $request->server->get('REMOTE_ADDR')); + $that->assertSame('10.0.0.2', $request->getClientIp()); + $that->assertSame('foo.bar', $request->getHttpHost()); + $that->assertSame('https', $request->getScheme()); + }); + + SubRequestHandler::handle($kernel, $request, HttpKernelInterface::MASTER_REQUEST, true); + + $this->assertSame($globalState, $this->getGlobalState()); + } + + private function getGlobalState() + { + return array( + Request::getTrustedProxies(), + array( + Request::HEADER_FORWARDED => Request::getTrustedHeaderName(Request::HEADER_FORWARDED), + Request::HEADER_CLIENT_IP => Request::getTrustedHeaderName(Request::HEADER_CLIENT_IP), + Request::HEADER_CLIENT_HOST => Request::getTrustedHeaderName(Request::HEADER_CLIENT_HOST), + Request::HEADER_CLIENT_PROTO => Request::getTrustedHeaderName(Request::HEADER_CLIENT_PROTO), + Request::HEADER_CLIENT_PORT => Request::getTrustedHeaderName(Request::HEADER_CLIENT_PORT), + ), + ); + } +} + +class TestSubRequestHandlerKernel implements HttpKernelInterface +{ + private $assertCallback; + + public function __construct(\Closure $assertCallback) + { + $this->assertCallback = $assertCallback; + } + + public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true) + { + $assertCallback = $this->assertCallback; + $assertCallback($request, $type, $catch); + + return new Response(); + } +} diff --git a/src/Symfony/Component/HttpKernel/Tests/HttpCache/TestHttpKernel.php b/src/Symfony/Component/HttpKernel/Tests/HttpCache/TestHttpKernel.php index 5546ba2ed8..5ad9508da7 100644 --- a/src/Symfony/Component/HttpKernel/Tests/HttpCache/TestHttpKernel.php +++ b/src/Symfony/Component/HttpKernel/Tests/HttpCache/TestHttpKernel.php @@ -34,19 +34,40 @@ class TestHttpKernel extends HttpKernel implements ControllerResolverInterface $this->status = $status; $this->headers = $headers; $this->customizer = $customizer; + $this->trustedHeadersReflector = new \ReflectionProperty('Symfony\Component\HttpFoundation\Request', 'trustedHeaders'); + $this->trustedHeadersReflector->setAccessible(true); parent::__construct(new EventDispatcher(), $this); } - public function getBackendRequest() + public function assert(\Closure $callback) { - return $this->backendRequest; + $trustedConfig = array(Request::getTrustedProxies(), $this->trustedHeadersReflector->getValue()); + + list($trustedProxies, $trustedHeaders, $backendRequest) = $this->backendRequest; + Request::setTrustedProxies($trustedProxies); + $this->trustedHeadersReflector->setValue(null, $trustedHeaders); + + try { + $e = null; + $callback($backendRequest); + } catch (\Throwable $e) { + } catch (\Exception $e) { + } + + list($trustedProxies, $trustedHeaders) = $trustedConfig; + Request::setTrustedProxies($trustedProxies); + $this->trustedHeadersReflector->setValue(null, $trustedHeaders); + + if (null !== $e) { + throw $e; + } } public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = false) { $this->catch = $catch; - $this->backendRequest = $request; + $this->backendRequest = array($request::getTrustedProxies(), $this->trustedHeadersReflector->getValue(), $request); return parent::handle($request, $type, $catch); } From 5999020906de1d4a378d83a82877d2919940518d Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Wed, 1 Aug 2018 15:51:13 +0200 Subject: [PATCH 06/20] updated CHANGELOG for 2.7.49 --- CHANGELOG-2.7.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG-2.7.md b/CHANGELOG-2.7.md index c343a87407..a209a91738 100644 --- a/CHANGELOG-2.7.md +++ b/CHANGELOG-2.7.md @@ -7,6 +7,11 @@ in 2.7 minor versions. To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v2.7.0...v2.7.1 +* 2.7.49 (2018-08-01) + + * security #cve-2018-14774 [HttpKernel] fix trusted headers management in HttpCache and InlineFragmentRenderer (nicolas-grekas) + * security #cve-2018-14773 [HttpFoundation] Remove support for legacy and risky HTTP headers (nicolas-grekas) + * 2.7.48 (2018-05-25) * bug #27359 [HttpFoundation] Fix perf issue during MimeTypeGuesser intialization (nicolas-grekas) From 62184c0a33a8970c31a20ded149e429479ab233f Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Wed, 1 Aug 2018 15:56:47 +0200 Subject: [PATCH 07/20] updated VERSION for 2.7.49 --- src/Symfony/Component/HttpKernel/Kernel.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index 9ec779c8ce..6106c9cf70 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -58,11 +58,11 @@ abstract class Kernel implements KernelInterface, TerminableInterface protected $startTime; protected $loadClassCache; - const VERSION = '2.7.48'; - const VERSION_ID = 20748; + const VERSION = '2.7.49'; + const VERSION_ID = 20749; const MAJOR_VERSION = 2; const MINOR_VERSION = 7; - const RELEASE_VERSION = 48; + const RELEASE_VERSION = 49; const EXTRA_VERSION = ''; const END_OF_MAINTENANCE = '05/2018'; From ced4201b43ffbfc86f8ea8a7f9a039955721dc63 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 2 Aug 2018 08:59:56 +0200 Subject: [PATCH 08/20] [2.7] Make CI green --- .travis.yml | 2 +- src/Symfony/Component/HttpFoundation/Response.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 25ca170da9..c0367f1072 100644 --- a/.travis.yml +++ b/.travis.yml @@ -144,7 +144,7 @@ before_install: tfold ext.apcu tpecl apcu-4.0.11 apcu.so $INI elif [[ ! $skip && $PHP = 7.* ]]; then tfold ext.apcu tpecl apcu-5.1.6 apcu.so $INI - tfold ext.mongodb tpecl mongodb-1.4.0RC1 mongodb.so $INI + tfold ext.mongodb tpecl mongodb-1.5.0 mongodb.so $INI fi install: diff --git a/src/Symfony/Component/HttpFoundation/Response.php b/src/Symfony/Component/HttpFoundation/Response.php index 13b85b15d4..0feaad2803 100644 --- a/src/Symfony/Component/HttpFoundation/Response.php +++ b/src/Symfony/Component/HttpFoundation/Response.php @@ -170,7 +170,7 @@ class Response 422 => 'Unprocessable Entity', // RFC4918 423 => 'Locked', // RFC4918 424 => 'Failed Dependency', // RFC4918 - 425 => 'Reserved for WebDAV advanced collections expired proposal', // RFC2817 + 425 => 'Too Early', // RFC-ietf-httpbis-replay-04 426 => 'Upgrade Required', // RFC2817 428 => 'Precondition Required', // RFC6585 429 => 'Too Many Requests', // RFC6585 From 548e9f71b7521082020b90a060c70e53830ba294 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 3 Aug 2018 12:00:22 +0200 Subject: [PATCH 09/20] minor #28114 [travis] merge "same Symfony version" jobs in one (nicolas-grekas) This PR was merged into the 2.8 branch. Discussion ---------- [travis] merge "same Symfony version" jobs in one | Q | A | ------------- | --- | Branch? | 2.8 | Bug fix? | no | New feature? | | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | - | License | MIT | Doc PR | - Allowing to consume fewer jobs and save the 1 to 2 minutes bootstrap time of workers. Commits ------- 9857ca07aa [travis] merge "same Symfony version" jobs in one --- .travis.yml | 132 ++++++++++-------- .../Tests/phpt/decorate_exception_hander.phpt | 3 +- .../Component/Process/Tests/ProcessTest.php | 6 - 3 files changed, 77 insertions(+), 64 deletions(-) diff --git a/.travis.yml b/.travis.yml index c0367f1072..d51b72b429 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,9 +22,7 @@ matrix: sudo: required group: edge - php: 5.4 - - php: 5.5 - - php: 5.6 - - php: 7.0 + env: php_extra="5.5 5.6 7.0" - php: 7.1 env: deps=high - php: 7.2 @@ -42,15 +40,26 @@ services: mongodb before_install: - | # General configuration + set -e stty cols 120 - PHP=$TRAVIS_PHP_VERSION [ -d ~/.composer ] || mkdir ~/.composer cp .composer/* ~/.composer/ export PHPUNIT=$(readlink -f ./phpunit) export PHPUNIT_X="$PHPUNIT --exclude-group tty,benchmark,intl-data" export COMPOSER_UP='composer update --no-progress --no-suggest --ansi' + export COMPONENTS=$(find src/Symfony -mindepth 3 -type f -name phpunit.xml.dist -printf '%h\n') + find ~/.phpenv -name xdebug.ini -delete - nanoseconds() { + if [[ $TRAVIS_PHP_VERSION = 5.* || $TRAVIS_PHP_VERSION = hhvm* ]]; then + composer () { + $HOME/.phpenv/versions/7.1/bin/composer config platform.php $(echo ' > $INI - echo memory_limit = -1 >> $INI - echo session.gc_probability = 0 >> $INI - echo opcache.enable_cli = 1 >> $INI - echo hhvm.jit = 0 >> $INI - echo apc.enable_cli = 1 >> $INI - [[ $PHP = 5.* ]] && echo extension = memcache.so >> $INI - if [[ $PHP = 5.* ]]; then - echo extension = mongo.so >> $INI - fi - # tpecl is a helper to compile and cache php extensions tpecl () { local ext_name=$1 @@ -114,6 +105,7 @@ before_install: if [[ -e $ext_cache/$ext_so ]]; then echo extension = $ext_cache/$ext_so >> $INI else + rm ~/.pearrc /tmp/pear 2>/dev/null || true mkdir -p $ext_cache echo yes | pecl install -f $ext_name && cp $ext_dir/$ext_so $ext_cache @@ -121,38 +113,62 @@ before_install: } export -f tpecl - # Matrix lines for intermediate PHP versions are skipped for pull requests - if [[ ! $deps && ! $PHP = ${MIN_PHP%.*} && ! $PHP = hhvm* && $TRAVIS_PULL_REQUEST != false ]]; then - deps=skip - skip=1 - else - COMPONENTS=$(find src/Symfony -mindepth 3 -type f -name phpunit.xml.dist -printf '%h\n') - fi - - | # Install sigchild-enabled PHP to test the Process component on the lowest PHP matrix line - if [[ ! $deps && $PHP = ${MIN_PHP%.*} && ! -d php-$MIN_PHP/sapi ]]; then + if [[ ! $deps && $TRAVIS_PHP_VERSION = ${MIN_PHP%.*} && ! -d php-$MIN_PHP/sapi ]]; then wget http://museum.php.net/php5/php-$MIN_PHP.tar.bz2 -O - | tar -xj && (cd php-$MIN_PHP && ./configure --enable-sigchild --enable-pcntl && make -j2) fi + - | + # php.ini configuration + for PHP in $TRAVIS_PHP_VERSION $php_extra; do + if [[ $PHP = hhvm* ]]; then + INI=/etc/hhvm/php.ini + else + phpenv global $PHP 2>/dev/null || (cd / && wget https://s3.amazonaws.com/travis-php-archives/binaries/ubuntu/14.04/x86_64/php-$PHP.tar.bz2 -O - | tar -xj) + INI=~/.phpenv/versions/$PHP/etc/conf.d/travis.ini + fi + echo date.timezone = Europe/Paris >> $INI + echo memory_limit = -1 >> $INI + echo session.gc_probability = 0 >> $INI + echo opcache.enable_cli = 1 >> $INI + echo hhvm.jit = 0 >> $INI + echo apc.enable_cli = 1 >> $INI + [[ $PHP = 5.* ]] && echo extension = memcache.so >> $INI + if [[ $PHP = 5.* ]]; then + echo extension = mongo.so >> $INI + fi + done + - | # Install extra PHP extensions - if [[ ! $skip && $PHP = 5.* ]]; then - ([[ $deps ]] || tfold ext.symfony_debug 'cd src/Symfony/Component/Debug/Resources/ext && phpize && ./configure && make && echo extension = $(pwd)/modules/symfony_debug.so >> '"$INI") - tfold ext.memcached tpecl memcached-2.1.0 memcached.so $INI - tfold ext.apcu tpecl apcu-4.0.11 apcu.so $INI - elif [[ ! $skip && $PHP = 7.* ]]; then - tfold ext.apcu tpecl apcu-5.1.6 apcu.so $INI - tfold ext.mongodb tpecl mongodb-1.5.0 mongodb.so $INI - fi + for PHP in $TRAVIS_PHP_VERSION $php_extra; do + if [[ $PHP = hhvm* ]]; then + continue + fi + export PHP=$PHP + phpenv global $PHP + INI=~/.phpenv/versions/$PHP/etc/conf.d/travis.ini + if [[ $PHP = 5.* ]]; then + tfold ext.memcached tpecl memcached-2.1.0 memcached.so $INI + tfold ext.apcu tpecl apcu-4.0.11 apcu.so $INI + [[ $deps ]] && continue + ext_cache=~/php-ext/$(php -r "echo basename(ini_get('extension_dir'));")/symfony_debug.so + [[ -e $ext_cache ]] || (tfold ext.symfony_debug "cd src/Symfony/Component/Debug/Resources/ext && phpize && ./configure && make && mv modules/symfony_debug.so $ext_cache && phpize --clean") + echo extension = $ext_cache >> $INI + elif [[ $PHP = 7.* ]]; then + tfold ext.apcu tpecl apcu-5.1.6 apcu.so $INI + tfold ext.mongodb tpecl mongodb-1.5.0 mongodb.so $INI + fi + done install: - | # Create local composer packages for each patched components and reference them in composer.json files when cross-testing components if [[ ! $deps ]]; then php .github/build-packages.php HEAD^ src/Symfony/Bridge/PhpUnit - elif [[ ! $skip ]]; then + else export SYMFONY_DEPRECATIONS_HELPER=weak && cp composer.json composer.json.orig && echo -e '{\n"require":{'"$(grep phpunit-bridge composer.json)"'"php":"*"},"minimum-stability":"dev"}' > composer.json && @@ -168,7 +184,7 @@ install: git fetch origin $SYMFONY_VERSION && git checkout -m FETCH_HEAD && COMPONENTS=$(find src/Symfony -mindepth 3 -type f -name phpunit.xml.dist -printf '%h\n') - elif [[ ! $skip ]]; then + else SYMFONY_VERSION=$(cat composer.json | grep '^ *"dev-master". *"[1-9]' | grep -o '[0-9.]*') fi @@ -177,24 +193,27 @@ install: [[ $deps = high && ${SYMFONY_VERSION%.*} != $(git show $(git ls-remote --heads | grep -FA1 /$SYMFONY_VERSION | tail -n 1):composer.json | grep '^ *"dev-master". *"[1-9]' | grep -o '[0-9]*' | head -n 1) ]] && LEGACY=,legacy export COMPOSER_ROOT_VERSION=$SYMFONY_VERSION.x-dev - if [[ ! $skip && $deps ]]; then mv composer.json.phpunit composer.json; fi + if [[ $deps ]]; then mv composer.json.phpunit composer.json; fi - if [[ ! $skip && $PHP = 7.* ]]; then - ([[ $deps ]] && cd src/Symfony/Component/HttpFoundation; composer require --dev --no-update mongodb/mongodb) - fi - - - if [[ ! $skip ]]; then $COMPOSER_UP; fi - - if [[ ! $skip ]]; then ./phpunit install; fi - | # phpinfo - if [[ ! $PHP = hhvm* ]]; then php -i; else hhvm --php -r 'print_r($_SERVER);print_r(ini_get_all());'; fi + if [[ ! $TRAVIS_PHP_VERSION = hhvm* ]]; then php -i; else hhvm --php -r 'print_r($_SERVER);print_r(ini_get_all());'; fi - | run_tests () { set -e - if [[ $skip ]]; then + export PHP=$1 + if [[ $PHP != $TRAVIS_PHP_VERSION && $TRAVIS_PULL_REQUEST != false ]]; then echo -e "\\n\\e[1;34mIntermediate PHP version $PHP is skipped for pull requests.\\e[0m" - elif [[ $deps = high ]]; then + break + fi + phpenv global ${PHP/hhvm*/hhvm} + tfold 'composer update' $COMPOSER_UP + tfold 'phpunit install' ./phpunit install + if [[ $PHP = 7.* ]]; then + ([[ $deps ]] && cd src/Symfony/Component/HttpFoundation; composer require --dev --no-update mongodb/mongodb) + fi + if [[ $deps = high ]]; then echo "$COMPONENTS" | parallel --gnu -j10% "tfold {} 'cd {} && $COMPOSER_UP && $PHPUNIT_X$LEGACY'" elif [[ $deps = low ]]; then echo "$COMPONENTS" | parallel --gnu -j10% "tfold {} 'cd {} && $COMPOSER_UP --prefer-lowest --prefer-stable && $PHPUNIT_X'" @@ -202,12 +221,13 @@ install: $PHPUNIT --exclude-group no-hhvm,benchmark,intl-data else echo "$COMPONENTS" | parallel --gnu "tfold {} $PHPUNIT_X {}" - tfold tty-group $PHPUNIT --group tty + tfold src/Symfony/Component/Console.tty $PHPUNIT src/Symfony/Component/Console --group tty if [[ $PHP = ${MIN_PHP%.*} ]]; then - echo -e "1\\n0" | xargs -I{} bash -c "tfold src/Symfony/Component/Process.sigchild{} ENHANCE_SIGCHLD={} php-$MIN_PHP/sapi/cli/php .phpunit/phpunit-4.8/phpunit --colors=always src/Symfony/Component/Process/" + export PHP=$MIN_PHP + echo -e "1\\n0" | xargs -I{} bash -c "tfold src/Symfony/Component/Process.sigchild{} SYMFONY_DEPRECATIONS_HELPER=weak ENHANCE_SIGCHLD={} php-$MIN_PHP/sapi/cli/php .phpunit/phpunit-4.8/phpunit --colors=always src/Symfony/Component/Process/" fi fi } script: - - (run_tests) + - for PHP in $TRAVIS_PHP_VERSION $php_extra; do (run_tests $PHP); done diff --git a/src/Symfony/Component/Debug/Tests/phpt/decorate_exception_hander.phpt b/src/Symfony/Component/Debug/Tests/phpt/decorate_exception_hander.phpt index 7ce7b9dc6f..1de9b29ac0 100644 --- a/src/Symfony/Component/Debug/Tests/phpt/decorate_exception_hander.phpt +++ b/src/Symfony/Component/Debug/Tests/phpt/decorate_exception_hander.phpt @@ -38,8 +38,7 @@ Did you forget a "use" statement for another namespace?" ["line":protected]=> int(%d) ["trace":"Exception":private]=> - array(0) { - } + array(%d) {%A} ["previous":"Exception":private]=> NULL ["severity":protected]=> diff --git a/src/Symfony/Component/Process/Tests/ProcessTest.php b/src/Symfony/Component/Process/Tests/ProcessTest.php index c614b3aa40..58d86ce357 100644 --- a/src/Symfony/Component/Process/Tests/ProcessTest.php +++ b/src/Symfony/Component/Process/Tests/ProcessTest.php @@ -439,9 +439,6 @@ class ProcessTest extends TestCase $this->assertGreaterThan(0, $process->getExitCode()); } - /** - * @group tty - */ public function testTTYCommand() { if ('\\' === DIRECTORY_SEPARATOR) { @@ -457,9 +454,6 @@ class ProcessTest extends TestCase $this->assertSame(Process::STATUS_TERMINATED, $process->getStatus()); } - /** - * @group tty - */ public function testTTYCommandExitCode() { if ('\\' === DIRECTORY_SEPARATOR) { From fa4d95a3ba2b879de47c4497ae4bb5dad32df663 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 3 Aug 2018 14:53:54 +0200 Subject: [PATCH 10/20] [travis] fix requiring mongodb/mongodb before composer up --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index d51b72b429..390621f402 100644 --- a/.travis.yml +++ b/.travis.yml @@ -208,11 +208,11 @@ install: break fi phpenv global ${PHP/hhvm*/hhvm} - tfold 'composer update' $COMPOSER_UP - tfold 'phpunit install' ./phpunit install if [[ $PHP = 7.* ]]; then ([[ $deps ]] && cd src/Symfony/Component/HttpFoundation; composer require --dev --no-update mongodb/mongodb) fi + tfold 'composer update' $COMPOSER_UP + tfold 'phpunit install' ./phpunit install if [[ $deps = high ]]; then echo "$COMPONENTS" | parallel --gnu -j10% "tfold {} 'cd {} && $COMPOSER_UP && $PHPUNIT_X$LEGACY'" elif [[ $deps = low ]]; then From 79ce6eae8fe148e3d911efe345156ac39910f1a8 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 3 Aug 2018 15:09:12 +0200 Subject: [PATCH 11/20] fix ci --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 390621f402..882701f0db 100644 --- a/.travis.yml +++ b/.travis.yml @@ -209,7 +209,7 @@ install: fi phpenv global ${PHP/hhvm*/hhvm} if [[ $PHP = 7.* ]]; then - ([[ $deps ]] && cd src/Symfony/Component/HttpFoundation; composer require --dev --no-update mongodb/mongodb) + ([[ $deps ]] && cd src/Symfony/Component/HttpFoundation; composer config platform.ext-mongodb 1.5.0; composer require --dev --no-update mongodb/mongodb) fi tfold 'composer update' $COMPOSER_UP tfold 'phpunit install' ./phpunit install From 4b13fc5d9e887fecc795843872265148f720796e Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 7 Aug 2018 11:32:16 +0200 Subject: [PATCH 12/20] minor #28146 [travis] cache composer.lock files for deps=low (nicolas-grekas) This PR was merged into the 2.8 branch. Discussion ---------- [travis] cache composer.lock files for deps=low | Q | A | ------------- | --- | Branch? | 2.8 | Bug fix? | no | New feature? | no | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | - | License | MIT | Doc PR | - I just realized that the resolved package versions for lowest deps depends only on the root composer.json, and not on transitive deps. This means we can cache the lock files and save ~10 minutes required to resolve the lowest deps of the SecurityBundle. Commits ------- caaa74cd9b [travis] cache composer.lock files for deps=low --- .github/rm-invalid-lowest-lock-files.php | 80 ++++++++++++++++++++++++ .travis.yml | 6 +- 2 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 .github/rm-invalid-lowest-lock-files.php diff --git a/.github/rm-invalid-lowest-lock-files.php b/.github/rm-invalid-lowest-lock-files.php new file mode 100644 index 0000000000..85d582fe62 --- /dev/null +++ b/.github/rm-invalid-lowest-lock-files.php @@ -0,0 +1,80 @@ + Date: Tue, 7 Aug 2018 17:19:50 +0200 Subject: [PATCH 13/20] [travis] ignore ordering when validating composer.lock files for deps=low --- .github/rm-invalid-lowest-lock-files.php | 27 +++++++++++++++--------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/.github/rm-invalid-lowest-lock-files.php b/.github/rm-invalid-lowest-lock-files.php index 85d582fe62..5515238d9a 100644 --- a/.github/rm-invalid-lowest-lock-files.php +++ b/.github/rm-invalid-lowest-lock-files.php @@ -3,7 +3,7 @@ array_shift($_SERVER['argv']); $dirs = $_SERVER['argv']; -function getContentHash($composerJson) +function getRelevantContent(array $composerJson) { $relevantKeys = array( 'name', @@ -27,12 +27,18 @@ function getContentHash($composerJson) $relevantContent['config']['platform'] = $composerJson['config']['platform']; } + return $relevantContent; +} + +function getContentHash(array $composerJson) +{ + $relevantContent = getRelevantContent($composerJson); ksort($relevantContent); return md5(json_encode($relevantContent)); } -$composerLocks = array(); +$composerJsons = array(); foreach ($dirs as $dir) { if (!file_exists($dir.'/composer.lock') || !$composerLock = @json_decode(file_get_contents($dir.'/composer.lock'), true)) { @@ -50,28 +56,29 @@ foreach ($dirs as $dir) { @unlink($dir.'/composer.lock'); continue; } - $composerLocks[$composerJson['name']] = array($dir, $composerLock, $composerJson); + $composerJsons[$composerJson['name']] = array($dir, $composerLock['packages'], getRelevantContent($composerJson)); } -foreach ($composerLocks as list($dir, $composerLock)) { - foreach ($composerLock['packages'] as $composerJson) { - if (0 !== strpos($version = $composerJson['version'], 'dev-') && '-dev' !== substr($version, -4)) { +foreach ($composerJsons as list($dir, $lockedPackages)) { + foreach ($lockedPackages as $lockedJson) { + if (0 !== strpos($version = $lockedJson['version'], 'dev-') && '-dev' !== substr($version, -4)) { continue; } - if (!isset($composerLocks[$name = $composerJson['name']])) { + if (!isset($composerJsons[$name = $lockedJson['name']])) { echo "$dir/composer.lock references missing $name.\n"; @unlink($dir.'/composer.lock'); continue 2; } foreach (array('minimum-stability', 'prefer-stable', 'repositories') as $key) { - if (array_key_exists($key, $composerLocks[$name][2])) { - $composerJson[$key] = $composerLocks[$name][2][$key]; + if (array_key_exists($key, $composerJsons[$name][2])) { + $lockedJson[$key] = $composerJsons[$name][2][$key]; } } - if (getContentHash($composerJson) !== $composerLocks[$name][1]['content-hash']) { + // use weak comparison to ignore ordering + if (getRelevantContent($lockedJson) != $composerJsons[$name][2]) { echo "$dir/composer.lock is not in sync with $name.\n"; @unlink($dir.'/composer.lock'); continue 2; From e61cb8e14ab302312d6b0cbfd5d560d7ec64ac51 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sat, 18 Aug 2018 18:26:55 +0200 Subject: [PATCH 14/20] minor #28199 [travis][appveyor] use symfony/flex to accelerate builds (nicolas-grekas) This PR was merged into the 2.8 branch. Discussion ---------- [travis][appveyor] use symfony/flex to accelerate builds | Q | A | ------------- | --- | Branch? | 2.8 | Bug fix? | no | New feature? | no | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | - | License | MIT | Doc PR | - Playing with https://github.com/symfony/flex/pull/409 The optimization is required because appveyor is transiently failing with OOM errors, see e.g. https://ci.appveyor.com/project/fabpot/symfony/build/1.0.39377 Commits ------- 940ec8f2d5 [travis][appveyor] use symfony/flex to accelerate builds --- .travis.yml | 11 ++++++++++- appveyor.yml | 4 +++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 007101fcc4..173e3792af 100644 --- a/.travis.yml +++ b/.travis.yml @@ -52,7 +52,7 @@ before_install: if [[ $TRAVIS_PHP_VERSION = 5.* || $TRAVIS_PHP_VERSION = hhvm* ]]; then composer () { - $HOME/.phpenv/versions/7.1/bin/composer config platform.php $(echo ' =2.3' + else + export SYMFONY_REQUIRE=">=$SYMFONY_VERSION" + fi + composer global require symfony/flex dev-master + - | # Legacy tests are skipped when deps=high and when the current branch version has not the same major version number than the next one [[ $deps = high && ${SYMFONY_VERSION%.*} != $(git show $(git ls-remote --heads | grep -FA1 /$SYMFONY_VERSION | tail -n 1):composer.json | grep '^ *"dev-master". *"[1-9]' | grep -o '[0-9]*' | head -n 1) ]] && LEGACY=,legacy diff --git a/appveyor.yml b/appveyor.yml index 5cbbb67386..6e8a7a1a06 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -10,6 +10,7 @@ init: - SET PATH=c:\php;%PATH% - SET COMPOSER_NO_INTERACTION=1 - SET SYMFONY_DEPRECATIONS_HELPER=strict + - SET "SYMFONY_REQUIRE=>=2.7" - SET ANSICON=121x90 (121x90) - SET SYMFONY_PHPUNIT_VERSION=4.8 - REG ADD "HKEY_CURRENT_USER\Software\Microsoft\Command Processor" /v DelayedExpansion /t REG_DWORD /d 1 /f @@ -50,9 +51,10 @@ install: - copy /Y php.ini-min php.ini - echo extension=php_openssl.dll >> php.ini - cd c:\projects\symfony - - IF NOT EXIST composer.phar (appveyor DownloadFile https://getcomposer.org/download/1.3.0/composer.phar) + - IF NOT EXIST composer.phar (appveyor DownloadFile https://github.com/composer/composer/releases/download/1.7.1/composer.phar) - php composer.phar self-update - copy /Y .composer\* %APPDATA%\Composer\ + - php composer.phar global require --no-progress symfony/flex dev-master - php .github/build-packages.php "HEAD^" src\Symfony\Bridge\PhpUnit - IF %APPVEYOR_REPO_BRANCH%==master (SET COMPOSER_ROOT_VERSION=dev-master) ELSE (SET COMPOSER_ROOT_VERSION=%APPVEYOR_REPO_BRANCH%.x-dev) - php composer.phar config platform.php 5.3.9 From caf69aa3c4afd0ef8c5e5f63ef6d109aed9c5307 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sun, 19 Aug 2018 11:09:49 +0200 Subject: [PATCH 15/20] [travis] fix composer.lock invalidation for deps=low --- .github/rm-invalid-lowest-lock-files.php | 64 ++++++++++++++++++++++++ .travis.yml | 2 +- appveyor.yml | 2 +- 3 files changed, 66 insertions(+), 2 deletions(-) diff --git a/.github/rm-invalid-lowest-lock-files.php b/.github/rm-invalid-lowest-lock-files.php index 5515238d9a..2b037519cf 100644 --- a/.github/rm-invalid-lowest-lock-files.php +++ b/.github/rm-invalid-lowest-lock-files.php @@ -1,5 +1,11 @@ $dirsByCommit) { + $chs[] = $ch = array(curl_init(), fopen($_SERVER['HOME'].'/.cache/composer/repo/https---repo.packagist.org/provider-'.strtr($name, '/', '$').'.json', 'wb')); + curl_setopt($ch[0], CURLOPT_URL, 'https://repo.packagist.org/p/'.$name.'.json'); + curl_setopt($ch[0], CURLOPT_FILE, $ch[1]); + curl_setopt($ch[0], CURLOPT_SHARE, $sh); + curl_multi_add_handle($mh, $ch[0]); +} + +do { + curl_multi_exec($mh, $active); + curl_multi_select($mh); +} while ($active); + +foreach ($chs as list($ch, $fd)) { + curl_multi_remove_handle($mh, $ch); + curl_close($ch); + fclose($fd); +} + +foreach ($referencedCommits as $name => $dirsByCommit) { + $repo = file_get_contents($_SERVER['HOME'].'/.cache/composer/repo/https---repo.packagist.org/provider-'.strtr($name, '/', '$').'.json'); + $repo = json_decode($repo, true); + + foreach ($repo['packages'][$name] as $version) { + unset($referencedCommits[$name][$version['source']['reference']]); + } +} + +foreach ($referencedCommits as $name => $dirsByCommit) { + foreach ($dirsByCommit as $dirs) { + foreach ($dirs as $dir) { + if (file_exists($dir.'/composer.lock')) { + echo "$dir/composer.lock references old commit for $name.\n"; + @unlink($dir.'/composer.lock'); + } + } } } diff --git a/.travis.yml b/.travis.yml index 173e3792af..6896ac7a16 100644 --- a/.travis.yml +++ b/.travis.yml @@ -195,7 +195,7 @@ install: else export SYMFONY_REQUIRE=">=$SYMFONY_VERSION" fi - composer global require symfony/flex dev-master + composer global require --no-progress --no-scripts --no-plugins symfony/flex dev-master - | # Legacy tests are skipped when deps=high and when the current branch version has not the same major version number than the next one diff --git a/appveyor.yml b/appveyor.yml index 6e8a7a1a06..4781687ff6 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -54,7 +54,7 @@ install: - IF NOT EXIST composer.phar (appveyor DownloadFile https://github.com/composer/composer/releases/download/1.7.1/composer.phar) - php composer.phar self-update - copy /Y .composer\* %APPDATA%\Composer\ - - php composer.phar global require --no-progress symfony/flex dev-master + - php composer.phar global require --no-progress --no-scripts --no-plugins symfony/flex dev-master - php .github/build-packages.php "HEAD^" src\Symfony\Bridge\PhpUnit - IF %APPVEYOR_REPO_BRANCH%==master (SET COMPOSER_ROOT_VERSION=dev-master) ELSE (SET COMPOSER_ROOT_VERSION=%APPVEYOR_REPO_BRANCH%.x-dev) - php composer.phar config platform.php 5.3.9 From 74aef7a3ec98c587114931d1d5796f96d2ab9095 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sun, 19 Aug 2018 14:57:42 +0200 Subject: [PATCH 16/20] [travis] fix composer.lock invalidation for PRs patching several components --- .github/rm-invalid-lowest-lock-files.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/rm-invalid-lowest-lock-files.php b/.github/rm-invalid-lowest-lock-files.php index 2b037519cf..aca35ca64d 100644 --- a/.github/rm-invalid-lowest-lock-files.php +++ b/.github/rm-invalid-lowest-lock-files.php @@ -79,7 +79,13 @@ foreach ($composerJsons as list($dir, $lockedPackages)) { continue 2; } - foreach (array('minimum-stability', 'prefer-stable', 'repositories') as $key) { + if (isset($composerJsons[$name][2]['repositories']) && !isset($lockedJson[$key]['repositories'])) { + // the locked package has been patched locally but the lock references a commit, + // which means the referencing package itself is not modified + continue; + } + + foreach (array('minimum-stability', 'prefer-stable') as $key) { if (array_key_exists($key, $composerJsons[$name][2])) { $lockedJson[$key] = $composerJsons[$name][2][$key]; } @@ -92,7 +98,9 @@ foreach ($composerJsons as list($dir, $lockedPackages)) { continue 2; } - $referencedCommits[$name][$lockedJson['source']['reference']][] = $dir; + if ($lockedJson['dist']['reference']) { + $referencedCommits[$name][$lockedJson['dist']['reference']][] = $dir; + } } } From c40cf26c5fdd7f357d561f7ffe7e6597c59a3d47 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 24 Aug 2018 14:40:49 +0200 Subject: [PATCH 17/20] minor #28258 [travis] fix composer.lock invalidation for deps=low (nicolas-grekas) This PR was merged into the 2.8 branch. Discussion ---------- [travis] fix composer.lock invalidation for deps=low | Q | A | ------------- | --- | Branch? | 2.8 | Bug fix? | no | New feature? | no | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | - | License | MIT | Doc PR | - Commits ------- 41ffba1916 [travis] fix composer.lock invalidation for deps=low --- .github/rm-invalid-lowest-lock-files.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/rm-invalid-lowest-lock-files.php b/.github/rm-invalid-lowest-lock-files.php index aca35ca64d..c714632621 100644 --- a/.github/rm-invalid-lowest-lock-files.php +++ b/.github/rm-invalid-lowest-lock-files.php @@ -48,7 +48,6 @@ $composerJsons = array(); foreach ($dirs as $dir) { if (!file_exists($dir.'/composer.lock') || !$composerLock = @json_decode(file_get_contents($dir.'/composer.lock'), true)) { - echo "$dir/composer.lock not found or invalid.\n"; @unlink($dir.'/composer.lock'); continue; } @@ -62,7 +61,8 @@ foreach ($dirs as $dir) { @unlink($dir.'/composer.lock'); continue; } - $composerJsons[$composerJson['name']] = array($dir, $composerLock['packages'], getRelevantContent($composerJson)); + $composerLock += array('packages' => array(), 'packages-dev' => array()); + $composerJsons[$composerJson['name']] = array($dir, $composerLock['packages'] + $composerLock['packages-dev'], getRelevantContent($composerJson)); } $referencedCommits = array(); From cb8302cb7676fda28d2ddf6d0f0068cc557f17c3 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 21 Nov 2018 12:58:55 +0100 Subject: [PATCH 18/20] Fix CI --- appveyor.yml => .appveyor.yml | 2 +- .github/build-packages.php | 15 +++++++++------ .github/rm-invalid-lowest-lock-files.php | 5 ++--- .travis.yml | 13 ++++++++----- .../Doctrine/Tests/Form/Type/EntityTypeTest.php | 5 +++++ 5 files changed, 25 insertions(+), 15 deletions(-) rename appveyor.yml => .appveyor.yml (99%) diff --git a/appveyor.yml b/.appveyor.yml similarity index 99% rename from appveyor.yml rename to .appveyor.yml index 4781687ff6..1383a1070d 100644 --- a/appveyor.yml +++ b/.appveyor.yml @@ -1,5 +1,5 @@ build: false -clone_depth: 1 +clone_depth: 2 clone_folder: c:\projects\symfony cache: diff --git a/.github/build-packages.php b/.github/build-packages.php index b67a699609..b09cea2fe2 100644 --- a/.github/build-packages.php +++ b/.github/build-packages.php @@ -6,9 +6,14 @@ if (3 > $_SERVER['argc']) { } chdir(dirname(__DIR__)); +$json = ltrim(file_get_contents('composer.json')); +if ($json !== $package = preg_replace('/\n "repositories": \[\n.*?\n \],/s', '', $json)) { + file_put_contents('composer.json', $package); +} + $dirs = $_SERVER['argv']; array_shift($dirs); -$mergeBase = trim(shell_exec(sprintf('git merge-base %s HEAD', array_shift($dirs)))); +$mergeBase = trim(shell_exec(sprintf('git merge-base "%s" HEAD', array_shift($dirs)))); $packages = array(); $flags = \PHP_VERSION_ID >= 50400 ? JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE : 0; @@ -49,7 +54,7 @@ foreach ($dirs as $k => $dir) { $packages[$package->name][$package->version] = $package; - $versions = file_get_contents('https://packagist.org/p/'.$package->name.'.json'); + $versions = @file_get_contents('https://repo.packagist.org/p/'.$package->name.'.json') ?: sprintf('{"packages":{"%s":{"dev-master":%s}}}', $package->name, file_get_contents($dir.'/composer.json')); $versions = json_decode($versions)->packages->{$package->name}; if ($package->version === str_replace('-dev', '.x-dev', $versions->{'dev-master'}->extra->{'branch-alias'}->{'dev-master'})) { @@ -74,8 +79,6 @@ if ($dirs) { 'type' => 'composer', 'url' => 'file://'.str_replace(DIRECTORY_SEPARATOR, '/', dirname(__DIR__)).'/', )); - if (false === strpos($json, "\n \"repositories\": [\n")) { - $json = rtrim(json_encode(array('repositories' => $package->repositories), $flags), "\n}").','.substr($json, 1); - file_put_contents('composer.json', $json); - } + $json = rtrim(json_encode(array('repositories' => $package->repositories), $flags), "\n}").','.substr($json, 1); + file_put_contents('composer.json', $json); } diff --git a/.github/rm-invalid-lowest-lock-files.php b/.github/rm-invalid-lowest-lock-files.php index c714632621..c036fd356f 100644 --- a/.github/rm-invalid-lowest-lock-files.php +++ b/.github/rm-invalid-lowest-lock-files.php @@ -79,7 +79,7 @@ foreach ($composerJsons as list($dir, $lockedPackages)) { continue 2; } - if (isset($composerJsons[$name][2]['repositories']) && !isset($lockedJson[$key]['repositories'])) { + if (isset($composerJsons[$name][2]['repositories']) && !isset($lockedJson['repositories'])) { // the locked package has been patched locally but the lock references a commit, // which means the referencing package itself is not modified continue; @@ -104,8 +104,7 @@ foreach ($composerJsons as list($dir, $lockedPackages)) { } } -if (!$referencedCommits || (isset($_SERVER['TRAVIS_PULL_REQUEST']) && 'false' !== $_SERVER['TRAVIS_PULL_REQUEST'])) { - // cached commits cannot be stale for PRs +if (!$referencedCommits) { return; } diff --git a/.travis.yml b/.travis.yml index 6896ac7a16..f359dea238 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,9 @@ language: php dist: trusty -sudo: false git: - depth: 1 + depth: 2 addons: apt_packages: @@ -159,7 +158,7 @@ before_install: echo extension = $ext_cache >> $INI elif [[ $PHP = 7.* ]]; then tfold ext.apcu tpecl apcu-5.1.6 apcu.so $INI - tfold ext.mongodb tpecl mongodb-1.5.0 mongodb.so $INI + tfold ext.mongodb tpecl mongodb-1.5.2 mongodb.so $INI fi done @@ -218,10 +217,14 @@ install: fi phpenv global ${PHP/hhvm*/hhvm} if [[ $PHP = 7.* ]]; then - ([[ $deps ]] && cd src/Symfony/Component/HttpFoundation; composer config platform.ext-mongodb 1.5.0; composer require --dev --no-update mongodb/mongodb) + ([[ $deps ]] && cd src/Symfony/Component/HttpFoundation; composer config platform.ext-mongodb 1.5.2; composer require --dev --no-update mongodb/mongodb) fi tfold 'composer update' $COMPOSER_UP - tfold 'phpunit install' ./phpunit install + if [[ $TRAVIS_PHP_VERSION = 5.* || $TRAVIS_PHP_VERSION = hhvm* ]]; then + tfold 'phpunit install' 'composer global remove symfony/flex && ./phpunit install && composer global require --no-progress --no-scripts --no-plugins symfony/flex dev-master' + else + tfold 'phpunit install' ./phpunit install + fi if [[ $deps = high ]]; then echo "$COMPONENTS" | parallel --gnu -j10% "tfold {} 'cd {} && $COMPOSER_UP && $PHPUNIT_X$LEGACY'" elif [[ $deps = low ]]; then diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php index c1dfa2ad36..59b453c439 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php @@ -1509,4 +1509,9 @@ class EntityTypeTest extends BaseTypeTest $this->assertEquals(array(), $form->getNormData()); $this->assertSame(array(), $form->getViewData(), 'View data is always an array'); } + + public function testSubmitNullUsesDefaultEmptyData($emptyData = 'empty', $expectedData = null) + { + $this->markTestIncomplete('Added in symfony/form 2.8.'); + } } From 205a44ea7db84e6408c7bdef0b89b071be235f28 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 21 Nov 2018 13:01:00 +0100 Subject: [PATCH 19/20] [Form] Filter file uploads out of regular form types --- .../Form/Extension/Core/Type/FileType.php | 1 + .../Form/Extension/Core/Type/FormType.php | 1 + src/Symfony/Component/Form/Form.php | 9 +++++++++ .../Component/Form/Tests/CompoundFormTest.php | 17 ++++++++++++++++- 4 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/Form/Extension/Core/Type/FileType.php b/src/Symfony/Component/Form/Extension/Core/Type/FileType.php index 321cbc0369..f7f7fe72db 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/FileType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/FileType.php @@ -105,6 +105,7 @@ class FileType extends AbstractType 'data_class' => $dataClass, 'empty_data' => $emptyData, 'multiple' => false, + 'allow_file_upload' => true, )); } diff --git a/src/Symfony/Component/Form/Extension/Core/Type/FormType.php b/src/Symfony/Component/Form/Extension/Core/Type/FormType.php index 9c5642b116..2929071655 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/FormType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/FormType.php @@ -213,6 +213,7 @@ class FormType extends BaseType 'attr' => $defaultAttr, 'post_max_size_message' => 'The uploaded file was too large. Please try to upload a smaller file.', 'upload_max_size_message' => $uploadMaxSizeMessage, // internal + 'allow_file_upload' => false, )); $resolver->setAllowedTypes('label_attr', 'array'); diff --git a/src/Symfony/Component/Form/Form.php b/src/Symfony/Component/Form/Form.php index ec4f96783c..a4c25a8a71 100644 --- a/src/Symfony/Component/Form/Form.php +++ b/src/Symfony/Component/Form/Form.php @@ -541,6 +541,11 @@ class Form implements \IteratorAggregate, FormInterface $submittedData = null; } elseif (is_scalar($submittedData)) { $submittedData = (string) $submittedData; + } elseif ($this->config->getOption('allow_file_upload')) { + // no-op + } elseif ($this->config->getRequestHandler()->isFileUpload($submittedData)) { + $submittedData = null; + $this->transformationFailure = new TransformationFailedException('Submitted data was expected to be text or number, file upload given.'); } $dispatcher = $this->config->getEventDispatcher(); @@ -550,6 +555,10 @@ class Form implements \IteratorAggregate, FormInterface $viewData = null; try { + if (null !== $this->transformationFailure) { + throw $this->transformationFailure; + } + // Hook to change content of the data submitted by the browser if ($dispatcher->hasListeners(FormEvents::PRE_SUBMIT)) { $event = new FormEvent($this, $submittedData); diff --git a/src/Symfony/Component/Form/Tests/CompoundFormTest.php b/src/Symfony/Component/Form/Tests/CompoundFormTest.php index 7975570ccc..96f2a7cf55 100644 --- a/src/Symfony/Component/Form/Tests/CompoundFormTest.php +++ b/src/Symfony/Component/Form/Tests/CompoundFormTest.php @@ -712,7 +712,7 @@ class CompoundFormTest extends AbstractFormTest 'REQUEST_METHOD' => $method, )); - $form = $this->getBuilder('image') + $form = $this->getBuilder('image', null, null, array('allow_file_upload' => true)) ->setMethod($method) ->setRequestHandler(new HttpFoundationRequestHandler()) ->getForm(); @@ -1088,6 +1088,21 @@ class CompoundFormTest extends AbstractFormTest $this->assertFalse($submit->isSubmitted()); } + public function testFileUpload() + { + $reqHandler = new HttpFoundationRequestHandler(); + $this->form->add($this->getBuilder('foo')->setRequestHandler($reqHandler)->getForm()); + $this->form->add($this->getBuilder('bar')->setRequestHandler($reqHandler)->getForm()); + + $this->form->submit(array( + 'foo' => 'Foo', + 'bar' => new UploadedFile(__FILE__, 'upload.png', 'image/png', 123, UPLOAD_ERR_OK), + )); + + $this->assertSame('Submitted data was expected to be text or number, file upload given.', $this->form->get('bar')->getTransformationFailure()->getMessage()); + $this->assertNull($this->form->get('bar')->getData()); + } + protected function createForm() { return $this->getBuilder() From 99a0cec0a6be39ce5ef38386e57339603b33ee5b Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Thu, 13 Sep 2018 19:04:50 +0200 Subject: [PATCH 20/20] [Security\Http] detect bad redirect targets using backslashes --- .../Component/Security/Http/HttpUtils.php | 2 +- .../Security/Http/Tests/HttpUtilsTest.php | 18 ++++++++++++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Component/Security/Http/HttpUtils.php b/src/Symfony/Component/Security/Http/HttpUtils.php index 30071b880d..a16a1489e4 100644 --- a/src/Symfony/Component/Security/Http/HttpUtils.php +++ b/src/Symfony/Component/Security/Http/HttpUtils.php @@ -59,7 +59,7 @@ class HttpUtils */ public function createRedirectResponse(Request $request, $path, $status = 302) { - if (null !== $this->domainRegexp && preg_match('#^https?://[^/]++#i', $path, $host) && !preg_match(sprintf($this->domainRegexp, preg_quote($request->getHttpHost())), $host[0])) { + if (null !== $this->domainRegexp && preg_match('#^https?:[/\\\\]{2,}+[^/]++#i', $path, $host) && !preg_match(sprintf($this->domainRegexp, preg_quote($request->getHttpHost())), $host[0])) { $path = '/'; } diff --git a/src/Symfony/Component/Security/Http/Tests/HttpUtilsTest.php b/src/Symfony/Component/Security/Http/Tests/HttpUtilsTest.php index 352f8977e7..2ab000dceb 100644 --- a/src/Symfony/Component/Security/Http/Tests/HttpUtilsTest.php +++ b/src/Symfony/Component/Security/Http/Tests/HttpUtilsTest.php @@ -54,14 +54,28 @@ class HttpUtilsTest extends TestCase $this->assertTrue($response->isRedirect('http://localhost/blog')); } - public function testCreateRedirectResponseWithBadRequestsDomain() + /** + * @dataProvider badRequestDomainUrls + */ + public function testCreateRedirectResponseWithBadRequestsDomain($url) { $utils = new HttpUtils($this->getUrlGenerator(), null, '#^https?://%s$#i'); - $response = $utils->createRedirectResponse($this->getRequest(), 'http://pirate.net/foo'); + $response = $utils->createRedirectResponse($this->getRequest(), $url); $this->assertTrue($response->isRedirect('http://localhost/')); } + public function badRequestDomainUrls() + { + return array( + array('http://pirate.net/foo'), + array('http:\\\\pirate.net/foo'), + array('http:/\\pirate.net/foo'), + array('http:\\/pirate.net/foo'), + array('http://////pirate.net/foo'), + ); + } + public function testCreateRedirectResponseWithProtocolRelativeTarget() { $utils = new HttpUtils($this->getUrlGenerator(), null, '#^https?://%s$#i');