Wordpress 2.0.2-scripts
[autoinstalls/wordpress.git] / wp-includes / kses.php
1 <?php
2
3 // Added wp_ prefix to avoid conflicts with existing kses users
4 # kses 0.2.1 - HTML/XHTML filter that only allows some elements and attributes
5 # Copyright (C) 2002, 2003  Ulf Harnhammar
6 # *** CONTACT INFORMATION ***
7 #
8 # E-mail:      metaur at users dot sourceforge dot net
9 # Web page:    http://sourceforge.net/projects/kses
10 # Paper mail:  Ulf Harnhammar
11 #              Ymergatan 17 C
12 #              753 25  Uppsala
13 #              SWEDEN
14 #
15 # [kses strips evil scripts!]
16 if (!defined('CUSTOM_TAGS'))
17         define('CUSTOM_TAGS', false);
18
19 // You can override this in your my-hacks.php file
20 if (!CUSTOM_TAGS) {
21         $allowedposttags = array ('address' => array (), 'a' => array ('href' => array (), 'title' => array (), 'rel' => array (), 'rev' => array (), 'name' => array ()), 'abbr' => array ('title' => array ()), 'acronym' => array ('title' => array ()), 'b' => array (), 'big' => array (), 'blockquote' => array ('cite' => array ()), 'br' => array (), 'button' => array ('disabled' => array (), 'name' => array (), 'type' => array (), 'value' => array ()), 'caption' => array ('align' => array ()), 'code' => array (), 'col' => array ('align' => array (), 'char' => array (), 'charoff' => array (), 'span' => array (), 'valign' => array (), 'width' => array ()), 'del' => array ('datetime' => array ()), 'dd' => array (), 'div' => array ('align' => array ()), 'dl' => array (), 'dt' => array (), 'em' => array (), 'fieldset' => array (), 'font' => array ('color' => array (), 'face' => array (), 'size' => array ()), 'form' => array ('action' => array (), 'accept' => array (), 'accept-charset' => array (), 'enctype' => array (), 'method' => array (), 'name' => array (), 'target' => array ()), 'h1' => array ('align' => array ()), 'h2' => array ('align' => array ()), 'h3' => array ('align' => array ()), 'h4' => array ('align' => array ()), 'h5' => array ('align' => array ()), 'h6' => array ('align' => array ()), 'hr' => array ('align' => array (), 'noshade' => array (), 'size' => array (), 'width' => array ()), 'i' => array (), 'img' => array ('alt' => array (), 'align' => array (), 'border' => array (), 'height' => array (), 'hspace' => array (), 'longdesc' => array (), 'vspace' => array (), 'src' => array (), 'width' => array ()), 'ins' => array ('datetime' => array (), 'cite' => array ()), 'kbd' => array (), 'label' => array ('for' => array ()), 'legend' => array ('align' => array ()), 'li' => array (), 'p' => array ('align' => array ()), 'pre' => array ('width' => array ()), 'q' => array ('cite' => array ()), 's' => array (), 'strike' => array (), 'strong' => array (), 'sub' => array (), 'sup' => array (), 'table' => array ('align' => array (), 'bgcolor' => array (), 'border' => array (), 'cellpadding' => array (), 'cellspacing' => array (), 'rules' => array (), 'summary' => array (), 'width' => array ()), 'tbody' => array ('align' => array (), 'char' => array (), 'charoff' => array (), 'valign' => array ()), 'td' => array ('abbr' => array (), 'align' => array (), 'axis' => array (), 'bgcolor' => array (), 'char' => array (), 'charoff' => array (), 'colspan' => array (), 'headers' => array (), 'height' => array (), 'nowrap' => array (), 'rowspan' => array (), 'scope' => array (), 'valign' => array (), 'width' => array ()), 'textarea' => array ('cols' => array (), 'rows' => array (), 'disabled' => array (), 'name' => array (), 'readonly' => array ()), 'tfoot' => array ('align' => array (), 'char' => array (), 'charoff' => array (), 'valign' => array ()), 'th' => array ('abbr' => array (), 'align' => array (), 'axis' => array (), 'bgcolor' => array (), 'char' => array (), 'charoff' => array (), 'colspan' => array (), 'headers' => array (), 'height' => array (), 'nowrap' => array (), 'rowspan' => array (), 'scope' => array (), 'valign' => array (), 'width' => array ()), 'thead' => array ('align' => array (), 'char' => array (), 'charoff' => array (), 'valign' => array ()), 'title' => array (), 'tr' => array ('align' => array (), 'bgcolor' => array (), 'char' => array (), 'charoff' => array (), 'valign' => array ()), 'tt' => array (), 'u' => array (), 'ul' => array (), 'ol' => array (), 'var' => array () );
22         $allowedtags = array ('a' => array ('href' => array (), 'title' => array ()), 'abbr' => array ('title' => array ()), 'acronym' => array ('title' => array ()), 'b' => array (), 'blockquote' => array ('cite' => array ()),
23                 //      'br' => array(),
24         'code' => array (),
25                 //      'del' => array('datetime' => array()),
26                 //      'dd' => array(),
27                 //      'dl' => array(),
28                 //      'dt' => array(),
29         'em' => array (), 'i' => array (),
30                 //      'ins' => array('datetime' => array(), 'cite' => array()),
31                 //      'li' => array(),
32                 //      'ol' => array(),
33                 //      'p' => array(),
34                 //      'q' => array(),
35         'strike' => array (), 'strong' => array (),
36                 //      'sub' => array(),
37                 //      'sup' => array(),
38                 //      'u' => array(),
39                 //      'ul' => array(),
40         );
41 }
42 function wp_kses($string, $allowed_html, $allowed_protocols = array ('http', 'https', 'ftp', 'news', 'nntp', 'telnet', 'feed', 'gopher', 'mailto'))
43         ###############################################################################
44                 # This function makes sure that only the allowed HTML element names, attribute
45                 # names and attribute values plus only sane HTML entities will occur in
46                 # $string. You have to remove any slashes from PHP's magic quotes before you
47                 # call this function.
48                 ###############################################################################
49         {
50         $string = wp_kses_no_null($string);
51         $string = wp_kses_js_entities($string);
52         $string = wp_kses_normalize_entities($string);
53         $string = wp_kses_hook($string);
54         $allowed_html_fixed = wp_kses_array_lc($allowed_html);
55         return wp_kses_split($string, $allowed_html_fixed, $allowed_protocols);
56 } # function wp_kses
57
58 function wp_kses_hook($string)
59 ###############################################################################
60 # You add any kses hooks here.
61 ###############################################################################
62 {
63         return $string;
64 } # function wp_kses_hook
65
66 function wp_kses_version()
67 ###############################################################################
68 # This function returns kses' version number.
69 ###############################################################################
70 {
71         return '0.2.2';
72 } # function wp_kses_version
73
74 function wp_kses_split($string, $allowed_html, $allowed_protocols)
75 ###############################################################################
76 # This function searches for HTML tags, no matter how malformed. It also
77 # matches stray ">" characters.
78 ###############################################################################
79 {
80         return preg_replace('%((<!--.*?(-->|$))|(<[^>]*(>|$)|>))%e',
81         "wp_kses_split2('\\1', \$allowed_html, ".'$allowed_protocols)', $string);
82 } # function wp_kses_split
83
84 function wp_kses_split2($string, $allowed_html, $allowed_protocols)
85 ###############################################################################
86 # This function does a lot of work. It rejects some very malformed things
87 # like <:::>. It returns an empty string, if the element isn't allowed (look
88 # ma, no strip_tags()!). Otherwise it splits the tag into an element and an
89 # attribute list.
90 ###############################################################################
91 {
92         $string = wp_kses_stripslashes($string);
93
94         if (substr($string, 0, 1) != '<')
95                 return '&gt;';
96         # It matched a ">" character
97
98         if (preg_match('%^<!--(.*?)(-->)?$%', $string, $matches)) {
99                 $string = str_replace(array('<!--', '-->'), '', $matches[1]);
100                 while ( $string != $newstring = wp_kses($string, $allowed_html, $allowed_protocols) )
101                         $string = $newstring;
102                 if ( $string == '' )
103                         return '';
104                 return "<!--{$string}-->";
105         }
106         # Allow HTML comments
107
108         if (!preg_match('%^<\s*(/\s*)?([a-zA-Z0-9]+)([^>]*)>?$%', $string, $matches))
109                 return '';
110         # It's seriously malformed
111
112         $slash = trim($matches[1]);
113         $elem = $matches[2];
114         $attrlist = $matches[3];
115
116         if (!@ is_array($allowed_html[strtolower($elem)]))
117                 return '';
118         # They are using a not allowed HTML element
119
120         if ($slash != '')
121                 return "<$slash$elem>";
122         # No attributes are allowed for closing elements
123
124         return wp_kses_attr("$slash$elem", $attrlist, $allowed_html, $allowed_protocols);
125 } # function wp_kses_split2
126
127 function wp_kses_attr($element, $attr, $allowed_html, $allowed_protocols)
128 ###############################################################################
129 # This function removes all attributes, if none are allowed for this element.
130 # If some are allowed it calls wp_kses_hair() to split them further, and then it
131 # builds up new HTML code from the data that kses_hair() returns. It also
132 # removes "<" and ">" characters, if there are any left. One more thing it
133 # does is to check if the tag has a closing XHTML slash, and if it does,
134 # it puts one in the returned code as well.
135 ###############################################################################
136 {
137         # Is there a closing XHTML slash at the end of the attributes?
138
139         $xhtml_slash = '';
140         if (preg_match('%\s/\s*$%', $attr))
141                 $xhtml_slash = ' /';
142
143         # Are any attributes allowed at all for this element?
144
145         if (@ count($allowed_html[strtolower($element)]) == 0)
146                 return "<$element$xhtml_slash>";
147
148         # Split it
149
150         $attrarr = wp_kses_hair($attr, $allowed_protocols);
151
152         # Go through $attrarr, and save the allowed attributes for this element
153         # in $attr2
154
155         $attr2 = '';
156
157         foreach ($attrarr as $arreach) {
158                 if (!@ isset ($allowed_html[strtolower($element)][strtolower($arreach['name'])]))
159                         continue; # the attribute is not allowed
160
161                 $current = $allowed_html[strtolower($element)][strtolower($arreach['name'])];
162                 if ($current == '')
163                         continue; # the attribute is not allowed
164
165                 if (!is_array($current))
166                         $attr2 .= ' '.$arreach['whole'];
167                 # there are no checks
168
169                 else {
170                         # there are some checks
171                         $ok = true;
172                         foreach ($current as $currkey => $currval)
173                                 if (!wp_kses_check_attr_val($arreach['value'], $arreach['vless'], $currkey, $currval)) {
174                                         $ok = false;
175                                         break;
176                                 }
177
178                         if ($ok)
179                                 $attr2 .= ' '.$arreach['whole']; # it passed them
180                 } # if !is_array($current)
181         } # foreach
182
183         # Remove any "<" or ">" characters
184
185         $attr2 = preg_replace('/[<>]/', '', $attr2);
186
187         return "<$element$attr2$xhtml_slash>";
188 } # function wp_kses_attr
189
190 function wp_kses_hair($attr, $allowed_protocols)
191 ###############################################################################
192 # This function does a lot of work. It parses an attribute list into an array
193 # with attribute data, and tries to do the right thing even if it gets weird
194 # input. It will add quotes around attribute values that don't have any quotes
195 # or apostrophes around them, to make it easier to produce HTML code that will
196 # conform to W3C's HTML specification. It will also remove bad URL protocols
197 # from attribute values.
198 ###############################################################################
199 {
200         $attrarr = array ();
201         $mode = 0;
202         $attrname = '';
203
204         # Loop through the whole attribute list
205
206         while (strlen($attr) != 0) {
207                 $working = 0; # Was the last operation successful?
208
209                 switch ($mode) {
210                         case 0 : # attribute name, href for instance
211
212                                 if (preg_match('/^([-a-zA-Z]+)/', $attr, $match)) {
213                                         $attrname = $match[1];
214                                         $working = $mode = 1;
215                                         $attr = preg_replace('/^[-a-zA-Z]+/', '', $attr);
216                                 }
217
218                                 break;
219
220                         case 1 : # equals sign or valueless ("selected")
221
222                                 if (preg_match('/^\s*=\s*/', $attr)) # equals sign
223                                         {
224                                         $working = 1;
225                                         $mode = 2;
226                                         $attr = preg_replace('/^\s*=\s*/', '', $attr);
227                                         break;
228                                 }
229
230                                 if (preg_match('/^\s+/', $attr)) # valueless
231                                         {
232                                         $working = 1;
233                                         $mode = 0;
234                                         $attrarr[] = array ('name' => $attrname, 'value' => '', 'whole' => $attrname, 'vless' => 'y');
235                                         $attr = preg_replace('/^\s+/', '', $attr);
236                                 }
237
238                                 break;
239
240                         case 2 : # attribute value, a URL after href= for instance
241
242                                 if (preg_match('/^"([^"]*)"(\s+|$)/', $attr, $match))
243                                         # "value"
244                                         {
245                                         $thisval = wp_kses_bad_protocol($match[1], $allowed_protocols);
246
247                                         $attrarr[] = array ('name' => $attrname, 'value' => $thisval, 'whole' => "$attrname=\"$thisval\"", 'vless' => 'n');
248                                         $working = 1;
249                                         $mode = 0;
250                                         $attr = preg_replace('/^"[^"]*"(\s+|$)/', '', $attr);
251                                         break;
252                                 }
253
254                                 if (preg_match("/^'([^']*)'(\s+|$)/", $attr, $match))
255                                         # 'value'
256                                         {
257                                         $thisval = wp_kses_bad_protocol($match[1], $allowed_protocols);
258
259                                         $attrarr[] = array ('name' => $attrname, 'value' => $thisval, 'whole' => "$attrname='$thisval'", 'vless' => 'n');
260                                         $working = 1;
261                                         $mode = 0;
262                                         $attr = preg_replace("/^'[^']*'(\s+|$)/", '', $attr);
263                                         break;
264                                 }
265
266                                 if (preg_match("%^([^\s\"']+)(\s+|$)%", $attr, $match))
267                                         # value
268                                         {
269                                         $thisval = wp_kses_bad_protocol($match[1], $allowed_protocols);
270
271                                         $attrarr[] = array ('name' => $attrname, 'value' => $thisval, 'whole' => "$attrname=\"$thisval\"", 'vless' => 'n');
272                                         # We add quotes to conform to W3C's HTML spec.
273                                         $working = 1;
274                                         $mode = 0;
275                                         $attr = preg_replace("%^[^\s\"']+(\s+|$)%", '', $attr);
276                                 }
277
278                                 break;
279                 } # switch
280
281                 if ($working == 0) # not well formed, remove and try again
282                         {
283                         $attr = wp_kses_html_error($attr);
284                         $mode = 0;
285                 }
286         } # while
287
288         if ($mode == 1)
289                 # special case, for when the attribute list ends with a valueless
290                 # attribute like "selected"
291                 $attrarr[] = array ('name' => $attrname, 'value' => '', 'whole' => $attrname, 'vless' => 'y');
292
293         return $attrarr;
294 } # function wp_kses_hair
295
296 function wp_kses_check_attr_val($value, $vless, $checkname, $checkvalue)
297 ###############################################################################
298 # This function performs different checks for attribute values. The currently
299 # implemented checks are "maxlen", "minlen", "maxval", "minval" and "valueless"
300 # with even more checks to come soon.
301 ###############################################################################
302 {
303         $ok = true;
304
305         switch (strtolower($checkname)) {
306                 case 'maxlen' :
307                         # The maxlen check makes sure that the attribute value has a length not
308                         # greater than the given value. This can be used to avoid Buffer Overflows
309                         # in WWW clients and various Internet servers.
310
311                         if (strlen($value) > $checkvalue)
312                                 $ok = false;
313                         break;
314
315                 case 'minlen' :
316                         # The minlen check makes sure that the attribute value has a length not
317                         # smaller than the given value.
318
319                         if (strlen($value) < $checkvalue)
320                                 $ok = false;
321                         break;
322
323                 case 'maxval' :
324                         # The maxval check does two things: it checks that the attribute value is
325                         # an integer from 0 and up, without an excessive amount of zeroes or
326                         # whitespace (to avoid Buffer Overflows). It also checks that the attribute
327                         # value is not greater than the given value.
328                         # This check can be used to avoid Denial of Service attacks.
329
330                         if (!preg_match('/^\s{0,6}[0-9]{1,6}\s{0,6}$/', $value))
331                                 $ok = false;
332                         if ($value > $checkvalue)
333                                 $ok = false;
334                         break;
335
336                 case 'minval' :
337                         # The minval check checks that the attribute value is a positive integer,
338                         # and that it is not smaller than the given value.
339
340                         if (!preg_match('/^\s{0,6}[0-9]{1,6}\s{0,6}$/', $value))
341                                 $ok = false;
342                         if ($value < $checkvalue)
343                                 $ok = false;
344                         break;
345
346                 case 'valueless' :
347                         # The valueless check checks if the attribute has a value
348                         # (like <a href="blah">) or not (<option selected>). If the given value
349                         # is a "y" or a "Y", the attribute must not have a value.
350                         # If the given value is an "n" or an "N", the attribute must have one.
351
352                         if (strtolower($checkvalue) != $vless)
353                                 $ok = false;
354                         break;
355         } # switch
356
357         return $ok;
358 } # function wp_kses_check_attr_val
359
360 function wp_kses_bad_protocol($string, $allowed_protocols)
361 ###############################################################################
362 # This function removes all non-allowed protocols from the beginning of
363 # $string. It ignores whitespace and the case of the letters, and it does
364 # understand HTML entities. It does its work in a while loop, so it won't be
365 # fooled by a string like "javascript:javascript:alert(57)".
366 ###############################################################################
367 {
368         $string = wp_kses_no_null($string);
369         $string2 = $string.'a';
370
371         while ($string != $string2) {
372                 $string2 = $string;
373                 $string = wp_kses_bad_protocol_once($string, $allowed_protocols);
374         } # while
375
376         return $string;
377 } # function wp_kses_bad_protocol
378
379 function wp_kses_no_null($string)
380 ###############################################################################
381 # This function removes any NULL or chr(173) characters in $string.
382 ###############################################################################
383 {
384         $string = preg_replace('/\0+/', '', $string);
385         $string = preg_replace('/(\\\\0)+/', '', $string);
386
387         return $string;
388 } # function wp_kses_no_null
389
390 function wp_kses_stripslashes($string)
391 ###############################################################################
392 # This function changes the character sequence  \"  to just  "
393 # It leaves all other slashes alone. It's really weird, but the quoting from
394 # preg_replace(//e) seems to require this.
395 ###############################################################################
396 {
397         return preg_replace('%\\\\"%', '"', $string);
398 } # function wp_kses_stripslashes
399
400 function wp_kses_array_lc($inarray)
401 ###############################################################################
402 # This function goes through an array, and changes the keys to all lower case.
403 ###############################################################################
404 {
405         $outarray = array ();
406
407         foreach ($inarray as $inkey => $inval) {
408                 $outkey = strtolower($inkey);
409                 $outarray[$outkey] = array ();
410
411                 foreach ($inval as $inkey2 => $inval2) {
412                         $outkey2 = strtolower($inkey2);
413                         $outarray[$outkey][$outkey2] = $inval2;
414                 } # foreach $inval
415         } # foreach $inarray
416
417         return $outarray;
418 } # function wp_kses_array_lc
419
420 function wp_kses_js_entities($string)
421 ###############################################################################
422 # This function removes the HTML JavaScript entities found in early versions of
423 # Netscape 4.
424 ###############################################################################
425 {
426         return preg_replace('%&\s*\{[^}]*(\}\s*;?|$)%', '', $string);
427 } # function wp_kses_js_entities
428
429 function wp_kses_html_error($string)
430 ###############################################################################
431 # This function deals with parsing errors in wp_kses_hair(). The general plan is
432 # to remove everything to and including some whitespace, but it deals with
433 # quotes and apostrophes as well.
434 ###############################################################################
435 {
436         return preg_replace('/^("[^"]*("|$)|\'[^\']*(\'|$)|\S)*\s*/', '', $string);
437 } # function wp_kses_html_error
438
439 function wp_kses_bad_protocol_once($string, $allowed_protocols)
440 ###############################################################################
441 # This function searches for URL protocols at the beginning of $string, while
442 # handling whitespace and HTML entities.
443 ###############################################################################
444 {
445         return preg_replace('/^((&[^;]*;|[\sA-Za-z0-9])*)'.'(:|&#58;|&#[Xx]3[Aa];)\s*/e', 'wp_kses_bad_protocol_once2("\\1", $allowed_protocols)', $string);
446 } # function wp_kses_bad_protocol_once
447
448 function wp_kses_bad_protocol_once2($string, $allowed_protocols)
449 ###############################################################################
450 # This function processes URL protocols, checks to see if they're in the white-
451 # list or not, and returns different data depending on the answer.
452 ###############################################################################
453 {
454         $string2 = wp_kses_decode_entities($string);
455         $string2 = preg_replace('/\s/', '', $string2);
456         $string2 = wp_kses_no_null($string2);
457         $string2 = strtolower($string2);
458
459         $allowed = false;
460         foreach ($allowed_protocols as $one_protocol)
461                 if (strtolower($one_protocol) == $string2) {
462                         $allowed = true;
463                         break;
464                 }
465
466         if ($allowed)
467                 return "$string2:";
468         else
469                 return '';
470 } # function wp_kses_bad_protocol_once2
471
472 function wp_kses_normalize_entities($string)
473 ###############################################################################
474 # This function normalizes HTML entities. It will convert "AT&T" to the correct
475 # "AT&amp;T", "&#00058;" to "&#58;", "&#XYZZY;" to "&amp;#XYZZY;" and so on.
476 ###############################################################################
477 {
478         # Disarm all entities by converting & to &amp;
479
480         $string = str_replace('&', '&amp;', $string);
481
482         # Change back the allowed entities in our entity whitelist
483
484         $string = preg_replace('/&amp;([A-Za-z][A-Za-z0-9]{0,19});/', '&\\1;', $string);
485         $string = preg_replace('/&amp;#0*([0-9]{1,5});/e', 'wp_kses_normalize_entities2("\\1")', $string);
486         $string = preg_replace('/&amp;#([Xx])0*(([0-9A-Fa-f]{2}){1,2});/', '&#\\1\\2;', $string);
487
488         return $string;
489 } # function wp_kses_normalize_entities
490
491 function wp_kses_normalize_entities2($i)
492 ###############################################################################
493 # This function helps wp_kses_normalize_entities() to only accept 16 bit values
494 # and nothing more for &#number; entities.
495 ###############################################################################
496 {
497         return (($i > 65535) ? "&amp;#$i;" : "&#$i;");
498 } # function wp_kses_normalize_entities2
499
500 function wp_kses_decode_entities($string)
501 ###############################################################################
502 # This function decodes numeric HTML entities (&#65; and &#x41;). It doesn't
503 # do anything with other entities like &auml;, but we don't need them in the
504 # URL protocol whitelisting system anyway.
505 ###############################################################################
506 {
507         $string = preg_replace('/&#([0-9]+);/e', 'chr("\\1")', $string);
508         $string = preg_replace('/&#[Xx]([0-9A-Fa-f]+);/e', 'chr(hexdec("\\1"))', $string);
509
510         return $string;
511 } # function wp_kses_decode_entities
512
513 function wp_filter_kses($data) {
514         global $allowedtags;
515         return wp_kses($data, $allowedtags);
516 }
517
518 function wp_filter_post_kses($data) {
519         global $allowedposttags;
520         return addslashes ( wp_kses(stripslashes( $data ), $allowedposttags) );
521 }
522
523 function kses_init_filters() {
524                 add_filter('pre_comment_author', 'wp_filter_kses');
525                 add_filter('pre_comment_content', 'wp_filter_kses');
526                 add_filter('content_save_pre', 'wp_filter_post_kses');
527                 add_filter('title_save_pre', 'wp_filter_kses');
528 }
529
530 function kses_init() {
531         global $current_user;
532
533         remove_filter('pre_comment_author', 'wp_filter_kses');
534         remove_filter('pre_comment_content', 'wp_filter_kses');
535         remove_filter('content_save_pre', 'wp_filter_post_kses');
536         remove_filter('title_save_pre', 'wp_filter_kses');
537
538         if (! defined('XMLRPC_REQUEST') )
539                 get_currentuserinfo();
540
541         if (current_user_can('unfiltered_html') == false)
542                 kses_init_filters();
543 }
544 add_action('init', 'kses_init');
545 add_action('set_current_user', 'kses_init');
546 ?>