Index: modules/simpletest/tests/file.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/file.test,v
retrieving revision 1.54
diff -u -r1.54 file.test
--- modules/simpletest/tests/file.test	11 May 2010 10:56:04 -0000	1.54
+++ modules/simpletest/tests/file.test	1 Jun 2010 19:06:21 -0000
@@ -2286,11 +2286,6 @@
     $instance = file_stream_wrapper_get_instance_by_uri('public://foo');
     $this->assertEqual('DrupalPublicStreamWrapper', get_class($instance), t('Got correct class type for public URI.'));
 
-    // Test file_stream_wrapper_uri_normalize().
-    $uri = 'public:///' . file_directory_path() . '/foo/bar/';
-    $uri = file_stream_wrapper_uri_normalize($uri);
-    $this->assertEqual('public://foo/bar', $uri, t('Got a properly normalized URI @uri', array('@uri' => $uri)));
-
     // Test file_uri_target().
     $this->assertEqual(file_uri_target('public://foo/bar.txt'), 'foo/bar.txt', t('Got a valid stream target from public://foo/bar.txt.'));
     $this->assertFalse(file_uri_target('foo/bar.txt'), t('foo/bar.txt is not a valid stream.'));
Index: includes/file.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/file.inc,v
retrieving revision 1.209
diff -u -r1.209 file.inc
--- includes/file.inc	11 May 2010 10:56:04 -0000	1.209
+++ includes/file.inc	1 Jun 2010 19:06:19 -0000
@@ -165,6 +165,8 @@
  * @return
  *   A string containing the name of the scheme, or FALSE if none. For example,
  *   the URI "public://example.txt" would return "public".
