getServerParams() / $GLOBALS['_SERVER']). * * An instance of this class is available as PSR-7 ServerRequestInterface attribute: * * ``` * $normalizedParams = $request->getAttribute('normalizedParams') * ``` */ class NormalizedParams { /** * Sanitized HTTP_HOST value * * host[:port] * * - www.domain.com * - www.domain.com:443 * - 192.168.1.42:80 */ protected string $httpHost = ''; /** * True if request has been done via HTTPS */ protected bool $isHttps = false; /** * Sanitized HTTP_HOST with protocol * * scheme://host[:port] * * - https://www.domain.com */ protected string $requestHost = ''; /** * Host / domain part of HTTP_HOST, no port, no protocol * * - www.domain.com * - 192.168.1.42 */ protected string $requestHostOnly = ''; /** * Port of HTTP_HOST if given */ protected int $requestPort = 0; /** * Entry script path of URI, without domain and without query parameters, with leading / * * [path_script] * * - /index.php * - /typo3/index.php */ protected string $scriptName = ''; /** * REQUEST URI without domain and scheme, with trailing slash * * [path][?[query]] * * - /some/path?p1=parameter1&p2[key]=value * - /typo3/some/path?p1=parameter1&p2[key]=value */ protected string $requestUri = ''; /** * REQUEST URI with scheme, host, port, path and query * * scheme://host[:[port]][path][?[query]] * * - http://www.domain.com/some/path?p1=parameter1&p2[key]=value * - http://www.domain.com/typo3/some/path?id=42 */ protected string $requestUrl = ''; /** * REQUEST URI with scheme, host, port and path, but *without* query part * * scheme://host[:[port]][path_script] * * - http://www.domain.com/index.php * - http://www.domain.com/typo3/index.php */ protected string $requestScript = ''; /** * Full Uri with path, but without script name and query parts * * scheme://host[:[port]][path_dir] * * - http://www.domain.com/ * - http://www.domain.com/typo3/ */ protected string $requestDir = ''; /** * True if request via a reverse proxy is detected */ protected bool $isBehindReverseProxy = false; /** * IPv4 or IPv6 address of remote client with resolved proxy setup */ protected string $remoteAddress = ''; /** * Absolute server path to entry script on server filesystem * * - /var/www/index.php * - /var/www/typo3/index.php */ protected string $scriptFilename = ''; /** * Absolute server path to web document root without trailing slash * * - /var/www */ protected string $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/ */ protected string $siteUrl = ''; /** * Path part to frontend, no domain, no protocol * * - / * - /some/sub/dir/ */ protected string $sitePath = ''; /** * Path to script, without sub path if TYPO3 is running in sub directory, without trailing slash * * - index.php?id=42 * - /some/path?id=42 * - typo3/some/path?id=411 */ protected string $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] */ protected string $pathInfo = ''; /** * HTTP_REFERER * Will be deprecated later, use $request->getServerParams()['HTTP_REFERER'] instead * * scheme://host[:[port]][path] * * - https://www.domain.com/typo3/module/web/layout?id=42 */ protected string $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 */ protected string $httpUserAgent = ''; /** * HTTP_ACCEPT_ENCODING * Will be deprecated later, use $request->getServerParams()['HTTP_ACCEPT_ENCODING'] instead * * - gzip, deflate */ protected string $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 */ protected string $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 */ protected string $remoteHost = ''; /** * QUERY_STRING * Will be deprecated later, use $request->getServerParams()['QUERY_STRING'] instead * * [query] * * - id=42&foo=bar */ protected string $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 array $serverParams , usually coming from $_SERVER or $request->getServerParams() * @param array $configuration $GLOBALS['TYPO3_CONF_VARS']['SYS'] * @param string $pathThisScript Absolute server entry script path, usually found within Environment::getCurrentScript() * @param string $pathSite Absolute server path to document root, Environment::getPublicPath() */ public function __construct(array $serverParams, array $configuration, string $pathThisScript, string $pathSite) { $isBehindReverseProxy = $this->isBehindReverseProxy = self::determineIsBehindReverseProxy( $serverParams, $configuration ); $httpHost = $this->httpHost = self::determineHttpHost($serverParams, $configuration, $isBehindReverseProxy); $isHttps = $this->isHttps = self::determineHttps($serverParams, $configuration); $requestHost = $this->requestHost = ($isHttps ? 'https://' : 'http://') . $httpHost; $requestHostOnly = $this->requestHostOnly = self::determineRequestHostOnly($httpHost); $this->requestPort = self::determineRequestPort($httpHost, $requestHostOnly); $scriptNameOnFileSystem = self::determineScriptName( $serverParams, $configuration, $isHttps, $isBehindReverseProxy ); $scriptName = $this->scriptName = self::encodeFileSystemPathComponentForUrlPath($scriptNameOnFileSystem); $requestUri = $this->requestUri = self::determineRequestUri( $serverParams, $configuration, $isHttps, $scriptName, $isBehindReverseProxy ); $requestUrl = $this->requestUrl = $requestHost . $requestUri; $this->requestScript = $requestHost . $scriptName; $requestDir = $this->requestDir = $requestHost . GeneralUtility::dirname($scriptName) . '/'; $this->remoteAddress = self::determineRemoteAddress($serverParams, $configuration, $isBehindReverseProxy); $scriptFilename = $this->scriptFilename = $pathThisScript; $this->documentRoot = self::determineDocumentRoot($scriptNameOnFileSystem, $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'] ?? ''; } /** * Factory method, to allow TYPO3 to handle configuration options directly. * * @param array $serverParams - could be fulfilled by $_SERVER (on web requests) * @internal framework method. Not part of TYPO3 API. */ public static function createFromServerParams(array $serverParams, ?array $systemConfiguration = null): self { return new NormalizedParams( $serverParams, $systemConfiguration ?? $GLOBALS['TYPO3_CONF_VARS']['SYS'], Environment::getCurrentScript(), Environment::getPublicPath() ); } /** * Factory method for creating normalized params from a PSR-7 server request object * * @internal framework method. Not part of TYPO3 API. */ public static function createFromRequest(ServerRequestInterface $request, ?array $systemConfiguration = null): self { return static::createFromServerParams( $request->getServerParams(), $systemConfiguration ?? $GLOBALS['TYPO3_CONF_VARS']['SYS'] ); } private static function encodeFileSystemPathComponentForUrlPath(string $path): string { return implode('/', array_map(rawurlencode(...), explode('/', $path))); } /** * @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/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; } /** * Normalize HTTP_HOST by taking proxy configuration into account. * * @param array $serverParams Basically the $_SERVER, but from $request object * @param array $configuration $TYPO3_CONF_VARS['SYS'] array * @param bool $isBehindReverseProxy True if reverse proxy setup is detected * @return string Normalized HTTP_HOST */ protected static function determineHttpHost( array $serverParams, array $configuration, 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($configuration['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 // be configured for a working setup. It would be easier to only configure SYS/reverseProxyIP and fall // 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; } } return $httpHost; } /** * Determine if the client called via HTTPS. Takes proxy ssl terminator * configurations into account. * * How does TYPO3 determine if the connection was established via TLS/SSL/https? * 1. If reverseProxySSL matches, then we now that Client -> Proxy is SSL, * and Proxy -> App Server is non-SSL. SSL Termination happens at Proxy at ALL times. * 2. If reverseProxyIP matches, and HTTP_X_FORWARDED_PROTO is set, it is evaluated * 3. If no other matches, see webserverUsesHttps() * * Note: HTTP_X_FORWARDED_PROTO is ONLY evaluated at the point, where we know * that the incoming REMOTE_ADDR is a trusted proxy! * * @param array $serverParams Basically the $_SERVER, but from $request object * @param array $configuration $TYPO3_CONF_VARS['SYS'] array * @return bool True if request has been done via HTTPS */ protected static function determineHttps(array $serverParams, array $configuration): bool { $configuredProxySSL = trim($configuration['reverseProxySSL'] ?? ''); $configuredProxyRegular = trim($configuration['reverseProxyIP'] ?? ''); if ($configuredProxySSL === '*') { $configuredProxySSL = $configuredProxyRegular; } $httpsParam = (string)($serverParams['HTTPS'] ?? ''); if (GeneralUtility::cmpIP($serverParams['REMOTE_ADDR'] ?? '', $configuredProxySSL)) { $isHttps = true; } elseif (isset($serverParams['HTTP_X_FORWARDED_PROTO']) && GeneralUtility::cmpIP($serverParams['REMOTE_ADDR'] ?? '', $configuredProxyRegular)) { // If the X-Forwarded-Proto header is set, and we can trust the RemoteAddr to be a proxy, check it $isHttps = strtolower($serverParams['HTTP_X_FORWARDED_PROTO']) === 'https'; } else { // https://secure.php.net/manual/en/reserved.variables.server.php // "Set to a non-empty value if the script was queried through the HTTPS protocol." $isHttps = ($serverParams['SSL_SESSION_ID'] ?? '') || ($httpsParam !== '' && $httpsParam !== 'off' && $httpsParam !== '0'); } return $isHttps; } /** * Determine script name and path * * @param array $serverParams Basically the $_SERVER, but from $request object * @param array $configuration TYPO3_CONF_VARS['SYS'] 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 $configuration, bool $isHttps, bool $isBehindReverseProxy ): string { $scriptName = $serverParams['SCRIPT_NAME'] ?? ''; if ($isBehindReverseProxy) { // Add a prefix if TYPO3 is behind a proxy: ext-domain.com => int-server.com/prefix if ($isHttps && !empty($configuration['reverseProxyPrefixSSL'])) { $scriptName = $configuration['reverseProxyPrefixSSL'] . $scriptName; } elseif (!empty($configuration['reverseProxyPrefix'])) { $scriptName = $configuration['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 $configuration $TYPO3_CONF_VARS['SYS'] 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 $configuration, bool $isHttps, string $scriptName, bool $isBehindReverseProxy ): string { $proxyPrefixApplied = false; if (!empty($configuration['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'] [$firstLevel, $secondLevel] = GeneralUtility::trimExplode('|', $configuration['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($configuration['reverseProxyPrefixSSL'])) { $requestUri = $configuration['reverseProxyPrefixSSL'] . $requestUri; } elseif (!empty($configuration['reverseProxyPrefix'])) { $requestUri = $configuration['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 $configuration $TYPO3_CONF_VARS[SYS] array * @param bool $isBehindReverseProxy True if reverse proxy setup is detected * @return string Resolved REMOTE_ADDR */ protected static function determineRemoteAddress( array $serverParams, array $configuration, 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($configuration['reverseProxyHeaderMultiValue'] ?? ''); if (!empty($ip) && $configuredReverseProxyHeaderMultiValue === 'last') { $ip = (string)array_pop($ip); } elseif (!empty($ip) && $configuredReverseProxyHeaderMultiValue === 'first') { $ip = (string)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 $configuration $TYPO3_CONF_VARS[SYS] array * @return bool True if TYPO3 is behind a reverse proxy */ protected static function determineIsBehindReverseProxy($serverParams, $configuration): bool { return GeneralUtility::cmpIP( trim($serverParams['REMOTE_ADDR'] ?? ''), trim($configuration['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 $scriptNameOnFileSystem Entry script path of URI on file system, 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 $scriptNameOnFileSystem, 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($scriptNameOnFileSystem)); $scriptFilenameArray = explode('/', strrev($scriptFilename)); $path = []; foreach ($scriptNameArray as $segmentNumber => $segment) { if ($scriptFilenameArray[$segmentNumber] === (string)$segment) { $path[] = $segment; } else { break; } } $commonEnd = strrev(implode('/', $path)); if ($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 { $pathThisScriptDir = substr(dirname($pathThisScript), strlen($pathSite)) . '/'; $siteUrl = substr($requestDir, 0, -strlen($pathThisScriptDir)); return rtrim($siteUrl, '/') . '/'; } /** * Determine site path * * @param string $requestHost scheme://host[:port] * @param string $siteUrl Full Frontend Url */ protected static function determineSitePath(string $requestHost, string $siteUrl): string { return substr($siteUrl, strlen($requestHost)); } /** * Determine site script */ protected static function determineSiteScript(string $requestUrl, string $siteUrl): string { return substr($requestUrl, strlen($siteUrl)); } }