19 public function testDecodeNamedEntities() {
22 Sanitizer::decodeCharReferences(
'école' ),
23 'decode named entities'
30 public function testDecodeNumericEntities() {
32 "\xc4\x88io bonas dans l'\xc3\xa9cole!",
33 Sanitizer::decodeCharReferences(
"Ĉio bonas dans l'école!" ),
34 'decode numeric entities'
41 public function testDecodeMixedEntities() {
43 "\xc4\x88io bonas dans l'\xc3\xa9cole!",
44 Sanitizer::decodeCharReferences(
"Ĉio bonas dans l'école!" ),
45 'decode mixed numeric/named entities'
52 public function testDecodeMixedComplexEntities() {
54 "\xc4\x88io bonas dans l'\xc3\xa9cole! (mais pas Ĉio dans l'école)",
55 Sanitizer::decodeCharReferences(
56 "Ĉio bonas dans l'école! (mais pas Ĉio dans l'école)"
58 'decode mixed complex entities'
65 public function testInvalidAmpersand() {
68 Sanitizer::decodeCharReferences(
'a & b' ),
76 public function testInvalidEntities() {
79 Sanitizer::decodeCharReferences(
'&foo;' ),
80 'Invalid named entity'
87 public function testInvalidNumberedEntities() {
89 UtfNormal\Constants::UTF8_REPLACEMENT,
90 Sanitizer::decodeCharReferences(
"�" ),
91 'Invalid numbered entity'
102 public function testRemovehtmltagsOnHtml5Tags( $tag, $escaped ) {
108 $this->assertEquals(
"<$tag>",
109 Sanitizer::removeHTMLtags(
"<$tag>" )
112 $this->assertEquals(
"<$tag></$tag>\n",
113 Sanitizer::removeHTMLtags(
"<$tag>" )
121 public static function provideHtml5Tags() {
122 $ESCAPED =
true; # We want
tag to be escaped
123 $VERBATIM =
false; # We want to keep the
tag
125 [
'data', $VERBATIM ],
126 [
'mark', $VERBATIM ],
127 [
'time', $VERBATIM ],
128 [
'video', $ESCAPED ],
132 function dataRemoveHTMLtags() {
136 '<div>Hello world</div />',
137 '<div>Hello world</div>',
138 'Self-closing closing div'
143 '<kbd><kbd>Shift</kbd>+<kbd>F3</kbd></kbd>',
144 '<kbd><kbd>Shift</kbd>+<kbd>F3</kbd></kbd>',
149 '<var>x<sub><var>i</var></sub></var>, <var>y<sub><var>i</var></sub></var>',
150 '<var>x<sub><var>i</var></sub></var>, <var>y<sub><var>i</var></sub></var>',
155 '<dfn><abbr title="Garage Door Opener">GDO</abbr></dfn>',
156 '<dfn><abbr title="Garage Door Opener">GDO</abbr></dfn>',
157 '<abbr> inside <dfn>',
166 public function testRemoveHTMLtags(
$input,
$output, $msg =
null ) {
170 $this->assertEquals(
$output, Sanitizer::removeHTMLtags(
$input ), $msg );
177 public function testDecodeTagAttributes( $expected, $attributes, $message =
'' ) {
178 $this->assertEquals( $expected,
179 Sanitizer::decodeTagAttributes( $attributes ),
184 public static function provideTagAttributesToDecode() {
186 [ [
'foo' =>
'bar' ],
'foo=bar',
'Unquoted attribute' ],
187 [ [
'עברית' =>
'bar' ],
'עברית=bar',
'Non-Latin attribute' ],
188 [ [
'६' =>
'bar' ],
'६=bar',
'Devanagari number' ],
189 [ [
'搭𨋢' =>
'bar' ],
'搭𨋢=bar',
'Non-BMP character' ],
190 [ [],
'ńgh=bar',
'Combining accent is not allowed' ],
191 [ [
'foo' =>
'bar' ],
' foo = bar ',
'Spaced attribute' ],
192 [ [
'foo' =>
'bar' ],
'foo="bar"',
'Double-quoted attribute' ],
193 [ [
'foo' =>
'bar' ],
'foo=\'bar\'',
'Single-quoted attribute' ],
195 [
'foo' =>
'bar',
'baz' =>
'foo' ],
196 'foo=\'bar\' baz="foo"',
200 [
'foo' =>
'bar',
'baz' =>
'foo' ],
201 'foo=\'bar\' baz="foo"',
205 [
'foo' =>
'bar',
'baz' =>
'foo' ],
206 'foo=\'bar\' baz="foo"',
209 [ [
':foo' =>
'bar' ],
':foo=\'bar\'',
'Leading :' ],
210 [ [
'_foo' =>
'bar' ],
'_foo=\'bar\'',
'Leading _' ],
211 [ [
'foo' =>
'bar' ],
'Foo=\'bar\'',
'Leading capital' ],
212 [ [
'foo' =>
'BAR' ],
'FOO=BAR',
'Attribute keys are normalized to lowercase' ],
215 [ [],
'-foo=bar',
'Leading - is forbidden' ],
216 [ [],
'.foo=bar',
'Leading . is forbidden' ],
217 [ [
'foo-bar' =>
'bar' ],
'foo-bar=bar',
'A - is allowed inside the attribute' ],
218 [ [
'foo-' =>
'bar' ],
'foo-=bar',
'A - is allowed inside the attribute' ],
219 [ [
'foo.bar' =>
'baz' ],
'foo.bar=baz',
'A . is allowed inside the attribute' ],
220 [ [
'foo.' =>
'baz' ],
'foo.=baz',
'A . is allowed as last character' ],
221 [ [
'foo6' =>
'baz' ],
'foo6=baz',
'Numbers are allowed' ],
223 # This bit is more relaxed than XML rules, but some extensions use
224 # it, like ProofreadPage (see T29539)
225 [ [
'1foo' =>
'baz' ],
'1foo=baz',
'Leading numbers are allowed' ],
226 [ [],
'foo$=baz',
'Symbols are not allowed' ],
227 [ [],
'foo@=baz',
'Symbols are not allowed' ],
228 [ [],
'foo~=baz',
'Symbols are not allowed' ],
230 [
'foo' =>
'1[#^`*%w/(' ],
232 'All kind of characters are allowed as values'
235 [
'foo' =>
'1[#^`*%\'w/(' ],
236 'foo="1[#^`*%\'w/("',
237 'Double quotes are allowed if quoted by single quotes'
240 [
'foo' =>
'1[#^`*%"w/(' ],
241 'foo=\'1[#^`*%"w/(\'',
242 'Single quotes are allowed if quoted by double quotes'
244 [ [
'foo' =>
'&"' ],
'foo=&"',
'Special chars can be provided as entities' ],
245 [ [
'foo' =>
'&foobar;' ],
'foo=&foobar;',
'Entity-like items are accepted' ],
253 public function testDeprecatedAttributesUnaltered( $inputAttr, $inputEl, $message =
'' ) {
254 $this->assertEquals(
" $inputAttr",
255 Sanitizer::fixTagAttributes( $inputAttr, $inputEl ),
260 public static function provideDeprecatedAttributes() {
263 [
'clear="left"',
'br' ],
264 [
'clear="all"',
'br' ],
265 [
'width="100"',
'td' ],
266 [
'nowrap="true"',
'td' ],
267 [
'nowrap=""',
'td' ],
268 [
'align="right"',
'td' ],
269 [
'align="center"',
'table' ],
270 [
'align="left"',
'tr' ],
271 [
'align="center"',
'div' ],
272 [
'align="left"',
'h1' ],
273 [
'align="left"',
'p' ],
281 public function testCssCommentsChecking( $expected,
$css, $message =
'' ) {
282 $this->assertEquals( $expected,
283 Sanitizer::checkCss(
$css ),
288 public static function provideCssCommentsFixtures() {
293 [
'/* comment */',
'/* comment */' ],
297 [
'display: block;',
"display:/* foo */block;" ],
298 [
'display: block;',
"display:\\2f\\2a foo \\2a\\2f block;",
299 'Backslash-escaped comments must be stripped (T30450)' ],
300 [
'',
'/* unfinished comment structure',
301 'Remove anything after a comment-start token' ],
302 [
'',
"\\2f\\2a unifinished comment'",
303 'Remove anything after a backslash-escaped comment-start token' ],
305 '/* insecure input */',
306 'filter: progid:DXImageTransform.Microsoft.AlphaImageLoader'
307 .
'(src=\'asdf.png\',sizingMethod=\'scale\');'
310 '/* insecure input */',
311 '-ms-filter: "progid:DXImageTransform.Microsoft.AlphaImageLoader'
312 .
'(src=\'asdf.png\',sizingMethod=\'scale\')";'
314 [
'/* insecure input */',
'width: expression(1+1);' ],
315 [
'/* insecure input */',
'background-image: image(asdf.png);' ],
316 [
'/* insecure input */',
'background-image: -webkit-image(asdf.png);' ],
317 [
'/* insecure input */',
'background-image: -moz-image(asdf.png);' ],
318 [
'/* insecure input */',
'background-image: image-set("asdf.png" 1x, "asdf.png" 2x);' ],
320 '/* insecure input */',
321 'background-image: -webkit-image-set("asdf.png" 1x, "asdf.png" 2x);'
324 '/* insecure input */',
325 'background-image: -moz-image-set("asdf.png" 1x, "asdf.png" 2x);'
327 [
'/* insecure input */',
'foo: attr( title, url );' ],
328 [
'/* insecure input */',
'foo: attr( title url );' ],
329 [
'/* insecure input */',
'foo: var(--evil-attribute)' ],
337 public function testEscapeHtmlAllowEntities( $expected,
$html ) {
340 Sanitizer::escapeHtmlAllowEntities(
$html )
344 public static function provideEscapeHtmlAllowEntities() {
347 [
'a¡b',
'a¡b' ],
348 [
'foo'bar',
"foo'bar" ],
349 [
'<script>foo</script>',
'<script>foo</script>' ],
362 Sanitizer::escapeId(
$input, [
'noninitial',
'legacy' ] )
366 public static function provideEscapeId() {
383 [
'Test:A & B/Here',
'Test:A_.26_B.2FHere' ],
384 [
'A&B&C&amp;D&amp;amp;E',
'A.26B.26amp.3BC.26amp.3Bamp.3BD.26amp.3Bamp.3Bamp.3BE' ],
394 public function testEscapeIdReferenceList( $referenceList, $id1, $id2 ) {
396 Sanitizer::escapeIdReferenceList( $referenceList ),
397 Sanitizer::escapeIdForAttribute( $id1 )
399 . Sanitizer::escapeIdForAttribute( $id2 )
403 public static function provideEscapeIdReferenceList() {
406 [
'foo bar',
'foo',
'bar' ],
407 [
'#1 #2',
'#1',
'#2' ],
408 [
'+1 +2',
'+1',
'+2' ],
416 public function testIsReservedDataAttribute( $attr, $expected ) {
417 $this->assertSame( $expected, Sanitizer::isReservedDataAttribute( $attr ) );
420 public static function provideIsReservedDataAttribute() {
424 [
'data-foo',
false ],
426 [
'data-ooui',
true ],
427 [
'data-parsoid',
true ],
428 [
'data-mw-foo',
true ],
429 [
'data-ooui-foo',
true ],
430 [
'data-mwfoo',
true ],
448 public function testEscapeIdForStuff( $stuff,
array $config, $id, $expected, $mode =
null ) {
449 $func =
"Sanitizer::escapeIdFor{$stuff}";
450 $iwFlavor = array_pop( $config );
452 'wgFragmentMode' => $config,
453 'wgExternalInterwikiFragmentMode' => $iwFlavor,
455 $escaped = call_user_func( $func, $id, $mode );
456 self::assertEquals( $expected, $escaped );
459 public function provideEscapeIdForStuff() {
461 $text =
'foo тест_#%!\'()[]:<>&&&amp;';
462 $legacyEncoded =
'foo_.D1.82.D0.B5.D1.81.D1.82_.23.25.21.27.28.29.5B.5D:.3C.3E' .
463 '.26.26amp.3B.26amp.3Bamp.3B';
464 $html5Encoded =
'foo_тест_#%!\'()[]:<>&&&amp;';
467 $legacy = [
'legacy',
'legacy' ];
468 $legacyNew = [
'legacy',
'html5',
'legacy' ];
469 $newLegacy = [
'html5',
'legacy',
'legacy' ];
470 $new = [
'html5',
'legacy' ];
471 $allNew = [
'html5',
'html5' ];
475 [
'Attribute', $legacy, $text, $legacyEncoded, Sanitizer::ID_PRIMARY ],
476 [
'Attribute', $legacy, $text,
false, Sanitizer::ID_FALLBACK ],
477 [
'Link', $legacy, $text, $legacyEncoded ],
478 [
'ExternalInterwiki', $legacy, $text, $legacyEncoded ],
481 [
'Attribute', $legacyNew, $text, $legacyEncoded, Sanitizer::ID_PRIMARY ],
482 [
'Attribute', $legacyNew, $text, $html5Encoded, Sanitizer::ID_FALLBACK ],
483 [
'Link', $legacyNew, $text, $legacyEncoded ],
484 [
'ExternalInterwiki', $legacyNew, $text, $legacyEncoded ],
487 [
'Attribute', $newLegacy, $text, $html5Encoded, Sanitizer::ID_PRIMARY ],
488 [
'Attribute', $newLegacy, $text, $legacyEncoded, Sanitizer::ID_FALLBACK ],
489 [
'Link', $newLegacy, $text, $html5Encoded ],
490 [
'ExternalInterwiki', $newLegacy, $text, $legacyEncoded ],
493 [
'Attribute', $new, $text, $html5Encoded, Sanitizer::ID_PRIMARY ],
494 [
'Attribute', $new, $text,
false, Sanitizer::ID_FALLBACK ],
495 [
'Link', $new, $text, $html5Encoded ],
496 [
'ExternalInterwiki', $new, $text, $legacyEncoded ],
499 [
'Attribute', $allNew, $text, $html5Encoded, Sanitizer::ID_PRIMARY ],
500 [
'Attribute', $allNew, $text,
false, Sanitizer::ID_FALLBACK ],
501 [
'Link', $allNew, $text, $html5Encoded ],
502 [
'ExternalInterwiki', $allNew, $text, $html5Encoded ],
515 public function testStripAllTags(
$input, $expected ) {
516 $this->assertEquals( $expected, Sanitizer::stripAllTags(
$input ) );
519 public function provideStripAllTags() {
521 [
'<p>Foo</p>',
'Foo' ],
522 [
'<p id="one">Foo</p><p id="two">Bar</p>',
'Foo Bar' ],
523 [
"<p>Foo</p>\n<p>Bar</p>",
'Foo Bar' ],
524 [
'<p>Hello <strong> world café</p>',
'Hello <strong> world café' ],
526 '<p><small data-foo=\'bar"<baz>quux\'><a href="./Foo">Bar</a></small> Whee!</p>',
529 [
'1<span class="<?php">2</span>3',
'123' ],
530 [
'1<span class="<?">2</span>3',
'123' ],
531 [
'<th>1</th><td>2</td>',
'1 2' ],
539 public function testInvalidFragmentThrows() {
541 Sanitizer::escapeIdForAttribute(
'This should throw' );
548 public function testNoPrimaryFragmentModeThrows() {
549 $this->
setMwGlobals(
'wgFragmentMode', [ 666 =>
'html5' ] );
550 Sanitizer::escapeIdForAttribute(
'This should throw' );
557 public function testNoPrimaryFragmentModeThrows2() {
558 $this->
setMwGlobals(
'wgFragmentMode', [ 666 =>
'html5' ] );
559 Sanitizer::escapeIdForLink(
'This should throw' );