Loading src/Plugin/DataField/FieldFormatter/FileUriFormatter.php +2 −1 Changes for src/Plugin/DataField/FieldFormatter/FileUriFormatter.php: 2 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -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)) { Loading tests/src/Kernel/DataFieldFileNameEscapingTest.php 0 → 100644 +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('<img src=x onerror', $output); } } Loading
src/Plugin/DataField/FieldFormatter/FileUriFormatter.php +2 −1 Changes for src/Plugin/DataField/FieldFormatter/FileUriFormatter.php: 2 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -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)) { Loading
tests/src/Kernel/DataFieldFileNameEscapingTest.php 0 → 100644 +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('<img src=x onerror', $output); } }