diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc index 7dc75f5..c521f69 100644 --- a/core/includes/bootstrap.inc +++ b/core/includes/bootstrap.inc @@ -1539,15 +1539,11 @@ function check_plain($text) { * * @return * TRUE if the text is valid UTF-8, FALSE if not. + * + * @see \Drupal\Component\Utility\String::validateUtf8() */ function drupal_validate_utf8($text) { - if (strlen($text) == 0) { - return TRUE; - } - // With the PCRE_UTF8 modifier 'u', preg_match() fails silently on strings - // containing invalid UTF-8 byte sequences. It does not reject character - // codes above U+10FFFF (represented by 4 or more octets), though. - return (preg_match('/^./us', $text) == 1); + return String::validateUtf8($text); } /** @@ -2130,6 +2126,9 @@ function _drupal_bootstrap_kernel() { $kernel = new DrupalKernel('prod', FALSE, drupal_classloader()); $kernel->boot(); } + + // Set the allowed protocols. + String::setAllowedProtocols(config('system.filter')->get('protocols')); } /** diff --git a/core/includes/common.inc b/core/includes/common.inc index 4668a90..9b29d6a 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -1,6 +1,7 @@ get('protocols') ?: array('http', 'https')); - } - - // Iteratively remove any invalid protocol found. - do { - $before = $uri; - $colonpos = strpos($uri, ':'); - if ($colonpos > 0) { - // We found a colon, possibly a protocol. Verify. - $protocol = substr($uri, 0, $colonpos); - // If a colon is preceded by a slash, question mark or hash, it cannot - // possibly be part of the URL scheme. This must be a relative URL, which - // inherits the (safe) protocol of the base document. - if (preg_match('![/?#]!', $protocol)) { - break; - } - // Check if this is a disallowed protocol. Per RFC2616, section 3.2.3 - // (URI Comparison) scheme comparison must be case-insensitive. - if (!isset($allowed_protocols[strtolower($protocol)])) { - $uri = substr($uri, $colonpos + 1); - } - } - } while ($before != $uri); - - return $uri; + return String::stripDangerousProtocols($uri); } /** @@ -962,10 +933,10 @@ function drupal_strip_dangerous_protocols($uri) { * Drupal\Core\Template\Attribute, call drupal_strip_dangerous_protocols() * instead. * - * @see drupal_strip_dangerous_protocols() + * @see \Drupal\Component\Utility\String::stripDangerousProtocols() */ function check_url($uri) { - return check_plain(drupal_strip_dangerous_protocols($uri)); + return String::checkPlain(String::stripDangerousProtocols($uri)); } /** @@ -977,9 +948,17 @@ function check_url($uri) { * * 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. + * + * * @see \Drupal\Component\Utility\String::filterXssAdmin() */ function filter_xss_admin($string) { - return filter_xss($string, array('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')); + return String::filterXssAdmin($string); } /** @@ -1006,217 +985,12 @@ function filter_xss_admin($string) { * valid UTF-8. * * @see drupal_validate_utf8() + * @see \Drupal\Component\Utility\String::filterXss() + * * @ingroup sanitization */ function filter_xss($string, $allowed_tags = array('a', 'em', 'strong', 'cite', 'blockquote', 'code', 'ul', 'ol', 'li', 'dl', 'dt', 'dd')) { - // Only operate on valid UTF-8 strings. This is necessary to prevent cross - // site scripting issues on Internet Explorer 6. - if (!drupal_validate_utf8($string)) { - return ''; - } - // Store the text format. - _filter_xss_split($allowed_tags, TRUE); - // 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 whitelist: - // 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); - - 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', '_filter_xss_split', $string); -} - -/** - * Processes an HTML tag. - * - * @param $m - * An array with various meaning depending on the value of $store. - * If $store is TRUE then the array contains the allowed tags. - * If $store is FALSE then the array has one element, the HTML tag to process. - * @param $store - * Whether to store $m. - * - * @return - * If the element isn't allowed, an empty string. Otherwise, the cleaned up - * version of the HTML element. - */ -function _filter_xss_split($m, $store = FALSE) { - static $allowed_html; - - if ($store) { - $allowed_html = array_flip($m); - return; - } - - $string = $m[1]; - - if (substr($string, 0, 1) != '<') { - // We matched a lone ">" character. - return '>'; - } - elseif (strlen($string) == 1) { - // We matched a lone "<" character. - return '<'; - } - - if (!preg_match('%^<\s*(/\s*)?([a-zA-Z0-9]+)([^>]*)>?|()$%', $string, $matches)) { - // Seriously malformed. - return ''; - } - - $slash = trim($matches[1]); - $elem = &$matches[2]; - $attrlist = &$matches[3]; - $comment = &$matches[4]; - - if ($comment) { - $elem = '!--'; - } - - if (!isset($allowed_html[strtolower($elem)])) { - // Disallowed HTML element. - return ''; - } - - if ($comment) { - return $comment; - } - - if ($slash != '') { - return ""; - } - - // Is there a closing XHTML slash at the end of the attributes? - $attrlist = preg_replace('%(\s?)/\s*$%', '\1', $attrlist, -1, $count); - $xhtml_slash = $count ? ' /' : ''; - - // Clean up attributes. - $attr2 = implode(' ', _filter_xss_attributes($attrlist)); - $attr2 = preg_replace('/[<>]/', '', $attr2); - $attr2 = strlen($attr2) ? ' ' . $attr2 : ''; - - return "<$elem$attr2$xhtml_slash>"; -} - -/** - * Processes a string of HTML attributes. - * - * @return - * Cleaned up version of the HTML attributes. - */ -function _filter_xss_attributes($attr) { - $attrarr = array(); - $mode = 0; - $attrname = ''; - - while (strlen($attr) != 0) { - // Was the last operation successful? - $working = 0; - - switch ($mode) { - case 0: - // Attribute name, href for instance. - if (preg_match('/^([-a-zA-Z]+)/', $attr, $match)) { - $attrname = strtolower($match[1]); - $skip = ($attrname == 'style' || substr($attrname, 0, 2) == 'on'); - $working = $mode = 1; - $attr = preg_replace('/^[-a-zA-Z]+/', '', $attr); - } - break; - - case 1: - // Equals sign or valueless ("selected"). - if (preg_match('/^\s*=\s*/', $attr)) { - $working = 1; $mode = 2; - $attr = preg_replace('/^\s*=\s*/', '', $attr); - break; - } - - if (preg_match('/^\s+/', $attr)) { - $working = 1; $mode = 0; - if (!$skip) { - $attrarr[] = $attrname; - } - $attr = preg_replace('/^\s+/', '', $attr); - } - break; - - case 2: - // Attribute value, a URL after href= for instance. - if (preg_match('/^"([^"]*)"(\s+|$)/', $attr, $match)) { - $thisval = filter_xss_bad_protocol($match[1]); - - if (!$skip) { - $attrarr[] = "$attrname=\"$thisval\""; - } - $working = 1; - $mode = 0; - $attr = preg_replace('/^"[^"]*"(\s+|$)/', '', $attr); - break; - } - - if (preg_match("/^'([^']*)'(\s+|$)/", $attr, $match)) { - $thisval = filter_xss_bad_protocol($match[1]); - - if (!$skip) { - $attrarr[] = "$attrname='$thisval'"; - } - $working = 1; $mode = 0; - $attr = preg_replace("/^'[^']*'(\s+|$)/", '', $attr); - break; - } - - if (preg_match("%^([^\s\"']+)(\s+|$)%", $attr, $match)) { - $thisval = filter_xss_bad_protocol($match[1]); - - if (!$skip) { - $attrarr[] = "$attrname=\"$thisval\""; - } - $working = 1; $mode = 0; - $attr = preg_replace("%^[^\s\"']+(\s+|$)%", '', $attr); - } - break; - } - - if ($working == 0) { - // Not well formed; remove and try again. - $attr = 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', '', $attr); - $mode = 0; - } - } - - // The attribute list ends with a valueless attribute like "selected". - if ($mode == 1 && !$skip) { - $attrarr[] = $attrname; - } - return $attrarr; + return String::filterXss($string, $allowed_tags); } /** @@ -1224,27 +998,14 @@ function _filter_xss_attributes($attr) { * * @param $string * The string with the attribute value. - * @param $decode - * (deprecated) Whether to decode entities in the $string. Set to FALSE if the - * $string is in plain text, TRUE otherwise. Defaults to TRUE. This parameter - * is deprecated and will be removed in Drupal 8. To process a plain-text URI, - * call drupal_strip_dangerous_protocols() or check_url() instead. * * @return * Cleaned up and HTML-escaped version of $string. + * + * @see \Drupal\Component\Utility\String::filterBadProtocol() */ -function filter_xss_bad_protocol($string, $decode = TRUE) { - // Get the plain text representation of the attribute value (i.e. its meaning). - // @todo Remove the $decode parameter in Drupal 8, and always assume an HTML - // string that needs decoding. - if ($decode) { - if (!function_exists('decode_entities')) { - require_once __DIR__ . '/unicode.inc'; - } - - $string = decode_entities($string); - } - return check_plain(drupal_strip_dangerous_protocols($string)); +function filter_xss_bad_protocol($string) { + return String::filterBadProtocol($string); } /** diff --git a/core/lib/Drupal/Component/Utility/String.php b/core/lib/Drupal/Component/Utility/String.php index a6df8fe..6bfd4e5 100644 --- a/core/lib/Drupal/Component/Utility/String.php +++ b/core/lib/Drupal/Component/Utility/String.php @@ -13,6 +13,23 @@ class String { /** + * The list of html tags allowed by filterXssAdmin(). + * + * @var array + * + * @see \Drupal\Component\Utility\String::filterXssAdmin() + */ + protected static $xssAdminTags = array('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 list of allowed protocols configured for the drupal site. + * + * @var array + */ + protected static $allowedProtocols = array('http', 'https'); + + /** * Encodes special characters in a plain-text string for display as HTML. * * Also validates strings as UTF-8. @@ -25,6 +42,7 @@ class String { * valid UTF-8. * * @see drupal_validate_utf8() + * * @ingroup sanitization */ public static function checkPlain($text) { @@ -122,4 +140,370 @@ public static function placeholder($text) { return '' . static::checkPlain($text) . ''; } + /** + * Checks whether a string is valid UTF-8. + * + * All functions designed to filter input should use drupal_validate_utf8 + * to ensure they operate on valid UTF-8 strings to prevent bypass of the + * filter. + * + * When text containing an invalid UTF-8 lead byte (0xC0 - 0xFF) is presented + * as UTF-8 to Internet Explorer 6, the program may misinterpret subsequent + * bytes. When these subsequent bytes are HTML control characters such as + * quotes or angle brackets, parts of the text that were deemed safe by filters + * end up in locations that are potentially unsafe; An onerror attribute that + * is outside of a tag, and thus deemed safe by a filter, can be interpreted + * by the browser as if it were inside the tag. + * + * The function does not return FALSE for strings containing character codes + * above U+10FFFF, even though these are prohibited by RFC 3629. + * + * @param string $text + * The text to check. + * + * @return bool + * TRUE if the text is valid UTF-8, FALSE if not. + */ + public static function validateUtf8($text) { + if (strlen($text) == 0) { + return TRUE; + } + // With the PCRE_UTF8 modifier 'u', preg_match() fails silently on strings + // containing invalid UTF-8 byte sequences. It does not reject character + // codes above U+10FFFF (represented by 4 or more octets), though. + return (preg_match('/^./us', $text) == 1); + } + + /** + * 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 check_plain() 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. + */ + public static function filterXssAdmin($string) { + return static::filterXss($string, static::$xssAdminTags); + } + + /** + * 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 + * The string with raw HTML in it. It will be stripped of everything that can + * cause an XSS attack. + * @param array $allowed_tags + * An array of allowed tags. + * + * @return mixed|string An XSS safe version of $string, or an empty string if $string is not@see drupal_validate_utf8() + * + * @ingroup sanitization + */ + public static function filterXss($string, $allowed_tags = array('a', 'em', 'strong', 'cite', 'blockquote', 'code', 'ul', 'ol', 'li', 'dl', 'dt', 'dd')) { + // Only operate on valid UTF-8 strings. This is necessary to prevent cross + // site scripting issues on Internet Explorer 6. + if (!static::validateUtf8($string)) { + return ''; + } + // Store the text format. + static::xssSplit($allowed_tags, TRUE); + // 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 whitelist: + // 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); + + 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', array('\Drupal\Component\Utility\String', 'xssSplit'), $string); + } + + /** + * Processes an HTML tag. + * + * @param array $m + * An array with various meaning depending on the value of $store. + * If $store is TRUE then the array contains the allowed tags. + * If $store is FALSE then the array has one element, the HTML tag to process. + * @param bool $store + * Whether to store $m. + * + * @return string + * If the element isn't allowed, an empty string. Otherwise, the cleaned up + * version of the HTML element. + */ + protected static function xssSplit($m, $store = FALSE) { + static $allowed_html; + + if ($store) { + $allowed_html = array_flip($m); + return; + } + + $string = $m[1]; + + if (substr($string, 0, 1) != '<') { + // We matched a lone ">" character. + return '>'; + } + elseif (strlen($string) == 1) { + // We matched a lone "<" character. + return '<'; + } + + if (!preg_match('%^<\s*(/\s*)?([a-zA-Z0-9]+)([^>]*)>?|()$%', $string, $matches)) { + // Seriously malformed. + return ''; + } + + $slash = trim($matches[1]); + $elem = &$matches[2]; + $attrlist = &$matches[3]; + $comment = &$matches[4]; + + if ($comment) { + $elem = '!--'; + } + + if (!isset($allowed_html[strtolower($elem)])) { + // Disallowed HTML element. + return ''; + } + + if ($comment) { + return $comment; + } + + if ($slash != '') { + return ""; + } + + // Is there a closing XHTML slash at the end of the attributes? + $attrlist = preg_replace('%(\s?)/\s*$%', '\1', $attrlist, -1, $count); + $xhtml_slash = $count ? ' /' : ''; + + // Clean up attributes. + $attr2 = implode(' ', static::xssAttributes($attrlist)); + $attr2 = preg_replace('/[<>]/', '', $attr2); + $attr2 = strlen($attr2) ? ' ' . $attr2 : ''; + + return "<$elem$attr2$xhtml_slash>"; + } + + /** + * Processes a string of HTML attributes. + * + * @param string $attr + * The html attribute to process. + * + * @return string + * Cleaned up version of the HTML attributes. + */ + protected static function xssAttributes($attr) { + $attrarr = array(); + $mode = 0; + $attrname = ''; + + while (strlen($attr) != 0) { + // Was the last operation successful? + $working = 0; + + switch ($mode) { + case 0: + // Attribute name, href for instance. + if (preg_match('/^([-a-zA-Z]+)/', $attr, $match)) { + $attrname = strtolower($match[1]); + $skip = ($attrname == 'style' || substr($attrname, 0, 2) == 'on'); + $working = $mode = 1; + $attr = preg_replace('/^[-a-zA-Z]+/', '', $attr); + } + break; + + case 1: + // Equals sign or valueless ("selected"). + if (preg_match('/^\s*=\s*/', $attr)) { + $working = 1; $mode = 2; + $attr = preg_replace('/^\s*=\s*/', '', $attr); + break; + } + + if (preg_match('/^\s+/', $attr)) { + $working = 1; $mode = 0; + if (!$skip) { + $attrarr[] = $attrname; + } + $attr = preg_replace('/^\s+/', '', $attr); + } + break; + + case 2: + // Attribute value, a URL after href= for instance. + if (preg_match('/^"([^"]*)"(\s+|$)/', $attr, $match)) { + $thisval = filter_xss_bad_protocol($match[1]); + + if (!$skip) { + $attrarr[] = "$attrname=\"$thisval\""; + } + $working = 1; + $mode = 0; + $attr = preg_replace('/^"[^"]*"(\s+|$)/', '', $attr); + break; + } + + if (preg_match("/^'([^']*)'(\s+|$)/", $attr, $match)) { + $thisval = filter_xss_bad_protocol($match[1]); + + if (!$skip) { + $attrarr[] = "$attrname='$thisval'"; + } + $working = 1; $mode = 0; + $attr = preg_replace("/^'[^']*'(\s+|$)/", '', $attr); + break; + } + + if (preg_match("%^([^\s\"']+)(\s+|$)%", $attr, $match)) { + $thisval = filter_xss_bad_protocol($match[1]); + + if (!$skip) { + $attrarr[] = "$attrname=\"$thisval\""; + } + $working = 1; $mode = 0; + $attr = preg_replace("%^[^\s\"']+(\s+|$)%", '', $attr); + } + break; + } + + if ($working == 0) { + // Not well formed; remove and try again. + $attr = 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', '', $attr); + $mode = 0; + } + } + + // The attribute list ends with a valueless attribute like "selected". + if ($mode == 1 && !$skip) { + $attrarr[] = $attrname; + } + return $attrarr; + } + + /** + * Processes an HTML attribute value and strips dangerous protocols from URLs. + * + * @param string $string + * The string with the attribute value. + * + * @return string + * Cleaned up and HTML-escaped version of $string. + */ + public static function filterBadProtocol($string) { + // Get the plain text representation of the attribute value (i.e. its meaning). + $string = static::decodeEntities($string); + + return static::checkPlain(static::stripDangerousProtocols($string)); + } + + /** + * Sets the allowed protocols. + * + * @param array $protocols + * An array of protocols, for example http, https and irc. + */ + public static function setAllowedProtocols(array $protocols = array()) { + static::$allowedProtocols = $protocols; + } + + /** + * Strips dangerous protocols (e.g. 'javascript:') from a URI. + * + * This function must be called for all URIs within user-entered input prior + * to being output to an HTML attribute value. It is often called as part of + * check_url() or filter_xss(), but those functions return an HTML-encoded + * string, so this function can be called independently when the output needs to + * be a plain-text string for passing to t(), l(), + * Drupal\Core\Template\Attribute, or another function that will call + * check_plain() separately. + * + * @param string $uri + * A plain-text URI that might contain dangerous protocols. + * + * @return string + * A plain-text URI stripped of dangerous protocols. As with all plain-text + * strings, this return value must not be output to an HTML page without + * check_plain() being called on it. However, it can be passed to functions + * expecting plain-text strings. + * + * @see check_url() + */ + public static function stripDangerousProtocols($uri) { + $allowed_protocols = array_flip(static::$allowedProtocols); + + // Iteratively remove any invalid protocol found. + do { + $before = $uri; + $colonpos = strpos($uri, ':'); + if ($colonpos > 0) { + // We found a colon, possibly a protocol. Verify. + $protocol = substr($uri, 0, $colonpos); + // If a colon is preceded by a slash, question mark or hash, it cannot + // possibly be part of the URL scheme. This must be a relative URL, which + // inherits the (safe) protocol of the base document. + if (preg_match('![/?#]!', $protocol)) { + break; + } + // Check if this is a disallowed protocol. Per RFC2616, section 3.2.3 + // (URI Comparison) scheme comparison must be case-insensitive. + if (!isset($allowed_protocols[strtolower($protocol)])) { + $uri = substr($uri, $colonpos + 1); + } + } + } while ($before != $uri); + + return $uri; + } + } diff --git a/core/modules/system/lib/Drupal/system/Tests/Common/ColorTest.php b/core/modules/system/lib/Drupal/system/Tests/Common/ColorTest.php deleted file mode 100644 index 2c32b31..0000000 --- a/core/modules/system/lib/Drupal/system/Tests/Common/ColorTest.php +++ /dev/null @@ -1,100 +0,0 @@ - 'Color conversion', - 'description' => 'Tests Color utility class conversions.', - 'group' => 'Common', - ); - } - - /** - * Tests Color::hexToRgb(). - */ - function testHexToRgb() { - // Any invalid arguments should throw an exception. - $values = array('', '-1', '1', '12', '12345', '1234567', '123456789', '123456789a', 'foo'); - // Duplicate all invalid value tests with additional '#' prefix. - // The '#' prefix inherently turns the data type into a string. - foreach ($values as $value) { - $values[] = '#' . $value; - } - // Add invalid data types (hex value must be a string). - $values = array_merge($values, array( - 1, 12, 1234, 12345, 123456, 1234567, 12345678, 123456789, 123456789, - -1, PHP_INT_MAX, PHP_INT_MAX + 1, -PHP_INT_MAX, - 0x0, 0x010, - )); - - foreach ($values as $test) { - $this->assertFalse(Color::validateHex($test), var_export($test, TRUE) . ' is invalid.'); - try { - Color::hexToRgb($test); - $this->fail('Color::hexToRgb(' . var_export($test, TRUE) . ') did not throw an exception.'); - } - catch (\InvalidArgumentException $e) { - $this->pass('Color::hexToRgb(' . var_export($test, TRUE) . ') threw an exception.'); - } - } - - // PHP automatically casts a numeric array key into an integer. - // Since hex values may consist of 0-9 only, they need to be defined as - // array values. - $tests = array( - // Shorthands without alpha. - array('hex' => '#000', 'rgb' => array('red' => 0, 'green' => 0, 'blue' => 0)), - array('hex' => '#fff', 'rgb' => array('red' => 255, 'green' => 255, 'blue' => 255)), - array('hex' => '#abc', 'rgb' => array('red' => 170, 'green' => 187, 'blue' => 204)), - array('hex' => 'cba', 'rgb' => array('red' => 204, 'green' => 187, 'blue' => 170)), - // Full without alpha. - array('hex' => '#000000', 'rgb' => array('red' => 0, 'green' => 0, 'blue' => 0)), - array('hex' => '#ffffff', 'rgb' => array('red' => 255, 'green' => 255, 'blue' => 255)), - array('hex' => '#010203', 'rgb' => array('red' => 1, 'green' => 2, 'blue' => 3)), - ); - foreach ($tests as $test) { - $result = Color::hexToRgb($test['hex']); - $this->assertIdentical($result, $test['rgb']); - } - } - - /** - * Tests Color::rgbToHex(). - */ - function testRgbToHex() { - $tests = array( - '#000000' => array('red' => 0, 'green' => 0, 'blue' => 0), - '#ffffff' => array('red' => 255, 'green' => 255, 'blue' => 255), - '#777777' => array('red' => 119, 'green' => 119, 'blue' => 119), - '#010203' => array('red' => 1, 'green' => 2, 'blue' => 3), - ); - // Input using named RGB array (e.g., as returned by Color::hexToRgb()). - foreach ($tests as $expected => $rgb) { - $this->assertIdentical(Color::rgbToHex($rgb), $expected); - } - // Input using indexed RGB array (e.g.: array(10, 10, 10)). - foreach ($tests as $expected => $rgb) { - $rgb = array_values($rgb); - $this->assertIdentical(Color::rgbToHex($rgb), $expected); - } - // Input using CSS RGB string notation (e.g.: 10, 10, 10). - foreach ($tests as $expected => $rgb) { - $rgb = implode(', ', $rgb); - $this->assertIdentical(Color::rgbToHex($rgb), $expected); - } - } -}