? .DS_Store
? .cache
? .cvsignore
? .git
? .project
? .settings
? diff.patch
? hook_file_c206.patch
? modules/.DS_Store
? sites/all/modules
? sites/default/files
? sites/default/settings.php
? sites/default/test
Index: includes/file.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/file.inc,v
retrieving revision 1.130
diff -u -p -r1.130 file.inc
--- includes/file.inc	6 Sep 2008 08:36:19 -0000	1.130
+++ includes/file.inc	15 Sep 2008 08:15:53 -0000
@@ -10,6 +10,20 @@
  * @defgroup file File interface
  * @{
  * Common file handling functions.
+ *
+ * Fields on the file object:
+ * - fid - File ID
+ * - uid - The {users}.uid of the user who is associated with the file.
+ * - filename - Name of the file with no path components. This may differ
+ *   from the basename of the filepath if the file is renamed to avoid
+ *   overwriting an existing file.
+ * - filepath - Path of the file relative to Drupal root.
+ * - filemime - The file's MIME type.
+ * - filesize - The size of the file in bytes.
+ * - status - A bitmapped field indicating the status of the file the least
+ *   sigifigant bit indicates temporary (1) or permanent (0). Temporary files
+ *   older than DRUPAL_MAXIMUM_TEMP_FILE_AGE will be removed during a cron run.
+ * - timestamp - UNIX timestamp for the date the file was added to the database.
  */
 
 /**
@@ -29,17 +43,17 @@ define('FILE_DOWNLOADS_PUBLIC', 1);
 define('FILE_DOWNLOADS_PRIVATE', 2);
 
 /**
- * Flag used by file_create_directory() -- create directory if not present.
+ * Flag used by file_check_directory() -- create directory if not present.
  */
 define('FILE_CREATE_DIRECTORY', 1);
 
 /**
- * Flag used by file_create_directory() -- file permissions may be changed.
+ * Flag used by file_check_directory() -- file permissions may be changed.
  */
 define('FILE_MODIFY_PERMISSIONS', 2);
 
 /**
- * Flag for dealing with existing files: Append number until filename is unique.
+ * Flag for dealing with existing files: Appends number until name is unique.
  */
 define('FILE_EXISTS_RENAME', 0);
 
@@ -77,7 +91,8 @@ define('FILE_STATUS_PERMANENT', 1);
  * @return A string containing a URL that can be used to download the file.
  */
 function file_create_url($path) {
-  // Strip file_directory_path from $path. We only include relative paths in urls.
+  // Strip file_directory_path from $path. We only include relative paths in
+  // URLs.
   if (strpos($path, file_directory_path() . '/') === 0) {
     $path = trim(substr($path, strlen(file_directory_path())), '\\/');
   }
@@ -93,28 +108,30 @@ function file_create_url($path) {
  * Make sure the destination is a complete path and resides in the file system
  * directory, if it is not prepend the file system directory.
  *
- * @param $dest A string containing the path to verify. If this value is
+ * @param $destination A string containing the path to verify. If this value is
  *   omitted, Drupal's 'files' directory will be used.
  * @return A string containing the path to file, with file system directory
  *   appended if necessary, or FALSE if the path is invalid (i.e. outside the
  *   configured 'files' or temp directories).
  */
-function file_create_path($dest = 0) {
+function file_create_path($destination = NULL) {
   $file_path = file_directory_path();
-  if (!$dest) {
+  if (is_null($destination)) {
     return $file_path;
   }
-  // file_check_location() checks whether the destination is inside the Drupal files directory.
-  if (file_check_location($dest, $file_path)) {
-    return $dest;
-  }
-  // check if the destination is instead inside the Drupal temporary files directory.
-  else if (file_check_location($dest, file_directory_temp())) {
-    return $dest;
+  // file_check_location() checks whether the destination is inside the Drupal
+  // files directory.
+  if (file_check_location($destination, $file_path)) {
+    return $destination;
+  }
+  // Check if the destination is instead inside the Drupal temporary files
+  // directory.
+  elseif (file_check_location($destination, file_directory_temp())) {
+    return $destination;
   }
   // Not found, try again with prefixed directory path.
-  else if (file_check_location($file_path . '/' . $dest, $file_path)) {
-    return $file_path . '/' . $dest;
+  elseif (file_check_location($file_path . '/' . $destination, $file_path)) {
+    return $file_path . '/' . $destination;
   }
   // File not found.
   return FALSE;
@@ -133,7 +150,7 @@ function file_create_path($dest = 0) {
  *   work, a form error 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) {
+function file_check_directory(&$directory, $mode = FALSE, $form_item = NULL) {
   $directory = rtrim($directory, '/\\');
 
   // Check if directory exists.
@@ -152,9 +169,13 @@ function file_check_directory(&$director
 
   // Check to see if the directory is writable.
   if (!is_writable($directory)) {
-    if (($mode & FILE_MODIFY_PERMISSIONS) && !@chmod($directory, 0775)) {
-      form_set_error($form_item, t('The directory %directory is not writable', array('%directory' => $directory)));
-      watchdog('file system', 'The directory %directory is not writable, because it does not have the correct permissions set.', array('%directory' => $directory), WATCHDOG_ERROR);
+    // If not able to modify permissions, or if able to, but chmod
+    // fails, return false.
+    if (!$mode || (($mode & FILE_MODIFY_PERMISSIONS) && !@chmod($directory, 0775))) {
+      if ($form_item) {
+        form_set_error($form_item, t('The directory %directory is not writable', array('%directory' => $directory)));
+        watchdog('file system', 'The directory %directory is not writable, because it does not have the correct permissions set.', array('%directory' => $directory), WATCHDOG_ERROR);
+      }
       return FALSE;
     }
   }
@@ -176,7 +197,7 @@ function file_check_directory(&$director
 }
 
 /**
- * Checks path to see if it is a directory, or a dir/file.
+ * Checks path to see if it is a directory, or a directory/file.
  *
  * @param $path A string containing a file path. This will be set to the
  *   directory's path.
@@ -200,9 +221,11 @@ function file_check_path(&$path) {
 }
 
 /**
- * Check if a file is really located inside $directory. Should be used to make
- * sure a file specified is really located within the directory to prevent
- * exploits.
+ * Check if a file is really located inside $directory.
+ *
+ * This should be used to make sure a file specified is really located within
+ * the directory to prevent exploits. Note that the file or path being checked
+ * does not actually need to exist yet.
  *
  * @code
  *   // Returns FALSE:
@@ -211,7 +234,8 @@ 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 for invalid path or the real path of the source.
+ * @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);
@@ -219,7 +243,7 @@ function file_check_location($source, $d
     $source = $check;
   }
   else {
-    // This file does not yet exist
+    // This file does not yet exist.
     $source = realpath(dirname($source)) . '/' . basename($source);
   }
   $directory = realpath($directory);
@@ -230,88 +254,75 @@ function file_check_location($source, $d
 }
 
 /**
- * Copies a file to a new location. This is a powerful function that in many ways
- * performs like an advanced version of copy().
- * - Checks if $source and $dest are valid and readable/writable.
- * - Performs a file copy if $source is not equal to $dest.
- * - If file already exists in $dest either the call will error out, replace the
- *   file or rename the file based on the $replace parameter.
- *
- * @param $source A string specifying the file location of the original file.
- *   This parameter will contain the resulting destination filename in case of
- *   success.
- * @param $dest A string containing the directory $source should be copied to.
- *   If this value is omitted, Drupal's 'files' directory will be used.
- * @param $replace Replace behavior when the destination file already exists.
- *   - FILE_EXISTS_REPLACE - Replace the existing file
- *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is unique
+ * Copy a file to a new location.
+ *
+ * This is a powerful function that in many ways performs like an advanced
+ * version of copy().
+ * - Checks if $source and $destination are valid and readable/writable.
+ * - Checks that $source is not equal to $destination; if they are an error
+ *   is reported.
+ * - If file already exists in $destination either the call will error out,
+ *   replace the file or rename the file based on the $replace parameter.
+ *
+ * @param $source
+ *   A string specifying the file location of the original file.
+ * @param $destination
+ *   A string containing the directory $source should be copied to. If this
+ *   value is omitted, Drupal's 'files' directory will be used.
+ * @param $replace
+ *   Replace behavior when the destination file already exists:
+ *   - FILE_EXISTS_REPLACE - Replace the existing file.
+ *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
+ *                          unique.
  *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
- * @return True for success, FALSE for failure.
+ * @return
+ *   The path to the new file, or FALSE in the event of an error.
  */
-function file_copy(&$source, $dest = 0, $replace = FILE_EXISTS_RENAME) {
-  $dest = file_create_path($dest);
+function file_copy($source, $destination = NULL, $replace = FILE_EXISTS_RENAME) {
+  $source = 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;
+  }
 
-  $directory = $dest;
+  $destination = file_create_path($destination);
+  $directory = $destination;
   $basename = file_check_path($directory);
 
   // Make sure we at least have a valid directory.
   if ($basename === FALSE) {
-    $source = is_object($source) ? $source->filepath : $source;
-    drupal_set_message(t('The selected file %file could not be uploaded, because the destination %directory is not properly configured.', array('%file' => $source, '%directory' => $dest)), 'error');
-    watchdog('file system', 'The selected file %file could not be uploaded, because the destination %directory could not be found, or because its permissions do not allow the file to be written.', array('%file' => $source, '%directory' => $dest), WATCHDOG_ERROR);
+    drupal_set_message(t('The specified file %file could not be copied, because the destination %directory is not properly configured.', array('%file' => $source, '%directory' => $destination)), 'error');
     return FALSE;
   }
 
-  // Process a file upload object.
-  if (is_object($source)) {
-    $file = $source;
-    $source = $file->filepath;
-    if (!$basename) {
-      $basename = $file->filename;
-    }
-  }
-
-  $source = realpath($source);
-  if (!file_exists($source)) {
-    drupal_set_message(t('The selected 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;
-  }
-
-  // If the destination file is not specified then use the filename of the source file.
+  // If the destination file is not specified then use the filename of the
+  // source file.
   $basename = $basename ? $basename : basename($source);
-  $dest = $directory . '/' . $basename;
-
-  // 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($dest)) {
-    if (!$dest = file_destination($dest, $replace)) {
-      drupal_set_message(t('The selected file %file could not be copied, because a file by that name already exists in the destination.', array('%file' => $source)), 'error');
-      return FALSE;
-    }
-
-    if (!@copy($source, $dest)) {
-      drupal_set_message(t('The selected file %file could not be copied.', array('%file' => $source)), 'error');
-      return FALSE;
-    }
+  $destination = file_destination($directory . '/' . $basename, $replace);
 
-    // Give everyone read access so that FTP'd users or
-    // non-webserver users can see/read these files,
-    // and give group write permissions so group members
-    // can alter files uploaded by the webserver.
-    @chmod($dest, 0664);
+  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;
   }
-
-  if (isset($file) && is_object($file)) {
-    $file->filename = $basename;
-    $file->filepath = $dest;
-    $source = $file;
+  // 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)) {
+    drupal_set_message(t('The specified file %file was not copied because it would overwrite itself.', array('%file' => $source)), 'error');
+    return FALSE;
   }
-  else {
-    $source = $dest;
+  if (!@copy($source, $destination)) {
+    drupal_set_message(t('The specified file %file could not be copied.', array('%file' => $source)), 'error');
+    return FALSE;
   }
 
-  return TRUE; // Everything went ok.
+  // Give everyone read access so that FTP'd users or
+  // non-webserver users can see/read these files,
+  // and give group write permissions so group members
+  // can alter files uploaded by the webserver.
+  @chmod($destination, 0664);
+
+  return $destination;
 }
 
 /**
@@ -320,9 +331,9 @@ function file_copy(&$source, $dest = 0, 
  *
  * @param $destination A string specifying the desired path.
  * @param $replace Replace behavior when the destination file already exists.
- *   - FILE_EXISTS_REPLACE - Replace the existing file
+ *   - FILE_EXISTS_REPLACE - Replace the existing file.
  *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
- *     unique
+ *                          unique.
  *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
  * @return The destination file path or FALSE if the file already exists and
  *   FILE_EXISTS_ERROR was specified.
@@ -330,6 +341,10 @@ function file_copy(&$source, $dest = 0, 
 function file_destination($destination, $replace) {
   if (file_exists($destination)) {
     switch ($replace) {
+      case FILE_EXISTS_REPLACE:
+        // Do nothing here, we want to overwrite the existing file.
+        break;
+
       case FILE_EXISTS_RENAME:
         $basename = basename($destination);
         $directory = dirname($destination);
@@ -337,7 +352,7 @@ function file_destination($destination, 
         break;
 
       case FILE_EXISTS_ERROR:
-        drupal_set_message(t('The selected file %file could not be copied, because a file by that name already exists in the destination.', array('%file' => $destination)), 'error');
+        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' => $destination)), 'error');
         return FALSE;
     }
   }
@@ -345,35 +360,28 @@ function file_destination($destination, 
 }
 
 /**
- * Moves a file to a new location.
- * - Checks if $source and $dest are valid and readable/writable.
- * - Performs a file move if $source is not equal to $dest.
- * - If file already exists in $dest either the call will error out, replace the
- *   file or rename the file based on the $replace parameter.
- *
- * @param $source A string specifying the file location of the original file.
- *   This parameter will contain the resulting destination filename in case of
- *   success.
- * @param $dest A string containing the directory $source should be copied to.
- *   If this value is omitted, Drupal's 'files' directory will be used.
- * @param $replace Replace behavior when the destination file already exists.
- *   - FILE_EXISTS_REPLACE - Replace the existing file
- *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is unique
+ * Move a file to a new location.
+ *
+ * @param $source
+ *   A string specifying the file location of the original file.
+ * @param $destination
+ *   A string containing the directory $source should be copied to. If this
+ *   value is omitted, Drupal's 'files' directory will be used.
+ * @param $replace
+ *   Replace behavior when the destination file already exists:
+ *   - FILE_EXISTS_REPLACE - Replace the existing file.
+ *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
+ *                          unique.
  *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
- * @return TRUE for success, FALSE for failure.
+ * @return
+ *   The filepath of the moved file, or FALSE in the event of an error.
  */
-function file_move(&$source, $dest = 0, $replace = FILE_EXISTS_RENAME) {
-  $path_original = is_object($source) ? $source->filepath : $source;
-
-  if (file_copy($source, $dest, $replace)) {
-    $path_current = is_object($source) ? $source->filepath : $source;
-
-    if ($path_original == $path_current || file_delete($path_original)) {
-      return TRUE;
-    }
-    drupal_set_message(t('The removal of the original file %file has failed.', array('%file' => $path_original)), 'error');
+function file_move($source, $destination = NULL, $replace = FILE_EXISTS_RENAME) {
+  $filepath = file_copy($source, $destination, $replace);
+  if ($filepath == FALSE || file_delete($source) == FALSE) {
+    return FALSE;
   }
-  return FALSE;
+  return $filepath;
 }
 
 /**
@@ -438,9 +446,9 @@ function file_unmunge_filename($filename
  * @return
  */
 function file_create_filename($basename, $directory) {
-  $dest = $directory . '/' . $basename;
+  $destination = $directory . '/' . $basename;
 
-  if (file_exists($dest)) {
+  if (file_exists($destination)) {
     // Destination file already exists, generate an alternative.
     if ($pos = strrpos($basename, '.')) {
       $name = substr($basename, 0, $pos);
@@ -452,45 +460,65 @@ function file_create_filename($basename,
 
     $counter = 0;
     do {
-      $dest = $directory . '/' . $name . '_' . $counter++ . $ext;
-    } while (file_exists($dest));
+      $destination = $directory . '/' . $name . '_' . $counter++ . $ext;
+    } while (file_exists($destination));
   }
 
-  return $dest;
+  return $destination;
 }
 
 /**
  * Delete a file.
  *
- * @param $path A string containing a file path.
- * @return TRUE for success, FALSE for failure.
+ * @param $path
+ *   A string containing a file path.
+ * @return
+ *   TRUE for success or path does not exist, or FALSE in the event of an
+ *   error.
  */
 function file_delete($path) {
+  if (is_dir($path)) {
+    watchdog('file', t('%path is a directory and cannot be removed using file_delete().', array('%path' => $path)), WATCHDOG_ERROR);
+    return FALSE;
+  }
   if (is_file($path)) {
     return unlink($path);
   }
+  // Return TRUE for non-existant file, but log that nothing was actually
+  // deleted, as the current state is the indended result.
+  if (!file_exists($path)) {
+    watchdog('file', t('The file %path was not deleted, because it does not exist.', array('%path' => $path)), WATCHDOG_NOTICE);
+    return TRUE;
+  }
+  // Catch all for everything else: sockets, symbolic links, etc.
+  return FALSE;
 }
 
 /**
  * Determine total disk space used by a single user or the whole filesystem.
  *
  * @param $uid
- *   An optional user id. A NULL value returns the total space used
- *   by all files.
+ *   Optional. A user id, specifying NULL returns the total space used by all
+ *   non-temporary files.
+ * @param $status
+ *   Optional. File Status to return. Combine with a bitwise OR(|) to return
+ *   multiple statuses. The default status is FILE_STATUS_PERMANENT.
+ * @return
+ *   An integer containing the number of bytes used.
  */
-function file_space_used($uid = NULL) {
-  if (isset($uid)) {
-    return (int) db_result(db_query('SELECT SUM(filesize) FROM {files} WHERE uid = %d', $uid));
+function file_space_used($uid = NULL, $status = FILE_STATUS_PERMANENT) {
+  if (!is_null($uid)) {
+    return (int)db_result(db_query('SELECT SUM(filesize) FROM {files} WHERE uid = %d AND status & %d', array($uid, $status)));
   }
-  return (int) db_result(db_query('SELECT SUM(filesize) FROM {files}'));
+  return (int)db_result(db_query('SELECT SUM(filesize) FROM {files} WHERE status & %d', array($status)));
 }
 
 /**
  * Saves a file upload to a new location. The source file is validated as a
  * proper upload and handled as such.
  *
- * The file will be added to the files table as a temporary file. Temporary files
- * are periodically cleaned. To make the file permanent file call
+ * The file will be added to the files table as a temporary file. Temporary
+ * files are periodically cleaned. To make the file permanent file call
  * file_set_status() to change its status.
  *
  * @param $source
@@ -502,7 +530,7 @@ function file_space_used($uid = NULL) {
  *   functions should return an array of error messages, an empty array
  *   indicates that the file passed validation. The functions will be called in
  *   the order specified.
- * @param $dest
+ * @param $destination
  *   A string containing the directory $source should be copied to. If this is
  *   not provided or is not writable, the temporary directory will be used.
  * @param $replace
@@ -510,25 +538,27 @@ function file_space_used($uid = NULL) {
  *   destination directory should overwritten. A false value will generate a
  *   new, unique filename in the destination directory.
  * @return
- *   An object containing the file information, or FALSE in the event of an error.
+ *   An object containing the file information, or FALSE in the event of an
+ *   error.
  */
-function file_save_upload($source, $validators = array(), $dest = FALSE, $replace = FILE_EXISTS_RENAME) {
+function file_save_upload($source, $validators = array(), $destination = FALSE, $replace = FILE_EXISTS_RENAME) {
   global $user;
   static $upload_cache;
 
-  // Add in our check of the the file name length.
-  $validators['file_validate_name_length'] = array();
-
   // Return cached objects without processing since the file will have
   // already been processed and the paths in _FILES will be invalid.
   if (isset($upload_cache[$source])) {
     return $upload_cache[$source];
   }
 
+  // Add in our check of the the file name length.
+  $validators['file_validate_name_length'] = array();
+
+
   // If a file was uploaded, process it.
   if (isset($_FILES['files']) && $_FILES['files']['name'][$source] && is_uploaded_file($_FILES['files']['tmp_name'][$source])) {
-    // Check for file upload errors and return FALSE if a
-    // lower level system error occurred.
+    // Check for file upload errors and return FALSE if a lower level system
+    // error occurred.
     switch ($_FILES['files']['error'][$source]) {
       // @see http://php.net/manual/en/features.file-upload.errors.php
       case UPLOAD_ERR_OK:
@@ -560,9 +590,12 @@ function file_save_upload($source, $vali
 
     // Begin building file object.
     $file = new stdClass();
+    $file->uid      = $user->uid;
+    $file->status   = FILE_STATUS_TEMPORARY;
     $file->filename = file_munge_filename(trim(basename($_FILES['files']['name'][$source]), '.'), $extensions);
     $file->filepath = $_FILES['files']['tmp_name'][$source];
     $file->filemime = file_get_mimetype($file->filename);
+    $file->filesize = $_FILES['files']['size'][$source];
 
     // Rename potentially executable files, to help prevent exploits.
     if (preg_match('/\.(php|pl|py|cgi|asp|js)$/i', $file->filename) && (substr($file->filename, -4) != '.txt')) {
@@ -573,26 +606,21 @@ function file_save_upload($source, $vali
 
     // If the destination is not provided, or is not writable, then use the
     // temporary directory.
-    if (empty($dest) || file_check_path($dest) === FALSE) {
-      $dest = file_directory_temp();
+    if (empty($destination) || file_check_path($destination) === FALSE) {
+      $destination = file_directory_temp();
     }
 
     $file->source = $source;
-    $file->destination = file_destination(file_create_path($dest . '/' . $file->filename), $replace);
-    $file->filesize = $_FILES['files']['size'][$source];
+    $file->destination = file_destination(file_create_path($destination . '/' . $file->filename), $replace);
 
-    // Call the validation functions.
-    $errors = array();
-    foreach ($validators as $function => $args) {
-      array_unshift($args, $file);
-      $errors = array_merge($errors, call_user_func_array($function, $args));
-    }
+    // Call the validation functions specified by this function's caller.
+    $errors = file_validate($file, $validators);
 
-    // Check for validation errors.
+    // Check for errors.
     if (!empty($errors)) {
-      $message = t('The selected file %name could not be uploaded.', array('%name' => $file->filename));
+      $message = t('The specified file %name could not be uploaded.', array('%name' => $file->filename));
       if (count($errors) > 1) {
-        $message .= '<ul><li>' . implode('</li><li>', $errors) . '</li></ul>';
+        $message .= theme('item_list', $errors);
       }
       else {
         $message .= ' ' . array_pop($errors);
@@ -601,8 +629,9 @@ function file_save_upload($source, $vali
       return FALSE;
     }
 
-    // Move uploaded files from PHP's upload_tmp_dir to Drupal's temporary directory.
-    // This overcomes open_basedir restrictions for future file operations.
+    // Move uploaded files from PHP's upload_tmp_dir to Drupal's temporary
+    // directory. This overcomes open_basedir restrictions for future file
+    // operations.
     $file->filepath = $file->destination;
     if (!move_uploaded_file($_FILES['files']['tmp_name'][$source], $file->filepath)) {
       form_set_error($source, t('File upload error. Could not move uploaded file.'));
@@ -611,7 +640,6 @@ function file_save_upload($source, $vali
     }
 
     // If we made it this far it's safe to record this file in the database.
-    $file->uid = $user->uid;
     $file->status = FILE_STATUS_TEMPORARY;
     $file->timestamp = $_SERVER['REQUEST_TIME'];
     drupal_write_record('files', $file);
@@ -619,10 +647,38 @@ function file_save_upload($source, $vali
     // Add file to the cache.
     $upload_cache[$source] = $file;
     return $file;
+
   }
   return FALSE;
 }
 
+
+/**
+ * Check that a file meets the criteria specified by the validators.
+ *
+ * @param $file
+ *   A Drupal file object.
+ * @param $validators
+ *   An optional, associative array of callback functions used to validate the
+ *   file. The keys are function names and the values arrays of callback
+ *   parameters which will be passed in after the user and file objects. The
+ *   functions should return an array of error messages, an empty array
+ *   indicates that the file passed validation. The functions will be called in
+ *   the order specified.
+ * @return
+ *   An array contaning validation error messages.
+ */
+function file_validate(&$file, $validators = array()) {
+  // Call the validation functions specified by this function's caller.
+  $errors = array();
+  foreach ($validators as $function => $args) {
+    array_unshift($args, $file);
+    $errors = array_merge($errors, call_user_func_array($function, $args));
+  }
+
+  return $errors;
+}
+
 /**
  * Check for files with names longer than we can store in the database.
  *
@@ -634,34 +690,34 @@ function file_save_upload($source, $vali
 function file_validate_name_length($file) {
   $errors = array();
 
+  if (empty($file->filename)) {
+    $errors[] = t("The file's name is empty. Please give a name to the file.");
+  }
   if (strlen($file->filename) > 255) {
-    $errors[] = t('Its name exceeds the 255 characters limit. Please rename the file and try again.');
+    $errors[] = t("The file's name exceeds the 255 characters limit. Please rename the file and try again.");
   }
   return $errors;
 }
 
 /**
- * Check that the filename ends with an allowed extension. This check is not
- * enforced for the user #1.
+ * Check that the filename ends with an allowed extension.
  *
  * @param $file
  *   A Drupal file object.
  * @param $extensions
  *   A string with a space separated
  * @return
- *   An array. If the file extension is not allowed, it will contain an error message.
+ *   An array. If the file extension is not allowed, it will contain an error
+ *   message.
  */
 function file_validate_extensions($file, $extensions) {
   global $user;
 
   $errors = array();
 
-  // Bypass validation for uid  = 1.
-  if ($user->uid != 1) {
-    $regex = '/\.(' . preg_replace('/ +/', '|', preg_quote($extensions)) . ')$/i';
-    if (!preg_match($regex, $file->filename)) {
-      $errors[] = t('Only files with the following extensions are allowed: %files-allowed.', array('%files-allowed' => $extensions));
-    }
+  $regex = '/\.(' . preg_replace('/ +/', '|', preg_quote($extensions)) . ')$/i';
+  if (!preg_match($regex, $file->filename)) {
+    $errors[] = t('Only files with the following extensions are allowed: %files-allowed.', array('%files-allowed' => $extensions));
   }
   return $errors;
 }
@@ -676,10 +732,11 @@ function file_validate_extensions($file,
  *   An integer specifying the maximum file size in bytes. Zero indicates that
  *   no limit should be enforced.
  * @param $$user_limit
- *   An integer specifying the maximum number of bytes the user is allowed. Zero
- *   indicates that no limit should be enforced.
+ *   An integer specifying the maximum number of bytes the user is allowed.
+ *   Zero indicates that no limit should be enforced.
  * @return
- *   An array. If the file size exceeds limits, it will contain an error message.
+ *   An array. If the file size exceeds limits, it will contain an error
+ *   message.
  */
 function file_validate_size($file, $file_limit = 0, $user_limit = 0) {
   global $user;
@@ -724,15 +781,16 @@ function file_validate_is_image(&$file) 
  * maximum and minimum dimensions. Non-image files will be ignored.
  *
  * @param $file
- *   A Drupal file object. This function may resize the file affecting its size.
+ *   A Drupal file object. This function may resize the file affecting its
+ *   size.
  * @param $maximum_dimensions
  *   An optional string in the form WIDTHxHEIGHT e.g. '640x480' or '85x85'. If
  *   an image toolkit is installed the image will be resized down to these
  *   dimensions. A value of 0 indicates no restriction on size, so resizing
  *   will be attempted.
  * @param $minimum_dimensions
- *   An optional string in the form WIDTHxHEIGHT. This will check that the image
- *   meets a minimum size. A value of 0 indicates no restriction.
+ *   An optional string in the form WIDTHxHEIGHT. This will check that the
+ *   image meets a minimum size. A value of 0 indicates no restriction.
  * @return
  *   An array. If the file is an image and did not meet the requirements, it
  *   will contain an error message.
@@ -776,43 +834,50 @@ function file_validate_image_resolution(
 /**
  * Save a string to the specified destination.
  *
- * @param $data A string containing the contents of the file.
- * @param $dest A string containing the destination location.
- * @param $replace Replace behavior when the destination file already exists.
- *   - FILE_EXISTS_REPLACE - Replace the existing file
- *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is unique
+ * @param $data
+ *   A string containing the contents of the file.
+ * @param $destination
+ *   A string containing the destination location. If no value is provided
+ *   then a randomly name will be generated and the file saved in Drupal's
+ *   files directory.
+ * @param $replace
+ *   Replace behavior when the destination file already exists:
+ *   - FILE_EXISTS_REPLACE - Replace the existing file.
+ *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
+ *                          unique.
  *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
- *
- * @return A string containing the resulting filename or FALSE on error
+ * @return
+ *   A string with the path of the resulting file, or FALSE on error.
  */
-function file_save_data($data, $dest, $replace = FILE_EXISTS_RENAME) {
-  $temp = file_directory_temp();
-  // On Windows, tempnam() requires an absolute path, so we use realpath().
-  $file = tempnam(realpath($temp), 'file');
-  if (!$fp = fopen($file, 'wb')) {
+function file_save_data($data, $destination = NULL, $replace = FILE_EXISTS_RENAME) {
+  // Write the data to a temporary file.
+  $temp_name = tempnam(file_directory_temp(), 'file');
+  if (file_put_contents($temp_name, $data) === FALSE) {
     drupal_set_message(t('The file could not be created.'), 'error');
     return FALSE;
   }
-  fwrite($fp, $data);
-  fclose($fp);
 
-  if (!file_move($file, $dest, $replace)) {
-    return FALSE;
-  }
-
-  return $file;
+  // Move the file to its final destination.
+  return file_move($temp_name, $destination, $replace);
 }
 
 /**
  * Set the status of a file.
  *
- * @param file A Drupal file object
- * @param status A status value to set the file to.
- * @return FALSE on failure, TRUE on success and $file->status will contain the
- *     status.
+ * @param $file
+ *   A Drupal file object.
+ * @param $status
+ *   A status value to set the file to.
+ *   - FILE_STATUS_TEMPORARY - A temporary file that Drupal's garbage
+ *                             collection will remove.
+ *   - FILE_STATUS_PERMANENT - A permanent file that Drupal's garbage
+ *                             collection will not remove.
+ * @return
+ *   File object if the change is successful, or FALSE in the event of an
+ *   error.
  */
-function file_set_status(&$file, $status) {
-  if (db_query('UPDATE {files} SET status = %d WHERE fid = %d', $status, $file->fid)) {
+function file_set_status($file, $status =  FILE_STATUS_PERMANENT) {
+  if (db_query('UPDATE {files} SET status = %d WHERE fid = %d', array($status, $file->fid))) {
     $file->status = $status;
     return TRUE;
   }
@@ -820,11 +885,13 @@ function file_set_status(&$file, $status
 }
 
 /**
- * Transfer file using http to client. Pipes a file through Drupal to the
+ * Transfer file using HTTP to client. Pipes a file through Drupal to the
  * client.
  *
- * @param $source File to transfer.
- * @param $headers An array of http headers to send along with file.
+ * @param $source
+ *   String specifying the file path to transfer.
+ * @param $headers
+ *   An array of HTTP headers to send along with file.
  */
 function file_transfer($source, $headers) {
   ob_end_clean();
@@ -853,6 +920,8 @@ function file_transfer($source, $headers
 }
 
 /**
+ * Menu handler private file transfers.
+ *
  * Call modules that implement hook_file_download() to find out if a file is
  * accessible and what headers it should be transferred with. If a module
  * returns -1 drupal_access_denied() will be returned. If one or more modules
@@ -870,6 +939,7 @@ function file_download() {
   }
 
   if (file_exists(file_create_path($filepath))) {
+    // Let other modules provide headers and controls access to the file.
     $headers = module_invoke_all('file_download', $filepath);
     if (in_array(-1, $headers)) {
       return drupal_access_denied();
@@ -907,7 +977,8 @@ function file_download() {
  * @param $min_depth
  *   Minimum depth of directories to return files from.
  * @param $depth
- *   Current depth of recursion. This parameter is only used internally and should not be passed.
+ *   Current depth of recursion. This parameter is only used internally and
+ *   should not be passed.
  *
  * @return
  *   An associative array (keyed on the provided key) of objects with
@@ -926,7 +997,8 @@ function file_scan_directory($dir, $mask
           $files = array_merge(file_scan_directory("$dir/$file", $mask, $nomask, $callback, $recurse, $key, $min_depth, $depth + 1), $files);
         }
         elseif ($depth >= $min_depth && ereg($mask, $file)) {
-          // Always use this match over anything already set in $files with the same $$key.
+          // Always use this match over anything already set in $files with the
+          // same $$key.
           $filename = "$dir/$file";
           $basename = basename($file);
           $name = substr($basename, 0, strrpos($basename, '.'));
@@ -965,13 +1037,11 @@ function file_directory_temp() {
 
     // Operating system specific dirs.
     if (substr(PHP_OS, 0, 3) == 'WIN') {
-      $directories[] = 'c:\\windows\\temp';
-      $directories[] = 'c:\\winnt\\temp';
-      $path_delimiter = '\\';
+      $directories[] = 'c:/windows/temp';
+      $directories[] = 'c:/winnt/temp';
     }
     else {
       $directories[] = '/tmp';
-      $path_delimiter = '/';
     }
 
     foreach ($directories as $directory) {
@@ -980,8 +1050,8 @@ function file_directory_temp() {
       }
     }
 
-    // if a directory has been found, use it, otherwise default to 'files/tmp' or 'files\\tmp';
-    $temporary_directory = $temporary_directory ? $temporary_directory : file_directory_path() . $path_delimiter . 'tmp';
+    // if a directory has been found, use it, otherwise default to 'files/tmp'
+    $temporary_directory = $temporary_directory ? $temporary_directory : file_directory_path() . '/tmp';
     variable_set('file_directory_temp', $temporary_directory);
   }
 
@@ -1001,7 +1071,8 @@ function file_directory_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
+ *   A file size limit in bytes based on the PHP upload_max_filesize and
+ *   post_max_size
  */
 function file_upload_max_size() {
   static $max_size = -1;
Index: modules/blogapi/blogapi.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/blogapi/blogapi.module,v
retrieving revision 1.123
diff -u -p -r1.123 blogapi.module
--- modules/blogapi/blogapi.module	6 Sep 2008 08:36:19 -0000	1.123
+++ modules/blogapi/blogapi.module	15 Sep 2008 08:15:53 -0000
@@ -385,12 +385,12 @@ function blogapi_metaweblog_new_media_ob
     return blogapi_error(t('No file sent.'));
   }
 
-  if (!$file = file_save_data($data, $name)) {
+  if (!$filepath = file_save_data($data, $name)) {
     return blogapi_error(t('Error storing file.'));
   }
 
   // Return the successful result.
-  return array('url' => file_create_url($file), 'struct');
+  return array('url' => file_create_url($filepath), 'struct');
 }
 /**
  * Blogging API callback. Returns a list of the taxonomy terms that can be
Index: modules/color/color.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/color/color.module,v
retrieving revision 1.43
diff -u -p -r1.43 color.module
--- modules/color/color.module	31 Aug 2008 09:15:12 -0000	1.43
+++ modules/color/color.module	15 Sep 2008 08:15:53 -0000
@@ -435,8 +435,8 @@ function _color_rewrite_stylesheet($them
  * Save the rewritten stylesheet to disk.
  */
 function _color_save_stylesheet($file, $style, &$paths) {
-  file_save_data($style, $file, FILE_EXISTS_REPLACE);
-  $paths['files'][] = $file;
+  $filepath = file_save_data($style, $file, FILE_EXISTS_REPLACE);
+  $paths['files'][] = $filepath;
 
   // Set standard file permissions for webserver-generated files.
   @chmod($file, 0664);
Index: modules/simpletest/tests/file.test
===================================================================
RCS file: modules/simpletest/tests/file.test
diff -N modules/simpletest/tests/file.test
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ modules/simpletest/tests/file.test	15 Sep 2008 08:15:53 -0000
@@ -0,0 +1,601 @@
+<?php
+// $Id$
+
+/**
+ *  @file
+ *  This provides SimpleTests for the core file handling functionality.
+ *  These include FileValidateTest and FileSaveTest.
+ */
+
+/**
+ * Helper validator that returns the $errors parameter.
+ */
+function file_test_validator($file, $errors) {
+  return $errors;
+}
+
+/**
+ *  This will run tests against the file validation functions (file_validate_*).
+ */
+class FileValidateTest extends DrupalWebTestCase {
+  /**
+   * Implementation of getInfo().
+   */
+  function getInfo() {
+    return array(
+      'name' => t('File validation'),
+      'description' => t('Tests the functions used to validate uploaded files.'),
+      'group' => t('File'),
+    );
+  }
+
+  /**
+   * Implementation of setUp().
+   */
+  function setUp() {
+    parent::setUp();
+
+    $this->image = new stdClass();
+    $this->image->filepath = 'misc/druplicon.png';
+    $this->iamge->filename = basename($this->image->filepath);
+
+    $this->non_image = new stdClass();
+    $this->non_image->filepath = 'misc/jquery.js';
+    $this->non_image->filename = basename($this->non_image->filepath);
+  }
+
+  function testFileValidate() {
+    // Empty validators
+    $this->assertEqual(file_validate($this->image, array()), array(), t('Validating an empty array works succesfully.'));
+
+    // Use the file_test.module's test validator to ensure that passing tests
+    // return correctly.
+    $passing = array('file_test_validator' => array(array()));
+    $this->assertEqual(file_validate($this->image, $passing), array(), t('Validating passes.'));
+
+    $failing = array('file_test_validator' => array(array('Failed', 'Badly')));
+    $this->assertEqual(file_validate($this->image, $failing), array('Failed', 'Badly'), t('Validating returns errors.'));
+  }
+
+  /**
+   * Test the file_validate_extensions() function.
+   */
+  function testFileValidateExtensions() {
+    $file = new stdClass();
+    $file->filename = 'asdf.txt';
+    $errors = file_validate_extensions($file, 'asdf txt pork');
+    $this->assertEqual(count($errors), 0, t("Valid extension accepted."), 'File');
+
+    $file->filename = 'asdf.txt';
+    $errors = file_validate_extensions($file, 'exe png');
+    $this->assertEqual(count($errors), 1, t("Invalid extension blocked."), 'File');
+  }
+
+  /**
+   *  This ensures a specific file is actually an image.
+   */
+  function testFileValidateIsImage() {
+    $this->assertTrue(file_exists($this->image->filepath), t('The image being tested exists.'), 'File');
+    $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');
+  }
+
+  /**
+   *  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');
+    $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');
+    $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');
+      $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'));
+    }
+    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');
+    }
+
+    // Clear out any resizing messages.
+#    drupal_get_messages();
+  }
+
+  /**
+   *  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);
+    $errors = file_validate_name_length($file);
+    $this->assertEqual(count($errors), 0, t('No errors reported for 255 length filename.'), 'File');
+
+    // Add a filename with a length too long and test it.
+    $file->filename = str_repeat('x', 256);
+    $errors = file_validate_name_length($file);
+    $this->assertEqual(count($errors), 1, t('An error reported for 256 length filename.'), 'File');
+
+    // Add a filename with an empty string and test it.
+    $file->filename = '';
+    $errors = file_validate_name_length($file);
+    $this->assertEqual(count($errors), 1, t('An error reported for 0 length filename.'), 'File');
+  }
+
+
+  /**
+   * Test file_validate_size().
+   */
+  function testFileValidateSize() {
+    global $user;
+    $original_user = $user;
+    session_save_session(FALSE);
+
+    // Run these test as uid = 1
+    $user = user_load(array('uid' => 1));
+
+    $file = new stdClass();
+    $file->filesize = 999999;
+    $errors = file_validate_size($file, 1, 1);
+    $this->assertEqual(count($errors), 0, t("No size limits enforced on uid=1."), 'File');
+
+
+    // Run these test as a regular user
+    $user = $this->drupalCreateUser();
+
+    $file = new stdClass();
+    $file->filesize = 1000;
+    $errors = file_validate_size($file, 0, 0);
+    $this->assertEqual(count($errors), 0, t("No limits means no errors."), 'File');
+    $errors = file_validate_size($file, 1, 0);
+    $this->assertEqual(count($errors), 1, t("Error for the file being over the limit."), 'File');
+    $errors = file_validate_size($file, 0, 1);
+    $this->assertEqual(count($errors), 1, t("Error for the user being over their limit."), 'File');
+    $errors = file_validate_size($file, 1, 1);
+    $this->assertEqual(count($errors), 2, t("Errors for both the file and their limit."), 'File');
+
+
+    $user = $original_user;
+    session_save_session(TRUE);
+  }
+}
+
+
+/**
+ *  This will run tests against file validation.
+ *
+ */
+class FileLoadSaveTest extends DrupalWebTestCase {
+  /**
+   * Implementation of getInfo().
+   */
+  function getInfo() {
+    return array(
+      'name' => t('File loading and saving'),
+      'description' => t('Tests the file loading and saving functions.'),
+      'group' => t('File'),
+    );
+  }
+
+  /**
+   * Implementation of setUp().
+   */
+  function setUp() {
+    parent::setUp();
+  }
+
+  function testFileSaveData() {
+    $contents = $this->randomName(8);
+
+    // No filename
+    $filepath = file_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."));
+
+    // Provide a filename
+    $filepath = file_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."));
+  }
+}
+
+/**
+ * Directory related tests.
+ */
+class FileDirectoryTest extends DrupalWebTestCase {
+  /**
+   * Implementation of getInfo().
+   */
+  function getInfo() {
+    return array(
+      'name' => t('File paths and directories'),
+      'description' => t('Tests operations dealing with directories.'),
+      'group' => t('File'),
+    );
+  }
+
+  /**
+   * Implementation of setUp().
+   */
+  function setUp() {
+    parent::setUp();
+
+    // A directory to operate on.
+    $this->directory = file_directory_path() . '/' . $this->randomName();
+    // Save initial temp directory as this gets modified.
+    $this->initial_temp_directory = variable_get('file_directory_temp', NULL);
+  }
+
+  /**
+   * Implementation of tearDown().
+   */
+  function tearDown() {
+    @rmdir($this->directory);
+    variable_set('file_directory_temp', $this->initial_temp_directory);
+
+    // clear out form error messages generated
+    drupal_get_messages();
+
+    parent::tearDown();
+  }
+
+  /**
+   * Check directory creation and validation
+   */
+  function testFileCheckDirectory() {
+    // non-existent directory
+    $form_element = $this->randomName();
+    $this->assertFalse(file_check_directory($this->directory, 0, $form_element), t("Error reported for non-existing directory."), 'File');
+
+    // check that an error was set for the form element above
+    $errors = form_get_errors();
+    $this->assertEqual($errors[$form_element], t('The directory %directory does not exist.', array('%directory' => $this->directory)), t("Properly generated an error for the passed form element."), 'File');
+
+    // make a directory
+    $this->assertTrue(file_check_directory($this->directory, FILE_CREATE_DIRECTORY), t("No error reported when creating a new directory"), 'File');
+
+    // make sure directory actually exists
+    $this->assertTrue(is_dir($this->directory), t("Directory actually exists"), 'File');
+
+    // make directory read only
+    @chmod($this->directory, 0444);
+    $form_element = $this->randomName();
+    $this->assertFalse(file_check_directory($this->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' => $this->directory)), t("Properly generated an error for the passed form element."), 'File');
+
+    // test directory permission modification
+    $this->assertTrue(file_check_directory($this->directory, FILE_MODIFY_PERMISSIONS), t("No error reported when making directory writeable."), 'File');
+
+    // verify directory actually is writeable
+    $this->assertTrue(is_writeable($this->directory), t("Directory is writeable"), 'File');
+
+    // remove .htaccess file to then test the writing of .htaccess file
+    @unlink(file_directory_path() .'/.htaccess');
+    file_check_directory(file_directory_path());
+    $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');
+  }
+
+  /**
+   * 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');
+  }
+
+  /**
+   * Check file_directory_path() and file_directory_temp().
+   */
+  function testFileDirectoryTemp() {
+    // temp directory handling
+    variable_set('file_directory_temp', NULL);
+    $temp = file_directory_temp();
+    $this->assertTrue(!is_null($temp), t("Properly set and retrieved temp directory %directory", array('%directory' => $temp)), 'File');
+  }
+
+  /**
+   *  This tests that a file is actually in the specified directory, to prevent exploits.
+   */
+  function testFileCheckLocation() {
+    $source = 'misc/xyz.txt';
+    $directory = 'misc';
+    $result = file_check_location($source, $directory);
+    $this->assertTrue($result, t("Non-existent file validates when checked for location in existing directory."), 'File');
+
+    $source = 'fake/xyz.txt';
+    $directory = '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 = 'misc/../install.php';
+    $directory = 'misc';
+    $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 = '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 will take a directory and path, and find a valid filepath that is not taken by another file.
+   * First we test against an imaginary file that does not exist in a directory.
+   * Then we test against a file that already exists within that directory.
+   * @TODO: Finally we copy a file into a directory several times, to ensure a properly iterating filename suffix.
+   */
+  function testFileCreateNewFilepath() {
+    $basename = 'xyz.txt';
+    $directory = 'misc';
+    $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');
+
+    $basename = 'druplicon.png';
+    $original = $directory .'/'. $basename;
+    $expected = $directory .'/druplicon_0.png';
+    $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');
+  }
+
+  /**
+   * 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, or 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';
+    $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';
+    $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');
+  }
+}
+
+
+/**
+ * Deletion related tests
+ */
+class FileCopyDeleteMoveTest extends DrupalWebTestCase {
+  /**
+   * Implementation of getInfo().
+   */
+  function getInfo() {
+    return array(
+      'name' => t('File management'),
+      'description' => t('Tests the file copy, delete and move functions.'),
+      'group' => t('File'),
+    );
+  }
+
+  /**
+   * Implementation of setUp().
+   */
+  function setUp() {
+    // Install file_test module
+    parent::setUp();
+
+    // A directory to operate on.
+    $this->dirname = file_directory_path() . '/' . $this->randomName();
+    mkdir($this->dirname);
+
+    // Create a file for testing
+    $f = new stdClass();
+    $f->filepath = file_directory_path() . '/' . $this->randomName();
+    $f->filename = basename($f->filepath);
+    touch($f->filepath);
+    $f->filemime = 'text/plain';
+    $f->uid = 1;
+    $f->timestamp = $_SERVER['REQUEST_TIME'];
+    $f->filesize = 0;
+    drupal_write_record('files', $f);
+    $this->file = $f;
+  }
+
+  /**
+   * Implementation of tearDown().
+   */
+  function tearDown() {
+    @rmdir($this->dirname);
+    @unlink($this->file->filepath);
+    if (!empty($this->file->fid)) {
+      db_query('DELETE FROM {files} WHERE fid = %d', array($this->file->fid));
+    }
+
+    parent::tearDown();
+  }
+
+
+  function testFileDelete() {
+    // Delete a regular file
+    $this->assertTrue(is_file($this->file->filepath), t("File exists."));
+    $this->assertTrue(file_delete($this->file->filepath), t("Deleted worked."));
+    $this->assertFalse(file_exists($this->file->filepath), t("Test file has actually been deleted."));
+  }
+
+  function testFileDelete_Missing() {
+    // Try to delete a non-existing file
+    $this->assertTrue(file_delete(file_directory_path() . '/' . $this->randomName()), t("Returns true when deleting a non-existant file."));
+  }
+
+  function testFileDelete_Directory() {
+    // Try to delete a directory
+    $this->assertTrue(is_dir($this->dirname), t("Directory exists."));
+    $this->assertFalse(file_delete($this->dirname), t("Could not delete the delete directory."));
+    $this->assertTrue(file_exists($this->dirname), t("Directory has not been deleted."));
+  }
+
+  function testFileMove() {
+    // Moving to a new name.
+    $this->assertTrue(file_exists($this->file->filepath), t("File exists before moving."));
+    $desired_filepath = file_directory_path() . '/' . $this->randomName();
+    $new_filepath = file_move($this->file->filepath, $desired_filepath, FILE_EXISTS_ERROR);
+    $this->assertTrue($new_filepath, t("Move was successful."));
+    $this->assertEqual($new_filepath, $desired_filepath, t("Returned expected filepath."));
+    $this->assertTrue(file_exists($new_filepath), t("File exists at the new location."));
+    $this->assertFalse(file_exists($this->file->filepath), t("No file remains at the old location."));
+
+    // Moving with rename.
+    $desired_filepath = file_directory_path() . '/' . $this->randomName();
+    $this->assertTrue(file_exists($new_filepath), t("File exists before moving."));
+    $this->assertTrue(touch($desired_filepath), t('Created a file so a rename will have to happen.'));
+    $newer_filepath = file_move($new_filepath, $desired_filepath, FILE_EXISTS_RENAME);
+    $this->assertTrue($newer_filepath, t("Move was successful."));
+    $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."));
+
+    // TODO: test moving to a directory (rather than full directory/file path)
+  }
+
+  function testFileMove_Missing() {
+    // Move non-existant file
+    $new_filepath = file_move($this->randomName(), $this->randomName());
+    $this->assertFalse($new_filepath, t("Moving a missing file fails"));
+
+    drupal_get_messages();
+  }
+
+  function testFileMove_OverwriteSelf() {
+    // Move the file onto itself without renaming shouldn't make changes.
+    $this->assertTrue(file_exists($this->file->filepath), t("File exists before moving."));
+    $new_filepath = file_move($this->file->filepath, $this->file->filepath, FILE_EXISTS_REPLACE);
+    $this->assertFalse($new_filepath, t("Moving onto itself without renaming fails."));
+    $this->assertTrue(file_exists($this->file->filepath), t("File exists after moving onto itself."));
+
+    // Move the file onto itself with renaming will result in a new filename.
+    $this->assertTrue(file_exists($this->file->filepath), t("File exists before moving."));
+    $new_filepath = file_move($this->file->filepath, $this->file->filepath, FILE_EXISTS_RENAME);
+    $this->assertTrue($new_filepath, t("Moving onto itself with renaming works."));
+    $this->assertFalse(file_exists($this->file->filepath), t("Original file has been removed."));
+    $this->assertTrue(file_exists($new_filepath), t("File exists after moving onto itself."));
+
+    drupal_get_messages();
+  }
+
+  function testFileCopy() {
+    // Copying to a new name.
+    $desired_filepath = file_directory_path() . '/' . $this->randomName();
+    $new_filepath = file_copy($this->file->filepath, $desired_filepath, FILE_EXISTS_ERROR);
+    $this->assertTrue($new_filepath, t("Copy was successful."));
+    $this->assertEqual($new_filepath, $desired_filepath, t("Returned expected filepath."));
+    $this->assertTrue(file_exists($this->file->filepath), t("Original file remains."));
+    $this->assertTrue(file_exists($new_filepath), t("New file exists."));
+
+    // Copying with rename.
+    $desired_filepath = file_directory_path() . '/' . $this->randomName();
+    $this->assertTrue(touch($desired_filepath), t('Created a file so a rename will have to happen.'));
+    $newer_filepath = file_copy($new_filepath, $desired_filepath, FILE_EXISTS_RENAME);
+    $this->assertTrue($newer_filepath, t("Copy was successful."));
+    $this->assertNotEqual($newer_filepath, $desired_filepath, t("Returned expected filepath."));
+    $this->assertTrue(file_exists($this->file->filepath), t("Original file remains."));
+    $this->assertTrue(file_exists($new_filepath), t("New file exists."));
+
+    // TODO: test copying to a directory (rather than full directory/file path)
+  }
+
+  function testFileCopy_NonExistant() {
+    // Copy non-existant file
+    $desired_filepath = $this->randomName();
+    $this->assertFalse(file_exists($desired_filepath), t("Randomly named file doesn't exists."));
+    $new_filepath = file_copy($desired_filepath, $this->randomName());
+    $this->assertFalse($new_filepath, t("Copying a missing file fails"));
+
+    drupal_get_messages();
+  }
+
+  function testFileCopy_OverwriteSelf() {
+    // Copy the file onto itself with renaming works.
+    $this->assertTrue(file_exists($this->file->filepath), t("File exists before copying."));
+    $new_filepath = file_copy($this->file->filepath, $this->file->filepath, FILE_EXISTS_RENAME);
+    $this->assertTrue($new_filepath, t("Copying onto itself with renaming works."));
+    $this->assertNotEqual($new_filepath, $this->file->filepath, t("Copied file has a new name."));
+    $this->assertTrue(file_exists($this->file->filepath), t("Original file exists after copying onto itself."));
+    $this->assertTrue(file_exists($new_filepath), t("Copied file exists after copying onto itself."));
+
+    // Copy the file onto itself without renaming fails.
+    $this->assertTrue(file_exists($this->file->filepath), t("File exists before copying."));
+    $new_filepath = file_copy($this->file->filepath, $this->file->filepath, FILE_EXISTS_ERROR);
+    $this->assertFalse($new_filepath, t("Copying onto itself without renaming fails."));
+    $this->assertTrue(file_exists($this->file->filepath), t("File exists after copying onto itself."));
+
+    // Copy the file into same directory without renaming fails.
+    $this->assertTrue(file_exists($this->file->filepath), t("File exists before copying."));
+    $new_filepath = file_copy($this->file->filepath, dirname($this->file->filepath), FILE_EXISTS_ERROR);
+    $this->assertFalse($new_filepath, t("Copying onto itself fails."));
+    $this->assertTrue(file_exists($this->file->filepath), t("File exists after copying onto itself."));
+
+    // Copy the file into same directory with renaming works.
+    $this->assertTrue(file_exists($this->file->filepath), t("File exists before copying."));
+    $new_filepath = file_copy($this->file->filepath, dirname($this->file->filepath), FILE_EXISTS_RENAME);
+    $this->assertTrue($new_filepath, t("Copying into same directory works."));
+    $this->assertNotEqual($new_filepath, $this->file->filepath, t("Copied file has a new name."));
+    $this->assertTrue(file_exists($this->file->filepath), t("Original file exists after copying onto itself."));
+    $this->assertTrue(file_exists($new_filepath), t("Copied file exists after copying onto itself."));
+
+    drupal_get_messages();
+  }
+}
+
Index: modules/system/system.admin.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.admin.inc,v
retrieving revision 1.87
diff -u -p -r1.87 system.admin.inc
--- modules/system/system.admin.inc	6 Sep 2008 08:36:21 -0000	1.87
+++ modules/system/system.admin.inc	15 Sep 2008 08:15:57 -0000
@@ -338,9 +338,9 @@ function system_theme_settings(&$form_st
     // The image was saved using file_save_upload() and was added to the
     // files table as a temporary file. We'll make a copy and let the garbage
     // collector delete the original upload.
-    if (file_copy($file, $filename, FILE_EXISTS_REPLACE)) {
+    if ($filepath = file_copy($file->filepath, $filename, FILE_EXISTS_REPLACE)) {
       $_POST['default_logo'] = 0;
-      $_POST['logo_path'] = $file->filepath;
+      $_POST['logo_path'] = $filepath;
       $_POST['toggle_logo'] = 1;
     }
   }
@@ -353,9 +353,9 @@ function system_theme_settings(&$form_st
     // The image was saved using file_save_upload() and was added to the
     // files table as a temporary file. We'll make a copy and let the garbage
     // collector delete the original upload.
-    if (file_copy($file, $filename)) {
+    if ($filepath = file_copy($file->filepath, $filename, FILE_EXISTS_REPLACE)) {
       $_POST['default_favicon'] = 0;
-      $_POST['favicon_path'] = $file->filepath;
+      $_POST['favicon_path'] = $filepath;
       $_POST['toggle_favicon'] = 1;
     }
   }
Index: modules/system/system.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.install,v
retrieving revision 1.263
diff -u -p -r1.263 system.install
--- modules/system/system.install	6 Sep 2008 08:36:21 -0000	1.263
+++ modules/system/system.install	15 Sep 2008 08:16:01 -0000
@@ -629,7 +629,7 @@ function system_schema() {
     'description' => t('Stores information for uploaded files.'),
     'fields' => array(
       'fid' => array(
-        'description' => t('Primary Key: Unique files ID.'),
+        'description' => t('File ID.'),
         'type' => 'serial',
         'unsigned' => TRUE,
         'not null' => TRUE,
@@ -642,7 +642,7 @@ function system_schema() {
         'default' => 0,
       ),
       'filename' => array(
-        'description' => t('Name of the file.'),
+        'description' => t('Name of the file with no path components. This may differ from the basename of the filepath if the file is renamed to avoid overwriting an existing file.'),
         'type' => 'varchar',
         'length' => 255,
         'not null' => TRUE,
@@ -656,7 +656,7 @@ function system_schema() {
         'default' => '',
       ),
       'filemime' => array(
-        'description' => t('The file MIME type.'),
+        'description' => t("The file's MIME type."),
         'type' => 'varchar',
         'length' => 255,
         'not null' => TRUE,
@@ -670,7 +670,7 @@ function system_schema() {
         'default' => 0,
       ),
       'status' => array(
-        'description' => t('A flag indicating whether file is temporary (1) or permanent (0).'),
+        'description' => t('A bitmapped field indicating the status of the file the least sigifigant bit indicates temporary (1) or permanent (0). Temporary files older than DRUPAL_MAXIMUM_TEMP_FILE_AGE will be removed during a cron run.'),
         'type' => 'int',
         'not null' => TRUE,
         'default' => 0,
Index: modules/upload/upload.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/upload/upload.module,v
retrieving revision 1.205
diff -u -p -r1.205 upload.module
--- modules/upload/upload.module	24 Jul 2008 16:25:19 -0000	1.205
+++ modules/upload/upload.module	15 Sep 2008 08:16:01 -0000
@@ -420,13 +420,12 @@ function upload_save(&$node) {
     // Create a new revision, or associate a new file needed.
     if (!empty($node->old_vid) || isset($_SESSION['upload_files'][$fid])) {
       db_query("INSERT INTO {upload} (fid, nid, vid, list, description, weight) VALUES (%d, %d, %d, %d, '%s', %d)", $file->fid, $node->nid, $node->vid, $file->list, $file->description, $file->weight);
-      file_set_status($file, FILE_STATUS_PERMANENT);
     }
     // Update existing revision.
     else {
       db_query("UPDATE {upload} SET list = %d, description = '%s', weight = %d WHERE fid = %d AND vid = %d", $file->list, $file->description, $file->weight, $file->fid, $node->vid);
-      file_set_status($file, FILE_STATUS_PERMANENT);
     }
+    file_set_status($file, FILE_STATUS_PERMANENT);
   }
   // Empty the session storage after save. We use this variable to track files
   // that haven't been related to the node yet.
Index: modules/upload/upload.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/upload/upload.test,v
retrieving revision 1.3
diff -u -p -r1.3 upload.test
--- modules/upload/upload.test	6 Jun 2008 10:36:44 -0000	1.3
+++ modules/upload/upload.test	15 Sep 2008 08:16:01 -0000
@@ -106,7 +106,7 @@ class UploadTestCase extends DrupalWebTe
 
     // Attempt to upload .txt file when .test is only extension allowed.
     $this->uploadFile($node, $files[0], FALSE);
-    $this->assertRaw(t('The selected file %name could not be uploaded. Only files with the following extensions are allowed: %files-allowed.', array('%name' => basename($files[0]), '%files-allowed' => $settings['upload_extensions'])), 'File '. $files[0] . ' was not allowed to be uploaded');
+    $this->assertRaw(t('The specified file %name could not be uploaded. Only files with the following extensions are allowed: %files-allowed.', array('%name' => basename($files[0]), '%files-allowed' => $settings['upload_extensions'])), 'File '. $files[0] . ' was not allowed to be uploaded');
 
     // Attempt to upload .test file when .test is only extension allowed.
     $this->uploadFile($node, $files[1]);
@@ -143,7 +143,7 @@ class UploadTestCase extends DrupalWebTe
     $filename = basename($file);
     $filesize = format_size($info['size']);
     $maxsize = format_size(parse_size(($settings['upload_uploadsize'] * 1024) . 'KB')); // Won't parse decimals.
-    $this->assertRaw(t('The selected file %name could not be uploaded. The file is %filesize exceeding the maximum file size of %maxsize.', array('%name' => $filename, '%filesize' => $filesize, '%maxsize' => $maxsize)), t('File upload was blocked since it was larger than maxsize.'));
+    $this->assertRaw(t('The specified file %name could not be uploaded. The file is %filesize exceeding the maximum file size of %maxsize.', array('%name' => $filename, '%filesize' => $filesize, '%maxsize' => $maxsize)), t('File upload was blocked since it was larger than maxsize.'));
   }
 
   function setUploadSettings($settings, $rid = NULL) {
Index: modules/user/user.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/user/user.module,v
retrieving revision 1.919
diff -u -p -r1.919 user.module
--- modules/user/user.module	8 Sep 2008 15:44:57 -0000	1.919
+++ modules/user/user.module	15 Sep 2008 08:16:01 -0000
@@ -414,9 +414,9 @@ function user_validate_picture(&$form, &
     // files table as a temporary file. We'll make a copy and let the garbage
     // collector delete the original upload.
     $info = image_get_info($file->filepath);
-    $destination = variable_get('user_picture_path', 'pictures') . '/picture-' . $form['#uid'] . '.' . $info['extension'];
-    if (file_copy($file, $destination, FILE_EXISTS_REPLACE)) {
-      $form_state['values']['picture'] = $file->filepath;
+    $destination = file_create_path(variable_get('user_picture_path', 'pictures') . '/picture-' . $form['#uid'] . '.' . $info['extension']);
+    if ($filepath = file_copy($file->filepath, $destination, FILE_EXISTS_REPLACE)) {
+      $form_state['values']['picture'] = $filepath;
     }
     else {
       form_set_error('picture_upload', t("Failed to upload the picture image; the %directory directory doesn't exist or is not writable.", array('%directory' => variable_get('user_picture_path', 'pictures'))));
