11 protected function tearDown() {
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 );' ],
325 [
'/* insecure input */',
'foo: var(--evil-attribute)' ],
333 public function testEscapeHtmlAllowEntities( $expected,
$html ) {
336 Sanitizer::escapeHtmlAllowEntities(
$html )
340 public static function provideEscapeHtmlAllowEntities() {
343 [
'a¡b',
'a¡b' ],
344 [
'foo'bar',
"foo'bar" ],
345 [
'<script>foo</script>',
'<script>foo</script>' ],
358 Sanitizer::escapeId(
$input, [
'noninitial',
'legacy' ] )
362 public static function provideEscapeId() {
379 [
'Test:A & B/Here',
'Test:A_.26_B.2FHere' ],
380 [
'A&B&C&amp;D&amp;amp;E',
'A.26B.26amp.3BC.26amp.3Bamp.3BD.26amp.3Bamp.3Bamp.3BE' ],
390 public function testEscapeIdReferenceList( $referenceList, $id1, $id2 ) {
392 Sanitizer::escapeIdReferenceList( $referenceList ),
393 Sanitizer::escapeIdForAttribute( $id1 )
395 . Sanitizer::escapeIdForAttribute( $id2 )
399 public static function provideEscapeIdReferenceList() {
402 [
'foo bar',
'foo',
'bar' ],
403 [
'#1 #2',
'#1',
'#2' ],
404 [
'+1 +2',
'+1',
'+2' ],
412 public function testIsReservedDataAttribute( $attr, $expected ) {
413 $this->assertSame( $expected, Sanitizer::isReservedDataAttribute( $attr ) );
416 public static function provideIsReservedDataAttribute() {
420 [
'data-foo',
false ],
422 [
'data-ooui',
true ],
423 [
'data-parsoid',
true ],
424 [
'data-mw-foo',
true ],
425 [
'data-ooui-foo',
true ],
426 [
'data-mwfoo',
true ],
444 public function testEscapeIdForStuff( $stuff, array $config, $id, $expected, $mode =
null ) {
445 $func =
"Sanitizer::escapeIdFor{$stuff}";
446 $iwFlavor = array_pop( $config );
447 $this->setMwGlobals( [
448 'wgFragmentMode' => $config,
449 'wgExternalInterwikiFragmentMode' => $iwFlavor,
451 $escaped = call_user_func( $func, $id, $mode );
452 self::assertEquals( $expected, $escaped );
455 public function provideEscapeIdForStuff() {
457 $text =
'foo тест_#%!\'()[]:<>&&&amp;';
458 $legacyEncoded =
'foo_.D1.82.D0.B5.D1.81.D1.82_.23.25.21.27.28.29.5B.5D:.3C.3E' .
459 '.26.26amp.3B.26amp.3Bamp.3B';
460 $html5Encoded =
'foo_тест_#%!\'()[]:<>&&&amp;';
461 $html5Experimental =
'foo_тест_!_()[]:<>_amp;_amp;amp;';
464 $legacy = [
'legacy',
'legacy' ];
465 $legacyNew = [
'legacy',
'html5',
'legacy' ];
466 $newLegacy = [
'html5',
'legacy',
'legacy' ];
467 $new = [
'html5',
'legacy' ];
468 $allNew = [
'html5',
'html5' ];
469 $experimentalLegacy = [
'html5-legacy',
'legacy',
'legacy' ];
470 $newExperimental = [
'html5',
'html5-legacy',
'legacy' ];
476 [
'Link', $legacy, $text, $legacyEncoded ],
477 [
'ExternalInterwiki', $legacy, $text, $legacyEncoded ],
482 [
'Link', $legacyNew, $text, $legacyEncoded ],
483 [
'ExternalInterwiki', $legacyNew, $text, $legacyEncoded ],
488 [
'Link', $newLegacy, $text, $html5Encoded ],
489 [
'ExternalInterwiki', $newLegacy, $text, $legacyEncoded ],
494 [
'Link', $new, $text, $html5Encoded ],
495 [
'ExternalInterwiki', $new, $text, $legacyEncoded ],
500 [
'Link', $allNew, $text, $html5Encoded ],
501 [
'ExternalInterwiki', $allNew, $text, $html5Encoded ],
506 [
'Link', $experimentalLegacy, $text, $html5Experimental ],
507 [
'ExternalInterwiki', $experimentalLegacy, $text, $legacyEncoded ],
512 [
'Link', $newExperimental, $text, $html5Encoded ],
513 [
'ExternalInterwiki', $newExperimental, $text, $legacyEncoded ],
526 public function testStripAllTags(
$input, $expected ) {
527 $this->assertEquals( $expected, Sanitizer::stripAllTags(
$input ) );
530 public function provideStripAllTags() {
532 [
'<p>Foo</p>',
'Foo' ],
533 [
'<p id="one">Foo</p><p id="two">Bar</p>',
'FooBar' ],
534 [
"<p>Foo</p>\n<p>Bar</p>",
'Foo Bar' ],
535 [
'<p>Hello <strong> world café</p>',
'Hello <strong> world café' ],
537 '<p><small data-foo=\'bar"<baz>quux\'><a href="./Foo">Bar</a></small> Whee!</p>',
540 [
'1<span class="<?php">2</span>3',
'123' ],
541 [
'1<span class="<?">2</span>3',
'123' ],
549 public function testInvalidFragmentThrows() {
550 $this->setMwGlobals(
'wgFragmentMode', [
'boom!' ] );
558 public function testNoPrimaryFragmentModeThrows() {
559 $this->setMwGlobals(
'wgFragmentMode', [ 666 =>
'html5' ] );
567 public function testNoPrimaryFragmentModeThrows2() {
568 $this->setMwGlobals(
'wgFragmentMode', [ 666 =>
'html5' ] );
static setInstance( $instance)
Set the driver to be used.
static destroySingleton()
Destroy the current singleton instance.
static escapeIdForLink( $id)
Given a section name or other user-generated or otherwise unsafe string, escapes it to be a valid URL...
const ID_FALLBACK
Tells escapeUrlForHtml() to encode the ID using the fallback encoding, or return false if no fallback...
static escapeIdForAttribute( $id, $mode=self::ID_PRIMARY)
Given a section name or other user-generated or otherwise unsafe string, escapes it to be a valid HTM...
const ID_PRIMARY
Tells escapeUrlForHtml() to encode the ID using the wiki's primary encoding.
Unicode normalization routines for working with UTF-8 strings.
static configuration should be added through ResourceLoaderGetConfigVars instead can be used to get the real title after the basic globals have been set but before ordinary actions take place $output
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return true
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses & $html
processing should stop and the error should be shown to the user * false
if(is_array($mode)) switch( $mode) $input