Escaper.php 4.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. <?php
  2. /*
  3. * This file is part of Twig.
  4. *
  5. * (c) 2009 Fabien Potencier
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. /**
  11. * Twig_NodeVisitor_Escaper implements output escaping.
  12. *
  13. * @package twig
  14. * @author Fabien Potencier <fabien@symfony.com>
  15. */
  16. class Twig_NodeVisitor_Escaper implements Twig_NodeVisitorInterface
  17. {
  18. protected $statusStack = array();
  19. protected $blocks = array();
  20. protected $safeAnalysis;
  21. protected $traverser;
  22. function __construct()
  23. {
  24. $this->safeAnalysis = new Twig_NodeVisitor_SafeAnalysis();
  25. }
  26. /**
  27. * Called before child nodes are visited.
  28. *
  29. * @param Twig_NodeInterface $node The node to visit
  30. * @param Twig_Environment $env The Twig environment instance
  31. *
  32. * @param Twig_NodeInterface The modified node
  33. */
  34. public function enterNode(Twig_NodeInterface $node, Twig_Environment $env)
  35. {
  36. if ($node instanceof Twig_Node_AutoEscape) {
  37. $this->statusStack[] = $node->getAttribute('value');
  38. } elseif ($node instanceof Twig_Node_Block) {
  39. $this->statusStack[] = isset($this->blocks[$node->getAttribute('name')]) ? $this->blocks[$node->getAttribute('name')] : $this->needEscaping($env);
  40. }
  41. return $node;
  42. }
  43. /**
  44. * Called after child nodes are visited.
  45. *
  46. * @param Twig_NodeInterface $node The node to visit
  47. * @param Twig_Environment $env The Twig environment instance
  48. *
  49. * @param Twig_NodeInterface The modified node
  50. */
  51. public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env)
  52. {
  53. if ($node instanceof Twig_Node_Expression_Filter) {
  54. return $this->preEscapeFilterNode($node, $env);
  55. } elseif ($node instanceof Twig_Node_Print) {
  56. return $this->escapePrintNode($node, $env, $this->needEscaping($env));
  57. }
  58. if ($node instanceof Twig_Node_AutoEscape || $node instanceof Twig_Node_Block) {
  59. array_pop($this->statusStack);
  60. } elseif ($node instanceof Twig_Node_BlockReference) {
  61. $this->blocks[$node->getAttribute('name')] = $this->needEscaping($env);
  62. }
  63. return $node;
  64. }
  65. protected function escapePrintNode(Twig_Node_Print $node, Twig_Environment $env, $type)
  66. {
  67. if (false === $type) {
  68. return $node;
  69. }
  70. $expression = $node->getNode('expr');
  71. if ($this->isSafeFor($type, $expression, $env)) {
  72. return $node;
  73. }
  74. $class = get_class($node);
  75. return new $class(
  76. $this->getEscaperFilter($type, $expression),
  77. $node->getLine()
  78. );
  79. }
  80. protected function preEscapeFilterNode(Twig_Node_Expression_Filter $filter, Twig_Environment $env)
  81. {
  82. $name = $filter->getNode('filter')->getAttribute('value');
  83. if (false !== $f = $env->getFilter($name)) {
  84. $type = $f->getPreEscape();
  85. if (null === $type) {
  86. return $filter;
  87. }
  88. $node = $filter->getNode('node');
  89. if ($this->isSafeFor($type, $node, $env)) {
  90. return $filter;
  91. }
  92. $filter->setNode('node', $this->getEscaperFilter($type, $node));
  93. return $filter;
  94. }
  95. return $filter;
  96. }
  97. protected function isSafeFor($type, Twig_NodeInterface $expression, $env)
  98. {
  99. $safe = $this->safeAnalysis->getSafe($expression);
  100. if (null === $safe) {
  101. if (null === $this->traverser) {
  102. $this->traverser = new Twig_NodeTraverser($env, array($this->safeAnalysis));
  103. }
  104. $this->traverser->traverse($expression);
  105. $safe = $this->safeAnalysis->getSafe($expression);
  106. }
  107. return in_array($type, $safe) || in_array('all', $safe);
  108. }
  109. protected function needEscaping(Twig_Environment $env)
  110. {
  111. if (count($this->statusStack)) {
  112. return $this->statusStack[count($this->statusStack) - 1];
  113. }
  114. if ($env->hasExtension('escaper') && $env->getExtension('escaper')->isGlobal()) {
  115. return 'html';
  116. }
  117. return false;
  118. }
  119. protected function getEscaperFilter($type, Twig_NodeInterface $node)
  120. {
  121. $line = $node->getLine();
  122. $name = new Twig_Node_Expression_Constant('escape', $line);
  123. $args = new Twig_Node(array(new Twig_Node_Expression_Constant((string) $type, $line)));
  124. return new Twig_Node_Expression_Filter($node, $name, $args, $line);
  125. }
  126. /**
  127. * {@inheritdoc}
  128. */
  129. public function getPriority()
  130. {
  131. return 0;
  132. }
  133. }