diff --git a/changelog.txt b/changelog.txt index 45a47d7fc..bf3661f63 100644 --- a/changelog.txt +++ b/changelog.txt @@ -5,6 +5,7 @@ * Fix - Improved security of permission checks for MCP-based form and entry access. * Fix - Improved security of admin notification message rendering. * Fix - Improved security of WYSIWYG field values shown in the Entries list. +* Fix - Improved security of file deletion when processing uploaded file submissions. = 3.6.1 - 07-09-2026 * Fix - Date/Time dropdown fields showed today's date instead of the saved value, and Smart Phone fields lost their country code, when editing an existing entry. diff --git a/includes/abstracts/class-evf-form-fields-upload.php b/includes/abstracts/class-evf-form-fields-upload.php index 977bf5346..af396d872 100644 --- a/includes/abstracts/class-evf-form-fields-upload.php +++ b/includes/abstracts/class-evf-form-fields-upload.php @@ -30,6 +30,13 @@ abstract class EVF_Form_Fields_Upload extends EVF_Form_Fields { */ protected $protected_files = array( 'index.html', 'index.php', '.htaccess', '.htpasswd' ); + /** + * Real paths of files moved into the form uploads folder during this request. + * + * @var array + */ + protected $request_files = array(); + /** * Hook in tabs. */ @@ -1376,6 +1383,12 @@ static function ( $file ) { $this->create_dir( dirname( $file['path'] ) ); @rename( $file['tmp_path'], $file['path'] ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged $this->set_file_fs_permissions( $file['path'] ); + + $real_path = realpath( $file['path'] ); + + if ( false !== $real_path ) { + $this->request_files[] = wp_normalize_path( $real_path ); + } } $data[] = $this->generate_file_data( $file ); @@ -1659,31 +1672,12 @@ public function send_csv_file_as_email_attachment( $attachment, $entry, $form_da public function remove_csv_file_after_email_send( $attachment, $entry, $form_data, $context, $connection_id, $entry_id ) { if ( isset( $form_data['settings']['disabled_entries'] ) && '1' === $form_data['settings']['disabled_entries'] ) { - if ( ! empty( $entry ) && is_array( $entry ) ) { - foreach ( $entry as $meta_key => $meta_value ) { - if ( empty( $meta_value ) ) { - continue; - } - - if ( preg_match( '/signature_/', $meta_key ) && file_exists( $meta_value ) ) { - unlink( $meta_value ); - } - - if ( isset( $meta_value['type'] ) && ( 'file-upload' === $meta_value['type'] && ! empty( $meta_value['value_raw'] ) || 'image-upload' === $meta_value['type'] && isset( $meta_value['value_raw'] ) ) ) { - - foreach ( $meta_value['value_raw'] as $file_data ) { - if ( isset( $file_data['value'] ) ) { - $file_url = $file_data['value']; - - $uploaded_file = ABSPATH . preg_replace( '/.*wp-content/', 'wp-content', wp_parse_url( $file_url, PHP_URL_PATH ) ); - if ( file_exists( $uploaded_file ) ) { - unlink( $uploaded_file ); - } - } - } - } - } + // File URLs in the entry can come from the request (old_files), so only delete what this request uploaded. + foreach ( $this->request_files as $file_path ) { + wp_delete_file( $file_path ); } + + $this->request_files = array(); } if ( ! $entry_id ) { @@ -1756,7 +1750,7 @@ public function attach_entry_files_upload( $entry ) { } if ( preg_match( '/signature_/', $meta_key ) ) { - if ( file_exists( $meta_value ) ) { + if ( is_string( $meta_value ) && file_exists( $meta_value ) ) { $entry_files[] = $meta_value; } } elseif ( isset( $meta_value['type'] ) && ( 'file-upload' === $meta_value['type'] && ! empty( $meta_value['value_raw'] ) || 'image-upload' === $meta_value['type'] && isset( $meta_value['value_raw'] ) ) ) {