Wordpress 2.6.2
[autoinstalls/wordpress.git] / wp-includes / kses.php
1 <?php
2 /**
3  * HTML/XHTML filter that only allows some elements and attributes
4  *
5  * Added wp_ prefix to avoid conflicts with existing kses users
6  *
7  * @version 0.2.2
8  * @copyright (C) 2002, 2003, 2005
9  * @author Ulf Harnhammar <metaur@users.sourceforge.net>
10  *
11  * @package External
12  * @subpackage KSES
13  *
14  * @internal
15  * *** CONTACT INFORMATION ***
16  * E-mail:      metaur at users dot sourceforge dot net
17  * Web page:    http://sourceforge.net/projects/kses
18  * Paper mail:  Ulf Harnhammar
19  *              Ymergatan 17 C
20  *              753 25  Uppsala
21  *              SWEDEN
22  *
23  * [kses strips evil scripts!]
24  */
25
26 /**
27  * You can override this in your my-hacks.php file
28  * You can also override this in a plugin file. The
29  * my-hacks.php is deprecated in its usage.
30  *
31  * @since 1.2.0
32  */
33 if (!defined('CUSTOM_TAGS'))
34         define('CUSTOM_TAGS', false);
35
36 if (!CUSTOM_TAGS) {
37         /**
38          * Kses global for default allowable HTML tags
39          *
40          * Can be override by using CUSTOM_TAGS constant
41          * @global array $allowedposttags
42          * @since 2.0.0
43          */
44         $allowedposttags = array(
45                 'address' => array(),
46                 'a' => array(
47                         'class' => array (),
48                         'href' => array (),
49                         'id' => array (),
50                         'title' => array (),
51                         'rel' => array (),
52                         'rev' => array (),
53                         'name' => array (),
54                         'target' => array()),
55                 'abbr' => array(
56                         'class' => array (),
57                         'title' => array ()),
58                 'acronym' => array(
59                         'title' => array ()),
60                 'b' => array(),
61                 'big' => array(),
62                 'blockquote' => array(
63                         'id' => array (),
64                         'cite' => array (),
65                         'class' => array(),
66                         'lang' => array(),
67                         'xml:lang' => array()),
68                 'br' => array (
69                         'class' => array ()),
70                 'button' => array(
71                         'disabled' => array (),
72                         'name' => array (),
73                         'type' => array (),
74                         'value' => array ()),
75                 'caption' => array(
76                         'align' => array (),
77                         'class' => array ()),
78                 'cite' => array (
79                         'class' => array(),
80                         'dir' => array(),
81                         'lang' => array(),
82                         'title' => array ()),
83                 'code' => array (
84                         'style' => array()),
85                 'col' => array(
86                         'align' => array (),
87                         'char' => array (),
88                         'charoff' => array (),
89                         'span' => array (),
90                         'dir' => array(),
91                         'style' => array (),
92                         'valign' => array (),
93                         'width' => array ()),
94                 'del' => array(
95                         'datetime' => array ()),
96                 'dd' => array(),
97                 'div' => array(
98                         'align' => array (),
99                         'class' => array (),
100                         'dir' => array (),
101                         'lang' => array(),
102                         'style' => array (),
103                         'xml:lang' => array()),
104                 'dl' => array(),
105                 'dt' => array(),
106                 'em' => array(),
107                 'fieldset' => array(),
108                 'font' => array(
109                         'color' => array (),
110                         'face' => array (),
111                         'size' => array ()),
112                 'form' => array(
113                         'action' => array (),
114                         'accept' => array (),
115                         'accept-charset' => array (),
116                         'enctype' => array (),
117                         'method' => array (),
118                         'name' => array (),
119                         'target' => array ()),
120                 'h1' => array(
121                         'align' => array (),
122                         'class' => array ()),
123                 'h2' => array(
124                         'align' => array (),
125                         'class' => array ()),
126                 'h3' => array(
127                         'align' => array (),
128                         'class' => array ()),
129                 'h4' => array(
130                         'align' => array (),
131                         'class' => array ()),
132                 'h5' => array(
133                         'align' => array (),
134                         'class' => array ()),
135                 'h6' => array(
136                         'align' => array (),
137                         'class' => array ()),
138                 'hr' => array(
139                         'align' => array (),
140                         'class' => array (),
141                         'noshade' => array (),
142                         'size' => array (),
143                         'width' => array ()),
144                 'i' => array(),
145                 'img' => array(
146                         'alt' => array (),
147                         'align' => array (),
148                         'border' => array (),
149                         'class' => array (),
150                         'height' => array (),
151                         'hspace' => array (),
152                         'longdesc' => array (),
153                         'vspace' => array (),
154                         'src' => array (),
155                         'style' => array (),
156                         'width' => array ()),
157                 'ins' => array(
158                         'datetime' => array (),
159                         'cite' => array ()),
160                 'kbd' => array(),
161                 'label' => array(
162                         'for' => array ()),
163                 'legend' => array(
164                         'align' => array ()),
165                 'li' => array (
166                         'align' => array (),
167                         'class' => array ()),
168                 'p' => array(
169                         'class' => array (),
170                         'align' => array (),
171                         'dir' => array(),
172                         'lang' => array(),
173                         'style' => array (),
174                         'xml:lang' => array()),
175                 'pre' => array(
176                         'style' => array(),
177                         'width' => array ()),
178                 'q' => array(
179                         'cite' => array ()),
180                 's' => array(),
181                 'span' => array (
182                         'class' => array (),
183                         'dir' => array (),
184                         'align' => array (),
185                         'lang' => array (),
186                         'style' => array (),
187                         'title' => array (),
188                         'xml:lang' => array()),
189                 'strike' => array(),
190                 'strong' => array(),
191                 'sub' => array(),
192                 'sup' => array(),
193                 'table' => array(
194                         'align' => array (),
195                         'bgcolor' => array (),
196                         'border' => array (),
197                         'cellpadding' => array (),
198                         'cellspacing' => array (),
199                         'class' => array (),
200                         'dir' => array(),
201                         'id' => array(),
202                         'rules' => array (),
203                         'style' => array (),
204                         'summary' => array (),
205                         'width' => array ()),
206                 'tbody' => array(
207                         'align' => array (),
208                         'char' => array (),
209                         'charoff' => array (),
210                         'valign' => array ()),
211                 'td' => array(
212                         'abbr' => array (),
213                         'align' => array (),
214                         'axis' => array (),
215                         'bgcolor' => array (),
216                         'char' => array (),
217                         'charoff' => array (),
218                         'class' => array (),
219                         'colspan' => array (),
220                         'dir' => array(),
221                         'headers' => array (),
222                         'height' => array (),
223                         'nowrap' => array (),
224                         'rowspan' => array (),
225                         'scope' => array (),
226                         'style' => array (),
227                         'valign' => array (),
228                         'width' => array ()),
229                 'textarea' => array(
230                         'cols' => array (),
231                         'rows' => array (),
232                         'disabled' => array (),
233                         'name' => array (),
234                         'readonly' => array ()),
235                 'tfoot' => array(
236                         'align' => array (),
237                         'char' => array (),
238                         'class' => array (),
239                         'charoff' => array (),
240                         'valign' => array ()),
241                 'th' => array(
242                         'abbr' => array (),
243                         'align' => array (),
244                         'axis' => array (),
245                         'bgcolor' => array (),
246                         'char' => array (),
247                         'charoff' => array (),
248                         'class' => array (),
249                         'colspan' => array (),
250                         'headers' => array (),
251                         'height' => array (),
252                         'nowrap' => array (),
253                         'rowspan' => array (),
254                         'scope' => array (),
255                         'valign' => array (),
256                         'width' => array ()),
257                 'thead' => array(
258                         'align' => array (),
259                         'char' => array (),
260                         'charoff' => array (),
261                         'class' => array (),
262                         'valign' => array ()),
263                 'title' => array(),
264                 'tr' => array(
265                         'align' => array (),
266                         'bgcolor' => array (),
267                         'char' => array (),
268                         'charoff' => array (),
269                         'class' => array (),
270                         'style' => array (),
271                         'valign' => array ()),
272                 'tt' => array(),
273                 'u' => array(),
274                 'ul' => array (
275                         'class' => array (),
276                         'style' => array (), 
277                         'type' => array ()),
278                 'ol' => array (
279                         'class' => array (),
280                         'start' => array (),
281                         'style' => array (), 
282                         'type' => array ()),
283                 'var' => array ());
284         /**
285          * Kses allowed HTML elements
286          *
287          * @global array $allowedtags
288          * @since 1.0.0
289          */
290         $allowedtags = array(
291                 'a' => array(
292                         'href' => array (),
293                         'title' => array ()),
294                 'abbr' => array(
295                         'title' => array ()),
296                 'acronym' => array(
297                         'title' => array ()),
298                 'b' => array(),
299                 'blockquote' => array(
300                         'cite' => array ()),
301                 //      'br' => array(),
302                 'cite' => array (),
303                 'code' => array(),
304                 'del' => array(
305                         'datetime' => array ()),
306                 //      'dd' => array(),
307                 //      'dl' => array(),
308                 //      'dt' => array(),
309                 'em' => array (), 'i' => array (),
310                 //      'ins' => array('datetime' => array(), 'cite' => array()),
311                 //      'li' => array(),
312                 //      'ol' => array(),
313                 //      'p' => array(),
314                 'q' => array(
315                         'cite' => array ()),
316                 'strike' => array(),
317                 'strong' => array(),
318                 //      'sub' => array(),
319                 //      'sup' => array(),
320                 //      'u' => array(),
321                 //      'ul' => array(),
322         );
323 }
324
325 /**
326  * wp_kses() - Filters content and keeps only allowable HTML elements.
327  *
328  * This function makes sure that only the allowed HTML element names,
329  * attribute names and attribute values plus only sane HTML entities
330  * will occur in $string. You have to remove any slashes from PHP's
331  * magic quotes before you call this function.
332  *
333  * The default allowed protocols are 'http', 'https', 'ftp', 'mailto',
334  * 'news', 'irc', 'gopher', 'nntp', 'feed', and finally 'telnet. This
335  * covers all common link protocols, except for 'javascript' which
336  * should not be allowed for untrusted users.
337  *
338  * @since 1.0.0
339  *
340  * @param string $string Content to filter through kses
341  * @param array $allowed_html List of allowed HTML elements
342  * @param array $allowed_protocols Optional. Allowed protocol in links.
343  * @return string Filtered content with only allowed HTML elements
344  */
345 function wp_kses($string, $allowed_html, $allowed_protocols = array ('http', 'https', 'ftp', 'ftps', 'mailto', 'news', 'irc', 'gopher', 'nntp', 'feed', 'telnet')) {
346         $string = wp_kses_no_null($string);
347         $string = wp_kses_js_entities($string);
348         $string = wp_kses_normalize_entities($string);
349         $allowed_html_fixed = wp_kses_array_lc($allowed_html);
350         $string = wp_kses_hook($string, $allowed_html_fixed, $allowed_protocols); // WP changed the order of these funcs and added args to wp_kses_hook
351         return wp_kses_split($string, $allowed_html_fixed, $allowed_protocols);
352 }
353
354 /**
355  * wp_kses_hook() - You add any kses hooks here.
356  *
357  * There is currently only one kses WordPress hook and it is
358  * called here. All parameters are passed to the hooks and
359  * expected to recieve a string.
360  *
361  * @since 1.0.0
362  *
363  * @param string $string Content to filter through kses
364  * @param array $allowed_html List of allowed HTML elements
365  * @param array $allowed_protocols Allowed protocol in links
366  * @return string Filtered content through 'pre_kses' hook
367  */
368 function wp_kses_hook($string, $allowed_html, $allowed_protocols) {
369         $string = apply_filters('pre_kses', $string, $allowed_html, $allowed_protocols);
370         return $string;
371 }
372
373 /**
374  * wp_kses_version() - This function returns kses' version number.
375  *
376  * @since 1.0.0
377  *
378  * @return string Version Number
379  */
380 function wp_kses_version() {
381         return '0.2.2';
382 }
383
384 /**
385  * wp_kses_split() - Searches for HTML tags, no matter how malformed
386  *
387  * It also matches stray ">" characters.
388  *
389  * @since 1.0.0
390  *
391  * @param string $string Content to filter
392  * @param array $allowed_html Allowed HTML elements
393  * @param array $allowed_protocols Allowed protocols to keep
394  * @return string Content with fixed HTML tags
395  */
396 function wp_kses_split($string, $allowed_html, $allowed_protocols) {
397         return preg_replace('%((<!--.*?(-->|$))|(<[^>]*(>|$)|>))%e',
398         "wp_kses_split2('\\1', \$allowed_html, ".'$allowed_protocols)', $string);
399 }
400
401 /**
402  * wp_kses_split2() - Callback for wp_kses_split for fixing malformed HTML tags
403  *
404  * This function does a lot of work. It rejects some very malformed things
405  * like <:::>. It returns an empty string, if the element isn't allowed (look
406  * ma, no strip_tags()!). Otherwise it splits the tag into an element and an
407  * attribute list.
408  *
409  * After the tag is split into an element and an attribute list, it is run
410  * through another filter which will remove illegal attributes and once
411  * that is completed, will be returned.
412  *
413  * @since 1.0.0
414  * @uses wp_kses_attr()
415  *
416  * @param string $string Content to filter
417  * @param array $allowed_html Allowed HTML elements
418  * @param array $allowed_protocols Allowed protocols to keep
419  * @return string Fixed HTML element
420  */
421 function wp_kses_split2($string, $allowed_html, $allowed_protocols) {
422         $string = wp_kses_stripslashes($string);
423
424         if (substr($string, 0, 1) != '<')
425                 return '&gt;';
426         # It matched a ">" character
427
428         if (preg_match('%^<!--(.*?)(-->)?$%', $string, $matches)) {
429                 $string = str_replace(array('<!--', '-->'), '', $matches[1]);
430                 while ( $string != $newstring = wp_kses($string, $allowed_html, $allowed_protocols) )
431                         $string = $newstring;
432                 if ( $string == '' )
433                         return '';
434                 // prevent multiple dashes in comments
435                 $string = preg_replace('/--+/', '-', $string);
436                 // prevent three dashes closing a comment
437                 $string = preg_replace('/-$/', '', $string);
438                 return "<!--{$string}-->";
439         }
440         # Allow HTML comments
441
442         if (!preg_match('%^<\s*(/\s*)?([a-zA-Z0-9]+)([^>]*)>?$%', $string, $matches))
443                 return '';
444         # It's seriously malformed
445
446         $slash = trim($matches[1]);
447         $elem = $matches[2];
448         $attrlist = $matches[3];
449
450         if (!@isset($allowed_html[strtolower($elem)]))
451                 return '';
452         # They are using a not allowed HTML element
453
454         if ($slash != '')
455                 return "<$slash$elem>";
456         # No attributes are allowed for closing elements
457
458         return wp_kses_attr("$slash$elem", $attrlist, $allowed_html, $allowed_protocols);
459 }
460
461 /**
462  * wp_kses_attr() - Removes all attributes, if none are allowed for this element
463  *
464  * If some are allowed it calls wp_kses_hair() to split them further, and then
465  * it builds up new HTML code from the data that kses_hair() returns. It also
466  * removes "<" and ">" characters, if there are any left. One more thing it
467  * does is to check if the tag has a closing XHTML slash, and if it does, it
468  * puts one in the returned code as well.
469  *
470  * @since 1.0.0
471  *
472  * @param string $element HTML element/tag
473  * @param string $attr HTML attributes from HTML element to closing HTML element tag
474  * @param array $allowed_html Allowed HTML elements
475  * @param array $allowed_protocols Allowed protocols to keep
476  * @return string Sanitized HTML element
477  */
478 function wp_kses_attr($element, $attr, $allowed_html, $allowed_protocols) {
479         # Is there a closing XHTML slash at the end of the attributes?
480
481         $xhtml_slash = '';
482         if (preg_match('%\s/\s*$%', $attr))
483                 $xhtml_slash = ' /';
484
485         # Are any attributes allowed at all for this element?
486
487         if (@ count($allowed_html[strtolower($element)]) == 0)
488                 return "<$element$xhtml_slash>";
489
490         # Split it
491
492         $attrarr = wp_kses_hair($attr, $allowed_protocols);
493
494         # Go through $attrarr, and save the allowed attributes for this element
495         # in $attr2
496
497         $attr2 = '';
498
499         foreach ($attrarr as $arreach) {
500                 if (!@ isset ($allowed_html[strtolower($element)][strtolower($arreach['name'])]))
501                         continue; # the attribute is not allowed
502
503                 $current = $allowed_html[strtolower($element)][strtolower($arreach['name'])];
504                 if ($current == '')
505                         continue; # the attribute is not allowed
506
507                 if (!is_array($current))
508                         $attr2 .= ' '.$arreach['whole'];
509                 # there are no checks
510
511                 else {
512                         # there are some checks
513                         $ok = true;
514                         foreach ($current as $currkey => $currval)
515                                 if (!wp_kses_check_attr_val($arreach['value'], $arreach['vless'], $currkey, $currval)) {
516                                         $ok = false;
517                                         break;
518                                 }
519
520                         if ($ok)
521                                 $attr2 .= ' '.$arreach['whole']; # it passed them
522                 } # if !is_array($current)
523         } # foreach
524
525         # Remove any "<" or ">" characters
526
527         $attr2 = preg_replace('/[<>]/', '', $attr2);
528
529         return "<$element$attr2$xhtml_slash>";
530 }
531
532 /**
533  * wp_kses_hair() - Builds an attribute list from string containing attributes.
534  *
535  * This function does a lot of work. It parses an attribute list into an array
536  * with attribute data, and tries to do the right thing even if it gets weird
537  * input. It will add quotes around attribute values that don't have any quotes
538  * or apostrophes around them, to make it easier to produce HTML code that will
539  * conform to W3C's HTML specification. It will also remove bad URL protocols
540  * from attribute values.  It also reduces duplicate attributes by using the
541  * attribute defined first (foo='bar' foo='baz' will result in foo='bar').
542  *
543  * @since 1.0.0
544  *
545  * @param string $attr Attribute list from HTML element to closing HTML element tag
546  * @param array $allowed_protocols Allowed protocols to keep
547  * @return array List of attributes after parsing
548  */
549 function wp_kses_hair($attr, $allowed_protocols) {
550         $attrarr = array ();
551         $mode = 0;
552         $attrname = '';
553
554         # Loop through the whole attribute list
555
556         while (strlen($attr) != 0) {
557                 $working = 0; # Was the last operation successful?
558
559                 switch ($mode) {
560                         case 0 : # attribute name, href for instance
561
562                                 if (preg_match('/^([-a-zA-Z]+)/', $attr, $match)) {
563                                         $attrname = $match[1];
564                                         $working = $mode = 1;
565                                         $attr = preg_replace('/^[-a-zA-Z]+/', '', $attr);
566                                 }
567
568                                 break;
569
570                         case 1 : # equals sign or valueless ("selected")
571
572                                 if (preg_match('/^\s*=\s*/', $attr)) # equals sign
573                                         {
574                                         $working = 1;
575                                         $mode = 2;
576                                         $attr = preg_replace('/^\s*=\s*/', '', $attr);
577                                         break;
578                                 }
579
580                                 if (preg_match('/^\s+/', $attr)) # valueless
581                                         {
582                                         $working = 1;
583                                         $mode = 0;
584                                         if(FALSE === array_key_exists($attrname, $attrarr)) {
585                                                 $attrarr[$attrname] = array ('name' => $attrname, 'value' => '', 'whole' => $attrname, 'vless' => 'y');
586                                         }
587                                         $attr = preg_replace('/^\s+/', '', $attr);
588                                 }
589
590                                 break;
591
592                         case 2 : # attribute value, a URL after href= for instance
593
594                                 if (preg_match('/^"([^"]*)"(\s+|$)/', $attr, $match))
595                                         # "value"
596                                         {
597                                         $thisval = wp_kses_bad_protocol($match[1], $allowed_protocols);
598
599                                         if(FALSE === array_key_exists($attrname, $attrarr)) {
600                                                 $attrarr[$attrname] = array ('name' => $attrname, 'value' => $thisval, 'whole' => "$attrname=\"$thisval\"", 'vless' => 'n');
601                                         }
602                                         $working = 1;
603                                         $mode = 0;
604                                         $attr = preg_replace('/^"[^"]*"(\s+|$)/', '', $attr);
605                                         break;
606                                 }
607
608                                 if (preg_match("/^'([^']*)'(\s+|$)/", $attr, $match))
609                                         # 'value'
610                                         {
611                                         $thisval = wp_kses_bad_protocol($match[1], $allowed_protocols);
612
613                                         if(FALSE === array_key_exists($attrname, $attrarr)) {
614                                                 $attrarr[$attrname] = array ('name' => $attrname, 'value' => $thisval, 'whole' => "$attrname='$thisval'", 'vless' => 'n');
615                                         }
616                                         $working = 1;
617                                         $mode = 0;
618                                         $attr = preg_replace("/^'[^']*'(\s+|$)/", '', $attr);
619                                         break;
620                                 }
621
622                                 if (preg_match("%^([^\s\"']+)(\s+|$)%", $attr, $match))
623                                         # value
624                                         {
625                                         $thisval = wp_kses_bad_protocol($match[1], $allowed_protocols);
626
627                                         if(FALSE === array_key_exists($attrname, $attrarr)) {
628                                                 $attrarr[$attrname] = array ('name' => $attrname, 'value' => $thisval, 'whole' => "$attrname=\"$thisval\"", 'vless' => 'n');
629                                         }
630                                         # We add quotes to conform to W3C's HTML spec.
631                                         $working = 1;
632                                         $mode = 0;
633                                         $attr = preg_replace("%^[^\s\"']+(\s+|$)%", '', $attr);
634                                 }
635
636                                 break;
637                 } # switch
638
639                 if ($working == 0) # not well formed, remove and try again
640                 {
641                         $attr = wp_kses_html_error($attr);
642                         $mode = 0;
643                 }
644         } # while
645
646         if ($mode == 1 && FALSE === array_key_exists($attrname, $attrarr))
647                 # special case, for when the attribute list ends with a valueless
648                 # attribute like "selected"
649                 $attrarr[$attrname] = array ('name' => $attrname, 'value' => '', 'whole' => $attrname, 'vless' => 'y');
650
651         return $attrarr;
652 }
653
654 /**
655  * wp_kses_check_attr_val() - Performs different checks for attribute values.
656  *
657  * The currently implemented checks are "maxlen", "minlen", "maxval", "minval"
658  * and "valueless" with even more checks to come soon.
659  *
660  * @since 1.0.0
661  *
662  * @param string $value Attribute value
663  * @param string $vless Whether the value is valueless or not. Use 'y' or 'n'
664  * @param string $checkname What $checkvalue is checking for.
665  * @param mixed $checkvalue What constraint the value should pass
666  * @return bool Whether check passes (true) or not (false)
667  */
668 function wp_kses_check_attr_val($value, $vless, $checkname, $checkvalue) {
669         $ok = true;
670
671         switch (strtolower($checkname)) {
672                 case 'maxlen' :
673                         # The maxlen check makes sure that the attribute value has a length not
674                         # greater than the given value. This can be used to avoid Buffer Overflows
675                         # in WWW clients and various Internet servers.
676
677                         if (strlen($value) > $checkvalue)
678                                 $ok = false;
679                         break;
680
681                 case 'minlen' :
682                         # The minlen check makes sure that the attribute value has a length not
683                         # smaller than the given value.
684
685                         if (strlen($value) < $checkvalue)
686                                 $ok = false;
687                         break;
688
689                 case 'maxval' :
690                         # The maxval check does two things: it checks that the attribute value is
691                         # an integer from 0 and up, without an excessive amount of zeroes or
692                         # whitespace (to avoid Buffer Overflows). It also checks that the attribute
693                         # value is not greater than the given value.
694                         # This check can be used to avoid Denial of Service attacks.
695
696                         if (!preg_match('/^\s{0,6}[0-9]{1,6}\s{0,6}$/', $value))
697                                 $ok = false;
698                         if ($value > $checkvalue)
699                                 $ok = false;
700                         break;
701
702                 case 'minval' :
703                         # The minval check checks that the attribute value is a positive integer,
704                         # and that it is not smaller than the given value.
705
706                         if (!preg_match('/^\s{0,6}[0-9]{1,6}\s{0,6}$/', $value))
707                                 $ok = false;
708                         if ($value < $checkvalue)
709                                 $ok = false;
710                         break;
711
712                 case 'valueless' :
713                         # The valueless check checks if the attribute has a value
714                         # (like <a href="blah">) or not (<option selected>). If the given value
715                         # is a "y" or a "Y", the attribute must not have a value.
716                         # If the given value is an "n" or an "N", the attribute must have one.
717
718                         if (strtolower($checkvalue) != $vless)
719                                 $ok = false;
720                         break;
721         } # switch
722
723         return $ok;
724 }
725
726 /**
727  * wp_kses_bad_protocol() - Sanitize string from bad protocols
728  *
729  * This function removes all non-allowed protocols from the beginning
730  * of $string. It ignores whitespace and the case of the letters, and
731  * it does understand HTML entities. It does its work in a while loop,
732  * so it won't be fooled by a string like "javascript:javascript:alert(57)".
733  *
734  * @since 1.0.0
735  *
736  * @param string $string Content to filter bad protocols from
737  * @param array $allowed_protocols Allowed protocols to keep
738  * @return string Filtered content
739  */
740 function wp_kses_bad_protocol($string, $allowed_protocols) {
741         $string = wp_kses_no_null($string);
742         $string = preg_replace('/\xad+/', '', $string); # deals with Opera "feature"
743         $string2 = $string.'a';
744
745         while ($string != $string2) {
746                 $string2 = $string;
747                 $string = wp_kses_bad_protocol_once($string, $allowed_protocols);
748         } # while
749
750         return $string;
751 }
752
753 /**
754  * wp_kses_no_null() - Removes any NULL characters in $string.
755  *
756  * @since 1.0.0
757  *
758  * @param string $string
759  * @return string
760  */
761 function wp_kses_no_null($string) {
762         $string = preg_replace('/\0+/', '', $string);
763         $string = preg_replace('/(\\\\0)+/', '', $string);
764
765         return $string;
766 }
767
768 /**
769  * wp_kses_stripslashes() - Strips slashes from in front of quotes
770  *
771  * This function changes the character sequence  \"  to just  "
772  * It leaves all other slashes alone. It's really weird, but the
773  * quoting from preg_replace(//e) seems to require this.
774  *
775  * @since 1.0.0
776  *
777  * @param string $string String to strip slashes
778  * @return string Fixed strings with quoted slashes
779  */
780 function wp_kses_stripslashes($string) {
781         return preg_replace('%\\\\"%', '"', $string);
782 }
783
784 /**
785  * wp_kses_array_lc() - Goes through an array and changes the keys to all lower case.
786  *
787  * @since 1.0.0
788  *
789  * @param array $inarray Unfiltered array
790  * @return array Fixed array with all lowercase keys
791  */
792 function wp_kses_array_lc($inarray) {
793         $outarray = array ();
794
795         foreach ($inarray as $inkey => $inval) {
796                 $outkey = strtolower($inkey);
797                 $outarray[$outkey] = array ();
798
799                 foreach ($inval as $inkey2 => $inval2) {
800                         $outkey2 = strtolower($inkey2);
801                         $outarray[$outkey][$outkey2] = $inval2;
802                 } # foreach $inval
803         } # foreach $inarray
804
805         return $outarray;
806 }
807
808 /**
809  * wp_kses_js_entities() - Removes the HTML JavaScript entities found in early versions of Netscape 4.
810  *
811  * @since 1.0.0
812  *
813  * @param string $string
814  * @return string
815  */
816 function wp_kses_js_entities($string) {
817         return preg_replace('%&\s*\{[^}]*(\}\s*;?|$)%', '', $string);
818 }
819
820 /**
821  * wp_kses_html_error() - Handles parsing errors in wp_kses_hair()
822  *
823  * The general plan is to remove everything to and including some
824  * whitespace, but it deals with quotes and apostrophes as well.
825  *
826  * @since 1.0.0
827  *
828  * @param string $string
829  * @return string
830  */
831 function wp_kses_html_error($string) {
832         return preg_replace('/^("[^"]*("|$)|\'[^\']*(\'|$)|\S)*\s*/', '', $string);
833 }
834
835 /**
836  * wp_kses_bad_protocol_once() - Sanitizes content from bad protocols and other characters
837  *
838  * This function searches for URL protocols at the beginning of $string,
839  * while handling whitespace and HTML entities.
840  *
841  * @since 1.0.0
842  *
843  * @param string $string Content to check for bad protocols
844  * @param string $allowed_protocols Allowed protocols
845  * @return string Sanitized content
846  */
847 function wp_kses_bad_protocol_once($string, $allowed_protocols) {
848         global $_kses_allowed_protocols;
849         $_kses_allowed_protocols = $allowed_protocols;
850
851         $string2 = preg_split('/:|&#58;|&#x3a;/i', $string, 2);
852         if ( isset($string2[1]) && !preg_match('%/\?%', $string2[0]) )
853                 $string = wp_kses_bad_protocol_once2($string2[0], $allowed_protocols) . trim($string2[1]);
854         else
855                 $string = preg_replace_callback('/^((&[^;]*;|[\sA-Za-z0-9])*)'.'(:|&#58;|&#[Xx]3[Aa];)\s*/', create_function('$matches', 'global $_kses_allowed_protocols; return wp_kses_bad_protocol_once2($matches[1], $_kses_allowed_protocols);'), $string);
856
857         return $string;
858 }
859
860 /**
861  * wp_kses_bad_protocol_once2() - Callback for wp_kses_bad_protocol_once() regular expression.
862  *
863  * This function processes URL protocols, checks to see if they're in the
864  * white-list or not, and returns different data depending on the answer.
865  *
866  * @since 1.0.0
867  *
868  * @param string $string Content to check for bad protocols
869  * @param array $allowed_protocols Allowed protocols
870  * @return string Sanitized content
871  */
872 function wp_kses_bad_protocol_once2($string, $allowed_protocols) {
873         $string2 = wp_kses_decode_entities($string);
874         $string2 = preg_replace('/\s/', '', $string2);
875         $string2 = wp_kses_no_null($string2);
876         $string2 = preg_replace('/\xad+/', '', $string2);
877         # deals with Opera "feature"
878         $string2 = strtolower($string2);
879
880         $allowed = false;
881         foreach ($allowed_protocols as $one_protocol)
882                 if (strtolower($one_protocol) == $string2) {
883                         $allowed = true;
884                         break;
885                 }
886
887         if ($allowed)
888                 return "$string2:";
889         else
890                 return '';
891 }
892
893 /**
894  * wp_kses_normalize_entities() - Converts and fixes HTML entities
895  *
896  * This function normalizes HTML entities. It will convert "AT&T" to the
897  * correct "AT&amp;T", "&#00058;" to "&#58;", "&#XYZZY;" to "&amp;#XYZZY;"
898  * and so on.
899  *
900  * @since 1.0.0
901  *
902  * @param string $string Content to normalize entities
903  * @return string Content with normalized entities
904  */
905 function wp_kses_normalize_entities($string) {
906         # Disarm all entities by converting & to &amp;
907
908         $string = str_replace('&', '&amp;', $string);
909
910         # Change back the allowed entities in our entity whitelist
911
912         $string = preg_replace('/&amp;([A-Za-z][A-Za-z0-9]{0,19});/', '&\\1;', $string);
913         $string = preg_replace_callback('/&amp;#0*([0-9]{1,5});/', create_function('$matches', 'return wp_kses_normalize_entities2($matches[1]);'), $string);
914         $string = preg_replace('/&amp;#([Xx])0*(([0-9A-Fa-f]{2}){1,2});/', '&#\\1\\2;', $string);
915
916         return $string;
917 }
918
919 /**
920  * wp_kses_normalize_entities2() - Callback for wp_kses_normalize_entities() regular expression
921  *
922  * This function helps wp_kses_normalize_entities() to only accept 16 bit
923  * values and nothing more for &#number; entities.
924  *
925  * @since 1.0.0
926  *
927  * @param int $i Number encoded entity
928  * @return string Correctly encoded entity
929  */
930 function wp_kses_normalize_entities2($i) {
931         return (($i > 65535) ? "&amp;#$i;" : "&#$i;");
932 }
933
934 /**
935  * wp_kses_decode_entities() - Convert all entities to their character counterparts.
936  *
937  * This function decodes numeric HTML entities (&#65; and &#x41;). It
938  * doesn't do anything with other entities like &auml;, but we don't need
939  * them in the URL protocol whitelisting system anyway.
940  *
941  * @since 1.0.0
942  *
943  * @param string $string Content to change entities
944  * @return string Content after decoded entities
945  */
946 function wp_kses_decode_entities($string) {
947         $string = preg_replace('/&#([0-9]+);/e', 'chr("\\1")', $string);
948         $string = preg_replace('/&#[Xx]([0-9A-Fa-f]+);/e', 'chr(hexdec("\\1"))', $string);
949
950         return $string;
951 }
952
953 /**
954  * wp_filter_kses() - Sanitize content with allowed HTML Kses rules
955  *
956  * @since 1.0.0
957  * @uses $allowedtags
958  *
959  * @param string $data Content to filter
960  * @return string Filtered content
961  */
962 function wp_filter_kses($data) {
963         global $allowedtags;
964         return addslashes( wp_kses(stripslashes( $data ), $allowedtags) );
965 }
966
967 /**
968  * wp_filter_post_kses() - Sanitize content for allowed HTML tags for post content
969  *
970  * Post content refers to the page contents of the 'post' type and not
971  * $_POST data from forms.
972  *
973  * @since 2.0.0
974  * @uses $allowedposttags
975  *
976  * @param string $data Post content to filter
977  * @return string Filtered post content with allowed HTML tags and attributes intact.
978  */
979 function wp_filter_post_kses($data) {
980         global $allowedposttags;
981         return addslashes ( wp_kses(stripslashes( $data ), $allowedposttags) );
982 }
983
984 /**
985  * wp_filter_nohtml_kses() - Strips all of the HTML in the content
986  *
987  * @since 2.1.0
988  *
989  * @param string $data Content to strip all HTML from
990  * @return string Filtered content without any HTML
991  */
992 function wp_filter_nohtml_kses($data) {
993         return addslashes ( wp_kses(stripslashes( $data ), array()) );
994 }
995
996 /**
997  * kses_init_filters() - Adds all Kses input form content filters
998  *
999  * All hooks have default priority. The wp_filter_kses() fucntion
1000  * is added to the 'pre_comment_content' and 'title_save_pre'
1001  * hooks. The wp_filter_post_kses() function is added to the
1002  * 'content_save_pre', 'excerpt_save_pre', and 'content_filtered_save_pre'
1003  * hooks.
1004  *
1005  * @since 2.0.0
1006  * @uses add_filter() See description for what functions are added to what hooks.
1007  */
1008 function kses_init_filters() {
1009         // Normal filtering.
1010         add_filter('pre_comment_content', 'wp_filter_kses');
1011         add_filter('title_save_pre', 'wp_filter_kses');
1012
1013         // Post filtering
1014         add_filter('content_save_pre', 'wp_filter_post_kses');
1015         add_filter('excerpt_save_pre', 'wp_filter_post_kses');
1016         add_filter('content_filtered_save_pre', 'wp_filter_post_kses');
1017 }
1018
1019 /**
1020  * kses_remove_filters() - Removes all Kses input form content filters
1021  *
1022  * A quick procedural method to removing all of the filters
1023  * that kses uses for content in WordPress Loop.
1024  *
1025  * Does not remove the kses_init() function from 'init' hook
1026  * (priority is default). Also does not remove kses_init()
1027  * function from 'set_current_user' hook (priority is also
1028  * default).
1029  *
1030  * @since 2.0.6
1031  */
1032 function kses_remove_filters() {
1033         // Normal filtering.
1034         remove_filter('pre_comment_content', 'wp_filter_kses');
1035         remove_filter('title_save_pre', 'wp_filter_kses');
1036
1037         // Post filtering
1038         remove_filter('content_save_pre', 'wp_filter_post_kses');
1039         remove_filter('excerpt_save_pre', 'wp_filter_post_kses');
1040         remove_filter('content_filtered_save_pre', 'wp_filter_post_kses');
1041 }
1042
1043 /**
1044  * kses_init() - Sets up most of the Kses filters for input form content
1045  *
1046  * If you remove the kses_init() function from 'init' hook and
1047  * 'set_current_user' (priority is default), then none of the
1048  * Kses filter hooks will be added.
1049  *
1050  * First removes all of the Kses filters in case the current user
1051  * does not need to have Kses filter the content. If the user does
1052  * not have unfiltered html capability, then Kses filters are added.
1053  *
1054  * @uses kses_remove_filters() Removes the Kses filters
1055  * @uses kses_init_filters() Adds the Kses filters back if the user
1056  *              does not have unfiltered HTML capability.
1057  * @since 2.0.0
1058  */
1059 function kses_init() {
1060         kses_remove_filters();
1061
1062         if (current_user_can('unfiltered_html') == false)
1063                 kses_init_filters();
1064 }
1065
1066 add_action('init', 'kses_init');
1067 add_action('set_current_user', 'kses_init');
1068 ?>