From 75c6bb9b6edabef924862bcc17e73eed2b13f873 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 20 Mar 2023 14:13:06 +0100 Subject: [PATCH 01/25] RequestFactory: fixed port detection when HTTP_HOST & SERVER_PORT are used [Closes #223] --- src/Http/RequestFactory.php | 2 +- tests/Http/RequestFactory.port.phpt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Http/RequestFactory.php b/src/Http/RequestFactory.php index 82522f8a..125a8cc9 100644 --- a/src/Http/RequestFactory.php +++ b/src/Http/RequestFactory.php @@ -94,7 +94,7 @@ private function getServer(Url $url): void $url->setHost(rtrim(strtolower($pair[1]), '.')); if (isset($pair[2])) { $url->setPort((int) substr($pair[2], 1)); - } elseif (isset($_SERVER['SERVER_PORT'])) { + } elseif ($tmp === 'SERVER_NAME' && isset($_SERVER['SERVER_PORT'])) { $url->setPort((int) $_SERVER['SERVER_PORT']); } } diff --git a/tests/Http/RequestFactory.port.phpt b/tests/Http/RequestFactory.port.phpt index 03a0d107..9dbeaa65 100644 --- a/tests/Http/RequestFactory.port.phpt +++ b/tests/Http/RequestFactory.port.phpt @@ -31,7 +31,7 @@ class RequestFactoryPortTest extends Tester\TestCase [8080, ['SERVER_NAME' => 'localhost:8080']], [8080, ['HTTP_HOST' => 'localhost:8080', 'SERVER_PORT' => '666']], [8080, ['SERVER_NAME' => 'localhost:8080', 'SERVER_PORT' => '666']], - [8080, ['HTTP_HOST' => 'localhost', 'SERVER_PORT' => '8080']], + [80, ['HTTP_HOST' => 'localhost', 'SERVER_PORT' => '8080']], [8080, ['SERVER_NAME' => 'localhost', 'SERVER_PORT' => '8080']], [80, ['HTTP_X_FORWARDED_PORT' => '8080']], @@ -41,7 +41,7 @@ class RequestFactoryPortTest extends Tester\TestCase [8080, ['SERVER_NAME' => 'localhost:8080', 'SERVER_PORT' => '80', 'HTTP_X_FORWARDED_PORT' => '666']], [80, ['HTTP_HOST' => 'localhost', 'HTTP_X_FORWARDED_PORT' => '666']], [80, ['SERVER_NAME' => 'localhost', 'HTTP_X_FORWARDED_PORT' => '666']], - [8080, ['HTTP_HOST' => 'localhost', 'SERVER_PORT' => '8080', 'HTTP_X_FORWARDED_PORT' => '666']], + [80, ['HTTP_HOST' => 'localhost', 'SERVER_PORT' => '8080', 'HTTP_X_FORWARDED_PORT' => '666']], [8080, ['SERVER_NAME' => 'localhost', 'SERVER_PORT' => '8080', 'HTTP_X_FORWARDED_PORT' => '666']], [44443, ['HTTPS' => 'on', 'SERVER_NAME' => 'localhost:44443', 'HTTP_X_FORWARDED_PORT' => '666']], ]; From 625195fe1712248120e5d4a5f8be20dd14fba679 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 30 Jul 2023 18:08:37 +0200 Subject: [PATCH 02/25] support for PHP 8.3 --- .github/workflows/tests.yml | 2 +- composer.json | 2 +- readme.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 382f4f27..a3a4d8db 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -12,7 +12,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, windows-latest, macOS-latest] - php: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2'] + php: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3'] sapi: ['php', 'php-cgi'] fail-fast: false diff --git a/composer.json b/composer.json index 01cfc80f..682344d5 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ } ], "require": { - "php": ">=7.2 <8.3", + "php": "7.2 - 8.3", "nette/utils": "^3.2.1 || ~4.0.0" }, "require-dev": { diff --git a/readme.md b/readme.md index 859ff0ed..b8b7e6c0 100644 --- a/readme.md +++ b/readme.md @@ -35,7 +35,7 @@ Installation composer require nette/http ``` -It requires PHP version 7.2 and supports PHP up to 8.2. +It requires PHP version 7.2 and supports PHP up to 8.3. HTTP Request From 1749bc16fcd934be09033304b28bfd4cb1eb912d Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sat, 16 Sep 2023 11:50:38 +0200 Subject: [PATCH 03/25] removed fix for IE --- src/Http/Response.php | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/src/Http/Response.php b/src/Http/Response.php index c7d6c6ce..32c5fe39 100644 --- a/src/Http/Response.php +++ b/src/Http/Response.php @@ -37,7 +37,7 @@ final class Response implements IResponse /** @var bool Whether warn on possible problem with data in output buffer */ public $warnOnBuffer = true; - /** @var bool Send invisible garbage for IE 6? */ + /** @deprecated */ private static $fixIE = true; /** @var int HTTP response code */ @@ -239,20 +239,6 @@ public function getHeaders(): array } - public function __destruct() - { - if ( - self::$fixIE - && strpos($_SERVER['HTTP_USER_AGENT'] ?? '', 'MSIE ') !== false - && in_array($this->code, [400, 403, 404, 405, 406, 408, 409, 410, 500, 501, 505], true) - && preg_match('#^text/html(?:;|$)#', (string) $this->getHeader('Content-Type')) - ) { - echo Nette\Utils\Random::generate(2000, " \t\r\n"); // sends invisible garbage for IE - self::$fixIE = false; - } - } - - /** * Sends a cookie. * @param string|int|\DateTimeInterface $expire expiration time, value null means "until the browser session ends" From 17f16c1a866c9ac30fcf77b7526b7572842c0966 Mon Sep 17 00:00:00 2001 From: David <96302578+Bongyseek@users.noreply.github.com> Date: Mon, 21 Aug 2023 19:50:59 +0200 Subject: [PATCH 04/25] Typo (#227) --- src/Http/FileUpload.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Http/FileUpload.php b/src/Http/FileUpload.php index 20203d0d..74c1c105 100644 --- a/src/Http/FileUpload.php +++ b/src/Http/FileUpload.php @@ -137,7 +137,7 @@ public function getContentType(): ?string /** - * Returns the path of the temporary location of the uploaded file. + * Returns the size of the uploaded file in bytes. */ public function getSize(): int { From 54d79711ff3a8dfd86201e3bdbdd6972177ea20b Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 2 Nov 2023 03:35:25 +0100 Subject: [PATCH 05/25] FileUpload: detects supported images [Closes #224] --- src/Http/FileUpload.php | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/Http/FileUpload.php b/src/Http/FileUpload.php index 74c1c105..08a29285 100644 --- a/src/Http/FileUpload.php +++ b/src/Http/FileUpload.php @@ -29,10 +29,8 @@ final class FileUpload { use Nette\SmartObject; - public const ImageMimeTypes = ['image/gif', 'image/png', 'image/jpeg', 'image/webp']; - - /** @deprecated use FileUpload::ImageMimeTypes */ - public const IMAGE_MIME_TYPES = self::ImageMimeTypes; + /** @deprecated */ + public const IMAGE_MIME_TYPES = ['image/gif', 'image/png', 'image/jpeg', 'image/webp']; /** @var string */ private $name; @@ -214,12 +212,20 @@ function (string $message) use ($dest): void { /** - * Returns true if the uploaded file is a JPEG, PNG, GIF, or WebP image. + * Returns true if the uploaded file is an image supported by PHP. * Detection is based on its signature, the integrity of the file is not checked. Requires PHP extension fileinfo. */ public function isImage(): bool { - return in_array($this->getContentType(), self::ImageMimeTypes, true); + $flag = imagetypes(); + $types = array_filter([ + $flag & IMG_GIF ? 'image/gif' : null, + $flag & IMG_JPG ? 'image/jpeg' : null, + $flag & IMG_PNG ? 'image/png' : null, + $flag & IMG_WEBP ? 'image/webp' : null, + $flag & 256 ? 'image/avif' : null, // IMG_AVIF + ]); + return in_array($this->getContentType(), $types, true); } From 9cf6132a35cd26753f00c26ce9bfd2c950d7a32e Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 1 Mar 2021 15:28:42 +0100 Subject: [PATCH 06/25] opened 4.0-dev --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 682344d5..5da36d5d 100644 --- a/composer.json +++ b/composer.json @@ -42,7 +42,7 @@ }, "extra": { "branch-alias": { - "dev-master": "3.2-dev" + "dev-master": "4.0-dev" } } } From 53a8df9b0109968f54d0aecee30bea8238fd7300 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 1 Mar 2021 17:18:24 +0100 Subject: [PATCH 07/25] requires PHP 8.1 --- .github/workflows/coding-style.yml | 4 ++-- .github/workflows/static-analysis.yml | 2 +- .github/workflows/tests.yml | 6 +++--- composer.json | 2 +- readme.md | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/coding-style.yml b/.github/workflows/coding-style.yml index e86f4940..7803901d 100644 --- a/.github/workflows/coding-style.yml +++ b/.github/workflows/coding-style.yml @@ -10,7 +10,7 @@ jobs: - uses: actions/checkout@v3 - uses: shivammathur/setup-php@v2 with: - php-version: 7.2 + php-version: 8.1 coverage: none - run: composer create-project nette/code-checker temp/code-checker ^3 --no-progress @@ -24,7 +24,7 @@ jobs: - uses: actions/checkout@v3 - uses: shivammathur/setup-php@v2 with: - php-version: 8.0 + php-version: 8.1 coverage: none - run: composer create-project nette/coding-standard temp/coding-standard ^3 --no-progress diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index fcbf28d5..24dcb19c 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -13,7 +13,7 @@ jobs: - uses: actions/checkout@v3 - uses: shivammathur/setup-php@v2 with: - php-version: 7.4 + php-version: 8.1 coverage: none - run: composer install --no-progress --prefer-dist diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a3a4d8db..c7ea210c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -12,7 +12,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, windows-latest, macOS-latest] - php: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3'] + php: ['8.1', '8.2', '8.3'] sapi: ['php', 'php-cgi'] fail-fast: false @@ -42,7 +42,7 @@ jobs: - uses: actions/checkout@v3 - uses: shivammathur/setup-php@v2 with: - php-version: 7.2 + php-version: 8.1 coverage: none extensions: ${{ env.php-extensions }} @@ -57,7 +57,7 @@ jobs: - uses: actions/checkout@v3 - uses: shivammathur/setup-php@v2 with: - php-version: 7.4 + php-version: 8.1 coverage: none extensions: ${{ env.php-extensions }} diff --git a/composer.json b/composer.json index 5da36d5d..ab83264c 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ } ], "require": { - "php": "7.2 - 8.3", + "php": "8.1 - 8.3", "nette/utils": "^3.2.1 || ~4.0.0" }, "require-dev": { diff --git a/readme.md b/readme.md index b8b7e6c0..ddb92992 100644 --- a/readme.md +++ b/readme.md @@ -35,7 +35,7 @@ Installation composer require nette/http ``` -It requires PHP version 7.2 and supports PHP up to 8.3. +It requires PHP version 8.1 and supports PHP up to 8.3. HTTP Request From e959e24aa84b7308de8de6aab85ebd4f07828880 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sat, 5 Aug 2023 21:07:43 +0200 Subject: [PATCH 08/25] composer: updated dependencies --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index ab83264c..86fbb722 100644 --- a/composer.json +++ b/composer.json @@ -19,9 +19,9 @@ "nette/utils": "^3.2.1 || ~4.0.0" }, "require-dev": { - "nette/di": "^3.0", + "nette/di": "^3.1 || ^4.0", "nette/tester": "^2.4", - "nette/security": "^3.0", + "nette/security": "^4.0", "tracy/tracy": "^2.8", "phpstan/phpstan": "^1.0" }, From 40f89e4310eab938cf9ab7d1a7ddfe709f59f22b Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 27 Nov 2022 16:02:15 +0100 Subject: [PATCH 09/25] coding style --- src/Bridges/HttpDI/HttpExtension.php | 4 +- src/Http/FileUpload.php | 6 +-- src/Http/Helpers.php | 2 +- src/Http/IResponse.php | 2 +- src/Http/Request.php | 6 +-- src/Http/RequestFactory.php | 19 +++---- src/Http/Response.php | 10 ++-- src/Http/Session.php | 12 ++--- src/Http/Url.php | 8 +-- tests/Http.DI/HttpExtension.csp.phpt | 49 ++++++++++--------- .../Http.DI/HttpExtension.featurePolicy.phpt | 25 +++++----- tests/Http.DI/HttpExtension.headers.phpt | 21 ++++---- ...Extension.sameSiteProtection.disabled.phpt | 7 ++- .../HttpExtension.sameSiteProtection.phpt | 2 +- tests/Http.DI/SessionExtension.config.phpt | 2 +- tests/Http/Helpers.phpt | 2 +- tests/Http/Request.getRawBody.phpt | 4 +- tests/Http/Request.invalidType.phpt | 16 +++--- tests/Http/RequestFactory.authorization.phpt | 4 +- tests/Http/Response.setCookie.phpt | 2 +- tests/Http/Session.autoStart.phpt | 7 +-- tests/Http/Session.handler-exceptions.phpt | 18 ++++--- tests/Http/Session.sameSite.phpt | 2 +- tests/Http/Session.setOptions.error.phpt | 20 +++++--- tests/Http/Session.start.error.phpt | 7 +-- tests/Http/Url.malformedUri.phpt | 8 +-- tests/Http/UrlImmutable.malformedUri.phpt | 8 +-- tests/Http/UrlScript.error.phpt | 28 ++++++----- 28 files changed, 159 insertions(+), 142 deletions(-) diff --git a/src/Bridges/HttpDI/HttpExtension.php b/src/Bridges/HttpDI/HttpExtension.php index e58d2383..c92049ed 100644 --- a/src/Bridges/HttpDI/HttpExtension.php +++ b/src/Bridges/HttpDI/HttpExtension.php @@ -119,7 +119,7 @@ private function sendHeaders() $this->initialization->addBody('$cspNonce = base64_encode(random_bytes(16));'); $value = Nette\DI\ContainerBuilder::literal( 'str_replace(?, ? . $cspNonce, ?)', - ["'nonce", "'nonce-", $value] + ["'nonce", "'nonce-", $value], ); } @@ -140,7 +140,7 @@ private function sendHeaders() if (!$config->disableNetteCookie) { $this->initialization->addBody( 'Nette\Http\Helpers::initCookie($this->getService(?), $response);', - [$this->prefix('request')] + [$this->prefix('request')], ); } } diff --git a/src/Http/FileUpload.php b/src/Http/FileUpload.php index 08a29285..a4274987 100644 --- a/src/Http/FileUpload.php +++ b/src/Http/FileUpload.php @@ -94,7 +94,7 @@ public function getUntrustedName(): string */ public function getSanitizedName(): string { - $name = Nette\Utils\Strings::webalize($this->name, '.', false); + $name = Nette\Utils\Strings::webalize($this->name, '.', lower: false); $name = str_replace(['-.', '.-'], '.', $name); $name = trim($name, '.-'); $name = $name === '' ? 'unknown' : $name; @@ -203,7 +203,7 @@ public function move(string $dest) [$this->tmpName, $dest], function (string $message) use ($dest): void { throw new Nette\InvalidStateException("Unable to move uploaded file '$this->tmpName' to '$dest'. $message"); - } + }, ); @chmod($dest, 0666); // @ - possible low permission to chmod $this->tmpName = $dest; @@ -225,7 +225,7 @@ public function isImage(): bool $flag & IMG_WEBP ? 'image/webp' : null, $flag & 256 ? 'image/avif' : null, // IMG_AVIF ]); - return in_array($this->getContentType(), $types, true); + return in_array($this->getContentType(), $types, strict: true); } diff --git a/src/Http/Helpers.php b/src/Http/Helpers.php index 5ff39703..e837ba75 100644 --- a/src/Http/Helpers.php +++ b/src/Http/Helpers.php @@ -44,7 +44,7 @@ public static function formatDate($time): string public static function ipMatch(string $ip, string $mask): bool { [$mask, $size] = explode('/', $mask . '/'); - $tmp = function (int $n): string { return sprintf('%032b', $n); }; + $tmp = fn(int $n): string => sprintf('%032b', $n); $ip = implode('', array_map($tmp, unpack('N*', inet_pton($ip)))); $mask = implode('', array_map($tmp, unpack('N*', inet_pton($mask)))); $max = strlen($ip); diff --git a/src/Http/IResponse.php b/src/Http/IResponse.php index 0cbd2754..b421220b 100644 --- a/src/Http/IResponse.php +++ b/src/Http/IResponse.php @@ -408,7 +408,7 @@ function setCookie( ?string $path = null, ?string $domain = null, ?bool $secure = null, - ?bool $httpOnly = null + ?bool $httpOnly = null, ); /** diff --git a/src/Http/Request.php b/src/Http/Request.php index c16b7551..37d6554f 100644 --- a/src/Http/Request.php +++ b/src/Http/Request.php @@ -70,7 +70,7 @@ public function __construct( ?string $method = null, ?string $remoteAddress = null, ?string $remoteHost = null, - ?callable $rawBodyCallback = null + ?callable $rawBodyCallback = null, ) { $this->url = $url; $this->post = (array) $post; @@ -325,9 +325,9 @@ public function getBasicCredentials(): ?array return preg_match( '~^Basic (\S+)$~', $this->headers['authorization'] ?? '', - $t + $t, ) - && ($t = base64_decode($t[1], true)) + && ($t = base64_decode($t[1], strict: true)) && ($t = explode(':', $t, 2)) && (count($t) === 2) ? $t diff --git a/src/Http/RequestFactory.php b/src/Http/RequestFactory.php index 125a8cc9..c947f38c 100644 --- a/src/Http/RequestFactory.php +++ b/src/Http/RequestFactory.php @@ -76,9 +76,7 @@ public function fromGlobals(): Request $this->getMethod(), $remoteAddr, $remoteHost, - function (): string { - return file_get_contents('php://input'); - } + fn(): string => file_get_contents('php://input') ); } @@ -284,9 +282,7 @@ private function getClient(Url $url): array : null; // use real client address and host if trusted proxy is used - $usingTrustedProxy = $remoteAddr && Arrays::some($this->proxies, function (string $proxy) use ($remoteAddr): bool { - return Helpers::ipMatch($remoteAddr, $proxy); - }); + $usingTrustedProxy = $remoteAddr && Arrays::some($this->proxies, fn(string $proxy): bool => Helpers::ipMatch($remoteAddr, $proxy)); if ($usingTrustedProxy) { $remoteHost = null; $remoteAddr = empty($_SERVER['HTTP_FORWARDED']) @@ -355,12 +351,11 @@ private function useNonstandardProxy(Url $url): ?string } if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { - $xForwardedForWithoutProxies = array_filter(explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']), function (string $ip): bool { - return filter_var(trim($ip), FILTER_VALIDATE_IP) === false || - !Arrays::some($this->proxies, function (string $proxy) use ($ip): bool { - return Helpers::ipMatch(trim($ip), $proxy); - }); - }); + $xForwardedForWithoutProxies = array_filter( + explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']), + fn(string $ip): bool => filter_var($ip = trim($ip), FILTER_VALIDATE_IP) === false + || !Arrays::some($this->proxies, fn(string $proxy): bool => Helpers::ipMatch($ip, $proxy)), + ); if ($xForwardedForWithoutProxies) { $remoteAddr = trim(end($xForwardedForWithoutProxies)); $xForwardedForRealIpKey = key($xForwardedForWithoutProxies); diff --git a/src/Http/Response.php b/src/Http/Response.php index 32c5fe39..8d4fbc80 100644 --- a/src/Http/Response.php +++ b/src/Http/Response.php @@ -110,7 +110,7 @@ public function setHeader(string $name, ?string $value) public function addHeader(string $name, string $value) { self::checkHeaders(); - header($name . ': ' . $value, false); + header($name . ': ' . $value, replace: false); return $this; } @@ -150,7 +150,7 @@ public function sendAsFile(string $fileName) $this->setHeader( 'Content-Disposition', 'attachment; filename="' . str_replace('"', '', $fileName) . '"; ' - . "filename*=utf-8''" . rawurlencode($fileName) + . "filename*=utf-8''" . rawurlencode($fileName), ); return $this; } @@ -253,7 +253,7 @@ public function setCookie( ?string $domain = null, ?bool $secure = null, ?bool $httpOnly = null, - ?string $sameSite = null + ?string $sameSite = null, ) { self::checkHeaders(); $options = [ @@ -274,7 +274,7 @@ public function setCookie( $options['path'] . ($sameSite ? "; SameSite=$sameSite" : ''), $options['domain'], $options['secure'], - $options['httponly'] + $options['httponly'], ); } @@ -301,7 +301,7 @@ private function checkHeaders(): void } elseif ( $this->warnOnBuffer && ob_get_length() && - !array_filter(ob_get_status(true), function (array $i): bool { return !$i['chunk_size']; }) + !array_filter(ob_get_status(true), fn(array $i): bool => !$i['chunk_size']) ) { trigger_error('Possible problem: you are sending a HTTP header while already having some data in output buffer. Try Tracy\OutputDebugger or send cookies/start session earlier.'); } diff --git a/src/Http/Session.php b/src/Http/Session.php index ed705e5f..a17cea25 100644 --- a/src/Http/Session.php +++ b/src/Http/Session.php @@ -117,7 +117,7 @@ private function doStart($mustExists = false): void [['read_and_close' => $this->readAndClose]], function (string $message) use (&$e): void { $e = new Nette\InvalidStateException($message); - } + }, ); } catch (\Throwable $e) { } @@ -389,7 +389,7 @@ private function clean(): void public function setOptions(array $options) { $normalized = []; - $allowed = ini_get_all('session', false) + ['session.read_and_close' => 1, 'session.cookie_samesite' => 1]; // for PHP < 7.3 + $allowed = ini_get_all('session', details: false) + ['session.read_and_close' => 1, 'session.cookie_samesite' => 1]; // for PHP < 7.3 foreach ($options as $key => $value) { if (!strncmp($key, 'session.', 8)) { // back compatibility @@ -401,7 +401,7 @@ public function setOptions(array $options) if (!isset($allowed["session.$normKey"])) { $hint = substr((string) Nette\Utils\Helpers::getSuggestion(array_keys($allowed), "session.$normKey"), 8); if ($key !== $normKey) { - $hint = preg_replace_callback('#_(.)#', function ($m) { return strtoupper($m[1]); }, $hint); // snake_case -> camelCase + $hint = preg_replace_callback('#_(.)#', fn($m) => strtoupper($m[1]), $hint); // snake_case -> camelCase } throw new Nette\InvalidStateException("Invalid session configuration option '$key'" . ($hint ? ", did you mean '$hint'?" : '.')); @@ -481,7 +481,7 @@ private function configure(array $config): void $cookie['path'] . (isset($cookie['samesite']) ? '; SameSite=' . $cookie['samesite'] : ''), $cookie['domain'], $cookie['secure'], - $cookie['httponly'] + $cookie['httponly'], ); } @@ -527,7 +527,7 @@ public function setCookieParameters( string $path, ?string $domain = null, ?bool $secure = null, - ?string $sameSite = null + ?string $sameSite = null, ) { return $this->setOptions([ 'cookie_path' => $path, @@ -587,7 +587,7 @@ private function sendCookie(): void $cookie['domain'], $cookie['secure'], $cookie['httponly'], - $cookie['samesite'] ?? null + $cookie['samesite'] ?? null, ); } } diff --git a/src/Http/Url.php b/src/Http/Url.php index 351ba5fe..31e30dcc 100644 --- a/src/Http/Url.php +++ b/src/Http/Url.php @@ -380,8 +380,8 @@ public function canonicalize() { $this->path = preg_replace_callback( '#[^!$&\'()*+,/:;=@%]+#', - function (array $m): string { return rawurlencode($m[0]); }, - self::unescape($this->path, '%/') + fn(array $m): string => rawurlencode($m[0]), + self::unescape($this->path, '%/'), ); $this->host = rtrim($this->host, '.'); $this->host = self::idnHostToUnicode(strtolower($this->host)); @@ -436,8 +436,8 @@ public static function unescape(string $s, string $reserved = '%;/?:@&=+$,'): st if ($reserved !== '') { $s = preg_replace_callback( '#%(' . substr(chunk_split(bin2hex($reserved), 2, '|'), 0, -1) . ')#i', - function (array $m): string { return '%25' . strtoupper($m[1]); }, - $s + fn(array $m): string => '%25' . strtoupper($m[1]), + $s, ); } diff --git a/tests/Http.DI/HttpExtension.csp.phpt b/tests/Http.DI/HttpExtension.csp.phpt index 642e7b27..486407f7 100644 --- a/tests/Http.DI/HttpExtension.csp.phpt +++ b/tests/Http.DI/HttpExtension.csp.phpt @@ -21,26 +21,25 @@ $compiler = new DI\Compiler; $compiler->addExtension('http', new HttpExtension); $loader = new DI\Config\Loader; $config = $loader->load(Tester\FileMock::create(<<<'EOD' -http: - csp: - default-src: "'self' https://example.com" - upgrade-insecure-requests: - script-src: 'nonce' - style-src: - - self - - https://example.com - - http: - require-sri-for: style - sandbox: allow-forms - plugin-types: application/x-java-applet - - cspReportOnly: - default-src: "'nonce'" - report-uri: https://example.com/report - upgrade-insecure-requests: true - block-all-mixed-content: false -EOD - , 'neon')); + http: + csp: + default-src: "'self' https://example.com" + upgrade-insecure-requests: + script-src: 'nonce' + style-src: + - self + - https://example.com + - http: + require-sri-for: style + sandbox: allow-forms + plugin-types: application/x-java-applet + + cspReportOnly: + default-src: "'nonce'" + report-uri: https://example.com/report + upgrade-insecure-requests: true + block-all-mixed-content: false + EOD, 'neon')); eval($compiler->addConfig($config)->compile()); @@ -49,7 +48,7 @@ $container->initialize(); $headers = headers_list(); -preg_match('#nonce-([\w+/]+=*)#', implode($headers), $nonce); +preg_match('#nonce-([\w+/]+=*)#', implode('', $headers), $nonce); Assert::contains("Content-Security-Policy: default-src 'self' https://example.com; upgrade-insecure-requests; script-src 'nonce-$nonce[1]'; style-src 'self' https://example.com http:; require-sri-for style; sandbox allow-forms; plugin-types application/x-java-applet;", $headers); Assert::contains("Content-Security-Policy-Report-Only: default-src 'nonce-$nonce[1]'; report-uri https://example.com/report; upgrade-insecure-requests;", $headers); @@ -58,6 +57,8 @@ echo str_repeat(' ', ini_get('output_buffering') + 1); Assert::true(headers_sent()); -Assert::exception(function () use ($container) { - $container->initialize(); -}, Nette\InvalidStateException::class, 'Cannot send header after %a%'); +Assert::exception( + fn() => $container->initialize(), + Nette\InvalidStateException::class, + 'Cannot send header after %a%', +); diff --git a/tests/Http.DI/HttpExtension.featurePolicy.phpt b/tests/Http.DI/HttpExtension.featurePolicy.phpt index a5c6096b..bdefbdb2 100644 --- a/tests/Http.DI/HttpExtension.featurePolicy.phpt +++ b/tests/Http.DI/HttpExtension.featurePolicy.phpt @@ -21,15 +21,14 @@ $compiler = new DI\Compiler; $compiler->addExtension('http', new HttpExtension); $loader = new DI\Config\Loader; $config = $loader->load(Tester\FileMock::create(<<<'EOD' -http: - featurePolicy: - unsized-media: none - geolocation: - - self - - https://example.com - camera: * -EOD - , 'neon')); + http: + featurePolicy: + unsized-media: none + geolocation: + - self + - https://example.com + camera: * + EOD, 'neon')); eval($compiler->addConfig($config)->compile()); @@ -46,6 +45,8 @@ echo str_repeat(' ', ini_get('output_buffering') + 1); Assert::true(headers_sent()); -Assert::exception(function () use ($container) { - $container->initialize(); -}, Nette\InvalidStateException::class, 'Cannot send header after %a%'); +Assert::exception( + fn() => $container->initialize(), + Nette\InvalidStateException::class, + 'Cannot send header after %a%', +); diff --git a/tests/Http.DI/HttpExtension.headers.phpt b/tests/Http.DI/HttpExtension.headers.phpt index cfa9f85a..4831e03b 100644 --- a/tests/Http.DI/HttpExtension.headers.phpt +++ b/tests/Http.DI/HttpExtension.headers.phpt @@ -21,13 +21,12 @@ $compiler = new DI\Compiler; $compiler->addExtension('http', new HttpExtension); $loader = new DI\Config\Loader; $config = $loader->load(Tester\FileMock::create(<<<'EOD' -http: - headers: - A: b - C: - D: 0 -EOD - , 'neon')); + http: + headers: + A: b + C: + D: 0 + EOD, 'neon')); eval($compiler->addConfig($config)->compile()); @@ -48,6 +47,8 @@ echo str_repeat(' ', ini_get('output_buffering') + 1); Assert::true(headers_sent()); -Assert::exception(function () use ($container) { - $container->initialize(); -}, Nette\InvalidStateException::class, 'Cannot send header after %a%'); +Assert::exception( + fn() => $container->initialize(), + Nette\InvalidStateException::class, + 'Cannot send header after %a%', +); diff --git a/tests/Http.DI/HttpExtension.sameSiteProtection.disabled.phpt b/tests/Http.DI/HttpExtension.sameSiteProtection.disabled.phpt index 5fc6fc70..b18b557e 100644 --- a/tests/Http.DI/HttpExtension.sameSiteProtection.disabled.phpt +++ b/tests/Http.DI/HttpExtension.sameSiteProtection.disabled.phpt @@ -17,10 +17,9 @@ $compiler = new DI\Compiler; $compiler->addExtension('http', new HttpExtension); $loader = new DI\Config\Loader; $config = $loader->load(Tester\FileMock::create(<<<'EOD' -http: - disableNetteCookie: yes -EOD - , 'neon')); + http: + disableNetteCookie: yes + EOD, 'neon')); eval($compiler->addConfig($config)->compile()); diff --git a/tests/Http.DI/HttpExtension.sameSiteProtection.phpt b/tests/Http.DI/HttpExtension.sameSiteProtection.phpt index d93613b1..f6de3332 100644 --- a/tests/Http.DI/HttpExtension.sameSiteProtection.phpt +++ b/tests/Http.DI/HttpExtension.sameSiteProtection.phpt @@ -27,5 +27,5 @@ Assert::contains( PHP_VERSION_ID >= 70300 ? 'Set-Cookie: _nss=1; path=/; HttpOnly; SameSite=Strict' : 'Set-Cookie: _nss=1; path=/; SameSite=Strict; HttpOnly', - $headers + $headers, ); diff --git a/tests/Http.DI/SessionExtension.config.phpt b/tests/Http.DI/SessionExtension.config.phpt index 78ad21c5..36bb11a8 100644 --- a/tests/Http.DI/SessionExtension.config.phpt +++ b/tests/Http.DI/SessionExtension.config.phpt @@ -42,7 +42,7 @@ Assert::same( PHP_VERSION_ID >= 70300 ? ['lifetime' => 0, 'path' => '/x', 'domain' => 'nette.org', 'secure' => true, 'httponly' => true, 'samesite' => 'Lax'] : ['lifetime' => 0, 'path' => '/x; SameSite=Lax', 'domain' => 'nette.org', 'secure' => true, 'httponly' => true], - session_get_cookie_params() + session_get_cookie_params(), ); // readAndClose diff --git a/tests/Http/Helpers.phpt b/tests/Http/Helpers.phpt index e7213064..87b1a4dd 100644 --- a/tests/Http/Helpers.phpt +++ b/tests/Http/Helpers.phpt @@ -44,5 +44,5 @@ test('', function () { Assert::same('Tue, 15 Nov 1994 08:12:31 GMT', Helpers::formatDate('1994-11-15T08:12:31+0000')); Assert::same('Tue, 15 Nov 1994 08:12:31 GMT', Helpers::formatDate('1994-11-15T10:12:31+0200')); Assert::same('Tue, 15 Nov 1994 08:12:31 GMT', Helpers::formatDate(new DateTime('1994-11-15T06:12:31-0200'))); - Assert::same('Tue, 15 Nov 1994 08:12:31 GMT', Helpers::formatDate(784887151)); + Assert::same('Tue, 15 Nov 1994 08:12:31 GMT', Helpers::formatDate(784_887_151)); }); diff --git a/tests/Http/Request.getRawBody.phpt b/tests/Http/Request.getRawBody.phpt index 16be3064..13f09d72 100644 --- a/tests/Http/Request.getRawBody.phpt +++ b/tests/Http/Request.getRawBody.phpt @@ -13,9 +13,7 @@ require __DIR__ . '/../bootstrap.php'; test('', function () { - $request = new Http\Request(new Http\UrlScript, null, null, null, null, null, null, null, function () { - return 'raw body'; - }); + $request = new Http\Request(new Http\UrlScript, null, null, null, null, null, null, null, fn() => 'raw body'); Assert::same('raw body', $request->getRawBody()); }); diff --git a/tests/Http/Request.invalidType.phpt b/tests/Http/Request.invalidType.phpt index 53a0b3af..f3d328fa 100644 --- a/tests/Http/Request.invalidType.phpt +++ b/tests/Http/Request.invalidType.phpt @@ -17,9 +17,11 @@ test('invalid POST', function () { 'int' => 1, ]; - Assert::exception(function () { - (new Http\RequestFactory)->fromGlobals(); - }, Nette\InvalidStateException::class, 'Invalid value in $_POST/$_COOKIE in key \'int\', expected string, integer given.'); + Assert::exception( + fn() => (new Http\RequestFactory)->fromGlobals(), + Nette\InvalidStateException::class, + 'Invalid value in $_POST/$_COOKIE in key \'int\', expected string, integer given.', + ); }); @@ -27,7 +29,9 @@ test('invalid COOKIE', function () { $_POST = []; $_COOKIE = ['x' => [1]]; - Assert::exception(function () { - (new Http\RequestFactory)->fromGlobals(); - }, Nette\InvalidStateException::class, 'Invalid value in $_POST/$_COOKIE in key \'0\', expected string, integer given.'); + Assert::exception( + fn() => (new Http\RequestFactory)->fromGlobals(), + Nette\InvalidStateException::class, + 'Invalid value in $_POST/$_COOKIE in key \'0\', expected string, integer given.', + ); }); diff --git a/tests/Http/RequestFactory.authorization.phpt b/tests/Http/RequestFactory.authorization.phpt index 31dc021e..7ddbb6d4 100644 --- a/tests/Http/RequestFactory.authorization.phpt +++ b/tests/Http/RequestFactory.authorization.phpt @@ -22,7 +22,7 @@ test('Basic', function () { $request = $factory->fromGlobals(); Assert::same( 'Basic dXNlcjpwYXNzd29yZA==', - $request->getHeader('Authorization') + $request->getHeader('Authorization'), ); Assert::same(['user', 'password'], $request->getBasicCredentials()); @@ -41,7 +41,7 @@ test('Digest', function () { $request = $factory->fromGlobals(); Assert::same( 'Digest username="admin"', - $request->getHeader('Authorization') + $request->getHeader('Authorization'), ); Assert::null($request->getBasicCredentials()); }); diff --git a/tests/Http/Response.setCookie.phpt b/tests/Http/Response.setCookie.phpt index 10f043da..e60fb9e2 100644 --- a/tests/Http/Response.setCookie.phpt +++ b/tests/Http/Response.setCookie.phpt @@ -35,7 +35,7 @@ Assert::same( PHP_VERSION_ID >= 70300 ? ['Set-Cookie: test=value; path=/; HttpOnly; SameSite=Lax', 'Set-Cookie: test=newvalue; path=/; HttpOnly; SameSite=Lax'] : ['Set-Cookie: test=value; path=/; SameSite=Lax; HttpOnly', 'Set-Cookie: test=newvalue; path=/; SameSite=Lax; HttpOnly'], - $headers + $headers, ); diff --git a/tests/Http/Session.autoStart.phpt b/tests/Http/Session.autoStart.phpt index 0cf0528b..365efbbb 100644 --- a/tests/Http/Session.autoStart.phpt +++ b/tests/Http/Session.autoStart.phpt @@ -31,7 +31,8 @@ Assert::true(file_exists(getTempDir() . '/sess_' . $session->getId())); $session->close(); $session->setOptions(['autoStart' => false]); -Assert::error(function () use ($session) { - $session->autoStart(true); -}, E_USER_WARNING); +Assert::error( + fn() => $session->autoStart(true), + E_USER_WARNING, +); Assert::false($session->isStarted()); diff --git a/tests/Http/Session.handler-exceptions.phpt b/tests/Http/Session.handler-exceptions.phpt index 1ca3399b..539df07c 100644 --- a/tests/Http/Session.handler-exceptions.phpt +++ b/tests/Http/Session.handler-exceptions.phpt @@ -32,13 +32,17 @@ class ThrowsOnReadHandler extends SessionHandler $session = new Nette\Http\Session(new Http\Request(new Http\UrlScript('http://nette.org')), new Http\Response); $session->setHandler(new ThrowsOnReadHandler); -Assert::exception(function () use ($session) { - $session->start(); -}, RuntimeException::class, 'Session can\'t be started for whatever reason!'); - -Assert::exception(function () use ($session) { - $session->start(); -}, RuntimeException::class, 'Session can\'t be started for whatever reason!'); +Assert::exception( + fn() => $session->start(), + RuntimeException::class, + 'Session can\'t be started for whatever reason!', +); + +Assert::exception( + fn() => $session->start(), + RuntimeException::class, + 'Session can\'t be started for whatever reason!', +); $session->setHandler(new SessionHandler); $session->start(); diff --git a/tests/Http/Session.sameSite.phpt b/tests/Http/Session.sameSite.phpt index 7dd234b5..def15881 100644 --- a/tests/Http/Session.sameSite.phpt +++ b/tests/Http/Session.sameSite.phpt @@ -25,5 +25,5 @@ Assert::contains( PHP_VERSION_ID >= 70300 ? 'Set-Cookie: PHPSESSID=' . $session->getId() . '; path=/; HttpOnly; SameSite=Lax' : 'Set-Cookie: PHPSESSID=' . $session->getId() . '; path=/; SameSite=Lax; HttpOnly', - headers_list() + headers_list(), ); diff --git a/tests/Http/Session.setOptions.error.phpt b/tests/Http/Session.setOptions.error.phpt index 8187266e..489528da 100644 --- a/tests/Http/Session.setOptions.error.phpt +++ b/tests/Http/Session.setOptions.error.phpt @@ -15,14 +15,18 @@ $factory = new Nette\Http\RequestFactory; $session = new Nette\Http\Session($factory->fromGlobals(), new Nette\Http\Response); $session->start(); -Assert::exception(function () use ($session) { - $session->setOptions([ +Assert::exception( + fn() => $session->setOptions([ 'gc_malifetime' => 123, - ]); -}, Nette\InvalidStateException::class, "Invalid session configuration option 'gc_malifetime', did you mean 'gc_maxlifetime'?"); + ]), + Nette\InvalidStateException::class, + "Invalid session configuration option 'gc_malifetime', did you mean 'gc_maxlifetime'?", +); -Assert::exception(function () use ($session) { - $session->setOptions([ +Assert::exception( + fn() => $session->setOptions([ 'cookieDoman' => '.domain.com', - ]); -}, Nette\InvalidStateException::class, "Invalid session configuration option 'cookieDoman', did you mean 'cookieDomain'?"); + ]), + Nette\InvalidStateException::class, + "Invalid session configuration option 'cookieDoman', did you mean 'cookieDomain'?", +); diff --git a/tests/Http/Session.start.error.phpt b/tests/Http/Session.start.error.phpt index 1a6f6509..66d210d3 100644 --- a/tests/Http/Session.start.error.phpt +++ b/tests/Http/Session.start.error.phpt @@ -18,6 +18,7 @@ ini_set('session.gc_probability', '0'); $session = new Session(new Nette\Http\Request(new Nette\Http\UrlScript), new Nette\Http\Response); -Assert::exception(function () use ($session) { - $session->start(); -}, Nette\InvalidStateException::class); +Assert::exception( + fn() => $session->start(), + Nette\InvalidStateException::class, +); diff --git a/tests/Http/Url.malformedUri.phpt b/tests/Http/Url.malformedUri.phpt index 80f483cb..e1e71ed9 100644 --- a/tests/Http/Url.malformedUri.phpt +++ b/tests/Http/Url.malformedUri.phpt @@ -12,6 +12,8 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -Assert::exception(function () { - $url = new Url('http:///'); -}, InvalidArgumentException::class, "Malformed or unsupported URI 'http:///'."); +Assert::exception( + fn() => new Url('http:///'), + InvalidArgumentException::class, + "Malformed or unsupported URI 'http:///'.", +); diff --git a/tests/Http/UrlImmutable.malformedUri.phpt b/tests/Http/UrlImmutable.malformedUri.phpt index 2259abe8..e2128d3e 100644 --- a/tests/Http/UrlImmutable.malformedUri.phpt +++ b/tests/Http/UrlImmutable.malformedUri.phpt @@ -12,6 +12,8 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -Assert::exception(function () { - new UrlImmutable('http:///'); -}, InvalidArgumentException::class, "Malformed or unsupported URI 'http:///'."); +Assert::exception( + fn() => new UrlImmutable('http:///'), + InvalidArgumentException::class, + "Malformed or unsupported URI 'http:///'.", +); diff --git a/tests/Http/UrlScript.error.phpt b/tests/Http/UrlScript.error.phpt index b0aa6405..8f596b28 100644 --- a/tests/Http/UrlScript.error.phpt +++ b/tests/Http/UrlScript.error.phpt @@ -8,21 +8,25 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -Assert::exception(function () { - new UrlScript('http://nette.org/file.php?q=search', '/a/'); -}, Nette\InvalidArgumentException::class); +Assert::exception( + fn() => new UrlScript('http://nette.org/file.php?q=search', '/a/'), + Nette\InvalidArgumentException::class, +); -Assert::exception(function () { - new UrlScript('http://nette.org/file.php?q=search', 'a'); -}, Nette\InvalidArgumentException::class); +Assert::exception( + fn() => new UrlScript('http://nette.org/file.php?q=search', 'a'), + Nette\InvalidArgumentException::class, +); -Assert::exception(function () { - new UrlScript('http://nette.org/dir/', '/d/'); -}, Nette\InvalidArgumentException::class); +Assert::exception( + fn() => new UrlScript('http://nette.org/dir/', '/d/'), + Nette\InvalidArgumentException::class, +); -Assert::exception(function () { - new UrlScript('http://nette.org/dir/', '/dir/index/'); -}, Nette\InvalidArgumentException::class); +Assert::exception( + fn() => new UrlScript('http://nette.org/dir/', '/dir/index/'), + Nette\InvalidArgumentException::class, +); From 34bd3f127c66a46a4e6ceb98cdc63110829fccaa Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 1 Mar 2021 18:33:58 +0100 Subject: [PATCH 10/25] removed support for PHP 7 --- src/Http/RequestFactory.php | 4 +- src/Http/Response.php | 20 ++------ src/Http/Session.php | 14 +----- .../HttpExtension.sameSiteProtection.phpt | 4 +- tests/Http.DI/SessionExtension.config.phpt | 4 +- tests/Http/Response.setCookie.phpt | 49 +++---------------- tests/Http/Session.sameSite.phpt | 7 +-- 7 files changed, 17 insertions(+), 85 deletions(-) diff --git a/src/Http/RequestFactory.php b/src/Http/RequestFactory.php index c947f38c..58457d9a 100644 --- a/src/Http/RequestFactory.php +++ b/src/Http/RequestFactory.php @@ -277,9 +277,7 @@ private function getMethod(): string private function getClient(Url $url): array { - $remoteAddr = !empty($_SERVER['REMOTE_ADDR']) - ? trim($_SERVER['REMOTE_ADDR'], '[]') // workaround for PHP 7.3.0 - : null; + $remoteAddr = !empty($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null; // use real client address and host if trusted proxy is used $usingTrustedProxy = $remoteAddr && Arrays::some($this->proxies, fn(string $proxy): bool => Helpers::ipMatch($remoteAddr, $proxy)); diff --git a/src/Http/Response.php b/src/Http/Response.php index 8d4fbc80..a4caabea 100644 --- a/src/Http/Response.php +++ b/src/Http/Response.php @@ -256,28 +256,14 @@ public function setCookie( ?string $sameSite = null, ) { self::checkHeaders(); - $options = [ + setcookie($name, $value, [ 'expires' => $expire ? (int) DateTime::from($expire)->format('U') : 0, 'path' => $path ?? ($domain ? '/' : $this->cookiePath), 'domain' => $domain ?? ($path ? '' : $this->cookieDomain), 'secure' => $secure ?? $this->cookieSecure, 'httponly' => $httpOnly ?? true, - 'samesite' => $sameSite = ($sameSite ?? self::SameSiteLax), - ]; - if (PHP_VERSION_ID >= 70300) { - setcookie($name, $value, $options); - } else { - setcookie( - $name, - $value, - $options['expires'], - $options['path'] . ($sameSite ? "; SameSite=$sameSite" : ''), - $options['domain'], - $options['secure'], - $options['httponly'], - ); - } - + 'samesite' => $sameSite ?? self::SameSiteLax, + ]); return $this; } diff --git a/src/Http/Session.php b/src/Http/Session.php index a17cea25..e0fa46ed 100644 --- a/src/Http/Session.php +++ b/src/Http/Session.php @@ -389,7 +389,7 @@ private function clean(): void public function setOptions(array $options) { $normalized = []; - $allowed = ini_get_all('session', details: false) + ['session.read_and_close' => 1, 'session.cookie_samesite' => 1]; // for PHP < 7.3 + $allowed = ini_get_all('session', details: false) + ['session.read_and_close' => 1]; foreach ($options as $key => $value) { if (!strncmp($key, 'session.', 8)) { // back compatibility @@ -473,17 +473,7 @@ private function configure(array $config): void } if ($cookie !== $origCookie) { - if (PHP_VERSION_ID >= 70300) { - @session_set_cookie_params($cookie); // @ may trigger warning when session is active since PHP 7.2 - } else { - @session_set_cookie_params( // @ may trigger warning when session is active since PHP 7.2 - $cookie['lifetime'], - $cookie['path'] . (isset($cookie['samesite']) ? '; SameSite=' . $cookie['samesite'] : ''), - $cookie['domain'], - $cookie['secure'], - $cookie['httponly'], - ); - } + @session_set_cookie_params($cookie); // @ may trigger warning when session is active since PHP 7.2 if (session_status() === PHP_SESSION_ACTIVE) { $this->sendCookie(); diff --git a/tests/Http.DI/HttpExtension.sameSiteProtection.phpt b/tests/Http.DI/HttpExtension.sameSiteProtection.phpt index f6de3332..49142ab4 100644 --- a/tests/Http.DI/HttpExtension.sameSiteProtection.phpt +++ b/tests/Http.DI/HttpExtension.sameSiteProtection.phpt @@ -24,8 +24,6 @@ $container->initialize(); $headers = headers_list(); Assert::contains( - PHP_VERSION_ID >= 70300 - ? 'Set-Cookie: _nss=1; path=/; HttpOnly; SameSite=Strict' - : 'Set-Cookie: _nss=1; path=/; SameSite=Strict; HttpOnly', + 'Set-Cookie: _nss=1; path=/; HttpOnly; SameSite=Strict', $headers, ); diff --git a/tests/Http.DI/SessionExtension.config.phpt b/tests/Http.DI/SessionExtension.config.phpt index 36bb11a8..f826db3b 100644 --- a/tests/Http.DI/SessionExtension.config.phpt +++ b/tests/Http.DI/SessionExtension.config.phpt @@ -39,9 +39,7 @@ $container = new Container; $container->getService('session')->start(); Assert::same( - PHP_VERSION_ID >= 70300 - ? ['lifetime' => 0, 'path' => '/x', 'domain' => 'nette.org', 'secure' => true, 'httponly' => true, 'samesite' => 'Lax'] - : ['lifetime' => 0, 'path' => '/x; SameSite=Lax', 'domain' => 'nette.org', 'secure' => true, 'httponly' => true], + ['lifetime' => 0, 'path' => '/x', 'domain' => 'nette.org', 'secure' => true, 'httponly' => true, 'samesite' => 'Lax'], session_get_cookie_params(), ); diff --git a/tests/Http/Response.setCookie.phpt b/tests/Http/Response.setCookie.phpt index e60fb9e2..6799c6aa 100644 --- a/tests/Http/Response.setCookie.phpt +++ b/tests/Http/Response.setCookie.phpt @@ -22,21 +22,12 @@ $response = new Http\Response; $response->setCookie('test', 'value', 0); $headers = array_values(array_diff(headers_list(), $old, ['Set-Cookie:'])); -Assert::same([ - PHP_VERSION_ID >= 70300 - ? 'Set-Cookie: test=value; path=/; HttpOnly; SameSite=Lax' - : 'Set-Cookie: test=value; path=/; SameSite=Lax; HttpOnly', -], $headers); +Assert::same(['Set-Cookie: test=value; path=/; HttpOnly; SameSite=Lax'], $headers); $response->setCookie('test', 'newvalue', 0); $headers = array_values(array_diff(headers_list(), $old, ['Set-Cookie:'])); -Assert::same( - PHP_VERSION_ID >= 70300 - ? ['Set-Cookie: test=value; path=/; HttpOnly; SameSite=Lax', 'Set-Cookie: test=newvalue; path=/; HttpOnly; SameSite=Lax'] - : ['Set-Cookie: test=value; path=/; SameSite=Lax; HttpOnly', 'Set-Cookie: test=newvalue; path=/; SameSite=Lax; HttpOnly'], - $headers, -); +Assert::same(['Set-Cookie: test=value; path=/; HttpOnly; SameSite=Lax', 'Set-Cookie: test=newvalue; path=/; HttpOnly; SameSite=Lax'], $headers); // cookiePath @@ -45,31 +36,19 @@ $response->cookiePath = '/foo'; $old = headers_list(); $response->setCookie('test', 'a', 0); $headers = array_values(array_diff(headers_list(), $old, ['Set-Cookie:'])); -Assert::same([ - PHP_VERSION_ID >= 70300 - ? 'Set-Cookie: test=a; path=/foo; HttpOnly; SameSite=Lax' - : 'Set-Cookie: test=a; path=/foo; SameSite=Lax; HttpOnly', -], $headers); +Assert::same(['Set-Cookie: test=a; path=/foo; HttpOnly; SameSite=Lax'], $headers); // cookiePath + path $old = headers_list(); $response->setCookie('test', 'b', 0, '/bar'); $headers = array_values(array_diff(headers_list(), $old, ['Set-Cookie:'])); -Assert::same([ - PHP_VERSION_ID >= 70300 - ? 'Set-Cookie: test=b; path=/bar; HttpOnly; SameSite=Lax' - : 'Set-Cookie: test=b; path=/bar; SameSite=Lax; HttpOnly', -], $headers); +Assert::same(['Set-Cookie: test=b; path=/bar; HttpOnly; SameSite=Lax'], $headers); // cookiePath + domain $old = headers_list(); $response->setCookie('test', 'c', 0, null, 'nette.org'); $headers = array_values(array_diff(headers_list(), $old, ['Set-Cookie:'])); -Assert::same([ - PHP_VERSION_ID >= 70300 - ? 'Set-Cookie: test=c; path=/; domain=nette.org; HttpOnly; SameSite=Lax' - : 'Set-Cookie: test=c; path=/; SameSite=Lax; domain=nette.org; HttpOnly', -], $headers); +Assert::same(['Set-Cookie: test=c; path=/; domain=nette.org; HttpOnly; SameSite=Lax'], $headers); // cookieDomain @@ -78,28 +57,16 @@ $response->cookieDomain = 'nette.org'; $old = headers_list(); $response->setCookie('test', 'd', 0); $headers = array_values(array_diff(headers_list(), $old, ['Set-Cookie:'])); -Assert::same([ - PHP_VERSION_ID >= 70300 - ? 'Set-Cookie: test=d; path=/; domain=nette.org; HttpOnly; SameSite=Lax' - : 'Set-Cookie: test=d; path=/; SameSite=Lax; domain=nette.org; HttpOnly', -], $headers); +Assert::same(['Set-Cookie: test=d; path=/; domain=nette.org; HttpOnly; SameSite=Lax'], $headers); // cookieDomain + path $old = headers_list(); $response->setCookie('test', 'e', 0, '/bar'); $headers = array_values(array_diff(headers_list(), $old, ['Set-Cookie:'])); -Assert::same([ - PHP_VERSION_ID >= 70300 - ? 'Set-Cookie: test=e; path=/bar; HttpOnly; SameSite=Lax' - : 'Set-Cookie: test=e; path=/bar; SameSite=Lax; HttpOnly', -], $headers); +Assert::same(['Set-Cookie: test=e; path=/bar; HttpOnly; SameSite=Lax'], $headers); // cookieDomain + domain $old = headers_list(); $response->setCookie('test', 'f', 0, null, 'example.org'); $headers = array_values(array_diff(headers_list(), $old, ['Set-Cookie:'])); -Assert::same([ - PHP_VERSION_ID >= 70300 - ? 'Set-Cookie: test=f; path=/; domain=example.org; HttpOnly; SameSite=Lax' - : 'Set-Cookie: test=f; path=/; SameSite=Lax; domain=example.org; HttpOnly', -], $headers); +Assert::same(['Set-Cookie: test=f; path=/; domain=example.org; HttpOnly; SameSite=Lax'], $headers); diff --git a/tests/Http/Session.sameSite.phpt b/tests/Http/Session.sameSite.phpt index def15881..5ce44cfb 100644 --- a/tests/Http/Session.sameSite.phpt +++ b/tests/Http/Session.sameSite.phpt @@ -21,9 +21,4 @@ $session->setOptions([ $session->start(); -Assert::contains( - PHP_VERSION_ID >= 70300 - ? 'Set-Cookie: PHPSESSID=' . $session->getId() . '; path=/; HttpOnly; SameSite=Lax' - : 'Set-Cookie: PHPSESSID=' . $session->getId() . '; path=/; SameSite=Lax; HttpOnly', - headers_list(), -); +Assert::contains('Set-Cookie: PHPSESSID=' . $session->getId() . '; path=/; HttpOnly; SameSite=Lax', headers_list()); From fc586fe25be54851a2d6be7519e8464b042fc0e4 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 2 Nov 2023 03:53:57 +0100 Subject: [PATCH 11/25] uses nette/utils 4 --- composer.json | 2 +- src/Http/FileUpload.php | 14 ++++---------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/composer.json b/composer.json index 86fbb722..e9fccb2c 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,7 @@ ], "require": { "php": "8.1 - 8.3", - "nette/utils": "^3.2.1 || ~4.0.0" + "nette/utils": "^4.0.4" }, "require-dev": { "nette/di": "^3.1 || ^4.0", diff --git a/src/Http/FileUpload.php b/src/Http/FileUpload.php index a4274987..fd8e78e0 100644 --- a/src/Http/FileUpload.php +++ b/src/Http/FileUpload.php @@ -10,6 +10,7 @@ namespace Nette\Http; use Nette; +use Nette\Utils\Image; /** @@ -217,14 +218,7 @@ function (string $message) use ($dest): void { */ public function isImage(): bool { - $flag = imagetypes(); - $types = array_filter([ - $flag & IMG_GIF ? 'image/gif' : null, - $flag & IMG_JPG ? 'image/jpeg' : null, - $flag & IMG_PNG ? 'image/png' : null, - $flag & IMG_WEBP ? 'image/webp' : null, - $flag & 256 ? 'image/avif' : null, // IMG_AVIF - ]); + $types = array_map(fn($type) => Image::typeToMimeType($type), Image::getSupportedTypes()); return in_array($this->getContentType(), $types, strict: true); } @@ -233,9 +227,9 @@ public function isImage(): bool * Loads an image. * @throws Nette\Utils\ImageException If the upload was not successful or is not a valid image */ - public function toImage(): Nette\Utils\Image + public function toImage(): Image { - return Nette\Utils\Image::fromFile($this->tmpName); + return Image::fromFile($this->tmpName); } From b7119d8c7231a6eee35f269aee2098d817938192 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 25 Nov 2022 14:35:34 +0100 Subject: [PATCH 12/25] added property typehints --- src/Bridges/HttpDI/HttpExtension.php | 3 +- src/Bridges/HttpDI/SessionExtension.php | 7 ++--- src/Http/Context.php | 7 ++--- src/Http/FileUpload.php | 23 ++++----------- src/Http/Request.php | 35 +++++++---------------- src/Http/RequestFactory.php | 8 ++---- src/Http/Response.php | 22 +++++++------- src/Http/Session.php | 38 ++++++++----------------- src/Http/SessionSection.php | 11 ++----- src/Http/Url.php | 34 ++++++---------------- src/Http/UrlImmutable.php | 35 ++++++----------------- src/Http/UrlScript.php | 9 ++---- src/Http/UserStorage.php | 11 ++----- 13 files changed, 74 insertions(+), 169 deletions(-) diff --git a/src/Bridges/HttpDI/HttpExtension.php b/src/Bridges/HttpDI/HttpExtension.php index c92049ed..040ca2b9 100644 --- a/src/Bridges/HttpDI/HttpExtension.php +++ b/src/Bridges/HttpDI/HttpExtension.php @@ -18,8 +18,7 @@ */ class HttpExtension extends Nette\DI\CompilerExtension { - /** @var bool */ - private $cliMode; + private bool $cliMode; public function __construct(bool $cliMode = false) diff --git a/src/Bridges/HttpDI/SessionExtension.php b/src/Bridges/HttpDI/SessionExtension.php index 1c0b598b..d9ac76f8 100644 --- a/src/Bridges/HttpDI/SessionExtension.php +++ b/src/Bridges/HttpDI/SessionExtension.php @@ -19,11 +19,8 @@ */ class SessionExtension extends Nette\DI\CompilerExtension { - /** @var bool */ - private $debugMode; - - /** @var bool */ - private $cliMode; + private bool $debugMode; + private bool $cliMode; public function __construct(bool $debugMode = false, bool $cliMode = false) diff --git a/src/Http/Context.php b/src/Http/Context.php index 1f98655e..f00d6793 100644 --- a/src/Http/Context.php +++ b/src/Http/Context.php @@ -19,11 +19,8 @@ class Context { use Nette\SmartObject; - /** @var IRequest */ - private $request; - - /** @var IResponse */ - private $response; + private IRequest $request; + private IResponse $response; public function __construct(IRequest $request, IResponse $response) diff --git a/src/Http/FileUpload.php b/src/Http/FileUpload.php index fd8e78e0..9840fccc 100644 --- a/src/Http/FileUpload.php +++ b/src/Http/FileUpload.php @@ -33,23 +33,12 @@ final class FileUpload /** @deprecated */ public const IMAGE_MIME_TYPES = ['image/gif', 'image/png', 'image/jpeg', 'image/webp']; - /** @var string */ - private $name; - - /** @var string|null */ - private $fullPath; - - /** @var string|false|null */ - private $type; - - /** @var int */ - private $size; - - /** @var string */ - private $tmpName; - - /** @var int */ - private $error; + private string $name; + private string|null $fullPath; + private string|false|null $type = null; + private int $size; + private string $tmpName; + private int $error; public function __construct(?array $value) diff --git a/src/Http/Request.php b/src/Http/Request.php index 37d6554f..6de415ff 100644 --- a/src/Http/Request.php +++ b/src/Http/Request.php @@ -33,31 +33,16 @@ class Request implements IRequest { use Nette\SmartObject; - /** @var string */ - private $method; - - /** @var UrlScript */ - private $url; - - /** @var array */ - private $post; - - /** @var array */ - private $files; - - /** @var array */ - private $cookies; - - /** @var array */ - private $headers; - - /** @var string|null */ - private $remoteAddress; - - /** @var string|null */ - private $remoteHost; - - /** @var callable|null */ + private string $method; + private UrlScript $url; + private array $post; + private array $files; + private array $cookies; + private array $headers; + private ?string $remoteAddress; + private ?string $remoteHost; + + /** @var ?callable */ private $rawBodyCallback; diff --git a/src/Http/RequestFactory.php b/src/Http/RequestFactory.php index 58457d9a..43a10e8d 100644 --- a/src/Http/RequestFactory.php +++ b/src/Http/RequestFactory.php @@ -24,17 +24,15 @@ class RequestFactory /** @internal */ private const ValidChars = '\x09\x0A\x0D\x20-\x7E\xA0-\x{10FFFF}'; - /** @var array */ - public $urlFilters = [ + public array $urlFilters = [ 'path' => ['#//#' => '/'], // '%20' => '' 'url' => [], // '#[.,)]$#D' => '' ]; - /** @var bool */ - private $binary = false; + private bool $binary = false; /** @var string[] */ - private $proxies = []; + private array $proxies = []; /** @return static */ diff --git a/src/Http/Response.php b/src/Http/Response.php index a4caabea..fb6f2be9 100644 --- a/src/Http/Response.php +++ b/src/Http/Response.php @@ -22,26 +22,26 @@ final class Response implements IResponse { use Nette\SmartObject; - /** @var string The domain in which the cookie will be available */ - public $cookieDomain = ''; + /** The domain in which the cookie will be available */ + public string $cookieDomain = ''; - /** @var string The path in which the cookie will be available */ - public $cookiePath = '/'; + /** The path in which the cookie will be available */ + public string $cookiePath = '/'; - /** @var bool Whether the cookie is available only through HTTPS */ - public $cookieSecure = false; + /** Whether the cookie is available only through HTTPS */ + public bool $cookieSecure = false; /** @deprecated */ public $cookieHttpOnly; - /** @var bool Whether warn on possible problem with data in output buffer */ - public $warnOnBuffer = true; + /** Whether warn on possible problem with data in output buffer */ + public bool $warnOnBuffer = true; /** @deprecated */ - private static $fixIE = true; + private static bool $fixIE = true; - /** @var int HTTP response code */ - private $code = self::S200_OK; + /** HTTP response code */ + private int $code = self::S200_OK; public function __construct() diff --git a/src/Http/Session.php b/src/Http/Session.php index e0fa46ed..c2695393 100644 --- a/src/Http/Session.php +++ b/src/Http/Session.php @@ -32,41 +32,27 @@ class Session ]; /** @var array Occurs when the session is started */ - public $onStart = []; + public array $onStart = []; /** @var array Occurs before the session is written to disk */ - public $onBeforeWrite = []; + public array $onBeforeWrite = []; - /** @var bool has been session ID regenerated? */ - private $regenerated = false; + private bool $regenerated = false; + private bool $started = false; - /** @var bool has been session started by Nette? */ - private $started = false; - - /** @var array default configuration */ - private $options = [ + /** default configuration */ + private array $options = [ 'cookie_samesite' => IResponse::SameSiteLax, 'cookie_lifetime' => 0, // for a maximum of 3 hours or until the browser is closed 'gc_maxlifetime' => self::DefaultFileLifetime, // 3 hours ]; - /** @var IRequest */ - private $request; - - /** @var IResponse */ - private $response; - - /** @var \SessionHandlerInterface */ - private $handler; - - /** @var bool */ - private $readAndClose = false; - - /** @var bool */ - private $fileExists = true; - - /** @var bool */ - private $autoStart = true; + private IRequest $request; + private IResponse $response; + private ?\SessionHandlerInterface $handler = null; + private bool $readAndClose = false; + private bool $fileExists = true; + private bool $autoStart = true; public function __construct(IRequest $request, IResponse $response) diff --git a/src/Http/SessionSection.php b/src/Http/SessionSection.php index 8c98b9eb..31740c8b 100644 --- a/src/Http/SessionSection.php +++ b/src/Http/SessionSection.php @@ -19,14 +19,9 @@ class SessionSection implements \IteratorAggregate, \ArrayAccess { use Nette\SmartObject; - /** @var bool */ - public $warnOnUndefined = false; - - /** @var Session */ - private $session; - - /** @var string */ - private $name; + public bool $warnOnUndefined = false; + private Session $session; + private string $name; /** diff --git a/src/Http/Url.php b/src/Http/Url.php index 31e30dcc..c0d67452 100644 --- a/src/Http/Url.php +++ b/src/Http/Url.php @@ -45,36 +45,20 @@ class Url implements \JsonSerializable { use Nette\SmartObject; - /** @var array */ - public static $defaultPorts = [ + public static array $defaultPorts = [ 'http' => 80, 'https' => 443, 'ftp' => 21, ]; - /** @var string */ - private $scheme = ''; - - /** @var string */ - private $user = ''; - - /** @var string */ - private $password = ''; - - /** @var string */ - private $host = ''; - - /** @var int|null */ - private $port; - - /** @var string */ - private $path = ''; - - /** @var array */ - private $query = []; - - /** @var string */ - private $fragment = ''; + private string $scheme = ''; + private string $user = ''; + private string $password = ''; + private string $host = ''; + private ?int $port = null; + private string $path = ''; + private array $query = []; + private string $fragment = ''; /** diff --git a/src/Http/UrlImmutable.php b/src/Http/UrlImmutable.php index 2ab4c926..52c137d9 100644 --- a/src/Http/UrlImmutable.php +++ b/src/Http/UrlImmutable.php @@ -42,32 +42,15 @@ class UrlImmutable implements \JsonSerializable { use Nette\SmartObject; - /** @var string */ - private $scheme = ''; - - /** @var string */ - private $user = ''; - - /** @var string */ - private $password = ''; - - /** @var string */ - private $host = ''; - - /** @var int|null */ - private $port; - - /** @var string */ - private $path = ''; - - /** @var array */ - private $query = []; - - /** @var string */ - private $fragment = ''; - - /** @var string */ - private $authority = ''; + private string $scheme = ''; + private string $user = ''; + private string $password = ''; + private string $host = ''; + private ?int $port = null; + private string $path = ''; + private array $query = []; + private string $fragment = ''; + private string $authority = ''; /** diff --git a/src/Http/UrlScript.php b/src/Http/UrlScript.php index bcae5669..9b17a040 100644 --- a/src/Http/UrlScript.php +++ b/src/Http/UrlScript.php @@ -34,17 +34,14 @@ */ class UrlScript extends UrlImmutable { - /** @var string */ - private $scriptPath; - - /** @var string */ - private $basePath; + private string $scriptPath; + private string $basePath; public function __construct($url = '/', string $scriptPath = '') { - parent::__construct($url); $this->scriptPath = $scriptPath; + parent::__construct($url); $this->build(); } diff --git a/src/Http/UserStorage.php b/src/Http/UserStorage.php index d04b985a..3b6b1a4d 100644 --- a/src/Http/UserStorage.php +++ b/src/Http/UserStorage.php @@ -20,14 +20,9 @@ class UserStorage implements Nette\Security\IUserStorage { use Nette\SmartObject; - /** @var string */ - private $namespace = ''; - - /** @var Session */ - private $sessionHandler; - - /** @var SessionSection */ - private $sessionSection; + private string $namespace = ''; + private Session $sessionHandler; + private SessionSection $sessionSection; public function __construct(Session $sessionHandler) From f2b65305c75829283e9b30b293ff91e49e7eb088 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 12 Dec 2021 18:37:13 +0100 Subject: [PATCH 13/25] added PHP 8 typehints --- src/Http/Context.php | 3 +- src/Http/FileUpload.php | 3 +- src/Http/Helpers.php | 3 +- src/Http/IRequest.php | 12 ++--- src/Http/IResponse.php | 28 +++++----- src/Http/Request.php | 15 ++---- src/Http/RequestFactory.php | 6 +-- src/Http/Response.php | 35 ++++++------ src/Http/Session.php | 20 +++---- src/Http/SessionSection.php | 19 +++---- src/Http/Url.php | 54 +++++-------------- src/Http/UrlImmutable.php | 51 +++++------------- src/Http/UrlScript.php | 5 +- src/Http/UserStorage.php | 12 ++--- tests/Http.DI/SessionExtension.handler.phpt | 5 +- tests/Http/Session.handler-exceptions.phpt | 6 +-- tests/Http/Session.handler.phpt | 21 +++----- .../Http/SessionSection.setExpiration().phpt | 4 +- ...SessionSection.setExpirationUnlimited.phpt | 2 +- tests/Http/Url.query.phpt | 4 -- 20 files changed, 104 insertions(+), 204 deletions(-) diff --git a/src/Http/Context.php b/src/Http/Context.php index f00d6793..5ba90d71 100644 --- a/src/Http/Context.php +++ b/src/Http/Context.php @@ -32,9 +32,8 @@ public function __construct(IRequest $request, IResponse $response) /** * Attempts to cache the sent entity by its last modification date. - * @param string|int|\DateTimeInterface $lastModified */ - public function isModified($lastModified = null, ?string $etag = null): bool + public function isModified(string|int|\DateTimeInterface|null $lastModified = null, ?string $etag = null): bool { if ($lastModified) { $this->response->setHeader('Last-Modified', Helpers::formatDate($lastModified)); diff --git a/src/Http/FileUpload.php b/src/Http/FileUpload.php index 9840fccc..5dd972d5 100644 --- a/src/Http/FileUpload.php +++ b/src/Http/FileUpload.php @@ -181,9 +181,8 @@ public function hasFile(): bool /** * Moves an uploaded file to a new location. If the destination file already exists, it will be overwritten. - * @return static */ - public function move(string $dest) + public function move(string $dest): static { $dir = dirname($dest); Nette\Utils\FileSystem::createDir($dir); diff --git a/src/Http/Helpers.php b/src/Http/Helpers.php index e837ba75..66f145b9 100644 --- a/src/Http/Helpers.php +++ b/src/Http/Helpers.php @@ -29,9 +29,8 @@ final class Helpers /** * Returns HTTP valid date format. - * @param string|int|\DateTimeInterface $time */ - public static function formatDate($time): string + public static function formatDate(string|int|\DateTimeInterface $time): string { $time = DateTime::from($time)->setTimezone(new \DateTimeZone('GMT')); return $time->format('D, d M Y H:i:s \G\M\T'); diff --git a/src/Http/IRequest.php b/src/Http/IRequest.php index 9c3fb8f7..434682c1 100644 --- a/src/Http/IRequest.php +++ b/src/Http/IRequest.php @@ -58,22 +58,19 @@ function getUrl(): UrlScript; /** * Returns variable provided to the script via URL query ($_GET). * If no key is passed, returns the entire array. - * @return mixed */ - function getQuery(?string $key = null); + function getQuery(?string $key = null): mixed; /** * Returns variable provided to the script via POST method ($_POST). * If no key is passed, returns the entire array. - * @return mixed */ - function getPost(?string $key = null); + function getPost(?string $key = null): mixed; /** * Returns uploaded file. - * @return FileUpload|array|null */ - function getFile(string $key); + function getFile(string $key): ?FileUpload; /** * Returns uploaded files. @@ -82,9 +79,8 @@ function getFiles(): array; /** * Returns variable provided to the script via HTTP cookies. - * @return mixed */ - function getCookie(string $key); + function getCookie(string $key): mixed; /** * Returns variables provided to the script via HTTP cookies. diff --git a/src/Http/IResponse.php b/src/Http/IResponse.php index b421220b..61eaa5f1 100644 --- a/src/Http/IResponse.php +++ b/src/Http/IResponse.php @@ -343,9 +343,8 @@ interface IResponse /** * Sets HTTP response code. - * @return static */ - function setCode(int $code, ?string $reason = null); + function setCode(int $code, ?string $reason = null): static; /** * Returns HTTP response code. @@ -354,21 +353,18 @@ function getCode(): int; /** * Sends a HTTP header and replaces a previous one. - * @return static */ - function setHeader(string $name, string $value); + function setHeader(string $name, string $value): static; /** * Adds HTTP header. - * @return static */ - function addHeader(string $name, string $value); + function addHeader(string $name, string $value): static; /** * Sends a Content-type HTTP header. - * @return static */ - function setContentType(string $type, ?string $charset = null); + function setContentType(string $type, ?string $charset = null): static; /** * Redirects to a new URL. @@ -377,9 +373,8 @@ function redirect(string $url, int $code = self::S302_Found): void; /** * Sets the time (like '20 minutes') before a page cached on a browser expires, null means "must-revalidate". - * @return static */ - function setExpiration(?string $expire); + function setExpiration(?string $expire): static; /** * Checks if headers have been sent. @@ -398,21 +393,24 @@ function getHeaders(): array; /** * Sends a cookie. - * @param string|int|\DateTimeInterface $expire time, value null means "until the browser session ends" - * @return static */ function setCookie( string $name, string $value, - $expire, + ?int $expire, ?string $path = null, ?string $domain = null, ?bool $secure = null, ?bool $httpOnly = null, - ); + ): static; /** * Deletes a cookie. */ - function deleteCookie(string $name, ?string $path = null, ?string $domain = null, ?bool $secure = null); + function deleteCookie( + string $name, + ?string $path = null, + ?string $domain = null, + ?bool $secure = null, + ); } diff --git a/src/Http/Request.php b/src/Http/Request.php index 6de415ff..22fe77be 100644 --- a/src/Http/Request.php +++ b/src/Http/Request.php @@ -71,9 +71,8 @@ public function __construct( /** * Returns a clone with a different URL. - * @return static */ - public function withUrl(UrlScript $url) + public function withUrl(UrlScript $url): static { $dolly = clone $this; $dolly->url = $url; @@ -96,9 +95,8 @@ public function getUrl(): UrlScript /** * Returns variable provided to the script via URL query ($_GET). * If no key is passed, returns the entire array. - * @return mixed */ - public function getQuery(?string $key = null) + public function getQuery(?string $key = null): mixed { if (func_num_args() === 0) { return $this->url->getQueryParameters(); @@ -113,9 +111,8 @@ public function getQuery(?string $key = null) /** * Returns variable provided to the script via POST method ($_POST). * If no key is passed, returns the entire array. - * @return mixed */ - public function getPost(?string $key = null) + public function getPost(?string $key = null): mixed { if (func_num_args() === 0) { return $this->post; @@ -130,9 +127,8 @@ public function getPost(?string $key = null) /** * Returns uploaded file. * @param string|string[] $key - * @return ?FileUpload */ - public function getFile($key) + public function getFile($key): ?FileUpload { $res = Nette\Utils\Arrays::get($this->files, $key, null); return $res instanceof FileUpload ? $res : null; @@ -150,9 +146,8 @@ public function getFiles(): array /** * Returns a cookie or `null` if it does not exist. - * @return mixed */ - public function getCookie(string $key) + public function getCookie(string $key): mixed { if (func_num_args() > 1) { trigger_error(__METHOD__ . '() parameter $default is deprecated, use operator ??', E_USER_DEPRECATED); diff --git a/src/Http/RequestFactory.php b/src/Http/RequestFactory.php index 43a10e8d..667a7667 100644 --- a/src/Http/RequestFactory.php +++ b/src/Http/RequestFactory.php @@ -35,8 +35,7 @@ class RequestFactory private array $proxies = []; - /** @return static */ - public function setBinary(bool $binary = true) + public function setBinary(bool $binary = true): static { $this->binary = $binary; return $this; @@ -45,9 +44,8 @@ public function setBinary(bool $binary = true) /** * @param string|string[] $proxy - * @return static */ - public function setProxy($proxy) + public function setProxy($proxy): static { $this->proxies = (array) $proxy; return $this; diff --git a/src/Http/Response.php b/src/Http/Response.php index fb6f2be9..c9c2585d 100644 --- a/src/Http/Response.php +++ b/src/Http/Response.php @@ -54,11 +54,10 @@ public function __construct() /** * Sets HTTP response code. - * @return static * @throws Nette\InvalidArgumentException if code is invalid * @throws Nette\InvalidStateException if HTTP headers have been sent */ - public function setCode(int $code, ?string $reason = null) + public function setCode(int $code, ?string $reason = null): static { if ($code < 100 || $code > 599) { throw new Nette\InvalidArgumentException("Bad HTTP response '$code'."); @@ -84,10 +83,9 @@ public function getCode(): int /** * Sends an HTTP header and overwrites previously sent header of the same name. - * @return static * @throws Nette\InvalidStateException if HTTP headers have been sent */ - public function setHeader(string $name, ?string $value) + public function setHeader(string $name, ?string $value): static { self::checkHeaders(); if ($value === null) { @@ -104,10 +102,9 @@ public function setHeader(string $name, ?string $value) /** * Sends an HTTP header and doesn't overwrite previously sent header of the same name. - * @return static * @throws Nette\InvalidStateException if HTTP headers have been sent */ - public function addHeader(string $name, string $value) + public function addHeader(string $name, string $value): static { self::checkHeaders(); header($name . ': ' . $value, replace: false); @@ -117,10 +114,9 @@ public function addHeader(string $name, string $value) /** * Deletes a previously sent HTTP header. - * @return static * @throws Nette\InvalidStateException if HTTP headers have been sent */ - public function deleteHeader(string $name) + public function deleteHeader(string $name): static { self::checkHeaders(); header_remove($name); @@ -130,10 +126,9 @@ public function deleteHeader(string $name) /** * Sends a Content-type HTTP header. - * @return static * @throws Nette\InvalidStateException if HTTP headers have been sent */ - public function setContentType(string $type, ?string $charset = null) + public function setContentType(string $type, ?string $charset = null): static { $this->setHeader('Content-Type', $type . ($charset ? '; charset=' . $charset : '')); return $this; @@ -142,10 +137,9 @@ public function setContentType(string $type, ?string $charset = null) /** * Response should be downloaded with 'Save as' dialog. - * @return static * @throws Nette\InvalidStateException if HTTP headers have been sent */ - public function sendAsFile(string $fileName) + public function sendAsFile(string $fileName): static { $this->setHeader( 'Content-Disposition', @@ -174,10 +168,9 @@ public function redirect(string $url, int $code = self::S302_Found): void /** * Sets the expiration of the HTTP document using the `Cache-Control` and `Expires` headers. * The parameter is either a time interval (as text) or `null`, which disables caching. - * @return static * @throws Nette\InvalidStateException if HTTP headers have been sent */ - public function setExpiration(?string $expire) + public function setExpiration(?string $expire): static { $this->setHeader('Pragma', null); if (!$expire) { // no cache @@ -241,20 +234,19 @@ public function getHeaders(): array /** * Sends a cookie. - * @param string|int|\DateTimeInterface $expire expiration time, value null means "until the browser session ends" - * @return static * @throws Nette\InvalidStateException if HTTP headers have been sent */ public function setCookie( string $name, string $value, - $expire, + string|int|\DateTimeInterface|null $expire, ?string $path = null, ?string $domain = null, ?bool $secure = null, ?bool $httpOnly = null, ?string $sameSite = null, - ) { + ): static + { self::checkHeaders(); setcookie($name, $value, [ 'expires' => $expire ? (int) DateTime::from($expire)->format('U') : 0, @@ -272,7 +264,12 @@ public function setCookie( * Deletes a cookie. * @throws Nette\InvalidStateException if HTTP headers have been sent */ - public function deleteCookie(string $name, ?string $path = null, ?string $domain = null, ?bool $secure = null): void + public function deleteCookie( + string $name, + ?string $path = null, + ?string $domain = null, + ?bool $secure = null, + ): void { $this->setCookie($name, '', 0, $path, $domain, $secure); } diff --git a/src/Http/Session.php b/src/Http/Session.php index c2695393..257e9365 100644 --- a/src/Http/Session.php +++ b/src/Http/Session.php @@ -274,9 +274,8 @@ public function getId(): string /** * Sets the session name to a specified one. - * @return static */ - public function setName(string $name) + public function setName(string $name): static { if (!preg_match('#[^0-9.][^.]*$#DA', $name)) { throw new Nette\InvalidArgumentException('Session name cannot contain dot.'); @@ -303,7 +302,6 @@ public function getName(): string /** * Returns specified session section. - * @throws Nette\InvalidArgumentException */ public function getSection(string $section, string $class = SessionSection::class): SessionSection { @@ -368,11 +366,10 @@ private function clean(): void /** * Sets session options. - * @return static * @throws Nette\NotSupportedException * @throws Nette\InvalidStateException */ - public function setOptions(array $options) + public function setOptions(array $options): static { $normalized = []; $allowed = ini_get_all('session', details: false) + ['session.read_and_close' => 1]; @@ -475,9 +472,8 @@ private function configure(array $config): void /** * Sets the amount of time (like '20 minutes') allowed between requests before the session will be terminated, * null means "for a maximum of 3 hours or until the browser is closed". - * @return static */ - public function setExpiration(?string $expire) + public function setExpiration(?string $expire): static { if ($expire === null) { return $this->setOptions([ @@ -497,14 +493,14 @@ public function setExpiration(?string $expire) /** * Sets the session cookie parameters. - * @return static */ public function setCookieParameters( string $path, ?string $domain = null, ?bool $secure = null, ?string $sameSite = null, - ) { + ): static + { return $this->setOptions([ 'cookie_path' => $path, 'cookie_domain' => $domain, @@ -524,9 +520,8 @@ public function getCookieParameters(): array /** * Sets path of the directory used to save session data. - * @return static */ - public function setSavePath(string $path) + public function setSavePath(string $path): static { return $this->setOptions([ 'save_path' => $path, @@ -536,9 +531,8 @@ public function setSavePath(string $path) /** * Sets user session handler. - * @return static */ - public function setHandler(\SessionHandlerInterface $handler) + public function setHandler(\SessionHandlerInterface $handler): static { if ($this->started) { throw new Nette\InvalidStateException('Unable to set handler when session has been started.'); diff --git a/src/Http/SessionSection.php b/src/Http/SessionSection.php index 31740c8b..06b8572a 100644 --- a/src/Http/SessionSection.php +++ b/src/Http/SessionSection.php @@ -46,9 +46,8 @@ public function getIterator(): \Iterator /** * Sets a variable in this session section. - * @param mixed $value */ - public function set(string $name, $value, ?string $expire = null): void + public function set(string $name, mixed $value, ?string $expire = null): void { if ($value === null) { $this->remove($name); @@ -62,9 +61,8 @@ public function set(string $name, $value, ?string $expire = null): void /** * Gets a variable from this session section. - * @return mixed */ - public function get(string $name) + public function get(string $name): mixed { if (func_num_args() > 1) { throw new \ArgumentCountError(__METHOD__ . '() expects 1 arguments, given more.'); @@ -79,7 +77,7 @@ public function get(string $name) * Removes a variable or whole section. * @param string|string[]|null $name */ - public function remove($name = null): void + public function remove(string|array|null $name = null): void { $this->session->autoStart(false); if (func_num_args() > 1) { @@ -112,7 +110,7 @@ public function __set(string $name, $value): void * Gets a variable from this session section. * @deprecated use get() instead */ - public function &__get(string $name) + public function &__get(string $name): mixed { $this->session->autoStart(true); $data = &$this->getData(); @@ -159,8 +157,7 @@ public function offsetSet($name, $value): void * Gets a variable from this session section. * @deprecated use get() instead */ - #[\ReturnTypeWillChange] - public function offsetGet($name) + public function offsetGet($name): mixed { return $this->get($name); } @@ -188,11 +185,9 @@ public function offsetUnset($name): void /** * Sets the expiration of the section or specific variables. - * @param ?string $expire * @param string|string[]|null $variables list of variables / single variable to expire - * @return static */ - public function setExpiration($expire, $variables = null) + public function setExpiration(?string $expire, string|array|null $variables = null): static { $this->session->autoStart((bool) $expire); $meta = &$this->getMeta(); @@ -219,7 +214,7 @@ public function setExpiration($expire, $variables = null) * Removes the expiration from the section or specific variables. * @param string|string[]|null $variables list of variables / single variable to expire */ - public function removeExpiration($variables = null): void + public function removeExpiration(string|array|null $variables = null): void { $this->setExpiration(null, $variables); } diff --git a/src/Http/Url.php b/src/Http/Url.php index c0d67452..bbaffc9e 100644 --- a/src/Http/Url.php +++ b/src/Http/Url.php @@ -62,10 +62,9 @@ class Url implements \JsonSerializable /** - * @param string|self|UrlImmutable $url * @throws Nette\InvalidArgumentException if URL is malformed */ - public function __construct($url = null) + public function __construct(string|self|UrlImmutable|null $url = null) { if (is_string($url)) { $p = @parse_url($url); // @ - is escalated to exception @@ -84,15 +83,11 @@ public function __construct($url = null) } elseif ($url instanceof UrlImmutable || $url instanceof self) { [$this->scheme, $this->user, $this->password, $this->host, $this->port, $this->path, $this->query, $this->fragment] = $url->export(); - - } elseif ($url !== null) { - throw new Nette\InvalidArgumentException; } } - /** @return static */ - public function setScheme(string $scheme) + public function setScheme(string $scheme): static { $this->scheme = $scheme; return $this; @@ -105,8 +100,7 @@ public function getScheme(): string } - /** @return static */ - public function setUser(string $user) + public function setUser(string $user): static { $this->user = $user; return $this; @@ -119,8 +113,7 @@ public function getUser(): string } - /** @return static */ - public function setPassword(string $password) + public function setPassword(string $password): static { $this->password = $password; return $this; @@ -133,8 +126,7 @@ public function getPassword(): string } - /** @return static */ - public function setHost(string $host) + public function setHost(string $host): static { $this->host = $host; $this->setPath($this->path); @@ -163,8 +155,7 @@ public function getDomain(int $level = 2): string } - /** @return static */ - public function setPort(int $port) + public function setPort(int $port): static { $this->port = $port; return $this; @@ -183,8 +174,7 @@ public function getDefaultPort(): ?int } - /** @return static */ - public function setPath(string $path) + public function setPath(string $path): static { $this->path = $path; if ($this->host && substr($this->path, 0, 1) !== '/') { @@ -201,22 +191,14 @@ public function getPath(): string } - /** - * @param string|array $value - * @return static - */ - public function setQuery($query) + public function setQuery(string|array $query): static { $this->query = is_array($query) ? $query : self::parseQuery($query); return $this; } - /** - * @param string|array $value - * @return static - */ - public function appendQuery($query) + public function appendQuery(string|array $query): static { $this->query = is_array($query) ? $query + $this->query @@ -237,8 +219,7 @@ public function getQueryParameters(): array } - /** @return mixed */ - public function getQueryParameter(string $name) + public function getQueryParameter(string $name): mixed { if (func_num_args() > 1) { trigger_error(__METHOD__ . '() parameter $default is deprecated, use operator ??', E_USER_DEPRECATED); @@ -248,19 +229,14 @@ public function getQueryParameter(string $name) } - /** - * @param mixed $value null unsets the parameter - * @return static - */ - public function setQueryParameter(string $name, $value) + public function setQueryParameter(string $name, mixed $value): static { $this->query[$name] = $value; return $this; } - /** @return static */ - public function setFragment(string $fragment) + public function setFragment(string $fragment): static { $this->fragment = $fragment; return $this; @@ -332,9 +308,8 @@ public function getRelativeUrl(): string /** * URL comparison. - * @param string|self $url */ - public function isEqual($url): bool + public function isEqual(string|self|UrlImmutable $url): bool { $url = new self($url); $query = $url->query; @@ -357,10 +332,9 @@ public function isEqual($url): bool /** * Transforms URL to canonical form. - * @return static * @deprecated */ - public function canonicalize() + public function canonicalize(): static { $this->path = preg_replace_callback( '#[^!$&\'()*+,/:;=@%]+#', diff --git a/src/Http/UrlImmutable.php b/src/Http/UrlImmutable.php index 52c137d9..e97f2139 100644 --- a/src/Http/UrlImmutable.php +++ b/src/Http/UrlImmutable.php @@ -54,23 +54,17 @@ class UrlImmutable implements \JsonSerializable /** - * @param string|self|Url $url * @throws Nette\InvalidArgumentException if URL is malformed */ - public function __construct($url) + public function __construct(string|self|Url $url) { - if (!$url instanceof Url && !$url instanceof self && !is_string($url)) { - throw new Nette\InvalidArgumentException; - } - $url = is_string($url) ? new Url($url) : $url; [$this->scheme, $this->user, $this->password, $this->host, $this->port, $this->path, $this->query, $this->fragment] = $url->export(); $this->build(); } - /** @return static */ - public function withScheme(string $scheme) + public function withScheme(string $scheme): static { $dolly = clone $this; $dolly->scheme = $scheme; @@ -85,8 +79,7 @@ public function getScheme(): string } - /** @return static */ - public function withUser(string $user) + public function withUser(string $user): static { $dolly = clone $this; $dolly->user = $user; @@ -101,8 +94,7 @@ public function getUser(): string } - /** @return static */ - public function withPassword(string $password) + public function withPassword(string $password): static { $dolly = clone $this; $dolly->password = $password; @@ -117,8 +109,7 @@ public function getPassword(): string } - /** @return static */ - public function withoutUserInfo() + public function withoutUserInfo(): static { $dolly = clone $this; $dolly->user = $dolly->password = ''; @@ -127,8 +118,7 @@ public function withoutUserInfo() } - /** @return static */ - public function withHost(string $host) + public function withHost(string $host): static { $dolly = clone $this; $dolly->host = $host; @@ -155,8 +145,7 @@ public function getDomain(int $level = 2): string } - /** @return static */ - public function withPort(int $port) + public function withPort(int $port): static { $dolly = clone $this; $dolly->port = $port; @@ -177,8 +166,7 @@ public function getDefaultPort(): ?int } - /** @return static */ - public function withPath(string $path) + public function withPath(string $path): static { $dolly = clone $this; $dolly->path = $path; @@ -193,11 +181,7 @@ public function getPath(): string } - /** - * @param string|array $query - * @return static - */ - public function withQuery($query) + public function withQuery(string|array $query): static { $dolly = clone $this; $dolly->query = is_array($query) ? $query : Url::parseQuery($query); @@ -212,11 +196,7 @@ public function getQuery(): string } - /** - * @param mixed $value null unsets the parameter - * @return static - */ - public function withQueryParameter(string $name, $value) + public function withQueryParameter(string $name, mixed $value): static { $dolly = clone $this; $dolly->query[$name] = $value; @@ -230,15 +210,13 @@ public function getQueryParameters(): array } - /** @return array|string|null */ - public function getQueryParameter(string $name) + public function getQueryParameter(string $name): array|string|null { return $this->query[$name] ?? null; } - /** @return static */ - public function withFragment(string $fragment) + public function withFragment(string $fragment): static { $dolly = clone $this; $dolly->fragment = $fragment; @@ -289,10 +267,7 @@ public function __toString(): string } - /** - * @param string|Url|self $url - */ - public function isEqual($url): bool + public function isEqual(string|Url|self $url): bool { return (new Url($this))->isEqual($url); } diff --git a/src/Http/UrlScript.php b/src/Http/UrlScript.php index 9b17a040..7b02ea73 100644 --- a/src/Http/UrlScript.php +++ b/src/Http/UrlScript.php @@ -38,7 +38,7 @@ class UrlScript extends UrlImmutable private string $basePath; - public function __construct($url = '/', string $scriptPath = '') + public function __construct(string|Url $url = '/', string $scriptPath = '') { $this->scriptPath = $scriptPath; parent::__construct($url); @@ -46,8 +46,7 @@ public function __construct($url = '/', string $scriptPath = '') } - /** @return static */ - public function withPath(string $path, string $scriptPath = '') + public function withPath(string $path, string $scriptPath = ''): static { $dolly = clone $this; $dolly->scriptPath = $scriptPath; diff --git a/src/Http/UserStorage.php b/src/Http/UserStorage.php index 3b6b1a4d..a557601a 100644 --- a/src/Http/UserStorage.php +++ b/src/Http/UserStorage.php @@ -33,9 +33,8 @@ public function __construct(Session $sessionHandler) /** * Sets the authenticated status of this user. - * @return static */ - public function setAuthenticated(bool $state) + public function setAuthenticated(bool $state): self { $section = $this->getSessionSection(true); $section->authenticated = $state; @@ -68,9 +67,8 @@ public function isAuthenticated(): bool /** * Sets the user identity. - * @return static */ - public function setIdentity(?IIdentity $identity) + public function setIdentity(?IIdentity $identity): self { $this->getSessionSection(true)->identity = $identity; return $this; @@ -89,9 +87,8 @@ public function getIdentity(): ?Nette\Security\IIdentity /** * Changes namespace; allows more users to share a session. - * @return static */ - public function setNamespace(string $namespace) + public function setNamespace(string $namespace): self { if ($this->namespace !== $namespace) { $this->namespace = $namespace; @@ -113,9 +110,8 @@ public function getNamespace(): string /** * Enables log out after inactivity. Accepts flag IUserStorage::CLEAR_IDENTITY. - * @return static */ - public function setExpiration(?string $time, int $flags = 0) + public function setExpiration(?string $time, int $flags = 0): self { $section = $this->getSessionSection(true); if ($time) { diff --git a/tests/Http.DI/SessionExtension.handler.phpt b/tests/Http.DI/SessionExtension.handler.phpt index 19c61e18..2da0fdb6 100644 --- a/tests/Http.DI/SessionExtension.handler.phpt +++ b/tests/Http.DI/SessionExtension.handler.phpt @@ -19,11 +19,10 @@ class TestHandler extends SessionHandler public $called = false; - #[ReturnTypeWillChange] - public function open($save_path, $session_name) + public function open(string $savePath, string $sessionName): bool { $this->called = true; - return parent::open($save_path, $session_name); + return parent::open($savePath, $sessionName); } } diff --git a/tests/Http/Session.handler-exceptions.phpt b/tests/Http/Session.handler-exceptions.phpt index 539df07c..ecd4df73 100644 --- a/tests/Http/Session.handler-exceptions.phpt +++ b/tests/Http/Session.handler-exceptions.phpt @@ -14,15 +14,13 @@ require __DIR__ . '/../bootstrap.php'; class ThrowsOnReadHandler extends SessionHandler { - #[ReturnTypeWillChange] - public function open($save_path, $session_id) + public function open(string $savePath, string $sessionName): bool { return true; // never throw an exception from here, the universe might implode } - #[ReturnTypeWillChange] - public function read($session_id) + public function read(string $id): string|false { throw new RuntimeException("Session can't be started for whatever reason!"); } diff --git a/tests/Http/Session.handler.phpt b/tests/Http/Session.handler.phpt index 05c8bb6b..5f979703 100644 --- a/tests/Http/Session.handler.phpt +++ b/tests/Http/Session.handler.phpt @@ -16,44 +16,38 @@ class MySessionStorage implements SessionHandlerInterface private $path; - #[ReturnTypeWillChange] - public function open($savePath, $sessionName) + public function open(string $savePath, string $sessionName): bool { $this->path = $savePath; return true; } - #[ReturnTypeWillChange] - public function close() + public function close(): bool { return true; } - #[ReturnTypeWillChange] - public function read($id) + public function read(string $id): string|false { return (string) @file_get_contents("$this->path/sess_$id"); } - #[ReturnTypeWillChange] - public function write($id, $data) + public function write(string $id, string $data): bool { return (bool) file_put_contents("$this->path/sess_$id", $data); } - #[ReturnTypeWillChange] - public function destroy($id) + public function destroy(string $id): bool { return !is_file("$this->path/sess_$id") || @unlink("$this->path/sess_$id"); } - #[ReturnTypeWillChange] - public function gc($maxlifetime) + public function gc(int $maxlifetime): int|false { foreach (glob("$this->path/sess_*") as $filename) { if (filemtime($filename) + $maxlifetime < time()) { @@ -65,8 +59,7 @@ class MySessionStorage implements SessionHandlerInterface } - #[ReturnTypeWillChange] - public function validateId($key) + public function validateId(string $id): bool { return true; } diff --git a/tests/Http/SessionSection.setExpiration().phpt b/tests/Http/SessionSection.setExpiration().phpt index 7eb82321..44fa99e6 100644 --- a/tests/Http/SessionSection.setExpiration().phpt +++ b/tests/Http/SessionSection.setExpiration().phpt @@ -34,7 +34,7 @@ test('try to expire whole namespace', function () use ($session) { test('try to expire only 1 of the keys', function () use ($session) { $namespace = $session->getSection('expireSingle'); - $namespace->setExpiration(1, 'g'); + $namespace->setExpiration('1 second', 'g'); $namespace->g = 'guava'; $namespace->p = 'plum'; $namespace->set('a', 'apple', '1 second'); @@ -51,5 +51,5 @@ test('try to expire only 1 of the keys', function () use ($session) { // small expiration Assert::error(function () use ($session) { $namespace = $session->getSection('tmp'); - $namespace->setExpiration(100); + $namespace->setExpiration('100 second'); }, E_USER_NOTICE, 'The expiration time is greater than the session expiration %d% seconds'); diff --git a/tests/Http/SessionSection.setExpirationUnlimited.phpt b/tests/Http/SessionSection.setExpirationUnlimited.phpt index b387068c..9d17fe7f 100644 --- a/tests/Http/SessionSection.setExpirationUnlimited.phpt +++ b/tests/Http/SessionSection.setExpirationUnlimited.phpt @@ -17,6 +17,6 @@ $session->setOptions(['gc_maxlifetime' => '0']); //memcache handler supports unl //try to set section to shorter expiration $namespace = $session->getSection('maxlifetime'); -$namespace->setExpiration(100); +$namespace->setExpiration('100 second'); Assert::same(true, true); // fix Error: This test forgets to execute an assertion. diff --git a/tests/Http/Url.query.phpt b/tests/Http/Url.query.phpt index cd48cab7..cd3a56c6 100644 --- a/tests/Http/Url.query.phpt +++ b/tests/Http/Url.query.phpt @@ -16,10 +16,6 @@ $url = new Url('http://hostname/path?arg=value'); Assert::same('arg=value', $url->query); Assert::same(['arg' => 'value'], $url->getQueryParameters()); -$url->appendQuery(null); -Assert::same('arg=value', $url->query); -Assert::same(['arg' => 'value'], $url->getQueryParameters()); - $url->appendQuery([null]); Assert::same('arg=value', $url->query); Assert::same([null, 'arg' => 'value'], $url->getQueryParameters()); From 6796300e846bb5d46ce6929f7d21803c9ca13956 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 2 Mar 2021 14:59:23 +0100 Subject: [PATCH 14/25] removed deprecated stuff & UserStorage --- src/Bridges/HttpDI/SessionExtension.php | 12 +- src/Http/Helpers.php | 3 - src/Http/IResponse.php | 6 - src/Http/Request.php | 12 -- src/Http/Response.php | 12 +- src/Http/Session.php | 16 --- src/Http/Url.php | 5 - src/Http/UserStorage.php | 179 ------------------------ 8 files changed, 2 insertions(+), 243 deletions(-) delete mode 100644 src/Http/UserStorage.php diff --git a/src/Bridges/HttpDI/SessionExtension.php b/src/Bridges/HttpDI/SessionExtension.php index d9ac76f8..81485e6c 100644 --- a/src/Bridges/HttpDI/SessionExtension.php +++ b/src/Bridges/HttpDI/SessionExtension.php @@ -38,7 +38,7 @@ public function getConfigSchema(): Nette\Schema\Schema 'expiration' => Expect::string()->dynamic(), 'handler' => Expect::string()->dynamic(), 'readAndClose' => Expect::bool(), - 'cookieSamesite' => Expect::anyOf(IResponse::SameSiteLax, IResponse::SameSiteStrict, IResponse::SameSiteNone, true) + 'cookieSamesite' => Expect::anyOf(IResponse::SameSiteLax, IResponse::SameSiteStrict, IResponse::SameSiteNone) ->firstIsDefault(), ])->otherItems('mixed'); } @@ -64,16 +64,6 @@ public function loadConfiguration() $config->cookieDomain = $builder::literal('$this->getByType(Nette\Http\IRequest::class)->getUrl()->getDomain(2)'); } - if (isset($config->cookieSecure)) { - trigger_error("The item 'session\u{a0}›\u{a0}cookieSecure' is deprecated, use 'http\u{a0}›\u{a0}cookieSecure' (it has default value 'auto').", E_USER_DEPRECATED); - unset($config->cookieSecure); - } - - if ($config->cookieSamesite === true) { - trigger_error("In 'session\u{a0}›\u{a0}cookieSamesite' replace true with 'Lax'.", E_USER_DEPRECATED); - $config->cookieSamesite = IResponse::SameSiteLax; - } - $this->compiler->addExportedType(Nette\Http\IRequest::class); if ($this->debugMode && $config->debugger) { diff --git a/src/Http/Helpers.php b/src/Http/Helpers.php index 66f145b9..0ed24811 100644 --- a/src/Http/Helpers.php +++ b/src/Http/Helpers.php @@ -23,9 +23,6 @@ final class Helpers /** @internal */ public const StrictCookieName = '_nss'; - /** @deprecated */ - public const STRICT_COOKIE_NAME = self::StrictCookieName; - /** * Returns HTTP valid date format. diff --git a/src/Http/IResponse.php b/src/Http/IResponse.php index 61eaa5f1..4dd15367 100644 --- a/src/Http/IResponse.php +++ b/src/Http/IResponse.php @@ -16,12 +16,6 @@ */ interface IResponse { - /** @deprecated */ - public const PERMANENT = 2116333333; - - /** @deprecated */ - public const BROWSER = 0; - /** HTTP 1.1 response code */ public const S100_Continue = 100, diff --git a/src/Http/Request.php b/src/Http/Request.php index 22fe77be..d270cfee 100644 --- a/src/Http/Request.php +++ b/src/Http/Request.php @@ -100,8 +100,6 @@ public function getQuery(?string $key = null): mixed { if (func_num_args() === 0) { return $this->url->getQueryParameters(); - } elseif (func_num_args() > 1) { - trigger_error(__METHOD__ . '() parameter $default is deprecated, use operator ??', E_USER_DEPRECATED); } return $this->url->getQueryParameter($key); @@ -116,8 +114,6 @@ public function getPost(?string $key = null): mixed { if (func_num_args() === 0) { return $this->post; - } elseif (func_num_args() > 1) { - trigger_error(__METHOD__ . '() parameter $default is deprecated, use operator ??', E_USER_DEPRECATED); } return $this->post[$key] ?? null; @@ -149,10 +145,6 @@ public function getFiles(): array */ public function getCookie(string $key): mixed { - if (func_num_args() > 1) { - trigger_error(__METHOD__ . '() parameter $default is deprecated, use operator ??', E_USER_DEPRECATED); - } - return $this->cookies[$key] ?? null; } @@ -192,10 +184,6 @@ public function isMethod(string $method): bool */ public function getHeader(string $header): ?string { - if (func_num_args() > 1) { - trigger_error(__METHOD__ . '() parameter $default is deprecated, use operator ??', E_USER_DEPRECATED); - } - $header = strtolower($header); return $this->headers[$header] ?? null; } diff --git a/src/Http/Response.php b/src/Http/Response.php index c9c2585d..9989b17e 100644 --- a/src/Http/Response.php +++ b/src/Http/Response.php @@ -31,15 +31,9 @@ final class Response implements IResponse /** Whether the cookie is available only through HTTPS */ public bool $cookieSecure = false; - /** @deprecated */ - public $cookieHttpOnly; - /** Whether warn on possible problem with data in output buffer */ public bool $warnOnBuffer = true; - /** @deprecated */ - private static bool $fixIE = true; - /** HTTP response code */ private int $code = self::S200_OK; @@ -66,7 +60,7 @@ public function setCode(int $code, ?string $reason = null): static self::checkHeaders(); $this->code = $code; $protocol = $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.1'; - $reason = $reason ?? self::ReasonPhrases[$code] ?? 'Unknown status'; + $reason ??= self::ReasonPhrases[$code] ?? 'Unknown status'; header("$protocol $code $reason"); return $this; } @@ -201,10 +195,6 @@ public function isSent(): bool */ public function getHeader(string $header): ?string { - if (func_num_args() > 1) { - trigger_error(__METHOD__ . '() parameter $default is deprecated, use operator ??', E_USER_DEPRECATED); - } - $header .= ':'; $len = strlen($header); foreach (headers_list() as $item) { diff --git a/src/Http/Session.php b/src/Http/Session.php index 257e9365..4811326a 100644 --- a/src/Http/Session.php +++ b/src/Http/Session.php @@ -333,14 +333,6 @@ public function getSectionNames(): array } - /** @deprecated use getSectionNames() */ - public function getIterator(): \Iterator - { - trigger_error(__METHOD__ . '() is deprecated', E_USER_DEPRECATED); - return new \ArrayIterator($this->getSectionNames()); - } - - /** * Cleans and minimizes meta structures. */ @@ -510,14 +502,6 @@ public function setCookieParameters( } - /** @deprecated */ - public function getCookieParameters(): array - { - trigger_error(__METHOD__ . '() is deprecated.', E_USER_DEPRECATED); - return session_get_cookie_params(); - } - - /** * Sets path of the directory used to save session data. */ diff --git a/src/Http/Url.php b/src/Http/Url.php index bbaffc9e..278c3f2d 100644 --- a/src/Http/Url.php +++ b/src/Http/Url.php @@ -221,10 +221,6 @@ public function getQueryParameters(): array public function getQueryParameter(string $name): mixed { - if (func_num_args() > 1) { - trigger_error(__METHOD__ . '() parameter $default is deprecated, use operator ??', E_USER_DEPRECATED); - } - return $this->query[$name] ?? null; } @@ -332,7 +328,6 @@ public function isEqual(string|self|UrlImmutable $url): bool /** * Transforms URL to canonical form. - * @deprecated */ public function canonicalize(): static { diff --git a/src/Http/UserStorage.php b/src/Http/UserStorage.php deleted file mode 100644 index a557601a..00000000 --- a/src/Http/UserStorage.php +++ /dev/null @@ -1,179 +0,0 @@ -sessionHandler = $sessionHandler; - } - - - /** - * Sets the authenticated status of this user. - */ - public function setAuthenticated(bool $state): self - { - $section = $this->getSessionSection(true); - $section->authenticated = $state; - - // Session Fixation defence - $this->sessionHandler->regenerateId(); - - if ($state) { - $section->reason = null; - $section->authTime = time(); // informative value - - } else { - $section->reason = self::MANUAL; - $section->authTime = null; - } - - return $this; - } - - - /** - * Is this user authenticated? - */ - public function isAuthenticated(): bool - { - $session = $this->getSessionSection(false); - return $session && $session->authenticated; - } - - - /** - * Sets the user identity. - */ - public function setIdentity(?IIdentity $identity): self - { - $this->getSessionSection(true)->identity = $identity; - return $this; - } - - - /** - * Returns current user identity, if any. - */ - public function getIdentity(): ?Nette\Security\IIdentity - { - $session = $this->getSessionSection(false); - return $session ? $session->identity : null; - } - - - /** - * Changes namespace; allows more users to share a session. - */ - public function setNamespace(string $namespace): self - { - if ($this->namespace !== $namespace) { - $this->namespace = $namespace; - $this->sessionSection = null; - } - - return $this; - } - - - /** - * Returns current namespace. - */ - public function getNamespace(): string - { - return $this->namespace; - } - - - /** - * Enables log out after inactivity. Accepts flag IUserStorage::CLEAR_IDENTITY. - */ - public function setExpiration(?string $time, int $flags = 0): self - { - $section = $this->getSessionSection(true); - if ($time) { - $time = Nette\Utils\DateTime::from($time)->format('U'); - $section->expireTime = $time; - $section->expireDelta = $time - time(); - - } else { - unset($section->expireTime, $section->expireDelta); - } - - $section->expireIdentity = (bool) ($flags & self::CLEAR_IDENTITY); - $section->setExpiration($time, 'foo'); // time check - return $this; - } - - - /** - * Why was user logged out? - */ - public function getLogoutReason(): ?int - { - $session = $this->getSessionSection(false); - return $session ? $session->reason : null; - } - - - /** - * Returns and initializes $this->sessionSection. - */ - protected function getSessionSection(bool $need): ?SessionSection - { - if ($this->sessionSection !== null) { - return $this->sessionSection; - } - - if (!$need && !$this->sessionHandler->exists()) { - return null; - } - - $this->sessionSection = $section = $this->sessionHandler->getSection('Nette.Http.UserStorage/' . $this->namespace); - - if (!$section->identity instanceof IIdentity || !is_bool($section->authenticated)) { - $section->remove(); - } - - if ($section->authenticated && $section->expireDelta > 0) { // check time expiration - if ($section->expireTime < time()) { - $section->reason = self::INACTIVITY; - $section->authenticated = false; - if ($section->expireIdentity) { - unset($section->identity); - } - } - - $section->expireTime = time() + $section->expireDelta; // sliding expiration - } - - if (!$section->authenticated) { - unset($section->expireTime, $section->expireDelta, $section->expireIdentity, $section->authTime); - } - - return $this->sessionSection; - } -} From 377e739e9e5132d80b74c279981e7aea133bcdc4 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 29 Aug 2023 14:08:57 +0200 Subject: [PATCH 15/25] removed Nette\SmartObject --- src/Bridges/HttpTracy/SessionPanel.php | 2 -- src/Http/Context.php | 4 ---- src/Http/RequestFactory.php | 2 -- src/Http/Session.php | 2 -- src/Http/SessionSection.php | 2 -- 5 files changed, 12 deletions(-) diff --git a/src/Bridges/HttpTracy/SessionPanel.php b/src/Bridges/HttpTracy/SessionPanel.php index 8374e09f..b1590eec 100644 --- a/src/Bridges/HttpTracy/SessionPanel.php +++ b/src/Bridges/HttpTracy/SessionPanel.php @@ -18,8 +18,6 @@ */ class SessionPanel implements Tracy\IBarPanel { - use Nette\SmartObject; - /** * Renders tab. */ diff --git a/src/Http/Context.php b/src/Http/Context.php index 5ba90d71..d021b096 100644 --- a/src/Http/Context.php +++ b/src/Http/Context.php @@ -9,16 +9,12 @@ namespace Nette\Http; -use Nette; - /** * HTTP-specific tasks. */ class Context { - use Nette\SmartObject; - private IRequest $request; private IResponse $response; diff --git a/src/Http/RequestFactory.php b/src/Http/RequestFactory.php index 667a7667..dc141878 100644 --- a/src/Http/RequestFactory.php +++ b/src/Http/RequestFactory.php @@ -19,8 +19,6 @@ */ class RequestFactory { - use Nette\SmartObject; - /** @internal */ private const ValidChars = '\x09\x0A\x0D\x20-\x7E\xA0-\x{10FFFF}'; diff --git a/src/Http/Session.php b/src/Http/Session.php index 4811326a..ed82c560 100644 --- a/src/Http/Session.php +++ b/src/Http/Session.php @@ -17,8 +17,6 @@ */ class Session { - use Nette\SmartObject; - /** Default file lifetime */ private const DefaultFileLifetime = 3 * Nette\Utils\DateTime::HOUR; diff --git a/src/Http/SessionSection.php b/src/Http/SessionSection.php index 06b8572a..1bce6fd3 100644 --- a/src/Http/SessionSection.php +++ b/src/Http/SessionSection.php @@ -17,8 +17,6 @@ */ class SessionSection implements \IteratorAggregate, \ArrayAccess { - use Nette\SmartObject; - public bool $warnOnUndefined = false; private Session $session; private string $name; From d100d25e56729e0a0cc38a9230fb5b5e886ec723 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 26 Sep 2023 02:09:09 +0200 Subject: [PATCH 16/25] used native PHP 8.1 features --- src/Bridges/HttpDI/HttpExtension.php | 2 +- src/Http/Context.php | 2 +- src/Http/Helpers.php | 2 +- src/Http/RequestFactory.php | 8 ++++---- src/Http/Response.php | 2 +- src/Http/Url.php | 4 ++-- src/Http/UrlImmutable.php | 2 +- src/Http/UrlScript.php | 4 ++-- tests/Http/Request.detectLanguage.phpt | 8 ++++---- tests/Http/Request.getOrigin.phpt | 4 ++-- tests/Http/Request.getRawBody.phpt | 2 +- tests/Http/Request.headers.phpt | 4 ++-- tests/Http/Request.invalidType.phpt | 4 ++-- 13 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/Bridges/HttpDI/HttpExtension.php b/src/Bridges/HttpDI/HttpExtension.php index 040ca2b9..c419f40e 100644 --- a/src/Bridges/HttpDI/HttpExtension.php +++ b/src/Bridges/HttpDI/HttpExtension.php @@ -114,7 +114,7 @@ private function sendHeaders() } $value = self::buildPolicy($config->$key); - if (strpos($value, "'nonce'")) { + if (str_contains($value, "'nonce'")) { $this->initialization->addBody('$cspNonce = base64_encode(random_bytes(16));'); $value = Nette\DI\ContainerBuilder::literal( 'str_replace(?, ? . $cspNonce, ?)', diff --git a/src/Http/Context.php b/src/Http/Context.php index d021b096..fdbc27a5 100644 --- a/src/Http/Context.php +++ b/src/Http/Context.php @@ -46,7 +46,7 @@ public function isModified(string|int|\DateTimeInterface|null $lastModified = nu } elseif ($ifNoneMatch !== null) { $etag = $this->response->getHeader('ETag'); - if ($etag === null || strpos(' ' . strtr($ifNoneMatch, ",\t", ' '), ' ' . $etag) === false) { + if ($etag === null || !str_contains(' ' . strtr($ifNoneMatch, ",\t", ' '), ' ' . $etag)) { return true; } else { diff --git a/src/Http/Helpers.php b/src/Http/Helpers.php index 0ed24811..807c8d64 100644 --- a/src/Http/Helpers.php +++ b/src/Http/Helpers.php @@ -54,6 +54,6 @@ public static function ipMatch(string $ip, string $mask): bool public static function initCookie(IRequest $request, IResponse $response) { - $response->setCookie(self::StrictCookieName, '1', 0, '/', null, null, true, IResponse::SameSiteStrict); + $response->setCookie(self::StrictCookieName, '1', 0, '/', sameSite: IResponse::SameSiteStrict); } } diff --git a/src/Http/RequestFactory.php b/src/Http/RequestFactory.php index dc141878..5ecff489 100644 --- a/src/Http/RequestFactory.php +++ b/src/Http/RequestFactory.php @@ -157,7 +157,7 @@ private function getGetPostCookie(Url $url): array $list[$key][$k] = (string) preg_replace('#[^' . self::ValidChars . ']+#u', '', $v); } else { - throw new Nette\InvalidStateException(sprintf('Invalid value in $_POST/$_COOKIE in key %s, expected string, %s given.', "'$k'", gettype($v))); + throw new Nette\InvalidStateException(sprintf('Invalid value in $_POST/$_COOKIE in key %s, expected string, %s given.', "'$k'", get_debug_type($v))); } } } @@ -299,9 +299,9 @@ private function useForwardedProxy(Url $url): ?string if (isset($proxyParams['for'])) { $address = $proxyParams['for'][0]; - $remoteAddr = strpos($address, '[') === false - ? explode(':', $address)[0] // IPv4 - : substr($address, 1, strpos($address, ']') - 1); // IPv6 + $remoteAddr = str_contains($address, '[') + ? substr($address, 1, strpos($address, ']') - 1) // IPv6 + : explode(':', $address)[0]; // IPv4 } if (isset($proxyParams['proto']) && count($proxyParams['proto']) === 1) { diff --git a/src/Http/Response.php b/src/Http/Response.php index 9989b17e..86de0b4c 100644 --- a/src/Http/Response.php +++ b/src/Http/Response.php @@ -215,7 +215,7 @@ public function getHeaders(): array $headers = []; foreach (headers_list() as $header) { $a = strpos($header, ':'); - $headers[substr($header, 0, $a)] = (string) substr($header, $a + 2); + $headers[substr($header, 0, $a)] = substr($header, $a + 2); } return $headers; diff --git a/src/Http/Url.php b/src/Http/Url.php index 278c3f2d..162530ec 100644 --- a/src/Http/Url.php +++ b/src/Http/Url.php @@ -177,7 +177,7 @@ public function getDefaultPort(): ?int public function setPath(string $path): static { $this->path = $path; - if ($this->host && substr($this->path, 0, 1) !== '/') { + if ($this->host && !str_starts_with($this->path, '/')) { $this->path = '/' . $this->path; } @@ -366,7 +366,7 @@ final public function export(): array */ private static function idnHostToUnicode(string $host): string { - if (strpos($host, '--') === false) { // host does not contain IDN + if (!str_contains($host, '--')) { // host does not contain IDN return $host; } diff --git a/src/Http/UrlImmutable.php b/src/Http/UrlImmutable.php index e97f2139..ea9e85b8 100644 --- a/src/Http/UrlImmutable.php +++ b/src/Http/UrlImmutable.php @@ -288,7 +288,7 @@ final public function export(): array protected function build(): void { - if ($this->host && substr($this->path, 0, 1) !== '/') { + if ($this->host && !str_starts_with($this->path, '/')) { $this->path = '/' . $this->path; } diff --git a/src/Http/UrlScript.php b/src/Http/UrlScript.php index 7b02ea73..9de766d6 100644 --- a/src/Http/UrlScript.php +++ b/src/Http/UrlScript.php @@ -50,7 +50,7 @@ public function withPath(string $path, string $scriptPath = ''): static { $dolly = clone $this; $dolly->scriptPath = $scriptPath; - $parent = \Closure::fromCallable([UrlImmutable::class, 'withPath'])->bindTo($dolly); + $parent = UrlImmutable::withPath(...)->bindTo($dolly); return $parent($path); } @@ -90,7 +90,7 @@ public function getRelativeUrl(): string */ public function getPathInfo(): string { - return (string) substr($this->getPath(), strlen($this->scriptPath)); + return substr($this->getPath(), strlen($this->scriptPath)); } diff --git a/tests/Http/Request.detectLanguage.phpt b/tests/Http/Request.detectLanguage.phpt index 0441d469..c1dea634 100644 --- a/tests/Http/Request.detectLanguage.phpt +++ b/tests/Http/Request.detectLanguage.phpt @@ -14,7 +14,7 @@ require __DIR__ . '/../bootstrap.php'; test('', function () { $headers = ['Accept-Language' => 'en, cs']; - $request = new Http\Request(new Http\UrlScript, null, null, null, $headers); + $request = new Http\Request(new Http\UrlScript, headers: $headers); Assert::same('en', $request->detectLanguage(['en', 'cs'])); Assert::same('en', $request->detectLanguage(['cs', 'en'])); @@ -24,7 +24,7 @@ test('', function () { test('', function () { $headers = ['Accept-Language' => 'da, en-gb;q=0.8, en;q=0.7']; - $request = new Http\Request(new Http\UrlScript, null, null, null, $headers); + $request = new Http\Request(new Http\UrlScript, headers: $headers); Assert::same('en-gb', $request->detectLanguage(['en', 'en-gb'])); Assert::same('en', $request->detectLanguage(['en'])); @@ -33,7 +33,7 @@ test('', function () { test('', function () { $headers = []; - $request = new Http\Request(new Http\UrlScript, null, null, null, $headers); + $request = new Http\Request(new Http\UrlScript, headers: $headers); Assert::null($request->detectLanguage(['en'])); }); @@ -41,7 +41,7 @@ test('', function () { test('', function () { $headers = ['Accept-Language' => 'garbage']; - $request = new Http\Request(new Http\UrlScript, null, null, null, $headers); + $request = new Http\Request(new Http\UrlScript, headers: $headers); Assert::null($request->detectLanguage(['en'])); }); diff --git a/tests/Http/Request.getOrigin.phpt b/tests/Http/Request.getOrigin.phpt index 380e12d6..febebb89 100644 --- a/tests/Http/Request.getOrigin.phpt +++ b/tests/Http/Request.getOrigin.phpt @@ -16,7 +16,7 @@ test('missing origin', function () { test('opaque origin', function () { - $request = new Http\Request(new Http\UrlScript, null, null, null, [ + $request = new Http\Request(new Http\UrlScript, headers: [ 'Origin' => 'null', ]); Assert::null($request->getOrigin()); @@ -24,7 +24,7 @@ test('opaque origin', function () { test('normal origin', function () { - $request = new Http\Request(new Http\UrlScript, null, null, null, [ + $request = new Http\Request(new Http\UrlScript, headers: [ 'Origin' => 'https://nette.org', ]); Assert::equal(new UrlImmutable('https://nette.org'), $request->getOrigin()); diff --git a/tests/Http/Request.getRawBody.phpt b/tests/Http/Request.getRawBody.phpt index 13f09d72..9d2f556f 100644 --- a/tests/Http/Request.getRawBody.phpt +++ b/tests/Http/Request.getRawBody.phpt @@ -13,7 +13,7 @@ require __DIR__ . '/../bootstrap.php'; test('', function () { - $request = new Http\Request(new Http\UrlScript, null, null, null, null, null, null, null, fn() => 'raw body'); + $request = new Http\Request(new Http\UrlScript, rawBodyCallback: fn() => 'raw body'); Assert::same('raw body', $request->getRawBody()); }); diff --git a/tests/Http/Request.headers.phpt b/tests/Http/Request.headers.phpt index 947d76ee..7c607151 100644 --- a/tests/Http/Request.headers.phpt +++ b/tests/Http/Request.headers.phpt @@ -18,12 +18,12 @@ test('', function () { }); test('', function () { - $request = new Http\Request(new Http\UrlScript, null, null, null, []); + $request = new Http\Request(new Http\UrlScript); Assert::same([], $request->getHeaders()); }); test('', function () { - $request = new Http\Request(new Http\UrlScript, null, null, null, [ + $request = new Http\Request(new Http\UrlScript, headers: [ 'one' => '1', 'TWO' => '2', 'X-Header' => 'X', diff --git a/tests/Http/Request.invalidType.phpt b/tests/Http/Request.invalidType.phpt index f3d328fa..68875dca 100644 --- a/tests/Http/Request.invalidType.phpt +++ b/tests/Http/Request.invalidType.phpt @@ -20,7 +20,7 @@ test('invalid POST', function () { Assert::exception( fn() => (new Http\RequestFactory)->fromGlobals(), Nette\InvalidStateException::class, - 'Invalid value in $_POST/$_COOKIE in key \'int\', expected string, integer given.', + 'Invalid value in $_POST/$_COOKIE in key \'int\', expected string, int given.', ); }); @@ -32,6 +32,6 @@ test('invalid COOKIE', function () { Assert::exception( fn() => (new Http\RequestFactory)->fromGlobals(), Nette\InvalidStateException::class, - 'Invalid value in $_POST/$_COOKIE in key \'0\', expected string, integer given.', + 'Invalid value in $_POST/$_COOKIE in key \'0\', expected string, int given.', ); }); From 578854032dbf4477499d84f7be35f79335de89ca Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sat, 4 Sep 2021 13:02:37 +0200 Subject: [PATCH 17/25] SessionSection: removed $warnOnUndefined (BC break) --- src/Http/SessionSection.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Http/SessionSection.php b/src/Http/SessionSection.php index 1bce6fd3..a8542dae 100644 --- a/src/Http/SessionSection.php +++ b/src/Http/SessionSection.php @@ -17,7 +17,6 @@ */ class SessionSection implements \IteratorAggregate, \ArrayAccess { - public bool $warnOnUndefined = false; private Session $session; private string $name; @@ -112,10 +111,6 @@ public function &__get(string $name): mixed { $this->session->autoStart(true); $data = &$this->getData(); - if ($this->warnOnUndefined && !array_key_exists($name, $data ?? [])) { - trigger_error("The variable '$name' does not exist in session section"); - } - return $data[$name]; } From 9a03ebc9c4ebfa590e1cfcbcf6f7ec793b118bb7 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 3 Sep 2021 19:16:30 +0200 Subject: [PATCH 18/25] Session::clean() clears all null values from the session --- src/Http/Session.php | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/Http/Session.php b/src/Http/Session.php index ed82c560..539e8330 100644 --- a/src/Http/Session.php +++ b/src/Http/Session.php @@ -343,7 +343,19 @@ private function clean(): void Nette\Utils\Arrays::invoke($this->onBeforeWrite, $this); $nf = &$_SESSION['__NF']; - foreach ($nf['META'] ?? [] as $name => $foo) { + foreach ($nf['DATA'] ?? [] as $name => $data) { + foreach ($data ?? [] as $k => $v) { + if ($v === null) { + unset($nf['DATA'][$name][$k], $nf['META'][$name][$k]); + } + } + + if (empty($nf['DATA'][$name])) { + unset($nf['DATA'][$name], $nf['META'][$name]); + } + } + + foreach ($nf['META'] ?? [] as $name => $data) { if (empty($nf['META'][$name])) { unset($nf['META'][$name]); } From af0ae13ab5fbd8e9239a8d20d50385f9ff8f89fc Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 27 Nov 2022 21:36:23 +0100 Subject: [PATCH 19/25] some methods use real default values instead of nulls (BC break) --- src/Http/IResponse.php | 6 +++--- src/Http/Request.php | 20 ++++++++++---------- src/Http/Response.php | 8 ++++---- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/Http/IResponse.php b/src/Http/IResponse.php index 4dd15367..da799f4b 100644 --- a/src/Http/IResponse.php +++ b/src/Http/IResponse.php @@ -394,8 +394,8 @@ function setCookie( ?int $expire, ?string $path = null, ?string $domain = null, - ?bool $secure = null, - ?bool $httpOnly = null, + bool $secure = false, + bool $httpOnly = true, ): static; /** @@ -405,6 +405,6 @@ function deleteCookie( string $name, ?string $path = null, ?string $domain = null, - ?bool $secure = null, + bool $secure = false, ); } diff --git a/src/Http/Request.php b/src/Http/Request.php index d270cfee..b80dae7b 100644 --- a/src/Http/Request.php +++ b/src/Http/Request.php @@ -48,21 +48,21 @@ class Request implements IRequest public function __construct( UrlScript $url, - ?array $post = null, - ?array $files = null, - ?array $cookies = null, - ?array $headers = null, - ?string $method = null, + array $post = [], + array $files = [], + array $cookies = [], + array $headers = [], + string $method = 'GET', ?string $remoteAddress = null, ?string $remoteHost = null, ?callable $rawBodyCallback = null, ) { $this->url = $url; - $this->post = (array) $post; - $this->files = (array) $files; - $this->cookies = (array) $cookies; - $this->headers = array_change_key_case((array) $headers, CASE_LOWER); - $this->method = $method ?: 'GET'; + $this->post = $post; + $this->files = $files; + $this->cookies = $cookies; + $this->headers = array_change_key_case($headers, CASE_LOWER); + $this->method = $method; $this->remoteAddress = $remoteAddress; $this->remoteHost = $remoteHost; $this->rawBodyCallback = $rawBodyCallback; diff --git a/src/Http/Response.php b/src/Http/Response.php index 86de0b4c..65725da5 100644 --- a/src/Http/Response.php +++ b/src/Http/Response.php @@ -233,8 +233,8 @@ public function setCookie( ?string $path = null, ?string $domain = null, ?bool $secure = null, - ?bool $httpOnly = null, - ?string $sameSite = null, + bool $httpOnly = true, + string $sameSite = self::SameSiteLax, ): static { self::checkHeaders(); @@ -243,8 +243,8 @@ public function setCookie( 'path' => $path ?? ($domain ? '/' : $this->cookiePath), 'domain' => $domain ?? ($path ? '' : $this->cookieDomain), 'secure' => $secure ?? $this->cookieSecure, - 'httponly' => $httpOnly ?? true, - 'samesite' => $sameSite ?? self::SameSiteLax, + 'httponly' => $httpOnly, + 'samesite' => $sameSite, ]); return $this; } From bd99d4fea93a436a371dd9a9178ddc6796adf45a Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 1 Mar 2022 16:10:31 +0100 Subject: [PATCH 20/25] used constructor promotion --- src/Http/Request.php | 28 +++++++--------------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/src/Http/Request.php b/src/Http/Request.php index b80dae7b..553db3d1 100644 --- a/src/Http/Request.php +++ b/src/Http/Request.php @@ -33,38 +33,24 @@ class Request implements IRequest { use Nette\SmartObject; - private string $method; - private UrlScript $url; - private array $post; - private array $files; - private array $cookies; private array $headers; - private ?string $remoteAddress; - private ?string $remoteHost; /** @var ?callable */ private $rawBodyCallback; public function __construct( - UrlScript $url, - array $post = [], - array $files = [], - array $cookies = [], + private UrlScript $url, + private array $post = [], + private array $files = [], + private array $cookies = [], array $headers = [], - string $method = 'GET', - ?string $remoteAddress = null, - ?string $remoteHost = null, + private string $method = 'GET', + private ?string $remoteAddress = null, + private ?string $remoteHost = null, ?callable $rawBodyCallback = null, ) { - $this->url = $url; - $this->post = $post; - $this->files = $files; - $this->cookies = $cookies; $this->headers = array_change_key_case($headers, CASE_LOWER); - $this->method = $method; - $this->remoteAddress = $remoteAddress; - $this->remoteHost = $remoteHost; $this->rawBodyCallback = $rawBodyCallback; } From ebe3ea691a898124937644e2631cb6c19c072692 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 1 Mar 2022 16:21:19 +0100 Subject: [PATCH 21/25] unification of Response and IResponse params (BC break) --- src/Http/IResponse.php | 9 +++++++-- src/Http/Response.php | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Http/IResponse.php b/src/Http/IResponse.php index da799f4b..e92160ce 100644 --- a/src/Http/IResponse.php +++ b/src/Http/IResponse.php @@ -12,7 +12,6 @@ /** * HTTP response interface. - * @method self deleteHeader(string $name) */ interface IResponse { @@ -355,6 +354,11 @@ function setHeader(string $name, string $value): static; */ function addHeader(string $name, string $value): static; + /** + * Deletes a previously sent HTTP header. + */ + function deleteHeader(string $name): static; + /** * Sends a Content-type HTTP header. */ @@ -391,11 +395,12 @@ function getHeaders(): array; function setCookie( string $name, string $value, - ?int $expire, + string|int|null $expire, ?string $path = null, ?string $domain = null, bool $secure = false, bool $httpOnly = true, + string $sameSite = self::SAME_SITE_LAX, ): static; /** diff --git a/src/Http/Response.php b/src/Http/Response.php index 65725da5..56bca4e7 100644 --- a/src/Http/Response.php +++ b/src/Http/Response.php @@ -229,7 +229,7 @@ public function getHeaders(): array public function setCookie( string $name, string $value, - string|int|\DateTimeInterface|null $expire, + string|int|null $expire, ?string $path = null, ?string $domain = null, ?bool $secure = null, From a0015b480f7911a6a655e036ccaf28aed2d17b35 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 24 Jan 2023 03:13:31 +0100 Subject: [PATCH 22/25] tests: uses new session api --- tests/Http/Session.handler.phpt | 4 +-- tests/Http/Session.id.phpt | 2 +- ...regenerate-empty-session-readAndClose.phpt | 2 +- .../Session.regenerate-empty-session.phpt | 2 +- .../Session.restart-after-regenerate.phpt | 4 +-- tests/Http/Session.sections.phpt | 4 +-- tests/Http/SessionSection.basic.phpt | 23 ++++++--------- tests/Http/SessionSection.remove.phpt | 28 ------------------- .../SessionSection.removeExpiration().phpt | 4 +-- tests/Http/SessionSection.separated.phpt | 28 ++++++++++--------- .../Http/SessionSection.setExpiration().phpt | 12 ++++---- tests/Http/SessionSection.undefined.phpt | 20 ------------- tests/Http/Url.httpScheme.phpt | 3 -- 13 files changed, 40 insertions(+), 96 deletions(-) delete mode 100644 tests/Http/SessionSection.remove.phpt delete mode 100644 tests/Http/SessionSection.undefined.phpt diff --git a/tests/Http/Session.handler.phpt b/tests/Http/Session.handler.phpt index 5f979703..2f78a6e6 100644 --- a/tests/Http/Session.handler.phpt +++ b/tests/Http/Session.handler.phpt @@ -73,10 +73,10 @@ $session->setHandler(new MySessionStorage); $session->start(); $namespace = $session->getSection('one'); -$namespace->a = 'apple'; +$namespace->set('a', 'apple'); $session->close(); unset($_SESSION); $session->start(); $namespace = $session->getSection('one'); -Assert::same('apple', $namespace->a); +Assert::same('apple', $namespace->get('a')); diff --git a/tests/Http/Session.id.phpt b/tests/Http/Session.id.phpt index 90faf27c..89cb2363 100644 --- a/tests/Http/Session.id.phpt +++ b/tests/Http/Session.id.phpt @@ -25,7 +25,7 @@ $session->start(); Assert::same($sessionId, $session->getId()); Assert::same([$sessionName => $leet], $_COOKIE); -Assert::same('yes', $session->getSection('temp')->value); +Assert::same('yes', $session->getSection('temp')->get('value')); $session->close(); // session was not regenerated diff --git a/tests/Http/Session.regenerate-empty-session-readAndClose.phpt b/tests/Http/Session.regenerate-empty-session-readAndClose.phpt index 3587b2ae..65365584 100644 --- a/tests/Http/Session.regenerate-empty-session-readAndClose.phpt +++ b/tests/Http/Session.regenerate-empty-session-readAndClose.phpt @@ -20,7 +20,7 @@ file_put_contents(getTempDir() . '/sess_' . $sessionId, '__NF|a:1:{s:4:"DATA";a: $session = new Session(new Http\Request(new Http\UrlScript('http://nette.org'), [], [], $cookies), new Http\Response); $session->setOptions(['readAndClose' => true]); $session->start(); -Assert::same('yes', $session->getSection('temp')->value); +Assert::same('yes', $session->getSection('temp')->get('value')); // session was not regenerated Assert::same($session->getId(), $sessionId); diff --git a/tests/Http/Session.regenerate-empty-session.phpt b/tests/Http/Session.regenerate-empty-session.phpt index b558216c..f46afe2f 100644 --- a/tests/Http/Session.regenerate-empty-session.phpt +++ b/tests/Http/Session.regenerate-empty-session.phpt @@ -19,7 +19,7 @@ file_put_contents(getTempDir() . '/sess_' . $sessionId, '__NF|a:1:{s:4:"DATA";a: $session = new Session(new Http\Request(new Http\UrlScript('http://nette.org'), [], [], $cookies), new Http\Response); $session->start(); -Assert::same('yes', $session->getSection('temp')->value); +Assert::same('yes', $session->getSection('temp')->get('value')); $newSessionId = $session->getId(); $session->close(); diff --git a/tests/Http/Session.restart-after-regenerate.phpt b/tests/Http/Session.restart-after-regenerate.phpt index 86a8122f..5164a587 100644 --- a/tests/Http/Session.restart-after-regenerate.phpt +++ b/tests/Http/Session.restart-after-regenerate.phpt @@ -18,7 +18,7 @@ $session = new Http\Session(new Http\Request(new Http\UrlScript, [], [], $cookie $session->start(); Assert::same($sessionId, $session->getId()); -Assert::same('yes', $session->getSection('temp')->value); +Assert::same('yes', $session->getSection('temp')->get('value')); $session->regenerateId(); Assert::notSame($sessionId, $session->getId()); @@ -26,7 +26,7 @@ Assert::same(session_id(), $session->getId()); $session->close(); $session->start(); -Assert::same('yes', $session->getSection('temp')->value); +Assert::same('yes', $session->getSection('temp')->get('value')); Assert::true(file_exists(getTempDir() . '/sess_' . $session->getId())); Assert::count(1, glob(getTempDir() . '/sess_*')); diff --git a/tests/Http/Session.sections.phpt b/tests/Http/Session.sections.phpt index 2e004c2f..45eab766 100644 --- a/tests/Http/Session.sections.phpt +++ b/tests/Http/Session.sections.phpt @@ -22,11 +22,11 @@ $section = $session->getSection('trees'); Assert::type(Nette\Http\SessionSection::class, $section); Assert::false($session->hasSection('trees')); // hasSection() should have returned false for a section with no keys set -$section->hello = 'world'; +$section->set('hello', 'world'); Assert::true($session->hasSection('trees')); // hasSection() should have returned true for a section with keys set $section = $session->getSection('default'); Assert::same(['trees'], $session->getSectionNames()); -$section->hello = 'world'; +$section->set('hello', 'world'); Assert::same(['trees', 'default'], $session->getSectionNames()); diff --git a/tests/Http/SessionSection.basic.phpt b/tests/Http/SessionSection.basic.phpt index 5f056fc0..c33afc3e 100644 --- a/tests/Http/SessionSection.basic.phpt +++ b/tests/Http/SessionSection.basic.phpt @@ -15,13 +15,11 @@ require __DIR__ . '/../bootstrap.php'; $session = new Session(new Nette\Http\Request(new Nette\Http\UrlScript), new Nette\Http\Response); $namespace = $session->getSection('one'); -$namespace->a = 'apple'; +$namespace->set('a', 'apple'); $namespace->set('p', 'pear'); -$namespace['o'] = 'orange'; - -Assert::same('apple', $namespace->a); +$namespace->set('o', 'orange'); Assert::same('pear', $namespace->get('p')); -Assert::same('orange', $namespace['o']); +Assert::null($namespace->get('undefined')); foreach ($namespace as $key => $val) { $tmp[] = "$key=$val"; @@ -33,16 +31,11 @@ Assert::same([ 'o=orange', ], $tmp); +$namespace->remove('a'); +Assert::same('p=pear&o=orange', http_build_query(iterator_to_array($namespace->getIterator()))); -Assert::true(isset($namespace['p'])); -Assert::true(isset($namespace->o)); -Assert::false(isset($namespace->undefined)); - -unset($namespace['a'], $namespace->o, $namespace->undef); -$namespace->remove('p'); -$namespace->remove(['x']); - - - +$namespace->remove(['x', 'p']); +Assert::same('o=orange', http_build_query(iterator_to_array($namespace->getIterator()))); +$namespace->remove(); Assert::same('', http_build_query(iterator_to_array($namespace->getIterator()))); diff --git a/tests/Http/SessionSection.remove.phpt b/tests/Http/SessionSection.remove.phpt deleted file mode 100644 index cf1349f6..00000000 --- a/tests/Http/SessionSection.remove.phpt +++ /dev/null @@ -1,28 +0,0 @@ -getSection('three'); -$namespace->a = 'apple'; -$namespace->p = 'papaya'; -$namespace['c'] = 'cherry'; - -$namespace = $session->getSection('three'); -Assert::same('a=apple&p=papaya&c=cherry', http_build_query(iterator_to_array($namespace->getIterator()))); - - -// removing -$namespace->remove(); -Assert::same('', http_build_query(iterator_to_array($namespace->getIterator()))); diff --git a/tests/Http/SessionSection.removeExpiration().phpt b/tests/Http/SessionSection.removeExpiration().phpt index af90c54f..630c348c 100644 --- a/tests/Http/SessionSection.removeExpiration().phpt +++ b/tests/Http/SessionSection.removeExpiration().phpt @@ -15,8 +15,8 @@ $session = new Session(new Nette\Http\Request(new Nette\Http\UrlScript), new Net $session->setExpiration('+10 seconds'); $section = $session->getSection('expireRemove'); -$section->a = 'apple'; -$section->b = 'banana'; +$section->set('a', 'apple'); +$section->set('b', 'banana'); $section->setExpiration('+2 seconds', 'a'); $section->removeExpiration('a'); diff --git a/tests/Http/SessionSection.separated.phpt b/tests/Http/SessionSection.separated.phpt index 26e43c8c..f2646962 100644 --- a/tests/Http/SessionSection.separated.phpt +++ b/tests/Http/SessionSection.separated.phpt @@ -14,16 +14,18 @@ require __DIR__ . '/../bootstrap.php'; $session = new Session(new Nette\Http\Request(new Nette\Http\UrlScript), new Nette\Http\Response); -$namespace1 = $session->getSection('namespace1'); -$namespace1b = $session->getSection('namespace1'); -$namespace2 = $session->getSection('namespace2'); -$namespace2b = $session->getSection('namespace2'); -$namespace3 = $session->getSection('default'); -$namespace3b = $session->getSection('default'); -$namespace1->a = 'apple'; -$namespace2->a = 'pear'; -$namespace3->a = 'orange'; -Assert::true($namespace1->a !== $namespace2->a && $namespace1->a !== $namespace3->a && $namespace2->a !== $namespace3->a); -Assert::same($namespace1->a, $namespace1b->a); -Assert::same($namespace2->a, $namespace2b->a); -Assert::same($namespace3->a, $namespace3b->a); +$section1 = $session->getSection('namespace1'); +$section1b = $session->getSection('namespace1'); +$section2 = $session->getSection('namespace2'); +$section2b = $session->getSection('namespace2'); +$section3 = $session->getSection('default'); +$section3b = $session->getSection('default'); +$section1->set('a', 'apple'); +$section2->set('a', 'pear'); +$section3->set('a', 'orange'); +Assert::same('apple', $section1->get('a')); +Assert::same('apple', $section1b->get('a')); +Assert::same('pear', $section2->get('a')); +Assert::same('pear', $section2b->get('a')); +Assert::same('orange', $section3->get('a')); +Assert::same('orange', $section3b->get('a')); diff --git a/tests/Http/SessionSection.setExpiration().phpt b/tests/Http/SessionSection.setExpiration().phpt index 44fa99e6..0328ad3b 100644 --- a/tests/Http/SessionSection.setExpiration().phpt +++ b/tests/Http/SessionSection.setExpiration().phpt @@ -18,9 +18,8 @@ $session->setExpiration('+10 seconds'); test('try to expire whole namespace', function () use ($session) { $namespace = $session->getSection('expire'); - $namespace->a = 'apple'; - $namespace->p = 'pear'; - $namespace['o'] = 'orange'; + $namespace->set('a', 'apple'); + $namespace->set('p', 'pear'); $namespace->setExpiration('+ 1 seconds'); $session->close(); @@ -35,8 +34,9 @@ test('try to expire whole namespace', function () use ($session) { test('try to expire only 1 of the keys', function () use ($session) { $namespace = $session->getSection('expireSingle'); $namespace->setExpiration('1 second', 'g'); - $namespace->g = 'guava'; - $namespace->p = 'plum'; + $namespace->set('g', 'guava'); + $namespace->set('p', 'plum'); + $namespace->setExpiration('1 second', 'p'); $namespace->set('a', 'apple', '1 second'); $session->close(); @@ -44,7 +44,7 @@ test('try to expire only 1 of the keys', function () use ($session) { $session->start(); $namespace = $session->getSection('expireSingle'); - Assert::same('p=plum', http_build_query(iterator_to_array($namespace->getIterator()))); + Assert::same('g=guava', http_build_query(iterator_to_array($namespace->getIterator()))); }); diff --git a/tests/Http/SessionSection.undefined.phpt b/tests/Http/SessionSection.undefined.phpt deleted file mode 100644 index 6eb5577c..00000000 --- a/tests/Http/SessionSection.undefined.phpt +++ /dev/null @@ -1,20 +0,0 @@ -getSection('one'); -Assert::false(isset($namespace->undefined)); -Assert::null($namespace->undefined); // Getting value of non-existent key -Assert::same('', http_build_query(iterator_to_array($namespace->getIterator()))); diff --git a/tests/Http/Url.httpScheme.phpt b/tests/Http/Url.httpScheme.phpt index aff3c01b..1ee32b68 100644 --- a/tests/Http/Url.httpScheme.phpt +++ b/tests/Http/Url.httpScheme.phpt @@ -23,14 +23,11 @@ Assert::same('hostname', $url->host); Assert::same(60, $url->port); Assert::same(80, $url->getDefaultPort()); Assert::same('/p%61th/script.php', $url->path); -Assert::same('/p%61th/', $url->basePath); Assert::same('arg=value', $url->query); Assert::same('anchor', $url->fragment); Assert::same('username%3A:password%3A@hostname:60', $url->authority); Assert::same('http://username%3A:password%3A@hostname:60', $url->hostUrl); Assert::same('http://username%3A:password%3A@hostname:60/p%61th/script.php?arg=value#anchor', $url->absoluteUrl); -Assert::same('http://username%3A:password%3A@hostname:60/p%61th/', $url->baseUrl); -Assert::same('script.php?arg=value#anchor', $url->relativeUrl); $url->setPath(''); Assert::same('/', $url->getPath()); From ace52339f2e4da861e6187275b6e9ea8cfd94e1c Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 30 Nov 2022 18:14:42 +0100 Subject: [PATCH 23/25] silently deprecated methods trigger E_USER_DEPRECATED --- src/Http/FileUpload.php | 1 + src/Http/Request.php | 1 + src/Http/RequestFactory.php | 3 ++- src/Http/SessionSection.php | 8 ++++++++ src/Http/Url.php | 3 +++ tests/Http/FileUpload.basic.phpt | 4 ++-- tests/Http/Request.invalidEncoding.phpt | 2 +- tests/Http/RequestFactory.proxy.forwarded.phpt | 10 +++++----- tests/Http/RequestFactory.proxy.x-forwarded.phpt | 8 ++++---- 9 files changed, 27 insertions(+), 13 deletions(-) diff --git a/src/Http/FileUpload.php b/src/Http/FileUpload.php index 5dd972d5..8513adc7 100644 --- a/src/Http/FileUpload.php +++ b/src/Http/FileUpload.php @@ -63,6 +63,7 @@ public function __construct(?array $value) */ public function getName(): string { + trigger_error(__METHOD__ . '() is deprecated, use getUntrustedName()', E_USER_DEPRECATED); return $this->name; } diff --git a/src/Http/Request.php b/src/Http/Request.php index 553db3d1..9000990e 100644 --- a/src/Http/Request.php +++ b/src/Http/Request.php @@ -190,6 +190,7 @@ public function getHeaders(): array */ public function getReferer(): ?UrlImmutable { + trigger_error(__METHOD__ . '() is deprecated', E_USER_DEPRECATED); return isset($this->headers['referer']) ? new UrlImmutable($this->headers['referer']) : null; diff --git a/src/Http/RequestFactory.php b/src/Http/RequestFactory.php index 5ecff489..56ad04bb 100644 --- a/src/Http/RequestFactory.php +++ b/src/Http/RequestFactory.php @@ -365,9 +365,10 @@ private function useNonstandardProxy(Url $url): ?string } - /** @deprecated */ + /** @deprecated use fromGlobals() */ public function createHttpRequest(): Request { + trigger_error(__METHOD__ . '() is deprecated, use fromGlobals()', E_USER_DEPRECATED); return $this->fromGlobals(); } } diff --git a/src/Http/SessionSection.php b/src/Http/SessionSection.php index a8542dae..97f6c503 100644 --- a/src/Http/SessionSection.php +++ b/src/Http/SessionSection.php @@ -98,6 +98,7 @@ public function remove(string|array|null $name = null): void */ public function __set(string $name, $value): void { + trigger_error("Writing to \$session->$name is deprecated, use \$session->set('$name', \$value) instead", E_USER_DEPRECATED); $this->session->autoStart(true); $this->getData()[$name] = $value; } @@ -109,6 +110,7 @@ public function __set(string $name, $value): void */ public function &__get(string $name): mixed { + trigger_error("Reading from \$session->$name is deprecated, use \$session->get('$name') instead", E_USER_DEPRECATED); $this->session->autoStart(true); $data = &$this->getData(); return $data[$name]; @@ -121,6 +123,7 @@ public function &__get(string $name): mixed */ public function __isset(string $name): bool { + trigger_error("Using \$session->$name is deprecated, use \$session->get('$name') instead", E_USER_DEPRECATED); $this->session->autoStart(false); return isset($this->getData()[$name]); } @@ -132,6 +135,7 @@ public function __isset(string $name): bool */ public function __unset(string $name): void { + trigger_error("Unset(\$session->$name) is deprecated, use \$session->remove('$name') instead", E_USER_DEPRECATED); $this->remove($name); } @@ -142,6 +146,7 @@ public function __unset(string $name): void */ public function offsetSet($name, $value): void { + trigger_error("Writing to \$session['$name'] is deprecated, use \$session->set('$name', \$value) instead", E_USER_DEPRECATED); $this->__set($name, $value); } @@ -152,6 +157,7 @@ public function offsetSet($name, $value): void */ public function offsetGet($name): mixed { + trigger_error("Reading from \$session['$name'] is deprecated, use \$session->get('$name') instead", E_USER_DEPRECATED); return $this->get($name); } @@ -162,6 +168,7 @@ public function offsetGet($name): mixed */ public function offsetExists($name): bool { + trigger_error("Using \$session['$name'] is deprecated, use \$session->get('$name') instead", E_USER_DEPRECATED); return $this->__isset($name); } @@ -172,6 +179,7 @@ public function offsetExists($name): bool */ public function offsetUnset($name): void { + trigger_error("Unset(\$session['$name']) is deprecated, use \$session->remove('$name') instead", E_USER_DEPRECATED); $this->remove($name); } diff --git a/src/Http/Url.php b/src/Http/Url.php index 162530ec..2c8f2585 100644 --- a/src/Http/Url.php +++ b/src/Http/Url.php @@ -283,6 +283,7 @@ public function getHostUrl(): string /** @deprecated use UrlScript::getBasePath() instead */ public function getBasePath(): string { + trigger_error(__METHOD__ . '() is deprecated, use UrlScript object', E_USER_DEPRECATED); $pos = strrpos($this->path, '/'); return $pos === false ? '' : substr($this->path, 0, $pos + 1); } @@ -291,6 +292,7 @@ public function getBasePath(): string /** @deprecated use UrlScript::getBaseUrl() instead */ public function getBaseUrl(): string { + trigger_error(__METHOD__ . '() is deprecated, use UrlScript object', E_USER_DEPRECATED); return $this->getHostUrl() . $this->getBasePath(); } @@ -298,6 +300,7 @@ public function getBaseUrl(): string /** @deprecated use UrlScript::getRelativeUrl() instead */ public function getRelativeUrl(): string { + trigger_error(__METHOD__ . '() is deprecated, use UrlScript object', E_USER_DEPRECATED); return substr($this->getAbsoluteUrl(), strlen($this->getBaseUrl())); } diff --git a/tests/Http/FileUpload.basic.phpt b/tests/Http/FileUpload.basic.phpt index 573568af..abb7098e 100644 --- a/tests/Http/FileUpload.basic.phpt +++ b/tests/Http/FileUpload.basic.phpt @@ -22,7 +22,7 @@ test('', function () { 'size' => 209, ]); - Assert::same('readme.txt', $upload->getName()); + Assert::same('readme.txt', @$upload->getName()); // deprecated Assert::same('readme.txt', $upload->getUntrustedName()); Assert::same('readme.txt', $upload->getSanitizedName()); Assert::same('path/to/readme.txt', $upload->getUntrustedFullPath()); @@ -47,7 +47,7 @@ test('', function () { 'size' => 209, ]); - Assert::same('../.image.png', $upload->getName()); + Assert::same('../.image.png', $upload->getUntrustedName()); Assert::same('image.png', $upload->getSanitizedName()); Assert::same('../.image.png', $upload->getUntrustedFullPath()); Assert::same('image/png', $upload->getContentType()); diff --git a/tests/Http/Request.invalidEncoding.phpt b/tests/Http/Request.invalidEncoding.phpt index 96410a3f..30fb80c1 100644 --- a/tests/Http/Request.invalidEncoding.phpt +++ b/tests/Http/Request.invalidEncoding.phpt @@ -118,5 +118,5 @@ test('filtered data', function () { Assert::null($request->getFile(INVALID)); Assert::null($request->getFile(CONTROL_CHARACTERS)); Assert::type(Nette\Http\FileUpload::class, $request->files['file1']); - Assert::same('', $request->files['file1']->name); + Assert::same('', $request->files['file1']->getUntrustedName()); }); diff --git a/tests/Http/RequestFactory.proxy.forwarded.phpt b/tests/Http/RequestFactory.proxy.forwarded.phpt index c1a92659..1f364e1b 100644 --- a/tests/Http/RequestFactory.proxy.forwarded.phpt +++ b/tests/Http/RequestFactory.proxy.forwarded.phpt @@ -21,11 +21,11 @@ test('', function () { $factory = new RequestFactory; $factory->setProxy('127.0.0.1'); Assert::same('127.0.0.3', $factory->fromGlobals()->getRemoteAddress()); - Assert::same('localhost', $factory->fromGlobals()->getRemoteHost()); + Assert::same('localhost', @$factory->fromGlobals()->getRemoteHost()); // deprecated $factory->setProxy('127.0.0.1/8'); Assert::same('23.75.45.200', $factory->fromGlobals()->getRemoteAddress()); - Assert::same('a23-75-45-200.deploy.static.akamaitechnologies.com', $factory->fromGlobals()->getRemoteHost()); + Assert::same('a23-75-45-200.deploy.static.akamaitechnologies.com', @$factory->fromGlobals()->getRemoteHost()); // deprecated $url = $factory->fromGlobals()->getUrl(); Assert::same('http', $url->getScheme()); @@ -43,7 +43,7 @@ test('', function () { $factory->setProxy('127.0.0.3'); Assert::same('23.75.45.200', $factory->fromGlobals()->getRemoteAddress()); - Assert::same('a23-75-45-200.deploy.static.akamaitechnologies.com', $factory->fromGlobals()->getRemoteHost()); + Assert::same('a23-75-45-200.deploy.static.akamaitechnologies.com', @$factory->fromGlobals()->getRemoteHost()); // deprecated $url = $factory->fromGlobals()->getUrl(); Assert::same(8080, $url->getPort()); @@ -62,7 +62,7 @@ test('', function () { $factory->setProxy('127.0.0.3'); Assert::same('2001:db8:cafe::17', $factory->fromGlobals()->getRemoteAddress()); - Assert::same('2001:db8:cafe::17', $factory->fromGlobals()->getRemoteHost()); + Assert::same('2001:db8:cafe::17', @$factory->fromGlobals()->getRemoteHost()); // deprecated $url = $factory->fromGlobals()->getUrl(); Assert::same('2001:db8:cafe::18', $url->getHost()); @@ -79,7 +79,7 @@ test('', function () { $factory->setProxy('127.0.0.3'); Assert::same('2001:db8:cafe::17', $factory->fromGlobals()->getRemoteAddress()); - Assert::same('2001:db8:cafe::17', $factory->fromGlobals()->getRemoteHost()); + Assert::same('2001:db8:cafe::17', @$factory->fromGlobals()->getRemoteHost()); // deprecated $url = $factory->fromGlobals()->getUrl(); Assert::same(47832, $url->getPort()); diff --git a/tests/Http/RequestFactory.proxy.x-forwarded.phpt b/tests/Http/RequestFactory.proxy.x-forwarded.phpt index d44753cf..e91151e3 100644 --- a/tests/Http/RequestFactory.proxy.x-forwarded.phpt +++ b/tests/Http/RequestFactory.proxy.x-forwarded.phpt @@ -23,11 +23,11 @@ test('', function () { $factory = new RequestFactory; $factory->setProxy('127.0.0.1'); Assert::same('127.0.0.3', $factory->fromGlobals()->getRemoteAddress()); - Assert::same('localhost', $factory->fromGlobals()->getRemoteHost()); + Assert::same('localhost', @$factory->fromGlobals()->getRemoteHost()); // deprecated $factory->setProxy('127.0.0.1/8'); Assert::same('23.75.45.200', $factory->fromGlobals()->getRemoteAddress()); - Assert::same('a23-75-45-200.deploy.static.akamaitechnologies.com', $factory->fromGlobals()->getRemoteHost()); + Assert::same('a23-75-45-200.deploy.static.akamaitechnologies.com', @$factory->fromGlobals()->getRemoteHost()); // deprecated $url = $factory->fromGlobals()->getUrl(); Assert::same('otherhost', $url->getHost()); @@ -44,11 +44,11 @@ test('', function () { $factory = new RequestFactory; $factory->setProxy('10.0.0.0/24'); Assert::same('172.16.0.1', $factory->fromGlobals()->getRemoteAddress()); - Assert::same('172.16.0.1', $factory->fromGlobals()->getRemoteHost()); + Assert::same('172.16.0.1', @$factory->fromGlobals()->getRemoteHost()); // deprecated Assert::same('real', $factory->fromGlobals()->getUrl()->getHost()); $factory->setProxy(['10.0.0.1', '10.0.0.2']); Assert::same('172.16.0.1', $factory->fromGlobals()->getRemoteAddress()); - Assert::same('172.16.0.1', $factory->fromGlobals()->getRemoteHost()); + Assert::same('172.16.0.1', @$factory->fromGlobals()->getRemoteHost()); // deprecated Assert::same('real', $factory->fromGlobals()->getUrl()->getHost()); }); From d00c864b530cceefb9c1da7ff9a317d928f53859 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 4 Dec 2022 22:54:18 +0100 Subject: [PATCH 24/25] Request::getRemoteHost() does not perform DNS resolving [Closes #218] --- src/Http/Request.php | 4 ---- tests/Http/RequestFactory.proxy.forwarded.phpt | 8 ++++---- tests/Http/RequestFactory.proxy.x-forwarded.phpt | 6 +++--- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/Http/Request.php b/src/Http/Request.php index 9000990e..f0511b77 100644 --- a/src/Http/Request.php +++ b/src/Http/Request.php @@ -254,10 +254,6 @@ public function getRemoteAddress(): ?string */ public function getRemoteHost(): ?string { - if ($this->remoteHost === null && $this->remoteAddress !== null) { - $this->remoteHost = gethostbyaddr($this->remoteAddress); - } - return $this->remoteHost; } diff --git a/tests/Http/RequestFactory.proxy.forwarded.phpt b/tests/Http/RequestFactory.proxy.forwarded.phpt index 1f364e1b..b8ed7851 100644 --- a/tests/Http/RequestFactory.proxy.forwarded.phpt +++ b/tests/Http/RequestFactory.proxy.forwarded.phpt @@ -25,7 +25,7 @@ test('', function () { $factory->setProxy('127.0.0.1/8'); Assert::same('23.75.45.200', $factory->fromGlobals()->getRemoteAddress()); - Assert::same('a23-75-45-200.deploy.static.akamaitechnologies.com', @$factory->fromGlobals()->getRemoteHost()); // deprecated + Assert::null(@$factory->fromGlobals()->getRemoteHost()); // deprecated $url = $factory->fromGlobals()->getUrl(); Assert::same('http', $url->getScheme()); @@ -43,7 +43,7 @@ test('', function () { $factory->setProxy('127.0.0.3'); Assert::same('23.75.45.200', $factory->fromGlobals()->getRemoteAddress()); - Assert::same('a23-75-45-200.deploy.static.akamaitechnologies.com', @$factory->fromGlobals()->getRemoteHost()); // deprecated + Assert::null(@$factory->fromGlobals()->getRemoteHost()); // deprecated $url = $factory->fromGlobals()->getUrl(); Assert::same(8080, $url->getPort()); @@ -62,7 +62,7 @@ test('', function () { $factory->setProxy('127.0.0.3'); Assert::same('2001:db8:cafe::17', $factory->fromGlobals()->getRemoteAddress()); - Assert::same('2001:db8:cafe::17', @$factory->fromGlobals()->getRemoteHost()); // deprecated + Assert::null(@$factory->fromGlobals()->getRemoteHost()); // deprecated $url = $factory->fromGlobals()->getUrl(); Assert::same('2001:db8:cafe::18', $url->getHost()); @@ -79,7 +79,7 @@ test('', function () { $factory->setProxy('127.0.0.3'); Assert::same('2001:db8:cafe::17', $factory->fromGlobals()->getRemoteAddress()); - Assert::same('2001:db8:cafe::17', @$factory->fromGlobals()->getRemoteHost()); // deprecated + Assert::null(@$factory->fromGlobals()->getRemoteHost()); // deprecated $url = $factory->fromGlobals()->getUrl(); Assert::same(47832, $url->getPort()); diff --git a/tests/Http/RequestFactory.proxy.x-forwarded.phpt b/tests/Http/RequestFactory.proxy.x-forwarded.phpt index e91151e3..ec7a0ad4 100644 --- a/tests/Http/RequestFactory.proxy.x-forwarded.phpt +++ b/tests/Http/RequestFactory.proxy.x-forwarded.phpt @@ -27,7 +27,7 @@ test('', function () { $factory->setProxy('127.0.0.1/8'); Assert::same('23.75.45.200', $factory->fromGlobals()->getRemoteAddress()); - Assert::same('a23-75-45-200.deploy.static.akamaitechnologies.com', @$factory->fromGlobals()->getRemoteHost()); // deprecated + Assert::null(@$factory->fromGlobals()->getRemoteHost()); // deprecated $url = $factory->fromGlobals()->getUrl(); Assert::same('otherhost', $url->getHost()); @@ -44,11 +44,11 @@ test('', function () { $factory = new RequestFactory; $factory->setProxy('10.0.0.0/24'); Assert::same('172.16.0.1', $factory->fromGlobals()->getRemoteAddress()); - Assert::same('172.16.0.1', @$factory->fromGlobals()->getRemoteHost()); // deprecated + Assert::null(@$factory->fromGlobals()->getRemoteHost()); // deprecated Assert::same('real', $factory->fromGlobals()->getUrl()->getHost()); $factory->setProxy(['10.0.0.1', '10.0.0.2']); Assert::same('172.16.0.1', $factory->fromGlobals()->getRemoteAddress()); - Assert::same('172.16.0.1', @$factory->fromGlobals()->getRemoteHost()); // deprecated + Assert::null(@$factory->fromGlobals()->getRemoteHost()); // deprecated Assert::same('real', $factory->fromGlobals()->getUrl()->getHost()); }); From 04fbf19a25436dbf5b6db96c31e8a80d992640a4 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 29 Jan 2023 04:28:57 +0100 Subject: [PATCH 25/25] Request::getBody() WIP --- src/Http/Request.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/Http/Request.php b/src/Http/Request.php index f0511b77..5c7ac021 100644 --- a/src/Http/Request.php +++ b/src/Http/Request.php @@ -267,6 +267,20 @@ public function getRawBody(): ?string } + /** + * Returns decoded content of HTTP request body. + */ + public function getBody(): mixed + { + $type = $this->getHeader('Content-Type'); + return match ($type) { + 'application/json' => json_decode($this->getRawBody()), + 'application/x-www-form-urlencoded' => $_POST, + default => throw new \Exception("Unsupported content type: $type"), + }; + } + + /** * Returns basic HTTP authentication credentials. * @return array{string, string}|null