예제 #1
0
 /**
  * Checks that harmful protocols are stripped.
  */
 function testBadProtocolStripping()
 {
     // Ensure that check_url() strips out harmful protocols, and encodes for
     // HTML.
     // Ensure \Drupal\Component\Utility\UrlHelper::stripDangerousProtocols() can
     // be used to return a plain-text string stripped of harmful protocols.
     $url = 'javascript:http://www.example.com/?x=1&y=2';
     $expected_plain = 'http://www.example.com/?x=1&y=2';
     $expected_html = 'http://www.example.com/?x=1&y=2';
     $this->assertIdentical(check_url($url), $expected_html, 'check_url() filters a URL and encodes it for HTML.');
     $this->assertIdentical(UrlHelper::stripDangerousProtocols($url), $expected_plain, '\\Drupal\\Component\\Utility\\Url::stripDangerousProtocols() filters a URL and returns plain text.');
 }
예제 #2
0
 /**
  * Replaces placeholders in a string with values.
  *
  * @param string $string
  *   A string containing placeholders. The string itself is expected to be
  *   safe and correct HTML. Any unsafe content must be in $args and
  *   inserted via placeholders.
  * @param array $args
  *   An associative array of replacements. Each array key should be the same
  *   as a placeholder in $string. The corresponding value should be a string
  *   or an object that implements
  *   \Drupal\Component\Render\MarkupInterface. The value replaces the
  *   placeholder in $string. Sanitization and formatting will be done before
  *   replacement. The type of sanitization and formatting depends on the first
  *   character of the key:
  *   - @variable: When the placeholder replacement value is:
  *     - A string, the replaced value in the returned string will be sanitized
  *       using \Drupal\Component\Utility\Html::escape().
  *     - A MarkupInterface object, the replaced value in the returned string
  *       will not be sanitized.
  *     - A MarkupInterface object cast to a string, the replaced value in the
  *       returned string be forcibly sanitized using
  *       \Drupal\Component\Utility\Html::escape().
  *       @code
  *         $this->placeholderFormat('This will force HTML-escaping of the replacement value: @text', ['@text' => (string) $safe_string_interface_object));
  *       @endcode
  *     Use this placeholder as the default choice for anything displayed on
  *     the site, but not within HTML attributes, JavaScript, or CSS. Doing so
  *     is a security risk.
  *   - %variable: Use when the replacement value is to be wrapped in <em>
  *     tags.
  *     A call like:
  *     @code
  *       $string = "%output_text";
  *       $arguments = ['output_text' => 'text output here.'];
  *       $this->placeholderFormat($string, $arguments);
  *     @endcode
  *     makes the following HTML code:
  *     @code
  *       <em class="placeholder">text output here.</em>
  *     @endcode
  *     As with @variable, do not use this within HTML attributes, JavaScript,
  *     or CSS. Doing so is a security risk.
  *   - :variable: Return value is escaped with
  *     \Drupal\Component\Utility\Html::escape() and filtered for dangerous
  *     protocols using UrlHelper::stripDangerousProtocols(). Use this when
  *     using the "href" attribute, ensuring the attribute value is always
  *     wrapped in quotes:
  *     @code
  *     // Secure (with quotes):
  *     $this->placeholderFormat('<a href=":url">@variable</a>', [':url' => $url, '@variable' => $variable]);
  *     // Insecure (without quotes):
  *     $this->placeholderFormat('<a href=:url>@variable</a>', [':url' => $url, '@variable' => $variable]);
  *     @endcode
  *     When ":variable" comes from arbitrary user input, the result is secure,
  *     but not guaranteed to be a valid URL (which means the resulting output
  *     could fail HTML validation). To guarantee a valid URL, use
  *     Url::fromUri($user_input)->toString() (which either throws an exception
  *     or returns a well-formed URL) before passing the result into a
  *     ":variable" placeholder.
  *
  * @return string
  *   A formatted HTML string with the placeholders replaced.
  *
  * @ingroup sanitization
  *
  * @see \Drupal\Core\StringTranslation\TranslatableMarkup
  * @see \Drupal\Core\StringTranslation\PluralTranslatableMarkup
  * @see \Drupal\Component\Utility\Html::escape()
  * @see \Drupal\Component\Utility\UrlHelper::stripDangerousProtocols()
  * @see \Drupal\Core\Url::fromUri()
  */
 protected static function placeholderFormat($string, array $args)
 {
     // Transform arguments before inserting them.
     foreach ($args as $key => $value) {
         switch ($key[0]) {
             case '@':
                 // Escape if the value is not an object from a class that implements
                 // \Drupal\Component\Render\MarkupInterface, for example strings will
                 // be escaped.
                 // Strings that are safe within HTML fragments, but not within other
                 // contexts, may still be an instance of
                 // \Drupal\Component\Render\MarkupInterface, so this placeholder type
                 // must not be used within HTML attributes, JavaScript, or CSS.
                 $args[$key] = static::placeholderEscape($value);
                 break;
             case ':':
                 // Strip URL protocols that can be XSS vectors.
                 $value = UrlHelper::stripDangerousProtocols($value);
                 // Escape unconditionally, without checking whether the value is an
                 // instance of \Drupal\Component\Render\MarkupInterface. This forces
                 // characters that are unsafe for use in an "href" HTML attribute to
                 // be encoded. If a caller wants to pass a value that is extracted
                 // from HTML and therefore is already HTML encoded, it must invoke
                 // \Drupal\Component\Render\OutputStrategyInterface::renderFromHtml()
                 // on it prior to passing it in as a placeholder value of this type.
                 // @todo Add some advice and stronger warnings.
                 //   https://www.drupal.org/node/2569041.
                 $args[$key] = Html::escape($value);
                 break;
             case '%':
                 // Similarly to @, escape non-safe values. Also, add wrapping markup
                 // in order to render as a placeholder. Not for use within attributes,
                 // per the warning above about
                 // \Drupal\Component\Render\MarkupInterface and also due to the
                 // wrapping markup.
                 $args[$key] = '<em class="placeholder">' . static::placeholderEscape($value) . '</em>';
                 break;
             default:
                 // We do not trigger an error for placeholder that start with an
                 // alphabetic character.
                 if (!ctype_alpha($key[0])) {
                     // We trigger an error as we may want to introduce new placeholders
                     // in the future without breaking backward compatibility.
                     trigger_error('Invalid placeholder (' . $key . ') in string: ' . $string, E_USER_ERROR);
                 }
                 break;
         }
     }
     return strtr($string, $args);
 }
