From a2d1389339ed9f802b812bca0ba5f4a8fe610620 Mon Sep 17 00:00:00 2001
From: Benni Mack <benni@typo3.org>
Date: Mon, 3 Jul 2023 19:25:38 +0200
Subject: [PATCH] [BUGFIX] Avoid fatal error with invalid soft reference parser
 links

When a link href is empty or invalid, TYPO3 now does not
crash anymore.

Resolves: #100958
Releases: main, 12.4
Change-Id: I768d43b72a3abd55af06cd9750ac8a400bc41d63
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/79732
Reviewed-by: Benni Mack <benni@typo3.org>
Tested-by: core-ci <typo3@b13.com>
Tested-by: Benni Mack <benni@typo3.org>
---
 .../backend/Classes/Form/Element/LinkElement.php   | 12 +++++-------
 .../SoftReference/TypolinkSoftReferenceParser.php  | 14 +++++++-------
 .../TypolinkTagSoftReferenceParser.php             | 10 +++++-----
 .../LinkHandling/LegacyLinkNotationConverter.php   |  6 ++++++
 .../TypoLinkSoftReferenceParserTest.php            | 11 +++++++++++
 .../TypoLinkTagSoftReferenceParserTest.php         | 13 +++++++++++++
 .../Classes/Typolink/LegacyLinkBuilder.php         |  4 ++--
 7 files changed, 49 insertions(+), 21 deletions(-)

diff --git a/typo3/sysext/backend/Classes/Form/Element/LinkElement.php b/typo3/sysext/backend/Classes/Form/Element/LinkElement.php
index 9a410567e40d..a65996046e1e 100644
--- a/typo3/sysext/backend/Classes/Form/Element/LinkElement.php
+++ b/typo3/sysext/backend/Classes/Form/Element/LinkElement.php
@@ -356,20 +356,19 @@ class LinkElement extends AbstractFormElement
                 break;
             case LinkService::TYPE_EMAIL:
                 $data = [
-                    'text' => $linkData['email'],
+                    'text' => $linkData['email'] ?? '',
                     'icon' => $this->iconFactory->getIcon('content-elements-mailform', Icon::SIZE_SMALL)->render(),
                 ];
                 break;
             case LinkService::TYPE_URL:
                 $data = [
-                    'text' => $linkData['url'],
+                    'text' => $linkData['url'] ?? '',
                     'icon' => $this->iconFactory->getIcon('apps-pagetree-page-shortcut-external', Icon::SIZE_SMALL)->render(),
 
                 ];
                 break;
             case LinkService::TYPE_FILE:
-                /** @var File $file */
-                $file = $linkData['file'];
+                $file = $linkData['file'] ?? null;
                 if ($file instanceof File) {
                     $data = [
                         'text' => $file->getPublicUrl(),
@@ -378,8 +377,7 @@ class LinkElement extends AbstractFormElement
                 }
                 break;
             case LinkService::TYPE_FOLDER:
-                /** @var Folder $folder */
-                $folder = $linkData['folder'];
+                $folder = $linkData['folder'] ?? null;
                 if ($folder instanceof Folder) {
                     $data = [
                         'text' => $folder->getPublicUrl(),
@@ -415,7 +413,7 @@ class LinkElement extends AbstractFormElement
                 break;
             case LinkService::TYPE_UNKNOWN:
                 $data = [
-                    'text' => $linkData['file'],
+                    'text' => $linkData['file'] ?? $linkData['url'] ?? '',
                     'icon' => $this->iconFactory->getIcon('actions-link', Icon::SIZE_SMALL)->render(),
                 ];
                 break;
diff --git a/typo3/sysext/core/Classes/DataHandling/SoftReference/TypolinkSoftReferenceParser.php b/typo3/sysext/core/Classes/DataHandling/SoftReference/TypolinkSoftReferenceParser.php
index 6a5d3999bc93..489b13d3c10d 100644
--- a/typo3/sysext/core/Classes/DataHandling/SoftReference/TypolinkSoftReferenceParser.php
+++ b/typo3/sysext/core/Classes/DataHandling/SoftReference/TypolinkSoftReferenceParser.php
@@ -168,7 +168,7 @@ class TypolinkSoftReferenceParser extends AbstractSoftReferenceParser
                 $elements[$tokenID . ':' . $idx]['subst'] = [
                     'type' => 'string',
                     'tokenID' => $tokenID,
-                    'tokenValue' => $tLP['email'],
+                    'tokenValue' => (string)($tLP['email'] ?? ''),
                 ];
                 // Output content will be the token instead:
                 $content = '{softref:' . $tokenID . '}';
@@ -178,7 +178,7 @@ class TypolinkSoftReferenceParser extends AbstractSoftReferenceParser
                 $elements[$tokenID . ':' . $idx]['subst'] = [
                     'type' => 'string',
                     'tokenID' => $tokenID,
-                    'tokenValue' => $tLP['telephone'],
+                    'tokenValue' => (string)($tLP['telephone'] ?? ''),
                 ];
                 // Output content will be the token instead:
                 $content = '{softref:' . $tokenID . '}';
@@ -188,7 +188,7 @@ class TypolinkSoftReferenceParser extends AbstractSoftReferenceParser
                 $elements[$tokenID . ':' . $idx]['subst'] = [
                     'type' => 'external',
                     'tokenID' => $tokenID,
-                    'tokenValue' => $tLP['url'],
+                    'tokenValue' => (string)($tLP['url'] ?? ''),
                 ];
                 // Output content will be the token instead:
                 $content = '{softref:' . $tokenID . '}';
@@ -218,7 +218,7 @@ class TypolinkSoftReferenceParser extends AbstractSoftReferenceParser
                             'type' => 'db',
                             'recordRef' => 'sys_file:' . $linkHandlerValue,
                             'tokenID' => $tokenID,
-                            'tokenValue' => $tLP['identifier'],
+                            'tokenValue' => (string)$tLP['identifier'],
                         ];
                         // Output content will be the token instead:
                         $content = '{softref:' . $tokenID . '}';
@@ -240,7 +240,7 @@ class TypolinkSoftReferenceParser extends AbstractSoftReferenceParser
                         'type' => 'db',
                         'recordRef' => 'pages:' . $tLP['pageuid'],
                         'tokenID' => $tokenID,
-                        'tokenValue' => $tLP['pageuid'],
+                        'tokenValue' => (string)$tLP['pageuid'],
                     ];
                 }
                 // Add type if applicable
