Index: includes/file.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/file.inc,v
retrieving revision 1.94
diff -u -r1.94 file.inc
--- includes/file.inc	15 Feb 2007 11:40:17 -0000	1.94
+++ includes/file.inc	23 Apr 2007 09:51:49 -0000
@@ -152,115 +152,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', t('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 +238,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)) {
@@ -394,6 +265,34 @@
   return 1; // Everything went ok.
 }
 
+function file_destination($destination, $replace) {
+  if (file_exists($destination)) {
+    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;
+    }
+  }
+  return $dest;
+}
+
 /**
  * Moves a file to a new location.
  * - Checks if $source and $dest are valid and readable/writable.
@@ -428,6 +327,68 @@
 }
 
 /**
+ * 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 = 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 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.
  *
@@ -470,6 +431,155 @@
 }
 
 /**
+ * get the file space used by a single user, or the filesystem as a whole.
+ */
+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.
+ * @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;
+}
+
+/**
+ * Save a file and add it to the drupal database.
+ *
+ * This function validates and save a file to the drupal database.
+ * It is primarily an internal function used by the fileapi
+ * but can be accessed by other modules wanting to utilize the
+ * drupal files table for file tracking and core file validation.
+ *
+ * @param object $src A drupal file object
+ * @param string $dst destination path for the file being save.
+ * @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.
+ */
+function _file_save($src, $dst = '', $replace = FILE_EXISTS_RENAME) {
+  if (!file_validate($file)) {
+    return FALSE;
+  }
+
+  if (isset($file->fid)) {
+    //$result = db_query('UPDATE {files} ...
+     
+  }
+  else {
+    $file->fid = db_next_id('{files}_fid');
+    // insert a record for the file in the database.
+    $result = db_query("INSERT INTO {files} (fid, uid, filename, filepath, filemime, filesize) VALUES (%d, %d, '%s', '%s', '%s', %d)", $file->fid, $user->uid, $file->filename, $file->filepath, $file->filemime, $file->filesize);
+  }
+    
+  if (!$result) {
+    return FALSE;
+  }
+  return TRUE;
+}
+
+/**
+ * Saves a file to a new location & inserts it into the database.
+ *
+ * This function builds a file object from a $src path and copies
+ * the file to the $dst. It can be used to manage files already on 
+ * the server.
+ *
+ * @param $src path of file to be saved.
+ * @param $dst A string containing the directory $src should be copied to.
+ * @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 FALSE in case of error.
+ */
+
+function file_save_file($src, $dst, $replace = FILE_EXISTS_RENAME) {
+  // Begin building file object.
+  $file = new stdClass();
+  $file->filename = file_munge_filename(trim(basename($src), '.'));
+
+  // Create temporary name/path for newly uploaded files.
+  //$file->filepath = $dest;
+  $file->filepath = $src;
+  
+  // @todo: get mime info for existing files.
+
+  $file->filemime = '';
+
+  // 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 = filesize($src);
+  $file->source = $src;
+
+  if (file_validate($file)) { 
+    return file_save($file, $dst, $rename);
+  }
+  return FALSE;
+}
+
+
+
+/**
  * Saves a file upload to a new location. The source file is validated as a
  * proper upload and handled as such.
  *
@@ -483,32 +593,93 @@
  * @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;
+  // upload cache.
+  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;
+
+      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]), '.'));
 
-    // This should be refactored, file_check_upload has already
-    // moved the file to the temporary folder.
+    // 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_create_path($file->filename);
     }
+    $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 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', t('Upload Error. Could not move uploaded file (%file) to destination (%destination).', array('%file' => $file->filename, '%destination', $file->filepath)));
+      return FALSE;
+    }
+
+    // If we got this fat lets get our fid save this puppy to the db.
+    $file->fid = db_next_id('fid');
+    // insert a record for the file in the database.
+    db_query("INSERT INTO {files} (fid, uid, filename, filepath, filemime, filesize) VALUES (%d, %d, '%s', '%s', '%s', %d)", $file->fid, $user->uid, $file->filename, $file->filepath, $file->filemime, $file->filesize);
+    
+    // Add file to the cache.
+    $upload_cache[$source] = $file;
+    return $file;
   }
-  return 0;
+  return FALSE;
 }
 
+
 /**
  * Save a string to the specified destination.
  *
Index: modules/system/system.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.module,v
retrieving revision 1.457
diff -u -r1.457 system.module
--- modules/system/system.module	17 Mar 2007 18:30:14 -0000	1.457
+++ modules/system/system.module	23 Apr 2007 09:51:52 -0000
@@ -706,7 +706,6 @@
 }
 
 function system_file_system_settings() {
-
   $form['file_directory_path'] = array(
     '#type' => 'textfield',
     '#title' => t('File system path'),
@@ -733,9 +732,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() {
+  $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) {
Index: modules/upload/upload.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/upload/upload.module,v
retrieving revision 1.154
diff -u -r1.154 upload.module
--- modules/upload/upload.module	6 Mar 2007 16:43:10 -0000	1.154
+++ modules/upload/upload.module	23 Apr 2007 09:51:53 -0000
@@ -81,24 +81,6 @@
   $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.
  */
@@ -108,55 +90,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'),
@@ -178,70 +117,6 @@
     '#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);
 }
 
@@ -281,56 +156,32 @@
  * append file_previews to the node object as well.
  */
 function _upload_prepare(&$node) {
-
-  // 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']);
-    }
-  }
-
   // $_SESSION['file_current_upload'] 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['file_current_upload']);
+  unset($_SESSION['upload_current_file']);
 
   global $user;
 
   // Save new file uploads to tmp dir.
-  if (($file = file_check_upload()) && user_access('upload files')) {
-
+  if (($user->uid != 1 || user_access('upload files')) && ($file = file_save_upload('upload'))) {
     // Scale image uploads.
     $file = _upload_image($file);
-
-    $key = 'upload_'. 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;
+    $file->description = $file->filename;
+    $_SESSION['upload_current_file'] = $file->fid;
+    $_SESSION['upload_files'][$file->fid] = $file;
   }
 
-  // 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;
     }
   }
+  
 }
 
 function upload_form_alter($form_id, &$form) {
@@ -390,79 +241,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 && !$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().
@@ -482,10 +260,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
@@ -500,31 +274,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')) {
@@ -599,7 +349,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 db_result(db_query('SELECT SUM(filesize) FROM {files} f WHERE uid = %d', $uid));
 }
 
 /**
@@ -612,66 +362,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 (!is_array($node->files)) {
     return;
@@ -684,83 +374,44 @@
     // 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('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 delete before node submission.
+      unset($_SESSION['upload_files'][$fid]);
+    }
+   
+    // Create a new revision, or associate a new file needed
+    if ($node->old_vid || array_key_exists($fid, $_SESSION['upload_files'])) {
       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
     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);
     }
   }
+  // 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);
+  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
   db_query('DELETE FROM {file_revisions} WHERE vid = %d', $node->vid);
 }
 
 function _upload_form($node) {
+  // 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();
+  }
 
   $form['#theme'] = 'upload_form_new';
 
@@ -778,7 +429,7 @@
       $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 ($_SESSION['file_current_upload'] == $file->fid) {
+      if ($_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);
@@ -880,7 +531,6 @@
 
   // Handle new uploads, and merge tmp files into node-files.
   _upload_prepare($node);
-  _upload_validate($node);
 
   $form = _upload_form($node);
   foreach (module_implements('form_alter') as $module) {
@@ -888,6 +538,8 @@
     $function('upload_js', $form);
   }
   $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));