예제 #3
0
 /**
  * {@inheritdoc}
  */
 public function sanitizeValue($value, $type = NULL)
 {
     switch ($type) {
         case 'xss':
             $value = Xss::filter($value);
             break;
         case 'xss_admin':
             $value = Xss::filterAdmin($value);
             break;
         case 'url':
             $value = Html::escape(UrlHelper::stripDangerousProtocols($value));
             break;
         default:
             $value = Html::escape($value);
             break;
     }
     return ViewsRenderPipelineMarkup::create($value);
 }
예제 #4
0
 /**
  * {@inheritdoc}
  */
 public function sanitizeValue($value, $type = NULL)
 {
     switch ($type) {
         case 'xss':
             $value = Xss::filter($value);
             break;
         case 'xss_admin':
             $value = Xss::filterAdmin($value);
             break;
         case 'url':
             $value = SafeMarkup::checkPlain(UrlHelper::stripDangerousProtocols($value));
             break;
         default:
             $value = SafeMarkup::checkPlain($value);
             break;
     }
     return $value;
 }
예제 #5
0
 /**
  * {@inheritdoc}
  */
 public function generateFromPath($path = NULL, $options = array(), $collect_cacheability_metadata = FALSE)
 {
     $generated_url = $collect_cacheability_metadata ? new GeneratedUrl() : NULL;
     $request = $this->requestStack->getCurrentRequest();
     $current_base_path = $request->getBasePath() . '/';
     $current_base_url = $request->getSchemeAndHttpHost() . $current_base_path;
     $current_script_path = '';
     $base_path_with_script = $request->getBaseUrl();
     if (!empty($base_path_with_script)) {
         $script_name = $request->getScriptName();
         if (strpos($base_path_with_script, $script_name) !== FALSE) {
             $current_script_path = ltrim(substr($script_name, strlen($current_base_path)), '/') . '/';
         }
     }
     // Merge in defaults.
     $options += array('fragment' => '', 'query' => array(), 'absolute' => FALSE, 'prefix' => '');
     // A duplicate of the code from
     // \Drupal\Component\Utility\UrlHelper::isExternal() to avoid needing
     // another function call, since performance inside url() is critical.
     if (!isset($options['external'])) {
         $colonpos = strpos($path, ':');
         // Avoid calling drupal_strip_dangerous_protocols() if there is any slash
         // (/), hash (#) or question_mark (?) before the colon (:) occurrence -
         // if any - as this would clearly mean it is not a URL. If the path starts
         // with 2 slashes then it is always considered an external URL without an
         // explicit protocol part.
         $options['external'] = strpos($path, '//') === 0 || $colonpos !== FALSE && !preg_match('![/?#]!', substr($path, 0, $colonpos)) && UrlHelper::stripDangerousProtocols($path) == $path;
     }
     if (isset($options['fragment']) && $options['fragment'] !== '') {
         $options['fragment'] = '#' . $options['fragment'];
     }
     if ($options['external']) {
         // Split off the fragment.
         if (strpos($path, '#') !== FALSE) {
             list($path, $old_fragment) = explode('#', $path, 2);
             // If $options contains no fragment, take it over from the path.
             if (isset($old_fragment) && !$options['fragment']) {
                 $options['fragment'] = '#' . $old_fragment;
             }
         }
         // Append the query.
         if ($options['query']) {
             $path .= (strpos($path, '?') !== FALSE ? '&' : '?') . UrlHelper::buildQuery($options['query']);
         }
         if (isset($options['https'])) {
             if ($options['https'] === TRUE) {
                 $path = str_replace('http://', 'https://', $path);
             } elseif ($options['https'] === FALSE) {
                 $path = str_replace('https://', 'http://', $path);
             }
         }
         // Reassemble.
         $url = $path . $options['fragment'];
         return $collect_cacheability_metadata ? $generated_url->setGeneratedUrl($url) : $url;
     } else {
         $path = ltrim($this->processPath($path, $options, $generated_url), '/');
     }
     if (!isset($options['script'])) {
         $options['script'] = $current_script_path;
     }
     // The base_url might be rewritten from the language rewrite in domain mode.
     if (!isset($options['base_url'])) {
         if (isset($options['https'])) {
             if ($options['https'] === TRUE) {
                 $options['base_url'] = str_replace('http://', 'https://', $current_base_url);
                 $options['absolute'] = TRUE;
             } elseif ($options['https'] === FALSE) {
                 $options['base_url'] = str_replace('https://', 'http://', $current_base_url);
                 $options['absolute'] = TRUE;
             }
         } else {
             $options['base_url'] = $current_base_url;
         }
     } elseif (rtrim($options['base_url'], '/') == $options['base_url']) {
         $options['base_url'] .= '/';
     }
     $base = $options['absolute'] ? $options['base_url'] : $current_base_path;
     $prefix = empty($path) ? rtrim($options['prefix'], '/') : $options['prefix'];
     if ($options['absolute'] && $collect_cacheability_metadata) {
         $generated_url->addCacheContexts(['url.site']);
     }
     $path = str_replace('%2F', '/', rawurlencode($prefix . $path));
     $query = $options['query'] ? '?' . UrlHelper::buildQuery($options['query']) : '';
     $url = $base . $options['script'] . $path . $query . $options['fragment'];
     return $collect_cacheability_metadata ? $generated_url->setGeneratedUrl($url) : $url;
 }
