From 195c57e1f50765aff33137689b16e126a689056a Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 16 Mar 2015 15:12:02 +0100 Subject: [PATCH] Safe escaping of fragments for eval() --- .../Component/HttpKernel/HttpCache/Esi.php | 62 +++++++++---------- .../HttpKernel/Tests/HttpCache/EsiTest.php | 4 +- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/src/Symfony/Component/HttpKernel/HttpCache/Esi.php b/src/Symfony/Component/HttpKernel/HttpCache/Esi.php index 54e841530f..58b6265656 100644 --- a/src/Symfony/Component/HttpKernel/HttpCache/Esi.php +++ b/src/Symfony/Component/HttpKernel/HttpCache/Esi.php @@ -29,6 +29,10 @@ use Symfony\Component\HttpKernel\HttpKernelInterface; class Esi { private $contentTypes; + private $phpEscapeMap = array( + array('', '', '', ''), + ); /** * Constructor. @@ -158,10 +162,34 @@ class Esi // we don't use a proper XML parser here as we can have ESI tags in a plain text response $content = $response->getContent(); - $content = str_replace(array('', ''), $content); - $content = preg_replace_callback('##', array($this, 'handleEsiIncludeTag'), $content); - $content = preg_replace('#]*(?:/|#', '', $content); $content = preg_replace('#.*?#', '', $content); + $content = preg_replace('#]*(?:/|#', '', $content); + + $chunks = preg_split('##', $content, -1, PREG_SPLIT_DELIM_CAPTURE); + $chunks[0] = str_replace($this->phpEscapeMap[0], $this->phpEscapeMap[1], $chunks[0]); + + $i = 1; + while (isset($chunks[$i])) { + $options = array(); + preg_match_all('/(src|onerror|alt)="([^"]*?)"/', $chunks[$i], $matches, PREG_SET_ORDER); + foreach ($matches as $set) { + $options[$set[1]] = $set[2]; + } + + if (!isset($options['src'])) { + throw new \RuntimeException('Unable to process an ESI tag without a "src" attribute.'); + } + + $chunks[$i] = sprintf('esi->handle($this, %s, %s, %s) ?>'."\n", + var_export($options['src'], true), + var_export(isset($options['alt']) ? $options['alt'] : '', true), + isset($options['onerror']) && 'continue' == $options['onerror'] ? 'true' : 'false' + ); + ++$i; + $chunks[$i] = str_replace($this->phpEscapeMap[0], $this->phpEscapeMap[1], $chunks[$i]); + ++$i; + } + $content = implode('', $chunks); $response->setContent($content); $response->headers->set('X-Body-Eval', 'ESI'); @@ -214,32 +242,4 @@ class Esi } } } - - /** - * Handles an ESI include tag (called internally). - * - * @param array $attributes An array containing the attributes. - * - * @return string The response content for the include. - * - * @throws \RuntimeException - */ - private function handleEsiIncludeTag($attributes) - { - $options = array(); - preg_match_all('/(src|onerror|alt)="([^"]*?)"/', $attributes[1], $matches, PREG_SET_ORDER); - foreach ($matches as $set) { - $options[$set[1]] = $set[2]; - } - - if (!isset($options['src'])) { - throw new \RuntimeException('Unable to process an ESI tag without a "src" attribute.'); - } - - return sprintf('esi->handle($this, %s, %s, %s) ?>'."\n", - var_export($options['src'], true), - var_export(isset($options['alt']) ? $options['alt'] : '', true), - isset($options['onerror']) && 'continue' == $options['onerror'] ? 'true' : 'false' - ); - } } diff --git a/src/Symfony/Component/HttpKernel/Tests/HttpCache/EsiTest.php b/src/Symfony/Component/HttpKernel/Tests/HttpCache/EsiTest.php index ad400c69ae..d1411f016b 100644 --- a/src/Symfony/Component/HttpKernel/Tests/HttpCache/EsiTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/HttpCache/EsiTest.php @@ -124,10 +124,10 @@ class EsiTest extends \PHPUnit_Framework_TestCase $esi = new Esi(); $request = Request::create('/'); - $response = new Response('foo <%= "lala" %>'); + $response = new Response(''); $esi->process($request, $response); - $this->assertEquals('foo php die("foo"); ?>= "lala" %>', $response->getContent()); + $this->assertEquals('php cript language=php>', $response->getContent()); } /**