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() {
90 Sanitizer::decodeCharReferences(
"�" ),
91 'Invalid numbered entity'
102 public function testRemovehtmltagsOnHtml5Tags( $tag, $escaped ) {
106 $this->assertEquals(
"<$tag>",
107 Sanitizer::removeHTMLtags(
"<$tag>" )
110 $this->assertEquals(
"<$tag></$tag>\n",
111 Sanitizer::removeHTMLtags(
"<$tag>" )
119 public static function provideHtml5Tags() {
120 $ESCAPED =
true; # We want
tag to be escaped
121 $VERBATIM =
false; # We want to keep the
tag
123 [
'data', $VERBATIM ],
124 [
'mark', $VERBATIM ],
125 [
'time', $VERBATIM ],
126 [
'video', $ESCAPED ],
130 function dataRemoveHTMLtags() {
134 '<div>Hello world</div />',
135 '<div>Hello world</div>',
136 'Self-closing closing div'
141 '<kbd><kbd>Shift</kbd>+<kbd>F3</kbd></kbd>',
142 '<kbd><kbd>Shift</kbd>+<kbd>F3</kbd></kbd>',
147 '<var>x<sub><var>i</var></sub></var>, <var>y<sub><var>i</var></sub></var>',
148 '<var>x<sub><var>i</var></sub></var>, <var>y<sub><var>i</var></sub></var>',
153 '<dfn><abbr title="Garage Door Opener">GDO</abbr></dfn>',
154 '<dfn><abbr title="Garage Door Opener">GDO</abbr></dfn>',
155 '<abbr> inside <dfn>',
164 public function testRemoveHTMLtags(
$input,
$output, $msg =
null ) {
166 $this->assertEquals(
$output, Sanitizer::removeHTMLtags(
$input ), $msg );
173 public function testDecodeTagAttributes( $expected, $attributes, $message =
'' ) {
174 $this->assertEquals( $expected,
175 Sanitizer::decodeTagAttributes( $attributes ),
180 public static function provideTagAttributesToDecode() {
182 [ [
'foo' =>
'bar' ],
'foo=bar',
'Unquoted attribute' ],
183 [ [
'עברית' =>
'bar' ],
'עברית=bar',
'Non-Latin attribute' ],
184 [ [
'६' =>
'bar' ],
'६=bar',
'Devanagari number' ],
185 [ [
'搭𨋢' =>
'bar' ],
'搭𨋢=bar',
'Non-BMP character' ],
186 [ [],
'ńgh=bar',
'Combining accent is not allowed' ],
187 [ [
'foo' =>
'bar' ],
' foo = bar ',
'Spaced attribute' ],
188 [ [
'foo' =>
'bar' ],
'foo="bar"',
'Double-quoted attribute' ],
189 [ [
'foo' =>
'bar' ],
'foo=\'bar\'',
'Single-quoted attribute' ],
191 [
'foo' =>
'bar',
'baz' =>
'foo' ],
192 'foo=\'bar\' baz="foo"',
196 [
'foo' =>
'bar',
'baz' =>
'foo' ],
197 'foo=\'bar\' baz="foo"',
201 [
'foo' =>
'bar',
'baz' =>
'foo' ],
202 'foo=\'bar\' baz="foo"',
205 [ [
':foo' =>
'bar' ],
':foo=\'bar\'',
'Leading :' ],
206 [ [
'_foo' =>
'bar' ],
'_foo=\'bar\'',
'Leading _' ],
207 [ [
'foo' =>
'bar' ],
'Foo=\'bar\'',
'Leading capital' ],
208 [ [
'foo' =>
'BAR' ],
'FOO=BAR',
'Attribute keys are normalized to lowercase' ],
211 [ [],
'-foo=bar',
'Leading - is forbidden' ],
212 [ [],
'.foo=bar',
'Leading . is forbidden' ],
213 [ [
'foo-bar' =>
'bar' ],
'foo-bar=bar',
'A - is allowed inside the attribute' ],
214 [ [
'foo-' =>
'bar' ],
'foo-=bar',
'A - is allowed inside the attribute' ],
215 [ [
'foo.bar' =>
'baz' ],
'foo.bar=baz',
'A . is allowed inside the attribute' ],
216 [ [
'foo.' =>
'baz' ],
'foo.=baz',
'A . is allowed as last character' ],
217 [ [
'foo6' =>
'baz' ],
'foo6=baz',
'Numbers are allowed' ],
219 # This bit is more relaxed than XML rules, but some extensions use
220 # it, like ProofreadPage (see T29539)
221 [ [
'1foo' =>
'baz' ],
'1foo=baz',
'Leading numbers are allowed' ],
222 [ [],
'foo$=baz',
'Symbols are not allowed' ],
223 [ [],
'foo@=baz',
'Symbols are not allowed' ],
224 [ [],
'foo~=baz',
'Symbols are not allowed' ],
226 [
'foo' =>
'1[#^`*%w/(' ],
228 'All kind of characters are allowed as values'
231 [
'foo' =>
'1[#^`*%\'w/(' ],
232 'foo="1[#^`*%\'w/("',
233 'Double quotes are allowed if quoted by single quotes'
236 [
'foo' =>
'1[#^`*%"w/(' ],
237 'foo=\'1[#^`*%"w/(\'',
238 'Single quotes are allowed if quoted by double quotes'
240 [ [
'foo' =>
'&"' ],
'foo=&"',
'Special chars can be provided as entities' ],
241 [ [
'foo' =>
'&foobar;' ],
'foo=&foobar;',
'Entity-like items are accepted' ],
249 public function testDeprecatedAttributesUnaltered( $inputAttr, $inputEl, $message =
'' ) {
250 $this->assertEquals(
" $inputAttr",
251 Sanitizer::fixTagAttributes( $inputAttr, $inputEl ),
256 public static function provideDeprecatedAttributes() {
259 [
'clear="left"',
'br' ],
260 [
'clear="all"',
'br' ],
261 [
'width="100"',
'td' ],
262 [
'nowrap="true"',
'td' ],
263 [
'nowrap=""',
'td' ],
264 [
'align="right"',
'td' ],
265 [
'align="center"',
'table' ],
266 [
'align="left"',
'tr' ],
267 [
'align="center"',
'div' ],
268 [
'align="left"',
'h1' ],
269 [
'align="left"',
'p' ],
277 public function testCssCommentsChecking( $expected,
$css, $message =
'' ) {
278 $this->assertEquals( $expected,
279 Sanitizer::checkCss(
$css ),
284 public static function provideCssCommentsFixtures() {
289 [
'/* comment */',
'/* comment */' ],
293 [
'display: block;',
"display:/* foo */block;" ],
294 [
'display: block;',
"display:\\2f\\2a foo \\2a\\2f block;",
295 'Backslash-escaped comments must be stripped (T30450)' ],
296 [
'',
'/* unfinished comment structure',
297 'Remove anything after a comment-start token' ],
298 [
'',
"\\2f\\2a unifinished comment'",
299 'Remove anything after a backslash-escaped comment-start token' ],
301 '/* insecure input */',
302 'filter: progid:DXImageTransform.Microsoft.AlphaImageLoader'
303 .
'(src=\'asdf.png\',sizingMethod=\'scale\');'
306 '/* insecure input */',
307 '-ms-filter: "progid:DXImageTransform.Microsoft.AlphaImageLoader'
308 .
'(src=\'asdf.png\',sizingMethod=\'scale\')";'
310 [
'/* insecure input */',
'width: expression(1+1);' ],
311 [
'/* insecure input */',
'background-image: image(asdf.png);' ],
312 [
'/* insecure input */',
'background-image: -webkit-image(asdf.png);' ],
313 [
'/* insecure input */',
'background-image: -moz-image(asdf.png);' ],
314 [
'/* insecure input */',
'background-image: image-set("asdf.png" 1x, "asdf.png" 2x);' ],
316 '/* insecure input */',
317 'background-image: -webkit-image-set("asdf.png" 1x, "asdf.png" 2x);'
320 '/* insecure input */',
321 'background-image: -moz-image-set("asdf.png" 1x, "asdf.png" 2x);'
323 [
'/* insecure input */',
'foo: attr( title, url );' ],
324 [
'/* insecure input */',
'foo: attr( title url );' ],
332 public function testEscapeHtmlAllowEntities( $expected,
$html ) {
335 Sanitizer::escapeHtmlAllowEntities(
$html )
339 public static function provideEscapeHtmlAllowEntities() {
342 [
'a¡b',
'a¡b' ],
343 [
'foo'bar',
"foo'bar" ],
344 [
'<script>foo</script>',
'<script>foo</script>' ],
357 Sanitizer::escapeId(
$input, [
'noninitial',
'legacy' ] )
361 public static function provideEscapeId() {
378 [
'Test:A & B/Here',
'Test:A_.26_B.2FHere' ],
379 [
'A&B&C&amp;D&amp;amp;E',
'A.26B.26amp.3BC.26amp.3Bamp.3BD.26amp.3Bamp.3Bamp.3BE' ],
389 public function testEscapeIdReferenceList( $referenceList, $id1, $id2 ) {
391 Sanitizer::escapeIdReferenceList( $referenceList,
'noninitial' ),
392 Sanitizer::escapeIdForAttribute( $id1 )
394 . Sanitizer::escapeIdForAttribute( $id2 )
398 public static function provideEscapeIdReferenceList() {
401 [
'foo bar',
'foo',
'bar' ],
402 [
'#1 #2',
'#1',
'#2' ],
403 [
'+1 +2',
'+1',
'+2' ],
410 public function testIsReservedDataAttribute( $attr, $expected ) {
411 $this->assertSame( $expected, Sanitizer::isReservedDataAttribute( $attr ) );
414 public static function provideIsReservedDataAttribute() {
418 [
'data-foo',
false ],
420 [
'data-ooui',
true ],
421 [
'data-parsoid',
true ],
422 [
'data-mw-foo',
true ],
423 [
'data-ooui-foo',
true ],
424 [
'data-mwfoo',
true ],
442 public function testEscapeIdForStuff( $stuff,
array $config, $id, $expected, $mode =
null ) {
443 $func =
"Sanitizer::escapeIdFor{$stuff}";
444 $iwFlavor = array_pop( $config );
446 'wgFragmentMode' => $config,
447 'wgExternalInterwikiFragmentMode' => $iwFlavor,
449 $escaped = call_user_func( $func, $id, $mode );
450 self::assertEquals( $expected, $escaped );
453 public function provideEscapeIdForStuff() {
455 $text =
'foo тест_#%!\'()[]:<>&&&amp;';
456 $legacyEncoded =
'foo_.D1.82.D0.B5.D1.81.D1.82_.23.25.21.27.28.29.5B.5D:.3C.3E' .
457 '.26.26amp.3B.26amp.3Bamp.3B';
458 $html5Encoded =
'foo_тест_#%!\'()[]:<>&&&amp;';
459 $html5Experimental =
'foo_тест_!_()[]:<>_amp;_amp;amp;';
462 $legacy = [
'legacy',
'legacy' ];
463 $legacyNew = [
'legacy',
'html5',
'legacy' ];
464 $newLegacy = [
'html5',
'legacy',
'legacy' ];
465 $new = [
'html5',
'legacy' ];
466 $allNew = [
'html5',
'html5' ];
467 $experimentalLegacy = [
'html5-legacy',
'legacy',
'legacy' ];
468 $newExperimental = [
'html5',
'html5-legacy',
'legacy' ];
472 [
'Attribute', $legacy, $text, $legacyEncoded, Sanitizer::ID_PRIMARY ],
473 [
'Attribute', $legacy, $text,
false, Sanitizer::ID_FALLBACK ],
474 [
'Link', $legacy, $text, $legacyEncoded ],
475 [
'ExternalInterwiki', $legacy, $text, $legacyEncoded ],
478 [
'Attribute', $legacyNew, $text, $legacyEncoded, Sanitizer::ID_PRIMARY ],
479 [
'Attribute', $legacyNew, $text, $html5Encoded, Sanitizer::ID_FALLBACK ],
480 [
'Link', $legacyNew, $text, $legacyEncoded ],
481 [
'ExternalInterwiki', $legacyNew, $text, $legacyEncoded ],
484 [
'Attribute', $newLegacy, $text, $html5Encoded, Sanitizer::ID_PRIMARY ],
485 [
'Attribute', $newLegacy, $text, $legacyEncoded, Sanitizer::ID_FALLBACK ],
486 [
'Link', $newLegacy, $text, $html5Encoded ],
487 [
'ExternalInterwiki', $newLegacy, $text, $legacyEncoded ],
490 [
'Attribute', $new, $text, $html5Encoded, Sanitizer::ID_PRIMARY ],
491 [
'Attribute', $new, $text,
false, Sanitizer::ID_FALLBACK ],
492 [
'Link', $new, $text, $html5Encoded ],
493 [
'ExternalInterwiki', $new, $text, $legacyEncoded ],
496 [
'Attribute', $allNew, $text, $html5Encoded, Sanitizer::ID_PRIMARY ],
497 [
'Attribute', $allNew, $text,
false, Sanitizer::ID_FALLBACK ],
498 [
'Link', $allNew, $text, $html5Encoded ],
499 [
'ExternalInterwiki', $allNew, $text, $html5Encoded ],
502 [
'Attribute', $experimentalLegacy, $text, $html5Experimental, Sanitizer::ID_PRIMARY ],
503 [
'Attribute', $experimentalLegacy, $text, $legacyEncoded, Sanitizer::ID_FALLBACK ],
504 [
'Link', $experimentalLegacy, $text, $html5Experimental ],
505 [
'ExternalInterwiki', $experimentalLegacy, $text, $legacyEncoded ],
508 [
'Attribute', $newExperimental, $text, $html5Encoded, Sanitizer::ID_PRIMARY ],
509 [
'Attribute', $newExperimental, $text, $html5Experimental, Sanitizer::ID_FALLBACK ],
510 [
'Link', $newExperimental, $text, $html5Encoded ],
511 [
'ExternalInterwiki', $newExperimental, $text, $legacyEncoded ],
519 public function testInvalidFragmentThrows() {
521 Sanitizer::escapeIdForAttribute(
'This should throw' );
528 public function testNoPrimaryFragmentModeThrows() {
529 $this->
setMwGlobals(
'wgFragmentMode', [ 666 =>
'html5' ] );
530 Sanitizer::escapeIdForAttribute(
'This should throw' );
537 public function testNoPrimaryFragmentModeThrows2() {
538 $this->
setMwGlobals(
'wgFragmentMode', [ 666 =>
'html5' ] );
539 Sanitizer::escapeIdForLink(
'This should throw' );