ILIAS  release_5-0 Revision 5.0.0-1144-gc4397b1f870
Sanitizer.php
Go to the documentation of this file.
1<?php
30define( 'MW_CHAR_REFS_REGEX',
31 '/&([A-Za-z0-9\x80-\xff]+);
32 |&\#([0-9]+);
33 |&\#x([0-9A-Za-z]+);
34 |&\#X([0-9A-Za-z]+);
35 |(&)/x' );
36
42$attrib = '[A-Za-z0-9]';
43$space = '[\x09\x0a\x0d\x20]';
44define( 'MW_ATTRIBS_REGEX',
45 "/(?:^|$space)($attrib+)
46 ($space*=$space*
47 (?:
48 # The attribute value: quoted or alone
49 \"([^<\"]*)\"
50 | '([^<']*)'
51 | ([a-zA-Z0-9!#$%&()*,\\-.\\/:;<>?@[\\]^_`{|}~]+)
52 | (\#[0-9a-fA-F]+) # Technically wrong, but lots of
53 # colors are specified like this.
54 # We'll be normalizing it.
55 )
56 )?(?=$space|\$)/sx" );
57
63global $wgHtmlEntities;
64$wgHtmlEntities = array(
65 'Aacute' => 193,
66 'aacute' => 225,
67 'Acirc' => 194,
68 'acirc' => 226,
69 'acute' => 180,
70 'AElig' => 198,
71 'aelig' => 230,
72 'Agrave' => 192,
73 'agrave' => 224,
74 'alefsym' => 8501,
75 'Alpha' => 913,
76 'alpha' => 945,
77 'amp' => 38,
78 'and' => 8743,
79 'ang' => 8736,
80 'Aring' => 197,
81 'aring' => 229,
82 'asymp' => 8776,
83 'Atilde' => 195,
84 'atilde' => 227,
85 'Auml' => 196,
86 'auml' => 228,
87 'bdquo' => 8222,
88 'Beta' => 914,
89 'beta' => 946,
90 'brvbar' => 166,
91 'bull' => 8226,
92 'cap' => 8745,
93 'Ccedil' => 199,
94 'ccedil' => 231,
95 'cedil' => 184,
96 'cent' => 162,
97 'Chi' => 935,
98 'chi' => 967,
99 'circ' => 710,
100 'clubs' => 9827,
101 'cong' => 8773,
102 'copy' => 169,
103 'crarr' => 8629,
104 'cup' => 8746,
105 'curren' => 164,
106 'dagger' => 8224,
107 'Dagger' => 8225,
108 'darr' => 8595,
109 'dArr' => 8659,
110 'deg' => 176,
111 'Delta' => 916,
112 'delta' => 948,
113 'diams' => 9830,
114 'divide' => 247,
115 'Eacute' => 201,
116 'eacute' => 233,
117 'Ecirc' => 202,
118 'ecirc' => 234,
119 'Egrave' => 200,
120 'egrave' => 232,
121 'empty' => 8709,
122 'emsp' => 8195,
123 'ensp' => 8194,
124 'Epsilon' => 917,
125 'epsilon' => 949,
126 'equiv' => 8801,
127 'Eta' => 919,
128 'eta' => 951,
129 'ETH' => 208,
130 'eth' => 240,
131 'Euml' => 203,
132 'euml' => 235,
133 'euro' => 8364,
134 'exist' => 8707,
135 'fnof' => 402,
136 'forall' => 8704,
137 'frac12' => 189,
138 'frac14' => 188,
139 'frac34' => 190,
140 'frasl' => 8260,
141 'Gamma' => 915,
142 'gamma' => 947,
143 'ge' => 8805,
144 'gt' => 62,
145 'harr' => 8596,
146 'hArr' => 8660,
147 'hearts' => 9829,
148 'hellip' => 8230,
149 'Iacute' => 205,
150 'iacute' => 237,
151 'Icirc' => 206,
152 'icirc' => 238,
153 'iexcl' => 161,
154 'Igrave' => 204,
155 'igrave' => 236,
156 'image' => 8465,
157 'infin' => 8734,
158 'int' => 8747,
159 'Iota' => 921,
160 'iota' => 953,
161 'iquest' => 191,
162 'isin' => 8712,
163 'Iuml' => 207,
164 'iuml' => 239,
165 'Kappa' => 922,
166 'kappa' => 954,
167 'Lambda' => 923,
168 'lambda' => 955,
169 'lang' => 9001,
170 'laquo' => 171,
171 'larr' => 8592,
172 'lArr' => 8656,
173 'lceil' => 8968,
174 'ldquo' => 8220,
175 'le' => 8804,
176 'lfloor' => 8970,
177 'lowast' => 8727,
178 'loz' => 9674,
179 'lrm' => 8206,
180 'lsaquo' => 8249,
181 'lsquo' => 8216,
182 'lt' => 60,
183 'macr' => 175,
184 'mdash' => 8212,
185 'micro' => 181,
186 'middot' => 183,
187 'minus' => 8722,
188 'Mu' => 924,
189 'mu' => 956,
190 'nabla' => 8711,
191 'nbsp' => 160,
192 'ndash' => 8211,
193 'ne' => 8800,
194 'ni' => 8715,
195 'not' => 172,
196 'notin' => 8713,
197 'nsub' => 8836,
198 'Ntilde' => 209,
199 'ntilde' => 241,
200 'Nu' => 925,
201 'nu' => 957,
202 'Oacute' => 211,
203 'oacute' => 243,
204 'Ocirc' => 212,
205 'ocirc' => 244,
206 'OElig' => 338,
207 'oelig' => 339,
208 'Ograve' => 210,
209 'ograve' => 242,
210 'oline' => 8254,
211 'Omega' => 937,
212 'omega' => 969,
213 'Omicron' => 927,
214 'omicron' => 959,
215 'oplus' => 8853,
216 'or' => 8744,
217 'ordf' => 170,
218 'ordm' => 186,
219 'Oslash' => 216,
220 'oslash' => 248,
221 'Otilde' => 213,
222 'otilde' => 245,
223 'otimes' => 8855,
224 'Ouml' => 214,
225 'ouml' => 246,
226 'para' => 182,
227 'part' => 8706,
228 'permil' => 8240,
229 'perp' => 8869,
230 'Phi' => 934,
231 'phi' => 966,
232 'Pi' => 928,
233 'pi' => 960,
234 'piv' => 982,
235 'plusmn' => 177,
236 'pound' => 163,
237 'prime' => 8242,
238 'Prime' => 8243,
239 'prod' => 8719,
240 'prop' => 8733,
241 'Psi' => 936,
242 'psi' => 968,
243 'quot' => 34,
244 'radic' => 8730,
245 'rang' => 9002,
246 'raquo' => 187,
247 'rarr' => 8594,
248 'rArr' => 8658,
249 'rceil' => 8969,
250 'rdquo' => 8221,
251 'real' => 8476,
252 'reg' => 174,
253 'rfloor' => 8971,
254 'Rho' => 929,
255 'rho' => 961,
256 'rlm' => 8207,
257 'rsaquo' => 8250,
258 'rsquo' => 8217,
259 'sbquo' => 8218,
260 'Scaron' => 352,
261 'scaron' => 353,
262 'sdot' => 8901,
263 'sect' => 167,
264 'shy' => 173,
265 'Sigma' => 931,
266 'sigma' => 963,
267 'sigmaf' => 962,
268 'sim' => 8764,
269 'spades' => 9824,
270 'sub' => 8834,
271 'sube' => 8838,
272 'sum' => 8721,
273 'sup' => 8835,
274 'sup1' => 185,
275 'sup2' => 178,
276 'sup3' => 179,
277 'supe' => 8839,
278 'szlig' => 223,
279 'Tau' => 932,
280 'tau' => 964,
281 'there4' => 8756,
282 'Theta' => 920,
283 'theta' => 952,
284 'thetasym' => 977,
285 'thinsp' => 8201,
286 'THORN' => 222,
287 'thorn' => 254,
288 'tilde' => 732,
289 'times' => 215,
290 'trade' => 8482,
291 'Uacute' => 218,
292 'uacute' => 250,
293 'uarr' => 8593,
294 'uArr' => 8657,
295 'Ucirc' => 219,
296 'ucirc' => 251,
297 'Ugrave' => 217,
298 'ugrave' => 249,
299 'uml' => 168,
300 'upsih' => 978,
301 'Upsilon' => 933,
302 'upsilon' => 965,
303 'Uuml' => 220,
304 'uuml' => 252,
305 'weierp' => 8472,
306 'Xi' => 926,
307 'xi' => 958,
308 'Yacute' => 221,
309 'yacute' => 253,
310 'yen' => 165,
311 'Yuml' => 376,
312 'yuml' => 255,
313 'Zeta' => 918,
314 'zeta' => 950,
315 'zwj' => 8205,
316 'zwnj' => 8204 );
317
323 'רלמ' => 'rlm',
324 'رلم' => 'rlm',
325);
326
327
332class Sanitizer {
342 static function removeHTMLtags( $text, $processCallback = null, $args = array() ) {
343 global $wgUseTidy;
344
345 static $htmlpairs, $htmlsingle, $htmlsingleonly, $htmlnest, $tabletags,
346 $htmllist, $listtags, $htmlsingleallowed, $htmlelements, $staticInitialised;
347
348 wfProfileIn( __METHOD__ );
349
350 if ( !$staticInitialised ) {
351
352 $htmlpairs = array( # Tags that must be closed
353 'b', 'del', 'i', 'ins', 'u', 'font', 'big', 'small', 'sub', 'sup', 'h1',
354 'h2', 'h3', 'h4', 'h5', 'h6', 'cite', 'code', 'em', 's',
355 'strike', 'strong', 'tt', 'var', 'div', 'center',
356 'blockquote', 'ol', 'ul', 'dl', 'table', 'caption', 'pre',
357 'ruby', 'rt' , 'rb' , 'rp', 'p', 'span', 'u'
358 );
359 $htmlsingle = array(
360 'br', 'hr', 'li', 'dt', 'dd'
361 );
362 $htmlsingleonly = array( # Elements that cannot have close tags
363 'br', 'hr'
364 );
365 $htmlnest = array( # Tags that can be nested--??
366 'table', 'tr', 'td', 'th', 'div', 'blockquote', 'ol', 'ul',
367 'dl', 'font', 'big', 'small', 'sub', 'sup', 'span'
368 );
369 $tabletags = array( # Can only appear inside table, we will close them
370 'td', 'th', 'tr',
371 );
372 $htmllist = array( # Tags used by list
373 'ul','ol',
374 );
375 $listtags = array( # Tags that can appear in a list
376 'li',
377 );
378
379 $htmlsingleallowed = array_merge( $htmlsingle, $tabletags );
380 $htmlelements = array_merge( $htmlsingle, $htmlpairs, $htmlnest );
381
382 # Convert them all to hashtables for faster lookup
383 $vars = array( 'htmlpairs', 'htmlsingle', 'htmlsingleonly', 'htmlnest', 'tabletags',
384 'htmllist', 'listtags', 'htmlsingleallowed', 'htmlelements' );
385 foreach ( $vars as $var ) {
386 $$var = array_flip( $$var );
387 }
388 $staticInitialised = true;
389 }
390
391 # Remove HTML comments
392 $text = Sanitizer::removeHTMLcomments( $text );
393 $bits = explode( '<', $text );
394 $text = str_replace( '>', '&gt;', array_shift( $bits ) );
395 if(!$wgUseTidy) {
396 $tagstack = $tablestack = array();
397 foreach ( $bits as $x ) {
398 $regs = array();
399 if( preg_match( '!^(/?)(\\w+)([^>]*?)(/{0,1}>)([^<]*)$!', $x, $regs ) ) {
400 list( /* $qbar */, $slash, $t, $params, $brace, $rest ) = $regs;
401 } else {
402 $slash = $t = $params = $brace = $rest = null;
403 }
404
405 $badtag = 0 ;
406 if ( isset( $htmlelements[$t = strtolower( $t )] ) ) {
407 # Check our stack
408 if ( $slash ) {
409 # Closing a tag...
410 if( isset( $htmlsingleonly[$t] ) ) {
411 $badtag = 1;
412 } elseif ( ( $ot = @array_pop( $tagstack ) ) != $t ) {
413 if ( isset( $htmlsingleallowed[$ot] ) ) {
414 # Pop all elements with an optional close tag
415 # and see if we find a match below them
416 $optstack = array();
417 array_push ($optstack, $ot);
418 while ( ( ( $ot = @array_pop( $tagstack ) ) != $t ) &&
419 isset( $htmlsingleallowed[$ot] ) )
420 {
421 array_push ($optstack, $ot);
422 }
423 if ( $t != $ot ) {
424 # No match. Push the optinal elements back again
425 $badtag = 1;
426 while ( $ot = @array_pop( $optstack ) ) {
427 array_push( $tagstack, $ot );
428 }
429 }
430 } else {
431 @array_push( $tagstack, $ot );
432 # <li> can be nested in <ul> or <ol>, skip those cases:
433 if(!(isset( $htmllist[$ot] ) && isset( $listtags[$t] ) )) {
434 $badtag = 1;
435 }
436 }
437 } else {
438 if ( $t == 'table' ) {
439 $tagstack = array_pop( $tablestack );
440 }
441 }
442 $newparams = '';
443 } else {
444 # Keep track for later
445 if ( isset( $tabletags[$t] ) &&
446 ! in_array( 'table', $tagstack ) ) {
447 $badtag = 1;
448 } else if ( in_array( $t, $tagstack ) &&
449 ! isset( $htmlnest [$t ] ) ) {
450 $badtag = 1 ;
451 # Is it a self closed htmlpair ? (bug 5487)
452 } else if( $brace == '/>' &&
453 isset( $htmlpairs[$t] ) ) {
454 $badtag = 1;
455 } elseif( isset( $htmlsingleonly[$t] ) ) {
456 # Hack to force empty tag for uncloseable elements
457 $brace = '/>';
458 } else if( isset( $htmlsingle[$t] ) ) {
459 # Hack to not close $htmlsingle tags
460 $brace = NULL;
461 } else if( isset( $tabletags[$t] )
462 && in_array($t ,$tagstack) ) {
463 // New table tag but forgot to close the previous one
464 $text .= "</$t>";
465 } else {
466 if ( $t == 'table' ) {
467 array_push( $tablestack, $tagstack );
468 $tagstack = array();
469 }
470 array_push( $tagstack, $t );
471 }
472
473 # Replace any variables or template parameters with
474 # plaintext results.
475 if( is_callable( $processCallback ) ) {
476 call_user_func_array( $processCallback, array( &$params, $args ) );
477 }
478
479 # Strip non-approved attributes from the tag
480 $newparams = Sanitizer::fixTagAttributes( $params, $t );
481 }
482 if ( ! $badtag ) {
483 $rest = str_replace( '>', '&gt;', $rest );
484 $close = ( $brace == '/>' && !$slash ) ? ' /' : '';
485 $text .= "<$slash$t$newparams$close>$rest";
486 continue;
487 }
488 }
489 $text .= '&lt;' . str_replace( '>', '&gt;', $x);
490 }
491 # Close off any remaining tags
492 while ( is_array( $tagstack ) && ($t = array_pop( $tagstack )) ) {
493 $text .= "</$t>\n";
494 if ( $t == 'table' ) { $tagstack = array_pop( $tablestack ); }
495 }
496 } else {
497 # this might be possible using tidy itself
498 foreach ( $bits as $x ) {
499 preg_match( '/^(\\/?)(\\w+)([^>]*?)(\\/{0,1}>)([^<]*)$/',
500 $x, $regs );
501 @list( /* $qbar */, $slash, $t, $params, $brace, $rest ) = $regs;
502 if ( isset( $htmlelements[$t = strtolower( $t )] ) ) {
503 if( is_callable( $processCallback ) ) {
504 call_user_func_array( $processCallback, array( &$params, $args ) );
505 }
506 $newparams = Sanitizer::fixTagAttributes( $params, $t );
507 $rest = str_replace( '>', '&gt;', $rest );
508 $text .= "<$slash$t$newparams$brace$rest";
509 } else {
510 $text .= '&lt;' . str_replace( '>', '&gt;', $x);
511 }
512 }
513 }
514 wfProfileOut( __METHOD__ );
515 return $text;
516 }
517
528 static function removeHTMLcomments( $text ) {
529 wfProfileIn( __METHOD__ );
530 while (($start = strpos($text, '<!--')) !== false) {
531 $end = strpos($text, '-->', $start + 4);
532 if ($end === false) {
533 # Unterminated comment; bail out
534 break;
535 }
536
537 $end += 3;
538
539 # Trim space and newline if the comment is both
540 # preceded and followed by a newline
541 $spaceStart = max($start - 1, 0);
542 $spaceLen = $end - $spaceStart;
543 while (substr($text, $spaceStart, 1) === ' ' && $spaceStart > 0) {
544 $spaceStart--;
545 $spaceLen++;
546 }
547 while (substr($text, $spaceStart + $spaceLen, 1) === ' ')
548 $spaceLen++;
549 if (substr($text, $spaceStart, 1) === "\n" and substr($text, $spaceStart + $spaceLen, 1) === "\n") {
550 # Remove the comment, leading and trailing
551 # spaces, and leave only one newline.
552 $text = substr_replace($text, "\n", $spaceStart, $spaceLen + 1);
553 }
554 else {
555 # Remove just the comment.
556 $text = substr_replace($text, '', $start, $end - $start);
557 }
558 }
559 wfProfileOut( __METHOD__ );
560 return $text;
561 }
562
577 static function validateTagAttributes( $attribs, $element ) {
578 $whitelist = array_flip( Sanitizer::attributeWhitelist( $element ) );
579 $out = array();
580 foreach( $attribs as $attribute => $value ) {
581 if( !isset( $whitelist[$attribute] ) ) {
582 continue;
583 }
584 # Strip javascript "expression" from stylesheets.
585 # http://msdn.microsoft.com/workshop/author/dhtml/overview/recalc.asp
586 if( $attribute == 'style' ) {
587 $value = Sanitizer::checkCss( $value );
588 if( $value === false ) {
589 # haxx0r
590 continue;
591 }
592 }
593
594 if ( $attribute === 'id' )
595 $value = Sanitizer::escapeId( $value );
596
597 // If this attribute was previously set, override it.
598 // Output should only have one attribute of each name.
599 $out[$attribute] = $value;
600 }
601 return $out;
602 }
603
613 static function checkCss( $value ) {
614 $stripped = Sanitizer::decodeCharReferences( $value );
615
616 // Remove any comments; IE gets token splitting wrong
617 $stripped = StringUtils::delimiterReplace( '/*', '*/', ' ', $stripped );
618
619 $value = $stripped;
620
621 // ... and continue checks
622 $stripped = preg_replace( '!\\\\([0-9A-Fa-f]{1,6})[ \\n\\r\\t\\f]?!e',
623 'codepointToUtf8(hexdec("$1"))', $stripped );
624 $stripped = str_replace( '\\', '', $stripped );
625 if( preg_match( '/(?:expression|tps*:\/\/|url\\s*\‍().*/is',
626 $stripped ) ) {
627 # haxx0r
628 return false;
629 }
630
631 return $value;
632 }
633
653 static function fixTagAttributes( $text, $element ) {
654 if( trim( $text ) == '' ) {
655 return '';
656 }
657
659 Sanitizer::decodeTagAttributes( $text ), $element );
660
661 $attribs = array();
662 foreach( $stripped as $attribute => $value ) {
663 $encAttribute = htmlspecialchars( $attribute );
664 $encValue = Sanitizer::safeEncodeAttribute( $value );
665
666 $attribs[] = "$encAttribute=\"$encValue\"";
667 }
668 return count( $attribs ) ? ' ' . implode( ' ', $attribs ) : '';
669 }
670
676 static function encodeAttribute( $text ) {
677 $encValue = htmlspecialchars( $text );
678
679 // Whitespace is normalized during attribute decoding,
680 // so if we've been passed non-spaces we must encode them
681 // ahead of time or they won't be preserved.
682 $encValue = strtr( $encValue, array(
683 "\n" => '&#10;',
684 "\r" => '&#13;',
685 "\t" => '&#9;',
686 ) );
687
688 return $encValue;
689 }
690
697 static function safeEncodeAttribute( $text ) {
698 $encValue = Sanitizer::encodeAttribute( $text );
699
700 # Templates and links may be expanded in later parsing,
701 # creating invalid or dangerous output. Suppress this.
702 $encValue = strtr( $encValue, array(
703 '<' => '&lt;', // This should never happen,
704 '>' => '&gt;', // we've received invalid input
705 '"' => '&quot;', // which should have been escaped.
706 '{' => '&#123;',
707 '[' => '&#91;',
708 "''" => '&#39;&#39;',
709 'ISBN' => '&#73;SBN',
710 'RFC' => '&#82;FC',
711 'PMID' => '&#80;MID',
712 '|' => '&#124;',
713 '__' => '&#95;_',
714 ) );
715
716 # Stupid hack
717 $encValue = preg_replace_callback(
718 '/(' . wfUrlProtocols() . ')/',
719 array( 'Sanitizer', 'armorLinksCallback' ),
720 $encValue );
721 return $encValue;
722 }
723
738 static function escapeId( $id ) {
739 static $replace = array(
740 '%3A' => ':',
741 '%' => '.'
742 );
743
744 $id = urlencode( Sanitizer::decodeCharReferences( strtr( $id, ' ', '_' ) ) );
745
746 return str_replace( array_keys( $replace ), array_values( $replace ), $id );
747 }
748
760 static function escapeClass( $class ) {
761 // Convert ugly stuff to underscores and kill underscores in ugly places
762 return rtrim(preg_replace(
763 array('/(^[0-9\\-])|[\\x00-\\x20!"#$%&\'()*+,.\\/:;<=>?@[\\]^`{|}~]|\\xC2\\xA0/','/_+/'),
764 '_',
765 $class ), '_');
766 }
767
774 private static function armorLinksCallback( $matches ) {
775 return str_replace( ':', '&#58;', $matches[1] );
776 }
777
786 static function decodeTagAttributes( $text ) {
787 $attribs = array();
788
789 if( trim( $text ) == '' ) {
790 return $attribs;
791 }
792
793 $pairs = array();
794 if( !preg_match_all(
796 $text,
797 $pairs,
798 PREG_SET_ORDER ) ) {
799 return $attribs;
800 }
801
802 foreach( $pairs as $set ) {
803 $attribute = strtolower( $set[1] );
804 $value = Sanitizer::getTagAttributeCallback( $set );
805
806 // Normalize whitespace
807 $value = preg_replace( '/[\t\r\n ]+/', ' ', $value );
808 $value = trim( $value );
809
810 // Decode character references
811 $attribs[$attribute] = Sanitizer::decodeCharReferences( $value );
812 }
813 return $attribs;
814 }
815
824 private static function getTagAttributeCallback( $set ) {
825 if( isset( $set[6] ) ) {
826 # Illegal #XXXXXX color with no quotes.
827 return $set[6];
828 } elseif( isset( $set[5] ) ) {
829 # No quotes.
830 return $set[5];
831 } elseif( isset( $set[4] ) ) {
832 # Single-quoted
833 return $set[4];
834 } elseif( isset( $set[3] ) ) {
835 # Double-quoted
836 return $set[3];
837 } elseif( !isset( $set[2] ) ) {
838 # In XHTML, attributes must have a value.
839 # For 'reduced' form, return explicitly the attribute name here.
840 return $set[1];
841 } else {
842 throw new MWException( "Tag conditions not met. This should never happen and is a bug." );
843 }
844 }
845
858 private static function normalizeAttributeValue( $text ) {
859 return str_replace( '"', '&quot;',
860 self::normalizeWhitespace(
863
864 private static function normalizeWhitespace( $text ) {
865 return preg_replace(
866 '/\r\n|[\x20\x0d\x0a\x09]/',
867 ' ',
868 $text );
869 }
870
885 static function normalizeCharReferences( $text ) {
886 return preg_replace_callback(
888 array( 'Sanitizer', 'normalizeCharReferencesCallback' ),
889 $text );
890 }
895 static function normalizeCharReferencesCallback( $matches ) {
896 $ret = null;
897 if( $matches[1] != '' ) {
898 $ret = Sanitizer::normalizeEntity( $matches[1] );
899 } elseif( $matches[2] != '' ) {
900 $ret = Sanitizer::decCharReference( $matches[2] );
901 } elseif( $matches[3] != '' ) {
902 $ret = Sanitizer::hexCharReference( $matches[3] );
903 } elseif( $matches[4] != '' ) {
904 $ret = Sanitizer::hexCharReference( $matches[4] );
905 }
906 if( is_null( $ret ) ) {
907 return htmlspecialchars( $matches[0] );
908 } else {
909 return $ret;
910 }
911 }
912
923 static function normalizeEntity( $name ) {
925 if ( isset( $wgHtmlEntityAliases[$name] ) ) {
926 return "&{$wgHtmlEntityAliases[$name]};";
927 } elseif( isset( $wgHtmlEntities[$name] ) ) {
928 return "&$name;";
929 } else {
930 return "&amp;$name;";
931 }
933
934 static function decCharReference( $codepoint ) {
935 $point = intval( $codepoint );
936 if( Sanitizer::validateCodepoint( $point ) ) {
937 return sprintf( '&#%d;', $point );
938 } else {
939 return null;
940 }
942
943 static function hexCharReference( $codepoint ) {
944 $point = hexdec( $codepoint );
945 if( Sanitizer::validateCodepoint( $point ) ) {
946 return sprintf( '&#x%x;', $point );
947 } else {
948 return null;
949 }
950 }
951
957 private static function validateCodepoint( $codepoint ) {
958 return ($codepoint == 0x09)
959 || ($codepoint == 0x0a)
960 || ($codepoint == 0x0d)
961 || ($codepoint >= 0x20 && $codepoint <= 0xd7ff)
962 || ($codepoint >= 0xe000 && $codepoint <= 0xfffd)
963 || ($codepoint >= 0x10000 && $codepoint <= 0x10ffff);
964 }
965
975 public static function decodeCharReferences( $text ) {
976 return preg_replace_callback(
978 array( 'Sanitizer', 'decodeCharReferencesCallback' ),
979 $text );
980 }
981
986 static function decodeCharReferencesCallback( $matches ) {
987 if( $matches[1] != '' ) {
988 return Sanitizer::decodeEntity( $matches[1] );
989 } elseif( $matches[2] != '' ) {
990 return Sanitizer::decodeChar( intval( $matches[2] ) );
991 } elseif( $matches[3] != '' ) {
992 return Sanitizer::decodeChar( hexdec( $matches[3] ) );
993 } elseif( $matches[4] != '' ) {
994 return Sanitizer::decodeChar( hexdec( $matches[4] ) );
995 }
996 # Last case should be an ampersand by itself
997 return $matches[0];
998 }
999
1007 static function decodeChar( $codepoint ) {
1008 if( Sanitizer::validateCodepoint( $codepoint ) ) {
1009 return codepointToUtf8( $codepoint );
1010 } else {
1011 return UTF8_REPLACEMENT;
1012 }
1013 }
1014
1023 static function decodeEntity( $name ) {
1025
1026 if ( isset( $wgHtmlEntityAliases[$name] ) ) {
1027 $name = $wgHtmlEntityAliases[$name];
1028 }
1029 if( isset( $wgHtmlEntities[$name] ) ) {
1030 return codepointToUtf8( $wgHtmlEntities[$name] );
1031 } else {
1032 return "&$name;";
1033 }
1034 }
1035
1043 static function attributeWhitelist( $element ) {
1044 static $list;
1045 if( !isset( $list ) ) {
1047 }
1048 return isset( $list[$element] )
1049 ? $list[$element]
1050 : array();
1051 }
1052
1057 static function setupAttributeWhitelist() {
1058 $common = array( 'id', 'class', 'lang', 'dir', 'title', 'style' );
1059 $block = array_merge( $common, array( 'align' ) );
1060 $tablealign = array( 'align', 'char', 'charoff', 'valign' );
1061 $tablecell = array( 'abbr',
1062 'axis',
1063 'headers',
1064 'scope',
1065 'rowspan',
1066 'colspan',
1067 'nowrap', # deprecated
1068 'width', # deprecated
1069 'height', # deprecated
1070 'bgcolor' # deprecated
1071 );
1072
1073 # Numbers refer to sections in HTML 4.01 standard describing the element.
1074 # See: http://www.w3.org/TR/html4/
1075 $whitelist = array (
1076 # 7.5.4
1077 'div' => $block,
1078 'center' => $common, # deprecated
1079 'span' => $block, # ??
1080
1081 # 7.5.5
1082 'h1' => $block,
1083 'h2' => $block,
1084 'h3' => $block,
1085 'h4' => $block,
1086 'h5' => $block,
1087 'h6' => $block,
1088
1089 # 7.5.6
1090 # address
1091
1092 # 8.2.4
1093 # bdo
1094
1095 # 9.2.1
1096 'em' => $common,
1097 'strong' => $common,
1098 'cite' => $common,
1099 # dfn
1100 'code' => $common,
1101 # samp
1102 # kbd
1103 'var' => $common,
1104 # abbr
1105 # acronym
1106
1107 # 9.2.2
1108 'blockquote' => array_merge( $common, array( 'cite' ) ),
1109 # q
1110
1111 # 9.2.3
1112 'sub' => $common,
1113 'sup' => $common,
1114
1115 # 9.3.1
1116 'p' => $block,
1117
1118 # 9.3.2
1119 'br' => array( 'id', 'class', 'title', 'style', 'clear' ),
1120
1121 # 9.3.4
1122 'pre' => array_merge( $common, array( 'width' ) ),
1123
1124 # 9.4
1125 'ins' => array_merge( $common, array( 'cite', 'datetime' ) ),
1126 'del' => array_merge( $common, array( 'cite', 'datetime' ) ),
1127
1128 # 10.2
1129 'ul' => array_merge( $common, array( 'type' ) ),
1130 'ol' => array_merge( $common, array( 'type', 'start' ) ),
1131 'li' => array_merge( $common, array( 'type', 'value' ) ),
1132
1133 # 10.3
1134 'dl' => $common,
1135 'dd' => $common,
1136 'dt' => $common,
1137
1138 # 11.2.1
1139 'table' => array_merge( $common,
1140 array( 'summary', 'width', 'border', 'frame',
1141 'rules', 'cellspacing', 'cellpadding',
1142 'align', 'bgcolor',
1143 ) ),
1144
1145 # 11.2.2
1146 'caption' => array_merge( $common, array( 'align' ) ),
1147
1148 # 11.2.3
1149 'thead' => array_merge( $common, $tablealign ),
1150 'tfoot' => array_merge( $common, $tablealign ),
1151 'tbody' => array_merge( $common, $tablealign ),
1152
1153 # 11.2.4
1154 'colgroup' => array_merge( $common, array( 'span', 'width' ), $tablealign ),
1155 'col' => array_merge( $common, array( 'span', 'width' ), $tablealign ),
1156
1157 # 11.2.5
1158 'tr' => array_merge( $common, array( 'bgcolor' ), $tablealign ),
1159
1160 # 11.2.6
1161 'td' => array_merge( $common, $tablecell, $tablealign ),
1162 'th' => array_merge( $common, $tablecell, $tablealign ),
1163
1164 # 15.2.1
1165 'tt' => $common,
1166 'b' => $common,
1167 'i' => $common,
1168 'big' => $common,
1169 'small' => $common,
1170 'strike' => $common,
1171 's' => $common,
1172 'u' => $common,
1173
1174 # 15.2.2
1175 'font' => array_merge( $common, array( 'size', 'color', 'face' ) ),
1176 # basefont
1177
1178 # 15.3
1179 'hr' => array_merge( $common, array( 'noshade', 'size', 'width' ) ),
1180
1181 # XHTML Ruby annotation text module, simple ruby only.
1182 # http://www.w3c.org/TR/ruby/
1183 'ruby' => $common,
1184 # rbc
1185 # rtc
1186 'rb' => $common,
1187 'rt' => $common, #array_merge( $common, array( 'rbspan' ) ),
1188 'rp' => $common,
1189 );
1190 return $whitelist;
1191 }
1192
1203 static function stripAllTags( $text ) {
1204 # Actual <tags>
1205 $text = StringUtils::delimiterReplace( '<', '>', '', $text );
1206
1207 # Normalize &entities and whitespace
1208 $text = self::decodeCharReferences( $text );
1209 $text = self::normalizeWhitespace( $text );
1210
1211 return $text;
1212 }
1213
1224 static function hackDocType() {
1225 global $wgHtmlEntities;
1226 $out = "<!DOCTYPE html [\n";
1227 foreach( $wgHtmlEntities as $entity => $codepoint ) {
1228 $out .= "<!ENTITY $entity \"&#$codepoint;\">";
1229 }
1230 $out .= "]>\n";
1231 return $out;
1233
1234 static function cleanUrl( $url, $hostname=true ) {
1235 # Normalize any HTML entities in input. They will be
1236 # re-escaped by makeExternalLink().
1237
1238 $url = Sanitizer::decodeCharReferences( $url );
1239
1240 # Escape any control characters introduced by the above step
1241 $url = preg_replace( '/[\][<>"\\x00-\\x20\\x7F]/e', "urlencode('\\0')", $url );
1242
1243 # Validate hostname portion
1244 $matches = array();
1245 if( preg_match( '!^([^:]+:)(//[^/]+)?(.*)$!iD', $url, $matches ) ) {
1246 list( /* $whole */, $protocol, $host, $rest ) = $matches;
1247
1248 // Characters that will be ignored in IDNs.
1249 // http://tools.ietf.org/html/3454#section-3.1
1250 // Strip them before further processing so blacklists and such work.
1251 $strip = "/
1252 \\s| # general whitespace
1253 \xc2\xad| # 00ad SOFT HYPHEN
1254 \xe1\xa0\x86| # 1806 MONGOLIAN TODO SOFT HYPHEN
1255 \xe2\x80\x8b| # 200b ZERO WIDTH SPACE
1256 \xe2\x81\xa0| # 2060 WORD JOINER
1257 \xef\xbb\xbf| # feff ZERO WIDTH NO-BREAK SPACE
1258 \xcd\x8f| # 034f COMBINING GRAPHEME JOINER
1259 \xe1\xa0\x8b| # 180b MONGOLIAN FREE VARIATION SELECTOR ONE
1260 \xe1\xa0\x8c| # 180c MONGOLIAN FREE VARIATION SELECTOR TWO
1261 \xe1\xa0\x8d| # 180d MONGOLIAN FREE VARIATION SELECTOR THREE
1262 \xe2\x80\x8c| # 200c ZERO WIDTH NON-JOINER
1263 \xe2\x80\x8d| # 200d ZERO WIDTH JOINER
1264 [\xef\xb8\x80-\xef\xb8\x8f] # fe00-fe00f VARIATION SELECTOR-1-16
1265 /xuD";
1266
1267 $host = preg_replace( $strip, '', $host );
1268
1269 // @fixme: validate hostnames here
1270
1271 return $protocol . $host . $rest;
1272 } else {
1273 return $url;
1274 }
1275 }
1276
1277}
1278
1279?>
const MW_ATTRIBS_REGEX
Definition: Sanitizer.php:43
global $wgHtmlEntities
List of all named character entities defined in HTML 4.01 http://www.w3.org/TR/html4/sgml/entities....
Definition: Sanitizer.php:61
const MW_CHAR_REFS_REGEX
Regular expression to match various types of character references in Sanitizer::normalizeCharReferenc...
Definition: Sanitizer.php:30
$space
Definition: Sanitizer.php:42
$attrib
Regular expression to match HTML/XML attribute pairs within a tag.
Definition: Sanitizer.php:41
global $wgHtmlEntityAliases
Character entity aliases accepted by MediaWiki.
Definition: Sanitizer.php:319
static armorLinksCallback( $matches)
Regex replace callback for armoring links against further processing.
Definition: Sanitizer.php:772
static stripAllTags( $text)
Take a fragment of (potentially invalid) HTML and return a version with any tags removed,...
Definition: Sanitizer.php:1201
static decCharReference( $codepoint)
Definition: Sanitizer.php:932
static decodeChar( $codepoint)
Return UTF-8 string for a codepoint if that is a valid character reference, otherwise U+FFFD REPLACEM...
Definition: Sanitizer.php:1005
static checkCss( $value)
Pick apart some CSS and check it for forbidden or unsafe structures.
Definition: Sanitizer.php:611
static decodeEntity( $name)
If the named entity is defined in the HTML 4.0/XHTML 1.0 DTD, return the UTF-8 encoding of that chara...
Definition: Sanitizer.php:1021
static normalizeEntity( $name)
If the named entity is defined in the HTML 4.0/XHTML 1.0 DTD, return the named entity reference as is...
Definition: Sanitizer.php:921
static fixTagAttributes( $text, $element)
Take a tag soup fragment listing an HTML element's attributes and normalize it to well-formed XML,...
Definition: Sanitizer.php:651
static removeHTMLcomments( $text)
Remove '', and everything between.
Definition: Sanitizer.php:526
static encodeAttribute( $text)
Encode an attribute value for HTML output.
Definition: Sanitizer.php:674
static hexCharReference( $codepoint)
Definition: Sanitizer.php:941
static validateTagAttributes( $attribs, $element)
Take an array of attribute names and values and normalize or discard illegal values for the given ele...
Definition: Sanitizer.php:575
static escapeClass( $class)
Given a value, escape it so that it can be used as a CSS class and return it.
Definition: Sanitizer.php:758
static setupAttributeWhitelist()
Definition: Sanitizer.php:1055
static normalizeCharReferencesCallback( $matches)
Definition: Sanitizer.php:893
static hackDocType()
Hack up a private DOCTYPE with HTML's standard entity declarations.
Definition: Sanitizer.php:1222
static normalizeCharReferences( $text)
Ensure that any entities and character references are legal for XML and XHTML specifically.
Definition: Sanitizer.php:883
static normalizeWhitespace( $text)
Definition: Sanitizer.php:862
static cleanUrl( $url, $hostname=true)
Definition: Sanitizer.php:1232
static validateCodepoint( $codepoint)
Returns true if a given Unicode codepoint is a valid character in XML.
Definition: Sanitizer.php:955
static removeHTMLtags( $text, $processCallback=null, $args=array())
Cleans up HTML, removes dangerous tags and attributes, and removes HTML comments.
Definition: Sanitizer.php:340
static decodeCharReferences( $text)
Decode any character references, numeric or named entities, in the text and return a UTF-8 string.
Definition: Sanitizer.php:973
static getTagAttributeCallback( $set)
Pick the appropriate attribute value from a match set from the MW_ATTRIBS_REGEX matches.
Definition: Sanitizer.php:822
static normalizeAttributeValue( $text)
Normalize whitespace and character references in an XML source- encoded text for an attribute value.
Definition: Sanitizer.php:856
static attributeWhitelist( $element)
Fetch the whitelist of acceptable attributes for a given element name.
Definition: Sanitizer.php:1041
static decodeCharReferencesCallback( $matches)
Definition: Sanitizer.php:984
static decodeTagAttributes( $text)
Return an associative array of attribute names and values from a partial tag string.
Definition: Sanitizer.php:784
static escapeId( $id)
Given a value escape it so that it can be used in an id attribute and return it, this does not valida...
Definition: Sanitizer.php:736
static safeEncodeAttribute( $text)
Encode an attribute value for HTML tags, with extra armoring against further wiki processing.
Definition: Sanitizer.php:695
wfUrlProtocols()
Returns a regular expression of url protocols.
$rest
Definition: goto.php:85
codepointToUtf8( $codepoint)
Return UTF-8 sequence for a given Unicode code point.
const UTF8_REPLACEMENT
Definition: UtfNormal.php:83