From eadf935586a7c6c5870a42753348deb83bbb5e65 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 1 Mar 2021 15:28:42 +0100 Subject: [PATCH 01/23] opened 4.0-dev --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index ad09e79b..90c28983 100644 --- a/composer.json +++ b/composer.json @@ -42,7 +42,7 @@ }, "extra": { "branch-alias": { - "dev-master": "3.1-dev" + "dev-master": "4.0-dev" } } } From 461b4c63eca473ffde188c07b5494e6bff0b432a Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 1 Mar 2021 17:18:24 +0100 Subject: [PATCH 02/23] requires PHP 8.0 --- .github/workflows/coding-style.yml | 2 +- .github/workflows/static-analysis.yml | 2 +- .github/workflows/tests.yml | 6 +++--- composer.json | 2 +- readme.md | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/coding-style.yml b/.github/workflows/coding-style.yml index 9ecee213..50d857de 100644 --- a/.github/workflows/coding-style.yml +++ b/.github/workflows/coding-style.yml @@ -10,7 +10,7 @@ jobs: - uses: actions/checkout@v2 - uses: shivammathur/setup-php@v2 with: - php-version: 7.2 + php-version: 8.0 coverage: none - run: composer create-project nette/code-checker temp/code-checker ^3 --no-progress diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index a051b78b..2289a804 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -13,7 +13,7 @@ jobs: - uses: actions/checkout@v2 - uses: shivammathur/setup-php@v2 with: - php-version: 7.4 + php-version: 8.0 coverage: none - run: composer install --no-progress --prefer-dist diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b3c41693..2d7df58b 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'] + php: ['8.0', '8.1'] sapi: ['php', 'php-cgi'] fail-fast: false @@ -42,7 +42,7 @@ jobs: - uses: actions/checkout@v2 - uses: shivammathur/setup-php@v2 with: - php-version: 7.2 + php-version: 8.0 coverage: none extensions: ${{ env.php-extensions }} @@ -57,7 +57,7 @@ jobs: - uses: actions/checkout@v2 - uses: shivammathur/setup-php@v2 with: - php-version: 7.4 + php-version: 8.0 coverage: none extensions: ${{ env.php-extensions }} diff --git a/composer.json b/composer.json index 90c28983..129d3bbe 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ } ], "require": { - "php": ">=7.2 <8.2", + "php": ">=8.0 <8.2", "nette/utils": "^3.1" }, "require-dev": { diff --git a/readme.md b/readme.md index 7cffd108..6376c888 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.1. +It requires PHP version 8.0 and supports PHP up to 8.1. HTTP Request From 8d1091835015176ad3f72cb539aa49d3a0b03112 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 1 Mar 2021 16:44:09 +0100 Subject: [PATCH 03/23] composer: updated dependencies --- composer.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index 129d3bbe..f39dd84b 100644 --- a/composer.json +++ b/composer.json @@ -16,14 +16,14 @@ ], "require": { "php": ">=8.0 <8.2", - "nette/utils": "^3.1" + "nette/utils": "^3.2 || ^4.0" }, "require-dev": { - "nette/di": "^3.0", - "nette/tester": "^2.0", - "nette/security": "^3.0", - "tracy/tracy": "^2.4", - "phpstan/phpstan": "^0.12" + "nette/di": "^4.0", + "nette/tester": "^2.4", + "nette/security": "^4.0", + "tracy/tracy": "^2.8", + "phpstan/phpstan": "^1.0" }, "conflict": { "nette/di": "<3.0.3", From 24c2daff4f93aa1901369e633a8e947320b0e4f7 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 12 Dec 2021 18:36:50 +0100 Subject: [PATCH 04/23] coding style --- src/Bridges/HttpDI/HttpExtension.php | 4 +- src/Http/FileUpload.php | 2 +- src/Http/Helpers.php | 2 +- src/Http/IResponse.php | 2 +- src/Http/Request.php | 2 +- src/Http/RequestFactory.php | 20 +++++----- src/Http/Response.php | 8 ++-- src/Http/Session.php | 10 ++--- src/Http/Url.php | 8 ++-- tests/Http.DI/HttpExtension.csp.phpt | 38 +++++++++---------- .../Http.DI/HttpExtension.featurePolicy.phpt | 16 ++++---- tests/Http.DI/HttpExtension.headers.phpt | 12 +++--- ...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/Response.setCookie.phpt | 2 +- tests/Http/Session.sameSite.phpt | 2 +- 19 files changed, 70 insertions(+), 75 deletions(-) diff --git a/src/Bridges/HttpDI/HttpExtension.php b/src/Bridges/HttpDI/HttpExtension.php index 86fc72fa..f224a3d2 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 5eac3138..ab7c03a7 100644 --- a/src/Http/FileUpload.php +++ b/src/Http/FileUpload.php @@ -184,7 +184,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; diff --git a/src/Http/Helpers.php b/src/Http/Helpers.php index 283f9af6..5739f92f 100644 --- a/src/Http/Helpers.php +++ b/src/Http/Helpers.php @@ -41,7 +41,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 24b329a1..43d9f36a 100644 --- a/src/Http/IResponse.php +++ b/src/Http/IResponse.php @@ -220,7 +220,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 f04473d5..7de39854 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; diff --git a/src/Http/RequestFactory.php b/src/Http/RequestFactory.php index fe406289..09b7ff16 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'), ); } @@ -285,9 +283,7 @@ private function getClient(Url $url): array : null; // use real client address and host if trusted proxy is used - $usingTrustedProxy = $remoteAddr && array_filter($this->proxies, function (string $proxy) use ($remoteAddr): bool { - return Helpers::ipMatch($remoteAddr, $proxy); - }); + $usingTrustedProxy = $remoteAddr && array_filter($this->proxies, fn(string $proxy): bool => Helpers::ipMatch($remoteAddr, $proxy)); if ($usingTrustedProxy) { empty($_SERVER['HTTP_FORWARDED']) ? $this->useNonstandardProxy($url, $remoteAddr, $remoteHost) @@ -353,11 +349,13 @@ private function useNonstandardProxy(Url $url, &$remoteAddr, &$remoteHost): void } if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { - $xForwardedForWithoutProxies = array_filter(explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']), function (string $ip): bool { - return !array_filter($this->proxies, function (string $proxy) use ($ip): bool { - return filter_var(trim($ip), FILTER_VALIDATE_IP) !== false && Helpers::ipMatch(trim($ip), $proxy); - }); - }); + $xForwardedForWithoutProxies = array_filter( + explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']), + fn(string $ip): bool => !array_filter( + $this->proxies, + fn(string $proxy): bool => filter_var(trim($ip), FILTER_VALIDATE_IP) !== false && Helpers::ipMatch(trim($ip), $proxy), + ), + ); if ($xForwardedForWithoutProxies) { $remoteAddr = trim(end($xForwardedForWithoutProxies)); $xForwardedForRealIpKey = key($xForwardedForWithoutProxies); diff --git a/src/Http/Response.php b/src/Http/Response.php index 53fa4c2c..4d01274f 100644 --- a/src/Http/Response.php +++ b/src/Http/Response.php @@ -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; } @@ -267,7 +267,7 @@ public function setCookie( ?string $domain = null, ?bool $secure = null, ?bool $httpOnly = null, - ?string $sameSite = null + ?string $sameSite = null, ) { self::checkHeaders(); $options = [ @@ -288,7 +288,7 @@ public function setCookie( $options['path'] . ($sameSite ? "; SameSite=$sameSite" : ''), $options['domain'], $options['secure'], - $options['httponly'] + $options['httponly'], ); } @@ -315,7 +315,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 87536242..ad34f0af 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) { } @@ -396,7 +396,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'?" : '.')); @@ -476,7 +476,7 @@ private function configure(array $config): void $cookie['path'] . (isset($cookie['samesite']) ? '; SameSite=' . $cookie['samesite'] : ''), $cookie['domain'], $cookie['secure'], - $cookie['httponly'] + $cookie['httponly'], ); } @@ -522,7 +522,7 @@ public function setCookieParameters( string $path, ?string $domain = null, ?bool $secure = null, - ?string $sameSite = null + ?string $sameSite = null, ) { return $this->setOptions([ 'cookie_path' => $path, @@ -582,7 +582,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 a80e1ec6..81635f6c 100644 --- a/src/Http/Url.php +++ b/src/Http/Url.php @@ -372,8 +372,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 = self::idnHostToUnicode(strtolower($this->host)); return $this; @@ -427,8 +427,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 e34937a6..558dbc77 100644 --- a/tests/Http.DI/HttpExtension.csp.phpt +++ b/tests/Http.DI/HttpExtension.csp.phpt @@ -21,25 +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 + 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()); diff --git a/tests/Http.DI/HttpExtension.featurePolicy.phpt b/tests/Http.DI/HttpExtension.featurePolicy.phpt index e84b0417..51d5e7d1 100644 --- a/tests/Http.DI/HttpExtension.featurePolicy.phpt +++ b/tests/Http.DI/HttpExtension.featurePolicy.phpt @@ -21,14 +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 + http: + featurePolicy: + unsized-media: none + geolocation: + - self + - https://example.com + camera: * + EOD , 'neon')); eval($compiler->addConfig($config)->compile()); diff --git a/tests/Http.DI/HttpExtension.headers.phpt b/tests/Http.DI/HttpExtension.headers.phpt index d755e954..58e0f9fa 100644 --- a/tests/Http.DI/HttpExtension.headers.phpt +++ b/tests/Http.DI/HttpExtension.headers.phpt @@ -21,12 +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 + http: + headers: + A: b + C: + D: 0 + EOD , 'neon')); eval($compiler->addConfig($config)->compile()); diff --git a/tests/Http.DI/HttpExtension.sameSiteProtection.disabled.phpt b/tests/Http.DI/HttpExtension.sameSiteProtection.disabled.phpt index ea2a9a1f..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/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.sameSite.phpt b/tests/Http/Session.sameSite.phpt index d487a3f7..045c55d5 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(), ); From fb2f4e8b77839fdfc6f6536de9d93561e8bff1ed Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 1 Mar 2021 18:33:58 +0100 Subject: [PATCH 05/23] removed support for PHP 7 --- src/Http/RequestFactory.php | 2 +- src/Http/Response.php | 20 +++----------------- src/Http/Session.php | 14 ++------------ 3 files changed, 6 insertions(+), 30 deletions(-) diff --git a/src/Http/RequestFactory.php b/src/Http/RequestFactory.php index 09b7ff16..2125f858 100644 --- a/src/Http/RequestFactory.php +++ b/src/Http/RequestFactory.php @@ -276,7 +276,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 + ? $_SERVER['REMOTE_ADDR'] : null; $remoteHost = !empty($_SERVER['REMOTE_HOST']) ? $_SERVER['REMOTE_HOST'] diff --git a/src/Http/Response.php b/src/Http/Response.php index 4d01274f..acaa03d8 100644 --- a/src/Http/Response.php +++ b/src/Http/Response.php @@ -270,28 +270,14 @@ public function setCookie( ?string $sameSite = null, ) { self::checkHeaders(); - $options = [ + setcookie($name, $value, [ 'expires' => $time ? (int) DateTime::from($time)->format('U') : 0, 'path' => $path ?? ($domain ? '/' : $this->cookiePath), 'domain' => $domain ?? ($path ? '' : $this->cookieDomain), 'secure' => $secure ?? $this->cookieSecure, 'httponly' => $httpOnly ?? true, - 'samesite' => $sameSite = ($sameSite ?? self::SAME_SITE_LAX), - ]; - 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::SAME_SITE_LAX, + ]); return $this; } diff --git a/src/Http/Session.php b/src/Http/Session.php index ad34f0af..c3862ac5 100644 --- a/src/Http/Session.php +++ b/src/Http/Session.php @@ -384,7 +384,7 @@ public 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', false) + ['session.read_and_close' => 1]; foreach ($options as $key => $value) { if (!strncmp($key, 'session.', 8)) { // back compatibility @@ -468,17 +468,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(); From 384dc31ba0d5cf90f1be0cecc162c8755f0e1509 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 1 Mar 2021 19:14:39 +0100 Subject: [PATCH 06/23] added property typehints --- src/Bridges/HttpDI/HttpExtension.php | 3 +- src/Bridges/HttpDI/SessionExtension.php | 7 ++--- src/Http/Context.php | 7 ++--- src/Http/FileUpload.php | 19 ++++--------- src/Http/Request.php | 35 +++++++---------------- src/Http/RequestFactory.php | 8 ++---- src/Http/Response.php | 24 ++++++++-------- 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(+), 167 deletions(-) diff --git a/src/Bridges/HttpDI/HttpExtension.php b/src/Bridges/HttpDI/HttpExtension.php index f224a3d2..bfbe241f 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 f3d13b47..79c4c4e4 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 bfe6fb9b..a221fc7f 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 ab7c03a7..087c1221 100644 --- a/src/Http/FileUpload.php +++ b/src/Http/FileUpload.php @@ -30,20 +30,11 @@ final class FileUpload public const IMAGE_MIME_TYPES = ['image/gif', 'image/png', 'image/jpeg', 'image/webp']; - /** @var string */ - private $name; - - /** @var string|false|null */ - private $type; - - /** @var int */ - private $size; - - /** @var string */ - private $tmpName; - - /** @var int */ - private $error; + private string $name; + 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 7de39854..8b0f47ce 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 2125f858..1d20ba83 100644 --- a/src/Http/RequestFactory.php +++ b/src/Http/RequestFactory.php @@ -23,17 +23,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 acaa03d8..670c3b1e 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; - /** @var bool Send invisible garbage for IE 6? */ - private static $fixIE = true; + /** Send invisible garbage for IE 6? */ + 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 c3862ac5..b0d108f1 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 iterable $onStart = []; /** @var array Occurs before the session is written to disk */ - public $onBeforeWrite = []; + public iterable $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::SAME_SITE_LAX, '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 09e040ea..75535ab6 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 81635f6c..4d757b37 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 2301c662..9b8b4df7 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 3bb4aa099abbf1a5f41b2c33b238da9d6a2d15c6 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 12 Dec 2021 18:37:13 +0100 Subject: [PATCH 07/23] 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 | 19 +++---- src/Http/SessionSection.php | 21 +++----- 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 | 18 +++---- .../Http/SessionSection.setExpiration().phpt | 4 +- ...SessionSection.setExpirationUnlimited.phpt | 2 +- tests/Http/Url.query.phpt | 4 -- 20 files changed, 101 insertions(+), 205 deletions(-) diff --git a/src/Http/Context.php b/src/Http/Context.php index a221fc7f..32548e1c 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 087c1221..3651ef94 100644 --- a/src/Http/FileUpload.php +++ b/src/Http/FileUpload.php @@ -163,9 +163,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 5739f92f..f977e03f 100644 --- a/src/Http/Helpers.php +++ b/src/Http/Helpers.php @@ -26,9 +26,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 94356a33..8ca55e23 100644 --- a/src/Http/IRequest.php +++ b/src/Http/IRequest.php @@ -37,22 +37,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. @@ -61,9 +58,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 43d9f36a..90c21aa6 100644 --- a/src/Http/IResponse.php +++ b/src/Http/IResponse.php @@ -155,9 +155,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. @@ -166,21 +165,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. @@ -189,9 +185,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. @@ -210,21 +205,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 8b0f47ce..4f08bbff 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 1d20ba83..40d454d8 100644 --- a/src/Http/RequestFactory.php +++ b/src/Http/RequestFactory.php @@ -34,8 +34,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; @@ -44,9 +43,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 670c3b1e..2fbcae4f 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, 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 $time) + public function setExpiration(?string $time): static { $this->setHeader('Pragma', null); if (!$time) { // no cache @@ -255,20 +248,18 @@ public function __destruct() /** * Sends a cookie. - * @param string|int|\DateTimeInterface $time 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, - $time, + string|int|\DateTimeInterface|null $time, ?string $path = null, ?string $domain = null, ?bool $secure = null, ?bool $httpOnly = null, ?string $sameSite = null, - ) { + ): static { self::checkHeaders(); setcookie($name, $value, [ 'expires' => $time ? (int) DateTime::from($time)->format('U') : 0, @@ -286,8 +277,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 b0d108f1..0e6a5fcc 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 { @@ -363,11 +361,10 @@ public 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', false) + ['session.read_and_close' => 1]; @@ -470,9 +467,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 $time) + public function setExpiration(?string $time): static { if ($time === null) { return $this->setOptions([ @@ -492,14 +488,13 @@ public function setExpiration(?string $time) /** * 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, @@ -519,9 +514,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, @@ -531,9 +525,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 75535ab6..81daaa1d 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 $expiration = null): void + public function set(string $name, mixed $value, ?string $expiration = null): void { if ($value === null) { $this->remove($name); @@ -62,9 +61,8 @@ public function set(string $name, $value, ?string $expiration = 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) { @@ -109,9 +107,8 @@ public function __set(string $name, $value): void /** * Gets a variable from this session section. - * @return mixed */ - public function &__get(string $name) + public function &__get(string $name): mixed { $this->session->autoStart(true); $data = &$this->getData(); @@ -151,12 +148,10 @@ public function offsetSet($name, $value): void } - #[\ReturnTypeWillChange] /** * Gets a variable from this session section. - * @return mixed */ - public function offsetGet($name) + public function offsetGet($name): mixed { return $this->get($name); } @@ -182,11 +177,9 @@ public function offsetUnset($name): void /** * Sets the expiration of the section or specific variables. - * @param ?string $time * @param string|string[]|null $variables list of variables / single variable to expire - * @return static */ - public function setExpiration($time, $variables = null) + public function setExpiration(?string $time, string|array|null $variables = null): static { $this->session->autoStart((bool) $time); $meta = &$this->getMeta(); @@ -213,7 +206,7 @@ public function setExpiration($time, $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 4d757b37..8902a280 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; @@ -177,8 +168,7 @@ public function getPort(): ?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) !== '/') { @@ -195,22 +185,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 @@ -231,8 +213,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); @@ -242,19 +223,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; @@ -326,9 +302,8 @@ public function getRelativeUrl(): string /** * URL comparison. - * @param string|self $url */ - public function isEqual($url): bool + public function isEqual(string|self $url): bool { $url = new self($url); $query = $url->query; @@ -349,10 +324,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 9b8b4df7..9e09ae7a 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; @@ -171,8 +160,7 @@ public function getPort(): ?int } - /** @return static */ - public function withPath(string $path) + public function withPath(string $path): static { $dolly = clone $this; $dolly->path = $path; @@ -187,11 +175,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); @@ -206,11 +190,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; @@ -224,15 +204,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; @@ -283,10 +261,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 1ca3399b..fe976d48 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 d64d56b0..0e907ce5 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()) { 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 7be61aba6830855d0a9e8e95ca5a278cc4587220 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 2 Mar 2021 14:59:23 +0100 Subject: [PATCH 08/23] removed deprecated stuff & UserStorage --- src/Bridges/HttpDI/SessionExtension.php | 12 +- src/Http/IResponse.php | 6 - src/Http/Request.php | 12 -- src/Http/Response.php | 9 +- src/Http/Session.php | 8 -- src/Http/Url.php | 5 - src/Http/UserStorage.php | 179 ------------------------ 7 files changed, 2 insertions(+), 229 deletions(-) delete mode 100644 src/Http/UserStorage.php diff --git a/src/Bridges/HttpDI/SessionExtension.php b/src/Bridges/HttpDI/SessionExtension.php index 79c4c4e4..5647c273 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::SAME_SITE_LAX, IResponse::SAME_SITE_STRICT, IResponse::SAME_SITE_NONE, true) + 'cookieSamesite' => Expect::anyOf(IResponse::SAME_SITE_LAX, IResponse::SAME_SITE_STRICT, IResponse::SAME_SITE_NONE) ->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::SAME_SITE_LAX; - } - $this->compiler->addExportedType(Nette\Http\IRequest::class); if ($this->debugMode && $config->debugger) { diff --git a/src/Http/IResponse.php b/src/Http/IResponse.php index 90c21aa6..c0858d8b 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 4f08bbff..e7d2b17f 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 2fbcae4f..c40b743b 100644 --- a/src/Http/Response.php +++ b/src/Http/Response.php @@ -31,9 +31,6 @@ 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; @@ -66,7 +63,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::REASON_PHRASES[$code] ?? 'Unknown status'; + $reason ??= self::REASON_PHRASES[$code] ?? 'Unknown status'; header("$protocol $code $reason"); return $this; } @@ -201,10 +198,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 0e6a5fcc..09621157 100644 --- a/src/Http/Session.php +++ b/src/Http/Session.php @@ -504,14 +504,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 8902a280..80388393 100644 --- a/src/Http/Url.php +++ b/src/Http/Url.php @@ -215,10 +215,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; } @@ -324,7 +320,6 @@ public function isEqual(string|self $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 90a87eb385a225b0dc94391bdbe388a98ec0be4e Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 11 Mar 2021 21:53:22 +0100 Subject: [PATCH 09/23] removed community health files --- .github/ISSUE_TEMPLATE/Bug_report.md | 19 ------------- .github/ISSUE_TEMPLATE/Feature_request.md | 9 ------ .github/ISSUE_TEMPLATE/Support_question.md | 12 -------- .github/ISSUE_TEMPLATE/Support_us.md | 21 -------------- .github/funding.yml | 2 -- .github/pull_request_template.md | 11 -------- contributing.md | 33 ---------------------- 7 files changed, 107 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/Bug_report.md delete mode 100644 .github/ISSUE_TEMPLATE/Feature_request.md delete mode 100644 .github/ISSUE_TEMPLATE/Support_question.md delete mode 100644 .github/ISSUE_TEMPLATE/Support_us.md delete mode 100644 .github/funding.yml delete mode 100644 .github/pull_request_template.md delete mode 100644 contributing.md diff --git a/.github/ISSUE_TEMPLATE/Bug_report.md b/.github/ISSUE_TEMPLATE/Bug_report.md deleted file mode 100644 index a4cd1263..00000000 --- a/.github/ISSUE_TEMPLATE/Bug_report.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -name: "🐛 Bug Report" -about: "If something isn't working as expected 🤔" - ---- - -Version: ?.?.? - -### Bug Description -... A clear and concise description of what the bug is. A good bug report shouldn't leave others needing to chase you up for more information. - -### Steps To Reproduce -... If possible a minimal demo of the problem ... - -### Expected Behavior -... A clear and concise description of what you expected to happen. - -### Possible Solution -... Only if you have suggestions on a fix for the bug diff --git a/.github/ISSUE_TEMPLATE/Feature_request.md b/.github/ISSUE_TEMPLATE/Feature_request.md deleted file mode 100644 index d2e21948..00000000 --- a/.github/ISSUE_TEMPLATE/Feature_request.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -name: "🚀 Feature Request" -about: "I have a suggestion (and may want to implement it) 🙂" - ---- - -- Is your feature request related to a problem? Please describe. -- Explain your intentions. -- It's up to you to make a strong case to convince the project's developers of the merits of this feature. diff --git a/.github/ISSUE_TEMPLATE/Support_question.md b/.github/ISSUE_TEMPLATE/Support_question.md deleted file mode 100644 index 75c48b6e..00000000 --- a/.github/ISSUE_TEMPLATE/Support_question.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -name: "🤗 Support Question" -about: "If you have a question 💬, please check out our forum!" - ---- - ---------------^ Click "Preview" for a nicer view! -We primarily use GitHub as an issue tracker; for usage and support questions, please check out these resources below. Thanks! 😁. - -* Nette Forum: https://forum.nette.org -* Nette Gitter: https://gitter.im/nette/nette -* Slack (czech): https://pehapkari.slack.com/messages/C2R30BLKA diff --git a/.github/ISSUE_TEMPLATE/Support_us.md b/.github/ISSUE_TEMPLATE/Support_us.md deleted file mode 100644 index 92d8a4c3..00000000 --- a/.github/ISSUE_TEMPLATE/Support_us.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -name: "❤️ Support us" -about: "If you would like to support our efforts in maintaining this project 🙌" - ---- - ---------------^ Click "Preview" for a nicer view! - -> https://nette.org/donate - -Help support Nette! - -We develop Nette Framework for more than 14 years. In order to make your life more comfortable. Nette cares about the safety of your sites. Nette saves you time. And gives job opportunities. - -Nette earns you money. And is absolutely free. - -To ensure future development and improving the documentation, we need your donation. - -Whether you are chief of IT company which benefits from Nette, or developer who goes for advice on our forum, if you like Nette, [please make a donation now](https://nette.org/donate). - -Thank you! diff --git a/.github/funding.yml b/.github/funding.yml deleted file mode 100644 index 25adc952..00000000 --- a/.github/funding.yml +++ /dev/null @@ -1,2 +0,0 @@ -github: dg -custom: "https://nette.org/donate" diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md deleted file mode 100644 index f8aa3f40..00000000 --- a/.github/pull_request_template.md +++ /dev/null @@ -1,11 +0,0 @@ -- bug fix / new feature? -- BC break? yes/no -- doc PR: nette/docs#??? - - diff --git a/contributing.md b/contributing.md deleted file mode 100644 index 184152c0..00000000 --- a/contributing.md +++ /dev/null @@ -1,33 +0,0 @@ -How to contribute & use the issue tracker -========================================= - -Nette welcomes your contributions. There are several ways to help out: - -* Create an issue on GitHub, if you have found a bug -* Write test cases for open bug issues -* Write fixes for open bug/feature issues, preferably with test cases included -* Contribute to the [documentation](https://nette.org/en/writing) - -Issues ------- - -Please **do not use the issue tracker to ask questions**. We will be happy to help you -on [Nette forum](https://forum.nette.org) or chat with us on [Gitter](https://gitter.im/nette/nette). - -A good bug report shouldn't leave others needing to chase you up for more -information. Please try to be as detailed as possible in your report. - -**Feature requests** are welcome. But take a moment to find out whether your idea -fits with the scope and aims of the project. It's up to *you* to make a strong -case to convince the project's developers of the merits of this feature. - -Contributing ------------- - -If you'd like to contribute, please take a moment to read [the contributing guide](https://nette.org/en/contributing). - -The best way to propose a feature is to discuss your ideas on [Nette forum](https://forum.nette.org) before implementing them. - -Please do not fix whitespace, format code, or make a purely cosmetic patch. - -Thanks! :heart: From d777bf388ab83d3a98e3b4479611d77a03f34069 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 28 Jul 2021 13:13:46 +0200 Subject: [PATCH 10/23] used native PHP 8 features --- src/Bridges/HttpDI/HttpExtension.php | 2 +- src/Http/Context.php | 2 +- src/Http/Helpers.php | 2 +- src/Http/RequestFactory.php | 6 +++--- src/Http/Response.php | 2 +- src/Http/Url.php | 4 ++-- src/Http/UrlImmutable.php | 2 +- tests/Http/Request.detectLanguage.phpt | 8 ++++---- tests/Http/Request.getRawBody.phpt | 2 +- tests/Http/Request.headers.phpt | 4 ++-- 10 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/Bridges/HttpDI/HttpExtension.php b/src/Bridges/HttpDI/HttpExtension.php index bfbe241f..b8f9d6df 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 32548e1c..464cf0a0 100644 --- a/src/Http/Context.php +++ b/src/Http/Context.php @@ -50,7 +50,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 f977e03f..9d433041 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::STRICT_COOKIE_NAME, '1', 0, '/', null, null, true, IResponse::SAME_SITE_STRICT); + $response->setCookie(self::STRICT_COOKIE_NAME, '1', 0, '/', sameSite: IResponse::SAME_SITE_STRICT); } } diff --git a/src/Http/RequestFactory.php b/src/Http/RequestFactory.php index 40d454d8..102f38b0 100644 --- a/src/Http/RequestFactory.php +++ b/src/Http/RequestFactory.php @@ -300,9 +300,9 @@ private function useForwardedProxy(Url $url, &$remoteAddr, &$remoteHost): void 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['host']) && count($proxyParams['host']) === 1) { diff --git a/src/Http/Response.php b/src/Http/Response.php index c40b743b..85799e3f 100644 --- a/src/Http/Response.php +++ b/src/Http/Response.php @@ -229,7 +229,7 @@ public function __destruct() { if ( self::$fixIE - && strpos($_SERVER['HTTP_USER_AGENT'] ?? '', 'MSIE ') !== false + && str_contains($_SERVER['HTTP_USER_AGENT'] ?? '', 'MSIE ') && 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')) ) { diff --git a/src/Http/Url.php b/src/Http/Url.php index 80388393..a8167c33 100644 --- a/src/Http/Url.php +++ b/src/Http/Url.php @@ -171,7 +171,7 @@ public function getPort(): ?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; } @@ -357,7 +357,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 9e09ae7a..b6d38fc1 100644 --- a/src/Http/UrlImmutable.php +++ b/src/Http/UrlImmutable.php @@ -282,7 +282,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/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.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', From 5339667f86b39cd99d50ee8a5af9d52b3995fe88 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 3 Sep 2021 16:31:04 +0200 Subject: [PATCH 11/23] used nette/utils 3.2 --- src/Http/Session.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Http/Session.php b/src/Http/Session.php index 09621157..d994f0f7 100644 --- a/src/Http/Session.php +++ b/src/Http/Session.php @@ -120,7 +120,7 @@ function (string $message) use (&$e): void { } $this->initialize(); - $this->onStart($this); + Nette\Utils\Arrays::invoke($this->onStart, $this); } @@ -345,7 +345,7 @@ public function clean(): void return; } - $this->onBeforeWrite($this); + Nette\Utils\Arrays::invoke($this->onBeforeWrite, $this); $nf = &$_SESSION['__NF']; foreach ($nf['META'] ?? [] as $name => $foo) { From 4e8b381173d10ec1dd5faea65058bcf8d25c4f3f Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sat, 4 Sep 2021 00:27:23 +0200 Subject: [PATCH 12/23] Session::clean() is private (BC break) --- src/Http/Session.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Http/Session.php b/src/Http/Session.php index d994f0f7..6b79c714 100644 --- a/src/Http/Session.php +++ b/src/Http/Session.php @@ -337,9 +337,8 @@ public function getIterator(): \Iterator /** * Cleans and minimizes meta structures. This method is called automatically on shutdown, do not call it directly. - * @internal */ - public function clean(): void + private function clean(): void { if (!$this->isStarted()) { return; From 78e5c46a6a2a4ccd6cab052197abf5fee82966d6 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sat, 4 Sep 2021 13:02:37 +0200 Subject: [PATCH 13/23] 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 81daaa1d..a4b95033 100644 --- a/src/Http/SessionSection.php +++ b/src/Http/SessionSection.php @@ -19,7 +19,6 @@ class SessionSection implements \IteratorAggregate, \ArrayAccess { use Nette\SmartObject; - 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 e042154890b3c698fe988be8434d01d94d109fac Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sat, 4 Sep 2021 13:05:37 +0200 Subject: [PATCH 14/23] Session::getIterator() is deprecated --- src/Http/Session.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Http/Session.php b/src/Http/Session.php index 6b79c714..2faa3f88 100644 --- a/src/Http/Session.php +++ b/src/Http/Session.php @@ -322,11 +322,10 @@ public function hasSection(string $section): bool } - /** - * Iteration over all sections. - */ + /** @deprecated */ public function getIterator(): \Iterator { + trigger_error(__METHOD__ . '() is deprecated', E_USER_DEPRECATED); if ($this->exists() && !$this->started) { $this->autoStart(false); } From 88f79daeb45a31ab843374ea6a58bb9ae847a5d4 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 3 Sep 2021 19:16:30 +0200 Subject: [PATCH 15/23] 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 2faa3f88..b290c56a 100644 --- a/src/Http/Session.php +++ b/src/Http/Session.php @@ -346,7 +346,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 25ada857072a1b218a80ca40e57f3275476b06ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Pudil?= Date: Wed, 20 Oct 2021 01:50:45 +0200 Subject: [PATCH 16/23] FileUpload: support directory upload (#207) --- src/Http/FileUpload.php | 16 ++++++++++++++++ tests/Http/FileUpload.basic.phpt | 3 +++ 2 files changed, 19 insertions(+) diff --git a/src/Http/FileUpload.php b/src/Http/FileUpload.php index 3651ef94..503d184b 100644 --- a/src/Http/FileUpload.php +++ b/src/Http/FileUpload.php @@ -17,6 +17,7 @@ * * @property-read string $name * @property-read string $sanitizedName + * @property-read string $untrustedFullPath * @property-read string|null $contentType * @property-read int $size * @property-read string $temporaryFile @@ -31,6 +32,7 @@ final class FileUpload public const IMAGE_MIME_TYPES = ['image/gif', 'image/png', 'image/jpeg', 'image/webp']; private string $name; + private string|null $fullPath; private string|false|null $type = null; private int $size; private string $tmpName; @@ -47,6 +49,7 @@ public function __construct(?array $value) } $this->name = $value['name']; + $this->fullPath = $value['full_path'] ?? null; $this->size = $value['size']; $this->tmpName = $value['tmp_name']; $this->error = $value['error']; @@ -92,6 +95,19 @@ public function getSanitizedName(): string } + /** + * Returns the original full path as submitted by the browser during directory upload. Do not trust the value + * returned by this method. A client could send a malicious directory structure with the intention to corrupt + * or hack your application. + * + * The full path is only available in PHP 8.1 and above. In previous versions, this method returns the file name. + */ + public function getUntrustedFullPath(): string + { + return $this->fullPath ?? $this->name; + } + + /** * Detects the MIME content type of the uploaded file based on its signature. Requires PHP extension fileinfo. * If the upload was not successful or the detection failed, it returns null. diff --git a/tests/Http/FileUpload.basic.phpt b/tests/Http/FileUpload.basic.phpt index 6d77d3ed..573568af 100644 --- a/tests/Http/FileUpload.basic.phpt +++ b/tests/Http/FileUpload.basic.phpt @@ -15,6 +15,7 @@ require __DIR__ . '/../bootstrap.php'; test('', function () { $upload = new FileUpload([ 'name' => 'readme.txt', + 'full_path' => 'path/to/readme.txt', 'type' => 'text/plain', 'tmp_name' => __DIR__ . '/files/file.txt', 'error' => 0, @@ -24,6 +25,7 @@ test('', function () { Assert::same('readme.txt', $upload->getName()); Assert::same('readme.txt', $upload->getUntrustedName()); Assert::same('readme.txt', $upload->getSanitizedName()); + Assert::same('path/to/readme.txt', $upload->getUntrustedFullPath()); Assert::same(209, $upload->getSize()); Assert::same(__DIR__ . '/files/file.txt', $upload->getTemporaryFile()); Assert::same(__DIR__ . '/files/file.txt', (string) $upload); @@ -47,6 +49,7 @@ test('', function () { Assert::same('../.image.png', $upload->getName()); Assert::same('image.png', $upload->getSanitizedName()); + Assert::same('../.image.png', $upload->getUntrustedFullPath()); Assert::same('image/png', $upload->getContentType()); Assert::same('png', $upload->getImageFileExtension()); Assert::same([108, 46], $upload->getImageSize()); From 27883467fbacd78b52eb21f738fe033000e578ba Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 29 Nov 2021 20:21:44 +0100 Subject: [PATCH 17/23] SessionSection: magic accessors & ArrayAccess are silently deprecated --- src/Http/SessionSection.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Http/SessionSection.php b/src/Http/SessionSection.php index a4b95033..1329d51d 100644 --- a/src/Http/SessionSection.php +++ b/src/Http/SessionSection.php @@ -96,6 +96,7 @@ public function remove(string|array|null $name = null): void /** * Sets a variable in this session section. + * @deprecated */ public function __set(string $name, $value): void { @@ -106,6 +107,7 @@ public function __set(string $name, $value): void /** * Gets a variable from this session section. + * @deprecated */ public function &__get(string $name): mixed { @@ -117,6 +119,7 @@ public function &__get(string $name): mixed /** * Determines whether a variable in this session section is set. + * @deprecated */ public function __isset(string $name): bool { @@ -127,6 +130,7 @@ public function __isset(string $name): bool /** * Unsets a variable in this session section. + * @deprecated */ public function __unset(string $name): void { @@ -136,6 +140,7 @@ public function __unset(string $name): void /** * Sets a variable in this session section. + * @deprecated */ public function offsetSet($name, $value): void { @@ -145,6 +150,7 @@ public function offsetSet($name, $value): void /** * Gets a variable from this session section. + * @deprecated */ public function offsetGet($name): mixed { @@ -154,6 +160,7 @@ public function offsetGet($name): mixed /** * Determines whether a variable in this session section is set. + * @deprecated */ public function offsetExists($name): bool { @@ -163,6 +170,7 @@ public function offsetExists($name): bool /** * Unsets a variable in this session section. + * @deprecated */ public function offsetUnset($name): void { From fb9f0f362e48290a0e8432a9fd81f12331b1fdb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Bou=C4=8Dek?= Date: Tue, 1 Mar 2022 16:06:13 +0100 Subject: [PATCH 18/23] Request: Move Basic Auth credential from Url to Request due to prevent leak it (#211) --- src/Http/Request.php | 18 +++++++++++++++++ src/Http/RequestFactory.php | 20 +++++++++++-------- .../Http/RequestFactory.userAndPassword.phpt | 8 ++++---- 3 files changed, 34 insertions(+), 12 deletions(-) diff --git a/src/Http/Request.php b/src/Http/Request.php index e7d2b17f..73ec7e2b 100644 --- a/src/Http/Request.php +++ b/src/Http/Request.php @@ -44,6 +44,8 @@ class Request implements IRequest /** @var ?callable */ private $rawBodyCallback; + private ?string $user; + private ?string $password; public function __construct( @@ -56,6 +58,8 @@ public function __construct( ?string $remoteAddress = null, ?string $remoteHost = null, ?callable $rawBodyCallback = null, + ?string $user = null, + ?string $password = null, ) { $this->url = $url; $this->post = (array) $post; @@ -66,6 +70,8 @@ public function __construct( $this->remoteAddress = $remoteAddress; $this->remoteHost = $remoteHost; $this->rawBodyCallback = $rawBodyCallback; + $this->user = $user; + $this->password = $password; } @@ -284,6 +290,18 @@ public function getRawBody(): ?string } + public function getUser(): ?string + { + return $this->user; + } + + + public function getPassword(): ?string + { + return $this->password; + } + + /** * Returns the most preferred language by browser. Uses the `Accept-Language` header. If no match is reached, it returns `null`. * @param string[] $langs supported languages diff --git a/src/Http/RequestFactory.php b/src/Http/RequestFactory.php index 102f38b0..d60fa933 100644 --- a/src/Http/RequestFactory.php +++ b/src/Http/RequestFactory.php @@ -59,9 +59,9 @@ public function fromGlobals(): Request $url = new Url; $this->getServer($url); $this->getPathAndQuery($url); - $this->getUserAndPassword($url); [$post, $cookies] = $this->getGetPostCookie($url); [$remoteAddr, $remoteHost] = $this->getClient($url); + [$user, $password] = $this->getUserAndPassword(); return new Request( new UrlScript($url, $this->getScriptPath($url)), @@ -73,6 +73,8 @@ public function fromGlobals(): Request $remoteAddr, $remoteHost, fn(): string => file_get_contents('php://input'), + $user, + $password, ); } @@ -109,13 +111,6 @@ private function getPathAndQuery(Url $url): void } - private function getUserAndPassword(Url $url): void - { - $url->setUser($_SERVER['PHP_AUTH_USER'] ?? ''); - $url->setPassword($_SERVER['PHP_AUTH_PW'] ?? ''); - } - - private function getScriptPath(Url $url): string { if (PHP_SAPI === 'cli-server') { @@ -290,6 +285,15 @@ private function getClient(Url $url): array } + private function getUserAndPassword(): array + { + $user = $_SERVER['PHP_AUTH_USER'] ?? null; + $password = $_SERVER['PHP_AUTH_PW'] ?? null; + + return [$user, $password]; + } + + private function useForwardedProxy(Url $url, &$remoteAddr, &$remoteHost): void { $forwardParams = preg_split('/[,;]/', $_SERVER['HTTP_FORWARDED']); diff --git a/tests/Http/RequestFactory.userAndPassword.phpt b/tests/Http/RequestFactory.userAndPassword.phpt index c9e91e05..73238c61 100644 --- a/tests/Http/RequestFactory.userAndPassword.phpt +++ b/tests/Http/RequestFactory.userAndPassword.phpt @@ -17,11 +17,11 @@ $_SERVER = [ 'PHP_AUTH_PW' => 'password', ]; $factory = new RequestFactory; -Assert::same('user', $factory->fromGlobals()->getUrl()->getUser()); -Assert::same('password', $factory->fromGlobals()->getUrl()->getPassword()); +Assert::same('user', $factory->fromGlobals()->getUser()); +Assert::same('password', $factory->fromGlobals()->getPassword()); $_SERVER = []; $factory = new RequestFactory; -Assert::same('', $factory->fromGlobals()->getUrl()->getUser()); -Assert::same('', $factory->fromGlobals()->getUrl()->getPassword()); +Assert::same(null, $factory->fromGlobals()->getUser()); +Assert::same(null, $factory->fromGlobals()->getPassword()); From fca6d132eb5621a5a2f73388f7a4cd91c2eed4ad Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 1 Mar 2022 16:21:19 +0100 Subject: [PATCH 19/23] some methods use real default values instead of nulls (BC break) --- src/Http/IResponse.php | 6 +++--- src/Http/Request.php | 20 ++++++++++---------- src/Http/RequestFactory.php | 4 ++-- src/Http/Response.php | 8 ++++---- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/Http/IResponse.php b/src/Http/IResponse.php index c0858d8b..efea4821 100644 --- a/src/Http/IResponse.php +++ b/src/Http/IResponse.php @@ -206,8 +206,8 @@ function setCookie( ?int $expire, ?string $path = null, ?string $domain = null, - ?bool $secure = null, - ?bool $httpOnly = null, + bool $secure = false, + bool $httpOnly = true, ): static; /** @@ -217,6 +217,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 73ec7e2b..1a4b7be7 100644 --- a/src/Http/Request.php +++ b/src/Http/Request.php @@ -50,11 +50,11 @@ 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, @@ -62,11 +62,11 @@ public function __construct( ?string $password = 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/RequestFactory.php b/src/Http/RequestFactory.php index d60fa933..69c7fc0d 100644 --- a/src/Http/RequestFactory.php +++ b/src/Http/RequestFactory.php @@ -250,9 +250,9 @@ private function getHeaders(): array } - private function getMethod(): ?string + private function getMethod(): string { - $method = $_SERVER['REQUEST_METHOD'] ?? null; + $method = $_SERVER['REQUEST_METHOD'] ?? 'GET'; if ( $method === 'POST' && preg_match('#^[A-Z]+$#D', $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] ?? '') diff --git a/src/Http/Response.php b/src/Http/Response.php index 85799e3f..25fdacc4 100644 --- a/src/Http/Response.php +++ b/src/Http/Response.php @@ -250,8 +250,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::SAME_SITE_LAX, ): static { self::checkHeaders(); setcookie($name, $value, [ @@ -259,8 +259,8 @@ public function setCookie( 'path' => $path ?? ($domain ? '/' : $this->cookiePath), 'domain' => $domain ?? ($path ? '' : $this->cookieDomain), 'secure' => $secure ?? $this->cookieSecure, - 'httponly' => $httpOnly ?? true, - 'samesite' => $sameSite ?? self::SAME_SITE_LAX, + 'httponly' => $httpOnly, + 'samesite' => $sameSite, ]); return $this; } From 6e9f551ae6cd6bc8d29c6dc244b13dcbf59bc55f Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 1 Mar 2022 16:21:19 +0100 Subject: [PATCH 20/23] unification of Response and IResponse params (BC break) --- src/Http/IResponse.php | 13 +++++++++---- src/Http/Response.php | 14 +++++++------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/Http/IResponse.php b/src/Http/IResponse.php index efea4821..d4a8763e 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 { @@ -167,6 +166,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. */ @@ -180,7 +184,7 @@ 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". */ - function setExpiration(?string $expire): static; + function setExpiration(?string $expires): static; /** * Checks if headers have been sent. @@ -193,7 +197,7 @@ function isSent(): bool; function getHeader(string $header): ?string; /** - * Returns a associative array of headers to sent. + * Returns an associative array of headers to sent. */ function getHeaders(): array; @@ -203,11 +207,12 @@ function getHeaders(): array; function setCookie( string $name, string $value, - ?int $expire, + string|int|null $expires, ?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 25fdacc4..f2572438 100644 --- a/src/Http/Response.php +++ b/src/Http/Response.php @@ -167,18 +167,18 @@ public function redirect(string $url, int $code = self::S302_FOUND): void * The parameter is either a time interval (as text) or `null`, which disables caching. * @throws Nette\InvalidStateException if HTTP headers have been sent */ - public function setExpiration(?string $time): static + public function setExpiration(?string $expires): static { $this->setHeader('Pragma', null); - if (!$time) { // no cache + if (!$expires) { // no cache $this->setHeader('Cache-Control', 's-maxage=0, max-age=0, must-revalidate'); $this->setHeader('Expires', 'Mon, 23 Jan 1978 10:00:00 GMT'); return $this; } - $time = DateTime::from($time); - $this->setHeader('Cache-Control', 'max-age=' . ($time->format('U') - time())); - $this->setHeader('Expires', Helpers::formatDate($time)); + $expires = DateTime::from($expires); + $this->setHeader('Cache-Control', 'max-age=' . ($expires->format('U') - time())); + $this->setHeader('Expires', Helpers::formatDate($expires)); return $this; } @@ -246,7 +246,7 @@ public function __destruct() public function setCookie( string $name, string $value, - string|int|\DateTimeInterface|null $time, + string|int|null $expires, ?string $path = null, ?string $domain = null, ?bool $secure = null, @@ -255,7 +255,7 @@ public function setCookie( ): static { self::checkHeaders(); setcookie($name, $value, [ - 'expires' => $time ? (int) DateTime::from($time)->format('U') : 0, + 'expires' => $expires ? (int) DateTime::from($expires)->format('U') : 0, 'path' => $path ?? ($domain ? '/' : $this->cookiePath), 'domain' => $domain ?? ($path ? '' : $this->cookieDomain), 'secure' => $secure ?? $this->cookieSecure, From 831c08b35a30be8e399294f692bdab796da81065 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 1 Mar 2022 16:10:31 +0100 Subject: [PATCH 21/23] used constructor promotion --- src/Http/Request.php | 36 +++++++++--------------------------- 1 file changed, 9 insertions(+), 27 deletions(-) diff --git a/src/Http/Request.php b/src/Http/Request.php index 1a4b7be7..053d28be 100644 --- a/src/Http/Request.php +++ b/src/Http/Request.php @@ -33,45 +33,27 @@ 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; - private ?string $user; - private ?string $password; 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, - ?string $user = null, - ?string $password = null, + private ?string $user = null, + private ?string $password = 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; - $this->user = $user; - $this->password = $password; } From 0679e7c51a76a2db1da0804b99c83eb3152d84b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Pudil?= Date: Tue, 12 Apr 2022 14:18:43 +0200 Subject: [PATCH 22/23] RequestFactory: support full_path (#212) --- src/Http/RequestFactory.php | 1 + tests/Http/Request.files.directory.phpt | 37 +++++++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 tests/Http/Request.files.directory.phpt diff --git a/src/Http/RequestFactory.php b/src/Http/RequestFactory.php index 69c7fc0d..77b50d45 100644 --- a/src/Http/RequestFactory.php +++ b/src/Http/RequestFactory.php @@ -218,6 +218,7 @@ private function getFiles(): array 'name' => $v['name'][$k], 'type' => $v['type'][$k], 'size' => $v['size'][$k], + 'full_path' => $v['full_path'][$k] ?? null, 'tmp_name' => $v['tmp_name'][$k], 'error' => $v['error'][$k], '@' => &$v['@'][$k], diff --git a/tests/Http/Request.files.directory.phpt b/tests/Http/Request.files.directory.phpt new file mode 100644 index 00000000..366c105a --- /dev/null +++ b/tests/Http/Request.files.directory.phpt @@ -0,0 +1,37 @@ + [ + 'name' => ['a.jpg', 'c.jpg'], + 'type' => ['image/jpeg', 'image/jpeg'], + 'full_path' => ['a.jpg', 'b/c.jpg'], + 'tmp_name' => ['C:\\PHP\\temp\\php1D5D.tmp', 'C:\\PHP\\temp\\php1D5E.tmp'], + 'error' => [0, 0], + 'size' => [12345, 54321], + ], +]; + +$factory = new Http\RequestFactory; +$request = $factory->fromGlobals(); + +Assert::type('array', $request->files['files']); +Assert::count(2, $request->files['files']); +Assert::type(Nette\Http\FileUpload::class, $request->files['files'][0]); +Assert::type(Nette\Http\FileUpload::class, $request->files['files'][1]); + +Assert::same('a.jpg', $request->files['files'][0]->getUntrustedFullPath()); +Assert::same('b/c.jpg', $request->files['files'][1]->getUntrustedFullPath()); From c93f7f23c42b9eacba6567783dfee436747bd2f0 Mon Sep 17 00:00:00 2001 From: website21cz <62235288+website21cz@users.noreply.github.com> Date: Wed, 31 Aug 2022 08:28:57 +0200 Subject: [PATCH 23/23] Dynamic set of cookiePath and cookieDomain Add possibility to use dynamic set cookiePath and cookieDomain --- src/Bridges/HttpDI/HttpExtension.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Bridges/HttpDI/HttpExtension.php b/src/Bridges/HttpDI/HttpExtension.php index b8f9d6df..a4786104 100644 --- a/src/Bridges/HttpDI/HttpExtension.php +++ b/src/Bridges/HttpDI/HttpExtension.php @@ -39,8 +39,12 @@ public function getConfigSchema(): Nette\Schema\Schema 'csp' => Expect::arrayOf('array|scalar|null'), // Content-Security-Policy 'cspReportOnly' => Expect::arrayOf('array|scalar|null'), // Content-Security-Policy-Report-Only 'featurePolicy' => Expect::arrayOf('array|scalar|null'), // Feature-Policy - 'cookiePath' => Expect::string(), - 'cookieDomain' => Expect::string(), + 'cookiePath' => Expect::anyOf( + Expect::string(), Expect::type(\Nette\DI\Definitions\Statement::class) + ), + 'cookieDomain' => Expect::anyOf( + Expect::string(), Expect::type(\Nette\DI\Definitions\Statement::class) + ), 'cookieSecure' => Expect::anyOf('auto', null, true, false)->firstIsDefault(), // Whether the cookie is available only through HTTPS 'disableNetteCookie' => Expect::bool(false), // disables cookie use by Nette ]);