Index: includes/file.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/file.inc,v
retrieving revision 1.97
diff -u -r1.97 file.inc
--- includes/file.inc	24 Apr 2007 13:53:10 -0000	1.97
+++ includes/file.inc	8 May 2007 16:10:52 -0000
@@ -21,6 +21,15 @@
 define('FILE_EXISTS_ERROR', 2);
 
 /**
+ * A files status can be one of two values: temorary or permanent. When a file
+ * is first uploaded using file_save_upload() it's saved as a temporary file.
+ * When a the file is saved for use by Drupal it's marked as stored. A cron
+ * garbage collection function removes old temporary files.
+ */
+define('FILE_STATUS_TEMPORARY', 0);
+define('FILE_STATUS_PERMANENT', 1);
+
+/**
  * Create the download path to a file.
  *
  * @param $path A string containing the path of the file to generate URL for.
@@ -152,115 +161,6 @@
   return FALSE;
 }
 
-
-/**
- * Check if $source is a valid file upload. If so, move the file to Drupal's tmp dir
- * and return it as an object.
- *
- * The use of SESSION['file_uploads'] should probably be externalized to upload.module
- *
- * @todo Rename file_check_upload to file_prepare upload.
- * @todo Refactor or merge file_save_upload.
- * @todo Extenalize SESSION['file_uploads'] to modules.
- *
- * @param $source An upload source (the name of the upload form item), or a file
- * @return FALSE for an invalid file or upload. A file object for valid uploads/files.
- *
- */
-
-function file_check_upload($source = 'upload') {
-  // Cache for uploaded files. Since the data in _FILES is modified
-  // by this function, we cache the result.
-  static $upload_cache;
-
-  // Test source to see if it is an object.
-  if (is_object($source)) {
-
-    // Validate the file path if an object was passed in instead of
-    // an upload key.
-    if (is_file($source->filepath)) {
-      return $source;
-    }
-    else {
-      return FALSE;
-    }
-  }
-
-  // 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];
-  }
-
-  // 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.
-    switch ($_FILES["files"]["error"][$source]) {
-
-      // @see http://php.net/manual/en/features.file-upload.errors.php
-      case UPLOAD_ERR_OK:
-        break;
-
-      case UPLOAD_ERR_INI_SIZE:
-      case UPLOAD_ERR_FORM_SIZE:
-        drupal_set_message(t('The file %file could not be saved, because it exceeds the maximum allowed size for uploads.', array('%file' => $source)), 'error');
-        return 0;
-
-      case UPLOAD_ERR_PARTIAL:
-      case UPLOAD_ERR_NO_FILE:
-        drupal_set_message(t('The file %file could not be saved, because the upload did not complete.', array('%file' => $source)), 'error');
-        return 0;
-
-      // Unknown error
-      default:
-        drupal_set_message(t('The file %file could not be saved. An unknown error has occurred.', array('%file' => $source)), 'error');
-        return 0;
-    }
-
-    // Begin building file object.
-    $file = new stdClass();
-    $file->filename = trim(basename($_FILES["files"]["name"][$source]), '.');
-
-    // Create temporary name/path for newly uploaded files.
-    $file->filepath = tempnam(file_directory_temp(), 'tmp_');
-
-    $file->filemime = $_FILES["files"]["type"][$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')) {
-      $file->filemime = 'text/plain';
-      $file->filepath .= '.txt';
-      $file->filename .= '.txt';
-    }
-
-    // Move uploaded files from php's upload_tmp_dir to Drupal's file temp.
-    // This overcomes open_basedir restrictions for future file operations.
-    if (!move_uploaded_file($_FILES["files"]["tmp_name"][$source], $file->filepath)) {
-      drupal_set_message(t('File upload error. Could not move uploaded file.'));
-      watchdog('file', 'Upload Error. Could not move uploaded file (%file) to destination (%destination).', array('%file' => $_FILES["files"]["tmp_name"][$source], '%destination' => $file->filepath));
-      return FALSE;
-    }
-
-    $file->filesize = $_FILES["files"]["size"][$source];
-    $file->source = $source;
-
-    // Add processed file to the cache.
-    $upload_cache[$source] = $file;
-    return $file;
-  }
-
-  else {
-    // In case of previews return previous file object.
-    if (isset($_SESSION['file_uploads']) && file_exists($_SESSION['file_uploads'][$source]->filepath)) {
-      return $_SESSION['file_uploads'][$source];
-    }
-  }
-  // If nothing was done, return FALSE.
-  return FALSE;
-}
-
 /**
  * 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
@@ -347,29 +247,9 @@
   // 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 (file_exists($dest)) {
-      switch ($replace) {
-        case FILE_EXISTS_RENAME:
-          // Destination file already exists and we can't replace is so we try and
-          // and find a new filename.
-          if ($pos = strrpos($basename, '.')) {
-            $name = substr($basename, 0, $pos);
-            $ext = substr($basename, $pos);
-          }
-          else {
-            $name = $basename;
-          }
-
-          $counter = 0;
-          do {
-            $dest = $directory .'/'. $name .'_'. $counter++ . $ext;
-          } while (file_exists($dest));
-          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' => $source)), 'error');
-          return 0;
-      }
+    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)) {
@@ -395,6 +275,36 @@
 }
 
 /**
+ * Determines the destination path for a file depending on how replacement of
+ * existing files should be handled.
+ *
+ * @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_RENAME - Append _{incrementing number} until the filename is
+ *     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.
+ */
+function file_destination($destination, $replace) {
+  if (file_exists($destination)) {
+    switch ($replace) {
+      case FILE_EXISTS_RENAME:
+        $basename = basename($destination);
+        $directory = dirname($destination);
+        $destination = file_create_filename($basename, $directory);
+        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' => $source)), 'error');
+        return FALSE;
+    }
+  }
+  return $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.
@@ -428,6 +338,67 @@
 }
 
 /**
+ * Munge the filename as needed for security purposes.
+ *
+ * @param $filename The name of a file to modify.
+ * @param $extensions A space separated list of valid extensions. If this is
+ *   blank, we'll use the admin-defined defaults for the user role from
+ *   upload_extensions_$rid.
+ * @param $alerts Whether alerts (watchdog, drupal_set_message()) should be
+ *   displayed.
+ * @return $filename The potentially modified $filename.
+ */
+function file_munge_filename($filename, $extensions = NULL, $alerts = TRUE) {
+  global $user;
+
+  $original = $filename;
+
+  // Allow potentially insecure uploads for very savvy users and admin
+  if (!variable_get('allow_insecure_uploads', 0)) {
+
+    if (!isset($extensions)) {
+      $extensions = '';
+      foreach ($user->roles as $rid => $name) {
+        $extensions .= ' '. variable_get("upload_extensions_$rid",
+        variable_get('upload_extensions_default', 'jpg jpeg gif png txt html doc xls pdf ppt pps odt ods odp'));
+      }
+    }
+
+    $whitelist = array_unique(explode(' ', trim($extensions)));
+
+    $filename_parts = explode('.', $filename);
+
+    $new_filename = array_shift($filename_parts); // Remove file basename.
+    $final_extension = array_pop($filename_parts); // Remove final extension.
+
+    foreach ($filename_parts as $filename_part) {
+      $new_filename .= ".$filename_part";
+      if (!in_array($filename_part, $whitelist) && preg_match("/^[a-zA-Z]{2,5}\d?$/", $filename_part)) {
+        $new_filename .= '_';
+      }
+    }
+    $filename = "$new_filename.$final_extension";
+  }
+
+  if ($alerts && $original != $filename) {
+    $message = t('Your filename has been renamed to conform to site policy.');
+    drupal_set_message($message);
+  }
+
+  return $filename;
+}
+
+/**
+ * Undo the effect of upload_munge_filename().
+ *
+ * @param $filename string filename
+ * @return string
+ */
+function file_unmunge_filename($filename) {
+  return str_replace('_.', '.', $filename);
+}
+
+/**
  * Create a full file path from a directory and filename. If a file with the
  * specified name already exists, an alternative will be used.
  *
@@ -461,7 +432,7 @@
  * Delete a file.
  *
  * @param $path A string containing a file path.
- * @return True for success, FALSE for failure.
+ * @return TRUE for success, FALSE for failure.
  */
 function file_delete($path) {
   if (is_file($path)) {
@@ -470,6 +441,82 @@
 }
 
 /**
+ * Get the total amount of disk space used by a single user's files, or the
+ * filesystem as a whole.
+ *
+ * @param $uid An optional, user id if you'd like to know how much disk space
+ *   is being used by a user.
+ */
+function file_space_used($uid = FALSE) {
+  if ($uid) {
+    return db_result(db_query('SELECT SUM(filesize) FROM {files} WHERE uid = %d', $uid));
+  }
+  return db_result(db_query('SELECT SUM(filesize) FROM {files}'));
+}
+
+
+/**
+ * Validate a file being added to the Drupal files table meets the restrictions
+ * on file names, sizes, extensions, as well as a user's disk quota. 
+ *
+ * @param $file
+ *   a Drupal file object.
+ * @return bool.
+ */
+function file_validate($file) {
+  // Validate upload limits.
+  global $user;
+  $valid = TRUE;
+
+  // Bypass validation for uid  = 1.
+  if ($user->uid != 1) {
+
+    // Validate file against all users roles.
+    // Only denies an upload when all roles prevent it.
+    $total_usersize = file_space_used($user->uid) + $file->filesize;
+    $error = array();
+    foreach ($user->roles as $rid => $name) {
+      $extensions = variable_get("upload_extensions_$rid", variable_get('upload_extensions_default', 'jpg jpeg gif png txt html doc xls pdf ppt pps odt ods odp'));
+      $uploadsize = variable_get("upload_uploadsize_$rid", variable_get('upload_uploadsize_default', 1)) * 1024 * 1024;
+      $usersize = variable_get("upload_usersize_$rid", variable_get('upload_usersize_default', 1)) * 1024 * 1024;
+
+      $regex = '/\.('. ereg_replace(' +', '|', preg_quote($extensions)) .')$/i';
+
+      if (!preg_match($regex, $file->filename)) {
+        $error['extension']++;
+      }
+
+      if ($uploadsize && $file->filesize > $uploadsize) {
+        $error['uploadsize']++;
+      }
+
+      if ($usersize && $total_usersize + $file->filesize > $usersize) {
+        $error['usersize']++;
+      }
+    }
+
+    $user_roles = count($user->roles);
+    if ($error['extension'] == $user_roles) {
+      form_set_error('upload', t('The selected file %name can not be attached to this post, because it is only possible to attach files with the following extensions: %files-allowed.', array('%name' => $file->filename, '%files-allowed' => $extensions)));
+      $valid = FALSE;
+    }
+    elseif ($error['uploadsize'] == $user_roles) {
+      form_set_error('upload', t('The selected file %name can not be attached to this post, because it exceeded the maximum filesize of %maxsize.', array('%name' => $file->filename, '%maxsize' => format_size($uploadsize))));
+      $valid = FALSE;
+    }
+    elseif ($error['usersize'] == $user_roles) {
+      form_set_error('upload', t('The selected file %name can not be attached to this post, because the disk quota of %quota has been reached.', array('%name' => $file->filename, '%quota' => format_size($usersize))));
+      $valid = FALSE;
+    }
+    elseif (strlen($file->filename) > 255) {
+      form_set_error('upload', t('The selected file %name can not be attached to this post, because the filename is too long.', array('%name' => $file->filename)));
+      $valid = FALSE;
+    }
+  }
+  return $valid;
+}
+
+/**
  * Saves a file upload to a new location. The source file is validated as a
  * proper upload and handled as such.
  *
@@ -483,32 +530,88 @@
  * @return An object containing file info or 0 in case of error.
  */
 function file_save_upload($source, $dest = FALSE, $replace = FILE_EXISTS_RENAME) {
-  // Make sure $source exists && is valid.
-  if ($file = file_check_upload($source)) {
+  global $user;
+
+  static $upload_cache;
+
+  // 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];
+  }
+
+
+  // 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.
+    switch ($_FILES['files']['error'][$source]) {
+      // @see http://php.net/manual/en/features.file-upload.errors.php
+      case UPLOAD_ERR_OK:
+        break;
+
+      case UPLOAD_ERR_INI_SIZE:
+      case UPLOAD_ERR_FORM_SIZE:
+        drupal_set_message(t('The file %file could not be saved, because it exceeds the maximum allowed size for uploads.', array('%file' => $source)), 'error');
+        return 0;
 
-    // This should be refactored, file_check_upload has already
-    // moved the file to the temporary folder.
+      case UPLOAD_ERR_PARTIAL:
+      case UPLOAD_ERR_NO_FILE:
+        drupal_set_message(t('The file %file could not be saved, because the upload did not complete.', array('%file' => $source)), 'error');
+        return 0;
+
+        // Unknown error
+      default:
+        drupal_set_message(t('The file %file could not be saved. An unknown error has occurred.', array('%file' => $source)),'error');
+        return 0;
+    }
+
+    // Begin building file object.
+    $file = new stdClass();
+    $file->filename = file_munge_filename(trim(basename($_FILES['files']['name'][$source]), '.'));
+
+    // Create temporary name/path for newly uploaded files.
     if (!$dest) {
-      $dest = file_directory_temp();
-      $temporary = 1;
-      if (is_file($file->filepath)) {
-        // If this file was uploaded by this user before replace the temporary copy.
-        $replace = FILE_EXISTS_REPLACE;
-      }
+      $dest = file_destination(file_create_path($file->filename), FILE_EXISTS_RENAME);
     }
+    $file->filepath = $dest;
+    $file->filemime = $_FILES['files']['type'][$source];
 
-    unset($_SESSION['file_uploads'][is_object($source) ? $source->source : $source]);
-    if (file_move($file, $dest, $replace)) {
-      if ($temporary) {
-        $_SESSION['file_uploads'][is_object($source) ? $source->source : $source] = $file;
-      }
-      return $file;
+    // 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')) {
+      $file->filemime = 'text/plain';
+      $file->filepath .= '.txt';
+      $file->filename .= '.txt';
     }
-    return 0;
+
+    $file->filesize = $_FILES['files']['size'][$source];
+    $file->source = $source;
+
+    if (!file_validate($file)) {
+      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.
+    if (!move_uploaded_file($_FILES['files']['tmp_name'][$source], $file->filepath)) {
+      drupal_set_message(t('File upload error. Could not move uploaded file.'));
+      watchdog('file', t('Upload Error. Could not move uploaded file (%file) to destination (%destination).', array('%file' => $file->filename, '%destination', $file->filepath)));
+      return FALSE;
+    }
+
+    // If we made it this far it's safe to record this file in the database.
+    $file->fid = db_next_id('fid');
+    db_query("INSERT INTO {files} (fid, uid, filename, filepath, filemime, filesize, status, timestamp) VALUES (%d, %d, '%s', '%s', '%s', %d, %d, %d)", $file->fid, $user->uid, $file->filename, $file->filepath, $file->filemime, $file->filesize, FILE_STATUS_TEMPORARY, time());
+
+    // Add file to the cache.
+    $upload_cache[$source] = $file;
+    return $file;
   }
-  return 0;
+  return FALSE;
 }
 
+
 /**
  * Save a string to the specified destination.
  *
@@ -539,6 +642,22 @@
 }
 
 /**
+ * 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.
+ */
+
+function file_set_status(&$file, $status) {
+  if (db_query('UPDATE {files} SET status = %d WHERE fid = %d', $status, $file->fid)) {
+    $file->status = $status;
+    return TRUE;
+  }
+  return FALSE;
+}
+
+/**
  * Transfer file using http to client. Pipes a file through Drupal to the
  * client.
  *
@@ -592,10 +711,10 @@
   if (file_exists(file_create_path($filepath))) {
     $headers = module_invoke_all('file_download', $filepath);
     if (in_array(-1, $headers)) {
-        return drupal_access_denied();
+      return drupal_access_denied();
     }
     if (count($headers)) {
-        file_transfer($filepath, $headers);
+      file_transfer($filepath, $headers);
     }
   }
   return drupal_not_found();
Index: includes/locale.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/locale.inc,v
retrieving revision 1.122
diff -u -r1.122 locale.inc
--- includes/locale.inc	8 May 2007 09:48:14 -0000	1.122
+++ includes/locale.inc	8 May 2007 15:50:46 -0000
@@ -590,7 +590,7 @@
  */
 function locale_translate_import_form_submit($form_id, $form_values) {
   // Ensure we have the file uploaded
-  if ($file = file_check_upload('file')) {
+  if ($file = file_save_upload('file')) {
 
     // Add language, if not yet supported
     $languages = language_list('language', TRUE);
Index: modules/system/system.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.install,v
retrieving revision 1.102
diff -u -r1.102 system.install
--- modules/system/system.install	6 May 2007 05:50:43 -0000	1.102
+++ modules/system/system.install	7 May 2007 22:23:50 -0000
@@ -287,22 +287,28 @@
 
       db_query("CREATE TABLE {files} (
         fid int unsigned NOT NULL default 0,
-        nid int unsigned NOT NULL default 0,
+        uid int unsigned NOT NULL default 0,
         filename varchar(255) NOT NULL default '',
         filepath varchar(255) NOT NULL default '',
         filemime varchar(255) NOT NULL default '',
         filesize int unsigned NOT NULL default 0,
+        status int NOT NULL default 0,
+        timestamp int unsigned NOT NULL default '0', 
         PRIMARY KEY (fid),
-        KEY nid (nid)
+        KEY uid (uid),
+        KEY status (status),
+        KEY timestamp (timestamp)
       ) /*!40100 DEFAULT CHARACTER SET UTF8 */ ");
 
       db_query("CREATE TABLE {file_revisions} (
         fid int unsigned NOT NULL default 0,
+        nid int unsigned NOT NULL default 0,
         vid int unsigned NOT NULL default 0,
         description varchar(255) NOT NULL default '',
         list tinyint unsigned NOT NULL default 0,
         PRIMARY KEY (fid, vid),
-        KEY (vid)
+        KEY (vid),
+        KEY (nid)
       ) /*!40100 DEFAULT CHARACTER SET UTF8 */ ");
 
       db_query("CREATE TABLE {filter_formats} (
@@ -772,23 +778,29 @@
 
       db_query("CREATE TABLE {files} (
         fid serial CHECK (fid >= 0),
-        nid int_unsigned NOT NULL default 0,
+        uid int_unsigned NOT NULL default 0,
         filename varchar(255) NOT NULL default '',
         filepath varchar(255) NOT NULL default '',
         filemime varchar(255) NOT NULL default '',
-        filesize int_unsigned NOT NULL default 0,
+        filesize int_unsigned  NOT NULL default 0,
+        status int NOT NULL default 0,
+        timestamp int_unsigned NOT NULL default 0, 
         PRIMARY KEY (fid)
       )");
-      db_query("CREATE INDEX {files}_nid_idx ON {files} (nid)");
+      db_query("CREATE INDEX {files}_uid_idx ON {files} (uid)");
+      db_query("CREATE INDEX {files}_status_idx ON {files} (status)");
+      db_query("CREATE INDEX {files}_timestamp_idx ON {files} (timestamp)");
 
       db_query("CREATE TABLE {file_revisions} (
         fid int_unsigned NOT NULL default 0,
+        nid int_unsigned NOT NULL default 0,
         vid int_unsigned NOT NULL default 0,
         description varchar(255) NOT NULL default '',
         list smallint_unsigned NOT NULL default 0,
         PRIMARY KEY (fid, vid)
       )");
       db_query("CREATE INDEX {file_revisions}_vid_idx ON {file_revisions} (vid)");
+      db_query("CREATE INDEX {file_revisions}_nid_idx ON {file_revisions} (nid)");
 
       db_query("CREATE TABLE {filter_formats} (
         format serial,
@@ -3875,6 +3887,34 @@
 
 
 /**
+ * Update files tables to associate files to a uid by default instead of a nid.
+ */
+function system_update_6013() {
+  $ret = array();
+  switch ($GLOBALS['db_type']) {
+    case 'mysql':
+    case 'mysqli':
+      // Add nid to file_revisions table since it is basically becoming the node_files table.
+      $ret[] = update_sql('ALTER TABLE {file_revisions} ADD COLUMN nid int unsigned NOT NULL default 0 AFTER fid');
+      $ret[] = update_sql('UPDATE {file_revisions} fr JOIN {files} f ON fr.fid = f.fid SET fr.nid = f.nid');
+
+      // Change owernship of files to users.
+      $ret[] = update_sql('ALTER TABLE {files} CHANGE COLUMN nid uid int unsigned NOT NULL default 0');
+      $ret[] = update_sql('UPDATE {files} f JOIN {node} n ON f.uid = n.nid SET f.uid = n.uid');
+      break;
+
+    case 'pgsql':
+      // @todo:
+      break;
+  }
+  $ret = update_sql("CREATE INDEX {files}_uid_idx ON {files} (uid)");
+  $ret = update_sql("CREATE INDEX {files}_status_idx ON {files} (status)");
+  $ret = update_sql("CREATE INDEX {files}_timestamp_idx ON {files} (timestamp)");
+  return $ret;
+}
+
+
+/**
  * @} End of "defgroup updates-5.x-to-6.x"
  * The next series of updates should start at 7000.
  */
Index: modules/system/system.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.module,v
retrieving revision 1.474
diff -u -r1.474 system.module
--- modules/system/system.module	6 May 2007 05:47:52 -0000	1.474
+++ modules/system/system.module	8 May 2007 16:14:57 -0000
@@ -13,6 +13,9 @@
 define('DRUPAL_MINIMUM_PGSQL',  '7.4');   // If using PostgreSQL
 define('DRUPAL_MINIMUM_APACHE', '1.3');   // If using Apache
 
+// Maximum age of temporary files in seconds.
+define('DRUPAL_MAXIMUM_TEMP_FILE_AGE', 1440);
+
 /**
  * Implementation of hook_help().
  */
@@ -83,7 +86,7 @@
  * Implementation of hook_perm().
  */
 function system_perm() {
-  return array('administer site configuration', 'access administration pages', 'select different theme');
+  return array('administer site configuration', 'access administration pages', 'select different theme', 'administer files');
 }
 
 /**
@@ -783,9 +786,123 @@
     '#description' => t('If you want any sort of access control on the downloading of files, this needs to be set to <em>private</em>. You can change this at any time, however all download URLs will change and there may be unexpected problems so it is not recommended.')
   );
 
+  // Upload Limits
+  // @todo: change namespace to file_system instead of upload.
+  $upload_extensions_default = variable_get('upload_extensions_default', 'jpg jpeg gif png txt html doc xls pdf ppt pps odt ods odp');
+  $upload_uploadsize_default = variable_get('upload_uploadsize_default', 1);
+  $upload_usersize_default = variable_get('upload_usersize_default', 1);
+
+  $form['file_system_limits'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Filesystem Limits'),
+    '#collapsible' => TRUE,
+  );
+  $form['file_system_limits']['upload_extensions_default'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Default permitted file extensions'),
+    '#default_value' => $upload_extensions_default,
+    '#maxlength' => 255,
+    '#description' => t('Default extensions that users can upload. Separate extensions with a space and do not include the leading dot.'),
+  );
+  $form['file_system_limits']['upload_uploadsize_default'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Default maximum file size per upload'),
+    '#default_value' => $upload_uploadsize_default,
+    '#size' => 5,
+    '#maxlength' => 5,
+    '#description' => t('The default maximum file size a user can upload.'),
+    '#field_suffix' => t('MB')
+  );
+  $form['file_system_limits']['upload_usersize_default'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Default total file size per user'),
+    '#default_value' => $upload_usersize_default,
+    '#size' => 5,
+    '#maxlength' => 5,
+    '#description' => t('The default maximum size of all files a user can have on the site.'),
+    '#field_suffix' => t('MB')
+  );
+
+  $form['file_system_limits']['upload_max_size'] = array('#value' => '<p>'. t('Your PHP settings limit the maximum file size per upload to %size.', array('%size' => format_size(file_upload_max_size()))).'</p>');
+
+  $roles = user_roles(0, 'upload files');
+  $form['roles'] = array('#type' => 'value', '#value' => $roles);
+
+  foreach ($roles as $rid => $role) {
+    $form['settings_role_'. $rid] = array(
+      '#type' => 'fieldset',
+      '#title' => t('Settings for @role', array('@role' => $role)),
+      '#collapsible' => TRUE,
+      '#collapsed' => TRUE,
+    );
+    $form['settings_role_'. $rid]['upload_extensions_'. $rid] = array(
+      '#type' => 'textfield',
+      '#title' => t('Permitted file extensions'),
+      '#default_value' => variable_get('upload_extensions_'. $rid, $upload_extensions_default),
+      '#maxlength' => 255,
+      '#description' => t('Extensions that users in this role can upload. Separate extensions with a space and do not include the leading dot.'),
+    );
+    $form['settings_role_'. $rid]['upload_uploadsize_'. $rid] = array(
+      '#type' => 'textfield',
+      '#title' => t('Maximum file size per upload'),
+      '#default_value' => variable_get('upload_uploadsize_'. $rid, $upload_uploadsize_default),
+      '#size' => 5,
+      '#maxlength' => 5,
+      '#description' => t('The maximum size of a file a user can upload (in megabytes).'),
+    );
+    $form['settings_role_'. $rid]['upload_usersize_'. $rid] = array(
+      '#type' => 'textfield',
+      '#title' => t('Total file size per user'),
+      '#default_value' => variable_get('upload_usersize_'. $rid, $upload_usersize_default),
+      '#size' => 5,
+      '#maxlength' => 5,
+      '#description' => t('The maximum size of all files a user can have on the site (in megabytes).'),
+    );
+  }
   return system_settings_form($form);
 }
 
+function system_file_system_settings_validate($form_id, $form_values) {
+  $default_uploadsize = $form_values['upload_uploadsize_default'];
+  $default_usersize = $form_values['upload_usersize_default'];
+
+  $exceed_max_msg = t('Your PHP settings limit the maximum file size per upload to %size MB.', array('%size' => file_upload_max_size())).'<br/>';
+  $more_info = t("Depending on your sever environment, these settings may be changed in the system-wide php.ini file, a php.ini file in your Drupal root directory, in your Drupal site's settings.php file, or in the .htaccess file in your Drupal root directory.");
+
+  if (!is_numeric($default_uploadsize) || ($default_uploadsize <= 0)) {
+    form_set_error('upload_uploadsize_default', t('The %role file size limit must be a number and greater than zero.', array('%role' => t('default'))));
+  }
+  if (!is_numeric($default_usersize) || ($default_usersize <= 0)) {
+    form_set_error('upload_usersize_default', t('The %role file size limit must be a number and greater than zero.', array('%role' => t('default'))));
+  }
+  if ($default_uploadsize > file_upload_max_size()) {
+    form_set_error('upload_uploadsize_default', $exceed_max_msg . $more_info);
+    $more_info = '';
+  }
+  if ($default_uploadsize > $default_usersize) {
+    form_set_error('upload_uploadsize_default', t('The %role maximum file size per upload is greater than the total file size allowed per user', array('%role' => t('default'))));
+  }
+
+  foreach ($form_values['roles'] as $rid => $role) {
+    $uploadsize = $form_values['upload_uploadsize_'. $rid];
+    $usersize = $form_values['upload_usersize_'. $rid];
+
+    if (!is_numeric($uploadsize) || ($uploadsize <= 0)) {
+      form_set_error('upload_uploadsize_'. $rid, t('The %role file size limit must be a number and greater than zero.', array('%role' => $role)));
+    }
+    if (!is_numeric($usersize) || ($usersize <= 0)) {
+      form_set_error('upload_usersize_'. $rid, t('The %role file size limit must be a number and greater than zero.', array('%role' => $role)));
+    }
+    if ($uploadsize > file_upload_max_size()) {
+      form_set_error('upload_uploadsize_'. $rid, $exceed_max_msg . $more_info);
+      $more_info = '';
+    }
+    if ($uploadsize > $usersize) {
+      form_set_error('upload_uploadsize_'. $rid, t('The %role maximum file size per upload is greater than the total file size allowed per user', array('%role' => $role)));
+    }
+  }
+}
+
 function system_image_toolkit_settings() {
   $toolkits_available = image_get_available_toolkits();
   if (count($toolkits_available) > 1) {
@@ -2054,12 +2171,15 @@
   $form['var'] = array('#type' => 'hidden', '#value' => $var);
 
   // Check for a new uploaded logo, and use that instead.
-  if ($file = file_check_upload('logo_upload')) {
+  if ($file = file_save_upload('logo_upload')) {
     if ($info = image_get_info($file->filepath)) {
       $parts = pathinfo($file->filename);
       $filename = ($key) ? str_replace('/', '_', $key) .'_logo.'. $parts['extension'] : 'logo.'. $parts['extension'];
 
-      if ($file = file_save_upload('logo_upload', $filename, 1)) {
+      // Since the status is not set, file system garbage
+      // collection will clean up the database, unless another
+      // module uses the file and sets the status.
+      if (file_copy($file, $filename, FILE_EXISTS_REPLACE)) {
         $_POST['default_logo'] = 0;
         $_POST['logo_path'] = $file->filepath;
         $_POST['toggle_logo'] = 1;
@@ -2071,11 +2191,14 @@
   }
 
   // Check for a new uploaded favicon, and use that instead.
-  if ($file = file_check_upload('favicon_upload')) {
+  if ($file = file_save_upload('favicon_upload')) {
     $parts = pathinfo($file->filename);
     $filename = ($key) ? str_replace('/', '_', $key) .'_favicon.'. $parts['extension'] : 'favicon.'. $parts['extension'];
 
-    if ($file = file_save_upload('favicon_upload', $filename, 1)) {
+    // Since the status is not set, file system garbage
+    // collection will clean up the database, unless another
+    // module uses the file and sets the status.
+    if (file_copy($file, $filename)) {
       $_POST['default_favicon'] = 0;
       $_POST['favicon_path'] = $file->filepath;
       $_POST['toggle_favicon'] = 1;
@@ -2516,13 +2639,27 @@
 /**
  * Implementation of hook_cron().
  *
- * Remove older rows from flood table
+ * Remove older rows from flood and batch table. Remove old temporary files.
  */
 function system_cron() {
-  // Cleanup the flood
+  // Cleanup the flood.
   db_query('DELETE FROM {flood} WHERE timestamp < %d', time() - 3600);
-  // Cleanup the batch table
+  // Cleanup the batch table.
   db_query('DELETE FROM {batch} WHERE timestamp < %d', time() - 864000);
+
+  // Remove temporary files that are older than DRUPAL_MAXIMUM_TEMP_FILE_AGE.
+  $result = db_query('SELECT * FROM {files} WHERE status = %s and timestamp < %d', FILE_STATUS_TEMPORARY, time() - DRUPAL_MAXIMUM_TEMP_FILE_AGE);
+  while ($file = db_fetch_object($result)) {
+    if (file_exists($file->filepath)) {
+      // If files that exist cannot be deleted, continue so the database remains
+      // consistant.
+      if (!file_delete($file->filepath)) {
+        watchdog('file system', t('Could not delete temporary file "%path" during garbage collection', array('%path' => $file->filepath)), 'error');
+        continue;
+      }
+    }
+    db_query('DELETE FROM {files} WHERE fid = %d', $file->fid);
+  }
 }
 
 /**
Index: modules/upload/upload.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/upload/upload.module,v
retrieving revision 1.159
diff -u -r1.159 upload.module
--- modules/upload/upload.module	30 Apr 2007 17:03:29 -0000	1.159
+++ modules/upload/upload.module	8 May 2007 16:07:10 -0000
@@ -94,28 +94,9 @@
 }
 
 function upload_menu_alter(&$items) {
-  $items['system/files']['page callback'] = 'upload_download';
   $items['system/files']['access arguments'] = array('view uploaded files');
 }
 
-function upload_init() {
-  if (arg(0) == 'system' && arg(1) == 'files' && isset($_SESSION['file_previews'])) {
-    $item = menu_get_item('system/files');
-    foreach ($_SESSION['file_previews'] as $fid => $file) {
-      $filename = file_create_filename($file->filename, file_create_path());
-      if (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) ==  FILE_DOWNLOADS_PRIVATE) {
-        // strip file_directory_path() from filename. @see file_create_url
-        if (strpos($filename, file_directory_path()) !== FALSE) {
-          $filename = trim(substr($filename, strlen(file_directory_path())), '\\/');
-        }
-        $filename = 'system/files/'. $filename;
-      }
-      $_SESSION['file_previews'][$fid]->_filename = $filename;
-      menu_set_item($filename, $item);
-    }
-  }
-}
-
 /**
  * Form API callback to validate the upload settings form.
  */
@@ -125,55 +106,12 @@
       form_set_error('upload_max_resolution', t('The maximum allowed image size expressed as WIDTHxHEIGHT (e.g. 640x480). Set to 0 for no restriction.'));
     }
   }
-
-  $default_uploadsize = $form_values['upload_uploadsize_default'];
-  $default_usersize = $form_values['upload_usersize_default'];
-
-  $exceed_max_msg = t('Your PHP settings limit the maximum file size per upload to %size MB.', array('%size' => file_upload_max_size())) .'<br/>';
-  $more_info = t("Depending on your sever environment, these settings may be changed in the system-wide php.ini file, a php.ini file in your Drupal root directory, in your Drupal site's settings.php file, or in the .htaccess file in your Drupal root directory.");
-
-  if (!is_numeric($default_uploadsize) || ($default_uploadsize <= 0)) {
-    form_set_error('upload_uploadsize_default', t('The %role file size limit must be a number and greater than zero.', array('%role' => t('default'))));
-  }
-  if (!is_numeric($default_usersize) || ($default_usersize <= 0)) {
-    form_set_error('upload_usersize_default', t('The %role file size limit must be a number and greater than zero.', array('%role' => t('default'))));
-  }
-  if ($default_uploadsize > file_upload_max_size()) {
-   form_set_error('upload_uploadsize_default', $exceed_max_msg . $more_info);
-   $more_info = '';
-  }
-  if ($default_uploadsize > $default_usersize) {
-   form_set_error('upload_uploadsize_default', t('The %role maximum file size per upload is greater than the total file size allowed per user', array('%role' => t('default'))));
-  }
-
-  foreach ($form_values['roles'] as $rid => $role) {
-    $uploadsize = $form_values['upload_uploadsize_'. $rid];
-    $usersize = $form_values['upload_usersize_'. $rid];
-
-    if (!is_numeric($uploadsize) || ($uploadsize <= 0)) {
-      form_set_error('upload_uploadsize_'. $rid, t('The %role file size limit must be a number and greater than zero.', array('%role' => $role)));
-    }
-    if (!is_numeric($usersize) || ($usersize <= 0)) {
-      form_set_error('upload_usersize_'. $rid, t('The %role file size limit must be a number and greater than zero.', array('%role' => $role)));
-    }
-    if ($uploadsize > file_upload_max_size()) {
-     form_set_error('upload_uploadsize_'. $rid, $exceed_max_msg . $more_info);
-     $more_info = '';
-    }
-    if ($uploadsize > $usersize) {
-     form_set_error('upload_uploadsize_'. $rid, t('The %role maximum file size per upload is greater than the total file size allowed per user', array('%role' => $role)));
-    }
-  }
 }
 
 /**
  * Menu callback for the upload settings form.
  */
 function upload_admin_settings() {
-  $upload_extensions_default = variable_get('upload_extensions_default', 'jpg jpeg gif png txt html doc xls pdf ppt pps odt ods odp');
-  $upload_uploadsize_default = variable_get('upload_uploadsize_default', 1);
-  $upload_usersize_default = variable_get('upload_usersize_default', 1);
-
   $form['settings_general'] = array(
     '#type' => 'fieldset',
     '#title' => t('General settings'),
@@ -195,156 +133,64 @@
     '#options' => array(0 => t('No'), 1 => t('Yes')),
     '#description' => t('Set whether files attached to nodes are listed or not in the node view by default.'),
   );
-
-  $form['settings_general']['upload_extensions_default'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Default permitted file extensions'),
-    '#default_value' => $upload_extensions_default,
-    '#maxlength' => 255,
-    '#description' => t('Default extensions that users can upload. Separate extensions with a space and do not include the leading dot.'),
-  );
-  $form['settings_general']['upload_uploadsize_default'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Default maximum file size per upload'),
-    '#default_value' => $upload_uploadsize_default,
-    '#size' => 5,
-    '#maxlength' => 5,
-    '#description' => t('The default maximum file size a user can upload.'),
-    '#field_suffix' => t('MB')
-  );
-  $form['settings_general']['upload_usersize_default'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Default total file size per user'),
-    '#default_value' => $upload_usersize_default,
-    '#size' => 5,
-    '#maxlength' => 5,
-    '#description' => t('The default maximum size of all files a user can have on the site.'),
-    '#field_suffix' => t('MB')
-  );
-
-  $form['settings_general']['upload_max_size'] = array('#value' => '<p>'. t('Your PHP settings limit the maximum file size per upload to %size.', array('%size' => format_size(file_upload_max_size()))) .'</p>');
-
-  $roles = user_roles(0, 'upload files');
-  $form['roles'] = array('#type' => 'value', '#value' => $roles);
-
-  foreach ($roles as $rid => $role) {
-    $form['settings_role_'. $rid] = array(
-      '#type' => 'fieldset',
-      '#title' => t('Settings for @role', array('@role' => $role)),
-      '#collapsible' => TRUE,
-      '#collapsed' => TRUE,
-    );
-    $form['settings_role_'. $rid]['upload_extensions_'. $rid] = array(
-      '#type' => 'textfield',
-      '#title' => t('Permitted file extensions'),
-      '#default_value' => variable_get('upload_extensions_'. $rid, $upload_extensions_default),
-      '#maxlength' => 255,
-      '#description' => t('Extensions that users in this role can upload. Separate extensions with a space and do not include the leading dot.'),
-    );
-    $form['settings_role_'. $rid]['upload_uploadsize_'. $rid] = array(
-      '#type' => 'textfield',
-      '#title' => t('Maximum file size per upload'),
-      '#default_value' => variable_get('upload_uploadsize_'. $rid, $upload_uploadsize_default),
-      '#size' => 5,
-      '#maxlength' => 5,
-      '#description' => t('The maximum size of a file a user can upload (in megabytes).'),
-    );
-    $form['settings_role_'. $rid]['upload_usersize_'. $rid] = array(
-      '#type' => 'textfield',
-      '#title' => t('Total file size per user'),
-      '#default_value' => variable_get('upload_usersize_'. $rid, $upload_usersize_default),
-      '#size' => 5,
-      '#maxlength' => 5,
-      '#description' => t('The maximum size of all files a user can have on the site (in megabytes).'),
-    );
-  }
-
   return system_settings_form($form);
 }
 
-function upload_download() {
-  foreach ($_SESSION['file_previews'] as $file) {
-    if ($file->_filename == $_GET['q']) {
-      file_transfer($file->filepath, array('Content-Type: '. mime_header_encode($file->filemime), 'Content-Length: '. $file->filesize));
-    }
-  }
-}
-
+/**
+ * Implementation of hook_file_download().
+ */
 function upload_file_download($file) {
+  if (!user_access('view uploaded files')) {
+    return -1;
+  }
   $file = file_create_path($file);
   $result = db_query("SELECT f.* FROM {files} f WHERE filepath = '%s'", $file);
   if ($file = db_fetch_object($result)) {
-    if (user_access('view uploaded files')) {
-      $node = node_load($file->nid);
-      if (node_access('view', $node)) {
-        $type = mime_header_encode($file->filemime);
-        return array(
-          'Content-Type: '. $type,
-          'Content-Length: '. $file->filesize,
-        );
-      }
-      else {
-        return -1;
-      }
-    }
-    else {
-      return -1;
-    }
+    return array(
+      'Content-Type: '. $file->filemime,
+      'Content-Length: '. $file->filesize,
+    );
   }
 }
 
 /**
- * Save new uploads and attach them to the node object.
- * append file_previews to the node object as well.
+ * Save new uploads and store them in the session to be associated to the node
+ * on upload_save.
+ *
+ * @param $node 
+ *   A node object to associate with uploaded files.
  */
 function _upload_prepare(&$node) {
+  global $user;
 
-  // Clean up old file previews if a post didn't get the user to this page.
-  // i.e. the user left the edit page, because they didn't want to upload anything.
-  if (count($_POST) == 0) {
-    if (!empty($_SESSION['file_previews']) && is_array($_SESSION['file_previews'])) {
-      foreach ($_SESSION['file_previews'] as $fid => $file) {
-        file_delete($file->filepath);
-      }
-      unset($_SESSION['file_previews']);
-    }
+  // Initialize _SESSION['upload_files'] if no post occured. 
+  // This clears the variable from old forms and makes sure it 
+  // is an array to prevent notices and errors in other parts
+  // of upload.module.
+  if (!$_POST) {
+    $_SESSION['upload_files'] = array();
   }
 
-  // $_SESSION['file_current_upload'] tracks the fid of the file submitted this page request.
+  // $_SESSION['upload_current_file'] tracks the fid of the file submitted this page request.
   // form_builder sets the value of file->list to 0 for checkboxes added to a form after
   // it has been submitted. Since unchecked checkboxes have no return value and do not
   // get a key in _POST form_builder has no way of knowing the difference between a check
   // box that wasn't present on the last form build, and a checkbox that is unchecked.
+  unset($_SESSION['upload_current_file']);
 
-  unset($_SESSION['file_current_upload']);
-
-  global $user;
-
-  // Save new file uploads to tmp dir.
-  if (($file = file_check_upload()) && user_access('upload files')) {
-
+  // Save new file uploads.
+  if (($user->uid != 1 || user_access('upload files')) && ($file = file_save_upload('upload'))) {
     // Scale image uploads.
     $file = _upload_image($file);
+    $file->list = variable_get('upload_list_default',1);
+    $file->description = $file->filename;
+    $_SESSION['upload_current_file'] = $file->fid;
+    $_SESSION['upload_files'][$file->fid] = $file;
+  }
 
-    $key = 'upload_'. (isset($_SESSION['file_previews']) ? 0 : count($_SESSION['file_previews']));
-    $file->fid = $key;
-    $file->source = $key;
-    $file->list = variable_get('upload_list_default', 1);
-    $_SESSION['file_previews'][$key] = $file;
-
-    // Store the uploaded fid for this page request in case of submit without
-    // preview or attach. See earlier notes.
-    $_SESSION['file_current_upload'] = $key;
-  }
-
-  // Attach file previews to node object.
-  if (!empty($_SESSION['file_previews']) && is_array($_SESSION['file_previews'])) {
-    foreach ($_SESSION['file_previews'] as $fid => $file) {
-      if ($user->uid != 1) {
-        // Here something.php.pps becomes something.php_.pps
-        $file->filename = upload_munge_filename($file->filename, NULL, 0);
-        $file->description = $file->filename;
-      }
+  // attach session files to node.
+  if (count($_SESSION['upload_files'])) {
+    foreach($_SESSION['upload_files'] as $fid => $file) {
       $node->files[$fid] = $file;
     }
   }
@@ -407,80 +253,6 @@
   }
 }
 
-function _upload_validate(&$node) {
-  // Accumulator for disk space quotas.
-  $filesize = 0;
-
-  // Check if node->files exists, and if it contains something.
-  if (isset($node->files) && is_array($node->files)) {
-    // Update existing files with form data.
-    foreach ($node->files as $fid => $file) {
-      // Convert file to object for compatibility
-      $file = (object)$file;
-
-      // Validate new uploads.
-      if (strpos($fid, 'upload') !== FALSE && empty($file->remove)) {
-        global $user;
-
-        // Bypass validation for uid  = 1.
-        if ($user->uid != 1) {
-          // Update filesize accumulator.
-          $filesize += $file->filesize;
-
-          // Validate file against all users roles.
-          // Only denies an upload when all roles prevent it.
-
-          $total_usersize = upload_space_used($user->uid) + $filesize;
-          $error = array();
-          foreach ($user->roles as $rid => $name) {
-            $extensions = variable_get("upload_extensions_$rid", variable_get('upload_extensions_default', 'jpg jpeg gif png txt html doc xls pdf ppt pps odt ods odp'));
-            $uploadsize = variable_get("upload_uploadsize_$rid", variable_get('upload_uploadsize_default', 1)) * 1024 * 1024;
-            $usersize = variable_get("upload_usersize_$rid", variable_get('upload_usersize_default', 1)) * 1024 * 1024;
-
-            $regex = '/\.('. ereg_replace(' +', '|', preg_quote($extensions)) .')$/i';
-
-            if (!preg_match($regex, $file->filename)) {
-              $error['extension']++;
-            }
-
-            if ($uploadsize && $file->filesize > $uploadsize) {
-              $error['uploadsize']++;
-            }
-
-            if ($usersize && $total_usersize + $file->filesize > $usersize) {
-              $error['usersize']++;
-            }
-          }
-
-          $user_roles = count($user->roles);
-          $valid = TRUE;
-          if ($error['extension'] == $user_roles) {
-            form_set_error('upload', t('The selected file %name can not be attached to this post, because it is only possible to attach files with the following extensions: %files-allowed.', array('%name' => $file->filename, '%files-allowed' => $extensions)));
-            $valid = FALSE;
-          }
-          elseif ($error['uploadsize'] == $user_roles) {
-            form_set_error('upload', t('The selected file %name can not be attached to this post, because it exceeded the maximum filesize of %maxsize.', array('%name' => $file->filename, '%maxsize' => format_size($uploadsize))));
-            $valid = FALSE;
-          }
-          elseif ($error['usersize'] == $user_roles) {
-            form_set_error('upload', t('The selected file %name can not be attached to this post, because the disk quota of %quota has been reached.', array('%name' => $file->filename, '%quota' => format_size($usersize))));
-            $valid = FALSE;
-          }
-          elseif (strlen($file->filename) > 255) {
-            form_set_error('upload', t('The selected file %name can not be attached to this post, because the filename is too long.', array('%name' => $file->filename)));
-            $valid = FALSE;
-          }
-
-          if (!$valid) {
-            unset($node->files[$fid], $_SESSION['file_previews'][$fid]);
-            file_delete($file->filepath);
-          }
-        }
-      }
-    }
-  }
-}
-
 /**
  * Implementation of hook_nodeapi().
  */
@@ -499,10 +271,6 @@
       _upload_prepare($node);
       break;
 
-    case 'validate':
-      _upload_validate($node);
-      break;
-
     case 'view':
       if (isset($node->files) && user_access('view uploaded files')) {
         // Add the attachments list to node body with a heavy
@@ -517,31 +285,7 @@
         }
       }
       break;
-    case 'alter':
-      if (isset($node->files) && user_access('view uploaded files')) {
-        // Manipulate so that inline references work in preview
-        if (!variable_get('clean_url', 0)) {
-          $previews = array();
-          foreach ($node->files as $file) {
-            if (strpos($file->fid, 'upload') !== FALSE) {
-              $previews[] = $file;
-            }
-          }
 
-          // URLs to files being previewed are actually Drupal paths. When Clean
-          // URLs are disabled, the two do not match. We perform an automatic
-          // replacement from temporary to permanent URLs. That way, the author
-          // can use the final URL in the body before having actually saved (to
-          // place inline images for example).
-          foreach ($previews as $file) {
-            $old = file_create_filename($file->filename, file_create_path());
-            $new = url($old);
-            $node->body = str_replace($old, $new, $node->body);
-            $node->teaser = str_replace($old, $new, $node->teaser);
-          }
-        }
-      }
-      break;
     case 'insert':
     case 'update':
       if (user_access('upload files')) {
@@ -595,7 +339,7 @@
   $rows = array();
   foreach ($files as $file) {
    $file = (object)$file;
-   if ($file->list && !$file->remove) {
+   if ($file->list && empty($file->remove)) {
       // Generate valid URL for both existing attachments and preview of new attachments (these have 'upload' in fid)
       $href = file_create_url((strpos($file->fid, 'upload') === FALSE ? $file->filepath : file_create_filename($file->filename, file_create_path())));
       $text = $file->description ? $file->description : $file->filename;
@@ -616,7 +360,7 @@
  *   The amount of disk space used by the user in bytes.
  */
 function upload_space_used($uid) {
-  return db_result(db_query('SELECT SUM(filesize) FROM {files} f INNER JOIN {node} n ON f.nid = n.nid WHERE n.uid = %d', $uid));
+  return file_space_used($uid);
 }
 
 /**
@@ -629,66 +373,6 @@
   return db_result(db_query('SELECT SUM(filesize) FROM {files}'));
 }
 
-/**
- * Munge the filename as needed for security purposes.
- *
- * @param $filename
- *   The name of a file to modify.
- * @param $extensions
- *   A space separated list of valid extensions. If this is blank, we'll use
- *   the admin-defined defaults for the user role from upload_extensions_$rid.
- * @param $alerts
- *   Whether alerts (watchdog, drupal_set_message()) should be displayed.
- * @return $filename
- *   The potentially modified $filename.
- */
-function upload_munge_filename($filename, $extensions = NULL, $alerts = 1) {
-  global $user;
-
-  $original = $filename;
-
-  // Allow potentially insecure uploads for very savvy users and admin
-  if (!variable_get('allow_insecure_uploads', 0)) {
-
-    if (!isset($extensions)) {
-      $extensions = '';
-      foreach ($user->roles as $rid => $name) {
-        $extensions .= ' '. variable_get("upload_extensions_$rid", variable_get('upload_extensions_default', 'jpg jpeg gif png txt html doc xls pdf ppt pps odt ods odp'));
-      }
-
-    }
-
-    $whitelist = array_unique(explode(' ', trim($extensions)));
-
-    $filename_parts = explode('.', $filename);
-
-    $new_filename = array_shift($filename_parts); // Remove file basename.
-    $final_extension = array_pop($filename_parts); // Remove final extension.
-
-    foreach ($filename_parts as $filename_part) {
-      $new_filename .= ".$filename_part";
-      if (!in_array($filename_part, $whitelist) && preg_match("/^[a-zA-Z]{2,5}\d?$/", $filename_part)) {
-        $new_filename .= '_';
-      }
-    }
-    $filename = "$new_filename.$final_extension";
-  }
-
-  if ($alerts && $original != $filename) {
-    $message = t('Your filename has been renamed to conform to site policy.');
-    drupal_set_message($message);
-  }
-
-  return $filename;
-}
-
-/**
- * Undo the effect of upload_munge_filename().
- */
-function upload_unmunge_filename($filename) {
-  return str_replace('_.', '.', $filename);
-}
-
 function upload_save(&$node) {
   if (empty($node->files) || !is_array($node->files)) {
     return;
@@ -701,91 +385,49 @@
     // Remove file. Process removals first since no further processing
     // will be required.
     if ($file->remove) {
-      // Remove file previews...
-      if (strpos($file->fid, 'upload') !== FALSE) {
-        file_delete($file->filepath);
-      }
-
-      // Remove managed files.
-      else {
-        db_query('DELETE FROM {file_revisions} WHERE fid = %d AND vid = %d', $fid, $node->vid);
-        // Only delete a file if it isn't used by any revision
-        $count = db_result(db_query('SELECT COUNT(fid) FROM {file_revisions} WHERE fid = %d', $fid));
-        if ($count < 1) {
-          db_query('DELETE FROM {files} WHERE fid = %d', $fid);
-          file_delete($file->filepath);
-        }
-      }
-    }
-
-    // New file upload
-    elseif (strpos($file->fid, 'upload') !== FALSE) {
-      if ($file = file_save_upload($file, $file->filename)) {
-        $file->fid = db_next_id('{files}_fid');
-        db_query("INSERT INTO {files} (fid, nid, filename, filepath, filemime, filesize) VALUES (%d, %d, '%s', '%s', '%s', %d)", $file->fid, $node->nid, $file->filename, $file->filepath, $file->filemime, $file->filesize);
-        db_query("INSERT INTO {file_revisions} (fid, vid, list, description) VALUES (%d, %d, %d, '%s')", $file->fid, $node->vid, $file->list, $file->description);
-        // Tell other modules where the file was stored.
-        $node->files[$fid] = $file;
-      }
-      unset($_SESSION['file_previews'][$fid]);
-    }
-
-    // Create a new revision, as needed
-    elseif ($node->old_vid && is_numeric($fid)) {
-      db_query("INSERT INTO {file_revisions} (fid, vid, list, description) VALUES (%d, %d, %d, '%s')", $file->fid, $node->vid, $file->list, $file->description);
+      db_query('DELETE FROM {file_revisions} WHERE fid = %d AND vid = %d', $fid, $node->vid);
+      // Remove it from the session in the case of new uploads, 
+      // that you want to disassociate before node submission.
+      unset($_SESSION['upload_files'][$fid]);
+      // Move on, so the removed file won't be added to new revisions.
+      continue;
+    }
+   
+    // Create a new revision, or associate a new file needed.
+    if (!empty($node->old_vid) || array_key_exists($fid, $_SESSION['upload_files'])) {
+      db_query("INSERT INTO {file_revisions} (fid, nid, vid, list, description) VALUES (%d, %d, %d, %d, '%s')", $file->fid, $node->nid, $node->vid, $file->list, $file->description);
+      file_set_status($file, FILE_STATUS_PERMANENT);
     }
-
-    // Update existing revision
+    // Update existing revision.
     else {
       db_query("UPDATE {file_revisions} SET list = %d, description = '%s' WHERE fid = %d AND vid = %d", $file->list, $file->description, $file->fid, $node->vid);
+      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.
+  unset($_SESSION['upload_files']);
 }
 
 function upload_delete($node) {
-  $files = array();
-  $result = db_query('SELECT * FROM {files} WHERE nid = %d', $node->nid);
-  while ($file = db_fetch_object($result)) {
-    $files[$file->fid] = $file;
-  }
-
-  foreach ($files as $fid => $file) {
-    // Delete all file revision information associated with the node
-    db_query('DELETE FROM {file_revisions} WHERE fid = %d', $fid);
-    file_delete($file->filepath);
-  }
-
-  // Delete all files associated with the node
-  db_query('DELETE FROM {files} WHERE nid = %d', $node->nid);
+  // Delete all files associated with the node.
+  db_query('DELETE FROM {file_revisions} WHERE nid = %d', $node->nid);
 }
 
 function upload_delete_revision($node) {
-  if (is_array($node->files)) {
-    foreach ($node->files as $file) {
-      // Check if the file will be used after this revision is deleted
-      $count = db_result(db_query('SELECT COUNT(fid) FROM {file_revisions} WHERE fid = %d', $file->fid));
-
-      // if the file won't be used, delete it
-      if ($count < 2) {
-        db_query('DELETE FROM {files} WHERE fid = %d', $file->fid);
-        file_delete($file->filepath);
-      }
-    }
-  }
-
-  // delete the revision
+  // Delete the revision.
   db_query('DELETE FROM {file_revisions} WHERE vid = %d', $node->vid);
 }
 
 function _upload_form($node) {
-
   $form['#theme'] = 'upload_form_new';
 
   if (!empty($node->files) && is_array($node->files)) {
     $form['files']['#theme'] = 'upload_form_current';
     $form['files']['#tree'] = TRUE;
     foreach ($node->files as $key => $file) {
-      // Generate valid URL for both existing attachments and preview of new attachments (these have 'upload' in fid)
+      // Generate valid URL for both existing attachments and preview of new
+      // attachments (these have 'upload' in fid).
       $description = file_create_url((strpos($file->fid, 'upload') === FALSE ? $file->filepath : file_create_filename($file->filename, file_create_path())));
       $description = "<small>". check_plain($description) ."</small>";
       $form['files'][$key]['description'] = array('#type' => 'textfield', '#default_value' => !empty($file->description) ? $file->description : $file->filename, '#maxlength' => 256, '#description' => $description );
@@ -793,9 +435,10 @@
       $form['files'][$key]['size'] = array('#value' => format_size($file->filesize));
       $form['files'][$key]['remove'] = array('#type' => 'checkbox', '#default_value' => !empty($file->remove));
       $form['files'][$key]['list'] = array('#type' => 'checkbox',  '#default_value' => $file->list);
-      // if the file was uploaded this page request, set value. this fixes the problem
-      // formapi has recognizing new checkboxes. see comments in _upload_prepare.
-      if (isset($_SESSION['file_current_upload']) && $_SESSION['file_current_upload'] == $file->fid) {
+      // If the file was uploaded this page request, set value. this fixes the
+      // problem formapi has recognizing new checkboxes. see comments in 
+      // _upload_prepare.
+      if (isset($_SESSION['upload_current_file']) && $_SESSION['upload_current_file'] == $file->fid) {
         $form['files'][$key]['list']['#value'] = variable_get('upload_list_default', 1);
       }
       $form['files'][$key]['filename'] = array('#type' => 'value',  '#value' => $file->filename);
@@ -818,7 +461,7 @@
     $form['attach-url'] = array('#type' => 'hidden', '#value' => url('upload/js', array('absolute' => TRUE)), '#attributes' => array('class' => 'upload'));
   }
 
-  // Needed for JS
+  // Needed for JS.
   $form['current']['vid'] = array('#type' => 'hidden', '#value' => isset($node->vid) ? $node->vid : 0);
   return $form;
 }
@@ -827,7 +470,7 @@
  * Theme the attachments list.
  */
 function theme_upload_form_current(&$form) {
-  $header = array(t('Delete'), t('List'), t('Description'), t('Size'));
+  $header = array(t('Remove'), t('List'), t('Description'), t('Size'));
 
   foreach (element_children($form) as $key) {
     $row = array();
@@ -870,9 +513,10 @@
  */
 function _upload_image($file) {
   $info = image_get_info($file->filepath);
+  $resolution = variable_get('upload_max_resolution', 0);
 
-  if ($info) {
-    list($width, $height) = explode('x', variable_get('upload_max_resolution', 0));
+  if ($info && $resolution) {
+    list($width, $height) = explode('x', $resolution);
     if ($width && $height) {
       $result = image_scale($file->filepath, $file->filepath, $width, $height);
       if ($result) {
@@ -897,7 +541,6 @@
 
   // Handle new uploads, and merge tmp files into node-files.
   _upload_prepare($node);
-  _upload_validate($node);
 
   $form = _upload_form($node);
   $form += array(
@@ -909,6 +552,8 @@
   $GLOBALS['form_button_counter'] = array(0, 0);
   drupal_alter('form', $form, 'upload_js');
   $form = form_builder('upload_js', $form);
+  // @todo: Put status messages inside wrapper, instead of above so they do not
+  // persist across ajax reloads.
   $output = theme('status_messages') . drupal_render($form);
   // We send the updated file attachments form.
   print drupal_to_js(array('status' => TRUE, 'data' => $output));
Index: modules/user/user.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/user/user.module,v
retrieving revision 1.777
diff -u -r1.777 user.module
--- modules/user/user.module	30 Apr 2007 17:03:29 -0000	1.777
+++ modules/user/user.module	7 May 2007 22:23:50 -0000
@@ -337,7 +337,9 @@
   }
 
   if (!form_get_errors()) {
-    if ($file = file_save_upload('picture_upload', variable_get('user_picture_path', 'pictures') .'/picture-'. $user->uid .'.'. $info['extension'], 1)) {
+    // Since the status is not set, file system garbage collection will  
+    // clean up the file.
+    if (file_copy($file, variable_get('user_picture_path', 'pictures') .'/picture-'. $user->uid .'.'. $info['extension'], FILE_EXISTS_REPLACE)) {
       $form_values['picture'] = $file->filepath;
     }
     else {
@@ -1528,7 +1530,7 @@
   }
 
   // If required, validate the uploaded picture.
-  if ($file = file_check_upload('picture_upload')) {
+  if ($file = file_save_upload('picture_upload')) {
     user_validate_picture($file, $edit, $user);
   }
 }
