Index: wp-admin/includes/plugin.php
===================================================================
--- wp-admin/includes/plugin.php	(revision 18151)
+++ wp-admin/includes/plugin.php	(working copy)
@@ -212,18 +212,20 @@
  * @since 1.5.0
  *
  * @param string $plugin_folder Optional. Relative path to single plugin folder.
+ * @param string $plugin_root Optional. Allows data to be retrieved for plugin(s) in a working directory (e.g. for zip upgrades)
  * @return array Key is the plugin file path and the value is an array of the plugin data.
  */
-function get_plugins($plugin_folder = '') {
+function get_plugins($plugin_folder = '', $plugin_root = '') {
 
-	if ( ! $cache_plugins = wp_cache_get('plugins', 'plugins') )
+	if ( empty( $plugin_root ) and ! $cache_plugins = wp_cache_get('plugins', 'plugins') )
 		$cache_plugins = array();
 
 	if ( isset($cache_plugins[ $plugin_folder ]) )
 		return $cache_plugins[ $plugin_folder ];
 
 	$wp_plugins = array ();
-	$plugin_root = WP_PLUGIN_DIR;
+	if ( empty($plugin_root ) )
+		$plugin_root = WP_PLUGIN_DIR;
 	if ( !empty($plugin_folder) )
 		$plugin_root .= $plugin_folder;
 
Index: wp-admin/includes/plugin-install.php
===================================================================
--- wp-admin/includes/plugin-install.php	(revision 18151)
+++ wp-admin/includes/plugin-install.php	(working copy)
@@ -147,6 +147,8 @@
 		<?php wp_nonce_field( 'plugin-upload') ?>
 		<label class="screen-reader-text" for="pluginzip"><?php _e('Plugin zip file'); ?></label>
 		<input type="file" id="pluginzip" name="pluginzip" />
+		<input type="checkbox" id="upgrade_checked" name="upgrade_checked" />
+		<label for="upgrade_checked"><?php _e('Replace current plugin'); ?></label>
 		<input type="submit" class="button" value="<?php esc_attr_e('Install Now') ?>" />
 	</form>
 <?php
Index: wp-admin/includes/class-wp-upgrader.php
===================================================================
--- wp-admin/includes/class-wp-upgrader.php	(revision 18151)
+++ wp-admin/includes/class-wp-upgrader.php	(working copy)
@@ -270,7 +270,8 @@
 							'clear_destination' => false,
 							'clear_working' => true,
 							'is_multi' => false,
-							'hook_extra' => array() //Pass any extra $hook_extra args here, this will be passed to any hooked filters.
+							'hook_extra' => array(), //Pass any extra $hook_extra args here, this will be passed to any hooked filters.
+							'is_upload_upgrade' => false
 						);
 
 		$options = wp_parse_args($options, $defaults);
@@ -309,6 +310,32 @@
 			return $working_dir;
 		}
 
+		if ( $is_upload_upgrade and empty( $hook_extra ) ) {
+
+			$plugin = get_plugins( '', $working_dir );
+			if ( is_wp_error($plugin) ) {
+				$this->skin->error($plugin);
+				return $plugin;
+			}
+			// Not (yet?) attempting to handle multple plugins in a package
+			if ( count( $plugin ) != 1 )
+				return $this->skin( new WP_Error('bad_package', $this->strings['bad_package']) );
+
+			// Make sure the plugin name matches the one it will replace
+			$installed_plugins = get_plugins();
+			$plugin_paths = array_keys( $plugin );
+			$plugin_path = $plugin_paths[0];
+			if ( ! isset( $installed_plugins[$plugin_path] ) or $installed_plugins[$plugin_path]['Name'] != $plugin[$plugin_path]['Name'] ) {
+				// May want a different error message for this
+				$error = new WP_Error( 'bad_package', $this->strings['bad_package'] );
+				$this->skin->error( $error );
+				return $error;
+			}
+
+			// This upgrade method depends on this identification of the plugin for hooks
+			$hook_extra = array( 'plugin' => $plugin_path );
+		}
+
 		//With the given options, this installs it to the destination directory.
 		$result = $this->install_package( array(
 											'source' => $working_dir,
@@ -404,6 +431,34 @@
 
 	}
 
+	function upload_upgrade($package) {
+
+		$this->init();
+		$this->upgrade_strings();
+
+		add_filter('upgrader_pre_install', array(&$this, 'deactivate_plugin_before_upgrade'), 10, 2);
+		add_filter('upgrader_clear_destination', array(&$this, 'delete_old_plugin'), 10, 4);
+
+		$this->run(array(
+					'package' => $package,
+					'destination' => WP_PLUGIN_DIR,
+					'clear_destination' => true,
+					'clear_working' => true,
+					'hook_extra' => array(), // run must supply this after looking in the package for plugin name
+					'is_upload_upgrade' => true
+				));
+
+		// Cleanup our hooks, in case something else does a upgrade on this connection.
+		remove_filter('upgrader_pre_install', array(&$this, 'deactivate_plugin_before_upgrade'));
+		remove_filter('upgrader_clear_destination', array(&$this, 'delete_old_plugin'));
+
+		if ( ! $this->result || is_wp_error($this->result) )
+			return $this->result;
+
+		// Force refresh of plugin update information
+		delete_site_transient('update_plugins');
+	}
+
 	function upgrade($plugin) {
 
 		$this->init();
Index: wp-admin/update.php
===================================================================
--- wp-admin/update.php	(revision 18151)
+++ wp-admin/update.php	(working copy)
@@ -138,7 +138,10 @@
 		$type = 'upload'; //Install plugin type, From Web or an Upload.
 
 		$upgrader = new Plugin_Upgrader( new Plugin_Installer_Skin( compact('type', 'title', 'nonce', 'url') ) );
-		$upgrader->install( $file_upload->package );
+		if ( isset( $_REQUEST['upgrade_checked'] ) )
+			$upgrader->upload_upgrade( $file_upload->package );
+		else
+			$upgrader->install( $file_upload->package );
 
 		include(ABSPATH . 'wp-admin/admin-footer.php');
 