예제 #6
0
 /**
  * Tests dangerous url protocol filtering.
  *
  * @dataProvider providerTestStripDangerousProtocols
  * @covers ::setAllowedProtocols
  * @covers ::stripDangerousProtocols
  *
  * @param string $uri
  *    Protocol URI.
  * @param string $expected
  *    Expected escaped value.
  * @param array $protocols
  *    Protocols to allow.
  */
 public function testStripDangerousProtocols($uri, $expected, $protocols)
 {
     UrlHelper::setAllowedProtocols($protocols);
     $stripped = UrlHelper::stripDangerousProtocols($uri);
     $this->assertEquals($expected, $stripped);
 }
 /**
  * {@inheritdoc}
  */
 public function generateFromPath($path = NULL, $options = array())
 {
     $request = $this->requestStack->getCurrentRequest();
     $current_base_path = $request->getBasePath() . '/';
     $current_base_url = $request->getSchemeAndHttpHost() . $current_base_path;
     $current_script_path = '';
     $base_path_with_script = $request->getBaseUrl();
     if (!empty($base_path_with_script)) {
         $script_name = $request->getScriptName();
         if (strpos($base_path_with_script, $script_name) !== FALSE) {
             $current_script_path = ltrim(substr($script_name, strlen($current_base_path)), '/') . '/';
         }
     }
     // Merge in defaults.
     $options += array('fragment' => '', 'query' => array(), 'absolute' => FALSE, 'prefix' => '');
     if (!isset($options['external'])) {
         // Return an external link if $path contains an allowed absolute URL. Only
         // call the slow
         // \Drupal\Component\Utility\UrlHelper::stripDangerousProtocols() if $path
         // contains a ':' before any / ? or #. Note: we could use
         // \Drupal\Component\Utility\UrlHelper::isExternal($path) here, but that
         // would require another function call, and performance inside _url() is
         // critical.
         $colonpos = strpos($path, ':');
         $options['external'] = $colonpos !== FALSE && !preg_match('![/?#]!', substr($path, 0, $colonpos)) && UrlHelper::stripDangerousProtocols($path) == $path;
     }
     if (isset($options['fragment']) && $options['fragment'] !== '') {
         $options['fragment'] = '#' . $options['fragment'];
     }
     if ($options['external']) {
         // Split off the fragment.
         if (strpos($path, '#') !== FALSE) {
             list($path, $old_fragment) = explode('#', $path, 2);
             // If $options contains no fragment, take it over from the path.
             if (isset($old_fragment) && !$options['fragment']) {
                 $options['fragment'] = '#' . $old_fragment;
             }
         }
         // Append the query.
         if ($options['query']) {
             $path .= (strpos($path, '?') !== FALSE ? '&' : '?') . UrlHelper::buildQuery($options['query']);
         }
         if (isset($options['https']) && $this->mixedModeSessions) {
             if ($options['https'] === TRUE) {
                 $path = str_replace('http://', 'https://', $path);
             } elseif ($options['https'] === FALSE) {
                 $path = str_replace('https://', 'http://', $path);
             }
         }
         // Reassemble.
         return $path . $options['fragment'];
     } else {
         $path = ltrim($this->processPath($path, $options), '/');
     }
     if (!isset($options['script'])) {
         $options['script'] = $current_script_path;
     }
     // The base_url might be rewritten from the language rewrite in domain mode.
     if (!isset($options['base_url'])) {
         if (isset($options['https']) && $this->mixedModeSessions) {
             if ($options['https'] === TRUE) {
                 $options['base_url'] = str_replace('http://', 'https://', $current_base_url);
                 $options['absolute'] = TRUE;
             } elseif ($options['https'] === FALSE) {
                 $options['base_url'] = str_replace('https://', 'http://', $current_base_url);
                 $options['absolute'] = TRUE;
             }
         } else {
             $options['base_url'] = $current_base_url;
         }
     } elseif (rtrim($options['base_url'], '/') == $options['base_url']) {
         $options['base_url'] .= '/';
     }
     $base = $options['absolute'] ? $options['base_url'] : $current_base_path;
     $prefix = empty($path) ? rtrim($options['prefix'], '/') : $options['prefix'];
     $path = str_replace('%2F', '/', rawurlencode($prefix . $path));
     $query = $options['query'] ? '?' . UrlHelper::buildQuery($options['query']) : '';
     return $base . $options['script'] . $path . $query . $options['fragment'];
 }
