Skip to main content
Drupal API
User account menu
  • Log in

Breadcrumb

  1. Drupal Core 11.1.x
  2. Xss.php

class Xss

Provides helper to filter for cross-site scripting.

Hierarchy

  • class \Drupal\Component\Utility\Xss

Expanded class hierarchy of Xss

Related topics

Utility classes and functions
Overview of utility classes and functions for developers.
39 files declare their use of Xss
Boolean.php in core/modules/views/src/Plugin/views/field/Boolean.php
Custom.php in core/modules/views/src/Plugin/views/field/Custom.php
DbLogController.php in core/modules/dblog/src/Controller/DbLogController.php
EntityField.php in core/modules/views/src/Plugin/views/field/EntityField.php
EntityReference.php in core/modules/views/src/Plugin/views/style/EntityReference.php

... See full list

2 string references to 'Xss'
HandlerBase::sanitizeValue in core/modules/views/src/Plugin/views/HandlerBase.php
Sanitize the value for output.
NumericField::render in core/modules/views/src/Plugin/views/field/NumericField.php
Renders the field.

File

core/lib/Drupal/Component/Utility/Xss.php, line 12

Namespace

Drupal\Component\Utility
View source
class Xss {
    
    /**
     * The list of HTML tags allowed by filterAdmin().
     *
     * @var array
     *
     * @see \Drupal\Component\Utility\Xss::filterAdmin()
     */
    protected static $adminTags = [
        'a',
        'abbr',
        'acronym',
        'address',
        'article',
        'aside',
        'b',
        'bdi',
        'bdo',
        'big',
        'blockquote',
        'br',
        'caption',
        'cite',
        'code',
        'col',
        'colgroup',
        'command',
        'dd',
        'del',
        'details',
        'dfn',
        'div',
        'dl',
        'dt',
        'em',
        'figcaption',
        'figure',
        'footer',
        'h1',
        'h2',
        'h3',
        'h4',
        'h5',
        'h6',
        'header',
        'hgroup',
        'hr',
        'i',
        'img',
        'ins',
        'kbd',
        'li',
        'mark',
        'menu',
        'meter',
        'nav',
        'ol',
        'output',
        'p',
        'pre',
        'progress',
        'q',
        'rp',
        'rt',
        'ruby',
        's',
        'samp',
        'section',
        'small',
        'span',
        'strong',
        'sub',
        'summary',
        'sup',
        'table',
        'tbody',
        'td',
        'tfoot',
        'th',
        'thead',
        'time',
        'tr',
        'tt',
        'u',
        'ul',
        'var',
        'wbr',
    ];
    
    /**
     * The default list of HTML tags allowed by filter().
     *
     * @var array
     *
     * @see \Drupal\Component\Utility\Xss::filter()
     */
    protected static $htmlTags = [
        'a',
        'em',
        'strong',
        'cite',
        'blockquote',
        'code',
        'ul',
        'ol',
        'li',
        'dl',
        'dt',
        'dd',
    ];
    
