Index: wp-includes/post-template.php
===================================================================
--- wp-includes/post-template.php	(revision 18329)
+++ wp-includes/post-template.php	(working copy)
@@ -735,7 +735,7 @@
 		echo "<ul class='post-meta'>\n";
 		foreach ( (array) $keys as $key ) {
 			$keyt = trim($key);
-			if ( '_' == $keyt[0] )
+			if ( is_hidden_meta( $keyt ) )
 				continue;
 			$values = array_map('trim', get_post_custom_values($key));
 			$value = implode($values,', ');
Index: wp-includes/class-wp-xmlrpc-server.php
===================================================================
--- wp-includes/class-wp-xmlrpc-server.php	(revision 18329)
+++ wp-includes/class-wp-xmlrpc-server.php	(working copy)
@@ -234,7 +234,7 @@
 
 		foreach ( (array) has_meta($post_id) as $meta ) {
 			// Don't expose protected fields.
-			if ( strpos($meta['meta_key'], '_wp_') === 0 ) {
+			if ( is_protected_meta( $meta['meta_key'], 'post' ) ) {
 				continue;
 			}
 
@@ -264,17 +264,16 @@
 				$meta['id'] = (int) $meta['id'];
 
 				if ( isset($meta['key']) ) {
-					update_meta($meta['id'], $meta['key'], $meta['value']);
+					if ( current_user_can( 'edit_post_meta', $meta['key'], $post_id ) )
+						update_meta($meta['id'], $meta['key'], $meta['value']);
+				} else {
+					if ( ! is_protected_meta( $meta['key'] ) )
+						delete_meta($meta['id']);
 				}
-				else {
-					delete_meta($meta['id']);
-				}
+			} else {
+				if ( ! is_protected_meta( $meta['key'] ) )
+					add_post_meta( $post_id, $meta['key'], $meta['value'] );
 			}
-			else {
-				$_POST['metakeyinput'] = $meta['key'];
-				$_POST['metavalue'] = $meta['value'];
-				add_meta($post_id);
-			}
 		}
 	}
 
Index: wp-includes/capabilities.php
===================================================================
--- wp-includes/capabilities.php	(revision 18329)
+++ wp-includes/capabilities.php	(working copy)
@@ -951,6 +951,23 @@
 		else
 			$caps[] = $post_type->cap->read_private_posts;
 		break;
+	case 'edit_post_meta':
+	case 'delete_post_meta':
+	case 'create_post_meta':
+		$post = get_post( $args[1] );
+		$post_type_object = get_post_type_object( $post->post_type );
+		$caps = map_meta_cap( $post_type_object->cap->edit_post, $user_id, $post->ID );	
+
+		global $_wp_meta;
+		if ( isset( $_wp_meta[ 'post' ][ $args[ 0 ] ] ) && isset( $_wp_meta[ 'post' ][ $args[ 0 ] ]->auth_callback ) ) {
+			$meta_cb = $_wp_meta[ 'post' ][ $args[ 0 ] ]->auth_callback;
+			$allowed = call_user_func( $meta_cb, $meta_key, $post->ID, $user_id );
+			if ( ! $allowed )
+				$caps[] = $cap;
+		} elseif ( is_protected_meta( $args[ 0 ], 'post' ) ) {
+			$caps[] = $cap;
+		}
+		break;
 	case 'edit_comment':
 		$comment = get_comment( $args[0] );
 		$post = get_post( $comment->comment_post_ID );
Index: wp-includes/meta.php
===================================================================
--- wp-includes/meta.php	(revision 18329)
+++ wp-includes/meta.php	(working copy)
@@ -588,12 +588,26 @@
  * @return bool True if the key is protected, false otherwise.
  */
 function is_protected_meta( $meta_key, $meta_type = null ) {
-	$protected = (  '_' == $meta_key[0] );
+	$protected = ( '_' == $meta_key[0] );
 
 	return apply_filters( 'is_protected_meta', $protected, $meta_key, $meta_type );
 }
 
 /**
+ * Determine whether a meta key is hidden
+ *
+ * @since 3.2.0
+ *
+ * @param string $meta_key Meta key
+ * @return bool True if the key is hidden, false otherwise.
+ */
+function is_hidden_meta( $meta_key, $meta_type = null ) {
+	$hidden = ( '_' == $meta_key[0] );
+
+	return apply_filters( 'is_hidden_meta', $hidden, $meta_key, $meta_type );
+}
+
+/**
  * Sanitize meta value
  *
  * @since 3.1.3
@@ -603,8 +617,14 @@
  * @param string $meta_type Type of meta
  * @return mixed Sanitized $meta_value
  */
