Commit 3f459914 authored by Joseph Olstad's avatar Joseph Olstad
Browse files

Security Advisory fix for XSS, sanitize user suppled SVG content before saving...

Security Advisory fix for XSS, sanitize user suppled SVG content before saving to the public file system. Added tests
parent 74a4d114
Loading
Loading
Loading
Loading
Loading
+2 −1
Changes for src/Plugin/DataField/FieldFormatter/FileUriFormatter.php: 2 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -109,8 +109,9 @@ class FileUriFormatter implements DataFieldFormatterInterface, ContainerFactoryP
    $fieldName = $file->getFilename();
    $ext = pathinfo($fieldName, PATHINFO_EXTENSION);

    $title = new FormattableMarkup('<i class="bi bi-filetype-@ext"></i> ' . $fieldName, [
    $title = new FormattableMarkup('<i class="bi bi-filetype-@ext"></i> @name', [
      '@ext' => strtolower($ext),
      '@name' => $fieldName,
    ]);
    $usage = $this->fileUsage->listUsage($file);
    if (empty($usage)) {
+102 −0
Changes for tests/src/Kernel/DataFieldFileNameEscapingTest.php: 102 added lines, 0 removed lines.
Original line number Diff line number Diff line
<?php

declare(strict_types=1);

namespace Drupal\Tests\datafield\Kernel;

use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\file\Entity\File;
use Drupal\KernelTests\KernelTestBase;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;

/**
 * Tests that the File URI formatter escapes the file name on output.
 *
 * @group datafield
 */
#[RunTestsInSeparateProcesses]
class DataFieldFileNameEscapingTest extends KernelTestBase {

  /**
   * {@inheritdoc}
   */
  protected static $modules = [
    'system', 'user', 'field', 'file', 'node', 'text', 'filter', 'datafield',
  ];

  /**
   * {@inheritdoc}
   */
  protected function setUp(): void {
    parent::setUp();
    $this->installEntitySchema('user');
    $this->installEntitySchema('file');
    $this->installEntitySchema('node');
    $this->installSchema('file', ['file_usage']);
    $this->installConfig(['field', 'node', 'filter']);

    NodeType::create(['type' => 'article', 'name' => 'Article'])->save();
    FieldStorageConfig::create([
      'field_name' => 'field_data',
      'entity_type' => 'node',
      'type' => 'data_field',
      'settings' => [
        'columns' => [
          'value' => [
            'name' => 'value', 'type' => 'file', 'max_length' => 255,
            'size' => 'normal', 'unsigned' => FALSE, 'precision' => 10,
            'scale' => 2, 'datetime_type' => 'date',
          ],
        ],
      ],
    ])->save();
    FieldConfig::create([
      'field_name' => 'field_data',
      'entity_type' => 'node',
      'bundle' => 'article',
      'label' => 'Data',
      'settings' => [
        'field_settings' => [
          'value' => ['label' => 'Value'],
        ],
      ],
    ])->save();
  }

  /**
   * The file name is escaped when rendered by the File URI formatter.
   */
  public function testFileNameIsEscaped(): void {
    // A file name may legitimately contain HTML metacharacters; the formatter
    // must escape them on output rather than emit them as live markup.
    $name = 'y<img src=x onerror=alert(1)>.txt';
    $file = File::create([
      'uri' => 'public://example.txt',
      'filename' => $name,
      'filemime' => 'text/plain',
      'filesize' => 1,
      'status' => 1,
    ]);
    $file->save();

    $node = Node::create([
      'type' => 'article',
      'title' => 'Test',
      'field_data' => ['value' => $file->id()],
    ]);
    $node->save();

    $build = $node->get('field_data')->view([
      'type' => 'data_field_unformatted_list',
      'settings' => ['formatter_settings' => ['value' => ['format_type' => 'file_uri']]],
    ]);
    $output = (string) $this->container->get('renderer')->renderInIsolation($build);

    $this->assertStringNotContainsString('<img src=x onerror', $output);
    $this->assertStringContainsString('&lt;img src=x onerror', $output);
  }

}