Completed
Push — master ( d577b1...045d4d )
by
unknown
20:39
created

NormalizedParams::getSiteUrl()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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