Merge branch '2.5'
* 2.5: (23 commits) [HttpKernel] fixed some unit tests for 2.4 (signature now uses SHA256 instead of MD5) [HttpKernel] simplified code [HttpKernel] fixed internal fragment handling fixing yaml indentation Unexpexted ));" [WebProfiler] replaced the import/export feature from the web interface to a CLI tool Forced all fragment uris to be signed, even for ESI Add tests and more assertions [FrameworkBundle][Translator] Validate locales. [HttpFoundation] added some missing tests [HttpFoundation] Improve string values in test codes [Security] Add more tests for StringUtils::equals fix comment: not fourth but sixth argument fixing typo in a comment [FrameworkBundle] fixed CS [FrameworkBundle] PhpExtractor bugfix and improvements [Finder] Fix findertest readability [Filesystem] Add FTP stream wrapper context option to enable overwrite (override) fix parsing of Authorization header Test examples from Drupal SA-CORE-2014-003 ... Conflicts: src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/admin.html.twig src/Symfony/Component/Filesystem/Filesystem.php src/Symfony/Component/HttpKernel/Fragment/EsiFragmentRenderer.php
This commit is contained in:
commit
d31bf634da
12
.travis.yml
12
.travis.yml
@ -1,12 +1,12 @@
|
||||
language: php
|
||||
|
||||
php:
|
||||
- 5.3.3
|
||||
- 5.3
|
||||
- 5.4
|
||||
- 5.5
|
||||
- 5.6
|
||||
- hhvm-nightly
|
||||
- 5.3.3
|
||||
- 5.3
|
||||
- 5.4
|
||||
- 5.5
|
||||
- 5.6
|
||||
- hhvm-nightly
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
|
@ -863,7 +863,6 @@ UPGRADE FROM 2.x to 3.0
|
||||
->atPath('property')
|
||||
->setParameter('{{ value }}', $invalidValue)
|
||||
->addViolation();
|
||||
));
|
||||
```
|
||||
|
||||
The methods `validate()` and `validateValue()` were removed. You should use
|
||||
|
@ -40,6 +40,7 @@
|
||||
<tag name="kernel.fragment_renderer" />
|
||||
<argument type="service" id="esi" on-invalid="null" />
|
||||
<argument type="service" id="fragment.renderer.inline" />
|
||||
<argument type="service" id="uri_signer" />
|
||||
<call method="setFragmentPath"><argument>%fragment.path%</argument></call>
|
||||
</service>
|
||||
|
||||
|
@ -1,3 +1,33 @@
|
||||
This template is used for translation message extraction tests
|
||||
<?php echo $view['translator']->trans('single-quoted key') ?>
|
||||
<?php echo $view['translator']->trans("double-quoted key") ?>
|
||||
<?php echo $view['translator']->trans(<<<EOF
|
||||
heredoc key
|
||||
EOF
|
||||
) ?>
|
||||
<?php echo $view['translator']->trans(<<<'EOF'
|
||||
nowdoc key
|
||||
EOF
|
||||
) ?>
|
||||
<?php echo $view['translator']->trans(
|
||||
"double-quoted key with whitespace and escaped \$\n\" sequences"
|
||||
) ?>
|
||||
<?php echo $view['translator']->trans(
|
||||
'single-quoted key with whitespace and nonescaped \$\n\' sequences'
|
||||
) ?>
|
||||
<?php echo $view['translator']->trans( <<<EOF
|
||||
heredoc key with whitespace and escaped \$\n sequences
|
||||
EOF
|
||||
) ?>
|
||||
<?php echo $view['translator']->trans( <<<'EOF'
|
||||
nowdoc key with whitespace and nonescaped \$\n sequences
|
||||
EOF
|
||||
) ?>
|
||||
|
||||
<?php echo $view['translator']->trans('single-quoted key with "quote mark at the end"') ?>
|
||||
|
||||
<?php echo $view['translator']->transChoice(
|
||||
'{0} There is no apples|{1} There is one apple|]1,Inf[ There are %count% apples',
|
||||
10,
|
||||
array('%count%' => 10)
|
||||
) ?>
|
||||
|
@ -27,10 +27,27 @@ class PhpExtractorTest extends TestCase
|
||||
// Act
|
||||
$extractor->extract(__DIR__.'/../Fixtures/Resources/views/', $catalogue);
|
||||
|
||||
$expectedHeredoc = <<<EOF
|
||||
heredoc key with whitespace and escaped \$\n sequences
|
||||
EOF;
|
||||
$expectedNowdoc = <<<'EOF'
|
||||
nowdoc key with whitespace and nonescaped \$\n sequences
|
||||
EOF;
|
||||
// Assert
|
||||
$this->assertCount(2, $catalogue->all('messages'), '->extract() should find 1 translation');
|
||||
$this->assertTrue($catalogue->has('single-quoted key'), '->extract() should find the "single-quoted key" message');
|
||||
$this->assertTrue($catalogue->has('double-quoted key'), '->extract() should find the "double-quoted key" message');
|
||||
$this->assertEquals('prefixsingle-quoted key', $catalogue->get('single-quoted key'), '->extract() should apply "prefix" as prefix');
|
||||
$expectedCatalogue = array('messages' => array(
|
||||
'single-quoted key' => 'prefixsingle-quoted key',
|
||||
'double-quoted key' => 'prefixdouble-quoted key',
|
||||
'heredoc key' => 'prefixheredoc key',
|
||||
'nowdoc key' => 'prefixnowdoc key',
|
||||
"double-quoted key with whitespace and escaped \$\n\" sequences" => "prefixdouble-quoted key with whitespace and escaped \$\n\" sequences",
|
||||
'single-quoted key with whitespace and nonescaped \$\n\' sequences' => 'prefixsingle-quoted key with whitespace and nonescaped \$\n\' sequences',
|
||||
'single-quoted key with "quote mark at the end"' => 'prefixsingle-quoted key with "quote mark at the end"',
|
||||
$expectedHeredoc => "prefix".$expectedHeredoc,
|
||||
$expectedNowdoc => "prefix".$expectedNowdoc,
|
||||
'{0} There is no apples|{1} There is one apple|]1,Inf[ There are %count% apples' => 'prefix{0} There is no apples|{1} There is one apple|]1,Inf[ There are %count% apples',
|
||||
));
|
||||
$actualCatalogue = $catalogue->all();
|
||||
|
||||
$this->assertEquals($expectedCatalogue, $actualCatalogue);
|
||||
}
|
||||
}
|
||||
|
@ -93,6 +93,16 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
|
||||
$this->assertEquals('foobarbax (sr@latin)', $translator->trans('foobarbax'));
|
||||
}
|
||||
|
||||
public function testTransWithCachingWithInvalidLocale()
|
||||
{
|
||||
$loader = $this->getMock('Symfony\Component\Translation\Loader\LoaderInterface');
|
||||
$translator = $this->getTranslator($loader, array('cache_dir' => $this->tmpDir), '\Symfony\Bundle\FrameworkBundle\Tests\Translation\TranslatorWithInvalidLocale');
|
||||
$translator->setLocale('invalid locale');
|
||||
|
||||
$this->setExpectedException('\InvalidArgumentException');
|
||||
$translator->trans('foo');
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getGetLocaleData
|
||||
*/
|
||||
@ -102,7 +112,7 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
|
||||
if ($inRequestScope) {
|
||||
$request = $this->getMock('Symfony\Component\HttpFoundation\Request');
|
||||
$request
|
||||
->expects($this->once())
|
||||
->expects($this->any())
|
||||
->method('getLocale')
|
||||
->will($this->returnValue('en'))
|
||||
;
|
||||
@ -135,6 +145,37 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
|
||||
);
|
||||
}
|
||||
|
||||
public function testGetLocaleWithInvalidLocale()
|
||||
{
|
||||
$request = $this->getMock('Symfony\Component\HttpFoundation\Request');
|
||||
|
||||
$request
|
||||
->expects($this->any())
|
||||
->method('getLocale')
|
||||
->will($this->returnValue('foo bar'))
|
||||
;
|
||||
$request
|
||||
->expects($this->once())
|
||||
->method('getDefaultLocale')
|
||||
->will($this->returnValue('en-US'))
|
||||
;
|
||||
|
||||
$requestStack = new RequestStack();
|
||||
$requestStack->push($request);
|
||||
|
||||
$container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface');
|
||||
$container
|
||||
->expects($this->once())
|
||||
->method('get')
|
||||
->with('request_stack')
|
||||
->will($this->returnValue($requestStack))
|
||||
;
|
||||
|
||||
$translator = new Translator($container, new MessageSelector());
|
||||
$this->assertSame('en-US', $translator->getLocale());
|
||||
}
|
||||
|
||||
|
||||
protected function getCatalogue($locale, $messages)
|
||||
{
|
||||
$catalogue = new MessageCatalogue($locale);
|
||||
@ -215,9 +256,9 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
|
||||
return $container;
|
||||
}
|
||||
|
||||
public function getTranslator($loader, $options = array())
|
||||
public function getTranslator($loader, $options = array(), $translatorClass = '\Symfony\Bundle\FrameworkBundle\Translation\Translator')
|
||||
{
|
||||
$translator = new Translator(
|
||||
$translator = new $translatorClass(
|
||||
$this->getContainer($loader),
|
||||
new MessageSelector(),
|
||||
array('loader' => array('loader')),
|
||||
@ -235,3 +276,14 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
|
||||
return $translator;
|
||||
}
|
||||
}
|
||||
|
||||
class TranslatorWithInvalidLocale extends Translator
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setLocale($locale)
|
||||
{
|
||||
$this->locale = $locale;
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,6 @@ use Symfony\Component\Translation\Extractor\ExtractorInterface;
|
||||
class PhpExtractor implements ExtractorInterface
|
||||
{
|
||||
const MESSAGE_TOKEN = 300;
|
||||
const IGNORE_TOKEN = 400;
|
||||
|
||||
/**
|
||||
* Prefix for new found message.
|
||||
@ -39,15 +38,16 @@ class PhpExtractor implements ExtractorInterface
|
||||
*/
|
||||
protected $sequences = array(
|
||||
array(
|
||||
'$view',
|
||||
'[',
|
||||
'\'translator\'',
|
||||
']',
|
||||
'->',
|
||||
'trans',
|
||||
'(',
|
||||
self::MESSAGE_TOKEN,
|
||||
')',
|
||||
),
|
||||
array(
|
||||
'->',
|
||||
'transChoice',
|
||||
'(',
|
||||
self::MESSAGE_TOKEN,
|
||||
),
|
||||
);
|
||||
|
||||
@ -76,6 +76,7 @@ class PhpExtractor implements ExtractorInterface
|
||||
* Normalizes a token.
|
||||
*
|
||||
* @param mixed $token
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function normalizeToken($token)
|
||||
@ -87,6 +88,56 @@ class PhpExtractor implements ExtractorInterface
|
||||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Seeks to a non-whitespace token.
|
||||
*/
|
||||
private function seekToNextReleventToken(\Iterator $tokenIterator)
|
||||
{
|
||||
for (; $tokenIterator->valid(); $tokenIterator->next()) {
|
||||
$t = $tokenIterator->current();
|
||||
if (!is_array($t) || ($t[0] !== T_WHITESPACE)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the message from the iterator while the tokens
|
||||
* match allowed message tokens
|
||||
*/
|
||||
private function getMessage(\Iterator $tokenIterator)
|
||||
{
|
||||
$message = '';
|
||||
$docToken = '';
|
||||
|
||||
for (; $tokenIterator->valid(); $tokenIterator->next()) {
|
||||
$t = $tokenIterator->current();
|
||||
if (!is_array($t)) {
|
||||
break;
|
||||
}
|
||||
|
||||
switch ($t[0]) {
|
||||
case T_START_HEREDOC:
|
||||
$docToken = $t[1];
|
||||
break;
|
||||
case T_ENCAPSED_AND_WHITESPACE:
|
||||
case T_CONSTANT_ENCAPSED_STRING:
|
||||
$message .= $t[1];
|
||||
break;
|
||||
case T_END_HEREDOC:
|
||||
return PhpStringTokenParser::parseDocString($docToken, $message);
|
||||
default:
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
|
||||
if ($message) {
|
||||
$message = PhpStringTokenParser::parse($message);
|
||||
}
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts trans message from PHP tokens.
|
||||
*
|
||||
@ -95,24 +146,27 @@ class PhpExtractor implements ExtractorInterface
|
||||
*/
|
||||
protected function parseTokens($tokens, MessageCatalogue $catalog)
|
||||
{
|
||||
foreach ($tokens as $key => $token) {
|
||||
$tokenIterator = new \ArrayIterator($tokens);
|
||||
|
||||
for ($key = 0; $key < $tokenIterator->count(); $key++) {
|
||||
foreach ($this->sequences as $sequence) {
|
||||
$message = '';
|
||||
$tokenIterator->seek($key);
|
||||
|
||||
foreach ($sequence as $id => $item) {
|
||||
if ($this->normalizeToken($tokens[$key + $id]) == $item) {
|
||||
foreach ($sequence as $item) {
|
||||
$this->seekToNextReleventToken($tokenIterator);
|
||||
|
||||
if ($this->normalizeToken($tokenIterator->current()) == $item) {
|
||||
$tokenIterator->next();
|
||||
continue;
|
||||
} elseif (self::MESSAGE_TOKEN == $item) {
|
||||
$message = $this->normalizeToken($tokens[$key + $id]);
|
||||
} elseif (self::IGNORE_TOKEN == $item) {
|
||||
continue;
|
||||
$message = $this->getMessage($tokenIterator);
|
||||
break;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$message = trim($message, '\'"');
|
||||
|
||||
if ($message) {
|
||||
$catalog->set($message, $this->prefix.$message);
|
||||
break;
|
||||
|
@ -0,0 +1,142 @@
|
||||
<?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\Bundle\FrameworkBundle\Translation;
|
||||
|
||||
/*
|
||||
* The following is derived from code at http://github.com/nikic/PHP-Parser
|
||||
*
|
||||
* Copyright (c) 2011 by Nikita Popov
|
||||
*
|
||||
* Some rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following
|
||||
* disclaimer in the documentation and/or other materials provided
|
||||
* with the distribution.
|
||||
*
|
||||
* * The names of the contributors may not be used to endorse or
|
||||
* promote products derived from this software without specific
|
||||
* prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
class PhpStringTokenParser
|
||||
{
|
||||
protected static $replacements = array(
|
||||
'\\' => '\\',
|
||||
'$' => '$',
|
||||
'n' => "\n",
|
||||
'r' => "\r",
|
||||
't' => "\t",
|
||||
'f' => "\f",
|
||||
'v' => "\v",
|
||||
'e' => "\x1B",
|
||||
);
|
||||
|
||||
/**
|
||||
* Parses a string token.
|
||||
*
|
||||
* @param string $str String token content
|
||||
*
|
||||
* @return string The parsed string
|
||||
*/
|
||||
public static function parse($str)
|
||||
{
|
||||
$bLength = 0;
|
||||
if ('b' === $str[0]) {
|
||||
$bLength = 1;
|
||||
}
|
||||
|
||||
if ('\'' === $str[$bLength]) {
|
||||
return str_replace(
|
||||
array('\\\\', '\\\''),
|
||||
array( '\\', '\''),
|
||||
substr($str, $bLength + 1, -1)
|
||||
);
|
||||
} else {
|
||||
return self::parseEscapeSequences(substr($str, $bLength + 1, -1), '"');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses escape sequences in strings (all string types apart from single quoted).
|
||||
*
|
||||
* @param string $str String without quotes
|
||||
* @param null|string $quote Quote type
|
||||
*
|
||||
* @return string String with escape sequences parsed
|
||||
*/
|
||||
public static function parseEscapeSequences($str, $quote)
|
||||
{
|
||||
if (null !== $quote) {
|
||||
$str = str_replace('\\' . $quote, $quote, $str);
|
||||
}
|
||||
|
||||
return preg_replace_callback(
|
||||
'~\\\\([\\\\$nrtfve]|[xX][0-9a-fA-F]{1,2}|[0-7]{1,3})~',
|
||||
array(__CLASS__, 'parseCallback'),
|
||||
$str
|
||||
);
|
||||
}
|
||||
|
||||
public static function parseCallback($matches)
|
||||
{
|
||||
$str = $matches[1];
|
||||
|
||||
if (isset(self::$replacements[$str])) {
|
||||
return self::$replacements[$str];
|
||||
} elseif ('x' === $str[0] || 'X' === $str[0]) {
|
||||
return chr(hexdec($str));
|
||||
} else {
|
||||
return chr(octdec($str));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a constant doc string.
|
||||
*
|
||||
* @param string $startToken Doc string start token content (<<<SMTHG)
|
||||
* @param string $str String token content
|
||||
*
|
||||
* @return string Parsed string
|
||||
*/
|
||||
public static function parseDocString($startToken, $str)
|
||||
{
|
||||
// strip last newline (thanks tokenizer for sticking it into the string!)
|
||||
$str = preg_replace('~(\r\n|\n|\r)$~', '', $str);
|
||||
|
||||
// nowdoc string
|
||||
if (false !== strpos($startToken, '\'')) {
|
||||
return $str;
|
||||
}
|
||||
|
||||
return self::parseEscapeSequences($str, null);
|
||||
}
|
||||
}
|
@ -67,6 +67,11 @@ class Translator extends BaseTranslator
|
||||
{
|
||||
if (null === $this->locale && $request = $this->container->get('request_stack')->getCurrentRequest()) {
|
||||
$this->locale = $request->getLocale();
|
||||
try {
|
||||
$this->setLocale($request->getLocale());
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
$this->setLocale($request->getDefaultLocale());
|
||||
}
|
||||
}
|
||||
|
||||
return $this->locale;
|
||||
@ -87,6 +92,8 @@ class Translator extends BaseTranslator
|
||||
return parent::loadCatalogue($locale);
|
||||
}
|
||||
|
||||
$this->assertValidLocale($locale);
|
||||
|
||||
$cache = new ConfigCache($this->options['cache_dir'].'/catalogue.'.$locale.'.php', $this->options['debug']);
|
||||
if (!$cache->isFresh()) {
|
||||
$this->initialize();
|
||||
|
@ -0,0 +1,75 @@
|
||||
<?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\Bundle\WebProfilerBundle\Command;
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\HttpKernel\Profiler\Profiler;
|
||||
|
||||
/**
|
||||
* Exports a profile.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class ExportCommand extends Command
|
||||
{
|
||||
private $profiler;
|
||||
|
||||
public function __construct(Profiler $profiler = null)
|
||||
{
|
||||
$this->profiler = $profiler;
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isEnabled()
|
||||
{
|
||||
if (null === $this->profiler) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return parent::isEnabled();
|
||||
}
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('profiler:export')
|
||||
->setDescription('Exports a profile')
|
||||
->setDefinition(array(
|
||||
new InputArgument('token', InputArgument::REQUIRED, 'The profile token'),
|
||||
))
|
||||
->setHelp(<<<EOF
|
||||
The <info>%command.name%</info> command exports a profile to the standard output:
|
||||
|
||||
<info>php %command.full_name% profile_token</info>
|
||||
EOF
|
||||
)
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$token = $input->getArgument('token');
|
||||
|
||||
if (!$profile = $this->profiler->loadProfile($token)) {
|
||||
throw new \LogicException(sprintf('Profile with token "%s" does not exist.', $token));
|
||||
}
|
||||
|
||||
$output->writeln($this->profiler->export($profile), OutputInterface::OUTPUT_RAW);
|
||||
}
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
<?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\Bundle\WebProfilerBundle\Command;
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\HttpKernel\Profiler\Profiler;
|
||||
|
||||
/**
|
||||
* Imports a profile.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class ImportCommand extends Command
|
||||
{
|
||||
private $profiler;
|
||||
|
||||
public function __construct(Profiler $profiler = null)
|
||||
{
|
||||
$this->profiler = $profiler;
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isEnabled()
|
||||
{
|
||||
if (null === $this->profiler) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return parent::isEnabled();
|
||||
}
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('profiler:import')
|
||||
->setDescription('Imports a profile')
|
||||
->setDefinition(array(
|
||||
new InputArgument('filename', InputArgument::OPTIONAL, 'The profile path'),
|
||||
))
|
||||
->setHelp(<<<EOF
|
||||
The <info>%command.name%</info> command imports a profile:
|
||||
|
||||
<info>php %command.full_name% profile_filepath</info>
|
||||
|
||||
You can also pipe the profile via STDIN:
|
||||
|
||||
<info>cat profile_file | php %command.full_name%</info>
|
||||
EOF
|
||||
)
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$data = '';
|
||||
if ($input->getArgument('filename')) {
|
||||
$data = file_get_contents($input->getArgument('filename'));
|
||||
} else {
|
||||
if (0 !== ftell(STDIN)) {
|
||||
throw new \RuntimeException('Please provide a filename or pipe the profile to STDIN.');
|
||||
}
|
||||
|
||||
while (!feof(STDIN)) {
|
||||
$data .= fread(STDIN, 1024);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$profile = $this->profiler->import($data)) {
|
||||
throw new \LogicException('The profile already exists in the database.');
|
||||
}
|
||||
|
||||
$output->writeln(sprintf('Profile "%s" has been successfully imported.', $profile->getToken()));
|
||||
}
|
||||
}
|
@ -111,33 +111,6 @@ class ProfilerController
|
||||
)), 200, array('Content-Type' => 'text/html'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports data for a given token.
|
||||
*
|
||||
* @param string $token The profiler token
|
||||
*
|
||||
* @return Response A Response instance
|
||||
*
|
||||
* @throws NotFoundHttpException
|
||||
*/
|
||||
public function exportAction($token)
|
||||
{
|
||||
if (null === $this->profiler) {
|
||||
throw new NotFoundHttpException('The profiler must be enabled.');
|
||||
}
|
||||
|
||||
$this->profiler->disable();
|
||||
|
||||
if (!$profile = $this->profiler->loadProfile($token)) {
|
||||
throw new NotFoundHttpException(sprintf('Token "%s" does not exist.', $token));
|
||||
}
|
||||
|
||||
return new Response($this->profiler->export($profile), 200, array(
|
||||
'Content-Type' => 'text/plain',
|
||||
'Content-Disposition' => 'attachment; filename= '.$token.'.txt',
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Purges all tokens.
|
||||
*
|
||||
@ -157,36 +130,6 @@ class ProfilerController
|
||||
return new RedirectResponse($this->generator->generate('_profiler_info', array('about' => 'purge')), 302, array('Content-Type' => 'text/html'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports token data.
|
||||
*
|
||||
* @param Request $request The current HTTP Request
|
||||
*
|
||||
* @return Response A Response instance
|
||||
*
|
||||
* @throws NotFoundHttpException
|
||||
*/
|
||||
public function importAction(Request $request)
|
||||
{
|
||||
if (null === $this->profiler) {
|
||||
throw new NotFoundHttpException('The profiler must be enabled.');
|
||||
}
|
||||
|
||||
$this->profiler->disable();
|
||||
|
||||
$file = $request->files->get('file');
|
||||
|
||||
if (empty($file) || !$file->isValid()) {
|
||||
return new RedirectResponse($this->generator->generate('_profiler_info', array('about' => 'upload_error')), 302, array('Content-Type' => 'text/html'));
|
||||
}
|
||||
|
||||
if (!$profile = $this->profiler->import(file_get_contents($file->getPathname()))) {
|
||||
return new RedirectResponse($this->generator->generate('_profiler_info', array('about' => 'already_exists')), 302, array('Content-Type' => 'text/html'));
|
||||
}
|
||||
|
||||
return new RedirectResponse($this->generator->generate('_profiler', array('token' => $profile->getToken())), 302, array('Content-Type' => 'text/html'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays information page.
|
||||
*
|
||||
|
@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" ?>
|
||||
|
||||
<container xmlns="http://symfony.com/schema/dic/services"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
|
||||
|
||||
<parameters>
|
||||
<parameter key="web_profiler.command.import.class">Symfony\Bundle\WebProfilerBundle\Command\ImportCommand</parameter>
|
||||
<parameter key="web_profiler.command.export.class">Symfony\Bundle\WebProfilerBundle\Command\ExportCommand</parameter>
|
||||
</parameters>
|
||||
|
||||
<services>
|
||||
<service id="web_profiler.command.import" class="%web_profiler.command.import.class%">
|
||||
<argument type="service" id="profiler" on-invalid="null" />
|
||||
<tag name="console.command" />
|
||||
</service>
|
||||
|
||||
<service id="web_profiler.command.export" class="%web_profiler.command.export.class%">
|
||||
<argument type="service" id="profiler" on-invalid="null" />
|
||||
<tag name="console.command" />
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
@ -1,27 +1,10 @@
|
||||
<div class="search import clearfix" id="adminBar">
|
||||
<h3>
|
||||
<img style="margin: 0 5px 0 0; vertical-align: middle; height: 16px" width="16" height="16" alt="Import" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAADo0lEQVR42u2XS0hUURjHD5njA1oYbXQ2MqCmIu2iEEISUREEEURxFB8ovt+DEsLgaxBRQQeUxnQ0ZRYSQasgiDaFqxAy2jUtCjdCoEjFwHj6/+F+dbvN6PQAN37wm++c7/z/35x7uPcOo7TW58rFBs59A7GGQ51XBAIBlZmZuYOhE1zm/A/4PxvY3NwMO53OYEJCgp+nccqXXQc94D54boAxalyLNayNtra2NJmbmzvOyMj4cRqoKYK4AsZzc3Nft7e3f5qZmTnCpk8Ix6xxjRpDGzmkUU5Ozuu2trZP09PTR+vr6ycbGxtaWFtbC9fU1AQTExPdmNNzLSUlZXt4ePhANNGghlp6lDWkkcvlOsCX6LNYXV0N8BTS0tK2cDJfWIsFaumhV0lIIxzXl5WVFX0aPp8vhDwJbMnJyc6JiYkji8YP7oI4YowfmDX00KskOHG73UfLy8vahB/cBXFSW1pa2kPOA7RdqqysfGtaCyOXA2VGgmvUiJ5e9lD8qKioeOv1ejVZXFwMI5eLEWOFWgh5Etg4J0lJSTdwYiHxLSwseFi3Yg5qRE8veyh+TE1Nhebn5zWZnZ31mE2okTxmM6WlpS7xeDyeQ2Qb61bMQQ214mMPVVxc7MJuNBkfHz9EtplNmEcET4JPfL29va+i6azR19f3UnzV1dUrqqqqyocT0KSzs/OV1YB6ROrr67fF19TU9DSazhp1dXXPxdfS0vJQNTY2+sfGxjSpra19YTWgHhHs/pn40OhRNJ0lLuON+kF8ra2tY9yAe3R0VBMc6wfr84n6b1BDrfiam5snImgczObAq7ylv7//q/hGRkbuqMHBwTt4Q2nS3d39jSKzCfXfoKarq+ur+NhD1owLcNrt9h3OTXGrqKgoKJ6hoaFD5DhuIA43xiGyJoWFhUGKxYXaL3CNGtH39PR8Zg9jzREfH+8vKCgI4krDRu0GcGVnZ78ZGBg4ER/Wf+4OVzOMRhrwFE6ysrLe0EQzaopII65RI3p478lVp6am7uDmPJY11F44HI7dsrKyfc5Nnj1km5Lo6Oiw4cdnD1kLJSUl++np6btsQjhmzayB5x29uGp3fn5+EPMw66eBX8b3yHZlDdyRdtzN75F1LED7kR6gMA7E6HsMrqpogbv5KngM9Bk8MbTKwAYmQSiCdhd4wW0VazQ0NNwEXrALNDHGS+A2UFHIA3smj/rX4JvrT7GBSRDi/J8Db8e/JY/5jLj4Y3KxgfPfwHc53iL+IQDMOgAAAABJRU5ErkJggg==">
|
||||
Admin
|
||||
</h3>
|
||||
{% if token is not empty %}
|
||||
<div class="search import clearfix" id="adminBar">
|
||||
<h3>
|
||||
<img style="margin: 0 5px 0 0; vertical-align: middle; height: 16px" width="16" height="16" alt="Import" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAADo0lEQVR42u2XS0hUURjHD5njA1oYbXQ2MqCmIu2iEEISUREEEURxFB8ovt+DEsLgaxBRQQeUxnQ0ZRYSQasgiDaFqxAy2jUtCjdCoEjFwHj6/+F+dbvN6PQAN37wm++c7/z/35x7uPcOo7TW58rFBs59A7GGQ51XBAIBlZmZuYOhE1zm/A/4PxvY3NwMO53OYEJCgp+nccqXXQc94D54boAxalyLNayNtra2NJmbmzvOyMj4cRqoKYK4AsZzc3Nft7e3f5qZmTnCpk8Ix6xxjRpDGzmkUU5Ozuu2trZP09PTR+vr6ycbGxtaWFtbC9fU1AQTExPdmNNzLSUlZXt4ePhANNGghlp6lDWkkcvlOsCX6LNYXV0N8BTS0tK2cDJfWIsFaumhV0lIIxzXl5WVFX0aPp8vhDwJbMnJyc6JiYkji8YP7oI4YowfmDX00KskOHG73UfLy8vahB/cBXFSW1pa2kPOA7RdqqysfGtaCyOXA2VGgmvUiJ5e9lD8qKioeOv1ejVZXFwMI5eLEWOFWgh5Etg4J0lJSTdwYiHxLSwseFi3Yg5qRE8veyh+TE1Nhebn5zWZnZ31mE2okTxmM6WlpS7xeDyeQ2Qb61bMQQ214mMPVVxc7MJuNBkfHz9EtplNmEcET4JPfL29va+i6azR19f3UnzV1dUrqqqqyocT0KSzs/OV1YB6ROrr67fF19TU9DSazhp1dXXPxdfS0vJQNTY2+sfGxjSpra19YTWgHhHs/pn40OhRNJ0lLuON+kF8ra2tY9yAe3R0VBMc6wfr84n6b1BDrfiam5snImgczObAq7ylv7//q/hGRkbuqMHBwTt4Q2nS3d39jSKzCfXfoKarq+ur+NhD1owLcNrt9h3OTXGrqKgoKJ6hoaFD5DhuIA43xiGyJoWFhUGKxYXaL3CNGtH39PR8Zg9jzREfH+8vKCgI4krDRu0GcGVnZ78ZGBg4ER/Wf+4OVzOMRhrwFE6ysrLe0EQzaopII65RI3p478lVp6am7uDmPJY11F44HI7dsrKyfc5Nnj1km5Lo6Oiw4cdnD1kLJSUl++np6btsQjhmzayB5x29uGp3fn5+EPMw66eBX8b3yHZlDdyRdtzN75F1LED7kR6gMA7E6HsMrqpogbv5KngM9Bk8MbTKwAYmQSiCdhd4wW0VazQ0NNwEXrALNDHGS+A2UFHIA3smj/rX4JvrT7GBSRDi/J8Db8e/JY/5jLj4Y3KxgfPfwHc53iL+IQDMOgAAAABJRU5ErkJggg==">
|
||||
Admin
|
||||
</h3>
|
||||
|
||||
<form action="{{ path('_profiler_import') }}" method="post" enctype="multipart/form-data">
|
||||
{% if token is not empty %}
|
||||
<div style="margin-bottom: 10px">
|
||||
» <a href="{{ path('_profiler_purge', { 'token': token }) }}">Purge</a>
|
||||
</div>
|
||||
<div style="margin-bottom: 10px">
|
||||
» <a href="{{ path('_profiler_export', { 'token': token }) }}">Export</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
» <label for="file">Import</label><br />
|
||||
<input type="file" name="file" id="file"><br />
|
||||
<button type="submit" class="sf-button">
|
||||
<span class="border-l">
|
||||
<span class="border-r">
|
||||
<span class="btn-bg">UPLOAD</span>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
<div class="clear-fix"></div>
|
||||
</form>
|
||||
</div>
|
||||
<div style="margin-bottom: 10px">» <a href="{{ path('_profiler_purge', { 'token': token }) }}">Purge</a></div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
@ -0,0 +1,53 @@
|
||||
<?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\Bundle\WebProfilerBundle\Tests\Command;
|
||||
|
||||
use Symfony\Bundle\WebProfilerBundle\Command\ExportCommand;
|
||||
use Symfony\Component\Console\Tester\CommandTester;
|
||||
use Symfony\Component\Console\Application;
|
||||
use Symfony\Component\HttpKernel\Profiler\Profile;
|
||||
|
||||
class ExportCommandTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
/**
|
||||
* @expectedException \LogicException
|
||||
*/
|
||||
public function testExecuteWithUnknownToken()
|
||||
{
|
||||
$profiler = $this
|
||||
->getMockBuilder('Symfony\Component\HttpKernel\Profiler\Profiler')
|
||||
->disableOriginalConstructor()
|
||||
->getMock()
|
||||
;
|
||||
|
||||
$command = new ExportCommand($profiler);
|
||||
$commandTester = new CommandTester($command);
|
||||
$commandTester->execute(array('token' => 'TOKEN'));
|
||||
}
|
||||
|
||||
public function testExecuteWithToken()
|
||||
{
|
||||
$profiler = $this
|
||||
->getMockBuilder('Symfony\Component\HttpKernel\Profiler\Profiler')
|
||||
->disableOriginalConstructor()
|
||||
->getMock()
|
||||
;
|
||||
|
||||
$profile = new Profile('TOKEN');
|
||||
$profiler->expects($this->once())->method('loadProfile')->with('TOKEN')->will($this->returnValue($profile));
|
||||
|
||||
$command = new ExportCommand($profiler);
|
||||
$commandTester = new CommandTester($command);
|
||||
$commandTester->execute(array('token' => 'TOKEN'));
|
||||
$this->assertEquals($profiler->export($profile), $commandTester->getDisplay());
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
<?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\Bundle\WebProfilerBundle\Tests\Command;
|
||||
|
||||
use Symfony\Bundle\WebProfilerBundle\Command\ImportCommand;
|
||||
use Symfony\Component\Console\Tester\CommandTester;
|
||||
use Symfony\Component\Console\Application;
|
||||
use Symfony\Component\HttpKernel\Profiler\Profile;
|
||||
|
||||
class ImportCommandTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testExecute()
|
||||
{
|
||||
$profiler = $this
|
||||
->getMockBuilder('Symfony\Component\HttpKernel\Profiler\Profiler')
|
||||
->disableOriginalConstructor()
|
||||
->getMock()
|
||||
;
|
||||
|
||||
$profiler->expects($this->once())->method('import')->will($this->returnValue(new Profile('TOKEN')));
|
||||
|
||||
$command = new ImportCommand($profiler);
|
||||
$commandTester = new CommandTester($command);
|
||||
$commandTester->execute(array('filename' => __DIR__.'/../Fixtures/profile.data'));
|
||||
$this->assertRegExp('/Profile "TOKEN" has been successfully imported\./', $commandTester->getDisplay());
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
Tzo0NToiU3ltZm9ueVxDb21wb25lbnRcSHR0cEtlcm5lbFxQcm9maWxlclxQcm9maWxlIjo4OntzOjUyOiIAU3ltZm9ueVxDb21wb25lbnRcSHR0cEtlcm5lbFxQcm9maWxlclxQcm9maWxlAHRva2VuIjtzOjU6IlRPS0VOIjtzOjUzOiIAU3ltZm9ueVxDb21wb25lbnRcSHR0cEtlcm5lbFxQcm9maWxlclxQcm9maWxlAHBhcmVudCI7TjtzOjU1OiIAU3ltZm9ueVxDb21wb25lbnRcSHR0cEtlcm5lbFxQcm9maWxlclxQcm9maWxlAGNoaWxkcmVuIjthOjA6e31zOjU3OiIAU3ltZm9ueVxDb21wb25lbnRcSHR0cEtlcm5lbFxQcm9maWxlclxQcm9maWxlAGNvbGxlY3RvcnMiO2E6MDp7fXM6NDk6IgBTeW1mb255XENvbXBvbmVudFxIdHRwS2VybmVsXFByb2ZpbGVyXFByb2ZpbGUAaXAiO047czo1MzoiAFN5bWZvbnlcQ29tcG9uZW50XEh0dHBLZXJuZWxcUHJvZmlsZXJcUHJvZmlsZQBtZXRob2QiO047czo1MDoiAFN5bWZvbnlcQ29tcG9uZW50XEh0dHBLZXJuZWxcUHJvZmlsZXJcUHJvZmlsZQB1cmwiO047czo1MToiAFN5bWZvbnlcQ29tcG9uZW50XEh0dHBLZXJuZWxcUHJvZmlsZXJcUHJvZmlsZQB0aW1lIjtOO30=
|
@ -54,9 +54,12 @@ class Filesystem
|
||||
if (false === $source = @fopen($originFile, 'r')) {
|
||||
throw new IOException(sprintf('Failed to copy "%s" to "%s" because source file could not be opened for reading.', $originFile, $targetFile), 0, null, $originFile);
|
||||
}
|
||||
if (false === $target = @fopen($targetFile, 'w')) {
|
||||
|
||||
// Stream context created to allow files overwrite when using FTP stream wrapper - disabled by default
|
||||
if (false === $target = fopen($targetFile, 'w', null, stream_context_create(array('ftp' => array('overwrite' => true))))) {
|
||||
throw new IOException(sprintf('Failed to copy "%s" to "%s" because target file could not be opened for writing.', $originFile, $targetFile), 0, null, $originFile);
|
||||
}
|
||||
|
||||
stream_copy_to_stream($source, $target);
|
||||
fclose($source);
|
||||
fclose($target);
|
||||
|
@ -739,17 +739,30 @@ class FinderTest extends Iterator\RealIteratorTestCase
|
||||
$finder->files()->in(self::$tmpDir);
|
||||
|
||||
// make 'foo' directory non-readable
|
||||
chmod(self::$tmpDir.DIRECTORY_SEPARATOR.'foo', 0333);
|
||||
$testDir = self::$tmpDir.DIRECTORY_SEPARATOR.'foo';
|
||||
chmod($testDir, 0333);
|
||||
|
||||
try {
|
||||
$this->assertIterator($this->toAbsolute(array('foo bar', 'test.php', 'test.py')), $finder->getIterator());
|
||||
$this->fail('Finder should throw an exception when opening a non-readable directory.');
|
||||
} catch (\Exception $e) {
|
||||
$this->assertInstanceOf('Symfony\\Component\\Finder\\Exception\\AccessDeniedException', $e);
|
||||
if (false === $couldRead = is_readable($testDir)) {
|
||||
try {
|
||||
$this->assertIterator($this->toAbsolute(array('foo bar', 'test.php', 'test.py')), $finder->getIterator());
|
||||
$this->fail('Finder should throw an exception when opening a non-readable directory.');
|
||||
} catch (\Exception $e) {
|
||||
$expectedExceptionClass = 'Symfony\\Component\\Finder\\Exception\\AccessDeniedException';
|
||||
if ($e instanceof \PHPUnit_Framework_ExpectationFailedException) {
|
||||
$this->fail(sprintf("Expected exception:\n%s\nGot:\n%s\nWith comparison failure:\n%s", $expectedExceptionClass, 'PHPUnit_Framework_ExpectationFailedException', $e->getComparisonFailure()->getExpectedAsString()));
|
||||
}
|
||||
|
||||
$this->assertInstanceOf($expectedExceptionClass, $e);
|
||||
}
|
||||
}
|
||||
|
||||
// restore original permissions
|
||||
chmod(self::$tmpDir.DIRECTORY_SEPARATOR.'foo', 0777);
|
||||
chmod($testDir, 0777);
|
||||
clearstatcache($testDir);
|
||||
|
||||
if ($couldRead) {
|
||||
$this->markTestSkipped('could read test files while test requires unreadable');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -765,12 +778,20 @@ class FinderTest extends Iterator\RealIteratorTestCase
|
||||
$finder->files()->ignoreUnreadableDirs()->in(self::$tmpDir);
|
||||
|
||||
// make 'foo' directory non-readable
|
||||
chmod(self::$tmpDir.DIRECTORY_SEPARATOR.'foo', 0333);
|
||||
$testDir = self::$tmpDir.DIRECTORY_SEPARATOR.'foo';
|
||||
chmod($testDir, 0333);
|
||||
|
||||
$this->assertIterator($this->toAbsolute(array('foo bar', 'test.php', 'test.py')), $finder->getIterator());
|
||||
if (false === ($couldRead = is_readable($testDir))) {
|
||||
$this->assertIterator($this->toAbsolute(array('foo bar', 'test.php', 'test.py')), $finder->getIterator());
|
||||
}
|
||||
|
||||
// restore original permissions
|
||||
chmod(self::$tmpDir.DIRECTORY_SEPARATOR.'foo', 0777);
|
||||
chmod($testDir, 0777);
|
||||
clearstatcache($testDir);
|
||||
|
||||
if ($couldRead) {
|
||||
$this->markTestSkipped('could read test files while test requires unreadable');
|
||||
}
|
||||
}
|
||||
|
||||
private function buildTestData(array $tests)
|
||||
|
@ -1183,7 +1183,8 @@ class Request
|
||||
|
||||
// as the host can come from the user (HTTP_HOST and depending on the configuration, SERVER_NAME too can come from the user)
|
||||
// check that it does not contain forbidden characters (see RFC 952 and RFC 2181)
|
||||
if ($host && !preg_match('/^\[?(?:[a-zA-Z0-9-:\]_]+\.?)+$/', $host)) {
|
||||
// use preg_replace() instead of preg_match() to prevent DoS attacks with long host names
|
||||
if ($host && '' !== preg_replace('/(?:^\[)?[a-zA-Z0-9-:\]_]+\.?/', '', $host)) {
|
||||
throw new \UnexpectedValueException(sprintf('Invalid Host "%s"', $host));
|
||||
}
|
||||
|
||||
@ -1392,6 +1393,16 @@ class Request
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default locale.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDefaultLocale()
|
||||
{
|
||||
return $this->defaultLocale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the locale.
|
||||
*
|
||||
|
@ -65,13 +65,13 @@ class ServerBag extends ParameterBag
|
||||
}
|
||||
|
||||
if (null !== $authorizationHeader) {
|
||||
if (0 === stripos($authorizationHeader, 'basic')) {
|
||||
if (0 === stripos($authorizationHeader, 'basic ')) {
|
||||
// Decode AUTHORIZATION header into PHP_AUTH_USER and PHP_AUTH_PW when authorization header is basic
|
||||
$exploded = explode(':', base64_decode(substr($authorizationHeader, 6)));
|
||||
$exploded = explode(':', base64_decode(substr($authorizationHeader, 6)), 2);
|
||||
if (count($exploded) == 2) {
|
||||
list($headers['PHP_AUTH_USER'], $headers['PHP_AUTH_PW']) = $exploded;
|
||||
}
|
||||
} elseif (empty($this->parameters['PHP_AUTH_DIGEST']) && (0 === stripos($authorizationHeader, 'digest'))) {
|
||||
} elseif (empty($this->parameters['PHP_AUTH_DIGEST']) && (0 === stripos($authorizationHeader, 'digest '))) {
|
||||
// In some circumstances PHP_AUTH_DIGEST needs to be set
|
||||
$headers['PHP_AUTH_DIGEST'] = $authorizationHeader;
|
||||
$this->parameters['PHP_AUTH_DIGEST'] = $authorizationHeader;
|
||||
|
@ -42,7 +42,7 @@ class RequestTest extends \PHPUnit_Framework_TestCase
|
||||
$this->assertEquals('bar', $request->attributes->get('foo'), '->initialize() takes an array of attributes as its third argument');
|
||||
|
||||
$request->initialize(array(), array(), array(), array(), array(), array('HTTP_FOO' => 'bar'));
|
||||
$this->assertEquals('bar', $request->headers->get('FOO'), '->initialize() takes an array of HTTP headers as its fourth argument');
|
||||
$this->assertEquals('bar', $request->headers->get('FOO'), '->initialize() takes an array of HTTP headers as its sixth argument');
|
||||
}
|
||||
|
||||
public function testGetLocale()
|
||||
@ -200,23 +200,23 @@ class RequestTest extends \PHPUnit_Framework_TestCase
|
||||
$this->assertEquals('test.com:90', $request->getHttpHost());
|
||||
$this->assertFalse($request->isSecure());
|
||||
|
||||
$request = Request::create('http://test:test@test.com');
|
||||
$request = Request::create('http://username:password@test.com');
|
||||
$this->assertEquals('http://test.com/', $request->getUri());
|
||||
$this->assertEquals('/', $request->getPathInfo());
|
||||
$this->assertEquals('', $request->getQueryString());
|
||||
$this->assertEquals(80, $request->getPort());
|
||||
$this->assertEquals('test.com', $request->getHttpHost());
|
||||
$this->assertEquals('test', $request->getUser());
|
||||
$this->assertEquals('test', $request->getPassword());
|
||||
$this->assertEquals('username', $request->getUser());
|
||||
$this->assertEquals('password', $request->getPassword());
|
||||
$this->assertFalse($request->isSecure());
|
||||
|
||||
$request = Request::create('http://testnopass@test.com');
|
||||
$request = Request::create('http://username@test.com');
|
||||
$this->assertEquals('http://test.com/', $request->getUri());
|
||||
$this->assertEquals('/', $request->getPathInfo());
|
||||
$this->assertEquals('', $request->getQueryString());
|
||||
$this->assertEquals(80, $request->getPort());
|
||||
$this->assertEquals('test.com', $request->getHttpHost());
|
||||
$this->assertEquals('testnopass', $request->getUser());
|
||||
$this->assertEquals('username', $request->getUser());
|
||||
$this->assertSame('',$request->getPassword());
|
||||
$this->assertFalse($request->isSecure());
|
||||
|
||||
@ -1657,6 +1657,59 @@ class RequestTest extends \PHPUnit_Framework_TestCase
|
||||
|
||||
Request::setFactory(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getLongHostNames
|
||||
*/
|
||||
public function testVeryLongHosts($host)
|
||||
{
|
||||
$start = microtime(true);
|
||||
|
||||
$request = Request::create('/');
|
||||
$request->headers->set('host', $host);
|
||||
$this->assertEquals($host, $request->getHost());
|
||||
$this->assertLessThan(1, microtime(true) - $start);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getHostValidities
|
||||
*/
|
||||
public function testHostValidity($host, $isValid, $expectedHost = null, $expectedPort = null)
|
||||
{
|
||||
$request = Request::create('/');
|
||||
$request->headers->set('host', $host);
|
||||
|
||||
if ($isValid) {
|
||||
$this->assertSame($expectedHost ?: $host, $request->getHost());
|
||||
if ($expectedPort) {
|
||||
$this->assertSame($expectedPort, $request->getPort());
|
||||
}
|
||||
} else {
|
||||
$this->setExpectedException('UnexpectedValueException', 'Invalid Host');
|
||||
$request->getHost();
|
||||
}
|
||||
}
|
||||
|
||||
public function getHostValidities()
|
||||
{
|
||||
return array(
|
||||
array('.a', false),
|
||||
array('a..', false),
|
||||
array('a.', true),
|
||||
array("\xE9", false),
|
||||
array('[::1]', true),
|
||||
array('[::1]:80', true, '[::1]', 80),
|
||||
array(str_repeat('.', 101), false),
|
||||
);
|
||||
}
|
||||
|
||||
public function getLongHostNames()
|
||||
{
|
||||
return array(
|
||||
array('a'.str_repeat('.a', 40000)),
|
||||
array(str_repeat(':', 101)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class RequestContentProxy extends Request
|
||||
|
@ -67,14 +67,24 @@ class ServerBagTest extends \PHPUnit_Framework_TestCase
|
||||
), $bag->getHeaders());
|
||||
}
|
||||
|
||||
public function testHttpBasicAuthWithPhpCgiBogus()
|
||||
{
|
||||
$bag = new ServerBag(array('HTTP_AUTHORIZATION' => 'Basic_'.base64_encode('foo:bar')));
|
||||
|
||||
// Username and passwords should not be set as the header is bogus
|
||||
$headers = $bag->getHeaders();
|
||||
$this->assertFalse(isset($headers['PHP_AUTH_USER']));
|
||||
$this->assertFalse(isset($headers['PHP_AUTH_PW']));
|
||||
}
|
||||
|
||||
public function testHttpBasicAuthWithPhpCgiRedirect()
|
||||
{
|
||||
$bag = new ServerBag(array('REDIRECT_HTTP_AUTHORIZATION' => 'Basic '.base64_encode('foo:bar')));
|
||||
$bag = new ServerBag(array('REDIRECT_HTTP_AUTHORIZATION' => 'Basic '.base64_encode('username:pass:word')));
|
||||
|
||||
$this->assertEquals(array(
|
||||
'AUTHORIZATION' => 'Basic '.base64_encode('foo:bar'),
|
||||
'PHP_AUTH_USER' => 'foo',
|
||||
'PHP_AUTH_PW' => 'bar'
|
||||
'AUTHORIZATION' => 'Basic '.base64_encode('username:pass:word'),
|
||||
'PHP_AUTH_USER' => 'username',
|
||||
'PHP_AUTH_PW' => 'pass:word'
|
||||
), $bag->getHeaders());
|
||||
}
|
||||
|
||||
@ -100,6 +110,17 @@ class ServerBagTest extends \PHPUnit_Framework_TestCase
|
||||
), $bag->getHeaders());
|
||||
}
|
||||
|
||||
public function testHttpDigestAuthWithPhpCgiBogus()
|
||||
{
|
||||
$digest = 'Digest_username="foo", realm="acme", nonce="'.md5('secret').'", uri="/protected, qop="auth"';
|
||||
$bag = new ServerBag(array('HTTP_AUTHORIZATION' => $digest));
|
||||
|
||||
// Username and passwords should not be set as the header is bogus
|
||||
$headers = $bag->getHeaders();
|
||||
$this->assertFalse(isset($headers['PHP_AUTH_USER']));
|
||||
$this->assertFalse(isset($headers['PHP_AUTH_PW']));
|
||||
}
|
||||
|
||||
public function testHttpDigestAuthWithPhpCgiRedirect()
|
||||
{
|
||||
$digest = 'Digest username="foo", realm="acme", nonce="'.md5('secret').'", uri="/protected, qop="auth"';
|
||||
|
@ -12,11 +12,11 @@
|
||||
namespace Symfony\Component\HttpKernel\EventListener;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\IpUtils;
|
||||
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
use Symfony\Component\HttpKernel\UriSigner;
|
||||
use Symfony\Component\HttpKernel\HttpKernelInterface;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
/**
|
||||
@ -25,8 +25,8 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
* All URL paths starting with /_fragment are handled as
|
||||
* content fragments by this listener.
|
||||
*
|
||||
* If the request does not come from a trusted IP, it throws an
|
||||
* AccessDeniedHttpException exception.
|
||||
* If throws an AccessDeniedHttpException exception if the request
|
||||
* is not signed or if it is not an internal sub-request.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
@ -62,7 +62,9 @@ class FragmentListener implements EventSubscriberInterface
|
||||
return;
|
||||
}
|
||||
|
||||
$this->validateRequest($request);
|
||||
if ($event->isMasterRequest()) {
|
||||
$this->validateRequest($request);
|
||||
}
|
||||
|
||||
parse_str($request->query->get('_path', ''), $attributes);
|
||||
$request->attributes->add($attributes);
|
||||
@ -77,13 +79,6 @@ class FragmentListener implements EventSubscriberInterface
|
||||
throw new AccessDeniedHttpException();
|
||||
}
|
||||
|
||||
// does the Request come from a trusted IP?
|
||||
$trustedIps = array_merge($this->getLocalIpAddresses(), $request->getTrustedProxies());
|
||||
$remoteAddress = $request->server->get('REMOTE_ADDR');
|
||||
if (IpUtils::checkIp($remoteAddress, $trustedIps)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// is the Request signed?
|
||||
// we cannot use $request->getUri() here as we want to work with the original URI (no query string reordering)
|
||||
if ($this->signer->check($request->getSchemeAndHttpHost().$request->getBaseUrl().$request->getPathInfo().(null !== ($qs = $request->server->get('QUERY_STRING')) ? '?'.$qs : ''))) {
|
||||
@ -93,6 +88,11 @@ class FragmentListener implements EventSubscriberInterface
|
||||
throw new AccessDeniedHttpException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Deprecated since 2.3.19, to be removed in 3.0.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
protected function getLocalIpAddresses()
|
||||
{
|
||||
return array('127.0.0.1', 'fe80::1', '::1');
|
||||
|
@ -25,7 +25,7 @@ abstract class SessionListener implements EventSubscriberInterface
|
||||
{
|
||||
public function onKernelRequest(GetResponseEvent $event)
|
||||
{
|
||||
if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) {
|
||||
if (!$event->isMasterRequest()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,7 @@ abstract class TestSessionListener implements EventSubscriberInterface
|
||||
{
|
||||
public function onKernelRequest(GetResponseEvent $event)
|
||||
{
|
||||
if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) {
|
||||
if (!$event->isMasterRequest()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -55,7 +55,7 @@ abstract class TestSessionListener implements EventSubscriberInterface
|
||||
*/
|
||||
public function onKernelResponse(FilterResponseEvent $event)
|
||||
{
|
||||
if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) {
|
||||
if (!$event->isMasterRequest()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,7 @@ use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Controller\ControllerReference;
|
||||
use Symfony\Component\HttpKernel\HttpCache\SurrogateInterface;
|
||||
use Symfony\Component\HttpKernel\UriSigner;
|
||||
|
||||
/**
|
||||
* Implements Surrogate rendering strategy.
|
||||
@ -25,6 +26,7 @@ abstract class AbstractSurrogateFragmentRenderer extends RoutableFragmentRendere
|
||||
{
|
||||
private $surrogate;
|
||||
private $inlineStrategy;
|
||||
private $signer;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
@ -34,11 +36,13 @@ abstract class AbstractSurrogateFragmentRenderer extends RoutableFragmentRendere
|
||||
*
|
||||
* @param SurrogateInterface $surrogate An Surrogate instance
|
||||
* @param FragmentRendererInterface $inlineStrategy The inline strategy to use when the surrogate is not supported
|
||||
* @param UriSigner $signer
|
||||
*/
|
||||
public function __construct(SurrogateInterface $surrogate = null, FragmentRendererInterface $inlineStrategy)
|
||||
public function __construct(SurrogateInterface $surrogate = null, FragmentRendererInterface $inlineStrategy, UriSigner $signer = null)
|
||||
{
|
||||
$this->surrogate = $surrogate;
|
||||
$this->inlineStrategy = $inlineStrategy;
|
||||
$this->signer = $signer;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -64,16 +68,28 @@ abstract class AbstractSurrogateFragmentRenderer extends RoutableFragmentRendere
|
||||
}
|
||||
|
||||
if ($uri instanceof ControllerReference) {
|
||||
$uri = $this->generateFragmentUri($uri, $request);
|
||||
$uri = $this->generateSignedFragmentUri($uri, $request);
|
||||
}
|
||||
|
||||
$alt = isset($options['alt']) ? $options['alt'] : null;
|
||||
if ($alt instanceof ControllerReference) {
|
||||
$alt = $this->generateFragmentUri($alt, $request);
|
||||
$alt = $this->generateSignedFragmentUri($alt, $request);
|
||||
}
|
||||
|
||||
$tag = $this->surrogate->renderIncludeTag($uri, $alt, isset($options['ignore_errors']) ? $options['ignore_errors'] : false, isset($options['comment']) ? $options['comment'] : '');
|
||||
|
||||
return new Response($tag);
|
||||
}
|
||||
|
||||
private function generateSignedFragmentUri($uri, Request $request)
|
||||
{
|
||||
if (null === $this->signer) {
|
||||
throw new \LogicException('You must use a URI when using the ESI rendering strategy or set a URL signer.');
|
||||
}
|
||||
|
||||
// we need to sign the absolute URI, but want to return the path only.
|
||||
$fragmentUri = $this->signer->sign($this->generateFragmentUri($uri, $request, true));
|
||||
|
||||
return substr($fragmentUri, strlen($request->getSchemeAndHttpHost()));
|
||||
}
|
||||
}
|
||||
|
@ -11,10 +11,6 @@
|
||||
|
||||
namespace Symfony\Component\HttpKernel\Fragment;
|
||||
|
||||
use Symfony\Component\HttpKernel\Controller\ControllerReference;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\UriSigner;
|
||||
|
||||
/**
|
||||
* Implements the SSI rendering strategy.
|
||||
*
|
||||
@ -22,19 +18,6 @@ use Symfony\Component\HttpKernel\UriSigner;
|
||||
*/
|
||||
class SsiFragmentRenderer extends AbstractSurrogateFragmentRenderer
|
||||
{
|
||||
/** @var UriSigner */
|
||||
private $signer;
|
||||
|
||||
/**
|
||||
* Set uri signer
|
||||
*
|
||||
* @param UriSigner $signer
|
||||
*/
|
||||
public function setUriSigner(UriSigner $signer)
|
||||
{
|
||||
$this->signer = $signer;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
@ -42,18 +25,4 @@ class SsiFragmentRenderer extends AbstractSurrogateFragmentRenderer
|
||||
{
|
||||
return 'ssi';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function generateFragmentUri(ControllerReference $reference, Request $request, $absolute = false, $strict = true)
|
||||
{
|
||||
$uri = parent::generateFragmentUri($reference, $request, $absolute, $strict);
|
||||
|
||||
if ($this->signer) {
|
||||
$uri = $this->signer->sign($uri);
|
||||
}
|
||||
|
||||
return $uri;
|
||||
}
|
||||
}
|
||||
|
@ -47,19 +47,6 @@ class FragmentListenerTest extends \PHPUnit_Framework_TestCase
|
||||
$listener->onKernelRequest($event);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
|
||||
*/
|
||||
public function testAccessDeniedWithNonLocalIps()
|
||||
{
|
||||
$request = Request::create('http://example.com/_fragment', 'GET', array(), array(), array(), array('REMOTE_ADDR' => '10.0.0.1'));
|
||||
|
||||
$listener = new FragmentListener(new UriSigner('foo'));
|
||||
$event = $this->createGetResponseEvent($request);
|
||||
|
||||
$listener->onKernelRequest($event);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
|
||||
*/
|
||||
|
@ -15,6 +15,7 @@ use Symfony\Component\HttpKernel\Controller\ControllerReference;
|
||||
use Symfony\Component\HttpKernel\Fragment\EsiFragmentRenderer;
|
||||
use Symfony\Component\HttpKernel\HttpCache\Esi;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\UriSigner;
|
||||
|
||||
class EsiFragmentRendererTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
@ -41,7 +42,52 @@ class EsiFragmentRendererTest extends \PHPUnit_Framework_TestCase
|
||||
$this->assertEquals('<esi:include src="/" />', $strategy->render('/', $request)->getContent());
|
||||
$this->assertEquals("<esi:comment text=\"This is a comment\" />\n<esi:include src=\"/\" />", $strategy->render('/', $request, array('comment' => 'This is a comment'))->getContent());
|
||||
$this->assertEquals('<esi:include src="/" alt="foo" />', $strategy->render('/', $request, array('alt' => 'foo'))->getContent());
|
||||
$this->assertEquals('<esi:include src="/_fragment?_path=_format%3Dhtml%26_locale%3Dfr%26_controller%3Dmain_controller" alt="/_fragment?_path=_format%3Dhtml%26_locale%3Dfr%26_controller%3Dalt_controller" />', $strategy->render(new ControllerReference('main_controller', array(), array()), $request, array('alt' => new ControllerReference('alt_controller', array(), array())))->getContent());
|
||||
}
|
||||
|
||||
public function testRenderControllerReference()
|
||||
{
|
||||
$signer = new UriSigner('foo');
|
||||
$strategy = new EsiFragmentRenderer(new Esi(), $this->getInlineStrategy(), $signer);
|
||||
|
||||
$request = Request::create('/');
|
||||
$request->setLocale('fr');
|
||||
$request->headers->set('Surrogate-Capability', 'ESI/1.0');
|
||||
|
||||
$reference = new ControllerReference('main_controller', array(), array());
|
||||
$altReference = new ControllerReference('alt_controller', array(), array());
|
||||
|
||||
$this->assertEquals(
|
||||
'<esi:include src="/_fragment?_path=_format%3Dhtml%26_locale%3Dfr%26_controller%3Dmain_controller&_hash=Jz1P8NErmhKTeI6onI1EdAXTB85359MY3RIk5mSJ60w%3D" alt="/_fragment?_path=_format%3Dhtml%26_locale%3Dfr%26_controller%3Dalt_controller&_hash=iPJEdRoUpGrM1ztqByiorpfMPtiW%2FOWwdH1DBUXHhEc%3D" />',
|
||||
$strategy->render($reference, $request, array('alt' => $altReference))->getContent()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \LogicException
|
||||
*/
|
||||
public function testRenderControllerReferenceWithoutSignerThrowsException()
|
||||
{
|
||||
$strategy = new EsiFragmentRenderer(new Esi(), $this->getInlineStrategy());
|
||||
|
||||
$request = Request::create('/');
|
||||
$request->setLocale('fr');
|
||||
$request->headers->set('Surrogate-Capability', 'ESI/1.0');
|
||||
|
||||
$strategy->render(new ControllerReference('main_controller'), $request);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \LogicException
|
||||
*/
|
||||
public function testRenderAltControllerReferenceWithoutSignerThrowsException()
|
||||
{
|
||||
$strategy = new EsiFragmentRenderer(new Esi(), $this->getInlineStrategy());
|
||||
|
||||
$request = Request::create('/');
|
||||
$request->setLocale('fr');
|
||||
$request->headers->set('Surrogate-Capability', 'ESI/1.0');
|
||||
|
||||
$strategy->render('/', $request, array('alt' => new ControllerReference('alt_controller')));
|
||||
}
|
||||
|
||||
private function getInlineStrategy($called = false)
|
||||
|
@ -13,11 +13,49 @@ namespace Symfony\Component\Security\Core\Tests\Util;
|
||||
|
||||
use Symfony\Component\Security\Core\Util\StringUtils;
|
||||
|
||||
/**
|
||||
* Data from PHP.net's hash_equals tests
|
||||
*/
|
||||
class StringUtilsTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testEquals()
|
||||
public function dataProviderTrue()
|
||||
{
|
||||
$this->assertTrue(StringUtils::equals('password', 'password'));
|
||||
$this->assertFalse(StringUtils::equals('password', 'foo'));
|
||||
return array(
|
||||
array('same', 'same'),
|
||||
array('', ''),
|
||||
array(123, 123),
|
||||
array(null, ''),
|
||||
array(null, null),
|
||||
);
|
||||
}
|
||||
|
||||
public function dataProviderFalse()
|
||||
{
|
||||
return array(
|
||||
array('not1same', 'not2same'),
|
||||
array('short', 'longer'),
|
||||
array('longer', 'short'),
|
||||
array('', 'notempty'),
|
||||
array('notempty', ''),
|
||||
array(123, 'NaN'),
|
||||
array('NaN', 123),
|
||||
array(null, 123),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider dataProviderTrue
|
||||
*/
|
||||
public function testEqualsTrue($known, $user)
|
||||
{
|
||||
$this->assertTrue(StringUtils::equals($known, $user));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider dataProviderFalse
|
||||
*/
|
||||
public function testEqualsFalse($known, $user)
|
||||
{
|
||||
$this->assertFalse(StringUtils::equals($known, $user));
|
||||
}
|
||||
}
|
||||
|
@ -35,23 +35,19 @@ class StringUtils
|
||||
*/
|
||||
public static function equals($knownString, $userInput)
|
||||
{
|
||||
// Prevent issues if string length is 0
|
||||
$knownString .= chr(0);
|
||||
$userInput .= chr(0);
|
||||
|
||||
$knownLen = strlen($knownString);
|
||||
$userLen = strlen($userInput);
|
||||
|
||||
// Extend the known string to avoid uninitialized string offsets
|
||||
$knownString .= $userInput;
|
||||
|
||||
// Set the result to the difference between the lengths
|
||||
$result = $knownLen - $userLen;
|
||||
|
||||
// Note that we ALWAYS iterate over the user-supplied length
|
||||
// This is to prevent leaking length information
|
||||
for ($i = 0; $i < $userLen; $i++) {
|
||||
// Using % here is a trick to prevent notices
|
||||
// It's safe, since if the lengths are different
|
||||
// $result is already non-0
|
||||
$result |= (ord($knownString[$i % $knownLen]) ^ ord($userInput[$i]));
|
||||
$result |= (ord($knownString[$i]) ^ ord($userInput[$i]));
|
||||
}
|
||||
|
||||
// They are only identical strings if $result is exactly 0...
|
||||
|
@ -347,7 +347,7 @@ class Translator implements TranslatorInterface
|
||||
*
|
||||
* @throws \InvalidArgumentException If the locale contains invalid characters
|
||||
*/
|
||||
private function assertValidLocale($locale)
|
||||
protected function assertValidLocale($locale)
|
||||
{
|
||||
if (1 !== preg_match('/^[a-z0-9@_\\.\\-]*$/i', $locale)) {
|
||||
throw new \InvalidArgumentException(sprintf('Invalid "%s" locale.', $locale));
|
||||
|
@ -851,6 +851,17 @@ abstract class AbstractValidatorTest extends \PHPUnit_Framework_TestCase
|
||||
$this->validateProperty('VALUE', 'someProperty');
|
||||
}
|
||||
|
||||
/**
|
||||
* https://github.com/symfony/symfony/issues/11604
|
||||
*/
|
||||
public function testValidatePropertyWithoutConstraints()
|
||||
{
|
||||
$entity = new Entity();
|
||||
$violations = $this->validateProperty($entity, 'lastName');
|
||||
|
||||
$this->assertCount(0, $violations, '->validateProperty() returns no violations if no constraints have been configured for the property being validated');
|
||||
}
|
||||
|
||||
public function testValidatePropertyValue()
|
||||
{
|
||||
$test = $this;
|
||||
@ -970,6 +981,17 @@ abstract class AbstractValidatorTest extends \PHPUnit_Framework_TestCase
|
||||
$this->validatePropertyValue('VALUE', 'someProperty', 'someValue');
|
||||
}
|
||||
|
||||
/**
|
||||
* https://github.com/symfony/symfony/issues/11604
|
||||
*/
|
||||
public function testValidatePropertyValueWithoutConstraints()
|
||||
{
|
||||
$entity = new Entity();
|
||||
$violations = $this->validatePropertyValue($entity, 'lastName', 'foo');
|
||||
|
||||
$this->assertCount(0, $violations, '->validatePropertyValue() returns no violations if no constraints have been configured for the property being validated');
|
||||
}
|
||||
|
||||
public function testValidateObjectOnlyOncePerGroup()
|
||||
{
|
||||
$entity = new Entity();
|
||||
|
Reference in New Issue
Block a user