From 02c75e7160e691abadd4fd7def6fc0c86fedb6d6 Mon Sep 17 00:00:00 2001
From: Lee Rowlands <lee.rowlands@previousnext.com.au>
Date: Thu, 26 Oct 2023 07:30:41 +1000
Subject: [PATCH] Issue #3394406 by kim.pepper, larowlan, bnjmnm, smustgrave:
 FileUploadHandler::handleExtensionValidation does not have fallback for sites
 still using file_validate_extensions

---
 .../file/src/Upload/FileUploadHandler.php     |  9 +++
 .../file_validator_test.services.yml          |  4 +
 .../FileSanitizationEventSubscriber.php       | 44 +++++++++++
 .../src/Kernel/FileUploadHandlerTest.php      | 73 +++++++++++++++++++
 4 files changed, 130 insertions(+)
 create mode 100644 core/modules/file/tests/modules/file_validator_test/src/EventSubscriber/FileSanitizationEventSubscriber.php
 create mode 100644 core/modules/file/tests/src/Kernel/FileUploadHandlerTest.php

diff --git a/core/modules/file/src/Upload/FileUploadHandler.php b/core/modules/file/src/Upload/FileUploadHandler.php
index bbc95e0fef6b..a2c07911fdb8 100644
--- a/core/modules/file/src/Upload/FileUploadHandler.php
+++ b/core/modules/file/src/Upload/FileUploadHandler.php
@@ -376,10 +376,19 @@ protected function handleExtensionValidation(array &$validators): string {
       }
     }
     else {
+      if (!empty($validators['file_validate_extensions'][0])) {
+        // The deprecated 'file_validate_extensions' has configuration, so that
+        // should be used.
+        $validators['FileExtension']['extensions'] = $validators['file_validate_extensions'][0];
+        @trigger_error('\'file_validate_extensions\' is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use the \'FileExtension\' constraint instead. See https://www.drupal.org/node/3363700', E_USER_DEPRECATED);
+        return $validators['FileExtension']['extensions'];
+      }
+
       // No validator was provided, so add one using the default list.
       // Build a default non-munged safe list for
       // \Drupal\system\EventSubscriber\SecurityFileUploadEventSubscriber::sanitizeName().
       $validators['FileExtension'] = ['extensions' => self::DEFAULT_EXTENSIONS];
+
     }
     return $validators['FileExtension']['extensions'] ?? '';
   }
diff --git a/core/modules/file/tests/modules/file_validator_test/file_validator_test.services.yml b/core/modules/file/tests/modules/file_validator_test/file_validator_test.services.yml
index bbf16998a255..52a45d1fb1be 100644
--- a/core/modules/file/tests/modules/file_validator_test/file_validator_test.services.yml
+++ b/core/modules/file/tests/modules/file_validator_test/file_validator_test.services.yml
@@ -3,3 +3,7 @@ services:
     class: Drupal\file_validator_test\EventSubscriber\FileValidationTestSubscriber
     tags:
       - { name: event_subscriber }
