‪TYPO3CMS  11.5
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 Psr\Log\LogLevel;
28 use TYPO3\HtmlSanitizer\Behavior;
29 use TYPO3\HtmlSanitizer\Sanitizer;
30 use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
31 
32 class ‪DefaultSanitizerBuilderTest extends FunctionalTestCase
33 {
37  protected ‪$initializeDatabase = false;
38 
40  'LOG' => [
41  'TYPO3' => [
42  'HtmlSanitizer' => [
43  'writerConfiguration' => [
44  LogLevel::DEBUG => [
45  DummyWriter::class => [],
46  ],
47  ],
48  ],
49  ],
50  ],
51  ];
52 
53  protected function ‪tearDown(): void
54  {
55  parent::tearDown();
57  }
58 
59  public static function ‪isSanitizedDataProvider(): array
60  {
61  return [
62  '#010' => [
63  '<unknown unknown="unknown">value</unknown>',
64  '&lt;unknown unknown="unknown"&gt;value&lt;/unknown&gt;',
65  ],
66  '#011' => [
67  '<div class="nested"><unknown unknown="unknown">value</unknown></div>',
68  '<div class="nested">&lt;unknown unknown="unknown"&gt;value&lt;/unknown&gt;</div>',
69  ],
70  '#012' => [
71  '&lt;script&gt;alert(1)&lt;/script&gt;',
72  '&lt;script&gt;alert(1)&lt;/script&gt;',
73  ],
74  // @todo bug in https://github.com/Masterminds/html5-php/issues
75  // '#013' => [
76  // '<strong>Given that x < y and y > z...</strong>',
77  // '<strong>Given that x &lt; y and y &gt; z...</strong>',
78  // ],
79  '#020' => [
80  '<div unknown="unknown">value</div>',
81  '<div>value</div>',
82  ],
83  '#030' => [
84  '<div class="class">value</div>',
85  '<div class="class">value</div>',
86  ],
87  '#031' => [
88  '<div data-value="value">value</div>',
89  '<div data-value="value">value</div>',
90  ],
91  '#032' => [
92  '<div data-bool>value</div>',
93  '<div data-bool>value</div>',
94  ],
95  '#040' => [
96  '<img src="mailto:noreply@typo3.org" onerror="alert(1)">',
97  '',
98  ],
99  '#041' => [
100  '<img src="https://typo3.org/logo.svg" onerror="alert(1)">',
101  '<img src="https://typo3.org/logo.svg">',
102  ],
103  '#042' => [
104  '<img src="http://typo3.org/logo.svg" onerror="alert(1)">',
105  '<img src="http://typo3.org/logo.svg">',
106  ],
107  '#043' => [
108  '<img src="/typo3.org/logo.svg" onerror="alert(1)">',
109  '<img src="/typo3.org/logo.svg">',
110  ],
111  '#044' => [
112  '<img src="typo3.org/logo.svg" onerror="alert(1)">',
113  '<img src="typo3.org/logo.svg">',
114  ],
115  '#045' => [
116  '<img src="//typo3.org/logo.svg" onerror="alert(1)">',
117  '',
118  ],
119  '#050' => [
120  '<a href="https://typo3.org/" role="button">value</a>',
121  '<a href="https://typo3.org/" role="button">value</a>',
122  ],
123  '#051' => [
124  '<a href="ssh://example.org/" role="button">value</a>',
125  '<a role="button">value</a>',
126  ],
127  '#052' => [
128  '<a href="javascript:alert(1)" role="button">value</a>',
129  '<a role="button">value</a>',
130  ],
131  '#053' => [
132  '<a href="data:text/html;..." role="button">value</a>',
133  '<a role="button">value</a>',
134  ],
135  '#054' => [
136  '<a href="t3://page?uid=1" role="button">value</a>',
137  '<a href="t3://page?uid=1" role="button">value</a>',
138  ],
139  '#055' => [
140  '<a href="tel:123456789" role="button">value</a>',
141  '<a href="tel:123456789" role="button">value</a>',
142  ],
143  '#056' => [
144  // config.spamProtectEmailAddresses = [n]
145  // @deprecated Only used in f:uri.email view-helper, which is deprecated and will be removed in TYPO3 v12.0
146  '<a href="javascript:linkTo_UnCryptMailto(%27ocknvq%2CkphqBrtczku%5C%2Fmkghgt0fg%27);">email(at)domain.tld</a>',
147  '<a href="javascript:linkTo_UnCryptMailto(%27ocknvq%2CkphqBrtczku%5C%2Fmkghgt0fg%27);">email(at)domain.tld</a>',
148  ],
149  '#057' => [
150  // config.spamProtectEmailAddresses = ascii
151  '<a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#115;&#111;&#109;&#101;&#46;&#98;&#111;&#100;&#121;&#64;&#116;&#101;&#115;&#116;&#46;&#116;&#121;&#112;&#111;&#51;&#46;&#111;&#114;&#103;">some.body(at)test.typo3(dot)org</a>',
152  // HTML entity encoding is not really a "protection", `Masterminds/html5-php` per default
153  // decodes those entities, which is good to have normalized attr values
154  '<a href="mailto:some.body@test.typo3.org">some.body(at)test.typo3(dot)org</a>',
155  ],
156  '#090' => [
157  '<p data-bool><span data-bool><strong data-bool>value</strong></span></p>',
158  '<p data-bool><span data-bool><strong data-bool>value</strong></span></p>',
159  ],
160  // @todo `style` used in Introduction Package, inline CSS should be removed
161  '#810' => [
162  '<span style="color: orange">value</span>',
163  '<span style="color: orange">value</span>',
164  ],
165  '#912' => [
166  '<!---><p>',
167  '<!---&gt;&lt;p&gt;-->',
168  ],
169  '#913' => [
170  '<!---!><p>',
171  '<!---!&gt;&lt;p&gt;-->',
172  ],
173  '#941' => [
174  '<?xml >s<img src=x onerror=alert(1)> ?>',
175  '&lt;?xml &gt;s&lt;img src=x onerror=alert(1)&gt; ?&gt;',
176  ],
177  ];
178  }
179 
186  public function ‪isSanitized(string $payload, string $expectation): void
187  {
188  $factory = new ‪SanitizerBuilderFactory();
189  $builder = $factory->build('default');
190  $sanitizer = $builder->build();
191  self::assertSame($expectation, $sanitizer->sanitize($payload));
192  }
193 
197  public function ‪behaviorIsCachedInMemory(): void
198  {
199  $default = GeneralUtility::makeInstance(DefaultSanitizerBuilder::class);
200  $defaultSanitizer = $default->build();
201  $defaultBehavior = $this->‪resolveBehaviorFromSanitizer($defaultSanitizer);
202 
203  self::assertSame(
204  $defaultBehavior,
205  $this->‪resolveBehaviorFromSanitizer($default->build()),
206  'in-memory caching failed for same scope DefaultSanitizerBuilder'
207  );
208 
209  $extended = GeneralUtility::makeInstance(ExtendedSanitizerBuilder::class);
210  $extendedSanitizer = $extended->build();
211  $extendedBehavior = $this->‪resolveBehaviorFromSanitizer($extendedSanitizer);
212 
213  self::assertSame(
214  $extendedBehavior,
215  $this->‪resolveBehaviorFromSanitizer($extended->build()),
216  'in-memory caching failed for same scope ExtendedSanitizerBuilder'
217  );
218 
219  self::assertNotSame(
220  $defaultBehavior,
221  $extendedBehavior,
222  'in-memory cache violation for different scopes'
223  );
224  }
225 
229  public function ‪incidentIsLogged(): void
230  {
231  $trace = bin2hex(random_bytes(8));
232  $sanitizer = (new DefaultSanitizerBuilder())->build();
233  $sanitizer->sanitize('<script>alert(1)</script>', new SanitizerInitiator($trace));
234  $logItemDataExpectation = [
235  'behavior' => 'default',
236  'nodeType' => 1,
237  'nodeName' => 'script',
238  'initiator' => $trace,
239  ];
240  $logItem = end(‪DummyWriter::$logs);
241  self::assertInstanceOf(LogRecord::class, $logItem);
242  self::assertSame($logItemDataExpectation, $logItem->getData());
243  self::assertSame('TYPO3.HtmlSanitizer.Visitor.CommonVisitor', $logItem->getComponent());
244  }
245 
246  private function ‪resolveBehaviorFromSanitizer(Sanitizer $sanitizer): Behavior
247  {
248  $visitorsProp = (new \ReflectionObject($sanitizer))->getProperty('visitors');
249  $visitorsProp->setAccessible(true);
250  $visitor = $visitorsProp->getValue($sanitizer)[0];
251 
252  $behaviorProp = (new \ReflectionObject($visitor))->getProperty('behavior');
253  $behaviorProp->setAccessible(true);
254  return $behaviorProp->getValue($visitor);
255  }
256 }
‪TYPO3\CMS\Core\Html\DefaultSanitizerBuilder
Definition: DefaultSanitizerBuilder.php:32
‪TYPO3\CMS\Core\Tests\Functional\Html\DefaultSanitizerBuilderTest\isSanitized
‪isSanitized(string $payload, string $expectation)
Definition: DefaultSanitizerBuilderTest.php:185
‪TYPO3\CMS\Core\Tests\Functional\Html\Fixtures\ExtendedSanitizerBuilder
Definition: ExtendedSanitizerBuilder.php:26
‪TYPO3\CMS\Core\Tests\Functional\Html\DefaultSanitizerBuilderTest\tearDown
‪tearDown()
Definition: DefaultSanitizerBuilderTest.php:52
‪TYPO3\CMS\Core\Tests\Functional\Html\DefaultSanitizerBuilderTest\incidentIsLogged
‪incidentIsLogged()
Definition: DefaultSanitizerBuilderTest.php:228
‪TYPO3\CMS\Core\Tests\Functional\Html\DefaultSanitizerBuilderTest\isSanitizedDataProvider
‪static isSanitizedDataProvider()
Definition: DefaultSanitizerBuilderTest.php:58
‪TYPO3\CMS\Core\Tests\Functional\Html\DefaultSanitizerBuilderTest\$configurationToUseInTestInstance
‪$configurationToUseInTestInstance
Definition: DefaultSanitizerBuilderTest.php:38
‪TYPO3\CMS\Core\Log\LogRecord
Definition: LogRecord.php:22
‪TYPO3\CMS\Core\Tests\Functional\Html
Definition: DefaultSanitizerBuilderTest.php:18
‪TYPO3\CMS\Core\Tests\Functional\Html\DefaultSanitizerBuilderTest\behaviorIsCachedInMemory
‪behaviorIsCachedInMemory()
Definition: DefaultSanitizerBuilderTest.php:196
‪TYPO3\CMS\Core\Html\SanitizerInitiator
Definition: SanitizerInitiator.php:25
‪TYPO3\CMS\Core\Tests\Functional\Html\DefaultSanitizerBuilderTest\resolveBehaviorFromSanitizer
‪resolveBehaviorFromSanitizer(Sanitizer $sanitizer)
Definition: DefaultSanitizerBuilderTest.php:245
‪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:33
‪TYPO3\CMS\Core\Tests\Functional\Html\DefaultSanitizerBuilderTest\$initializeDatabase
‪bool $initializeDatabase
Definition: DefaultSanitizerBuilderTest.php:36
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:50
‪TYPO3\CMS\Core\Html\SanitizerBuilderFactory
Definition: SanitizerBuilderFactory.php:35
‪TYPO3\CMS\Core\Tests\Functional\Fixtures\Log\DummyWriter
Definition: DummyWriter.php:24