fix(user): harden native avatar uploads
This commit is contained in:
@@ -140,13 +140,26 @@ add_filter('get_avatar_data', static function (array $args, $subject): array {
|
|||||||
|
|
||||||
add_action('show_user_profile', 'aksrt_render_admin_avatar_field');
|
add_action('show_user_profile', 'aksrt_render_admin_avatar_field');
|
||||||
add_action('edit_user_profile', 'aksrt_render_admin_avatar_field');
|
add_action('edit_user_profile', 'aksrt_render_admin_avatar_field');
|
||||||
|
add_action('user_edit_form_tag', static function (): void {
|
||||||
|
echo ' enctype="multipart/form-data"';
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!function_exists('aksrt_is_avatar_upload_enabled')) {
|
||||||
|
/**
|
||||||
|
* Whether local avatar uploads are enabled by attachment policy.
|
||||||
|
*/
|
||||||
|
function aksrt_is_avatar_upload_enabled(): bool {
|
||||||
|
$settings = class_exists('\\AKSRT\\Settings\\Settings') ? \AKSRT\Settings\Settings::get_all() : [];
|
||||||
|
return !isset($settings['allow_avatar_upload']) || !empty($settings['allow_avatar_upload']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!function_exists('aksrt_render_admin_avatar_field')) {
|
if (!function_exists('aksrt_render_admin_avatar_field')) {
|
||||||
/**
|
/**
|
||||||
* Render local avatar controls on the native WordPress user profile screen.
|
* Render local avatar controls on the native WordPress user profile screen.
|
||||||
*/
|
*/
|
||||||
function aksrt_render_admin_avatar_field(WP_User $user): void {
|
function aksrt_render_admin_avatar_field(WP_User $user): void {
|
||||||
if (!current_user_can('edit_user', $user->ID)) {
|
if (!current_user_can('edit_user', $user->ID) || !aksrt_is_avatar_upload_enabled()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
@@ -169,6 +182,33 @@ if (!function_exists('aksrt_render_admin_avatar_field')) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
add_filter('user_profile_update_errors', static function (WP_Error $errors, bool $update, WP_User $user): void {
|
||||||
|
if (empty($_FILES['aksrt_avatar_file']['name']) || !aksrt_is_avatar_upload_enabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$file = $_FILES['aksrt_avatar_file'];
|
||||||
|
$settings = class_exists('\\AKSRT\\Settings\\Settings') ? \AKSRT\Settings\Settings::get_all() : [];
|
||||||
|
$max_size_mb = max(1, min(10, (int) ($settings['avatar_max_size'] ?? 2)));
|
||||||
|
$actual_size = !empty($file['tmp_name']) && is_readable($file['tmp_name']) ? (int) filesize($file['tmp_name']) : 0;
|
||||||
|
if ((int) ($file['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK || $actual_size <= 0 || $actual_size > $max_size_mb * MB_IN_BYTES) {
|
||||||
|
$errors->add('aksrt_avatar_size', sprintf(__('头像文件无效或超过 %dMB', 'aksrt'), $max_size_mb));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once ABSPATH . 'wp-admin/includes/file.php';
|
||||||
|
$checked = wp_check_filetype_and_ext($file['tmp_name'], $file['name'], [
|
||||||
|
'jpg|jpeg|jpe' => 'image/jpeg',
|
||||||
|
'png' => 'image/png',
|
||||||
|
'gif' => 'image/gif',
|
||||||
|
'webp' => 'image/webp',
|
||||||
|
]);
|
||||||
|
$image_size = wp_getimagesize($file['tmp_name']);
|
||||||
|
if (empty($checked['type']) || empty($checked['ext']) || !wp_match_mime_types('image', $checked['type']) || !$image_size || ((int) $image_size[0] * (int) $image_size[1]) > 40000000) {
|
||||||
|
$errors->add('aksrt_avatar_type', __('头像必须是有效的 JPG、PNG、GIF 或 WebP 图片,且总像素不能超过 4000 万。', 'aksrt'));
|
||||||
|
}
|
||||||
|
}, 10, 3);
|
||||||
|
|
||||||
add_action('personal_options_update', 'aksrt_save_admin_avatar_field');
|
add_action('personal_options_update', 'aksrt_save_admin_avatar_field');
|
||||||
add_action('edit_user_profile_update', 'aksrt_save_admin_avatar_field');
|
add_action('edit_user_profile_update', 'aksrt_save_admin_avatar_field');
|
||||||
|
|
||||||
@@ -177,7 +217,7 @@ if (!function_exists('aksrt_save_admin_avatar_field')) {
|
|||||||
* Save local avatar changes from the native WordPress profile screen.
|
* Save local avatar changes from the native WordPress profile screen.
|
||||||
*/
|
*/
|
||||||
function aksrt_save_admin_avatar_field(int $user_id): void {
|
function aksrt_save_admin_avatar_field(int $user_id): void {
|
||||||
if (!current_user_can('edit_user', $user_id)) {
|
if (!current_user_can('edit_user', $user_id) || !aksrt_is_avatar_upload_enabled()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$nonce = isset($_POST['aksrt_admin_avatar_nonce']) ? sanitize_text_field(wp_unslash($_POST['aksrt_admin_avatar_nonce'])) : '';
|
$nonce = isset($_POST['aksrt_admin_avatar_nonce']) ? sanitize_text_field(wp_unslash($_POST['aksrt_admin_avatar_nonce'])) : '';
|
||||||
@@ -200,11 +240,23 @@ if (!function_exists('aksrt_save_admin_avatar_field')) {
|
|||||||
$settings = class_exists('\\AKSRT\\Settings\\Settings') ? \AKSRT\Settings\Settings::get_all() : [];
|
$settings = class_exists('\\AKSRT\\Settings\\Settings') ? \AKSRT\Settings\Settings::get_all() : [];
|
||||||
$max_size = max(1, min(10, (int) ($settings['avatar_max_size'] ?? 2))) * MB_IN_BYTES;
|
$max_size = max(1, min(10, (int) ($settings['avatar_max_size'] ?? 2))) * MB_IN_BYTES;
|
||||||
$file = $_FILES['aksrt_avatar_file'];
|
$file = $_FILES['aksrt_avatar_file'];
|
||||||
if ((int) ($file['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK || (int) ($file['size'] ?? 0) <= 0 || (int) $file['size'] > $max_size) {
|
$actual_size = !empty($file['tmp_name']) && is_readable($file['tmp_name']) ? (int) filesize($file['tmp_name']) : 0;
|
||||||
|
if ((int) ($file['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK || $actual_size <= 0 || $actual_size > $max_size) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
require_once ABSPATH . 'wp-admin/includes/file.php';
|
require_once ABSPATH . 'wp-admin/includes/file.php';
|
||||||
|
$checked = wp_check_filetype_and_ext($file['tmp_name'], $file['name'], [
|
||||||
|
'jpg|jpeg|jpe' => 'image/jpeg',
|
||||||
|
'png' => 'image/png',
|
||||||
|
'gif' => 'image/gif',
|
||||||
|
'webp' => 'image/webp',
|
||||||
|
]);
|
||||||
|
$image_size = wp_getimagesize($file['tmp_name']);
|
||||||
|
if (empty($checked['type']) || empty($checked['ext']) || !wp_match_mime_types('image', $checked['type']) || !$image_size || ((int) $image_size[0] * (int) $image_size[1]) > 40000000) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
require_once ABSPATH . 'wp-admin/includes/media.php';
|
require_once ABSPATH . 'wp-admin/includes/media.php';
|
||||||
require_once ABSPATH . 'wp-admin/includes/image.php';
|
require_once ABSPATH . 'wp-admin/includes/image.php';
|
||||||
$attachment_id = media_handle_upload('aksrt_avatar_file', 0, [], [
|
$attachment_id = media_handle_upload('aksrt_avatar_file', 0, [], [
|
||||||
|
|||||||
Reference in New Issue
Block a user