-function sanitize_meta( $meta_key, $meta_value, $meta_type = null ) {
-	return apply_filters( 'sanitize_meta', $meta_value, $meta_key, $meta_type );
+function sanitize_meta( $meta_key, $meta_value, $meta_type ) {
+	return apply_filters( "sanitize_{$meta_type}_meta_{$meta_key}", $meta_value, $meta_key, $meta_type );
 }
 
+function register_meta( $meta_key, $meta_type, $args = array() ) {
+	global $_wp_meta;
+
+	$_wp_meta[ $meta_type ][ $meta_key ] = (object) $args;
+}
+
 ?>
Index: wp-admin/admin-ajax.php
===================================================================
--- wp-admin/admin-ajax.php	(revision 18329)
+++ wp-admin/admin-ajax.php	(working copy)
@@ -396,7 +396,7 @@
 	if ( !$meta = get_post_meta_by_id( $id ) )
 		die('1');
 
-	if ( !current_user_can( 'edit_post', $meta->post_id ) || is_protected_meta( $meta->meta_key ) )
+	if ( !current_user_can( 'delete_post_meta', $meta->meta_key, $meta->post_id ) || is_hidden_meta( $meta->meta_key ) )
 		die('-1');
 	if ( delete_meta( $meta->meta_id ) )
 		die('1');
@@ -868,10 +868,8 @@
 			die(__('Please provide a custom field value.'));
 		if ( !$meta = get_post_meta_by_id( $mid ) )
 			die('0'); // if meta doesn't exist
-		if ( !current_user_can( 'edit_post', $meta->post_id ) )
+		if ( is_hidden_meta( $meta->meta_key ) || !current_user_can( 'edit_post_meta', $meta->meta_key, $meta->post_id ) )
 			die('-1');
-		if ( is_protected_meta( $meta->meta_key ) )
-			die('-1');
 		if ( $meta->meta_value != stripslashes($value) || $meta->meta_key != stripslashes($key) ) {
 			if ( !$u = update_meta( $mid, $key, $value ) )
 				die('0'); // We know meta exists; we also know it's unchanged (or DB error, in which case there are bigger problems).
Index: wp-admin/includes/post.php
===================================================================
--- wp-admin/includes/post.php	(revision 18329)
+++ wp-admin/includes/post.php	(working copy)
@@ -207,7 +207,7 @@
 				continue;
 			if ( $meta->post_id != $post_ID )
 				continue;
-			if ( is_protected_meta( $value['key'] ) )
+			if ( is_hidden_meta( $value['key'] ) || !current_user_can( 'edit_post_meta', $value['key'], $post_ID ) )
 				continue;
 			update_meta( $key, $value['key'], $value['value'] );
 		}
@@ -219,7 +219,7 @@
 				continue;
 			if ( $meta->post_id != $post_ID )
 				continue;
-			if ( is_protected_meta( $meta->meta_key ) )
+			if ( is_hidden_meta( $meta->meta_key ) || !current_user_can( 'edit_post_meta', $meta->meta_key, $post_ID ) )
 				continue;
 			delete_meta( $key );
 		}
@@ -662,7 +662,7 @@
 		if ( $metakeyinput)
 			$metakey = $metakeyinput; // default
 
-		if ( is_protected_meta( $metakey ) )
+		if ( is_hidden_meta( $metakey ) || is_protected_meta( $metakey, 'post' ) )
 			return false;
 
 		wp_cache_delete($post_ID, 'post_meta');
@@ -770,7 +770,7 @@
 
 	$meta_key = stripslashes($meta_key);
 
-	if ( is_protected_meta( $meta_key ) )
+	if ( is_protected_meta( $meta_key, 'post' ) )
 		return false;
 
 	if ( '' === trim( $meta_value ) )
Index: wp-admin/includes/template.php
===================================================================
--- wp-admin/includes/template.php	(revision 18329)
+++ wp-admin/includes/template.php	(working copy)
@@ -466,7 +466,7 @@
 function _list_meta_row( $entry, &$count ) {
 	static $update_nonce = false;
 
-	if ( is_protected_meta( $entry['meta_key'] ) )
+	if ( is_hidden_meta( $entry['meta_key'] ) || is_protected_meta( $entry['meta_key'], 'post' ) )
 		return;
 
 	if ( !$update_nonce )
