home *** CD-ROM | disk | FTP | other *** search
/ HTML Examples / WP.iso / wordpress / wp-includes / kses.php < prev    next >
Encoding:
PHP Script  |  2017-06-25  |  49.0 KB  |  1,832 lines

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