root/tags/1_0-rc1/wp-includes/kses.php

Revision 543, 25.3 kB (checked in by matt, 3 years ago)

Lots and lots of changes.

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