diff --git includes/file.inc includes/file.inc
index 6a28a0c..df2a8d1 100644
--- includes/file.inc
+++ includes/file.inc
@@ -6,6 +6,17 @@
  * API for handling file uploads and server file management.
  */
 
+// Stream wrapper interface and base class implementation
+require_once DRUPAL_ROOT . '/includes/stream_wrappers.inc';
+
+// Stream wrapper registry
+require_once DRUPAL_ROOT . '/includes/stream_wrapper_registry.inc';
+
+// Register core wrappers
+DrupalStreamWrapperRegistry::register('public',  'DrupalPublicStreamWrapper');
+DrupalStreamWrapperRegistry::register('private', 'DrupalPrivateStreamWrapper');
+DrupalStreamWrapperRegistry::register('temp',    'DrupalTempStreamWrapper');
+
 /**
  * @defgroup file File interface
  * @{
@@ -79,10 +90,10 @@ define('FILE_STATUS_PERMANENT', 1);
 /**
  * Create the download path to a file.
  *
- * @param $path A string containing the path of the file to generate URL for.
+ * @param $file A file object.
  * @return A string containing a URL that can be used to download the file.
  */
-function file_create_url($path) {
+function file_create_url($file) {
   // Strip file_directory_path from $path. We only include relative paths in
   // URLs.
   if (strpos($path, file_directory_path() . '/') === 0) {
@@ -103,12 +114,17 @@ function file_create_url($path) {
  * @param $destination
  *   A string containing the path to verify. If this value is omitted, Drupal's
  *   'files' directory will be used.
+ * @param mixed $scheme 
+ *   An optional string specifiying the name of a stream wrapper. 
+ *   TODO: Link to stream wrappers.
  * @return
  *   A string containing the path to file, with file system directory appended
  *   if necessary, or FALSE if the path is invalid (i.e. outside the configured
  *   'files' or temp directories).
  */
-function file_create_path($destination = NULL) {
+function file_create_path($destination = NULL, $scheme = NULL) {
+  // Get the wrapper for this scheme.
+  // Ask the scheme for the directory.
   $file_path = file_directory_path();
   if (is_null($destination)) {
     return $file_path;
@@ -242,15 +258,16 @@ function file_check_path(&$path) {
  *   path of the source.
  */
 function file_check_location($source, $directory = '') {
-  $check = realpath($source);
+  $check = drupal_realpath($source);
+
   if ($check) {
     $source = $check;
   }
   else {
     // This file does not yet exist.
-    $source = realpath(dirname($source)) . '/' . basename($source);
+    $source = drupal_realpath(dirname($source)) . '/' . basename($source);
   }
-  $directory = realpath($directory);
+  $directory = drupal_realpath($directory);
   if ($directory && strpos($source, $directory) !== 0) {
     return FALSE;
   }
@@ -445,7 +462,7 @@ function file_copy($source, $destination = NULL, $replace = FILE_EXISTS_RENAME)
  * @see file_copy()
  */
 function file_unmanaged_copy($source, $destination = NULL, $replace = FILE_EXISTS_RENAME) {
-  $source = realpath($source);
+  $source = drupal_realpath($source);
   if (!file_exists($source)) {
     drupal_set_message(t('The specified file %file could not be copied, because no file by that name exists. Please check that you supplied the correct filename.', array('%file' => $source)), 'error');
     return FALSE;
@@ -473,7 +490,7 @@ function file_unmanaged_copy($source, $destination = NULL, $replace = FILE_EXIST
   // Make sure source and destination filenames are not the same, makes no
   // sense to copy it if they are. In fact copying the file will most likely
   // result in a 0 byte file. Which is bad. Real bad.
-  if ($source == realpath($destination)) {
+  if ($source == drupal_realpath($destination)) {
     drupal_set_message(t('The specified file %file was not copied because it would overwrite itself.', array('%file' => $source)), 'error');
     return FALSE;
   }
@@ -864,8 +881,8 @@ function file_space_used($uid = NULL, $status = FILE_STATUS_PERMANENT) {
  * Saves a file upload to a new location.
  *
  * The file will be added to the files table as a temporary file. Temporary
- * files are periodically cleaned. To make the file a permanent file call
- * assign the status and use file_save() to save it.
+ * files are periodically cleaned. To make the file a permanent file, move it with
+ * file_move() from the temp hanlder to a one with a permanent store, eg public:// or private://.
  *
  * @param $source
  *   A string specifying the name of the upload field to save.
@@ -873,8 +890,10 @@ function file_space_used($uid = NULL, $status = FILE_STATUS_PERMANENT) {
  *   An optional, associative array of callback functions used to validate the
  *   file. See file_validate() for a full discussion of the array format.
  * @param $destination
- *   A string containing the directory $source should be copied to. If this is
- *   not provided or is not writable, the temporary directory will be used.
+ *   A string containing the location $source should be copied to within the temp directory.
+ *   $destination can be a directory or directory and filename within the temp directory.
+ *   If not provided, the temporary directory will be used, and the $replace flag will
+ *   will be honored.
  * @param $replace
  *   A boolean indicating whether an existing file of the same name in the
  *   destination directory should overwritten. A false value will generate a
@@ -1013,7 +1032,6 @@ function file_save_upload($source, $validators = array(), $destination = FALSE,
   return FALSE;
 }
 
-
 /**
  * Check that a file meets the criteria specified by the validators.
  *
@@ -1490,10 +1508,13 @@ function file_directory_temp() {
 /**
  * Determine the default 'files' directory.
  *
+ *  TODO: this should be deprecated.
+ *
  * @return
  *   A string containing the path to Drupal's 'files' directory.
  */
 function file_directory_path() {
+
   return variable_get('file_directory_path', conf_path() . '/files');
 }
 
@@ -1561,7 +1582,7 @@ function file_get_mimetype($filename, $mapping = NULL) {
 }
 
 /**
- * Set the permissions on a file or directory.
+ * Set the permissions on a URI (stream).
  *
  * This function will use the 'file_chmod_directory' and 'file_chmod_file'
  * variables for the default modes for directories and uploaded/generated files.
@@ -1570,17 +1591,24 @@ function file_get_mimetype($filename, $mapping = NULL) {
  * these files, and give group write permissions so webserver group members
  * (e.g. a vhost account) can alter files uploaded and owned by the webserver.
  *
- * @param $path
+ * PHP's chmod does not support stream wrappers so we use our wrapper implementation
+ * which interfaces with chmod() by default. Contrib wrappers can override this
+ * bahavior in their implementations as needed.
+ *
+ * @see http://php.net/manual/en/function.chmod.php
+ *
+ * @param $uri
  *   String containing the path to a file or directory.
  * @param $mode
  *   Integer value for the permissions. Consult PHP chmod() documentation for
  *   more information.
  * @return
- *   TRUE for success, FALSE in the event of an error.
+ *   TRUE for success, FALSE in the event of an error. Errors are logged by watchdog.
  */
-function drupal_chmod($path, $mode = NULL) {
+function drupal_chmod($uri, $mode = NULL) {
+
   if (!isset($mode)) {
-    if (is_dir($path)) {
+    if (is_dir($uri)) {
       $mode = variable_get('file_chmod_directory', 0775);
     }
     else {
@@ -1588,14 +1616,82 @@ function drupal_chmod($path, $mode = NULL) {
     }
   }
 
-  if (@chmod($path, $mode)) {
-    return TRUE;
+  // If this URI is a stream, pass it off to the appropriate stream wrapper.
+  // Otherwise, attempt PHP's chmod. This allows use of drupal_chmod even
+  // for unmanaged files outside of the stream wrapper interface.
+  if (DrupalStreamWrapperRegistry::getStreamScheme($uri)) {
+    if (DrupalStreamWrapperRegistry::getInstance($uri)->stream_chmod($uri, $mode)) {
+      return TRUE;
+    }
+  }
+  else {
+    if (@chmod($uri, $mode)) {
+      return TRUE;
+    }
   }
 
-  watchdog('file', 'The file permissions could not be set on %path.', array('%path' => $path), WATCHDOG_ERROR);
+  watchdog('file', 'The file permissions could not be set on %uri.', array('%uri' => $uri), WATCHDOG_ERROR);
   return FALSE;
 }
 
 /**
+ * Get canonicalized absolute path of a file or directory
+ *
+ * PHP's realpath() does not properly support streams, so this function
+ * fills that gap. If a stream wrapped URI is provided, it will be passed
+ * to the registered wrapper for handling. If the URI does not contain a
+ * scheme or the wrapper implementation does not implement realpath, then
+ * FALSE will be returned.
+ *
+ * @see http://php.net/manual/en/function.realpath.php
+ *
+ * @param string $uri
+ *   A string containing a path to a stream (file, directory, etc.)
+ * @return mixed
+ *   A string containing the absolute pathname, or FALSE on failure.
+ */
+function drupal_realpath($uri) {
+
+  // If this URI is a stream, pass it off to the appropriate stream wrapper.
+  // Otherwise, attempt PHP's realpath. This allows use of drupal_realpath even
+  // for unmanaged files outside of the stream wrapper interface.
+  if (DrupalStreamWrapperRegistry::getStreamScheme($uri)) {
+    return DrupalStreamWrapperRegistry::getInstance($uri)->stream_realpath($uri);
+  }
+  else {
+    return realpath($uri);
+  }
+}
+
+/**
+ * Create file with unique file name.
+ *
+ * Stream wrapper friendly PHP tempnam() replacement. Creates a file with a unique
+ * filename, with access permission set to 0600, in the predeclared temp directory.
+ * Drupal will perform garbage collection on these files during cron runs based on
+ * DRUPAL_MAXIMUM_TEMP_FILE_AGE.
+ *
+ * Note the absense of param $dir, unlike PHP's tempnam. $dir is determined by the
+ * temp stream wrapper. It first tries the temp directory specified in
+ * admin/settings/file-system and if that directory does not exist it will fall back
+ * to a system default tmp directory.
+ *
+ * @see http://php.net/manual/en/function.tempnam.php
+ *
+ * @param string $prefix
+ *   The prefix of the generated temporary filename. Note: Windows uses only
+ *   the first three characters of prefix.
+ * @return mixed
+ *   Returns the new temporary filename, or FALSE on failure.
+ */
+function drupal_tempnam($prefix) {
+
+  /*
+    TODO This implementation will change once we get the rest of the API sorted.
+  */
+  return tempnam(DrupalStreamWrapperRegistry::getInstance('temp://')->interpolate_uri(''), $prefix);
+}
+
+/**
  * @} End of "defgroup file".
  */
diff --git includes/stream_wrapper_registry.inc includes/stream_wrapper_registry.inc
new file mode 100644
index 0000000..edee76f
--- /dev/null
+++ includes/stream_wrapper_registry.inc
@@ -0,0 +1,144 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Drupal stream wrapper manager
+ *
+ * Provide a class for managing and querying user defined stream wrappers
+ * in PHP. PHP's internal stream_get_wrappers doesn't return the class
+ * registered to handle a stream. We need to be able to find the handler
+ * for class instantiation.
+ *
+ * A stream is referenced as: scheme://target
+ */
+
+/**
+ * Drupal stream wrapper manager class
+ */
+class DrupalStreamWrapperRegistry {
+
+  // stream wrapper registry
+  private static $wrappers = array();
+
+  /**
+   * Private constructor to enforce singleton.
+   */
+  private function __construct() { }
+
+  /**
+   * Register a stream wrapper scheme.
+   *
+   * @see: http://php.net/manual/en/function.stream-wrapper-register.php
+   *
+   * @param string $scheme
+   *   URI scheme.
+   * @param string $class
+   *   Class name for the stream wrapper.
+   * @return bool
+   *   result of stream_wrapper_register()
+   */
+  public static function register($scheme, $class_name) {
+    self::$wrappers[$scheme] = $class_name;
+
+    return stream_wrapper_register($scheme, $class_name);
+  }
+
+  /**
+   * Unregister a stream wrapper.
+   *
+   * @see: http://php.net/manual/en/function.stream-wrapper-unregister.php
+   *
+   * @param string $scheme
+   *   URI scheme.
+   * @return bool
+   *   result of stream_wrapper_unregister()
+   */
+  public static function unregister($scheme) {
+    if (stream_wrapper_unregister($scheme)) {
+      unset(self::$wrappers[$scheme]);
+      return TRUE;
+    }
+
+    return FALSE;
+  }
+
+  /**
+   * Return the stream wrapper class name for a given scheme.
+   *
+   * @param string $scheme
+   *   Stream scheme.
+   * @return mixed
+   *   Return string if a scheme has a registered handler, or FALSE.
+   */
+  public static function getClassName($scheme) {
+    if (empty(self::$wrappers[$scheme])) {
+      return FALSE;
+    }
+
+    return self::$wrappers[$scheme];
+  }
+
+  /**
+   * Return the stream class name for a given scheme.
+   *
+   * @param string $scheme
+   *   Stream scheme.
+   * @return mixed
+   *   Return string if a scheme has a registered handler, or FALSE.
+   */
+  function scheme($class) {
+    return array_search(self::$wrappers, $class);
+  }
+
+  /**
+   * Return the entire Drupal stream wrapper registry.
+   *
+   * @return array
+   */
+  function wrappers() {
+    return self::$wrappers;
+  }
+
+  /**
+   * Get the scheme of a URI (stream).
+   *
+   * A stream is referenced as scheme://target.
+   *
+   * @param $uri
+   *   A stream, referenced as scheme://target
+   * @return mixed
+   *   A string containing the name of the scheme, or FALSE if none.
+   *   For example, the URI public://example.txt would return public.
+   */
+  public static function getStreamScheme($uri) {
+    $data = explode('://', $uri, 2);
+    return count($data) == 2 ? $data[0] : FALSE;
+  }
+
+  /**
+   * Get reference to stream wrapper class responsible for given URI (stream).
+   *
+   * The scheme determines the stream wrapper class that should be
+   * used by consulting the stream wrapper registry.
+   *
+   * @param $uri
+   *   A stream, referenced as scheme://target
+   * @return mixed
+   *   Returns a new stream wrapper object appropriate for the given URI.
+   *   For example, a URI of public://example.txt would return a new
+   *   private stream wrapper object (DrupalPrivateStreamWrapper).
+   *   FALSE is returned if no registered handler could be found.
+   */
+  public static function getInstance($uri) {
+
+    $class = self::getClassName(self::getStreamScheme($uri));
+    if (class_exists($class)) {
+      return new $class;
+    }
+    else {
+      return FALSE;
+    }
+  }
+
+}
diff --git includes/stream_wrappers.inc includes/stream_wrappers.inc
new file mode 100644
index 0000000..9281e6f
--- /dev/null
+++ includes/stream_wrappers.inc
@@ -0,0 +1,531 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Drupal stream wrapper interface.
+ *
+ * Provides a Drupal interface and classes to implement PHP stream wrappers for
+ * public, private and temporary files.
+ */
+
+
+/**
+ * Generic PHP stream wrapper interface.
+ *
+ * @see: http://www.php.net/manual/en/class.streamwrapper.php
+ */
+interface stream_wrapper_interface {
+  public function stream_open($uri, $mode, $options, &$opened_url);
+  public function stream_close();
+  public function stream_lock($operation);
+  public function stream_read($count);
+  public function stream_write($data);
+  public function stream_eof();
+  public function stream_seek($offset, $whence);
+  public function stream_flush();
+  public function stream_tell();
+  public function stream_stat();
+  public function unlink($uri);
+  public function rename($from_uri, $to_uri);
+  public function mkdir($uri, $mode, $options);
+  public function rmdir($uri, $options);
+  public function url_stat($uri, $flags);
+  public function dir_opendir($uri, $options);
+  public function dir_readdir();
+  public function dir_rewinddir();
+  public function dir_closedir();
+}
+
+
+/**
+ * Drupal stream wrapper extension.
+ *
+ * Extend the stream_wrapper_interface with methods expected by
+ * Drupal stream wrapper classes.
+ */
+interface DrupalStreamWrapperInterface extends stream_wrapper_interface {
+
+  /**
+   * Return an absolute stream resource URL.
+   *
+   * @param $uri
+   *   Stream wrapper resource url.
+   * @return string
+   */
+  function interpolate_uri($uri);
+
+  /**
+   * Return the html accessible URL for a resource.
+   *
+   * @param $uri
+   *   Stream wrapper resource url.
+   * @return string
+   */
+  function html_uri($uri);
+
+  /**
+   * Return the mime type of a resource.
+   *
+   * @param $uri
+   *   Stream wrapper resource url.
+   * @return string
+   */
+  function mime($uri);
+
+  /**
+   * Change permissions of stream.
+   *
+   * PHP lacks this functionality and it is not part of the official
+   * stream wrapper interface. This is a custom implementation for
+   * Drupal.
+   *
+   * @param string $uri
+   * @param mixed $mode
+   * @return bool
+   *   Returns TRUE on success or FALSE on failure.
+   */
+  function stream_chmod($uri, $mode);
+
+  /**
+   * Returns canonicalized absolute pathname.
+   *
+   * Implementation placeholder. PHP's realpath does not support
+   * stream wrappers. We provide this as a default so that
+   * individual wrappers may implement their own solutions.
+   * The base class will always return FALSE.
+   *
+   * @param string $uri
+   * @return mixed
+   *   A string with absolute pathname on success (implemented
+   *   by core wrappers), or FALSE on failure or the registered
+   *   wrapper does not provide an implementation.
+   */
+  function stream_realpath($uri);
+}
+
+
+/**
+ * Drupal stream wrapper base class for local files.
+ *
+ * This class provides a complete stream wrapper implementation. It passes
+ * incoming URI's through an interpolation method and then recursively calls
+ * the invoking PHP filesystem function.
+ *
+ * DrupalLocalStreamWrapper implementations need to override at least the
+ * interpolate_uri() method to rewrite the URI before is it passed back to the
+ * calling function.
+ */
+abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface {
+
+  private $handle = NULL;
+
+  /**
+   * Where are the files located?
+   *
+   * @return
+   *   String specifying the path
+   */
+  abstract static protected function directoryPath();
+
+  /**
+   * Interpolate the URI path, adding the base path from $this->directoryPath().
+   */
+  function interpolate_uri($uri) {
+    $basepath = realpath($this->directoryPath());
+    return drupal_realpath($basepath) . str_replace('/..','', parse_url($uri, PHP_URL_PATH));
+  }
+
+  function html_uri($uri) {
+    return $uri;
+  }
+
+  function mime($uri) {
+    return file_get_mimetype(basename($uri));
+  }
+
+  function stream_chmod($uri, $mode) {
+    return @chmod($uri, $mode);
+  }
+
+  function stream_realpath($uri) {
+    return realpath($this->interpolate_uri($uri));
+  }
+
+
+  /**
+   * Support for fopen(), file_get_contents(), file_put_contents() etc.
+   *
+   * @see http://php.net/manual/en/streamwrapper.stream-open.php
+   *
+   * @param $path
+   *   A string containing the path to the file to open.
+   * @param $mode
+   *   The file mode ("r", "wb" etc.).
+   * @param $options
+   *   A bit mask of STREAM_USE_PATH and STREAM_REPORT_ERRORS.
+   * @param &$opened_path
+   *   A string containing the path actually opened.
+   * @return bool
+   *   TRUE if file was opened successfully.
+   */
+  public function stream_open($uri, $mode, $options, &$opened_url) {
+    $uri = $this->interpolate_uri($uri);
+    $this->handle = ($options & STREAM_REPORT_ERRORS) ? fopen($uri, $mode) : @fopen($uri, $mode);
+
+    if ((bool)$this->handle && $options & STREAM_USE_PATH) {
+      $opened_url = $uri;
+    }
+
+    return (bool)$this->handle;
+  }
+
+  /**
+   * Support for flock().
+   *
+   * @see http://php.net/manual/en/streamwrapper.stream-lock.php
+   *
+   * @param int $operation
+   * @return bool
+   *   Always returns TRUE.
+   */
+  function stream_lock($operation) {
+    if (in_array($operation, array(LOCK_SH, LOCK_EX, LOCK_UN, LOCK_NB))) {
+      return flock($this->handle, $operation);
+    }
+
+    return TRUE;
+  }
+
+  /**
+   * Support for fread(), file_get_contents() etc.
+   *
+   * @see http://php.net/manual/en/streamwrapper.stream-read.php
+   *
+   * @param $count
+   *   Maximum number of bytes to be read.
+   * @return
+   *   The string that was read, or FALSE in case of an error.
+   */
+  public function stream_read($count) {
+    return fread($this->handle, $count);
+  }
+
+  /**
+   * Support for fwrite(), file_put_contents() etc.
+   *
+   * @see http://php.net/manual/en/streamwrapper.stream-write.php
+   *
+   * @param $data
+   *   The string to be written.
+   * @return int
+   *   The number of bytes written.
+   */
+  public function stream_write($data) {
+    return fwrite($this->handle, $data);
+  }
+
+  /**
+   * Support for feof().
+   *
+   * @see http://php.net/manual/en/streamwrapper.stream-eof.php
+   *
+   * @return bool
+   *   TRUE if end-of-file has been reached.
+   */
+  public function stream_eof() {
+    return feof($this->handle);
+  }
+
+  /**
+   * Support for fseek().
+   *
+   * @see http://php.net/manual/en/streamwrapper.stream-seek.php
+   *
+   * @param $offset
+   *   The byte offset to got to.
+   * @param $whence
+   *   SEEK_SET, SEEK_CUR, or SEEK_END.
+   * @return
+   *   TRUE on success
+   */
+  public function stream_seek($offset, $whence) {
+    return fseek($this->handle, $offset, $whence);
+  }
+
+  /**
+   * Support for fflush().
+   *
+   * @see http://php.net/manual/en/streamwrapper.stream-flush.php
+   *
+   * @return
+   *   TRUE if data was successfully stored (or there was no data to store).
+   */
+  public function stream_flush() {
+    return fflush($this->handle);
+  }
+
+  /**
+   * Support for ftell().
+   *
+   * @see http://php.net/manual/en/streamwrapper.stream-tell.php
+   *
+   * @return
+   *   The current offset in bytes from the beginning of file.
+   */
+  public function stream_tell() {
+    return ftell($this->handle);
+  }
+
+  /**
+   * Support for fstat().
+   *
+   * @see http://php.net/manual/en/streamwrapper.stream-stat.php
+   *
+   * @return
+   *   An array with file status, or FALSE in case of an error - see fstat()
+   *   for a description of this array.
+   */
+  public function stream_stat() {
+    return fstat($this->handle);
+  }
+
+  /**
+   * Support for fclose().
+   *
+   * @see http://php.net/manual/en/streamwrapper.stream-close.php
+   *
+   * @return
+   *   TRUE if stream was successfully closed.
+   */
+  public function stream_close() {
+    return fclose($this->handle);
+  }
+
+  /**
+   * Support for unlink().
+   *
+   * @see http://php.net/manual/en/streamwrapper.unlink.php
+   *
+   * @param $uri
+   *   A string containing the uri to the resource to delete.
+   * @return
+   *   TRUE if resource was successfully deleted.
+   */
+  public function unlink($uri) {
+    return unlink($this->interpolate_uri($uri));
+  }
+
+  /**
+   * Support for rename().
+   *
+   * @see http://php.net/manual/en/streamwrapper.rename.php
+   *
+   * @param $from_uri,
+   *   The uri to the file to rename.
+   * @param $to_uri
+   *   The new uri for file.
+   * @return
+   *   TRUE if file was successfully renamed.
+   */
+  public function rename($from_uri, $to_uri) {
+    return rename($this->interpolate_uri($from_uri), $this->interpolate_uri($to_uri));
+  }
+
+  /**
+   * Support for mkdir().
+   *
+   * @see http://php.net/manual/en/streamwrapper.mkdir.php
+   *
+   * @param $uri
+   *   A string containing the url to the directory to create.
+   * @param $mode
+   *   Permission flags - see mkdir().
+   * @param $options
+   *   A bit mask of STREAM_REPORT_ERRORS and STREAM_MKDIR_RECURSIVE.
+   * @return
+   *   TRUE if directory was successfully created.
+   */
+  public function mkdir($uri, $mode, $options) {
+    $recursive = (bool)($options & STREAM_MKDIR_RECURSIVE);
+    if ($options & STREAM_REPORT_ERRORS) {
+      return mkdir($this->interpolate_uri($uri), $mode, $recursive);
+    }
+    else {
+      return @mkdir($this->interpolate_uri($uri), $mode, $recursive);
+    }
+  }
+
+  /**
+   * Support for rmdir().
+   *
+   * @see http://php.net/manual/en/streamwrapper.rmdir.php
+   *
+   * @param $uri
+   *   A string containing the url to the directory to delete.
+   * @param $options
+   *   A bit mask of STREAM_REPORT_ERRORS.
+   * @return
+   *   TRUE if directory was successfully removed.
+   */
+  public function rmdir($uri, $options) {
+    if ($options & STREAM_REPORT_ERRORS) {
+      return rmdir($this->interpolate_uri($uri));
+    }
+    else {
+      return @rmdir($this->interpolate_uri($uri));
+    }
+  }
+
+  /**
+   * Support for stat().
+   *
+   * @see http://php.net/manual/en/streamwrapper.url-stat.php
+   *
+   * @param $uri
+   *   A string containing the url 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.
+   */
+  public function url_stat($uri, $flags) {
+    if ($flags & STREAM_URL_STAT_QUIET) {
+      return @stat($this->interpolate_uri($uri));
+    }
+    else {
+      return stat($this->interpolate_uri($uri));
+    }
+  }
+
+  /**
+   * Support for opendir().
+   *
+   * @see http://php.net/manual/en/streamwrapper.dir-opendir.php
+   *
+   * @param $uri
+   *   A string containing the url to the directory to open.
+   * @param $options
+   *   Unknown (parameter is not documented in PHP Manual).
+   * @return
+   *   TRUE on success.
+   */
+  public function dir_opendir($uri, $options) {
+    $this->handle = opendir($this->interpolate_uri($uri));
+
+    return (bool)$this->handle;
+  }
+
+  /**
+   * Support for readdir().
+   *
+   * @see http://php.net/manual/en/streamwrapper.dir-readdir.php
+   *
+   * @return
+   *   The next filename, or FALSE if there are no more files in the directory.
+   */
+  public function dir_readdir() {
+    return readdir($this->handle);
+  }
+
+  /**
+   * Support for rewinddir().
+   *
+   * @see http://php.net/manual/en/streamwrapper.dir-rewinddir.php
+   *
+   * @return
+   *   TRUE on success.
+   */
+  public function dir_rewinddir() {
+    return rewinddir($this->handle);
+  }
+
+  /**
+   * Support for closedir().
+   *
+   * @see http://php.net/manual/en/streamwrapper.dir-closedir.php
+   *
+   * @return
+   *   TRUE on success.
+   */
+  public function dir_closedir() {
+    return closedir($this->handle);
+  }
+}
+
+
+/**
+ * Drupal public (public://) stream wrapper class.
+ *
+ * Provides support for storing publicly accessible
+ * files with the Drupal file interface.
+ */
+class DrupalPublicStreamWrapper extends DrupalLocalStreamWrapper {
+
+  static protected function directoryPath() {
+    return variable_get('stream_public_path', 'sites/default/files');
+  }
+
+  /**
+   * Override html_uri().
+   *
+   * Return the HTML URI of a public file.
+   */
+  function html_uri($uri) {
+    $basepath = $this->directoryPath();
+    $path     = parse_url($uri, PHP_URL_PATH);
+
+    return $GLOBALS['base_url'] . '/' . $basepath . '/' . str_replace('\\', '/', $path);
+  }
+}
+
+
+/**
+ * Drupal private (private://) stream wrapper class.
+ *
+ * Provides support for storing privately accessible
+ * files with the Drupal file interface.
+ *
+ * Extends DrupalPublicStreamWrapper.
+ */
+class DrupalPrivateStreamWrapper extends DrupalLocalStreamWrapper {
+
+  static protected function directoryPath() {
+    return variable_get('stream_private_path', 'sites/default/files-private');
+  }
+
+  /**
+   * Override html_uri.
+   *
+   * Return the HTML URI of a private file.
+   */
+  function html_uri($uri) {
+    return url('system/files/' . parse_url($uri, PHP_URL_PATH), array('absolute' => TRUE));
+  }
+}
+
+
+/**
+ * Drupal temp (temp://) stream wrapper class.
+ *
+ * Provides support for storing temporarily accessible
+ * files with the Drupal file interface.
+ *
+ * Extends DrupalPublicStreamWrapper.
+ */
+class DrupalTempStreamWrapper extends DrupalLocalStreamWrapper {
+
+  static protected function directoryPath() {
+    return variable_get('file_directory_temp', 'sites/default/files/tmp');
+  }
+
+  /**
+   * Override html_uri.
+   *
+   * Return the HTML URI of a private file.
+   */
+  function html_uri($uri) {
+    return url('system/files/' . parse_url($uri, PHP_URL_PATH), array('absolute' => TRUE));
+  }
+}
diff --git modules/simpletest/tests/file.test modules/simpletest/tests/file.test
index 18bc651..d1a1af0 100644
--- modules/simpletest/tests/file.test
+++ modules/simpletest/tests/file.test
@@ -41,6 +41,37 @@ function file_test_file_scan_callback($filepath, $reset = FALSE) {
 }
 
 /**
+ * Helper class for testing the stream wrapper registry.
+ *
+ * Dummy stream wrapper implementation (dummy://).
+ */
+class DrupalDummyStreamWrapper extends DrupalLocalStreamWrapper {
+  // TODO figure out what this should really return.
+  static protected function directoryPath() {
+    return variable_get('stream_public_path', 'sites/default/files');
+  }
+
+  /**
+   * Override interpolate_uri().
+   *
+   * Return a dummy path for testing.
+   */
+  function interpolate_uri($uri) {
+    return '/dummy/example.txt';
+  }
+
+  /**
+   * Override html_uri().
+   *
+   * Return the HTML URI of a public file.
+   */
+  function html_uri($uri) {
+    return '/dummy/example.txt';
+  }
+
+}
+
+/**
  * Base class for file tests that adds some additional file specific
  * assertions and helper functions.
  */
@@ -401,7 +432,7 @@ class FileValidatorTest extends DrupalWebTestCase {
     if (image_get_toolkit()) {
       // Copy the image so that the original doesn't get resized.
       $temp_dir = file_directory_temp();
-      copy(realpath('misc/druplicon.png'), realpath($temp_dir) . '/druplicon.png');
+      copy(drupal_realpath('misc/druplicon.png'), drupal_realpath($temp_dir) . '/druplicon.png');
       $this->image->filepath = $temp_dir . '/druplicon.png';
 
       $errors = file_validate_image_resolution($this->image, '10x5');
@@ -411,7 +442,7 @@ class FileValidatorTest extends DrupalWebTestCase {
       $this->assertTrue($info['width'] <= 10, t('Image scaled to correct width.'), 'File');
       $this->assertTrue($info['height'] <= 5, t('Image scaled to correct height.'), 'File');
 
-      unlink(realpath($temp_dir . '/druplicon.png'));
+      unlink(drupal_realpath($temp_dir . '/druplicon.png'));
     }
     else {
       // TODO: should check that the error is returned if no toolkit is available.
@@ -505,14 +536,14 @@ class FileUnmanagedSaveDataTest extends FileTestCase {
     $filepath = file_unmanaged_save_data($contents);
     $this->assertTrue($filepath, t('Unnamed file saved correctly.'));
     $this->assertEqual(file_directory_path(), dirname($filepath), t("File was placed in Drupal's files directory."));
-    $this->assertEqual($contents, file_get_contents(realpath($filepath)), t('Contents of the file are correct.'));
+    $this->assertEqual($contents, file_get_contents(drupal_realpath($filepath)), t('Contents of the file are correct.'));
 
     // Provide a filename.
     $filepath = file_unmanaged_save_data($contents, 'asdf.txt', FILE_EXISTS_REPLACE);
     $this->assertTrue($filepath, t('Unnamed file saved correctly.'));
     $this->assertEqual(file_directory_path(), dirname($filepath), t("File was placed in Drupal's files directory."));
     $this->assertEqual('asdf.txt', basename($filepath), t('File was named correctly.'));
-    $this->assertEqual($contents, file_get_contents(realpath($filepath)), t('Contents of the file are correct.'));
+    $this->assertEqual($contents, file_get_contents(drupal_realpath($filepath)), t('Contents of the file are correct.'));
     $this->assertFilePermissions($filepath, variable_get('file_chmod_file', 0664));
   }
 }
@@ -552,7 +583,7 @@ class FileSaveUploadTest extends FileHookTestCase {
     // Upload with replace to gurantee there's something there.
     $edit = array(
       'file_test_replace' => FILE_EXISTS_REPLACE,
-      'files[file_test_upload]' => realpath($this->image->filepath)
+      'files[file_test_upload]' => drupal_realpath($this->image->filepath)
     );
     $this->drupalPost('file-test/upload', $edit, t('Submit'));
     $this->assertResponse(200, t('Received a 200 response for posted test file.'));
@@ -579,7 +610,7 @@ class FileSaveUploadTest extends FileHookTestCase {
     // Upload a second file.
     $max_fid_before = db_query('SELECT MAX(fid) AS fid FROM {files}')->fetchField();
     $image2 = current($this->drupalGetTestFiles('image'));
-    $edit = array('files[file_test_upload]' => realpath($image2->filepath));
+    $edit = array('files[file_test_upload]' => drupal_realpath($image2->filepath));
     $this->drupalPost('file-test/upload', $edit, t('Submit'));
     $this->assertResponse(200, t('Received a 200 response for posted test file.'));
     $this->assertRaw(t('You WIN!'));
@@ -604,7 +635,7 @@ class FileSaveUploadTest extends FileHookTestCase {
   function testExistingRename() {
     $edit = array(
       'file_test_replace' => FILE_EXISTS_RENAME,
-      'files[file_test_upload]' => realpath($this->image->filepath)
+      'files[file_test_upload]' => drupal_realpath($this->image->filepath)
     );
     $this->drupalPost('file-test/upload', $edit, t('Submit'));
     $this->assertResponse(200, t('Received a 200 response for posted test file.'));
@@ -620,7 +651,7 @@ class FileSaveUploadTest extends FileHookTestCase {
   function testExistingReplace() {
     $edit = array(
       'file_test_replace' => FILE_EXISTS_REPLACE,
-      'files[file_test_upload]' => realpath($this->image->filepath)
+      'files[file_test_upload]' => drupal_realpath($this->image->filepath)
     );
     $this->drupalPost('file-test/upload', $edit, t('Submit'));
     $this->assertResponse(200, t('Received a 200 response for posted test file.'));
@@ -636,7 +667,7 @@ class FileSaveUploadTest extends FileHookTestCase {
   function testExistingError() {
     $edit = array(
       'file_test_replace' => FILE_EXISTS_ERROR,
-      'files[file_test_upload]' => realpath($this->image->filepath)
+      'files[file_test_upload]' => drupal_realpath($this->image->filepath)
     );
     $this->drupalPost('file-test/upload', $edit, t('Submit'));
     $this->assertResponse(200, t('Received a 200 response for posted test file.'));
@@ -2031,3 +2062,49 @@ class FileMimeTypeTest extends DrupalWebTestCase {
     }
   }
 }
+
+/**
+ * Tests stream wrapper registry.
+ */
+class StreamWrapperRegistryUnitTest extends DrupalWebTestCase {
+
+  public static function getInfo() {
+    return array(
+      'name' => t('Stream Wrapper Registry'),
+      'description' => t('Tests stream wrapper registry.'),
+      'group' => t('File'),
+    );
+  }
+
+  /**
+   * Test stream wrapper registry.
+   */
+  function testStreamWrapperRegistry() {
+    $scheme    = 'dummy';
+    $filename  = 'example.txt';
+    $class     = 'DrupalDummyStreamWrapper';
+    $uri = $scheme .'://'. $filename;
+
+    DrupalStreamWrapperRegistry::register($scheme, $class);
+    $this->assertTrue(DrupalStreamWrapperRegistry::getInstance($uri), 'Dummy stream wrapper registered');
+
+    // $this->assertEqual($this->scheme, stream_scheme($this->uri),  'stream_scheme() with valid stream wrapped URI.');
+    // $this->assertFalse(stream_scheme('/foo/bar'),                 'stream_scheme() with faulty URI.');
+    //$this->assertEqual('/dummy/example.txt', stream_wrapper($dummy_uri)->interpolate_uri($dummy_uri), 'Stream wrapper URI interpolation.');
+
+    // Unregister dummy wrapper
+    DrupalStreamWrapperRegistry::unregister($scheme);
+    $this->assertFalse(DrupalStreamWrapperRegistry::getInstance($uri), 'Dummy stream wrapper unregistered.');
+  }
+
+  /**
+   * Confirm core stream wrappers are available.
+   */
+   function testCoreStreamWrapperAvailability() {
+
+     $filename = 'example.txt';
+     $this->assertTrue(DrupalStreamWrapperRegistry::getInstance('public://'. $filename),  'Public stream wrapper registered and available.');
+     $this->assertTrue(DrupalStreamWrapperRegistry::getInstance('private://'. $filename), 'Private stream wrapper registered and available.');
+     $this->assertTrue(DrupalStreamWrapperRegistry::getInstance('temp://'. $filename),    'Temp stream wrapper registered and available.');
+   }
+}
