? .cache
? .project
? .projectOptions
? drupal_files_115267_6.patch
? files
? modules/upload/upload.install
? sites/all/modules
? sites/default/settings.php
Index: CHANGELOG.txt
===================================================================
RCS file: /cvs/drupal/drupal/CHANGELOG.txt,v
retrieving revision 1.203
diff -u -r1.203 CHANGELOG.txt
--- CHANGELOG.txt	22 May 2007 07:42:36 -0000	1.203
+++ CHANGELOG.txt	24 May 2007 02:11:59 -0000
@@ -47,6 +47,9 @@
     * Tags are now automatically closed at the end of the teaser.
 - Performance:
     * Made it easier to conditionally load include files.
+- File handling improvements:
+    * Entries in the files table are now keyed to a user, not a node. Modules need to have their own file node join table.
+    * The upload module's file_revisions table has been renamed to upload.
 
 Drupal 5.0, 2007-01-15
 ----------------------
Index: includes/file.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/file.inc,v
retrieving revision 1.98
diff -u -r1.98 file.inc
--- includes/file.inc	22 May 2007 17:43:17 -0000	1.98
+++ includes/file.inc	24 May 2007 02:11:59 -0000
@@ -21,6 +21,18 @@
 define('FILE_EXISTS_ERROR', 2);
 
 /**
+ * A files status can be one of two values: temorary or permanent. The status
+ * for each file Drupal manages is stored in the {files} tables. If the status
+ * is temporary Drupal's file garbage collection will delete the file and
+ * remove it from the files table after a set period of time.
+ *
+ * If you wish to add custom statuses for use by contrib modules please expand as
+ * binary flags and consider the first 8 bits reserved. (0,1,2,4,8,16,32,64,128)
+ */
+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 +164,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 +250,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)) {
@@ -378,7 +261,9 @@
     }
 
     // Give everyone read access so that FTP'd users or
-    // non-webserver users can see/read these files.
+    // non-webserver users can see/read these files,
+    // and give group write permissions do group memebers
+    // can alter files uploaded by the webserver.
     @chmod($dest, 0664);
   }
 
@@ -395,6 +280,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 +343,61 @@
 }
 
 /**
+ * Munge the filename as needed for security purposes. For instance the file 
+ * name "exploit.php.pps" would become "exploit.php_.pps".
+ *
+ * @param $filename The name of a file to modify.
+ * @param $extensions A space separated list of extensions that should not 
+ *   be altered.
+ * @param $alerts Whether alerts (watchdog, drupal_set_message()) should be
+ *   displayed.
+ * @return $filename The potentially modified $filename.
+ */
+function file_munge_filename($filename, $extensions, $alerts = TRUE) {
+  global $user;
+
+  $original = $filename;
+
+  // Allow potentially insecure uploads for very savvy users and admin
+  if (!variable_get('allow_insecure_uploads', 0)) {
+    $whitelist = array_unique(explode(' ', trim($extensions)));
+    
+    // Split the filename up by periods. The first part becomes the basename
+    // the last part the final extension. 
+    $filename_parts = explode('.', $filename);
+    $new_filename = array_shift($filename_parts); // Remove file basename.
+    $final_extension = array_pop($filename_parts); // Remove final extension.
+
+    // Loop through the middle parts and add an underscore to the end of each 
+    // section that could be a file extension but isn't in the list of allowed
+    // extensions. 
+    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) {
+      drupal_set_message(t('Your upload has been renamed to %filename to conform to the site policy.', array('%filename' => $filename)));
+    }
+  }
+
+  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 +431,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,46 +440,227 @@
 }
 
 /**
+ * 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}'));
+}
+
+/**
  * Saves a file upload to a new location. The source file is validated as a
  * proper upload and handled as such.
  *
- * @param $source A string specifying the name of the upload field to save.
- *   This parameter will contain the resulting destination filename in case of
- *   success.
- * @param $dest A string containing the directory $source should be copied to,
- *   will use the temporary directory in case no other value is set.
- * @param $replace A boolean, set to TRUE if the destination should be replaced
- *   when in use, but when FALSE append a _X to the filename.
- * @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)) {
-
-    // This should be refactored, file_check_upload has already
-    // moved the file to the temporary folder.
-    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;
-      }
+ * @param $source 
+ *   A string specifying the name of the upload field to save.
+ * @param $options
+ *   An associative array of additional options, with the following keys:
+ *     'destination'
+ *       A string containing the directory $source should be copied to. If this 
+ *       is not provided, the temporary directory will be used.
+ *     'replace'
+ *       A boolean indicating whether an existing file of the same name in the 
+ *       destination directory should overwritten. A false value will generate 
+ *       a new, unique filename in the destination directory.
+ *     'validate'
+ *       An 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.
+ * @return 
+ *   An object containing the file information, or 0 in the event of an error.
+ */
+function file_save_upload($source, $options = array()) {
+  global $user;
+  static $upload_cache;
+
+  // Merge in defaults.
+  $options += array(
+    'replace' => FILE_EXISTS_RENAME,
+    'destination' => FALSE,
+    'validate' => array(),
+  );
+  // Check the file length.
+  $options['validate']['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];
+  }
+
+  // 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;
+    }
+
+    // Build the list of non-munged extensions. 
+    // @todo: this should not be here. we need to figure out the right place. 
+    $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'));
     }
 
