[TASK] Migrate Redirect Url Validator into own class
[Packages/TYPO3.CMS.git] / typo3 / sysext / felogin / Classes / Validation / RedirectUrlValidator.php
1 <?php
2 declare(strict_types = 1);
3
4 namespace TYPO3\CMS\Felogin\Validation;
5
6 /*
7 * This file is part of the TYPO3 CMS project.
8 *
9 * It is free software; you can redistribute it and/or modify it under
10 * the terms of the GNU General Public License, either version 2
11 * of the License, or any later version.
12 *
13 * For the full copyright and license information, please read the
14 * LICENSE.txt file that was distributed with this source code.
15 *
16 * The TYPO3 project - inspiring people to share!
17 */
18
19 use Psr\Log\LoggerAwareInterface;
20 use Psr\Log\LoggerAwareTrait;
21 use TYPO3\CMS\Core\Exception\SiteNotFoundException;
22 use TYPO3\CMS\Core\Site\SiteFinder;
23 use TYPO3\CMS\Core\Utility\GeneralUtility;
24
25 /**
26 * Used to check if a referrer or a redirect URL is valid to be used as within Frontend Logins
27 * for redirects.
28 * @internal for now as it might get adopted for further streamlining against other validation paradigms
29 */
30 class RedirectUrlValidator implements LoggerAwareInterface
31 {
32 use LoggerAwareTrait;
33
34 /**
35 * @var SiteFinder
36 */
37 protected $siteFinder;
38
39 /**
40 * @var int
41 */
42 protected $pageId;
43
44 /**
45 * @param SiteFinder|null $siteFinder
46 * @param int $currentPageId
47 */
48 public function __construct(?SiteFinder $siteFinder, int $currentPageId)
49 {
50 $this->siteFinder = $siteFinder ?? GeneralUtility::makeInstance(SiteFinder::class);
51 $this->pageId = $currentPageId;
52 }
53
54 /**
55 * Checks if a given URL is valid / properly sanitized and/or the domain is known to TYPO3.
56 *
57 * @param string $value
58 * @return bool
59 */
60 public function isValid(string $value): bool
61 {
62 if ($value === '') {
63 return false;
64 }
65 // Validate the URL
66 if ($this->isRelativeUrl($value) || $this->isInCurrentDomain($value) || $this->isInLocalDomain($value)) {
67 return true;
68 }
69 // URL is not allowed
70 $this->logger->warning('Url "' . $value . '" was not accepted.');
71 return false;
72 }
73
74 /**
75 * Determines whether the URL is on the current host and belongs to the
76 * current TYPO3 installation. The scheme part is ignored in the comparison.
77 *
78 * @param string $url URL to be checked
79 * @return bool Whether the URL belongs to the current TYPO3 installation
80 */
81 protected function isInCurrentDomain(string $url): bool
82 {
83 $urlWithoutSchema = preg_replace('#^https?://#', '', $url);
84 $siteUrlWithoutSchema = preg_replace('#^https?://#', '', GeneralUtility::getIndpEnv('TYPO3_SITE_URL'));
85 return strpos($urlWithoutSchema . '/', GeneralUtility::getIndpEnv('HTTP_HOST') . '/') === 0
86 && strpos($urlWithoutSchema, $siteUrlWithoutSchema) === 0;
87 }
88
89 /**
90 * Determines whether the URL matches a domain known to TYPO3.
91 *
92 * @param string $url Absolute URL which needs to be checked
93 * @return bool Whether the URL is considered to be local
94 */
95 protected function isInLocalDomain(string $url): bool
96 {
97 if (!GeneralUtility::isValidUrl($url)) {
98 return false;
99 }
100 $parsedUrl = parse_url($url);
101 if ($parsedUrl['scheme'] === 'http' || $parsedUrl['scheme'] === 'https') {
102 $host = $parsedUrl['host'];
103 try {
104 $site = $this->siteFinder->getSiteByPageId($this->pageId);
105 return $site->getBase()->getHost() === $host;
106 } catch (SiteNotFoundException $e) {
107 // nothing found
108 }
109 }
110 return false;
111 }
112
113 /**
114 * Determines whether the URL is relative to the current TYPO3 installation.
115 *
116 * @param string $url URL which needs to be checked
117 * @return bool Whether the URL is considered to be relative
118 */
119 protected function isRelativeUrl($url): bool
120 {
121 $url = GeneralUtility::sanitizeLocalUrl($url);
122 if (!empty($url)) {
123 $parsedUrl = @parse_url($url);
124 if ($parsedUrl !== false && !isset($parsedUrl['scheme']) && !isset($parsedUrl['host'])) {
125 // If the relative URL starts with a slash, we need to check if it's within the current site path
126 return $parsedUrl['path'][0] !== '/' || GeneralUtility::isFirstPartOfStr($parsedUrl['path'], GeneralUtility::getIndpEnv('TYPO3_SITE_PATH'));
127 }
128 }
129 return false;
130 }
131 }