Commit 9d2ce55e authored by Oliver Hader's avatar Oliver Hader Committed by Benni Mack
Browse files

[BUGFIX] Adjust default behavior of HTML sanitization in parseFunc

As a result of TYPO3-CORE-SA-2021-013, new `htmlSanitize` behavior -
when invoking `ContentObjectRenderer::parseFunc` - is enabled per
default, in case it was not declared otherwise. That also happened
when no processing configuration was given (or could be resolved).
Without having any configuration, it was obviously not possible to
disable `htmlSanitize`.

Fluid's `HtmlViewHelper` can be used with an empty `parseFuncTSPath`
(e.g. `<f:format.html parseFuncTSPath="">`) - due to missing (empty)
configuration, sanitization was enabled per default in `parseFunc`.

With this change, property `htmlSanitize` either needs to be enabled
or disabled explicitly - otherwise deprecation logs will be generated,
if not given, the fall-back behavior is inferred from new feature flag
`security.frontend.htmlSanitizeParseFuncDefault`.

Invoking `ContentObjectRenderer::parseFunc` without any configuration
behaves like before TYPO3-CORE-SA-2021-013 was applied - it just does
not process anything.

Resolves: #94786
Releases: master, 11.3, 10.4, 9.5
Change-Id: I4aee54d712ce4758f6c9c2e64a43f80b6c076406
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/70588


