Index: includes/file.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/file.inc,v
retrieving revision 1.145
diff -u -9 -p -r1.145 file.inc
--- includes/file.inc	16 Nov 2008 19:41:14 -0000	1.145
+++ includes/file.inc	30 Nov 2008 12:10:06 -0000
@@ -158,20 +158,20 @@ 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.
+    if ($mode & FILE_CREATE_DIRECTORY) {
+      @mkdir($directory, 0775);
     }
     else {
       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;
     }
   }
@@ -187,19 +187,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 +245,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 +428,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 +456,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
@@ -1309,19 +1314,22 @@ function file_scan_directory($dir, $mask
     }
 
     closedir($handle);
   }
 
   return $files;
 }
 
 /**
- * Determine the default temporary directory.
+ * 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 several 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();
@@ -1749,11 +1757,75 @@ function file_get_mimetype($filename, $m
     if (preg_match('!\.('. $ext_preg .')$!i', $filename)) {
       return $mime_match;
     }
   }
 
   return 'application/octet-stream';
 }
 
 /**
+ * Check whether the path uses a wrapper prefix, e.g. "foo://".
+ *
+ * @param $path
+ *   A string containing a path to a file or directory.
+ * @return
+ *   TRUE if path contains a wrapper prefix, FALSE otherwise.
+ */
+function file_is_wrapper($path) {
+  // A wrapper prefix is at least two characters so that it can be distinguised
+  // from a Windows drive letter, "C:/temp"?.
+  return preg_match('@^([a-z0-9.+-]{2,})://@i', $path);
+}
+
+/**
+ * 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, $reg)) {
+    $wrappedPath = $reg[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 = $reg[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	30 Nov 2008 12:10:07 -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.64
diff -u -9 -p -r1.64 drupal_web_test_case.php
--- modules/simpletest/drupal_web_test_case.php	30 Nov 2008 07:04:45 -0000	1.64
+++ modules/simpletest/drupal_web_test_case.php	30 Nov 2008 12:10:07 -0000
@@ -765,18 +765,20 @@ class DrupalWebTestCase {
    * @param ...
    *   List of modules to enable for the duration of the test.
    */
   protected function setUp() {
     global $db_prefix;
 
     // 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();
 
@@ -799,23 +801,23 @@ class DrupalWebTestCase {
     actions_synchronize();
     _drupal_flush_css_js();
     $this->refreshVariables();
     $this->checkPermissions(array(), TRUE);
 
     // 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);
 
     // 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.
   }
 
   /**
    * 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 the a clean environment for the current test run. 
    */ 
   protected function preloadRegistry() {
@@ -1051,45 +1053,73 @@ 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_is_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);
             }
             $post = implode('&', $post);
           }
           $out = $this->curlExec(array(CURLOPT_URL => $action, CURLOPT_POST => TRUE, CURLOPT_POSTFIELDS => $post, CURLOPT_HEADER => FALSE));
+
+          // Remove temporary directory and its contents.
+          if ($temp_dir) {
+            $files = scandir($temp_dir);
+            foreach ($files as $file) {
+              if ($file != '.' && $file != '..') {
+                file_unmanaged_delete($temp_dir . '/' . $file);
+              }
+            }
+            rmdir($temp_dir);
+          }
+
           // Ensure that any changes to variables in the other thread are picked up.
           $this->refreshVariables();
 
           // Replace original page output with new output from redirected page(s).
           if (($new = $this->checkForMetaRefresh())) {
             $out = $new;
           }
           return $out;
         }
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	30 Nov 2008 12:10:07 -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_is_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)));
       }
