Skip to content

Commit

Permalink
Safe escaping of fragments for eval()
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolas-grekas authored and fabpot committed Apr 1, 2015
1 parent 9215c22 commit 195c57e
Show file tree
Hide file tree
Showing 2 changed files with 33 additions and 33 deletions.
62 changes: 31 additions & 31 deletions src/Symfony/Component/HttpKernel/HttpCache/Esi.php
Expand Up @@ -29,6 +29,10 @@
class Esi
{
private $contentTypes;
private $phpEscapeMap = array(
array('<?', '<%', '<s', '<S'),
array('<?php echo "<?"; ?>', '<?php echo "<%"; ?>', '<?php echo "<s"; ?>', '<?php echo "<S"; ?>'),
);

/**
* Constructor.
Expand Down Expand Up @@ -158,10 +162,34 @@ public function process(Request $request, Response $response)

// 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('<?', '<%'), array('<?php echo "<?"; ?>', '<?php echo "<%"; ?>'), $content);
$content = preg_replace_callback('#<esi\:include\s+(.*?)\s*(?:/|</esi\:include)>#', array($this, 'handleEsiIncludeTag'), $content);
$content = preg_replace('#<esi\:comment[^>]*(?:/|</esi\:comment)>#', '', $content);
$content = preg_replace('#<esi\:remove>.*?</esi\:remove>#', '', $content);
$content = preg_replace('#<esi\:comment[^>]*(?:/|</esi\:comment)>#', '', $content);

$chunks = preg_split('#<esi\:include\s+(.*?)\s*(?:/|</esi\:include)>#', $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('<?php echo $this->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');
Expand Down Expand Up @@ -214,32 +242,4 @@ public function handle(HttpCache $cache, $uri, $alt, $ignoreErrors)
}
}
}

/**
* 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('<?php echo $this->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'
);
}
}
4 changes: 2 additions & 2 deletions src/Symfony/Component/HttpKernel/Tests/HttpCache/EsiTest.php
Expand Up @@ -124,10 +124,10 @@ public function testProcessEscapesPhpTags()
$esi = new Esi();

$request = Request::create('/');
$response = new Response('foo <?php die("foo"); ?><%= "lala" %>');
$response = new Response('<?php <? <% <script language=php>');
$esi->process($request, $response);

$this->assertEquals('foo <?php echo "<?"; ?>php die("foo"); ?><?php echo "<%"; ?>= "lala" %>', $response->getContent());
$this->assertEquals('<?php echo "<?"; ?>php <?php echo "<?"; ?> <?php echo "<%"; ?> <?php echo "<s"; ?>cript language=php>', $response->getContent());
}

/**
Expand Down

0 comments on commit 195c57e

Please sign in to comment.