Index: includes/bootstrap.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/bootstrap.inc,v
retrieving revision 1.257
diff -u -9 -p -r1.257 bootstrap.inc
--- includes/bootstrap.inc	3 Dec 2008 14:51:53 -0000	1.257
+++ includes/bootstrap.inc	18 Dec 2008 18:57:53 -0000
@@ -1101,18 +1101,21 @@ function _drupal_bootstrap($phase) {
       break;
 
     case DRUPAL_BOOTSTRAP_DATABASE:
       // Initialize the database system.  Note that the connection
       // won't be initialized until it is actually requested.
       require_once DRUPAL_ROOT . '/includes/database/database.inc';
       // Register autoload functions so that we can access classes and interfaces.
       spl_autoload_register('drupal_autoload_class');
       spl_autoload_register('drupal_autoload_interface');
+      if (preg_match("/^simpletest\d+$/", $_SERVER['HTTP_USER_AGENT'])) {
+        stream_wrapper_register('simpletest', 'DummyStreamWrapper');
+      }
       break;
 
     case DRUPAL_BOOTSTRAP_ACCESS:
       // Deny access to blocked IP addresses - t() is not yet available.
       if (drupal_is_denied(ip_address())) {
         header($_SERVER['SERVER_PROTOCOL'] . ' 403 Forbidden');
         print 'Sorry, ' . check_plain(ip_address()) . ' has been banned.';
         exit();
       }
Index: includes/file.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/file.inc,v
retrieving revision 1.146
diff -u -9 -p -r1.146 file.inc
--- includes/file.inc	4 Dec 2008 11:09:33 -0000	1.146
+++ includes/file.inc	18 Dec 2008 18:57:53 -0000
@@ -158,22 +158,19 @@ function file_create_path($destination =
  *   will be set preventing them from saving the settings.
  * @return
  *   FALSE when directory not found, or TRUE when directory exists.
  */
 function file_check_directory(&$directory, $mode = 0, $form_item = NULL) {
   $directory = rtrim($directory, '/\\');
 
   // Check if directory exists.
   if (!is_dir($directory)) {
-    if (($mode & FILE_CREATE_DIRECTORY) && @mkdir($directory)) {
-      @chmod($directory, 0775); // Necessary for non-webserver users.
-    }
-    else {
+    if (!($mode & FILE_CREATE_DIRECTORY) || !@mkdir($directory, 0775)) {
       if ($form_item) {
         form_set_error($form_item, t('The directory %directory does not exist.', array('%directory' => $directory)));
         watchdog('file system', 'The directory %directory does not exist.', array('%directory' => $directory), WATCHDOG_ERROR);
       }
       return FALSE;
     }
   }
 
   // Check to see if the directory is writable.
@@ -187,19 +184,19 @@ function file_check_directory(&$director
       }
       return FALSE;
     }
   }
 
   if ((file_directory_path() == $directory || file_directory_temp() == $directory) && !is_file("$directory/.htaccess")) {
     $htaccess_lines = "SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006\nOptions None\nOptions +FollowSymLinks";
     if (($fp = fopen("$directory/.htaccess", 'w')) && fputs($fp, $htaccess_lines)) {
       fclose($fp);
-      chmod($directory . '/.htaccess', 0664);
+      @chmod($directory . '/.htaccess', 0664);
     }
     else {
       $variables = array('%directory' => $directory, '!htaccess' => '<br />' . nl2br(check_plain($htaccess_lines)));
       form_set_error($form_item, t("Security warning: Couldn't write .htaccess file. Please create a .htaccess file in your %directory directory which contains the following lines: <code>!htaccess</code>", $variables));
       watchdog('security', "Security warning: Couldn't write .htaccess file. Please create a .htaccess file in your %directory directory which contains the following lines: <code>!htaccess</code>", $variables, WATCHDOG_ERROR);
     }
   }
 
   return TRUE;
@@ -245,27 +242,32 @@ function file_check_path(&$path) {
  * @param $source
  *   A string set to the file to check.
  * @param $directory
  *   A string where the file should be located.
  * @return
  *   FALSE if the path does not exist in the directory; otherwise, the real
  *   path of the source.
  */
 function file_check_location($source, $directory = '') {
-  $check = realpath($source);
+  $check = drupal_realpath($source);
   if ($check) {
     $source = $check;
   }
   else {
+    // drupal_realpath() does not always resolve '/..'
+    $basename = basename($source);
+    if ($basename == '..') {
+      return FALSE;
+    }
     // This file does not yet exist.
-    $source = realpath(dirname($source)) . '/' . basename($source);
+    $source = drupal_realpath(dirname($source)) . '/' . $basename;
   }
-  $directory = realpath($directory);
+  $directory = drupal_realpath($directory);
   if ($directory && strpos($source, $directory) !== 0) {
     return FALSE;
   }
   return $source;
 }
 
 /**
  * Load a file object from the database.
  *
@@ -423,19 +425,19 @@ function file_copy($source, $destination
  *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
  *                          unique.
  *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
  * @return
  *   The path to the new file, or FALSE in the event of an error.
  *
  * @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;
   }
 
   $destination = file_create_path($destination);
   $directory = $destination;
   $basename = file_check_path($directory);
 
@@ -451,19 +453,19 @@ function file_unmanaged_copy($source, $d
   $destination = file_destination($directory . '/' . $basename, $replace);
 
   if ($destination === FALSE) {
     drupal_set_message(t('The specified file %file could not be copied because a file by that name already exists in the destination.', array('%file' => $source)), 'error');
     return FALSE;
   }
   // 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;
   }
   if (!@copy($source, $destination)) {
     drupal_set_message(t('The specified file %file could not be copied.', array('%file' => $source)), 'error');
     return FALSE;
   }
 
   // Give everyone read access so that FTP'd users or
@@ -1311,18 +1313,22 @@ function file_scan_directory($dir, $mask
     closedir($handle);
   }
 
   return $files;
 }
 
 /**
  * Determine the default temporary directory.
  *
+ * This may be used for storing temporary files within a single request. If a
+ * temporary file is to be used in multiple requests, it should be saved in
+ * file_directory_path with its status set to FILE_STATUS_TEMPORARY.
+ *
  * @return
  *   A string containing a temp directory.
  */
 function file_directory_temp() {
   $temporary_directory = variable_get('file_directory_temp', NULL);
 
   if (is_null($temporary_directory)) {
     $directories = array();
 
@@ -1355,19 +1361,20 @@ function file_directory_temp() {
 }
 
 /**
  * Determine the default 'files' directory.
  *
  * @return
  *   A string containing the path to Drupal's 'files' directory.
  */
 function file_directory_path() {
-  return variable_get('file_directory_path', conf_path() . '/files');
+  $path = variable_get('file_directory_path', conf_path() . '/files');
+  return $path;
 }
 
 /**
  * Determine the maximum file upload size by querying the PHP settings.
  *
  * @return
  *   A file size limit in bytes based on the PHP upload_max_filesize and
  *   post_max_size
  */
@@ -1749,11 +1756,79 @@ function file_get_mimetype($filename, $m
     if (preg_match('!\.('. $ext_preg .')$!i', $filename)) {
       return $mime_match;
     }
   }
 
   return 'application/octet-stream';
 }
 
 /**
+ * Get the stream wrapper prefix from of path.
+ *
+ * @param $path
+ *   A string containing a path to a file or directory.
+ * @return
+ *   The wrapper prefix, e.g. "foo" for the path "foo://bar.txt", or FALSE is
+ *   $path is a regular filesystem path.
+ */
+function file_get_wrapper($path) {
+  // A wrapper prefix is at least two characters so that it can be distinguished
+  // from a Windows drive letter, "C:/temp".
+  preg_match('@^(?<wrapper>[a-z0-9.+-]{2,})://@i', $path, $matches);
+  return $matches ? $matches['wrapper'] : FALSE;
+}
+
+/**
+ * Get canonicalized absolute path of a file or directory.
+ *
+ * Consecutive directory separator characters ("/" or "\") are stripped. If path
+ * is a directory, the trailing directory separator is stripped.
+ *
+ * For regular files:
+ * - Symbolic links are expanded.
+ * - "/./" and "/../" segments are resolved.
+ * For paths with protocol/wrapper prefix (e.g. "mywrapper://foo/bar.txt"):
+ * - Paths containing "/../" are blocked (FALSE is returned).
+ * - Paths are assumed to be case-sensitive (no case normalization is done).
+ * - On Windows, "\" is normalized to "/".
+ *
+ * @code
+ *   // Returns "/foo/bar/boo", or FALSE if the file does not exist:
+ *   drupal_realpath('/foo//bar/./baz/..\\boo');
+ *
+ *   // Returns FALSE due to "/../":
+ *   drupal_realpath('mywrapper://foo/bar/../baz');
+ * @endcode
+ *
+ * @param $path
+ *   A string containing a path to a file or directory.
+ * @return
+ *   A string containing the absolute path to the file/directory, or FALSE if
+ *   the file/directory does not exist.
+ */
+function drupal_realpath($path) {
+  // Does $path include an explicit protocol/wrapper prefix "foo://" (not a
+  // Windows drive letter "C:/temp")?
+  if (preg_match('@^([a-z0-9.+-]{2,})://(.*)@i', $path, $matches)) {
+    $wrappedPath = $matches[2];
+    // Replace platform-specific directory separator with "/".
+    if (DIRECTORY_SEPARATOR != '/') {
+      $wrappedPath = strtr($wrappedPath, DIRECTORY_SEPARATOR, '/');
+    }
+    // Replace "//" with "/", except when "//" is preceded by a colon (this
+    // indicates a nested stream wrapper prefix, e.g. "foo://bar://".
+    $wrappedPath = preg_replace('@(?<!:)/+@', '/', $wrappedPath);
+    // Look for ".." separated by "/" or string boundary
+    if (preg_match('@(?<=^|/)\.\.(?=/|$)@', $wrappedPath)) {
+      return FALSE;
+    }
+    $path = $matches[1] . '://' . $wrappedPath;
+    return file_exists($path) ? rtrim($path, '/') : FALSE;
+  }
+  else {
+    return realpath($path);
+  }
+}
+
+/**
  * @} End of "defgroup file".
  */
Index: modules/color/color.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/color/color.module,v
retrieving revision 1.50
diff -u -9 -p -r1.50 color.module
--- modules/color/color.module	10 Nov 2008 05:22:59 -0000	1.50
+++ modules/color/color.module	18 Dec 2008 18:57:53 -0000
@@ -491,19 +491,19 @@ function _color_render_images($theme, &$
       imagecopy($slice, $target, 0, 0, $x, $y, $width, $height);
     }
 
     // Save image.
     imagepng($slice, $image);
     imagedestroy($slice);
     $paths['files'][] = $image;
 
     // Set standard file permissions for webserver-generated files
-    @chmod(realpath($image), 0664);
+    @chmod($image, 0664);
 
     // Build before/after map of image paths.
     $paths['map'][$file] = $base;
   }
 
   // Clean up target buffer.
   imagedestroy($target);
 }
 
Index: modules/simpletest/drupal_web_test_case.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/drupal_web_test_case.php,v
retrieving revision 1.75
diff -u -9 -p -r1.75 drupal_web_test_case.php
--- modules/simpletest/drupal_web_test_case.php	18 Dec 2008 00:42:55 -0000	1.75
+++ modules/simpletest/drupal_web_test_case.php	18 Dec 2008 18:57:54 -0000
@@ -574,20 +574,25 @@ class DrupalWebTestCase {
    *   File size in bytes to match. Please check the tests/files folder.
    * @return
    *   List of files that match filter.
    */
   protected function drupalGetTestFiles($type, $size = NULL) {
     $files = array();
 
     // Make sure type is valid.
     if (in_array($type, array('binary', 'html', 'image', 'javascript', 'php', 'sql', 'text'))) {
-     // Use original file directory instead of one created during setUp().
+      // Use original file directory instead of one created during setUp().
       $path = $this->originalFileDirectory . '/simpletest';
+      // When useStreamWrapper() has been called, also return test files with
+      // a wrapper prefix.
+      if (file_get_wrapper(file_directory_path()) == 'dummy') {
+        $path = 'dummy://' . $path;
+      }
       $files = file_scan_directory($path, '/' . $type . '\-.*/');
 
       // If size is set then remove any files that are not of that size.
       if ($size !== NULL) {
         foreach ($files as $file) {
           $stats = stat($file->filename);
           if ($stats['size'] != $size) {
             unset($files[$file->filename]);
           }
@@ -785,18 +790,20 @@ class DrupalWebTestCase {
    * @param ...
    *   List of modules to enable for the duration of the test.
    */
   protected function setUp() {
     global $db_prefix, $user;
 
     // Store necessary current values before switching to prefixed database.
     $this->originalPrefix = $db_prefix;
     $clean_url_original = variable_get('clean_url', 0);
+    $file_downloads_original = variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC);
+    $this->originalFileDirectory = file_directory_path();
 
     // Generate temporary prefixed database to ensure that tests have a clean starting point.
     $db_prefix = Database::getActiveConnection()->prefixTables('{simpletest' . mt_rand(1000, 1000000) . '}');
 
     include_once DRUPAL_ROOT . '/includes/install.inc';
     drupal_install_system();
 
     $this->preloadRegistry();
 
@@ -824,28 +831,41 @@ class DrupalWebTestCase {
     // Log in with a clean $user.
     $this->originalUser = $user;
     drupal_save_session(FALSE);
     $user = user_load(array('uid' => 1));
 
     // Restore necessary variables.
     variable_set('install_profile', 'default');
     variable_set('install_task', 'profile-finished');
     variable_set('clean_url', $clean_url_original);
+    variable_set('file_downloads', $file_downloads_original);
     variable_set('site_mail', 'simpletest@example.com');
 
     // Use temporary files directory with the same prefix as database.
-    $this->originalFileDirectory = file_directory_path();
-    variable_set('file_directory_path', file_directory_path() . '/' . $db_prefix);
-    $directory = file_directory_path();
+    $directory = $this->originalFileDirectory . '/' . $db_prefix;
+    variable_set('file_directory_path', $directory);
     file_check_directory($directory, FILE_CREATE_DIRECTORY); // Create the files directory.
   }
 
   /**
+   * Register "simpletest://" stream wrapper and set file_directory_path to a
+   * path with this wrapper prefix, and set file_downloads to
+   * FILE_DOWNLOADS_PRIVATE.
+   */
+  function useStreamWrapper($register = TRUE) {
+    if (!in_array('simpletest', stream_get_wrappers())) {
+      stream_wrapper_register('simpletest', 'DummyStreamWrapper');
+    }
+    variable_set('file_directory_path', 'simpletest://' . file_directory_path());
+    variable_set('file_downloads', FILE_DOWNLOADS_PRIVATE);
+  }
+
+  /**
    * This method is called by DrupalWebTestCase::setUp, and preloads the
    * registry from the testing site to cut down on the time it takes to
    * setup a clean environment for the current test run.
    */
   protected function preloadRegistry() {
     db_query('INSERT INTO {registry} SELECT * FROM ' . $this->originalPrefix . 'registry');
     db_query('INSERT INTO {registry_file} SELECT * FROM ' . $this->originalPrefix . 'registry_file');
   }
 
@@ -1089,32 +1109,48 @@ class DrupalWebTestCase {
     if ($this->parse()) {
       $edit_save = $edit;
       // Let's iterate over all the forms.
       $forms = $this->xpath('//form');
       foreach ($forms as $form) {
         // We try to set the fields of this form as specified in $edit.
         $edit = $edit_save;
         $post = array();
         $upload = array();
+        $temp_dir = FALSE;
         $submit_matches = $this->handleForm($post, $edit, $upload, $submit, $form);
         $action = isset($form['action']) ? $this->getAbsoluteUrl($form['action']) : $this->getUrl();
 
         // We post only if we managed to handle every field in edit and the
         // submit button matches.
         if (!$edit && $submit_matches) {
           if ($upload) {
             // TODO: cURL handles file uploads for us, but the implementation
             // is broken. This is a less than elegant workaround. Alternatives
             // are being explored at #253506.
             foreach ($upload as $key => $file) {
-              $file = realpath($file);
-              if ($file && is_file($file)) {
-                $post[$key] = '@' . $file;
+              // cURL does not support stream wrappers
+              if (file_get_wrapper($file)) {
+                $temp_dir = file_directory_temp() . '/simpletest_drupal_web_test_case';
+                if (!is_dir($temp_dir)) {
+                  mkdir($temp_dir);
+                }
+                $upload_file = $temp_dir . '/' . basename($file);
+                copy($file, $upload_file);
+                if (!is_file($upload_file)) {
+                  $this->fail(t('Failed to create temporary file @file', array('@file' => $upload_file)));
+                  return;
+                }
+              }
+              else {
+                $upload_file = realpath($file);
+              }
+              if ($upload_file && is_file($upload_file)) {
+                $post[$key] = '@' . $upload_file;
               }
             }
           }
           else {
             foreach ($post as $key => $value) {
               // Encode according to application/x-www-form-urlencoded
               // Both names and values needs to be urlencoded, according to
               // http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.1
               $post[$key] = urlencode($key) . '=' . urlencode($value);
Index: modules/simpletest/tests/file.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/file.test,v
retrieving revision 1.14
diff -u -9 -p -r1.14 file.test
--- modules/simpletest/tests/file.test	27 Nov 2008 08:41:45 -0000	1.14
+++ modules/simpletest/tests/file.test	18 Dec 2008 18:57:54 -0000
@@ -24,18 +24,22 @@ class FileTestCase extends DrupalWebTest
    *
    * @param $filepath
    *   String file path.
    * @param $expected_mode
    *   Octal integer like 0664 or 0777.
    * @param $message
    *   Optional message.
    */
   function assertFilePermissions($filepath, $expected_mode, $message = NULL) {
+    // File permissions are not supported by stream wrappers
+    if (file_get_wrapper($filepath)) {
+      return;
+    }
     // Mask out all but the last three octets.
     $actual_mode = fileperms($filepath) & 511;
     if (is_null($message)) {
       if ($actual_mode == $expected_mode) {
         $message = t('File permissions set correctly.');
       }
       else {
         $message = t('Expected file permission to be %expected, actually were %actual.', array('%actual' => decoct($actual_mode), '%expected' => decoct($expected_mode)));
       }
@@ -176,62 +180,97 @@ class FileValidatorTest extends DrupalWe
     $errors = file_validate_is_image($this->image);
     $this->assertEqual(count($errors), 0, t('No error reported for our image file.'), 'File');
 
     $this->assertTrue(file_exists($this->non_image->filepath), t('The non-image being tested exists.'), 'File');
     $errors = file_validate_is_image($this->non_image);
     $this->assertEqual(count($errors), 1, t('An error reported for our non-image file.'), 'File');
   }
 
   /**
+   * Test file_validate_is_image() on files with stream wrapper prefix.
+   */
+  function testFileValidateIsImageWithStreamWrappers() {
+    $this->useStreamWrapper();
+
+    $image = current($this->drupalGetTestFiles('image'));
+    $this->image->filepath = $image->filename;
+    $this->image->filename = $image->basename;
+
+    $non_image = current($this->drupalGetTestFiles('text'));
+    $this->non_image->filepath = $non_image->filename;
+    $this->non_image->filename = $non_image->basename;
+
+    $this->testFileValidateIsImage();
+  }
+
+  /**
    *  This ensures the resolution of a specific file is within bounds.
    *  The image will be resized if it's too large.
    */
   function testFileValidateImageResolution() {
     // Non-images.
     $errors = file_validate_image_resolution($this->non_image);
     $this->assertEqual(count($errors), 0, t("Shouldn't get any errors for a non-image file."), 'File');
     $errors = file_validate_image_resolution($this->non_image, '50x50', '100x100');
     $this->assertEqual(count($errors), 0, t("Don't check the resolution on non files."), 'File');
 
     // Minimum size.
     $errors = file_validate_image_resolution($this->image);
     $this->assertEqual(count($errors), 0, t('No errors for an image when there is no minimum or maximum resolution.'), 'File');
-    $errors = file_validate_image_resolution($this->image, 0, '200x1');
+    $errors = file_validate_image_resolution($this->image, 0, '400x1');
     $this->assertEqual(count($errors), 1, t("Got an error for an image that wasn't wide enough."), 'File');
-    $errors = file_validate_image_resolution($this->image, 0, '1x200');
+    $errors = file_validate_image_resolution($this->image, 0, '1x400');
     $this->assertEqual(count($errors), 1, t("Got an error for an image that wasn't tall enough."), 'File');
-    $errors = file_validate_image_resolution($this->image, 0, '200x200');
+    $errors = file_validate_image_resolution($this->image, 0, '400x400');
     $this->assertEqual(count($errors), 1, t('Small images report an error.'), 'File');
 
     // Maximum size.
     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('misc/druplicon.png', $temp_dir . '/druplicon.png');
       $this->image->filepath = $temp_dir . '/druplicon.png';
 
       $errors = file_validate_image_resolution($this->image, '10x5');
       $this->assertEqual(count($errors), 0, t('No errors should be reported when an oversized image can be scaled down.'), 'File');
 
       $info = image_get_info($this->image->filepath);
       $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($temp_dir . '/druplicon.png');
     }
     else {
       // TODO: should check that the error is returned if no toolkit is available.
       $errors = file_validate_image_resolution($this->image, '5x10');
       $this->assertEqual(count($errors), 1, t("Oversize images that can't be scaled get an error."), 'File');
     }
   }
 
   /**
+   * Test file_validate_image_resolution() on files with stream wrapper
+   * prefix.
+   */
+  function testFileValidateImageResolutionWithStreamWrappers() {
+    $this->useStreamWrapper();
+
+    $image = current($this->drupalGetTestFiles('image'));
+    $this->image->filepath = $image->filename;
+    $this->image->filename = $image->basename;
+
+    $non_image = current($this->drupalGetTestFiles('text'));
+    $this->non_image->filepath = $non_image->filename;
+    $this->non_image->filename = $non_image->basename;
+
+    $this->testFileValidateImageResolution();
+  }
+
+  /**
    *  This will ensure the filename length is valid.
    */
   function testFileValidateNameLength() {
     // Create a new file object.
     $file = new stdClass();
 
     // Add a filename with an allowed length and test it.
     $file->filename = str_repeat('x', 255);
     $this->assertEqual(strlen($file->filename), 255);
@@ -304,28 +343,37 @@ class FileUnmanagedSaveDataTest extends 
    * Test the file_unmanaged_save_data() function.
    */
   function testFileSaveData() {
     $contents = $this->randomName(8);
 
     // No filename.
     $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($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($filepath), t('Contents of the file are correct.'));
     $this->assertFilePermissions($filepath, 0664);
   }
+
+  /**
+   * Test the file_unmanaged_save_data() function with a file with stream
+   * wrapper prefix.
+   */
+  function testFileSaveDataWithStreamWrapper() {
+    $this->useStreamWrapper();
+    $this->testFileSaveData();
+  }
 }
 
 /**
  * Test the file_save_upload() function.
  */
 class FileSaveUploadTest extends FileHookTestCase {
   function getInfo() {
     return array(
       'name' => t('File uploading'),
@@ -338,46 +386,55 @@ class FileSaveUploadTest extends FileHoo
    * Test the file_save_upload() function.
    */
   function testFileSaveUpload() {
     $max_fid_before = db_result(db_query('SELECT MAX(fid) AS fid FROM {files}'));
     $upload_user = $this->drupalCreateUser(array('access content'));
     $this->drupalLogin($upload_user);
 
     $image = current($this->drupalGetTestFiles('image'));
     $this->assertTrue(is_file($image->filename), t("The file we're going to upload exists."));
-    $edit = array('files[file_test_upload]' => realpath($image->filename));
+    $edit = array('files[file_test_upload]' => $image->filename);
     $this->drupalPost('file-test/upload', $edit, t('Submit'));
     $this->assertResponse(200, t('Received a 200 response for posted test file.'));
 
     // We can't easily check that the hooks were called but since
     // file_save_upload() calles file_save() we can rely on file_save()'s
     // test to catch problems invoking the hooks.
 
     $max_fid_after = db_result(db_query('SELECT MAX(fid) AS fid FROM {files}'));
     $this->assertTrue($max_fid_after > $max_fid_before, t('A new file was created.'));
     $this->assertTrue(file_load($max_fid_after), t('Loaded the file.'));
   }
+
+  /**
+   * Test the file_save_upload() function when file_directory_path contains a
+   * stream wrapper prefix.
+   */
+  function testFileSaveUploadWithStreamWrapper() {
+    $this->useStreamWrapper();
+    $this->testFileSaveUpload();
+  }
 }
 
 /**
  * Directory related tests.
  */
 class FileDirectoryTest extends FileTestCase {
   function getInfo() {
     return array(
       'name' => t('File paths and directories'),
       'description' => t('Tests operations dealing with directories.'),
       'group' => t('File'),
     );
   }
 
   /**
-   * Test the file_directory_path() function.
+   * Test the file_check_directory() function.
    */
   function testFileCheckDirectory() {
     // A directory to operate on.
     $directory = file_directory_path() . '/' . $this->randomName();
     $this->assertFalse(is_dir($directory), t('Directory does not exist prior to testing.'));
 
     // Non-existent directory.
     $form_element = $this->randomName();
     $this->assertFalse(file_check_directory($directory, 0, $form_element), t('Error reported for non-existing directory.'), 'File');
@@ -386,26 +443,29 @@ class FileDirectoryTest extends FileTest
     $errors = form_get_errors();
     $this->assertEqual($errors[$form_element], t('The directory %directory does not exist.', array('%directory' => $directory)), t('Properly generated an error for the passed form element.'), 'File');
 
     // Make a directory.
     $this->assertTrue(file_check_directory($directory, FILE_CREATE_DIRECTORY), t('No error reported when creating a new directory.'), 'File');
 
     // Make sure directory actually exists.
     $this->assertTrue(is_dir($directory), t('Directory actually exists.'), 'File');
 
-    // Make directory read only.
-    @chmod($directory, 0444);
-    $form_element = $this->randomName();
-    $this->assertFalse(file_check_directory($directory, 0, $form_element), t('Error reported for a non-writeable directory.'), 'File');
-
-    // Check if form error was set.
-    $errors = form_get_errors();
-    $this->assertEqual($errors[$form_element], t('The directory %directory is not writable', array('%directory' => $directory)), t('Properly generated an error for the passed form element.'), 'File');
+    // chmod() does not support stream wrappers.
+    if (!file_get_wrapper($directory)) {
+      // Make directory read only.
+      @chmod($directory, 0444);
+      $form_element = $this->randomName();
+      $this->assertFalse(file_check_directory($directory, 0, $form_element), t('Error reported for a non-writeable directory.'), 'File');
+
+      // Check if form error was set.
+      $errors = form_get_errors();
+      $this->assertEqual($errors[$form_element], t('The directory %directory is not writable', array('%directory' => $directory)), t('Properly generated an error for the passed form element.'), 'File');
+    }
 
     // Test directory permission modification.
     $this->assertTrue(file_check_directory($directory, FILE_MODIFY_PERMISSIONS), t('No error reported when making directory writeable.'), 'File');
 
     // Verify directory actually is writeable.
     $this->assertTrue(is_writeable($directory), t('Directory is writeable.'), 'File');
 
     // Remove .htaccess file to then test that it gets re-created.
     @unlink(file_directory_path() .'/.htaccess');
@@ -413,18 +473,27 @@ class FileDirectoryTest extends FileTest
     file_check_directory($directory);
     $this->assertTrue(is_file(file_directory_path() . '/.htaccess'), t('Successfully created the .htaccess file in the files directory.'), 'File');
 
     // Verify contents of .htaccess file.
     $file = file_get_contents(file_directory_path() .'/.htaccess');
     $this->assertEqual($file, "SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006\nOptions None\nOptions +FollowSymLinks", t('The .htaccess file contains the proper content.'), 'File');
   }
 
   /**
+   * Test the file_check_directory() function on paths that contain a stream
+   * wrapper prefix.
+   */
+  function testFileCheckDirectoryWithStreamWrapper() {
+    $this->useStreamWrapper();
+    $this->testFileCheckDirectory();
+  }
+
+  /**
    * Check file_directory_path() and file_directory_temp().
    */
   function testFileDirectoryPath() {
     // Directory path.
     $path = variable_get('file_directory_path', conf_path() . '/files');
     $this->assertEqual($path, file_directory_path(), t('Properly returns the stored file directory path.'), 'File');
   }
 
   /**
@@ -458,74 +527,120 @@ class FileDirectoryTest extends FileTest
     $this->assertFalse($result, t('Existing file fails validation when it exists outside the directory path, using a /../ exploit.'), 'File');
 
     $source = 'misc/druplicon.png';
     $directory = 'misc';
     $result = file_check_location($source, $directory);
     $this->assertTrue($result, t('Existing file passes validation when checked for location in directory path, and filepath contains a subfolder of the checked path.'), 'File');
 
     $result = file_check_location($source, $directory);
     $this->assertTrue($result, t('Existing file passes validation, returning the source when checked for location in directory.'), 'File');
-  }
 
+    $this->useStreamWrapper();
+    mkdir(file_directory_path() . '/foo');
+    $this->createFile(file_directory_path() . '/abc.txt');
+    $this->createFile(file_directory_path() . '/foo/def.txt');
+    $this->assertTrue(is_dir(file_directory_path() . '/foo'), t('Directory was created successfully.'), 'File');
+
+    $source = file_directory_path() . '/foo/xyz.txt';
+    $directory = file_directory_path() . '/foo';
+    $result = file_check_location($source, $directory);
+    $this->assertTrue($result, t('Non-existent file validates when checked for location in existing directory.'), 'File');
+
+    $source = file_directory_path() . '/xyz.txt';
+    $directory = file_directory_path() . '/fake';
+    $result = file_check_location($source, $directory);
+    $this->assertTrue($result, t('Non-existent file validates when checked for location in non-existing directory.'), 'File');
+
+    $source = file_directory_path() . '/foo/../abc.txt';
+    $directory = file_directory_path() . '/foo';
+    $this->assertTrue(is_file($source), t('File exists outside the directory path.'), 'File');
+    $result = file_check_location($source, $directory);
+    $this->assertFalse($result, t('Existing file fails validation when it exists outside the directory path, using a /../ exploit.'), 'File');
+
+    $source = file_directory_path() . '/foo/def.txt';
+    $directory = file_directory_path() . '/foo';
+    $result = file_check_location($source, $directory);
+    $this->assertTrue($result, t('Existing file passes validation when checked for location in directory path, and filepath contains a subfolder of the checked path.'), 'File');
+  }
 
   /**
    * This will take a directory and path, and find a valid filepath that is not
    * taken by another file.
    */
   function testFileCreateNewFilepath() {
     // First we test against an imaginary file that does not exist in a
     // directory.
     $basename = 'xyz.txt';
-    $directory = 'misc';
+    $directory = file_directory_path();
     $original = $directory .'/'. $basename;
     $path = file_create_filename($basename, $directory);
     $this->assertEqual($path, $original, t('New filepath %new equals %original.', array('%new' => $path, '%original' => $original)), 'File');
 
     // Then we test against a file that already exists within that directory.
-    $basename = 'druplicon.png';
-    $original = $directory .'/'. $basename;
-    $expected = $directory .'/druplicon_0.png';
+    $basename = 'abc.txt';
+    $original = $directory . '/' . $basename;
+    $expected = $directory . '/abc_0.txt';
+    $this->createFile($original);
     $path = file_create_filename($basename, $directory);
     $this->assertEqual($path, $expected, t('Creating a new filepath from %original equals %new.', array('%new' => $path, '%original' => $original)), 'File');
 
     // @TODO: Finally we copy a file into a directory several times, to ensure a properly iterating filename suffix.
   }
 
   /**
+   * This will take a directory and path, and find a valid filepath that is not
+   * taken by another file.
+   */
+  function testFileCreateNewFilepathWithStreamWrapper() {
+    $this->useStreamWrapper();
+    $this->testFileCreateNewFilepath();
+  }
+
+  /**
    * This will test the filepath for a destination based on passed flags and
    * whether or not the file exists.
    *
    * If a file exists, file_destination($destination, $replace) will either
    * return:
    * - the existing filepath, if $replace is FILE_EXISTS_REPLACE
    * - a new filepath if FILE_EXISTS_RENAME
    * - an error (returning FALSE) if FILE_EXISTS_ERROR.
    * If the file doesn't currently exist, then it will simply return the
    * filepath.
    */
   function testFileDestination() {
     // First test for non-existent file.
-    $destination = 'misc/xyz.txt';
+    $destination = file_directory_path() . '/xyz.txt';
     $path = file_destination($destination, FILE_EXISTS_REPLACE);
     $this->assertEqual($path, $destination, t('Non-existing filepath destination is correct with FILE_EXISTS_REPLACE.'), 'File');
     $path = file_destination($destination, FILE_EXISTS_RENAME);
     $this->assertEqual($path, $destination, t('Non-existing filepath destination is correct with FILE_EXISTS_RENAME.'), 'File');
     $path = file_destination($destination, FILE_EXISTS_ERROR);
     $this->assertEqual($path, $destination, t('Non-existing filepath destination is correct with FILE_EXISTS_ERROR.'), 'File');
 
-    $destination = 'misc/druplicon.png';
+    $destination = file_directory_path() . '/abc.txt';
+    $this->createFile($destination);
     $path = file_destination($destination, FILE_EXISTS_REPLACE);
     $this->assertEqual($path, $destination, t('Existing filepath destination remains the same with FILE_EXISTS_REPLACE.'), 'File');
     $path = file_destination($destination, FILE_EXISTS_RENAME);
     $this->assertNotEqual($path, $destination, t('A new filepath destination is created when filepath destination already exists with FILE_EXISTS_RENAME.'), 'File');
     $path = file_destination($destination, FILE_EXISTS_ERROR);
     $this->assertEqual($path, FALSE, t('An error is returned when filepath destination already exists with FILE_EXISTS_ERROR.'), 'File');
   }
+
+  /**
+   * This will take a directory and path with stream wrapper prefixes, and find
+   * a valid filepath that is not taken by another file.
+   */
+  function testFileDestinationWithStreamWrapper() {
+    $this->useStreamWrapper();
+    $this->testFileDestination();
+  }
 }
 
 
 /**
  * Tests the file_scan_directory() function.
  */
 class FileScanDirectoryTest extends FileTestCase {
   function getInfo() {
     return array(
@@ -533,26 +648,28 @@ class FileScanDirectoryTest extends File
       'description' => t('Tests the file_scan_directory() function.'),
       'group' => t('File'),
     );
   }
 
   /**
    * Check that the no-mask parameter is honored.
    */
   function testNoMask() {
-    $path = $this->originalFileDirectory . '/simpletest';
+    $path = file_directory_path();
+    $this->createFile($path . '/abc.txt');
+    $this->createFile($path . '/def.txt');
 
     // Grab a listing of all the JS files.
-    $all_files = file_scan_directory($path, '/javascript*/');
-    $this->assertEqual(2, count($all_files), t('Found two, expected javascript files.'));
+    $all_files = file_scan_directory($path, '/\.txt$/');
+    $this->assertEqual(2, count($all_files), t('Found two, expected txt files.'));
 
-    // Now use the nomast parameter to filter out the .script file.
-    $filtered_files = file_scan_directory($path, '/javascript*/', '/.script$/');
+    // Now use the nomast parameter to filter out abc.txt.
+    $filtered_files = file_scan_directory($path, '/\.txt$/', '/^abc/');
     $this->assertEqual(1, count($filtered_files), t('Filtered correctly.'));
   }
 }
 
 
 /**
  * Deletion related tests.
  */
 class FileUnmanagedDeleteTest extends FileTestCase {
@@ -571,36 +688,60 @@ class FileUnmanagedDeleteTest extends Fi
     // Create a file for testing
     $file = $this->createFile();
 
     // Delete a regular file
     $this->assertTrue(file_unmanaged_delete($file->filepath), t('Deleted worked.'));
     $this->assertFalse(file_exists($file->filepath), t('Test file has actually been deleted.'));
   }
 
   /**
+   * Delete a normal file with stream wrapper prefix.
+   */
+  function testNormalWithStreamWrapper() {
+    $this->useStreamWrapper();
+    $this->testNormal();
+  }
+
+  /**
    * Try deleting a missing file.
    */
   function testMissing() {
     // Try to delete a non-existing file
     $this->assertTrue(file_unmanaged_delete(file_directory_path() . '/' . $this->randomName()), t('Returns true when deleting a non-existant file.'));
   }
 
   /**
+   * Try deleting a missing file with stream wrapper prefix.
+   */
+  function testMissingWithStreamWrapper() {
+    $this->useStreamWrapper();
+    $this->testMissing();
+  }
+
+  /**
    * Try deleting a directory.
    */
   function testDirectory() {
     // A directory to operate on.
     $directory = $this->createDirectory();
 
     // Try to delete a directory
     $this->assertFalse(file_unmanaged_delete($directory), t('Could not delete the delete directory.'));
     $this->assertTrue(file_exists($directory), t('Directory has not been deleted.'));
   }
+
+  /**
+   * Try deleting a directory with stream wrapper prefix.
+   */
+  function testDirectoryWithStreamWrapper() {
+    $this->useStreamWrapper();
+    $this->testDirectory();
+  }
 }
 
 
 /**
  * Unmanaged move related tests.
  */
 class FileUnmanagedMoveTest extends FileTestCase {
   function getInfo() {
     return array(
@@ -635,44 +776,68 @@ class FileUnmanagedMoveTest extends File
     $this->assertNotEqual($newer_filepath, $desired_filepath, t('Returned expected filepath.'));
     $this->assertTrue(file_exists($newer_filepath), t('File exists at the new location.'));
     $this->assertFalse(file_exists($new_filepath), t('No file remains at the old location.'));
     $this->assertFilePermissions($newer_filepath, 0664);
 
     // TODO: test moving to a directory (rather than full directory/file path)
   }
 
   /**
+   * Move a normal file with stream wrapper prefix.
+   */
+  function testNormalWithStreamWrapper() {
+    $this->useStreamWrapper();
+    $this->testNormal();
+  }
+
+  /**
    * Try to move a missing file.
    */
   function testMissing() {
     // Move non-existant file.
     $new_filepath = file_unmanaged_move($this->randomName(), $this->randomName());
     $this->assertFalse($new_filepath, t('Moving a missing file fails.'));
   }
 
   /**
+   * Try to move a missing file with stream wrapper prefix.
+   */
+  function testMissingWithStreamWrapper() {
+    $this->useStreamWrapper();
+    $this->testMissing();
+  }
+
+  /**
    * Try to move a file onto itself.
    */
   function testOverwriteSelf() {
     // Create a file for testing.
     $file = $this->createFile();
 
     // Move the file onto itself without renaming shouldn't make changes.
     $new_filepath = file_unmanaged_move($file->filepath, $file->filepath, FILE_EXISTS_REPLACE);
     $this->assertFalse($new_filepath, t('Moving onto itself without renaming fails.'));
     $this->assertTrue(file_exists($file->filepath), t('File exists after moving onto itself.'));
 
     // Move the file onto itself with renaming will result in a new filename.
     $new_filepath = file_unmanaged_move($file->filepath, $file->filepath, FILE_EXISTS_RENAME);
     $this->assertTrue($new_filepath, t('Moving onto itself with renaming works.'));
     $this->assertFalse(file_exists($file->filepath), t('Original file has been removed.'));
     $this->assertTrue(file_exists($new_filepath), t('File exists after moving onto itself.'));
   }
+
+  /**
+   * Try to move a file with stream wrapper prefix onto itself.
+   */
+  function testOverwriteSelfWithStreamWrapper() {
+    $this->useStreamWrapper();
+    $this->testOverwriteSelf();
+  }
 }
 
 
 /**
  * Unmanaged copy related tests.
  */
 class FileUnmanagedCopyTest extends FileTestCase {
   function getInfo() {
     return array(
@@ -706,29 +871,45 @@ class FileUnmanagedCopyTest extends File
     $this->assertNotEqual($newer_filepath, $desired_filepath, t('Returned expected filepath.'));
     $this->assertTrue(file_exists($file->filepath), t('Original file remains.'));
     $this->assertTrue(file_exists($new_filepath), t('New file exists.'));
     $this->assertFilePermissions($new_filepath, 0664);
 
     // TODO: test copying to a directory (rather than full directory/file path)
   }
 
   /**
+   * Copy a normal file with stream wrapper prefix.
+   */
+  function testNormalWithStreamWrapper() {
+    $this->useStreamWrapper();
+    $this->testNormal();
+  }
+
+  /**
    * Copy a non-existant file.
    */
   function testNonExistant() {
     // Copy non-existant file
     $desired_filepath = $this->randomName();
     $this->assertFalse(file_exists($desired_filepath), t("Randomly named file doesn't exists."));
     $new_filepath = file_unmanaged_copy($desired_filepath, $this->randomName());
     $this->assertFalse($new_filepath, t('Copying a missing file fails.'));
   }
 
   /**
+   * Copy a non-existant file with stream wrapper prefix.
+   */
+  function testNonExistantWithStreamWrapper() {
+    $this->useStreamWrapper();
+    $this->testNonExistant();
+  }
+
+  /**
    * Copy a file onto itself.
    */
   function testOverwriteSelf() {
     // Create a file for testing
     $file = $this->createFile();
 
     // Copy the file onto itself with renaming works.
     $new_filepath = file_unmanaged_copy($file->filepath, $file->filepath, FILE_EXISTS_RENAME);
     $this->assertTrue($new_filepath, t('Copying onto itself with renaming works.'));
@@ -747,18 +928,26 @@ class FileUnmanagedCopyTest extends File
     $this->assertTrue(file_exists($file->filepath), t('File exists after copying onto itself.'));
 
     // Copy the file into same directory with renaming works.
     $new_filepath = file_unmanaged_copy($file->filepath, dirname($file->filepath), FILE_EXISTS_RENAME);
     $this->assertTrue($new_filepath, t('Copying into same directory works.'));
     $this->assertNotEqual($new_filepath, $file->filepath, t('Copied file has a new name.'));
     $this->assertTrue(file_exists($file->filepath), t('Original file exists after copying onto itself.'));
     $this->assertTrue(file_exists($new_filepath), t('Copied file exists after copying onto itself.'));
   }
+
+  /**
+   * Try to copy a file with stream wrapper prefix onto itself.
+   */
+  function testOverwriteSelfWithStreamWrapper() {
+    $this->useStreamWrapper();
+    $this->testOverwriteSelf();
+  }
 }
 
 
 
 /**
  * Deletion related tests.
  */
 class FileDeleteTest extends FileHookTestCase {
   function getInfo() {
@@ -780,18 +969,26 @@ class FileDeleteTest extends FileHookTes
     $this->assertIdentical(file_delete($file), TRUE, t("Delete worked."));
     $this->assertFileHookCalled('references');
     $this->assertFileHookCalled('delete');
     $this->assertFalse(file_exists($file->filepath), t("Test file has actually been deleted."));
     $this->assertFalse(file_load(array('filepath' => $file->filepath)), t("File was removed from the database."));
 
     // TODO: implement hook_file_references() in file_test.module and report a
     // file in use and test the $force parameter.
   }
+
+  /**
+   * Try deleting a normal file with stream wrapper prefix.
+   */
+  function testNormalWithStreamWrapper() {
+    $this->useStreamWrapper();
+    $this->testNormal();
+  }
 }
 
 
 /**
  * Move related tests
  */
 class FileMoveTest extends FileHookTestCase {
   function getInfo() {
     return array(
@@ -813,18 +1010,26 @@ class FileMoveTest extends FileHookTestC
     $this->assertFileHookCalled('move');
     $this->assertFileHookCalled('update');
     $this->assertEqual($file->fid, $file->fid, t("File id $file->fid is unchanged after move."));
 
     $loaded_file = file_load($file->fid, TRUE);
     $this->assertTrue($loaded_file, t("File can be loaded from the database."));
     $this->assertEqual($file->filename, $loaded_file->filename, t("File name was updated correctly in the database."));
     $this->assertEqual($file->filepath, $loaded_file->filepath, t("File path was updated correctly in the database."));
   }
+
+  /**
+   * Move a normal file with stream wrapper prefix.
+   */
+  function testNormalWithStreamWrapper() {
+    $this->useStreamWrapper();
+    $this->testNormal();
+  }
 }
 
 
 /**
  * Copy related tests.
  */
 class FileCopyTest extends FileHookTestCase {
   function getInfo() {
     return array(
@@ -851,18 +1056,26 @@ class FileCopyTest extends FileHookTestC
     $this->assertTrue(file_exists($source_file->filepath), t('The original file still exists.'));
     $this->assertTrue(file_exists($file->filepath), t('The copied file exists.'));
 
     // Check that the changes were actually saved to the database.
     $loaded_file = file_load($file->fid, TRUE);
     $this->assertTrue($loaded_file, t("File can be loaded from the database."));
     $this->assertEqual($file->filename, $loaded_file->filename, t("File name was updated correctly in the database."));
     $this->assertEqual($file->filepath, $loaded_file->filepath, t("File path was updated correctly in the database."));
   }
+
+  /**
+   * Test copying a normal file with stream wrapper prefix.
+   */
+  function testNormalWithStreamWrapper() {
+    $this->useStreamWrapper();
+    $this->testNormal();
+  }
 }
 
 
 /**
  * Tests the file_load() function.
  */
 class FileLoadTest extends FileHookTestCase {
   function getInfo() {
     return array(
@@ -891,27 +1104,28 @@ class FileLoadTest extends FileHookTestC
   /**
    * Try to load a non-existant file by status.
    */
   function testLoadInvalidStatus() {
     $this->assertFalse(file_load(array('status' => -99)), t("Trying to load a file with an invalid status fails."));
     $this->assertFileHookCalled('load', 0);
   }
 
   /**
-   * This will test lading file data from the database.
+   * This will test loading file data from the database.
    */
   function testFileLoad() {
+    $temp_file = current($this->drupalGetTestFiles('text'));
     // Create a new file object.
     $file = array(
       'uid' => 1,
-      'filename' => 'druplicon.png',
-      'filepath' => 'misc/druplicon.png',
-      'filemime' => 'image/png',
+      'filename' => $temp_file->basename,
+      'filepath' => $temp_file->filename,
+      'filemime' => 'text/plain',
       'timestamp' => 1,
       'status' => FILE_STATUS_PERMANENT,
     );
     $file = file_save($file);
 
     // Load by path.
     file_test_reset();
     $by_path_file = file_load(array('filepath' => $file->filepath));
     $this->assertFileHookCalled('load');
@@ -925,38 +1139,50 @@ class FileLoadTest extends FileHookTestC
     $this->assertTrue($by_fid_file->file_test['loaded'], t('file_test_file_load() was able to modify the file during load.'));
     $this->assertEqual($by_fid_file->filepath, $file->filepath, t("Loading by fid got the correct filepath."), 'File');
 
     // Load again by fid but use the reset param to reload.
     file_test_reset();
     $by_fid_file = file_load($file->fid, TRUE);
     $this->assertFileHookCalled('load');
     $this->assertEqual($by_fid_file->filepath, $file->filepath, t("Loading by fid got the correct filepath."), 'File');
   }
+
+  /**
+   * Load a file with stream wrapper prefix from the database.
+   */
+  function testFileLoadWithStreamWrapper() {
+    $this->useStreamWrapper();
+    $this->testFileLoad();
+  }
 }
 /**
  * Tests the file_save() function.
  */
 class FileSaveTest extends FileHookTestCase {
   function getInfo() {
     return array(
       'name' => t('File saving'),
       'description' => t('Tests the file_save() function.'),
       'group' => t('File'),
     );
   }
 
+  /**
+   * Save a file to the database.
+   */
   function testFileSave() {
+    $temp_file = current($this->drupalGetTestFiles('text'));
     // Create a new file object.
     $file = array(
       'uid' => 1,
-      'filename' => 'druplicon.png',
-      'filepath' => 'misc/druplicon.png',
-      'filemime' => 'image/png',
+      'filename' => $temp_file->basename,
+      'filepath' => $temp_file->filename,
+      'filemime' => 'text/plain',
       'timestamp' => 1,
       'status' => FILE_STATUS_PERMANENT,
     );
     $file = (object) $file;
 
     // Save it, inserting a new record.
     $saved_file = file_save($file);
     $this->assertFileHookCalled('insert');
     $this->assertNotNull($saved_file, t("Saving the file should give us back a file object."), 'File');
@@ -972,18 +1198,26 @@ class FileSaveTest extends FileHookTestC
     $saved_file->status = 7;
     $resaved_file = file_save($saved_file);
     $this->assertFileHookCalled('update');
     $this->assertEqual($resaved_file->fid, $saved_file->fid, t("The file ID of an existing file is not changed when updating the database."), 'File');
     $this->assertTrue($resaved_file->timestamp >= $saved_file->timestamp, t("Timestamp didn't go backwards."), 'File');
     $loaded_file = db_query('SELECT * FROM {files} f WHERE f.fid = :fid', array(':fid' => $saved_file->fid))->fetch(PDO::FETCH_OBJ);
     $this->assertNotNull($loaded_file, t("Record still exists in the database."), 'File');
     $this->assertEqual($loaded_file->status, $saved_file->status, t("Status was saved correctly."));
   }
+
+  /**
+   * Save a file with stream wrapper prefix to the database.
+   */
+  function testFileSaveWithStreamWrapper() {
+    $this->useStreamWrapper();
+    $this->testFileSave();
+  }
 }
 
 
 /**
  * Tests the file_validate() function..
  */
 class FileValidateTest extends FileHookTestCase {
   function getInfo() {
     return array(
@@ -1036,29 +1270,126 @@ class FileSaveDataTest extends FileHookT
    * Test the file_save_data() function.
    */
   function testFileSaveData() {
     $contents = $this->randomName(8);
 
     // No filename.
     $file = file_save_data($contents);
     $this->assertTrue($file, t("Unnamed file saved correctly."));
     $this->assertEqual(file_directory_path(), dirname($file->filepath), t("File was placed in Drupal's files directory."));
-    $this->assertEqual($contents, file_get_contents(realpath($file->filepath)), t("Contents of the file are correct."));
+    $this->assertEqual($contents, file_get_contents($file->filepath), t("Contents of the file are correct."));
     $this->assertEqual($file->filemime, 'application/octet-stream', t("A MIME type was set."));
     $this->assertEqual($file->status, FILE_STATUS_PERMANENT, t("The file's status was set to permanent."));
 
     // Try loading the file.
     $loaded_file = file_load($file->fid);
     $this->assertTrue($loaded_file, t("File loaded from database."));
 
     // Provide a filename.
     $file = file_save_data($contents, 'asdf.txt', FILE_EXISTS_REPLACE);
     $this->assertTrue($file, t("Unnamed file saved correctly."));
     $this->assertEqual(file_directory_path(), dirname($file->filepath), t("File was placed in Drupal's files directory."));
     $this->assertEqual('asdf.txt', basename($file->filepath), t("File was named correctly."));
-    $this->assertEqual($contents, file_get_contents(realpath($file->filepath)), t("Contents of the file are correct."));
+    $this->assertEqual($contents, file_get_contents($file->filepath), t("Contents of the file are correct."));
 
     // Check the overwrite error.
     $file = file_save_data($contents, 'asdf.txt', FILE_EXISTS_ERROR);
     $this->assertFalse($file, t("Overwriting a file fails when FILE_EXISTS_ERROR is specified."));
   }
-}
\ No newline at end of file
+
+  /**
+   * Test the file_save_data() function when file_directory_path contains a
+   * stream wrapper prefix.
+   */
+  function testFileSaveDataWithStreamWrapper() {
+    $this->useStreamWrapper();
+    $this->testFileSaveData();
+  }
+}
+
+/**
+ * Test the file_get_wrapper() function.
+ */
+class FileGetWrapperUnitTest extends DrupalWebTestCase {
+  function getInfo() {
+    return array(
+      'name' => t('Stream wrapper support'),
+      'description' => t('Tests the file_get_wrapper() function.'),
+      'group' => t('File'),
+    );
+  }
+
+  /**
+   * Test the file_get_wrapper() function.
+   */
+  function testFileGetWrapper() {
+    // Array containing path => expected_wrapper pairs.
+    $paths = array(
+      'C:\\' => FALSE,
+      'C:/' => FALSE,
+      'C:\Windows' => FALSE,
+      'C:/Windows' => FALSE,
+      '/tmp' => FALSE,
+      '' => FALSE,
+      '.' => FALSE,
+      '..' => FALSE,
+      'foo:' => FALSE,
+      'foo:/' => FALSE,
+      'foo://' => 'foo',
+      'foo://bar' => 'foo',
+      'f-o-o://bar' => 'f-o-o',
+    );
+    foreach ($paths as $path => $expected_wrapper) {
+      $this->assertIdentical(file_get_wrapper($path), $expected_wrapper, t('Found expected wrapper prefix on %file.', array('%file' => $path)));
+    }
+  }
+}
+
+/**
+ * Test the drupal_realpath() function.
+ */
+class DrupalRealpathUnitTest extends DrupalWebTestCase {
+  function getInfo() {
+    return array(
+      'name' => t('Realpath resolving'),
+      'description' => t('Tests the drupal_realpath() function.'),
+      'group' => t('File'),
+    );
+  }
+
+  /**
+   * Test the drupal_realpath() function.
+   */
+  function testDrupalRealpath() {
+    $original_file_directory_path = file_directory_path();
+    $this->useStreamWrapper();
+
+    // These tests exploit that "simpletest://" is a dummy wrapper that maps to
+    // the filesystem, i.e. the same file is accessible with and without the
+    // stream wrapper prefix.
+    $original_file_directory_realpath = drupal_realpath($original_file_directory_path);
+    $this->assertTrue($original_file_directory_realpath, t('%path exists.', array('%path' => $original_file_directory_realpath)));
+    mkdir($original_file_directory_realpath . '/foo/bar', 0775, TRUE);
+
+    $this->assertTrue(is_dir($original_file_directory_realpath . '/foo/bar'), t('Directory was created successfully.'));
+    $this->assertTrue(is_dir('simpletest://' . $original_file_directory_path . '/foo/bar'), t('Directory was accessible through dummy stream wrapper.'));
+    $this->assertEqual(drupal_realpath('simpletest://' . $original_file_directory_path . '/foo/bar/'), 'simpletest://' . $original_file_directory_path . '/foo/bar', t('Trailing slash in directory was removed.'));
+
+    file_put_contents($original_file_directory_path . '/foo/baz', 'Lorem ipsum.');
+    $this->assertTrue(is_file($original_file_directory_realpath . '/foo/baz'), t('File was created successfully.'));
+    $this->assertTrue(is_file('simpletest://' . $original_file_directory_path . '/foo/baz'), t('File was found through dummy stream wrapper.'));
+    $this->assertEqual(drupal_realpath('simpletest://' . $original_file_directory_path . '///foo//baz'), 'simpletest://' . $original_file_directory_path . '/foo/baz', t('Duplicate slashes were removed.'));
+
+    // "/../" segments are not supported with stream wrappers.
+    $this->assertFalse(drupal_realpath('simpletest://' . $original_file_directory_path . '/foo/bar/../1'), t('".." segments are rejected.'));
+
+    // If file_directory_path did not contain a stream wrapper prefix prior to
+    // running the test, test drupal_realpath() on regular files in the
+    // filesystem.
+    if (!file_get_wrapper($original_file_directory_path)) {
+      $this->assertEqual(drupal_realpath($original_file_directory_path . '/foo/./baz'), $original_file_directory_realpath . DIRECTORY_SEPARATOR . 'foo' . DIRECTORY_SEPARATOR . 'baz', t('"." segment was resolved.'));
+      $this->assertEqual(drupal_realpath($original_file_directory_path . '/foo/bar/../baz'), $original_file_directory_realpath . DIRECTORY_SEPARATOR . 'foo' . DIRECTORY_SEPARATOR . 'baz', t('".." segment was resolved.'));
+      $this->assertEqual(drupal_realpath($original_file_directory_path . '/foo/bar/'), $original_file_directory_realpath . DIRECTORY_SEPARATOR . 'foo' . DIRECTORY_SEPARATOR . 'bar', t('Trailing slash in directory was removed.'));
+      $this->assertEqual(drupal_realpath($original_file_directory_path . '///foo//baz'), $original_file_directory_realpath . DIRECTORY_SEPARATOR . 'foo' . DIRECTORY_SEPARATOR . 'baz', t('Duplicate slashes in path were removed.'));
+    }
+  }
+}
Index: modules/system/image.gd.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/image.gd.inc,v
retrieving revision 1.2
diff -u -9 -p -r1.2 image.gd.inc
--- modules/system/image.gd.inc	16 Jul 2008 21:59:28 -0000	1.2
+++ modules/system/image.gd.inc	18 Dec 2008 18:57:54 -0000
@@ -190,32 +190,40 @@ function image_gd_open($file, $extension
   return $open_func($file);
 }
 
 /**
  * GD helper to write an image resource to a destination file.
  *
  * @param $res
  *   An image resource created with image_gd_open().
  * @param $destination
- *   A string file path where the iamge should be saved.
+ *   A string file path where the image should be saved.
  * @param $extension
  *   A string containing one of the following extensions: gif, jpg, jpeg, png.
  * @return
  *   Boolean indicating success.
  */
 function image_gd_close($res, $destination, $extension) {
   $extension = str_replace('jpg', 'jpeg', $extension);
   $close_func = 'image' . $extension;
   if (!function_exists($close_func)) {
     return FALSE;
   }
+
+  // These functions do not support stream wrappers.
+  $output_file = file_get_wrapper($destination) ? tempnam(drupal_realpath(file_directory_temp()), 'image') : $destination;
   if ($extension == 'jpeg') {
-    return $close_func($res, $destination, variable_get('image_jpeg_quality', 75));
+    $result = $close_func($res, $output_file, variable_get('image_jpeg_quality', 75));
   }
   else {
-    return $close_func($res, $destination);
+    $result = $close_func($res, $output_file);
+  }
+
+  if ($result && $destination != $output_file) {
+    $result = file_unmanaged_move($output_file, $destination, FILE_EXISTS_REPLACE);
   }
+  return $result;
 }
 
 /**
  * @} End of "ingroup image".
  */
Index: modules/system/system.admin.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.admin.inc,v
retrieving revision 1.110
diff -u -9 -p -r1.110 system.admin.inc
--- modules/system/system.admin.inc	26 Nov 2008 13:54:05 -0000	1.110
+++ modules/system/system.admin.inc	18 Dec 2008 18:57:54 -0000
@@ -1445,19 +1445,19 @@ function system_file_system_settings() {
     '#description' => t('A file system path where the files will be stored. This directory must exist and be writable by Drupal. If the download method is set to public, this directory must be relative to the Drupal installation directory and be accessible over the web. If the download method is set to private, this directory should not be accessible over the web. Changing this location will modify all download paths and may cause unexpected problems on an existing site.'),
     '#after_build' => array('system_check_directory'),
   );
 
   $form['file_directory_temp'] = array(
     '#type' => 'textfield',
     '#title' => t('Temporary directory'),
     '#default_value' => file_directory_temp(),
     '#maxlength' => 255,
-    '#description' => t('A file system path where uploaded files will be stored during previews.'),
+    '#description' => t('A file system path where temporary files may be stored.'),
     '#after_build' => array('system_check_directory'),
   );
 
   $form['file_downloads'] = array(
     '#type' => 'radios',
     '#title' => t('Download method'),
     '#default_value' => variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC),
     '#options' => array(FILE_DOWNLOADS_PUBLIC => t('Public - files are available using HTTP directly.'), FILE_DOWNLOADS_PRIVATE => t('Private - files are transferred by Drupal.')),
     '#description' => t('Choose the <em>Public download</em> method unless you wish to enforce fine-grained access controls over file downloads. Changing the download method will modify all download paths and may cause unexpected problems on an existing site.')
Index: modules/upload/upload.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/upload/upload.test,v
retrieving revision 1.9
diff -u -9 -p -r1.9 upload.test
--- modules/upload/upload.test	16 Dec 2008 22:05:51 -0000	1.9
+++ modules/upload/upload.test	18 Dec 2008 18:57:54 -0000
@@ -69,27 +69,36 @@ class UploadTestCase extends DrupalWebTe
       $this->assertNoText($upload->description, $upload->description . ' not found on node.');
 
       // Delete a file.
       $edit = array();
       $edit['files[' . $upload->fid . '][remove]'] = TRUE;
       $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
       $this->assertRaw(t('Page %title has been updated.', array('%title' => $node->title)), 'File deleted successfully.');
 
       $this->assertNoText($new_name, $new_name . ' not found on node.');
-      $this->drupalGet($base_url . '/' . file_directory_path() . '/' . $upload->description, array('external' => TRUE));
+      $this->drupalGet(file_create_url($upload->description), array('external' => TRUE));
       $this->assertResponse(array(404), 'Uploaded ' . $upload->description . ' is not accessible.');
     }
     else {
       $this->fail('File upload record not found in database.');
     }
   }
 
   /**
+   * Create node; upload files to node; and edit, and delete uploads, when
+   * file_directory_path contains a stream wrapper prefix.
+   */
+  function testNodeUploadWithStreamWrappers() {
+    $this->useStreamWrapper();
+    $this->testNodeUpload();
+  }
+
+  /**
    * Ensure the the file filter works correctly by attempting to upload a non-allowed file extension.
    */
   function testFilesFilter() {
     $admin_user = $this->drupalCreateUser(array('administer site configuration'));
     $web_user = $this->drupalCreateUser(array('access content', 'edit any page content', 'upload files', 'view uploaded files'));
 
     $this->drupalLogin($admin_user);
 
     // Setup upload settings.
@@ -189,20 +198,20 @@ class UploadTestCase extends DrupalWebTe
   }
 
   /**
    * Check that uploaded file is accessible and verify the contents against the original.
    *
    * @param string $filename Name of file to verifiy.
    */
   function checkUploadedFile($filename) {
     global $base_url;
-    $file = realpath(file_directory_path() . '/' . $filename);
-    $this->drupalGet($base_url . '/' . file_directory_path() . '/' . $filename, array('external' => TRUE));
+    $file = file_directory_path() . '/' . $filename;
+    $this->drupalGet(file_create_url($file), array('external' => TRUE));
     $this->assertResponse(array(200), 'Uploaded ' . $filename . ' is accessible.');
     $this->assertEqual(file_get_contents($file), $this->drupalGetContent(), 'Uploaded contents of ' . $filename . ' verified.');
   }
 
   /**
    * Get the role id of the 'simpletest' role associated with a SimpleTest test user.
    *
    * @param object $user User object.
    * @return interger SimpleTest role id.
Index: modules/user/user.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/user/user.test,v
retrieving revision 1.23
diff -u -9 -p -r1.23 user.test
--- modules/user/user.test	16 Dec 2008 23:57:33 -0000	1.23
+++ modules/user/user.test	18 Dec 2008 18:57:54 -0000
@@ -262,18 +262,30 @@ class UserPictureTestCase extends Drupal
 
         // Check if file is located in proper directory.
         $this->assertTrue(is_file($pic_path), t("File is located in proper directory"));
       }
   }
 
   /**
    * Do the test:
    *  GD Toolkit is installed
+   *  Picture has invalid dimension
+   *
+   * results: The image should be uploaded because ImageGDToolkit resizes the picture
+   */
+  function testWithGDinvalidDimensionWithStreamWrapper() {
+    $this->useStreamWrapper();
+    $this->testWithGDinvalidDimension();
+  }
+
+  /**
+   * Do the test:
+   *  GD Toolkit is installed
    *  Picture has invalid size
    *
    * results: The image should be uploaded because ImageGDToolkit resizes the picture
    */
   function testWithGDinvalidSize() {
     if ($this->_directory_test)
       if (image_get_toolkit()) {
 
         $this->drupalLogin($this->user);
@@ -296,19 +308,31 @@ class UserPictureTestCase extends Drupal
         $this->assertRaw($text, t('File size cited as reason for failure.'));
 
         // Check if file is not uploaded.
         $this->assertFalse(is_file($pic_path), t('File was not uploaded.'));
       }
   }
 
   /**
    * Do the test:
-   *  GD Toolkit is not installed
+   *  GD Toolkit is installed
+   *  Picture has invalid size
+   *
+   * results: The image should be uploaded because ImageGDToolkit resizes the picture
+   */
+  function testWithGDinvalidSizeWithStreamWrapper() {
+    $this->useStreamWrapper();
+    $this->testWithGDinvalidSize();
+  }
+
+  /**
+   * Do the test:
+   *  GD Toolkit is installed
    *  Picture has invalid size
    *
    * results: The image shouldn't be uploaded
    */
    function testWithoutGDinvalidDimension() {
     if ($this->_directory_test)
       if (!image_get_toolkit()) {
 
         $this->drupalLogin($this->user);
@@ -383,27 +407,39 @@ class UserPictureTestCase extends Drupal
 
       // Set new variables: valid dimensions, valid filesize (0 = no limit).
       $test_dim = ($info['width'] + 10) . 'x' . ($info['height'] + 10);
       variable_set('user_picture_dimensions', $test_dim);
       variable_set('user_picture_file_size', 0);
 
       $pic_path = $this->saveUserPicture($image);
 
       // check if image is displayed in user's profile page
-      $this->assertRaw($pic_path, t("Image is displayed in user's profile page"));
+      $this->assertRaw(basename($pic_path), t("Image is displayed in user's profile page"));
 
       // check if file is located in proper directory
       $this->assertTrue(is_file($pic_path), t('File is located in proper directory'));
     }
   }
 
+
+  /**
+   * Do the test:
+   *  Picture is valid (proper size and dimension)
+   *
+   * results: The image should be uploaded
+   */
+  function testPictureIsValidWithStreamWrapper() {
+    $this->useStreamWrapper();
+    $this->testPictureIsValid();
+  }
+
   function saveUserPicture($image) {
-    $edit = array('files[picture_upload]' => realpath($image->filename));
+    $edit = array('files[picture_upload]' => $image->filename);
     $this->drupalPost('user/' . $this->user->uid.'/edit', $edit, t('Save'));
 
     $img_info = image_get_info($image->filename);
     $picture_dir = variable_get('user_picture_path', 'pictures');
     $pic_path = file_directory_path() . '/' . $picture_dir . '/picture-' . $this->user->uid . '.' . $img_info['extension'];
 
     return $pic_path;
   }
 }
