? includes/handlers.inc
? modules/simpletest/tests/block_test.info
? modules/simpletest/tests/block_test.module
? modules/system/system.path_lazy.inc
? modules/system/system.path_precache.inc
Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.925
diff -u -p -r1.925 common.inc
--- includes/common.inc	18 Jun 2009 21:19:01 -0000	1.925
+++ includes/common.inc	1 Jul 2009 18:12:20 -0000
@@ -233,7 +233,7 @@ function drupal_query_string_encode($que
   $params = array();
 
   foreach ($query as $key => $value) {
-    $key = drupal_urlencode($key);
+    $key = rawurlencode($key);
     if ($parent) {
       $key = $parent . '[' . $key . ']';
     }
@@ -246,7 +246,7 @@ function drupal_query_string_encode($que
       $params[] = drupal_query_string_encode($value, $exclude, $key);
     }
     else {
-      $params[] = $key . '=' . drupal_urlencode($value);
+      $params[] = $key . '=' . rawurlencode($value);
     }
   }
 
@@ -1958,8 +1958,8 @@ function format_date($timestamp, $type =
  * @param $options
  *   An associative array of additional options, with the following keys:
  *   - 'query'
- *       A query string to append to the link, or an array of query key/value
- *       properties.
+ *       A URL-encoded query string to append to the link, or an array of query
+ *       key/value-pairs without any URL-encoding.
  *   - 'fragment'
  *       A fragment identifier (or named anchor) to append to the link.
  *       Do not include the '#' character.
@@ -2063,7 +2063,7 @@ function url($path = NULL, array $option
 
   $base = $options['absolute'] ? $options['base_url'] . '/' : base_path();
   $prefix = empty($path) ? rtrim($options['prefix'], '/') : $options['prefix'];
-  $path = drupal_urlencode($prefix . $path);
+  $path = drupal_encode_path($prefix . $path);
 
   if (variable_get('clean_url', '0')) {
     // With Clean URLs.
@@ -3168,11 +3168,13 @@ function drupal_json($var = NULL) {
  *   characters are double escaped so PHP will still see the encoded version.
  * - With clean URLs, Apache changes '//' to '/', so every second slash is
  *   double escaped.
+ * - This function should only be used on paths, not on query string arguments,
+ *   otherwise unwanted double encoding will occur.
  *
  * @param $text
  *   String to encode
  */
-function drupal_urlencode($text) {
+function drupal_encode_path($text) {
   if (variable_get('clean_url', '0')) {
     return str_replace(array('%2F', '%26', '%23', '//'),
                         array('/', '%2526', '%2523', '/%252F'),
Index: modules/comment/comment.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/comment/comment.module,v
retrieving revision 1.733
diff -u -p -r1.733 comment.module
--- modules/comment/comment.module	1 Jul 2009 12:06:21 -0000	1.733
+++ modules/comment/comment.module	1 Jul 2009 18:12:20 -0000
@@ -2028,10 +2028,10 @@ function theme_comment_post_forbidden($n
       // We cannot use drupal_get_destination() because these links
       // sometimes appear on /node and taxonomy listing pages.
       if (variable_get('comment_form_location_' . $node->type, COMMENT_FORM_BELOW) == COMMENT_FORM_SEPARATE_PAGE) {
-        $destination = 'destination=' . drupal_urlencode("comment/reply/$node->nid#comment-form");
+        $destination = 'destination=' . rawurlencode("comment/reply/$node->nid#comment-form");
       }
       else {
-        $destination = 'destination=' . drupal_urlencode("node/$node->nid#comment-form");
+        $destination = 'destination=' . rawurlencode("node/$node->nid#comment-form");
       }
 
       if (variable_get('user_register', 1)) {
Index: modules/search/search.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/search/search.test,v
retrieving revision 1.23
diff -u -p -r1.23 search.test
--- modules/search/search.test	12 Jun 2009 08:39:38 -0000	1.23
+++ modules/search/search.test	1 Jul 2009 18:12:20 -0000
@@ -266,11 +266,11 @@ class SearchAdvancedSearchForm extends D
     $this->assertNotEqual($dummy_title, $this->node->title, t("Dummy title doens't equal node title"));
 
     // Search for the dummy title with a GET query.
-    $this->drupalGet('search/node/' . drupal_urlencode($dummy_title));
+    $this->drupalGet('search/node/' . $dummy_title);
     $this->assertNoText($this->node->title, t('Page node is not found with dummy title.'));
 
     // Search for the title of the node with a GET query.
-    $this->drupalGet('search/node/' . drupal_urlencode($this->node->title));
+    $this->drupalGet('search/node/' . $this->node->title);
     $this->assertText($this->node->title, t('Page node is found with GET query.'));
 
     // Search for the title of the node with a POST query.
Index: modules/simpletest/tests/common.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/common.test,v
retrieving revision 1.47
diff -u -p -r1.47 common.test
--- modules/simpletest/tests/common.test	30 Jun 2009 09:39:19 -0000	1.47
+++ modules/simpletest/tests/common.test	1 Jul 2009 18:12:20 -0000
@@ -8,8 +8,8 @@ class CommonLUnitTest extends DrupalUnit
 
   public static function getInfo() {
     return array(
-      'name' => t('Tests for the l() function'),
-      'description' => t('Confirm that url() works correctly with various input.'),
+      'name' => t('URL generation tests'),
+      'description' => t('Confirm that url(), drupal_query_string_encode(), and l() work correctly with various input.'),
       'group' => t('System'),
     );
   }
@@ -22,8 +22,18 @@ class CommonLUnitTest extends DrupalUnit
     $path = "<SCRIPT>alert('XSS')</SCRIPT>";
     $link = l($text, $path);
     $sanitized_path = check_url(url($path));
-    $this->assertTrue(strpos($link, $sanitized_path) != FALSE, t('XSS attack @path was filtered', array('@path' => $path)));
+    $this->assertTrue(strpos($link, $sanitized_path) !== FALSE, t('XSS attack @path was filtered', array('@path' => $path)));
   }
+
+  /**
+    * Test drupal_query_string_encode().
+    */
+   function testDrupalQueryStringEncode() {
+     $this->assertEqual(drupal_query_string_encode(array('a' => ' &#//+%20@۞')), 'a=%20%26%23%2F%2F%2B%2520%40%DB%9E', t('Value was properly encoded.'));
+     $this->assertEqual(drupal_query_string_encode(array(' &#//+%20@۞' => 'a')), '%20%26%23%2F%2F%2B%2520%40%DB%9E=a', t('Key was properly encoded.'));
+     $this->assertEqual(drupal_query_string_encode(array('a' => '1', 'b' => '2', 'c' => '3'), array('b')), 'a=1&c=3', t('Value was properly excluded.'));
+     $this->assertEqual(drupal_query_string_encode(array('a' => array('b' => '2', 'c' => '3')), array('b', 'a[c]')), 'a[b]=2', t('Nested array was properly encoded.'));
+   }
 }
 
 class CommonSizeTestCase extends DrupalUnitTestCase {
Index: modules/system/system.js
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.js,v
retrieving revision 1.27
diff -u -p -r1.27 system.js
--- modules/system/system.js	20 Jun 2009 08:02:29 -0000	1.27
+++ modules/system/system.js	1 Jul 2009 18:12:20 -0000
@@ -92,7 +92,7 @@ Drupal.behaviors.dateTime = {
     // Attach keyup handler to custom format inputs.
     $('input.custom-format:not(.date-time-processed)', context).addClass('date-time-processed').keyup(function () {
       var input = $(this);
-      var url = settings.dateTime.lookup +(settings.dateTime.lookup.match(/\?q=/) ? '&format=' : '?format=') + Drupal.encodeURIComponent(input.val());
+      var url = settings.dateTime.lookup + (settings.dateTime.lookup.match(/\?q=/) ? '&format=' : '?format=') + encodeURIComponent(input.val());
       $.getJSON(url, function (data) {
         $('div.description span', input.parent()).html(data);
       });
Index: modules/update/update.fetch.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/update/update.fetch.inc,v
retrieving revision 1.21
diff -u -p -r1.21 update.fetch.inc
--- modules/update/update.fetch.inc	6 Jun 2009 06:26:13 -0000	1.21
+++ modules/update/update.fetch.inc	1 Jul 2009 18:12:20 -0000
@@ -114,10 +114,10 @@ function _update_build_fetch_url($projec
   if (!empty($site_key) && (strpos($project['project_type'], 'disabled') === FALSE)) {
     $url .= (strpos($url, '?') === TRUE) ? '&' : '?';
     $url .= 'site_key=';
-    $url .= drupal_urlencode($site_key);
+    $url .= rawurlencode($site_key);
     if (!empty($project['info']['version'])) {
       $url .= '&version=';
-      $url .= drupal_urlencode($project['info']['version']);
+      $url .= rawurlencode($project['info']['version']);
     }
   }
   return $url;
Index: misc/autocomplete.js
===================================================================
RCS file: /cvs/drupal/drupal/misc/autocomplete.js,v
retrieving revision 1.30
diff -u -p -r1.30 autocomplete.js
--- misc/autocomplete.js	28 Jun 2009 13:37:29 -0000	1.30
+++ misc/autocomplete.js	1 Jul 2009 18:12:20 -0000
@@ -276,7 +276,7 @@ Drupal.ACDB.prototype.search = function 
     // Ajax GET request for autocompletion.
     $.ajax({
       type: 'GET',
-      url: db.uri + '/' + Drupal.encodeURIComponent(searchString),
+      url: db.uri + '/' + Drupal.encodePath(searchString),
       dataType: 'json',
       success: function (matches) {
         if (typeof matches.status == 'undefined' || matches.status != 0) {
Index: misc/drupal.js
===================================================================
RCS file: /cvs/drupal/drupal/misc/drupal.js,v
retrieving revision 1.55
diff -u -p -r1.55 drupal.js
--- misc/drupal.js	3 May 2009 07:35:37 -0000	1.55
+++ misc/drupal.js	1 Jul 2009 18:12:20 -0000
@@ -263,10 +263,11 @@ Drupal.unfreezeHeight = function () {
 };
 
 /**
- * Wrapper to address the mod_rewrite url encoding bug
- * (equivalent of drupal_urlencode() in PHP).
+ * Wrapper around encodeURIComponent() which avoids Apache quirks (equivalent of
+ * drupal_encode_path() in PHP). This function should only be used on paths, not
+ * on query string arguments.
  */
-Drupal.encodeURIComponent = function (item, uri) {
+Drupal.encodePath = function (item, uri) {
   uri = uri || location.href;
   item = encodeURIComponent(item).replace(/%2F/g, '/');
   return (uri.indexOf('?q=') != -1) ? item : item.replace(/%26/g, '%2526').replace(/%23/g, '%2523').replace(/\/\//g, '/%252F');
