/vendor/twig/twig/test/Twig/Tests/escapingTest.php

https://github.com/fatra/kurs · PHP · 324 lines · 255 code · 23 blank · 46 comment · 32 complexity · 42d0f7f1a17f25a06b8a8df92b06b5c1 MD5 · raw file

  1. <?php
  2. /**
  3. * This class is adapted from code coming from Zend Framework.
  4. *
  5. * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
  6. * @license http://framework.zend.com/license/new-bsd New BSD License
  7. */
  8. class Twig_Test_EscapingTest extends PHPUnit_Framework_TestCase
  9. {
  10. /**
  11. * All character encodings supported by htmlspecialchars()
  12. */
  13. protected $htmlSpecialChars = array(
  14. '\'' => '&#039;',
  15. '"' => '&quot;',
  16. '<' => '&lt;',
  17. '>' => '&gt;',
  18. '&' => '&amp;'
  19. );
  20. protected $htmlAttrSpecialChars = array(
  21. '\'' => '&#x27;',
  22. '"' => '&quot;',
  23. '<' => '&lt;',
  24. '>' => '&gt;',
  25. '&' => '&amp;',
  26. /* Characters beyond ASCII value 255 to unicode escape */
  27. 'Ā' => '&#x0100;',
  28. /* Immune chars excluded */
  29. ',' => ',',
  30. '.' => '.',
  31. '-' => '-',
  32. '_' => '_',
  33. /* Basic alnums exluded */
  34. 'a' => 'a',
  35. 'A' => 'A',
  36. 'z' => 'z',
  37. 'Z' => 'Z',
  38. '0' => '0',
  39. '9' => '9',
  40. /* Basic control characters and null */
  41. "\r" => '&#x0D;',
  42. "\n" => '&#x0A;',
  43. "\t" => '&#x09;',
  44. "\0" => '&#xFFFD;', // should use Unicode replacement char
  45. /* Encode chars as named entities where possible */
  46. '<' => '&lt;',
  47. '>' => '&gt;',
  48. '&' => '&amp;',
  49. '"' => '&quot;',
  50. /* Encode spaces for quoteless attribute protection */
  51. ' ' => '&#x20;',
  52. );
  53. protected $jsSpecialChars = array(
  54. /* HTML special chars - escape without exception to hex */
  55. '<' => '\\x3C',
  56. '>' => '\\x3E',
  57. '\'' => '\\x27',
  58. '"' => '\\x22',
  59. '&' => '\\x26',
  60. /* Characters beyond ASCII value 255 to unicode escape */
  61. 'Ā' => '\\u0100',
  62. /* Immune chars excluded */
  63. ',' => ',',
  64. '.' => '.',
  65. '_' => '_',
  66. /* Basic alnums exluded */
  67. 'a' => 'a',
  68. 'A' => 'A',
  69. 'z' => 'z',
  70. 'Z' => 'Z',
  71. '0' => '0',
  72. '9' => '9',
  73. /* Basic control characters and null */
  74. "\r" => '\\x0D',
  75. "\n" => '\\x0A',
  76. "\t" => '\\x09',
  77. "\0" => '\\x00',
  78. /* Encode spaces for quoteless attribute protection */
  79. ' ' => '\\x20',
  80. );
  81. protected $urlSpecialChars = array(
  82. /* HTML special chars - escape without exception to percent encoding */
  83. '<' => '%3C',
  84. '>' => '%3E',
  85. '\'' => '%27',
  86. '"' => '%22',
  87. '&' => '%26',
  88. /* Characters beyond ASCII value 255 to hex sequence */
  89. 'Ā' => '%C4%80',
  90. /* Punctuation and unreserved check */
  91. ',' => '%2C',
  92. '.' => '.',
  93. '_' => '_',
  94. '-' => '-',
  95. ':' => '%3A',
  96. ';' => '%3B',
  97. '!' => '%21',
  98. /* Basic alnums excluded */
  99. 'a' => 'a',
  100. 'A' => 'A',
  101. 'z' => 'z',
  102. 'Z' => 'Z',
  103. '0' => '0',
  104. '9' => '9',
  105. /* Basic control characters and null */
  106. "\r" => '%0D',
  107. "\n" => '%0A',
  108. "\t" => '%09',
  109. "\0" => '%00',
  110. /* PHP quirks from the past */
  111. ' ' => '%20',
  112. '~' => '~',
  113. '+' => '%2B',
  114. );
  115. protected $cssSpecialChars = array(
  116. /* HTML special chars - escape without exception to hex */
  117. '<' => '\\3C ',
  118. '>' => '\\3E ',
  119. '\'' => '\\27 ',
  120. '"' => '\\22 ',
  121. '&' => '\\26 ',
  122. /* Characters beyond ASCII value 255 to unicode escape */
  123. 'Ā' => '\\100 ',
  124. /* Immune chars excluded */
  125. ',' => '\\2C ',
  126. '.' => '\\2E ',
  127. '_' => '\\5F ',
  128. /* Basic alnums exluded */
  129. 'a' => 'a',
  130. 'A' => 'A',
  131. 'z' => 'z',
  132. 'Z' => 'Z',
  133. '0' => '0',
  134. '9' => '9',
  135. /* Basic control characters and null */
  136. "\r" => '\\D ',
  137. "\n" => '\\A ',
  138. "\t" => '\\9 ',
  139. "\0" => '\\0 ',
  140. /* Encode spaces for quoteless attribute protection */
  141. ' ' => '\\20 ',
  142. );
  143. protected $env;
  144. public function setUp()
  145. {
  146. $this->env = new Twig_Environment();
  147. }
  148. public function testHtmlEscapingConvertsSpecialChars()
  149. {
  150. foreach ($this->htmlSpecialChars as $key => $value) {
  151. $this->assertEquals($value, twig_escape_filter($this->env, $key, 'html'), 'Failed to escape: '.$key);
  152. }
  153. }
  154. public function testHtmlAttributeEscapingConvertsSpecialChars()
  155. {
  156. foreach ($this->htmlAttrSpecialChars as $key => $value) {
  157. $this->assertEquals($value, twig_escape_filter($this->env, $key, 'html_attr'), 'Failed to escape: '.$key);
  158. }
  159. }
  160. public function testJavascriptEscapingConvertsSpecialChars()
  161. {
  162. foreach ($this->jsSpecialChars as $key => $value) {
  163. $this->assertEquals($value, twig_escape_filter($this->env, $key, 'js'), 'Failed to escape: '.$key);
  164. }
  165. }
  166. public function testJavascriptEscapingReturnsStringIfZeroLength()
  167. {
  168. $this->assertEquals('', twig_escape_filter($this->env, '', 'js'));
  169. }
  170. public function testJavascriptEscapingReturnsStringIfContainsOnlyDigits()
  171. {
  172. $this->assertEquals('123', twig_escape_filter($this->env, '123', 'js'));
  173. }
  174. public function testCssEscapingConvertsSpecialChars()
  175. {
  176. foreach ($this->cssSpecialChars as $key => $value) {
  177. $this->assertEquals($value, twig_escape_filter($this->env, $key, 'css'), 'Failed to escape: '.$key);
  178. }
  179. }
  180. public function testCssEscapingReturnsStringIfZeroLength()
  181. {
  182. $this->assertEquals('', twig_escape_filter($this->env, '', 'css'));
  183. }
  184. public function testCssEscapingReturnsStringIfContainsOnlyDigits()
  185. {
  186. $this->assertEquals('123', twig_escape_filter($this->env, '123', 'css'));
  187. }
  188. public function testUrlEscapingConvertsSpecialChars()
  189. {
  190. foreach ($this->urlSpecialChars as $key => $value) {
  191. $this->assertEquals($value, twig_escape_filter($this->env, $key, 'url'), 'Failed to escape: '.$key);
  192. }
  193. }
  194. /**
  195. * Range tests to confirm escaped range of characters is within OWASP recommendation
  196. */
  197. /**
  198. * Only testing the first few 2 ranges on this prot. function as that's all these
  199. * other range tests require
  200. */
  201. public function testUnicodeCodepointConversionToUtf8()
  202. {
  203. $expected = " ~ޙ";
  204. $codepoints = array(0x20, 0x7e, 0x799);
  205. $result = '';
  206. foreach ($codepoints as $value) {
  207. $result .= $this->codepointToUtf8($value);
  208. }
  209. $this->assertEquals($expected, $result);
  210. }
  211. /**
  212. * Convert a Unicode Codepoint to a literal UTF-8 character.
  213. *
  214. * @param int Unicode codepoint in hex notation
  215. * @return string UTF-8 literal string
  216. */
  217. protected function codepointToUtf8($codepoint)
  218. {
  219. if ($codepoint < 0x80) {
  220. return chr($codepoint);
  221. }
  222. if ($codepoint < 0x800) {
  223. return chr($codepoint >> 6 & 0x3f | 0xc0)
  224. . chr($codepoint & 0x3f | 0x80);
  225. }
  226. if ($codepoint < 0x10000) {
  227. return chr($codepoint >> 12 & 0x0f | 0xe0)
  228. . chr($codepoint >> 6 & 0x3f | 0x80)
  229. . chr($codepoint & 0x3f | 0x80);
  230. }
  231. if ($codepoint < 0x110000) {
  232. return chr($codepoint >> 18 & 0x07 | 0xf0)
  233. . chr($codepoint >> 12 & 0x3f | 0x80)
  234. . chr($codepoint >> 6 & 0x3f | 0x80)
  235. . chr($codepoint & 0x3f | 0x80);
  236. }
  237. throw new Exception('Codepoint requested outside of Unicode range');
  238. }
  239. public function testJavascriptEscapingEscapesOwaspRecommendedRanges()
  240. {
  241. $immune = array(',', '.', '_'); // Exceptions to escaping ranges
  242. for ($chr=0; $chr < 0xFF; $chr++) {
  243. if ($chr >= 0x30 && $chr <= 0x39
  244. || $chr >= 0x41 && $chr <= 0x5A
  245. || $chr >= 0x61 && $chr <= 0x7A) {
  246. $literal = $this->codepointToUtf8($chr);
  247. $this->assertEquals($literal, twig_escape_filter($this->env, $literal, 'js'));
  248. } else {
  249. $literal = $this->codepointToUtf8($chr);
  250. if (in_array($literal, $immune)) {
  251. $this->assertEquals($literal, twig_escape_filter($this->env, $literal, 'js'));
  252. } else {
  253. $this->assertNotEquals(
  254. $literal,
  255. twig_escape_filter($this->env, $literal, 'js'),
  256. "$literal should be escaped!");
  257. }
  258. }
  259. }
  260. }
  261. public function testHtmlAttributeEscapingEscapesOwaspRecommendedRanges()
  262. {
  263. $immune = array(',', '.', '-', '_'); // Exceptions to escaping ranges
  264. for ($chr=0; $chr < 0xFF; $chr++) {
  265. if ($chr >= 0x30 && $chr <= 0x39
  266. || $chr >= 0x41 && $chr <= 0x5A
  267. || $chr >= 0x61 && $chr <= 0x7A) {
  268. $literal = $this->codepointToUtf8($chr);
  269. $this->assertEquals($literal, twig_escape_filter($this->env, $literal, 'html_attr'));
  270. } else {
  271. $literal = $this->codepointToUtf8($chr);
  272. if (in_array($literal, $immune)) {
  273. $this->assertEquals($literal, twig_escape_filter($this->env, $literal, 'html_attr'));
  274. } else {
  275. $this->assertNotEquals(
  276. $literal,
  277. twig_escape_filter($this->env, $literal, 'html_attr'),
  278. "$literal should be escaped!");
  279. }
  280. }
  281. }
  282. }
  283. public function testCssEscapingEscapesOwaspRecommendedRanges()
  284. {
  285. $immune = array(); // CSS has no exceptions to escaping ranges
  286. for ($chr=0; $chr < 0xFF; $chr++) {
  287. if ($chr >= 0x30 && $chr <= 0x39
  288. || $chr >= 0x41 && $chr <= 0x5A
  289. || $chr >= 0x61 && $chr <= 0x7A) {
  290. $literal = $this->codepointToUtf8($chr);
  291. $this->assertEquals($literal, twig_escape_filter($this->env, $literal, 'css'));
  292. } else {
  293. $literal = $this->codepointToUtf8($chr);
  294. $this->assertNotEquals(
  295. $literal,
  296. twig_escape_filter($this->env, $literal, 'css'),
  297. "$literal should be escaped!");
  298. }
  299. }
  300. }
  301. }