335 lines
11 KiB
PHP
335 lines
11 KiB
PHP
<?php
|
|
|
|
/**
|
|
* @file
|
|
* Contains \Drupal\Component\Utility\SafeMarkup.
|
|
*/
|
|
|
|
namespace Drupal\Component\Utility;
|
|
|
|
/**
|
|
* Manages known safe strings for rendering at the theme layer.
|
|
*
|
|
* The Twig theme engine autoescapes string variables in the template, so it
|
|
* is possible for a string of markup to become double-escaped. SafeMarkup
|
|
* provides a store for known safe strings and methods to manage them
|
|
* throughout the page request.
|
|
*
|
|
* Strings sanitized by self::checkPlain() or Xss::filter() are automatically
|
|
* marked safe, as are markup strings created from render arrays via
|
|
* drupal_render().
|
|
*
|
|
* This class should be limited to internal use only. Module developers should
|
|
* instead use the appropriate
|
|
* @link sanitization sanitization functions @endlink or the
|
|
* @link theme_render theme and render systems @endlink so that the output can
|
|
* can be themed, escaped, and altered properly.
|
|
*
|
|
* @see TwigExtension::escapeFilter()
|
|
* @see twig_render_template()
|
|
* @see sanitization
|
|
* @see theme_render
|
|
*/
|
|
class SafeMarkup {
|
|
|
|
/**
|
|
* The list of safe strings.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected static $safeStrings = array();
|
|
|
|
/**
|
|
* Adds a string to a list of strings marked as secure.
|
|
*
|
|
* This method is for internal use. Do not use it to prevent escaping of
|
|
* markup; instead, use the appropriate
|
|
* @link sanitization sanitization functions @endlink or the
|
|
* @link theme_render theme and render systems @endlink so that the output
|
|
* can be themed, escaped, and altered properly.
|
|
*
|
|
* This marks strings as secure for the entire page render, not just the code
|
|
* or element that set it. Therefore, only valid HTML should be
|
|
* marked as safe (never partial markup). For example, you should never do:
|
|
* @code
|
|
* SafeMarkup::set('<');
|
|
* @endcode
|
|
* or:
|
|
* @code
|
|
* SafeMarkup::set('<script>');
|
|
* @endcode
|
|
*
|
|
* @param string $string
|
|
* The content to be marked as secure.
|
|
* @param string $strategy
|
|
* The escaping strategy used for this string. Two values are supported
|
|
* by default:
|
|
* - 'html': (default) The string is safe for use in HTML code.
|
|
* - 'all': The string is safe for all use cases.
|
|
* See the
|
|
* @link http://twig.sensiolabs.org/doc/filters/escape.html Twig escape documentation @endlink
|
|
* for more information on escaping strategies in Twig.
|
|
*
|
|
* @return string
|
|
* The input string that was marked as safe.
|
|
*/
|
|
public static function set($string, $strategy = 'html') {
|
|
$string = (string) $string;
|
|
static::$safeStrings[$string][$strategy] = TRUE;
|
|
return $string;
|
|
}
|
|
|
|
/**
|
|
* Checks if a string is safe to output.
|
|
*
|
|
* @param string|\Drupal\Component\Utility\SafeStringInterface $string
|
|
* The content to be checked.
|
|
* @param string $strategy
|
|
* The escaping strategy. See self::set(). Defaults to 'html'.
|
|
*
|
|
* @return bool
|
|
* TRUE if the string has been marked secure, FALSE otherwise.
|
|
*/
|
|
public static function isSafe($string, $strategy = 'html') {
|
|
// Do the instanceof checks first to save unnecessarily casting the object
|
|
// to a string.
|
|
return $string instanceOf SafeStringInterface || isset(static::$safeStrings[(string) $string][$strategy]) ||
|
|
isset(static::$safeStrings[(string) $string]['all']);
|
|
}
|
|
|
|
/**
|
|
* Adds previously retrieved known safe strings to the safe string list.
|
|
*
|
|
* This is useful for the batch and form APIs, where it is important to
|
|
* preserve the safe markup state across page requests. The strings will be
|
|
* added to any safe strings already marked for the current request.
|
|
*
|
|
* @param array $safe_strings
|
|
* A list of safe strings as previously retrieved by self::getAll().
|
|
*
|
|
* @throws \UnexpectedValueException
|
|
*/
|
|
public static function setMultiple(array $safe_strings) {
|
|
foreach ($safe_strings as $string => $strategies) {
|
|
foreach ($strategies as $strategy => $value) {
|
|
$string = (string) $string;
|
|
if ($value === TRUE) {
|
|
static::$safeStrings[$string][$strategy] = TRUE;
|
|
}
|
|
else {
|
|
// Danger - something is very wrong.
|
|
throw new \UnexpectedValueException('Only the value TRUE is accepted for safe strings');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Encodes special characters in a plain-text string for display as HTML.
|
|
*
|
|
* @param string $string
|
|
* A string.
|
|
*
|
|
* @return string
|
|
* The escaped string. If $string was already set as safe with
|
|
* self::set(), it won't be escaped again.
|
|
*/
|
|
public static function escape($string) {
|
|
return static::isSafe($string) ? $string : static::checkPlain($string);
|
|
}
|
|
|
|
/**
|
|
* Applies a very permissive XSS/HTML filter for admin-only use.
|
|
*
|
|
* @param string $string
|
|
* A string.
|
|
*
|
|
* @return string
|
|
* The escaped string. If $string was already set as safe with
|
|
* self::set(), it won't be escaped again.
|
|
*
|
|
* @see \Drupal\Component\Utility\Xss::filterAdmin()
|
|
*/
|
|
public static function checkAdminXss($string) {
|
|
return static::isSafe($string) ? $string : Xss::filterAdmin($string);
|
|
}
|
|
|
|
/**
|
|
* Gets all strings currently marked as safe.
|
|
*
|
|
* This is useful for the batch and form APIs, where it is important to
|
|
* preserve the safe markup state across page requests.
|
|
*
|
|
* @return array
|
|
* An array of strings currently marked safe.
|
|
*/
|
|
public static function getAll() {
|
|
return static::$safeStrings;
|
|
}
|
|
|
|
/**
|
|
* Encodes special characters in a plain-text string for display as HTML.
|
|
*
|
|
* Also validates strings as UTF-8. All processed strings are also
|
|
* automatically flagged as safe markup strings for rendering.
|
|
*
|
|
* @param string $text
|
|
* The text to be checked or processed.
|
|
*
|
|
* @return string
|
|
* An HTML safe version of $text, or an empty string if $text is not valid
|
|
* UTF-8.
|
|
*
|
|
* @ingroup sanitization
|
|
*
|
|
* @see drupal_validate_utf8()
|
|
*/
|
|
public static function checkPlain($text) {
|
|
$string = htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
|
|
static::$safeStrings[$string]['html'] = TRUE;
|
|
return $string;
|
|
}
|
|
|
|
/**
|
|
* Formats a string for HTML display by replacing variable placeholders.
|
|
*
|
|
* This function replaces variable placeholders in a string with the requested
|
|
* values and escapes the values so they can be safely displayed as HTML. It
|
|
* should be used on any unknown text that is intended to be printed to an
|
|
* HTML page (especially text that may have come from untrusted users, since
|
|
* in that case it prevents cross-site scripting and other security problems).
|
|
*
|
|
* In most cases, you should use t() rather than calling this function
|
|
* directly, since it will translate the text (on non-English-only sites) in
|
|
* addition to formatting it.
|
|
*
|
|
* @param string $string
|
|
* A string containing placeholders. The string itself is not escaped, any
|
|
* unsafe content must be in $args and inserted via placeholders.
|
|
* @param array $args
|
|
* An associative array of replacements to make. Occurrences in $string of
|
|
* any key in $args are replaced with the corresponding value, after
|
|
* optional sanitization and formatting. The type of sanitization and
|
|
* formatting depends on the first character of the key:
|
|
* - @variable: Escaped to HTML using self::escape(). Use this as the
|
|
* default choice for anything displayed on a page on the site.
|
|
* - %variable: Escaped to HTML and formatted using self::placeholder(),
|
|
* which makes the following HTML code:
|
|
* @code
|
|
* <em class="placeholder">text output here.</em>
|
|
* @endcode
|
|
* - !variable: Inserted as is, with no sanitization or formatting. Only
|
|
* use this when the resulting string is being generated for one of:
|
|
* - Non-HTML usage, such as a plain-text email.
|
|
* - Non-direct HTML output, such as a plain-text variable that will be
|
|
* printed as an HTML attribute value and therefore formatted with
|
|
* self::checkPlain() as part of that.
|
|
* - Some other special reason for suppressing sanitization.
|
|
*
|
|
* @return string
|
|
* The formatted string, which is marked as safe unless sanitization of an
|
|
* unsafe argument was suppressed (see above).
|
|
*
|
|
* @ingroup sanitization
|
|
*
|
|
* @see t()
|
|
*/
|
|
public static function format($string, array $args = array()) {
|
|
$safe = TRUE;
|
|
|
|
// Transform arguments before inserting them.
|
|
foreach ($args as $key => $value) {
|
|
switch ($key[0]) {
|
|
case '@':
|
|
// Escaped only.
|
|
$args[$key] = static::escape($value);
|
|
break;
|
|
|
|
case '%':
|
|
default:
|
|
// Escaped and placeholder.
|
|
$args[$key] = static::placeholder($value);
|
|
break;
|
|
|
|
case '!':
|
|
// Pass-through.
|
|
if (!static::isSafe($value)) {
|
|
$safe = FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
$output = strtr($string, $args);
|
|
if ($safe) {
|
|
static::$safeStrings[$output]['html'] = TRUE;
|
|
}
|
|
|
|
return $output;
|
|
}
|
|
|
|
/**
|
|
* Formats text for emphasized display in a placeholder inside a sentence.
|
|
*
|
|
* Used automatically by self::format().
|
|
*
|
|
* @param string $text
|
|
* The text to format (plain-text).
|
|
*
|
|
* @return string
|
|
* The formatted text (html).
|
|
*/
|
|
public static function placeholder($text) {
|
|
$string = '<em class="placeholder">' . static::escape($text) . '</em>';
|
|
static::$safeStrings[$string]['html'] = TRUE;
|
|
return $string;
|
|
}
|
|
|
|
/**
|
|
* Replaces all occurrences of the search string with the replacement string.
|
|
*
|
|
* Functions identically to str_replace(), but marks the returned output as
|
|
* safe if all the inputs and the subject have also been marked as safe.
|
|
*
|
|
* @param string|array $search
|
|
* The value being searched for. An array may be used to designate multiple
|
|
* values to search for.
|
|
* @param string|array $replace
|
|
* The replacement value that replaces found search values. An array may be
|
|
* used to designate multiple replacements.
|
|
* @param string $subject
|
|
* The string or array being searched and replaced on.
|
|
*
|
|
* @return string
|
|
* The passed subject with replaced values.
|
|
*/
|
|
public static function replace($search, $replace, $subject) {
|
|
$output = str_replace($search, $replace, $subject);
|
|
|
|
// If any replacement is unsafe, then the output is also unsafe, so just
|
|
// return the output.
|
|
if (!is_array($replace)) {
|
|
if (!SafeMarkup::isSafe($replace)) {
|
|
return $output;
|
|
}
|
|
}
|
|
else {
|
|
foreach ($replace as $replacement) {
|
|
if (!SafeMarkup::isSafe($replacement)) {
|
|
return $output;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If the subject is unsafe, then the output is as well, so return it.
|
|
if (!SafeMarkup::isSafe($subject)) {
|
|
return $output;
|
|
}
|
|
else {
|
|
// If we have reached this point, then all replacements were safe. If the
|
|
// subject was also safe, then mark the entire output as safe.
|
|
return SafeMarkup::set($output);
|
|
}
|
|
}
|
|
|
|
}
|