    /**
     * Filters HTML to prevent cross-site-scripting (XSS) vulnerabilities.
     *
     * Based on kses by Ulf Harnhammar, see http://sourceforge.net/projects/kses.
     * For examples of various XSS attacks, see: http://ha.ckers.org/xss.html.
     *
     * This code does four things:
     * - Removes characters and constructs that can trick browsers.
     * - Makes sure all HTML entities are well-formed.
     * - Makes sure all HTML tags and attributes are well-formed.
     * - Makes sure no HTML tags contain URLs with a disallowed protocol (e.g.
     *   javascript:).
     *
     * @param string $string
     *   The string with raw HTML in it. It will be stripped of everything that
     *   can cause an XSS attack.
     * @param string[]|null $allowed_html_tags
     *   An array of allowed HTML tags.
     *
     * @return string
     *   An XSS safe version of $string, or an empty string if $string is not
     *   valid UTF-8.
     *
     * @see \Drupal\Component\Utility\Unicode::validateUtf8()
     *
     * @ingroup sanitization
     */
    public static function filter($string, ?array $allowed_html_tags = NULL) {
        if (is_null($allowed_html_tags)) {
            $allowed_html_tags = static::$htmlTags;
        }
        // Only operate on valid UTF-8 strings. This is necessary to prevent cross
        // site scripting issues on Internet Explorer 6.
        if (!Unicode::validateUtf8($string)) {
            return '';
        }
        // Remove NULL characters (ignored by some browsers).
        $string = str_replace(chr(0), '', $string);
        // Remove Netscape 4 JS entities.
        $string = preg_replace('%&\\s*\\{[^}]*(\\}\\s*;?|$)%', '', $string);
        // Defuse all HTML entities.
        $string = str_replace('&', '&', $string);
        // Change back only well-formed entities in our list of allowed html tags:
        // Decimal numeric entities.
        $string = preg_replace('/&#([0-9]+;)/', '&#\\1', $string);
        // Hexadecimal numeric entities.
        $string = preg_replace('/&#[Xx]0*((?:[0-9A-Fa-f]{2})+;)/', '&#x\\1', $string);
        // Named entities.
        $string = preg_replace('/&([A-Za-z][A-Za-z0-9]*;)/', '&\\1', $string);
        $allowed_html_tags = array_flip($allowed_html_tags);
        // Late static binding does not work inside anonymous functions.
        $class = static::class;
        $splitter = function ($matches) use ($allowed_html_tags, $class) {
            return $class::split($matches[1], $allowed_html_tags, $class);
        };
        // Strip any tags that are not in the list of allowed html tags.
        return preg_replace_callback('%
      (
      <(?=[^a-zA-Z!/])  # a lone <
      |                 # or
      <!--.*?-->        # a comment
      |                 # or
      <[^>]*(>|$)       # a string that starts with a <, up until the > or the end of the string
      |                 # or
      >                 # just a >
      )%x', $splitter, $string);
    }
    
    /**
     * Applies a very permissive XSS/HTML filter for admin-only use.
     *
     * Use only for fields where it is impractical to use the
     * whole filter system, but where some (mainly inline) mark-up
     * is desired (so \Drupal\Component\Utility\Html::escape() is
     * not acceptable).
     *
     * Allows all tags that can be used inside an HTML body, save
     * for scripts and styles.
     *
     * @param string $string
     *   The string to apply the filter to.
     *
     * @return string
     *   The filtered string.
     *
     * @ingroup sanitization
     *
     * @see \Drupal\Component\Utility\Xss::getAdminTagList()
     */
    public static function filterAdmin($string) {
        return static::filter($string, static::$adminTags);
    }
    
    /**
     * Processes an HTML tag.
     *
     * @param string $string
     *   The HTML tag to process.
     * @param array $html_tags
     *   An array where the keys are the allowed tags and the values are not
     *   used.
     * @param string $class
     *   The called class. This method is called from an anonymous function which
     *   breaks late static binding. See https://bugs.php.net/bug.php?id=66622 for
     *   more information.
     *
     * @return string
     *   If the element isn't allowed, an empty string. Otherwise, the cleaned up
     *   version of the HTML element.
     */
    protected static function split($string, array $html_tags, $class) {
        if (!str_starts_with($string, '<')) {
            // We matched a lone ">" character.
            return '&gt;';
        }
        elseif (strlen($string) == 1) {
            // We matched a lone "<" character.
            return '&lt;';
        }
        if (!preg_match('%^<\\s*(/\\s*)?([a-zA-Z0-9\\-]+)\\s*([^>]*)>?|(<!--.*?-->)$%', $string, $matches)) {
            // Seriously malformed.
            return '';
        }
        $slash = trim($matches[1]);
        $elem =& $matches[2];
        $attributes =& $matches[3];
        $comment =& $matches[4];
        if ($comment) {
            $elem = '!--';
        }
        // Defer to the ::needsRemoval() method to decide if the element is to be
        // removed. This allows the list of tags to be treated as either a list of
        // allowed tags or a list of denied tags.
        if ($class::needsRemoval($html_tags, $elem)) {
            return '';
        }
        if ($comment) {
            return $comment;
        }
        if ($slash != '') {
            return "</{$elem}>";
        }
        // Is there a closing XHTML slash at the end of the attributes?
        $attributes = preg_replace('%(\\s?)/\\s*$%', '\\1', $attributes, -1, $count);
        $xhtml_slash = $count ? ' /' : '';
        // Clean up attributes.
        $attr2 = implode(' ', $class::attributes($attributes));
        $attr2 = preg_replace('/[<>]/', '', $attr2);
        $attr2 = strlen($attr2) ? ' ' . $attr2 : '';
        return "<{$elem}{$attr2}{$xhtml_slash}>";
    }
    
    /**
     * Processes a string of HTML attributes.
     *
     * @param string $attributes
     *   The html attribute to process.
     *
     * @return string
     *   Cleaned up version of the HTML attributes.
     */
    protected static function attributes($attributes) {
        $attributes_array = [];
        $mode = 0;
        $attribute_name = '';
        $skip = FALSE;
        $skip_protocol_filtering = FALSE;
        while (strlen($attributes) != 0) {
            // Was the last operation successful?
            $working = 0;
            switch ($mode) {
                case 0:
                    // Attribute name, href for instance.
                    if (preg_match('/^([-a-zA-Z][-a-zA-Z0-9]*)/', $attributes, $match)) {
                        $attribute_name = strtolower($match[1]);
                        $skip = $attribute_name == 'style' || str_starts_with($attribute_name, 'on') || str_starts_with($attribute_name, '-') || strlen($attribute_name) > 96;
                        // Values for attributes of type URI should be filtered for
                        // potentially malicious protocols (for example, an href-attribute
                        // starting with "javascript:"). However, for some non-URI
                        // attributes performing this filtering causes valid and safe data
                        // to be mangled. We prevent this by skipping protocol filtering on
                        // such attributes.
                        // @see \Drupal\Component\Utility\UrlHelper::filterBadProtocol()
                        // @see https://www.w3.org/TR/html4/index/attributes.html
                        $skip_protocol_filtering = str_starts_with($attribute_name, 'data-') || in_array($attribute_name, [
                            'title',
                            'alt',
                            'rel',
                            'property',
                            'class',
                            'datetime',
                        ]);
                        $working = $mode = 1;
                        $attributes = preg_replace('/^[-a-zA-Z][-a-zA-Z0-9]*/', '', $attributes);
                    }
                    break;
                case 1:
                    // Equals sign or valueless ("selected").
                    if (preg_match('/^\\s*=\\s*/', $attributes)) {
                        $working = 1;
                        $mode = 2;
                        $attributes = preg_replace('/^\\s*=\\s*/', '', $attributes);
                        break;
                    }
                    if (preg_match('/^\\s+/', $attributes)) {
                        $working = 1;
                        $mode = 0;
                        if (!$skip) {
                            $attributes_array[] = $attribute_name;
                        }
                        $attributes = preg_replace('/^\\s+/', '', $attributes);
                    }
                    break;
                case 2:
                    // Once we've finished processing the attribute value continue to look
                    // for attributes.
                    $mode = 0;
                    $working = 1;
                    // Attribute value, a URL after href= for instance.
                    if (preg_match('/^"([^"]*)"(\\s+|$)/', $attributes, $match)) {
                        $value = $skip_protocol_filtering ? $match[1] : UrlHelper::filterBadProtocol($match[1]);
                        if (!$skip) {
                            $attributes_array[] = "{$attribute_name}=\"{$value}\"";
                        }
                        $attributes = preg_replace('/^"[^"]*"(\\s+|$)/', '', $attributes);
                        break;
                    }
                    if (preg_match("/^'([^']*)'(\\s+|\$)/", $attributes, $match)) {
                        $value = $skip_protocol_filtering ? $match[1] : UrlHelper::filterBadProtocol($match[1]);
                        if (!$skip) {
                            $attributes_array[] = "{$attribute_name}='{$value}'";
                        }
                        $attributes = preg_replace("/^'[^']*'(\\s+|\$)/", '', $attributes);
                        break;
                    }
                    if (preg_match("%^([^\\s\"']+)(\\s+|\$)%", $attributes, $match)) {
                        $value = $skip_protocol_filtering ? $match[1] : UrlHelper::filterBadProtocol($match[1]);
                        if (!$skip) {
                            $attributes_array[] = "{$attribute_name}=\"{$value}\"";
                        }
                        $attributes = preg_replace("%^[^\\s\"']+(\\s+|\$)%", '', $attributes);
                    }
                    break;
            }
            if ($working == 0) {
                // Not well-formed; remove and try again.
                $attributes = preg_replace('/
          ^
          (
          "[^"]*("|$)     # - a string that starts with a double quote, up until the next double quote or the end of the string
          |               # or
          \'[^\']*(\'|$)| # - a string that starts with a quote, up until the next quote or the end of the string
          |               # or
          \\S              # - a non-whitespace character
          )*              # any number of the above three
          \\s*             # any number of whitespaces
          /x', '', $attributes);
                $mode = 0;
            }
        }
        // The attribute list ends with a valueless attribute like "selected".
        if ($mode == 1 && !$skip) {
            $attributes_array[] = $attribute_name;
        }
        return $attributes_array;
    }
    
    /**
     * Whether this element needs to be removed altogether.
     *
     * @param string[] $html_tags
     *   The list of HTML tags.
     * @param string $elem
     *   The name of the HTML element.
     *
     * @return bool
     *   TRUE if this element needs to be removed.
     */
    protected static function needsRemoval(array $html_tags, $elem) {
        return !isset($html_tags[strtolower($elem)]);
    }
    
    /**
     * Gets the list of HTML tags allowed by Xss::filterAdmin().
     *
     * @return array
     *   The list of HTML tags allowed by filterAdmin().
     */
    public static function getAdminTagList() {
        return static::$adminTags;
    }
    
    /**
     * Gets the standard list of HTML tags allowed by Xss::filter().
     *
     * @return array
     *   The list of HTML tags allowed by Xss::filter().
     */
    public static function getHtmlTagList() {
        return static::$htmlTags;
    }

}

Members

Title Sort descending Modifiers Object type Summary Overrides
Xss::$adminTags protected static property The list of HTML tags allowed by filterAdmin().
Xss::$htmlTags protected static property The default list of HTML tags allowed by filter().
Xss::attributes protected static function Processes a string of HTML attributes.
Xss::filter public static function Filters HTML to prevent cross-site-scripting (XSS) vulnerabilities.
Xss::filterAdmin public static function Applies a very permissive XSS/HTML filter for admin-only use.
Xss::getAdminTagList public static function Gets the list of HTML tags allowed by Xss::filterAdmin().
Xss::getHtmlTagList public static function Gets the standard list of HTML tags allowed by Xss::filter().
Xss::needsRemoval protected static function Whether this element needs to be removed altogether. 1
Xss::split protected static function Processes an HTML tag.

API Navigation

  • Drupal Core 11.1.x
  • Topics
  • Classes
  • Functions
  • Constants
  • Globals
  • Files
  • Namespaces
  • Deprecated
  • Services
RSS feed
Powered by Drupal