WordPress 3.4
[autoinstalls/wordpress.git] / wp-includes / kses.php
1 <?php
2 /**
3  * kses 0.2.2 - HTML/XHTML filter that only allows some elements and attributes
4  * Copyright (C) 2002, 2003, 2005  Ulf Harnhammar
5  *
6  * This program is free software and open source software; you can redistribute
7  * it and/or modify it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation; either version 2 of the License,
9  * or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
14  * more details.
15  *
16  * You should have received a copy of the GNU General Public License along
17  * with this program; if not, write to the Free Software Foundation, Inc.,
18  * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  * http://www.gnu.org/licenses/gpl.html
20  *
21  * [kses strips evil scripts!]
22  *
23  * Added wp_ prefix to avoid conflicts with existing kses users
24  *
25  * @version 0.2.2
26  * @copyright (C) 2002, 2003, 2005
27  * @author Ulf Harnhammar <http://advogato.org/person/metaur/>
28  *
29  * @package External
30  * @subpackage KSES
31  *
32  */
33
34 /**
35  * You can override this in a plugin.
36  *
37  * @since 1.2.0
38  */
39 if ( ! defined( 'CUSTOM_TAGS' ) )
40         define( 'CUSTOM_TAGS', false );
41
42 if ( ! CUSTOM_TAGS ) {
43         /**
44          * Kses global for default allowable HTML tags.
45          *
46          * Can be override by using CUSTOM_TAGS constant.
47          *
48          * @global array $allowedposttags
49          * @since 2.0.0
50          */
51         $allowedposttags = array(
52                 'address' => array(),
53                 'a' => array(
54                         'class' => true,
55                         'href' => true,
56                         'id' => true,
57                         'title' => true,
58                         'rel' => true,
59                         'rev' => true,
60                         'name' => true,
61                         'target' => true,
62                 ),
63                 'abbr' => array(
64                         'class' => true,
65                         'title' => true,
66                 ),
67                 'acronym' => array(
68                         'title' => true,
69                 ),
70                 'article' => array(
71                         'align' => true,
72                         'class' => true,
73                         'dir' => true,
74                         'lang' => true,
75                         'style' => true,
76                         'xml:lang' => true,
77                 ),
78                 'aside' => array(
79                         'align' => true,
80                         'class' => true,
81                         'dir' => true,
82                         'lang' => true,
83                         'style' => true,
84                         'xml:lang' => true,
85                 ),
86                 'b' => array(),
87                 'big' => array(),
88                 'blockquote' => array(
89                         'id' => true,
90                         'cite' => true,
91                         'class' => true,
92                         'lang' => true,
93                         'xml:lang' => true,
94                 ),
95                 'br' => array (
96                         'class' => true,
97                 ),
98                 'button' => array(
99                         'disabled' => true,
100                         'name' => true,
101                         'type' => true,
102                         'value' => true,
103                 ),
104                 'caption' => array(
105                         'align' => true,
106                         'class' => true,
107                 ),
108                 'cite' => array (
109                         'class' => true,
110                         'dir' => true,
111                         'lang' => true,
112                         'title' => true,
113                 ),
114                 'code' => array (
115                         'style' => true,
116                 ),
117                 'col' => array(
118                         'align' => true,
119                         'char' => true,
120                         'charoff' => true,
121                         'span' => true,
122                         'dir' => true,
123                         'style' => true,
124                         'valign' => true,
125                         'width' => true,
126                 ),
127                 'del' => array(
128                         'datetime' => true,
129                 ),
130                 'dd' => array(),
131                 'details' => array(
132                         'align' => true,
133                         'class' => true,
134                         'dir' => true,
135                         'lang' => true,
136                         'open' => true,
137                         'style' => true,
138                         'xml:lang' => true,
139                 ),
140                 'div' => array(
141                         'align' => true,
142                         'class' => true,
143                         'dir' => true,
144                         'lang' => true,
145                         'style' => true,
146                         'xml:lang' => true,
147                 ),
148                 'dl' => array(),
149                 'dt' => array(),
150                 'em' => array(),
151                 'fieldset' => array(),
152                 'figure' => array(
153                         'align' => true,
154                         'class' => true,
155                         'dir' => true,
156                         'lang' => true,
157                         'style' => true,
158                         'xml:lang' => true,
159                 ),
160                 'figcaption' => array(
161                         'align' => true,
162                         'class' => true,
163                         'dir' => true,
164                         'lang' => true,
165                         'style' => true,
166                         'xml:lang' => true,
167                 ),
168                 'font' => array(
169                         'color' => true,
170                         'face' => true,
171                         'size' => true,
172                 ),
173                 'footer' => array(
174                         'align' => true,
175                         'class' => true,
176                         'dir' => true,
177                         'lang' => true,
178                         'style' => true,
179                         'xml:lang' => true,
180                 ),
181                 'form' => array(
182                         'action' => true,
183                         'accept' => true,
184                         'accept-charset' => true,
185                         'enctype' => true,
186                         'method' => true,
187                         'name' => true,
188                         'target' => true,
189                 ),
190                 'h1' => array(
191                         'align' => true,
192                         'class' => true,
193                         'id'    => true,
194                         'style' => true,
195                 ),
196                 'h2' => array (
197                         'align' => true,
198                         'class' => true,
199                         'id'    => true,
200                         'style' => true,
201                 ),
202                 'h3' => array (
203                         'align' => true,
204                         'class' => true,
205                         'id'    => true,
206                         'style' => true,
207                 ),
208                 'h4' => array (
209                         'align' => true,
210                         'class' => true,
211                         'id'    => true,
212                         'style' => true,
213                 ),
214                 'h5' => array (
215                         'align' => true,
216                         'class' => true,
217                         'id'    => true,
218                         'style' => true,
219                 ),
220                 'h6' => array (
221                         'align' => true,
222                         'class' => true,
223                         'id'    => true,
224                         'style' => true,
225                 ),
226                 'header' => array(
227                         'align' => true,
228                         'class' => true,
229                         'dir' => true,
230                         'lang' => true,
231                         'style' => true,
232                         'xml:lang' => true,
233                 ),
234                 'hgroup' => array(
235                         'align' => true,
236                         'class' => true,
237                         'dir' => true,
238                         'lang' => true,
239                         'style' => true,
240                         'xml:lang' => true,
241                 ),
242                 'hr' => array (
243                         'align' => true,
244                         'class' => true,
245                         'noshade' => true,
246                         'size' => true,
247                         'width' => true,
248                 ),
249                 'i' => array(),
250                 'img' => array(
251                         'alt' => true,
252                         'align' => true,
253                         'border' => true,
254                         'class' => true,
255                         'height' => true,
256                         'hspace' => true,
257                         'longdesc' => true,
258                         'vspace' => true,
259                         'src' => true,
260                         'style' => true,
261                         'width' => true,
262                 ),
263                 'ins' => array(
264                         'datetime' => true,
265                         'cite' => true,
266                 ),
267                 'kbd' => array(),
268                 'label' => array(
269                         'for' => true,
270                 ),
271                 'legend' => array(
272                         'align' => true,
273                 ),
274                 'li' => array (
275                         'align' => true,
276                         'class' => true,
277                 ),
278                 'menu' => array (
279                         'class' => true,
280                         'style' => true,
281                         'type' => true,
282                 ),
283                 'nav' => array(
284                         'align' => true,
285                         'class' => true,
286                         'dir' => true,
287                         'lang' => true,
288                         'style' => true,
289                         'xml:lang' => true,
290                 ),
291                 'p' => array(
292                         'class' => true,
293                         'align' => true,
294                         'dir' => true,
295                         'lang' => true,
296                         'style' => true,
297                         'xml:lang' => true,
298                 ),
299                 'pre' => array(
300                         'style' => true,
301                         'width' => true,
302                 ),
303                 'q' => array(
304                         'cite' => true,
305                 ),
306                 's' => array(),
307                 'span' => array (
308                         'class' => true,
309                         'dir' => true,
310                         'align' => true,
311                         'lang' => true,
312                         'style' => true,
313                         'title' => true,
314                         'xml:lang' => true,
315                 ),
316                 'section' => array(
317                         'align' => true,
318                         'class' => true,
319                         'dir' => true,
320                         'lang' => true,
321                         'style' => true,
322                         'xml:lang' => true,
323                 ),
324                 'strike' => array(),
325                 'strong' => array(),
326                 'sub' => array(),
327                 'summary' => array(
328                         'align' => true,
329                         'class' => true,
330                         'dir' => true,
331                         'lang' => true,
332                         'style' => true,
333                         'xml:lang' => true,
334                 ),
335                 'sup' => array(),
336                 'table' => array(
337                         'align' => true,
338                         'bgcolor' => true,
339                         'border' => true,
340                         'cellpadding' => true,
341                         'cellspacing' => true,
342                         'class' => true,
343                         'dir' => true,
344                         'id' => true,
345                         'rules' => true,
346                         'style' => true,
347                         'summary' => true,
348                         'width' => true,
349                 ),
350                 'tbody' => array(
351                         'align' => true,
352                         'char' => true,
353                         'charoff' => true,
354                         'valign' => true,
355                 ),
356                 'td' => array(
357                         'abbr' => true,
358                         'align' => true,
359                         'axis' => true,
360                         'bgcolor' => true,
361                         'char' => true,
362                         'charoff' => true,
363                         'class' => true,
364                         'colspan' => true,
365                         'dir' => true,
366                         'headers' => true,
367                         'height' => true,
368                         'nowrap' => true,
369                         'rowspan' => true,
370                         'scope' => true,
371                         'style' => true,
372                         'valign' => true,
373                         'width' => true,
374                 ),
375                 'textarea' => array(
376                         'cols' => true,
377                         'rows' => true,
378                         'disabled' => true,
379                         'name' => true,
380                         'readonly' => true,
381                 ),
382                 'tfoot' => array(
383                         'align' => true,
384                         'char' => true,
385                         'class' => true,
386                         'charoff' => true,
387                         'valign' => true,
388                 ),
389                 'th' => array(
390                         'abbr' => true,
391                         'align' => true,
392                         'axis' => true,
393                         'bgcolor' => true,
394                         'char' => true,
395                         'charoff' => true,
396                         'class' => true,
397                         'colspan' => true,
398                         'headers' => true,
399                         'height' => true,
400                         'nowrap' => true,
401                         'rowspan' => true,
402                         'scope' => true,
403                         'valign' => true,
404                         'width' => true,
405                 ),
406                 'thead' => array(
407                         'align' => true,
408                         'char' => true,
409                         'charoff' => true,
410                         'class' => true,
411                         'valign' => true,
412                 ),
413                 'title' => array(),
414                 'tr' => array(
415                         'align' => true,
416                         'bgcolor' => true,
417                         'char' => true,
418                         'charoff' => true,
419                         'class' => true,
420                         'style' => true,
421                         'valign' => true,
422                 ),
423                 'tt' => array(),
424                 'u' => array(),
425                 'ul' => array (
426                         'class' => true,
427                         'style' => true,
428                         'type' => true,
429                 ),
430                 'ol' => array (
431                         'class' => true,
432                         'start' => true,
433                         'style' => true,
434                         'type' => true,
435                 ),
436                 'var' => array(),
437         );
438
439         /**
440          * Kses allowed HTML elements.
441          *
442          * @global array $allowedtags
443          * @since 1.0.0
444          */
445         $allowedtags = array(
446                 'a' => array(
447                         'href' => true,
448                         'title' => true,
449                 ),
450                 'abbr' => array(
451                         'title' => true,
452                 ),
453                 'acronym' => array(
454                         'title' => true,
455                 ),
456                 'b' => array(),
457                 'blockquote' => array(
458                         'cite' => true,
459                 ),
460                 //      'br' => array(),
461                 'cite' => array(),
462                 'code' => array(),
463                 'del' => array(
464                         'datetime' => true,
465                 ),
466                 //      'dd' => array(),
467                 //      'dl' => array(),
468                 //      'dt' => array(),
469                 'em' => array (), 'i' => array (),
470                 //      'ins' => array('datetime' => array(), 'cite' => array()),
471                 //      'li' => array(),
472                 //      'ol' => array(),
473                 //      'p' => array(),
474                 'q' => array(
475                         'cite' => true,
476                 ),
477                 'strike' => array(),
478                 'strong' => array(),
479                 //      'sub' => array(),
480                 //      'sup' => array(),
481                 //      'u' => array(),
482                 //      'ul' => array(),
483         );
484
485         $allowedentitynames = array(
486                 'nbsp',    'iexcl',  'cent',    'pound',  'curren', 'yen',
487                 'brvbar',  'sect',   'uml',     'copy',   'ordf',   'laquo',
488                 'not',     'shy',    'reg',     'macr',   'deg',    'plusmn',
489                 'acute',   'micro',  'para',    'middot', 'cedil',  'ordm',
490                 'raquo',   'iquest', 'Agrave',  'Aacute', 'Acirc',  'Atilde',
491                 'Auml',    'Aring',  'AElig',   'Ccedil', 'Egrave', 'Eacute',
492                 'Ecirc',   'Euml',   'Igrave',  'Iacute', 'Icirc',  'Iuml',
493                 'ETH',     'Ntilde', 'Ograve',  'Oacute', 'Ocirc',  'Otilde',
494                 'Ouml',    'times',  'Oslash',  'Ugrave', 'Uacute', 'Ucirc',
495                 'Uuml',    'Yacute', 'THORN',   'szlig',  'agrave', 'aacute',
496                 'acirc',   'atilde', 'auml',    'aring',  'aelig',  'ccedil',
497                 'egrave',  'eacute', 'ecirc',   'euml',   'igrave', 'iacute',
498                 'icirc',   'iuml',   'eth',     'ntilde', 'ograve', 'oacute',
499                 'ocirc',   'otilde', 'ouml',    'divide', 'oslash', 'ugrave',
500                 'uacute',  'ucirc',  'uuml',    'yacute', 'thorn',  'yuml',
501                 'quot',    'amp',    'lt',      'gt',     'apos',   'OElig',
502                 'oelig',   'Scaron', 'scaron',  'Yuml',   'circ',   'tilde',
503                 'ensp',    'emsp',   'thinsp',  'zwnj',   'zwj',    'lrm',
504                 'rlm',     'ndash',  'mdash',   'lsquo',  'rsquo',  'sbquo',
505                 'ldquo',   'rdquo',  'bdquo',   'dagger', 'Dagger', 'permil',
506                 'lsaquo',  'rsaquo', 'euro',    'fnof',   'Alpha',  'Beta',
507                 'Gamma',   'Delta',  'Epsilon', 'Zeta',   'Eta',    'Theta',
508                 'Iota',    'Kappa',  'Lambda',  'Mu',     'Nu',     'Xi',
509                 'Omicron', 'Pi',     'Rho',     'Sigma',  'Tau',    'Upsilon',
510                 'Phi',     'Chi',    'Psi',     'Omega',  'alpha',  'beta',
511                 'gamma',   'delta',  'epsilon', 'zeta',   'eta',    'theta',
512                 'iota',    'kappa',  'lambda',  'mu',     'nu',     'xi',
513                 'omicron', 'pi',     'rho',     'sigmaf', 'sigma',  'tau',
514                 'upsilon', 'phi',    'chi',     'psi',    'omega',  'thetasym',
515                 'upsih',   'piv',    'bull',    'hellip', 'prime',  'Prime',
516                 'oline',   'frasl',  'weierp',  'image',  'real',   'trade',
517                 'alefsym', 'larr',   'uarr',    'rarr',   'darr',   'harr',
518                 'crarr',   'lArr',   'uArr',    'rArr',   'dArr',   'hArr',
519                 'forall',  'part',   'exist',   'empty',  'nabla',  'isin',
520                 'notin',   'ni',     'prod',    'sum',    'minus',  'lowast',
521                 'radic',   'prop',   'infin',   'ang',    'and',    'or',
522                 'cap',     'cup',    'int',     'sim',    'cong',   'asymp',
523                 'ne',      'equiv',  'le',      'ge',     'sub',    'sup',
524                 'nsub',    'sube',   'supe',    'oplus',  'otimes', 'perp',
525                 'sdot',    'lceil',  'rceil',   'lfloor', 'rfloor', 'lang',
526                 'rang',    'loz',    'spades',  'clubs',  'hearts', 'diams',
527         );
528 }
529
530 /**
531  * Filters content and keeps only allowable HTML elements.
532  *
533  * This function makes sure that only the allowed HTML element names, attribute
534  * names and attribute values plus only sane HTML entities will occur in
535  * $string. You have to remove any slashes from PHP's magic quotes before you
536  * call this function.
537  *
538  * The default allowed protocols are 'http', 'https', 'ftp', 'mailto', 'news',
539  * 'irc', 'gopher', 'nntp', 'feed', 'telnet, 'mms', 'rtsp' and 'svn'. This
540  * covers all common link protocols, except for 'javascript' which should not
541  * be allowed for untrusted users.
542  *
543  * @since 1.0.0
544  *
545  * @param string $string Content to filter through kses
546  * @param array $allowed_html List of allowed HTML elements
547  * @param array $allowed_protocols Optional. Allowed protocol in links.
548  * @return string Filtered content with only allowed HTML elements
549  */
550 function wp_kses($string, $allowed_html, $allowed_protocols = array ()) {
551         if ( empty( $allowed_protocols ) )
552                 $allowed_protocols = wp_allowed_protocols();
553         $string = wp_kses_no_null($string);
554         $string = wp_kses_js_entities($string);
555         $string = wp_kses_normalize_entities($string);
556         $allowed_html_fixed = wp_kses_array_lc($allowed_html);
557         $string = wp_kses_hook($string, $allowed_html_fixed, $allowed_protocols); // WP changed the order of these funcs and added args to wp_kses_hook
558         return wp_kses_split($string, $allowed_html_fixed, $allowed_protocols);
559 }
560
561 /**
562  * You add any kses hooks here.
563  *
564  * There is currently only one kses WordPress hook and it is called here. All
565  * parameters are passed to the hooks and expected to receive a string.
566  *
567  * @since 1.0.0
568  *
569  * @param string $string Content to filter through kses
570  * @param array $allowed_html List of allowed HTML elements
571  * @param array $allowed_protocols Allowed protocol in links
572  * @return string Filtered content through 'pre_kses' hook
573  */
574 function wp_kses_hook($string, $allowed_html, $allowed_protocols) {
575         $string = apply_filters('pre_kses', $string, $allowed_html, $allowed_protocols);
576         return $string;
577 }
578
579 /**
580  * This function returns kses' version number.
581  *
582  * @since 1.0.0
583  *
584  * @return string KSES Version Number
585  */
586 function wp_kses_version() {
587         return '0.2.2';
588 }
589
590 /**
591  * Searches for HTML tags, no matter how malformed.
592  *
593  * It also matches stray ">" characters.
594  *
595  * @since 1.0.0
596  *
597  * @param string $string Content to filter
598  * @param array $allowed_html Allowed HTML elements
599  * @param array $allowed_protocols Allowed protocols to keep
600  * @return string Content with fixed HTML tags
601  */
602 function wp_kses_split($string, $allowed_html, $allowed_protocols) {
603         global $pass_allowed_html, $pass_allowed_protocols;
604         $pass_allowed_html = $allowed_html;
605         $pass_allowed_protocols = $allowed_protocols;
606         return preg_replace_callback( '%(<!--.*?(-->|$))|(<[^>]*(>|$)|>)%', '_wp_kses_split_callback', $string );
607 }
608
609 /**
610  * Callback for wp_kses_split.
611  *
612  * @since 3.1.0
613  * @access private
614  */
615 function _wp_kses_split_callback( $match ) {
616         global $pass_allowed_html, $pass_allowed_protocols;
617         return wp_kses_split2( $match[0], $pass_allowed_html, $pass_allowed_protocols );
618 }
619
620 /**
621  * Callback for wp_kses_split for fixing malformed HTML tags.
622  *
623  * This function does a lot of work. It rejects some very malformed things like
624  * <:::>. It returns an empty string, if the element isn't allowed (look ma, no
625  * strip_tags()!). Otherwise it splits the tag into an element and an attribute
626  * list.
627  *
628  * After the tag is split into an element and an attribute list, it is run
629  * through another filter which will remove illegal attributes and once that is
630  * completed, will be returned.
631  *
632  * @access private
633  * @since 1.0.0
634  * @uses wp_kses_attr()
635  *
636  * @param string $string Content to filter
637  * @param array $allowed_html Allowed HTML elements
638  * @param array $allowed_protocols Allowed protocols to keep
639  * @return string Fixed HTML element
640  */
641 function wp_kses_split2($string, $allowed_html, $allowed_protocols) {
642         $string = wp_kses_stripslashes($string);
643
644         if (substr($string, 0, 1) != '<')
645                 return '&gt;';
646         # It matched a ">" character
647
648         if ( '<!--' == substr( $string, 0, 4 ) ) {
649                 $string = str_replace( array('<!--', '-->'), '', $string );
650                 while ( $string != ($newstring = wp_kses($string, $allowed_html, $allowed_protocols)) )
651                         $string = $newstring;
652                 if ( $string == '' )
653                         return '';
654                 // prevent multiple dashes in comments
655                 $string = preg_replace('/--+/', '-', $string);
656                 // prevent three dashes closing a comment
657                 $string = preg_replace('/-$/', '', $string);
658                 return "<!--{$string}-->";
659         }
660         # Allow HTML comments
661
662         if (!preg_match('%^<\s*(/\s*)?([a-zA-Z0-9]+)([^>]*)>?$%', $string, $matches))
663                 return '';
664         # It's seriously malformed
665
666         $slash = trim($matches[1]);
667         $elem = $matches[2];
668         $attrlist = $matches[3];
669
670         if ( ! isset($allowed_html[strtolower($elem)]) )
671                 return '';
672         # They are using a not allowed HTML element
673
674         if ($slash != '')
675                 return "</$elem>";
676         # No attributes are allowed for closing elements
677
678         return wp_kses_attr( $elem, $attrlist, $allowed_html, $allowed_protocols );
679 }
680
681 /**
682  * Removes all attributes, if none are allowed for this element.
683  *
684  * If some are allowed it calls wp_kses_hair() to split them further, and then
685  * it builds up new HTML code from the data that kses_hair() returns. It also
686  * removes "<" and ">" characters, if there are any left. One more thing it does
687  * is to check if the tag has a closing XHTML slash, and if it does, it puts one
688  * in the returned code as well.
689  *
690  * @since 1.0.0
691  *
692  * @param string $element HTML element/tag
693  * @param string $attr HTML attributes from HTML element to closing HTML element tag
694  * @param array $allowed_html Allowed HTML elements
695  * @param array $allowed_protocols Allowed protocols to keep
696  * @return string Sanitized HTML element
697  */
698 function wp_kses_attr($element, $attr, $allowed_html, $allowed_protocols) {
699         # Is there a closing XHTML slash at the end of the attributes?
700
701         $xhtml_slash = '';
702         if (preg_match('%\s*/\s*$%', $attr))
703                 $xhtml_slash = ' /';
704
705         # Are any attributes allowed at all for this element?
706         if ( ! isset($allowed_html[strtolower($element)]) || count($allowed_html[strtolower($element)]) == 0 )
707                 return "<$element$xhtml_slash>";
708
709         # Split it
710         $attrarr = wp_kses_hair($attr, $allowed_protocols);
711
712         # Go through $attrarr, and save the allowed attributes for this element
713         # in $attr2
714         $attr2 = '';
715
716         $allowed_attr = $allowed_html[strtolower($element)];
717         foreach ($attrarr as $arreach) {
718                 if ( ! isset( $allowed_attr[strtolower($arreach['name'])] ) )
719                         continue; # the attribute is not allowed
720
721                 $current = $allowed_attr[strtolower($arreach['name'])];
722                 if ( $current == '' )
723                         continue; # the attribute is not allowed
724
725                 if ( strtolower( $arreach['name'] ) == 'style' ) {
726                         $orig_value = $arreach['value'];
727                         $value = safecss_filter_attr( $orig_value );
728
729                         if ( empty( $value ) )
730                                 continue;
731
732                         $arreach['value'] = $value;
733                         $arreach['whole'] = str_replace( $orig_value, $value, $arreach['whole'] );
734                 }
735
736                 if ( ! is_array($current) ) {
737                         $attr2 .= ' '.$arreach['whole'];
738                 # there are no checks
739
740                 } else {
741                         # there are some checks
742                         $ok = true;
743                         foreach ($current as $currkey => $currval) {
744                                 if ( ! wp_kses_check_attr_val($arreach['value'], $arreach['vless'], $currkey, $currval) ) {
745                                         $ok = false;
746                                         break;
747                                 }
748                         }
749
750                         if ( $ok )
751                                 $attr2 .= ' '.$arreach['whole']; # it passed them
752                 } # if !is_array($current)
753         } # foreach
754
755         # Remove any "<" or ">" characters
756         $attr2 = preg_replace('/[<>]/', '', $attr2);
757
758         return "<$element$attr2$xhtml_slash>";
759 }
760
761 /**
762  * Builds an attribute list from string containing attributes.
763  *
764  * This function does a lot of work. It parses an attribute list into an array
765  * with attribute data, and tries to do the right thing even if it gets weird
766  * input. It will add quotes around attribute values that don't have any quotes
767  * or apostrophes around them, to make it easier to produce HTML code that will
768  * conform to W3C's HTML specification. It will also remove bad URL protocols
769  * from attribute values. It also reduces duplicate attributes by using the
770  * attribute defined first (foo='bar' foo='baz' will result in foo='bar').
771  *
772  * @since 1.0.0
773  *
774  * @param string $attr Attribute list from HTML element to closing HTML element tag
775  * @param array $allowed_protocols Allowed protocols to keep
776  * @return array List of attributes after parsing
777  */
778 function wp_kses_hair($attr, $allowed_protocols) {
779         $attrarr = array ();
780         $mode = 0;
781         $attrname = '';
782         $uris = array('xmlns', 'profile', 'href', 'src', 'cite', 'classid', 'codebase', 'data', 'usemap', 'longdesc', 'action');
783
784         # Loop through the whole attribute list
785
786         while (strlen($attr) != 0) {
787                 $working = 0; # Was the last operation successful?
788
789                 switch ($mode) {
790                         case 0 : # attribute name, href for instance
791
792                                 if (preg_match('/^([-a-zA-Z]+)/', $attr, $match)) {
793                                         $attrname = $match[1];
794                                         $working = $mode = 1;
795                                         $attr = preg_replace('/^[-a-zA-Z]+/', '', $attr);
796                                 }
797
798                                 break;
799
800                         case 1 : # equals sign or valueless ("selected")
801
802                                 if (preg_match('/^\s*=\s*/', $attr)) # equals sign
803                                         {
804                                         $working = 1;
805                                         $mode = 2;
806                                         $attr = preg_replace('/^\s*=\s*/', '', $attr);
807                                         break;
808                                 }
809
810                                 if (preg_match('/^\s+/', $attr)) # valueless
811                                         {
812                                         $working = 1;
813                                         $mode = 0;
814                                         if(false === array_key_exists($attrname, $attrarr)) {
815                                                 $attrarr[$attrname] = array ('name' => $attrname, 'value' => '', 'whole' => $attrname, 'vless' => 'y');
816                                         }
817                                         $attr = preg_replace('/^\s+/', '', $attr);
818                                 }
819
820                                 break;
821
822                         case 2 : # attribute value, a URL after href= for instance
823
824                                 if (preg_match('%^"([^"]*)"(\s+|/?$)%', $attr, $match))
825                                         # "value"
826                                         {
827                                         $thisval = $match[1];
828                                         if ( in_array(strtolower($attrname), $uris) )
829                                                 $thisval = wp_kses_bad_protocol($thisval, $allowed_protocols);
830
831                                         if(false === array_key_exists($attrname, $attrarr)) {
832                                                 $attrarr[$attrname] = array ('name' => $attrname, 'value' => $thisval, 'whole' => "$attrname=\"$thisval\"", 'vless' => 'n');
833                                         }
834                                         $working = 1;
835                                         $mode = 0;
836                                         $attr = preg_replace('/^"[^"]*"(\s+|$)/', '', $attr);
837                                         break;
838                                 }
839
840                                 if (preg_match("%^'([^']*)'(\s+|/?$)%", $attr, $match))
841                                         # 'value'
842                                         {
843                                         $thisval = $match[1];
844                                         if ( in_array(strtolower($attrname), $uris) )
845                                                 $thisval = wp_kses_bad_protocol($thisval, $allowed_protocols);
846
847                                         if(false === array_key_exists($attrname, $attrarr)) {
848                                                 $attrarr[$attrname] = array ('name' => $attrname, 'value' => $thisval, 'whole' => "$attrname='$thisval'", 'vless' => 'n');
849                                         }
850                                         $working = 1;
851                                         $mode = 0;
852                                         $attr = preg_replace("/^'[^']*'(\s+|$)/", '', $attr);
853                                         break;
854                                 }
855
856                                 if (preg_match("%^([^\s\"']+)(\s+|/?$)%", $attr, $match))
857                                         # value
858                                         {
859                                         $thisval = $match[1];
860                                         if ( in_array(strtolower($attrname), $uris) )
861                                                 $thisval = wp_kses_bad_protocol($thisval, $allowed_protocols);
862
863                                         if(false === array_key_exists($attrname, $attrarr)) {
864                                                 $attrarr[$attrname] = array ('name' => $attrname, 'value' => $thisval, 'whole' => "$attrname=\"$thisval\"", 'vless' => 'n');
865                                         }
866                                         # We add quotes to conform to W3C's HTML spec.
867                                         $working = 1;
868                                         $mode = 0;
869                                         $attr = preg_replace("%^[^\s\"']+(\s+|$)%", '', $attr);
870                                 }
871
872                                 break;
873                 } # switch
874
875                 if ($working == 0) # not well formed, remove and try again
876                 {
877                         $attr = wp_kses_html_error($attr);
878                         $mode = 0;
879                 }
880         } # while
881
882         if ($mode == 1 && false === array_key_exists($attrname, $attrarr))
883                 # special case, for when the attribute list ends with a valueless
884                 # attribute like "selected"
885                 $attrarr[$attrname] = array ('name' => $attrname, 'value' => '', 'whole' => $attrname, 'vless' => 'y');
886
887         return $attrarr;
888 }
889
890 /**
891  * Performs different checks for attribute values.
892  *
893  * The currently implemented checks are "maxlen", "minlen", "maxval", "minval"
894  * and "valueless".
895  *
896  * @since 1.0.0
897  *
898  * @param string $value Attribute value
899  * @param string $vless Whether the value is valueless. Use 'y' or 'n'
900  * @param string $checkname What $checkvalue is checking for.
901  * @param mixed $checkvalue What constraint the value should pass
902  * @return bool Whether check passes
903  */
904 function wp_kses_check_attr_val($value, $vless, $checkname, $checkvalue) {
905         $ok = true;
906
907         switch (strtolower($checkname)) {
908                 case 'maxlen' :
909                         # The maxlen check makes sure that the attribute value has a length not
910                         # greater than the given value. This can be used to avoid Buffer Overflows
911                         # in WWW clients and various Internet servers.
912
913                         if (strlen($value) > $checkvalue)
914                                 $ok = false;
915                         break;
916
917                 case 'minlen' :
918                         # The minlen check makes sure that the attribute value has a length not
919                         # smaller than the given value.
920
921                         if (strlen($value) < $checkvalue)
922                                 $ok = false;
923                         break;
924
925                 case 'maxval' :
926                         # The maxval check does two things: it checks that the attribute value is
927                         # an integer from 0 and up, without an excessive amount of zeroes or
928                         # whitespace (to avoid Buffer Overflows). It also checks that the attribute
929                         # value is not greater than the given value.
930                         # This check can be used to avoid Denial of Service attacks.
931
932                         if (!preg_match('/^\s{0,6}[0-9]{1,6}\s{0,6}$/', $value))
933                                 $ok = false;
934                         if ($value > $checkvalue)
935                                 $ok = false;
936                         break;
937
938                 case 'minval' :
939                         # The minval check makes sure that the attribute value is a positive integer,
940                         # and that it is not smaller than the given value.
941
942                         if (!preg_match('/^\s{0,6}[0-9]{1,6}\s{0,6}$/', $value))
943                                 $ok = false;
944                         if ($value < $checkvalue)
945                                 $ok = false;
946                         break;
947
948                 case 'valueless' :
949                         # The valueless check makes sure if the attribute has a value
950                         # (like <a href="blah">) or not (<option selected>). If the given value
951                         # is a "y" or a "Y", the attribute must not have a value.
952                         # If the given value is an "n" or an "N", the attribute must have one.
953
954                         if (strtolower($checkvalue) != $vless)
955                                 $ok = false;
956                         break;
957         } # switch
958
959         return $ok;
960 }
961
962 /**
963  * Sanitize string from bad protocols.
964  *
965  * This function removes all non-allowed protocols from the beginning of
966  * $string. It ignores whitespace and the case of the letters, and it does
967  * understand HTML entities. It does its work in a while loop, so it won't be
968  * fooled by a string like "javascript:javascript:alert(57)".
969  *
970  * @since 1.0.0
971  *
972  * @param string $string Content to filter bad protocols from
973  * @param array $allowed_protocols Allowed protocols to keep
974  * @return string Filtered content
975  */
976 function wp_kses_bad_protocol($string, $allowed_protocols) {
977         $string = wp_kses_no_null($string);
978         $iterations = 0;
979
980         do {
981                 $original_string = $string;
982                 $string = wp_kses_bad_protocol_once($string, $allowed_protocols);
983         } while ( $original_string != $string && ++$iterations < 6 );
984
985         if ( $original_string != $string )
986                 return '';
987
988         return $string;
989 }
990
991 /**
992  * Removes any null characters in $string.
993  *
994  * @since 1.0.0
995  *
996  * @param string $string
997  * @return string
998  */
999 function wp_kses_no_null($string) {
1000         $string = preg_replace('/\0+/', '', $string);
1001         $string = preg_replace('/(\\\\0)+/', '', $string);
1002
1003         return $string;
1004 }
1005
1006 /**
1007  * Strips slashes from in front of quotes.
1008  *
1009  * This function changes the character sequence \" to just ". It leaves all
1010  * other slashes alone. It's really weird, but the quoting from
1011  * preg_replace(//e) seems to require this.
1012  *
1013  * @since 1.0.0
1014  *
1015  * @param string $string String to strip slashes
1016  * @return string Fixed string with quoted slashes
1017  */
1018 function wp_kses_stripslashes($string) {
1019         return preg_replace('%\\\\"%', '"', $string);
1020 }
1021
1022 /**
1023  * Goes through an array and changes the keys to all lower case.
1024  *
1025  * @since 1.0.0
1026  *
1027  * @param array $inarray Unfiltered array
1028  * @return array Fixed array with all lowercase keys
1029  */
1030 function wp_kses_array_lc($inarray) {
1031         $outarray = array ();
1032
1033         foreach ( (array) $inarray as $inkey => $inval) {
1034                 $outkey = strtolower($inkey);
1035                 $outarray[$outkey] = array ();
1036
1037                 foreach ( (array) $inval as $inkey2 => $inval2) {
1038                         $outkey2 = strtolower($inkey2);
1039                         $outarray[$outkey][$outkey2] = $inval2;
1040                 } # foreach $inval
1041         } # foreach $inarray
1042
1043         return $outarray;
1044 }
1045
1046 /**
1047  * Removes the HTML JavaScript entities found in early versions of Netscape 4.
1048  *
1049  * @since 1.0.0
1050  *
1051  * @param string $string
1052  * @return string
1053  */
1054 function wp_kses_js_entities($string) {
1055         return preg_replace('%&\s*\{[^}]*(\}\s*;?|$)%', '', $string);
1056 }
1057
1058 /**
1059  * Handles parsing errors in wp_kses_hair().
1060  *
1061  * The general plan is to remove everything to and including some whitespace,
1062  * but it deals with quotes and apostrophes as well.
1063  *
1064  * @since 1.0.0
1065  *
1066  * @param string $string
1067  * @return string
1068  */
1069 function wp_kses_html_error($string) {
1070         return preg_replace('/^("[^"]*("|$)|\'[^\']*(\'|$)|\S)*\s*/', '', $string);
1071 }
1072
1073 /**
1074  * Sanitizes content from bad protocols and other characters.
1075  *
1076  * This function searches for URL protocols at the beginning of $string, while
1077  * handling whitespace and HTML entities.
1078  *
1079  * @since 1.0.0
1080  *
1081  * @param string $string Content to check for bad protocols
1082  * @param string $allowed_protocols Allowed protocols
1083  * @return string Sanitized content
1084  */
1085 function wp_kses_bad_protocol_once($string, $allowed_protocols, $count = 1 ) {
1086         $string2 = preg_split( '/:|&#0*58;|&#x0*3a;/i', $string, 2 );
1087         if ( isset($string2[1]) && ! preg_match('%/\?%', $string2[0]) ) {
1088                 $string = trim( $string2[1] );
1089                 $protocol = wp_kses_bad_protocol_once2( $string2[0], $allowed_protocols );
1090                 if ( 'feed:' == $protocol ) {
1091                         if ( $count > 2 )
1092                                 return '';
1093                         $string = wp_kses_bad_protocol_once( $string, $allowed_protocols, ++$count );
1094                         if ( empty( $string ) )
1095                                 return $string;
1096                 }
1097                 $string = $protocol . $string;
1098         }
1099
1100         return $string;
1101 }
1102
1103 /**
1104  * Callback for wp_kses_bad_protocol_once() regular expression.
1105  *
1106  * This function processes URL protocols, checks to see if they're in the
1107  * whitelist or not, and returns different data depending on the answer.
1108  *
1109  * @access private
1110  * @since 1.0.0
1111  *
1112  * @param string $string URI scheme to check against the whitelist
1113  * @param string $allowed_protocols Allowed protocols
1114  * @return string Sanitized content
1115  */
1116 function wp_kses_bad_protocol_once2( $string, $allowed_protocols ) {
1117         $string2 = wp_kses_decode_entities($string);
1118         $string2 = preg_replace('/\s/', '', $string2);
1119         $string2 = wp_kses_no_null($string2);
1120         $string2 = strtolower($string2);
1121
1122         $allowed = false;
1123         foreach ( (array) $allowed_protocols as $one_protocol )
1124                 if ( strtolower($one_protocol) == $string2 ) {
1125                         $allowed = true;
1126                         break;
1127                 }
1128
1129         if ($allowed)
1130                 return "$string2:";
1131         else
1132                 return '';
1133 }
1134
1135 /**
1136  * Converts and fixes HTML entities.
1137  *
1138  * This function normalizes HTML entities. It will convert "AT&T" to the correct
1139  * "AT&amp;T", "&#00058;" to "&#58;", "&#XYZZY;" to "&amp;#XYZZY;" and so on.
1140  *
1141  * @since 1.0.0
1142  *
1143  * @param string $string Content to normalize entities
1144  * @return string Content with normalized entities
1145  */
1146 function wp_kses_normalize_entities($string) {
1147         # Disarm all entities by converting & to &amp;
1148
1149         $string = str_replace('&', '&amp;', $string);
1150
1151         # Change back the allowed entities in our entity whitelist
1152
1153         $string = preg_replace_callback('/&amp;([A-Za-z]{2,8});/', 'wp_kses_named_entities', $string);
1154         $string = preg_replace_callback('/&amp;#(0*[0-9]{1,7});/', 'wp_kses_normalize_entities2', $string);
1155         $string = preg_replace_callback('/&amp;#[Xx](0*[0-9A-Fa-f]{1,6});/', 'wp_kses_normalize_entities3', $string);
1156
1157         return $string;
1158 }
1159
1160 /**
1161  * Callback for wp_kses_normalize_entities() regular expression.
1162  *
1163  * This function only accepts valid named entity references, which are finite,
1164  * case-sensitive, and highly scrutinized by HTML and XML validators.
1165  *
1166  * @since 3.0.0
1167  *
1168  * @param array $matches preg_replace_callback() matches array
1169  * @return string Correctly encoded entity
1170  */
1171 function wp_kses_named_entities($matches) {
1172         global $allowedentitynames;
1173
1174         if ( empty($matches[1]) )
1175                 return '';
1176
1177         $i = $matches[1];
1178         return ( ( ! in_array($i, $allowedentitynames) ) ? "&amp;$i;" : "&$i;" );
1179 }
1180
1181 /**
1182  * Callback for wp_kses_normalize_entities() regular expression.
1183  *
1184  * This function helps wp_kses_normalize_entities() to only accept 16-bit values
1185  * and nothing more for &#number; entities.
1186  *
1187  * @access private
1188  * @since 1.0.0
1189  *
1190  * @param array $matches preg_replace_callback() matches array
1191  * @return string Correctly encoded entity
1192  */
1193 function wp_kses_normalize_entities2($matches) {
1194         if ( empty($matches[1]) )
1195                 return '';
1196
1197         $i = $matches[1];
1198         if (valid_unicode($i)) {
1199                 $i = str_pad(ltrim($i,'0'), 3, '0', STR_PAD_LEFT);
1200                 $i = "&#$i;";
1201         } else {
1202                 $i = "&amp;#$i;";
1203         }
1204
1205         return $i;
1206 }
1207
1208 /**
1209  * Callback for wp_kses_normalize_entities() for regular expression.
1210  *
1211  * This function helps wp_kses_normalize_entities() to only accept valid Unicode
1212  * numeric entities in hex form.
1213  *
1214  * @access private
1215  *
1216  * @param array $matches preg_replace_callback() matches array
1217  * @return string Correctly encoded entity
1218  */
1219 function wp_kses_normalize_entities3($matches) {
1220         if ( empty($matches[1]) )
1221                 return '';
1222
1223         $hexchars = $matches[1];
1224         return ( ( ! valid_unicode(hexdec($hexchars)) ) ? "&amp;#x$hexchars;" : '&#x'.ltrim($hexchars,'0').';' );
1225 }
1226
1227 /**
1228  * Helper function to determine if a Unicode value is valid.
1229  *
1230  * @param int $i Unicode value
1231  * @return bool True if the value was a valid Unicode number
1232  */
1233 function valid_unicode($i) {
1234         return ( $i == 0x9 || $i == 0xa || $i == 0xd ||
1235                         ($i >= 0x20 && $i <= 0xd7ff) ||
1236                         ($i >= 0xe000 && $i <= 0xfffd) ||
1237                         ($i >= 0x10000 && $i <= 0x10ffff) );
1238 }
1239
1240 /**
1241  * Convert all entities to their character counterparts.
1242  *
1243  * This function decodes numeric HTML entities (&#65; and &#x41;). It doesn't do
1244  * anything with other entities like &auml;, but we don't need them in the URL
1245  * protocol whitelisting system anyway.
1246  *
1247  * @since 1.0.0
1248  *
1249  * @param string $string Content to change entities
1250  * @return string Content after decoded entities
1251  */
1252 function wp_kses_decode_entities($string) {
1253         $string = preg_replace_callback('/&#([0-9]+);/', '_wp_kses_decode_entities_chr', $string);
1254         $string = preg_replace_callback('/&#[Xx]([0-9A-Fa-f]+);/', '_wp_kses_decode_entities_chr_hexdec', $string);
1255
1256         return $string;
1257 }
1258
1259 /**
1260  * Regex callback for wp_kses_decode_entities()
1261  *
1262  * @param array $match preg match
1263  * @return string
1264  */
1265 function _wp_kses_decode_entities_chr( $match ) {
1266         return chr( $match[1] );
1267 }
1268
1269 /**
1270  * Regex callback for wp_kses_decode_entities()
1271  *
1272  * @param array $match preg match
1273  * @return string
1274  */
1275 function _wp_kses_decode_entities_chr_hexdec( $match ) {
1276         return chr( hexdec( $match[1] ) );
1277 }
1278
1279 /**
1280  * Sanitize content with allowed HTML Kses rules.
1281  *
1282  * @since 1.0.0
1283  * @uses $allowedtags
1284  *
1285  * @param string $data Content to filter, expected to be escaped with slashes
1286  * @return string Filtered content
1287  */
1288 function wp_filter_kses($data) {
1289         global $allowedtags;
1290         return addslashes( wp_kses(stripslashes( $data ), $allowedtags) );
1291 }
1292
1293 /**
1294  * Sanitize content with allowed HTML Kses rules.
1295  *
1296  * @since 2.9.0
1297  * @uses $allowedtags
1298  *
1299  * @param string $data Content to filter, expected to not be escaped
1300  * @return string Filtered content
1301  */
1302 function wp_kses_data($data) {
1303         global $allowedtags;
1304         return wp_kses( $data , $allowedtags );
1305 }
1306
1307 /**
1308  * Sanitize content for allowed HTML tags for post content.
1309  *
1310  * Post content refers to the page contents of the 'post' type and not $_POST
1311  * data from forms.
1312  *
1313  * @since 2.0.0
1314  * @uses $allowedposttags
1315  *
1316  * @param string $data Post content to filter, expected to be escaped with slashes
1317  * @return string Filtered post content with allowed HTML tags and attributes intact.
1318  */
1319 function wp_filter_post_kses($data) {
1320         global $allowedposttags;
1321         return addslashes ( wp_kses(stripslashes( $data ), $allowedposttags) );
1322 }
1323
1324 /**
1325  * Sanitize content for allowed HTML tags for post content.
1326  *
1327  * Post content refers to the page contents of the 'post' type and not $_POST
1328  * data from forms.
1329  *
1330  * @since 2.9.0
1331  * @uses $allowedposttags
1332  *
1333  * @param string $data Post content to filter
1334  * @return string Filtered post content with allowed HTML tags and attributes intact.
1335  */
1336 function wp_kses_post($data) {
1337         global $allowedposttags;
1338         return wp_kses( $data , $allowedposttags );
1339 }
1340
1341 /**
1342  * Strips all of the HTML in the content.
1343  *
1344  * @since 2.1.0
1345  *
1346  * @param string $data Content to strip all HTML from
1347  * @return string Filtered content without any HTML
1348  */
1349 function wp_filter_nohtml_kses($data) {
1350         return addslashes ( wp_kses(stripslashes( $data ), array()) );
1351 }
1352
1353 /**
1354  * Adds all Kses input form content filters.
1355  *
1356  * All hooks have default priority. The wp_filter_kses() function is added to
1357  * the 'pre_comment_content' and 'title_save_pre' hooks.
1358  *
1359  * The wp_filter_post_kses() function is added to the 'content_save_pre',
1360  * 'excerpt_save_pre', and 'content_filtered_save_pre' hooks.
1361  *
1362  * @since 2.0.0
1363  * @uses add_filter() See description for what functions are added to what hooks.
1364  */
1365 function kses_init_filters() {
1366         // Normal filtering
1367         add_filter('title_save_pre', 'wp_filter_kses');
1368
1369         // Comment filtering
1370         if ( current_user_can( 'unfiltered_html' ) )
1371                 add_filter( 'pre_comment_content', 'wp_filter_post_kses' );
1372         else
1373                 add_filter( 'pre_comment_content', 'wp_filter_kses' );
1374
1375         // Post filtering
1376         add_filter('content_save_pre', 'wp_filter_post_kses');
1377         add_filter('excerpt_save_pre', 'wp_filter_post_kses');
1378         add_filter('content_filtered_save_pre', 'wp_filter_post_kses');
1379 }
1380
1381 /**
1382  * Removes all Kses input form content filters.
1383  *
1384  * A quick procedural method to removing all of the filters that kses uses for
1385  * content in WordPress Loop.
1386  *
1387  * Does not remove the kses_init() function from 'init' hook (priority is
1388  * default). Also does not remove kses_init() function from 'set_current_user'
1389  * hook (priority is also default).
1390  *
1391  * @since 2.0.6
1392  */
1393 function kses_remove_filters() {
1394         // Normal filtering
1395         remove_filter('title_save_pre', 'wp_filter_kses');
1396
1397         // Comment filtering
1398         remove_filter( 'pre_comment_content', 'wp_filter_post_kses' );
1399         remove_filter( 'pre_comment_content', 'wp_filter_kses' );
1400
1401         // Post filtering
1402         remove_filter('content_save_pre', 'wp_filter_post_kses');
1403         remove_filter('excerpt_save_pre', 'wp_filter_post_kses');
1404         remove_filter('content_filtered_save_pre', 'wp_filter_post_kses');
1405 }
1406
1407 /**
1408  * Sets up most of the Kses filters for input form content.
1409  *
1410  * If you remove the kses_init() function from 'init' hook and
1411  * 'set_current_user' (priority is default), then none of the Kses filter hooks
1412  * will be added.
1413  *
1414  * First removes all of the Kses filters in case the current user does not need
1415  * to have Kses filter the content. If the user does not have unfiltered_html
1416  * capability, then Kses filters are added.
1417  *
1418  * @uses kses_remove_filters() Removes the Kses filters
1419  * @uses kses_init_filters() Adds the Kses filters back if the user
1420  *              does not have unfiltered HTML capability.
1421  * @since 2.0.0
1422  */
1423 function kses_init() {
1424         kses_remove_filters();
1425
1426         if (current_user_can('unfiltered_html') == false)
1427                 kses_init_filters();
1428 }
1429
1430 add_action('init', 'kses_init');
1431 add_action('set_current_user', 'kses_init');
1432
1433 /**
1434  * Inline CSS filter
1435  *
1436  * @since 2.8.1
1437  */
1438 function safecss_filter_attr( $css, $deprecated = '' ) {
1439         if ( !empty( $deprecated ) )
1440                 _deprecated_argument( __FUNCTION__, '2.8.1' ); // Never implemented
1441
1442         $css = wp_kses_no_null($css);
1443         $css = str_replace(array("\n","\r","\t"), '', $css);
1444
1445         if ( preg_match( '%[\\(&=}]|/\*%', $css ) ) // remove any inline css containing \ ( & } = or comments
1446                 return '';
1447
1448         $css_array = explode( ';', trim( $css ) );
1449         $allowed_attr = apply_filters( 'safe_style_css', array( 'text-align', 'margin', 'color', 'float',
1450         'border', 'background', 'background-color', 'border-bottom', 'border-bottom-color',
1451         'border-bottom-style', 'border-bottom-width', 'border-collapse', 'border-color', 'border-left',
1452         'border-left-color', 'border-left-style', 'border-left-width', 'border-right', 'border-right-color',
1453         'border-right-style', 'border-right-width', 'border-spacing', 'border-style', 'border-top',
1454         'border-top-color', 'border-top-style', 'border-top-width', 'border-width', 'caption-side',
1455         'clear', 'cursor', 'direction', 'font', 'font-family', 'font-size', 'font-style',
1456         'font-variant', 'font-weight', 'height', 'letter-spacing', 'line-height', 'margin-bottom',
1457         'margin-left', 'margin-right', 'margin-top', 'overflow', 'padding', 'padding-bottom',
1458         'padding-left', 'padding-right', 'padding-top', 'text-decoration', 'text-indent', 'vertical-align',
1459         'width' ) );
1460
1461         if ( empty($allowed_attr) )
1462                 return $css;
1463
1464         $css = '';
1465         foreach ( $css_array as $css_item ) {
1466                 if ( $css_item == '' )
1467                         continue;
1468                 $css_item = trim( $css_item );
1469                 $found = false;
1470                 if ( strpos( $css_item, ':' ) === false ) {
1471                         $found = true;
1472                 } else {
1473                         $parts = explode( ':', $css_item );
1474                         if ( in_array( trim( $parts[0] ), $allowed_attr ) )
1475                                 $found = true;
1476                 }
1477                 if ( $found ) {
1478                         if( $css != '' )
1479                                 $css .= ';';
1480                         $css .= $css_item;
1481                 }
1482         }
1483
1484         return $css;
1485 }