Index: modules/profile/profile.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/profile/profile.test,v
retrieving revision 1.9
diff -u -p -r1.9 profile.test
--- modules/profile/profile.test	5 Dec 2008 12:50:28 -0000	1.9
+++ modules/profile/profile.test	6 Dec 2008 21:57:05 -0000
@@ -134,7 +134,8 @@ class ProfileTestFields extends ProfileT
       'textarea' => $this->randomName(),
       'list' => $this->randomName(),
       'checkbox' => 1,
-      'url' => 'http://www.' . $this->randomName(10). '.org',
+      // _ is an invalid character for domain names.
+      'url' => 'http://www.' . str_replace('_', '', $this->randomName(10)). '.org',
     );
 
     // For each field type, create a field, give it a value and delete the field.
Index: modules/simpletest/tests/common.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/common.test,v
retrieving revision 1.17
diff -u -p -r1.17 common.test
--- modules/simpletest/tests/common.test	26 Nov 2008 13:48:49 -0000	1.17
+++ modules/simpletest/tests/common.test	6 Dec 2008 21:57:05 -0000
@@ -521,3 +521,75 @@ class DrupalErrorHandlerUnitTest extends
     $this->assertText($message, t("Found '%message' in error page.", array('%message' => $message)));
   }
 }
+
+/**
+ * Test for valid_url().
+ */
+class ValidUrlTestCase extends DrupalWebTestCase {
+  /**
+   * Implementation of getInfo().
+   */
+  function getInfo() {
+    return array(
+      'name' => t('Valid Url'),
+      'description' => t("Performs tests on Drupal's valid url function."),
+      'group' => t('System')
+    );
+  }
+
+  function testValidUrl() {
+    $url_schemes = array('http', 'https', 'ftp');
+    $valid_absolute_urls = array(
+      'example.com',
+      'www.example.com',
+      'ex-ample.com',
+      '3xampl3.com',
+      'example.com/paren(the)sis',
+      'example.com/index.html#pagetop',
+      'example.com:8080',
+      'sudomain.example.com',
+      'example.com/index.php?q=node',
+      'example.com/index.php?q=node&param=false',
+      'user@www.example.com',
+      'user:pass@www.example.com:8080/login.php?do=login&style=%23#pagetop',
+      '127.0.0.1',
+    );
+
+    foreach ($url_schemes as $scheme) {
+      foreach ($valid_absolute_urls as $url) {
+        $test_url = $scheme . '://' . $url;
+        $valid_url = valid_url($test_url, TRUE);
+        $this->assertTrue($valid_url, $test_url .t(' is a valid url.'));
+      }
+    }
+
+    $invalid_ablosule_urls = array(
+      '',
+      'ex!ample.com',
+    );
+
+    foreach ($url_schemes as $scheme) {
+      foreach ($invalid_ablosule_urls as $url) {
+        $test_url = $scheme . '://' . $url;
+        $valid_url = valid_url($test_url, TRUE);
+        $this->assertFalse($valid_url, $test_url .t(' is NOT a valid url.'));
+      }
+    }
+
+    $valid_relative_urls = array(
+      'paren(the)sis',
+      'index.html#pagetop',
+      'index.php?q=node',
+      'index.php?q=node&param=false',
+      'login.php?do=login&style=%23#pagetop',
+    );
+
+    foreach (array('', '/') as $front) {
+      foreach ($valid_relative_urls as $url) {
+        $test_url = $front . $url;
+        $valid_url = valid_url($test_url);
+        $this->assertTrue($valid_url, $test_url .t(' is a valid url.'));
+      }
+    }
+  }
+}
Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.834
diff -u -p -r1.834 common.inc
--- includes/common.inc	3 Dec 2008 12:31:37 -0000	1.834
+++ includes/common.inc	6 Dec 2008 21:57:07 -0000
@@ -1120,12 +1120,25 @@ function valid_email_address($mail) {
  *   TRUE if the URL is in a valid format.
  */
 function valid_url($url, $absolute = FALSE) {
-  $allowed_characters = '[a-z0-9\/:_\-_\.\?\$,;~=#&%\+]';
   if ($absolute) {
-    return (bool)preg_match("/^(http|https|ftp):\/\/" . $allowed_characters . "+$/i", $url);
+    return (bool)preg_match('
+      /^                                          # Start at the beginning of the text
+      (?:ftp|http|https):\/\/                     # Look for ftp, http, or https
+      (?:                                         # Username:password combinations (optional)
+        [\w\.\-\+]+                               # A username
+        :{0,1}                                    # an optional colon to separate the username and password
+        [\w\.\-\+]*@                              # An optional password
+      )?
+      (?:[a-z0-9\-\.]+)                           # The domain
+      (?::[0-9]+)?                                # Server port number (optional)
+      (?:                                         # The path (optional)
+        \/|                                       # a forward slash
+        (?:\/|\?)(?:[\w#!:\.\?\+=&%@!\-\/\(\)]+)  # or a forward slash or question mark followed by a full path
+       )?$
+     /xi', $url);
   }
   else {
-    return (bool)preg_match("/^" . $allowed_characters . "+$/i", $url);
+    return (bool)preg_match("/^[\w#!:.?+=&%@!\-\/\(\)]+$/i", $url);
   }
 }
 
