2010-06-23 20:42:41 +01:00
< ? php
/*
2011-01-15 13:29:43 +00:00
* This file is part of the Symfony package .
2010-06-23 20:42:41 +01:00
*
2011-03-06 11:40:06 +00:00
* ( c ) Fabien Potencier < fabien @ symfony . com >
2010-06-23 20:42:41 +01:00
*
2011-01-15 13:29:43 +00:00
* For the full copyright and license information , please view the LICENSE
* file that was distributed with this source code .
2010-06-23 20:42:41 +01:00
*/
2011-01-26 20:38:45 +00:00
namespace Symfony\Component\HttpKernel\HttpCache ;
2011-01-15 13:29:43 +00:00
use Symfony\Component\HttpFoundation\Request ;
use Symfony\Component\HttpFoundation\Response ;
use Symfony\Component\HttpKernel\HttpKernelInterface ;
2010-06-23 20:42:41 +01:00
/**
* Esi implements the ESI capabilities to Request and Response instances .
*
* For more information , read the following W3C notes :
*
* * ESI Language Specification 1.0 ( http :// www . w3 . org / TR / esi - lang )
*
* * Edge Architecture Specification ( http :// www . w3 . org / TR / edge - arch )
*
2011-03-06 11:40:06 +00:00
* @ author Fabien Potencier < fabien @ symfony . com >
2010-06-23 20:42:41 +01:00
*/
2014-03-23 00:04:57 +00:00
class Esi implements SurrogateInterface
2010-06-23 20:42:41 +01:00
{
2011-03-23 18:47:16 +00:00
private $contentTypes ;
2015-03-16 14:12:02 +00:00
private $phpEscapeMap = array (
array ( '<?' , '<%' , '<s' , '<S' ),
array ( '<?php echo "<?"; ?>' , '<?php echo "<%"; ?>' , '<?php echo "<s"; ?>' , '<?php echo "<S"; ?>' ),
);
2010-06-23 20:42:41 +01:00
/**
* Constructor .
*
* @ param array $contentTypes An array of content - type that should be parsed for ESI information .
2014-11-30 13:33:44 +00:00
* ( default : text / html , text / xml , application / xhtml + xml , and application / xml )
2010-06-23 20:42:41 +01:00
*/
2012-12-11 23:08:01 +00:00
public function __construct ( array $contentTypes = array ( 'text/html' , 'text/xml' , 'application/xhtml+xml' , 'application/xml' ))
2010-06-23 20:42:41 +01:00
{
$this -> contentTypes = $contentTypes ;
}
2014-03-23 00:04:57 +00:00
public function getName ()
{
return 'esi' ;
}
2011-02-22 12:50:26 +00:00
/**
* Returns a new cache strategy instance .
*
2014-03-23 00:04:57 +00:00
* @ return ResponseCacheStrategyInterface A ResponseCacheStrategyInterface instance
2011-02-22 12:50:26 +00:00
*/
public function createCacheStrategy ()
{
2015-01-08 09:07:02 +00:00
return new ResponseCacheStrategy ();
2011-02-22 12:50:26 +00:00
}
2010-06-23 20:42:41 +01:00
/**
* Checks that at least one surrogate has ESI / 1.0 capability .
*
2010-07-27 14:33:28 +01:00
* @ param Request $request A Request instance
2010-06-23 20:42:41 +01:00
*
2014-11-30 13:33:44 +00:00
* @ return bool true if one surrogate has ESI / 1.0 capability , false otherwise
2010-06-23 20:42:41 +01:00
*/
2014-03-23 00:04:57 +00:00
public function hasSurrogateCapability ( Request $request )
{
2015-01-08 09:07:02 +00:00
if ( null === $value = $request -> headers -> get ( 'Surrogate-Capability' )) {
return false ;
}
return false !== strpos ( $value , 'ESI/1.0' );
2014-03-23 00:04:57 +00:00
}
/**
* Checks that at least one surrogate has ESI / 1.0 capability .
*
* @ param Request $request A Request instance
*
2014-12-05 20:11:30 +00:00
* @ return bool true if one surrogate has ESI / 1.0 capability , false otherwise
2014-03-23 00:04:57 +00:00
*
2014-12-29 23:26:56 +00:00
* @ deprecated since version 2.6 , to be removed in 3.0 . Use hasSurrogateCapability () instead
2014-03-23 00:04:57 +00:00
*/
2010-06-23 20:42:41 +01:00
public function hasSurrogateEsiCapability ( Request $request )
{
2014-12-21 11:39:54 +00:00
trigger_error ( 'The ' . __METHOD__ . ' method is deprecated since version 2.6 and will be removed in 3.0. Use the hasSurrogateCapability() method instead.' , E_USER_DEPRECATED );
2015-01-08 09:07:02 +00:00
return $this -> hasSurrogateCapability ( $request );
2010-06-23 20:42:41 +01:00
}
/**
* Adds ESI / 1.0 capability to the given Request .
*
2010-07-27 14:33:28 +01:00
* @ param Request $request A Request instance
2010-06-23 20:42:41 +01:00
*/
2014-03-23 00:04:57 +00:00
public function addSurrogateCapability ( Request $request )
{
2015-01-08 09:07:02 +00:00
$current = $request -> headers -> get ( 'Surrogate-Capability' );
$new = 'symfony2="ESI/1.0"' ;
$request -> headers -> set ( 'Surrogate-Capability' , $current ? $current . ', ' . $new : $new );
2014-03-23 00:04:57 +00:00
}
/**
* Adds ESI / 1.0 capability to the given Request .
*
* @ param Request $request A Request instance
*
2014-12-29 23:26:56 +00:00
* @ deprecated since version 2.6 , to be removed in 3.0 . Use addSurrogateCapability () instead
2014-03-23 00:04:57 +00:00
*/
2010-06-23 20:42:41 +01:00
public function addSurrogateEsiCapability ( Request $request )
{
2014-12-21 11:39:54 +00:00
trigger_error ( 'The ' . __METHOD__ . ' method is deprecated since version 2.6 and will be removed in 3.0. Use the addSurrogateCapability() method instead.' , E_USER_DEPRECATED );
2015-01-08 09:07:02 +00:00
$this -> addSurrogateCapability ( $request );
2010-06-23 20:42:41 +01:00
}
/**
* Adds HTTP headers to specify that the Response needs to be parsed for ESI .
*
* This method only adds an ESI HTTP header if the Response has some ESI tags .
*
2010-07-27 14:33:28 +01:00
* @ param Response $response A Response instance
2010-06-23 20:42:41 +01:00
*/
public function addSurrogateControl ( Response $response )
{
if ( false !== strpos ( $response -> getContent (), '<esi:include' )) {
$response -> headers -> set ( 'Surrogate-Control' , 'content="ESI/1.0"' );
}
}
/**
* Checks that the Response needs to be parsed for ESI tags .
*
2010-07-27 14:33:28 +01:00
* @ param Response $response A Response instance
2010-06-23 20:42:41 +01:00
*
2014-11-30 13:33:44 +00:00
* @ return bool true if the Response needs to be parsed , false otherwise
2010-06-23 20:42:41 +01:00
*/
2014-03-23 00:04:57 +00:00
public function needsParsing ( Response $response )
{
2015-01-08 09:07:02 +00:00
if ( ! $control = $response -> headers -> get ( 'Surrogate-Control' )) {
return false ;
}
return ( bool ) preg_match ( '#content="[^"]*ESI/1.0[^"]*"#' , $control );
2014-03-23 00:04:57 +00:00
}
/**
* Checks that the Response needs to be parsed for ESI tags .
*
* @ param Response $response A Response instance
*
2014-12-05 20:11:30 +00:00
* @ return bool true if the Response needs to be parsed , false otherwise
2014-03-23 00:04:57 +00:00
*
2014-12-29 23:26:56 +00:00
* @ deprecated since version 2.6 , to be removed in 3.0 . Use needsParsing () instead
2014-03-23 00:04:57 +00:00
*/
2010-06-23 20:42:41 +01:00
public function needsEsiParsing ( Response $response )
{
2014-12-21 11:39:54 +00:00
trigger_error ( 'The ' . __METHOD__ . ' method is deprecated since version 2.6 and will be removed in 3.0. Use the needsParsing() method instead.' , E_USER_DEPRECATED );
2015-01-08 09:07:02 +00:00
return $this -> needsParsing ( $response );
2010-06-23 20:42:41 +01:00
}
/**
* Renders an ESI tag .
*
2014-11-30 13:33:44 +00:00
* @ param string $uri A URI
* @ param string $alt An alternate URI
* @ param bool $ignoreErrors Whether to ignore errors or not
* @ param string $comment A comment to add as an esi : include tag
2012-12-16 12:02:54 +00:00
*
* @ return string
2010-06-23 20:42:41 +01:00
*/
2010-11-02 19:00:18 +00:00
public function renderIncludeTag ( $uri , $alt = null , $ignoreErrors = true , $comment = '' )
2010-06-23 20:42:41 +01:00
{
$html = sprintf ( '<esi:include src="%s"%s%s />' ,
$uri ,
$ignoreErrors ? ' onerror="continue"' : '' ,
$alt ? sprintf ( ' alt="%s"' , $alt ) : ''
);
if ( ! empty ( $comment )) {
2010-11-02 19:00:18 +00:00
return sprintf ( " <esi:comment text= \" %s \" /> \n %s " , $comment , $html );
2010-06-23 20:42:41 +01:00
}
return $html ;
}
/**
* Replaces a Response ESI tags with the included resource content .
*
2010-07-27 14:33:28 +01:00
* @ param Request $request A Request instance
* @ param Response $response A Response instance
2012-12-16 12:02:54 +00:00
*
* @ return Response
2010-06-23 20:42:41 +01:00
*/
public function process ( Request $request , Response $response )
{
$this -> request = $request ;
$type = $response -> headers -> get ( 'Content-Type' );
if ( empty ( $type )) {
$type = 'text/html' ;
}
2010-07-27 12:36:58 +01:00
$parts = explode ( ';' , $type );
2010-07-26 14:10:59 +01:00
if ( ! in_array ( $parts [ 0 ], $this -> contentTypes )) {
2010-06-23 20:42:41 +01:00
return $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 = preg_replace ( '#<esi\:remove>.*?</esi\:remove>#' , '' , $content );
2015-03-16 14:12:02 +00:00
$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.' );
}
2015-04-01 17:50:12 +01:00
$chunks [ $i ] = sprintf ( '<?php echo $this->surrogate->handle($this, %s, %s, %s) ?>' . " \n " ,
2015-03-16 14:12:02 +00:00
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 );
2010-06-23 20:42:41 +01:00
$response -> setContent ( $content );
$response -> headers -> set ( 'X-Body-Eval' , 'ESI' );
// remove ESI/1.0 from the Surrogate-Control header
2010-11-02 19:00:18 +00:00
if ( $response -> headers -> has ( 'Surrogate-Control' )) {
$value = $response -> headers -> get ( 'Surrogate-Control' );
if ( 'content="ESI/1.0"' == $value ) {
2010-11-23 08:42:19 +00:00
$response -> headers -> remove ( 'Surrogate-Control' );
2010-11-02 19:00:18 +00:00
} elseif ( preg_match ( '#,\s*content="ESI/1.0"#' , $value )) {
$response -> headers -> set ( 'Surrogate-Control' , preg_replace ( '#,\s*content="ESI/1.0"#' , '' , $value ));
} elseif ( preg_match ( '#content="ESI/1.0",\s*#' , $value )) {
$response -> headers -> set ( 'Surrogate-Control' , preg_replace ( '#content="ESI/1.0",\s*#' , '' , $value ));
}
2010-06-23 20:42:41 +01:00
}
}
/**
* Handles an ESI from the cache .
*
2011-01-26 20:38:45 +00:00
* @ param HttpCache $cache An HttpCache instance
* @ param string $uri The main URI
* @ param string $alt An alternative URI
2014-04-12 18:54:57 +01:00
* @ param bool $ignoreErrors Whether to ignore errors or not
2012-12-16 12:02:54 +00:00
*
* @ return string
*
* @ throws \RuntimeException
* @ throws \Exception
2010-06-23 20:42:41 +01:00
*/
2011-01-26 20:38:45 +00:00
public function handle ( HttpCache $cache , $uri , $alt , $ignoreErrors )
2010-06-23 20:42:41 +01:00
{
$subRequest = Request :: create ( $uri , 'get' , array (), $cache -> getRequest () -> cookies -> all (), array (), $cache -> getRequest () -> server -> all ());
try {
2010-08-14 17:29:27 +01:00
$response = $cache -> handle ( $subRequest , HttpKernelInterface :: SUB_REQUEST , true );
2011-02-10 07:52:49 +00:00
if ( ! $response -> isSuccessful ()) {
2010-09-07 14:49:08 +01:00
throw new \RuntimeException ( sprintf ( 'Error when rendering "%s" (Status code is %s).' , $subRequest -> getUri (), $response -> getStatusCode ()));
2010-08-14 17:29:27 +01:00
}
return $response -> getContent ();
2010-06-23 20:42:41 +01:00
} catch ( \Exception $e ) {
if ( $alt ) {
return $this -> handle ( $cache , $alt , '' , $ignoreErrors );
}
if ( ! $ignoreErrors ) {
throw $e ;
}
}
}
}