Index: update.php
===================================================================
RCS file: /cvs/drupal/drupal/update.php,v
retrieving revision 1.319
diff -u -p -r1.319 update.php
--- update.php	11 Apr 2010 18:33:43 -0000	1.319
+++ update.php	13 Apr 2010 16:44:54 -0000
@@ -317,7 +317,7 @@ function update_check_requirements() {
     update_task_list('requirements');
     drupal_set_title('Requirements problem');
     $status_report = theme('status_report', array('requirements' => $requirements));
-    $status_report .= 'Check the error messages and <a href="' . check_url(request_uri()) . '">try again</a>.';
+    $status_report .= 'Check the error messages and <a href="' . check_plain(request_uri()) . '">try again</a>.';
     print theme('update_page', array('content' => $status_report));
     exit();
   }
Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.1146
diff -u -p -r1.1146 common.inc
--- includes/common.inc	11 Apr 2010 18:33:43 -0000	1.1146
+++ includes/common.inc	13 Apr 2010 16:44:55 -0000
@@ -1181,10 +1181,10 @@ function flood_is_allowed($name, $thresh
  */
 
 /**
- * Prepare a URL for use in an HTML attribute. Strips harmful protocols.
+ * Prepare a URL that comes from user input for use in an HTML attribute.
  */
 function check_url($uri) {
-  return filter_xss_bad_protocol($uri, FALSE);
+  return check_plain(filter_xss_bad_protocol($uri, FALSE));
 }
 
 /**
@@ -1422,25 +1422,29 @@ function _filter_xss_attributes($attr) {
 }
 
 /**
- * Processes an HTML attribute value and ensures it does not contain an URL with a disallowed protocol (e.g. javascript:).
+ * Processes a text or HTML string to ensure it does not contain a URL with a disallowed protocol (e.g. javascript:).
  *
  * @param $string
- *   The string with the attribute value.
- * @param $decode
- *   Whether to decode entities in the $string. Set to FALSE if the $string
- *   is in plain text, TRUE otherwise. Defaults to TRUE.
+ *   The string that contains one or more URLs within it. Can either be a plain
+ *   text string or be already encoded for HTML.
+ * @param $html_encoded
+ *   Whether $string is already encoded for HTML (for example, if it was parsed
+ *   from an HTML document or an HTML editor). If TRUE, the returned string will
+ *   also be escaped for HTML. If FALSE, the returned string will not be
+ *   HTML-escaped, so check_plain() will need to be called prior to outputting
+ *   to a Drupal page. Defaults to TRUE.
  * @return
- *   Cleaned up and HTML-escaped version of $string.
+ *   Cleaned up version of $string.
  */
-function filter_xss_bad_protocol($string, $decode = TRUE) {
+function filter_xss_bad_protocol($string, $html_encoded = TRUE) {
   static $allowed_protocols;
 
   if (!isset($allowed_protocols)) {
     $allowed_protocols = array_flip(variable_get('filter_allowed_protocols', array('ftp', 'http', 'https', 'irc', 'mailto', 'news', 'nntp', 'rtsp', 'sftp', 'ssh', 'telnet', 'webcal')));
   }
 
-  // Get the plain text representation of the attribute value (i.e. its meaning).
-  if ($decode) {
+  // If the string is HTML-encoded, decode it.
+  if ($html_encoded) {
     $string = decode_entities($string);
   }
 
@@ -1465,7 +1469,12 @@ function filter_xss_bad_protocol($string
     }
   } while ($before != $string);
 
-  return check_plain($string);
+  // If the string was HTML-encoded and we decoded it, re-encode it.
+  if ($html_encoded) {
+    $string = check_plain($string);
+  }
+
+  return $string;
 }
 
 /**
@@ -1489,7 +1498,7 @@ function format_rss_channel($title, $lin
 
   $output = "<channel>\n";
   $output .= ' <title>' . check_plain($title) . "</title>\n";
-  $output .= ' <link>' . check_url($link) . "</link>\n";
+  $output .= ' <link>' . check_plain($link) . "</link>\n";
 
   // The RSS 2.0 "spec" doesn't indicate HTML can be used in the description.
   // We strip all HTML tags, but need to prevent double encoding from properly
@@ -1511,7 +1520,7 @@ function format_rss_channel($title, $lin
 function format_rss_item($title, $link, $description, $args = array()) {
   $output = "<item>\n";
   $output .= ' <title>' . check_plain($title) . "</title>\n";
-  $output .= ' <link>' . check_url($link) . "</link>\n";
+  $output .= ' <link>' . check_plain($link) . "</link>\n";
   $output .= ' <description>' . check_plain($description) . "</description>\n";
   $output .= format_xml_elements($args);
   $output .= "</item>\n";
@@ -1961,7 +1970,7 @@ function url($path = NULL, array $option
     // Note: we could use url_is_external($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)) && filter_xss_bad_protocol($path, FALSE) == check_plain($path));
+    $options['external'] = ($colonpos !== FALSE && !preg_match('![/?#]!', substr($path, 0, $colonpos)) && filter_xss_bad_protocol($path, FALSE) == $path);
   }
 
   // Preserve the original path before altering or aliasing.
@@ -2081,7 +2090,7 @@ function url_is_external($path) {
   $colonpos = strpos($path, ':');
   // Only call the slow filter_xss_bad_protocol if $path contains a ':'
   // before any / ? or #.
-  return $colonpos !== FALSE && !preg_match('![/?#]!', substr($path, 0, $colonpos)) && filter_xss_bad_protocol($path, FALSE) == check_plain($path);
+  return $colonpos !== FALSE && !preg_match('![/?#]!', substr($path, 0, $colonpos)) && filter_xss_bad_protocol($path, FALSE) == $path;
 }
 
 /**
Index: includes/form.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/form.inc,v
retrieving revision 1.452
diff -u -p -r1.452 form.inc
--- includes/form.inc	13 Apr 2010 15:23:02 -0000	1.452
+++ includes/form.inc	13 Apr 2010 16:44:55 -0000
@@ -2834,7 +2834,7 @@ function theme_textfield($variables) {
   if ($element['#autocomplete_path'] && drupal_valid_path($element['#autocomplete_path'])) {
     drupal_add_js('misc/autocomplete.js');
     $class[] = 'form-autocomplete';
-    $extra =  '<input class="autocomplete" type="hidden" id="' . $element['#id'] . '-autocomplete" value="' . check_url(url($element['#autocomplete_path'], array('absolute' => TRUE))) . '" disabled="disabled" />';
+    $extra =  '<input class="autocomplete" type="hidden" id="' . $element['#id'] . '-autocomplete" value="' . check_plain(url($element['#autocomplete_path'], array('absolute' => TRUE))) . '" disabled="disabled" />';
   }
   _form_set_class($element, $class);
 
@@ -2856,7 +2856,7 @@ function theme_textfield($variables) {
 function theme_form($variables) {
   $element = $variables['element'];
   // Anonymous div to satisfy XHTML compliance.
-  $action = $element['#action'] ? 'action="' . check_url($element['#action']) . '" ' : '';
+  $action = $element['#action'] ? 'action="' . check_plain($element['#action']) . '" ' : '';
   return '<form ' . $action . ' accept-charset="UTF-8" method="' . $element['#method'] . '" id="' . $element['#id'] . '"' . drupal_attributes($element['#attributes']) . ">\n<div>" . $element['#children'] . "\n</div></form>\n";
 }
 
Index: includes/install.core.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/install.core.inc,v
retrieving revision 1.9
diff -u -p -r1.9 install.core.inc
--- includes/install.core.inc	11 Apr 2010 18:33:43 -0000	1.9
+++ includes/install.core.inc	13 Apr 2010 16:44:56 -0000
@@ -723,7 +723,7 @@ function install_verify_requirements(&$i
     if ($install_state['interactive']) {
       drupal_set_title(st('Requirements problem'));
       $status_report = theme('status_report', array('requirements' => $requirements));
-      $status_report .= st('Check the error messages and <a href="!url">proceed with the installation</a>.', array('!url' => check_url(request_uri())));
+      $status_report .= st('Check the error messages and <a href="@url">proceed with the installation</a>.', array('@url' => request_uri()));
       return $status_report;
     }
     else {
Index: includes/theme.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/theme.inc,v
retrieving revision 1.589
diff -u -p -r1.589 theme.inc
--- includes/theme.inc	13 Apr 2010 15:23:02 -0000	1.589
+++ includes/theme.inc	13 Apr 2010 16:44:56 -0000
@@ -1523,7 +1523,7 @@ function theme_image($variables) {
   if (!$getsize || (is_file($path) && (list($width, $height, $type, $image_attributes) = @getimagesize($path)))) {
     $attributes = drupal_attributes($attributes);
     $url = file_create_url($path);
-    return '<img src="' . check_url($url) . '" alt="' . check_plain($alt) . '" title="' . check_plain($title) . '" ' . (isset($image_attributes) ? $image_attributes : '') . $attributes . ' />';
+    return '<img src="' . check_plain($url) . '" alt="' . check_plain($alt) . '" title="' . check_plain($title) . '" ' . (isset($image_attributes) ? $image_attributes : '') . $attributes . ' />';
   }
 }
 
@@ -1867,7 +1867,7 @@ function theme_item_list($variables) {
  *   - url: The url for the link.
  */
 function theme_more_help_link($variables) {
-  return '<div class="more-help-link">' . t('<a href="@link">More help</a>', array('@link' => check_url($variables['url']))) . '</div>';
+  return '<div class="more-help-link">' . t('<a href="@link">More help</a>', array('@link' => $variables['url'])) . '</div>';
 }
 
 /**
@@ -1881,7 +1881,7 @@ function theme_more_help_link($variables
 function theme_feed_icon($variables) {
   $text = t('Subscribe to @feed-title', array('@feed-title' => $variables['title']));
   if ($image = theme('image', array('path' => 'misc/feed.png', 'alt' => $text))) {
-    return '<a href="' . check_url($variables['url']) . '" title="' . $text . '" class="feed-icon">' . $image . '</a>';
+    return '<a href="' . check_plain($variables['url']) . '" title="' . $text . '" class="feed-icon">' . $image . '</a>';
   }
 }
 
@@ -1931,7 +1931,7 @@ function theme_html_tag($variables) {
  *   - title: A descriptive verb for the link, like 'Read more'.
  */
 function theme_more_link($variables) {
-  return '<div class="more-link">' . t('<a href="@link" title="@title">More</a>', array('@link' => check_url($variables['url']), '@title' => $variables['title'])) . '</div>';
+  return '<div class="more-link">' . t('<a href="@link" title="@title">More</a>', array('@link' => $variables['url'], '@title' => $variables['title'])) . '</div>';
 }
 
 /**
@@ -2174,9 +2174,9 @@ function template_preprocess_html(&$vari
 
   // Add favicon.
   if (theme_get_setting('toggle_favicon')) {
-    $favicon = theme_get_setting('favicon');
+    $favicon = filter_xss_bad_protocol(theme_get_setting('favicon'), FALSE);
     $type = theme_get_setting('favicon_mimetype');
-    drupal_add_html_head_link(array('rel' => 'shortcut icon', 'href' => check_url($favicon), 'type' => $type));
+    drupal_add_html_head_link(array('rel' => 'shortcut icon', 'href' => $favicon, 'type' => $type));
   }
 
   // Construct page title.
@@ -2358,9 +2358,9 @@ function theme_get_suggestions($args, $b
 function template_preprocess_maintenance_page(&$variables) {
   // Add favicon
   if (theme_get_setting('toggle_favicon')) {
-    $favicon = theme_get_setting('favicon');
+    $favicon = filter_xss_bad_protocol(theme_get_setting('favicon'), FALSE);
     $type = theme_get_setting('favicon_mimetype');
-    drupal_add_html_head_link(array('rel' => 'shortcut icon', 'href' => check_url($favicon), 'type' => $type));
+    drupal_add_html_head_link(array('rel' => 'shortcut icon', 'href' => $favicon, 'type' => $type));
   }
 
   global $theme;
Index: includes/theme.maintenance.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/theme.maintenance.inc,v
retrieving revision 1.57
diff -u -p -r1.57 theme.maintenance.inc
--- includes/theme.maintenance.inc	13 Apr 2010 15:23:02 -0000	1.57
+++ includes/theme.maintenance.inc	13 Apr 2010 16:44:56 -0000
@@ -151,7 +151,7 @@ function theme_install_page($variables) 
     $title = count($messages['error']) > 1 ? st('The following errors must be resolved before you can continue the installation process') : st('The following error must be resolved before you can continue the installation process');
     $variables['messages'] .= '<h3>' . $title . ':</h3>';
     $variables['messages'] .= theme('status_messages', array('display' => 'error'));
-    $variables['content'] .= '<p>' . st('Check the error messages and <a href="!url">try again</a>.', array('!url' => check_url(request_uri()))) . '</p>';
+    $variables['content'] .= '<p>' . st('Check the error messages and <a href="@url">try again</a>.', array('@url' => request_uri())) . '</p>';
   }
 
   // Special handling of warning messages
Index: modules/comment/comment.tokens.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/comment/comment.tokens.inc,v
retrieving revision 1.10
diff -u -p -r1.10 comment.tokens.inc
--- modules/comment/comment.tokens.inc	9 Jan 2010 21:54:00 -0000	1.10
+++ modules/comment/comment.tokens.inc	13 Apr 2010 16:44:57 -0000
@@ -167,7 +167,7 @@ function comment_tokens($type, $tokens, 
           break;
 
         case 'homepage':
-          $replacements[$original] = $sanitize ? filter_xss_bad_protocol($comment->homepage) : $comment->homepage;
+          $replacements[$original] = $sanitize ? check_url($comment->homepage) : $comment->homepage;
           break;
 
         case 'title':
Index: modules/search/search.pages.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/search/search.pages.inc,v
retrieving revision 1.18
diff -u -p -r1.18 search.pages.inc
--- modules/search/search.pages.inc	13 Apr 2010 15:23:03 -0000	1.18
+++ modules/search/search.pages.inc	13 Apr 2010 16:44:58 -0000
@@ -90,7 +90,7 @@ function template_preprocess_search_resu
  */
 function template_preprocess_search_result(&$variables) {
   $result = $variables['result'];
-  $variables['url'] = check_url($result['link']);
+  $variables['url'] = check_plain($result['link']);
   $variables['title'] = check_plain($result['title']);
 
   $info = array();
Index: modules/simpletest/tests/common.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/common.test,v
retrieving revision 1.109
diff -u -p -r1.109 common.test
--- modules/simpletest/tests/common.test	11 Apr 2010 18:33:44 -0000	1.109
+++ modules/simpletest/tests/common.test	13 Apr 2010 16:44:58 -0000
@@ -82,7 +82,10 @@ class CommonURLUnitTest extends DrupalWe
     $text = $this->randomName();
     $path = "<SCRIPT>alert('XSS')</SCRIPT>";
     $link = l($text, $path);
-    $sanitized_path = check_url(url($path));
+    // Not check_url(), because l() is not responsible for stripping bad
+    // protocols. Code that calls l() with a path that comes from user input
+    // needs to do that.
+    $sanitized_path = check_plain(url($path));
     $this->assertTrue(strpos($link, $sanitized_path) !== FALSE, t('XSS attack @path was filtered', array('@path' => $path)));
   }
 
