View file vendor/symfony/http-kernel/HttpCache/Esi.php

File size: 3.6Kb
  1. <?php
  2.  
  3. /*
  4. * This file is part of the Symfony package.
  5. *
  6. * (c) Fabien Potencier <fabien@symfony.com>
  7. *
  8. * For the full copyright and license information, please view the LICENSE
  9. * file that was distributed with this source code.
  10. */
  11.  
  12. namespace Symfony\Component\HttpKernel\HttpCache;
  13.  
  14. use Symfony\Component\HttpFoundation\Request;
  15. use Symfony\Component\HttpFoundation\Response;
  16.  
  17. /**
  18. * Esi implements the ESI capabilities to Request and Response instances.
  19. *
  20. * For more information, read the following W3C notes:
  21. *
  22. * * ESI Language Specification 1.0 (http://www.w3.org/TR/esi-lang)
  23. *
  24. * * Edge Architecture Specification (http://www.w3.org/TR/edge-arch)
  25. *
  26. * @author Fabien Potencier <fabien@symfony.com>
  27. */
  28. class Esi extends AbstractSurrogate
  29. {
  30. public function getName()
  31. {
  32. return 'esi';
  33. }
  34.  
  35. /**
  36. * {@inheritdoc}
  37. */
  38. public function addSurrogateControl(Response $response)
  39. {
  40. if (false !== strpos($response->getContent(), '<esi:include')) {
  41. $response->headers->set('Surrogate-Control', 'content="ESI/1.0"');
  42. }
  43. }
  44.  
  45. /**
  46. * {@inheritdoc}
  47. */
  48. public function renderIncludeTag(string $uri, string $alt = null, bool $ignoreErrors = true, string $comment = '')
  49. {
  50. $html = sprintf('<esi:include src="%s"%s%s />',
  51. $uri,
  52. $ignoreErrors ? ' onerror="continue"' : '',
  53. $alt ? sprintf(' alt="%s"', $alt) : ''
  54. );
  55.  
  56. if (!empty($comment)) {
  57. return sprintf("<esi:comment text=\"%s\" />\n%s", $comment, $html);
  58. }
  59.  
  60. return $html;
  61. }
  62.  
  63. /**
  64. * {@inheritdoc}
  65. */
  66. public function process(Request $request, Response $response)
  67. {
  68. $type = $response->headers->get('Content-Type');
  69. if (empty($type)) {
  70. $type = 'text/html';
  71. }
  72.  
  73. $parts = explode(';', $type);
  74. if (!\in_array($parts[0], $this->contentTypes)) {
  75. return $response;
  76. }
  77.  
  78. // we don't use a proper XML parser here as we can have ESI tags in a plain text response
  79. $content = $response->getContent();
  80. $content = preg_replace('#<esi\:remove>.*?</esi\:remove>#s', '', $content);
  81. $content = preg_replace('#<esi\:comment[^>]+>#s', '', $content);
  82.  
  83. $chunks = preg_split('#<esi\:include\s+(.*?)\s*(?:/|</esi\:include)>#', $content, -1, \PREG_SPLIT_DELIM_CAPTURE);
  84. $chunks[0] = str_replace($this->phpEscapeMap[0], $this->phpEscapeMap[1], $chunks[0]);
  85.  
  86. $i = 1;
  87. while (isset($chunks[$i])) {
  88. $options = [];
  89. preg_match_all('/(src|onerror|alt)="([^"]*?)"/', $chunks[$i], $matches, \PREG_SET_ORDER);
  90. foreach ($matches as $set) {
  91. $options[$set[1]] = $set[2];
  92. }
  93.  
  94. if (!isset($options['src'])) {
  95. throw new \RuntimeException('Unable to process an ESI tag without a "src" attribute.');
  96. }
  97.  
  98. $chunks[$i] = sprintf('<?php echo $this->surrogate->handle($this, %s, %s, %s) ?>'."\n",
  99. var_export($options['src'], true),
  100. var_export($options['alt'] ?? '', true),
  101. isset($options['onerror']) && 'continue' === $options['onerror'] ? 'true' : 'false'
  102. );
  103. ++$i;
  104. $chunks[$i] = str_replace($this->phpEscapeMap[0], $this->phpEscapeMap[1], $chunks[$i]);
  105. ++$i;
  106. }
  107. $content = implode('', $chunks);
  108.  
  109. $response->setContent($content);
  110. $response->headers->set('X-Body-Eval', 'ESI');
  111.  
  112. // remove ESI/1.0 from the Surrogate-Control header
  113. $this->removeFromControl($response);
  114.  
  115. return $response;
  116. }
  117. }