Index: wp-includes/compat.php
===================================================================
--- wp-includes/compat.php	(revision 10294)
+++ wp-includes/compat.php	(working copy)
@@ -96,11 +96,23 @@
 	return implode( '', $chars );
 }
 
-// from php.net
-if ( !function_exists('htmlspecialchars_decode') ) {
-	function htmlspecialchars_decode( $str, $quote_style = ENT_COMPAT ) {
-        return strtr( $str, array_flip( get_html_translation_table(HTML_SPECIALCHARS, $quote_style) ) );
-    }
+if ( !function_exists( 'htmlspecialchars_decode' ) ) {
+	// Added in PHP 5.1.0
+	// Error checks from PEAR::PHP_Compat
+	function htmlspecialchars_decode( $str, $quote_style = ENT_COMPAT )
+	{
+		if ( !is_scalar( $string ) ) {
+			trigger_error( 'htmlspecialchars_decode() expects parameter 1 to be string, ' . gettype( $string ) . ' given', E_USER_WARNING );
+			return;
+		}
+
+		if ( !is_int( $quote_style ) && $quote_style !== null ) {
+			trigger_error( 'htmlspecialchars_decode() expects parameter 2 to be integer, ' . gettype( $quote_style ) . ' given', E_USER_WARNING );
+			return;
+		}
+
+		return wp_specialchars_decode( $str, $quote_style );
+	}
 }
 
 ?>
Index: wp-includes/formatting.php
===================================================================
--- wp-includes/formatting.php	(revision 10294)
+++ wp-includes/formatting.php	(working copy)
@@ -186,37 +186,176 @@
 /**
  * Converts a number of special characters into their HTML entities.
  *
- * Differs from htmlspecialchars as existing HTML entities will not be encoded.
- * Specifically changes: & to &#038;, < to &lt; and > to &gt;.
+ * Specifically deals with: &, <, >, ", and '.
  *
- * $quotes can be set to 'single' to encode ' to &#039;, 'double' to encode " to
- * &quot;, or '1' to do both. Default is 0 where no quotes are encoded.
+ * $quote_style can be set to ENT_COMPAT to encode " to
+ * &quot;, or ENT_QUOTES to do both. Default is ENT_NOQUOTES where no quotes are encoded.
  *
  * @since 1.2.2
  *
- * @param string $text The text which is to be encoded.
- * @param mixed $quotes Optional. Converts single quotes if set to 'single', double if set to 'double' or both if otherwise set. Default 0.
+ * @param string $string The text which is to be encoded.
+ * @param mixed $quote_style Optional. Converts double quotes if set to ENT_COMPAT, both single and double if set to ENT_QUOTES or none if set to ENT_NOQUOTES. Also compatible with old values; converting single quotes if set to 'single', double if set to 'double' or both if otherwise set. Default is ENT_NOQUOTES.
+ * @param string $charset Optional. The character encoding of the string. Default is false.
+ * @param boolean $double_encode Optional. Whether or not to encode existing html entities. Default is false.
  * @return string The encoded text with HTML entities.
  */
-function wp_specialchars( $text, $quotes = 0 ) {
-	// Like htmlspecialchars except don't double-encode HTML entities
-	$text = str_replace('&&', '&#038;&', $text);
-	$text = str_replace('&&', '&#038;&', $text);
-	$text = preg_replace('/&(?:$|([^#])(?![a-z1-4]{1,8};))/', '&#038;$1', $text);
-	$text = str_replace('<', '&lt;', $text);
-	$text = str_replace('>', '&gt;', $text);
-	if ( 'double' === $quotes ) {
-		$text = str_replace('"', '&quot;', $text);
-	} elseif ( 'single' === $quotes ) {
-		$text = str_replace("'", '&#039;', $text);
-	} elseif ( $quotes ) {
-		$text = str_replace('"', '&quot;', $text);
-		$text = str_replace("'", '&#039;', $text);
+function wp_specialchars( $string, $quote_style = ENT_NOQUOTES, $charset = false, $double_encode = false )
+{
+	$string = (string) $string;
+
+	if ( 0 === strlen( $string ) ) {
+		return '';
 	}
-	return $text;
+
+	if ( !$charset ) {
+		$charset = get_option( 'blog_charset' );
+	}
+	if ( in_array( $charset, array( 'utf8', 'utf-8', 'UTF8' ) ) ) {
+		$charset = 'UTF-8';
+	}
+
+	switch ( $quote_style ) {
+		case ENT_QUOTES:
+		default:
+			$quote_style = ENT_QUOTES;
+			$_quote_style = ENT_QUOTES;
+			break;
+		case ENT_COMPAT:
+		case 'double':
+			$quote_style = ENT_COMPAT;
+			$_quote_style = ENT_COMPAT;
+			break;
+		case 'single':
+			$quote_style = ENT_NOQUOTES;
+			$_quote_style = 'single';
+			break;
+		case ENT_NOQUOTES:
+		case false:
+		case 0:
+		case '':
+		case null:
+			$quote_style = ENT_NOQUOTES;
+			$_quote_style = ENT_NOQUOTES;
+			break;
+	}
+
+	// Handle double encoding ourselves
+	if ( !$double_encode ) {
+		$string = wp_specialchars_decode( $string, $_quote_style );
+		$string = preg_replace( '/&(#?x?[0-9]+|[a-z]+);/i', '|wp_entity|$1|/wp_entity|', $string );
+	}
+
+	$string = htmlspecialchars( $string, $quote_style, $charset );
+
+	// Handle double encoding ourselves
+	if ( !$double_encode ) {
+		$string = str_replace( array( '|wp_entity|', '|/wp_entity|' ), array( '&', ';' ), $string );
+	}
+
+	// Backwards compatibility
+	if ( 'single' === $_quote_style ) {
+		$string = str_replace( "'", '&#039;', $string );
+	}
+
+	return $string;
 }
 
 /**
+ * Converts a number of HTML entities into their special characters.
+ *
+ * Specifically deals with: &, <, >, ", and '.
+ *
+ * $quote_style can be set to ENT_COMPAT to decode " entities,
+ * or ENT_QUOTES to do both " and '. Default is ENT_NOQUOTES where no quotes are decoded.
+ *
+ * @since 2.8
+ *
+ * @param string $string The text which is to be decoded.
+ * @param mixed $quote_style Optional. Converts double quotes if set to ENT_COMPAT, both single and double if set to ENT_QUOTES or none if set to ENT_NOQUOTES. Also compatible with old wp_specialchars() values; converting single quotes if set to 'single', double if set to 'double' or both if otherwise set. Default is ENT_NOQUOTES.
+ * @return string The decoded text without HTML entities.
+ */
+function wp_specialchars_decode( $string, $quote_style = ENT_NOQUOTES )
+{
+	$string = (string) $string;
+
+	if ( 0 === strlen( $string ) ) {
+		return '';
+	}
+
+	// More complete than get_html_translation_table( HTML_SPECIALCHARS )
+	$single = array( '&#039;'  => '\'', '&#x27;' => '\'' );
+	$single_preg = array( '/&#0*39;/'  => '&#039;', '/&#x0*27;/i' => '&#x27;' );
+	$double = array( '&quot;' => '"', '&#034;'  => '"', '&#x22;' => '"' );
+	$double_preg = array( '/&#0*34;/'  => '&#034;', '/&#x0*22;/i' => '&#x22;' );
+	$others = array( '&lt;'   => '<', '&#060;'  => '<', '&gt;'   => '>', '&#062;'  => '>', '&amp;'  => '&', '&#038;'  => '&', '&#x26;' => '&' );
+	$others_preg = array( '/&#0*60;/'  => '&#060;', '/&#0*62;/'  => '&#062;', '/&#0*38;/'  => '&#038;', '/&#x0*26;/i' => '&#x26;' );
+
+	switch ( $quote_style ) {
+		case ENT_QUOTES:
+		default:
+			$translation = array_merge( $single, $double, $others );
+			$translation_preg = array_merge( $single_preg, $double_preg, $others_preg );
+			break;
+		case ENT_COMPAT:
+		case 'double':
+			$translation = array_merge( $double, $others );
+			$translation_preg = array_merge( $double_preg, $others_preg );
+			break;
+		case 'single':
+			$translation = array_merge( $single, $others );
+			$translation_preg = array_merge( $single_preg, $others_preg );
+			break;
+		case ENT_NOQUOTES:
+		case false:
+		case 0:
+		case '':
+		case null:
+			$translation = $others;
+			$translation_preg = $others_preg;
+			break;
+	}
+
+	// Remove zero padding on numeric entities
+	$string = preg_replace( array_keys( $translation_preg ), array_values( $translation_preg ), $string );
+
+	// Replace characters according to translation table
+	return strtr( $string, $translation );
+}
+
+/**
+ * Checks for invalid UTF8 in a string.
+ *
+ * @since 2.8
+ *
+ * @param string $string The text which is to be checked.
+ * @param boolean $strip Optional. Whether to attempt to strip out invalid UTF8. Default is false.
+ * @return string The checked text.
+ */
+function wp_check_invalid_utf8( $string, $strip = false )
+{
+	$string = (string) $string;
+
+	if ( 0 === strlen( $string ) ) {
+		return '';
+	}
+
+	if ( !in_array( get_option( 'blog_charset' ), array( 'utf8', 'utf-8', 'UTF8', 'UTF-8' ) ) ) {
+		return $string;
+	}
+
+	// preg_match fails when it encounters invalid UTF8 in $string
+	if ( 1 === @preg_match( '@^.@us', $string ) ) {
+		return $string;
+	}
+
+	if ( $strip && function_exists( 'iconv' ) ) {
+		return iconv( 'utf-8', 'utf-8', $string );
+	} else {
+		return '';
+	}
+}
+
+/**
  * Encode the Unicode values to be used in the URI.
  *
  * @since 1.5.0
@@ -1742,10 +1881,11 @@
  * @return string Escaped text.
  */
 function js_escape($text) {
-	$safe_text = wp_specialchars($text, 'double');
-	$safe_text = preg_replace('/&#(x)?0*(?(1)27|39);?/i', "'", stripslashes($safe_text));
-	$safe_text = preg_replace("/\r?\n/", "\\n", addslashes($safe_text));
-	return apply_filters('js_escape', $safe_text, $text);
+	$safe_text = wp_check_invalid_utf8( $text );
+	$safe_text = wp_specialchars( $safe_text, ENT_COMPAT );
+	$safe_text = preg_replace( '/&#(x)?0*(?(1)27|39);?/i', "'", stripslashes( $safe_text ) );
+	$safe_text = preg_replace( "/\r?\n/", "\\n", addslashes( $safe_text ) );
+	return apply_filters( 'js_escape', $safe_text, $text );
 }
 
 /**
@@ -1756,9 +1896,10 @@
  * @param string $text
  * @return string
  */
-function attribute_escape($text) {
-	$safe_text = wp_specialchars($text, true);
-	return apply_filters('attribute_escape', $safe_text, $text);
+function attribute_escape( $text ) {
+	$safe_text = wp_check_invalid_utf8( $text );
+	$safe_text = wp_specialchars( $safe_text, ENT_QUOTES );
+	return apply_filters( 'attribute_escape', $safe_text, $text );
 }
 
 /**
