7ad592237f92a337dadfc877f8551725f1308640
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Http / NormalizedParams.php
1 <?php
2 declare(strict_types = 1);
3 namespace TYPO3\CMS\Core\Http;
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
18 use Psr\Http\Message\ServerRequestInterface;
19 use TYPO3\CMS\Core\Utility\GeneralUtility;
20
21 /**
22 * This class provides normalized server parameters in HTTP request context.
23 * It normalizes reverse proxy scenarios and various other web server specific differences
24 * of the native PSR-7 request object parameters (->getServerParams() / $GLOBALS['_SERVER']).
25 *
26 * An instance of this class is available as PSR-7 ServerRequestInterface attribute:
27 * $normalizedParams = $request->getAttribute('normalizedParams')
28 *
29 * This class substitutes the old GeneralUtility::getIndpEnv() method.
30 */
31 class NormalizedParams
32 {
33 /**
34 * Sanitized HTTP_HOST value
35 *
36 * host[:port]
37 *
38 * - www.domain.com
39 * - www.domain.com:443
40 * - 192.168.1.42:80
41 *
42 * @var string
43 */
44 protected $httpHost = '';
45
46 /**
47 * @var bool True if request has been done via HTTPS
48 */
49 protected $isHttps = false;
50
51 /**
52 * Sanitized HTTP_HOST with protocol
53 *
54 * scheme://host[:port]
55 *
56 * - https://www.domain.com
57 *
58 * @var string
59 */
60 protected $requestHost = '';
61
62 /**
63 * Host / domain part of HTTP_HOST, no port, no protocol
64 *
65 * - www.domain.com
66 * - 192.168.1.42
67 *
68 * @var string
69 */
70 protected $requestHostOnly = '';
71
72 /**
73 * Port of HTTP_HOST if given
74 *
75 * @var int
76 */
77 protected $requestPort = 0;
78
79 /**
80 * Entry script path of URI, without domain and without query parameters, with leading /
81 *
82 * [path_script]
83 *
84 * - /typo3/index.php
85 *
86 * @var string
87 */
88 protected $scriptName = '';
89
90 /**
91 * REQUEST URI without domain and scheme, with trailing slash
92 *
93 * [path][?[query]]
94 *
95 * - /index.php
96 * - /typo3/index.php/arg1/arg2/?arg1,arg2&p1=parameter1&p2[key]=value
97 *
98 * @var string
99 */
100 protected $requestUri = '';
101
102 /**
103 * REQUEST URI with scheme, host, port, path and query
104 *
105 * scheme://host[:[port]][path][?[query]]
106 *
107 * - http://www.domain.com/typo3/index.php?route=foo/bar&id=42
108 *
109 * @var string
110 */
111 protected $requestUrl = '';
112
113 /**
114 * REQUEST URI with scheme, host, port and path, but *without* query part
115 *
116 * scheme://host[:[port]][path_script]
117 *
118 * - http://www.domain.com/typo3/index.php
119 *
120 * @var string
121 */
122 protected $requestScript = '';
123
124 /**
125 * Full Uri with path, but without script name and query parts
126 *
127 * scheme://host[:[port]][path_dir]
128 *
129 * - http://www.domain.com/typo3/
130 *
131 * @var string
132 */
133 protected $requestDir = '';
134
135 /**
136 * True if request via a reverse proxy is detected
137 *
138 * @var bool
139 */
140 protected $isBehindReverseProxy = false;
141
142 /**
143 * IPv4 or IPv6 address of remote client with resolved proxy setup
144 *
145 * @var string
146 */
147 protected $remoteAddress = '';
148
149 /**
150 * Absolute server path to entry script on server filesystem
151 *
152 * - /var/www/typo3/index.php
153 *
154 * @var string
155 */
156 protected $scriptFilename = '';
157
158 /**
159 * Absolute server path to web document root without trailing slash
160 *
161 * - /var/www/typo3
162 *
163 * @var string
164 */
165 protected $documentRoot = '';
166
167 /**
168 * Website frontend URL.
169 * Note this is note "safe" if called from Backend since sys_domain and
170 * other factors are not taken into account.
171 *
172 * scheme://host[:[port]]/[path_dir]
173 *
174 * - https://www.domain.com/
175 * - https://www.domain.com/some/sub/dir/
176 *
177 * @var string
178 */
179 protected $siteUrl = '';
180
181 /**
182 * Path part to frontend, no domain, no protocol
183 *
184 * - /
185 * - /some/sub/dir/
186 *
187 * @var string
188 */
189 protected $sitePath = '';
190
191 /**
192 * Path to script, without sub path if TYPO3 is running in sub directory, without trailing slash
193 *
194 * - typo/index.php?id=42
195 * - index.php?id=42
196 *
197 * @var string
198 */
199 protected $siteScript = '';
200
201 /**
202 * Entry script path of URI, without domain and without query parameters, with leading /
203 * This is often not set at all.
204 * Will be deprecated later, use $scriptName instead as more reliable solution.
205 *
206 * [path_script]
207 *
208 * - /typo3/index.php
209 *
210 * @var string
211 */
212 protected $pathInfo = '';
213
214 /**
215 * HTTP_REFERER
216 * Will be deprecated later, use $request->getServerParams()['HTTP_REFERER'] instead
217 *
218 * scheme://host[:[port]][path]
219 *
220 * - https://www.domain.com/typo3/index.php?id=42
221 *
222 * @var string
223 */
224 protected $httpReferer = '';
225
226 /**
227 * HTTP_USER_AGENT
228 * Will be deprecated later, use $request->getServerParams()['HTTP_USER_AGENT'] instead
229 *
230 * - Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36
231 *
232 * @var string
233 */
234 protected $httpUserAgent = '';
235
236 /**
237 * HTTP_ACCEPT_ENCODING
238 * Will be deprecated later, use $request->getServerParams()['HTTP_ACCEPT_ENCODING'] instead
239 *
240 * - gzip, deflate
241 *
242 * @var string
243 */
244 protected $httpAcceptEncoding = '';
245
246 /**
247 * HTTP_ACCEPT_LANGUAGE
248 * Will be deprecated later, use $request->getServerParams()['HTTP_ACCEPT_LANGUAGE'] instead
249 *
250 * - de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7
251 *
252 * @var string
253 */
254 protected $httpAcceptLanguage = '';
255
256 /**
257 * REMOTE_HOST Resolved host name of REMOTE_ADDR if configured in web server
258 * Will be deprecated later, use $request->getServerParams()['REMOTE_HOST'] instead
259 *
260 * - www.clientDomain.com
261 *
262 * @var string
263 */
264 protected $remoteHost = '';
265
266 /**
267 * QUERY_STRING
268 * Will be deprecated later, use $request->getServerParams()['QUERY_STRING'] instead
269 *
270 * [query]
271 *
272 * - id=42&foo=bar
273 *
274 * @var string
275 */
276 protected $queryString = '';
277
278 /**
279 * Constructor calculates all values by incoming variables.
280 *
281 * This object is immutable.
282 *
283 * All determine*() "detail worker methods" in this class retrieve their dependencies
284 * to other properties as method arguments, they are static, stateless and have no
285 * dependency to $this. This ensures the chain of inter-property dependencies
286 * is visible by only looking at the construct() method.
287 *
288 * @param ServerRequestInterface $serverRequest Used to access $_SERVER
289 * @param array $typo3ConfVars $GLOBALS['TYPO3_CONF_VARS']
290 * @param string $pathThisScript Absolute server entry script path, usually found within Environment::getCurrentScript()
291 * @param string $pathSite Absolute server path to document root, Environment::getPublicPath()
292 */
293 public function __construct(ServerRequestInterface $serverRequest, array $typo3ConfVars, string $pathThisScript, string $pathSite)
294 {
295 $serverParams = $serverRequest->getServerParams();
296 $isBehindReverseProxy = $this->isBehindReverseProxy = self::determineIsBehindReverseProxy($serverParams, $typo3ConfVars);
297 $httpHost = $this->httpHost = self::determineHttpHost($serverParams, $typo3ConfVars, $isBehindReverseProxy);
298 $isHttps = $this->isHttps = self::determineHttps($serverParams, $typo3ConfVars);
299 $requestHost = $this->requestHost = ($isHttps ? 'https://' : 'http://') . $httpHost;
300 $requestHostOnly = $this->requestHostOnly = self::determineRequestHostOnly($httpHost);
301 $this->requestPort = self::determineRequestPort($httpHost, $requestHostOnly);
302 $scriptName = $this->scriptName = self::determineScriptName($serverParams, $typo3ConfVars, $isHttps, $isBehindReverseProxy);
303 $requestUri = $this->requestUri = self::determineRequestUri($serverParams, $typo3ConfVars, $isHttps, $scriptName, $isBehindReverseProxy);
304 $requestUrl = $this->requestUrl = $requestHost . $requestUri;
305 $this->requestScript = $requestHost . $scriptName;
306 $requestDir = $this->requestDir = $requestHost . GeneralUtility::dirname($scriptName) . '/';
307 $this->remoteAddress = self::determineRemoteAddress($serverParams, $typo3ConfVars, $isBehindReverseProxy);
308 $scriptFilename = $this->scriptFilename = $pathThisScript;
309 $this->documentRoot = self::determineDocumentRoot($scriptName, $scriptFilename);
310 $siteUrl = $this->siteUrl = self::determineSiteUrl($requestDir, $pathThisScript, $pathSite . '/');
311 $this->sitePath = self::determineSitePath($requestHost, $siteUrl);
312 $this->siteScript = self::determineSiteScript($requestUrl, $siteUrl);
313
314 // @deprecated Below variables can be fully deprecated as soon as core does not use them anymore
315 $this->pathInfo = $serverParams['PATH_INFO'] ?? '';
316 $this->httpReferer = $serverParams['HTTP_REFERER'] ?? '';
317 $this->httpUserAgent = $serverParams['HTTP_USER_AGENT'] ?? '';
318 $this->httpAcceptEncoding = $serverParams['HTTP_ACCEPT_ENCODING'] ?? '';
319 $this->httpAcceptLanguage = $serverParams['HTTP_ACCEPT_LANGUAGE'] ?? '';
320 $this->remoteHost = $serverParams['REMOTE_HOST'] ?? '';
321 $this->queryString = $serverParams['QUERY_STRING'] ?? '';
322 }
323
324 /**
325 * @return string Sanitized HTTP_HOST value host[:port]
326 */
327 public function getHttpHost(): string
328 {
329 return $this->httpHost;
330 }
331
332 /**
333 * @return bool True if client request has been done using HTTPS
334 */
335 public function isHttps(): bool
336 {
337 return $this->isHttps;
338 }
339
340 /**
341 * @return string Sanitized HTTP_HOST with protocol scheme://host[:port], eg. https://www.domain.com/
342 */
343 public function getRequestHost(): string
344 {
345 return $this->requestHost;
346 }
347
348 /**
349 * @return string Host / domain /IP only, eg. www.domain.com
350 */
351 public function getRequestHostOnly(): string
352 {
353 return $this->requestHostOnly;
354 }
355
356 /**
357 * @return int Requested port if given, eg. 8080 - often not explicitly given, then 0
358 */
359 public function getRequestPort(): int
360 {
361 return $this->requestPort;
362 }
363
364 /**
365 * @return string Script path part of URI, eg. 'typo3/index.php'
366 */
367 public function getScriptName(): string
368 {
369 return $this->scriptName;
370 }
371
372 /**
373 * @return string Request Uri without domain and protocol, eg. /index.php?id=42
374 */
375 public function getRequestUri(): string
376 {
377 return $this->requestUri;
378 }
379
380 /**
381 * @return string Full REQUEST_URI, eg. http://www.domain.com/typo3/index.php?route=foo/bar&id=42
382 */
383 public function getRequestUrl(): string
384 {
385 return $this->requestUrl;
386 }
387
388 /**
389 * @return string REQUEST URI without query part, eg. http://www.domain.com/typo3/index.php
390 */
391 public function getRequestScript(): string
392 {
393 return $this->requestScript;
394 }
395
396 /**
397 * @return string REQUEST URI without script file name and query parts, eg. http://www.domain.com/typo3/
398 */
399 public function getRequestDir(): string
400 {
401 return $this->requestDir;
402 }
403
404 /**
405 * @return bool True if request comes from a configured reverse proxy
406 */
407 public function isBehindReverseProxy(): bool
408 {
409 return $this->isBehindReverseProxy;
410 }
411
412 /**
413 * @return string Client IP
414 */
415 public function getRemoteAddress(): string
416 {
417 return $this->remoteAddress;
418 }
419
420 /**
421 * @return string Absolute entry script path on server, eg. /var/www/typo3/index.php
422 */
423 public function getScriptFilename(): string
424 {
425 return $this->scriptFilename;
426 }
427
428 /**
429 * @return string Absolute path to web document root, eg. /var/www/typo3
430 */
431 public function getDocumentRoot(): string
432 {
433 return $this->documentRoot;
434 }
435
436 /**
437 * @return string Website frontend url, eg. https://www.domain.com/some/sub/dir/
438 */
439 public function getSiteUrl(): string
440 {
441 return $this->siteUrl;
442 }
443
444 /**
445 * @return string Path part to frontend, eg. /some/sub/dir/
446 */
447 public function getSitePath(): string
448 {
449 return $this->sitePath;
450 }
451
452 /**
453 * @return string Path part to entry script with parameters, without sub dir, eg 'typo3/index.php?id=42'
454 */
455 public function getSiteScript(): string
456 {
457 return $this->siteScript;
458 }
459
460 /**
461 * Will be deprecated later, use getScriptName() as reliable solution instead
462 *
463 * @return string Script path part of URI, eg. 'typo3/index.php'
464 */
465 public function getPathInfo(): string
466 {
467 return $this->pathInfo;
468 }
469
470 /**
471 * Will be deprecated later, use $request->getServerParams()['HTTP_REFERER'] instead
472 *
473 * @return string HTTP_REFERER, eg. 'https://www.domain.com/typo3/index.php?id=42'
474 */
475 public function getHttpReferer(): string
476 {
477 return $this->httpReferer;
478 }
479
480 /**
481 * Will be deprecated later, use $request->getServerParams()['HTTP_USER_AGENT'] instead
482 *
483 * @return string HTTP_USER_AGENT identifier
484 */
485 public function getHttpUserAgent(): string
486 {
487 return $this->httpUserAgent;
488 }
489
490 /**
491 * Will be deprecated later, use $request->getServerParams()['HTTP_ACCEPT_ENCODING'] instead
492 *
493 * @return string HTTP_ACCEPT_ENCODING, eg. 'gzip, deflate'
494 */
495 public function getHttpAcceptEncoding(): string
496 {
497 return $this->httpAcceptEncoding;
498 }
499
500 /**
501 * Will be deprecated later, use $request->getServerParams()['HTTP_ACCEPT_LANGUAGE'] instead
502 *
503 * @return string HTTP_ACCEPT_LANGUAGE, eg. 'de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7'
504 */
505 public function getHttpAcceptLanguage(): string
506 {
507 return $this->httpAcceptLanguage;
508 }
509
510 /**
511 * Will be deprecated later, use $request->getServerParams()['REMOTE_HOST'] instead
512 *
513 * @return string REMOTE_HOST if configured in web server, eg. 'www.clientDomain.com'
514 */
515 public function getRemoteHost(): string
516 {
517 return $this->remoteHost;
518 }
519
520 /**
521 * Will be deprecated later, use $request->getServerParams()['QUERY_STRING'] instead
522 *
523 * @return string QUERY_STRING, eg 'id=42&foo=bar'
524 */
525 public function getQueryString(): string
526 {
527 return $this->queryString;
528 }
529
530 /**
531 * Sanitize HTTP_HOST, take proxy configuration into account and
532 * verify allowed hosts with configured trusted hosts pattern.
533 *
534 * @param array $serverParams Basically the $_SERVER, but from $request object
535 * @param array $typo3ConfVars TYPO3_CONF_VARS array
536 * @param bool $isBehindReverseProxy True if reverse proxy setup is detected
537 * @return string Sanitized HTTP_HOST
538 */
539 protected static function determineHttpHost(array $serverParams, array $typo3ConfVars, bool $isBehindReverseProxy): string
540 {
541 $httpHost = $serverParams['HTTP_HOST'] ?? '';
542 if ($isBehindReverseProxy) {
543 // If the request comes from a configured proxy which has set HTTP_X_FORWARDED_HOST, then
544 // evaluate reverseProxyHeaderMultiValue and
545 $xForwardedHostArray = GeneralUtility::trimExplode(',', $serverParams['HTTP_X_FORWARDED_HOST'] ?? '', true);
546 $xForwardedHost = '';
547 // Choose which host in list to use
548 if (!empty($xForwardedHostArray)) {
549 $configuredReverseProxyHeaderMultiValue = trim($typo3ConfVars['SYS']['reverseProxyHeaderMultiValue'] ?? '');
550 // Default if reverseProxyHeaderMultiValue is not set or set to 'none', instead of 'first' / 'last' is to
551 // ignore $serverParams['HTTP_X_FORWARDED_HOST']
552 // @todo: Maybe this default is stupid: Both SYS/reverseProxyIP hand SYS/reverseProxyHeaderMultiValue have to
553 // @todo: be configured for a working setup. It would be easier to only configure SYS/reverseProxyIP and fall
554 // @todo: back to "first" if SYS/reverseProxyHeaderMultiValue is not set.
555 if ($configuredReverseProxyHeaderMultiValue === 'last') {
556 $xForwardedHost = array_pop($xForwardedHostArray);
557 } elseif ($configuredReverseProxyHeaderMultiValue === 'first') {
558 $xForwardedHost = array_shift($xForwardedHostArray);
559 }
560 }
561 if ($xForwardedHost) {
562 $httpHost = $xForwardedHost;
563 }
564 }
565 if (!GeneralUtility::isAllowedHostHeaderValue($httpHost)) {
566 throw new \UnexpectedValueException(
567 'The current host header value does not match the configured trusted hosts pattern!'
568 . ' Check the pattern defined in $GLOBALS[\'TYPO3_CONF_VARS\'][\'SYS\'][\'trustedHostsPattern\']'
569 . ' and adapt it, if you want to allow the current host header \'' . $httpHost . '\' for your installation.',
570 1396795886
571 );
572 }
573 return $httpHost;
574 }
575
576 /**
577 * Determine if the client called via HTTPS. Takes proxy ssl terminator
578 * configurations into account.
579 *
580 * @param array $serverParams Basically the $_SERVER, but from $request object
581 * @param array $typo3ConfVars TYPO3_CONF_VARS array
582 * @return bool True if request has been done via HTTPS
583 */
584 protected static function determineHttps(array $serverParams, array $typo3ConfVars): bool
585 {
586 $isHttps = false;
587 $configuredProxySSL = trim($typo3ConfVars['SYS']['reverseProxySSL'] ?? '');
588 if ($configuredProxySSL === '*') {
589 $configuredProxySSL = trim($typo3ConfVars['SYS']['reverseProxyIP'] ?? '');
590 }
591 $httpsParam = (string)($serverParams['HTTPS'] ?? '');
592 if (GeneralUtility::cmpIP(trim($serverParams['REMOTE_ADDR'] ?? ''), $configuredProxySSL)
593 || ($serverParams['SSL_SESSION_ID'] ?? '')
594 // https://secure.php.net/manual/en/reserved.variables.server.php
595 // "Set to a non-empty value if the script was queried through the HTTPS protocol."
596 || ($httpsParam !== '' && $httpsParam !== 'off' && $httpsParam !== '0')
597 ) {
598 $isHttps = true;
599 }
600 return $isHttps;
601 }
602
603 /**
604 * Determine script name and path
605 *
606 * @param array $serverParams Basically the $_SERVER, but from $request object
607 * @param array $typo3ConfVars TYPO3_CONF_VARS array
608 * @param bool $isHttps True if used protocol is HTTPS
609 * @param bool $isBehindReverseProxy True if reverse proxy setup is detected
610 * @return string Sanitized script name
611 */
612 protected static function determineScriptName(array $serverParams, array $typo3ConfVars, bool $isHttps, bool $isBehindReverseProxy): string
613 {
614 $scriptName = $serverParams['ORIG_PATH_INFO'] ??
615 $serverParams['PATH_INFO'] ??
616 $serverParams['ORIG_SCRIPT_NAME'] ??
617 $serverParams['SCRIPT_NAME'] ??
618 '';
619 if ($isBehindReverseProxy) {
620 // Add a prefix if TYPO3 is behind a proxy: ext-domain.com => int-server.com/prefix
621 if ($isHttps && !empty($typo3ConfVars['SYS']['reverseProxyPrefixSSL'])) {
622 $scriptName = $typo3ConfVars['SYS']['reverseProxyPrefixSSL'] . $scriptName;
623 } elseif (!empty($typo3ConfVars['SYS']['reverseProxyPrefix'])) {
624 $scriptName = $typo3ConfVars['SYS']['reverseProxyPrefix'] . $scriptName;
625 }
626 }
627 return $scriptName;
628 }
629
630 /**
631 * Determine REQUEST_URI, taking proxy configuration and various web server
632 * specifics into account.
633 *
634 * @param array $serverParams Basically the $_SERVER, but from $request object
635 * @param array $typo3ConfVars TYPO3_CONF_VARS array
636 * @param bool $isHttps True if used protocol is HTTPS
637 * @param string $scriptName Script name
638 * @param bool $isBehindReverseProxy True if reverse proxy setup is detected
639 * @return string Sanitized REQUEST_URI
640 */
641 protected static function determineRequestUri(array $serverParams, array $typo3ConfVars, bool $isHttps, string $scriptName, bool $isBehindReverseProxy): string
642 {
643 $proxyPrefixApplied = false;
644 if (!empty($typo3ConfVars['SYS']['requestURIvar'])) {
645 // This is for URL rewriter that store the original URI in a server
646 // variable (e.g. ISAPI Rewriter for IIS: HTTP_X_REWRITE_URL), a config then looks like:
647 // requestURIvar = '_SERVER|HTTP_X_REWRITE_URL' which will access $GLOBALS['_SERVER']['HTTP_X_REWRITE_URL']
648 list($firstLevel, $secondLevel) = GeneralUtility::trimExplode('|', $typo3ConfVars['SYS']['requestURIvar'], true);
649 $requestUri = $GLOBALS[$firstLevel][$secondLevel];
650 } elseif (empty($serverParams['REQUEST_URI'])) {
651 // This is for ISS/CGI which does not have the REQUEST_URI available.
652 $queryString = !empty($serverParams['QUERY_STRING']) ? '?' . $serverParams['QUERY_STRING'] : '';
653 // script name already had the proxy prefix handling, we must not add it a second time
654 $proxyPrefixApplied = true;
655 $requestUri = '/' . ltrim($scriptName, '/') . $queryString;
656 } else {
657 $requestUri = '/' . ltrim($serverParams['REQUEST_URI'], '/');
658 }
659 if (!$proxyPrefixApplied && $isBehindReverseProxy) {
660 // Add a prefix if TYPO3 is behind a proxy: ext-domain.com => int-server.com/prefix
661 if ($isHttps && !empty($typo3ConfVars['SYS']['reverseProxyPrefixSSL'])) {
662 $requestUri = $typo3ConfVars['SYS']['reverseProxyPrefixSSL'] . $requestUri;
663 } elseif (!empty($typo3ConfVars['SYS']['reverseProxyPrefix'])) {
664 $requestUri = $typo3ConfVars['SYS']['reverseProxyPrefix'] . $requestUri;
665 }
666 }
667 return $requestUri;
668 }
669
670 /**
671 * Determine clients REMOTE_ADDR, even if there is a reverse proxy in between.
672 *
673 * @param array $serverParams Basically the $_SERVER, but from $request object
674 * @param array $typo3ConfVars TYPO3_CONF_VARS array
675 * @param bool $isBehindReverseProxy True if reverse proxy setup is detected
676 * @return string Resolved REMOTE_ADDR
677 */
678 protected static function determineRemoteAddress(array $serverParams, array $typo3ConfVars, bool $isBehindReverseProxy): string
679 {
680 $remoteAddress = trim($serverParams['REMOTE_ADDR'] ?? '');
681 if ($isBehindReverseProxy) {
682 $ip = GeneralUtility::trimExplode(',', $serverParams['HTTP_X_FORWARDED_FOR'] ?? '', true);
683 // Choose which IP in list to use
684 $configuredReverseProxyHeaderMultiValue = trim($typo3ConfVars['SYS']['reverseProxyHeaderMultiValue'] ?? '');
685 if (!empty($ip) && $configuredReverseProxyHeaderMultiValue === 'last') {
686 $ip = array_pop($ip);
687 } elseif (!empty($ip) && $configuredReverseProxyHeaderMultiValue === 'first') {
688 $ip = array_shift($ip);
689 } else {
690 $ip = '';
691 }
692 if (GeneralUtility::validIP($ip)) {
693 $remoteAddress = $ip;
694 }
695 }
696 return $remoteAddress;
697 }
698
699 /**
700 * Check if a configured reverse proxy setup is detected.
701 *
702 * @param array $serverParams Basically the $_SERVER, but from $request object
703 * @param array $typo3ConfVars TYPO3_CONF_VARS array
704 * @return bool True if TYPO3 is behind a reverse proxy
705 */
706 protected static function determineIsBehindReverseProxy($serverParams, $typo3ConfVars): bool
707 {
708 return GeneralUtility::cmpIP(trim($serverParams['REMOTE_ADDR'] ?? ''), trim($typo3ConfVars['SYS']['reverseProxyIP'] ?? ''));
709 }
710
711 /**
712 * HTTP_HOST without port
713 *
714 * @param string $httpHost host[:[port]]
715 * @return string Resolved host
716 */
717 protected static function determineRequestHostOnly(string $httpHost): string
718 {
719 $httpHostBracketPosition = strpos($httpHost, ']');
720 $httpHostParts = explode(':', $httpHost);
721 return $httpHostBracketPosition !== false ? substr($httpHost, 0, $httpHostBracketPosition + 1) : array_shift($httpHostParts);
722 }
723
724 /**
725 * Requested port if given
726 *
727 * @param string $httpHost host[:[port]]
728 * @param string $httpHostOnly host
729 * @return int Resolved port if given, else 0
730 */
731 protected static function determineRequestPort(string $httpHost, string $httpHostOnly): int
732 {
733 return strlen($httpHost) > strlen($httpHostOnly) ? (int)substr($httpHost, strlen($httpHostOnly) + 1) : 0;
734 }
735
736 /**
737 * Calculate absolute path to web document root
738 *
739 * @param string $scriptName Entry script path of URI, without domain and without query parameters, with leading /
740 * @param string $scriptFilename Absolute path to entry script on server filesystem
741 * @return string Path to document root with trailing slash
742 */
743 protected static function determineDocumentRoot(string $scriptName, string $scriptFilename): string
744 {
745 // Get the web root (it is not the root of the TYPO3 installation)
746 // Some CGI-versions (LA13CGI) and mod-rewrite rules on MODULE versions will deliver a 'wrong'
747 // DOCUMENT_ROOT (according to our description). Further various aliases/mod_rewrite rules can
748 // disturb this as well. Therefore the DOCUMENT_ROOT is always calculated as the SCRIPT_FILENAME
749 // minus the end part shared with SCRIPT_NAME.
750 $webDocRoot = '';
751 $scriptNameArray = explode('/', strrev($scriptName));
752 $scriptFilenameArray = explode('/', strrev($scriptFilename));
753 $path = [];
754 foreach ($scriptNameArray as $segmentNumber => $segment) {
755 if ((string)$scriptFilenameArray[$segmentNumber] === (string)$segment) {
756 $path[] = $segment;
757 } else {
758 break;
759 }
760 }
761 $commonEnd = strrev(implode('/', $path));
762 if ((string)$commonEnd !== '') {
763 $webDocRoot = substr($scriptFilename, 0, -(strlen($commonEnd) + 1));
764 }
765 return $webDocRoot;
766 }
767
768 /**
769 * Determine frontend url
770 *
771 * @param string $requestDir Full Uri with path, but without script name and query parts
772 * @param string $pathThisScript Absolute path to entry script on server filesystem
773 * @param string $pathSite Absolute server path to document root
774 * @return string Calculated Frontend Url
775 */
776 protected static function determineSiteUrl(string $requestDir, string $pathThisScript, string $pathSite): string
777 {
778 if (defined('TYPO3_PATH_WEB')) {
779 // This can only be set by external entry scripts
780 $siteUrl = $requestDir;
781 } else {
782 $pathThisScriptDir = substr(dirname($pathThisScript), strlen($pathSite)) . '/';
783 $siteUrl = substr($requestDir, 0, -strlen($pathThisScriptDir));
784 $siteUrl = rtrim($siteUrl, '/') . '/';
785 }
786 return $siteUrl;
787 }
788
789 /**
790 * Determine site path
791 *
792 * @param string $requestHost scheme://host[:port]
793 * @param string $siteUrl Full Frontend Url
794 * @return string
795 */
796 protected static function determineSitePath(string $requestHost, string $siteUrl): string
797 {
798 return (string)substr($siteUrl, strlen($requestHost));
799 }
800
801 /**
802 * Determine site script
803 *
804 * @param string $requestUrl
805 * @param string $siteUrl
806 * @return string
807 */
808 protected static function determineSiteScript(string $requestUrl, string $siteUrl): string
809 {
810 return substr($requestUrl, strlen($siteUrl));
811 }
812 }