<?php
class Twig_Test_EscapingTest extends PHPUnit_Framework_TestCase {
protected $htmlSpecialChars = array(
'\'' => ''',
'"' => '"',
'<' => '<',
'>' => '>',
'&' => '&',
);
protected $htmlAttrSpecialChars = array(
'\'' => ''',
'Ā' => 'Ā',
',' => ',',
'.' => '.',
'-' => '-',
'_' => '_',
'a' => 'a',
'A' => 'A',
'z' => 'z',
'Z' => 'Z',
'0' => '0',
'9' => '9',
"\r" => '
',
"\n" => '
',
"\t" => '	',
"\0" => '�',
'<' => '<',
'>' => '>',
'&' => '&',
'"' => '"',
' ' => ' ',
);
protected $jsSpecialChars = array(
'<' => '\\x3C',
'>' => '\\x3E',
'\'' => '\\x27',
'"' => '\\x22',
'&' => '\\x26',
'Ā' => '\\u0100',
',' => ',',
'.' => '.',
'_' => '_',
'a' => 'a',
'A' => 'A',
'z' => 'z',
'Z' => 'Z',
'0' => '0',
'9' => '9',
"\r" => '\\x0D',
"\n" => '\\x0A',
"\t" => '\\x09',
"\0" => '\\x00',
' ' => '\\x20',
);
protected $urlSpecialChars = array(
'<' => '%3C',
'>' => '%3E',
'\'' => '%27',
'"' => '%22',
'&' => '%26',
'Ā' => '%C4%80',
',' => '%2C',
'.' => '.',
'_' => '_',
'-' => '-',
':' => '%3A',
';' => '%3B',
'!' => '%21',
'a' => 'a',
'A' => 'A',
'z' => 'z',
'Z' => 'Z',
'0' => '0',
'9' => '9',
"\r" => '%0D',
"\n" => '%0A',
"\t" => '%09',
"\0" => '%00',
' ' => '%20',
'~' => '~',
'+' => '%2B',
);
protected $cssSpecialChars = array(
'<' => '\\3C ',
'>' => '\\3E ',
'\'' => '\\27 ',
'"' => '\\22 ',
'&' => '\\26 ',
'Ā' => '\\100 ',
',' => '\\2C ',
'.' => '\\2E ',
'_' => '\\5F ',
'a' => 'a',
'A' => 'A',
'z' => 'z',
'Z' => 'Z',
'0' => '0',
'9' => '9',
"\r" => '\\D ',
"\n" => '\\A ',
"\t" => '\\9 ',
"\0" => '\\0 ',
' ' => '\\20 ',
);
protected $env;
public function setUp() {
$this->env = new Twig_Environment();
}
public function testHtmlEscapingConvertsSpecialChars() {
foreach ($this->htmlSpecialChars as $key => $value) {
$this
->assertEquals($value, twig_escape_filter($this->env, $key, 'html'), 'Failed to escape: ' . $key);
}
}
public function testHtmlAttributeEscapingConvertsSpecialChars() {
foreach ($this->htmlAttrSpecialChars as $key => $value) {
$this
->assertEquals($value, twig_escape_filter($this->env, $key, 'html_attr'), 'Failed to escape: ' . $key);
}
}
public function testJavascriptEscapingConvertsSpecialChars() {
foreach ($this->jsSpecialChars as $key => $value) {
$this
->assertEquals($value, twig_escape_filter($this->env, $key, 'js'), 'Failed to escape: ' . $key);
}
}
public function testJavascriptEscapingReturnsStringIfZeroLength() {
$this
->assertEquals('', twig_escape_filter($this->env, '', 'js'));
}
public function testJavascriptEscapingReturnsStringIfContainsOnlyDigits() {
$this
->assertEquals('123', twig_escape_filter($this->env, '123', 'js'));
}
public function testCssEscapingConvertsSpecialChars() {
foreach ($this->cssSpecialChars as $key => $value) {
$this
->assertEquals($value, twig_escape_filter($this->env, $key, 'css'), 'Failed to escape: ' . $key);
}
}
public function testCssEscapingReturnsStringIfZeroLength() {
$this
->assertEquals('', twig_escape_filter($this->env, '', 'css'));
}
public function testCssEscapingReturnsStringIfContainsOnlyDigits() {
$this
->assertEquals('123', twig_escape_filter($this->env, '123', 'css'));
}
public function testUrlEscapingConvertsSpecialChars() {
foreach ($this->urlSpecialChars as $key => $value) {
$this
->assertEquals($value, twig_escape_filter($this->env, $key, 'url'), 'Failed to escape: ' . $key);
}
}
public function testUnicodeCodepointConversionToUtf8() {
$expected = " ~ޙ";
$codepoints = array(
0x20,
0x7e,
0x799,
);
$result = '';
foreach ($codepoints as $value) {
$result .= $this
->codepointToUtf8($value);
}
$this
->assertEquals($expected, $result);
}
protected function codepointToUtf8($codepoint) {
if ($codepoint < 0x80) {
return chr($codepoint);
}
if ($codepoint < 0x800) {
return chr($codepoint >> 6 & 0x3f | 0xc0) . chr($codepoint & 0x3f | 0x80);
}
if ($codepoint < 0x10000) {
return chr($codepoint >> 12 & 0xf | 0xe0) . chr($codepoint >> 6 & 0x3f | 0x80) . chr($codepoint & 0x3f | 0x80);
}
if ($codepoint < 0x110000) {
return chr($codepoint >> 18 & 0x7 | 0xf0) . chr($codepoint >> 12 & 0x3f | 0x80) . chr($codepoint >> 6 & 0x3f | 0x80) . chr($codepoint & 0x3f | 0x80);
}
throw new Exception('Codepoint requested outside of Unicode range');
}
public function testJavascriptEscapingEscapesOwaspRecommendedRanges() {
$immune = array(
',',
'.',
'_',
);
for ($chr = 0; $chr < 0xff; $chr++) {
if ($chr >= 0x30 && $chr <= 0x39 || $chr >= 0x41 && $chr <= 0x5a || $chr >= 0x61 && $chr <= 0x7a) {
$literal = $this
->codepointToUtf8($chr);
$this
->assertEquals($literal, twig_escape_filter($this->env, $literal, 'js'));
}
else {
$literal = $this
->codepointToUtf8($chr);
if (in_array($literal, $immune)) {
$this
->assertEquals($literal, twig_escape_filter($this->env, $literal, 'js'));
}
else {
$this
->assertNotEquals($literal, twig_escape_filter($this->env, $literal, 'js'), "{$literal} should be escaped!");
}
}
}
}
public function testHtmlAttributeEscapingEscapesOwaspRecommendedRanges() {
$immune = array(
',',
'.',
'-',
'_',
);
for ($chr = 0; $chr < 0xff; $chr++) {
if ($chr >= 0x30 && $chr <= 0x39 || $chr >= 0x41 && $chr <= 0x5a || $chr >= 0x61 && $chr <= 0x7a) {
$literal = $this
->codepointToUtf8($chr);
$this
->assertEquals($literal, twig_escape_filter($this->env, $literal, 'html_attr'));
}
else {
$literal = $this
->codepointToUtf8($chr);
if (in_array($literal, $immune)) {
$this
->assertEquals($literal, twig_escape_filter($this->env, $literal, 'html_attr'));
}
else {
$this
->assertNotEquals($literal, twig_escape_filter($this->env, $literal, 'html_attr'), "{$literal} should be escaped!");
}
}
}
}
public function testCssEscapingEscapesOwaspRecommendedRanges() {
$immune = array();
for ($chr = 0; $chr < 0xff; $chr++) {
if ($chr >= 0x30 && $chr <= 0x39 || $chr >= 0x41 && $chr <= 0x5a || $chr >= 0x61 && $chr <= 0x7a) {
$literal = $this
->codepointToUtf8($chr);
$this
->assertEquals($literal, twig_escape_filter($this->env, $literal, 'css'));
}
else {
$literal = $this
->codepointToUtf8($chr);
$this
->assertNotEquals($literal, twig_escape_filter($this->env, $literal, 'css'), "{$literal} should be escaped!");
}
}
}
}