@@ -200,29 +204,29 @@ class FileValidatorTest extends DrupalWe
     $errors = file_validate_image_resolution($this->image, 0, '1x200');
     $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');
     $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');
     }
   }
 
   /**
@@ -304,26 +308,26 @@ 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_save_upload() function.
  */
 class FileSaveUploadTest extends FileHookTestCase {
   function getInfo() {
@@ -338,19 +342,19 @@ 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.'));
@@ -386,26 +390,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_is_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');
@@ -1036,29 +1043,199 @@ 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 stream wrapper support.
+ */
+class FileStreamWrapperTest extends FileTestCase {
+  function getInfo() {
+    return array(
+      'name' => t('Stream wrapper support'),
+      'description' => t('Test file system functions with file system path using a stream wrapper.'),
+      'group' => t('File'),
+    );
+  }
+
+  function setUp() {
+    // Install file_test module
+    parent::setUp('file_test', 'upload');
+    // Clear out any hook calls.
+    file_test_reset();
+  }
+
+  /**
+   * Test basic file operations.
+   */
+  function testFileFunctions() {
+    $original_file_directory_path = file_directory_path();
+    $this->registerStreamWrapper();
+
+    $file = $this->createFile();
+    $this->assertTrue(file_unmanaged_delete($file->filepath), t('File was deleted.'));
+    $this->assertFalse(file_exists($file->filepath), t('File no longer exists.'));
+
+    $path = $this->createDirectory();
+    $this->assertTrue(rmdir($path), t('Directory was deleted.'));
+    clearstatcache();
+    $this->assertFalse(file_exists($path), t('Directory no longer exists.'));
+
+    // Test file_is_wrapper().
+    // Array containing path => expected_is_wrapper pairs.
+    $paths = array(
+      'C:\\' => FALSE,
+      'C:/' => FALSE,
+      'C:\Windows' => FALSE,
+      'C:/Windows' => FALSE,
+      '/tmp' => FALSE,
+      '' => FALSE,
+      '.' => FALSE,
+      '..' => FALSE,
+      'foo:' => FALSE,
+      'foo:/' => FALSE,
+      'foo://' => TRUE,
+      'foo://bar' => TRUE,
+      'f-o-o://bar' => TRUE,
+    );
+    foreach ($paths as $path => $expected_is_wrapper) {
+      if ($expected_is_wrapper) {
+        $this->assertTrue(file_is_wrapper($path), t('%file contained a stream wrapper prefix.', array('%file' => $path)));
+      }
+      else {
+        $this->assertFalse(file_is_wrapper($path), t('%file did not contain a stream wrapper prefix.', array('%file' => $path)));
+      }
+    }
+
+    // Test drupal_realpath(). These tests exploit that "file-test://" 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('file-test://' . $original_file_directory_path . '/foo/bar'), t('Directory was accessible through dummy stream wrapper.'));
+    $this->assertEqual(drupal_realpath('file-test://' . $original_file_directory_path . '/foo/bar/'), 'file-test://' . $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('file-test://' . $original_file_directory_path . '/foo/baz'), t('File was found through dummy stream wrapper.'));
+    $this->assertEqual(drupal_realpath('file-test://' . $original_file_directory_path . '///foo//baz'), 'file-test://' . $original_file_directory_path . '/foo/baz', t('Duplicate slashes were removed.'));
+
+    // "/../" segments are not supported with stream wrappers.
+    $this->assertFalse(drupal_realpath('file-test://' . $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_is_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.'));
+    }
+  }
+
+  /**
+   * Test file upload and download.
+   */
+  function testUploadDownload() {
+    global $base_url;
+
+    $admin_user = $this->drupalCreateUser(array('administer site configuration', 'access content', 'edit any page content', 'upload files', 'view uploaded files'));
+    $this->drupalLogin($admin_user);
+
+    // "file-test-module://" prefix is registered in file_test_boot().
+    $edit = array();
+    $edit['file_directory_path'] = 'file-test-module://' . drupal_realpath(file_directory_path());
+    $edit['file_downloads'] = FILE_DOWNLOADS_PRIVATE;
+    $this->drupalPost('admin/settings/file-system', $edit, t('Save configuration'));
+    $this->assertText(t('The configuration options have been saved.'), t('File system path set to %path', array('%path' => $edit['file_directory_path'])));
+    $this->assertTrue($edit['file_directory_path'] . '/.htaccess', t('.htaccess file created.'));
+
+    // Create node and attach file.
+    $node = $this->drupalCreateNode();
+    list($text_file) = $this->drupalGetTestFiles('text');
+    $edit = array();
+    $edit['files[upload]'] = $text_file->filename;
+    $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
+    $this->assertRaw(t('Page %title has been updated.', array('%title' => $node->title)), 'File uploaded to node.');
+
+    // Verify that file was uploaded and is downloadable.
+    $this->drupalGet('node/' . $node->nid);
+    $links = $this->xpath('//a[text()="' . $text_file->basename . '"]');
+    $this->assertTrue(isset($links[0]), t('Link to uploaded file found.'));
+    if (isset($links[0])) {
+      $this->assertEqual($links[0]['href'], $base_url . '/system/files/' . $text_file->basename, t('Link URL matches expected URL.'));
+      $this->drupalGet($links[0]['href']);
+      $this->assertText(file_get_contents($text_file->filename), t('Contents of the file are correct.'));
+    }
+  }
+
+  /**
+   * Test image generation.
+   */
+  function testImageFunctions() {
+    $this->registerStreamWrapper();
+
+    list($image_file) = $this->drupalGetTestFiles('image');
+    $filepath = file_directory_path() . '/' . $this->randomName();
+    file_unmanaged_copy($image_file->filename, $filepath);
+    $this->assertTrue(file_exists($filepath), t('Image file was copied.'));
+
+    $details = image_get_info($filepath);
+    $this->assertTrue(!empty($details['width']), t('Image dimensions were detected.'));
+
+    $resized_filepath = file_directory_path() . '/' . $this->randomName();
+    $this->assertTrue(image_resize($filepath, $resized_filepath, 2 * $details['width'], 2 * $details['height']), t('Image was resized.'));
+    $resized_details = image_get_info($resized_filepath);
+    $this->assertTrue(!empty($resized_details['width']), t('Image dimensions of rized image were detected.'));
+    $this->assertEqual(2 * $details['width'], $resized_details['width'], t('Resized image dimensions match.'));
+  }
+
+  /**
+   * Register "file-test://" stream wrapper.
+   */
+  function registerStreamWrapper($register = TRUE) {
+    require_once DRUPAL_ROOT . '/modules/simpletest/tests/file_dummy_stream_wrapper.inc';
+    if (!in_array('file-test', stream_get_wrappers())) {
+      stream_wrapper_register('file-test', 'FileDummyStreamWrapper');
+    }
+    variable_set('file_directory_path', 'file-test://' . file_directory_path());
+  }
+
+  /**
+   * Implementation of tearDown().
+   */
+  function tearDown() {
+    // Strip "file-test-module://" prefix, because it will not be registered
+    // when simpletest_clean_temporary_directories() is called.
+    variable_set('file_directory_path', preg_replace('@^file-test-module://@', '', file_directory_path()));
+    parent::tearDown();
+  }
+}
Index: modules/simpletest/tests/file_dummy_stream_wrapper.inc
===================================================================
RCS file: modules/simpletest/tests/file_dummy_stream_wrapper.inc
diff -N modules/simpletest/tests/file_dummy_stream_wrapper.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ modules/simpletest/tests/file_dummy_stream_wrapper.inc	30 Nov 2008 12:10:07 -0000
@@ -0,0 +1,285 @@
+<?php
+// $Id: file_dummy_stream_wrapper.inc,v 1.1 2008/05/05 18:23:31 foo Exp $
+
+/**
+ * @file
+ * Dummy stream wrapper used for testing stream wrapper support.
+ */
+
+/*
+ * Dummy stream wrapper that stores its files with the path found by stripping
+ * the wrapper prefix. E.g. "dummy-wrapper:///tmp/foo" maps to "/tmp/foo".
+ * Nested wrappers are also supported, e.g. "dummy-wrapper://mywrapper://foo.txt"
+ * maps to "mywrapper://foo.txt".
+ *
+ * The class implements the methods required by PHP's stream_wrapper_register().
+ */
+class FileDummyStreamWrapper {
+  /**
+   * A file handle to the nested file opened by stream_open().
+   */
+  private $fileHandle;
+
+  /**
+   * A directory handle to the nested file opened by dir_opendir().
+   */
+  private $directoryHandle;
+
+  /**
+   * Support for fopen(), file_get_contents(), file_put_contents() etc.
+   *
+   * @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
+   *  TRUE if file was opened successfully.
+   */
+  public function stream_open($path, $mode, $options, &$opened_path) {
+     $nestedPath = self::getNestedPath($path);
+    if ($options & STREAM_REPORT_ERRORS) {
+      $this->fileHandle = fopen($nestedPath, $mode);
+    }
+    else {
+      $this->fileHandle = @fopen($nestedPath, $mode);
+    }
+    return (bool)$this->fileHandle;
+  }
+
+  /**
+   * Support for fread(), file_get_contents() etc.
+   *
+   * @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->fileHandle, $count);
+  }
+
+  /**
+   * Support for fwrite(), file_put_contents() etc.
+   *
+   * @param $data
+   *   The string to be written.
+   * @return
+   *   The number of bytes written.
+   */
+  public function stream_write($data) {
+    return fwrite($this->fileHandle, $data);
+  }
+
+  /**
+   * Support for feof().
+   *
+   * @return
+   *   TRUE if end-of-file has been reached.
+   */
+  public function stream_eof() {
+    return feof($this->fileHandle);
+  }
+
+  /**
+   * Support for fseek().
+   * 
+   * @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->fileHandle, $offset, $whence);
+  }
+
+  /**
+   * Support for fflush().
+   *
+   * @return
+   *   TRUE if data was successfully stored (or there was no data to store).
+   */
+  public function stream_flush() {
+    return fflush($this->fileHandle);
+  }
+
+  /**
+   * Support for ftell().
+   *
+   * @return
+   *   The current offset in bytes from the beginning of file.
+   */
+  public function stream_tell() {
+    return ftell($this->fileHandle);
+  }
+
+  /**
+   * Support for fstat().
+   *
+   * @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->fileHandle);
+  }
+
+  /**
+   * Support for fclose().
+   *
+   * @return
+   *   TRUE if stream was successfully closed.
+   */
+  public function stream_close() {
+    return fclose($this->fileHandle);
+  }
+
+  /**
+   * Support for unlink().
+   *
+   * @param $path
+   *   A string containing the path to the file to delete.
+   * @return
+   *   TRUE if file was successfully deleted.
+   */
+  public function unlink($path) {
+    return unlink(self::getNestedPath($path));
+  }
+
+  /**
+   * Support for rename().
+   *
+   * @param $fromPath
+   *   The path to the file to rename.
+   * @param $toPath
+   *   The new path to the file.
+   *
+   * @return
+   *   TRUE if file was successfully renamed.
+   */
+  public function rename($fromPath, $toPath) {
+    return rename(self::getNestedPath($fromPath), self::getNestedPath($toPath));
+  }
+
+  /**
+   * Support for mkdir().
+   *
+   * @param $path
+   *   A string containing the path 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($path, $mode, $options) {
+    $nestedPath = self::getNestedPath($path);
+    $recursive = (bool)($options & STREAM_MKDIR_RECURSIVE);
+    if ($options & STREAM_REPORT_ERRORS) {
+      return mkdir($nestedPath, $mode, $recursive);
+    }
+    else {
+      return @mkdir($nestedPath, $mode, $recursive);
+    }
+  }
+
+  /**
+   * Support for rmdir().
+   *
+   * @param $path
+   *   A string containing the path to the directory to delete.
+   * @param $options
+   *   A bit mask of STREAM_REPORT_ERRORS.
+   * @return
+   *   TRUE if directory was successfully removed.
+   */
+  public function rmdir($path, $options) {
+    $nestedPath = self::getNestedPath($path);
+    if ($options & STREAM_REPORT_ERRORS) {
+      return rmdir($nestedPath);
+    }
+    else {
+      return @rmdir($nestedPath);
+    }
+  }
+
+  /**
+   * Support for stat().
+   *
+   * @param $path
+   *   A string containing the path 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($path, $flags) {
+    $nestedPath = self::getNestedPath($path);
+    return ($flags & STREAM_URL_STAT_QUIET) ? (file_exists($nestedPath) ? stat($nestedPath) : FALSE) : stat($nestedPath);
+  }
+
+  /**
+   * Support for opendir().
+   *
+   * @param $path
+   *   A string containing the path to the directory to open.
+   * @param $options
+   *   Unknown (parameter is not documented in PHP Manual).
+   * @return
+   *   TRUE on success.
+   */
+  public function dir_opendir($path, $options) {
+    $this->dirHandle = opendir(self::getNestedPath($path));
+    return (bool)$this->dirHandle;
+  }
+
+  /**
+   * Support for readdir().
+   *
+   * @return
+   *   The next filename, or FALSE if there are no more files in the directory.
+   */
+  public function dir_readdir() {
+    return readdir($this->dirHandle);
+  }
+
+  /**
+   * Support for rewinddir().
+   *
+   * @return
+   *   TRUE on success.
+   */
+  public function dir_rewinddir() {
+    return rewinddir($this->dirHandle);
+  }
+
+  /**
+   * Support for closedir().
+   *
+   * @return
+   *   TRUE on success.
+   */
+  public function dir_closedir() {
+    return closedir($this->dirHandle);
+  }
+
+  /**
+   * Strip the stream wrapper prefix from the specified path.
+   *
+   * @param $path
+   *    A string containing a path with a stream wrapper prefix.
+   * @return
+   *    The without a stream wrapper prefix.
+   */
+  private static function getNestedPath($path) {
+    return preg_replace('@^([a-z0-9.+-]{2,})://@i', '', $path);
+  }
+}
+
Index: modules/simpletest/tests/file_test.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/file_test.module,v
retrieving revision 1.4
diff -u -9 -p -r1.4 file_test.module
--- modules/simpletest/tests/file_test.module	8 Nov 2008 21:35:09 -0000	1.4
+++ modules/simpletest/tests/file_test.module	30 Nov 2008 12:10:07 -0000
@@ -140,18 +140,26 @@ function _file_test_get_return($op) {
  * @see _file_test_get_return() and file_test_reset().
  */
 function file_test_set_return($op, $value) {
   $return = variable_get('file_test_return', array());
   $return[$op] = $value;
   variable_set('file_test_return', $return);
 }
 
 /**
+ * Implementation of hook_boot().
+ */
+function file_test_init() {
+  require_once DRUPAL_ROOT . '/modules/simpletest/tests/file_dummy_stream_wrapper.inc';
+  stream_wrapper_register('file-test-module', 'FileDummyStreamWrapper');
+}
+
+/**
  * Implementation of hook_file_load().
  */
 function file_test_file_load($file) {
   _file_test_log_call('load', array($file));
   // Assign a value on the object so that we can test that the $file is passed
   // by reference.
   $file->file_test['loaded'] = TRUE;
 }
 
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	30 Nov 2008 12:10:07 -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_is_wrapper($destination) ? tempnam(drupal_realpath(file_directory_temp()), 'image') : $destination;
   if ($extension == 'jpeg') {
-    return $close_func($res, $destination, variable_get('image_jpeg_quality', 75));
+    $ok = $close_func($res, $output_file, variable_get('image_jpeg_quality', 75));
   }
   else {
-    return $close_func($res, $destination);
+    $ok = $close_func($res, $output_file);
+  }
+
+  if ($ok && $destination != $output_file) {
+    $ok = file_unmanaged_move($output_file, $destination, FILE_EXISTS_REPLACE);
   }
+  return $ok;
 }
 
 /**
  * @} 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	30 Nov 2008 12:10:07 -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.8
diff -u -9 -p -r1.8 upload.test
--- modules/upload/upload.test	25 Nov 2008 13:14:29 -0000	1.8
+++ modules/upload/upload.test	30 Nov 2008 12:10:07 -0000
@@ -64,19 +64,19 @@ 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.');
     }
   }
 
   /**
    * Ensure the the file filter works correctly by attempting to upload a non-allowed file extension.
@@ -184,20 +184,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.22
diff -u -9 -p -r1.22 user.test
--- modules/user/user.test	25 Nov 2008 13:14:29 -0000	1.22
+++ modules/user/user.test	30 Nov 2008 12:10:07 -0000
@@ -383,27 +383,27 @@ 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'));
     }
   }
 
   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;
   }
 }
