diff --git a/typo3/sysext/core/Classes/LinkHandling/UrlLinkHandler.php b/typo3/sysext/core/Classes/LinkHandling/UrlLinkHandler.php
index aa9e05cbc112..f8008fcbdfcb 100644
--- a/typo3/sysext/core/Classes/LinkHandling/UrlLinkHandler.php
+++ b/typo3/sysext/core/Classes/LinkHandling/UrlLinkHandler.php
@@ -54,6 +54,7 @@ protected function addHttpSchemeAsFallback(string $url): string
$scheme = parse_url($url, PHP_URL_SCHEME);
if (empty($scheme)) {
$url = 'http://' . $url;
+ // 'java{TAB}script:' is parsed as empty URL scheme, thus not ending up here
} elseif (in_array(strtolower($scheme), ['javascript', 'data'], true)) {
// deny using insecure scheme's like `javascript:` or `data:` as URL scheme
$url = '';
diff --git a/typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php b/typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php
index 2b56c7958ce4..2eff67239dd8 100644
--- a/typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php
+++ b/typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php
@@ -5623,7 +5623,7 @@ protected function resolveMixedLinkParameter($linkText, $mixedLinkParameter, &$c
// Resource was not found
return $linkText;
}
- } elseif (in_array(strtolower(trim($linkHandlerKeyword)), ['javascript', 'data'], true)) {
+ } elseif (in_array(strtolower(preg_replace('#\s|[[:cntrl:]]#', '', $linkHandlerKeyword)), ['javascript', 'data'], true)) {
// Disallow insecure scheme's like javascript: or data:
return $linkText;
} else {
diff --git a/typo3/sysext/frontend/Classes/Typolink/AbstractTypolinkBuilder.php b/typo3/sysext/frontend/Classes/Typolink/AbstractTypolinkBuilder.php
index 2695f3ef90f9..0ff4237e5928 100644
--- a/typo3/sysext/frontend/Classes/Typolink/AbstractTypolinkBuilder.php
+++ b/typo3/sysext/frontend/Classes/Typolink/AbstractTypolinkBuilder.php
@@ -102,6 +102,20 @@ protected function forceAbsoluteUrl(string $url, array $configuration): string
return $url;
}
+ /**
+ * Determines whether lib.parseFunc is defined.
+ *
+ * @return bool
+ */
+ protected function isLibParseFuncDefined(): bool
+ {
+ $configuration = $this->contentObjectRenderer->mergeTSRef(
+ ['parseFunc' => '< lib.parseFunc'],
+ 'parseFunc'
+ );
+ return !empty($configuration['parseFunc.']) && is_array($configuration['parseFunc.']);
+ }
+
/**
* Helper method to a fallback method parsing HTML out of it
*
@@ -111,10 +125,29 @@ protected function forceAbsoluteUrl(string $url, array $configuration): string
*/
protected function parseFallbackLinkTextIfLinkTextIsEmpty(string $originalLinkText, string $fallbackLinkText): string
{
- if ($originalLinkText === '') {
+ if ($originalLinkText !== '') {
+ return $originalLinkText;
+ }
+ if ($this->isLibParseFuncDefined()) {
return $this->contentObjectRenderer->parseFunc($fallbackLinkText, ['makelinks' => 0], '< lib.parseFunc');
}
- return $originalLinkText;
+ // encode in case `lib.parseFunc` is not configured
+ return $this->encodeFallbackLinkTextIfLinkTextIsEmpty($originalLinkText, $fallbackLinkText);
+ }
+
+ /**
+ * Helper method to a fallback method properly encoding HTML.
+ *
+ * @param string $originalLinkText the original string, if empty, the fallback link text
+ * @param string $fallbackLinkText the string to be used.
+ * @return string the final text
+ */
+ protected function encodeFallbackLinkTextIfLinkTextIsEmpty(string $originalLinkText, string $fallbackLinkText): string
+ {
+ if ($originalLinkText !== '') {
+ return $originalLinkText;
+ }
+ return htmlspecialchars($fallbackLinkText, ENT_QUOTES);
}
/**
diff --git a/typo3/sysext/frontend/Classes/Typolink/ExternalUrlLinkBuilder.php b/typo3/sysext/frontend/Classes/Typolink/ExternalUrlLinkBuilder.php
index efa447455e16..284566b30802 100644
--- a/typo3/sysext/frontend/Classes/Typolink/ExternalUrlLinkBuilder.php
+++ b/typo3/sysext/frontend/Classes/Typolink/ExternalUrlLinkBuilder.php
@@ -28,7 +28,7 @@ public function build(array &$linkDetails, string $linkText, string $target, arr
{
return [
$this->processUrl(UrlProcessorInterface::CONTEXT_EXTERNAL, htmlspecialchars_decode($linkDetails['url']), $conf),
- $this->parseFallbackLinkTextIfLinkTextIsEmpty($linkText, $linkDetails['url']),
+ $this->encodeFallbackLinkTextIfLinkTextIsEmpty($linkText, $linkDetails['url']),
$target ?: $this->resolveTargetAttribute($conf, 'extTarget', true, $this->getTypoScriptFrontendController()->extTarget)
];
}
diff --git a/typo3/sysext/frontend/Classes/Typolink/FileOrFolderLinkBuilder.php b/typo3/sysext/frontend/Classes/Typolink/FileOrFolderLinkBuilder.php
index 1c1c8d1f584e..b9a9da29373b 100644
--- a/typo3/sysext/frontend/Classes/Typolink/FileOrFolderLinkBuilder.php
+++ b/typo3/sysext/frontend/Classes/Typolink/FileOrFolderLinkBuilder.php
@@ -47,7 +47,7 @@ public function build(array &$linkDetails, string $linkText, string $target, arr
$linkLocation = '';
}
// Setting title if blank value to link
- $linkText = $this->parseFallbackLinkTextIfLinkTextIsEmpty($linkText, rawurldecode($linkLocation));
+ $linkText = $this->encodeFallbackLinkTextIfLinkTextIsEmpty($linkText, rawurldecode($linkLocation));
if (strpos($linkLocation, '/') !== 0
&& parse_url($linkLocation, PHP_URL_SCHEME) === null
) {
diff --git a/typo3/sysext/frontend/Classes/Typolink/LegacyLinkBuilder.php b/typo3/sysext/frontend/Classes/Typolink/LegacyLinkBuilder.php
index 12c2b1a622bb..c3dcfd300a5d 100644
--- a/typo3/sysext/frontend/Classes/Typolink/LegacyLinkBuilder.php
+++ b/typo3/sysext/frontend/Classes/Typolink/LegacyLinkBuilder.php
@@ -33,7 +33,7 @@ public function build(array &$linkDetails, string $linkText, string $target, arr
$linkDetails['type'] = LinkService::TYPE_FILE;
$linkLocation = $linkDetails['file'];
// Setting title if blank value to link
- $linkText = $this->parseFallbackLinkTextIfLinkTextIsEmpty($linkText, rawurldecode($linkLocation));
+ $linkText = $this->encodeFallbackLinkTextIfLinkTextIsEmpty($linkText, rawurldecode($linkLocation));
$linkLocation = (strpos($linkLocation, '/') !== 0 ? $tsfe->absRefPrefix : '') . $linkLocation;
$url = $this->processUrl(UrlProcessorInterface::CONTEXT_FILE, $linkLocation, $conf);
$url = $this->forceAbsoluteUrl($url, $conf);
@@ -41,7 +41,7 @@ public function build(array &$linkDetails, string $linkText, string $target, arr
} elseif ($linkDetails['url']) {
$linkDetails['type'] = LinkService::TYPE_URL;
$target = $target ?: $this->resolveTargetAttribute($conf, 'extTarget', true, $tsfe->extTarget);
- $linkText = $this->parseFallbackLinkTextIfLinkTextIsEmpty($linkText, $linkDetails['url']);
+ $linkText = $this->encodeFallbackLinkTextIfLinkTextIsEmpty($linkText, $linkDetails['url']);
$url = $this->processUrl(UrlProcessorInterface::CONTEXT_EXTERNAL, $linkDetails['url'], $conf);
} else {
throw new UnableToLinkException('Unknown link detected, so ' . $linkText . ' was not linked.', 1490990031, null, $linkText);
diff --git a/typo3/sysext/frontend/Tests/Functional/SiteHandling/Fixtures/TypoLinkGenerator.libParseFunc.typoscript b/typo3/sysext/frontend/Tests/Functional/SiteHandling/Fixtures/TypoLinkGenerator.libParseFunc.typoscript
new file mode 100644
index 000000000000..b20df026e4e3
--- /dev/null
+++ b/typo3/sysext/frontend/Tests/Functional/SiteHandling/Fixtures/TypoLinkGenerator.libParseFunc.typoscript
@@ -0,0 +1,23 @@
+lib.parseFunc {
+ makelinks = 1
+ makelinks {
+ http {
+ keep = path
+ extTarget = _blank
+ }
+ mailto {
+ keep = path
+ }
+ }
+ allowTags = good
+ denyTags = *
+ constants = 1
+ nonTypoTagStdWrap {
+ HTMLparser = 1
+ HTMLparser {
+ tags.good.allowedAttribs = 0
+ keepNonMatchedTags = 1
+ htmlSpecialChars = 2
+ }
+ }
+}
diff --git a/typo3/sysext/frontend/Tests/Functional/SiteHandling/Fixtures/TypoLinkScenario.xml b/typo3/sysext/frontend/Tests/Functional/SiteHandling/Fixtures/TypoLinkScenario.xml
index 8c9d63e8bf73..378d91fcf478 100644
--- a/typo3/sysext/frontend/Tests/Functional/SiteHandling/Fixtures/TypoLinkScenario.xml
+++ b/typo3/sysext/frontend/Tests/Functional/SiteHandling/Fixtures/TypoLinkScenario.xml
@@ -38,6 +38,61 @@