Index: includes/ajax.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/ajax.inc,v
retrieving revision 1.29
diff -u -p -r1.29 ajax.inc
--- includes/ajax.inc	31 Mar 2010 19:34:56 -0000	1.29
+++ includes/ajax.inc	31 Mar 2010 23:46:14 -0000
@@ -209,9 +209,13 @@ function ajax_render($commands = array()
 /**
  * Get a form submitted via #ajax during an AJAX callback.
  *
- * This will load a form from the form cache used during AJAX operations. It
- * pulls the form info from $_POST.
+ * This retrieves the form identified by $_POST['form_build_id'] from the form
+ * cache and optionally processes it.
  *
+ * @param $process
+ *   If TRUE, the form is processed using drupal_build_form() prior to $form and
+ *   $form_state being returned. If FALSE, $form and $form_state are returned
+ *   from cache without processing.
  * @return
  *   An array containing the $form and $form_state. Use the list() function
  *   to break these apart:
@@ -219,32 +223,37 @@ function ajax_render($commands = array()
  *     list($form, $form_state, $form_id, $form_build_id) = ajax_get_form();
  *   @endcode
  */
-function ajax_get_form() {
-  $form_state = form_state_defaults();
+function ajax_get_form($process = FALSE) {
+  $form_state = array();
 
-  $form_build_id = $_POST['form_build_id'];
+  // Since some of the submit handlers are run, redirects need to be disabled.
+  $form_state['no_redirect'] = TRUE;
+
+  // If processing the form, we call drupal_build_form(), Drupal's default form
+  // build/process/rebuild controller function. Otherwise, retrieve the form
+  // from cache without processing it.
+  if ($process) {
+    // See drupal_rebuild_form() for why #build_id and #action need to be
+    // preserved during an AJAX-triggered rebuild.
+    $form = drupal_build_form(NULL, $form_state, array('#build_id', '#action'));
+  }
+  else {
+    $form_state += form_state_defaults();
+    $form = form_get_cache($_POST['form_build_id'], $form_state);
+    $form_state['input'] = $_POST;
+  }
 
-  // Get the form from the cache.
-  $form = form_get_cache($form_build_id, $form_state);
+  // If $form cannot be loaded from the cache, the form_build_id in $_POST must
+  // be invalid, which means that someone performed a POST request onto
+  // system/ajax without actually viewing the concerned form in the browser.
+  // This is likely a hacking attempt as it never happens under normal
+  // circumstances, so we just do nothing.
   if (!$form) {
-    // If $form cannot be loaded from the cache, the form_build_id in $_POST
-    // must be invalid, which means that someone performed a POST request onto
-    // system/ajax without actually viewing the concerned form in the browser.
-    // This is likely a hacking attempt as it never happens under normal
-    // circumstances, so we just do nothing.
     watchdog('ajax', 'Invalid form POST data.', array(), WATCHDOG_WARNING);
     drupal_exit();
   }
 
-  // Since some of the submit handlers are run, redirects need to be disabled.
-  $form_state['no_redirect'] = TRUE;
-
-  // The form needs to be processed; prepare for that by setting a few internal
-  // variables.
-  $form_state['input'] = $_POST;
-  $form_id = $form['#form_id'];
-
-  return array($form, $form_state, $form_id, $form_build_id);
+  return array($form, $form_state, $form['#form_id'], $form['#build_id']);
 }
 
 /**
@@ -263,14 +272,7 @@ function ajax_get_form() {
  * enhanced function.
  */
 function ajax_form_callback() {
-  list($form, $form_state, $form_id, $form_build_id) = ajax_get_form();
-
-  // Build, validate and if possible, submit the form.
-  drupal_process_form($form_id, $form, $form_state);
-
-  // This call recreates the form relying solely on the $form_state that
-  // drupal_process_form() set up.
-  $form = drupal_rebuild_form($form_id, $form_state, $form);
+  list($form, $form_state) = ajax_get_form(TRUE);
 
   // As part of drupal_process_form(), the element that triggered the form
   // submission is determined, and in the case of AJAX, it might not be a
Index: includes/form.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/form.inc,v
retrieving revision 1.447
diff -u -p -r1.447 form.inc
--- includes/form.inc	31 Mar 2010 19:34:56 -0000	1.447
+++ includes/form.inc	31 Mar 2010 23:46:14 -0000
@@ -79,11 +79,11 @@ function drupal_get_form($form_id) {
 }
 
 /**
- * Build and process a form based on a form id.
+ * Build and process a form based on a form id or from cache.
  *
- * The form may also be retrieved from the cache if the form was built in a
- * previous page-load. The form is then passed on for processing, validation
- * and submission if there is proper input.
+ * This function is most commonly called from drupal_get_form() for page
+ * requests that return complete pages or from ajax_get_form() for page requests
+ * that process an AJAX form submission and return a partial form.
  *
  * @param $form_id
  *   The unique string identifying the desired form. If a function with that
@@ -92,6 +92,9 @@ function drupal_get_form($form_id) {
  *   can implement hook_forms(), which maps different $form_id values to the
  *   proper form constructor function. Examples may be found in node_forms(),
  *   search_forms(), and user_forms().
+ *   NULL may be passed for this parameter, in which case, a form is processed
+ *   and returned only if it can be retrieved from cache, but if it can't be,
+ *   then no form constructor function is called.
  * @param &$form_state
  *   An array which stores information about the form. This is passed as a
  *   reference so that the caller can use it to examine what in the form changed
@@ -154,13 +157,21 @@ function drupal_get_form($form_id) {
  *     $form_state accordingly.
  *   Further $form_state properties controlling the redirection behavior after
  *   form submission may be found in drupal_redirect_form().
+ * @param $form_properties_to_preserve_during_rebuild
+ *   (optional) After form processing, the form may need to be rebuilt. During
+ *   AJAX callbacks and similar partial form rebuilds, some properties like
+ *   #build_id and #action may need to be the same in the rebuilt $form. This
+ *   parameter can be set to an array of these property names. See
+ *   drupal_rebuild_form() for more details.
  *
  * @return
  *   The rendered form or NULL, depending upon the $form_state flags that were set.
  *
  * @see drupal_redirect_form()
+ * @see drupal_rebuild_form()
+ * @see ajax_get_form()
  */
-function drupal_build_form($form_id, &$form_state) {
+function drupal_build_form($form_id, &$form_state, $form_properties_to_preserve_during_rebuild = array()) {
   // Ensure some defaults; if already set they will not be overridden.
   $form_state += form_state_defaults();
 
@@ -181,9 +192,21 @@ function drupal_build_form($form_id, &$f
     // have to rebuild the form to proceed. In addition, if there is stored
     // form_state data from a previous step, we'll retrieve it so it can
     // be passed on to the form processing code.
-    if (isset($form_state['input']['form_id']) && $form_state['input']['form_id'] == $form_id && !empty($form_state['input']['form_build_id'])) {
+    if (isset($form_state['input']['form_id']) && (!isset($form_id) || $form_state['input']['form_id'] == $form_id) && !empty($form_state['input']['form_build_id'])) {
       $form_build_id = $form_state['input']['form_build_id'];
       $form = form_get_cache($form_build_id, $form_state);
+      // If the calling function passed NULL as the form id, then it expected
+      // the form to be retrieved from cache using $form_build_id alone. If that
+      // was successful, $form_id can now be set. If it wasn't successful, we
+      // return NULL for the calling function to decide how to proceed.
+      if (!isset($form_id)) {
+        if ($form) {
+          $form_id = $form['#form_id'];
+        }
+        else {
+          return;
+        }
+      }
     }
 
     // If the previous bit of code didn't result in a populated $form
@@ -238,7 +261,10 @@ function drupal_build_form($form_id, &$f
   // passing in the latest $form_state in addition to any other variables passed
   // into drupal_get_form().
   if ($form_state['rebuild'] && $form_state['process_input'] && !form_get_errors()) {
-    $form = drupal_rebuild_form($form_id, $form_state);
+    // During AJAX and other partial rebuilds, some of the $form properties need
+    // to be preserved in the rebuilt $form.
+    $old_form_info = (isset($form) && !empty($form_properties_to_preserve_during_rebuild)) ? array_intersect_key($form, array_flip($form_properties_to_preserve_during_rebuild)) : NULL;
+    $form = drupal_rebuild_form($form_id, $form_state, $old_form_info);
   }
   // After processing the form, the form builder or a #process callback may
   // have set $form_state['cache'] to indicate that the original form and the
@@ -288,15 +314,6 @@ function form_state_defaults() {
 /**
  * Retrieves a form, caches it and processes it again.
  *
- * If your AJAX callback simulates the pressing of a button, then your AJAX
- * callback will need to do the same as what drupal_get_form() would do when the
- * button is pressed: get the form from the cache, run drupal_process_form over
- * it and then if it needs rebuild, run drupal_rebuild_form() over it. Then send
- * back a part of the returned form.
- * $form_state['triggering_element']['#array_parents'] will help you to find
- * which part.
- * @see ajax_form_callback() for an example.
- *
  * @param $form_id
  *   The unique string identifying the desired form. If a function
  *   with that name exists, it is called to build the form array.
@@ -316,11 +333,13 @@ function form_state_defaults() {
  *   The newly built form.
  */
 function drupal_rebuild_form($form_id, &$form_state, $old_form = NULL) {
-  // AJAX and other contexts may call drupal_rebuild_form() even when
-  // $form_state['rebuild'] isn't set, but _form_builder_handle_input_element()
-  // needs to distinguish a rebuild from an initial build in order to process
-  // user input correctly. Form constructors and form processing functions may
-  // also need to handle a rebuild differently than an initial build.
+  // _form_builder_handle_input_element() needs to distinguish a rebuild from an
+  // initial build in order to process user input correctly. Form constructors
+  // and form processing functions may also need to handle a rebuild differently
+  // than an initial build. There is no use-case within Drupal core for calling
+  // drupal_rebuild_form() without $form_state['rebuild'] already being TRUE.
+  // But to support legacy or edge-case modules, we make sure to set it here.
+  // @todo Remove this in Drupal 8.
   $form_state['rebuild'] = TRUE;
 
   $form = drupal_retrieve_form($form_id, $form_state);
Index: modules/file/file.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/file/file.module,v
retrieving revision 1.24
diff -u -p -r1.24 file.module
--- modules/file/file.module	31 Mar 2010 19:34:56 -0000	1.24
+++ modules/file/file.module	31 Mar 2010 23:46:15 -0000
@@ -217,8 +217,13 @@ function file_ajax_upload() {
     return array('#type' => 'ajax', '#commands' => $commands, '#header' => FALSE);
   }
 
+  // @todo It should be possible to refactor the code so that from here to the
+  //   call to drupal_rebuild_form() is replaced with ajax_get_form(TRUE). This
+  //   would require an alternate way of determining $current_file_count.
   list($form, $form_state, $form_id, $form_build_id) = ajax_get_form();
 
+  // @todo ajax_get_form() issues a drupal_exit() when the form can't be
+  //   retrieved, preventing this code from running.
   if (!$form) {
     // Invalid form_build_id.
     drupal_set_message(t('An unrecoverable error occurred. Use of this form has expired. Try reloading the page and submitting again.'), 'error');
@@ -239,7 +244,9 @@ function file_ajax_upload() {
 
   // This call recreates the form relying solely on the form_state that the
   // drupal_process_form() set up.
-  $form = drupal_rebuild_form($form_id, $form_state, $form);
+  if ($form_state['rebuild'] && $form_state['process_input'] && !form_get_errors()) {
+    $form = drupal_rebuild_form($form_id, $form_state, $form);
+  }
 
   // Retrieve the element to be rendered.
   foreach ($form_parents as $parent) {