-    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;
+    // Begin building file object.
+    $file = new stdClass();
+    $file->filename = file_munge_filename(trim(basename($_FILES['files']['name'][$source]), '.'), $extensions);
+
+    // Create temporary name/path for newly uploaded files.
+    if (!$options['destination']) {
+      $options['destination'] = file_destination(file_create_path($file->filename), FILE_EXISTS_RENAME);
+    }
+    $file->filepath = $options['destination'];
+    $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';
+    }
+
+    $file->filesize = $_FILES['files']['size'][$source];
+    $file->source = $source;
+
+    // Call the validation functions.
+    $errors = array();
+    foreach ($options['validate'] as $function => $args) {
+      array_unshift($args, $file);
+      if (function_exists($function)) {
+        $errors = array_merge($errors, call_user_func_array($function, $args));
       }
-      return $file;
     }
-    return 0;
+    // Check for validation errors.
+    if (!empty($errors)) {
+      foreach ($errors as $message) {
+        form_set_error($source, $message);
+      }
+      return 0;
+    }
+
+    // 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)) {
+      form_set_error($source, 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 0;
+    }
+
+    // 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;
 }
 
 /**
+ * Check for filenames that are longer than we can store in the database.
+ * 
+ * @param $file
+ *   A Drupal file object. 
+ * @return
+ *   An array. If the file name is too long, it will contain an error message.
+ */
+function file_validate_name_length($file) {
+  $errors = array();
+  
+  if (strlen($file->filename) > 255) {
+    $errors[] = t('The select file %name cannot be uploaded because its name is too long.', array('%name' => $file->filename));
+  }
+  return $errors;
+}
+
+/**
+ * Check that the filename ends with an allowed extension. This check is not 
+ * enforced for the user #1.
+ * 
+ * @param $file
+ *   A Drupal file object. 
+ * @param $extensions
+ *   A string with a space separated  
+ * @return
+ *   An array. If the file name is too long, 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 = '/\.('. ereg_replace(' +', '|', preg_quote($extensions)) .')$/i';
+    if (!preg_match($regex, $file->filename)) {
+      $errors[] = t('The selected file %name can not be uploaded, only files with the following extensions are allowed: %files-allowed.', array('%name' => $file->filename, '%files-allowed' => $extensions));
+    }
+  }
+  return $errors;
+}
+
+/**
+ * Check that the file's size is below certain limits. This check is not 
+ * enforced for the user #1. 
+ *
+ * @param $file
+ *   A Drupal file object. 
+ * @param $file_limit
+ *   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.
+ * @return
+ *   An array. If the file name is too long, it will contain an error message.
+ */
+function file_validate_size($file, $file_limit = 0, $user_limit = 0) {
+  global $user;
+  
+  $errors = array();
+  
+  // Bypass validation for uid  = 1.
+  if ($user->uid != 1) {
+    if ($file_limit && $file->filesize > $file_limit) {
+      $errors[] = t('The selected file %name can not be uploaded, because at %filesize it exceeds the maximum file size of %maxsize.', array('%name' => $file->filename, '%filesize' => format_size($file->filesize), '%maxsize' => format_size($file_limit)));
+    }
+    if ($user_limit && file_space_used($user->uid) + $file->filesize > $user_limit) {
+      $errors[] = t('The selected file %name can not be uploaded, because it would exceed your disk quota of %quota.', array('%name' => $file->filename, '%quota' => format_size($user_limit)));
+    }
+  }
+  return $errors;
+}
+
+/**
  * Save a string to the specified destination.
  *
  * @param $data A string containing the contents of the file.
@@ -539,6 +690,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 +759,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.129
diff -u -r1.129 locale.inc
--- includes/locale.inc	22 May 2007 07:42:36 -0000	1.129
+++ includes/locale.inc	24 May 2007 02:11:59 -0000
@@ -615,7 +615,7 @@
  */
 function locale_translate_import_form_submit($form_values, $form, &$form_state) {
   // 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.115
diff -u -r1.115 system.install
--- modules/system/system.install	22 May 2007 05:52:17 -0000	1.115
+++ modules/system/system.install	24 May 2007 02:11:59 -0000
@@ -299,22 +299,17 @@
 
       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)
