4 * This class is adapted from code coming from Zend Framework.
6 * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
7 * @license http://framework.zend.com/license/new-bsd New BSD License
9 class Twig_Test_EscapingTest extends PHPUnit_Framework_TestCase
12 * All character encodings supported by htmlspecialchars().
14 protected $htmlSpecialChars = array(
22 protected $htmlAttrSpecialChars = array(
24 /* Characters beyond ASCII value 255 to unicode escape */
26 /* Immune chars excluded */
31 /* Basic alnums excluded */
38 /* Basic control characters and null */
42 "\0" => '�', // should use Unicode replacement char
43 /* Encode chars as named entities where possible */
48 /* Encode spaces for quoteless attribute protection */
52 protected $jsSpecialChars = array(
53 /* HTML special chars - escape without exception to hex */
59 /* Characters beyond ASCII value 255 to unicode escape */
61 /* Immune chars excluded */
65 /* Basic alnums excluded */
72 /* Basic control characters and null */
77 /* Encode spaces for quoteless attribute protection */
81 protected $urlSpecialChars = array(
82 /* HTML special chars - escape without exception to percent encoding */
88 /* Characters beyond ASCII value 255 to hex sequence */
90 /* Punctuation and unreserved check */
98 /* Basic alnums excluded */
105 /* Basic control characters and null */
110 /* PHP quirks from the past */
116 protected $cssSpecialChars = array(
117 /* HTML special chars - escape without exception to hex */
123 /* Characters beyond ASCII value 255 to unicode escape */
125 /* Immune chars excluded */
129 /* Basic alnums excluded */
136 /* Basic control characters and null */
141 /* Encode spaces for quoteless attribute protection */
147 protected function setUp()
149 $this->env = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock());
152 public function testHtmlEscapingConvertsSpecialChars()
154 foreach ($this->htmlSpecialChars as $key => $value) {
155 $this->assertEquals($value, twig_escape_filter($this->env, $key, 'html'), 'Failed to escape: '.$key);
159 public function testHtmlAttributeEscapingConvertsSpecialChars()
161 foreach ($this->htmlAttrSpecialChars as $key => $value) {
162 $this->assertEquals($value, twig_escape_filter($this->env, $key, 'html_attr'), 'Failed to escape: '.$key);
166 public function testJavascriptEscapingConvertsSpecialChars()
168 foreach ($this->jsSpecialChars as $key => $value) {
169 $this->assertEquals($value, twig_escape_filter($this->env, $key, 'js'), 'Failed to escape: '.$key);
173 public function testJavascriptEscapingReturnsStringIfZeroLength()
175 $this->assertEquals('', twig_escape_filter($this->env, '', 'js'));
178 public function testJavascriptEscapingReturnsStringIfContainsOnlyDigits()
180 $this->assertEquals('123', twig_escape_filter($this->env, '123', 'js'));
183 public function testCssEscapingConvertsSpecialChars()
185 foreach ($this->cssSpecialChars as $key => $value) {
186 $this->assertEquals($value, twig_escape_filter($this->env, $key, 'css'), 'Failed to escape: '.$key);
190 public function testCssEscapingReturnsStringIfZeroLength()
192 $this->assertEquals('', twig_escape_filter($this->env, '', 'css'));
195 public function testCssEscapingReturnsStringIfContainsOnlyDigits()
197 $this->assertEquals('123', twig_escape_filter($this->env, '123', 'css'));
200 public function testUrlEscapingConvertsSpecialChars()
202 foreach ($this->urlSpecialChars as $key => $value) {
203 $this->assertEquals($value, twig_escape_filter($this->env, $key, 'url'), 'Failed to escape: '.$key);
208 * Range tests to confirm escaped range of characters is within OWASP recommendation.
212 * Only testing the first few 2 ranges on this prot. function as that's all these
213 * other range tests require.
215 public function testUnicodeCodepointConversionToUtf8()
218 $codepoints = array(0x20, 0x7e, 0x799);
220 foreach ($codepoints as $value) {
221 $result .= $this->codepointToUtf8($value);
223 $this->assertEquals($expected, $result);
227 * Convert a Unicode Codepoint to a literal UTF-8 character.
229 * @param int $codepoint Unicode codepoint in hex notation
231 * @return string UTF-8 literal string
233 protected function codepointToUtf8($codepoint)
235 if ($codepoint < 0x80) {
236 return chr($codepoint);
238 if ($codepoint < 0x800) {
239 return chr($codepoint >> 6 & 0x3f | 0xc0)
240 .chr($codepoint & 0x3f | 0x80);
242 if ($codepoint < 0x10000) {
243 return chr($codepoint >> 12 & 0x0f | 0xe0)
244 .chr($codepoint >> 6 & 0x3f | 0x80)
245 .chr($codepoint & 0x3f | 0x80);
247 if ($codepoint < 0x110000) {
248 return chr($codepoint >> 18 & 0x07 | 0xf0)
249 .chr($codepoint >> 12 & 0x3f | 0x80)
250 .chr($codepoint >> 6 & 0x3f | 0x80)
251 .chr($codepoint & 0x3f | 0x80);
253 throw new Exception('Codepoint requested outside of Unicode range.');
256 public function testJavascriptEscapingEscapesOwaspRecommendedRanges()
258 $immune = array(',', '.', '_'); // Exceptions to escaping ranges
259 for ($chr = 0; $chr < 0xFF; ++$chr) {
260 if ($chr >= 0x30 && $chr <= 0x39
261 || $chr >= 0x41 && $chr <= 0x5A
262 || $chr >= 0x61 && $chr <= 0x7A) {
263 $literal = $this->codepointToUtf8($chr);
264 $this->assertEquals($literal, twig_escape_filter($this->env, $literal, 'js'));
266 $literal = $this->codepointToUtf8($chr);
267 if (in_array($literal, $immune)) {
268 $this->assertEquals($literal, twig_escape_filter($this->env, $literal, 'js'));
270 $this->assertNotEquals(
272 twig_escape_filter($this->env, $literal, 'js'),
273 "$literal should be escaped!");
279 public function testHtmlAttributeEscapingEscapesOwaspRecommendedRanges()
281 $immune = array(',', '.', '-', '_'); // Exceptions to escaping ranges
282 for ($chr = 0; $chr < 0xFF; ++$chr) {
283 if ($chr >= 0x30 && $chr <= 0x39
284 || $chr >= 0x41 && $chr <= 0x5A
285 || $chr >= 0x61 && $chr <= 0x7A) {
286 $literal = $this->codepointToUtf8($chr);
287 $this->assertEquals($literal, twig_escape_filter($this->env, $literal, 'html_attr'));
289 $literal = $this->codepointToUtf8($chr);
290 if (in_array($literal, $immune)) {
291 $this->assertEquals($literal, twig_escape_filter($this->env, $literal, 'html_attr'));
293 $this->assertNotEquals(
295 twig_escape_filter($this->env, $literal, 'html_attr'),
296 "$literal should be escaped!");
302 public function testCssEscapingEscapesOwaspRecommendedRanges()
304 // CSS has no exceptions to escaping ranges
305 for ($chr = 0; $chr < 0xFF; ++$chr) {
306 if ($chr >= 0x30 && $chr <= 0x39
307 || $chr >= 0x41 && $chr <= 0x5A
308 || $chr >= 0x61 && $chr <= 0x7A) {
309 $literal = $this->codepointToUtf8($chr);
310 $this->assertEquals($literal, twig_escape_filter($this->env, $literal, 'css'));
312 $literal = $this->codepointToUtf8($chr);
313 $this->assertNotEquals(
315 twig_escape_filter($this->env, $literal, 'css'),
316 "$literal should be escaped!");