diff --git a/core/modules/file/file.module b/core/modules/file/file.module index ddc87ffc3e1..7f962abe7ba 100644 --- a/core/modules/file/file.module +++ b/core/modules/file/file.module @@ -1002,9 +1002,11 @@ function _file_save_upload_single(\SplFileInfo $file_info, $form_field_name, $va // rename filename.php.foo and filename.php to filename.php_.foo_.txt and // filename.php_.txt, respectively). if (preg_match(FILE_INSECURE_EXTENSION_REGEX, $file->getFilename())) { - // If the file will be rejected anyway due to a disallowed extension, it - // should not be renamed; rather, we'll let file_validate_extensions() - // reject it below. + // If there is no file extension validation at all, or allows the 'txt' + // extension and the file would otherwise pass validation, rename it. If + // the file will be rejected anyway due to a disallowed extension, it + // should not be renamed; rather, let file_validate_extensions() reject it + // below. if (!isset($validators['file_validate_extensions']) || (preg_match('/\btxt\b/', $extensions) && empty(file_validate_extensions($file, $extensions)))) { $file->setMimeType('text/plain'); $filename = $file->getFilename(); diff --git a/core/modules/file/file.post_update.php b/core/modules/file/file.post_update.php index f2a8f360933..2f30a84ec9a 100644 --- a/core/modules/file/file.post_update.php +++ b/core/modules/file/file.post_update.php @@ -12,24 +12,23 @@ /** * Add txt to allowed extensions for all fields that allow uploads of insecure files. */ -function file_post_update_update_allowed_file_extensions_if_insecure(&$sandbox = NULL) { +function file_post_update_add_txt_if_allows_insecure_extensions(&$sandbox = NULL) { if (\Drupal::config('system.file')->get('allow_insecure_uploads')) { return t('The system is configured to allow insecure file uploads. No file field updates are necessary.'); } $updater = function (FieldConfig $field) { - // Determine if this is field uses a item definition that extends FileItem. + // Determine if this field uses an item definition that extends FileItem. if (is_subclass_of($field->getItemDefinition()->getClass(), FileItem::class)) { $allowed_extensions_string = trim($field->getSetting('file_extensions')); $allowed_extensions = array_filter(explode(' ', $allowed_extensions_string)); if (in_array('txt', $allowed_extensions, TRUE)) { - // .txt is specifically allowed there's nothing to do. + // Since .txt is specifically allowed, there's nothing to do. return FALSE; } foreach ($allowed_extensions as $extension) { + // If any insecure extension is allowed, add the 'txt' extension. if (preg_match(FILE_INSECURE_EXTENSION_REGEX, 'test.' . $extension)) { - // Add the txt extension to the list of allowed extensions if an - // insecure extension is allowed. $allowed_extensions_string .= ' txt'; $field->setSetting('file_extensions', $allowed_extensions_string); return TRUE; diff --git a/core/modules/file/src/Plugin/Field/FieldType/FileItem.php b/core/modules/file/src/Plugin/Field/FieldType/FileItem.php index 6093c7f51cf..1f77571c031 100644 --- a/core/modules/file/src/Plugin/Field/FieldType/FileItem.php +++ b/core/modules/file/src/Plugin/Field/FieldType/FileItem.php @@ -225,8 +225,8 @@ public static function validateDirectory($element, FormStateInterface $form_stat public static function validateExtensions($element, FormStateInterface $form_state) { if (!empty($element['#value'])) { $extensions = preg_replace('/([, ]+\.?)/', ' ', trim(strtolower($element['#value']))); - $extension_array = array_filter(explode(' ', $extensions)); - $extensions = implode(' ', array_unique($extension_array)); + $extension_array = array_unique(array_filter(explode(' ', $extensions))); + $extensions = implode(' ', $extension_array); if (!preg_match('/^([a-z0-9]+([.][a-z0-9])* ?)+$/', $extensions)) { $form_state->setError($element, t('The list of allowed extensions is not valid, be sure to exclude leading dots and to separate extensions with a comma or space.')); } @@ -234,8 +234,8 @@ public static function validateExtensions($element, FormStateInterface $form_sta $form_state->setValueForElement($element, $extensions); } - // If insecure uploads are not allowed then error if txt is not an allowed - // extension. + // If insecure uploads are not allowed and txt is not in the list of + // allowed extensions, ensure that no insecure extensions are allowed. if (!in_array('txt', $extension_array, TRUE) && !\Drupal::config('system.file')->get('allow_insecure_uploads')) { foreach ($extension_array as $extension) { if (preg_match(FILE_INSECURE_EXTENSION_REGEX, 'test.' . $extension)) { diff --git a/core/modules/file/tests/src/Functional/FileFieldWidgetTest.php b/core/modules/file/tests/src/Functional/FileFieldWidgetTest.php index 83f55160bb4..af8000dcc15 100644 --- a/core/modules/file/tests/src/Functional/FileFieldWidgetTest.php +++ b/core/modules/file/tests/src/Functional/FileFieldWidgetTest.php @@ -483,19 +483,19 @@ public function testFileExtensionsSetting() { $field = FieldConfig::loadByName('node', $type_name, $field_name); $field_id = $field->id(); - // By default allowing PHP files without TXT is not permitted. + // By default allowing .php files without txt is not permitted. $this->drupalGet("admin/structure/types/manage/$type_name/fields/$field_id"); $edit = ['settings[file_extensions]' => 'jpg php']; $this->submitForm($edit, 'Save settings'); $this->assertSession()->pageTextContains('Add txt to the list of allowed extensions to securely upload files with a php extension. The txt extension will then be added automatically.'); - // Test allowing PHP and TXT. + // Test allowing .php and .txt. $edit = ['settings[file_extensions]' => 'jpg php txt']; $this->submitForm($edit, 'Save settings'); $this->assertSession()->pageTextContains('Saved ' . $field_name . ' configuration.'); - // If the system is configured to allow insecure uploads, TXT is not - // required when allowing PHP. + // If the system is configured to allow insecure uploads, .txt is not + // required when allowing .php. $this->config('system.file')->set('allow_insecure_uploads', TRUE)->save(); $this->drupalGet("admin/structure/types/manage/$type_name/fields/$field_id"); $edit = ['settings[file_extensions]' => 'jpg php']; diff --git a/core/modules/file/tests/src/Functional/Update/FileFieldFileExtensionsUpdateTest.php b/core/modules/file/tests/src/Functional/Update/FileFieldFileExtensionsUpdateTest.php index d52bf515c4d..66f0446dbc3 100644 --- a/core/modules/file/tests/src/Functional/Update/FileFieldFileExtensionsUpdateTest.php +++ b/core/modules/file/tests/src/Functional/Update/FileFieldFileExtensionsUpdateTest.php @@ -7,7 +7,7 @@ use Drupal\FunctionalTests\Update\UpdatePathTestBase; /** - * Tests file_post_update_update_allowed_file_extensions_if_insecure(). + * Tests file_post_update_add_txt_if_allows_insecure_extensions(). * * @group Update * @group legacy @@ -56,6 +56,7 @@ public function testAllFileTypesAllowed() { public function testInsecureUpdatesAllowed() { $this->setAllowedExtensions('php'); + // Do direct database updates to avoid dependencies. $connection = Database::getConnection(); $config = $connection->select('config') ->fields('config', ['data']) @@ -83,9 +84,10 @@ public function testInsecureUpdatesAllowed() { /** * Sets the allowed extensions on the article image field. * - * @param $allowed_extensions + * @param string $allowed_extensions + * The list of allowed extensions. */ - protected function setAllowedExtensions($allowed_extensions) { + protected function setAllowedExtensions(string $allowed_extensions) { // Do direct database updates to avoid dependencies. $connection = Database::getConnection(); diff --git a/core/modules/jsonapi/tests/src/Functional/FileUploadTest.php b/core/modules/jsonapi/tests/src/Functional/FileUploadTest.php index d87770106ad..1f84f9d298f 100644 --- a/core/modules/jsonapi/tests/src/Functional/FileUploadTest.php +++ b/core/modules/jsonapi/tests/src/Functional/FileUploadTest.php @@ -627,10 +627,9 @@ public function testFileUploadMaliciousExtension() { $this->assertResponseData($expected, $response); $this->assertFileExists('public://foobar/example.php_.txt'); - // Add php as an allowed format. Allow insecure uploads still being FALSE - // should still not allow this. So it should have a .txt extension appended. - $this->field->setSetting('file_extensions', 'php txt') - ->save(); + // Add .php and .txt as allowed extensions. Since 'allow_insecure_uploads' + // is FALSE, .php files should be renamed to have a .txt extension. + $this->field->setSetting('file_extensions', 'php txt')->save(); $this->rebuildAll(); $response = $this->fileRequest($uri, $php_string, ['Content-Disposition' => 'filename="example_2.php"']); diff --git a/core/modules/rest/tests/src/Functional/FileUploadResourceTestBase.php b/core/modules/rest/tests/src/Functional/FileUploadResourceTestBase.php index 520408f8e7d..3bbc392afd9 100644 --- a/core/modules/rest/tests/src/Functional/FileUploadResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/FileUploadResourceTestBase.php @@ -523,11 +523,9 @@ public function testFileUploadMaliciousExtension() { $this->assertResponseData($expected, $response); $this->assertFileExists('public://foobar/example.php_.txt'); - // Add php as an allowed format. Allow insecure uploads still being FALSE - // should still not allow this. So it should be renamed to have .txt - // extension. - $this->field->setSetting('file_extensions', 'php txt') - ->save(); + // Add .php and .txt as allowed extensions. Since 'allow_insecure_uploads' + // is FALSE, .php files should be renamed to have a .txt extension. + $this->field->setSetting('file_extensions', 'php txt')->save(); $this->refreshTestStateAfterRestConfigChange(); $response = $this->fileRequest($uri, $php_string, ['Content-Disposition' => 'filename="example_2.php"']);