‪TYPO3CMS  ‪main
DefaultSanitizerBuilderTest.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=1);
4 
5 /*
6  * This file is part of the TYPO3 CMS project.
7  *
8  * It is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU General Public License, either version 2
10  * of the License, or any later version.
11  *
12  * For the full copyright and license information, please read the
13  * LICENSE.txt file that was distributed with this source code.
14  *
15  * The TYPO3 project - inspiring people to share!
16  */
17 
19 
20 use PHPUnit\Framework\Attributes\DataProvider;
21 use PHPUnit\Framework\Attributes\Test;
22 use Psr\Log\LogLevel;
29 use TYPO3\HtmlSanitizer\Behavior;
30 use TYPO3\HtmlSanitizer\Sanitizer;
31 use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
32 
33 final class ‪DefaultSanitizerBuilderTest extends FunctionalTestCase
34 {
35  protected bool ‪$initializeDatabase = false;
36 
38  'LOG' => [
39  'TYPO3' => [
40  'HtmlSanitizer' => [
41  'writerConfiguration' => [
42  LogLevel::DEBUG => [
43  DummyWriter::class => [],
44  ],
45  ],
46  ],
47  ],
48  ],
49  ];
50 
51  protected function ‪tearDown(): void
52  {
53  parent::tearDown();
55  }
56 
57  public static function ‪isSanitizedDataProvider(): array
58  {
59  return [
60  '#010' => [
61  '<unknown unknown="unknown">value</unknown>',
62  '&lt;unknown unknown="unknown"&gt;value&lt;/unknown&gt;',
63  ],
64  '#011' => [
65  '<div class="nested"><unknown unknown="unknown">value</unknown></div>',
66  '<div class="nested">&lt;unknown unknown="unknown"&gt;value&lt;/unknown&gt;</div>',
67  ],
68  '#012' => [
69  '&lt;script&gt;alert(1)&lt;/script&gt;',
70  '&lt;script&gt;alert(1)&lt;/script&gt;',
71  ],
72  // @todo bug in https://github.com/Masterminds/html5-php/issues
73  // '#013' => [
74  // '<strong>Given that x < y and y > z...</strong>',
75  // '<strong>Given that x &lt; y and y &gt; z...</strong>',
76  // ],
77  '#020' => [
78  '<div unknown="unknown">value</div>',
79  '<div>value</div>',
80  ],
81  '#030' => [
82  '<div class="class">value</div>',
83  '<div class="class">value</div>',
84  ],
85  '#031' => [
86  '<div data-value="value">value</div>',
87  '<div data-value="value">value</div>',
88  ],
89  '#032' => [
90  '<div data-bool>value</div>',
91  '<div data-bool>value</div>',
92  ],
93  '#040' => [
94  '<img src="mailto:noreply@typo3.org" onerror="alert(1)">',
95  '',
96  ],
97  '#041' => [
98  '<img src="https://typo3.org/logo.svg" onerror="alert(1)">',
99  '<img src="https://typo3.org/logo.svg">',
100  ],
101  '#042' => [
102  '<img src="http://typo3.org/logo.svg" onerror="alert(1)">',
103  '<img src="http://typo3.org/logo.svg">',
104  ],
105  '#043' => [
106  '<img src="/typo3.org/logo.svg" onerror="alert(1)">',
107  '<img src="/typo3.org/logo.svg">',
108  ],
109  '#044' => [
110  '<img src="typo3.org/logo.svg" onerror="alert(1)">',
111  '<img src="typo3.org/logo.svg">',
112  ],
113  '#045' => [
114  '<img src="//typo3.org/logo.svg" onerror="alert(1)">',
115  '',
116  ],
117  '#050' => [
118  '<a href="https://typo3.org/" role="button">value</a>',
119  '<a href="https://typo3.org/" role="button">value</a>',
120  ],
121  '#051' => [
122  '<a href="ssh://example.org/" role="button">value</a>',
123  '<a role="button">value</a>',
124  ],
125  '#052' => [
126  '<a href="javascript:alert(1)" role="button">value</a>',
127  '<a role="button">value</a>',
128  ],
129  '#053' => [
130  '<a href="data:text/html;..." role="button">value</a>',
131  '<a role="button">value</a>',
132  ],
133  '#054' => [
134  '<a href="t3://page?uid=1" role="button">value</a>',
135  '<a href="t3://page?uid=1" role="button">value</a>',
136  ],
137  '#055' => [
138  '<a href="tel:123456789" role="button">value</a>',
139  '<a href="tel:123456789" role="button">value</a>',
140  ],
141  '#090' => [
142  '<p data-bool><span data-bool><strong data-bool>value</strong></span></p>',
143  '<p data-bool><span data-bool><strong data-bool>value</strong></span></p>',
144  ],
145  // @todo `style` used in Introduction Package, inline CSS should be removed
146  '#810' => [
147  '<span style="color: orange">value</span>',
148  '<span style="color: orange">value</span>',
149  ],
150  '#912' => [
151  '<!---><p>',
152  '<!---&gt;&lt;p&gt;-->',
153  ],
154  '#913' => [
155  '<!---!><p>',
156  '<!---!&gt;&lt;p&gt;-->',
157  ],
158  '#941' => [
159  '<?xml >s<img src=x onerror=alert(1)> ?>',
160  '&lt;?xml &gt;s&lt;img src=x onerror=alert(1)&gt; ?&gt;',
161  ],
162  ];
163  }
164 
165  #[DataProvider('isSanitizedDataProvider')]
166  #[Test]
167  public function ‪isSanitized(string $payload, string $expectation): void
168  {
169  $factory = new ‪SanitizerBuilderFactory();
170  $builder = $factory->build('default');
171  $sanitizer = $builder->build();
172  self::assertSame($expectation, $sanitizer->sanitize($payload));
173  }
174 
175  #[Test]
176  public function ‪behaviorIsCachedInMemory(): void
177  {
178  $default = new ‪DefaultSanitizerBuilder();
179  $defaultSanitizer = $default->build();
180  $defaultBehavior = $this->‪resolveBehaviorFromSanitizer($defaultSanitizer);
181 
182  self::assertSame(
183  $defaultBehavior,
184  $this->‪resolveBehaviorFromSanitizer($default->build()),
185  'in-memory caching failed for same scope DefaultSanitizerBuilder'
186  );
187 
188  $extended = new ‪ExtendedSanitizerBuilder();
189  $extendedSanitizer = $extended->build();
190  $extendedBehavior = $this->‪resolveBehaviorFromSanitizer($extendedSanitizer);
191 
192  self::assertSame(
193  $extendedBehavior,
194  $this->‪resolveBehaviorFromSanitizer($extended->build()),
195  'in-memory caching failed for same scope ExtendedSanitizerBuilder'
196  );
197 
198  self::assertNotSame(
199  $defaultBehavior,
200  $extendedBehavior,
201  'in-memory cache violation for different scopes'
202  );
203  }
204 
205  #[Test]
206  public function ‪incidentIsLogged(): void
207  {
208  $trace = bin2hex(random_bytes(8));
209  $sanitizer = (new ‪DefaultSanitizerBuilder())->build();
210  $sanitizer->sanitize('<script>alert(1)</script>', new ‪SanitizerInitiator($trace));
211  $logItemDataExpectation = [
212  'behavior' => 'default',
213  'nodeType' => 1,
214  'nodeName' => 'script',
215  'initiator' => $trace,
216  ];
217  $logItem = end(‪DummyWriter::$logs);
218  self::assertInstanceOf(LogRecord::class, $logItem);
219  self::assertSame($logItemDataExpectation, $logItem->getData());
220  self::assertSame('TYPO3.HtmlSanitizer.Visitor.CommonVisitor', $logItem->getComponent());
221  }
222 
223  private function ‪resolveBehaviorFromSanitizer(Sanitizer $sanitizer): Behavior
224  {
225  $visitor = (new \ReflectionObject($sanitizer))
226  ->getProperty('visitors')
227  ->getValue($sanitizer)[0];
228  return (new \ReflectionObject($visitor))
229  ->getProperty('behavior')
230  ->getValue($visitor);
231  }
232 }
‪TYPO3\CMS\Core\Html\DefaultSanitizerBuilder
Definition: DefaultSanitizerBuilder.php:35
‪TYPO3\CMS\Core\Tests\Functional\Html\DefaultSanitizerBuilderTest\$configurationToUseInTestInstance
‪array $configurationToUseInTestInstance
Definition: DefaultSanitizerBuilderTest.php:37
‪TYPO3\CMS\Core\Tests\Functional\Html\DefaultSanitizerBuilderTest\isSanitized
‪isSanitized(string $payload, string $expectation)
Definition: DefaultSanitizerBuilderTest.php:167
‪TYPO3\CMS\Core\Tests\Functional\Html\Fixtures\ExtendedSanitizerBuilder
Definition: ExtendedSanitizerBuilder.php:26
‪TYPO3\CMS\Core\Tests\Functional\Html\DefaultSanitizerBuilderTest\tearDown
‪tearDown()
Definition: DefaultSanitizerBuilderTest.php:51
‪TYPO3\CMS\Core\Tests\Functional\Html\DefaultSanitizerBuilderTest\incidentIsLogged
‪incidentIsLogged()
Definition: DefaultSanitizerBuilderTest.php:206
‪TYPO3\CMS\Core\Tests\Functional\Html\DefaultSanitizerBuilderTest\isSanitizedDataProvider
‪static isSanitizedDataProvider()
Definition: DefaultSanitizerBuilderTest.php:57
‪TYPO3\CMS\Core\Log\LogRecord
Definition: LogRecord.php:24
‪TYPO3\CMS\Core\Tests\Functional\Html
Definition: DefaultSanitizerBuilderTest.php:18
‪TYPO3\CMS\Core\Tests\Functional\Html\DefaultSanitizerBuilderTest\behaviorIsCachedInMemory
‪behaviorIsCachedInMemory()
Definition: DefaultSanitizerBuilderTest.php:176
‪TYPO3\CMS\Core\Html\SanitizerInitiator
Definition: SanitizerInitiator.php:28
‪TYPO3\CMS\Core\Tests\Functional\Html\DefaultSanitizerBuilderTest\resolveBehaviorFromSanitizer
‪resolveBehaviorFromSanitizer(Sanitizer $sanitizer)
Definition: DefaultSanitizerBuilderTest.php:223
‪TYPO3\CMS\Core\Tests\Functional\Fixtures\Log\DummyWriter\$logs
‪static array $logs
Definition: DummyWriter.php:26
‪TYPO3\CMS\Core\Tests\Functional\Html\DefaultSanitizerBuilderTest
Definition: DefaultSanitizerBuilderTest.php:34
‪TYPO3\CMS\Core\Tests\Functional\Html\DefaultSanitizerBuilderTest\$initializeDatabase
‪bool $initializeDatabase
Definition: DefaultSanitizerBuilderTest.php:35
‪TYPO3\CMS\Core\Html\SanitizerBuilderFactory
Definition: SanitizerBuilderFactory.php:37
‪TYPO3\CMS\Core\Tests\Functional\Fixtures\Log\DummyWriter
Definition: DummyWriter.php:24