+  file_validation_sanitization_subscriber:
+    class: Drupal\file_validator_test\EventSubscriber\FileSanitizationEventSubscriber
+    tags:
+      - {name: event_subscriber}
diff --git a/core/modules/file/tests/modules/file_validator_test/src/EventSubscriber/FileSanitizationEventSubscriber.php b/core/modules/file/tests/modules/file_validator_test/src/EventSubscriber/FileSanitizationEventSubscriber.php
new file mode 100644
index 000000000000..f7774d991f46
--- /dev/null
+++ b/core/modules/file/tests/modules/file_validator_test/src/EventSubscriber/FileSanitizationEventSubscriber.php
@@ -0,0 +1,44 @@
+<?php
+
+namespace Drupal\file_validator_test\EventSubscriber;
+
+use Drupal\Core\File\Event\FileUploadSanitizeNameEvent;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * Provides a file sanitization listener for file upload tests.
+ */
+class FileSanitizationEventSubscriber implements EventSubscriberInterface {
+
+  /**
+   * The allowed extensions.
+   *
+   * @var string[]
+   */
+  protected array $allowedExtensions = [];
+
+  /**
+   * Handles the file sanitization event.
+   */
+  public function onFileSanitization(FileUploadSanitizeNameEvent $event) {
+    $this->allowedExtensions = $event->getAllowedExtensions();
+  }
+
+  /**
+   * Gets the allowed extensions.
+   *
+   * @return string[]
+   *   The allowed extensions.
+   */
+  public function getAllowedExtensions(): array {
+    return $this->allowedExtensions;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getSubscribedEvents(): array {
+    return [FileUploadSanitizeNameEvent::class => 'onFileSanitization'];
+  }
+
+}
diff --git a/core/modules/file/tests/src/Kernel/FileUploadHandlerTest.php b/core/modules/file/tests/src/Kernel/FileUploadHandlerTest.php
new file mode 100644
index 000000000000..bdc5ccbcfdb0
--- /dev/null
+++ b/core/modules/file/tests/src/Kernel/FileUploadHandlerTest.php
@@ -0,0 +1,73 @@
+<?php
+
+namespace Drupal\Tests\file\Kernel;
+
+use Drupal\file\Upload\FileUploadHandler;
+use Drupal\file\Upload\UploadedFileInterface;
+use Drupal\KernelTests\KernelTestBase;
+use Symfony\Component\Mime\MimeTypeGuesserInterface;
+
+/**
+ * Tests the file upload handler.
+ *
+ * @group file
+ */
+class FileUploadHandlerTest extends KernelTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = ['file', 'file_validator_test'];
+
+  /**
+   * The file upload handler under test.
+   */
+  protected FileUploadHandler $fileUploadHandler;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+    $this->fileUploadHandler = $this->container->get('file.upload_handler');
+  }
+
+  /**
+   * Tests the legacy extension support.
+   *
+   * @group legacy
+   */
+  public function testLegacyExtensions(): void {
+    $filename = $this->randomMachineName() . '.txt';
+    $uploadedFile = $this->createMock(UploadedFileInterface::class);
+    $uploadedFile->expects($this->once())
+      ->method('getClientOriginalName')
+      ->willReturn($filename);
+    $uploadedFile->expects($this->once())->method('isValid')->willReturn(TRUE);
+
+    // Throw an exception in mimeTypeGuesser to return early from the method.
+    $mimeTypeGuesser = $this->createMock(MimeTypeGuesserInterface::class);
+    $mimeTypeGuesser->expects($this->once())->method('guessMimeType')
+      ->willThrowException(new \RuntimeException('Expected exception'));
+
+    $fileUploadHandler = new FileUploadHandler(
+      fileSystem: $this->container->get('file_system'),
+      entityTypeManager: $this->container->get('entity_type.manager'),
+      streamWrapperManager: $this->container->get('stream_wrapper_manager'),
+      eventDispatcher: $this->container->get('event_dispatcher'),
+      mimeTypeGuesser: $mimeTypeGuesser,
+      currentUser: $this->container->get('current_user'),
+      requestStack: $this->container->get('request_stack'),
+      fileRepository: $this->container->get('file.repository'),
+      file_validator: $this->container->get('file.validator'),
+    );
+
+    $this->expectException(\Exception::class);
+    $this->expectDeprecation('\'file_validate_extensions\' is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use the \'FileExtension\' constraint instead. See https://www.drupal.org/node/3363700');
+    $fileUploadHandler->handleFileUpload($uploadedFile, ['file_validate_extensions' => ['txt']]);
+
+    $subscriber = $this->container->get('file_validation_sanitization_subscriber');
+    $this->assertEquals(['txt'], $subscriber->getAllowedExtensions());
+  }
+
+}
-- 
GitLab