-      ) /*!40100 DEFAULT CHARACTER SET UTF8 */ ");
-
-      db_query("CREATE TABLE {file_revisions} (
-        fid 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 uid (uid),
+        KEY status (status),
+        KEY timestamp (timestamp)
       ) /*!40100 DEFAULT CHARACTER SET UTF8 */ ");
 
       db_query("CREATE TABLE {filter_formats} (
@@ -820,23 +815,18 @@
 
       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 TABLE {file_revisions} (
-        fid 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 {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 {filter_formats} (
         format serial,
@@ -4093,6 +4083,63 @@
 }
 
 /**
+ * Update files tables to associate files to a uid by default instead of a nid.
+ * Rename file_revisions to upload since it should only be used by the upload 
+ * module used by upload to link files to nodes.
+ */
+function system_update_6019() {
+  $ret = array();
+  switch ($GLOBALS['db_type']) {
+    case 'mysql':
+    case 'mysqli':
+      // Change owernship of files to users rather than nodes and add columns
+      // for file status and timestamp.
+      $ret[] = update_sql("ALTER TABLE {files} DROP INDEX nid"); 
+      $ret[] = update_sql('ALTER TABLE {files} CHANGE COLUMN nid uid int unsigned NOT NULL default 0');
+      $ret[] = update_sql("ALTER TABLE {files} ADD COLUMN status int NOT NULL default 0 AFTER filesize");
+      $ret[] = update_sql("ALTER TABLE {files} ADD COLUMN timestamp int unsigned NOT NULL default 0 AFTER status");
+      $ret[] = update_sql("ALTER TABLE {files} ADD KEY uid (uid)");
+      $ret[] = update_sql("ALTER TABLE {files} ADD KEY status (status)");
+      $ret[] = update_sql("ALTER TABLE {files} ADD KEY timestamp (timestamp)");
+
+      // Rename the file_revisions table to upload then add nid column.
+      $ret[] = update_sql('ALTER TABLE {file_revisions} RENAME TO {upload}');
+      $ret[] = update_sql('ALTER TABLE {upload} ADD COLUMN nid int unsigned NOT NULL default 0 AFTER fid');
+      $ret[] = update_sql("ALTER TABLE {upload} ADD KEY nid (nid)");
+      break;
+
+    case 'pgsql':
+      // @todo test the pgsql queries
+      // Change owernship of files to users rather than nodes and add columns
+      // for file status and timestamp.
+      $ret[] = update_sql("DROP INDEX {files}_nid_idx");
+      db_change_column($ret, 'files', 'nid', 'uid', 'int_unsigned', array('default' => '0', 'not null' => TRUE));
+      db_add_column($ret, 'files', 'status', 'uid', 'int', array('default' => '0', 'not null' => TRUE));
+      db_add_column($ret, 'files', 'timestamp', 'uid', 'int_unsigned', array('default' => '0', 'not null' => TRUE));
+      $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)");     
+
+      // Rename the file_revisions table to upload then add nid column.
+      $ret[] = update_sql("DROP INDEX {file_revisions}_vid_idx");
+      $ret[] = update_sql('ALTER TABLE {file_revisions} RENAME TO {upload}');
+      db_add_column($ret, 'upload', 'nid', 'int unsigned', array('default' => 0, 'not null' => TRUE));
+      $ret[] = update_sql("CREATE INDEX {upload}_vid_idx ON {upload} (vid)");
+      $ret[] = update_sql("CREATE INDEX {upload}_nid_idx ON {upload} (nid)");
+            
+
+      break;
+  }
+  // The nid column was renamed to uid. Use the old nid to find the node's uid.
+  $ret[] = update_sql('UPDATE {files} f JOIN {node} n ON f.uid = n.nid SET f.uid = n.uid');
+  // Use the existing vid to find the nid. 
+  $ret[] = update_sql('UPDATE {upload} u JOIN {node_revisions} r ON u.vid = r.vid SET u.nid = r.nid');
+  
+  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.481
diff -u -r1.481 system.module
--- modules/system/system.module	23 May 2007 08:00:46 -0000	1.481
+++ modules/system/system.module	24 May 2007 02:11:59 -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');
 }
 
 /**
@@ -2155,12 +2158,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)) {
+      // The image was saved using file_save_upload() and was added to the 
+      // files table as a temorary file. We'll make a copy and let the garbage
+      // collector delete the original upload. 
+      if (file_copy($file, $filename, FILE_EXISTS_REPLACE)) {
         $_POST['default_logo'] = 0;
         $_POST['logo_path'] = $file->filepath;
         $_POST['toggle_logo'] = 1;
@@ -2172,11 +2178,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)) {
+    // The image was saved using file_save_upload() and was added to the 
+    // files table as a temorary file. We'll make a copy and let the garbage
+    // collector delete the original upload. 
+    if (file_copy($file, $filename)) {
       $_POST['default_favicon'] = 0;
       $_POST['favicon_path'] = $file->filepath;
       $_POST['toggle_favicon'] = 1;
@@ -2620,13 +2629,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.160
diff -u -r1.160 upload.module
--- modules/upload/upload.module	14 May 2007 13:43:38 -0000	1.160
+++ modules/upload/upload.module	24 May 2007 02:14:52 -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.
  */
@@ -139,11 +120,11 @@
     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 = '';
+    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'))));
+    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) {
@@ -157,11 +138,11 @@
       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 = '';
+      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)));
+      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)));
     }
   }
 }