@@ -260,7 +260,7 @@ class TypolinkSoftReferenceParser extends AbstractSoftReferenceParser
                             'type' => 'db',
                             'recordRef' => 'tt_content:' . $tLP['anchor'],
                             'tokenID' => $newTokenID,
-                            'tokenValue' => $tLP['anchor'],
+                            'tokenValue' => (string)$tLP['anchor'],
                         ];
                     } else {
                         // Anchor is a hardcoded string
@@ -273,7 +273,7 @@ class TypolinkSoftReferenceParser extends AbstractSoftReferenceParser
                     'type' => 'db',
                     'recordRef' => $tLP['table'] . ':' . $tLP['uid'],
                     'tokenID' => $tokenID,
-                    'tokenValue' => $content,
+                    'tokenValue' => (string)$content,
                 ];
 
                 $content = '{softref:' . $tokenID . '}';
diff --git a/typo3/sysext/core/Classes/DataHandling/SoftReference/TypolinkTagSoftReferenceParser.php b/typo3/sysext/core/Classes/DataHandling/SoftReference/TypolinkTagSoftReferenceParser.php
index 6e9982321ff5..007ef2e6f99c 100644
--- a/typo3/sysext/core/Classes/DataHandling/SoftReference/TypolinkTagSoftReferenceParser.php
+++ b/typo3/sysext/core/Classes/DataHandling/SoftReference/TypolinkTagSoftReferenceParser.php
@@ -59,9 +59,9 @@ class TypolinkTagSoftReferenceParser extends AbstractSoftReferenceParser
                         $elements[$key]['matchString'] = $foundValue;
                         $elements[$key]['subst'] = [
                             'type' => 'db',
-                            'recordRef' => 'pages:' . $linkDetails['pageuid'],
+                            'recordRef' => 'pages:' . ($linkDetails['pageuid'] ?? 0),
                             'tokenID' => $token,
-                            'tokenValue' => $linkDetails['pageuid'],
+                            'tokenValue' => $linkDetails['pageuid'] ?? '',
                         ];
                         if (isset($pageAndAnchorMatches[2]) && $pageAndAnchorMatches[2] !== '') {
                             // Anchor is assumed to point to a content elements:
@@ -90,7 +90,7 @@ class TypolinkTagSoftReferenceParser extends AbstractSoftReferenceParser
                         $elements[$key]['subst'] = [
                             'type' => 'external',
                             'tokenID' => $token,
-                            'tokenValue' => $linkDetails['url'],
+                            'tokenValue' => (string)($linkDetails['url'] ?? ''),
                         ];
                     } elseif ($linkDetails['type'] === LinkService::TYPE_EMAIL) {
                         $token = $this->makeTokenID((string)$key);
@@ -99,7 +99,7 @@ class TypolinkTagSoftReferenceParser extends AbstractSoftReferenceParser
                         $elements[$key]['subst'] = [
                             'type' => 'string',
                             'tokenID' => $token,
-                            'tokenValue' => $linkDetails['email'],
+                            'tokenValue' => (string)($linkDetails['email'] ?? ''),
                         ];
                     } elseif ($linkDetails['type'] === LinkService::TYPE_TELEPHONE) {
                         $token = $this->makeTokenID((string)$key);
@@ -108,7 +108,7 @@ class TypolinkTagSoftReferenceParser extends AbstractSoftReferenceParser
                         $elements[$key]['subst'] = [
                             'type' => 'string',
                             'tokenID' => $token,
-                            'tokenValue' => $linkDetails['telephone'],
+                            'tokenValue' => (string)($linkDetails['telephone'] ?? ''),
                         ];
                     }
                 } catch (\Exception $e) {
diff --git a/typo3/sysext/core/Classes/LinkHandling/LegacyLinkNotationConverter.php b/typo3/sysext/core/Classes/LinkHandling/LegacyLinkNotationConverter.php
index e76e166b7870..1aa96c66a54c 100644
--- a/typo3/sysext/core/Classes/LinkHandling/LegacyLinkNotationConverter.php
+++ b/typo3/sysext/core/Classes/LinkHandling/LegacyLinkNotationConverter.php
@@ -83,6 +83,12 @@ class LegacyLinkNotationConverter
             // Check for link-handler keyword
             [$linkHandlerKeyword, $linkHandlerValue] = explode(':', $linkParameter, 2);
             $result['type'] = strtolower(trim($linkHandlerKeyword));
+            if ($linkHandlerValue === '') {
+                return [
+                    'type' => LinkService::TYPE_UNKNOWN,
+                    'url' => $linkParameter,
+                ];
+            }
             $result['url'] = $linkParameter;
             $result['value'] = $linkHandlerValue;
             if ($result['type'] === LinkService::TYPE_RECORD) {
diff --git a/typo3/sysext/core/Tests/Unit/DataHandling/SoftReference/TypoLinkSoftReferenceParserTest.php b/typo3/sysext/core/Tests/Unit/DataHandling/SoftReference/TypoLinkSoftReferenceParserTest.php
index c38fc76571ca..98c382917abb 100644
--- a/typo3/sysext/core/Tests/Unit/DataHandling/SoftReference/TypoLinkSoftReferenceParserTest.php
+++ b/typo3/sysext/core/Tests/Unit/DataHandling/SoftReference/TypoLinkSoftReferenceParserTest.php
@@ -134,6 +134,17 @@ final class TypoLinkSoftReferenceParserTest extends AbstractSoftReferenceParserT
                     ],
                 ],
             ],
