PageRenderTime 48ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/vendor/zendframework/zend-escaper/src/Escaper.php

https://gitlab.com/yousafsyed/easternglamor
PHP | 386 lines | 176 code | 37 blank | 173 comment | 35 complexity | 3c4ed12832595c67e15b86d101e99166 MD5 | raw file
  1. <?php
  2. /**
  3. * Zend Framework (http://framework.zend.com/)
  4. *
  5. * @link http://github.com/zendframework/zf2 for the canonical source repository
  6. * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
  7. * @license http://framework.zend.com/license/new-bsd New BSD License
  8. */
  9. namespace Zend\Escaper;
  10. /**
  11. * Context specific methods for use in secure output escaping
  12. */
  13. class Escaper
  14. {
  15. /**
  16. * Entity Map mapping Unicode codepoints to any available named HTML entities.
  17. *
  18. * While HTML supports far more named entities, the lowest common denominator
  19. * has become HTML5's XML Serialisation which is restricted to the those named
  20. * entities that XML supports. Using HTML entities would result in this error:
  21. * XML Parsing Error: undefined entity
  22. *
  23. * @var array
  24. */
  25. protected static $htmlNamedEntityMap = array(
  26. 34 => 'quot', // quotation mark
  27. 38 => 'amp', // ampersand
  28. 60 => 'lt', // less-than sign
  29. 62 => 'gt', // greater-than sign
  30. );
  31. /**
  32. * Current encoding for escaping. If not UTF-8, we convert strings from this encoding
  33. * pre-escaping and back to this encoding post-escaping.
  34. *
  35. * @var string
  36. */
  37. protected $encoding = 'utf-8';
  38. /**
  39. * Holds the value of the special flags passed as second parameter to
  40. * htmlspecialchars(). We modify these for PHP 5.4 to take advantage
  41. * of the new ENT_SUBSTITUTE flag for correctly dealing with invalid
  42. * UTF-8 sequences.
  43. *
  44. * @var string
  45. */
  46. protected $htmlSpecialCharsFlags = ENT_QUOTES;
  47. /**
  48. * Static Matcher which escapes characters for HTML Attribute contexts
  49. *
  50. * @var callable
  51. */
  52. protected $htmlAttrMatcher;
  53. /**
  54. * Static Matcher which escapes characters for Javascript contexts
  55. *
  56. * @var callable
  57. */
  58. protected $jsMatcher;
  59. /**
  60. * Static Matcher which escapes characters for CSS Attribute contexts
  61. *
  62. * @var callable
  63. */
  64. protected $cssMatcher;
  65. /**
  66. * List of all encoding supported by this class
  67. *
  68. * @var array
  69. */
  70. protected $supportedEncodings = array(
  71. 'iso-8859-1', 'iso8859-1', 'iso-8859-5', 'iso8859-5',
  72. 'iso-8859-15', 'iso8859-15', 'utf-8', 'cp866',
  73. 'ibm866', '866', 'cp1251', 'windows-1251',
  74. 'win-1251', '1251', 'cp1252', 'windows-1252',
  75. '1252', 'koi8-r', 'koi8-ru', 'koi8r',
  76. 'big5', '950', 'gb2312', '936',
  77. 'big5-hkscs', 'shift_jis', 'sjis', 'sjis-win',
  78. 'cp932', '932', 'euc-jp', 'eucjp',
  79. 'eucjp-win', 'macroman'
  80. );
  81. /**
  82. * Constructor: Single parameter allows setting of global encoding for use by
  83. * the current object. If PHP 5.4 is detected, additional ENT_SUBSTITUTE flag
  84. * is set for htmlspecialchars() calls.
  85. *
  86. * @param string $encoding
  87. * @throws Exception\InvalidArgumentException
  88. */
  89. public function __construct($encoding = null)
  90. {
  91. if ($encoding !== null) {
  92. $encoding = (string) $encoding;
  93. if ($encoding === '') {
  94. throw new Exception\InvalidArgumentException(
  95. get_class($this) . ' constructor parameter does not allow a blank value'
  96. );
  97. }
  98. $encoding = strtolower($encoding);
  99. if (!in_array($encoding, $this->supportedEncodings)) {
  100. throw new Exception\InvalidArgumentException(
  101. 'Value of \'' . $encoding . '\' passed to ' . get_class($this)
  102. . ' constructor parameter is invalid. Provide an encoding supported by htmlspecialchars()'
  103. );
  104. }
  105. $this->encoding = $encoding;
  106. }
  107. if (defined('ENT_SUBSTITUTE')) {
  108. $this->htmlSpecialCharsFlags|= ENT_SUBSTITUTE;
  109. }
  110. // set matcher callbacks
  111. $this->htmlAttrMatcher = array($this, 'htmlAttrMatcher');
  112. $this->jsMatcher = array($this, 'jsMatcher');
  113. $this->cssMatcher = array($this, 'cssMatcher');
  114. }
  115. /**
  116. * Return the encoding that all output/input is expected to be encoded in.
  117. *
  118. * @return string
  119. */
  120. public function getEncoding()
  121. {
  122. return $this->encoding;
  123. }
  124. /**
  125. * Escape a string for the HTML Body context where there are very few characters
  126. * of special meaning. Internally this will use htmlspecialchars().
  127. *
  128. * @param string $string
  129. * @return string
  130. */
  131. public function escapeHtml($string)
  132. {
  133. return htmlspecialchars($string, $this->htmlSpecialCharsFlags, $this->encoding);
  134. }
  135. /**
  136. * Escape a string for the HTML Attribute context. We use an extended set of characters
  137. * to escape that are not covered by htmlspecialchars() to cover cases where an attribute
  138. * might be unquoted or quoted illegally (e.g. backticks are valid quotes for IE).
  139. *
  140. * @param string $string
  141. * @return string
  142. */
  143. public function escapeHtmlAttr($string)
  144. {
  145. $string = $this->toUtf8($string);
  146. if ($string === '' || ctype_digit($string)) {
  147. return $string;
  148. }
  149. $result = preg_replace_callback('/[^a-z0-9,\.\-_]/iSu', $this->htmlAttrMatcher, $string);
  150. return $this->fromUtf8($result);
  151. }
  152. /**
  153. * Escape a string for the Javascript context. This does not use json_encode(). An extended
  154. * set of characters are escaped beyond ECMAScript's rules for Javascript literal string
  155. * escaping in order to prevent misinterpretation of Javascript as HTML leading to the
  156. * injection of special characters and entities. The escaping used should be tolerant
  157. * of cases where HTML escaping was not applied on top of Javascript escaping correctly.
  158. * Backslash escaping is not used as it still leaves the escaped character as-is and so
  159. * is not useful in a HTML context.
  160. *
  161. * @param string $string
  162. * @return string
  163. */
  164. public function escapeJs($string)
  165. {
  166. $string = $this->toUtf8($string);
  167. if ($string === '' || ctype_digit($string)) {
  168. return $string;
  169. }
  170. $result = preg_replace_callback('/[^a-z0-9,\._]/iSu', $this->jsMatcher, $string);
  171. return $this->fromUtf8($result);
  172. }
  173. /**
  174. * Escape a string for the URI or Parameter contexts. This should not be used to escape
  175. * an entire URI - only a subcomponent being inserted. The function is a simple proxy
  176. * to rawurlencode() which now implements RFC 3986 since PHP 5.3 completely.
  177. *
  178. * @param string $string
  179. * @return string
  180. */
  181. public function escapeUrl($string)
  182. {
  183. return rawurlencode($string);
  184. }
  185. /**
  186. * Escape a string for the CSS context. CSS escaping can be applied to any string being
  187. * inserted into CSS and escapes everything except alphanumerics.
  188. *
  189. * @param string $string
  190. * @return string
  191. */
  192. public function escapeCss($string)
  193. {
  194. $string = $this->toUtf8($string);
  195. if ($string === '' || ctype_digit($string)) {
  196. return $string;
  197. }
  198. $result = preg_replace_callback('/[^a-z0-9]/iSu', $this->cssMatcher, $string);
  199. return $this->fromUtf8($result);
  200. }
  201. /**
  202. * Callback function for preg_replace_callback that applies HTML Attribute
  203. * escaping to all matches.
  204. *
  205. * @param array $matches
  206. * @return string
  207. */
  208. protected function htmlAttrMatcher($matches)
  209. {
  210. $chr = $matches[0];
  211. $ord = ord($chr);
  212. /**
  213. * The following replaces characters undefined in HTML with the
  214. * hex entity for the Unicode replacement character.
  215. */
  216. if (($ord <= 0x1f && $chr != "\t" && $chr != "\n" && $chr != "\r")
  217. || ($ord >= 0x7f && $ord <= 0x9f)
  218. ) {
  219. return '&#xFFFD;';
  220. }
  221. /**
  222. * Check if the current character to escape has a name entity we should
  223. * replace it with while grabbing the integer value of the character.
  224. */
  225. if (strlen($chr) > 1) {
  226. $chr = $this->convertEncoding($chr, 'UTF-16BE', 'UTF-8');
  227. }
  228. $hex = bin2hex($chr);
  229. $ord = hexdec($hex);
  230. if (isset(static::$htmlNamedEntityMap[$ord])) {
  231. return '&' . static::$htmlNamedEntityMap[$ord] . ';';
  232. }
  233. /**
  234. * Per OWASP recommendations, we'll use upper hex entities
  235. * for any other characters where a named entity does not exist.
  236. */
  237. if ($ord > 255) {
  238. return sprintf('&#x%04X;', $ord);
  239. }
  240. return sprintf('&#x%02X;', $ord);
  241. }
  242. /**
  243. * Callback function for preg_replace_callback that applies Javascript
  244. * escaping to all matches.
  245. *
  246. * @param array $matches
  247. * @return string
  248. */
  249. protected function jsMatcher($matches)
  250. {
  251. $chr = $matches[0];
  252. if (strlen($chr) == 1) {
  253. return sprintf('\\x%02X', ord($chr));
  254. }
  255. $chr = $this->convertEncoding($chr, 'UTF-16BE', 'UTF-8');
  256. return sprintf('\\u%04s', strtoupper(bin2hex($chr)));
  257. }
  258. /**
  259. * Callback function for preg_replace_callback that applies CSS
  260. * escaping to all matches.
  261. *
  262. * @param array $matches
  263. * @return string
  264. */
  265. protected function cssMatcher($matches)
  266. {
  267. $chr = $matches[0];
  268. if (strlen($chr) == 1) {
  269. $ord = ord($chr);
  270. } else {
  271. $chr = $this->convertEncoding($chr, 'UTF-16BE', 'UTF-8');
  272. $ord = hexdec(bin2hex($chr));
  273. }
  274. return sprintf('\\%X ', $ord);
  275. }
  276. /**
  277. * Converts a string to UTF-8 from the base encoding. The base encoding is set via this
  278. * class' constructor.
  279. *
  280. * @param string $string
  281. * @throws Exception\RuntimeException
  282. * @return string
  283. */
  284. protected function toUtf8($string)
  285. {
  286. if ($this->getEncoding() === 'utf-8') {
  287. $result = $string;
  288. } else {
  289. $result = $this->convertEncoding($string, 'UTF-8', $this->getEncoding());
  290. }
  291. if (!$this->isUtf8($result)) {
  292. throw new Exception\RuntimeException(
  293. sprintf('String to be escaped was not valid UTF-8 or could not be converted: %s', $result)
  294. );
  295. }
  296. return $result;
  297. }
  298. /**
  299. * Converts a string from UTF-8 to the base encoding. The base encoding is set via this
  300. * class' constructor.
  301. * @param string $string
  302. * @return string
  303. */
  304. protected function fromUtf8($string)
  305. {
  306. if ($this->getEncoding() === 'utf-8') {
  307. return $string;
  308. }
  309. return $this->convertEncoding($string, $this->getEncoding(), 'UTF-8');
  310. }
  311. /**
  312. * Checks if a given string appears to be valid UTF-8 or not.
  313. *
  314. * @param string $string
  315. * @return bool
  316. */
  317. protected function isUtf8($string)
  318. {
  319. return ($string === '' || preg_match('/^./su', $string));
  320. }
  321. /**
  322. * Encoding conversion helper which wraps iconv and mbstring where they exist or throws
  323. * and exception where neither is available.
  324. *
  325. * @param string $string
  326. * @param string $to
  327. * @param array|string $from
  328. * @throws Exception\RuntimeException
  329. * @return string
  330. */
  331. protected function convertEncoding($string, $to, $from)
  332. {
  333. if (function_exists('iconv')) {
  334. $result = iconv($from, $to, $string);
  335. } elseif (function_exists('mb_convert_encoding')) {
  336. $result = mb_convert_encoding($string, $to, $from);
  337. } else {
  338. throw new Exception\RuntimeException(
  339. get_class($this)
  340. . ' requires either the iconv or mbstring extension to be installed'
  341. . ' when escaping for non UTF-8 strings.'
  342. );
  343. }
  344. if ($result === false) {
  345. return ''; // return non-fatal blank string on encoding errors from users
  346. }
  347. return $result;
  348. }
  349. }