@@ -210,7 +191,7 @@
     '#size' => 5,
     '#maxlength' => 5,
     '#description' => t('The default maximum file size a user can upload.'),
-    '#field_suffix' => t('MB')
+    '#field_suffix' => t('MB'),
   );
   $form['settings_general']['upload_usersize_default'] = array(
     '#type' => 'textfield',
@@ -219,7 +200,7 @@
     '#size' => 5,
     '#maxlength' => 5,
     '#description' => t('The default maximum size of all files a user can have on the site.'),
-    '#field_suffix' => t('MB')
+    '#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>');
@@ -248,6 +229,7 @@
       '#size' => 5,
       '#maxlength' => 5,
       '#description' => t('The maximum size of a file a user can upload (in megabytes).'),
+      '#field_suffix' => t('MB'),
     );
     $form['settings_role_'. $rid]['upload_usersize_'. $rid] = array(
       '#type' => 'textfield',
@@ -256,95 +238,90 @@
       '#size' => 5,
       '#maxlength' => 5,
       '#description' => t('The maximum size of all files a user can have on the site (in megabytes).'),
+      '#field_suffix' => t('MB'),
     );
   }
 
   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);
+  $result = db_query("SELECT f.* FROM {files} f INNER JOIN {upload} u ON f.fid = u.uid 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')) {
-
+  // Determine the validation restrictions for this user. They may be in 
+  // multiple roles so we select the most permissive limitations from all of 
+  // their roles. 
+  $file_limit = variable_get('upload_uploadsize_default', 1);
+  $user_limit = variable_get('upload_usersize_default', 1);
+  $all_extensions = explode(' ', variable_get('upload_extensions_default', 'jpg jpeg gif png txt html doc xls pdf ppt pps odt ods odp'));
+  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'));
+    $all_extensions = array_merge($all_extensions, explode(' ', $extensions));
+    
+    // A zero value indicates no limit, take the least restrictive limit.
+    $file_size = variable_get("upload_uploadsize_$rid", variable_get('upload_uploadsize_default', 1)) * 1024 * 1024;
+    $file_limit = ($file_limit && $file_size) ? max($file_limit, $file_size) : 0;
+
+    $user_size = variable_get("upload_usersize_$rid", variable_get('upload_usersize_default', 1)) * 1024 * 1024;
+    $user_limit = ($user_limit && $user_size) ? max($user_limit, $user_size) : 0;
+  }
+  $upload_options['validate'] = array(
+    'file_validate_extensions' => array(implode(' ', array_unique($all_extensions))),
+    'file_validate_size' => array($file_limit, $user_limit),
+  );
+  
+  // Save new file uploads.
+  if (($user->uid != 1 || user_access('upload files')) && ($file = file_save_upload('upload', $upload_options))) {
     // 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 +384,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 +402,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 +416,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 +470,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 +491,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);
 }
 
 /**
@@ -626,67 +501,7 @@
  *   The amount of disk space used by uploaded files in bytes.
  */
 function upload_total_space_used() {
-  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);
+  return db_result(db_query('SELECT SUM(f.filesize) FROM {files} f INNER JOIN {upload} u ON f.fid = u.fid'));
 }
 
 function upload_save(&$node) {
@@ -701,69 +516,52 @@
     // 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]);
+      db_query('DELETE FROM {upload} 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 {upload} (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);
     }
-
-    // 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);
-    }
-
-    // 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);
+      db_query("UPDATE {upload} 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);
+  $result = db_query('SELECT DISTINCT f.* FROM upload u INNER JOIN files f ON u.fid = f.fid WHERE u.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);
+    // Delete all files associated with the node
+    db_query('DELETE FROM {files} 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 file revision information associated with the node
+  db_query('DELETE FROM {upload} 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));
+      $count = db_result(db_query('SELECT COUNT(fid) FROM {upload} WHERE fid = %d', $file->fid));
 
       // if the file won't be used, delete it
       if ($count < 2) {
@@ -774,18 +572,19 @@
   }
 
   // delete the revision
-  db_query('DELETE FROM {file_revisions} WHERE vid = %d', $node->vid);
+  db_query('DELETE FROM {upload} 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;
+drupal_set_message("files ". print_r($node->files, 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 +592,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);
@@ -824,7 +624,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;
 }
@@ -861,7 +661,7 @@
   $files = array();
 
   if ($node->vid) {
-    $result = db_query('SELECT * FROM {files} f INNER JOIN {file_revisions} r ON f.fid = r.fid WHERE r.vid = %d ORDER BY f.fid', $node->vid);
+    $result = db_query('SELECT * FROM {files} f INNER JOIN {upload} r ON f.fid = r.fid WHERE r.vid = %d ORDER BY f.fid', $node->vid);
     while ($file = db_fetch_object($result)) {
       $files[$file->fid] = $file;
     }
@@ -876,9 +676,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', '0x0'));
+  if ($info && $resolution) {
+    list($width, $height) = explode('x', $resolution);
     if ($width && $height) {
       $result = image_scale($file->filepath, $file->filepath, $width, $height);
       if ($result) {
@@ -903,7 +704,6 @@
 
   // Handle new uploads, and merge tmp files into node-files.
   _upload_prepare($node);
-  _upload_validate($node);
 
   $form = _upload_form($node);
   $form += array(
@@ -915,6 +715,8 @@
   drupal_alter('form', $form, 'upload_js');
   $form_state = array('submitted' => FALSE);
   $form = form_builder('upload_js', $form, $form_state);
+  // @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));
--- modules/upload/upload.install
+++ modules/upload/upload.install
@@ -0,0 +1,43 @@
+<?php
+// $Id: $
+
+/**
+ * Implementation of hook_install().
+ */
+function upload_install() {
+  switch ($GLOBALS['db_type']) {
+    case 'mysql':
+    case 'mysqli':
+      db_query("CREATE TABLE {upload} (
+        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 (nid)
+      ) /*!40100 DEFAULT CHARACTER SET UTF8 */ ");
+      break;
+    case 'pgsql':
+      db_query("CREATE TABLE {upload} (
+        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 {upload}_vid_idx ON {upload} (vid)");
+      db_query("CREATE INDEX {upload}_nid_idx ON {upload} (nid)");
+      break;
+  }
+}
+
+/**
+ * Implementation of hook_uninstall().
+ */
+function upload_uninstall() {
+  // @todo should we delete the associated files?
+  db_query('DROP TABLE {upload}');
+}

