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