Merge branch '2.3' into 2.4

* 2.3:
  [HttpKernel] fixed internal fragment handling
  fixing yaml indentation
  [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
  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
  Fix potential DoS when parsing HOST
  Made optimization deprecating modulus operator

Conflicts:
	src/Symfony/Bundle/FrameworkBundle/Resources/config/esi.xml
	src/Symfony/Component/HttpFoundation/Request.php
	src/Symfony/Component/HttpFoundation/Tests/RequestTest.php
	src/Symfony/Component/HttpKernel/Fragment/EsiFragmentRenderer.php
This commit is contained in:
Fabien Potencier 2014-09-03 10:42:07 +02:00
commit 8358ebb15a
28 changed files with 842 additions and 172 deletions

View File

@ -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:

View File

@ -39,6 +39,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>
</services>

View File

@ -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)
) ?>

View File

@ -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);
}
}

View File

@ -92,6 +92,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');
}
public function testGetLocale()
{
$request = $this->getMock('Symfony\Component\HttpFoundation\Request');
@ -131,6 +141,49 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
$this->assertSame('en', $translator->getLocale());
}
public function testGetLocaleWithInvalidLocale()
{
$request = $this->getMock('Symfony\Component\HttpFoundation\Request');
$request
->expects($this->once())
->method('getLocale')
->will($this->returnValue('foo bar'))
;
$request
->expects($this->once())
->method('getDefaultLocale')
->will($this->returnValue('en-US'))
;
$container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface');
$container
->expects($this->once())
->method('isScopeActive')
->with('request')
->will($this->returnValue(true))
;
$container
->expects($this->once())
->method('has')
->with('request')
->will($this->returnValue(true))
;
$container
->expects($this->any())
->method('get')
->with('request')
->will($this->returnValue($request))
;
$translator = new Translator($container, new MessageSelector());
$this->assertSame('en-US', $translator->getLocale());
}
protected function getCatalogue($locale, $messages)
{
$catalogue = new MessageCatalogue($locale);
@ -211,9 +264,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')),
@ -231,3 +284,14 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
return $translator;
}
}
class TranslatorWithInvalidLocale extends Translator
{
/**
* {@inheritdoc}
*/
public function setLocale($locale)
{
$this->locale = $locale;
}
}

View File

@ -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;

View File

@ -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);
}
}

View File

@ -66,7 +66,11 @@ class Translator extends BaseTranslator
public function getLocale()
{
if (null === $this->locale && $this->container->isScopeActive('request') && $this->container->has('request')) {
$this->locale = $this->container->get('request')->getLocale();
try {
$this->setLocale($this->container->get('request')->getLocale());
} catch (\InvalidArgumentException $e) {
$this->setLocale($this->container->get('request')->getDefaultLocale());
}
}
return $this->locale;
@ -87,6 +91,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();

View File

@ -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);
}
}

View File

@ -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()));
}
}

View File

@ -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.
*

View File

@ -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>

View File

@ -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">
&#187;&#160;<a href="{{ path('_profiler_purge', { 'token': token }) }}">Purge</a>
</div>
<div style="margin-bottom: 10px">
&#187;&#160;<a href="{{ path('_profiler_export', { 'token': token }) }}">Export</a>
</div>
{% endif %}
&#187;&#160;<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">&#187;&#160;<a href="{{ path('_profiler_purge', { 'token': token }) }}">Purge</a></div>
</div>
{% endif %}

View File

@ -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());
}
}

View File

@ -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());
}
}

View File

