diff options
Diffstat (limited to 'lib/private/AppFramework/Http/Request.php')
-rw-r--r-- | lib/private/AppFramework/Http/Request.php | 372 |
1 files changed, 157 insertions, 215 deletions
diff --git a/lib/private/AppFramework/Http/Request.php b/lib/private/AppFramework/Http/Request.php index 91ae2269b7f..7cc7467675c 100644 --- a/lib/private/AppFramework/Http/Request.php +++ b/lib/private/AppFramework/Http/Request.php @@ -1,47 +1,11 @@ <?php declare(strict_types=1); - /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author b108@volgograd "b108@volgograd" - * @author Bart Visscher <bartv@thisnet.nl> - * @author Bernhard Posselt <dev@bernhard-posselt.com> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Daniel Kesselberg <mail@danielkesselberg.de> - * @author Georg Ehrke <oc.list@georgehrke.com> - * @author J0WI <J0WI@users.noreply.github.com> - * @author Joas Schilling <coding@schilljs.com> - * @author Juan Pablo Villafáñez <jvillafanez@solidgear.es> - * @author Julius Härtl <jus@bitgrid.net> - * @author Lukas Reschke <lukas@statuscode.ch> - * @author Mitar <mitar.git@tnode.com> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Oliver Wegner <void1976@gmail.com> - * @author Robin Appelman <robin@icewind.nl> - * @author Robin McCorkell <robin@mccorkell.me.uk> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * @author Thomas Müller <thomas.mueller@tmit.eu> - * @author Thomas Tanghus <thomas@tanghus.net> - * @author Vincent Petry <vincent@nextcloud.com> - * - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ - namespace OC\AppFramework\Http; use OC\Security\CSRF\CsrfToken; @@ -49,52 +13,42 @@ use OC\Security\CSRF\CsrfTokenManager; use OC\Security\TrustedDomainHelper; use OCP\IConfig; use OCP\IRequest; -use OCP\Security\ICrypto; -use OCP\Security\ISecureRandom; +use OCP\IRequestId; +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpFoundation\IpUtils; /** * Class for accessing variables in the request. * This class provides an immutable object with request variables. * - * @property mixed[] cookies - * @property mixed[] env - * @property mixed[] files - * @property string method - * @property mixed[] parameters - * @property mixed[] server + * @property mixed[] $cookies + * @property mixed[] $env + * @property mixed[] $files + * @property string $method + * @property mixed[] $parameters + * @property mixed[] $server + * @template-implements \ArrayAccess<string,mixed> */ class Request implements \ArrayAccess, \Countable, IRequest { public const USER_AGENT_IE = '/(MSIE)|(Trident)/'; // Microsoft Edge User Agent from https://msdn.microsoft.com/en-us/library/hh869301(v=vs.85).aspx - public const USER_AGENT_MS_EDGE = '/^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Chrome\/[0-9.]+ (Mobile Safari|Safari)\/[0-9.]+ Edge\/[0-9.]+$/'; + public const USER_AGENT_MS_EDGE = '/^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Chrome\/[0-9.]+ (Mobile Safari|Safari)\/[0-9.]+ Edge?\/[0-9.]+$/'; // Firefox User Agent from https://developer.mozilla.org/en-US/docs/Web/HTTP/Gecko_user_agent_string_reference public const USER_AGENT_FIREFOX = '/^Mozilla\/5\.0 \([^)]+\) Gecko\/[0-9.]+ Firefox\/[0-9.]+$/'; // Chrome User Agent from https://developer.chrome.com/multidevice/user-agent public const USER_AGENT_CHROME = '/^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\)( Ubuntu Chromium\/[0-9.]+|) Chrome\/[0-9.]+ (Mobile Safari|Safari)\/[0-9.]+( (Vivaldi|Brave|OPR)\/[0-9.]+|)$/'; // Safari User Agent from http://www.useragentstring.com/pages/Safari/ public const USER_AGENT_SAFARI = '/^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Version\/[0-9.]+ Safari\/[0-9.A-Z]+$/'; + public const USER_AGENT_SAFARI_MOBILE = '/^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Version\/[0-9.]+ (Mobile\/[0-9.A-Z]+) Safari\/[0-9.A-Z]+$/'; // Android Chrome user agent: https://developers.google.com/chrome/mobile/docs/user-agent public const USER_AGENT_ANDROID_MOBILE_CHROME = '#Android.*Chrome/[.0-9]*#'; public const USER_AGENT_FREEBOX = '#^Mozilla/5\.0$#'; public const REGEX_LOCALHOST = '/^(127\.0\.0\.1|localhost|\[::1\])$/'; - /** - * @deprecated use \OCP\IRequest::USER_AGENT_CLIENT_IOS instead - */ - public const USER_AGENT_OWNCLOUD_IOS = '/^Mozilla\/5\.0 \(iOS\) (ownCloud|Nextcloud)\-iOS.*$/'; - /** - * @deprecated use \OCP\IRequest::USER_AGENT_CLIENT_ANDROID instead - */ - public const USER_AGENT_OWNCLOUD_ANDROID = '/^Mozilla\/5\.0 \(Android\) ownCloud\-android.*$/'; - /** - * @deprecated use \OCP\IRequest::USER_AGENT_CLIENT_DESKTOP instead - */ - public const USER_AGENT_OWNCLOUD_DESKTOP = '/^Mozilla\/5\.0 \([A-Za-z ]+\) (mirall|csyncoC)\/.*$/'; - - protected $inputStream; - protected $content; - protected $items = []; - protected $allowedKeys = [ + protected string $inputStream; + private bool $isPutStreamContentAlreadySent = false; + protected array $items = []; + protected array $allowedKeys = [ 'get', 'post', 'files', @@ -106,45 +60,38 @@ class Request implements \ArrayAccess, \Countable, IRequest { 'method', 'requesttoken', ]; - /** @var ISecureRandom */ - protected $secureRandom; - /** @var IConfig */ - protected $config; - /** @var string */ - protected $requestId = ''; - /** @var ICrypto */ - protected $crypto; - /** @var CsrfTokenManager|null */ - protected $csrfTokenManager; + protected IRequestId $requestId; + protected IConfig $config; + protected ?CsrfTokenManager $csrfTokenManager; - /** @var bool */ - protected $contentDecoded = false; + protected bool $contentDecoded = false; + private ?\JsonException $decodingException = null; /** * @param array $vars An associative array with the following optional values: - * - array 'urlParams' the parameters which were matched from the URL - * - array 'get' the $_GET array - * - array|string 'post' the $_POST array or JSON string - * - array 'files' the $_FILES array - * - array 'server' the $_SERVER array - * - array 'env' the $_ENV array - * - array 'cookies' the $_COOKIE array - * - string 'method' the request method (GET, POST etc) - * - string|false 'requesttoken' the requesttoken or false when not available - * @param ISecureRandom $secureRandom + * - array 'urlParams' the parameters which were matched from the URL + * - array 'get' the $_GET array + * - array|string 'post' the $_POST array or JSON string + * - array 'files' the $_FILES array + * - array 'server' the $_SERVER array + * - array 'env' the $_ENV array + * - array 'cookies' the $_COOKIE array + * - string 'method' the request method (GET, POST etc) + * - string|false 'requesttoken' the requesttoken or false when not available + * @param IRequestId $requestId * @param IConfig $config * @param CsrfTokenManager|null $csrfTokenManager * @param string $stream * @see https://www.php.net/manual/en/reserved.variables.php */ public function __construct(array $vars, - ISecureRandom $secureRandom, - IConfig $config, - CsrfTokenManager $csrfTokenManager = null, - string $stream = 'php://input') { + IRequestId $requestId, + IConfig $config, + ?CsrfTokenManager $csrfTokenManager = null, + string $stream = 'php://input') { $this->inputStream = $stream; $this->items['params'] = []; - $this->secureRandom = $secureRandom; + $this->requestId = $requestId; $this->config = $config; $this->csrfTokenManager = $csrfTokenManager; @@ -153,9 +100,7 @@ class Request implements \ArrayAccess, \Countable, IRequest { } foreach ($this->allowedKeys as $name) { - $this->items[$name] = isset($vars[$name]) - ? $vars[$name] - : []; + $this->items[$name] = $vars[$name] ?? []; } $this->items['parameters'] = array_merge( @@ -213,10 +158,9 @@ class Request implements \ArrayAccess, \Countable, IRequest { * @param string $offset * @return mixed */ + #[\ReturnTypeWillChange] public function offsetGet($offset) { - return isset($this->items['parameters'][$offset]) - ? $this->items['parameters'][$offset] - : null; + return $this->items['parameters'][$offset] ?? null; } /** @@ -224,7 +168,7 @@ class Request implements \ArrayAccess, \Countable, IRequest { * @param string $offset * @param mixed $value */ - public function offsetSet($offset, $value) { + public function offsetSet($offset, $value): void { throw new \RuntimeException('You cannot change the contents of the request object'); } @@ -232,7 +176,7 @@ class Request implements \ArrayAccess, \Countable, IRequest { * @see offsetExists * @param string $offset */ - public function offsetUnset($offset) { + public function offsetUnset($offset): void { throw new \RuntimeException('You cannot change the contents of the request object'); } @@ -276,11 +220,12 @@ class Request implements \ArrayAccess, \Countable, IRequest { case 'cookies': case 'urlParams': case 'method': - return isset($this->items[$name]) - ? $this->items[$name] - : null; + return $this->items[$name] ?? null; case 'parameters': case 'params': + if ($this->isPutStreamContent()) { + return $this->items['parameters']; + } return $this->getContent(); default: return isset($this[$name]) @@ -316,7 +261,7 @@ class Request implements \ArrayAccess, \Countable, IRequest { * @return string */ public function getHeader(string $name): string { - $name = strtoupper(str_replace('-', '_',$name)); + $name = strtoupper(str_replace('-', '_', $name)); if (isset($this->server['HTTP_' . $name])) { return $this->server['HTTP_' . $name]; } @@ -341,11 +286,11 @@ class Request implements \ArrayAccess, \Countable, IRequest { * In case of json requests the encoded json body is accessed * * @param string $key the key which you want to access in the URL Parameter - * placeholder, $_POST or $_GET array. - * The priority how they're returned is the following: - * 1. URL parameters - * 2. POST parameters - * 3. GET parameters + * placeholder, $_POST or $_GET array. + * The priority how they're returned is the following: + * 1. URL parameters + * 2. POST parameters + * 3. GET parameters * @param mixed $default If the key is not found, this value will be returned * @return mixed the content of the array */ @@ -357,7 +302,7 @@ class Request implements \ArrayAccess, \Countable, IRequest { /** * Returns all params that were received, be it from the request - * (as GET or POST) or throuh the URL by the route + * (as GET or POST) or through the URL by the route * @return array the array with all parameters */ public function getParams(): array { @@ -412,19 +357,14 @@ class Request implements \ArrayAccess, \Countable, IRequest { */ protected function getContent() { // If the content can't be parsed into an array then return a stream resource. - if ($this->method === 'PUT' - && $this->getHeader('Content-Length') !== '0' - && $this->getHeader('Content-Length') !== '' - && strpos($this->getHeader('Content-Type'), 'application/x-www-form-urlencoded') === false - && strpos($this->getHeader('Content-Type'), 'application/json') === false - ) { - if ($this->content === false) { + if ($this->isPutStreamContent()) { + if ($this->isPutStreamContentAlreadySent) { throw new \LogicException( '"put" can only be accessed once if not ' . 'application/x-www-form-urlencoded or application/json.' ); } - $this->content = false; + $this->isPutStreamContentAlreadySent = true; return fopen($this->inputStream, 'rb'); } else { $this->decodeContent(); @@ -432,6 +372,14 @@ class Request implements \ArrayAccess, \Countable, IRequest { } } + private function isPutStreamContent(): bool { + return $this->method === 'PUT' + && $this->getHeader('Content-Length') !== '0' + && $this->getHeader('Content-Length') !== '' + && !str_contains($this->getHeader('Content-Type'), 'application/x-www-form-urlencoded') + && !str_contains($this->getHeader('Content-Type'), 'application/json'); + } + /** * Attempt to decode the content and populate parameters */ @@ -441,21 +389,27 @@ class Request implements \ArrayAccess, \Countable, IRequest { } $params = []; - // 'application/json' must be decoded manually. - if (strpos($this->getHeader('Content-Type'), 'application/json') !== false) { - $params = json_decode(file_get_contents($this->inputStream), true); - if ($params !== null && \count($params) > 0) { + // 'application/json' and other JSON-related content types must be decoded manually. + if (preg_match(self::JSON_CONTENT_TYPE_REGEX, $this->getHeader('Content-Type')) === 1) { + $content = file_get_contents($this->inputStream); + if ($content !== '') { + try { + $params = json_decode($content, true, flags:JSON_THROW_ON_ERROR); + } catch (\JsonException $e) { + $this->decodingException = $e; + } + } + if (\is_array($params) && \count($params) > 0) { $this->items['params'] = $params; if ($this->method === 'POST') { $this->items['post'] = $params; } } - // Handle application/x-www-form-urlencoded for methods other than GET - // or post correctly + // or post correctly } elseif ($this->method !== 'GET' && $this->method !== 'POST' - && strpos($this->getHeader('Content-Type'), 'application/x-www-form-urlencoded') !== false) { + && str_contains($this->getHeader('Content-Type'), 'application/x-www-form-urlencoded')) { parse_str(file_get_contents($this->inputStream), $params); if (\is_array($params)) { $this->items['params'] = $params; @@ -468,6 +422,12 @@ class Request implements \ArrayAccess, \Countable, IRequest { $this->contentDecoded = true; } + public function throwDecodingExceptionIfAny(): void { + if ($this->decodingException !== null) { + throw $this->decodingException; + } + } + /** * Checks if the CSRF check was correct @@ -482,6 +442,10 @@ class Request implements \ArrayAccess, \Countable, IRequest { return false; } + if ($this->getHeader('OCS-APIRequest') !== '') { + return true; + } + if (isset($this->items['get']['requesttoken'])) { $token = $this->items['get']['requesttoken']; } elseif (isset($this->items['post']['requesttoken'])) { @@ -535,7 +499,7 @@ class Request implements \ArrayAccess, \Countable, IRequest { $prefix = '__Host-'; } - return $prefix.$name; + return $prefix . $name; } /** @@ -584,39 +548,7 @@ class Request implements \ArrayAccess, \Countable, IRequest { * @return string */ public function getId(): string { - if (isset($this->server['UNIQUE_ID'])) { - return $this->server['UNIQUE_ID']; - } - - if (empty($this->requestId)) { - $validChars = ISecureRandom::CHAR_UPPER . ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_DIGITS; - $this->requestId = $this->secureRandom->generate(20, $validChars); - } - - return $this->requestId; - } - - /** - * Checks if given $remoteAddress matches given $trustedProxy. - * If $trustedProxy is an IPv4 IP range given in CIDR notation, true will be returned if - * $remoteAddress is an IPv4 address within that IP range. - * Otherwise $remoteAddress will be compared to $trustedProxy literally and the result - * will be returned. - * @return boolean true if $remoteAddress matches $trustedProxy, false otherwise - */ - protected function matchesTrustedProxy($trustedProxy, $remoteAddress) { - $cidrre = '/^([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})\/([0-9]{1,2})$/'; - - if (preg_match($cidrre, $trustedProxy, $match)) { - $net = $match[1]; - $shiftbits = min(32, max(0, 32 - intval($match[2]))); - $netnum = ip2long($net) >> $shiftbits; - $ipnum = ip2long($remoteAddress) >> $shiftbits; - - return $ipnum === $netnum; - } - - return $trustedProxy === $remoteAddress; + return $this->requestId->getId(); } /** @@ -625,13 +557,14 @@ class Request implements \ArrayAccess, \Countable, IRequest { * @return boolean true if $remoteAddress matches any entry in $trustedProxies, false otherwise */ protected function isTrustedProxy($trustedProxies, $remoteAddress) { - foreach ($trustedProxies as $tp) { - if ($this->matchesTrustedProxy($tp, $remoteAddress)) { - return true; - } + try { + return IpUtils::checkIp($remoteAddress, $trustedProxies); + } catch (\Throwable) { + // We can not log to our log here as the logger is using `getRemoteAddress` which uses the function, so we would have a cyclic dependency + // Reaching this line means `trustedProxies` is in invalid format. + error_log('Nextcloud trustedProxies has malformed entries'); + return false; } - - return false; } /** @@ -651,14 +584,25 @@ class Request implements \ArrayAccess, \Countable, IRequest { // only have one default, so we cannot ship an insecure product out of the box ]); - foreach ($forwardedForHeaders as $header) { + // Read the x-forwarded-for headers and values in reverse order as per + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For#selecting_an_ip_address + foreach (array_reverse($forwardedForHeaders) as $header) { if (isset($this->server[$header])) { - foreach (explode(',', $this->server[$header]) as $IP) { + foreach (array_reverse(explode(',', $this->server[$header])) as $IP) { $IP = trim($IP); + $colons = substr_count($IP, ':'); + if ($colons > 1) { + // Extract IP from string with brackets and optional port + if (preg_match('/^\[(.+?)\](?::\d+)?$/', $IP, $matches) && isset($matches[1])) { + $IP = $matches[1]; + } + } elseif ($colons === 1) { + // IPv4 with port + $IP = substr($IP, 0, strpos($IP, ':')); + } - // remove brackets from IPv6 addresses - if (strpos($IP, '[') === 0 && substr($IP, -1) === ']') { - $IP = substr($IP, 1, -1); + if ($this->isTrustedProxy($trustedProxies, $IP)) { + continue; } if (filter_var($IP, FILTER_VALIDATE_IP) !== false) { @@ -674,48 +618,56 @@ class Request implements \ArrayAccess, \Countable, IRequest { /** * Check overwrite condition - * @param string $type * @return bool */ - private function isOverwriteCondition(string $type = ''): bool { - $regex = '/' . $this->config->getSystemValue('overwritecondaddr', '') . '/'; + private function isOverwriteCondition(): bool { + $regex = '/' . $this->config->getSystemValueString('overwritecondaddr', '') . '/'; $remoteAddr = isset($this->server['REMOTE_ADDR']) ? $this->server['REMOTE_ADDR'] : ''; - return $regex === '//' || preg_match($regex, $remoteAddr) === 1 - || $type !== 'protocol'; + return $regex === '//' || preg_match($regex, $remoteAddr) === 1; } /** * Returns the server protocol. It respects one or more reverse proxies servers - * and load balancers + * and load balancers. Precedence: + * 1. `overwriteprotocol` config value + * 2. `X-Forwarded-Proto` header value + * 3. $_SERVER['HTTPS'] value + * If an invalid protocol is provided, defaults to http, continues, but logs as an error. + * * @return string Server protocol (http or https) */ public function getServerProtocol(): string { - if ($this->config->getSystemValue('overwriteprotocol') !== '' - && $this->isOverwriteCondition('protocol')) { - return $this->config->getSystemValue('overwriteprotocol'); - } + $proto = 'http'; - if ($this->fromTrustedProxy() && isset($this->server['HTTP_X_FORWARDED_PROTO'])) { - if (strpos($this->server['HTTP_X_FORWARDED_PROTO'], ',') !== false) { + if ($this->config->getSystemValueString('overwriteprotocol') !== '' + && $this->isOverwriteCondition() + ) { + $proto = strtolower($this->config->getSystemValueString('overwriteprotocol')); + } elseif ($this->fromTrustedProxy() + && isset($this->server['HTTP_X_FORWARDED_PROTO']) + ) { + if (str_contains($this->server['HTTP_X_FORWARDED_PROTO'], ',')) { $parts = explode(',', $this->server['HTTP_X_FORWARDED_PROTO']); $proto = strtolower(trim($parts[0])); } else { $proto = strtolower($this->server['HTTP_X_FORWARDED_PROTO']); } - - // Verify that the protocol is always HTTP or HTTPS - // default to http if an invalid value is provided - return $proto === 'https' ? 'https' : 'http'; + } elseif (!empty($this->server['HTTPS']) + && $this->server['HTTPS'] !== 'off' + ) { + $proto = 'https'; } - if (isset($this->server['HTTPS']) - && $this->server['HTTPS'] !== null - && $this->server['HTTPS'] !== 'off' - && $this->server['HTTPS'] !== '') { - return 'https'; + if ($proto !== 'https' && $proto !== 'http') { + // log unrecognized value so admin has a chance to fix it + \OCP\Server::get(LoggerInterface::class)->critical( + 'Server protocol is malformed [falling back to http] (check overwriteprotocol and/or X-Forwarded-Proto to remedy): ' . $proto, + ['app' => 'core'] + ); } - return 'http'; + // default to http if provided an invalid value + return $proto === 'https' ? 'https' : 'http'; } /** @@ -750,7 +702,7 @@ class Request implements \ArrayAccess, \Countable, IRequest { */ public function getRequestUri(): string { $uri = isset($this->server['REQUEST_URI']) ? $this->server['REQUEST_URI'] : ''; - if ($this->config->getSystemValue('overwritewebroot') !== '' && $this->isOverwriteCondition()) { + if ($this->config->getSystemValueString('overwritewebroot') !== '' && $this->isOverwriteCondition()) { $uri = $this->getScriptName() . substr($uri, \strlen($this->server['SCRIPT_NAME'])); } return $uri; @@ -776,9 +728,9 @@ class Request implements \ArrayAccess, \Countable, IRequest { // strip off the script name's dir and file name // FIXME: Sabre does not really belong here - list($path, $name) = \Sabre\Uri\split($scriptName); + [$path, $name] = \Sabre\Uri\split($scriptName); if (!empty($path)) { - if ($path === $pathInfo || strpos($pathInfo, $path.'/') === 0) { + if ($path === $pathInfo || str_starts_with($pathInfo, $path . '/')) { $pathInfo = substr($pathInfo, \strlen($path)); } else { throw new \Exception("The requested uri($requestUri) cannot be processed by the script '$scriptName')"); @@ -788,10 +740,10 @@ class Request implements \ArrayAccess, \Countable, IRequest { $name = ''; } - if (strpos($pathInfo, '/'.$name) === 0) { + if (str_starts_with($pathInfo, '/' . $name)) { $pathInfo = substr($pathInfo, \strlen($name) + 1); } - if ($name !== '' && strpos($pathInfo, $name) === 0) { + if ($name !== '' && str_starts_with($pathInfo, $name)) { $pathInfo = substr($pathInfo, \strlen($name)); } if ($pathInfo === false || $pathInfo === '/') { @@ -802,23 +754,13 @@ class Request implements \ArrayAccess, \Countable, IRequest { } /** - * Get PathInfo from request + * Get PathInfo from request (rawurldecoded) * @throws \Exception * @return string|false Path info or false when not found */ - public function getPathInfo() { + public function getPathInfo(): string|false { $pathInfo = $this->getRawPathInfo(); - // following is taken from \Sabre\HTTP\URLUtil::decodePathSegment - $pathInfo = rawurldecode($pathInfo); - $encoding = mb_detect_encoding($pathInfo, ['UTF-8', 'ISO-8859-1']); - - switch ($encoding) { - case 'ISO-8859-1': - $pathInfo = utf8_encode($pathInfo); - } - // end copy - - return $pathInfo; + return \Sabre\HTTP\decodePath($pathInfo); } /** @@ -828,7 +770,7 @@ class Request implements \ArrayAccess, \Countable, IRequest { */ public function getScriptName(): string { $name = $this->server['SCRIPT_NAME']; - $overwriteWebRoot = $this->config->getSystemValue('overwritewebroot'); + $overwriteWebRoot = $this->config->getSystemValueString('overwritewebroot'); if ($overwriteWebRoot !== '' && $this->isOverwriteCondition()) { // FIXME: This code is untestable due to __DIR__, also that hardcoded path is really dangerous $serverRoot = str_replace('\\', '/', substr(__DIR__, 0, -\strlen('lib/private/appframework/http/'))); @@ -867,7 +809,7 @@ class Request implements \ArrayAccess, \Countable, IRequest { $host = 'localhost'; if ($this->fromTrustedProxy() && isset($this->server['HTTP_X_FORWARDED_HOST'])) { - if (strpos($this->server['HTTP_X_FORWARDED_HOST'], ',') !== false) { + if (str_contains($this->server['HTTP_X_FORWARDED_HOST'], ',')) { $parts = explode(',', $this->server['HTTP_X_FORWARDED_HOST']); $host = trim(current($parts)); } else { @@ -920,11 +862,11 @@ class Request implements \ArrayAccess, \Countable, IRequest { * Returns the overwritehost setting from the config if set and * if the overwrite condition is met * @return string|null overwritehost value or null if not defined or the defined condition - * isn't met + * isn't met */ private function getOverwriteHost() { - if ($this->config->getSystemValue('overwritehost') !== '' && $this->isOverwriteCondition()) { - return $this->config->getSystemValue('overwritehost'); + if ($this->config->getSystemValueString('overwritehost') !== '' && $this->isOverwriteCondition()) { + return $this->config->getSystemValueString('overwritehost'); } return null; } |