Merge branch '3.4' into 4.0

* 3.4:
  [HttpKernel] Fixed invalid REMOTE_ADDR in inline subrequest when configuring trusted proxy with subnet
  [FrameworkBundle] fixed guard event names for transitions
  [DI] Improve class named servics error message
  [HttpFoundation] fixed using _method parameter with invalid type
  [Intl] Replace svn with git in the icu data update script
  [HttpFoundation] Fix Cookie::isCleared
This commit is contained in:
Nicolas Grekas 2018-08-01 10:23:45 +02:00
commit e0ce427aef
17 changed files with 307 additions and 271 deletions

View File

@ -525,7 +525,9 @@ class FrameworkExtension extends Extension
$guard = new Definition(Workflow\EventListener\GuardListener::class);
$guard->setPrivate(true);
$configuration = array();
foreach ($workflow['transitions'] as $transitionName => $config) {
foreach ($workflow['transitions'] as $config) {
$transitionName = $config['name'];
if (!isset($config['guard'])) {
continue;
}

View File

@ -48,6 +48,15 @@ class CheckDefinitionValidityPass implements CompilerPassInterface
throw new RuntimeException(sprintf('Please add the class to service "%s" even if it is constructed by a factory since we might need to add method calls based on compile-time checks.', $id));
}
if (class_exists($id) || interface_exists($id, false)) {
if (0 === strpos($id, '\\') && 1 < substr_count($id, '\\')) {
throw new RuntimeException(sprintf(
'The definition for "%s" has no class attribute, and appears to reference a class or interface. '
.'Please specify the class attribute explicitly or remove the leading backslash by renaming '
.'the service to "%s" to get rid of this error.',
$id, substr($id, 1)
));
}
throw new RuntimeException(sprintf(
'The definition for "%s" has no class attribute, and appears to reference a '
.'class or interface in the global namespace. Leaving out the "class" attribute '

View File

@ -1238,6 +1238,30 @@ class ContainerBuilderTest extends TestCase
$container->compile();
}
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
* @expectedExceptionMessage The definition for "\DateTime" has no class attribute, and appears to reference a class or interface in the global namespace.
*/
public function testNoClassFromGlobalNamespaceClassIdWithLeadingSlash()
{
$container = new ContainerBuilder();
$container->register('\\'.\DateTime::class);
$container->compile();
}
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
* @expectedExceptionMessage The definition for "\Symfony\Component\DependencyInjection\Tests\FooClass" has no class attribute, and appears to reference a class or interface. Please specify the class attribute explicitly or remove the leading backslash by renaming the service to "Symfony\Component\DependencyInjection\Tests\FooClass" to get rid of this error.
*/
public function testNoClassFromNamespaceClassIdWithLeadingSlash()
{
$container = new ContainerBuilder();
$container->register('\\'.FooClass::class);
$container->compile();
}
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
* @expectedExceptionMessage The definition for "123_abc" has no class.

View File

@ -266,7 +266,7 @@ class Cookie
*/
public function isCleared()
{
return $this->expire < time();
return 0 !== $this->expire && $this->expire < time();
}
/**

View File

@ -1221,7 +1221,10 @@ class Request
if ($method = $this->headers->get('X-HTTP-METHOD-OVERRIDE')) {
$this->method = strtoupper($method);
} elseif (self::$httpMethodParameterOverride) {
$this->method = strtoupper($this->request->get('_method', $this->query->get('_method', 'POST')));
$method = $this->request->get('_method', $this->query->get('_method', 'POST'));
if (\is_string($method)) {
$this->method = strtoupper($method);
}
}
}
}

View File

@ -154,6 +154,18 @@ class CookieTest extends TestCase
$cookie = new Cookie('foo', 'bar', time() - 20);
$this->assertTrue($cookie->isCleared(), '->isCleared() returns true if the cookie has expired');
$cookie = new Cookie('foo', 'bar');
$this->assertFalse($cookie->isCleared());
$cookie = new Cookie('foo', 'bar', 0);
$this->assertFalse($cookie->isCleared());
$cookie = new Cookie('foo', 'bar', -1);
$this->assertFalse($cookie->isCleared());
}
public function testToString()

View File

@ -848,6 +848,11 @@ class RequestTest extends TestCase
$request->setMethod('POST');
$request->headers->set('X-HTTP-METHOD-OVERRIDE', 'delete');
$this->assertEquals('DELETE', $request->getMethod(), '->getMethod() returns the method from X-HTTP-Method-Override if defined and POST');
$request = new Request();
$request->setMethod('POST');
$request->query->set('_method', array('delete', 'patch'));
$this->assertSame('POST', $request->getMethod(), '->getMethod() returns the request method if invalid type is defined in query');
}
/**

View File

@ -115,8 +115,7 @@ class InlineFragmentRenderer extends RoutableFragmentRenderer
$server['HTTP_X_FORWARDED_FOR'] = ($currentXForwardedFor ? $currentXForwardedFor.', ' : '').$request->getClientIp();
}
$trustedProxies = Request::getTrustedProxies();
$server['REMOTE_ADDR'] = $trustedProxies ? reset($trustedProxies) : '127.0.0.1';
$server['REMOTE_ADDR'] = $this->resolveTrustedProxy();
unset($server['HTTP_IF_MODIFIED_SINCE']);
unset($server['HTTP_IF_NONE_MATCH']);
@ -133,6 +132,17 @@ class InlineFragmentRenderer extends RoutableFragmentRenderer
return $subRequest;
}
private function resolveTrustedProxy()
{
if (!$trustedProxies = Request::getTrustedProxies()) {
return '127.0.0.1';
}
$firstTrustedProxy = reset($trustedProxies);
return false !== ($i = strpos($firstTrustedProxy, '/')) ? substr($firstTrustedProxy, 0, $i) : $firstTrustedProxy;
}
/**
* {@inheritdoc}
*/

View File

@ -195,6 +195,25 @@ class InlineFragmentRendererTest extends TestCase
Request::setTrustedProxies(array(), -1);
}
public function testIpAddressOfRangedTrustedProxyIsSetAsRemote()
{
$expectedSubRequest = Request::create('/');
$expectedSubRequest->headers->set('Surrogate-Capability', 'abc="ESI/1.0"');
$expectedSubRequest->server->set('REMOTE_ADDR', '1.1.1.1');
$expectedSubRequest->headers->set('x-forwarded-for', array('127.0.0.1'));
$expectedSubRequest->server->set('HTTP_X_FORWARDED_FOR', '127.0.0.1');
Request::setTrustedProxies(array('1.1.1.1/24'), -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(), -1);
}
/**
* Creates a Kernel expecting a request equals to $request
* Allows delta in comparison in case REQUEST_TIME changed by 1 second.

View File

@ -1,20 +0,0 @@
; ICU data source URLs
; We use always the latest release of a major version.
4.0 = http://source.icu-project.org/repos/icu/icu/tags/release-4-0-1/source
4.2 = http://source.icu-project.org/repos/icu/icu/tags/release-4-2-1/source
4.4 = http://source.icu-project.org/repos/icu/icu/tags/release-4-4-2/source
4.6 = http://source.icu-project.org/repos/icu/icu/tags/release-4-6-1/source
4.8 = http://source.icu-project.org/repos/icu/icu/tags/release-4-8-1-1/source
49 = http://source.icu-project.org/repos/icu/icu/tags/release-49-1-2/source
50 = http://source.icu-project.org/repos/icu/icu/tags/release-50-1-2/source
51 = http://source.icu-project.org/repos/icu/icu/tags/release-51-2/source
52 = http://source.icu-project.org/repos/icu/icu/tags/release-52-1/source
53 = http://source.icu-project.org/repos/icu/icu/tags/release-53-1/source
54 = http://source.icu-project.org/repos/icu/icu/tags/release-54-1/source
55 = http://source.icu-project.org/repos/icu/icu/tags/release-55-1/source
57 = http://source.icu-project.org/repos/icu/icu/tags/release-57-1/source
58 = http://source.icu-project.org/repos/icu/tags/release-58-2/icu4c/source
59 = http://source.icu-project.org/repos/icu/tags/release-59-1/icu4c/source
60 = http://source.icu-project.org/repos/icu/tags/release-60-2/icu4c/source
61 = http://source.icu-project.org/repos/icu/tags/release-61-1/icu4c/source
62 = http://source.icu-project.org/repos/icu/tags/release-62-1/icu4c/source

View File

@ -25,8 +25,7 @@ use Symfony\Component\Intl\Data\Provider\RegionDataProvider;
use Symfony\Component\Intl\Data\Provider\ScriptDataProvider;
use Symfony\Component\Intl\Intl;
use Symfony\Component\Intl\Locale;
use Symfony\Component\Intl\Util\IcuVersion;
use Symfony\Component\Intl\Util\SvnRepository;
use Symfony\Component\Intl\Util\GitRepository;
require_once __DIR__.'/common.php';
require_once __DIR__.'/autoload.php';
@ -40,7 +39,7 @@ Usage: php update-data.php <path/to/icu/source> <path/to/icu/build>
Updates the ICU data for Symfony to the latest version of ICU.
If you downloaded the SVN repository before, you can pass the path to the
If you downloaded the git repository before, you can pass the path to the
repository source in the first optional argument.
If you also built the repository before, you can pass the directory where that
@ -64,37 +63,31 @@ if (!Intl::isExtensionLoaded()) {
bailout('The intl extension for PHP is not installed.');
}
$filesystem = new Filesystem();
$urls = parse_ini_file(__DIR__.'/icu.ini');
echo "icu.ini parsed. Available versions:\n";
$maxVersion = 0;
foreach ($urls as $urlVersion => $url) {
$maxVersion = IcuVersion::compare($maxVersion, $urlVersion, '<')
? $urlVersion
: $maxVersion;
echo " $urlVersion\n";
}
$shortIcuVersion = strip_minor_versions($maxVersion);
if ($argc >= 2) {
$sourceDir = $argv[1];
$svn = new SvnRepository($sourceDir);
$repoDir = $argv[1];
$git = new GitRepository($repoDir);
echo "Using existing SVN repository at {$sourceDir}.\n";
echo "Using the existing git repository at {$repoDir}.\n";
} else {
echo "Starting SVN checkout for version $shortIcuVersion. This may take a while...\n";
echo "Starting git clone. This may take a while...\n";
$sourceDir = sys_get_temp_dir().'/icu-data/'.$shortIcuVersion.'/source';
$svn = SvnRepository::download($urls[$shortIcuVersion], $sourceDir);
$repoDir = sys_get_temp_dir().'/icu-data';
$git = GitRepository::download('https://github.com/unicode-org/icu.git', $repoDir);
echo "SVN checkout to {$sourceDir} complete.\n";
echo "Git clone to {$repoDir} complete.\n";
}
$gitTag = $git->getLastTag(function ($tag) {
return preg_match('#^release-[0-9]{1,}-[0-9]{1}$#', $tag);
});
$shortIcuVersion = strip_minor_versions(preg_replace('#release-([0-9]{1,})-([0-9]{1,})#', '$1.$2', $gitTag));
echo "Checking out `{$gitTag}` for version `{$shortIcuVersion}`...\n";
$git->checkout('tags/'.$gitTag);
$filesystem = new Filesystem();
$sourceDir = $repoDir.'/icu4c/source';
if ($argc >= 3) {
$buildDir = $argv[2];
} else {
@ -265,23 +258,23 @@ $generator->generateData($config);
echo "Resource bundle compilation complete.\n";
$svnInfo = <<<SVN_INFO
SVN information
$gitInfo = <<<GIT_INFO
Git information
===============
URL: {$svn->getUrl()}
Revision: {$svn->getLastCommit()->getRevision()}
Author: {$svn->getLastCommit()->getAuthor()}
Date: {$svn->getLastCommit()->getDate()}
URL: {$git->getUrl()}
Revision: {$git->getLastCommitHash()}
Author: {$git->getLastAuthor()}
Date: {$git->getLastAuthoredDate()->format('c')}
SVN_INFO;
GIT_INFO;
foreach ($targetDirs as $targetDir) {
$svnInfoFile = $targetDir.'/svn-info.txt';
$gitInfoFile = $targetDir.'/git-info.txt';
file_put_contents($svnInfoFile, $svnInfo);
file_put_contents($gitInfoFile, $gitInfo);
echo "Wrote $svnInfoFile.\n";
echo "Wrote $gitInfoFile.\n";
$versionFile = $targetDir.'/version.txt';

View File

@ -0,0 +1,7 @@
Git information
===============
URL: https://github.com/unicode-org/icu.git
Revision: 4a3ba8eee90ea1414d4f7ee36563e6c9b28fda96
Author: Yoshito Umaoka
Date: 2018-06-20T05:34:56+00:00

View File

@ -1,7 +0,0 @@
SVN information
===============
URL: http://source.icu-project.org/repos/icu/tags/release-62-1/icu4c/source
Revision: 41542
Author: yoshito
Date: 2018-06-20T05:34:56.496986Z

View File

@ -0,0 +1,73 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Intl\Tests\Util;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Intl\Util\GitRepository;
/**
* @group intl-data
*/
class GitRepositoryTest extends TestCase
{
private $targetDir;
const REPO_URL = 'https://github.com/symfony/intl.git';
/**
* @before
* @after
*/
protected function cleanup()
{
$this->targetDir = sys_get_temp_dir().'/GitRepositoryTest/source';
$fs = new Filesystem();
$fs->remove($this->targetDir);
}
public function testItThrowsAnExceptionIfInitialisedWithNonGitDirectory()
{
$this->expectException('Symfony\Component\Intl\Exception\RuntimeException');
@mkdir($this->targetDir, '0777', true);
new GitRepository($this->targetDir);
}
public function testItClonesTheRepository()
{
$git = GitRepository::download(self::REPO_URL, $this->targetDir);
$this->assertInstanceOf('Symfony\Component\Intl\Util\GitRepository', $git);
$this->assertDirectoryExists($this->targetDir.'/.git');
$this->assertSame($this->targetDir, $git->getPath());
$this->assertSame(self::REPO_URL, $git->getUrl());
$this->assertRegExp('#^[0-9a-z]{40}$#', $git->getLastCommitHash());
$this->assertNotEmpty($git->getLastAuthor());
$this->assertInstanceOf('DateTime', $git->getLastAuthoredDate());
$this->assertStringMatchesFormat('v%s', $git->getLastTag());
$this->assertStringMatchesFormat('v3%s', $git->getLastTag(function ($tag) { return 0 === strpos($tag, 'v3'); }));
}
public function testItCheckoutsToTheLastTag()
{
$git = GitRepository::download(self::REPO_URL, $this->targetDir);
$lastCommitHash = $git->getLastCommitHash();
$lastV3Tag = $git->getLastTag(function ($tag) { return 0 === strpos($tag, 'v3'); });
$git->checkout($lastV3Tag);
$this->assertNotEquals($lastCommitHash, $git->getLastCommitHash());
}
}

View File

@ -0,0 +1,108 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Intl\Util;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Intl\Exception\RuntimeException;
/**
* @internal
*/
final class GitRepository
{
private $path;
public function __construct(string $path)
{
$this->path = $path;
$this->getUrl();
}
public static function download(string $remote, string $targetDir): self
{
self::exec('which git', 'The command "git" is not installed.');
$filesystem = new Filesystem();
if (!$filesystem->exists($targetDir.'/.git')) {
$filesystem->remove($targetDir);
$filesystem->mkdir($targetDir);
self::exec(sprintf('git clone %s %s', escapeshellarg($remote), escapeshellarg($targetDir)));
}
return new self(realpath($targetDir));
}
public function getPath()
{
return $this->path;
}
public function getUrl()
{
return $this->getLastLine($this->execInPath('git config --get remote.origin.url'));
}
public function getLastCommitHash()
{
return $this->getLastLine($this->execInPath('git log -1 --format="%H"'));
}
public function getLastAuthor()
{
return $this->getLastLine($this->execInPath('git log -1 --format="%an"'));
}
public function getLastAuthoredDate()
{
return new \DateTime($this->getLastLine($this->execInPath('git log -1 --format="%ai"')));
}
public function getLastTag(callable $filter = null)
{
$tags = $this->execInPath('git tag -l --sort=v:refname');
if (null !== $filter) {
$tags = array_filter($tags, $filter);
}
return $this->getLastLine($tags);
}
public function checkout($branch)
{
$this->execInPath(sprintf('git checkout %s', escapeshellarg($branch)));
}
private function execInPath($command)
{
return self::exec(sprintf('cd %s && %s', escapeshellarg($this->path), $command));
}
private static function exec($command, $customErrorMessage = null)
{
exec(sprintf('%s 2>&1', $command), $output, $result);
if (0 !== $result) {
throw new RuntimeException(null !== $customErrorMessage ? $customErrorMessage : sprintf('The `%s` command failed.', $command));
}
return $output;
}
private function getLastLine(array $output)
{
return array_pop($output);
}
}

View File

@ -1,62 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Intl\Util;
/**
* An SVN commit.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class SvnCommit
{
private $svnInfo;
/**
* Creates a commit from the given "svn info" data.
*
* @param \SimpleXMLElement $svnInfo the XML result from the "svn info" command
*/
public function __construct(\SimpleXMLElement $svnInfo)
{
$this->svnInfo = $svnInfo;
}
/**
* Returns the revision of the commit.
*
* @return string The revision of the commit
*/
public function getRevision()
{
return (string) $this->svnInfo['revision'];
}
/**
* Returns the author of the commit.
*
* @return string The author name
*/
public function getAuthor()
{
return (string) $this->svnInfo->author;
}
/**
* Returns the date of the commit.
*
* @return string The commit date
*/
public function getDate()
{
return (string) $this->svnInfo->date;
}
}

View File

@ -1,140 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Intl\Util;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Intl\Exception\RuntimeException;
/**
* A SVN repository containing ICU data.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class SvnRepository
{
/**
* @var string The path to the repository
*/
private $path;
/**
* @var \SimpleXMLElement
*/
private $svnInfo;
/**
* @var SvnCommit
*/
private $lastCommit;
/**
* Downloads the ICU data for the given version.
*
* @param string $url The URL to download from
* @param string $targetDir The directory in which to store the repository
*
* @return static
*
* @throws RuntimeException if an error occurs during the download
*/
public static function download($url, $targetDir)
{
exec('which svn', $output, $result);
if (0 !== $result) {
throw new RuntimeException('The command "svn" is not installed.');
}
$filesystem = new Filesystem();
if (!$filesystem->exists($targetDir.'/.svn')) {
$filesystem->remove($targetDir);
$filesystem->mkdir($targetDir);
exec('svn checkout '.$url.' '.$targetDir, $output, $result);
if (0 !== $result) {
throw new RuntimeException('The SVN checkout of '.$url.'failed.');
}
}
return new static(realpath($targetDir));
}
/**
* Reads the SVN repository at the given path.
*
* @param string $path The path to the repository
*/
public function __construct(string $path)
{
$this->path = $path;
}
/**
* Returns the path to the repository.
*
* @return string The path to the repository
*/
public function getPath()
{
return $this->path;
}
/**
* Returns the URL of the repository.
*
* @return string The URL of the repository
*/
public function getUrl()
{
return (string) $this->getSvnInfo()->entry->url;
}
/**
* Returns the last commit of the repository.
*
* @return SvnCommit The last commit
*/
public function getLastCommit()
{
if (null === $this->lastCommit) {
$this->lastCommit = new SvnCommit($this->getSvnInfo()->entry->commit);
}
return $this->lastCommit;
}
/**
* Returns information about the SVN repository.
*
* @return \SimpleXMLElement The XML result from the "svn info" command
*
* @throws RuntimeException if the "svn info" command failed
*/
private function getSvnInfo()
{
if (null === $this->svnInfo) {
exec('svn info --xml '.$this->path, $output, $result);
$svnInfo = simplexml_load_string(implode("\n", $output));
if (0 !== $result) {
throw new RuntimeException('svn info failed');
}
$this->svnInfo = $svnInfo;
}
return $this->svnInfo;
}
}