Tested-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
Tested-by: core-ci's avatarcore-ci <typo3@b13.com>
Reviewed-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
parent 73f3f102
......@@ -1395,9 +1395,9 @@ class RteHtmlParser extends HtmlParser implements LoggerAwareInterface
{
$features = GeneralUtility::makeInstance(Features::class);
// either `htmlSanitize = null` or `htmlSanitize = false`
// or feature flag `rte.htmlSanitize` is explicitly disabled
// or feature flag `security.backend.htmlSanitizeRte` is disabled
if (array_key_exists('htmlSanitize', $configuration) && empty($configuration['htmlSanitize'])
|| !$features->isFeatureEnabled('rte.htmlSanitize')
|| !$features->isFeatureEnabled('security.backend.htmlSanitizeRte')
) {
return $content;
}
......
......@@ -73,11 +73,12 @@ return [
'folderCreateMask' => '2775',
'features' => [
'redirects.hitCount' => false,
'rte.htmlSanitize' => true,
'unifiedPageTranslationHandling' => false,
'TypoScript.strictSyntax' => true,
'simplifiedControllerActionDispatching' => false,
'security.frontend.keepSessionDataOnLogout' => false,
'security.frontend.htmlSanitizeParseFuncDefault' => true,
'security.backend.htmlSanitizeRte' => false,
'security.backend.enforceReferrer' => true,
'newTranslationServer' => false,
],
......
......@@ -218,9 +218,6 @@ SYS:
redirects.hitCount:
type: bool
description: 'If on, and if extension "redirects" is loaded, each performed redirect is counted and last hit time is logged to the database.'
rte.htmlSanitize:
type: bool
description: 'If on, rich-text content persisted in backend is processed with HTML Sanitizer to purge cross-site scripting from markup'
unifiedPageTranslationHandling:
type: bool
description: 'If on, TCA configuration for pages_language_overlay is never loaded and the database table "pages_language_overlay" is not created by core. Enable this feature if no extensions fiddles with table pages_language_overlay to have a slightly quicker system with less deprecation log entries.'
......@@ -233,6 +230,12 @@ SYS:
security.frontend.keepSessionDataOnLogout:
type: bool
description: 'If on, session data is kept in an anonymous session after frontend user logged out. As this is a potential security risk, it is recommended to disable this option if not specifically needed.'
security.frontend.htmlSanitizeParseFuncDefault:
type: bool
description: 'If on, HTML sanitizer is enabled in parseFunc during frontend rendering as fall-back, in case it has not define more specifically'
security.backend.htmlSanitizeRte:
type: bool
description: 'If on, rich-text content persisted in backend is processed with HTML Sanitizer to purge cross-site scripting from markup'
security.backend.enforceReferrer:
type: bool
description: 'If on, HTTP referrer headers are enforced for backend and install tool requests to mitigate
......
......@@ -169,8 +169,8 @@ can be adjusted in a similar way, e.g. in :file:`Configuration/Processing.yaml`.
# disable individually per use-case
# htmlSanitize: false
Sanitization for persisting data can be disabled globally using corresponding
feature flag :php:`$GLOBALS['TYPO3_CONF_VARS']['SYS']['features']['rte.htmlSanitize']`.
Sanitization for persisting data can be needs to be enabled globally using corresponding
feature flag :php:`$GLOBALS['TYPO3_CONF_VARS']['SYS']['features']['security.backend.htmlSanitizeRte']`.
Debugging & Logging
......
......@@ -245,8 +245,9 @@ class SecurityTest extends FunctionalTestCase
* @test
* @dataProvider crossSiteScriptingDataProvider
*/
public function markupIsSanitizedForContentBodytext(string $input, array $expectations): void
public function markupIsSanitizedForContentBodytextWithHtmlSanitizerEnabled(string $input, array $expectations): void
{
$GLOBALS['TYPO3_CONF_VARS']['SYS']['features']['security.backend.htmlSanitizeRte'] = true;
$newIds = $this->actionService->createNewRecord('tt_content', 1, [
'CType' => 'text',
'bodytext' => $input,
......@@ -276,7 +277,7 @@ class SecurityTest extends FunctionalTestCase
*/
public function markupIsSanitizedForContentBodytextWithHtmlSanitizerDisabled(string $input, array $expectations): void
{
$GLOBALS['TYPO3_CONF_VARS']['SYS']['features']['rte.htmlSanitize'] = false;
$GLOBALS['TYPO3_CONF_VARS']['SYS']['features']['security.backend.htmlSanitizeRte'] = false;
$newIds = $this->actionService->createNewRecord('tt_content', 1, [
'CType' => 'text',
'bodytext' => $input,
......
......@@ -106,7 +106,7 @@ class RteHtmlParserTest extends UnitTestCase
public function hrTagCorrectlyTransformedOnWayToDataBase($content, $expectedResult)
{
// @todo Explicitly disabled HTML Sanitizer (since it is based on HTML5)
$GLOBALS['TYPO3_CONF_VARS']['SYS']['features']['rte.htmlSanitize'] = false;
$GLOBALS['TYPO3_CONF_VARS']['SYS']['features']['security.backend.htmlSanitizeRte'] = false;
$subject = new RteHtmlParser();
$thisConfig = ['proc.' => $this->procOptions];
$this->assertEquals($expectedResult, $subject->RTE_transform($content, [], 'db', $thisConfig));
......@@ -184,7 +184,7 @@ class RteHtmlParserTest extends UnitTestCase
public function hrTagCorrectlyTransformedOnWayToDatabaseAndBackToRte($content, $expectedResult)
{
// @todo Explicitly disabled HTML Sanitizer (since it is based on HTML5)
$GLOBALS['TYPO3_CONF_VARS']['SYS']['features']['rte.htmlSanitize'] = false;
$GLOBALS['TYPO3_CONF_VARS']['SYS']['features']['security.backend.htmlSanitizeRte'] = false;
$subject = new RteHtmlParser();
$thisConfig = ['proc.' => $this->procOptions];
$this->assertEquals($expectedResult, $subject->RTE_transform($subject->RTE_transform($content, [], 'db', $thisConfig), [], 'rte', $thisConfig));
......
......@@ -97,7 +97,7 @@ class HtmlViewHelper extends AbstractViewHelper
*/
public function initializeArguments()
{
$this->registerArgument('parseFuncTSPath', 'string', ' path to TypoScript parseFunc setup.', false, 'lib.parseFunc_RTE');
$this->registerArgument('parseFuncTSPath', 'string', 'Path to TypoScript parseFunc setup', false, 'lib.parseFunc_RTE');
}
/**
......
......@@ -6,5 +6,8 @@
<f:case value="empty-parseFuncTSPath">
<f:format.html parseFuncTSPath="">{payload}</f:format.html>
</f:case>
<f:case value="disable-htmlSanitize">
<f:format.html>{payload}</f:format.html>
</f:case>
</f:switch>
</f:spaceless>
......@@ -33,6 +33,7 @@ class SecureHtmlRenderingTest extends FunctionalTestCase
private const TYPE_PLAIN = 'plain';
private const TYPE_EMPTY_PARSEFUNCTSPATH = 'empty-parseFuncTSPath';
private const TYPE_DISABLE_HTML_SANITIZE = 'disable-htmlSanitize';
private const ENCRYPTION_KEY = '4408d27a916d51e624b69af3554f516dbab61037a9f7b9fd6f81b4d3bedeccb6';
private const TYPO3_CONF_VARS = [
......@@ -124,7 +125,7 @@ class SecureHtmlRenderingTest extends FunctionalTestCase
parent::tearDown();
}
public function parseFuncAvoidCrossSiteScriptingDataProvider(): array
public function defaultParseFuncRteAvoidsCrossSiteScriptingDataProvider(): array
{
return [
'#01' => [
......@@ -162,12 +163,61 @@ class SecureHtmlRenderingTest extends FunctionalTestCase
* @param string $payload
* @param string$expectation
* @test
* @dataProvider parseFuncAvoidCrossSiteScriptingDataProvider
* @dataProvider defaultParseFuncRteAvoidsCrossSiteScriptingDataProvider
*/
public function parseFuncAvoidCrossSiteScripting(string $payload, string $expectation)
public function defaultParseFuncRteAvoidCrossSiteScripting(string $payload, string $expectation)
{
$instructions = [
$this->createTextContentObject($payload),
$this->createTextContentObjectWithDefaultParseFuncRteInstruction($payload),
];
$response = $this->invokeFrontendRendering(...$instructions);
self::assertSame($expectation, (string)$response->getBody());
}
public function customParseFuncAvoidsCrossSiteScriptingDataProvider(): array
{
return [
'#01' => [
'01: <script>alert(1)</script>',
'<p>01: &lt;script&gt;alert(1)&lt;/script&gt;</p>',
],
'#02' => [
'02: <unknown a="a" b="b">value</unknown>',
'<p>02: &lt;unknown a="a" b="b"&gt;value&lt;/unknown&gt;</p>',
],
'#03' => [
'03: <img img="img" alt="alt" onerror="alert(1)">',
'<p>03: <img alt="alt"></p>',
],
'#04' => [
'04: <img src="img" alt="alt" onerror="alert(1)">',
'<p>04: <img src="img" alt="alt"></p>',
],
'#05' => [
'05: <img/src="img"/onerror="alert(1)">',
'<p>05: <img src="img"></p>',
],
'#06' => [
'06: <strong>Given that x < y and y > z...</strong>',
'<p>06: <strong>Given that x y and y &gt; z...</strong></p>',
],
'#07' => [
'07: <a href="t3://page?uid=1000" target="_blank" rel="noreferrer" class="button" role="button" onmouseover="alert(1)">TYPO3</a>',
'<p>07: <a href="/" target="_blank" rel="noreferrer" class="button" role="button">TYPO3</a></p>',
],
];
}
/**
* @param string $payload
* @param string$expectation
* @test
* @dataProvider customParseFuncAvoidsCrossSiteScriptingDataProvider
*/
public function customParseFuncAvoidCrossSiteScripting(string $payload, string $expectation)
{
$instructions = [
$this->createTextContentObjectWithCustomParseFuncInstruction($payload),
];
$response = $this->invokeFrontendRendering(...$instructions);
self::assertSame($expectation, (string)$response->getBody());
......@@ -184,7 +234,12 @@ class SecureHtmlRenderingTest extends FunctionalTestCase
'#01 ' . self::TYPE_EMPTY_PARSEFUNCTSPATH => [
self::TYPE_EMPTY_PARSEFUNCTSPATH,
'01: <script>alert(1)</script>',
'01: &lt;script&gt;alert(1)&lt;/script&gt;',
'01: <script>alert(1)</script>',
],
'#01 ' . self::TYPE_DISABLE_HTML_SANITIZE => [
self::TYPE_DISABLE_HTML_SANITIZE,
'01: <script>alert(1)</script>',
'<p>01: &lt;script&gt;alert(1)&lt;/script&gt;</p>',
],
'#03 ' . self::TYPE_PLAIN => [
self::TYPE_PLAIN,
......@@ -194,7 +249,12 @@ class SecureHtmlRenderingTest extends FunctionalTestCase
'#03 ' . self::TYPE_EMPTY_PARSEFUNCTSPATH => [
self::TYPE_EMPTY_PARSEFUNCTSPATH,
'03: <img img="img" alt="alt" onerror="alert(1)">',
'03: <img alt="alt">',
'03: <img img="img" alt="alt" onerror="alert(1)">',
],
'#03 ' . self::TYPE_DISABLE_HTML_SANITIZE => [
self::TYPE_DISABLE_HTML_SANITIZE,
'03: <img img="img" alt="alt" onerror="alert(1)">',
'<p>03: <img img="img" alt="alt" onerror="alert(1)"></p>',
],
'#07 ' . self::TYPE_PLAIN => [
self::TYPE_PLAIN,
......@@ -205,7 +265,12 @@ class SecureHtmlRenderingTest extends FunctionalTestCase
self::TYPE_EMPTY_PARSEFUNCTSPATH,
'07: <a href="t3://page?uid=1000" target="_blank" rel="noreferrer" class="button" role="button" onmouseover="alert(1)">TYPO3</a>',
// expected, with empty parseFunc configuration internal link URN is not resolved
'07: <a href="t3://page?uid=1000" target="_blank" rel="noreferrer" class="button" role="button">TYPO3</a>',
'07: <a href="t3://page?uid=1000" target="_blank" rel="noreferrer" class="button" role="button" onmouseover="alert(1)">TYPO3</a>',
],
'#07 ' . self::TYPE_DISABLE_HTML_SANITIZE => [
self::TYPE_DISABLE_HTML_SANITIZE,
'07: <a href="t3://page?uid=1000" target="_blank" rel="noreferrer" class="button" role="button" onmouseover="alert(1)">TYPO3</a>',
'<p>07: <a href="/" target="_blank" rel="noreferrer" class="button" role="button" onmouseover="alert(1)">TYPO3</a></p>',
],
'#08 ' . self::TYPE_PLAIN => [
self::TYPE_PLAIN,
......@@ -215,7 +280,12 @@ class SecureHtmlRenderingTest extends FunctionalTestCase
'#08 ' . self::TYPE_EMPTY_PARSEFUNCTSPATH => [
self::TYPE_EMPTY_PARSEFUNCTSPATH,
'08: <meta whatever="whatever">',
'08: &lt;meta whatever="whatever"&gt;',
'08: <meta whatever="whatever">',
],
'#08 ' . self::TYPE_DISABLE_HTML_SANITIZE => [
self::TYPE_DISABLE_HTML_SANITIZE,
'08: <meta whatever="whatever">',
'<p>08: <meta whatever="whatever"></p>',
],
// `sdfield` is in `styles.content.allowTags` constant
'#09 ' . self::TYPE_PLAIN => [
......@@ -226,7 +296,12 @@ class SecureHtmlRenderingTest extends FunctionalTestCase
'#09 ' . self::TYPE_EMPTY_PARSEFUNCTSPATH => [
self::TYPE_EMPTY_PARSEFUNCTSPATH,
'09: <sdfield onmouseover="alert(1)">',
'09: &lt;sdfield onmouseover="alert(1)"&gt;&lt;/sdfield&gt;',
'09: <sdfield onmouseover="alert(1)">',
],
'#09 ' . self::TYPE_DISABLE_HTML_SANITIZE => [
self::TYPE_DISABLE_HTML_SANITIZE,
'09: <sdfield onmouseover="alert(1)">',
'<p>09: <sdfield onmouseover="alert(1)"></p>',
],
];
}
......@@ -243,6 +318,9 @@ class SecureHtmlRenderingTest extends FunctionalTestCase
$instructions = [
$this->createFluidTemplateContentObject($type, $payload),
];
if ($type === self::TYPE_DISABLE_HTML_SANITIZE) {
$instructions[] = $this->createDisableHtmlSanitizeInstruction();
}
$response = $this->invokeFrontendRendering(...$instructions);
self::assertSame($expectation, trim((string)$response->getBody(), "\n"));
}
......@@ -288,8 +366,9 @@ class SecureHtmlRenderingTest extends FunctionalTestCase
]);
}
private function createTextContentObject(string $value): TypoScriptInstruction
private function createTextContentObjectWithDefaultParseFuncRteInstruction(string $value): TypoScriptInstruction
{
// default configuration as shipped in ext:fluid_styled_content
return (new TypoScriptInstruction(TemplateService::class))
->withTypoScript([
'page.' => [
......@@ -302,6 +381,59 @@ class SecureHtmlRenderingTest extends FunctionalTestCase
]);
}
private function createTextContentObjectWithCustomParseFuncInstruction(string $value): TypoScriptInstruction
{
// basically considered "insecure setup"
// + no explicit htmlSanitize
// + no HTMLparser + HTMLparser.htmlSpecialChars
return (new TypoScriptInstruction(TemplateService::class))
->withTypoScript([
'page.' => [
'10' => 'TEXT',
'10.' => [
'value' => $value,
'parseFunc.' => [
'allowTags' => 'a,img,sdfield',
'tags.' => [
'a' => 'TEXT',
'a.' => [
'current' => 1,
'typolink' => [
'parameter.' => [
'data' => 'parameters:href'
],
'title.' => [
'data' => 'parameters:title'
],
'ATagParams.' => [
'data' => 'parameters:allParams'
],
],
],
],
'nonTypoTagStdWrap.' => [
'encapsLines.' => [
'nonWrappedTag' => 'p',
],
],
],
],
],
]);
}
private function createDisableHtmlSanitizeInstruction(): TypoScriptInstruction
{
return (new TypoScriptInstruction(TemplateService::class))
->withTypoScript([
'lib.' => [
'parseFunc_RTE.' => [
'htmlSanitize' => '0',
],
],
]);
}
private function createFluidTemplateContentObject(string $type, string $payload): TypoScriptInstruction
{
return (new TypoScriptInstruction(TemplateService::class))
......
......@@ -20,6 +20,7 @@ use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use TYPO3\CMS\Core\Authentication\AbstractUserAuthentication;
use TYPO3\CMS\Core\Cache\CacheManager;
use TYPO3\CMS\Core\Configuration\Features;
use TYPO3\CMS\Core\Context\Context;
use TYPO3\CMS\Core\Context\LanguageAspect;
use TYPO3\CMS\Core\Core\Environment;
......@@ -4082,10 +4083,25 @@ class ContentObjectRenderer implements LoggerAwareInterface
$temp_conf = $this->mergeTSRef($temp_conf, 'parseFunc');
$conf = $temp_conf['parseFunc.'];
}
// early return, no processing in case no configuration is given
if (empty($conf)) {
// @deprecated Invoking ContentObjectRenderer::parseFunc without any configuration will trigger an exception in TYPO3 v12.0
trigger_error('Invoking ContentObjectRenderer::parseFunc without any configuration will trigger an exception in TYPO3 v12.0', E_USER_DEPRECATED);
return $theValue;
}
// Handle HTML sanitizer invocation
if (!isset($conf['htmlSanitize'])) {
// @deprecated Property htmlSanitize was not defined, but will be mandatory in TYPO3 v12.0
trigger_error('Property htmlSanitize was not defined, but will be mandatory in TYPO3 v12.0', E_USER_DEPRECATED);
$features = GeneralUtility::makeInstance(Features::class);
$conf['htmlSanitize'] = $features->isFeatureEnabled('security.frontend.htmlSanitizeParseFuncDefault');
}
$conf['htmlSanitize'] = (bool)$conf['htmlSanitize'];
// Process:
if ((string)($conf['externalBlocks'] ?? '') === '') {
$result = $this->_parseFunc($theValue, $conf);
if ($conf['htmlSanitize'] ?? true) {
if ($conf['htmlSanitize']) {
$result = $this->stdWrap_htmlSanitize($result, $conf['htmlSanitize.'] ?? []);
}
return $result;
......@@ -4169,8 +4185,8 @@ class ContentObjectRenderer implements LoggerAwareInterface
}
}
$result = implode('', $parts);
if ($conf['htmlSanitize'] ?? true) {
$result = $this->stdWrap_htmlSanitize($result, ['htmlSanitize.' => $conf['htmlSanitize.'] ?? []]);
if ($conf['htmlSanitize']) {
$result = $this->stdWrap_htmlSanitize($result, $conf['htmlSanitize.'] ?? []);
}
return $result;
}
......
......@@ -75,6 +75,8 @@ use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
*/
class ContentObjectRendererTest extends UnitTestCase
{
use ContentObjectRendererTestTrait;
/**
* @var bool Reset singletons created by subject
*/
......@@ -2529,220 +2531,6 @@ class ContentObjectRendererTest extends UnitTestCase
];
}
/**
* @return array
*/
protected function getLibParseFunc_RTE(): array
{
return [
'parseFunc' => '',
'parseFunc.' => [
'allowTags' => 'a, abbr, acronym, address, article, aside, b, bdo, big, blockquote, br, caption, center, cite, code, col, colgroup, dd, del, dfn, dl, div, dt, em, font, footer, header, h1, h2, h3, h4, h5, h6, hr, i, img, ins, kbd, label, li, link, meta, nav, ol, p, pre, q, samp, sdfield, section, small, span, strike, strong, style, sub, sup, table, thead, tbody, tfoot, td, th, tr, title, tt, u, ul, var',
'constants' => '1',
'denyTags' => '*',
'externalBlocks' => 'article, aside, blockquote, div, dd, dl, footer, header, nav, ol, section, table, ul, pre',
'externalBlocks.' => [
'article.' => [
'callRecursive' => '1',
'stripNL' => '1',
],
'aside.' => [
'callRecursive' => '1',
'stripNL' => '1',
],
'blockquote.' => [
'callRecursive' => '1',
'stripNL' => '1',
],
'dd.' => [
'callRecursive' => '1',
'stripNL' => '1',
],
'div.' => [
'callRecursive' => '1',
'stripNL' => '1',
],
'dl.' => [
'callRecursive' => '1',
'stripNL' => '1',
],
'footer.' => [
'callRecursive' => '1',
'stripNL' => '1',
],
'header.' => [
'callRecursive' => '1',
'stripNL' => '1',
],
'nav.' => [
'callRecursive' => '1',
'stripNL' => '1',
],
'ol.' => [
'callRecursive' => '1',
'stripNL' => '1',
],
'section.' => [
'callRecursive' => '1',
'stripNL' => '1',
],
'table.' => [
'HTMLtableCells' => '1',
'HTMLtableCells.' => [
'addChr10BetweenParagraphs' => '1',
'default.' => [
'stdWrap.' => [
'parseFunc' => '=< lib.parseFunc_RTE',
'parseFunc.' => [
'nonTypoTagStdWrap.' => [
'encapsLines.' => [
'nonWrappedTag' => '',
],
],
],
],
],
],
'stdWrap.' => [
'HTMLparser' => '1',
'HTMLparser.' => [
'keepNonMatchedTags' => '1',
'tags.' => [
'table.' => [
'fixAttrib.' => [
'class.' => [
'always' => '1',
'default' => 'contenttable',
'list' => 'contenttable',
],
],
],
],
],
],
'stripNL' => '1',
],
'ul.' => [
'callRecursive' => '1',
'stripNL' => '1',
],
],
'makelinks' => '1',
'makelinks.' => [
'http.' => [
'extTarget.' => [
'override' => '_blank',
],
'keep' => 'path',
],
],
'nonTypoTagStdWrap.' => [
'encapsLines.' => [
'addAttributes.' => [
'P.' => [
'class' => 'bodytext',
'class.' => [
'setOnly' => 'blank',
],
],
],
'encapsTagList' => 'p,pre,h1,h2,h3,h4,h5,h6,hr,dt,li',
'innerStdWrap_all.' => [
'ifBlank' => '&nbsp;',
],
'nonWrappedTag' => 'P',
'remapTag.' => [
'DIV' => 'P',
],
],
'HTMLparser' => '1',
'HTMLparser.' => [
'htmlSpecialChars' => '2',
'keepNonMatchedTags' => '1',
],
],
'sword' => '<span class="csc-sword">|</span>',
'tags.' => [
'link' => 'TEXT',
'link.' => [
'current' => '1',
'parseFunc.' => [
'constants' => '1',
],
'typolink.' => [
'directImageLink' => false,
'extTarget.' => [
'override' => '',
],
'parameter.' => [
'data' => 'parameters : allParams',
],
'target.' => [
'override' => '',
],
],
],
],
],
];
}
/**
* @return array
*/
public function _parseFuncReturnsCorrectHtmlDataProvider(): array