Index: includes/form.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/form.inc,v
retrieving revision 1.379
diff -u -9 -p -r1.379 form.inc
--- includes/form.inc	5 Oct 2009 01:18:25 -0000	1.379
+++ includes/form.inc	7 Oct 2009 17:51:39 -0000
@@ -1109,26 +1109,18 @@ function form_builder($form_id, $element
       $element = $function($element, $form_state);
       $element['#after_build_done'] = TRUE;
     }
   }
 
   // Now that we've processed everything, we can go back to handle the funky
   // Internet Explorer button-click scenario.
   _form_builder_ie_cleanup($element, $form_state);
 
-  // We should keep the buttons array until the IE clean up function
-  // has recognized the submit button so the form has been marked
-  // as submitted. If we already know which button was submitted,
-  // we don't need the array.
-  if (!empty($form_state['submitted'])) {
-    unset($form_state['buttons']);
-  }
-
   // If some callback set #cache, we need to flip a flag so later it
   // can be found.
   if (!empty($element['#cache'])) {
     $form_state['cache'] = $element['#cache'];
   }
 
   // If there is a file element, we need to flip a flag so later the
   // form encoding can be set.
   if (isset($element['#type']) && $element['#type'] == 'file') {
@@ -1293,18 +1285,57 @@ function _form_builder_ie_cleanup($form,
       $form_state['submit_handlers'] = empty($button['#submit']) ? NULL : $button['#submit'];
       $form_state['validate_handlers'] = empty($button['#validate']) ? NULL : $button['#validate'];
       $form_state['values'][$button['#name']] = $button['#value'];
       $form_state['clicked_button'] = $button;
     }
   }
 }
 
 /**
+ * Helper function to remove internal Form API elements and buttons from submitted form values.
+ *
+ * This function may be used when all submitted values of a form need to be
+ * processed for storage, so internal Form API values and button elements should
+ * not be contained. The following internal values are removed:
+ * - form_id
+ * - form_token
+ * - form_build_id
+ * - op
+ *
+ * @param &$form_state
+ *   A keyed array containing the current state of the form, including
+ *   submitted form values; altered by reference.
+ * @param &$values
+ *   Internal use only. Used to recursively iterate over $form_state['values'].
+ */
+function form_state_values_clean(&$form_state, &$values = NULL) {
+  if (!isset($values)) {
+    $values = &$form_state['values'];
+
+    // Remove internal Form API values (once).
+    unset($form_state['values']['form_id'], $form_state['values']['form_token'], $form_state['values']['form_build_id'], $form_state['values']['op']);
+  }
+
+  // Remove button values.
+  foreach ($form_state['buttons'] as $button_type => $buttons) {
+    foreach ($buttons as $button) {
+      $parents = $button['#parents'];
+      $values = &$form_state['values'];
+      $last_parent = array_pop($parents);
+      foreach ($parents as $parent) {
+        $values = &$values[$parent];
+      }
+      unset($values[$last_parent]);
+    }
+  }
+}
+
+/**
  * Helper function to determine the value for an image button form element.
  *
  * @param $form
  *   The form element whose value is being populated.
  * @param $input
  *   The incoming input to populate the form element. If this is FALSE,
  *   the element's default value should be returned.
  * @param $form_state
  *   A keyed array containing the current state of the form.
Index: modules/simpletest/tests/form.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/form.test,v
retrieving revision 1.18
diff -u -9 -p -r1.18 form.test
--- modules/simpletest/tests/form.test	3 Oct 2009 19:16:04 -0000	1.18
+++ modules/simpletest/tests/form.test	7 Oct 2009 17:51:39 -0000
@@ -457,9 +457,72 @@ class FormsFormWrapperTestCase extends D
    * Tests using the form in a usual way.
    */
   function testWrapperCallback() {
     $this->drupalGet('form_test/wrapper-callback');
     $this->assertText('Form wrapper callback element output.', t('The form contains form wrapper elements.'));
     $this->assertText('Form builder element output.', t('The form contains form builder elements.'));
   }
 }
 
