View source  
  <?php
namespace Drupal\Tests\system\Unit\Event;
use Drupal\Core\File\Event\FileUploadSanitizeNameEvent;
use Drupal\system\EventSubscriber\SecurityFileUploadEventSubscriber;
use Drupal\Tests\UnitTestCase;
class SecurityFileUploadEventSubscriberTest extends UnitTestCase {
  
  public function testSanitizeName(string $filename, string $allowed_extensions, string $expected_filename, string $expected_filename_with_insecure_uploads = NULL) {
    
    $config_factory = $this
      ->getConfigFactoryStub([
      'system.file' => [
        'allow_insecure_uploads' => FALSE,
      ],
    ]);
    $subscriber = new SecurityFileUploadEventSubscriber($config_factory);
    $event = new FileUploadSanitizeNameEvent($filename, $allowed_extensions);
    $subscriber
      ->sanitizeName($event);
    
    $this
      ->assertSame($expected_filename, $event
      ->getFilename());
    $this
      ->assertSame($expected_filename !== $filename, $event
      ->isSecurityRename());
    
    $config_factory = $this
      ->getConfigFactoryStub([
      'system.file' => [
        'allow_insecure_uploads' => TRUE,
      ],
    ]);
    $subscriber = new SecurityFileUploadEventSubscriber($config_factory);
    $event = new FileUploadSanitizeNameEvent($filename, $allowed_extensions);
    $subscriber
      ->sanitizeName($event);
    
    $expected_filename_with_insecure_uploads = $expected_filename_with_insecure_uploads ?? $expected_filename;
    $this
      ->assertSame($expected_filename_with_insecure_uploads, $event
      ->getFilename());
    $this
      ->assertSame($expected_filename_with_insecure_uploads !== $filename, $event
      ->isSecurityRename());
  }
  
  public function provideFilenames() {
    return [
      'All extensions allowed filename not munged' => [
        'foo.txt',
        '',
        'foo.txt',
      ],
      'All extensions allowed with .php file' => [
        'foo.php',
        '',
        'foo.php_.txt',
        'foo.php',
      ],
      'All extensions allowed with .pHp file' => [
        'foo.pHp',
        '',
        'foo.pHp_.txt',
        'foo.pHp',
      ],
      'All extensions allowed with .PHP file' => [
        'foo.PHP',
        '',
        'foo.PHP_.txt',
        'foo.PHP',
      ],
      '.php extension allowed with .php file' => [
        'foo.php',
        'php',
        'foo.php',
        'foo.php',
      ],
      '.PhP extension allowed with .php file' => [
        'foo.php',
        'PhP',
        'foo.php',
        'foo.php',
      ],
      '.php, .txt extension allowed with .php file' => [
        'foo.php',
        'php txt',
        'foo.php_.txt',
        'foo.php',
      ],
      '.PhP, .tXt extension allowed with .php file' => [
        'foo.php',
        'PhP tXt',
        'foo.php_.txt',
        'foo.php',
      ],
      'no extension produces no errors' => [
        'foo',
        '',
        'foo',
      ],
      'filename is munged' => [
        'foo.phar.png.php.jpg',
        'jpg png',
        'foo.phar_.png_.php_.jpg',
      ],
      'filename is munged regardless of case' => [
        'FOO.pHAR.PNG.PhP.jpg',
        'jpg png',
        'FOO.pHAR_.PNG_.PhP_.jpg',
      ],
      'null bytes are removed' => [
        'foo' . chr(0) . '.txt' . chr(0),
        '',
        'foo.txt',
      ],
      'dot files are renamed' => [
        '.htaccess',
        '',
        'htaccess',
      ],
    ];
  }
  
  public function testSanitizeNameNoMunge(string $filename, string $allowed_extensions) {
    $config_factory = $this
      ->getConfigFactoryStub([
      'system.file' => [
        'allow_insecure_uploads' => FALSE,
      ],
    ]);
    $subscriber = new SecurityFileUploadEventSubscriber($config_factory);
    $event = new FileUploadSanitizeNameEvent($filename, $allowed_extensions);
    $subscriber
      ->sanitizeName($event);
    
    $this
      ->assertSame($filename, $event
      ->getFilename());
    $this
      ->assertSame(FALSE, $event
      ->isSecurityRename());
    $config_factory = $this
      ->getConfigFactoryStub([
      'system.file' => [
        'allow_insecure_uploads' => TRUE,
      ],
    ]);
    $event = new FileUploadSanitizeNameEvent($filename, $allowed_extensions);
    $subscriber = new SecurityFileUploadEventSubscriber($config_factory);
    $subscriber
      ->sanitizeName($event);
    
    $this
      ->assertSame($filename, $event
      ->getFilename());
    $this
      ->assertSame(FALSE, $event
      ->isSecurityRename());
  }
  
  public function provideFilenamesNoMunge() {
    return [
      
      '.php is not munged when it would be rejected' => [
        'foo.php.php',
        'jpg',
      ],
      '.php is not munged when it would be rejected and filename contains null byte character' => [
        'foo.' . chr(0) . 'php.php',
        'jpg',
      ],
      'extension less files are not munged when they would be rejected' => [
        'foo',
        'jpg',
      ],
      'dot files are not munged when they would be rejected' => [
        '.htaccess',
        'jpg png',
      ],
    ];
  }
}