Comments: Use a more precise check for disallowed keys on filtered comment data.

The previous approach of running `wp_allow_comment()` twice could have unintended consequences, e.g. the `check_comment_flood` action was also triggered twice, which might lead to false-positive identification of comment flood in case there is some custom callback hooked to it, which is not expecting identical data seeing twice.

This commit introduces a new function, `wp_check_comment_data()`, to specifically check for disallowed content before and after comment data is filtered.

Follow-up to [59267].

Reviewed by davidbaumwald.
Merges [59319] to the 6.7 branch.

Props david.binda, SergeyBiryukov.
Fixes #61827.
Built from https://develop.svn.wordpress.org/branches/6.7@59322


git-svn-id: http://core.svn.wordpress.org/branches/6.7@58708 1a063a9b-81f0-0310-95a4-ce76da25c4cd
This commit is contained in:
Sergey Biryukov 2024-10-29 16:35:17 +00:00
parent b3c1dfabd4
commit 218cf8089b

View File

@ -773,59 +773,7 @@ function wp_allow_comment( $commentdata, $wp_error = false ) {
return new WP_Error( 'comment_flood', $comment_flood_message, 429 ); return new WP_Error( 'comment_flood', $comment_flood_message, 429 );
} }
if ( ! empty( $commentdata['user_id'] ) ) { return wp_check_comment_data( $commentdata );
$user = get_userdata( $commentdata['user_id'] );
$post_author = $wpdb->get_var(
$wpdb->prepare(
"SELECT post_author FROM $wpdb->posts WHERE ID = %d LIMIT 1",
$commentdata['comment_post_ID']
)
);
}
if ( isset( $user ) && ( $commentdata['user_id'] == $post_author || $user->has_cap( 'moderate_comments' ) ) ) {
// The author and the admins get respect.
$approved = 1;
} else {
// Everyone else's comments will be checked.
if ( check_comment(
$commentdata['comment_author'],
$commentdata['comment_author_email'],
$commentdata['comment_author_url'],
$commentdata['comment_content'],
$commentdata['comment_author_IP'],
$commentdata['comment_agent'],
$commentdata['comment_type']
) ) {
$approved = 1;
} else {
$approved = 0;
}
if ( wp_check_comment_disallowed_list(
$commentdata['comment_author'],
$commentdata['comment_author_email'],
$commentdata['comment_author_url'],
$commentdata['comment_content'],
$commentdata['comment_author_IP'],
$commentdata['comment_agent']
) ) {
$approved = EMPTY_TRASH_DAYS ? 'trash' : 'spam';
}
}
/**
* Filters a comment's approval status before it is set.
*
* @since 2.1.0
* @since 4.9.0 Returning a WP_Error value from the filter will short-circuit comment insertion
* and allow skipping further processing.
*
* @param int|string|WP_Error $approved The approval status. Accepts 1, 0, 'spam', 'trash',
* or WP_Error.
* @param array $commentdata Comment data.
*/
return apply_filters( 'pre_comment_approved', $approved, $commentdata );
} }
/** /**
@ -1292,6 +1240,75 @@ function wp_check_comment_data_max_lengths( $comment_data ) {
return true; return true;
} }
/**
* Checks whether comment data passes internal checks or has disallowed content.
*
* @since 6.7.0
*
* @global wpdb $wpdb WordPress database abstraction object.
*
* @param array $comment_data Array of arguments for inserting a comment.
* @return int|string|WP_Error The approval status on success (0|1|'spam'|'trash'),
* WP_Error otherwise.
*/
function wp_check_comment_data( $comment_data ) {
global $wpdb;
if ( ! empty( $comment_data['user_id'] ) ) {
$user = get_userdata( $comment_data['user_id'] );
$post_author = $wpdb->get_var(
$wpdb->prepare(
"SELECT post_author FROM $wpdb->posts WHERE ID = %d LIMIT 1",
$comment_data['comment_post_ID']
)
);
}
if ( isset( $user ) && ( $comment_data['user_id'] == $post_author || $user->has_cap( 'moderate_comments' ) ) ) {
// The author and the admins get respect.
$approved = 1;
} else {
// Everyone else's comments will be checked.
if ( check_comment(
$comment_data['comment_author'],
$comment_data['comment_author_email'],
$comment_data['comment_author_url'],
$comment_data['comment_content'],
$comment_data['comment_author_IP'],
$comment_data['comment_agent'],
$comment_data['comment_type']
) ) {
$approved = 1;
} else {
$approved = 0;
}
if ( wp_check_comment_disallowed_list(
$comment_data['comment_author'],
$comment_data['comment_author_email'],
$comment_data['comment_author_url'],
$comment_data['comment_content'],
$comment_data['comment_author_IP'],
$comment_data['comment_agent']
) ) {
$approved = EMPTY_TRASH_DAYS ? 'trash' : 'spam';
}
}
/**
* Filters a comment's approval status before it is set.
*
* @since 2.1.0
* @since 4.9.0 Returning a WP_Error value from the filter will short-circuit comment insertion
* and allow skipping further processing.
*
* @param int|string|WP_Error $approved The approval status. Accepts 1, 0, 'spam', 'trash',
* or WP_Error.
* @param array $commentdata Comment data.
*/
return apply_filters( 'pre_comment_approved', $approved, $comment_data );
}
/** /**
* Checks if a comment contains disallowed characters or words. * Checks if a comment contains disallowed characters or words.
* *
@ -2279,11 +2296,15 @@ function wp_new_comment( $commentdata, $wp_error = false ) {
$commentdata['comment_approved'] = wp_allow_comment( $commentdata, $wp_error ); $commentdata['comment_approved'] = wp_allow_comment( $commentdata, $wp_error );
if ( is_wp_error( $commentdata['comment_approved'] ) ) {
return $commentdata['comment_approved'];
}
$commentdata = wp_filter_comment( $commentdata ); $commentdata = wp_filter_comment( $commentdata );
if ( ! in_array( $commentdata['comment_approved'], array( 'trash', 'spam' ), true ) ) { if ( ! in_array( $commentdata['comment_approved'], array( 'trash', 'spam' ), true ) ) {
// Validate the comment again after filters are applied to comment data. // Validate the comment again after filters are applied to comment data.
$commentdata['comment_approved'] = wp_allow_comment( $commentdata, $wp_error ); $commentdata['comment_approved'] = wp_check_comment_data( $commentdata );
} }
if ( is_wp_error( $commentdata['comment_approved'] ) ) { if ( is_wp_error( $commentdata['comment_approved'] ) ) {