+/**
+ * Test $form_state clearance.
+ */
+class FormStateValuesCleanTestCase extends DrupalWebTestCase {
+  public static function getInfo() {
+    return array(
+      'name' => 'Form state values clearance',
+      'description' => 'Test proper removal of submitted form values using form_state_values_clean().',
+      'group' => 'Form API',
+    );
+  }
+
+  /**
+   * Tests form_state_values_clean().
+   */
+  function testFormStateValuesClean() {
+    // Build an example form containing multiple submit and button elements; not
+    // only on the top-level.
+    $form_state = form_state_defaults();
+    $form = array('#tree' => TRUE);
+    $form['foo'] = array('#type' => 'submit', '#value' => t('Submit'));
+    $form['bar'] = array('#type' => 'submit', '#value' => t('Submit'));
+    $form['beer'] = array('#type' => 'value', '#value' => 1000);
+    $form['baz']['foo'] = array('#type' => 'button', '#value' => t('Submit'));
+    $form['baz']['baz'] = array('#type' => 'submit', '#value' => t('Submit'));
+    $form['baz']['beer'] = array('#type' => 'value', '#value' => 2000);
+    // Populate internal Form API values.
+    drupal_prepare_form('', $form, $form_state);
+    // Invoke form_builder() once to generate element default values in
+    // $form_state['values'].
+    form_builder('', $form, $form_state);
+    // By copying the auto-generated 'values' into 'input', we let form
+    // processing believe that this form was properly submitted, which in turn
+    // will lead to populated $form_state['buttons'].
+    $form_state['input'] = $form_state['values'];
+    // Lastly, process the form. Since there are no submit handlers in the game,
+    // this will not redirect.
+    drupal_process_form('', $form, $form_state);
+
+    // Setup the expected result.
+    $result = array(
+      'beer' => 1000,
+      'baz' => array('beer' => 2000),
+    );
+    form_state_values_clean($form_state);
+
+    // Verify that all internal Form API elements were removed.
+    $this->assertFalse(isset($form_state['values']['form_id']), t('%element was removed.', array('%element' => 'form_id')));
+    $this->assertFalse(isset($form_state['values']['form_token']), t('%element was removed.', array('%element' => 'form_token')));
+    $this->assertFalse(isset($form_state['values']['form_build_id']), t('%element was removed.', array('%element' => 'form_build_id')));
+    $this->assertFalse(isset($form_state['values']['op']), t('%element was removed.', array('%element' => 'op')));
+
+    // Verify that all buttons were removed.
+    $this->assertFalse(isset($form_state['values']['foo']), t('%element was removed.', array('%element' => 'foo')));
+    $this->assertFalse(isset($form_state['values']['bar']), t('%element was removed.', array('%element' => 'bar')));
+    $this->assertFalse(isset($form_state['values']['baz']['foo']), t('%element was removed.', array('%element' => 'foo')));
+    $this->assertFalse(isset($form_state['values']['baz']['baz']), t('%element was removed.', array('%element' => 'baz')));
+
+    // Verify that actual form values equal resulting form values.
+    $this->assertEqual($form_state['values'], $result, t('Expected form values equal actual form values.'));
+  }
+}
+
Index: modules/system/system.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.module,v
retrieving revision 1.801
diff -u -9 -p -r1.801 system.module
--- modules/system/system.module	5 Oct 2009 02:43:01 -0000	1.801
+++ modules/system/system.module	7 Oct 2009 17:51:39 -0000
@@ -2219,19 +2219,19 @@ function system_settings_form($form, $au
 
 /**
  * Execute the system_settings_form.
  *
  * If you want node type configure style handling of your checkboxes,
  * add an array_filter value to your form.
  */
 function system_settings_form_submit($form, &$form_state) {
   // Exclude unnecessary elements.
-  unset($form_state['values']['submit'], $form_state['values']['reset'], $form_state['values']['form_id'], $form_state['values']['op'], $form_state['values']['form_token'], $form_state['values']['form_build_id']);
+  form_state_values_clean($form_state);
 
   foreach ($form_state['values'] as $key => $value) {
     if (is_array($value) && isset($form_state['values']['array_filter'])) {
       $value = array_keys(array_filter($value));
     }
     variable_set($key, $value);
   }
 
   drupal_set_message(t('The configuration options have been saved.'));
Index: modules/user/user.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/user/user.module,v
retrieving revision 1.1055
diff -u -9 -p -r1.1055 user.module
--- modules/user/user.module	2 Oct 2009 14:49:10 -0000	1.1055
+++ modules/user/user.module	7 Oct 2009 17:51:40 -0000
@@ -3060,19 +3060,20 @@ function user_register_submit($form, &$f
   }
 
   if (!$admin && array_intersect(array_keys($form_state['values']), array('uid', 'roles', 'init', 'session', 'status'))) {
     watchdog('security', 'Detected malicious attempt to alter protected user fields.', array(), WATCHDOG_WARNING);
     $form_state['redirect'] = 'user/register';
     return;
   }
   // The unset below is needed to prevent these form values from being saved as
   // user data.
-  unset($form_state['values']['form_token'], $form_state['values']['submit'], $form_state['values']['op'], $form_state['values']['notify'], $form_state['values']['form_id'], $form_state['values']['affiliates'], $form_state['values']['destination'], $form_state['values']['form_build_id']);
+  form_state_values_clean($form_state);
+  unset($form_state['values']['notify']);
 
   $merge_data = array('pass' => $pass, 'init' => $mail, 'roles' => $roles);
   if (!$admin) {
     // Set the user's status because it was not displayed in the form.
     $merge_data['status'] = variable_get('user_register', 1) == 1;
   }
   $account = user_save('', array_merge($form_state['values'], $merge_data));
   // Terminate if an error occurred during user_save().
   if (!$account) {
Index: modules/user/user.pages.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/user/user.pages.inc,v
retrieving revision 1.57
diff -u -9 -p -r1.57 user.pages.inc
--- modules/user/user.pages.inc	29 Sep 2009 15:31:17 -0000	1.57
+++ modules/user/user.pages.inc	7 Oct 2009 17:51:40 -0000
@@ -268,19 +268,20 @@ function user_profile_form_validate($for
   }
 }
 
 /**
  * Submit function for the user account and profile editing form.
  */
 function user_profile_form_submit($form, &$form_state) {
   $account = $form['#user'];
   $category = $form['#user_category'];
-  unset($form_state['values']['op'], $form_state['values']['submit'], $form_state['values']['cancel'], $form_state['values']['form_token'], $form_state['values']['form_id'], $form_state['values']['form_build_id']);
+  // Remove unneeded values.
+  form_state_values_clean($form_state);
 
   $edit = (object)$form_state['values'];
   field_attach_submit('user', $edit, $form, $form_state);
   $edit = (array)$edit;
   user_module_invoke('submit', $edit, $account, $category);
   user_save($account, $edit, $category);
 
   // Clear the page cache because pages can contain usernames and/or profile information:
   cache_clear_all();
