diff --git a/.github/workflows/coding-style.yml b/.github/workflows/coding-style.yml index e86f4940..38aa5689 100644 --- a/.github/workflows/coding-style.yml +++ b/.github/workflows/coding-style.yml @@ -10,7 +10,7 @@ jobs: - uses: actions/checkout@v3 - uses: shivammathur/setup-php@v2 with: - php-version: 7.2 + php-version: 8.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 fcbf28d5..885c2817 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -13,7 +13,7 @@ jobs: - uses: actions/checkout@v3 - uses: shivammathur/setup-php@v2 with: - php-version: 7.4 + php-version: 8.0 coverage: none - run: composer install --no-progress --prefer-dist diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 382f4f27..a1844686 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -12,7 +12,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, windows-latest, macOS-latest] - php: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2'] + php: ['8.0', '8.1', '8.2'] sapi: ['php', 'php-cgi'] fail-fast: false @@ -42,7 +42,7 @@ jobs: - uses: actions/checkout@v3 - uses: shivammathur/setup-php@v2 with: - php-version: 7.2 + php-version: 8.0 coverage: none extensions: ${{ env.php-extensions }} @@ -57,7 +57,7 @@ jobs: - uses: actions/checkout@v3 - uses: shivammathur/setup-php@v2 with: - php-version: 7.4 + php-version: 8.0 coverage: none extensions: ${{ env.php-extensions }} diff --git a/composer.json b/composer.json index 01cfc80f..1610bbf1 100644 --- a/composer.json +++ b/composer.json @@ -15,13 +15,13 @@ } ], "require": { - "php": ">=7.2 <8.3", - "nette/utils": "^3.2.1 || ~4.0.0" + "php": ">=8.0 <8.3", + "nette/utils": "^3.2 || ^4.0" }, "require-dev": { - "nette/di": "^3.0", + "nette/di": "^3.1 || ^4.0", "nette/tester": "^2.4", - "nette/security": "^3.0", + "nette/security": "^4.0", "tracy/tracy": "^2.8", "phpstan/phpstan": "^1.0" }, @@ -42,7 +42,7 @@ }, "extra": { "branch-alias": { - "dev-master": "3.2-dev" + "dev-master": "4.0-dev" } } } diff --git a/readme.md b/readme.md index 859ff0ed..2760a75a 100644 --- a/readme.md +++ b/readme.md @@ -35,7 +35,7 @@ Installation composer require nette/http ``` -It requires PHP version 7.2 and supports PHP up to 8.2. +It requires PHP version 8.0 and supports PHP up to 8.2. HTTP Request diff --git a/src/Bridges/HttpDI/HttpExtension.php b/src/Bridges/HttpDI/HttpExtension.php index e58d2383..c419f40e 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) @@ -115,11 +114,11 @@ 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, ?)', - ["'nonce", "'nonce-", $value] + ["'nonce", "'nonce-", $value], ); } @@ -140,7 +139,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/Bridges/HttpDI/SessionExtension.php b/src/Bridges/HttpDI/SessionExtension.php index 1c0b598b..81485e6c 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) @@ -41,7 +38,7 @@ public function getConfigSchema(): Nette\Schema\Schema 'expiration' => Expect::string()->dynamic(), 'handler' => Expect::string()->dynamic(), 'readAndClose' => Expect::bool(), - 'cookieSamesite' => Expect::anyOf(IResponse::SameSiteLax, IResponse::SameSiteStrict, IResponse::SameSiteNone, true) + 'cookieSamesite' => Expect::anyOf(IResponse::SameSiteLax, IResponse::SameSiteStrict, IResponse::SameSiteNone) ->firstIsDefault(), ])->otherItems('mixed'); } @@ -67,16 +64,6 @@ public function loadConfiguration() $config->cookieDomain = $builder::literal('$this->getByType(Nette\Http\IRequest::class)->getUrl()->getDomain(2)'); } - if (isset($config->cookieSecure)) { - trigger_error("The item 'session\u{a0}›\u{a0}cookieSecure' is deprecated, use 'http\u{a0}›\u{a0}cookieSecure' (it has default value 'auto').", E_USER_DEPRECATED); - unset($config->cookieSecure); - } - - if ($config->cookieSamesite === true) { - trigger_error("In 'session\u{a0}›\u{a0}cookieSamesite' replace true with 'Lax'.", E_USER_DEPRECATED); - $config->cookieSamesite = IResponse::SameSiteLax; - } - $this->compiler->addExportedType(Nette\Http\IRequest::class); if ($this->debugMode && $config->debugger) { diff --git a/src/Http/Context.php b/src/Http/Context.php index 1f98655e..be3b50e6 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) @@ -35,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)); @@ -54,7 +50,7 @@ public function isModified($lastModified = null, ?string $etag = null): bool } 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/FileUpload.php b/src/Http/FileUpload.php index 20203d0d..a63fc0ed 100644 --- a/src/Http/FileUpload.php +++ b/src/Http/FileUpload.php @@ -29,28 +29,17 @@ final class FileUpload { use Nette\SmartObject; - public const ImageMimeTypes = ['image/gif', 'image/png', 'image/jpeg', 'image/webp']; + public const ImageMimeTypes = ['image/gif', 'image/png', 'image/jpeg', 'image/webp', 'image/avif']; /** @deprecated use FileUpload::ImageMimeTypes */ public const IMAGE_MIME_TYPES = self::ImageMimeTypes; - /** @var string */ - private $name; - - /** @var string|null */ - private $fullPath; - - /** @var string|false|null */ - private $type; - - /** @var int */ - private $size; - - /** @var string */ - private $tmpName; - - /** @var int */ - private $error; + private string $name; + private string|null $fullPath; + private string|false|null $type = null; + private int $size; + private string $tmpName; + private int $error; public function __construct(?array $value) @@ -75,6 +64,7 @@ public function __construct(?array $value) */ public function getName(): string { + trigger_error(__METHOD__ . '() is deprecated, use getUntrustedName()', E_USER_DEPRECATED); return $this->name; } @@ -193,9 +183,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); @@ -205,7 +194,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 5ff39703..807c8d64 100644 --- a/src/Http/Helpers.php +++ b/src/Http/Helpers.php @@ -23,15 +23,11 @@ final class Helpers /** @internal */ public const StrictCookieName = '_nss'; - /** @deprecated */ - public const STRICT_COOKIE_NAME = self::StrictCookieName; - /** * 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'); @@ -44,7 +40,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); @@ -58,6 +54,6 @@ public static function ipMatch(string $ip, string $mask): bool public static function initCookie(IRequest $request, IResponse $response) { - $response->setCookie(self::StrictCookieName, '1', 0, '/', null, null, true, IResponse::SameSiteStrict); + $response->setCookie(self::StrictCookieName, '1', 0, '/', sameSite: IResponse::SameSiteStrict); } } diff --git a/src/Http/IRequest.php b/src/Http/IRequest.php index 9c3fb8f7..434682c1 100644 --- a/src/Http/IRequest.php +++ b/src/Http/IRequest.php @@ -58,22 +58,19 @@ function getUrl(): UrlScript; /** * Returns variable provided to the script via URL query ($_GET). * If no key is passed, returns the entire array. - * @return mixed */ - function getQuery(?string $key = null); + function getQuery(?string $key = null): mixed; /** * Returns variable provided to the script via POST method ($_POST). * If no key is passed, returns the entire array. - * @return mixed */ - function getPost(?string $key = null); + function getPost(?string $key = null): mixed; /** * Returns uploaded file. - * @return FileUpload|array|null */ - function getFile(string $key); + function getFile(string $key): ?FileUpload; /** * Returns uploaded files. @@ -82,9 +79,8 @@ function getFiles(): array; /** * Returns variable provided to the script via HTTP cookies. - * @return mixed */ - function getCookie(string $key); + function getCookie(string $key): mixed; /** * Returns variables provided to the script via HTTP cookies. diff --git a/src/Http/IResponse.php b/src/Http/IResponse.php index 0cbd2754..e92160ce 100644 --- a/src/Http/IResponse.php +++ b/src/Http/IResponse.php @@ -12,16 +12,9 @@ /** * HTTP response interface. - * @method self deleteHeader(string $name) */ interface IResponse { - /** @deprecated */ - public const PERMANENT = 2116333333; - - /** @deprecated */ - public const BROWSER = 0; - /** HTTP 1.1 response code */ public const S100_Continue = 100, @@ -343,9 +336,8 @@ interface IResponse /** * Sets HTTP response code. - * @return static */ - function setCode(int $code, ?string $reason = null); + function setCode(int $code, ?string $reason = null): static; /** * Returns HTTP response code. @@ -354,21 +346,23 @@ 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; + + /** + * Deletes a previously sent HTTP header. + */ + function deleteHeader(string $name): static; /** * Sends a Content-type HTTP header. - * @return static */ - function setContentType(string $type, ?string $charset = null); + function setContentType(string $type, ?string $charset = null): static; /** * Redirects to a new URL. @@ -377,9 +371,8 @@ function redirect(string $url, int $code = self::S302_Found): void; /** * Sets the time (like '20 minutes') before a page cached on a browser expires, null means "must-revalidate". - * @return static */ - function setExpiration(?string $expire); + function setExpiration(?string $expire): static; /** * Checks if headers have been sent. @@ -398,21 +391,25 @@ 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, + string|int|null $expire, ?string $path = null, ?string $domain = null, - ?bool $secure = null, - ?bool $httpOnly = null - ); + bool $secure = false, + bool $httpOnly = true, + string $sameSite = self::SAME_SITE_LAX, + ): 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 = false, + ); } diff --git a/src/Http/Request.php b/src/Http/Request.php index c16b7551..e62c916e 100644 --- a/src/Http/Request.php +++ b/src/Http/Request.php @@ -33,62 +33,32 @@ class Request implements IRequest { use Nette\SmartObject; - /** @var string */ - private $method; + private array $headers; - /** @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 */ + /** @var ?callable */ private $rawBodyCallback; public function __construct( - UrlScript $url, - ?array $post = null, - ?array $files = null, - ?array $cookies = null, - ?array $headers = null, - ?string $method = null, - ?string $remoteAddress = null, - ?string $remoteHost = null, - ?callable $rawBodyCallback = null + private UrlScript $url, + private array $post = [], + private array $files = [], + private array $cookies = [], + array $headers = [], + private string $method = 'GET', + private ?string $remoteAddress = null, + private ?string $remoteHost = null, + ?callable $rawBodyCallback = null, ) { - $this->url = $url; - $this->post = (array) $post; - $this->files = (array) $files; - $this->cookies = (array) $cookies; - $this->headers = array_change_key_case((array) $headers, CASE_LOWER); - $this->method = $method ?: 'GET'; - $this->remoteAddress = $remoteAddress; - $this->remoteHost = $remoteHost; + $this->headers = array_change_key_case($headers, CASE_LOWER); $this->rawBodyCallback = $rawBodyCallback; } /** * 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; @@ -111,14 +81,11 @@ 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(); - } elseif (func_num_args() > 1) { - trigger_error(__METHOD__ . '() parameter $default is deprecated, use operator ??', E_USER_DEPRECATED); } return $this->url->getQueryParameter($key); @@ -128,14 +95,11 @@ 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; - } elseif (func_num_args() > 1) { - trigger_error(__METHOD__ . '() parameter $default is deprecated, use operator ??', E_USER_DEPRECATED); } return $this->post[$key] ?? null; @@ -145,9 +109,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; @@ -165,14 +128,9 @@ 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); - } - return $this->cookies[$key] ?? null; } @@ -212,10 +170,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; } @@ -236,6 +190,7 @@ public function getHeaders(): array */ public function getReferer(): ?UrlImmutable { + trigger_error(__METHOD__ . '() is deprecated', E_USER_DEPRECATED); return isset($this->headers['referer']) ? new UrlImmutable($this->headers['referer']) : null; @@ -299,10 +254,6 @@ public function getRemoteAddress(): ?string */ public function getRemoteHost(): ?string { - if ($this->remoteHost === null && $this->remoteAddress !== null) { - $this->remoteHost = gethostbyaddr($this->remoteAddress); - } - return $this->remoteHost; } @@ -316,6 +267,23 @@ public function getRawBody(): ?string } + /** + * Returns decoded content of HTTP request body. + */ + public function getBody(): mixed + { + $type = $this->getHeader('Content-Type'); + switch ($type) { + case 'application/json': + return json_decode($this->getRawBody()); + case 'application/x-www-form-urlencoded': + return $_POST; + default: + throw new \Exception("Unsupported content type: $type"); + } + } + + /** * Returns basic HTTP authentication credentials. * @return array{string, string}|null @@ -325,7 +293,7 @@ public function getBasicCredentials(): ?array return preg_match( '~^Basic (\S+)$~', $this->headers['authorization'] ?? '', - $t + $t, ) && ($t = base64_decode($t[1], true)) && ($t = explode(':', $t, 2)) diff --git a/src/Http/RequestFactory.php b/src/Http/RequestFactory.php index 125a8cc9..7f4ee960 100644 --- a/src/Http/RequestFactory.php +++ b/src/Http/RequestFactory.php @@ -24,21 +24,18 @@ 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 */ - public function setBinary(bool $binary = true) + public function setBinary(bool $binary = true): static { $this->binary = $binary; return $this; @@ -47,9 +44,8 @@ public function setBinary(bool $binary = true) /** * @param string|string[] $proxy - * @return static */ - public function setProxy($proxy) + public function setProxy($proxy): static { $this->proxies = (array) $proxy; return $this; @@ -76,9 +72,7 @@ public function fromGlobals(): Request $this->getMethod(), $remoteAddr, $remoteHost, - function (): string { - return file_get_contents('php://input'); - } + fn(): string => file_get_contents('php://input') ); } @@ -165,7 +159,7 @@ private function getGetPostCookie(Url $url): array $list[$key][$k] = (string) preg_replace('#[^' . self::ValidChars . ']+#u', '', $v); } else { - throw new Nette\InvalidStateException(sprintf('Invalid value in $_POST/$_COOKIE in key %s, expected string, %s given.', "'$k'", gettype($v))); + throw new Nette\InvalidStateException(sprintf('Invalid value in $_POST/$_COOKIE in key %s, expected string, %s given.', "'$k'", get_debug_type($v))); } } } @@ -279,14 +273,10 @@ private function getMethod(): string private function getClient(Url $url): array { - $remoteAddr = !empty($_SERVER['REMOTE_ADDR']) - ? trim($_SERVER['REMOTE_ADDR'], '[]') // workaround for PHP 7.3.0 - : null; + $remoteAddr = !empty($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null; // use real client address and host if trusted proxy is used - $usingTrustedProxy = $remoteAddr && Arrays::some($this->proxies, function (string $proxy) use ($remoteAddr): bool { - return Helpers::ipMatch($remoteAddr, $proxy); - }); + $usingTrustedProxy = $remoteAddr && Arrays::some($this->proxies, fn(string $proxy): bool => Helpers::ipMatch($remoteAddr, $proxy)); if ($usingTrustedProxy) { $remoteHost = null; $remoteAddr = empty($_SERVER['HTTP_FORWARDED']) @@ -311,9 +301,9 @@ private function useForwardedProxy(Url $url): ?string if (isset($proxyParams['for'])) { $address = $proxyParams['for'][0]; - $remoteAddr = strpos($address, '[') === false - ? explode(':', $address)[0] // IPv4 - : substr($address, 1, strpos($address, ']') - 1); // IPv6 + $remoteAddr = str_contains($address, '[') + ? substr($address, 1, strpos($address, ']') - 1) // IPv6 + : explode(':', $address)[0]; // IPv4 } if (isset($proxyParams['proto']) && count($proxyParams['proto']) === 1) { @@ -355,12 +345,11 @@ private function useNonstandardProxy(Url $url): ?string } if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { - $xForwardedForWithoutProxies = array_filter(explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']), function (string $ip): bool { - return filter_var(trim($ip), FILTER_VALIDATE_IP) === false || - !Arrays::some($this->proxies, function (string $proxy) use ($ip): bool { - return Helpers::ipMatch(trim($ip), $proxy); - }); - }); + $xForwardedForWithoutProxies = array_filter( + explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']), + fn(string $ip): bool => filter_var($ip = trim($ip), FILTER_VALIDATE_IP) === false + || !Arrays::some($this->proxies, fn(string $proxy): bool => Helpers::ipMatch($ip, $proxy)), + ); if ($xForwardedForWithoutProxies) { $remoteAddr = trim(end($xForwardedForWithoutProxies)); $xForwardedForRealIpKey = key($xForwardedForWithoutProxies); @@ -378,9 +367,10 @@ private function useNonstandardProxy(Url $url): ?string } - /** @deprecated */ + /** @deprecated use fromGlobals() */ public function createHttpRequest(): Request { + trigger_error(__METHOD__ . '() is deprecated, use fromGlobals()', E_USER_DEPRECATED); return $this->fromGlobals(); } } diff --git a/src/Http/Response.php b/src/Http/Response.php index c7d6c6ce..484fa026 100644 --- a/src/Http/Response.php +++ b/src/Http/Response.php @@ -22,26 +22,23 @@ 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; + /** Whether warn on possible problem with data in output buffer */ + public bool $warnOnBuffer = true; - /** @var bool Whether warn on possible problem with data in output buffer */ - public $warnOnBuffer = true; + /** Send invisible garbage for IE 6? */ + private static bool $fixIE = true; - /** @var bool Send invisible garbage for IE 6? */ - private static $fixIE = true; - - /** @var int HTTP response code */ - private $code = self::S200_OK; + /** HTTP response code */ + private int $code = self::S200_OK; public function __construct() @@ -54,11 +51,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'."); @@ -67,7 +63,7 @@ public function setCode(int $code, ?string $reason = null) self::checkHeaders(); $this->code = $code; $protocol = $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.1'; - $reason = $reason ?? self::ReasonPhrases[$code] ?? 'Unknown status'; + $reason ??= self::ReasonPhrases[$code] ?? 'Unknown status'; header("$protocol $code $reason"); return $this; } @@ -84,10 +80,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 +99,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 +111,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 +123,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,15 +134,14 @@ 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', 'attachment; filename="' . str_replace('"', '', $fileName) . '"; ' - . "filename*=utf-8''" . rawurlencode($fileName) + . "filename*=utf-8''" . rawurlencode($fileName), ); return $this; } @@ -174,10 +165,9 @@ public function redirect(string $url, int $code = self::S302_Found): void /** * Sets the expiration of the HTTP document using the `Cache-Control` and `Expires` headers. * The parameter is either a time interval (as text) or `null`, which disables caching. - * @return static * @throws Nette\InvalidStateException if HTTP headers have been sent */ - public function setExpiration(?string $expire) + public function setExpiration(?string $expire): static { $this->setHeader('Pragma', null); if (!$expire) { // no cache @@ -208,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) { @@ -243,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')) ) { @@ -255,43 +241,28 @@ public function __destruct() /** * Sends a cookie. - * @param string|int|\DateTimeInterface $expire expiration time, value null means "until the browser session ends" - * @return static * @throws Nette\InvalidStateException if HTTP headers have been sent */ public function setCookie( string $name, string $value, - $expire, + string|int|null $expire, ?string $path = null, ?string $domain = null, ?bool $secure = null, - ?bool $httpOnly = null, - ?string $sameSite = null - ) { + bool $httpOnly = true, + string $sameSite = self::SameSiteLax, + ): static + { self::checkHeaders(); - $options = [ + setcookie($name, $value, [ 'expires' => $expire ? (int) DateTime::from($expire)->format('U') : 0, 'path' => $path ?? ($domain ? '/' : $this->cookiePath), 'domain' => $domain ?? ($path ? '' : $this->cookieDomain), 'secure' => $secure ?? $this->cookieSecure, - 'httponly' => $httpOnly ?? true, - 'samesite' => $sameSite = ($sameSite ?? self::SameSiteLax), - ]; - if (PHP_VERSION_ID >= 70300) { - setcookie($name, $value, $options); - } else { - setcookie( - $name, - $value, - $options['expires'], - $options['path'] . ($sameSite ? "; SameSite=$sameSite" : ''), - $options['domain'], - $options['secure'], - $options['httponly'] - ); - } - + 'httponly' => $httpOnly, + 'samesite' => $sameSite, + ]); return $this; } @@ -300,7 +271,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); } @@ -315,7 +291,7 @@ private function checkHeaders(): void } elseif ( $this->warnOnBuffer && ob_get_length() && - !array_filter(ob_get_status(true), function (array $i): bool { return !$i['chunk_size']; }) + !array_filter(ob_get_status(true), fn(array $i): bool => !$i['chunk_size']) ) { trigger_error('Possible problem: you are sending a HTTP header while already having some data in output buffer. Try Tracy\OutputDebugger or send cookies/start session earlier.'); } diff --git a/src/Http/Session.php b/src/Http/Session.php index ed705e5f..0b4d9e34 100644 --- a/src/Http/Session.php +++ b/src/Http/Session.php @@ -32,41 +32,27 @@ class Session ]; /** @var array Occurs when the session is started */ - public $onStart = []; + public array $onStart = []; /** @var array Occurs before the session is written to disk */ - public $onBeforeWrite = []; + public array $onBeforeWrite = []; - /** @var bool has been session ID regenerated? */ - private $regenerated = false; + private bool $regenerated = false; + private bool $started = false; - /** @var bool has been session started by Nette? */ - private $started = false; - - /** @var array default configuration */ - private $options = [ + /** default configuration */ + private array $options = [ 'cookie_samesite' => IResponse::SameSiteLax, 'cookie_lifetime' => 0, // for a maximum of 3 hours or until the browser is closed 'gc_maxlifetime' => self::DefaultFileLifetime, // 3 hours ]; - /** @var IRequest */ - private $request; - - /** @var IResponse */ - private $response; - - /** @var \SessionHandlerInterface */ - private $handler; - - /** @var bool */ - private $readAndClose = false; - - /** @var bool */ - private $fileExists = true; - - /** @var bool */ - private $autoStart = true; + private IRequest $request; + private IResponse $response; + private ?\SessionHandlerInterface $handler = null; + private bool $readAndClose = false; + private bool $fileExists = true; + private bool $autoStart = true; public function __construct(IRequest $request, IResponse $response) @@ -117,7 +103,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) { } @@ -288,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.'); @@ -317,7 +302,9 @@ public function getName(): string /** * Returns specified session section. - * @throws Nette\InvalidArgumentException + * @template T of SessionSection + * @param class-string $class + * @return T */ public function getSection(string $section, string $class = SessionSection::class): SessionSection { @@ -349,14 +336,6 @@ public function getSectionNames(): array } - /** @deprecated use getSectionNames() */ - public function getIterator(): \Iterator - { - trigger_error(__METHOD__ . '() is deprecated', E_USER_DEPRECATED); - return new \ArrayIterator($this->getSectionNames()); - } - - /** * Cleans and minimizes meta structures. */ @@ -369,7 +348,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]); } @@ -382,14 +373,13 @@ private function clean(): void /** * Sets session options. - * @return static * @throws Nette\NotSupportedException * @throws Nette\InvalidStateException */ - public function setOptions(array $options) + public function setOptions(array $options): static { $normalized = []; - $allowed = ini_get_all('session', 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 @@ -401,7 +391,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'?" : '.')); @@ -473,17 +463,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(); @@ -499,9 +479,8 @@ private function configure(array $config): void /** * Sets the amount of time (like '20 minutes') allowed between requests before the session will be terminated, * null means "for a maximum of 3 hours or until the browser is closed". - * @return static */ - public function setExpiration(?string $expire) + public function setExpiration(?string $expire): static { if ($expire === null) { return $this->setOptions([ @@ -521,14 +500,14 @@ public function setExpiration(?string $expire) /** * Sets the session cookie parameters. - * @return static */ public function setCookieParameters( string $path, ?string $domain = null, ?bool $secure = null, - ?string $sameSite = null - ) { + ?string $sameSite = null, + ): static + { return $this->setOptions([ 'cookie_path' => $path, 'cookie_domain' => $domain, @@ -538,19 +517,10 @@ 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. - * @return static */ - public function setSavePath(string $path) + public function setSavePath(string $path): static { return $this->setOptions([ 'save_path' => $path, @@ -560,9 +530,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.'); @@ -587,7 +556,7 @@ private function sendCookie(): void $cookie['domain'], $cookie['secure'], $cookie['httponly'], - $cookie['samesite'] ?? null + $cookie['samesite'] ?? null, ); } } diff --git a/src/Http/SessionSection.php b/src/Http/SessionSection.php index 8c98b9eb..c9cdcfb2 100644 --- a/src/Http/SessionSection.php +++ b/src/Http/SessionSection.php @@ -19,14 +19,8 @@ class SessionSection implements \IteratorAggregate, \ArrayAccess { use Nette\SmartObject; - /** @var bool */ - public $warnOnUndefined = false; - - /** @var Session */ - private $session; - - /** @var string */ - private $name; + private Session $session; + private string $name; /** @@ -51,9 +45,8 @@ public function getIterator(): \Iterator /** * Sets a variable in this session section. - * @param mixed $value */ - public function set(string $name, $value, ?string $expire = null): void + public function set(string $name, mixed $value, ?string $expire = null): void { if ($value === null) { $this->remove($name); @@ -67,9 +60,8 @@ public function set(string $name, $value, ?string $expire = null): void /** * Gets a variable from this session section. - * @return mixed */ - public function get(string $name) + public function get(string $name): mixed { if (func_num_args() > 1) { throw new \ArgumentCountError(__METHOD__ . '() expects 1 arguments, given more.'); @@ -84,7 +76,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) { @@ -108,6 +100,7 @@ public function remove($name = null): void */ public function __set(string $name, $value): void { + trigger_error("Writing to \$session->$name is deprecated, use \$session->set('$name', \$value) instead", E_USER_DEPRECATED); $this->session->autoStart(true); $this->getData()[$name] = $value; } @@ -117,14 +110,11 @@ public function __set(string $name, $value): void * Gets a variable from this session section. * @deprecated use get() instead */ - public function &__get(string $name) + public function &__get(string $name): mixed { + trigger_error("Reading from \$session->$name is deprecated, use \$session->get('$name') instead", E_USER_DEPRECATED); $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]; } @@ -135,6 +125,7 @@ public function &__get(string $name) */ public function __isset(string $name): bool { + trigger_error("Using \$session->$name is deprecated, use \$session->get('$name') instead", E_USER_DEPRECATED); $this->session->autoStart(false); return isset($this->getData()[$name]); } @@ -146,6 +137,7 @@ public function __isset(string $name): bool */ public function __unset(string $name): void { + trigger_error("Unset(\$session->$name) is deprecated, use \$session->remove('$name') instead", E_USER_DEPRECATED); $this->remove($name); } @@ -156,6 +148,7 @@ public function __unset(string $name): void */ public function offsetSet($name, $value): void { + trigger_error("Writing to \$session['$name'] is deprecated, use \$session->set('$name', \$value) instead", E_USER_DEPRECATED); $this->__set($name, $value); } @@ -164,9 +157,9 @@ public function offsetSet($name, $value): void * Gets a variable from this session section. * @deprecated use get() instead */ - #[\ReturnTypeWillChange] - public function offsetGet($name) + public function offsetGet($name): mixed { + trigger_error("Reading from \$session['$name'] is deprecated, use \$session->get('$name') instead", E_USER_DEPRECATED); return $this->get($name); } @@ -177,6 +170,7 @@ public function offsetGet($name) */ public function offsetExists($name): bool { + trigger_error("Using \$session['$name'] is deprecated, use \$session->get('$name') instead", E_USER_DEPRECATED); return $this->__isset($name); } @@ -187,17 +181,16 @@ public function offsetExists($name): bool */ public function offsetUnset($name): void { + trigger_error("Unset(\$session['$name']) is deprecated, use \$session->remove('$name') instead", E_USER_DEPRECATED); $this->remove($name); } /** * Sets the expiration of the section or specific variables. - * @param ?string $expire * @param string|string[]|null $variables list of variables / single variable to expire - * @return static */ - public function setExpiration($expire, $variables = null) + public function setExpiration(?string $expire, string|array|null $variables = null): static { $this->session->autoStart((bool) $expire); $meta = &$this->getMeta(); @@ -224,7 +217,7 @@ public function setExpiration($expire, $variables = null) * Removes the expiration from the section or specific variables. * @param string|string[]|null $variables list of variables / single variable to expire */ - public function removeExpiration($variables = null): void + public function removeExpiration(string|array|null $variables = null): void { $this->setExpiration(null, $variables); } diff --git a/src/Http/Url.php b/src/Http/Url.php index 351ba5fe..2c8f2585 100644 --- a/src/Http/Url.php +++ b/src/Http/Url.php @@ -45,43 +45,26 @@ 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 = ''; /** - * @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 @@ -100,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; @@ -121,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; @@ -135,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; @@ -149,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); @@ -179,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; @@ -199,11 +174,10 @@ public function getDefaultPort(): ?int } - /** @return static */ - public function setPath(string $path) + public function setPath(string $path): static { $this->path = $path; - if ($this->host && substr($this->path, 0, 1) !== '/') { + if ($this->host && !str_starts_with($this->path, '/')) { $this->path = '/' . $this->path; } @@ -217,22 +191,14 @@ public function getPath(): string } - /** - * @param string|array $value - * @return static - */ - public function setQuery($query) + public function setQuery(string|array $query): static { $this->query = is_array($query) ? $query : self::parseQuery($query); return $this; } - /** - * @param string|array $value - * @return static - */ - public function appendQuery($query) + public function appendQuery(string|array $query): static { $this->query = is_array($query) ? $query + $this->query @@ -253,30 +219,20 @@ 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); - } - return $this->query[$name] ?? null; } - /** - * @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; @@ -327,6 +283,7 @@ public function getHostUrl(): string /** @deprecated use UrlScript::getBasePath() instead */ public function getBasePath(): string { + trigger_error(__METHOD__ . '() is deprecated, use UrlScript object', E_USER_DEPRECATED); $pos = strrpos($this->path, '/'); return $pos === false ? '' : substr($this->path, 0, $pos + 1); } @@ -335,6 +292,7 @@ public function getBasePath(): string /** @deprecated use UrlScript::getBaseUrl() instead */ public function getBaseUrl(): string { + trigger_error(__METHOD__ . '() is deprecated, use UrlScript object', E_USER_DEPRECATED); return $this->getHostUrl() . $this->getBasePath(); } @@ -342,15 +300,15 @@ public function getBaseUrl(): string /** @deprecated use UrlScript::getRelativeUrl() instead */ public function getRelativeUrl(): string { + trigger_error(__METHOD__ . '() is deprecated, use UrlScript object', E_USER_DEPRECATED); return substr($this->getAbsoluteUrl(), strlen($this->getBaseUrl())); } /** * URL comparison. - * @param string|self $url */ - public function isEqual($url): bool + public function isEqual(string|self|UrlImmutable $url): bool { $url = new self($url); $query = $url->query; @@ -373,15 +331,13 @@ 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( '#[^!$&\'()*+,/:;=@%]+#', - function (array $m): string { return rawurlencode($m[0]); }, - self::unescape($this->path, '%/') + fn(array $m): string => rawurlencode($m[0]), + self::unescape($this->path, '%/'), ); $this->host = rtrim($this->host, '.'); $this->host = self::idnHostToUnicode(strtolower($this->host)); @@ -413,7 +369,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; } @@ -436,8 +392,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/src/Http/UrlImmutable.php b/src/Http/UrlImmutable.php index 2ab4c926..ea9e85b8 100644 --- a/src/Http/UrlImmutable.php +++ b/src/Http/UrlImmutable.php @@ -42,52 +42,29 @@ 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 = ''; /** - * @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; @@ -102,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; @@ -118,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; @@ -134,8 +109,7 @@ public function getPassword(): string } - /** @return static */ - public function withoutUserInfo() + public function withoutUserInfo(): static { $dolly = clone $this; $dolly->user = $dolly->password = ''; @@ -144,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; @@ -172,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; @@ -194,8 +166,7 @@ public function getDefaultPort(): ?int } - /** @return static */ - public function withPath(string $path) + public function withPath(string $path): static { $dolly = clone $this; $dolly->path = $path; @@ -210,11 +181,7 @@ public function getPath(): string } - /** - * @param string|array $query - * @return static - */ - public function withQuery($query) + public function withQuery(string|array $query): static { $dolly = clone $this; $dolly->query = is_array($query) ? $query : Url::parseQuery($query); @@ -229,11 +196,7 @@ public function getQuery(): string } - /** - * @param mixed $value null unsets the parameter - * @return static - */ - public function withQueryParameter(string $name, $value) + public function withQueryParameter(string $name, mixed $value): static { $dolly = clone $this; $dolly->query[$name] = $value; @@ -247,15 +210,13 @@ public function getQueryParameters(): array } - /** @return array|string|null */ - public function getQueryParameter(string $name) + public function getQueryParameter(string $name): array|string|null { return $this->query[$name] ?? null; } - /** @return static */ - public function withFragment(string $fragment) + public function withFragment(string $fragment): static { $dolly = clone $this; $dolly->fragment = $fragment; @@ -306,10 +267,7 @@ public function __toString(): string } - /** - * @param string|Url|self $url - */ - public function isEqual($url): bool + public function isEqual(string|Url|self $url): bool { return (new Url($this))->isEqual($url); } @@ -330,7 +288,7 @@ final public function export(): array protected function build(): void { - if ($this->host && substr($this->path, 0, 1) !== '/') { + if ($this->host && !str_starts_with($this->path, '/')) { $this->path = '/' . $this->path; } diff --git a/src/Http/UrlScript.php b/src/Http/UrlScript.php index bcae5669..7b02ea73 100644 --- a/src/Http/UrlScript.php +++ b/src/Http/UrlScript.php @@ -34,23 +34,19 @@ */ class UrlScript extends UrlImmutable { - /** @var string */ - private $scriptPath; + private string $scriptPath; + private string $basePath; - /** @var string */ - private $basePath; - - public function __construct($url = '/', string $scriptPath = '') + public function __construct(string|Url $url = '/', string $scriptPath = '') { - parent::__construct($url); $this->scriptPath = $scriptPath; + parent::__construct($url); $this->build(); } - /** @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 deleted file mode 100644 index d04b985a..00000000 --- a/src/Http/UserStorage.php +++ /dev/null @@ -1,188 +0,0 @@ -sessionHandler = $sessionHandler; - } - - - /** - * Sets the authenticated status of this user. - * @return static - */ - public function setAuthenticated(bool $state) - { - $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. - * @return static - */ - public function setIdentity(?IIdentity $identity) - { - $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. - * @return static - */ - public function setNamespace(string $namespace) - { - 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. - * @return static - */ - public function setExpiration(?string $time, int $flags = 0) - { - $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; - } -} diff --git a/tests/Http.DI/HttpExtension.csp.phpt b/tests/Http.DI/HttpExtension.csp.phpt index 642e7b27..872c50cf 100644 --- a/tests/Http.DI/HttpExtension.csp.phpt +++ b/tests/Http.DI/HttpExtension.csp.phpt @@ -21,26 +21,25 @@ $compiler = new DI\Compiler; $compiler->addExtension('http', new HttpExtension); $loader = new DI\Config\Loader; $config = $loader->load(Tester\FileMock::create(<<<'EOD' -http: - csp: - default-src: "'self' https://example.com" - upgrade-insecure-requests: - script-src: 'nonce' - style-src: - - self - - https://example.com - - http: - require-sri-for: style - sandbox: allow-forms - plugin-types: application/x-java-applet - - cspReportOnly: - default-src: "'nonce'" - report-uri: https://example.com/report - upgrade-insecure-requests: true - block-all-mixed-content: false -EOD - , 'neon')); + http: + csp: + default-src: "'self' https://example.com" + upgrade-insecure-requests: + script-src: 'nonce' + style-src: + - self + - https://example.com + - http: + require-sri-for: style + sandbox: allow-forms + plugin-types: application/x-java-applet + + cspReportOnly: + default-src: "'nonce'" + report-uri: https://example.com/report + upgrade-insecure-requests: true + block-all-mixed-content: false + EOD, 'neon')); eval($compiler->addConfig($config)->compile()); @@ -49,7 +48,7 @@ $container->initialize(); $headers = headers_list(); -preg_match('#nonce-([\w+/]+=*)#', implode($headers), $nonce); +preg_match('#nonce-([\w+/]+=*)#', implode('', $headers), $nonce); Assert::contains("Content-Security-Policy: default-src 'self' https://example.com; upgrade-insecure-requests; script-src 'nonce-$nonce[1]'; style-src 'self' https://example.com http:; require-sri-for style; sandbox allow-forms; plugin-types application/x-java-applet;", $headers); Assert::contains("Content-Security-Policy-Report-Only: default-src 'nonce-$nonce[1]'; report-uri https://example.com/report; upgrade-insecure-requests;", $headers); diff --git a/tests/Http.DI/HttpExtension.featurePolicy.phpt b/tests/Http.DI/HttpExtension.featurePolicy.phpt index a5c6096b..74f4f3c2 100644 --- a/tests/Http.DI/HttpExtension.featurePolicy.phpt +++ b/tests/Http.DI/HttpExtension.featurePolicy.phpt @@ -21,15 +21,14 @@ $compiler = new DI\Compiler; $compiler->addExtension('http', new HttpExtension); $loader = new DI\Config\Loader; $config = $loader->load(Tester\FileMock::create(<<<'EOD' -http: - featurePolicy: - unsized-media: none - geolocation: - - self - - https://example.com - camera: * -EOD - , 'neon')); + http: + featurePolicy: + unsized-media: none + geolocation: + - self + - https://example.com + camera: * + EOD, 'neon')); eval($compiler->addConfig($config)->compile()); diff --git a/tests/Http.DI/HttpExtension.headers.phpt b/tests/Http.DI/HttpExtension.headers.phpt index cfa9f85a..3e98e711 100644 --- a/tests/Http.DI/HttpExtension.headers.phpt +++ b/tests/Http.DI/HttpExtension.headers.phpt @@ -21,13 +21,12 @@ $compiler = new DI\Compiler; $compiler->addExtension('http', new HttpExtension); $loader = new DI\Config\Loader; $config = $loader->load(Tester\FileMock::create(<<<'EOD' -http: - headers: - A: b - C: - D: 0 -EOD - , 'neon')); + http: + headers: + A: b + C: + D: 0 + EOD, 'neon')); eval($compiler->addConfig($config)->compile()); diff --git a/tests/Http.DI/HttpExtension.sameSiteProtection.disabled.phpt b/tests/Http.DI/HttpExtension.sameSiteProtection.disabled.phpt index 5fc6fc70..b18b557e 100644 --- a/tests/Http.DI/HttpExtension.sameSiteProtection.disabled.phpt +++ b/tests/Http.DI/HttpExtension.sameSiteProtection.disabled.phpt @@ -17,10 +17,9 @@ $compiler = new DI\Compiler; $compiler->addExtension('http', new HttpExtension); $loader = new DI\Config\Loader; $config = $loader->load(Tester\FileMock::create(<<<'EOD' -http: - disableNetteCookie: yes -EOD - , 'neon')); + http: + disableNetteCookie: yes + EOD, 'neon')); eval($compiler->addConfig($config)->compile()); diff --git a/tests/Http.DI/HttpExtension.sameSiteProtection.phpt b/tests/Http.DI/HttpExtension.sameSiteProtection.phpt index d93613b1..f6de3332 100644 --- a/tests/Http.DI/HttpExtension.sameSiteProtection.phpt +++ b/tests/Http.DI/HttpExtension.sameSiteProtection.phpt @@ -27,5 +27,5 @@ Assert::contains( PHP_VERSION_ID >= 70300 ? 'Set-Cookie: _nss=1; path=/; HttpOnly; SameSite=Strict' : 'Set-Cookie: _nss=1; path=/; SameSite=Strict; HttpOnly', - $headers + $headers, ); diff --git a/tests/Http.DI/SessionExtension.config.phpt b/tests/Http.DI/SessionExtension.config.phpt index 78ad21c5..36bb11a8 100644 --- a/tests/Http.DI/SessionExtension.config.phpt +++ b/tests/Http.DI/SessionExtension.config.phpt @@ -42,7 +42,7 @@ Assert::same( PHP_VERSION_ID >= 70300 ? ['lifetime' => 0, 'path' => '/x', 'domain' => 'nette.org', 'secure' => true, 'httponly' => true, 'samesite' => 'Lax'] : ['lifetime' => 0, 'path' => '/x; SameSite=Lax', 'domain' => 'nette.org', 'secure' => true, 'httponly' => true], - session_get_cookie_params() + session_get_cookie_params(), ); // readAndClose diff --git a/tests/Http.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/FileUpload.basic.phpt b/tests/Http/FileUpload.basic.phpt index 573568af..abb7098e 100644 --- a/tests/Http/FileUpload.basic.phpt +++ b/tests/Http/FileUpload.basic.phpt @@ -22,7 +22,7 @@ test('', function () { 'size' => 209, ]); - Assert::same('readme.txt', $upload->getName()); + Assert::same('readme.txt', @$upload->getName()); // deprecated Assert::same('readme.txt', $upload->getUntrustedName()); Assert::same('readme.txt', $upload->getSanitizedName()); Assert::same('path/to/readme.txt', $upload->getUntrustedFullPath()); @@ -47,7 +47,7 @@ test('', function () { 'size' => 209, ]); - Assert::same('../.image.png', $upload->getName()); + Assert::same('../.image.png', $upload->getUntrustedName()); Assert::same('image.png', $upload->getSanitizedName()); Assert::same('../.image.png', $upload->getUntrustedFullPath()); Assert::same('image/png', $upload->getContentType()); diff --git a/tests/Http/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.detectLanguage.phpt b/tests/Http/Request.detectLanguage.phpt index 0441d469..c1dea634 100644 --- a/tests/Http/Request.detectLanguage.phpt +++ b/tests/Http/Request.detectLanguage.phpt @@ -14,7 +14,7 @@ require __DIR__ . '/../bootstrap.php'; test('', function () { $headers = ['Accept-Language' => 'en, cs']; - $request = new Http\Request(new Http\UrlScript, null, null, null, $headers); + $request = new Http\Request(new Http\UrlScript, headers: $headers); Assert::same('en', $request->detectLanguage(['en', 'cs'])); Assert::same('en', $request->detectLanguage(['cs', 'en'])); @@ -24,7 +24,7 @@ test('', function () { test('', function () { $headers = ['Accept-Language' => 'da, en-gb;q=0.8, en;q=0.7']; - $request = new Http\Request(new Http\UrlScript, null, null, null, $headers); + $request = new Http\Request(new Http\UrlScript, headers: $headers); Assert::same('en-gb', $request->detectLanguage(['en', 'en-gb'])); Assert::same('en', $request->detectLanguage(['en'])); @@ -33,7 +33,7 @@ test('', function () { test('', function () { $headers = []; - $request = new Http\Request(new Http\UrlScript, null, null, null, $headers); + $request = new Http\Request(new Http\UrlScript, headers: $headers); Assert::null($request->detectLanguage(['en'])); }); @@ -41,7 +41,7 @@ test('', function () { test('', function () { $headers = ['Accept-Language' => 'garbage']; - $request = new Http\Request(new Http\UrlScript, null, null, null, $headers); + $request = new Http\Request(new Http\UrlScript, headers: $headers); Assert::null($request->detectLanguage(['en'])); }); diff --git a/tests/Http/Request.getOrigin.phpt b/tests/Http/Request.getOrigin.phpt index 380e12d6..febebb89 100644 --- a/tests/Http/Request.getOrigin.phpt +++ b/tests/Http/Request.getOrigin.phpt @@ -16,7 +16,7 @@ test('missing origin', function () { test('opaque origin', function () { - $request = new Http\Request(new Http\UrlScript, null, null, null, [ + $request = new Http\Request(new Http\UrlScript, headers: [ 'Origin' => 'null', ]); Assert::null($request->getOrigin()); @@ -24,7 +24,7 @@ test('opaque origin', function () { test('normal origin', function () { - $request = new Http\Request(new Http\UrlScript, null, null, null, [ + $request = new Http\Request(new Http\UrlScript, headers: [ 'Origin' => 'https://nette.org', ]); Assert::equal(new UrlImmutable('https://nette.org'), $request->getOrigin()); diff --git a/tests/Http/Request.getRawBody.phpt b/tests/Http/Request.getRawBody.phpt index 16be3064..9d2f556f 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, rawBodyCallback: fn() => 'raw body'); Assert::same('raw body', $request->getRawBody()); }); diff --git a/tests/Http/Request.headers.phpt b/tests/Http/Request.headers.phpt index 947d76ee..7c607151 100644 --- a/tests/Http/Request.headers.phpt +++ b/tests/Http/Request.headers.phpt @@ -18,12 +18,12 @@ test('', function () { }); test('', function () { - $request = new Http\Request(new Http\UrlScript, null, null, null, []); + $request = new Http\Request(new Http\UrlScript); Assert::same([], $request->getHeaders()); }); test('', function () { - $request = new Http\Request(new Http\UrlScript, null, null, null, [ + $request = new Http\Request(new Http\UrlScript, headers: [ 'one' => '1', 'TWO' => '2', 'X-Header' => 'X', diff --git a/tests/Http/Request.invalidEncoding.phpt b/tests/Http/Request.invalidEncoding.phpt index 96410a3f..30fb80c1 100644 --- a/tests/Http/Request.invalidEncoding.phpt +++ b/tests/Http/Request.invalidEncoding.phpt @@ -118,5 +118,5 @@ test('filtered data', function () { Assert::null($request->getFile(INVALID)); Assert::null($request->getFile(CONTROL_CHARACTERS)); Assert::type(Nette\Http\FileUpload::class, $request->files['file1']); - Assert::same('', $request->files['file1']->name); + Assert::same('', $request->files['file1']->getUntrustedName()); }); diff --git a/tests/Http/Request.invalidType.phpt b/tests/Http/Request.invalidType.phpt index 53a0b3af..07423897 100644 --- a/tests/Http/Request.invalidType.phpt +++ b/tests/Http/Request.invalidType.phpt @@ -19,7 +19,7 @@ test('invalid POST', function () { Assert::exception(function () { (new Http\RequestFactory)->fromGlobals(); - }, Nette\InvalidStateException::class, 'Invalid value in $_POST/$_COOKIE in key \'int\', expected string, integer given.'); + }, Nette\InvalidStateException::class, 'Invalid value in $_POST/$_COOKIE in key \'int\', expected string, int given.'); }); @@ -29,5 +29,5 @@ test('invalid COOKIE', function () { Assert::exception(function () { (new Http\RequestFactory)->fromGlobals(); - }, Nette\InvalidStateException::class, 'Invalid value in $_POST/$_COOKIE in key \'0\', expected string, integer given.'); + }, Nette\InvalidStateException::class, 'Invalid value in $_POST/$_COOKIE in key \'0\', expected string, int given.'); }); diff --git a/tests/Http/RequestFactory.authorization.phpt b/tests/Http/RequestFactory.authorization.phpt index 31dc021e..7ddbb6d4 100644 --- a/tests/Http/RequestFactory.authorization.phpt +++ b/tests/Http/RequestFactory.authorization.phpt @@ -22,7 +22,7 @@ test('Basic', function () { $request = $factory->fromGlobals(); Assert::same( 'Basic dXNlcjpwYXNzd29yZA==', - $request->getHeader('Authorization') + $request->getHeader('Authorization'), ); Assert::same(['user', 'password'], $request->getBasicCredentials()); @@ -41,7 +41,7 @@ test('Digest', function () { $request = $factory->fromGlobals(); Assert::same( 'Digest username="admin"', - $request->getHeader('Authorization') + $request->getHeader('Authorization'), ); Assert::null($request->getBasicCredentials()); }); diff --git a/tests/Http/RequestFactory.proxy.forwarded.phpt b/tests/Http/RequestFactory.proxy.forwarded.phpt index c1a92659..b8ed7851 100644 --- a/tests/Http/RequestFactory.proxy.forwarded.phpt +++ b/tests/Http/RequestFactory.proxy.forwarded.phpt @@ -21,11 +21,11 @@ test('', function () { $factory = new RequestFactory; $factory->setProxy('127.0.0.1'); Assert::same('127.0.0.3', $factory->fromGlobals()->getRemoteAddress()); - Assert::same('localhost', $factory->fromGlobals()->getRemoteHost()); + Assert::same('localhost', @$factory->fromGlobals()->getRemoteHost()); // deprecated $factory->setProxy('127.0.0.1/8'); Assert::same('23.75.45.200', $factory->fromGlobals()->getRemoteAddress()); - Assert::same('a23-75-45-200.deploy.static.akamaitechnologies.com', $factory->fromGlobals()->getRemoteHost()); + Assert::null(@$factory->fromGlobals()->getRemoteHost()); // deprecated $url = $factory->fromGlobals()->getUrl(); Assert::same('http', $url->getScheme()); @@ -43,7 +43,7 @@ test('', function () { $factory->setProxy('127.0.0.3'); Assert::same('23.75.45.200', $factory->fromGlobals()->getRemoteAddress()); - Assert::same('a23-75-45-200.deploy.static.akamaitechnologies.com', $factory->fromGlobals()->getRemoteHost()); + Assert::null(@$factory->fromGlobals()->getRemoteHost()); // deprecated $url = $factory->fromGlobals()->getUrl(); Assert::same(8080, $url->getPort()); @@ -62,7 +62,7 @@ test('', function () { $factory->setProxy('127.0.0.3'); Assert::same('2001:db8:cafe::17', $factory->fromGlobals()->getRemoteAddress()); - Assert::same('2001:db8:cafe::17', $factory->fromGlobals()->getRemoteHost()); + Assert::null(@$factory->fromGlobals()->getRemoteHost()); // deprecated $url = $factory->fromGlobals()->getUrl(); Assert::same('2001:db8:cafe::18', $url->getHost()); @@ -79,7 +79,7 @@ test('', function () { $factory->setProxy('127.0.0.3'); Assert::same('2001:db8:cafe::17', $factory->fromGlobals()->getRemoteAddress()); - Assert::same('2001:db8:cafe::17', $factory->fromGlobals()->getRemoteHost()); + Assert::null(@$factory->fromGlobals()->getRemoteHost()); // deprecated $url = $factory->fromGlobals()->getUrl(); Assert::same(47832, $url->getPort()); diff --git a/tests/Http/RequestFactory.proxy.x-forwarded.phpt b/tests/Http/RequestFactory.proxy.x-forwarded.phpt index d44753cf..ec7a0ad4 100644 --- a/tests/Http/RequestFactory.proxy.x-forwarded.phpt +++ b/tests/Http/RequestFactory.proxy.x-forwarded.phpt @@ -23,11 +23,11 @@ test('', function () { $factory = new RequestFactory; $factory->setProxy('127.0.0.1'); Assert::same('127.0.0.3', $factory->fromGlobals()->getRemoteAddress()); - Assert::same('localhost', $factory->fromGlobals()->getRemoteHost()); + Assert::same('localhost', @$factory->fromGlobals()->getRemoteHost()); // deprecated $factory->setProxy('127.0.0.1/8'); Assert::same('23.75.45.200', $factory->fromGlobals()->getRemoteAddress()); - Assert::same('a23-75-45-200.deploy.static.akamaitechnologies.com', $factory->fromGlobals()->getRemoteHost()); + Assert::null(@$factory->fromGlobals()->getRemoteHost()); // deprecated $url = $factory->fromGlobals()->getUrl(); Assert::same('otherhost', $url->getHost()); @@ -44,11 +44,11 @@ test('', function () { $factory = new RequestFactory; $factory->setProxy('10.0.0.0/24'); Assert::same('172.16.0.1', $factory->fromGlobals()->getRemoteAddress()); - Assert::same('172.16.0.1', $factory->fromGlobals()->getRemoteHost()); + Assert::null(@$factory->fromGlobals()->getRemoteHost()); // deprecated Assert::same('real', $factory->fromGlobals()->getUrl()->getHost()); $factory->setProxy(['10.0.0.1', '10.0.0.2']); Assert::same('172.16.0.1', $factory->fromGlobals()->getRemoteAddress()); - Assert::same('172.16.0.1', $factory->fromGlobals()->getRemoteHost()); + Assert::null(@$factory->fromGlobals()->getRemoteHost()); // deprecated Assert::same('real', $factory->fromGlobals()->getUrl()->getHost()); }); 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.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 05c8bb6b..2f78a6e6 100644 --- a/tests/Http/Session.handler.phpt +++ b/tests/Http/Session.handler.phpt @@ -16,44 +16,38 @@ class MySessionStorage implements SessionHandlerInterface private $path; - #[ReturnTypeWillChange] - public function open($savePath, $sessionName) + public function open(string $savePath, string $sessionName): bool { $this->path = $savePath; return true; } - #[ReturnTypeWillChange] - public function close() + public function close(): bool { return true; } - #[ReturnTypeWillChange] - public function read($id) + public function read(string $id): string|false { return (string) @file_get_contents("$this->path/sess_$id"); } - #[ReturnTypeWillChange] - public function write($id, $data) + public function write(string $id, string $data): bool { return (bool) file_put_contents("$this->path/sess_$id", $data); } - #[ReturnTypeWillChange] - public function destroy($id) + public function destroy(string $id): bool { return !is_file("$this->path/sess_$id") || @unlink("$this->path/sess_$id"); } - #[ReturnTypeWillChange] - public function gc($maxlifetime) + public function gc(int $maxlifetime): int|false { foreach (glob("$this->path/sess_*") as $filename) { if (filemtime($filename) + $maxlifetime < time()) { @@ -65,8 +59,7 @@ class MySessionStorage implements SessionHandlerInterface } - #[ReturnTypeWillChange] - public function validateId($key) + public function validateId(string $id): bool { return true; } @@ -80,10 +73,10 @@ $session->setHandler(new MySessionStorage); $session->start(); $namespace = $session->getSection('one'); -$namespace->a = 'apple'; +$namespace->set('a', 'apple'); $session->close(); unset($_SESSION); $session->start(); $namespace = $session->getSection('one'); -Assert::same('apple', $namespace->a); +Assert::same('apple', $namespace->get('a')); diff --git a/tests/Http/Session.id.phpt b/tests/Http/Session.id.phpt index 90faf27c..89cb2363 100644 --- a/tests/Http/Session.id.phpt +++ b/tests/Http/Session.id.phpt @@ -25,7 +25,7 @@ $session->start(); Assert::same($sessionId, $session->getId()); Assert::same([$sessionName => $leet], $_COOKIE); -Assert::same('yes', $session->getSection('temp')->value); +Assert::same('yes', $session->getSection('temp')->get('value')); $session->close(); // session was not regenerated diff --git a/tests/Http/Session.regenerate-empty-session-readAndClose.phpt b/tests/Http/Session.regenerate-empty-session-readAndClose.phpt index 3587b2ae..65365584 100644 --- a/tests/Http/Session.regenerate-empty-session-readAndClose.phpt +++ b/tests/Http/Session.regenerate-empty-session-readAndClose.phpt @@ -20,7 +20,7 @@ file_put_contents(getTempDir() . '/sess_' . $sessionId, '__NF|a:1:{s:4:"DATA";a: $session = new Session(new Http\Request(new Http\UrlScript('http://nette.org'), [], [], $cookies), new Http\Response); $session->setOptions(['readAndClose' => true]); $session->start(); -Assert::same('yes', $session->getSection('temp')->value); +Assert::same('yes', $session->getSection('temp')->get('value')); // session was not regenerated Assert::same($session->getId(), $sessionId); diff --git a/tests/Http/Session.regenerate-empty-session.phpt b/tests/Http/Session.regenerate-empty-session.phpt index b558216c..f46afe2f 100644 --- a/tests/Http/Session.regenerate-empty-session.phpt +++ b/tests/Http/Session.regenerate-empty-session.phpt @@ -19,7 +19,7 @@ file_put_contents(getTempDir() . '/sess_' . $sessionId, '__NF|a:1:{s:4:"DATA";a: $session = new Session(new Http\Request(new Http\UrlScript('http://nette.org'), [], [], $cookies), new Http\Response); $session->start(); -Assert::same('yes', $session->getSection('temp')->value); +Assert::same('yes', $session->getSection('temp')->get('value')); $newSessionId = $session->getId(); $session->close(); diff --git a/tests/Http/Session.restart-after-regenerate.phpt b/tests/Http/Session.restart-after-regenerate.phpt index 86a8122f..5164a587 100644 --- a/tests/Http/Session.restart-after-regenerate.phpt +++ b/tests/Http/Session.restart-after-regenerate.phpt @@ -18,7 +18,7 @@ $session = new Http\Session(new Http\Request(new Http\UrlScript, [], [], $cookie $session->start(); Assert::same($sessionId, $session->getId()); -Assert::same('yes', $session->getSection('temp')->value); +Assert::same('yes', $session->getSection('temp')->get('value')); $session->regenerateId(); Assert::notSame($sessionId, $session->getId()); @@ -26,7 +26,7 @@ Assert::same(session_id(), $session->getId()); $session->close(); $session->start(); -Assert::same('yes', $session->getSection('temp')->value); +Assert::same('yes', $session->getSection('temp')->get('value')); Assert::true(file_exists(getTempDir() . '/sess_' . $session->getId())); Assert::count(1, glob(getTempDir() . '/sess_*')); diff --git a/tests/Http/Session.sameSite.phpt b/tests/Http/Session.sameSite.phpt index 7dd234b5..def15881 100644 --- a/tests/Http/Session.sameSite.phpt +++ b/tests/Http/Session.sameSite.phpt @@ -25,5 +25,5 @@ Assert::contains( PHP_VERSION_ID >= 70300 ? 'Set-Cookie: PHPSESSID=' . $session->getId() . '; path=/; HttpOnly; SameSite=Lax' : 'Set-Cookie: PHPSESSID=' . $session->getId() . '; path=/; SameSite=Lax; HttpOnly', - headers_list() + headers_list(), ); diff --git a/tests/Http/Session.sections.phpt b/tests/Http/Session.sections.phpt index 2e004c2f..45eab766 100644 --- a/tests/Http/Session.sections.phpt +++ b/tests/Http/Session.sections.phpt @@ -22,11 +22,11 @@ $section = $session->getSection('trees'); Assert::type(Nette\Http\SessionSection::class, $section); Assert::false($session->hasSection('trees')); // hasSection() should have returned false for a section with no keys set -$section->hello = 'world'; +$section->set('hello', 'world'); Assert::true($session->hasSection('trees')); // hasSection() should have returned true for a section with keys set $section = $session->getSection('default'); Assert::same(['trees'], $session->getSectionNames()); -$section->hello = 'world'; +$section->set('hello', 'world'); Assert::same(['trees', 'default'], $session->getSectionNames()); diff --git a/tests/Http/SessionSection.basic.phpt b/tests/Http/SessionSection.basic.phpt index 5f056fc0..c33afc3e 100644 --- a/tests/Http/SessionSection.basic.phpt +++ b/tests/Http/SessionSection.basic.phpt @@ -15,13 +15,11 @@ require __DIR__ . '/../bootstrap.php'; $session = new Session(new Nette\Http\Request(new Nette\Http\UrlScript), new Nette\Http\Response); $namespace = $session->getSection('one'); -$namespace->a = 'apple'; +$namespace->set('a', 'apple'); $namespace->set('p', 'pear'); -$namespace['o'] = 'orange'; - -Assert::same('apple', $namespace->a); +$namespace->set('o', 'orange'); Assert::same('pear', $namespace->get('p')); -Assert::same('orange', $namespace['o']); +Assert::null($namespace->get('undefined')); foreach ($namespace as $key => $val) { $tmp[] = "$key=$val"; @@ -33,16 +31,11 @@ Assert::same([ 'o=orange', ], $tmp); +$namespace->remove('a'); +Assert::same('p=pear&o=orange', http_build_query(iterator_to_array($namespace->getIterator()))); -Assert::true(isset($namespace['p'])); -Assert::true(isset($namespace->o)); -Assert::false(isset($namespace->undefined)); - -unset($namespace['a'], $namespace->o, $namespace->undef); -$namespace->remove('p'); -$namespace->remove(['x']); - - - +$namespace->remove(['x', 'p']); +Assert::same('o=orange', http_build_query(iterator_to_array($namespace->getIterator()))); +$namespace->remove(); Assert::same('', http_build_query(iterator_to_array($namespace->getIterator()))); diff --git a/tests/Http/SessionSection.remove.phpt b/tests/Http/SessionSection.remove.phpt deleted file mode 100644 index cf1349f6..00000000 --- a/tests/Http/SessionSection.remove.phpt +++ /dev/null @@ -1,28 +0,0 @@ -getSection('three'); -$namespace->a = 'apple'; -$namespace->p = 'papaya'; -$namespace['c'] = 'cherry'; - -$namespace = $session->getSection('three'); -Assert::same('a=apple&p=papaya&c=cherry', http_build_query(iterator_to_array($namespace->getIterator()))); - - -// removing -$namespace->remove(); -Assert::same('', http_build_query(iterator_to_array($namespace->getIterator()))); diff --git a/tests/Http/SessionSection.removeExpiration().phpt b/tests/Http/SessionSection.removeExpiration().phpt index af90c54f..630c348c 100644 --- a/tests/Http/SessionSection.removeExpiration().phpt +++ b/tests/Http/SessionSection.removeExpiration().phpt @@ -15,8 +15,8 @@ $session = new Session(new Nette\Http\Request(new Nette\Http\UrlScript), new Net $session->setExpiration('+10 seconds'); $section = $session->getSection('expireRemove'); -$section->a = 'apple'; -$section->b = 'banana'; +$section->set('a', 'apple'); +$section->set('b', 'banana'); $section->setExpiration('+2 seconds', 'a'); $section->removeExpiration('a'); diff --git a/tests/Http/SessionSection.separated.phpt b/tests/Http/SessionSection.separated.phpt index 26e43c8c..f2646962 100644 --- a/tests/Http/SessionSection.separated.phpt +++ b/tests/Http/SessionSection.separated.phpt @@ -14,16 +14,18 @@ require __DIR__ . '/../bootstrap.php'; $session = new Session(new Nette\Http\Request(new Nette\Http\UrlScript), new Nette\Http\Response); -$namespace1 = $session->getSection('namespace1'); -$namespace1b = $session->getSection('namespace1'); -$namespace2 = $session->getSection('namespace2'); -$namespace2b = $session->getSection('namespace2'); -$namespace3 = $session->getSection('default'); -$namespace3b = $session->getSection('default'); -$namespace1->a = 'apple'; -$namespace2->a = 'pear'; -$namespace3->a = 'orange'; -Assert::true($namespace1->a !== $namespace2->a && $namespace1->a !== $namespace3->a && $namespace2->a !== $namespace3->a); -Assert::same($namespace1->a, $namespace1b->a); -Assert::same($namespace2->a, $namespace2b->a); -Assert::same($namespace3->a, $namespace3b->a); +$section1 = $session->getSection('namespace1'); +$section1b = $session->getSection('namespace1'); +$section2 = $session->getSection('namespace2'); +$section2b = $session->getSection('namespace2'); +$section3 = $session->getSection('default'); +$section3b = $session->getSection('default'); +$section1->set('a', 'apple'); +$section2->set('a', 'pear'); +$section3->set('a', 'orange'); +Assert::same('apple', $section1->get('a')); +Assert::same('apple', $section1b->get('a')); +Assert::same('pear', $section2->get('a')); +Assert::same('pear', $section2b->get('a')); +Assert::same('orange', $section3->get('a')); +Assert::same('orange', $section3b->get('a')); diff --git a/tests/Http/SessionSection.setExpiration().phpt b/tests/Http/SessionSection.setExpiration().phpt index 7eb82321..0328ad3b 100644 --- a/tests/Http/SessionSection.setExpiration().phpt +++ b/tests/Http/SessionSection.setExpiration().phpt @@ -18,9 +18,8 @@ $session->setExpiration('+10 seconds'); test('try to expire whole namespace', function () use ($session) { $namespace = $session->getSection('expire'); - $namespace->a = 'apple'; - $namespace->p = 'pear'; - $namespace['o'] = 'orange'; + $namespace->set('a', 'apple'); + $namespace->set('p', 'pear'); $namespace->setExpiration('+ 1 seconds'); $session->close(); @@ -34,9 +33,10 @@ 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->g = 'guava'; - $namespace->p = 'plum'; + $namespace->setExpiration('1 second', 'g'); + $namespace->set('g', 'guava'); + $namespace->set('p', 'plum'); + $namespace->setExpiration('1 second', 'p'); $namespace->set('a', 'apple', '1 second'); $session->close(); @@ -44,12 +44,12 @@ test('try to expire only 1 of the keys', function () use ($session) { $session->start(); $namespace = $session->getSection('expireSingle'); - Assert::same('p=plum', http_build_query(iterator_to_array($namespace->getIterator()))); + Assert::same('g=guava', http_build_query(iterator_to_array($namespace->getIterator()))); }); // 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/SessionSection.undefined.phpt b/tests/Http/SessionSection.undefined.phpt deleted file mode 100644 index 6eb5577c..00000000 --- a/tests/Http/SessionSection.undefined.phpt +++ /dev/null @@ -1,20 +0,0 @@ -getSection('one'); -Assert::false(isset($namespace->undefined)); -Assert::null($namespace->undefined); // Getting value of non-existent key -Assert::same('', http_build_query(iterator_to_array($namespace->getIterator()))); diff --git a/tests/Http/Url.httpScheme.phpt b/tests/Http/Url.httpScheme.phpt index aff3c01b..1ee32b68 100644 --- a/tests/Http/Url.httpScheme.phpt +++ b/tests/Http/Url.httpScheme.phpt @@ -23,14 +23,11 @@ Assert::same('hostname', $url->host); Assert::same(60, $url->port); Assert::same(80, $url->getDefaultPort()); Assert::same('/p%61th/script.php', $url->path); -Assert::same('/p%61th/', $url->basePath); Assert::same('arg=value', $url->query); Assert::same('anchor', $url->fragment); Assert::same('username%3A:password%3A@hostname:60', $url->authority); Assert::same('http://username%3A:password%3A@hostname:60', $url->hostUrl); Assert::same('http://username%3A:password%3A@hostname:60/p%61th/script.php?arg=value#anchor', $url->absoluteUrl); -Assert::same('http://username%3A:password%3A@hostname:60/p%61th/', $url->baseUrl); -Assert::same('script.php?arg=value#anchor', $url->relativeUrl); $url->setPath(''); Assert::same('/', $url->getPath()); 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());