[FEATURE] Add normalized server parameters to PSR-7 requests 06/55506/22
authorChristian Kuhn <lolli@schwarzbu.ch>
Sun, 11 Feb 2018 09:41:20 +0000 (10:41 +0100)
committerBenni Mack <benni@typo3.org>
Wed, 14 Feb 2018 12:58:02 +0000 (13:58 +0100)
To slowly substitute GeneralUtility::getIndpEnv() with a
better API, a new class is introduced that calculates all
normalized server parameters.
The object is added as PSR-7 request attribute in a frontend
and backend middleware.
For a transition phase, the request is made available
as $GLOBALS['TYPO3_REQUEST'] until enough core code has
been refactored to get rid of this again.

Resolves: #83736
Releases: master
Change-Id: I96c8cb6dda4cc38bbb51b64439b8e81f2c00d7ac
Reviewed-on: https://review.typo3.org/55506
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Georg Ringer <georg.ringer@gmail.com>
Reviewed-by: Benjamin Franzke <bfr@qbus.de>
Tested-by: Benjamin Franzke <bfr@qbus.de>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
Reviewed-by: Stefan Neufeind <typo3.neufeind@speedpartner.de>
Reviewed-by: Benni Mack <benni@typo3.org>
Tested-by: Benni Mack <benni@typo3.org>
typo3/sysext/backend/Configuration/RequestMiddlewares.php
typo3/sysext/core/Classes/Http/NormalizedParams.php [new file with mode: 0644]
typo3/sysext/core/Classes/Middleware/NormalizedParamsAttribute.php [new file with mode: 0644]
typo3/sysext/core/Documentation/Changelog/master/Deprecation-83736-DeprecatedGlobalsTYPO3_REQUEST.rst [new file with mode: 0644]
typo3/sysext/core/Documentation/Changelog/master/Feature-83736-ExtendedPSR-7RequestsWithTYPO3ServerParameters.rst [new file with mode: 0644]
typo3/sysext/core/Tests/Unit/Http/NormalizedParamsTest.php [new file with mode: 0644]
typo3/sysext/core/Tests/Unit/Utility/GeneralUtilityTest.php
typo3/sysext/frontend/Configuration/RequestMiddlewares.php
typo3/sysext/install/Configuration/ExtensionScanner/Php/ArrayGlobalMatcher.php