+ *
+ * @see file_uri_target()
  */
 function file_uri_scheme($uri) {
   $data = explode('://', $uri, 2);
@@ -205,18 +207,14 @@
  *   A string containing the target (path), or FALSE if none.
  *   For example, the URI "public://sample/test.txt" would return
  *   "sample/test.txt".
+ *
+ * @see file_uri_scheme()
  */
 function file_uri_target($uri) {
-  $data = explode('://', $uri, 2);
-
-  if (count($data) != 2) {
-    return FALSE;
+  if ($scheme = file_uri_scheme($uri)) {
+    return file_stream_wrapper_get_instance_by_scheme($scheme)->getTarget($uri);
   }
-
-  // Remove erroneous beginning forward slash.
-  $data[1] = ltrim($data[1], '\/');
-
-  return $data[1];
+  return FALSE;
 }
 
 /**
@@ -225,7 +223,6 @@
  * A stream is referenced as "scheme://target".
  *
  * The following actions are taken:
- * - Remove all occurrences of the wrapper's directory path
  * - Remove trailing slashes from target
  * - Trim erroneous leading slashes from target. e.g. ":///" becomes "://".
  *
@@ -240,15 +237,9 @@
   if ($scheme && file_stream_wrapper_valid_scheme($scheme)) {
     $target = file_uri_target($uri);
 
-    // Remove all occurrences of the wrapper's directory path.
-    $directory_path = file_stream_wrapper_get_instance_by_scheme($scheme)->getDirectoryPath();
-    $target = str_replace($directory_path, '', $target);
-
-    // Trim trailing slashes from target.
-    $target = rtrim($target, '/');
-
-    // Trim erroneous leading slashes from target.
-    $uri = $scheme . '://' . ltrim($target, '/');
+    if ($target !== FALSE) {
+      $uri = $scheme . '://' . $target;
+    }
   }
   return $uri;
 }
@@ -1880,14 +1871,7 @@
   $scheme = file_uri_scheme($uri);
 
   if ($scheme && file_stream_wrapper_valid_scheme($scheme)) {
-    $target  = file_uri_target($uri);
-    $dirname = dirname($target);
-
-    if ($dirname == '.') {
-      $dirname = '';
-    }
-
-    return $scheme . '://' . $dirname;
+    return file_stream_wrapper_get_instance_by_scheme($scheme)->dirname($uri);
   }
   else {
     return dirname($uri);
Index: includes/stream_wrappers.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/stream_wrappers.inc,v
retrieving revision 1.15
diff -u -r1.15 stream_wrappers.inc
--- includes/stream_wrappers.inc	6 May 2010 05:59:30 -0000	1.15
+++ includes/stream_wrappers.inc	1 Jun 2010 19:06:19 -0000
@@ -146,6 +146,24 @@
   public function getExternalUrl();
 
   /**
+   * Returns the local writable target of the resource within the stream.
+   *
+   * This function should be used in place of calls to realpath() or similar
+   * functions when attempting to determine the location of a file. While
+   * functions like realpath() may return the location of a read-only file, this
+   * method may return a URI or path suitable for writing that is completely
+   * separate from the URI used for reading.
+   *
+   * @param $uri
+   *   Optional URI.
+   *
+   * @return
+   *   Returns a string representing a location suitable for writing of a file,
+   *   or FALSE if unable to write to the file such as with read-only streams.
+   */
+  public function getTarget($uri = NULL);
+
+  /**
    * Returns the MIME type of the resource.
    *
    * @param $uri
@@ -155,6 +173,7 @@
    *    - 'mimetypes': a list of mimetypes, keyed by an identifier,
    *    - 'extensions': the mapping itself, an associative array in which
    *      the key is the extension and the value is the mimetype identifier.
+   *
    * @return
    *   Returns a string containing the MIME type of the resource.
    */
@@ -169,6 +188,7 @@
    * @param $mode
    *   Integer value for the permissions. Consult PHP chmod() documentation
    *   for more information.
+   *
    * @return
    *   Returns TRUE on success or FALSE on failure.
    */
@@ -187,6 +207,23 @@
    *   wrapper does not provide an implementation.
    */
   public function realpath();
+
+  /**
+   * Gets the name of the directory from a given path.
+   *
+   * This method is usually accessed through drupal_dirname(), which wraps
+   * around the normal PHP dirname() function, which does not support stream
+   * wrappers.
+   *
+   * @param $uri
+   *   An optional URI.
+   *
+   * @return
+   *   A string containing the directory name, or FALSE if not applicable.
+   *
+   * @see drupal_dirname()
+   */
+  public function dirname($uri = NULL);
 }
 
 
@@ -227,6 +264,7 @@
 
   /**
    * Gets the path that the wrapper is responsible for.
+   * @TODO: Review this method name in D8 per http://drupal.org/node/701358
    *
    * @return
    *   String specifying the path.
@@ -248,6 +286,20 @@
   }
 
   /**
+   *  Base implementation of getTarget().
+   */
+  function getTarget($uri = NULL) {
+    if (!isset($uri)) {
+      $uri = $this->uri;
+    }
+
+    list($scheme, $target) = explode('://', $uri, 2);
+
+    // Remove erroneous leading or trailing, forward-slashes and backslashes.
+    return trim($target, '\/');
+  }
+
+  /**
    * Base implementation of getMimeType().
    */
   static function getMimeType($uri, $mapping = NULL) {
@@ -303,7 +355,7 @@
     if (!isset($uri)) {
       $uri = $this->uri;
     }
-    $path = $this->getDirectoryPath() . '/' . file_uri_target($uri);
+    $path = $this->getDirectoryPath() . '/' . $this->getTarget($uri);
     $realpath = realpath($path);
     if (!$realpath) {
       // This file does not yet exist.
@@ -327,8 +379,10 @@
    *   A bit mask of STREAM_USE_PATH and STREAM_REPORT_ERRORS.
    * @param &$opened_path
    *   A string containing the path actually opened.
+   *
    * @return
    *   Returns TRUE if file was opened successfully.
+   *
    * @see http://php.net/manual/en/streamwrapper.stream-open.php
    */
   public function stream_open($uri, $mode, $options, &$opened_path) {
@@ -353,8 +407,10 @@
    *   - LOCK_UN to release a lock (shared or exclusive).
    *   - LOCK_NB if you don't want flock() to block while locking (not
    *     supported on Windows).
+   *
    * @return
    *   Always returns TRUE at the present time.
+   *
    * @see http://php.net/manual/en/streamwrapper.stream-lock.php
    */
   public function stream_lock($operation) {
@@ -370,8 +426,10 @@
    *
    * @param $count
    *   Maximum number of bytes to be read.
+   *
    * @return
    *   The string that was read, or FALSE in case of an error.
+   *
    * @see http://php.net/manual/en/streamwrapper.stream-read.php
    */
   public function stream_read($count) {
@@ -383,8 +441,10 @@
    *
    * @param $data
    *   The string to be written.
+   *
    * @return
    *   The number of bytes written (integer).
+   *
    * @see http://php.net/manual/en/streamwrapper.stream-write.php
    */
   public function stream_write($data) {
@@ -396,6 +456,7 @@
    *
    * @return
    *   TRUE if end-of-file has been reached.
+   *
    * @see http://php.net/manual/en/streamwrapper.stream-eof.php
    */
   public function stream_eof() {
@@ -409,8 +470,10 @@
    *   The byte offset to got to.
    * @param $whence
    *   SEEK_SET, SEEK_CUR, or SEEK_END.
+   *
    * @return
    *   TRUE on success.
+   *
    * @see http://php.net/manual/en/streamwrapper.stream-seek.php
    */
   public function stream_seek($offset, $whence) {
@@ -422,6 +485,7 @@
    *
    * @return
    *   TRUE if data was successfully stored (or there was no data to store).
+   *
    * @see http://php.net/manual/en/streamwrapper.stream-flush.php
    */
   public function stream_flush() {
@@ -433,6 +497,7 @@
    *
    * @return
    *   The current offset in bytes from the beginning of file.
+   *
    * @see http://php.net/manual/en/streamwrapper.stream-tell.php
    */
   public function stream_tell() {
@@ -445,6 +510,7 @@
    * @return
    *   An array with file status, or FALSE in case of an error - see fstat()
    *   for a description of this array.
+   *
    * @see http://php.net/manual/en/streamwrapper.stream-stat.php
    */
   public function stream_stat() {
@@ -456,6 +522,7 @@
    *
    * @return
    *   TRUE if stream was successfully closed.
+   *
    * @see http://php.net/manual/en/streamwrapper.stream-close.php
    */
   public function stream_close() {
@@ -467,8 +534,10 @@
    *
    * @param $uri
    *   A string containing the uri to the resource to delete.
+   *
    * @return
    *   TRUE if resource was successfully deleted.
+   *
    * @see http://php.net/manual/en/streamwrapper.unlink.php
    */
   public function unlink($uri) {
@@ -483,8 +552,10 @@
    *   The uri to the file to rename.
    * @param $to_uri
    *   The new uri for file.
+   *
    * @return
    *   TRUE if file was successfully renamed.
+   *
    * @see http://php.net/manual/en/streamwrapper.rename.php
    */
   public function rename($from_uri, $to_uri) {
@@ -492,6 +563,33 @@
   }
 
   /**
+   * Gets the name of the directory from a given path.
+   *
+   * This method is usually accessed through drupal_dirname(), which wraps
+   * around the PHP dirname() function because it does not support stream
+   * wrappers.
+   *
+   * @param $uri
+   *   A URI or path.
+   *
+   * @return
+   *   A string containing the directory name.
+   *
+   * @see drupal_dirname()
+   */
+  public function dirname($uri = NULL) {
+    list($scheme, $target) = explode('://', $uri, 2);
+    $target  = $this->getTarget($uri);
+    $dirname = dirname($target);
+
+    if ($dirname == '.') {
+      $dirname = '';
+    }
+
+    return $scheme . '://' . $dirname;
+  }
+
+  /**
    * Support for mkdir().
    *
    * @param $uri
@@ -500,8 +598,10 @@
    *   Permission flags - see mkdir().
    * @param $options
    *   A bit mask of STREAM_REPORT_ERRORS and STREAM_MKDIR_RECURSIVE.
+   *
    * @return
    *   TRUE if directory was successfully created.
+   *
    * @see http://php.net/manual/en/streamwrapper.mkdir.php
    */
   public function mkdir($uri, $mode, $options) {
@@ -510,7 +610,7 @@
     if ($recursive) {
       // $this->getLocalPath() fails if $uri has multiple levels of directories
       // that do not yet exist.
-      $localpath = $this->getDirectoryPath() . '/' . file_uri_target($uri);
+      $localpath = $this->getDirectoryPath() . '/' . $this->getTarget($uri);
     }
     else {
       $localpath = $this->getLocalPath($uri);
@@ -530,8 +630,10 @@
    *   A string containing the URI to the directory to delete.
    * @param $options
    *   A bit mask of STREAM_REPORT_ERRORS.
+   *
    * @return
    *   TRUE if directory was successfully removed.
+   *
    * @see http://php.net/manual/en/streamwrapper.rmdir.php
    */
   public function rmdir($uri, $options) {
@@ -551,9 +653,11 @@
    *   A string containing the URI to get information about.
    * @param $flags
    *   A bit mask of STREAM_URL_STAT_LINK and STREAM_URL_STAT_QUIET.
+   *
    * @return
    *   An array with file status, or FALSE in case of an error - see fstat()
    *   for a description of this array.
+   *
    * @see http://php.net/manual/en/streamwrapper.url-stat.php
    */
   public function url_stat($uri, $flags) {
@@ -573,8 +677,10 @@
    *   A string containing the URI to the directory to open.
    * @param $options
    *   Unknown (parameter is not documented in PHP Manual).
+   *
    * @return
    *   TRUE on success.
+   *
    * @see http://php.net/manual/en/streamwrapper.dir-opendir.php
    */
   public function dir_opendir($uri, $options) {
@@ -589,6 +695,7 @@
    *
    * @return
    *   The next filename, or FALSE if there are no more files in the directory.
+   *
    * @see http://php.net/manual/en/streamwrapper.dir-readdir.php
    */
   public function dir_readdir() {
@@ -600,6 +707,7 @@
    *
    * @return
    *   TRUE on success.
+   *
    * @see http://php.net/manual/en/streamwrapper.dir-rewinddir.php
    */
   public function dir_rewinddir() {
@@ -611,6 +719,7 @@
    *
    * @return
    *   TRUE on success.
+   *
    * @see http://php.net/manual/en/streamwrapper.dir-closedir.php
    */
   public function dir_closedir() {
@@ -638,7 +747,7 @@
    * Return the HTML URI of a public file.
    */
   function getExternalUrl() {
-    $path = str_replace('\\', '/', file_uri_target($this->uri));
+    $path = str_replace('\\', '/', $this->getTarget());
     return $GLOBALS['base_url'] . '/' . self::getDirectoryPath() . '/' . drupal_encode_path($path);
   }
 }
@@ -666,7 +775,7 @@
    * Return the HTML URI of a private file.
    */
   function getExternalUrl() {
-    $path = str_replace('\\', '/', file_uri_target($this->uri));
+    $path = str_replace('\\', '/', $this->getTarget());
     return url('system/files/' . $path, array('absolute' => TRUE));
   }
 }
@@ -691,7 +800,7 @@
    * Overrides getExternalUrl().
    */
   public function getExternalUrl() {
-    $path = str_replace('\\', '/', file_uri_target($this->uri));
+    $path = str_replace('\\', '/', $this->getTarget());
     return url('system/temporary/' . $path, array('absolute' => TRUE));
   }
 }