+            'link with invalid content' => [
+                [
+                    'content' => 'Email: andrew@example.com',
+                    'elementKey' => '8695f308356bcca1acac2749152a44a9:0',
+                    'matchString' => 'Email: andrew@example.com',
+                    'error' => 'Couldn\'t decide typolink mode.',
+                ],
+                [
+                    'error' => 'Couldn\'t decide typolink mode.',
+                ],
+            ],
         ];
     }
 
diff --git a/typo3/sysext/core/Tests/Unit/DataHandling/SoftReference/TypoLinkTagSoftReferenceParserTest.php b/typo3/sysext/core/Tests/Unit/DataHandling/SoftReference/TypoLinkTagSoftReferenceParserTest.php
index 1c3e1113ac2d..2c96f03814fc 100644
--- a/typo3/sysext/core/Tests/Unit/DataHandling/SoftReference/TypoLinkTagSoftReferenceParserTest.php
+++ b/typo3/sysext/core/Tests/Unit/DataHandling/SoftReference/TypoLinkTagSoftReferenceParserTest.php
@@ -120,6 +120,19 @@ final class TypoLinkTagSoftReferenceParserTest extends AbstractSoftReferencePars
                     ],
                 ],
             ],
+            'link with invalid content' => [
+                [
+                    'content' => '<p><a href="Email: hans@example.com">Click here</a></p>',
+                    'elementKey' => 1,
+                    'matchString' => '<a href="Email: hans@example.com">',
+                ],
+                [
+                    'subst' => [
+                        'type' => 'string',
+                        'tokenValue' => '',
+                    ],
+                ],
+            ],
         ];
     }
 
diff --git a/typo3/sysext/frontend/Classes/Typolink/LegacyLinkBuilder.php b/typo3/sysext/frontend/Classes/Typolink/LegacyLinkBuilder.php
index 79dfd3434737..50fb56538afe 100644
--- a/typo3/sysext/frontend/Classes/Typolink/LegacyLinkBuilder.php
+++ b/typo3/sysext/frontend/Classes/Typolink/LegacyLinkBuilder.php
@@ -28,7 +28,7 @@ class LegacyLinkBuilder extends AbstractTypolinkBuilder
     public function build(array &$linkDetails, string $linkText, string $target, array $conf): LinkResultInterface
     {
         $tsfe = $this->getTypoScriptFrontendController();
-        if ($linkDetails['file']) {
+        if ($linkDetails['file'] ?? false) {
             $linkDetails['type'] = LinkService::TYPE_FILE;
             $linkLocation = $linkDetails['file'];
             // Setting title if blank value to link
@@ -37,7 +37,7 @@ class LegacyLinkBuilder extends AbstractTypolinkBuilder
             $url = $linkLocation;
             $url = $this->forceAbsoluteUrl($url, $conf);
             $target = $target ?: $this->resolveTargetAttribute($conf, 'fileTarget');
-        } elseif ($linkDetails['url']) {
+        } elseif ($linkDetails['url'] ?? false) {
             $linkDetails['type'] = LinkService::TYPE_URL;
             $target = $target ?: $this->resolveTargetAttribute($conf, 'extTarget');
             $linkText = $this->encodeFallbackLinkTextIfLinkTextIsEmpty($linkText, $linkDetails['url']);
-- 
GitLab