root/tags/1.3/wp-includes/kses.php

Revision 1069, 23.9 kB (checked in by donncha, 1 year ago)

Merge with WP 2.3 - testing use only!
Move pluggable functions out of wpmu-functions and into pluggable.php, fixes #439

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