Index: wp-admin/includes/post.php
===================================================================
--- wp-admin/includes/post.php	(revision 20229)
+++ wp-admin/includes/post.php	(working copy)
@@ -229,9 +229,6 @@
 
 	wp_update_post( $post_data );
 
-	// Now that we have an ID we can fix any attachment anchor hrefs
-	_fix_attachment_links( $post_ID );
-
 	wp_set_post_lock( $post_ID );
 
 	if ( current_user_can( $ptype->cap->edit_others_posts ) ) {
@@ -588,9 +585,6 @@
 
 	add_post_meta( $post_ID, '_edit_last', $GLOBALS['current_user']->ID );
 
-	// Now that we have an ID we can fix any attachment anchor hrefs
-	_fix_attachment_links( $post_ID );
-
 	wp_set_post_lock( $post_ID );
 
 	return $post_ID;
@@ -742,59 +736,53 @@
 /**
  * Replace hrefs of attachment anchors with up-to-date permalinks.
  *
+ * Runs when the post is published or updated using the 'wp_insert_post_data' filter.
+ *
  * @since 2.3.0
  * @access private
  *
- * @param unknown_type $post_ID
+ * @param unknown_type $data
  * @return unknown
  */
-function _fix_attachment_links( $post_ID ) {
-	global $_fix_attachment_link_id;
+function _fix_attachment_links( $data ) {
+	// quick sanity check, don't run if no pretty permalinks or post is not published
+	if ( !is_array($data) || !get_option('permalink_structure') || $data['post_status'] != 'publish' )
+		return $data;
 
-	$post = & get_post( $post_ID, ARRAY_A );
+	$content = $data['post_content']; // expected slashed
+	$site_url = get_bloginfo('url');
+	$site_url = substr( $site_url, strpos($site_url, '/') ); // remove the protocol
+	$replace = '';
 
-	$search = "#<a[^>]+rel=('|\")[^'\"]*attachment[^>]*>#ie";
+	// Short if there aren't any links or no '?attachment_id=' strings (strpos cannot be zero)
+	if ( !strpos($content, '?attachment_id=') || !preg_match_all( '/<a ([^>]+)>[\s\S]+?<\/a>/', $content, $link_matches ) )
+		return $data;
 
-	// See if we have any rel="attachment" links
-	if ( 0 == preg_match_all( $search, $post['post_content'], $anchor_matches, PREG_PATTERN_ORDER ) )
-		return;
+	foreach ( $link_matches[1] as $key => $value ) {
+		if ( !strpos($value, '?attachment_id=') || !strpos($value, 'wp-att-')
+			|| !preg_match( '/href=(\\\\?["\'])[^"\']*\?attachment_id=(\d+)[^"\']*\\1/', $value, $url_match )
+			|| !preg_match( '/rel=\\\\?["\'][^"\']*wp-att-(\d+)/', $value, $rel_match ) )
+				continue;
 
-	$i = 0;
-	$search = "#[\s]+rel=(\"|')(.*?)wp-att-(\d+)\\1#i";
-	foreach ( $anchor_matches[0] as $anchor ) {
-		if ( 0 == preg_match( $search, $anchor, $id_matches ) )
+		$quote = $url_match[1]; // the quote (single or double) together with the slash
+		$url_id = (int) $url_match[2];
+		$rel_id = (int) $rel_match[1];
+
+		if ( !$url_id || !$rel_id || $url_id != $rel_id || !strpos($value, $site_url) )
 			continue;
 
-		$id = (int) $id_matches[3];
+		$orig_value = $link_matches[0][$key];
+		$replace = str_replace( $url_match[0], 'href=' . $quote . get_attachment_link( $url_id ) . $quote, $orig_value );
 
-		// While we have the attachment ID, let's adopt any orphans.
-		$attachment = & get_post( $id, ARRAY_A );
-		if ( ! empty( $attachment) && ! is_object( get_post( $attachment['post_parent'] ) ) ) {
-			$attachment['post_parent'] = $post_ID;
-			// Escape data pulled from DB.
-			$attachment = add_magic_quotes( $attachment );
-			wp_update_post( $attachment );
-		}
-
-		$post_search[$i] = $anchor;
-		 $_fix_attachment_link_id = $id;
-		$post_replace[$i] = preg_replace_callback( "#href=(\"|')[^'\"]*\\1#", '_fix_attachment_links_replace_cb', $anchor );
-		++$i;
+		$content = str_replace( $orig_value, $replace, $content );
 	}
 
-	$post['post_content'] = str_replace( $post_search, $post_replace, $post['post_content'] );
+	if ( $replace )
+		$data['post_content'] = $content;
 
-	// Escape data pulled from DB.
-	$post = add_magic_quotes( $post);
-
-	return wp_update_post( $post);
+	return $data;
 }
 
-function _fix_attachment_links_replace_cb($match) {
-        global $_fix_attachment_link_id;
-        return stripslashes( 'href='.$match[1] ).get_attachment_link( $_fix_attachment_link_id ).stripslashes( $match[1] );
-}
-
 /**
  * Move child posts to a new parent.
  *
Index: wp-includes/default-filters.php
===================================================================
--- wp-includes/default-filters.php	(revision 20229)
+++ wp-includes/default-filters.php	(working copy)
@@ -194,6 +194,7 @@
 add_filter( 'pings_open',               '_close_comments_for_old_post', 10, 2 );
 add_filter( 'editable_slug',            'urldecode'                           );
 add_filter( 'nav_menu_meta_box_object', '_wp_nav_menu_meta_box_object'        );
+add_filter( 'wp_insert_post_data',      '_fix_attachment_links',        1     );
 
 // Actions
 add_action( 'wp_head',             'wp_enqueue_scripts',              1     );