index 401cd94..62b3e6a 100644 (file)
@@ -14,10 +14,16 @@ return [
         'typo3/cms-core/legacy-request-handler-dispatcher' => [
             'target' => \TYPO3\CMS\Core\Middleware\LegacyRequestHandlerDispatcher::class,
         ],
+        'typo3/cms-core/normalized-params-attribute' => [
+            'target' => \TYPO3\CMS\Core\Middleware\NormalizedParamsAttribute::class,
+            'after' => [
+                'typo3/cms-core/legacy-request-handler-dispatcher',
+            ],
+        ],
         'typo3/cms-backend/locked-backend' => [
             'target' => \TYPO3\CMS\Backend\Middleware\LockedBackendGuard::class,
             'after' => [
-                'typo3/cms-core/legacy-request-handler-dispatcher'
+                'typo3/cms-core/normalized-params-attribute'
             ],
         ],
         'typo3/cms-backend/https-redirector' => [
diff --git a/typo3/sysext/core/Classes/Http/NormalizedParams.php b/typo3/sysext/core/Classes/Http/NormalizedParams.php
new file mode 100644 (file)
index 0000000..63af1d8
--- /dev/null
@@ -0,0 +1,810 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Core\Http;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use Psr\Http\Message\ServerRequestInterface;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+/**
+ * This class provides normalized server parameters in HTTP request context.
+ * It normalizes reverse proxy scenarios and various other web server specific differences
+ * of native the PSR-7 request object parameters (->getServerParams() / $GLOBALS['_SERVER']).
+ *
+ * An instance of this class is available as PSR-7 ServerRequestInterface attribute:
+ * $normalizedParams = $request->getAttribute('normalizedParams')
+ *
+ * This class substitutes the old GeneralUtility::getIndpEnv() method.
+ */
+class NormalizedParams
+{
+    /**
+     * Sanitized HTTP_HOST value
+     *
+     * host[:port]
+     *
+     * - www.domain.com
+     * - www.domain.com:443
+     * - 192.168.1.42:80
+     *
+     * @var string
+     */
+    protected $httpHost = '';
+
+    /**
+     * @var bool True if request has been done via HTTPS
+     */
+    protected $isHttps = false;
+
+    /**
+     * Sanitized HTTP_HOST with protocol
+     *
+     * scheme://host[:port]
+     *
+     * - https://www.domain.com
+     *
+     * @var string
+     */
+    protected $requestHost = '';
+
+    /**
+     * Host / domain part of HTTP_HOST, no port, no protocol
+     *
+     * - www.domain.com
+     * - 192.168.1.42
+     *
+     * @var string
+     */
+    protected $requestHostOnly = '';
+
+    /**
+     * Port of HTTP_HOST if given
+     *
+     * @var int
+     */
+    protected $requestPort = 0;
+
+    /**
+     * Entry script path of URI, without domain and without query parameters, with leading /
+     *
+     * [path_script]
+     *
+     * - /typo3/index.php
+     *
+     * @var string
+     */
+    protected $scriptName = '';
+
+    /**
+     * REQUEST URI without domain and scheme, with trailing slash
+     *
+     * [path][?[query]]
+     *
+     * - /index.php
+     * - /typo3/index.php/arg1/arg2/?arg1,arg2&p1=parameter1&p2[key]=value
+     *
+     * @var string
+     */
+    protected $requestUri = '';
+
+    /**
+     * REQUEST URI with scheme, host, port, path and query
+     *
+     * scheme://host[:[port]][path][?[query]]
+     *
+     * - http://www.domain.com/typo3/index.php?route=foo/bar&id=42
+     *
+     * @var string
+     */
+    protected $requestUrl = '';
+
+    /**
+     * REQUEST URI with scheme, host, port and path, but *without* query part
+     *
+     * scheme://host[:[port]][path_script]
+     *
+     * - http://www.domain.com/typo3/index.php
+     *
+     * @var string
+     */
+    protected $requestScript = '';
+
+    /**
+     * Full Uri with path, but without script name and query parts
+     *
+     * scheme://host[:[port]][path_dir]
+     *
+     * - http://www.domain.com/typo3/
+     *
+     * @var string
+     */
+    protected $requestDir = '';
+
+    /**
+     * True if request via a reverse proxy is detected
+     *
+     * @var bool
+     */
+    protected $isBehindReverseProxy = false;
+
+    /**
+     * IPv4 or IPv6 address of remote client with resolved proxy setup
+     *
+     * @var string
+     */
+    protected $remoteAddress = '';
+
+    /**
+     * Absolute server path to entry script on server filesystem
+     *
+     * - /var/www/typo3/index.php
+     *
+     * @var string
+     */
+    protected $scriptFilename = '';
+
+    /**
+     * Absolute server path to web document root without trailing slash
+     *
+     * - /var/www/typo3
+     *
+     * @var string
+     */
+    protected $documentRoot = '';
+
+    /**
+     * Website frontend URL.
+     * Note this is note "safe" if called from Backend since sys_domain and
+     * other factors are not taken into account.
+     *
+     * scheme://host[:[port]]/[path_dir]
+     *
+     * - https://www.domain.com/
+     * - https://www.domain.com/some/sub/dir/
+     *
+     * @var string
+     */
+    protected $siteUrl = '';
+
+    /**
+     * Path part to frontend, no domain, no protocol
+     *
+     * - /
+     * - /some/sub/dir/
+     *
+     * @var string
+     */
+    protected $sitePath = '';
+
+    /**
+     * Path to script, without sub path if TYPO3 is running in sub directory, without trailing slash
+     *
+     * - typo/index.php?id=42
+     * - index.php?id=42
+     *
+     * @var string
+     */
+    protected $siteScript = '';
+
+    /**
+     * Entry script path of URI, without domain and without query parameters, with leading /
+     * This is often not set at all.
+     * Will be deprecated later, use $scriptName instead as more reliable solution.
+     *
+     * [path_script]
+     *
+     * - /typo3/index.php
+     *
+     * @var string
+     */
+    protected $pathInfo = '';
+
+    /**
+     * HTTP_REFERER
+     * Will be deprecated later, use $request->getServerParams()['HTTP_REFERER'] instead
+     *
+     * scheme://host[:[port]][path]
+     *
+     * - https://www.domain.com/typo3/index.php?id=42
+     *
+     * @var string
+     */
+    protected $httpReferer = '';
+
+    /**
+     * HTTP_USER_AGENT
+     * Will be deprecated later, use $request->getServerParams()['HTTP_USER_AGENT'] instead
+     *
+     * - Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36
+     *
+     * @var string
+     */
+    protected $httpUserAgent = '';
+
+    /**
+     * HTTP_ACCEPT_ENCODING
+     * Will be deprecated later, use $request->getServerParams()['HTTP_ACCEPT_ENCODING'] instead
+     *
+     * - gzip, deflate
+     *
+     * @var string
+     */
+    protected $httpAcceptEncoding = '';
+
+    /**
+     * HTTP_ACCEPT_LANGUAGE
+     * Will be deprecated later, use $request->getServerParams()['HTTP_ACCEPT_LANGUAGE'] instead
+     *
+     * - de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7
+     *
+     * @var string
+     */
+    protected $httpAcceptLanguage = '';
+
+    /**
+     * REMOTE_HOST Resolved host name of REMOTE_ADDR if configured in web server
+     * Will be deprecated later, use $request->getServerParams()['REMOTE_HOST'] instead
+     *
+     * - www.clientDomain.com
+     *
+     * @var string
+     */
+    protected $remoteHost = '';
+
+    /**
+     * QUERY_STRING
+     * Will be deprecated later, use $request->getServerParams()['QUERY_STRING'] instead
+     *
+     * [query]
+     *
+     * - id=42&foo=bar
+     *
+     * @var string
+     */
+    protected $queryString = '';
+
+    /**
+     * Constructor calculates all values by incoming variables.
+     *
+     * This object is immutable.
+     *
+     * All determine*() "detail worker methods" in this class retrieve their dependencies
+     * to other properties as method arguments, they are static, stateless and have no
+     * dependency to $this. This ensures the chain of inter-property dependencies
+     * is visible by only looking at the construct() method.
+     *
+     * @param ServerRequestInterface $serverRequest Used to access $_SERVER
+     * @param array $typo3ConfVars $GLOBALS['TYPO3_CONF_VARS']
+     * @param string $pathThisScript Absolute server entry script path, constant PATH_thisScript
+     * @param string $pathSite Absolute server path to document root, constant PATH_site
+     */
+    public function __construct(ServerRequestInterface $serverRequest, array $typo3ConfVars, string $pathThisScript, string $pathSite)
+    {
+        $serverParams = $serverRequest->getServerParams();
+        $isBehindReverseProxy = $this->isBehindReverseProxy = self::determineIsBehindReverseProxy($serverParams, $typo3ConfVars);
+        $httpHost = $this->httpHost = self::determineHttpHost($serverParams, $typo3ConfVars, $isBehindReverseProxy);
+        $isHttps = $this->isHttps = self::determineHttps($serverParams, $typo3ConfVars);
+        $requestHost = $this->requestHost = ($isHttps ? 'https://' : 'http://') . $httpHost;
+        $requestHostOnly = $this->requestHostOnly = self::determineRequestHostOnly($httpHost);
+        $this->requestPort = self::determineRequestPort($httpHost, $requestHostOnly);
+        $scriptName = $this->scriptName = self::determineScriptName($serverParams, $typo3ConfVars, $isHttps, $isBehindReverseProxy);
+        $requestUri = $this->requestUri = self::determineRequestUri($serverParams, $typo3ConfVars, $isHttps, $scriptName, $isBehindReverseProxy);
+        $requestUrl = $this->requestUrl = $requestHost . $requestUri;
+        $this->requestScript = $requestHost . $scriptName;
+        $requestDir = $this->requestDir = $requestHost . GeneralUtility::dirname($scriptName) . '/';
+        $this->remoteAddress = self::determineRemoteAddress($serverParams, $typo3ConfVars, $isBehindReverseProxy);
+        $scriptFilename = $this->scriptFilename = $pathThisScript;
+        $this->documentRoot = self::determineDocumentRoot($scriptName, $scriptFilename);
+        $siteUrl = $this->siteUrl = self::determineSiteUrl($requestDir, $pathThisScript, $pathSite);
+        $this->sitePath = self::determineSitePath($requestHost, $siteUrl);
+        $this->siteScript = self::determineSiteScript($requestUrl, $siteUrl);
+
+        // @deprecated Below variables can be fully deprecated as soon as core does not use them anymore
+        $this->pathInfo = $serverParams['PATH_INFO'] ?? '';
+        $this->httpReferer = $serverParams['HTTP_REFERER'] ?? '';
+        $this->httpUserAgent = $serverParams['HTTP_USER_AGENT'] ?? '';
+        $this->httpAcceptEncoding = $serverParams['HTTP_ACCEPT_ENCODING'] ?? '';
+        $this->httpAcceptLanguage = $serverParams['HTTP_ACCEPT_LANGUAGE'] ?? '';
+        $this->remoteHost = $serverParams['REMOTE_HOST'] ?? '';
+        $this->queryString = $serverParams['QUERY_STRING'] ?? '';
+    }
+
+    /**
+     * @return string Sanitized HTTP_HOST value host[:port]
+     */
+    public function getHttpHost(): string
+    {
+        return $this->httpHost;
+    }
+
+    /**
+     * @return bool True if client request has been done using HTTPS
+     */
+    public function isHttps(): bool
+    {
+        return $this->isHttps;
+    }
+
+    /**
+     * @return string Sanitized HTTP_HOST with protocol scheme://host[:port], eg. https://www.domain.com/
+     */
+    public function getRequestHost(): string
+    {
+        return $this->requestHost;
+    }
+
+    /**
+     * @return string Host / domain /IP only, eg. www.domain.com
+     */
+    public function getRequestHostOnly(): string
+    {
+        return $this->requestHostOnly;
+    }
+
+    /**
+     * @return int Requested port if given, eg. 8080 - often not explicitly given, then 0
+     */
+    public function getRequestPort(): int
+    {
+        return $this->requestPort;
+    }
+
+    /**
+     * @return string Script path part of URI, eg. 'typo3/index.php'
+     */
+    public function getScriptName(): string
+    {
+        return $this->scriptName;
+    }
+
+    /**
+     * @return string Request Uri without domain and protocol, eg. /index.php?id=42
+     */
+    public function getRequestUri(): string
+    {
+        return $this->requestUri;
+    }
+
+    /**
+     * @return string Full REQUEST_URI, eg. http://www.domain.com/typo3/index.php?route=foo/bar&id=42
+     */
+    public function getRequestUrl(): string
+    {
+        return $this->requestUrl;
+    }
+
+    /**
+     * @return string REQUEST URI without query part, eg. http://www.domain.com/typo3/index.php
+     */
+    public function getRequestScript(): string
+    {
+        return $this->requestScript;
+    }
+
+    /**
+     * @return string REQUEST URI without script file name and query parts, eg. http://www.domain.com/typo3/
+     */
+    public function getRequestDir(): string
+    {
+        return $this->requestDir;
+    }
+
+    /**
+     * @return bool True if request comes from a configured reverse proxy
+     */
+    public function isBehindReverseProxy(): bool
+    {
+        return $this->isBehindReverseProxy;
+    }
+
+    /**
+     * @return string Client IP
+     */
+    public function getRemoteAddress(): string
+    {
+        return $this->remoteAddress;
+    }
+
+    /**
+     * @return string Absolute entry script path on server, eg. /var/www/typo3/index.php
+     */
+    public function getScriptFilename(): string
+    {
+        return $this->scriptFilename;
+    }
+
+    /**
+     * @return string Absolute path to web document root, eg. /var/www/typo3
+     */
+    public function getDocumentRoot(): string
+    {
+        return $this->documentRoot;
+    }
+
+    /**
+     * @return string Website frontend url, eg. https://www.domain.com/some/sub/dir/
+     */
+    public function getSiteUrl(): string
+    {
+        return $this->siteUrl;
+    }
+
+    /**
+     * @return string Path part to frontend, eg. /some/sub/dir/
+     */
+    public function getSitePath(): string
+    {
+        return $this->sitePath;
+    }
+
+    /**
+     * @return string Path part to entry script with parameters, without sub dir, eg 'typo3/index.php?id=42'
+     */
+    public function getSiteScript(): string
+    {
+        return $this->siteScript;
+    }
+
+    /**
+     * Will be deprecated later, use getScriptName() as reliable solution instead
+     *
+     * @return string Script path part of URI, eg. 'typo3/index.php'
+     */
+    public function getPathInfo(): string
+    {
+        return $this->pathInfo;
+    }
+
+    /**
+     * Will be deprecated later, use $request->getServerParams()['HTTP_REFERER'] instead
+     *
+     * @return string HTTP_REFERER, eg. 'https://www.domain.com/typo3/index.php?id=42'
+     */
+    public function getHttpReferer(): string
+    {
+        return $this->httpReferer;
+    }
+
+    /**
+     * Will be deprecated later, use $request->getServerParams()['HTTP_USER_AGENT'] instead
+     *
+     * @return string HTTP_USER_AGENT identifier
+     */
+    public function getHttpUserAgent(): string
+    {
+        return $this->httpUserAgent;
+    }
+
+    /**
+     * Will be deprecated later, use $request->getServerParams()['HTTP_ACCEPT_ENCODING'] instead
+     *
+     * @return string HTTP_ACCEPT_ENCODING, eg. 'gzip, deflate'
+     */
+    public function getHttpAcceptEncoding(): string
+    {
+        return $this->httpAcceptEncoding;
+    }
+
+    /**
+     * Will be deprecated later, use $request->getServerParams()['HTTP_ACCEPT_LANGUAGE'] instead
+     *
+     * @return string HTTP_ACCEPT_LANGUAGE, eg. 'de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7'
+     */
+    public function getHttpAcceptLanguage(): string
+    {
+        return $this->httpAcceptLanguage;
+    }
+
+    /**
+     * Will be deprecated later, use $request->getServerParams()['REMOTE_HOST'] instead
+     *
+     * @return string REMOTE_HOST if configured in web server, eg. 'www.clientDomain.com'
+     */
+    public function getRemoteHost(): string
+    {
+        return $this->remoteHost;
+    }
+
+    /**
+     * Will be deprecated later, use $request->getServerParams()['QUERY_STRING'] instead
+     *
+     * @return string QUERY_STRING, eg 'id=42&foo=bar'
+     */
+    public function getQueryString(): string
+    {
+        return $this->queryString;
+    }
+
+    /**
+     * Sanitize HTTP_HOST, take proxy configuration into account and
+     * verify allowed hosts with configured trusted hosts pattern.
+     *
+     * @param array $serverParams Basically the $_SERVER, but from $request object
+     * @param array $typo3ConfVars TYPO3_CONF_VARS array
+     * @param bool $isBehindReverseProxy True if reverse proxy setup is detected
+     * @return string Sanitized HTTP_HOST
+     */
+    protected static function determineHttpHost(array $serverParams, array $typo3ConfVars, bool $isBehindReverseProxy): string
+    {
+        $httpHost = $serverParams['HTTP_HOST'] ?? '';
+        if ($isBehindReverseProxy) {
+            // If the request comes from a configured proxy which has set HTTP_X_FORWARDED_HOST, then
+            // evaluate reverseProxyHeaderMultiValue and
+            $xForwardedHostArray = GeneralUtility::trimExplode(',', $serverParams['HTTP_X_FORWARDED_HOST'] ?? '', true);
+            $xForwardedHost = '';
+            // Choose which host in list to use
+            if (!empty($xForwardedHostArray)) {
+                $configuredReverseProxyHeaderMultiValue = trim($typo3ConfVars['SYS']['reverseProxyHeaderMultiValue'] ?? '');
+                // Default if reverseProxyHeaderMultiValue is not set or set to 'none', instead of 'first' / 'last' is to
+                // ignore $serverParams['HTTP_X_FORWARDED_HOST']
+                // @todo: Maybe this default is stupid: Both SYS/reverseProxyIP hand SYS/reverseProxyHeaderMultiValue have to
+                // @todo: be configured for a working setup. It would be easier to only configure SYS/reverseProxyIP and fall
+                // @todo: back to "first" if SYS/reverseProxyHeaderMultiValue is not set.
+                if ($configuredReverseProxyHeaderMultiValue === 'last') {
+                    $xForwardedHost = array_pop($xForwardedHostArray);
+                } elseif ($configuredReverseProxyHeaderMultiValue === 'first') {
+                    $xForwardedHost = array_shift($xForwardedHostArray);
+                }
+            }
+            if ($xForwardedHost) {
+                $httpHost = $xForwardedHost;
+            }
+        }
+        if (!GeneralUtility::isAllowedHostHeaderValue($httpHost)) {
+            throw new \UnexpectedValueException(
+                'The current host header value does not match the configured trusted hosts pattern!'
+                . ' Check the pattern defined in $GLOBALS[\'TYPO3_CONF_VARS\'][\'SYS\'][\'trustedHostsPattern\']'
+                . ' and adapt it, if you want to allow the current host header \'' . $httpHost . '\' for your installation.',
+                1396795886
+            );
+        }
+        return $httpHost;
+    }
+
+    /**
+     * Determine if the client called via HTTPS. Takes proxy ssl terminator
+     * configurations into account.
+     *
+     * @param array $serverParams Basically the $_SERVER, but from $request object
+     * @param array $typo3ConfVars TYPO3_CONF_VARS array
+     * @return bool True if request has been done via HTTPS
+     */
+    protected static function determineHttps(array $serverParams, array $typo3ConfVars): bool
+    {
+        $isHttps = false;
+        $configuredProxySSL = trim($typo3ConfVars['SYS']['reverseProxySSL'] ?? '');
+        if ($configuredProxySSL === '*') {
+            $configuredProxySSL = trim($typo3ConfVars['SYS']['reverseProxyIP'] ?? '');
+        }
+        if (GeneralUtility::cmpIP(trim($serverParams['REMOTE_ADDR'] ?? ''), $configuredProxySSL)
+            || ($serverParams['SSL_SESSION_ID'] ?? '')
+            || strtolower($serverParams['HTTPS'] ?? '') === 'on'
+            || (string)($serverParams['HTTPS'] ?? '') === '1'
+        ) {
+            $isHttps = true;
+        }
+        return $isHttps;
+    }
+
+    /**
+     * Determine script name and path
+     *
+     * @param array $serverParams Basically the $_SERVER, but from $request object
+     * @param array $typo3ConfVars TYPO3_CONF_VARS array
+     * @param bool $isHttps True if used protocol is HTTPS
+     * @param bool $isBehindReverseProxy True if reverse proxy setup is detected
+     * @return string Sanitized script name
+     */
+    protected static function determineScriptName(array $serverParams, array $typo3ConfVars, bool $isHttps, bool $isBehindReverseProxy): string
+    {
+        $scriptName = $serverParams['ORIG_PATH_INFO'] ??
+            $serverParams['PATH_INFO'] ??
+            $serverParams['ORIG_SCRIPT_NAME'] ??
+            $serverParams['SCRIPT_NAME'] ??
+            '';
+        if ($isBehindReverseProxy) {
+            // Add a prefix if TYPO3 is behind a proxy: ext-domain.com => int-server.com/prefix
+            if ($isHttps && !empty($typo3ConfVars['SYS']['reverseProxyPrefixSSL'])) {
+                $scriptName = $typo3ConfVars['SYS']['reverseProxyPrefixSSL'] . $scriptName;
+            } elseif (!empty($typo3ConfVars['SYS']['reverseProxyPrefix'])) {
+                $scriptName = $typo3ConfVars['SYS']['reverseProxyPrefix'] . $scriptName;
+            }
+        }
+        return $scriptName;
+    }
+
+    /**
+     * Determine REQUEST_URI, taking proxy configuration and various web server
+     * specifics into account.
+     *
+     * @param array $serverParams Basically the $_SERVER, but from $request object
+     * @param array $typo3ConfVars TYPO3_CONF_VARS array
+     * @param bool $isHttps True if used protocol is HTTPS
+     * @param string $scriptName Script name
+     * @param bool $isBehindReverseProxy True if reverse proxy setup is detected
+     * @return string Sanitized REQUEST_URI
+     */
+    protected static function determineRequestUri(array $serverParams, array $typo3ConfVars, bool $isHttps, string $scriptName, bool $isBehindReverseProxy): string
+    {
+        $proxyPrefixApplied = false;
+        if (!empty($typo3ConfVars['SYS']['requestURIvar'])) {
+            // This is for URL rewriter that store the original URI in a server
+            // variable (e.g. ISAPI Rewriter for IIS: HTTP_X_REWRITE_URL), a config then looks like:
+            // requestURIvar = '_SERVER|HTTP_X_REWRITE_URL' which will access $GLOBALS['_SERVER']['HTTP_X_REWRITE_URL']
+            list($firstLevel, $secondLevel) = GeneralUtility::trimExplode('|', $typo3ConfVars['SYS']['requestURIvar'], true);
+            $requestUri = $GLOBALS[$firstLevel][$secondLevel];
+        } elseif (empty($serverParams['REQUEST_URI'])) {
+            // This is for ISS/CGI which does not have the REQUEST_URI available.
+            $queryString = !empty($serverParams['QUERY_STRING']) ? '?' . $serverParams['QUERY_STRING'] : '';
+            // script name already had the proxy prefix handling, we must not add it a second time
+            $proxyPrefixApplied = true;
+            $requestUri = '/' . ltrim($scriptName, '/') . $queryString;
+        } else {
+            $requestUri = '/' . ltrim($serverParams['REQUEST_URI'], '/');
+        }
+        if (!$proxyPrefixApplied && $isBehindReverseProxy) {
+            // Add a prefix if TYPO3 is behind a proxy: ext-domain.com => int-server.com/prefix
+            if ($isHttps && !empty($typo3ConfVars['SYS']['reverseProxyPrefixSSL'])) {
+                $requestUri = $typo3ConfVars['SYS']['reverseProxyPrefixSSL'] . $requestUri;
+            } elseif (!empty($typo3ConfVars['SYS']['reverseProxyPrefix'])) {
+                $requestUri = $typo3ConfVars['SYS']['reverseProxyPrefix'] . $requestUri;
+            }
+        }
+        return $requestUri;
+    }
+
+    /**
+     * Determine clients REMOTE_ADDR, even if there is a reverse proxy in between.
+     *
+     * @param array $serverParams Basically the $_SERVER, but from $request object
+     * @param array $typo3ConfVars TYPO3_CONF_VARS array
+     * @param bool $isBehindReverseProxy True if reverse proxy setup is detected
+     * @return string Resolved REMOTE_ADDR
+     */
+    protected static function determineRemoteAddress(array $serverParams, array $typo3ConfVars, bool $isBehindReverseProxy): string
+    {
+        $remoteAddress = trim($serverParams['REMOTE_ADDR'] ?? '');
+        if ($isBehindReverseProxy) {
+            $ip = GeneralUtility::trimExplode(',', $serverParams['HTTP_X_FORWARDED_FOR'] ?? '', true);
+            // Choose which IP in list to use
+            $configuredReverseProxyHeaderMultiValue = trim($typo3ConfVars['SYS']['reverseProxyHeaderMultiValue'] ?? '');
+            if (!empty($ip) && $configuredReverseProxyHeaderMultiValue === 'last') {
+                $ip = array_pop($ip);
+            } elseif (!empty($ip) && $configuredReverseProxyHeaderMultiValue === 'first') {
+                $ip = array_shift($ip);
+            } else {
+                $ip = '';
+            }
+            if (GeneralUtility::validIP($ip)) {
+                $remoteAddress = $ip;
+            }
+        }
+        return $remoteAddress;
+    }
+
+    /**
+     * Check if a configured reverse proxy setup is detected.
+     *
+     * @param array $serverParams Basically the $_SERVER, but from $request object
+     * @param array $typo3ConfVars TYPO3_CONF_VARS array
+     * @return bool True if TYPO3 is behind a reverse proxy
+     */
+    protected static function determineIsBehindReverseProxy($serverParams, $typo3ConfVars): bool
+    {
+        return GeneralUtility::cmpIP(trim($serverParams['REMOTE_ADDR'] ?? ''), trim($typo3ConfVars['SYS']['reverseProxyIP'] ?? ''));
+    }
+
+    /**
+     * HTTP_HOST without port
+     *
+     * @param string $httpHost host[:[port]]
+     * @return string Resolved host
+     */
+    protected static function determineRequestHostOnly(string $httpHost): string
+    {
+        $httpHostBracketPosition = strpos($httpHost, ']');
+        $httpHostParts = explode(':', $httpHost);
+        return $httpHostBracketPosition !== false ? substr($httpHost, 0, $httpHostBracketPosition + 1) : array_shift($httpHostParts);
+    }
+
+    /**
+     * Requested port if given
+     *
+     * @param string $httpHost host[:[port]]
+     * @param string $httpHostOnly host
+     * @return int Resolved port if given, else 0
+     */
+    protected static function determineRequestPort(string $httpHost, string $httpHostOnly): int
+    {
+        return strlen($httpHost) > strlen($httpHostOnly) ? (int)substr($httpHost, strlen($httpHostOnly) + 1) : 0;
+    }
+
+    /**
+     * Calculate absolute path to web document root
+     *
+     * @param string $scriptName Entry script path of URI, without domain and without query parameters, with leading /
+     * @param string $scriptFilename Absolute path to entry script on server filesystem
+     * @return string Path to document root with trailing slash
+     */
+    protected static function determineDocumentRoot(string $scriptName, string $scriptFilename): string
+    {
+        // Get the web root (it is not the root of the TYPO3 installation)
+        // Some CGI-versions (LA13CGI) and mod-rewrite rules on MODULE versions will deliver a 'wrong'
+        // DOCUMENT_ROOT (according to our description). Further various aliases/mod_rewrite rules can
+        // disturb this as well. Therefore the DOCUMENT_ROOT is always calculated as the SCRIPT_FILENAME
+        // minus the end part shared with SCRIPT_NAME.
+        $webDocRoot = '';
+        $scriptNameArray = explode('/', strrev($scriptName));
+        $scriptFilenameArray = explode('/', strrev($scriptFilename));
+        $path = [];
+        foreach ($scriptNameArray as $segmentNumber => $segment) {
+            if ((string)$scriptFilenameArray[$segmentNumber] === (string)$segment) {
+                $path[] = $segment;
+            } else {
+                break;
+            }
+        }
+        $commonEnd = strrev(implode('/', $path));
+        if ((string)$commonEnd !== '') {
+            $webDocRoot = substr($scriptFilename, 0, -(strlen($commonEnd) + 1));
+        }
+        return $webDocRoot;
+    }
+
+    /**
+     * Determine frontend url
+     *
+     * @param string $requestDir Full Uri with path, but without script name and query parts
+     * @param string $pathThisScript Absolute path to entry script on server filesystem
+     * @param string $pathSite Absolute server path to document root
+     * @return string Calculated Frontend Url
+     */
+    protected static function determineSiteUrl(string $requestDir, string $pathThisScript, string $pathSite): string
+    {
+        if (defined('TYPO3_PATH_WEB')) {
+            // This can only be set by external entry scripts
+            $siteUrl = $requestDir;
+        } else {
+            $pathThisScriptDir = substr(dirname($pathThisScript), strlen($pathSite)) . '/';
+            $siteUrl = substr($requestDir, 0, -strlen($pathThisScriptDir));
+            $siteUrl = rtrim($siteUrl, '/') . '/';
+        }
+        return $siteUrl;
+    }
+
+    /**
+     * Determine site path
+     *
+     * @param string $requestHost scheme://host[:port]
+     * @param string $siteUrl Full Frontend Url
+     * @return string
+     */
+    protected static function determineSitePath(string $requestHost, string $siteUrl): string
+    {
+        return (string)substr($siteUrl, strlen($requestHost));
+    }
+
+    /**
+     * Determine site script
+     *
+     * @param string $requestUrl
+     * @param string $siteUrl
+     * @return string
+     */
+    protected static function determineSiteScript(string $requestUrl, string $siteUrl): string
+    {
+        return substr($requestUrl, strlen($siteUrl));
+    }
+}
diff --git a/typo3/sysext/core/Classes/Middleware/NormalizedParamsAttribute.php b/typo3/sysext/core/Classes/Middleware/NormalizedParamsAttribute.php
new file mode 100644 (file)
index 0000000..2a4eb5d
--- /dev/null
@@ -0,0 +1,51 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Core\Middleware;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Server\MiddlewareInterface;
+use Psr\Http\Server\RequestHandlerInterface;
+use TYPO3\CMS\Core\Http\NormalizedParams;
+
+/**
+ * Add NormalizedParams as 'normalizedParams' attribute.
+ * Used in FE, BE and install tool context.
+ *
+ * @internal
+ */
+class NormalizedParamsAttribute implements MiddlewareInterface
+{
+    /**
+     * Adds an instance of TYPO3\CMS\Core\Http\NormalizedParams as
+     * attribute to $request object
+     *
+     * @param ServerRequestInterface $request
+     * @param RequestHandlerInterface $handler
+     * @return ResponseInterface
+     */
+    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
+    {
+        $request = $request->withAttribute('normalizedParams', new NormalizedParams($request, $GLOBALS['TYPO3_CONF_VARS'], PATH_thisScript, PATH_site));
+
+        // Set $request as global variable. This is needed in a transition phase until core code has been
+        // refactored to have ServerRequest object available where it is needed. This global will be
+        // deprecated then and removed.
+        $GLOBALS['TYPO3_REQUEST'] = $request;
+
+        return $handler->handle($request);
+    }
+}
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Deprecation-83736-DeprecatedGlobalsTYPO3_REQUEST.rst b/typo3/sysext/core/Documentation/Changelog/master/Deprecation-83736-DeprecatedGlobalsTYPO3_REQUEST.rst
new file mode 100644 (file)
index 0000000..0321e6b
--- /dev/null
@@ -0,0 +1,38 @@
+.. include:: ../../Includes.txt
+
+======================================================
+Deprecation: #83736 - Deprecated globals TYPO3_REQUEST
+======================================================
+
+See :issue:`83736`
+
+Description
+===========
+
+The :php:`ServerRequestInterface $request` is available as :php:`$GLOBALS['TYPO3_REQUEST']`
+in HTTP requests. This global is available in a transition phase only and will be removed later.
+
+Extension authors are discouraged to use that global and the extension scanner marks any
+usage as deprecated.
+
+
+Impact
+======
+
+Accessing :php:`$GLOBALS['TYPO3_REQUEST']` is discouraged.
+
+
+Affected Installations
+======================
+
+Instances with extensions using :php:`$GLOBALS['TYPO3_REQUEST']`.
+
+
+Migration
+=========
+
+Controller classes for HTTP requests retrieve the request object. Access should either be
+done from within controllers or by passing :php:`$request` to service classes that
+need to access values from :php:`$request`.
+
+.. index:: PHP-API, FullyScanned
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-83736-ExtendedPSR-7RequestsWithTYPO3ServerParameters.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-83736-ExtendedPSR-7RequestsWithTYPO3ServerParameters.rst
new file mode 100644 (file)
index 0000000..6942b05
--- /dev/null
@@ -0,0 +1,72 @@
+.. include:: ../../Includes.txt
+
+=================================================================================
+Feature: #83736 - Extended PSR-7 requests with TYPO3 normalized server parameters
+=================================================================================
+
+See :issue:`83736`
+
+Description
+===========
+
+The PSR-7 based ServerRequest objects created by TYPO3 now contain a TYPO3-specific
+attribute object for normalized server parameters that for instance resolves variables
+if the instance is behind a reverse proxy. This substitutes :php:`GeneralUtility::getIndpEnv()`.
+
+The object is **for now** available from :php:`ServerRequestInterface $request` objects as
+attribute. The request object is given to controllers, example:
+
+.. code-block:: php
+
+    /** @var NormalizedParams $normalizedParams */
+    $normalizedParams = $request->getAttribute('normalizedParams');
+    $requestPort = $normalizedParams->getRequestPort();
+
+The request object is also available as a global variable in :php:`$GLOBALS['TYPO3_REQUEST']`.
+This is a workaround for the core which has to access the server parameters at places where
+$request is not available. So, while this object is globally available during any HTTP request,
+it is considered bad practice to use it, and the extension scanner will mark an access to this
+global variable as deprecated. The global object will vanish later if the core code has been
+refactored enough to not rely on it anymore.
+
+For now, class :php:`NormalizedParams` is a one-to-one transition of :php:`GeneralUtility::getIndpEnv()`,
+the old arguments can be substituted with these calls:
+
+- :php:`SCRIPT_NAME` is now :php:`->getScriptName()`
+- :php:`SCRIPT_FILENAME` is now :php:`->getScriptFilename()`
+- :php:`REQUEST_URI` is now :php:`->getRequestUri()`
+- :php:`TYPO3_REV_PROXY` is now :php:`->isBehindReverseProxy()`
+- :php:`REMOTE_ADDR` is now :php:`->getRemoteAddress()`
+- :php:`HTTP_HOST` is now :php:`->getHttpHost()`
+- :php:`TYPO3_DOCUMENT_ROOT` is now :php:`->getDocumentRoot()`
+- :php:`TYPO3_HOST_ONLY` is now :php:`->getRequestHostOnly()`
+- :php:`TYPO3_PORT` is now :php:`->getRequestPort()`
+- :php:`TYPO3_REQUEST_HOST` is now :php:`->getRequestHost()`
+- :php:`TYPO3_REQUEST_URL` is now :php:`->getRequestUrl()`
+- :php:`TYPO3_REQUEST_SCRIPT` is now :php:`->getRequestScript()`
+- :php:`TYPO3_REQUEST_DIR` is now :php:`->getRequestDir()`
+- :php:`TYPO3_SITE_URL` is now :php:`->getSiteUrl()`
+- :php:`TYPO3_SITE_PATH` is now :php:`->getSitePath()`
+- :php:`TYPO3_SITE_SCRIPT` is now :php:`->getSiteScript()`
+- :php:`TYPO3_SSL` is now :php:`->isHttps()`
+
+Some further old :php:`getIndpEnv()` arguments directly access :php:`$request->serverParams()` and
+do not apply any normalization. These have been transferred to the new class, too, but will be
+deprecated later if the core does not use these anymore:
+
+- :php:`PATH_INFO` is now :php:`->getPathInfo()`, but better use :php:`->getScriptPath()` instead
+- :php:`HTTP_REFERER` is now :php:`->getHttpReferer()`, but better use :php:`$request->getServerParams()['HTTP_REFERER']` instead
+- :php:`HTTP_USER_AGENT` is now :php:`->getHttpUserAgent()`, but better use :php:`$request->getServerParams()['HTTP_USER_AGENT']` instead
+- :php:`HTTP_ACCEPT_ENCODING` is now :php:`->getHttpAcceptEncoding()`, but better use :php:`$request->getServerParams()['HTTP_ACCEPT_ENCODING']` instead
+- :php:`HTTP_ACCEPT_LANGUAGE` is now :php:`->getHttpAcceptLanguage()`, but better use :php:`$request->getServerParams()['HTTP_ACCEPT_LANGUAGE']` instead
+- :php:`REMOTE_HOST` is now :php:`->getRemoteHost()`, but better use :php:`$request->getServerParams()['REMOTE_HOST']` instead
+- :php:`QUERY_STRING` is now :php:`->getQueryString()`, but better use :php:`$request->getServerParams()['QUERY_STRING']` instead
+
+
+Impact
+======
+
+The PSR-7 request objects created by TYPO3 now contain an instance of :php:`NormalizedParams` which can
+be used instead of :php:`GeneralUtility::getIndpEnv()` to access normalized server params.
+
+.. index:: PHP-API
\ No newline at end of file
diff --git a/typo3/sysext/core/Tests/Unit/Http/NormalizedParamsTest.php b/typo3/sysext/core/Tests/Unit/Http/NormalizedParamsTest.php
new file mode 100644 (file)
index 0000000..72e6e22
--- /dev/null
@@ -0,0 +1,1100 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Core\Tests\Unit\Http;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use Psr\Http\Message\ServerRequestInterface;
+use TYPO3\CMS\Core\Http\NormalizedParams;
+use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
+
+/**
+ * Test case
+ */
+class NormalizedParamsTest extends UnitTestCase
+{
+    /**
+     * @return array[]
+     */
+    public function getHttpHostReturnsSanitizedValueDataProvider(): array
+    {
+        return [
+            'simple HTTP_HOST' => [
+                [
+                    'HTTP_HOST' => 'www.domain.com'
+                ],
+                [],
+                'www.domain.com'
+            ],
+            'first HTTP_X_FORWARDED_HOST from configured proxy' => [
+                [
+                    'HTTP_HOST' => '',
+                    'REMOTE_ADDR' => '123.123.123.123',
+                    'HTTP_X_FORWARDED_HOST' => 'www.domain1.com, www.domain2.com,'
+                ],
+                [
+                    'SYS' => [
+                        'reverseProxyIP' => ' 123.123.123.123',
+                        'reverseProxyHeaderMultiValue' => 'first',
+                    ]
+                ],
+                'www.domain1.com',
+            ],
+            'last HTTP_X_FORWARDED_HOST from configured proxy' => [
+                [
+                    'HTTP_HOST' => '',
+                    'REMOTE_ADDR' => '123.123.123.123',
+                    'HTTP_X_FORWARDED_HOST' => 'www.domain1.com, www.domain2.com,'
+                ],
+                [
+                    'SYS' => [
+                        'reverseProxyIP' => '123.123.123.123',
+                        'reverseProxyHeaderMultiValue' => 'last',
+                    ]
+                ],
+                'www.domain2.com',
+            ],
+            'simple HTTP_HOST if reverseProxyHeaderMultiValue is not configured' => [
+                [
+                    'HTTP_HOST' => 'www.domain.com',
+                    'REMOTE_ADDR' => '123.123.123.123',
+                    'HTTP_X_FORWARDED_HOST' => 'www.domain1.com'
+                ],
+                [
+                    'SYS' => [
+                        'reverseProxyIP' => '123.123.123.123',
+                    ]
+                ],
+                'www.domain.com',
+            ],
+            'simple HTTP_HOST if proxy IP does not match' => [
+                [
+                    'HTTP_HOST' => 'www.domain.com',
+                    'REMOTE_ADDR' => '123.123.123.123',
+                    'HTTP_X_FORWARDED_HOST' => 'www.domain1.com'
+                ],
+                [
+                    'SYS' => [
+                        'reverseProxyIP' => '234.234.234.234',
+                        'reverseProxyHeaderMultiValue' => 'last',
+                    ]
+                ],
+                'www.domain.com',
+            ],
+            'simple HTTP_HOST if REMOTE_ADDR misses' => [
+                [
+                    'HTTP_HOST' => 'www.domain.com',
+                    'HTTP_X_FORWARDED_HOST' => 'www.domain1.com'
+                ],
+                [
+                    'SYS' => [
+                        'reverseProxyIP' => '234.234.234.234',
+                        'reverseProxyHeaderMultiValue' => 'last',
+                    ]
+                ],
+                'www.domain.com',
+            ],
+            'simple HTTP_HOST if HTTP_X_FORWARDED_HOST is empty' => [
+                [
+                    'HTTP_HOST' => 'www.domain.com',
+                    'REMOTE_ADDR' => '123.123.123.123',
+                    'HTTP_X_FORWARDED_HOST' => ''
+                ],
+                [
+                    'SYS' => [
+                        'reverseProxyIP' => '123.123.123.123',
+                        'reverseProxyHeaderMultiValue' => 'last',
+                    ]
+                ],
+                'www.domain.com',
+            ],
+        ];
+    }
+
+    /**
+     * @test
+     * @dataProvider getHttpHostReturnsSanitizedValueDataProvider
+     * @param array $serverParams
+     * @param array $typo3ConfVars
+     * @param string $expected
+     */
+    public function getHttpHostReturnsSanitizedValue(array $serverParams, array $typo3ConfVars, string $expected)
+    {
+        $serverRequestProphecy = $this->prophesize(ServerRequestInterface::class);
+        $serverRequestProphecy->getServerParams()->willReturn($serverParams);
+        $serverRequestParameters = new NormalizedParams($serverRequestProphecy->reveal(), $typo3ConfVars, '', '');
+        $this->assertSame($expected, $serverRequestParameters->getHttpHost());
+    }
+
+    /**
+     * @return array[]
+     */
+    public function isHttpsReturnSanitizedValueDataProvider(): array
+    {
+        return [
+            'false if nothing special is set' => [
+                [
+                    'HTTP_HOST' => 'www.domain.com',
+                ],
+                [],
+                false
+            ],
+            'true if SSL_SESSION_ID is set' => [
+                [
+                    'HTTP_HOST' => 'www.domain.com',
+                    'SSL_SESSION_ID' => 'foo',
+                ],
+                [],
+                true
+            ],
+            'false if SSL_SESSION_ID is empty' => [
+                [
+                    'HTTP_HOST' => 'www.domain.com',
+                    'SSL_SESSION_ID' => '',
+                ],
+                [],
+                false
+            ],
+            'true if HTTPS is "ON"' => [
+                [
+                    'HTTP_HOST' => 'www.domain.com',
+                    'HTTPS' => 'ON',
+                ],
+                [],
+                true,
+            ],
+            'true if HTTPS is "on"' => [
+                [
+                    'HTTP_HOST' => 'www.domain.com',
+                    'HTTPS' => 'on',
+                ],
+                [],
+                true,
+            ],
+            'true if HTTPS is "1"' => [
+                [
+                    'HTTP_HOST' => 'www.domain.com',
+                    'HTTPS' => '1',
+                ],
+                [],
+                true,
+            ],
+            'false if HTTPS is "0"' => [
+                [
+                    'HTTP_HOST' => 'www.domain.com',
+                    'HTTPS' => '0',
+                ],
+                [],
+                false,
+            ],
+            'false if HTTPS is not on' => [
+                [
+                    'HTTP_HOST' => 'www.domain.com',
+                    'HTTPS' => 'off',
+                ],
+                [],
+                false,
+            ],
+            'false if HTTPS is empty' => [
+                [
+                    'HTTP_HOST' => 'www.domain.com',
+                    'HTTPS' => '',
+                ],
+                [],
+                false,
+            ],
+            'true if ssl proxy IP matches REMOTE_ADDR' => [
+                [
+                    'HTTP_HOST' => 'www.domain.com',
+                    'REMOTE_ADDR' => '123.123.123.123 ',
+                ],
+                [
+                    'SYS' => [
+                        'reverseProxySSL' => ' 123.123.123.123',
+                    ],
+                ],
+                true
+            ],
+            'false if ssl proxy IP does not match REMOTE_ADDR' => [
+                [
+                    'HTTP_HOST' => 'www.domain.com',
+                    'REMOTE_ADDR' => '123.123.123.123',
+                ],
+                [
+                    'SYS' => [
+                        'reverseProxySSL' => '234.234.234.234',
+                    ],
+                ],
+                false
+            ],
+            'true if SSL proxy is * and reverse proxy IP matches REMOTE_ADDR' => [
+                [
+                    'HTTP_HOST' => 'www.domain.com',
+                    'REMOTE_ADDR' => '123.123.123.123',
+                ],
+                [
+                    'SYS' => [
+                        'reverseProxySSL' => '*',
+                        'reverseProxyIP' => '123.123.123.123',
+                    ],
+                ],
+                true
+            ],
+            'false if SSL proxy is * and reverse proxy IP does not match REMOTE_ADDR' => [
+                [
+                    'HTTP_HOST' => 'www.domain.com',
+                    'REMOTE_ADDR' => '123.123.123.123',
+                ],
+                [
+                    'SYS' => [
+                        'reverseProxySSL' => '*',
+                        'reverseProxyIP' => '234.234.234.234',
+                    ],
+                ],
+                false
+            ]
+        ];
+    }
+
+    /**
+     * @test
+     * @dataProvider isHttpsReturnSanitizedValueDataProvider
+     * @param array $serverParams
+     * @param array $typo3ConfVars
+     * @param bool $expected
+     */
+    public function isHttpsReturnSanitizedValue(array $serverParams, array $typo3ConfVars, bool $expected)
+    {
+        $serverRequestProphecy = $this->prophesize(ServerRequestInterface::class);
+        $serverRequestProphecy->getServerParams()->willReturn($serverParams);
+        $serverRequestParameters = new NormalizedParams($serverRequestProphecy->reveal(), $typo3ConfVars, '', '');
+        $this->assertSame($expected, $serverRequestParameters->isHttps());
+    }
+
+    /**
+     * @test
+     */
+    public function getRequestHostReturnsRequestHost()
+    {
+        $serverParams = [
+            'HTTP_HOST' => 'www.domain.com',
+            'HTTPS' => 'on',
+        ];
+        $expected = 'https://www.domain.com';
+        $serverRequestProphecy = $this->prophesize(ServerRequestInterface::class);
+        $serverRequestProphecy->getServerParams()->willReturn($serverParams);
+        $serverRequestParameters = new NormalizedParams($serverRequestProphecy->reveal(), [], '', '');
+        $this->assertSame($expected, $serverRequestParameters->getRequestHost());
+    }
+
+    /**
+     * @return array[]
+     */
+    public function getScriptNameReturnsExpectedValueDataProvider(): array
+    {
+        return [
+            'empty string if nothing is set' => [
+                [
+                    'HTTP_HOST' => 'www.domain.com',
+                ],
+                [],
+                ''
+            ],
+            'use ORIG_PATH_INFO' => [
+                [
+                    'ORIG_PATH_INFO' => '/orig/path/info.php',
+                    'PATH_INFO' => '/path/info.php',
+                    'ORIG_SCRIPT_NAME' => '/orig/script/name.php',
+                    'SCRIPT_NAME' => '/script/name.php',
+                ],
+                [],
+                '/orig/path/info.php',
+            ],
+            'use PATH_INFO' => [
+                [
+                    'PATH_INFO' => '/path/info.php',
+                    'ORIG_SCRIPT_NAME' => '/orig/script/name.php',
+                    'SCRIPT_NAME' => '/script/name.php',
+                ],
+                [],
+                '/path/info.php',
+            ],
+            'use ORIG_SCRIPT_NAME' => [
+                [
+                    'ORIG_SCRIPT_NAME' => '/orig/script/name.php',
+                    'SCRIPT_NAME' => '/script/name.php',
+                ],
+                [],
+                '/orig/script/name.php',
+            ],
+            'use SCRIPT_NAME' => [
+                [
+                    'SCRIPT_NAME' => '/script/name.php',
+                ],
+                [],
+                '/script/name.php',
+            ],
+            'add proxy ssl prefix' => [
+                [
+                    'REMOTE_ADDR' => '123.123.123.123',
+                    'HTTPS' => 'on',
+                    'PATH_INFO' => '/path/info.php',
+                ],
+                [
+                    'SYS' => [
+                        'reverseProxyIP' => '123.123.123.123',
+                        'reverseProxyPrefixSSL' => '/proxyPrefixSSL',
+                    ],
+                ],
+                '/proxyPrefixSSL/path/info.php',
+            ],
+            'add proxy prefix' => [
+                [
+                    'REMOTE_ADDR' => '123.123.123.123',
+                    'PATH_INFO' => '/path/info.php',
+                ],
+                [
+                    'SYS' => [
+                        'reverseProxyIP' => '123.123.123.123',
+                        'reverseProxyPrefix' => '/proxyPrefix',
+                    ],
+                ],
+                '/proxyPrefix/path/info.php',
+            ],
+        ];
+    }
+
+    /**
+     * @test
+     * @dataProvider getScriptNameReturnsExpectedValueDataProvider
+     * @param array $serverParams
+     * @param array $typo3ConfVars
+     * @param string $expected
+     */
+    public function getScriptNameReturnsExpectedValue(array $serverParams, array $typo3ConfVars, string $expected)
+    {
+        $serverRequestProphecy = $this->prophesize(ServerRequestInterface::class);
+        $serverRequestProphecy->getServerParams()->willReturn($serverParams);
+        $serverRequestParameters = new NormalizedParams($serverRequestProphecy->reveal(), $typo3ConfVars, '', '');
+        $this->assertSame($expected, $serverRequestParameters->getScriptName());
+    }
+
+    /**
+     * @return array[]
+     */
+    public function getRequestUriReturnsExpectedValueDataProvider(): array
+    {
+        return [
+            'slash if nothing is set' => [
+                [
+                    'HTTP_HOST' => 'www.domain.com',
+                ],
+                [],
+                '/'
+            ],
+            'use REQUEST_URI' => [
+                [
+                    'HTTP_HOST' => 'www.domain.com',
+                    'REQUEST_URI' => 'typo3/index.php?route=foo/bar&id=42',
+                ],
+                [],
+                '/typo3/index.php?route=foo/bar&id=42',
+            ],
+            'use query string and script name if REQUEST_URI is not set' => [
+                [
+                    'QUERY_STRING' => 'route=foo/bar&id=42',
+                    'SCRIPT_NAME' => '/typo3/index.php',
+                ],
+                [],
+                '/typo3/index.php?route=foo/bar&id=42',
+            ],
+            'prefix with proxy prefix with ssl if using REQUEST_URI' => [
+                [
+                    'HTTP_HOST' => 'www.domain.com',
+                    'REMOTE_ADDR' => '123.123.123.123',
+                    'HTTPS' => 'on',
+                    'REQUEST_URI' => 'typo3/index.php?route=foo/bar&id=42',
+                ],
+                [
+                    'SYS' => [
+                        'reverseProxyIP' => '123.123.123.123',
+                        'reverseProxyPrefixSSL' => '/proxyPrefixSSL',
+                    ],
+                ],
+                '/proxyPrefixSSL/typo3/index.php?route=foo/bar&id=42',
+            ],
+            'prefix with proxy prefix if using REQUEST_URI' => [
+                [
+                    'HTTP_HOST' => 'www.domain.com',
+                    'REMOTE_ADDR' => '123.123.123.123',
+                    'REQUEST_URI' => 'typo3/index.php?route=foo/bar&id=42',
+                ],
+                [
+                    'SYS' => [
+                        'reverseProxyIP' => '123.123.123.123',
+                        'reverseProxyPrefix' => '/proxyPrefix',
+                    ],
+                ],
+                '/proxyPrefix/typo3/index.php?route=foo/bar&id=42',
+            ],
+            'prefix with proxy prefix with ssl if using query string and script name' => [
+                [
+                    'REMOTE_ADDR' => '123.123.123.123',
+                    'HTTPS' => 'on',
+                    'QUERY_STRING' => 'route=foo/bar&id=42',
+                    'SCRIPT_NAME' => '/typo3/index.php',
+                ],
+                [
+                    'SYS' => [
+                        'reverseProxyIP' => '123.123.123.123',
+                        'reverseProxyPrefixSSL' => '/proxyPrefixSSL',
+                    ],
+                ],
+                '/proxyPrefixSSL/typo3/index.php?route=foo/bar&id=42',
+            ],
+            'prefix with proxy prefix if using query string and script name' => [
+                [
+                    'REMOTE_ADDR' => '123.123.123.123',
+                    'HTTPS' => 'on',
+                    'QUERY_STRING' => 'route=foo/bar&id=42',
+                    'SCRIPT_NAME' => '/typo3/index.php',
+                ],
+                [
+                    'SYS' => [
+                        'reverseProxyIP' => '123.123.123.123',
+                        'reverseProxyPrefix' => '/proxyPrefix',
+                    ],
+                ],
+                '/proxyPrefix/typo3/index.php?route=foo/bar&id=42',
+            ],
+        ];
+    }
+
+    /**
+     * @test
+     * @dataProvider getRequestUriReturnsExpectedValueDataProvider
+     * @param array $serverParams
+     * @param array $typo3ConfVars
+     * @param string $expected
+     */
+    public function getRequestUriReturnsExpectedValue(array $serverParams, array $typo3ConfVars, string $expected)
+    {
+        $serverRequestProphecy = $this->prophesize(ServerRequestInterface::class);
+        $serverRequestProphecy->getServerParams()->willReturn($serverParams);
+        $serverRequestParameters = new NormalizedParams($serverRequestProphecy->reveal(), $typo3ConfVars, '', '');
+        $this->assertSame($expected, $serverRequestParameters->getRequestUri());
+    }
+
+    /**
+     * @test
+     */
+    public function getRequestUriFetchesFromConfiguredRequestUriVar()
+    {
+        $GLOBALS['foo']['bar'] = '/foo/bar.php';
+        $serverParams = [
+            'HTTP_HOST' => 'www.domain.com',
+        ];
+        $typo3ConfVars = [
+            'SYS' => [
+                'requestURIvar' => 'foo|bar',
+            ],
+        ];
+        $expected = '/foo/bar.php';
+        $serverRequestProphecy = $this->prophesize(ServerRequestInterface::class);
+        $serverRequestProphecy->getServerParams()->willReturn($serverParams);
+        $serverRequestParameters = new NormalizedParams($serverRequestProphecy->reveal(), $typo3ConfVars, '', '');
+        $this->assertSame($expected, $serverRequestParameters->getRequestUri());
+    }
+
+    /**
+     * @test
+     */
+    public function getRequestUrlReturnsExpectedValue()
+    {
+        $serverParams = [
+            'HTTP_HOST' => 'www.domain.com',
+            'REQUEST_URI' => 'typo3/index.php?route=foo/bar&id=42',
+        ];
+        $expected = 'http://www.domain.com/typo3/index.php?route=foo/bar&id=42';
+        $serverRequestProphecy = $this->prophesize(ServerRequestInterface::class);
+        $serverRequestProphecy->getServerParams()->willReturn($serverParams);
+        $serverRequestParameters = new NormalizedParams($serverRequestProphecy->reveal(), [], '', '');
+        $this->assertSame($expected, $serverRequestParameters->getRequestUrl());
+    }
+
+    /**
+     * @test
+     */
+    public function getRequestScriptReturnsExpectedValue()
+    {
+        $serverParams = [
+            'HTTP_HOST' => 'www.domain.com',
+            'PATH_INFO' => '/typo3/index.php',
+        ];
+        $expected = 'http://www.domain.com/typo3/index.php';
+        $serverRequestProphecy = $this->prophesize(ServerRequestInterface::class);
+        $serverRequestProphecy->getServerParams()->willReturn($serverParams);
+        $serverRequestParameters = new NormalizedParams($serverRequestProphecy->reveal(), [], '', '');
+        $this->assertSame($expected, $serverRequestParameters->getRequestScript());
+    }
+
+    /**
+     * @test
+     */
+    public function getRequestDirReturnsExpectedValue()
+    {
+        $serverParams = [
+            'HTTP_HOST' => 'www.domain.com',
+            'PATH_INFO' => '/typo3/index.php',
+        ];
+        $expected = 'http://www.domain.com/typo3/';
+        $serverRequestProphecy = $this->prophesize(ServerRequestInterface::class);
+        $serverRequestProphecy->getServerParams()->willReturn($serverParams);
+        $serverRequestParameters = new NormalizedParams($serverRequestProphecy->reveal(), [], '', '');
+        $this->assertSame($expected, $serverRequestParameters->getRequestDir());
+    }
+
+    /**
+     * @return array[]
+     */
+    public function isBehindReverseProxyReturnsExpectedValueDataProvider(): array
+    {
+        return [
+            'false with empty data' => [
+                [
+                    'HTTP_HOST' => 'www.domain.com',
+                ],
+                [],
+                false
+            ],
+            'false if REMOTE_ADDR and reverseProxyIP do not match' => [
+                [
+                    'HTTP_HOST' => 'www.domain.com',
+                    'REMOTE_ADDR' => '100.100.100.100',
+                ],
+                [
+                    'SYS' => [
+                        'reverseProxyIP' => '200.200.200.200',
+                    ],
+                ],
+                false
+            ],
+            'true if REMOTE_ADDR matches configured reverseProxyIP' => [
+                [
+                    'HTTP_HOST' => 'www.domain.com',
+                    'REMOTE_ADDR' => '100.100.100.100',
+                ],
+                [
+                    'SYS' => [
+                        'reverseProxyIP' => '100.100.100.100',
+                    ],
+                ],
+                true
+            ],
+            'true if trimmed REMOTE_ADDR matches configured trimmed reverseProxyIP' => [
+                [
+                    'HTTP_HOST' => 'www.domain.com',
+                    'REMOTE_ADDR' => ' 100.100.100.100 ',
+                ],
+                [
+                    'SYS' => [
+                        'reverseProxyIP' => '  100.100.100.100  ',
+                    ],
+                ],
+                true
+            ]
+        ];
+    }
+
+    /**
+     * @test
+     * @dataProvider isBehindReverseProxyReturnsExpectedValueDataProvider
+     * @param array $serverParams
+     * @param array $typo3ConfVars
+     * @param bool $expected
+     */
+    public function isBehindReverseProxyReturnsExpectedValue(array $serverParams, array $typo3ConfVars, bool $expected)
+    {
+        $serverRequestProphecy = $this->prophesize(ServerRequestInterface::class);
+        $serverRequestProphecy->getServerParams()->willReturn($serverParams);
+        $serverRequestParameters = new NormalizedParams($serverRequestProphecy->reveal(), $typo3ConfVars, '', '');
+        $this->assertSame($expected, $serverRequestParameters->isBehindReverseProxy());
+    }
+
+    /**
+     * @return array[]
+     */
+    public function getRemoteAddressReturnsExpectedValueDataProvider(): array
+    {
+        return [
+            'simple REMOTE_ADDR' => [
+                [
+                    'HTTP_HOST' => 'www.domain.com',
+                    'REMOTE_ADDR' => ' 123.123.123.123 ',
+                ],
+                [],
+                '123.123.123.123'
+            ],
+            'reverse proxy with last HTTP_X_FORWARDED_FOR' => [
+                [
+                    'HTTP_HOST' => 'www.domain.com',
+                    'REMOTE_ADDR' => ' 123.123.123.123 ',
+                    'HTTP_X_FORWARDED_FOR' => ' 234.234.234.234, 235.235.235.235,',
+                ],
+                [
+                    'SYS' => [
+                        'reverseProxyIP' => '123.123.123.123',
+                        'reverseProxyHeaderMultiValue' => ' last ',
+                    ]
+                ],
+                '235.235.235.235'
+            ],
+            'reverse proxy with first HTTP_X_FORWARDED_FOR' => [
+                [
+                    'HTTP_HOST' => 'www.domain.com',
+                    'REMOTE_ADDR' => ' 123.123.123.123 ',
+                    'HTTP_X_FORWARDED_FOR' => ' 234.234.234.234, 235.235.235.235,',
+                ],
+                [
+                    'SYS' => [
+                        'reverseProxyIP' => '123.123.123.123 ',
+                        'reverseProxyHeaderMultiValue' => ' first ',
+                    ]
+                ],
+                '234.234.234.234'
+            ],
+            'reverse proxy with broken reverseProxyHeaderMultiValue returns REMOTE_ADDR' => [
+                [
+                    'HTTP_HOST' => 'www.domain.com',
+                    'REMOTE_ADDR' => ' 123.123.123.123 ',
+                    'HTTP_X_FORWARDED_FOR' => ' 234.234.234.234, 235.235.235.235,',
+                ],
+                [
+                    'SYS' => [
+                        'reverseProxyIP' => '123.123.123.123 ',
+                        'reverseProxyHeaderMultiValue' => ' foo ',
+                    ]
+                ],
+                '123.123.123.123'
+            ],
+        ];
+    }
+
+    /**
+     * @test
+     * @dataProvider getRemoteAddressReturnsExpectedValueDataProvider
+     * @param array $serverParams
+     * @param array $typo3ConfVars
+     * @param string $expected
+     */
+    public function getRemoteAddressReturnsExpectedValue(array $serverParams, array $typo3ConfVars, string $expected)
+    {
+        $serverRequestProphecy = $this->prophesize(ServerRequestInterface::class);
+        $serverRequestProphecy->getServerParams()->willReturn($serverParams);
+        $serverRequestParameters = new NormalizedParams($serverRequestProphecy->reveal(), $typo3ConfVars, '', '');
+        $this->assertSame($expected, $serverRequestParameters->getRemoteAddress());
+    }
+
+    /**
+     * @return array
+     */
+    public static function getRequestHostOnlyReturnsExpectedValueDataProvider(): array
+    {
+        return [
+            'localhost ipv4 without port' => [
+                [
+                    'HTTP_HOST' => '127.0.0.1',
+                ],
+                '127.0.0.1'
+            ],
+            'localhost ipv4 with port' => [
+                [
+                    'HTTP_HOST' => '127.0.0.1:81',
+                ],
+                '127.0.0.1'
+            ],
+            'localhost ipv6 without port' => [
+                [
+                    'HTTP_HOST' => '[::1]',
+                ],
+                '[::1]'
+            ],
+            'localhost ipv6 with port' => [
+                [
+                    'HTTP_HOST' => '[::1]:81',
+                ],
+                '[::1]'
+            ],
+            'ipv6 without port' => [
+                [
+                    'HTTP_HOST' => '[2001:DB8::1]',
+                ],
+                '[2001:DB8::1]'
+            ],
+            'ipv6 with port' => [
+                [
+                    'HTTP_HOST' => '[2001:DB8::1]:81',
+                ],
+                '[2001:DB8::1]'
+            ],
+            'hostname without port' => [
+                [
+                    'HTTP_HOST' => 'lolli.did.this',
+                ],
+                'lolli.did.this'
+            ],
+            'hostname with port' => [
+                [
+                    'HTTP_HOST' => 'lolli.did.this:42',
+                ],
+                'lolli.did.this'
+            ],
+        ];
+    }
+
+    /**
+     * @test
+     * @dataProvider getRequestHostOnlyReturnsExpectedValueDataProvider
+     * @param array $serverParams
+     * @param string $expected
+     */
+    public function getRequestHostOnlyReturnsExpectedValue(array $serverParams, string $expected)
+    {
+        $serverRequestProphecy = $this->prophesize(ServerRequestInterface::class);
+        $serverRequestProphecy->getServerParams()->willReturn($serverParams);
+        $serverRequestParameters = new NormalizedParams($serverRequestProphecy->reveal(), [], '', '');
+        $this->assertSame($expected, $serverRequestParameters->getRequestHostOnly());
+    }
+
+    /**
+     * @return array
+     */
+    public static function getRequestPortOnlyReturnsExpectedValueDataProvider(): array
+    {
+        return [
+            'localhost ipv4 without port' => [
+                [
+                    'HTTP_HOST' => '127.0.0.1',
+                ],
+                0
+            ],
+            'localhost ipv4 with port' => [
+                [
+                    'HTTP_HOST' => '127.0.0.1:81',
+                ],
+                81
+            ],
+            'localhost ipv6 without port' => [
+                [
+                    'HTTP_HOST' => '[::1]',
+                ],
+                0
+            ],
+            'localhost ipv6 with port' => [
+                [
+                    'HTTP_HOST' => '[::1]:81',
+                ],
+                81
+            ],
+            'ipv6 without port' => [
+                [
+                    'HTTP_HOST' => '[2001:DB8::1]',
+                ],
+                0
+            ],
+            'ipv6 with port' => [
+                [
+                    'HTTP_HOST' => '[2001:DB8::1]:81',
+                ],
+                81
+            ],
+            'hostname without port' => [
+                [
+                    'HTTP_HOST' => 'lolli.did.this',
+                ],
+                0
+            ],
+            'hostname with port' => [
+                [
+                    'HTTP_HOST' => 'lolli.did.this:42',
+                ],
+                42
+            ],
+        ];
+    }
+
+    /**
+     * @test
+     * @dataProvider getRequestPortOnlyReturnsExpectedValueDataProvider
+     * @param array $serverParams
+     * @param int $expected
+     */
+    public function getRequestPortReturnsExpectedValue(array $serverParams, int $expected)
+    {
+        $serverRequestProphecy = $this->prophesize(ServerRequestInterface::class);
+        $serverRequestProphecy->getServerParams()->willReturn($serverParams);
+        $serverRequestParameters = new NormalizedParams($serverRequestProphecy->reveal(), [], '', '');
+        $this->assertSame($expected, $serverRequestParameters->getRequestPort());
+    }
+
+    /**
+     * @test
+     */
+    public function getScriptFilenameReturnsThirdConstructorArgument()
+    {
+        $serverParams = [
+            'HTTP_HOST' => 'www.domain.com',
+            'SCRIPT_NAME' => '/typo3/index.php',
+        ];
+        $pathSite = '/var/www/';
+        $serverRequestProphecy = $this->prophesize(ServerRequestInterface::class);
+        $serverRequestProphecy->getServerParams()->willReturn($serverParams);
+        $serverRequestParameters = new NormalizedParams($serverRequestProphecy->reveal(), [], '/var/www/typo3/index.php', $pathSite);
+        $this->assertSame('/var/www/typo3/index.php', $serverRequestParameters->getScriptFilename());
+    }
+
+    /**
+     * @test
+     */
+    public function getDocumentRootReturnsExpectedPath()
+    {
+        $serverParams = [
+            'HTTP_HOST' => 'www.domain.com',
+            'SCRIPT_NAME' => '/typo3/index.php',
+        ];
+        $pathThisScript = '/var/www/myInstance/Web/typo3/index.php';
+        $pathSite = '/var/www/myInstance/Web/';
+        $expected = '/var/www/myInstance/Web';
+        $serverRequestProphecy = $this->prophesize(ServerRequestInterface::class);
+        $serverRequestProphecy->getServerParams()->willReturn($serverParams);
+        $serverRequestParameters = new NormalizedParams($serverRequestProphecy->reveal(), [], $pathThisScript, $pathSite);
+        $this->assertSame($expected, $serverRequestParameters->getDocumentRoot());
+    }
+
+    /**
+     * @test
+     */
+    public function getSiteUrlReturnsExpectedUrl()
+    {
+        $serverParams = [
+            'SCRIPT_NAME' => '/typo3/index.php',
+            'HTTP_HOST' => 'www.domain.com',
+            'PATH_INFO' => '/typo3/index.php',
+        ];
+        $pathThisScript = '/var/www/myInstance/Web/typo3/index.php';
+        $pathSite = '/var/www/myInstance/Web/';
+        $expected = 'http://www.domain.com/';
+        $serverRequestProphecy = $this->prophesize(ServerRequestInterface::class);
+        $serverRequestProphecy->getServerParams()->willReturn($serverParams);
+        $serverRequestParameters = new NormalizedParams($serverRequestProphecy->reveal(), [], $pathThisScript, $pathSite);
+        $this->assertSame($expected, $serverRequestParameters->getSiteUrl());
+    }
+
+    /**
+     * @return array[]
+     */
+    public function getSitePathReturnsExpectedPathDataProvider()
+    {
+        return [
+            'empty config' => [
+                [],
+                '',
+                '',
+                ''
+            ],
+            'not in a sub directory' => [
+                [
+                    'SCRIPT_NAME' => '/typo3/index.php',
+                    'HTTP_HOST' => 'www.domain.com',
+                ],
+                '/var/www/myInstance/Web/typo3/index.php',
+                '/var/www/myInstance/Web/',
+                '/'
+            ],
+            'in a sub directory' => [
+                [
+                    'SCRIPT_NAME' => '/some/sub/dir/typo3/index.php',
+                    'HTTP_HOST' => 'www.domain.com',
+                ],
+                '/var/www/myInstance/Web/typo3/index.php',
+                '/var/www/myInstance/Web/',
+                '/some/sub/dir/'
+            ],
+        ];
+    }
+
+    /**
+     * @test
+     * @dataProvider getSitePathReturnsExpectedPathDataProvider
+     * @param array $serverParams
+     * @param string $pathThisScript
+     * @param string $pathSite
+     * @param string $expected
+     */
+    public function getSitePathReturnsExpectedPath(array $serverParams, string $pathThisScript, string $pathSite, string $expected)
+    {
+        $serverRequestProphecy = $this->prophesize(ServerRequestInterface::class);
+        $serverRequestProphecy->getServerParams()->willReturn($serverParams);
+        $serverRequestParameters = new NormalizedParams($serverRequestProphecy->reveal(), [], $pathThisScript, $pathSite);
+        $this->assertSame($expected, $serverRequestParameters->getSitePath());
+    }
+
+    /**
+     * @return array[]
+     */
+    public function getSiteScriptReturnsExpectedPathDataProvider()
+    {
+        return [
+            'not in a sub directory' => [
+                [
+                    'SCRIPT_NAME' => '/typo3/index.php?id=42&foo=bar',
+                    'HTTP_HOST' => 'www.domain.com',
+                ],
+                '/var/www/myInstance/Web/typo3/index.php',
+                '/var/www/myInstance/Web/',
+                'typo3/index.php?id=42&foo=bar'
+            ],
+            'in a sub directory' => [
+                [
+                    'SCRIPT_NAME' => '/some/sub/dir/typo3/index.php?id=42&foo=bar',
+                    'HTTP_HOST' => 'www.domain.com',
+                ],
+                '/var/www/myInstance/Web/typo3/index.php',
+                '/var/www/myInstance/Web/',
+                'typo3/index.php?id=42&foo=bar'
+            ],
+        ];
+    }
+
+    /**
+     * @test
+     * @dataProvider getSiteScriptReturnsExpectedPathDataProvider
+     * @param array $serverParams
+     * @param string $pathThisScript
+     * @param string $pathSite
+     * @param string $expected
+     */
+    public function getSiteScriptReturnsExpectedPath(array $serverParams, string $pathThisScript, string $pathSite, string $expected)
+    {
+        $serverRequestProphecy = $this->prophesize(ServerRequestInterface::class);
+        $serverRequestProphecy->getServerParams()->willReturn($serverParams);
+        $serverRequestParameters = new NormalizedParams($serverRequestProphecy->reveal(), [], $pathThisScript, $pathSite);
+        $this->assertSame($expected, $serverRequestParameters->getSiteScript());
+    }
+
+    /**
+     * @test
+     */
+    public function getPathInfoReturnsExpectedValue()
+    {
+        $serverParams = [
+            'PATH_INFO' => '/typo3/index.php',
+        ];
+        $expected = '/typo3/index.php';
+        $serverRequestProphecy = $this->prophesize(ServerRequestInterface::class);
+        $serverRequestProphecy->getServerParams()->willReturn($serverParams);
+        $serverRequestParameters = new NormalizedParams($serverRequestProphecy->reveal(), [], '', '');
+        $this->assertSame($expected, $serverRequestParameters->getPathInfo());
+    }
+
+    /**
+     * @test
+     */
+    public function getHttpRefererReturnsExpectedValue()
+    {
+        $serverParams = [
+            'HTTP_REFERER' => 'https://www.domain.com/typo3/index.php?id=42',
+        ];
+        $expected = 'https://www.domain.com/typo3/index.php?id=42';
+        $serverRequestProphecy = $this->prophesize(ServerRequestInterface::class);
+        $serverRequestProphecy->getServerParams()->willReturn($serverParams);
+        $serverRequestParameters = new NormalizedParams($serverRequestProphecy->reveal(), [], '', '');
+        $this->assertSame($expected, $serverRequestParameters->getHttpReferer());
+    }
+
+    /**
+     * @test
+     */
+    public function getHttpUserAgentReturnsExpectedValue()
+    {
+        $serverParams = [
+            'HTTP_USER_AGENT' => 'the client browser',
+        ];
+        $expected = 'the client browser';
+        $serverRequestProphecy = $this->prophesize(ServerRequestInterface::class);
+        $serverRequestProphecy->getServerParams()->willReturn($serverParams);
+        $serverRequestParameters = new NormalizedParams($serverRequestProphecy->reveal(), [], '', '');
+        $this->assertSame($expected, $serverRequestParameters->getHttpUserAgent());
+    }
+
+    /**
+     * @test
+     */
+    public function getHttpAcceptEncodingReturnsExpectedValue()
+    {
+        $serverParams = [
+            'HTTP_ACCEPT_ENCODING' => 'gzip, deflate',
+        ];
+        $expected = 'gzip, deflate';
+        $serverRequestProphecy = $this->prophesize(ServerRequestInterface::class);
+        $serverRequestProphecy->getServerParams()->willReturn($serverParams);
+        $serverRequestParameters = new NormalizedParams($serverRequestProphecy->reveal(), [], '', '');
+        $this->assertSame($expected, $serverRequestParameters->getHttpAcceptEncoding());
+    }
+
+    /**
+     * @test
+     */
+    public function getHttpAcceptLanguageReturnsExpectedValue()
+    {
+        $serverParams = [
+            'HTTP_ACCEPT_LANGUAGE' => 'de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7',
+        ];
+        $expected = 'de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7';
+        $serverRequestProphecy = $this->prophesize(ServerRequestInterface::class);
+        $serverRequestProphecy->getServerParams()->willReturn($serverParams);
+        $serverRequestParameters = new NormalizedParams($serverRequestProphecy->reveal(), [], '', '');
+        $this->assertSame($expected, $serverRequestParameters->getHttpAcceptLanguage());
+    }
+
+    /**
+     * @test
+     */
+    public function getRemoteHostReturnsExpectedValue()
+    {
+        $serverParams = [
+            'REMOTE_HOST' => 'www.clientDomain.com',
+        ];
+        $expected = 'www.clientDomain.com';
+        $serverRequestProphecy = $this->prophesize(ServerRequestInterface::class);
+        $serverRequestProphecy->getServerParams()->willReturn($serverParams);
+        $serverRequestParameters = new NormalizedParams($serverRequestProphecy->reveal(), [], '', '');
+        $this->assertSame($expected, $serverRequestParameters->getRemoteHost());
+    }
+
+    /**
+     * @test
+     */
+    public function getQueryStringReturnsExpectedValue()
+    {
+        $serverParams = [
+            'QUERY_STRING' => 'id=42&foo=bar',
+        ];
+        $expected = 'id=42&foo=bar';
+        $serverRequestProphecy = $this->prophesize(ServerRequestInterface::class);
+        $serverRequestProphecy->getServerParams()->willReturn($serverParams);
+        $serverRequestParameters = new NormalizedParams($serverRequestProphecy->reveal(), [], '', '');
+        $this->assertSame($expected, $serverRequestParameters->getQueryString());
+    }
+}
index e88c2ee..ea5e23c 100644 (file)
@@ -1949,7 +1949,7 @@ class GeneralUtilityTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
             'localhost IP' => ['127.0.0.1'],
             'relative path' => ['./relpath/file.txt'],
             'absolute path' => ['/abspath/file.txt?arg=value'],
-            'differnt host' => [GeneralUtility::getIndpEnv('TYPO3_REQUEST_HOST') . '.example.org']
+            'different host' => [GeneralUtility::getIndpEnv('TYPO3_REQUEST_HOST') . '.example.org']
         ];
     }
 
index 1808342..537b568 100644 (file)
@@ -20,10 +20,16 @@ return [
                 'typo3/cms-core/legacy-request-handler-dispatcher'
             ],
         ],
+        'typo3/cms-core/normalized-params-attribute' => [
+            'target' => \TYPO3\CMS\Core\Middleware\NormalizedParamsAttribute::class,
+            'after' => [
+                'typo3/cms-frontend/timetracker',
+            ]
+        ],
         'typo3/cms-frontend/preprocessing' => [
             'target' => \TYPO3\CMS\Frontend\Middleware\PreprocessRequestHook::class,
             'after' => [
-                'typo3/cms-frontend/timetracker'
+                'typo3/cms-core/normalized-params-attribute',
             ]
         ],
         'typo3/cms-frontend/eid' => [
index 2409ec1..85f49df 100644 (file)
@@ -15,4 +15,9 @@ return [
             'Breaking-82893-RemoveGlobalVariablePARSETIME_START.rst'
         ],
     ],
+    '$GLOBALS[\'TYPO3_REQUEST\']' => [
+        'restFiles' => [
+            'Deprecation-83736-DeprecatedGlobalsTYPO3_REQUEST.rst',
+        ],
+    ],
 ];