예제 #8
0
 /**
  * Replaces placeholders in a string with values.
  *
  * @param string $string
  *   A string containing placeholders. The string itself is expected to be
  *   safe and correct HTML. Any unsafe content must be in $args and
  *   inserted via placeholders.
  * @param array $args
  *   An associative array of replacements. Each array key should be the same
  *   as a placeholder in $string. The corresponding value should be a string
  *   or an object that implements
  *   \Drupal\Component\Render\MarkupInterface. The value replaces the
  *   placeholder in $string. Sanitization and formatting will be done before
  *   replacement. The type of sanitization and formatting depends on the first
  *   character of the key:
  *   - @variable: When the placeholder replacement value is:
  *     - A string, the replaced value in the returned string will be sanitized
  *       using \Drupal\Component\Utility\Html::escape().
  *     - A MarkupInterface object, the replaced value in the returned string
  *       will not be sanitized.
  *     - A MarkupInterface object cast to a string, the replaced value in the
  *       returned string be forcibly sanitized using
  *       \Drupal\Component\Utility\Html::escape().
  *       @code
  *         $this->placeholderFormat('This will force HTML-escaping of the replacement value: @text', ['@text' => (string) $safe_string_interface_object));
  *       @endcode
  *     Use this placeholder as the default choice for anything displayed on
  *     the site, but not within HTML attributes, JavaScript, or CSS. Doing so
  *     is a security risk.
  *   - %variable: Use when the replacement value is to be wrapped in <em>
  *     tags.
  *     A call like:
  *     @code
  *       $string = "%output_text";
  *       $arguments = ['output_text' => 'text output here.'];
  *       $this->placeholderFormat($string, $arguments);
  *     @endcode
  *     makes the following HTML code:
  *     @code
  *       <em class="placeholder">text output here.</em>
  *     @endcode
  *     As with @variable, do not use this within HTML attributes, JavaScript,
  *     or CSS. Doing so is a security risk.
  *   - :variable: Return value is escaped with
  *     \Drupal\Component\Utility\Html::escape() and filtered for dangerous
  *     protocols using UrlHelper::stripDangerousProtocols(). Use this when
  *     using the "href" attribute, ensuring the attribute value is always
  *     wrapped in quotes:
  *     @code
  *     // Secure (with quotes):
  *     $this->placeholderFormat('<a href=":url">@variable</a>', [':url' => $url, @variable => $variable]);
  *     // Insecure (without quotes):
  *     $this->placeholderFormat('<a href=:url>@variable</a>', [':url' => $url, @variable => $variable]);
  *     @endcode
  *     When ":variable" comes from arbitrary user input, the result is secure,
  *     but not guaranteed to be a valid URL (which means the resulting output
  *     could fail HTML validation). To guarantee a valid URL, use
  *     Url::fromUri($user_input)->toString() (which either throws an exception
  *     or returns a well-formed URL) before passing the result into a
  *     ":variable" placeholder.
  *
  * @return string
  *   A formatted HTML string with the placeholders replaced.
  *
  * @ingroup sanitization
  *
  * @see \Drupal\Core\StringTranslation\TranslatableMarkup
  * @see \Drupal\Core\StringTranslation\PluralTranslatableMarkup
  * @see \Drupal\Component\Utility\Html::escape()
  * @see \Drupal\Component\Utility\UrlHelper::stripDangerousProtocols()
  * @see \Drupal\Core\Url::fromUri()
  */
 protected static function placeholderFormat($string, array $args)
 {
     // Transform arguments before inserting them.
     foreach ($args as $key => $value) {
         switch ($key[0]) {
             case '@':
                 // Escape if the value is not an object from a class that implements
                 // \Drupal\Component\Render\MarkupInterface, for example strings will
                 // be escaped.
                 // \Drupal\Component\Utility\SafeMarkup\SafeMarkup::isSafe() may
                 // return TRUE for content that is safe within HTML fragments, but not
                 // within other contexts, so this placeholder type must not be used
                 // within HTML attributes, JavaScript, or CSS.
                 $args[$key] = static::placeholderEscape($value);
                 break;
             case ':':
                 // Strip URL protocols that can be XSS vectors.
                 $value = UrlHelper::stripDangerousProtocols($value);
                 // Escape unconditionally, without checking
                 // \Drupal\Component\Utility\SafeMarkup\SafeMarkup::isSafe(). This
                 // forces characters that are unsafe for use in an "href" HTML
                 // attribute to be encoded. If a caller wants to pass a value that is
                 // extracted from HTML and therefore is already HTML encoded, it must
                 // invoke
                 // \Drupal\Component\Render\OutputStrategyInterface::renderFromHtml()
                 // on it prior to passing it in as a placeholder value of this type.
                 // @todo Add some advice and stronger warnings.
                 //   https://www.drupal.org/node/2569041.
                 $args[$key] = Html::escape($value);
                 break;
             case '%':
             default:
                 // Similarly to @, escape non-safe values. Also, add wrapping markup
                 // in order to render as a placeholder. Not for use within attributes,
                 // per the warning above about
                 // \Drupal\Component\Utility\SafeMarkup\SafeMarkup::isSafe() and also
                 // due to the wrapping markup.
                 $args[$key] = '<em class="placeholder">' . static::placeholderEscape($value) . '</em>';
                 break;
         }
     }
     return strtr($string, $args);
 }