PageRenderTime 50ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Symfony/Component/OutputEscaper/Escaper.php

https://github.com/yuchimiri/symfony
PHP | 359 lines | 147 code | 40 blank | 172 comment | 20 complexity | c8cebdef7756ad58d8de5b1da43b8075 MD5 | raw file
  1. <?php
  2. namespace Symfony\Component\OutputEscaper;
  3. /*
  4. * This file is part of the Symfony package.
  5. *
  6. * (c) Fabien Potencier <fabien.potencier@symfony-project.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. * Abstract class that provides an interface for escaping of output.
  13. *
  14. * @author Fabien Potencier <fabien.potencier@symfony-project.com>
  15. * @author Mike Squire <mike@somosis.co.uk>
  16. */
  17. abstract class Escaper
  18. {
  19. /**
  20. * The value that is to be escaped.
  21. *
  22. * @var mixed
  23. */
  24. protected $value;
  25. /**
  26. * The escaper (a PHP callable) that is going to be applied to the value and its
  27. * children.
  28. *
  29. * @var string
  30. */
  31. protected $escaper;
  32. static protected $charset = 'UTF-8';
  33. static protected $safeClasses = array();
  34. static protected $escapers;
  35. /**
  36. * Constructor.
  37. *
  38. * Since Escaper is an abstract class, instances cannot be created
  39. * directly but the constructor will be inherited by sub-classes.
  40. *
  41. * @param string $callable A PHP callable
  42. * @param string $value Escaping value
  43. */
  44. public function __construct($escaper, $value)
  45. {
  46. if (null === self::$escapers) {
  47. self::initializeEscapers();
  48. }
  49. $this->escaper = is_string($escaper) && isset(self::$escapers[$escaper]) ? self::$escapers[$escaper] : $escaper;
  50. $this->value = $value;
  51. }
  52. /**
  53. * Decorates a PHP variable with something that will escape any data obtained
  54. * from it.
  55. *
  56. * The following cases are dealt with:
  57. *
  58. * - The value is null or false: null or false is returned.
  59. * - The value is scalar: the result of applying the escaping method is
  60. * returned.
  61. * - The value is an array or an object that implements the ArrayAccess
  62. * interface: the array is decorated such that accesses to elements yield
  63. * an escaped value.
  64. * - The value implements the Traversable interface (either an Iterator, an
  65. * IteratorAggregate or an internal PHP class that implements
  66. * Traversable): decorated much like the array.
  67. * - The value is another type of object: decorated such that the result of
  68. * method calls is escaped.
  69. *
  70. * The escaping method is actually a PHP callable. This class hosts a set
  71. * of standard escaping strategies.
  72. *
  73. * @param string $escaper The escaping method (a PHP callable) to apply to the value
  74. * @param mixed $value The value to escape
  75. *
  76. * @return mixed Escaped value
  77. *
  78. * @throws \InvalidArgumentException If the escaping fails
  79. */
  80. static public function escape($escaper, $value)
  81. {
  82. if (null === $value) {
  83. return $value;
  84. }
  85. if (null === self::$escapers) {
  86. self::initializeEscapers();
  87. }
  88. if (is_string($escaper) && isset(self::$escapers[$escaper])) {
  89. $escaper = self::$escapers[$escaper];
  90. }
  91. // Scalars are anything other than arrays, objects and resources.
  92. if (is_scalar($value)) {
  93. return call_user_func($escaper, $value);
  94. }
  95. if (is_array($value)) {
  96. return new ArrayDecorator($escaper, $value);
  97. }
  98. if (is_object($value)) {
  99. if ($value instanceof Escaper) {
  100. // avoid double decoration
  101. $copy = clone $value;
  102. $copy->escaper = $escaper;
  103. return $copy;
  104. }
  105. if (self::isClassMarkedAsSafe(get_class($value))) {
  106. // the class or one of its children is marked as safe
  107. // return the unescaped object
  108. return $value;
  109. }
  110. if ($value instanceof SafeDecorator) {
  111. // do not escape objects marked as safe
  112. // return the original object
  113. return $value->getValue();
  114. }
  115. if ($value instanceof \Traversable) {
  116. return new IteratorDecorator($escaper, $value);
  117. }
  118. return new ObjectDecorator($escaper, $value);
  119. }
  120. // it must be a resource; cannot escape that.
  121. throw new \InvalidArgumentException(sprintf('Unable to escape value "%s".', var_export($value, true)));
  122. }
  123. /**
  124. * Unescapes a value that has been escaped previously with the escape() method.
  125. *
  126. * @param mixed $value The value to unescape
  127. *
  128. * @return mixed Unescaped value
  129. *
  130. * @throws \InvalidArgumentException If the escaping fails
  131. */
  132. static public function unescape($value)
  133. {
  134. if (null === $value || is_bool($value)) {
  135. return $value;
  136. }
  137. if (is_scalar($value)) {
  138. return html_entity_decode($value, ENT_QUOTES, self::$charset);
  139. }
  140. if (is_array($value)) {
  141. foreach ($value as $name => $v) {
  142. $value[$name] = self::unescape($v);
  143. }
  144. return $value;
  145. }
  146. if (is_object($value)) {
  147. return $value instanceof Escaper ? $value->getRawValue() : $value;
  148. }
  149. return $value;
  150. }
  151. /**
  152. * Returns true if the class if marked as safe.
  153. *
  154. * @param string $class A class name
  155. *
  156. * @return bool true if the class if safe, false otherwise
  157. */
  158. static public function isClassMarkedAsSafe($class)
  159. {
  160. if (in_array($class, self::$safeClasses)) {
  161. return true;
  162. }
  163. foreach (self::$safeClasses as $safeClass) {
  164. if (is_subclass_of($class, $safeClass)) {
  165. return true;
  166. }
  167. }
  168. return false;
  169. }
  170. /**
  171. * Marks an array of classes (and all its children) as being safe for output.
  172. *
  173. * @param array $classes An array of class names
  174. */
  175. static public function markClassesAsSafe(array $classes)
  176. {
  177. self::$safeClasses = array_unique(array_merge(self::$safeClasses, $classes));
  178. }
  179. /**
  180. * Marks a class (and all its children) as being safe for output.
  181. *
  182. * @param string $class A class name
  183. */
  184. static public function markClassAsSafe($class)
  185. {
  186. self::markClassesAsSafe(array($class));
  187. }
  188. /**
  189. * Returns the raw value associated with this instance.
  190. *
  191. * Concrete instances of Escaper classes decorate a value which is
  192. * stored by the constructor. This returns that original, unescaped, value.
  193. *
  194. * @return mixed The original value used to construct the decorator
  195. */
  196. public function getRawValue()
  197. {
  198. return $this->value;
  199. }
  200. /**
  201. * Sets the current charset.
  202. *
  203. * @param string $charset The current charset
  204. */
  205. static public function setCharset($charset)
  206. {
  207. self::$charset = $charset;
  208. }
  209. /**
  210. * Gets the current charset.
  211. *
  212. * @return string The current charset
  213. */
  214. static public function getCharset()
  215. {
  216. return self::$charset;
  217. }
  218. /**
  219. * Adds a named escaper.
  220. *
  221. * @param string $name The escaper name
  222. * @param mixed $escaper A PHP callable
  223. */
  224. static public function setEscaper($name, $escaper)
  225. {
  226. self::$escapers[$name] = $escaper;
  227. }
  228. /**
  229. * Initializes the built-in escapers.
  230. *
  231. * Each function specifies a way for applying a transformation to a string
  232. * passed to it. The purpose is for the string to be "escaped" so it is
  233. * suitable for the format it is being displayed in.
  234. *
  235. * For example, the string: "It's required that you enter a username & password.\n"
  236. * If this were to be displayed as HTML it would be sensible to turn the
  237. * ampersand into '&amp;' and the apostrophe into '&aps;'. However if it were
  238. * going to be used as a string in JavaScript to be displayed in an alert box
  239. * it would be right to leave the string as-is, but c-escape the apostrophe and
  240. * the new line.
  241. *
  242. * For each function there is a define to avoid problems with strings being
  243. * incorrectly specified.
  244. */
  245. static function initializeEscapers()
  246. {
  247. self::$escapers = array(
  248. 'htmlspecialchars' =>
  249. /**
  250. * Runs the PHP function htmlspecialchars on the value passed.
  251. *
  252. * @param string $value the value to escape
  253. *
  254. * @return string the escaped value
  255. */
  256. function ($value)
  257. {
  258. // Numbers and boolean values get turned into strings which can cause problems
  259. // with type comparisons (e.g. === or is_int() etc).
  260. return is_string($value) ? htmlspecialchars($value, ENT_QUOTES, Escaper::getCharset()) : $value;
  261. },
  262. 'entities' =>
  263. /**
  264. * Runs the PHP function htmlentities on the value passed.
  265. *
  266. * @param string $value the value to escape
  267. * @return string the escaped value
  268. */
  269. function ($value)
  270. {
  271. // Numbers and boolean values get turned into strings which can cause problems
  272. // with type comparisons (e.g. === or is_int() etc).
  273. return is_string($value) ? htmlentities($value, ENT_QUOTES, Escaper::getCharset()) : $value;
  274. },
  275. 'raw' =>
  276. /**
  277. * An identity function that merely returns that which it is given, the purpose
  278. * being to be able to specify that the value is not to be escaped in any way.
  279. *
  280. * @param string $value the value to escape
  281. * @return string the escaped value
  282. */
  283. function ($value)
  284. {
  285. return $value;
  286. },
  287. 'js' =>
  288. /**
  289. * A function that c-escapes a string after applying (cf. entities). The
  290. * assumption is that the value will be used to generate dynamic HTML in some
  291. * way and the safest way to prevent mishap is to assume the value should have
  292. * HTML entities set properly.
  293. *
  294. * The (cf. js_no_entities) method should be used to escape a string
  295. * that is ultimately not going to end up as text in an HTML document.
  296. *
  297. * @param string $value the value to escape
  298. * @return string the escaped value
  299. */
  300. function ($value)
  301. {
  302. return str_replace(array("\\" , "\n" , "\r" , "\"" , "'" ), array("\\\\", "\\n" , "\\r", "\\\"", "\\'"), (is_string($value) ? htmlentities($value, ENT_QUOTES, Escaper::getCharset()) : $value));
  303. },
  304. 'js_no_entities' =>
  305. /**
  306. * A function the c-escapes a string, making it suitable to be placed in a
  307. * JavaScript string.
  308. *
  309. * @param string $value the value to escape
  310. * @return string the escaped value
  311. */
  312. function ($value)
  313. {
  314. return str_replace(array("\\" , "\n" , "\r" , "\"" , "'" ), array("\\\\", "\\n" , "\\r", "\\\"", "\\'"), $value);
  315. },
  316. );
  317. }
  318. }