@ -0,0 +1 @@
Tzo0NToiU3ltZm9ueVxDb21wb25lbnRcSHR0cEtlcm5lbFxQcm9maWxlclxQcm9maWxlIjo4OntzOjUyOiIAU3ltZm9ueVxDb21wb25lbnRcSHR0cEtlcm5lbFxQcm9maWxlclxQcm9maWxlAHRva2VuIjtzOjU6IlRPS0VOIjtzOjUzOiIAU3ltZm9ueVxDb21wb25lbnRcSHR0cEtlcm5lbFxQcm9maWxlclxQcm9maWxlAHBhcmVudCI7TjtzOjU1OiIAU3ltZm9ueVxDb21wb25lbnRcSHR0cEtlcm5lbFxQcm9maWxlclxQcm9maWxlAGNoaWxkcmVuIjthOjA6e31zOjU3OiIAU3ltZm9ueVxDb21wb25lbnRcSHR0cEtlcm5lbFxQcm9maWxlclxQcm9maWxlAGNvbGxlY3RvcnMiO2E6MDp7fXM6NDk6IgBTeW1mb255XENvbXBvbmVudFxIdHRwS2VybmVsXFByb2ZpbGVyXFByb2ZpbGUAaXAiO047czo1MzoiAFN5bWZvbnlcQ29tcG9uZW50XEh0dHBLZXJuZWxcUHJvZmlsZXJcUHJvZmlsZQBtZXRob2QiO047czo1MDoiAFN5bWZvbnlcQ29tcG9uZW50XEh0dHBLZXJuZWxcUHJvZmlsZXJcUHJvZmlsZQB1cmwiO047czo1MToiAFN5bWZvbnlcQ29tcG9uZW50XEh0dHBLZXJuZWxcUHJvZmlsZXJcUHJvZmlsZQB0aW1lIjtOO30=

View File

@ -52,7 +52,8 @@ class Filesystem
if ($doCopy) {
// https://bugs.php.net/bug.php?id=64634
$source = fopen($originFile, 'r');
$target = fopen($targetFile, 'w');
// Stream context created to allow files overwrite when using FTP stream wrapper - disabled by default
$target = fopen($targetFile, 'w', null, stream_context_create(array('ftp' => array('overwrite' => true))));
stream_copy_to_stream($source, $target);
fclose($source);
fclose($target);

View File

@ -728,17 +728,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');
}
}
/**
@ -754,12 +767,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)

View File

@ -1172,7 +1172,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));
}
@ -1381,6 +1382,16 @@ class Request
}
}
/**
* Get the default locale.
*
* @return string
*/
public function getDefaultLocale()
{
return $this->defaultLocale;
}
/**
* Sets the locale.
*

View File

@ -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;

View File

@ -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

View File

@ -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"';

View File

@ -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 (HttpKernelInterface::MASTER_REQUEST === $event->getRequestType()) {
$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');

View File

@ -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\Esi;
use Symfony\Component\HttpKernel\UriSigner;
/**
* Implements the ESI rendering strategy.
@ -25,6 +26,7 @@ class EsiFragmentRenderer extends RoutableFragmentRenderer
{
private $esi;
private $inlineStrategy;
private $signer;
/**
* Constructor.
@ -34,11 +36,13 @@ class EsiFragmentRenderer extends RoutableFragmentRenderer
*
* @param Esi $esi An Esi instance
* @param FragmentRendererInterface $inlineStrategy The inline strategy to use when ESI is not supported
* @param UriSigner $signer
*/
public function __construct(Esi $esi = null, InlineFragmentRenderer $inlineStrategy)
public function __construct(Esi $esi = null, InlineFragmentRenderer $inlineStrategy, UriSigner $signer = null)
{
$this->esi = $esi;
$this->inlineStrategy = $inlineStrategy;
$this->signer = $signer;
}
/**
@ -61,12 +65,12 @@ class EsiFragmentRenderer extends RoutableFragmentRenderer
}
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->esi->renderIncludeTag($uri, $alt, isset($options['ignore_errors']) ? $options['ignore_errors'] : false, isset($options['comment']) ? $options['comment'] : '');
@ -81,4 +85,16 @@ class EsiFragmentRenderer extends RoutableFragmentRenderer
{
return 'esi';
}
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()));
}
}

View File

@ -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
*/

View File

@ -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=wDaFy1WsZUOWrrMdRMgJ1cOskFo%3D" alt="/_fragment?_path=_format%3Dhtml%26_locale%3Dfr%26_controller%3Dalt_controller&_hash=56ycnRUlgaremRQVStZsGbVhIv8%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)

View File

@ -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...

View File

@ -316,7 +316,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));