no message

This commit is contained in:
BarboraFleg
2019-04-04 18:34:40 +02:00
commit ce48ca4a21
589 changed files with 82184 additions and 0 deletions
+29
View File
@@ -0,0 +1,29 @@
build: off
cache:
- c:\php -> appveyor.yml
- '%LOCALAPPDATA%\Composer\files -> appveyor.yml'
clone_folder: c:\projects\http
init:
- SET PATH=c:\php;%PATH%
- SET PHP=1
- SET ANSICON=121x90 (121x90)
install:
# Install PHP
- IF EXIST c:\php (SET PHP=0) ELSE (mkdir c:\php)
- IF %PHP%==1 cd c:\php
- IF %PHP%==1 curl https://windows.php.net/downloads/releases/archives/php-5.6.14-Win32-VC11-x86.zip --output php.zip
- IF %PHP%==1 7z x php.zip >nul
- IF %PHP%==1 echo extension_dir=ext >> php.ini
- IF %PHP%==1 echo extension=php_openssl.dll >> php.ini
- IF %PHP%==1 del /Q *.zip
- cd c:\projects\http
# Install Nette Tester
- appveyor DownloadFile https://getcomposer.org/composer.phar
- php composer.phar install --prefer-dist --no-interaction --no-progress
test_script:
- vendor\bin\tester tests -s -c tests\php-win.ini
+42
View File
@@ -0,0 +1,42 @@
{
"name": "nette/http",
"description": "🌐 Nette Http: abstraction for HTTP request, response and session. Provides careful data sanitization and utility for URL and cookies manipulation.",
"keywords": ["nette", "http", "request", "response", "session", "security", "url", "proxy", "cookies"],
"homepage": "https://nette.org",
"license": ["BSD-3-Clause", "GPL-2.0", "GPL-3.0"],
"authors": [
{
"name": "David Grudl",
"homepage": "https://davidgrudl.com"
},
{
"name": "Nette Community",
"homepage": "https://nette.org/contributors"
}
],
"require": {
"php": ">=5.6.0",
"nette/utils": "^2.4 || ~3.0.0"
},
"require-dev": {
"nette/di": "^2.4.8 || ~3.0.0",
"nette/tester": "^2.0",
"tracy/tracy": "^2.4"
},
"conflict": {
"nette/nette": "<2.2"
},
"suggest": {
"ext-fileinfo": "to detect type of uploaded files",
"nette/security": "allows use Nette\\Http\\UserStorage"
},
"autoload": {
"classmap": ["src/"]
},
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-master": "2.4-dev"
}
}
}
+33
View File
@@ -0,0 +1,33 @@
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:
+60
View File
@@ -0,0 +1,60 @@
Licenses
========
Good news! You may use Nette Framework under the terms of either
the New BSD License or the GNU General Public License (GPL) version 2 or 3.
The BSD License is recommended for most projects. It is easy to understand and it
places almost no restrictions on what you can do with the framework. If the GPL
fits better to your project, you can use the framework under this license.
You don't have to notify anyone which license you are using. You can freely
use Nette Framework in commercial projects as long as the copyright header
remains intact.
Please be advised that the name "Nette Framework" is a protected trademark and its
usage has some limitations. So please do not use word "Nette" in the name of your
project or top-level domain, and choose a name that stands on its own merits.
If your stuff is good, it will not take long to establish a reputation for yourselves.
New BSD License
---------------
Copyright (c) 2004, 2014 David Grudl (https://davidgrudl.com)
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of "Nette Framework" nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
This software is provided by the copyright holders and contributors "as is" and
any express or implied warranties, including, but not limited to, the implied
warranties of merchantability and fitness for a particular purpose are
disclaimed. In no event shall the copyright owner or contributors be liable for
any direct, indirect, incidental, special, exemplary, or consequential damages
(including, but not limited to, procurement of substitute goods or services;
loss of use, data, or profits; or business interruption) however caused and on
any theory of liability, whether in contract, strict liability, or tort
(including negligence or otherwise) arising in any way out of the use of this
software, even if advised of the possibility of such damage.
GNU General Public License
--------------------------
GPL licenses are very very long, so instead of including them here we offer
you URLs with full text:
- [GPL version 2](http://www.gnu.org/licenses/gpl-2.0.html)
- [GPL version 3](http://www.gnu.org/licenses/gpl-3.0.html)
+222
View File
@@ -0,0 +1,222 @@
Nette HTTP Component
====================
[![Downloads this Month](https://img.shields.io/packagist/dm/nette/http.svg)](https://packagist.org/packages/nette/http)
[![Build Status](https://travis-ci.org/nette/http.svg?branch=master)](https://travis-ci.org/nette/http)
[![Build Status Windows](https://ci.appveyor.com/api/projects/status/github/nette/http?branch=master&svg=true)](https://ci.appveyor.com/project/dg/http/branch/master)
[![Coverage Status](https://coveralls.io/repos/github/nette/http/badge.svg?branch=master)](https://coveralls.io/github/nette/http?branch=master)
[![Latest Stable Version](https://poser.pugx.org/nette/http/v/stable)](https://github.com/nette/http/releases)
[![License](https://img.shields.io/badge/license-New%20BSD-blue.svg)](https://github.com/nette/http/blob/master/license.md)
HTTP request and response are encapsulated in `Nette\Http\Request` and `Nette\Http\Response` objects which offer comfortable API and also act as
sanitization filter.
HTTP Request
-------------
Nette cleans out data sent by user from control and invalid characters.
The URL of the request is available as [api:Nette\Http\UrlScript] instance:
```php
$url = $httpRequest->getUrl();
echo $url; // e.g. https://nette.org/en/documentation?action=edit
echo $url->host; // nette.org
```
Determine current HTTP method:
```php
echo $httpRequest->getMethod(); // GET, POST, HEAD, PUT
if ($httpRequest->isMethod('GET')) ...
```
Is the connection encrypted (HTTPS)?
```php
echo $httpRequest->isSecured() ? 'yes' : 'no';
```
Is this an AJAX request?
```php
echo $httpRequest->isAjax() ? 'yes' : 'no';
```
What is the user's IP address?
```php
echo $httpRequest->getRemoteAddress(); // user's IP address
echo $httpRequest->getRemoteHost(); // and its DNS translation
```
What URL the user came from? Returned as [Nette\Http\Url |urls] object.
```php
echo $httpRequest->getReferer()->host;
```
Request parameters:
```php
$get = $httpRequest->getQuery(); // array of all URL parameters
$id = $httpRequest->getQuery('id'); // returns GET parameter 'id' (or null)
$post = $httpRequest->getPost(); // array of all POST parameters
$id = $httpRequest->getPost('id'); // returns POST parameter 'id' (or null)
$cookies = $httpRequest->getCookies(); // array of all cookies
$sessId = $httpRequest->getCookie('sess_id'); // returns the cookie (or null)
```
Uploaded files are encapsulated into [api:Nette\Http\FileUpload] objects:
```php
$files = $httpRequest->getFiles(); // array of all uploaded files
$file = $httpRequest->getFile('avatar'); // returns one file
echo $file->getName(); // name of the file sent by user
echo $file->getSanitizedName(); // the name without dangerous characters
```
HTTP headers are also accessible:
```php
// returns associative array of HTTP headers
$headers = $httpRequest->getHeaders();
// returns concrete header (case-insensitive)
$userAgent = $httpRequest->getHeader('User-Agent');
```
A useful method is `detectLanguage()`. You can pass it an array with languages supported by application and it returns the one preferred by browser.
It is not magic, the method just uses the `Accept-Language` header.
```php
// Header sent by browser: Accept-Language: cs,en-us;q=0.8,en;q=0.5,sl;q=0.3
$langs = array('hu', 'pl', 'en'); // languages supported in application
echo $httpRequest->detectLanguage($langs); // en
```
RequestFactory and URL filtering
------------------
Object holding current HTTP request is created by [api:Nette\Http\RequestFactory]. Its behavior can be modified.
It's possible to clean up URLs from characters that can get into them because of poorly implemented comment systems on various other websites by using filters:
```php
$requestFactory = new Nette\Http\RequestFactory;
// remove spaces from path
$requestFactory->addUrlFilter('%20', '', PHP_URL_PATH);
// remove dot, comma or right parenthesis form the end of the URL
$requestFactory->addUrlFilter('[.,)]$');
// clean the path from duplicated slashes (default filter)
$requestFactory->addUrlFilter('/{2,}', '/', PHP_URL_PATH);
```
And then we let the factory generate a new `httpRequest` and we store it in a system container:
```php
// $container is a system container
$container->addService('httpRequest', $requestFactory->createHttpRequest());
```
HTTP response
--------------
Whether it is still possible to send headers or change the status code tells the `isSent()` method. If it returns true,
it won't be possible to send another header or change the status code.
In that case, any attempt to send header or change code invokes `Nette\InvalidStateException`. .[caution]
[Response status code | http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10] can be sent and retrieved this way:
```php
$httpResponse->setCode(Nette\Http\Response::S404_NOT_FOUND);
echo $httpResponse->getCode(); // 404
```
For better source code readability it is recommended to use predefined constants instead of actual numbers:
```
Http\IResponse::S200_OK
Http\IResponse::S204_NO_CONTENT
Http\IResponse::S300_MULTIPLE_CHOICES
Http\IResponse::S301_MOVED_PERMANENTLY
Http\IResponse::S302_FOUND
Http\IResponse::S303_SEE_OTHER
Http\IResponse::S303_POST_GET
Http\IResponse::S304_NOT_MODIFIED
Http\IResponse::S307_TEMPORARY_REDIRECT
Http\IResponse::S400_BAD_REQUEST
Http\IResponse::S401_UNAUTHORIZED
Http\IResponse::S403_FORBIDDEN
Http\IResponse::S404_NOT_FOUND
Http\IResponse::S410_GONE
Http\IResponse::S500_INTERNAL_SERVER_ERROR
Http\IResponse::S501_NOT_IMPLEMENTED
Http\IResponse::S503_SERVICE_UNAVAILABLE
```
Method `setContentType($type, $charset=null)` changes `Content-Type` response header:
```php
$httpResponse->setContentType('text/plain', 'UTF-8');
```
Redirection to another URL is done by `redirect($url, $code=302)` method. Do not forget to terminate the script afterwards!
```php
$httpResponse->redirect('http://example.com');
exit;
```
To set the document expiration date, we can use `setExpiration()` method. The parameter is either text data, number of seconds or a timestamp:
```php
// browser cache expires in one hour
$httpResponse->setExpiration('+ 1 hours');
```
Now we send the HTTP response header:
```php
$httpResponse->setHeader('Pragma', 'no-cache');
// or if we want to send the same header more times with different values
$httpResponse->addHeader('Pragma', 'no-cache');
```
Sent headers are also available:
```php
// returns associative array of headers
$headers = $httpResponse->getHeaders();
// returns concrete header (case-insensitive)
$pragma = $httpResponse->getHeader('Pragma');
```
There are two methods for cookie manipulation: `setCookie()` and `deleteCookie()`.
```php
// setCookie($name, $value, $time, [$path, [$domain, [$secure, [$httpOnly]]]])
$httpResponse->setCookie('lang', 'en', '100 days'); // send cookie
// deleteCookie($name, [$path, [$domain, [$secure]]])
$httpResponse->deleteCookie('lang'); // delete cookie
```
These two methods can take more parameters: `$path` (subdirectory where the cookie will be available),
`$domain` and `$secure`. Their detailed description can be found in PHP manual for [php:setcookie] function.
+160
View File
@@ -0,0 +1,160 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Bridges\HttpDI;
use Nette;
/**
* HTTP extension for Nette DI.
*/
class HttpExtension extends Nette\DI\CompilerExtension
{
public $defaults = [
'proxy' => [],
'headers' => [
'X-Powered-By' => 'Nette Framework',
'Content-Type' => 'text/html; charset=utf-8',
],
'frames' => 'SAMEORIGIN', // X-Frame-Options
'csp' => [], // Content-Security-Policy
'cspReportOnly' => [], // Content-Security-Policy-Report-Only
'csp-report' => null, // for compatibility
'featurePolicy' => [], // Feature-Policy
'cookieSecure' => null, // true|false|auto Whether the cookie is available only through HTTPS
'sameSiteProtection' => null, // activates Request::isSameSite() protection
];
/** @var bool */
private $cliMode;
public function __construct($cliMode = false)
{
$this->cliMode = $cliMode;
}
public function loadConfiguration()
{
$builder = $this->getContainerBuilder();
$config = $this->validateConfig($this->defaults);
$builder->addDefinition($this->prefix('requestFactory'))
->setClass(Nette\Http\RequestFactory::class)
->addSetup('setProxy', [$config['proxy']]);
$builder->addDefinition($this->prefix('request'))
->setClass(Nette\Http\Request::class)
->setFactory('@Nette\Http\RequestFactory::createHttpRequest');
$builder->addDefinition($this->prefix('response'))
->setClass(Nette\Http\Response::class);
$builder->addDefinition($this->prefix('context'))
->setClass(Nette\Http\Context::class)
->addSetup('::trigger_error', ['Service http.context is deprecated.', E_USER_DEPRECATED]);
if ($this->name === 'http') {
$builder->addAlias('nette.httpRequestFactory', $this->prefix('requestFactory'));
$builder->addAlias('nette.httpContext', $this->prefix('context'));
$builder->addAlias('httpRequest', $this->prefix('request'));
$builder->addAlias('httpResponse', $this->prefix('response'));
}
}
public function beforeCompile()
{
$builder = $this->getContainerBuilder();
if (isset($this->config['cookieSecure'])) {
$value = $this->config['cookieSecure'] === 'auto'
? $builder::literal('$this->getService(?)->isSecured()', [$this->prefix('request')])
: (bool) $this->config['cookieSecure'];
$builder->getDefinition($this->prefix('response'))
->addSetup('$cookieSecure', [$value]);
$builder->getDefinitionByType(Nette\Http\Session::class)
->addSetup('setOptions', [['cookie_secure' => $value]]);
}
}
public function afterCompile(Nette\PhpGenerator\ClassType $class)
{
if ($this->cliMode) {
return;
}
$initialize = $class->getMethod('initialize');
$config = $this->getConfig();
$headers = $config['headers'];
if (isset($config['frames']) && $config['frames'] !== true) {
$frames = $config['frames'];
if ($frames === false) {
$frames = 'DENY';
} elseif (preg_match('#^https?:#', $frames)) {
$frames = "ALLOW-FROM $frames";
}
$headers['X-Frame-Options'] = $frames;
}
if (isset($config['csp-report'])) {
trigger_error('Rename csp-repost to cspReportOnly in config.', E_USER_DEPRECATED);
$config['cspReportOnly'] = $config['csp-report'];
}
foreach (['csp', 'cspReportOnly'] as $key) {
if (empty($config[$key])) {
continue;
}
$value = self::buildPolicy($config[$key]);
if (strpos($value, "'nonce'")) {
$value = Nette\DI\ContainerBuilder::literal(
'str_replace(?, ? . (isset($cspNonce) \? $cspNonce : $cspNonce = base64_encode(Nette\Utils\Random::generate(16, "\x00-\xFF"))), ?)',
["'nonce", "'nonce-", $value]
);
}
$headers['Content-Security-Policy' . ($key === 'csp' ? '' : '-Report-Only')] = $value;
}
if (!empty($config['featurePolicy'])) {
$headers['Feature-Policy'] = self::buildPolicy($config['featurePolicy']);
}
foreach ($headers as $key => $value) {
if ($value != null) { // intentionally ==
$initialize->addBody('$this->getService(?)->setHeader(?, ?);', [$this->prefix('response'), $key, $value]);
}
}
if (!empty($config['sameSiteProtection'])) {
$initialize->addBody('$this->getService(?)->setCookie(...?);', [$this->prefix('response'), ['nette-samesite', '1', 0, '/', null, null, true, 'Strict']]);
}
}
private static function buildPolicy(array $config)
{
static $nonQuoted = ['require-sri-for' => 1, 'sandbox' => 1];
$value = '';
foreach ($config as $type => $policy) {
if ($policy === false) {
continue;
}
$policy = $policy === true ? [] : (array) $policy;
$value .= $type;
foreach ($policy as $item) {
$value .= !isset($nonQuoted[$type]) && preg_match('#^[a-z-]+\z#', $item) ? " '$item'" : " $item";
}
$value .= '; ';
}
return $value;
}
}
@@ -0,0 +1,95 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Bridges\HttpDI;
use Nette;
/**
* Session extension for Nette DI.
*/
class SessionExtension extends Nette\DI\CompilerExtension
{
public $defaults = [
'debugger' => false,
'autoStart' => 'smart', // true|false|smart
'expiration' => null,
'handler' => null,
];
/** @var bool */
private $debugMode;
/** @var bool */
private $cliMode;
public function __construct($debugMode = false, $cliMode = false)
{
$this->debugMode = $debugMode;
$this->cliMode = $cliMode;
}
public function loadConfiguration()
{
$builder = $this->getContainerBuilder();
$config = $this->getConfig() + $this->defaults;
$this->setConfig($config);
$session = $builder->addDefinition($this->prefix('session'))
->setFactory(Nette\Http\Session::class);
if ($config['expiration']) {
$session->addSetup('setExpiration', [$config['expiration']]);
}
if ($config['handler']) {
$session->addSetup('setHandler', [$config['handler']]);
}
if (isset($config['cookieDomain']) && $config['cookieDomain'] === 'domain') {
$config['cookieDomain'] = $builder::literal('$this->getByType(Nette\Http\IRequest::class)->getUrl()->getDomain(2)');
}
if (isset($config['cookieSecure']) && $config['cookieSecure'] === 'auto') {
$config['cookieSecure'] = $builder::literal('$this->getByType(Nette\Http\IRequest::class)->isSecured()');
}
if ($this->debugMode && $config['debugger']) {
$session->addSetup('@Tracy\Bar::addPanel', [
new Nette\DI\Statement(Nette\Bridges\HttpTracy\SessionPanel::class),
]);
}
unset($config['expiration'], $config['handler'], $config['autoStart'], $config['debugger']);
if (!empty($config)) {
$session->addSetup('setOptions', [$config]);
}
if ($this->name === 'session') {
$builder->addAlias('session', $this->prefix('session'));
}
}
public function afterCompile(Nette\PhpGenerator\ClassType $class)
{
if ($this->cliMode) {
return;
}
$initialize = $class->getMethod('initialize');
$config = $this->getConfig();
$name = $this->prefix('session');
if ($config['autoStart'] === 'smart') {
$initialize->addBody('$this->getService(?)->exists() && $this->getService(?)->start();', [$name, $name]);
} elseif ($config['autoStart']) {
$initialize->addBody('$this->getService(?)->start();', [$name]);
}
}
}
@@ -0,0 +1,43 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Bridges\HttpTracy;
use Nette;
use Tracy;
/**
* Session panel for Debugger Bar.
*/
class SessionPanel implements Tracy\IBarPanel
{
use Nette\SmartObject;
/**
* Renders tab.
* @return string
*/
public function getTab()
{
ob_start(function () {});
require __DIR__ . '/templates/SessionPanel.tab.phtml';
return ob_get_clean();
}
/**
* Renders panel.
* @return string
*/
public function getPanel()
{
ob_start(function () {});
require __DIR__ . '/templates/SessionPanel.panel.phtml';
return ob_get_clean();
}
}
@@ -0,0 +1,36 @@
<?php
namespace Nette\Bridges\HttpTracy;
use Tracy\Dumper;
?>
<style class="tracy-debug">
#tracy-debug .nette-SessionPanel-parameters pre {
background: #FDF5CE;
padding: .4em .7em;
border: 1px dotted silver;
overflow: auto;
}
</style>
<h1>Session #<?= htmlspecialchars(session_id(), ENT_IGNORE, 'UTF-8') ?> (Lifetime: <?= htmlspecialchars(ini_get('session.cookie_lifetime'), ENT_NOQUOTES, 'UTF-8'); ?>)</h1>
<div class="tracy-inner nette-SessionPanel">
<?php if (empty($_SESSION)):?>
<p><i>empty</i></p>
<?php else: ?>
<table>
<?php
foreach ($_SESSION as $k => $v) {
if ($k === '__NF') {
$k = 'Nette Session';
$v = isset($v['DATA']) ? $v['DATA'] : null;
} elseif ($k === '_tracy') {
continue;
}
echo '<tr><th>', htmlspecialchars($k, ENT_IGNORE, 'UTF-8'), '</th><td>', Dumper::toHtml($v, [Dumper::LIVE => true]), "</td></tr>\n";
}?>
</table>
<?php endif ?>
</div>
@@ -0,0 +1,8 @@
<?php
namespace Nette\Bridges\HttpTracy;
?>
<span title="Session #<?= htmlspecialchars(session_id(), ENT_IGNORE | ENT_QUOTES, 'UTF-8') ?>">
<svg viewBox="0 0 2048 2048"><path fill="#52362c" d="m1691 1396-67 394h-133s2-446 0-586v-4c0-109 89-197 200-197 110 0 200 88 200 197s-89 197-200 197zm-1131-192c-2 141 0 586 0 586h-133l-67-394h-1c-110 0-199-88-199-197s89-197 200-197c110 0 200 88 200 197v4zm865 61v394h-399-403v-394c262-27 529-27 802 0zm0-66c-273-27-541-27-802 0-2-163-119-258-266-262-176-545 233-693 669-693 440 0 841 149 665 693-148 4-265 99-266 263z"/></svg>
</span>
+100
View File
@@ -0,0 +1,100 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Http;
use Nette;
/**
* HTTP-specific tasks.
*/
class Context
{
use Nette\SmartObject;
/** @var IRequest */
private $request;
/** @var IResponse */
private $response;
public function __construct(IRequest $request, IResponse $response)
{
$this->request = $request;
$this->response = $response;
}
/**
* Attempts to cache the sent entity by its last modification date.
* @param string|int|\DateTimeInterface last modified time
* @param string strong entity tag validator
* @return bool
*/
public function isModified($lastModified = null, $etag = null)
{
if ($lastModified) {
$this->response->setHeader('Last-Modified', Helpers::formatDate($lastModified));
}
if ($etag) {
$this->response->setHeader('ETag', '"' . addslashes($etag) . '"');
}
$ifNoneMatch = $this->request->getHeader('If-None-Match');
if ($ifNoneMatch === '*') {
$match = true; // match, check if-modified-since
} elseif ($ifNoneMatch !== null) {
$etag = $this->response->getHeader('ETag');
if ($etag == null || strpos(' ' . strtr($ifNoneMatch, ",\t", ' '), ' ' . $etag) === false) {
return true;
} else {
$match = true; // match, check if-modified-since
}
}
$ifModifiedSince = $this->request->getHeader('If-Modified-Since');
if ($ifModifiedSince !== null) {
$lastModified = $this->response->getHeader('Last-Modified');
if ($lastModified != null && strtotime($lastModified) <= strtotime($ifModifiedSince)) {
$match = true;
} else {
return true;
}
}
if (empty($match)) {
return true;
}
$this->response->setCode(IResponse::S304_NOT_MODIFIED);
return false;
}
/**
* @return IRequest
*/
public function getRequest()
{
return $this->request;
}
/**
* @return IResponse
*/
public function getResponse()
{
return $this->response;
}
}
+215
View File
@@ -0,0 +1,215 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Http;
use Nette;
/**
* Provides access to individual files that have been uploaded by a client.
*
* @property-read string $name
* @property-read string $sanitizedName
* @property-read string|null $contentType
* @property-read int $size
* @property-read string $temporaryFile
* @property-read int $error
* @property-read bool $ok
* @property-read string|null $contents
*/
class FileUpload
{
use Nette\SmartObject;
/** @var string */
private $name;
/** @var string */
private $type;
/** @var int */
private $size;
/** @var string */
private $tmpName;
/** @var int */
private $error;
public function __construct($value)
{
foreach (['name', 'size', 'tmp_name', 'error'] as $key) {
if (!isset($value[$key]) || !is_scalar($value[$key])) {
$this->error = UPLOAD_ERR_NO_FILE;
return; // or throw exception?
}
}
$this->name = $value['name'];
$this->size = $value['size'];
$this->tmpName = $value['tmp_name'];
$this->error = $value['error'];
}
/**
* Returns the file name.
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Returns the sanitized file name.
* @return string
*/
public function getSanitizedName()
{
return trim(Nette\Utils\Strings::webalize($this->name, '.', false), '.-');
}
/**
* Returns the MIME content type of an uploaded file.
* @return string|null
*/
public function getContentType()
{
if ($this->isOk() && $this->type === null) {
$this->type = finfo_file(finfo_open(FILEINFO_MIME_TYPE), $this->tmpName);
}
return $this->type;
}
/**
* Returns the size of an uploaded file.
* @return int
*/
public function getSize()
{
return $this->size;
}
/**
* Returns the path to an uploaded file.
* @return string
*/
public function getTemporaryFile()
{
return $this->tmpName;
}
/**
* Returns the path to an uploaded file.
* @return string
*/
public function __toString()
{
return (string) $this->tmpName;
}
/**
* Returns the error code. {@link http://php.net/manual/en/features.file-upload.errors.php}
* @return int
*/
public function getError()
{
return $this->error;
}
/**
* Is there any error?
* @return bool
*/
public function isOk()
{
return $this->error === UPLOAD_ERR_OK;
}
/**
* @return bool
*/
public function hasFile()
{
return $this->error !== UPLOAD_ERR_NO_FILE;
}
/**
* Move uploaded file to new location.
* @param string
* @return static
*/
public function move($dest)
{
$dir = dirname($dest);
Nette\Utils\FileSystem::createDir($dir);
@unlink($dest); // @ - file may not exists
Nette\Utils\Callback::invokeSafe(
is_uploaded_file($this->tmpName) ? 'move_uploaded_file' : 'rename',
[$this->tmpName, $dest],
function ($message) use ($dest) {
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;
return $this;
}
/**
* Is uploaded file GIF, PNG or JPEG?
* @return bool
*/
public function isImage()
{
return in_array($this->getContentType(), ['image/gif', 'image/png', 'image/jpeg'], true);
}
/**
* Returns the image.
* @return Nette\Utils\Image
* @throws Nette\Utils\ImageException
*/
public function toImage()
{
return Nette\Utils\Image::fromFile($this->tmpName);
}
/**
* Returns the dimensions of an uploaded image as array.
* @return array|null
*/
public function getImageSize()
{
return $this->isOk() ? @getimagesize($this->tmpName) : null; // @ - files smaller than 12 bytes causes read error
}
/**
* Get file contents.
* @return string|null
*/
public function getContents()
{
// future implementation can try to work around safe_mode and open_basedir limitations
return $this->isOk() ? file_get_contents($this->tmpName) : null;
}
}
+74
View File
@@ -0,0 +1,74 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Http;
use Nette;
use Nette\Utils\DateTime;
/**
* Rendering helpers for HTTP.
*/
class Helpers
{
use Nette\StaticClass;
/**
* Returns HTTP valid date format.
* @param string|int|\DateTimeInterface
* @return string
*/
public static function formatDate($time)
{
$time = DateTime::from($time);
$time->setTimezone(new \DateTimeZone('GMT'));
return $time->format('D, d M Y H:i:s \G\M\T');
}
/**
* Is IP address in CIDR block?
* @return bool
*/
public static function ipMatch($ip, $mask)
{
list($mask, $size) = explode('/', $mask . '/');
$tmp = function ($n) { return 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);
if (!$max || $max !== strlen($mask) || (int) $size < 0 || (int) $size > $max) {
return false;
}
return strncmp($ip, $mask, $size === '' ? $max : (int) $size) === 0;
}
/**
* Removes duplicate cookies from response.
* @return void
* @internal
*/
public static function removeDuplicateCookies()
{
if (headers_sent($file, $line) || ini_get('suhosin.cookie.encrypt')) {
return;
}
$flatten = [];
foreach (headers_list() as $header) {
if (preg_match('#^Set-Cookie: .+?=#', $header, $m)) {
$flatten[$m[0]] = $header;
header_remove('Set-Cookie');
}
}
foreach (array_values($flatten) as $key => $header) {
header($header, $key === 0);
}
}
}
+138
View File
@@ -0,0 +1,138 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Http;
/**
* IHttpRequest provides access scheme for request sent via HTTP.
*/
interface IRequest
{
/** HTTP request method */
const
GET = 'GET',
POST = 'POST',
HEAD = 'HEAD',
PUT = 'PUT',
DELETE = 'DELETE',
PATCH = 'PATCH',
OPTIONS = 'OPTIONS';
/**
* Returns URL object.
* @return UrlScript
*/
function getUrl();
/********************* query, post, files & cookies ****************d*g**/
/**
* Returns variable provided to the script via URL query ($_GET).
* If no key is passed, returns the entire array.
* @param string key
* @param mixed default value
* @return mixed
*/
function getQuery($key = null, $default = null);
/**
* Returns variable provided to the script via POST method ($_POST).
* If no key is passed, returns the entire array.
* @param string key
* @param mixed default value
* @return mixed
*/
function getPost($key = null, $default = null);
/**
* Returns uploaded file.
* @param string key
* @return FileUpload|array|null
*/
function getFile($key);
/**
* Returns uploaded files.
* @return array
*/
function getFiles();
/**
* Returns variable provided to the script via HTTP cookies.
* @param string key
* @param mixed default value
* @return mixed
*/
function getCookie($key, $default = null);
/**
* Returns variables provided to the script via HTTP cookies.
* @return array
*/
function getCookies();
/********************* method & headers ****************d*g**/
/**
* Returns HTTP request method (GET, POST, HEAD, PUT, ...). The method is case-sensitive.
* @return string
*/
function getMethod();
/**
* Checks HTTP request method.
* @param string
* @return bool
*/
function isMethod($method);
/**
* Return the value of the HTTP header. Pass the header name as the
* plain, HTTP-specified header name (e.g. 'Accept-Encoding').
* @param string
* @param string|null
* @return string|null
*/
function getHeader($header, $default = null);
/**
* Returns all HTTP headers.
* @return array
*/
function getHeaders();
/**
* Is the request sent via secure channel (https)?
* @return bool
*/
function isSecured();
/**
* Is AJAX request?
* @return bool
*/
function isAjax();
/**
* Returns the IP address of the remote client.
* @return string|null
*/
function getRemoteAddress();
/**
* Returns the host of the remote client.
* @return string|null
*/
function getRemoteHost();
/**
* Returns raw content of HTTP request body.
* @return string|null
*/
function getRawBody();
}
+179
View File
@@ -0,0 +1,179 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Http;
/**
* IHttpResponse interface.
*/
interface IResponse
{
/** @var int cookie expiration: forever (23.1.2037) */
const PERMANENT = 2116333333;
/** @var int cookie expiration: until the browser is closed */
const BROWSER = 0;
/** HTTP 1.1 response code */
const
S100_CONTINUE = 100,
S101_SWITCHING_PROTOCOLS = 101,
S102_PROCESSING = 102,
S200_OK = 200,
S201_CREATED = 201,
S202_ACCEPTED = 202,
S203_NON_AUTHORITATIVE_INFORMATION = 203,
S204_NO_CONTENT = 204,
S205_RESET_CONTENT = 205,
S206_PARTIAL_CONTENT = 206,
S207_MULTI_STATUS = 207,
S208_ALREADY_REPORTED = 208,
S226_IM_USED = 226,
S300_MULTIPLE_CHOICES = 300,
S301_MOVED_PERMANENTLY = 301,
S302_FOUND = 302,
S303_SEE_OTHER = 303,
S303_POST_GET = 303,
S304_NOT_MODIFIED = 304,
S305_USE_PROXY = 305,
S307_TEMPORARY_REDIRECT = 307,
S308_PERMANENT_REDIRECT = 308,
S400_BAD_REQUEST = 400,
S401_UNAUTHORIZED = 401,
S402_PAYMENT_REQUIRED = 402,
S403_FORBIDDEN = 403,
S404_NOT_FOUND = 404,
S405_METHOD_NOT_ALLOWED = 405,
S406_NOT_ACCEPTABLE = 406,
S407_PROXY_AUTHENTICATION_REQUIRED = 407,
S408_REQUEST_TIMEOUT = 408,
S409_CONFLICT = 409,
S410_GONE = 410,
S411_LENGTH_REQUIRED = 411,
S412_PRECONDITION_FAILED = 412,
S413_REQUEST_ENTITY_TOO_LARGE = 413,
S414_REQUEST_URI_TOO_LONG = 414,
S415_UNSUPPORTED_MEDIA_TYPE = 415,
S416_REQUESTED_RANGE_NOT_SATISFIABLE = 416,
S417_EXPECTATION_FAILED = 417,
S421_MISDIRECTED_REQUEST = 421,
S422_UNPROCESSABLE_ENTITY = 422,
S423_LOCKED = 423,
S424_FAILED_DEPENDENCY = 424,
S426_UPGRADE_REQUIRED = 426,
S428_PRECONDITION_REQUIRED = 428,
S429_TOO_MANY_REQUESTS = 429,
S431_REQUEST_HEADER_FIELDS_TOO_LARGE = 431,
S451_UNAVAILABLE_FOR_LEGAL_REASONS = 451,
S500_INTERNAL_SERVER_ERROR = 500,
S501_NOT_IMPLEMENTED = 501,
S502_BAD_GATEWAY = 502,
S503_SERVICE_UNAVAILABLE = 503,
S504_GATEWAY_TIMEOUT = 504,
S505_HTTP_VERSION_NOT_SUPPORTED = 505,
S506_VARIANT_ALSO_NEGOTIATES = 506,
S507_INSUFFICIENT_STORAGE = 507,
S508_LOOP_DETECTED = 508,
S510_NOT_EXTENDED = 510,
S511_NETWORK_AUTHENTICATION_REQUIRED = 511;
/**
* Sets HTTP response code.
* @param int
* @return static
*/
function setCode($code);
/**
* Returns HTTP response code.
* @return int
*/
function getCode();
/**
* Sends a HTTP header and replaces a previous one.
* @param string header name
* @param string header value
* @return static
*/
function setHeader($name, $value);
/**
* Adds HTTP header.
* @param string header name
* @param string header value
* @return static
*/
function addHeader($name, $value);
/**
* Sends a Content-type HTTP header.
* @param string mime-type
* @param string charset
* @return static
*/
function setContentType($type, $charset = null);
/**
* Redirects to a new URL.
* @param string URL
* @param int HTTP code
* @return void
*/
function redirect($url, $code = self::S302_FOUND);
/**
* Sets the number of seconds before a page cached on a browser expires.
* @param string|int|\DateTimeInterface time, value 0 means "until the browser is closed"
* @return static
*/
function setExpiration($seconds);
/**
* Checks if headers have been sent.
* @return bool
*/
function isSent();
/**
* Returns value of an HTTP header.
* @param string
* @param string|null
* @return string|null
*/
function getHeader($header, $default = null);
/**
* Returns a list of headers to sent.
* @return array (name => value)
*/
function getHeaders();
/**
* Sends a cookie.
* @param string name of the cookie
* @param string value
* @param string|int|\DateTimeInterface time, value 0 means "until the browser is closed"
* @param string
* @param string
* @param bool
* @param bool
* @return static
*/
function setCookie($name, $value, $expire, $path = null, $domain = null, $secure = null, $httpOnly = null);
/**
* Deletes a cookie.
* @param string name of the cookie.
* @param string
* @param string
* @param bool
* @return void
*/
function deleteCookie($name, $path = null, $domain = null, $secure = null);
}
+29
View File
@@ -0,0 +1,29 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Http;
/**
* User session storage for PHP < 5.4. @see http://php.net/session_set_save_handler
*
* @deprecated since PHP 5.4, use \SessionHandlerInterface
*/
interface ISessionStorage
{
function open($savePath, $sessionName);
function close();
function read($id);
function write($id, $data);
function remove($id);
function clean($maxlifetime);
}
+339
View File
@@ -0,0 +1,339 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Http;
use Nette;
/**
* HttpRequest provides access scheme for request sent via HTTP.
*
* @property-read UrlScript $url
* @property-read array $query
* @property-read array $post
* @property-read array $files
* @property-read array $cookies
* @property-read string $method
* @property-read array $headers
* @property-read Url|null $referer
* @property-read bool $secured
* @property-read bool $ajax
* @property-read string|null $remoteAddress
* @property-read string|null $remoteHost
* @property-read string|null $rawBody
*/
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 $rawBodyCallback;
public function __construct(UrlScript $url, $query = null, $post = null, $files = null, $cookies = null,
$headers = null, $method = null, $remoteAddress = null, $remoteHost = null, $rawBodyCallback = null)
{
$this->url = $url;
if ($query !== null) {
trigger_error('Nette\Http\Request::__construct(): parameter $query is deprecated.', E_USER_DEPRECATED);
$url->setQuery($query);
}
$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->rawBodyCallback = $rawBodyCallback;
}
/**
* Returns URL object.
* @return UrlScript
*/
public function getUrl()
{
return clone $this->url;
}
/********************* query, post, files & cookies ****************d*g**/
/**
* Returns variable provided to the script via URL query ($_GET).
* If no key is passed, returns the entire array.
* @param string key
* @param mixed default value
* @return mixed
*/
public function getQuery($key = null, $default = null)
{
if (func_num_args() === 0) {
return $this->url->getQueryParameters();
} else {
return $this->url->getQueryParameter($key, $default);
}
}
/**
* Returns variable provided to the script via POST method ($_POST).
* If no key is passed, returns the entire array.
* @param string key
* @param mixed default value
* @return mixed
*/
public function getPost($key = null, $default = null)
{
if (func_num_args() === 0) {
return $this->post;
} elseif (isset($this->post[$key])) {
return $this->post[$key];
} else {
return $default;
}
}
/**
* Returns uploaded file.
* @param string key
* @return FileUpload|array|null
*/
public function getFile($key)
{
return isset($this->files[$key]) ? $this->files[$key] : null;
}
/**
* Returns uploaded files.
* @return array
*/
public function getFiles()
{
return $this->files;
}
/**
* Returns variable provided to the script via HTTP cookies.
* @param string key
* @param mixed default value
* @return mixed
*/
public function getCookie($key, $default = null)
{
return isset($this->cookies[$key]) ? $this->cookies[$key] : $default;
}
/**
* Returns variables provided to the script via HTTP cookies.
* @return array
*/
public function getCookies()
{
return $this->cookies;
}
/********************* method & headers ****************d*g**/
/**
* Returns HTTP request method (GET, POST, HEAD, PUT, ...). The method is case-sensitive.
* @return string
*/
public function getMethod()
{
return $this->method;
}
/**
* Checks if the request method is the given one.
* @param string
* @return bool
*/
public function isMethod($method)
{
return strcasecmp($this->method, $method) === 0;
}
/**
* @deprecated
*/
public function isPost()
{
trigger_error('Method isPost() is deprecated, use isMethod(\'POST\') instead.', E_USER_DEPRECATED);
return $this->isMethod('POST');
}
/**
* Return the value of the HTTP header. Pass the header name as the
* plain, HTTP-specified header name (e.g. 'Accept-Encoding').
* @param string
* @param string|null
* @return string|null
*/
public function getHeader($header, $default = null)
{
$header = strtolower($header);
return isset($this->headers[$header]) ? $this->headers[$header] : $default;
}
/**
* Returns all HTTP headers.
* @return array
*/
public function getHeaders()
{
return $this->headers;
}
/**
* Returns referrer.
* @return Url|null
*/
public function getReferer()
{
return isset($this->headers['referer']) ? new Url($this->headers['referer']) : null;
}
/**
* Is the request sent via secure channel (https)?
* @return bool
*/
public function isSecured()
{
return $this->url->getScheme() === 'https';
}
/**
* Is the request sent from the same origin?
* @return bool
*/
public function isSameSite()
{
return isset($this->cookies['nette-samesite']);
}
/**
* Is AJAX request?
* @return bool
*/
public function isAjax()
{
return $this->getHeader('X-Requested-With') === 'XMLHttpRequest';
}
/**
* Returns the IP address of the remote client.
* @return string|null
*/
public function getRemoteAddress()
{
return $this->remoteAddress;
}
/**
* Returns the host of the remote client.
* @return string|null
*/
public function getRemoteHost()
{
if ($this->remoteHost === null && $this->remoteAddress !== null) {
$this->remoteHost = gethostbyaddr($this->remoteAddress);
}
return $this->remoteHost;
}
/**
* Returns raw content of HTTP request body.
* @return string|null
*/
public function getRawBody()
{
return $this->rawBodyCallback ? call_user_func($this->rawBodyCallback) : null;
}
/**
* Parse Accept-Language header and returns preferred language.
* @param string[] supported languages
* @return string|null
*/
public function detectLanguage(array $langs)
{
$header = $this->getHeader('Accept-Language');
if (!$header) {
return null;
}
$s = strtolower($header); // case insensitive
$s = strtr($s, '_', '-'); // cs_CZ means cs-CZ
rsort($langs); // first more specific
preg_match_all('#(' . implode('|', $langs) . ')(?:-[^\s,;=]+)?\s*(?:;\s*q=([0-9.]+))?#', $s, $matches);
if (!$matches[0]) {
return null;
}
$max = 0;
$lang = null;
foreach ($matches[1] as $key => $value) {
$q = $matches[2][$key] === '' ? 1.0 : (float) $matches[2][$key];
if ($q > $max) {
$max = $q;
$lang = $value;
}
}
return $lang;
}
}
+287
View File
@@ -0,0 +1,287 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Http;
use Nette;
use Nette\Utils\Strings;
/**
* Current HTTP request factory.
*/
class RequestFactory
{
use Nette\SmartObject;
/** @internal */
const CHARS = '\x09\x0A\x0D\x20-\x7E\xA0-\x{10FFFF}';
/** @var array */
public $urlFilters = [
'path' => ['#/{2,}#' => '/'], // '%20' => ''
'url' => [], // '#[.,)]\z#' => ''
];
/** @var bool */
private $binary = false;
/** @var array */
private $proxies = [];
/**
* @param bool
* @return static
*/
public function setBinary($binary = true)
{
$this->binary = (bool) $binary;
return $this;
}
/**
* @param array|string
* @return static
*/
public function setProxy($proxy)
{
$this->proxies = (array) $proxy;
return $this;
}
/**
* Creates current HttpRequest object.
* @return Request
*/
public function createHttpRequest()
{
// DETECTS URI, base path and script path of the request.
$url = new UrlScript;
$url->setScheme(!empty($_SERVER['HTTPS']) && strcasecmp($_SERVER['HTTPS'], 'off') ? 'https' : 'http');
$url->setUser(isset($_SERVER['PHP_AUTH_USER']) ? $_SERVER['PHP_AUTH_USER'] : '');
$url->setPassword(isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : '');
// host & port
if (
(isset($_SERVER[$tmp = 'HTTP_HOST']) || isset($_SERVER[$tmp = 'SERVER_NAME']))
&& preg_match('#^([a-z0-9_.-]+|\[[a-f0-9:]+\])(:\d+)?\z#i', $_SERVER[$tmp], $pair)
) {
$url->setHost(strtolower($pair[1]));
if (isset($pair[2])) {
$url->setPort((int) substr($pair[2], 1));
} elseif (isset($_SERVER['SERVER_PORT'])) {
$url->setPort((int) $_SERVER['SERVER_PORT']);
}
}
// path & query
$requestUrl = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '/';
$requestUrl = preg_replace('#^\w++://[^/]++#', '', $requestUrl);
$requestUrl = Strings::replace($requestUrl, $this->urlFilters['url']);
$tmp = explode('?', $requestUrl, 2);
$path = Url::unescape($tmp[0], '%/?#');
$path = Strings::fixEncoding(Strings::replace($path, $this->urlFilters['path']));
$url->setPath($path);
$url->setQuery(isset($tmp[1]) ? $tmp[1] : '');
// detect script path
$lpath = strtolower($path);
$script = isset($_SERVER['SCRIPT_NAME']) ? strtolower($_SERVER['SCRIPT_NAME']) : '';
if ($lpath !== $script) {
$max = min(strlen($lpath), strlen($script));
for ($i = 0; $i < $max && $lpath[$i] === $script[$i]; $i++);
$path = $i ? substr($path, 0, strrpos($path, '/', $i - strlen($path) - 1) + 1) : '/';
}
$url->setScriptPath($path);
// GET, POST, COOKIE
$useFilter = (!in_array(ini_get('filter.default'), ['', 'unsafe_raw'], true) || ini_get('filter.default_flags'));
$query = $url->getQueryParameters();
$post = $useFilter ? filter_input_array(INPUT_POST, FILTER_UNSAFE_RAW) : (empty($_POST) ? [] : $_POST);
$cookies = $useFilter ? filter_input_array(INPUT_COOKIE, FILTER_UNSAFE_RAW) : (empty($_COOKIE) ? [] : $_COOKIE);
// remove invalid characters
$reChars = '#^[' . self::CHARS . ']*+\z#u';
if (!$this->binary) {
$list = [&$query, &$post, &$cookies];
while (list($key, $val) = @each($list)) { // @ intentionally, deprecated in PHP 7.2
foreach ($val as $k => $v) {
if (is_string($k) && (!preg_match($reChars, $k) || preg_last_error())) {
unset($list[$key][$k]);
} elseif (is_array($v)) {
$list[$key][$k] = $v;
$list[] = &$list[$key][$k];
} else {
$list[$key][$k] = (string) preg_replace('#[^' . self::CHARS . ']+#u', '', $v);
}
}
}
unset($list, $key, $val, $k, $v);
}
$url->setQuery($query);
// FILES and create FileUpload objects
$files = [];
$list = [];
if (!empty($_FILES)) {
foreach ($_FILES as $k => $v) {
if (
!is_array($v)
|| !isset($v['name'], $v['type'], $v['size'], $v['tmp_name'], $v['error'])
|| (!$this->binary && is_string($k) && (!preg_match($reChars, $k) || preg_last_error()))
) {
continue;
}
$v['@'] = &$files[$k];
$list[] = $v;
}
}
while (list(, $v) = @each($list)) { // @ intentionally, deprecated in PHP 7.2
if (!isset($v['name'])) {
continue;
} elseif (!is_array($v['name'])) {
if (!$this->binary && (!preg_match($reChars, $v['name']) || preg_last_error())) {
$v['name'] = '';
}
if ($v['error'] !== UPLOAD_ERR_NO_FILE) {
$v['@'] = new FileUpload($v);
}
continue;
}
foreach ($v['name'] as $k => $foo) {
if (!$this->binary && is_string($k) && (!preg_match($reChars, $k) || preg_last_error())) {
continue;
}
$list[] = [
'name' => $v['name'][$k],
'type' => $v['type'][$k],
'size' => $v['size'][$k],
'tmp_name' => $v['tmp_name'][$k],
'error' => $v['error'][$k],
'@' => &$v['@'][$k],
];
}
}
// HEADERS
if (function_exists('apache_request_headers')) {
$headers = apache_request_headers();
} else {
$headers = [];
foreach ($_SERVER as $k => $v) {
if (strncmp($k, 'HTTP_', 5) == 0) {
$k = substr($k, 5);
} elseif (strncmp($k, 'CONTENT_', 8)) {
continue;
}
$headers[strtr($k, '_', '-')] = $v;
}
}
$remoteAddr = !empty($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null;
$remoteHost = !empty($_SERVER['REMOTE_HOST']) ? $_SERVER['REMOTE_HOST'] : null;
// use real client address and host if trusted proxy is used
$usingTrustedProxy = $remoteAddr && array_filter($this->proxies, function ($proxy) use ($remoteAddr) {
return Helpers::ipMatch($remoteAddr, $proxy);
});
if ($usingTrustedProxy) {
if (!empty($_SERVER['HTTP_FORWARDED'])) {
$forwardParams = preg_split('/[,;]/', $_SERVER['HTTP_FORWARDED']);
foreach ($forwardParams as $forwardParam) {
list($key, $value) = explode('=', $forwardParam, 2) + [1 => null];
$proxyParams[strtolower(trim($key))][] = trim($value, " \t\"");
}
if (isset($proxyParams['for'])) {
$address = $proxyParams['for'][0];
if (strpos($address, '[') === false) { //IPv4
$remoteAddr = explode(':', $address)[0];
} else { //IPv6
$remoteAddr = substr($address, 1, strpos($address, ']') - 1);
}
}
if (isset($proxyParams['host']) && count($proxyParams['host']) === 1) {
$host = $proxyParams['host'][0];
$startingDelimiterPosition = strpos($host, '[');
if ($startingDelimiterPosition === false) { //IPv4
$remoteHostArr = explode(':', $host);
$remoteHost = $remoteHostArr[0];
if (isset($remoteHostArr[1])) {
$url->setPort((int) $remoteHostArr[1]);
}
} else { //IPv6
$endingDelimiterPosition = strpos($host, ']');
$remoteHost = substr($host, strpos($host, '[') + 1, $endingDelimiterPosition - 1);
$remoteHostArr = explode(':', substr($host, $endingDelimiterPosition));
if (isset($remoteHostArr[1])) {
$url->setPort((int) $remoteHostArr[1]);
}
}
}
$scheme = (isset($proxyParams['proto']) && count($proxyParams['proto']) === 1) ? $proxyParams['proto'][0] : 'http';
$url->setScheme(strcasecmp($scheme, 'https') === 0 ? 'https' : 'http');
} else {
if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO'])) {
$url->setScheme(strcasecmp($_SERVER['HTTP_X_FORWARDED_PROTO'], 'https') === 0 ? 'https' : 'http');
$url->setPort($url->getScheme() === 'https' ? 443 : 80);
}
if (!empty($_SERVER['HTTP_X_FORWARDED_PORT'])) {
$url->setPort((int) $_SERVER['HTTP_X_FORWARDED_PORT']);
}
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$xForwardedForWithoutProxies = array_filter(explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']), function ($ip) {
return !array_filter($this->proxies, function ($proxy) use ($ip) {
return filter_var(trim($ip), FILTER_VALIDATE_IP) !== false && Helpers::ipMatch(trim($ip), $proxy);
});
});
$remoteAddr = trim(end($xForwardedForWithoutProxies));
$xForwardedForRealIpKey = key($xForwardedForWithoutProxies);
}
if (isset($xForwardedForRealIpKey) && !empty($_SERVER['HTTP_X_FORWARDED_HOST'])) {
$xForwardedHost = explode(',', $_SERVER['HTTP_X_FORWARDED_HOST']);
if (isset($xForwardedHost[$xForwardedForRealIpKey])) {
$remoteHost = trim($xForwardedHost[$xForwardedForRealIpKey]);
}
}
}
}
// method, eg. GET, PUT, ...
$method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : null;
if (
$method === 'POST'
&& isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'])
&& preg_match('#^[A-Z]+\z#', $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'])
) {
$method = $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'];
}
// raw body
$rawBodyCallback = function () {
return file_get_contents('php://input');
};
return new Request($url, null, $post, $files, $cookies, $headers, $method, $remoteAddr, $remoteHost, $rawBodyCallback);
}
}
+322
View File
@@ -0,0 +1,322 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Http;
use Nette;
use Nette\Utils\DateTime;
/**
* HttpResponse class.
*
* @property-read array $headers
*/
class Response implements IResponse
{
use Nette\SmartObject;
/** @var string The domain in which the cookie will be available */
public $cookieDomain = '';
/** @var string The path in which the cookie will be available */
public $cookiePath = '/';
/** @var bool Whether the cookie is available only through HTTPS */
public $cookieSecure = false;
/** @var bool Whether the cookie is hidden from client-side */
public $cookieHttpOnly = true;
/** @var bool Whether warn on possible problem with data in output buffer */
public $warnOnBuffer = true;
/** @var bool Send invisible garbage for IE 6? */
private static $fixIE = true;
/** @var int HTTP response code */
private $code = self::S200_OK;
public function __construct()
{
if (is_int($code = http_response_code())) {
$this->code = $code;
}
}
/**
* Sets HTTP response code.
* @param int
* @param string
* @return static
* @throws Nette\InvalidArgumentException if code is invalid
* @throws Nette\InvalidStateException if HTTP headers have been sent
*/
public function setCode($code, $reason = null)
{
$code = (int) $code;
if ($code < 100 || $code > 599) {
throw new Nette\InvalidArgumentException("Bad HTTP response '$code'.");
}
self::checkHeaders();
$this->code = $code;
static $hasReason = [ // hardcoded in PHP
100, 101,
200, 201, 202, 203, 204, 205, 206,
300, 301, 302, 303, 304, 305, 307, 308,
400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 426, 428, 429, 431,
500, 501, 502, 503, 504, 505, 506, 511,
];
if ($reason || !in_array($code, $hasReason, true)) {
$protocol = isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.1';
header("$protocol $code " . ($reason ?: 'Unknown status'));
} else {
http_response_code($code);
}
return $this;
}
/**
* Returns HTTP response code.
* @return int
*/
public function getCode()
{
return $this->code;
}
/**
* Sends a HTTP header and replaces a previous one.
* @param string header name
* @param string header value
* @return static
* @throws Nette\InvalidStateException if HTTP headers have been sent
*/
public function setHeader($name, $value)
{
self::checkHeaders();
if ($value === null) {
header_remove($name);
} elseif (strcasecmp($name, 'Content-Length') === 0 && ini_get('zlib.output_compression')) {
// ignore, PHP bug #44164
} else {
header($name . ': ' . $value, true, $this->code);
}
return $this;
}
/**
* Adds HTTP header.
* @param string header name
* @param string header value
* @return static
* @throws Nette\InvalidStateException if HTTP headers have been sent
*/
public function addHeader($name, $value)
{
self::checkHeaders();
header($name . ': ' . $value, false, $this->code);
return $this;
}
/**
* Sends a Content-type HTTP header.
* @param string mime-type
* @param string charset
* @return static
* @throws Nette\InvalidStateException if HTTP headers have been sent
*/
public function setContentType($type, $charset = null)
{
$this->setHeader('Content-Type', $type . ($charset ? '; charset=' . $charset : ''));
return $this;
}
/**
* Redirects to a new URL. Note: call exit() after it.
* @param string URL
* @param int HTTP code
* @return void
* @throws Nette\InvalidStateException if HTTP headers have been sent
*/
public function redirect($url, $code = self::S302_FOUND)
{
$this->setCode($code);
$this->setHeader('Location', $url);
if (preg_match('#^https?:|^\s*+[a-z0-9+.-]*+[^:]#i', $url)) {
$escapedUrl = htmlspecialchars($url, ENT_IGNORE | ENT_QUOTES, 'UTF-8');
echo "<h1>Redirect</h1>\n\n<p><a href=\"$escapedUrl\">Please click here to continue</a>.</p>";
}
}
/**
* Sets the number of seconds before a page cached on a browser expires.
* @param string|int|\DateTimeInterface time, value 0 means "must-revalidate"
* @return static
* @throws Nette\InvalidStateException if HTTP headers have been sent
*/
public function setExpiration($time)
{
$this->setHeader('Pragma', null);
if (!$time) { // 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));
return $this;
}
/**
* Checks if headers have been sent.
* @return bool
*/
public function isSent()
{
return headers_sent();
}
/**
* Returns value of an HTTP header.
* @param string
* @param string|null
* @return string|null
*/
public function getHeader($header, $default = null)
{
$header .= ':';
$len = strlen($header);
foreach (headers_list() as $item) {
if (strncasecmp($item, $header, $len) === 0) {
return ltrim(substr($item, $len));
}
}
return $default;
}
/**
* Returns a list of headers to sent.
* @return array (name => value)
*/
public function getHeaders()
{
$headers = [];
foreach (headers_list() as $header) {
$a = strpos($header, ':');
$headers[substr($header, 0, $a)] = (string) substr($header, $a + 2);
}
return $headers;
}
/**
* @deprecated
*/
public static function date($time = null)
{
trigger_error('Method date() is deprecated, use Nette\Http\Helpers::formatDate() instead.', E_USER_DEPRECATED);
return Helpers::formatDate($time);
}
public function __destruct()
{
if (
self::$fixIE
&& isset($_SERVER['HTTP_USER_AGENT'])
&& strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE ') !== false
&& in_array($this->code, [400, 403, 404, 405, 406, 408, 409, 410, 500, 501, 505], true)
&& preg_match('#^text/html(?:;|$)#', $this->getHeader('Content-Type'))
) {
echo Nette\Utils\Random::generate(2e3, " \t\r\n"); // sends invisible garbage for IE
self::$fixIE = false;
}
}
/**
* Sends a cookie.
* @param string name of the cookie
* @param string value
* @param string|int|\DateTimeInterface expiration time, value 0 means "until the browser is closed"
* @param string
* @param string
* @param bool
* @param bool
* @param string
* @return static
* @throws Nette\InvalidStateException if HTTP headers have been sent
*/
public function setCookie($name, $value, $time, $path = null, $domain = null, $secure = null, $httpOnly = null, $sameSite = null)
{
self::checkHeaders();
$options = [
'expires' => $time ? (int) DateTime::from($time)->format('U') : 0,
'path' => $path === null ? $this->cookiePath : (string) $path,
'domain' => $domain === null ? $this->cookieDomain : (string) $domain,
'secure' => $secure === null ? $this->cookieSecure : (bool) $secure,
'httponly' => $httpOnly === null ? $this->cookieHttpOnly : (bool) $httpOnly,
'samesite' => (string) $sameSite,
];
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']
);
}
return $this;
}
/**
* Deletes a cookie.
* @param string name of the cookie.
* @param string
* @param string
* @param bool
* @return void
* @throws Nette\InvalidStateException if HTTP headers have been sent
*/
public function deleteCookie($name, $path = null, $domain = null, $secure = null)
{
$this->setCookie($name, false, 0, $path, $domain, $secure);
}
private function checkHeaders()
{
if (PHP_SAPI === 'cli') {
} elseif (headers_sent($file, $line)) {
throw new Nette\InvalidStateException('Cannot send header after HTTP headers have been sent' . ($file ? " (output started at $file:$line)." : '.'));
} elseif ($this->warnOnBuffer && ob_get_length() && !array_filter(ob_get_status(true), function ($i) { return !$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 start session earlier.');
}
}
}
+546
View File
@@ -0,0 +1,546 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Http;
use Nette;
/**
* Provides access to session sections as well as session settings and management methods.
*/
class Session
{
use Nette\SmartObject;
/** Default file lifetime */
const DEFAULT_FILE_LIFETIME = 3 * Nette\Utils\DateTime::HOUR;
/** @var bool has been session ID regenerated? */
private $regenerated = false;
/** @var bool has been session started? */
private static $started = false;
/** @var array default configuration */
private $options = [
// security
'referer_check' => '', // must be disabled because PHP implementation is invalid
'use_cookies' => 1, // must be enabled to prevent Session Hijacking and Fixation
'use_only_cookies' => 1, // must be enabled to prevent Session Fixation
'use_trans_sid' => 0, // must be disabled to prevent Session Hijacking and Fixation
// cookies
'cookie_lifetime' => 0, // until the browser is closed
'cookie_path' => '/', // cookie is available within the entire domain
'cookie_domain' => '', // cookie is available on current subdomain only
'cookie_secure' => false, // cookie is available on HTTP & HTTPS
'cookie_httponly' => true, // must be enabled to prevent Session Hijacking
// other
'gc_maxlifetime' => self::DEFAULT_FILE_LIFETIME, // 3 hours
];
/** @var IRequest */
private $request;
/** @var IResponse */
private $response;
/** @var \SessionHandlerInterface */
private $handler;
public function __construct(IRequest $request, IResponse $response)
{
$this->request = $request;
$this->response = $response;
}
/**
* Starts and initializes session data.
* @throws Nette\InvalidStateException
* @return void
*/
public function start()
{
if (self::$started) {
return;
}
$this->configure($this->options);
if (!session_id()) { // session is started for first time
$id = $this->request->getCookie(session_name());
if (is_string($id) && preg_match('#^[0-9a-zA-Z,-]{22,256}\z#i', $id)) {
session_id($id);
} else {
unset($_COOKIE[session_name()]);
}
}
try {
// session_start returns false on failure only sometimes
Nette\Utils\Callback::invokeSafe('session_start', [], function ($message) use (&$e) {
$e = new Nette\InvalidStateException($message);
});
} catch (\Exception $e) {
}
if ($e) {
@session_write_close(); // this is needed
throw $e;
}
self::$started = true;
/* structure:
__NF: Data, Meta, Time
DATA: section->variable = data
META: section->variable = Timestamp
*/
$nf = &$_SESSION['__NF'];
if (!is_array($nf)) {
$nf = [];
}
// regenerate empty session
if (empty($nf['Time'])) {
$nf['Time'] = time();
if (!empty($id)) { // ensures that the session was created in strict mode (see use_strict_mode)
$this->regenerateId();
}
}
// process meta metadata
if (isset($nf['META'])) {
$now = time();
// expire section variables
foreach ($nf['META'] as $section => $metadata) {
if (is_array($metadata)) {
foreach ($metadata as $variable => $value) {
if (!empty($value['T']) && $now > $value['T']) {
if ($variable === '') { // expire whole section
unset($nf['META'][$section], $nf['DATA'][$section]);
continue 2;
}
unset($nf['META'][$section][$variable], $nf['DATA'][$section][$variable]);
}
}
}
}
}
register_shutdown_function([$this, 'clean']);
}
/**
* Has been session started?
* @return bool
*/
public function isStarted()
{
return (bool) self::$started;
}
/**
* Ends the current session and store session data.
* @return void
*/
public function close()
{
if (self::$started) {
$this->clean();
session_write_close();
self::$started = false;
}
}
/**
* Destroys all data registered to a session.
* @return void
*/
public function destroy()
{
if (!self::$started) {
throw new Nette\InvalidStateException('Session is not started.');
}
session_destroy();
$_SESSION = null;
self::$started = false;
if (!$this->response->isSent()) {
$params = session_get_cookie_params();
$this->response->deleteCookie(session_name(), $params['path'], $params['domain'], $params['secure']);
}
}
/**
* Does session exists for the current request?
* @return bool
*/
public function exists()
{
return self::$started || $this->request->getCookie($this->getName()) !== null;
}
/**
* Regenerates the session ID.
* @throws Nette\InvalidStateException
* @return void
*/
public function regenerateId()
{
if (self::$started && !$this->regenerated) {
if (headers_sent($file, $line)) {
throw new Nette\InvalidStateException('Cannot regenerate session ID after HTTP headers have been sent' . ($file ? " (output started at $file:$line)." : '.'));
}
if (session_status() === PHP_SESSION_ACTIVE) {
session_regenerate_id(true);
session_write_close();
}
$backup = $_SESSION;
session_start();
$_SESSION = $backup;
}
$this->regenerated = true;
}
/**
* Returns the current session ID. Don't make dependencies, can be changed for each request.
* @return string
*/
public function getId()
{
return session_id();
}
/**
* Sets the session name to a specified one.
* @param string
* @return static
*/
public function setName($name)
{
if (!is_string($name) || !preg_match('#[^0-9.][^.]*\z#A', $name)) {
throw new Nette\InvalidArgumentException('Session name must be a string and cannot contain dot.');
}
session_name($name);
return $this->setOptions([
'name' => $name,
]);
}
/**
* Gets the session name.
* @return string
*/
public function getName()
{
return isset($this->options['name']) ? $this->options['name'] : session_name();
}
/********************* sections management ****************d*g**/
/**
* Returns specified session section.
* @param string
* @param string
* @return SessionSection
* @throws Nette\InvalidArgumentException
*/
public function getSection($section, $class = SessionSection::class)
{
return new $class($this, $section);
}
/**
* Checks if a session section exist and is not empty.
* @param string
* @return bool
*/
public function hasSection($section)
{
if ($this->exists() && !self::$started) {
$this->start();
}
return !empty($_SESSION['__NF']['DATA'][$section]);
}
/**
* Iteration over all sections.
* @return \Iterator
*/
public function getIterator()
{
if ($this->exists() && !self::$started) {
$this->start();
}
if (isset($_SESSION['__NF']['DATA'])) {
return new \ArrayIterator(array_keys($_SESSION['__NF']['DATA']));
} else {
return new \ArrayIterator;
}
}
/**
* Cleans and minimizes meta structures. This method is called automatically on shutdown, do not call it directly.
* @internal
* @return void
*/
public function clean()
{
if (!self::$started || empty($_SESSION)) {
return;
}
$nf = &$_SESSION['__NF'];
if (isset($nf['META']) && is_array($nf['META'])) {
foreach ($nf['META'] as $name => $foo) {
if (empty($nf['META'][$name])) {
unset($nf['META'][$name]);
}
}
}
if (empty($nf['META'])) {
unset($nf['META']);
}
if (empty($nf['DATA'])) {
unset($nf['DATA']);
}
}
/********************* configuration ****************d*g**/
/**
* Sets session options.
* @param array
* @return static
* @throws Nette\NotSupportedException
* @throws Nette\InvalidStateException
*/
public function setOptions(array $options)
{
$normalized = [];
foreach ($options as $key => $value) {
if (!strncmp($key, 'session.', 8)) { // back compatibility
$key = substr($key, 8);
}
$key = strtolower(preg_replace('#(.)(?=[A-Z])#', '$1_', $key)); // camelCase -> snake_case
$normalized[$key] = $value;
}
if (self::$started) {
$this->configure($normalized);
}
$this->options = $normalized + $this->options;
if (!empty($normalized['auto_start'])) {
$this->start();
}
return $this;
}
/**
* Returns all session options.
* @return array
*/
public function getOptions()
{
return $this->options;
}
/**
* Configures session environment.
* @param array
* @return void
*/
private function configure(array $config)
{
$special = ['cache_expire' => 1, 'cache_limiter' => 1, 'save_path' => 1, 'name' => 1];
$cookie = $origCookie = session_get_cookie_params();
foreach ($config as $key => $value) {
if ($value === null || ini_get("session.$key") == $value) { // intentionally ==
continue;
} elseif (strncmp($key, 'cookie_', 7) === 0) {
$cookie[substr($key, 7)] = $value;
} else {
if (session_status() === PHP_SESSION_ACTIVE) {
throw new Nette\InvalidStateException("Unable to set 'session.$key' to value '$value' when session has been started" . (self::$started ? '.' : ' by session.auto_start or session_start().'));
}
if (isset($special[$key])) {
$key = "session_$key";
$key($value);
} elseif (function_exists('ini_set')) {
ini_set("session.$key", (string) $value);
} elseif (ini_get("session.$key") != $value) { // intentionally !=
throw new Nette\NotSupportedException("Unable to set 'session.$key' to '$value' because function ini_set() is disabled.");
}
}
}
if ($cookie !== $origCookie) {
if (PHP_VERSION_ID >= 70300) {
session_set_cookie_params($cookie);
} else {
session_set_cookie_params(
$cookie['lifetime'],
$cookie['path'] . (isset($cookie['samesite']) ? '; SameSite=' . $cookie['samesite'] : ''),
$cookie['domain'],
$cookie['secure'],
$cookie['httponly']
);
}
if (self::$started) {
$this->sendCookie();
}
}
if ($this->handler) {
session_set_save_handler($this->handler);
}
}
/**
* Sets the amount of time allowed between requests before the session will be terminated.
* @param string|int|\DateTimeInterface time, value 0 means "until the browser is closed"
* @return static
*/
public function setExpiration($time)
{
if (empty($time)) {
return $this->setOptions([
'gc_maxlifetime' => self::DEFAULT_FILE_LIFETIME,
'cookie_lifetime' => 0,
]);
} else {
$time = Nette\Utils\DateTime::from($time)->format('U') - time();
return $this->setOptions([
'gc_maxlifetime' => $time,
'cookie_lifetime' => $time,
]);
}
}
/**
* Sets the session cookie parameters.
* @param string path
* @param string domain
* @param bool secure
* @param string samesite
* @return static
*/
public function setCookieParameters($path, $domain = null, $secure = null, $samesite = null)
{
return $this->setOptions([
'cookie_path' => $path,
'cookie_domain' => $domain,
'cookie_secure' => $secure,
'cookie_samesite' => $samesite,
]);
}
/**
* Returns the session cookie parameters.
* @return array containing items: lifetime, path, domain, secure, httponly
*/
public function getCookieParameters()
{
return session_get_cookie_params();
}
/**
* Sets path of the directory used to save session data.
* @return static
*/
public function setSavePath($path)
{
return $this->setOptions([
'save_path' => $path,
]);
}
/**
* @deprecated use setHandler().
* @return static
*/
public function setStorage(ISessionStorage $storage)
{
if (self::$started) {
throw new Nette\InvalidStateException('Unable to set storage when session has been started.');
}
session_set_save_handler(
[$storage, 'open'], [$storage, 'close'], [$storage, 'read'],
[$storage, 'write'], [$storage, 'remove'], [$storage, 'clean']
);
return $this;
}
/**
* Sets user session handler.
* @return static
*/
public function setHandler(\SessionHandlerInterface $handler)
{
if (self::$started) {
throw new Nette\InvalidStateException('Unable to set handler when session has been started.');
}
$this->handler = $handler;
return $this;
}
/**
* Sends the session cookies.
* @return void
*/
private function sendCookie()
{
$cookie = $this->getCookieParameters();
$this->response->setCookie(
session_name(), session_id(),
$cookie['lifetime'] ? $cookie['lifetime'] + time() : 0,
$cookie['path'], $cookie['domain'], $cookie['secure'], $cookie['httponly'],
isset($cookie['samesite']) ? $cookie['samesite'] : null
);
}
}
+223
View File
@@ -0,0 +1,223 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Http;
use Nette;
/**
* Session section.
*/
class SessionSection implements \IteratorAggregate, \ArrayAccess
{
use Nette\SmartObject;
/** @var bool */
public $warnOnUndefined = false;
/** @var Session */
private $session;
/** @var string */
private $name;
/** @var array|null session data storage */
private $data;
/** @var array|bool session metadata storage */
private $meta = false;
/**
* Do not call directly. Use Session::getSection().
*/
public function __construct(Session $session, $name)
{
if (!is_string($name)) {
throw new Nette\InvalidArgumentException('Session namespace must be a string, ' . gettype($name) . ' given.');
}
$this->session = $session;
$this->name = $name;
}
private function start()
{
if ($this->meta === false) {
$this->session->start();
$this->data = &$_SESSION['__NF']['DATA'][$this->name];
$this->meta = &$_SESSION['__NF']['META'][$this->name];
}
}
/**
* Returns an iterator over all section variables.
* @return \Iterator
*/
public function getIterator()
{
$this->start();
if (isset($this->data)) {
return new \ArrayIterator($this->data);
} else {
return new \ArrayIterator;
}
}
/**
* Sets a variable in this session section.
* @param string name
* @param mixed value
* @return void
*/
public function __set($name, $value)
{
$this->start();
$this->data[$name] = $value;
}
/**
* Gets a variable from this session section.
* @param string name
* @return mixed
*/
public function &__get($name)
{
$this->start();
if ($this->warnOnUndefined && !array_key_exists($name, $this->data)) {
trigger_error("The variable '$name' does not exist in session section");
}
return $this->data[$name];
}
/**
* Determines whether a variable in this session section is set.
* @param string name
* @return bool
*/
public function __isset($name)
{
if ($this->session->exists()) {
$this->start();
}
return isset($this->data[$name]);
}
/**
* Unsets a variable in this session section.
* @param string name
* @return void
*/
public function __unset($name)
{
$this->start();
unset($this->data[$name], $this->meta[$name]);
}
/**
* Sets a variable in this session section.
* @param string name
* @param mixed value
* @return void
*/
public function offsetSet($name, $value)
{
$this->__set($name, $value);
}
/**
* Gets a variable from this session section.
* @param string name
* @return mixed
*/
public function offsetGet($name)
{
return $this->__get($name);
}
/**
* Determines whether a variable in this session section is set.
* @param string name
* @return bool
*/
public function offsetExists($name)
{
return $this->__isset($name);
}
/**
* Unsets a variable in this session section.
* @param string name
* @return void
*/
public function offsetUnset($name)
{
$this->__unset($name);
}
/**
* Sets the expiration of the section or specific variables.
* @param string|int|\DateTimeInterface time
* @param mixed optional list of variables / single variable to expire
* @return static
*/
public function setExpiration($time, $variables = null)
{
$this->start();
if ($time) {
$time = Nette\Utils\DateTime::from($time)->format('U');
$max = (int) ini_get('session.gc_maxlifetime');
if ($max !== 0 && ($time - time() > $max + 3)) { // 0 - unlimited in memcache handler, 3 - bulgarian constant
trigger_error("The expiration time is greater than the session expiration $max seconds");
}
}
foreach (is_array($variables) ? $variables : [$variables] as $variable) {
$this->meta[$variable]['T'] = $time ?: null;
}
return $this;
}
/**
* Removes the expiration from the section or specific variables.
* @param mixed optional list of variables / single variable to expire
* @return void
*/
public function removeExpiration($variables = null)
{
$this->start();
foreach (is_array($variables) ? $variables : [$variables] as $variable) {
unset($this->meta[$variable]['T']);
}
}
/**
* Cancels the current session section.
* @return void
*/
public function remove()
{
$this->start();
$this->data = null;
$this->meta = null;
}
}
+514
View File
@@ -0,0 +1,514 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Http;
use Nette;
/**
* URI Syntax (RFC 3986).
*
* <pre>
* scheme user password host port basePath relativeUrl
* | | | | | | |
* /--\ /--\ /------\ /-------\ /--\/--\/----------------------------\
* http://john:x0y17575@nette.org:8042/en/manual.php?name=param#fragment <-- absoluteUrl
* \__________________________/\____________/^\________/^\______/
* | | | |
* authority path query fragment
* </pre>
*
* - authority: [user[:password]@]host[:port]
* - hostUrl: http://user:password@nette.org:8042
* - basePath: /en/ (everything before relative URI not including the script name)
* - baseUrl: http://user:password@nette.org:8042/en/
* - relativeUrl: manual.php
*
* @property string $scheme
* @property string $user
* @property string $password
* @property string $host
* @property int $port
* @property string $path
* @property string $query
* @property string $fragment
* @property-read string $absoluteUrl
* @property-read string $authority
* @property-read string $hostUrl
* @property-read string $basePath
* @property-read string $baseUrl
* @property-read string $relativeUrl
* @property-read array $queryParameters
*/
class Url implements \JsonSerializable
{
use Nette\SmartObject;
/** @var array */
public static $defaultPorts = [
'http' => 80,
'https' => 443,
'ftp' => 21,
'news' => 119,
'nntp' => 119,
];
/** @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 = '';
/**
* @param string|self
* @throws Nette\InvalidArgumentException if URL is malformed
*/
public function __construct($url = null)
{
if (is_string($url)) {
$p = @parse_url($url); // @ - is escalated to exception
if ($p === false) {
throw new Nette\InvalidArgumentException("Malformed or unsupported URI '$url'.");
}
$this->scheme = isset($p['scheme']) ? $p['scheme'] : '';
$this->port = isset($p['port']) ? $p['port'] : null;
$this->host = isset($p['host']) ? rawurldecode($p['host']) : '';
$this->user = isset($p['user']) ? rawurldecode($p['user']) : '';
$this->password = isset($p['pass']) ? rawurldecode($p['pass']) : '';
$this->setPath(isset($p['path']) ? $p['path'] : '');
$this->setQuery(isset($p['query']) ? $p['query'] : []);
$this->fragment = isset($p['fragment']) ? rawurldecode($p['fragment']) : '';
} elseif ($url instanceof self) {
foreach ($this as $key => $val) {
$this->$key = $url->$key;
}
}
}
/**
* Sets the scheme part of URI.
* @param string
* @return static
*/
public function setScheme($value)
{
$this->scheme = (string) $value;
return $this;
}
/**
* Returns the scheme part of URI.
* @return string
*/
public function getScheme()
{
return $this->scheme;
}
/**
* Sets the user name part of URI.
* @param string
* @return static
*/
public function setUser($value)
{
$this->user = (string) $value;
return $this;
}
/**
* Returns the user name part of URI.
* @return string
*/
public function getUser()
{
return $this->user;
}
/**
* Sets the password part of URI.
* @param string
* @return static
*/
public function setPassword($value)
{
$this->password = (string) $value;
return $this;
}
/**
* Returns the password part of URI.
* @return string
*/
public function getPassword()
{
return $this->password;
}
/**
* Sets the host part of URI.
* @param string
* @return static
*/
public function setHost($value)
{
$this->host = (string) $value;
$this->setPath($this->path);
return $this;
}
/**
* Returns the host part of URI.
* @return string
*/
public function getHost()
{
return $this->host;
}
/**
* Returns the part of domain.
* @return string
*/
public function getDomain($level = 2)
{
$parts = ip2long($this->host) ? [$this->host] : explode('.', $this->host);
$parts = $level >= 0 ? array_slice($parts, -$level) : array_slice($parts, 0, $level);
return implode('.', $parts);
}
/**
* Sets the port part of URI.
* @param int
* @return static
*/
public function setPort($value)
{
$this->port = (int) $value;
return $this;
}
/**
* Returns the port part of URI.
* @return int|null
*/
public function getPort()
{
return $this->port ?: (isset(self::$defaultPorts[$this->scheme]) ? self::$defaultPorts[$this->scheme] : null);
}
/**
* Sets the path part of URI.
* @param string
* @return static
*/
public function setPath($value)
{
$this->path = (string) $value;
if ($this->host && substr($this->path, 0, 1) !== '/') {
$this->path = '/' . $this->path;
}
return $this;
}
/**
* Returns the path part of URI.
* @return string
*/
public function getPath()
{
return $this->path;
}
/**
* Sets the query part of URI.
* @param string|array
* @return static
*/
public function setQuery($value)
{
$this->query = is_array($value) ? $value : self::parseQuery($value);
return $this;
}
/**
* Appends the query part of URI.
* @param string|array
* @return static
*/
public function appendQuery($value)
{
$this->query = is_array($value)
? $value + $this->query
: self::parseQuery($this->getQuery() . '&' . $value);
return $this;
}
/**
* Returns the query part of URI.
* @return string
*/
public function getQuery()
{
return http_build_query($this->query, '', '&', PHP_QUERY_RFC3986);
}
/**
* @return array
*/
public function getQueryParameters()
{
return $this->query;
}
/**
* @param string
* @param mixed
* @return mixed
*/
public function getQueryParameter($name, $default = null)
{
return isset($this->query[$name]) ? $this->query[$name] : $default;
}
/**
* @param string
* @param mixed null unsets the parameter
* @return static
*/
public function setQueryParameter($name, $value)
{
$this->query[$name] = $value;
return $this;
}
/**
* Sets the fragment part of URI.
* @param string
* @return static
*/
public function setFragment($value)
{
$this->fragment = (string) $value;
return $this;
}
/**
* Returns the fragment part of URI.
* @return string
*/
public function getFragment()
{
return $this->fragment;
}
/**
* Returns the entire URI including query string and fragment.
* @return string
*/
public function getAbsoluteUrl()
{
return $this->getHostUrl() . $this->path
. (($tmp = $this->getQuery()) ? '?' . $tmp : '')
. ($this->fragment === '' ? '' : '#' . $this->fragment);
}
/**
* Returns the [user[:pass]@]host[:port] part of URI.
* @return string
*/
public function getAuthority()
{
return $this->host === ''
? ''
: ($this->user !== '' && $this->scheme !== 'http' && $this->scheme !== 'https'
? rawurlencode($this->user) . ($this->password === '' ? '' : ':' . rawurlencode($this->password)) . '@'
: '')
. $this->host
. ($this->port && (!isset(self::$defaultPorts[$this->scheme]) || $this->port !== self::$defaultPorts[$this->scheme])
? ':' . $this->port
: '');
}
/**
* Returns the scheme and authority part of URI.
* @return string
*/
public function getHostUrl()
{
return ($this->scheme ? $this->scheme . ':' : '')
. (($authority = $this->getAuthority()) || $this->scheme ? '//' . $authority : '');
}
/**
* Returns the base-path.
* @return string
*/
public function getBasePath()
{
$pos = strrpos($this->path, '/');
return $pos === false ? '' : substr($this->path, 0, $pos + 1);
}
/**
* Returns the base-URI.
* @return string
*/
public function getBaseUrl()
{
return $this->getHostUrl() . $this->getBasePath();
}
/**
* Returns the relative-URI.
* @return string
*/
public function getRelativeUrl()
{
return (string) substr($this->getAbsoluteUrl(), strlen($this->getBaseUrl()));
}
/**
* URL comparison.
* @param string|self
* @return bool
*/
public function isEqual($url)
{
$url = new self($url);
$query = $url->query;
ksort($query);
$query2 = $this->query;
ksort($query2);
$http = in_array($this->scheme, ['http', 'https'], true);
return $url->scheme === $this->scheme
&& !strcasecmp($url->host, $this->host)
&& $url->getPort() === $this->getPort()
&& ($http || $url->user === $this->user)
&& ($http || $url->password === $this->password)
&& self::unescape($url->path, '%/') === self::unescape($this->path, '%/')
&& $query === $query2
&& $url->fragment === $this->fragment;
}
/**
* Transforms URL to canonical form.
* @return static
*/
public function canonicalize()
{
$this->path = preg_replace_callback(
'#[^!$&\'()*+,/:;=@%]+#',
function ($m) { return rawurlencode($m[0]); },
self::unescape($this->path, '%/')
);
$this->host = strtolower($this->host);
return $this;
}
/**
* @return string
*/
public function __toString()
{
return $this->getAbsoluteUrl();
}
/**
* @return string
*/
public function jsonSerialize()
{
return $this->getAbsoluteUrl();
}
/**
* Similar to rawurldecode, but preserves reserved chars encoded.
* @param string to decode
* @param string reserved characters
* @return string
*/
public static function unescape($s, $reserved = '%;/?:@&=+$,')
{
// reserved (@see RFC 2396) = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | ","
// within a path segment, the characters "/", ";", "=", "?" are reserved
// within a query component, the characters ";", "/", "?", ":", "@", "&", "=", "+", ",", "$" are reserved.
if ($reserved !== '') {
$s = preg_replace_callback(
'#%(' . substr(chunk_split(bin2hex($reserved), 2, '|'), 0, -1) . ')#i',
function ($m) { return '%25' . strtoupper($m[1]); },
$s
);
}
return rawurldecode($s);
}
/**
* Parses query string.
* @return array
*/
public static function parseQuery($s)
{
parse_str($s, $res);
return $res;
}
}
+332
View File
@@ -0,0 +1,332 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\Http;
use Nette;
/**
* Immutable representation of a URL.
*
* <pre>
* scheme user password host port path query fragment
* | | | | | | | |
* /--\ /--\ /------\ /-------\ /--\/------------\ /--------\ /------\
* http://john:x0y17575@nette.org:8042/en/manual.php?name=param#fragment <-- absoluteUrl
* \______\__________________________/
* | |
* hostUrl authority
* </pre>
*
* @property-read string $scheme
* @property-read string $user
* @property-read string $password
* @property-read string $host
* @property-read int $port
* @property-read string $path
* @property-read string $query
* @property-read string $fragment
* @property-read string $absoluteUrl
* @property-read string $authority
* @property-read string $hostUrl
* @property-read array $queryParameters
*/
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 = '';
/**
* @param string|self|Url $url
* @throws Nette\InvalidArgumentException if URL is malformed
*/
public function __construct($url)
{
if ($url instanceof Url || $url instanceof self || is_string($url)) {
$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();
} else {
throw new Nette\InvalidArgumentException;
}
$this->build();
}
/**
* @return static
*/
public function withScheme(string $scheme)
{
$dolly = clone $this;
$dolly->scheme = $scheme;
$dolly->build();
return $dolly;
}
public function getScheme(): string
{
return $this->scheme;
}
/**
* @return static
*/
public function withUser(string $user)
{
$dolly = clone $this;
$dolly->user = $user;
$dolly->build();
return $dolly;
}
public function getUser(): string
{
return $this->user;
}
/**
* @return static
*/
public function withPassword(string $password)
{
$dolly = clone $this;
$dolly->password = $password;
$dolly->build();
return $dolly;
}
public function getPassword(): string
{
return $this->password;
}
/**
* @return static
*/
public function withHost(string $host)
{
$dolly = clone $this;
$dolly->host = $host;
$dolly->build();
return $dolly;
}
public function getHost(): string
{
return $this->host;
}
public function getDomain(int $level = 2): string
{
$parts = ip2long($this->host) ? [$this->host] : explode('.', $this->host);
$parts = $level >= 0 ? array_slice($parts, -$level) : array_slice($parts, 0, $level);
return implode('.', $parts);
}
/**
* @return static
*/
public function withPort(int $port)
{
$dolly = clone $this;
$dolly->port = $port;
$dolly->build();
return $dolly;
}
public function getPort(): ?int
{
return $this->port ?: (Url::$defaultPorts[$this->scheme] ?? null);
}
/**
* @return static
*/
public function withPath(string $path)
{
$dolly = clone $this;
$dolly->path = $path;
$dolly->build();
return $dolly;
}
public function getPath(): string
{
return $this->path;
}
/**
* @param string|array $query
* @return static
*/
public function withQuery($query)
{
$dolly = clone $this;
$dolly->query = is_array($query) ? $query : Url::parseQuery($query);
$dolly->build();
return $dolly;
}
public function getQuery(): string
{
return http_build_query($this->query, '', '&', PHP_QUERY_RFC3986);
}
public function getQueryParameters(): array
{
return $this->query;
}
/**
* @return array|string|null
*/
public function getQueryParameter(string $name)
{
return $this->query[$name] ?? null;
}
/**
* @return static
*/
public function withFragment(string $fragment)
{
$dolly = clone $this;
$dolly->fragment = $fragment;
$dolly->build();
return $dolly;
}
public function getFragment(): string
{
return $this->fragment;
}
/**
* Returns the entire URI including query string and fragment.
*/
public function getAbsoluteUrl(): string
{
return $this->getHostUrl() . $this->path
. (($tmp = $this->getQuery()) ? '?' . $tmp : '')
. ($this->fragment === '' ? '' : '#' . $this->fragment);
}
/**
* Returns the [user[:pass]@]host[:port] part of URI.
*/
public function getAuthority(): string
{
return $this->authority;
}
/**
* Returns the scheme and authority part of URI.
*/
public function getHostUrl(): string
{
return ($this->scheme ? $this->scheme . ':' : '')
. ($this->authority ? '//' . $this->authority : '');
}
public function __toString(): string
{
return $this->getAbsoluteUrl();
}
/**
* @param string|Url|self $url
*/
public function isEqual($url): bool
{
return (new Url($this))->isEqual($url);
}
public function jsonSerialize(): string
{
return $this->getAbsoluteUrl();
}
/** @internal */
final public function export(): array
{
return [$this->scheme, $this->user, $this->password, $this->host, $this->port, $this->path, $this->query, $this->fragment];
}
protected function build(): void
{
if ($this->host && substr($this->path, 0, 1) !== '/') {
$this->path = '/' . $this->path;
}
$this->authority = $this->host === ''
? ''
: ($this->user !== ''
? rawurlencode($this->user) . ($this->password === '' ? '' : ':' . rawurlencode($this->password)) . '@'
: '')
. $this->host
. ($this->port && (!isset(Url::$defaultPorts[$this->scheme]) || $this->port !== Url::$defaultPorts[$this->scheme])
? ':' . $this->port
: '');
}
}
+81
View File
@@ -0,0 +1,81 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Http;
/**
* Extended HTTP URL.
*
* <pre>
* http://nette.org/admin/script.php/pathinfo/?name=param#fragment
* \_______________/\________/
* | |
* scriptPath pathInfo
* </pre>
*
* - scriptPath: /admin/script.php (or simply /admin/ when script is directory index)
* - pathInfo: /pathinfo/ (additional path information)
*
* @property string $scriptPath
* @property-read string $pathInfo
*/
class UrlScript extends Url
{
/** @var string */
private $scriptPath;
public function __construct($url = null, $scriptPath = '')
{
parent::__construct($url);
$this->setScriptPath($scriptPath);
}
/**
* Sets the script-path part of URI.
* @param string
* @return static
*/
public function setScriptPath($value)
{
$this->scriptPath = (string) $value;
return $this;
}
/**
* Returns the script-path part of URI.
* @return string
*/
public function getScriptPath()
{
return $this->scriptPath ?: $this->path;
}
/**
* Returns the base-path.
* @return string
*/
public function getBasePath()
{
$pos = strrpos($this->getScriptPath(), '/');
return $pos === false ? '' : substr($this->getPath(), 0, $pos + 1);
}
/**
* Returns the additional path information.
* @return string
*/
public function getPathInfo()
{
return (string) substr($this->getPath(), strlen($this->getScriptPath()));
}
}
+192
View File
@@ -0,0 +1,192 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Http;
use Nette;
use Nette\Security\IIdentity;
/**
* Session storage for user object.
*/
class UserStorage implements Nette\Security\IUserStorage
{
use Nette\SmartObject;
/** @var string */
private $namespace = '';
/** @var Session */
private $sessionHandler;
/** @var SessionSection */
private $sessionSection;
public function __construct(Session $sessionHandler)
{
$this->sessionHandler = $sessionHandler;
}
/**
* Sets the authenticated status of this user.
* @param bool
* @return static
*/
public function setAuthenticated($state)
{
$section = $this->getSessionSection(true);
$section->authenticated = (bool) $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?
* @return bool
*/
public function isAuthenticated()
{
$session = $this->getSessionSection(false);
return $session && $session->authenticated;
}
/**
* Sets the user identity.
* @return static
*/
public function setIdentity(IIdentity $identity = null)
{
$this->getSessionSection(true)->identity = $identity;
return $this;
}
/**
* Returns current user identity, if any.
* @return Nette\Security\IIdentity|null
*/
public function getIdentity()
{
$session = $this->getSessionSection(false);
return $session ? $session->identity : null;
}
/**
* Changes namespace; allows more users to share a session.
* @param string
* @return static
*/
public function setNamespace($namespace)
{
if ($this->namespace !== $namespace) {
$this->namespace = (string) $namespace;
$this->sessionSection = null;
}
return $this;
}
/**
* Returns current namespace.
* @return string
*/
public function getNamespace()
{
return $this->namespace;
}
/**
* Enables log out after inactivity.
* @param string|int|\DateTimeInterface Number of seconds or timestamp
* @param int flag IUserStorage::CLEAR_IDENTITY
* @return static
*/
public function setExpiration($time, $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?
* @return int|null
*/
public function getLogoutReason()
{
$session = $this->getSessionSection(false);
return $session ? $session->reason : null;
}
/**
* Returns and initializes $this->sessionSection.
* @return SessionSection|null
*/
protected function getSessionSection($need)
{
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;
}
}