Augmentation vers version 3.3.0

This commit is contained in:
Gauvain Boiché
2020-03-31 15:31:03 +02:00
parent d926806907
commit a1864c0414
2618 changed files with 406015 additions and 31377 deletions

View File

@@ -24,7 +24,7 @@ class AcceptHeader
/**
* @var AcceptHeaderItem[]
*/
private $items = array();
private $items = [];
/**
* @var bool

View File

@@ -21,13 +21,12 @@ class AcceptHeaderItem
private $value;
private $quality = 1.0;
private $index = 0;
private $attributes = array();
private $attributes = [];
/**
* @param string $value
* @param array $attributes
*/
public function __construct($value, array $attributes = array())
public function __construct($value, array $attributes = [])
{
$this->value = $value;
foreach ($attributes as $name => $value) {
@@ -46,7 +45,7 @@ class AcceptHeaderItem
{
$bits = preg_split('/\s*(?:;*("[^"]+");*|;*(\'[^\']+\');*|;+)\s*/', $itemValue, 0, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
$value = array_shift($bits);
$attributes = array();
$attributes = [];
$lastNullAttribute = null;
foreach ($bits as $bit) {

View File

@@ -44,7 +44,7 @@ class BinaryFileResponse extends Response
* @param bool $autoEtag Whether the ETag header should be automatically set
* @param bool $autoLastModified Whether the Last-Modified header should be automatically set
*/
public function __construct($file, $status = 200, $headers = array(), $public = true, $contentDisposition = null, $autoEtag = false, $autoLastModified = true)
public function __construct($file, $status = 200, $headers = [], $public = true, $contentDisposition = null, $autoEtag = false, $autoLastModified = true)
{
parent::__construct(null, $status, $headers);
@@ -66,7 +66,7 @@ class BinaryFileResponse extends Response
*
* @return static
*/
public static function create($file = null, $status = 200, $headers = array(), $public = true, $contentDisposition = null, $autoEtag = false, $autoLastModified = true)
public static function create($file = null, $status = 200, $headers = [], $public = true, $contentDisposition = null, $autoEtag = false, $autoLastModified = true)
{
return new static($file, $status, $headers, $public, $contentDisposition, $autoEtag, $autoLastModified);
}
@@ -139,7 +139,7 @@ class BinaryFileResponse extends Response
*/
public function setAutoEtag()
{
$this->setEtag(sha1_file($this->file->getPathname()));
$this->setEtag(base64_encode(hash_file('sha256', $this->file->getPathname(), true)));
return $this;
}
@@ -184,13 +184,6 @@ class BinaryFileResponse extends Response
*/
public function prepare(Request $request)
{
$this->headers->set('Content-Length', $this->file->getSize());
if (!$this->headers->has('Accept-Ranges')) {
// Only accept ranges on safe HTTP methods
$this->headers->set('Accept-Ranges', $request->isMethodSafe(false) ? 'bytes' : 'none');
}
if (!$this->headers->has('Content-Type')) {
$this->headers->set('Content-Type', $this->file->getMimeType() ?: 'application/octet-stream');
}
@@ -204,6 +197,16 @@ class BinaryFileResponse extends Response
$this->offset = 0;
$this->maxlen = -1;
if (false === $fileSize = $this->file->getSize()) {
return $this;
}
$this->headers->set('Content-Length', $fileSize);
if (!$this->headers->has('Accept-Ranges')) {
// Only accept ranges on safe HTTP methods
$this->headers->set('Accept-Ranges', $request->isMethodSafe(false) ? 'bytes' : 'none');
}
if (self::$trustXSendfileTypeHeader && $request->headers->has('X-Sendfile-Type')) {
// Use X-Sendfile, do not send any content.
$type = $request->headers->get('X-Sendfile-Type');
@@ -224,20 +227,24 @@ class BinaryFileResponse extends Response
if (substr($path, 0, \strlen($pathPrefix)) === $pathPrefix) {
$path = $location.substr($path, \strlen($pathPrefix));
// Only set X-Accel-Redirect header if a valid URI can be produced
// as nginx does not serve arbitrary file paths.
$this->headers->set($type, $path);
$this->maxlen = 0;
break;
}
}
}
} else {
$this->headers->set($type, $path);
$this->maxlen = 0;
}
$this->headers->set($type, $path);
$this->maxlen = 0;
} elseif ($request->headers->has('Range')) {
// Process the range headers.
if (!$request->headers->has('If-Range') || $this->hasValidIfRangeHeader($request->headers->get('If-Range'))) {
$range = $request->headers->get('Range');
$fileSize = $this->file->getSize();
list($start, $end) = explode('-', substr($range, 6), 2) + array(0);
list($start, $end) = explode('-', substr($range, 6), 2) + [0];
$end = ('' === $end) ? $fileSize - 1 : (int) $end;
@@ -303,7 +310,7 @@ class BinaryFileResponse extends Response
fclose($out);
fclose($file);
if ($this->deleteFileAfterSend) {
if ($this->deleteFileAfterSend && file_exists($this->file->getPathname())) {
unlink($this->file->getPathname());
}
@@ -320,12 +327,12 @@ class BinaryFileResponse extends Response
if (null !== $content) {
throw new \LogicException('The content cannot be set on a BinaryFileResponse instance.');
}
return $this;
}
/**
* {@inheritdoc}
*
* @return false
*/
public function getContent()
{
@@ -341,7 +348,7 @@ class BinaryFileResponse extends Response
}
/**
* If this is set to true, the file will be unlinked after the request is send
* If this is set to true, the file will be unlinked after the request is sent
* Note: If the X-Sendfile header is used, the deleteFileAfterSend setting will not be used.
*
* @param bool $shouldDelete

View File

@@ -18,6 +18,10 @@ namespace Symfony\Component\HttpFoundation;
*/
class Cookie
{
const SAMESITE_NONE = 'none';
const SAMESITE_LAX = 'lax';
const SAMESITE_STRICT = 'strict';
protected $name;
protected $value;
protected $domain;
@@ -26,21 +30,79 @@ class Cookie
protected $secure;
protected $httpOnly;
private $raw;
private $sameSite;
private static $reservedCharsList = "=,; \t\r\n\v\f";
private static $reservedCharsFrom = ['=', ',', ';', ' ', "\t", "\r", "\n", "\v", "\f"];
private static $reservedCharsTo = ['%3D', '%2C', '%3B', '%20', '%09', '%0D', '%0A', '%0B', '%0C'];
/**
* @param string $name The name of the cookie
* @param string $value The value of the cookie
* @param int|string|\DateTime|\DateTimeInterface $expire The time the cookie expires
* @param string $path The path on the server in which the cookie will be available on
* @param string $domain The domain that the cookie is available to
* @param bool $secure Whether the cookie should only be transmitted over a secure HTTPS connection from the client
* @param bool $httpOnly Whether the cookie will be made accessible only through the HTTP protocol
* Creates cookie from raw header string.
*
* @param string $cookie
* @param bool $decode
*
* @return static
*/
public static function fromString($cookie, $decode = false)
{
$data = [
'expires' => 0,
'path' => '/',
'domain' => null,
'secure' => false,
'httponly' => false,
'raw' => !$decode,
'samesite' => null,
];
foreach (explode(';', $cookie) as $part) {
if (false === strpos($part, '=')) {
$key = trim($part);
$value = true;
} else {
list($key, $value) = explode('=', trim($part), 2);
$key = trim($key);
$value = trim($value);
}
if (!isset($data['name'])) {
$data['name'] = $decode ? urldecode($key) : $key;
$data['value'] = true === $value ? null : ($decode ? urldecode($value) : $value);
continue;
}
switch ($key = strtolower($key)) {
case 'name':
case 'value':
break;
case 'max-age':
$data['expires'] = time() + (int) $value;
break;
default:
$data[$key] = $value;
break;
}
}
return new static($data['name'], $data['value'], $data['expires'], $data['path'], $data['domain'], $data['secure'], $data['httponly'], $data['raw'], $data['samesite']);
}
/**
* @param string $name The name of the cookie
* @param string|null $value The value of the cookie
* @param int|string|\DateTimeInterface $expire The time the cookie expires
* @param string $path The path on the server in which the cookie will be available on
* @param string|null $domain The domain that the cookie is available to
* @param bool $secure Whether the cookie should only be transmitted over a secure HTTPS connection from the client
* @param bool $httpOnly Whether the cookie will be made accessible only through the HTTP protocol
* @param bool $raw Whether the cookie value should be sent with no url encoding
* @param string|null $sameSite Whether the cookie will be available for cross-site requests
*
* @throws \InvalidArgumentException
*/
public function __construct($name, $value = null, $expire = 0, $path = '/', $domain = null, $secure = false, $httpOnly = true)
public function __construct($name, $value = null, $expire = 0, $path = '/', $domain = null, $secure = false, $httpOnly = true, $raw = false, $sameSite = null)
{
// from PHP source code
if (preg_match("/[=,; \t\r\n\013\014]/", $name)) {
if ($raw && false !== strpbrk($name, self::$reservedCharsList)) {
throw new \InvalidArgumentException(sprintf('The cookie name "%s" contains invalid characters.', $name));
}
@@ -49,7 +111,7 @@ class Cookie
}
// convert expiration time to a Unix timestamp
if ($expire instanceof \DateTime || $expire instanceof \DateTimeInterface) {
if ($expire instanceof \DateTimeInterface) {
$expire = $expire->format('U');
} elseif (!is_numeric($expire)) {
$expire = strtotime($expire);
@@ -66,6 +128,17 @@ class Cookie
$this->path = empty($path) ? '/' : $path;
$this->secure = (bool) $secure;
$this->httpOnly = (bool) $httpOnly;
$this->raw = (bool) $raw;
if (null !== $sameSite) {
$sameSite = strtolower($sameSite);
}
if (!\in_array($sameSite, [self::SAMESITE_LAX, self::SAMESITE_STRICT, self::SAMESITE_NONE, null], true)) {
throw new \InvalidArgumentException('The "sameSite" parameter value is not valid.');
}
$this->sameSite = $sameSite;
}
/**
@@ -75,20 +148,26 @@ class Cookie
*/
public function __toString()
{
$str = urlencode($this->getName()).'=';
if ($this->isRaw()) {
$str = $this->getName();
} else {
$str = str_replace(self::$reservedCharsFrom, self::$reservedCharsTo, $this->getName());
}
$str .= '=';
if ('' === (string) $this->getValue()) {
$str .= 'deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001);
$str .= 'deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001).'; Max-Age=0';
} else {
$str .= rawurlencode($this->getValue());
$str .= $this->isRaw() ? $this->getValue() : rawurlencode($this->getValue());
if (0 !== $this->getExpiresTime()) {
$str .= '; expires='.gmdate('D, d-M-Y H:i:s T', $this->getExpiresTime());
$str .= '; expires='.gmdate('D, d-M-Y H:i:s T', $this->getExpiresTime()).'; Max-Age='.$this->getMaxAge();
}
}
if ($this->path) {
$str .= '; path='.$this->path;
if ($this->getPath()) {
$str .= '; path='.$this->getPath();
}
if ($this->getDomain()) {
@@ -103,6 +182,10 @@ class Cookie
$str .= '; httponly';
}
if (null !== $this->getSameSite()) {
$str .= '; samesite='.$this->getSameSite();
}
return $str;
}
@@ -119,7 +202,7 @@ class Cookie
/**
* Gets the value of the cookie.
*
* @return string
* @return string|null
*/
public function getValue()
{
@@ -129,7 +212,7 @@ class Cookie
/**
* Gets the domain that the cookie is available to.
*
* @return string
* @return string|null
*/
public function getDomain()
{
@@ -146,6 +229,18 @@ class Cookie
return $this->expire;
}
/**
* Gets the max-age attribute.
*
* @return int
*/
public function getMaxAge()
{
$maxAge = $this->expire - time();
return 0 >= $maxAge ? 0 : $maxAge;
}
/**
* Gets the path on the server in which the cookie will be available on.
*
@@ -185,4 +280,24 @@ class Cookie
{
return 0 !== $this->expire && $this->expire < time();
}
/**
* Checks if the cookie value should be sent with no url encoding.
*
* @return bool
*/
public function isRaw()
{
return $this->raw;
}
/**
* Gets the SameSite attribute.
*
* @return string|null
*/
public function getSameSite()
{
return $this->sameSite;
}
}

View File

@@ -14,10 +14,8 @@ namespace Symfony\Component\HttpFoundation\Exception;
/**
* The HTTP request contains headers with conflicting information.
*
* This exception should trigger an HTTP 400 response in your application code.
*
* @author Magnus Nordlander <magnus@fervo.se>
*/
class ConflictingHeadersException extends \RuntimeException
class ConflictingHeadersException extends \UnexpectedValueException implements RequestExceptionInterface
{
}

View File

@@ -0,0 +1,21 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation\Exception;
/**
* Interface for Request exceptions.
*
* Exceptions implementing this interface should trigger an HTTP 400 response in the application code.
*/
interface RequestExceptionInterface
{
}

View File

@@ -0,0 +1,20 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation\Exception;
/**
* Raised when a user has performed an operation that should be considered
* suspicious from a security perspective.
*/
class SuspiciousOperationException extends \UnexpectedValueException implements RequestExceptionInterface
{
}

View File

@@ -35,13 +35,13 @@ class ExpressionRequestMatcher extends RequestMatcher
throw new \LogicException('Unable to match the request as the expression language is not available.');
}
return $this->language->evaluate($this->expression, array(
return $this->language->evaluate($this->expression, [
'request' => $request,
'method' => $request->getMethod(),
'path' => rawurldecode($request->getPathInfo()),
'host' => $request->getHost(),
'ip' => $request->getClientIp(),
'attributes' => $request->attributes->all(),
)) && parent::matches($request);
]) && parent::matches($request);
}
}

View File

@@ -37,7 +37,7 @@ class ExtensionGuesser implements ExtensionGuesserInterface
*
* @var array
*/
protected $guessers = array();
protected $guessers = [];
/**
* Returns the singleton instance.
@@ -90,5 +90,7 @@ class ExtensionGuesser implements ExtensionGuesserInterface
return $extension;
}
}
return null;
}
}

View File

@@ -31,7 +31,7 @@ class FileBinaryMimeTypeGuesser implements MimeTypeGuesserInterface
*
* @param string $cmd The command to run to get the mime type of a file
*/
public function __construct($cmd = 'file -b --mime %s 2>/dev/null')
public function __construct($cmd = 'file -b --mime -- %s 2>/dev/null')
{
$this->cmd = $cmd;
}
@@ -74,24 +74,24 @@ class FileBinaryMimeTypeGuesser implements MimeTypeGuesserInterface
}
if (!self::isSupported()) {
return;
return null;
}
ob_start();
// need to use --mime instead of -i. see #6641
passthru(sprintf($this->cmd, escapeshellarg($path)), $return);
passthru(sprintf($this->cmd, escapeshellarg((0 === strpos($path, '-') ? './' : '').$path)), $return);
if ($return > 0) {
ob_end_clean();
return;
return null;
}
$type = trim(ob_get_clean());
if (!preg_match('#^([a-z0-9\-]+/[a-z0-9\-\.]+)#i', $type, $match)) {
if (!preg_match('#^([a-z0-9\-]+/[a-z0-9\-\+\.]+)#i', $type, $match)) {
// it's not a type, but an error message
return;
return null;
}
return $match[1];

View File

@@ -26,7 +26,7 @@ class FileinfoMimeTypeGuesser implements MimeTypeGuesserInterface
/**
* @param string $magicFile A magic file to use with the finfo instance
*
* @see http://www.php.net/manual/en/function.finfo-open.php
* @see https://php.net/finfo-open
*/
public function __construct($magicFile = null)
{
@@ -57,11 +57,11 @@ class FileinfoMimeTypeGuesser implements MimeTypeGuesserInterface
}
if (!self::isSupported()) {
return;
return null;
}
if (!$finfo = new \finfo(FILEINFO_MIME_TYPE, $this->magicFile)) {
return;
return null;
}
return $finfo->file($path);

View File

@@ -20,11 +20,11 @@ class MimeTypeExtensionGuesser implements ExtensionGuesserInterface
* A map of mime types and their default extensions.
*
* This list has been placed under the public domain by the Apache HTTPD project.
* This list has been updated from upstream on 2013-04-23.
* This list has been updated from upstream on 2019-01-14.
*
* @see http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types
* @see https://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types
*/
protected $defaultExtensions = array(
protected $defaultExtensions = [
'application/andrew-inset' => 'ez',
'application/applixware' => 'aw',
'application/atom+xml' => 'atom',
@@ -618,7 +618,7 @@ class MimeTypeExtensionGuesser implements ExtensionGuesserInterface
'audio/adpcm' => 'adp',
'audio/basic' => 'au',
'audio/midi' => 'mid',
'audio/mp4' => 'mp4a',
'audio/mp4' => 'm4a',
'audio/mpeg' => 'mpga',
'audio/ogg' => 'oga',
'audio/s3m' => 's3m',
@@ -653,6 +653,11 @@ class MimeTypeExtensionGuesser implements ExtensionGuesserInterface
'chemical/x-cml' => 'cml',
'chemical/x-csml' => 'csml',
'chemical/x-xyz' => 'xyz',
'font/collection' => 'ttc',
'font/otf' => 'otf',
'font/ttf' => 'ttf',
'font/woff' => 'woff',
'font/woff2' => 'woff2',
'image/bmp' => 'bmp',
'image/x-ms-bmp' => 'bmp',
'image/cgm' => 'cgm',
@@ -669,8 +674,8 @@ class MimeTypeExtensionGuesser implements ExtensionGuesserInterface
'image/tiff' => 'tiff',
'image/vnd.adobe.photoshop' => 'psd',
'image/vnd.dece.graphic' => 'uvi',
'image/vnd.dvb.subtitle' => 'sub',
'image/vnd.djvu' => 'djvu',
'image/vnd.dvb.subtitle' => 'sub',
'image/vnd.dwg' => 'dwg',
'image/vnd.dxf' => 'dxf',
'image/vnd.fastbidsheet' => 'fbs',
@@ -732,8 +737,8 @@ class MimeTypeExtensionGuesser implements ExtensionGuesserInterface
'text/vcard' => 'vcard',
'text/vnd.curl' => 'curl',
'text/vnd.curl.dcurl' => 'dcurl',
'text/vnd.curl.scurl' => 'scurl',
'text/vnd.curl.mcurl' => 'mcurl',
'text/vnd.curl.scurl' => 'scurl',
'text/vnd.dvb.subtitle' => 'sub',
'text/vnd.fly' => 'fly',
'text/vnd.fmi.flexstor' => 'flx',
@@ -743,13 +748,14 @@ class MimeTypeExtensionGuesser implements ExtensionGuesserInterface
'text/vnd.sun.j2me.app-descriptor' => 'jad',
'text/vnd.wap.wml' => 'wml',
'text/vnd.wap.wmlscript' => 'wmls',
'text/vtt' => 'vtt',
'text/x-asm' => 's',
'text/x-c' => 'c',
'text/x-fortran' => 'f',
'text/x-pascal' => 'p',
'text/x-java-source' => 'java',
'text/x-opml' => 'opml',
'text/x-nfo' => 'nfo',
'text/x-opml' => 'opml',
'text/x-pascal' => 'p',
'text/x-setext' => 'etx',
'text/x-sfv' => 'sfv',
'text/x-uuencode' => 'uu',
@@ -795,13 +801,19 @@ class MimeTypeExtensionGuesser implements ExtensionGuesserInterface
'video/x-sgi-movie' => 'movie',
'video/x-smv' => 'smv',
'x-conference/x-cooltalk' => 'ice',
);
];
/**
* {@inheritdoc}
*/
public function guess($mimeType)
{
return isset($this->defaultExtensions[$mimeType]) ? $this->defaultExtensions[$mimeType] : null;
if (isset($this->defaultExtensions[$mimeType])) {
return $this->defaultExtensions[$mimeType];
}
$lcMimeType = strtolower($mimeType);
return isset($this->defaultExtensions[$lcMimeType]) ? $this->defaultExtensions[$lcMimeType] : null;
}
}

View File

@@ -51,7 +51,7 @@ class MimeTypeGuesser implements MimeTypeGuesserInterface
*
* @var array
*/
protected $guessers = array();
protected $guessers = [];
/**
* Returns the singleton instance.
@@ -129,5 +129,7 @@ class MimeTypeGuesser implements MimeTypeGuesserInterface
if (2 === \count($this->guessers) && !FileBinaryMimeTypeGuesser::isSupported() && !FileinfoMimeTypeGuesser::isSupported()) {
throw new \LogicException('Unable to guess the mime type as no guessers are available (Did you enable the php_fileinfo extension?)');
}
return null;
}
}

View File

@@ -26,7 +26,7 @@ interface MimeTypeGuesserInterface
*
* @param string $path The path to the file
*
* @return string The mime type or NULL, if none could be guessed
* @return string|null The mime type or NULL, if none could be guessed
*
* @throws FileNotFoundException If the file does not exist
* @throws AccessDeniedException If the file could not be read

View File

@@ -0,0 +1,28 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation\File;
/**
* A PHP stream of unknown size.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class Stream extends File
{
/**
* {@inheritdoc}
*/
public function getSize()
{
return false;
}
}

View File

@@ -214,13 +214,26 @@ class UploadedFile extends File
*/
public static function getMaxFilesize()
{
$iniMax = strtolower(ini_get('upload_max_filesize'));
$sizePostMax = self::parseFilesize(ini_get('post_max_size'));
$sizeUploadMax = self::parseFilesize(ini_get('upload_max_filesize'));
if ('' === $iniMax) {
return PHP_INT_MAX;
return min($sizePostMax ?: PHP_INT_MAX, $sizeUploadMax ?: PHP_INT_MAX);
}
/**
* Returns the given size from an ini value in bytes.
*
* @return int The given size in bytes
*/
private static function parseFilesize($size)
{
if ('' === $size) {
return 0;
}
$max = ltrim($iniMax, '+');
$size = strtolower($size);
$max = ltrim($size, '+');
if (0 === strpos($max, '0x')) {
$max = \intval($max, 16);
} elseif (0 === strpos($max, '0')) {
@@ -229,7 +242,7 @@ class UploadedFile extends File
$max = (int) $max;
}
switch (substr($iniMax, -1)) {
switch (substr($size, -1)) {
case 't': $max *= 1024;
// no break
case 'g': $max *= 1024;
@@ -249,7 +262,7 @@ class UploadedFile extends File
*/
public function getErrorMessage()
{
static $errors = array(
static $errors = [
UPLOAD_ERR_INI_SIZE => 'The file "%s" exceeds your upload_max_filesize ini directive (limit is %d KiB).',
UPLOAD_ERR_FORM_SIZE => 'The file "%s" exceeds the upload limit defined in your form.',
UPLOAD_ERR_PARTIAL => 'The file "%s" was only partially uploaded.',
@@ -257,7 +270,7 @@ class UploadedFile extends File
UPLOAD_ERR_CANT_WRITE => 'The file "%s" could not be written on disk.',
UPLOAD_ERR_NO_TMP_DIR => 'File could not be uploaded: missing temporary directory.',
UPLOAD_ERR_EXTENSION => 'File upload was stopped by a PHP extension.',
);
];
$errorCode = $this->error;
$maxFilesize = UPLOAD_ERR_INI_SIZE === $errorCode ? self::getMaxFilesize() / 1024 : 0;

View File

@@ -21,12 +21,12 @@ use Symfony\Component\HttpFoundation\File\UploadedFile;
*/
class FileBag extends ParameterBag
{
private static $fileKeys = array('error', 'name', 'size', 'tmp_name', 'type');
private static $fileKeys = ['error', 'name', 'size', 'tmp_name', 'type'];
/**
* @param array $parameters An array of HTTP files
*/
public function __construct(array $parameters = array())
public function __construct(array $parameters = [])
{
$this->replace($parameters);
}
@@ -34,9 +34,9 @@ class FileBag extends ParameterBag
/**
* {@inheritdoc}
*/
public function replace(array $files = array())
public function replace(array $files = [])
{
$this->parameters = array();
$this->parameters = [];
$this->add($files);
}
@@ -55,7 +55,7 @@ class FileBag extends ParameterBag
/**
* {@inheritdoc}
*/
public function add(array $files = array())
public function add(array $files = [])
{
foreach ($files as $key => $file) {
$this->set($key, $file);
@@ -75,8 +75,8 @@ class FileBag extends ParameterBag
return $file;
}
$file = $this->fixPhpFilesArray($file);
if (\is_array($file)) {
$file = $this->fixPhpFilesArray($file);
$keys = array_keys($file);
sort($keys);
@@ -87,7 +87,7 @@ class FileBag extends ParameterBag
$file = new UploadedFile($file['tmp_name'], $file['name'], $file['type'], $file['size'], $file['error']);
}
} else {
$file = array_map(array($this, 'convertFileInformation'), $file);
$file = array_map([$this, 'convertFileInformation'], $file);
if (array_keys($keys) === $keys) {
$file = array_filter($file);
}
@@ -109,14 +109,12 @@ class FileBag extends ParameterBag
* It's safe to pass an already converted array, in which case this method
* just returns the original array unmodified.
*
* @param array $data
*
* @return array
*/
protected function fixPhpFilesArray($data)
{
if (!\is_array($data)) {
return $data;
}
$keys = array_keys($data);
sort($keys);
@@ -130,13 +128,13 @@ class FileBag extends ParameterBag
}
foreach ($data['name'] as $key => $name) {
$files[$key] = $this->fixPhpFilesArray(array(
$files[$key] = $this->fixPhpFilesArray([
'error' => $data['error'][$key],
'name' => $name,
'type' => $data['type'][$key],
'tmp_name' => $data['tmp_name'][$key],
'size' => $data['size'][$key],
));
]);
}
return $files;

View File

@@ -18,13 +18,13 @@ namespace Symfony\Component\HttpFoundation;
*/
class HeaderBag implements \IteratorAggregate, \Countable
{
protected $headers = array();
protected $cacheControl = array();
protected $headers = [];
protected $cacheControl = [];
/**
* @param array $headers An array of HTTP headers
*/
public function __construct(array $headers = array())
public function __construct(array $headers = [])
{
foreach ($headers as $key => $values) {
$this->set($key, $values);
@@ -38,14 +38,14 @@ class HeaderBag implements \IteratorAggregate, \Countable
*/
public function __toString()
{
if (!$this->headers) {
if (!$headers = $this->all()) {
return '';
}
$max = max(array_map('strlen', array_keys($this->headers))) + 1;
ksort($headers);
$max = max(array_map('strlen', array_keys($headers))) + 1;
$content = '';
ksort($this->headers);
foreach ($this->headers as $name => $values) {
foreach ($headers as $name => $values) {
$name = implode('-', array_map('ucfirst', explode('-', $name)));
foreach ($values as $value) {
$content .= sprintf("%-{$max}s %s\r\n", $name.':', $value);
@@ -72,7 +72,7 @@ class HeaderBag implements \IteratorAggregate, \Countable
*/
public function keys()
{
return array_keys($this->headers);
return array_keys($this->all());
}
/**
@@ -80,9 +80,9 @@ class HeaderBag implements \IteratorAggregate, \Countable
*
* @param array $headers An array of HTTP headers
*/
public function replace(array $headers = array())
public function replace(array $headers = [])
{
$this->headers = array();
$this->headers = [];
$this->add($headers);
}
@@ -101,29 +101,38 @@ class HeaderBag implements \IteratorAggregate, \Countable
/**
* Returns a header value by name.
*
* @param string $key The header name
* @param string|string[]|null $default The default value
* @param bool $first Whether to return the first value or all header values
* @param string $key The header name
* @param string|null $default The default value
* @param bool $first Whether to return the first value or all header values
*
* @return string|string[]|null The first header value or default value if $first is true, an array of values otherwise
*/
public function get($key, $default = null, $first = true)
{
$key = str_replace('_', '-', strtolower($key));
$headers = $this->all();
if (!array_key_exists($key, $this->headers)) {
if (!\array_key_exists($key, $headers)) {
if (null === $default) {
return $first ? null : array();
return $first ? null : [];
}
return $first ? $default : array($default);
return $first ? $default : [$default];
}
if ($first) {
return \count($this->headers[$key]) ? $this->headers[$key][0] : $default;
if (!$headers[$key]) {
return $default;
}
if (null === $headers[$key][0]) {
return null;
}
return (string) $headers[$key][0];
}
return $this->headers[$key];
return $headers[$key];
}
/**
@@ -137,12 +146,20 @@ class HeaderBag implements \IteratorAggregate, \Countable
{
$key = str_replace('_', '-', strtolower($key));
$values = array_values((array) $values);
if (\is_array($values)) {
$values = array_values($values);
if (true === $replace || !isset($this->headers[$key])) {
$this->headers[$key] = $values;
if (true === $replace || !isset($this->headers[$key])) {
$this->headers[$key] = $values;
} else {
$this->headers[$key] = array_merge($this->headers[$key], $values);
}
} else {
$this->headers[$key] = array_merge($this->headers[$key], $values);
if (true === $replace || !isset($this->headers[$key])) {
$this->headers[$key] = [$values];
} else {
$this->headers[$key][] = $values;
}
}
if ('cache-control' === $key) {
@@ -159,7 +176,7 @@ class HeaderBag implements \IteratorAggregate, \Countable
*/
public function has($key)
{
return array_key_exists(str_replace('_', '-', strtolower($key)), $this->headers);
return \array_key_exists(str_replace('_', '-', strtolower($key)), $this->all());
}
/**
@@ -187,7 +204,7 @@ class HeaderBag implements \IteratorAggregate, \Countable
unset($this->headers[$key]);
if ('cache-control' === $key) {
$this->cacheControl = array();
$this->cacheControl = [];
}
}
@@ -236,7 +253,7 @@ class HeaderBag implements \IteratorAggregate, \Countable
*/
public function hasCacheControlDirective($key)
{
return array_key_exists($key, $this->cacheControl);
return \array_key_exists($key, $this->cacheControl);
}
/**
@@ -248,7 +265,7 @@ class HeaderBag implements \IteratorAggregate, \Countable
*/
public function getCacheControlDirective($key)
{
return array_key_exists($key, $this->cacheControl) ? $this->cacheControl[$key] : null;
return \array_key_exists($key, $this->cacheControl) ? $this->cacheControl[$key] : null;
}
/**
@@ -285,7 +302,7 @@ class HeaderBag implements \IteratorAggregate, \Countable
protected function getCacheControlHeader()
{
$parts = array();
$parts = [];
ksort($this->cacheControl);
foreach ($this->cacheControl as $key => $value) {
if (true === $value) {
@@ -311,7 +328,7 @@ class HeaderBag implements \IteratorAggregate, \Countable
*/
protected function parseCacheControl($header)
{
$cacheControl = array();
$cacheControl = [];
preg_match_all('#([a-zA-Z][a-zA-Z_-]*)\s*(?:=(?:"([^"]*)"|([^ \t",;]*)))?#', $header, $matches, PREG_SET_ORDER);
foreach ($matches as $match) {
$cacheControl[strtolower($match[1])] = isset($match[3]) ? $match[3] : (isset($match[2]) ? $match[2] : true);

View File

@@ -18,7 +18,7 @@ namespace Symfony\Component\HttpFoundation;
*/
class IpUtils
{
private static $checkedIps = array();
private static $checkedIps = [];
/**
* This class should not be instantiated.
@@ -38,7 +38,7 @@ class IpUtils
public static function checkIp($requestIp, $ips)
{
if (!\is_array($ips)) {
$ips = array($ips);
$ips = [$ips];
}
$method = substr_count($requestIp, ':') > 1 ? 'checkIp6' : 'checkIp4';

View File

@@ -18,7 +18,7 @@ namespace Symfony\Component\HttpFoundation;
* object. It is however recommended that you do return an object as it
* protects yourself against XSSI and JSON-JavaScript Hijacking.
*
* @see https://www.owasp.org/index.php/OWASP_AJAX_Security_Guidelines#Always_return_JSON_with_an_Object_on_the_outside
* @see https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/AJAX_Security_Cheat_Sheet.md#always-return-json-with-an-object-on-the-outside
*
* @author Igor Wiedler <igor@wiedler.ch>
*/
@@ -29,14 +29,17 @@ class JsonResponse extends Response
// Encode <, >, ', &, and " characters in the JSON, making it also safe to be embedded into HTML.
// 15 === JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT
protected $encodingOptions = 15;
const DEFAULT_ENCODING_OPTIONS = 15;
protected $encodingOptions = self::DEFAULT_ENCODING_OPTIONS;
/**
* @param mixed $data The response data
* @param int $status The response status code
* @param array $headers An array of response headers
* @param bool $json If the data is already a JSON string
*/
public function __construct($data = null, $status = 200, $headers = array())
public function __construct($data = null, $status = 200, $headers = [], $json = false)
{
parent::__construct('', $status, $headers);
@@ -44,7 +47,7 @@ class JsonResponse extends Response
$data = new \ArrayObject();
}
$this->setData($data);
$json ? $this->setJson($data) : $this->setData($data);
}
/**
@@ -52,20 +55,39 @@ class JsonResponse extends Response
*
* Example:
*
* return JsonResponse::create($data, 200)
* return JsonResponse::create(['key' => 'value'])
* ->setSharedMaxAge(300);
*
* @param mixed $data The json response data
* @param mixed $data The JSON response data
* @param int $status The response status code
* @param array $headers An array of response headers
*
* @return static
*/
public static function create($data = null, $status = 200, $headers = array())
public static function create($data = null, $status = 200, $headers = [])
{
return new static($data, $status, $headers);
}
/**
* Factory method for chainability.
*
* Example:
*
* return JsonResponse::fromJsonString('{"key": "value"}')
* ->setSharedMaxAge(300);
*
* @param string|null $data The JSON response string
* @param int $status The response status code
* @param array $headers An array of response headers
*
* @return static
*/
public static function fromJsonString($data = null, $status = 200, $headers = [])
{
return new static($data, $status, $headers, true);
}
/**
* Sets the JSONP callback.
*
@@ -78,16 +100,16 @@ class JsonResponse extends Response
public function setCallback($callback = null)
{
if (null !== $callback) {
// partially token from http://www.geekality.net/2011/08/03/valid-javascript-identifier/
// partially token from https://github.com/willdurand/JsonpCallbackValidator
// partially taken from https://geekality.net/2011/08/03/valid-javascript-identifier/
// partially taken from https://github.com/willdurand/JsonpCallbackValidator
// JsonpCallbackValidator is released under the MIT License. See https://github.com/willdurand/JsonpCallbackValidator/blob/v1.1.0/LICENSE for details.
// (c) William Durand <william.durand1@gmail.com>
$pattern = '/^[$_\p{L}][$_\p{L}\p{Mn}\p{Mc}\p{Nd}\p{Pc}\x{200C}\x{200D}]*(?:\[(?:"(?:\\\.|[^"\\\])*"|\'(?:\\\.|[^\'\\\])*\'|\d+)\])*?$/u';
$reserved = array(
$reserved = [
'break', 'do', 'instanceof', 'typeof', 'case', 'else', 'new', 'var', 'catch', 'finally', 'return', 'void', 'continue', 'for', 'switch', 'while',
'debugger', 'function', 'this', 'with', 'default', 'if', 'throw', 'delete', 'in', 'try', 'class', 'enum', 'extends', 'super', 'const', 'export',
'import', 'implements', 'let', 'private', 'public', 'yield', 'interface', 'package', 'protected', 'static', 'null', 'true', 'false',
);
];
$parts = explode('.', $callback);
foreach ($parts as $part) {
if (!preg_match($pattern, $part) || \in_array($part, $reserved, true)) {
@@ -101,6 +123,22 @@ class JsonResponse extends Response
return $this->update();
}
/**
* Sets a raw string containing a JSON document to be sent.
*
* @param string $json
*
* @return $this
*
* @throws \InvalidArgumentException
*/
public function setJson($json)
{
$this->data = $json;
return $this->update();
}
/**
* Sets the data to be sent as JSON.
*
@@ -110,7 +148,7 @@ class JsonResponse extends Response
*
* @throws \InvalidArgumentException
*/
public function setData($data = array())
public function setData($data = [])
{
if (\defined('HHVM_VERSION')) {
// HHVM does not trigger any warnings and let exceptions
@@ -118,45 +156,26 @@ class JsonResponse extends Response
// If only PHP did the same...
$data = json_encode($data, $this->encodingOptions);
} else {
try {
if (!interface_exists('JsonSerializable', false)) {
// PHP 5.3 triggers annoying warnings for some
// types that can't be serialized as JSON (INF, resources, etc.)
// but doesn't provide the JsonSerializable interface.
set_error_handler(function () { return false; });
if (!interface_exists('JsonSerializable', false)) {
set_error_handler(function () { return false; });
try {
$data = @json_encode($data, $this->encodingOptions);
} finally {
restore_error_handler();
} elseif (\PHP_VERSION_ID < 50500) {
// PHP 5.4 and up wrap exceptions thrown by JsonSerializable
// objects in a new exception that needs to be removed.
// Fortunately, PHP 5.5 and up do not trigger any warning anymore.
// Clear json_last_error()
json_encode(null);
$errorHandler = set_error_handler('var_dump');
restore_error_handler();
set_error_handler(function () use ($errorHandler) {
if (JSON_ERROR_NONE === json_last_error()) {
return $errorHandler && false !== \call_user_func_array($errorHandler, \func_get_args());
}
});
$data = json_encode($data, $this->encodingOptions);
restore_error_handler();
} else {
}
} else {
try {
$data = json_encode($data, $this->encodingOptions);
} catch (\Exception $e) {
if ('Exception' === \get_class($e) && 0 === strpos($e->getMessage(), 'Failed calling ')) {
throw $e->getPrevious() ?: $e;
}
throw $e;
}
} catch (\Error $e) {
if (\PHP_VERSION_ID < 50500 || !interface_exists('JsonSerializable', false)) {
restore_error_handler();
if (\PHP_VERSION_ID >= 70300 && (JSON_THROW_ON_ERROR & $this->encodingOptions)) {
return $this->setJson($data);
}
throw $e;
} catch (\Exception $e) {
if (\PHP_VERSION_ID < 50500 || !interface_exists('JsonSerializable', false)) {
restore_error_handler();
}
if (interface_exists('JsonSerializable', false) && 'Exception' === \get_class($e) && 0 === strpos($e->getMessage(), 'Failed calling ')) {
throw $e->getPrevious() ?: $e;
}
throw $e;
}
}
@@ -164,9 +183,7 @@ class JsonResponse extends Response
throw new \InvalidArgumentException(json_last_error_msg());
}
$this->data = $data;
return $this->update();
return $this->setJson($data);
}
/**

View File

@@ -1,4 +1,4 @@
Copyright (c) 2004-2018 Fabien Potencier
Copyright (c) 2004-2019 Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -26,7 +26,7 @@ class ParameterBag implements \IteratorAggregate, \Countable
/**
* @param array $parameters An array of parameters
*/
public function __construct(array $parameters = array())
public function __construct(array $parameters = [])
{
$this->parameters = $parameters;
}
@@ -56,7 +56,7 @@ class ParameterBag implements \IteratorAggregate, \Countable
*
* @param array $parameters An array of parameters
*/
public function replace(array $parameters = array())
public function replace(array $parameters = [])
{
$this->parameters = $parameters;
}
@@ -66,7 +66,7 @@ class ParameterBag implements \IteratorAggregate, \Countable
*
* @param array $parameters An array of parameters
*/
public function add(array $parameters = array())
public function add(array $parameters = [])
{
$this->parameters = array_replace($this->parameters, $parameters);
}
@@ -74,67 +74,14 @@ class ParameterBag implements \IteratorAggregate, \Countable
/**
* Returns a parameter by name.
*
* Note: Finding deep items is deprecated since version 2.8, to be removed in 3.0.
*
* @param string $key The key
* @param mixed $default The default value if the parameter key does not exist
* @param bool $deep If true, a path like foo[bar] will find deeper items
*
* @return mixed
*
* @throws \InvalidArgumentException
*/
public function get($key, $default = null, $deep = false)
public function get($key, $default = null)
{
if ($deep) {
@trigger_error('Using paths to find deeper items in '.__METHOD__.' is deprecated since Symfony 2.8 and will be removed in 3.0. Filter the returned value in your own code instead.', E_USER_DEPRECATED);
}
if (!$deep || false === $pos = strpos($key, '[')) {
return array_key_exists($key, $this->parameters) ? $this->parameters[$key] : $default;
}
$root = substr($key, 0, $pos);
if (!array_key_exists($root, $this->parameters)) {
return $default;
}
$value = $this->parameters[$root];
$currentKey = null;
for ($i = $pos, $c = \strlen($key); $i < $c; ++$i) {
$char = $key[$i];
if ('[' === $char) {
if (null !== $currentKey) {
throw new \InvalidArgumentException(sprintf('Malformed path. Unexpected "[" at position %d.', $i));
}
$currentKey = '';
} elseif (']' === $char) {
if (null === $currentKey) {
throw new \InvalidArgumentException(sprintf('Malformed path. Unexpected "]" at position %d.', $i));
}
if (!\is_array($value) || !array_key_exists($currentKey, $value)) {
return $default;
}
$value = $value[$currentKey];
$currentKey = null;
} else {
if (null === $currentKey) {
throw new \InvalidArgumentException(sprintf('Malformed path. Unexpected "%s" at position %d.', $char, $i));
}
$currentKey .= $char;
}
}
if (null !== $currentKey) {
throw new \InvalidArgumentException('Malformed path. Path must end with "]".');
}
return $value;
return \array_key_exists($key, $this->parameters) ? $this->parameters[$key] : $default;
}
/**
@@ -157,7 +104,7 @@ class ParameterBag implements \IteratorAggregate, \Countable
*/
public function has($key)
{
return array_key_exists($key, $this->parameters);
return \array_key_exists($key, $this->parameters);
}
/**
@@ -175,13 +122,12 @@ class ParameterBag implements \IteratorAggregate, \Countable
*
* @param string $key The parameter key
* @param string $default The default value if the parameter key does not exist
* @param bool $deep If true, a path like foo[bar] will find deeper items
*
* @return string The filtered value
*/
public function getAlpha($key, $default = '', $deep = false)
public function getAlpha($key, $default = '')
{
return preg_replace('/[^[:alpha:]]/', '', $this->get($key, $default, $deep));
return preg_replace('/[^[:alpha:]]/', '', $this->get($key, $default));
}
/**
@@ -189,13 +135,12 @@ class ParameterBag implements \IteratorAggregate, \Countable
*
* @param string $key The parameter key
* @param string $default The default value if the parameter key does not exist
* @param bool $deep If true, a path like foo[bar] will find deeper items
*
* @return string The filtered value
*/
public function getAlnum($key, $default = '', $deep = false)
public function getAlnum($key, $default = '')
{
return preg_replace('/[^[:alnum:]]/', '', $this->get($key, $default, $deep));
return preg_replace('/[^[:alnum:]]/', '', $this->get($key, $default));
}
/**
@@ -203,14 +148,13 @@ class ParameterBag implements \IteratorAggregate, \Countable
*
* @param string $key The parameter key
* @param string $default The default value if the parameter key does not exist
* @param bool $deep If true, a path like foo[bar] will find deeper items
*
* @return string The filtered value
*/
public function getDigits($key, $default = '', $deep = false)
public function getDigits($key, $default = '')
{
// we need to remove - and + because they're allowed in the filter
return str_replace(array('-', '+'), '', $this->filter($key, $default, FILTER_SANITIZE_NUMBER_INT, array(), $deep));
return str_replace(['-', '+'], '', $this->filter($key, $default, FILTER_SANITIZE_NUMBER_INT));
}
/**
@@ -218,13 +162,12 @@ class ParameterBag implements \IteratorAggregate, \Countable
*
* @param string $key The parameter key
* @param int $default The default value if the parameter key does not exist
* @param bool $deep If true, a path like foo[bar] will find deeper items
*
* @return int The filtered value
*/
public function getInt($key, $default = 0, $deep = false)
public function getInt($key, $default = 0)
{
return (int) $this->get($key, $default, $deep);
return (int) $this->get($key, $default);
}
/**
@@ -232,13 +175,12 @@ class ParameterBag implements \IteratorAggregate, \Countable
*
* @param string $key The parameter key
* @param bool $default The default value if the parameter key does not exist
* @param bool $deep If true, a path like foo[bar] will find deeper items
*
* @return bool The filtered value
*/
public function getBoolean($key, $default = false, $deep = false)
public function getBoolean($key, $default = false)
{
return $this->filter($key, $default, FILTER_VALIDATE_BOOLEAN, array(), $deep);
return $this->filter($key, $default, FILTER_VALIDATE_BOOLEAN);
}
/**
@@ -248,34 +190,18 @@ class ParameterBag implements \IteratorAggregate, \Countable
* @param mixed $default Default = null
* @param int $filter FILTER_* constant
* @param mixed $options Filter options
* @param bool $deep Default = false
*
* @see http://php.net/manual/en/function.filter-var.php
* @see https://php.net/filter-var
*
* @return mixed
*/
public function filter($key, $default = null, $filter = FILTER_DEFAULT, $options = array(), $deep = false)
public function filter($key, $default = null, $filter = FILTER_DEFAULT, $options = [])
{
static $filters = null;
if (null === $filters) {
foreach (filter_list() as $tmp) {
$filters[filter_id($tmp)] = 1;
}
}
if (\is_bool($filter) || !isset($filters[$filter]) || \is_array($deep)) {
@trigger_error('Passing the $deep boolean as 3rd argument to the '.__METHOD__.' method is deprecated since Symfony 2.8 and will be removed in 3.0. Remove it altogether as the $deep argument will be removed in 3.0.', E_USER_DEPRECATED);
$tmp = $deep;
$deep = $filter;
$filter = $options;
$options = $tmp;
}
$value = $this->get($key, $default, $deep);
$value = $this->get($key, $default);
// Always turn $options into an array - this allows filter_var option shortcuts.
if (!\is_array($options) && $options) {
$options = array('flags' => $options);
$options = ['flags' => $options];
}
// Add a convenience check for arrays.

View File

@@ -30,9 +30,9 @@ class RedirectResponse extends Response
*
* @throws \InvalidArgumentException
*
* @see http://tools.ietf.org/html/rfc2616#section-10.3
* @see https://tools.ietf.org/html/rfc2616#section-10.3
*/
public function __construct($url, $status = 302, $headers = array())
public function __construct($url, $status = 302, $headers = [])
{
parent::__construct('', $status, $headers);
@@ -41,6 +41,10 @@ class RedirectResponse extends Response
if (!$this->isRedirect()) {
throw new \InvalidArgumentException(sprintf('The HTTP status code is not a redirect ("%s" given).', $status));
}
if (301 == $status && !\array_key_exists('cache-control', array_change_key_case($headers, \CASE_LOWER))) {
$this->headers->remove('cache-control');
}
}
/**
@@ -52,7 +56,7 @@ class RedirectResponse extends Response
*
* @return static
*/
public static function create($url = '', $status = 302, $headers = array())
public static function create($url = '', $status = 302, $headers = [])
{
return new static($url, $status, $headers);
}
@@ -89,7 +93,7 @@ class RedirectResponse extends Response
<html>
<head>
<meta charset="UTF-8" />
<meta http-equiv="refresh" content="0;url=%1$s" />
<meta http-equiv="refresh" content="0;url=\'%1$s\'" />
<title>Redirecting to %1$s</title>
</head>

View File

@@ -12,6 +12,7 @@
namespace Symfony\Component\HttpFoundation;
use Symfony\Component\HttpFoundation\Exception\ConflictingHeadersException;
use Symfony\Component\HttpFoundation\Exception\SuspiciousOperationException;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
/**
@@ -29,11 +30,22 @@ use Symfony\Component\HttpFoundation\Session\SessionInterface;
*/
class Request
{
const HEADER_FORWARDED = 'forwarded';
const HEADER_CLIENT_IP = 'client_ip';
const HEADER_CLIENT_HOST = 'client_host';
const HEADER_CLIENT_PROTO = 'client_proto';
const HEADER_CLIENT_PORT = 'client_port';
const HEADER_FORWARDED = 0b00001; // When using RFC 7239
const HEADER_X_FORWARDED_FOR = 0b00010;
const HEADER_X_FORWARDED_HOST = 0b00100;
const HEADER_X_FORWARDED_PROTO = 0b01000;
const HEADER_X_FORWARDED_PORT = 0b10000;
const HEADER_X_FORWARDED_ALL = 0b11110; // All "X-Forwarded-*" headers
const HEADER_X_FORWARDED_AWS_ELB = 0b11010; // AWS ELB doesn't send X-Forwarded-Host
/** @deprecated since version 3.3, to be removed in 4.0 */
const HEADER_CLIENT_IP = self::HEADER_X_FORWARDED_FOR;
/** @deprecated since version 3.3, to be removed in 4.0 */
const HEADER_CLIENT_HOST = self::HEADER_X_FORWARDED_HOST;
/** @deprecated since version 3.3, to be removed in 4.0 */
const HEADER_CLIENT_PROTO = self::HEADER_X_FORWARDED_PROTO;
/** @deprecated since version 3.3, to be removed in 4.0 */
const HEADER_CLIENT_PORT = self::HEADER_X_FORWARDED_PORT;
const METHOD_HEAD = 'HEAD';
const METHOD_GET = 'GET';
@@ -49,17 +61,17 @@ class Request
/**
* @var string[]
*/
protected static $trustedProxies = array();
protected static $trustedProxies = [];
/**
* @var string[]
*/
protected static $trustedHostPatterns = array();
protected static $trustedHostPatterns = [];
/**
* @var string[]
*/
protected static $trustedHosts = array();
protected static $trustedHosts = [];
/**
* Names for headers that can be trusted when
@@ -69,63 +81,65 @@ class Request
*
* The other headers are non-standard, but widely used
* by popular reverse proxies (like Apache mod_proxy or Amazon EC2).
*
* @deprecated since version 3.3, to be removed in 4.0
*/
protected static $trustedHeaders = array(
protected static $trustedHeaders = [
self::HEADER_FORWARDED => 'FORWARDED',
self::HEADER_CLIENT_IP => 'X_FORWARDED_FOR',
self::HEADER_CLIENT_HOST => 'X_FORWARDED_HOST',
self::HEADER_CLIENT_PROTO => 'X_FORWARDED_PROTO',
self::HEADER_CLIENT_PORT => 'X_FORWARDED_PORT',
);
];
protected static $httpMethodParameterOverride = false;
/**
* Custom parameters.
*
* @var \Symfony\Component\HttpFoundation\ParameterBag
* @var ParameterBag
*/
public $attributes;
/**
* Request body parameters ($_POST).
*
* @var \Symfony\Component\HttpFoundation\ParameterBag
* @var ParameterBag
*/
public $request;
/**
* Query string parameters ($_GET).
*
* @var \Symfony\Component\HttpFoundation\ParameterBag
* @var ParameterBag
*/
public $query;
/**
* Server and execution environment parameters ($_SERVER).
*
* @var \Symfony\Component\HttpFoundation\ServerBag
* @var ServerBag
*/
public $server;
/**
* Uploaded files ($_FILES).
*
* @var \Symfony\Component\HttpFoundation\FileBag
* @var FileBag
*/
public $files;
/**
* Cookies ($_COOKIE).
*
* @var \Symfony\Component\HttpFoundation\ParameterBag
* @var ParameterBag
*/
public $cookies;
/**
* Headers (taken from the $_SERVER).
*
* @var \Symfony\Component\HttpFoundation\HeaderBag
* @var HeaderBag
*/
public $headers;
@@ -185,7 +199,7 @@ class Request
protected $format;
/**
* @var \Symfony\Component\HttpFoundation\Session\SessionInterface
* @var SessionInterface
*/
protected $session;
@@ -206,14 +220,26 @@ class Request
protected static $requestFactory;
private $isHostValid = true;
private $isForwardedValid = true;
private static $forwardedParams = array(
self::HEADER_CLIENT_IP => 'for',
self::HEADER_CLIENT_HOST => 'host',
self::HEADER_CLIENT_PROTO => 'proto',
self::HEADER_CLIENT_PORT => 'host',
);
private static $trustedHeaderSet = -1;
/** @deprecated since version 3.3, to be removed in 4.0 */
private static $trustedHeaderNames = [
self::HEADER_FORWARDED => 'FORWARDED',
self::HEADER_CLIENT_IP => 'X_FORWARDED_FOR',
self::HEADER_CLIENT_HOST => 'X_FORWARDED_HOST',
self::HEADER_CLIENT_PROTO => 'X_FORWARDED_PROTO',
self::HEADER_CLIENT_PORT => 'X_FORWARDED_PORT',
];
private static $forwardedParams = [
self::HEADER_X_FORWARDED_FOR => 'for',
self::HEADER_X_FORWARDED_HOST => 'host',
self::HEADER_X_FORWARDED_PROTO => 'proto',
self::HEADER_X_FORWARDED_PORT => 'host',
];
/**
* @param array $query The GET parameters
@@ -224,7 +250,7 @@ class Request
* @param array $server The SERVER parameters
* @param string|resource|null $content The raw body data
*/
public function __construct(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null)
public function __construct(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null)
{
$this->initialize($query, $request, $attributes, $cookies, $files, $server, $content);
}
@@ -242,7 +268,7 @@ class Request
* @param array $server The SERVER parameters
* @param string|resource|null $content The raw body data
*/
public function initialize(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null)
public function initialize(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null)
{
$this->request = new ParameterBag($request);
$this->query = new ParameterBag($query);
@@ -277,18 +303,18 @@ class Request
// HTTP_CONTENT_TYPE and HTTP_CONTENT_LENGTH fields.
$server = $_SERVER;
if ('cli-server' === \PHP_SAPI) {
if (array_key_exists('HTTP_CONTENT_LENGTH', $_SERVER)) {
if (\array_key_exists('HTTP_CONTENT_LENGTH', $_SERVER)) {
$server['CONTENT_LENGTH'] = $_SERVER['HTTP_CONTENT_LENGTH'];
}
if (array_key_exists('HTTP_CONTENT_TYPE', $_SERVER)) {
if (\array_key_exists('HTTP_CONTENT_TYPE', $_SERVER)) {
$server['CONTENT_TYPE'] = $_SERVER['HTTP_CONTENT_TYPE'];
}
}
$request = self::createRequestFromFactory($_GET, $_POST, array(), $_COOKIE, $_FILES, $server);
$request = self::createRequestFromFactory($_GET, $_POST, [], $_COOKIE, $_FILES, $server);
if (0 === strpos($request->headers->get('CONTENT_TYPE'), 'application/x-www-form-urlencoded')
&& \in_array(strtoupper($request->server->get('REQUEST_METHOD', 'GET')), array('PUT', 'DELETE', 'PATCH'))
&& \in_array(strtoupper($request->server->get('REQUEST_METHOD', 'GET')), ['PUT', 'DELETE', 'PATCH'])
) {
parse_str($request->getContent(), $data);
$request->request = new ParameterBag($data);
@@ -313,13 +339,13 @@ class Request
*
* @return static
*/
public static function create($uri, $method = 'GET', $parameters = array(), $cookies = array(), $files = array(), $server = array(), $content = null)
public static function create($uri, $method = 'GET', $parameters = [], $cookies = [], $files = [], $server = [], $content = null)
{
$server = array_replace(array(
$server = array_replace([
'SERVER_NAME' => 'localhost',
'SERVER_PORT' => 80,
'HTTP_HOST' => 'localhost',
'HTTP_USER_AGENT' => 'Symfony/2.X',
'HTTP_USER_AGENT' => 'Symfony/3.X',
'HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'HTTP_ACCEPT_LANGUAGE' => 'en-us,en;q=0.5',
'HTTP_ACCEPT_CHARSET' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
@@ -328,7 +354,7 @@ class Request
'SCRIPT_FILENAME' => '',
'SERVER_PROTOCOL' => 'HTTP/1.1',
'REQUEST_TIME' => time(),
), $server);
], $server);
$server['PATH_INFO'] = '';
$server['REQUEST_METHOD'] = strtoupper($method);
@@ -376,10 +402,10 @@ class Request
// no break
case 'PATCH':
$request = $parameters;
$query = array();
$query = [];
break;
default:
$request = array();
$request = [];
$query = $parameters;
break;
}
@@ -402,7 +428,7 @@ class Request
$server['REQUEST_URI'] = $components['path'].('' !== $queryString ? '?'.$queryString : '');
$server['QUERY_STRING'] = $queryString;
return self::createRequestFromFactory($query, $request, array(), $cookies, $files, $server, $content);
return self::createRequestFromFactory($query, $request, [], $cookies, $files, $server, $content);
}
/**
@@ -502,11 +528,15 @@ class Request
try {
$content = $this->getContent();
} catch (\LogicException $e) {
if (\PHP_VERSION_ID >= 70400) {
throw $e;
}
return trigger_error($e, E_USER_ERROR);
}
$cookieHeader = '';
$cookies = array();
$cookies = [];
foreach ($this->cookies as $k => $v) {
$cookies[] = $k.'='.$v;
@@ -540,19 +570,19 @@ class Request
foreach ($this->headers->all() as $key => $value) {
$key = strtoupper(str_replace('-', '_', $key));
if (\in_array($key, array('CONTENT_TYPE', 'CONTENT_LENGTH'))) {
if (\in_array($key, ['CONTENT_TYPE', 'CONTENT_LENGTH'])) {
$_SERVER[$key] = implode(', ', $value);
} else {
$_SERVER['HTTP_'.$key] = implode(', ', $value);
}
}
$request = array('g' => $_GET, 'p' => $_POST, 'c' => $_COOKIE);
$request = ['g' => $_GET, 'p' => $_POST, 'c' => $_COOKIE];
$requestOrder = ini_get('request_order') ?: ini_get('variables_order');
$requestOrder = preg_replace('#[^cgp]#', '', strtolower($requestOrder)) ?: 'gp';
$_REQUEST = array();
$_REQUEST = [];
foreach (str_split($requestOrder) as $order) {
$_REQUEST = array_merge($_REQUEST, $request[$order]);
}
@@ -563,11 +593,26 @@ class Request
*
* You should only list the reverse proxies that you manage directly.
*
* @param array $proxies A list of trusted proxies
* @param array $proxies A list of trusted proxies
* @param int $trustedHeaderSet A bit field of Request::HEADER_*, to set which headers to trust from your proxies
*
* @throws \InvalidArgumentException When $trustedHeaderSet is invalid
*/
public static function setTrustedProxies(array $proxies)
public static function setTrustedProxies(array $proxies/*, int $trustedHeaderSet*/)
{
self::$trustedProxies = $proxies;
if (2 > \func_num_args()) {
@trigger_error(sprintf('The %s() method expects a bit field of Request::HEADER_* as second argument since Symfony 3.3. Defining it will be required in 4.0. ', __METHOD__), E_USER_DEPRECATED);
return;
}
$trustedHeaderSet = (int) func_get_arg(1);
foreach (self::$trustedHeaderNames as $header => $name) {
self::$trustedHeaders[$header] = $header & $trustedHeaderSet ? $name : null;
}
self::$trustedHeaderSet = $trustedHeaderSet;
}
/**
@@ -580,6 +625,16 @@ class Request
return self::$trustedProxies;
}
/**
* Gets the set of trusted headers from trusted proxies.
*
* @return int A bit field of Request::HEADER_* that defines which headers are trusted from your proxies
*/
public static function getTrustedHeaderSet()
{
return self::$trustedHeaderSet;
}
/**
* Sets a list of trusted host patterns.
*
@@ -593,7 +648,7 @@ class Request
return sprintf('{%s}i', $hostPattern);
}, $hostPatterns);
// we need to reset trusted hosts on trusted host patterns change
self::$trustedHosts = array();
self::$trustedHosts = [];
}
/**
@@ -623,14 +678,35 @@ class Request
* @param string $value The header name
*
* @throws \InvalidArgumentException
*
* @deprecated since version 3.3, to be removed in 4.0. Use the $trustedHeaderSet argument of the Request::setTrustedProxies() method instead.
*/
public static function setTrustedHeaderName($key, $value)
{
if (!array_key_exists($key, self::$trustedHeaders)) {
@trigger_error(sprintf('The "%s()" method is deprecated since Symfony 3.3 and will be removed in 4.0. Use the $trustedHeaderSet argument of the Request::setTrustedProxies() method instead.', __METHOD__), E_USER_DEPRECATED);
if ('forwarded' === $key) {
$key = self::HEADER_FORWARDED;
} elseif ('client_ip' === $key) {
$key = self::HEADER_CLIENT_IP;
} elseif ('client_host' === $key) {
$key = self::HEADER_CLIENT_HOST;
} elseif ('client_proto' === $key) {
$key = self::HEADER_CLIENT_PROTO;
} elseif ('client_port' === $key) {
$key = self::HEADER_CLIENT_PORT;
} elseif (!\array_key_exists($key, self::$trustedHeaders)) {
throw new \InvalidArgumentException(sprintf('Unable to set the trusted header name for key "%s".', $key));
}
self::$trustedHeaders[$key] = $value;
if (null !== $value) {
self::$trustedHeaderNames[$key] = $value;
self::$trustedHeaderSet |= $key;
} else {
self::$trustedHeaderSet &= ~$key;
}
}
/**
@@ -641,10 +717,16 @@ class Request
* @return string The header name
*
* @throws \InvalidArgumentException
*
* @deprecated since version 3.3, to be removed in 4.0. Use the Request::getTrustedHeaderSet() method instead.
*/
public static function getTrustedHeaderName($key)
{
if (!array_key_exists($key, self::$trustedHeaders)) {
if (2 > \func_num_args() || func_get_arg(1)) {
@trigger_error(sprintf('The "%s()" method is deprecated since Symfony 3.3 and will be removed in 4.0. Use the Request::getTrustedHeaderSet() method instead.', __METHOD__), E_USER_DEPRECATED);
}
if (!\array_key_exists($key, self::$trustedHeaders)) {
throw new \InvalidArgumentException(sprintf('Unable to get the trusted header name for key "%s".', $key));
}
@@ -667,8 +749,8 @@ class Request
return '';
}
$parts = array();
$order = array();
$parts = [];
$order = [];
foreach (explode('&', $qs) as $param) {
if ('' === $param || '=' === $param[0]) {
@@ -721,43 +803,30 @@ class Request
}
/**
* Gets a "parameter" value.
* Gets a "parameter" value from any bag.
*
* This method is mainly useful for libraries that want to provide some flexibility.
* This method is mainly useful for libraries that want to provide some flexibility. If you don't need the
* flexibility in controllers, it is better to explicitly get request parameters from the appropriate
* public property instead (attributes, query, request).
*
* Order of precedence: GET, PATH, POST
*
* Avoid using this method in controllers:
*
* * slow
* * prefer to get from a "named" source
*
* It is better to explicitly get request parameters from the appropriate
* public property instead (query, attributes, request).
*
* Note: Finding deep items is deprecated since version 2.8, to be removed in 3.0.
* Order of precedence: PATH (routing placeholders or custom attributes), GET, BODY
*
* @param string $key The key
* @param mixed $default The default value if the parameter key does not exist
* @param bool $deep Is parameter deep in multidimensional array
*
* @return mixed
*/
public function get($key, $default = null, $deep = false)
public function get($key, $default = null)
{
if ($deep) {
@trigger_error('Using paths to find deeper items in '.__METHOD__.' is deprecated since Symfony 2.8 and will be removed in 3.0. Filter the returned value in your own code instead.', E_USER_DEPRECATED);
}
if ($this !== $result = $this->query->get($key, $this, $deep)) {
if ($this !== $result = $this->attributes->get($key, $this)) {
return $result;
}
if ($this !== $result = $this->attributes->get($key, $this, $deep)) {
if ($this !== $result = $this->query->get($key, $this)) {
return $result;
}
if ($this !== $result = $this->request->get($key, $this, $deep)) {
if ($this !== $result = $this->request->get($key, $this)) {
return $result;
}
@@ -828,10 +897,10 @@ class Request
$ip = $this->server->get('REMOTE_ADDR');
if (!$this->isFromTrustedProxy()) {
return array($ip);
return [$ip];
}
return $this->getTrustedValues(self::HEADER_CLIENT_IP, $ip) ?: array($ip);
return $this->getTrustedValues(self::HEADER_CLIENT_IP, $ip) ?: [$ip];
}
/**
@@ -844,13 +913,13 @@ class Request
* adding the IP address where it received the request from.
*
* If your reverse proxy uses a different header name than "X-Forwarded-For",
* ("Client-Ip" for instance), configure it via "setTrustedHeaderName()" with
* the "client-ip" key.
* ("Client-Ip" for instance), configure it via the $trustedHeaderSet
* argument of the Request::setTrustedProxies() method instead.
*
* @return string|null The client IP address
*
* @see getClientIps()
* @see http://en.wikipedia.org/wiki/X-Forwarded-For
* @see https://wikipedia.org/wiki/X-Forwarded-For
*/
public function getClientIp()
{
@@ -951,7 +1020,8 @@ class Request
* The "X-Forwarded-Port" header must contain the client port.
*
* If your reverse proxy uses a different header name than "X-Forwarded-Port",
* configure it via "setTrustedHeaderName()" with the "client-port" key.
* configure it via via the $trustedHeaderSet argument of the
* Request::setTrustedProxies() method instead.
*
* @return int|string can be a string if fetched from the server bag
*/
@@ -971,8 +1041,8 @@ class Request
$pos = strrpos($host, ':');
}
if (false !== $pos) {
return (int) substr($host, $pos + 1);
if (false !== $pos && $port = substr($host, $pos + 1)) {
return (int) $port;
}
return 'https' === $this->getScheme() ? 443 : 80;
@@ -1138,7 +1208,7 @@ class Request
// A reference to the same base directory or an empty subdirectory must be prefixed with "./".
// This also applies to a segment with a colon character (e.g., "file:colon") that cannot be used
// as the first segment of a relative-path reference, as it would be mistaken for a scheme name
// (see http://tools.ietf.org/html/rfc3986#section-4.2).
// (see https://tools.ietf.org/html/rfc3986#section-4.2).
return !isset($path[0]) || '/' === $path[0]
|| false !== ($colonPos = strpos($path, ':')) && ($colonPos < ($slashPos = strpos($path, '/')) || false === $slashPos)
? "./$path" : $path;
@@ -1168,15 +1238,15 @@ class Request
* The "X-Forwarded-Proto" header must contain the protocol: "https" or "http".
*
* If your reverse proxy uses a different header name than "X-Forwarded-Proto"
* ("SSL_HTTPS" for instance), configure it via "setTrustedHeaderName()" with
* the "client-proto" key.
* ("SSL_HTTPS" for instance), configure it via the $trustedHeaderSet
* argument of the Request::setTrustedProxies() method instead.
*
* @return bool
*/
public function isSecure()
{
if ($this->isFromTrustedProxy() && $proto = $this->getTrustedValues(self::HEADER_CLIENT_PROTO)) {
return \in_array(strtolower($proto[0]), array('https', 'on', 'ssl', '1'), true);
return \in_array(strtolower($proto[0]), ['https', 'on', 'ssl', '1'], true);
}
$https = $this->server->get('HTTPS');
@@ -1193,11 +1263,12 @@ class Request
* The "X-Forwarded-Host" header must contain the client host name.
*
* If your reverse proxy uses a different header name than "X-Forwarded-Host",
* configure it via "setTrustedHeaderName()" with the "client-host" key.
* configure it via the $trustedHeaderSet argument of the
* Request::setTrustedProxies() method instead.
*
* @return string
*
* @throws \UnexpectedValueException when the host name is invalid
* @throws SuspiciousOperationException when the host name is invalid or not trusted
*/
public function getHost()
{
@@ -1217,7 +1288,12 @@ class Request
// check that it does not contain forbidden characters (see RFC 952 and RFC 2181)
// use preg_replace() instead of preg_match() to prevent DoS attacks with long host names
if ($host && '' !== preg_replace('/(?:^\[)?[a-zA-Z0-9-:\]_]+\.?/', '', $host)) {
throw new \UnexpectedValueException(sprintf('Invalid Host "%s"', $host));
if (!$this->isHostValid) {
return '';
}
$this->isHostValid = false;
throw new SuspiciousOperationException(sprintf('Invalid Host "%s".', $host));
}
if (\count(self::$trustedHostPatterns) > 0) {
@@ -1235,7 +1311,12 @@ class Request
}
}
throw new \UnexpectedValueException(sprintf('Untrusted Host "%s"', $host));
if (!$this->isHostValid) {
return '';
}
$this->isHostValid = false;
throw new SuspiciousOperationException(sprintf('Untrusted Host "%s".', $host));
}
return $host;
@@ -1269,22 +1350,37 @@ class Request
*/
public function getMethod()
{
if (null === $this->method) {
$this->method = strtoupper($this->server->get('REQUEST_METHOD', 'GET'));
if ('POST' === $this->method) {
if ($method = $this->headers->get('X-HTTP-METHOD-OVERRIDE')) {
$this->method = strtoupper($method);
} elseif (self::$httpMethodParameterOverride) {
$method = $this->request->get('_method', $this->query->get('_method', 'POST'));
if (\is_string($method)) {
$this->method = strtoupper($method);
}
}
}
if (null !== $this->method) {
return $this->method;
}
return $this->method;
$this->method = strtoupper($this->server->get('REQUEST_METHOD', 'GET'));
if ('POST' !== $this->method) {
return $this->method;
}
$method = $this->headers->get('X-HTTP-METHOD-OVERRIDE');
if (!$method && self::$httpMethodParameterOverride) {
$method = $this->request->get('_method', $this->query->get('_method', 'POST'));
}
if (!\is_string($method)) {
return $this->method;
}
$method = strtoupper($method);
if (\in_array($method, ['GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'CONNECT', 'OPTIONS', 'PATCH', 'PURGE', 'TRACE'], true)) {
return $this->method = $method;
}
if (!preg_match('/^[A-Z]++$/D', $method)) {
throw new SuspiciousOperationException(sprintf('Invalid method override "%s".', $method));
}
return $this->method = $method;
}
/**
@@ -1315,6 +1411,22 @@ class Request
return isset(static::$formats[$format]) ? static::$formats[$format][0] : null;
}
/**
* Gets the mime types associated with the format.
*
* @param string $format The format
*
* @return array The associated mime types
*/
public static function getMimeTypes($format)
{
if (null === static::$formats) {
static::initializeFormats();
}
return isset(static::$formats[$format]) ? static::$formats[$format] : [];
}
/**
* Gets the format associated with the mime type.
*
@@ -1341,6 +1453,8 @@ class Request
return $format;
}
}
return null;
}
/**
@@ -1355,7 +1469,7 @@ class Request
static::initializeFormats();
}
static::$formats[$format] = \is_array($mimeTypes) ? $mimeTypes : array($mimeTypes);
static::$formats[$format] = \is_array($mimeTypes) ? $mimeTypes : [$mimeTypes];
}
/**
@@ -1364,17 +1478,17 @@ class Request
* Here is the process to determine the format:
*
* * format defined by the user (with setRequestFormat())
* * _format request parameter
* * _format request attribute
* * $default
*
* @param string|null $default The default format
*
* @return string The request format
* @return string|null The request format
*/
public function getRequestFormat($default = 'html')
{
if (null === $this->format) {
$this->format = $this->get('_format');
$this->format = $this->attributes->get('_format');
}
return null === $this->format ? $default : $this->format;
@@ -1457,7 +1571,7 @@ class Request
}
/**
* Checks whether the method is safe or not.
* Checks whether or not the method is safe.
*
* @see https://tools.ietf.org/html/rfc7231#section-4.2.1
*
@@ -1467,7 +1581,25 @@ class Request
*/
public function isMethodSafe(/* $andCacheable = true */)
{
return \in_array($this->getMethod(), 0 < \func_num_args() && !func_get_arg(0) ? array('GET', 'HEAD', 'OPTIONS', 'TRACE') : array('GET', 'HEAD'));
if (!\func_num_args() || func_get_arg(0)) {
// This deprecation should be turned into a BadMethodCallException in 4.0 (without adding the argument in the signature)
// then setting $andCacheable to false should be deprecated in 4.1
@trigger_error('Checking only for cacheable HTTP methods with Symfony\Component\HttpFoundation\Request::isMethodSafe() is deprecated since Symfony 3.2 and will throw an exception in 4.0. Disable checking only for cacheable methods by calling the method with `false` as first argument or use the Request::isMethodCacheable() instead.', E_USER_DEPRECATED);
return \in_array($this->getMethod(), ['GET', 'HEAD']);
}
return \in_array($this->getMethod(), ['GET', 'HEAD', 'OPTIONS', 'TRACE']);
}
/**
* Checks whether or not the method is idempotent.
*
* @return bool
*/
public function isMethodIdempotent()
{
return \in_array($this->getMethod(), ['HEAD', 'GET', 'PUT', 'DELETE', 'TRACE', 'OPTIONS', 'PURGE']);
}
/**
@@ -1479,7 +1611,31 @@ class Request
*/
public function isMethodCacheable()
{
return \in_array($this->getMethod(), array('GET', 'HEAD'));
return \in_array($this->getMethod(), ['GET', 'HEAD']);
}
/**
* Returns the protocol version.
*
* If the application is behind a proxy, the protocol version used in the
* requests between the client and the proxy and between the proxy and the
* server might be different. This returns the former (from the "Via" header)
* if the proxy is trusted (see "setTrustedProxies()"), otherwise it returns
* the latter (from the "SERVER_PROTOCOL" server parameter).
*
* @return string
*/
public function getProtocolVersion()
{
if ($this->isFromTrustedProxy()) {
preg_match('~^(HTTP/)?([1-9]\.[0-9]) ~', $this->headers->get('Via'), $matches);
if ($matches) {
return 'HTTP/'.$matches[2];
}
}
return $this->server->get('SERVER_PROTOCOL');
}
/**
@@ -1569,7 +1725,7 @@ class Request
return $locales[0];
}
$extendedPreferredLanguages = array();
$extendedPreferredLanguages = [];
foreach ($preferredLanguages as $language) {
$extendedPreferredLanguages[] = $language;
if (false !== $position = strpos($language, '_')) {
@@ -1597,7 +1753,7 @@ class Request
}
$languages = AcceptHeader::fromString($this->headers->get('Accept-Language'))->all();
$this->languages = array();
$this->languages = [];
foreach ($languages as $lang => $acceptHeaderItem) {
if (false !== strpos($lang, '-')) {
$codes = explode('-', $lang);
@@ -1673,7 +1829,7 @@ class Request
* It works if your JavaScript library sets an X-Requested-With HTTP header.
* It is known to work with common JavaScript frameworks:
*
* @see http://en.wikipedia.org/wiki/List_of_Ajax_frameworks#JavaScript
* @see https://wikipedia.org/wiki/List_of_Ajax_frameworks#JavaScript
*
* @return bool true if the request is an XMLHttpRequest, false otherwise
*/
@@ -1685,9 +1841,9 @@ class Request
/*
* The following methods are derived from code of the Zend Framework (1.10dev - 2010-01-24)
*
* Code subject to the new BSD license (http://framework.zend.com/license/new-bsd).
* Code subject to the new BSD license (https://framework.zend.com/license).
*
* Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
* Copyright (c) 2005-2010 Zend Technologies USA Inc. (https://www.zend.com/)
*/
protected function prepareRequestUri()
@@ -1701,10 +1857,24 @@ class Request
$this->server->remove('IIS_WasUrlRewritten');
} elseif ($this->server->has('REQUEST_URI')) {
$requestUri = $this->server->get('REQUEST_URI');
// HTTP proxy reqs setup request URI with scheme and host [and port] + the URL path, only use URL path
$schemeAndHttpHost = $this->getSchemeAndHttpHost();
if (0 === strpos($requestUri, $schemeAndHttpHost)) {
$requestUri = substr($requestUri, \strlen($schemeAndHttpHost));
if ('' !== $requestUri && '/' === $requestUri[0]) {
// To only use path and query remove the fragment.
if (false !== $pos = strpos($requestUri, '#')) {
$requestUri = substr($requestUri, 0, $pos);
}
} else {
// HTTP proxy reqs setup request URI with scheme and host [and port] + the URL path,
// only use URL path.
$uriComponents = parse_url($requestUri);
if (isset($uriComponents['path'])) {
$requestUri = $uriComponents['path'];
}
if (isset($uriComponents['query'])) {
$requestUri .= '?'.$uriComponents['query'];
}
}
} elseif ($this->server->has('ORIG_PATH_INFO')) {
// IIS 5.0, PHP as CGI
@@ -1797,12 +1967,12 @@ class Request
*/
protected function prepareBasePath()
{
$filename = basename($this->server->get('SCRIPT_FILENAME'));
$baseUrl = $this->getBaseUrl();
if (empty($baseUrl)) {
return '';
}
$filename = basename($this->server->get('SCRIPT_FILENAME'));
if (basename($baseUrl) === $filename) {
$basePath = \dirname($baseUrl);
} else {
@@ -1823,8 +1993,6 @@ class Request
*/
protected function preparePathInfo()
{
$baseUrl = $this->getBaseUrl();
if (null === ($requestUri = $this->getRequestUri())) {
return '/';
}
@@ -1837,12 +2005,14 @@ class Request
$requestUri = '/'.$requestUri;
}
if (null === ($baseUrl = $this->getBaseUrl())) {
return $requestUri;
}
$pathInfo = substr($requestUri, \strlen($baseUrl));
if (null !== $baseUrl && (false === $pathInfo || '' === $pathInfo)) {
if (false === $pathInfo || '' === $pathInfo) {
// If substr() returns false then PATH_INFO is set to an empty string
return '/';
} elseif (null === $baseUrl) {
return $requestUri;
}
return (string) $pathInfo;
@@ -1853,19 +2023,19 @@ class Request
*/
protected static function initializeFormats()
{
static::$formats = array(
'html' => array('text/html', 'application/xhtml+xml'),
'txt' => array('text/plain'),
'js' => array('application/javascript', 'application/x-javascript', 'text/javascript'),
'css' => array('text/css'),
'json' => array('application/json', 'application/x-json'),
'jsonld' => array('application/ld+json'),
'xml' => array('text/xml', 'application/xml', 'application/x-xml'),
'rdf' => array('application/rdf+xml'),
'atom' => array('application/atom+xml'),
'rss' => array('application/rss+xml'),
'form' => array('application/x-www-form-urlencoded'),
);
static::$formats = [
'html' => ['text/html', 'application/xhtml+xml'],
'txt' => ['text/plain'],
'js' => ['application/javascript', 'application/x-javascript', 'text/javascript'],
'css' => ['text/css'],
'json' => ['application/json', 'application/x-json'],
'jsonld' => ['application/ld+json'],
'xml' => ['text/xml', 'application/xml', 'application/x-xml'],
'rdf' => ['application/rdf+xml'],
'atom' => ['application/atom+xml'],
'rss' => ['application/rss+xml'],
'form' => ['application/x-www-form-urlencoded'],
];
}
/**
@@ -1886,7 +2056,7 @@ class Request
}
}
/*
/**
* Returns the prefix as encoded in the string when the string starts with
* the given prefix, false otherwise.
*
@@ -1910,7 +2080,7 @@ class Request
return false;
}
private static function createRequestFromFactory(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null)
private static function createRequestFromFactory(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null)
{
if (self::$requestFactory) {
$request = \call_user_func(self::$requestFactory, $query, $request, $attributes, $cookies, $files, $server, $content);
@@ -1925,15 +2095,23 @@ class Request
return new static($query, $request, $attributes, $cookies, $files, $server, $content);
}
private function isFromTrustedProxy()
/**
* Indicates whether this request originated from a trusted proxy.
*
* This can be useful to determine whether or not to trust the
* contents of a proxy-specific header.
*
* @return bool true if the request came from a trusted proxy, false otherwise
*/
public function isFromTrustedProxy()
{
return self::$trustedProxies && IpUtils::checkIp($this->server->get('REMOTE_ADDR'), self::$trustedProxies);
}
private function getTrustedValues($type, $ip = null)
{
$clientValues = array();
$forwardedValues = array();
$clientValues = [];
$forwardedValues = [];
if (self::$trustedHeaders[$type] && $this->headers->has(self::$trustedHeaders[$type])) {
foreach (explode(',', $this->headers->get(self::$trustedHeaders[$type])) as $v) {
@@ -1943,7 +2121,7 @@ class Request
if (self::$trustedHeaders[self::HEADER_FORWARDED] && $this->headers->has(self::$trustedHeaders[self::HEADER_FORWARDED])) {
$forwardedValues = $this->headers->get(self::$trustedHeaders[self::HEADER_FORWARDED]);
$forwardedValues = preg_match_all(sprintf('{(?:%s)="?([a-zA-Z0-9\.:_\-/\[\]]*+)}', self::$forwardedParams[$type]), $forwardedValues, $matches) ? $matches[1] : array();
$forwardedValues = preg_match_all(sprintf('{(?:%s)="?([a-zA-Z0-9\.:_\-/\[\]]*+)}', self::$forwardedParams[$type]), $forwardedValues, $matches) ? $matches[1] : [];
if (self::HEADER_CLIENT_PORT === $type) {
foreach ($forwardedValues as $k => $v) {
if (']' === substr($v, -1) || false === $v = strrchr($v, ':')) {
@@ -1968,7 +2146,7 @@ class Request
}
if (!$this->isForwardedValid) {
return null !== $ip ? array('0.0.0.0', $ip) : array();
return null !== $ip ? ['0.0.0.0', $ip] : [];
}
$this->isForwardedValid = false;
@@ -1978,7 +2156,7 @@ class Request
private function normalizeAndFilterClientIps(array $clientIps, $ip)
{
if (!$clientIps) {
return array();
return [];
}
$clientIps[] = $ip; // Complete the IP chain with the IP the request actually came from
$firstTrustedIp = null;
@@ -2014,6 +2192,6 @@ class Request
}
// Now the IP chain contains only untrusted proxies and the client IP
return $clientIps ? array_reverse($clientIps) : array($firstTrustedIp);
return $clientIps ? array_reverse($clientIps) : [$firstTrustedIp];
}
}

View File

@@ -31,32 +31,31 @@ class RequestMatcher implements RequestMatcherInterface
/**
* @var string[]
*/
private $methods = array();
private $methods = [];
/**
* @var string[]
*/
private $ips = array();
private $ips = [];
/**
* @var array
*/
private $attributes = array();
private $attributes = [];
/**
* @var string[]
*/
private $schemes = array();
private $schemes = [];
/**
* @param string|null $path
* @param string|null $host
* @param string|string[]|null $methods
* @param string|string[]|null $ips
* @param array $attributes
* @param string|string[]|null $schemes
*/
public function __construct($path = null, $host = null, $methods = null, $ips = null, array $attributes = array(), $schemes = null)
public function __construct($path = null, $host = null, $methods = null, $ips = null, array $attributes = [], $schemes = null)
{
$this->matchPath($path);
$this->matchHost($host);
@@ -76,7 +75,7 @@ class RequestMatcher implements RequestMatcherInterface
*/
public function matchScheme($scheme)
{
$this->schemes = null !== $scheme ? array_map('strtolower', (array) $scheme) : array();
$this->schemes = null !== $scheme ? array_map('strtolower', (array) $scheme) : [];
}
/**
@@ -116,7 +115,7 @@ class RequestMatcher implements RequestMatcherInterface
*/
public function matchIps($ips)
{
$this->ips = null !== $ips ? (array) $ips : array();
$this->ips = null !== $ips ? (array) $ips : [];
}
/**
@@ -126,7 +125,7 @@ class RequestMatcher implements RequestMatcherInterface
*/
public function matchMethod($method)
{
$this->methods = null !== $method ? array_map('strtoupper', (array) $method) : array();
$this->methods = null !== $method ? array_map('strtoupper', (array) $method) : [];
}
/**

View File

@@ -21,7 +21,7 @@ class RequestStack
/**
* @var Request[]
*/
private $requests = array();
private $requests = [];
/**
* Pushes a Request on the stack.
@@ -47,7 +47,7 @@ class RequestStack
public function pop()
{
if (!$this->requests) {
return;
return null;
}
return array_pop($this->requests);
@@ -73,7 +73,7 @@ class RequestStack
public function getMasterRequest()
{
if (!$this->requests) {
return;
return null;
}
return $this->requests[0];
@@ -95,7 +95,7 @@ class RequestStack
$pos = \count($this->requests) - 2;
if (!isset($this->requests[$pos])) {
return;
return null;
}
return $this->requests[$pos];

View File

@@ -88,7 +88,7 @@ class Response
const HTTP_NETWORK_AUTHENTICATION_REQUIRED = 511; // RFC6585
/**
* @var \Symfony\Component\HttpFoundation\ResponseHeaderBag
* @var ResponseHeaderBag
*/
public $headers;
@@ -121,14 +121,14 @@ class Response
* Status codes translation table.
*
* The list of codes is complete according to the
* {@link http://www.iana.org/assignments/http-status-codes/ Hypertext Transfer Protocol (HTTP) Status Code Registry}
* {@link https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml Hypertext Transfer Protocol (HTTP) Status Code Registry}
* (last updated 2016-03-01).
*
* Unless otherwise noted, the status code is defined in RFC2616.
*
* @var array
*/
public static $statusTexts = array(
public static $statusTexts = [
100 => 'Continue',
101 => 'Switching Protocols',
102 => 'Processing', // RFC2518
@@ -191,7 +191,7 @@ class Response
508 => 'Loop Detected', // RFC5842
510 => 'Not Extended', // RFC2774
511 => 'Network Authentication Required', // RFC6585
);
];
/**
* @param mixed $content The response content, see setContent()
@@ -200,17 +200,12 @@ class Response
*
* @throws \InvalidArgumentException When the HTTP status code is not valid
*/
public function __construct($content = '', $status = 200, $headers = array())
public function __construct($content = '', $status = 200, $headers = [])
{
$this->headers = new ResponseHeaderBag($headers);
$this->setContent($content);
$this->setStatusCode($status);
$this->setProtocolVersion('1.0');
/* RFC2616 - 14.18 says all Responses need to have a Date */
if (!$this->headers->has('Date')) {
$this->setDate(\DateTime::createFromFormat('U', time()));
}
}
/**
@@ -227,7 +222,7 @@ class Response
*
* @return static
*/
public static function create($content = '', $status = 200, $headers = array())
public static function create($content = '', $status = 200, $headers = [])
{
return new static($content, $status, $headers);
}
@@ -315,9 +310,9 @@ class Response
}
// Check if we need to send extra expire info headers
if ('1.0' == $this->getProtocolVersion() && 'no-cache' == $this->headers->get('Cache-Control')) {
$this->headers->set('pragma', 'no-cache');
$this->headers->set('expires', -1);
if ('1.0' == $this->getProtocolVersion() && false !== strpos($headers->get('Cache-Control'), 'no-cache')) {
$headers->set('pragma', 'no-cache');
$headers->set('expires', -1);
}
$this->ensureIEOverSSLCompatibility($request);
@@ -337,27 +332,22 @@ class Response
return $this;
}
/* RFC2616 - 14.18 says all Responses need to have a Date */
if (!$this->headers->has('Date')) {
$this->setDate(\DateTime::createFromFormat('U', time()));
}
// headers
foreach ($this->headers->allPreserveCase() as $name => $values) {
foreach ($this->headers->allPreserveCaseWithoutCookies() as $name => $values) {
$replace = 0 === strcasecmp($name, 'Content-Type');
foreach ($values as $value) {
header($name.': '.$value, $replace, $this->statusCode);
}
}
// status
header(sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText), true, $this->statusCode);
// cookies
foreach ($this->headers->getCookies() as $cookie) {
setcookie($cookie->getName(), $cookie->getValue(), $cookie->getExpiresTime(), $cookie->getPath(), $cookie->getDomain(), $cookie->isSecure(), $cookie->isHttpOnly());
header('Set-Cookie: '.$cookie, false, $this->statusCode);
}
// status
header(sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText), true, $this->statusCode);
return $this;
}
@@ -385,7 +375,7 @@ class Response
if (\function_exists('fastcgi_finish_request')) {
fastcgi_finish_request();
} elseif (!\in_array(\PHP_SAPI, array('cli', 'phpdbg'), true)) {
} elseif (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) {
static::closeOutputBuffers(0, true);
}
@@ -405,7 +395,7 @@ class Response
*/
public function setContent($content)
{
if (null !== $content && !\is_string($content) && !is_numeric($content) && !\is_callable(array($content, '__toString'))) {
if (null !== $content && !\is_string($content) && !is_numeric($content) && !\is_callable([$content, '__toString'])) {
throw new \UnexpectedValueException(sprintf('The Response content must be a string or object implementing __toString(), "%s" given.', \gettype($content)));
}
@@ -417,7 +407,7 @@ class Response
/**
* Gets the current response content.
*
* @return string Content
* @return string|false
*/
public function getContent()
{
@@ -430,6 +420,8 @@ class Response
* @param string $version The HTTP protocol version
*
* @return $this
*
* @final since version 3.2
*/
public function setProtocolVersion($version)
{
@@ -442,6 +434,8 @@ class Response
* Gets the HTTP protocol version.
*
* @return string The HTTP protocol version
*
* @final since version 3.2
*/
public function getProtocolVersion()
{
@@ -460,6 +454,8 @@ class Response
* @return $this
*
* @throws \InvalidArgumentException When the HTTP status code is not valid
*
* @final since version 3.2
*/
public function setStatusCode($code, $text = null)
{
@@ -489,6 +485,8 @@ class Response
* Retrieves the status code for the current web response.
*
* @return int Status code
*
* @final since version 3.2
*/
public function getStatusCode()
{
@@ -501,6 +499,8 @@ class Response
* @param string $charset Character set
*
* @return $this
*
* @final since version 3.2
*/
public function setCharset($charset)
{
@@ -513,6 +513,8 @@ class Response
* Retrieves the response charset.
*
* @return string Character set
*
* @final since version 3.2
*/
public function getCharset()
{
@@ -535,10 +537,12 @@ class Response
* (https://tools.ietf.org/html/rfc7231#section-6.1)
*
* @return bool true if the response is worth caching, false otherwise
*
* @final since version 3.3
*/
public function isCacheable()
{
if (!\in_array($this->statusCode, array(200, 203, 300, 301, 302, 404, 410))) {
if (!\in_array($this->statusCode, [200, 203, 300, 301, 302, 404, 410])) {
return false;
}
@@ -557,6 +561,8 @@ class Response
* indicator or Expires header and the calculated age is less than the freshness lifetime.
*
* @return bool true if the response is fresh, false otherwise
*
* @final since version 3.3
*/
public function isFresh()
{
@@ -568,6 +574,8 @@ class Response
* the response with the origin server using a conditional GET request.
*
* @return bool true if the response is validateable, false otherwise
*
* @final since version 3.3
*/
public function isValidateable()
{
@@ -580,6 +588,8 @@ class Response
* It makes the response ineligible for serving other clients.
*
* @return $this
*
* @final since version 3.2
*/
public function setPrivate()
{
@@ -595,6 +605,8 @@ class Response
* It makes the response eligible for serving other clients.
*
* @return $this
*
* @final since version 3.2
*/
public function setPublic()
{
@@ -604,6 +616,38 @@ class Response
return $this;
}
/**
* Marks the response as "immutable".
*
* @param bool $immutable enables or disables the immutable directive
*
* @return $this
*
* @final
*/
public function setImmutable($immutable = true)
{
if ($immutable) {
$this->headers->addCacheControlDirective('immutable');
} else {
$this->headers->removeCacheControlDirective('immutable');
}
return $this;
}
/**
* Returns true if the response is marked as "immutable".
*
* @return bool returns true if the response is marked as "immutable"; otherwise false
*
* @final
*/
public function isImmutable()
{
return $this->headers->hasCacheControlDirective('immutable');
}
/**
* Returns true if the response must be revalidated by caches.
*
@@ -613,6 +657,8 @@ class Response
* greater than the value provided by the origin.
*
* @return bool true if the response must be revalidated by a cache, false otherwise
*
* @final since version 3.3
*/
public function mustRevalidate()
{
@@ -625,18 +671,11 @@ class Response
* @return \DateTime A \DateTime instance
*
* @throws \RuntimeException When the header is not parseable
*
* @final since version 3.2
*/
public function getDate()
{
/*
RFC2616 - 14.18 says all Responses need to have a Date.
Make sure we provide one even if it the header
has been removed in the meantime.
*/
if (!$this->headers->has('Date')) {
$this->setDate(\DateTime::createFromFormat('U', time()));
}
return $this->headers->getDate('Date');
}
@@ -644,6 +683,8 @@ class Response
* Sets the Date header.
*
* @return $this
*
* @final since version 3.2
*/
public function setDate(\DateTime $date)
{
@@ -657,6 +698,8 @@ class Response
* Returns the age of the response.
*
* @return int The age of the response in seconds
*
* @final since version 3.2
*/
public function getAge()
{
@@ -664,7 +707,7 @@ class Response
return (int) $age;
}
return max(time() - $this->getDate()->format('U'), 0);
return max(time() - (int) $this->getDate()->format('U'), 0);
}
/**
@@ -686,6 +729,8 @@ class Response
* Returns the value of the Expires header as a DateTime instance.
*
* @return \DateTime|null A DateTime instance or null if the header does not exist
*
* @final since version 3.2
*/
public function getExpires()
{
@@ -705,6 +750,8 @@ class Response
* @param \DateTime|null $date A \DateTime instance or null to remove the header
*
* @return $this
*
* @final since version 3.2
*/
public function setExpires(\DateTime $date = null)
{
@@ -727,6 +774,8 @@ class Response
* back on an expires header. It returns null when no maximum age can be established.
*
* @return int|null Number of seconds
*
* @final since version 3.2
*/
public function getMaxAge()
{
@@ -739,8 +788,10 @@ class Response
}
if (null !== $this->getExpires()) {
return $this->getExpires()->format('U') - $this->getDate()->format('U');
return (int) $this->getExpires()->format('U') - (int) $this->getDate()->format('U');
}
return null;
}
/**
@@ -751,6 +802,8 @@ class Response
* @param int $value Number of seconds
*
* @return $this
*
* @final since version 3.2
*/
public function setMaxAge($value)
{
@@ -767,6 +820,8 @@ class Response
* @param int $value Number of seconds
*
* @return $this
*
* @final since version 3.2
*/
public function setSharedMaxAge($value)
{
@@ -785,12 +840,16 @@ class Response
* revalidating with the origin.
*
* @return int|null The TTL in seconds
*
* @final since version 3.2
*/
public function getTtl()
{
if (null !== $maxAge = $this->getMaxAge()) {
return $maxAge - $this->getAge();
}
return null;
}
/**
@@ -801,6 +860,8 @@ class Response
* @param int $seconds Number of seconds
*
* @return $this
*
* @final since version 3.2
*/
public function setTtl($seconds)
{
@@ -817,6 +878,8 @@ class Response
* @param int $seconds Number of seconds
*
* @return $this
*
* @final since version 3.2
*/
public function setClientTtl($seconds)
{
@@ -831,6 +894,8 @@ class Response
* @return \DateTime|null A DateTime instance or null if the header does not exist
*
* @throws \RuntimeException When the HTTP header is not parseable
*
* @final since version 3.2
*/
public function getLastModified()
{
@@ -845,6 +910,8 @@ class Response
* @param \DateTime|null $date A \DateTime instance or null to remove the header
*
* @return $this
*
* @final since version 3.2
*/
public function setLastModified(\DateTime $date = null)
{
@@ -863,6 +930,8 @@ class Response
* Returns the literal value of the ETag HTTP header.
*
* @return string|null The ETag HTTP header or null if it does not exist
*
* @final since version 3.2
*/
public function getEtag()
{
@@ -876,6 +945,8 @@ class Response
* @param bool $weak Whether you want a weak ETag or not
*
* @return $this
*
* @final since version 3.2
*/
public function setEtag($etag = null, $weak = false)
{
@@ -895,18 +966,20 @@ class Response
/**
* Sets the response's cache headers (validation and/or expiration).
*
* Available options are: etag, last_modified, max_age, s_maxage, private, and public.
* Available options are: etag, last_modified, max_age, s_maxage, private, public and immutable.
*
* @param array $options An array of cache options
*
* @return $this
*
* @throws \InvalidArgumentException
*
* @final since version 3.3
*/
public function setCache(array $options)
{
if ($diff = array_diff(array_keys($options), array('etag', 'last_modified', 'max_age', 's_maxage', 'private', 'public'))) {
throw new \InvalidArgumentException(sprintf('Response does not support the following options: "%s".', implode('", "', array_values($diff))));
if ($diff = array_diff(array_keys($options), ['etag', 'last_modified', 'max_age', 's_maxage', 'private', 'public', 'immutable'])) {
throw new \InvalidArgumentException(sprintf('Response does not support the following options: "%s".', implode('", "', $diff)));
}
if (isset($options['etag'])) {
@@ -941,6 +1014,10 @@ class Response
}
}
if (isset($options['immutable'])) {
$this->setImmutable((bool) $options['immutable']);
}
return $this;
}
@@ -952,7 +1029,9 @@ class Response
*
* @return $this
*
* @see http://tools.ietf.org/html/rfc2616#section-10.3.5
* @see https://tools.ietf.org/html/rfc2616#section-10.3.5
*
* @final since version 3.3
*/
public function setNotModified()
{
@@ -960,7 +1039,7 @@ class Response
$this->setContent(null);
// remove headers that MUST NOT be included with 304 Not Modified responses
foreach (array('Allow', 'Content-Encoding', 'Content-Language', 'Content-Length', 'Content-MD5', 'Content-Type', 'Last-Modified') as $header) {
foreach (['Allow', 'Content-Encoding', 'Content-Language', 'Content-Length', 'Content-MD5', 'Content-Type', 'Last-Modified'] as $header) {
$this->headers->remove($header);
}
@@ -971,6 +1050,8 @@ class Response
* Returns true if the response includes a Vary header.
*
* @return bool true if the response includes a Vary header, false otherwise
*
* @final since version 3.2
*/
public function hasVary()
{
@@ -981,14 +1062,16 @@ class Response
* Returns an array of header names given in the Vary header.
*
* @return array An array of Vary names
*
* @final since version 3.2
*/
public function getVary()
{
if (!$vary = $this->headers->get('Vary', null, false)) {
return array();
return [];
}
$ret = array();
$ret = [];
foreach ($vary as $item) {
$ret = array_merge($ret, preg_split('/[\s,]+/', $item));
}
@@ -1003,6 +1086,8 @@ class Response
* @param bool $replace Whether to replace the actual value or not (true by default)
*
* @return $this
*
* @final since version 3.2
*/
public function setVary($headers, $replace = true)
{
@@ -1019,6 +1104,8 @@ class Response
* removes the actual content by calling the setNotModified() method.
*
* @return bool true if the Response validators match the Request, false otherwise
*
* @final since version 3.3
*/
public function isNotModified(Request $request)
{
@@ -1050,7 +1137,9 @@ class Response
*
* @return bool
*
* @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
* @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
*
* @final since version 3.2
*/
public function isInvalid()
{
@@ -1061,6 +1150,8 @@ class Response
* Is response informative?
*
* @return bool
*
* @final since version 3.3
*/
public function isInformational()
{
@@ -1071,6 +1162,8 @@ class Response
* Is response successful?
*
* @return bool
*
* @final since version 3.2
*/
public function isSuccessful()
{
@@ -1081,6 +1174,8 @@ class Response
* Is the response a redirect?
*
* @return bool
*
* @final since version 3.2
*/
public function isRedirection()
{
@@ -1091,6 +1186,8 @@ class Response
* Is there a client error?
*
* @return bool
*
* @final since version 3.2
*/
public function isClientError()
{
@@ -1101,6 +1198,8 @@ class Response
* Was there a server side error?
*
* @return bool
*
* @final since version 3.3
*/
public function isServerError()
{
@@ -1111,6 +1210,8 @@ class Response
* Is the response OK?
*
* @return bool
*
* @final since version 3.2
*/
public function isOk()
{
@@ -1121,6 +1222,8 @@ class Response
* Is the response forbidden?
*
* @return bool
*
* @final since version 3.2
*/
public function isForbidden()
{
@@ -1131,6 +1234,8 @@ class Response
* Is the response a not found error?
*
* @return bool
*
* @final since version 3.2
*/
public function isNotFound()
{
@@ -1143,20 +1248,24 @@ class Response
* @param string $location
*
* @return bool
*
* @final since version 3.2
*/
public function isRedirect($location = null)
{
return \in_array($this->statusCode, array(201, 301, 302, 303, 307, 308)) && (null === $location ?: $location == $this->headers->get('Location'));
return \in_array($this->statusCode, [201, 301, 302, 303, 307, 308]) && (null === $location ?: $location == $this->headers->get('Location'));
}
/**
* Is the response empty?
*
* @return bool
*
* @final since version 3.2
*/
public function isEmpty()
{
return \in_array($this->statusCode, array(204, 304));
return \in_array($this->statusCode, [204, 304]);
}
/**
@@ -1166,11 +1275,14 @@ class Response
*
* @param int $targetLevel The target output buffering level
* @param bool $flush Whether to flush or clean the buffers
*
* @final since version 3.3
*/
public static function closeOutputBuffers($targetLevel, $flush)
{
$status = ob_get_status(true);
$level = \count($status);
// PHP_OUTPUT_HANDLER_* are not defined on HHVM 3.3
$flags = \defined('PHP_OUTPUT_HANDLER_REMOVABLE') ? PHP_OUTPUT_HANDLER_REMOVABLE | ($flush ? PHP_OUTPUT_HANDLER_FLUSHABLE : PHP_OUTPUT_HANDLER_CLEANABLE) : -1;
while ($level-- > $targetLevel && ($s = $status[$level]) && (!isset($s['del']) ? !isset($s['flags']) || ($s['flags'] & $flags) === $flags : $s['del'])) {
@@ -1186,6 +1298,8 @@ class Response
* Checks if we need to remove Cache-Control for SSL encrypted downloads when using IE < 9.
*
* @see http://support.microsoft.com/kb/323308
*
* @final since version 3.3
*/
protected function ensureIEOverSSLCompatibility(Request $request)
{

View File

@@ -24,32 +24,22 @@ class ResponseHeaderBag extends HeaderBag
const DISPOSITION_ATTACHMENT = 'attachment';
const DISPOSITION_INLINE = 'inline';
protected $computedCacheControl = array();
protected $cookies = array();
protected $headerNames = array();
protected $computedCacheControl = [];
protected $cookies = [];
protected $headerNames = [];
public function __construct(array $headers = array())
public function __construct(array $headers = [])
{
parent::__construct($headers);
if (!isset($this->headers['cache-control'])) {
$this->set('Cache-Control', '');
}
}
/**
* {@inheritdoc}
*/
public function __toString()
{
$cookies = '';
foreach ($this->getCookies() as $cookie) {
$cookies .= 'Set-Cookie: '.$cookie."\r\n";
/* RFC2616 - 14.18 says all Responses need to have a Date */
if (!isset($this->headers['date'])) {
$this->initDate();
}
ksort($this->headerNames);
return parent::__toString().$cookies;
}
/**
@@ -59,21 +49,53 @@ class ResponseHeaderBag extends HeaderBag
*/
public function allPreserveCase()
{
return array_combine($this->headerNames, $this->headers);
$headers = [];
foreach ($this->all() as $name => $value) {
$headers[isset($this->headerNames[$name]) ? $this->headerNames[$name] : $name] = $value;
}
return $headers;
}
public function allPreserveCaseWithoutCookies()
{
$headers = $this->allPreserveCase();
if (isset($this->headerNames['set-cookie'])) {
unset($headers[$this->headerNames['set-cookie']]);
}
return $headers;
}
/**
* {@inheritdoc}
*/
public function replace(array $headers = array())
public function replace(array $headers = [])
{
$this->headerNames = array();
$this->headerNames = [];
parent::replace($headers);
if (!isset($this->headers['cache-control'])) {
$this->set('Cache-Control', '');
}
if (!isset($this->headers['date'])) {
$this->initDate();
}
}
/**
* {@inheritdoc}
*/
public function all()
{
$headers = parent::all();
foreach ($this->getCookies() as $cookie) {
$headers['set-cookie'][] = (string) $cookie;
}
return $headers;
}
/**
@@ -81,15 +103,28 @@ class ResponseHeaderBag extends HeaderBag
*/
public function set($key, $values, $replace = true)
{
parent::set($key, $values, $replace);
$uniqueKey = str_replace('_', '-', strtolower($key));
if ('set-cookie' === $uniqueKey) {
if ($replace) {
$this->cookies = [];
}
foreach ((array) $values as $cookie) {
$this->setCookie(Cookie::fromString($cookie));
}
$this->headerNames[$uniqueKey] = $key;
return;
}
$this->headerNames[$uniqueKey] = $key;
parent::set($key, $values, $replace);
// ensure the cache-control header has sensible defaults
if (\in_array($uniqueKey, array('cache-control', 'etag', 'last-modified', 'expires'))) {
if (\in_array($uniqueKey, ['cache-control', 'etag', 'last-modified', 'expires'], true)) {
$computed = $this->computeCacheControlValue();
$this->headers['cache-control'] = array($computed);
$this->headers['cache-control'] = [$computed];
$this->headerNames['cache-control'] = 'Cache-Control';
$this->computedCacheControl = $this->parseCacheControl($computed);
}
@@ -100,13 +135,23 @@ class ResponseHeaderBag extends HeaderBag
*/
public function remove($key)
{
parent::remove($key);
$uniqueKey = str_replace('_', '-', strtolower($key));
unset($this->headerNames[$uniqueKey]);
if ('set-cookie' === $uniqueKey) {
$this->cookies = [];
return;
}
parent::remove($key);
if ('cache-control' === $uniqueKey) {
$this->computedCacheControl = array();
$this->computedCacheControl = [];
}
if ('date' === $uniqueKey) {
$this->initDate();
}
}
@@ -115,7 +160,7 @@ class ResponseHeaderBag extends HeaderBag
*/
public function hasCacheControlDirective($key)
{
return array_key_exists($key, $this->computedCacheControl);
return \array_key_exists($key, $this->computedCacheControl);
}
/**
@@ -123,12 +168,13 @@ class ResponseHeaderBag extends HeaderBag
*/
public function getCacheControlDirective($key)
{
return array_key_exists($key, $this->computedCacheControl) ? $this->computedCacheControl[$key] : null;
return \array_key_exists($key, $this->computedCacheControl) ? $this->computedCacheControl[$key] : null;
}
public function setCookie(Cookie $cookie)
{
$this->cookies[$cookie->getDomain()][$cookie->getPath()][$cookie->getName()] = $cookie;
$this->headerNames['set-cookie'] = 'Set-Cookie';
}
/**
@@ -153,6 +199,10 @@ class ResponseHeaderBag extends HeaderBag
unset($this->cookies[$domain]);
}
}
if (empty($this->cookies)) {
unset($this->headerNames['set-cookie']);
}
}
/**
@@ -166,15 +216,15 @@ class ResponseHeaderBag extends HeaderBag
*/
public function getCookies($format = self::COOKIES_FLAT)
{
if (!\in_array($format, array(self::COOKIES_FLAT, self::COOKIES_ARRAY))) {
throw new \InvalidArgumentException(sprintf('Format "%s" invalid (%s).', $format, implode(', ', array(self::COOKIES_FLAT, self::COOKIES_ARRAY))));
if (!\in_array($format, [self::COOKIES_FLAT, self::COOKIES_ARRAY])) {
throw new \InvalidArgumentException(sprintf('Format "%s" invalid (%s).', $format, implode(', ', [self::COOKIES_FLAT, self::COOKIES_ARRAY])));
}
if (self::COOKIES_ARRAY === $format) {
return $this->cookies;
}
$flattenedCookies = array();
$flattenedCookies = [];
foreach ($this->cookies as $path) {
foreach ($path as $cookies) {
foreach ($cookies as $cookie) {
@@ -217,7 +267,7 @@ class ResponseHeaderBag extends HeaderBag
*/
public function makeDisposition($disposition, $filename, $filenameFallback = '')
{
if (!\in_array($disposition, array(self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE))) {
if (!\in_array($disposition, [self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE])) {
throw new \InvalidArgumentException(sprintf('The disposition must be either "%s" or "%s".', self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE));
}
@@ -260,7 +310,7 @@ class ResponseHeaderBag extends HeaderBag
protected function computeCacheControlValue()
{
if (!$this->cacheControl && !$this->has('ETag') && !$this->has('Last-Modified') && !$this->has('Expires')) {
return 'no-cache';
return 'no-cache, private';
}
if (!$this->cacheControl) {
@@ -280,4 +330,11 @@ class ResponseHeaderBag extends HeaderBag
return $header;
}
private function initDate()
{
$now = \DateTime::createFromFormat('U', time());
$now->setTimezone(new \DateTimeZone('UTC'));
$this->set('Date', $now->format('D, d M Y H:i:s').' GMT');
}
}

View File

@@ -27,8 +27,8 @@ class ServerBag extends ParameterBag
*/
public function getHeaders()
{
$headers = array();
$contentHeaders = array('CONTENT_LENGTH' => true, 'CONTENT_MD5' => true, 'CONTENT_TYPE' => true);
$headers = [];
$contentHeaders = ['CONTENT_LENGTH' => true, 'CONTENT_MD5' => true, 'CONTENT_TYPE' => true];
foreach ($this->parameters as $key => $value) {
if (0 === strpos($key, 'HTTP_')) {
$headers[substr($key, 5)] = $value;
@@ -46,13 +46,13 @@ class ServerBag extends ParameterBag
/*
* php-cgi under Apache does not pass HTTP Basic user/pass to PHP by default
* For this workaround to work, add these lines to your .htaccess file:
* RewriteCond %{HTTP:Authorization} ^(.+)$
* RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
* RewriteCond %{HTTP:Authorization} .+
* RewriteRule ^ - [E=HTTP_AUTHORIZATION:%0]
*
* A sample .htaccess file:
* RewriteEngine On
* RewriteCond %{HTTP:Authorization} ^(.+)$
* RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
* RewriteCond %{HTTP:Authorization} .+
* RewriteRule ^ - [E=HTTP_AUTHORIZATION:%0]
* RewriteCond %{REQUEST_FILENAME} !-f
* RewriteRule ^(.*)$ app.php [QSA,L]
*/
@@ -79,7 +79,7 @@ class ServerBag extends ParameterBag
/*
* XXX: Since there is no PHP_AUTH_BEARER in PHP predefined variables,
* I'll just set $headers['AUTHORIZATION'] here.
* http://php.net/manual/en/reserved.variables.server.php
* https://php.net/reserved.variables.server
*/
$headers['AUTHORIZATION'] = $authorizationHeader;
}

View File

@@ -19,7 +19,7 @@ class AttributeBag implements AttributeBagInterface, \IteratorAggregate, \Counta
private $name = 'attributes';
private $storageKey;
protected $attributes = array();
protected $attributes = [];
/**
* @param string $storageKey The key used to store attributes in the session
@@ -63,7 +63,7 @@ class AttributeBag implements AttributeBagInterface, \IteratorAggregate, \Counta
*/
public function has($name)
{
return array_key_exists($name, $this->attributes);
return \array_key_exists($name, $this->attributes);
}
/**
@@ -71,7 +71,7 @@ class AttributeBag implements AttributeBagInterface, \IteratorAggregate, \Counta
*/
public function get($name, $default = null)
{
return array_key_exists($name, $this->attributes) ? $this->attributes[$name] : $default;
return \array_key_exists($name, $this->attributes) ? $this->attributes[$name] : $default;
}
/**
@@ -95,7 +95,7 @@ class AttributeBag implements AttributeBagInterface, \IteratorAggregate, \Counta
*/
public function replace(array $attributes)
{
$this->attributes = array();
$this->attributes = [];
foreach ($attributes as $key => $value) {
$this->set($key, $value);
}
@@ -107,7 +107,7 @@ class AttributeBag implements AttributeBagInterface, \IteratorAggregate, \Counta
public function remove($name)
{
$retval = null;
if (array_key_exists($name, $this->attributes)) {
if (\array_key_exists($name, $this->attributes)) {
$retval = $this->attributes[$name];
unset($this->attributes[$name]);
}
@@ -121,7 +121,7 @@ class AttributeBag implements AttributeBagInterface, \IteratorAggregate, \Counta
public function clear()
{
$return = $this->attributes;
$this->attributes = array();
$this->attributes = [];
return $return;
}

View File

@@ -44,7 +44,7 @@ class NamespacedAttributeBag extends AttributeBag
return false;
}
return array_key_exists($name, $attributes);
return \array_key_exists($name, $attributes);
}
/**
@@ -60,7 +60,7 @@ class NamespacedAttributeBag extends AttributeBag
return $default;
}
return array_key_exists($name, $attributes) ? $attributes[$name] : $default;
return \array_key_exists($name, $attributes) ? $attributes[$name] : $default;
}
/**
@@ -81,7 +81,7 @@ class NamespacedAttributeBag extends AttributeBag
$retval = null;
$attributes = &$this->resolveAttributePath($name);
$name = $this->resolveKey($name);
if (null !== $attributes && array_key_exists($name, $attributes)) {
if (null !== $attributes && \array_key_exists($name, $attributes)) {
$retval = $attributes[$name];
unset($attributes[$name]);
}
@@ -97,7 +97,7 @@ class NamespacedAttributeBag extends AttributeBag
* @param string $name Key name
* @param bool $writeContext Write context, default false
*
* @return array
* @return array|null
*/
protected function &resolveAttributePath($name, $writeContext = false)
{
@@ -115,7 +115,7 @@ class NamespacedAttributeBag extends AttributeBag
return $array;
}
$array[$parts[0]] = array();
$array[$parts[0]] = [];
return $array;
}
@@ -123,14 +123,14 @@ class NamespacedAttributeBag extends AttributeBag
unset($parts[\count($parts) - 1]);
foreach ($parts as $part) {
if (null !== $array && !array_key_exists($part, $array)) {
if (null !== $array && !\array_key_exists($part, $array)) {
if (!$writeContext) {
$null = null;
return $null;
}
$array[$part] = array();
$array[$part] = [];
}
$array = &$array[$part];

View File

@@ -19,13 +19,13 @@ namespace Symfony\Component\HttpFoundation\Session\Flash;
class AutoExpireFlashBag implements FlashBagInterface
{
private $name = 'flashes';
private $flashes = array('display' => array(), 'new' => array());
private $flashes = ['display' => [], 'new' => []];
private $storageKey;
/**
* @param string $storageKey The key used to store flashes in the session
*/
public function __construct($storageKey = '_sf2_flashes')
public function __construct($storageKey = '_symfony_flashes')
{
$this->storageKey = $storageKey;
}
@@ -53,8 +53,8 @@ class AutoExpireFlashBag implements FlashBagInterface
// The logic: messages from the last request will be stored in new, so we move them to previous
// This request we will show what is in 'display'. What is placed into 'new' this time round will
// be moved to display next time round.
$this->flashes['display'] = array_key_exists('new', $this->flashes) ? $this->flashes['new'] : array();
$this->flashes['new'] = array();
$this->flashes['display'] = \array_key_exists('new', $this->flashes) ? $this->flashes['new'] : [];
$this->flashes['new'] = [];
}
/**
@@ -68,7 +68,7 @@ class AutoExpireFlashBag implements FlashBagInterface
/**
* {@inheritdoc}
*/
public function peek($type, array $default = array())
public function peek($type, array $default = [])
{
return $this->has($type) ? $this->flashes['display'][$type] : $default;
}
@@ -78,13 +78,13 @@ class AutoExpireFlashBag implements FlashBagInterface
*/
public function peekAll()
{
return array_key_exists('display', $this->flashes) ? (array) $this->flashes['display'] : array();
return \array_key_exists('display', $this->flashes) ? (array) $this->flashes['display'] : [];
}
/**
* {@inheritdoc}
*/
public function get($type, array $default = array())
public function get($type, array $default = [])
{
$return = $default;
@@ -106,7 +106,7 @@ class AutoExpireFlashBag implements FlashBagInterface
public function all()
{
$return = $this->flashes['display'];
$this->flashes['display'] = array();
$this->flashes['display'] = [];
return $return;
}
@@ -132,7 +132,7 @@ class AutoExpireFlashBag implements FlashBagInterface
*/
public function has($type)
{
return array_key_exists($type, $this->flashes['display']) && $this->flashes['display'][$type];
return \array_key_exists($type, $this->flashes['display']) && $this->flashes['display'][$type];
}
/**

View File

@@ -14,20 +14,18 @@ namespace Symfony\Component\HttpFoundation\Session\Flash;
/**
* FlashBag flash message container.
*
* \IteratorAggregate implementation is deprecated and will be removed in 3.0.
*
* @author Drak <drak@zikula.org>
*/
class FlashBag implements FlashBagInterface, \IteratorAggregate
class FlashBag implements FlashBagInterface
{
private $name = 'flashes';
private $flashes = array();
private $flashes = [];
private $storageKey;
/**
* @param string $storageKey The key used to store flashes in the session
*/
public function __construct($storageKey = '_sf2_flashes')
public function __construct($storageKey = '_symfony_flashes')
{
$this->storageKey = $storageKey;
}
@@ -64,7 +62,7 @@ class FlashBag implements FlashBagInterface, \IteratorAggregate
/**
* {@inheritdoc}
*/
public function peek($type, array $default = array())
public function peek($type, array $default = [])
{
return $this->has($type) ? $this->flashes[$type] : $default;
}
@@ -80,7 +78,7 @@ class FlashBag implements FlashBagInterface, \IteratorAggregate
/**
* {@inheritdoc}
*/
public function get($type, array $default = array())
public function get($type, array $default = [])
{
if (!$this->has($type)) {
return $default;
@@ -99,7 +97,7 @@ class FlashBag implements FlashBagInterface, \IteratorAggregate
public function all()
{
$return = $this->peekAll();
$this->flashes = array();
$this->flashes = [];
return $return;
}
@@ -125,7 +123,7 @@ class FlashBag implements FlashBagInterface, \IteratorAggregate
*/
public function has($type)
{
return array_key_exists($type, $this->flashes) && $this->flashes[$type];
return \array_key_exists($type, $this->flashes) && $this->flashes[$type];
}
/**
@@ -151,18 +149,4 @@ class FlashBag implements FlashBagInterface, \IteratorAggregate
{
return $this->all();
}
/**
* Returns an iterator for flashes.
*
* @deprecated since version 2.4, to be removed in 3.0.
*
* @return \ArrayIterator An \ArrayIterator instance
*/
public function getIterator()
{
@trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.4 and will be removed in 3.0.', E_USER_DEPRECATED);
return new \ArrayIterator($this->all());
}
}

View File

@@ -21,7 +21,7 @@ use Symfony\Component\HttpFoundation\Session\SessionBagInterface;
interface FlashBagInterface extends SessionBagInterface
{
/**
* Adds a flash message for type.
* Adds a flash message for the given type.
*
* @param string $type
* @param mixed $message
@@ -29,12 +29,12 @@ interface FlashBagInterface extends SessionBagInterface
public function add($type, $message);
/**
* Registers a message for a given type.
* Registers one or more messages for a given type.
*
* @param string $type
* @param string|array $message
* @param string|array $messages
*/
public function set($type, $message);
public function set($type, $messages);
/**
* Gets flash messages for a given type.
@@ -44,7 +44,7 @@ interface FlashBagInterface extends SessionBagInterface
*
* @return array
*/
public function peek($type, array $default = array());
public function peek($type, array $default = []);
/**
* Gets all flash messages.
@@ -61,7 +61,7 @@ interface FlashBagInterface extends SessionBagInterface
*
* @return array
*/
public function get($type, array $default = array());
public function get($type, array $default = []);
/**
* Gets and clears flashes from the stack.

View File

@@ -28,6 +28,8 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable
private $flashName;
private $attributeName;
private $data = [];
private $usageIndex = 0;
/**
* @param SessionStorageInterface $storage A SessionStorageInterface instance
@@ -60,7 +62,7 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable
*/
public function has($name)
{
return $this->storage->getBag($this->attributeName)->has($name);
return $this->getAttributeBag()->has($name);
}
/**
@@ -68,7 +70,7 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable
*/
public function get($name, $default = null)
{
return $this->storage->getBag($this->attributeName)->get($name, $default);
return $this->getAttributeBag()->get($name, $default);
}
/**
@@ -76,7 +78,7 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable
*/
public function set($name, $value)
{
$this->storage->getBag($this->attributeName)->set($name, $value);
$this->getAttributeBag()->set($name, $value);
}
/**
@@ -84,7 +86,7 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable
*/
public function all()
{
return $this->storage->getBag($this->attributeName)->all();
return $this->getAttributeBag()->all();
}
/**
@@ -92,7 +94,7 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable
*/
public function replace(array $attributes)
{
$this->storage->getBag($this->attributeName)->replace($attributes);
$this->getAttributeBag()->replace($attributes);
}
/**
@@ -100,7 +102,7 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable
*/
public function remove($name)
{
return $this->storage->getBag($this->attributeName)->remove($name);
return $this->getAttributeBag()->remove($name);
}
/**
@@ -108,7 +110,7 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable
*/
public function clear()
{
$this->storage->getBag($this->attributeName)->clear();
$this->getAttributeBag()->clear();
}
/**
@@ -126,7 +128,7 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable
*/
public function getIterator()
{
return new \ArrayIterator($this->storage->getBag($this->attributeName)->all());
return new \ArrayIterator($this->getAttributeBag()->all());
}
/**
@@ -136,7 +138,36 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable
*/
public function count()
{
return \count($this->storage->getBag($this->attributeName)->all());
return \count($this->getAttributeBag()->all());
}
/**
* @return int
*
* @internal
*/
public function getUsageIndex()
{
return $this->usageIndex;
}
/**
* @return bool
*
* @internal
*/
public function isEmpty()
{
if ($this->isStarted()) {
++$this->usageIndex;
}
foreach ($this->data as &$data) {
if (!empty($data)) {
return false;
}
}
return true;
}
/**
@@ -204,6 +235,8 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable
*/
public function getMetadataBag()
{
++$this->usageIndex;
return $this->storage->getMetadataBag();
}
@@ -212,7 +245,7 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable
*/
public function registerBag(SessionBagInterface $bag)
{
$this->storage->registerBag($bag);
$this->storage->registerBag(new SessionBagProxy($bag, $this->data, $this->usageIndex));
}
/**
@@ -220,7 +253,9 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable
*/
public function getBag($name)
{
return $this->storage->getBag($name);
$bag = $this->storage->getBag($name);
return method_exists($bag, 'getBag') ? $bag->getBag() : $bag;
}
/**
@@ -232,4 +267,16 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable
{
return $this->getBag($this->flashName);
}
/**
* Gets the attributebag interface.
*
* Note that this method was added to help with IDE autocompletion.
*
* @return AttributeBagInterface
*/
private function getAttributeBag()
{
return $this->getBag($this->attributeName);
}
}

View File

@@ -0,0 +1,89 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation\Session;
/**
* @author Nicolas Grekas <p@tchwork.com>
*
* @internal
*/
final class SessionBagProxy implements SessionBagInterface
{
private $bag;
private $data;
private $usageIndex;
public function __construct(SessionBagInterface $bag, array &$data, &$usageIndex)
{
$this->bag = $bag;
$this->data = &$data;
$this->usageIndex = &$usageIndex;
}
/**
* @return SessionBagInterface
*/
public function getBag()
{
++$this->usageIndex;
return $this->bag;
}
/**
* @return bool
*/
public function isEmpty()
{
if (!isset($this->data[$this->bag->getStorageKey()])) {
return true;
}
++$this->usageIndex;
return empty($this->data[$this->bag->getStorageKey()]);
}
/**
* {@inheritdoc}
*/
public function getName()
{
return $this->bag->getName();
}
/**
* {@inheritdoc}
*/
public function initialize(array &$array)
{
++$this->usageIndex;
$this->data[$this->bag->getStorageKey()] = &$array;
$this->bag->initialize($array);
}
/**
* {@inheritdoc}
*/
public function getStorageKey()
{
return $this->bag->getStorageKey();
}
/**
* {@inheritdoc}
*/
public function clear()
{
return $this->bag->clear();
}
}

View File

@@ -0,0 +1,168 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
/**
* This abstract session handler provides a generic implementation
* of the PHP 7.0 SessionUpdateTimestampHandlerInterface,
* enabling strict and lazy session handling.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
abstract class AbstractSessionHandler implements \SessionHandlerInterface, \SessionUpdateTimestampHandlerInterface
{
private $sessionName;
private $prefetchId;
private $prefetchData;
private $newSessionId;
private $igbinaryEmptyData;
/**
* {@inheritdoc}
*/
public function open($savePath, $sessionName)
{
$this->sessionName = $sessionName;
if (!headers_sent() && !ini_get('session.cache_limiter') && '0' !== ini_get('session.cache_limiter')) {
header(sprintf('Cache-Control: max-age=%d, private, must-revalidate', 60 * (int) ini_get('session.cache_expire')));
}
return true;
}
/**
* @param string $sessionId
*
* @return string
*/
abstract protected function doRead($sessionId);
/**
* @param string $sessionId
* @param string $data
*
* @return bool
*/
abstract protected function doWrite($sessionId, $data);
/**
* @param string $sessionId
*
* @return bool
*/
abstract protected function doDestroy($sessionId);
/**
* {@inheritdoc}
*/
public function validateId($sessionId)
{
$this->prefetchData = $this->read($sessionId);
$this->prefetchId = $sessionId;
return '' !== $this->prefetchData;
}
/**
* {@inheritdoc}
*/
public function read($sessionId)
{
if (null !== $this->prefetchId) {
$prefetchId = $this->prefetchId;
$prefetchData = $this->prefetchData;
$this->prefetchId = $this->prefetchData = null;
if ($prefetchId === $sessionId || '' === $prefetchData) {
$this->newSessionId = '' === $prefetchData ? $sessionId : null;
return $prefetchData;
}
}
$data = $this->doRead($sessionId);
$this->newSessionId = '' === $data ? $sessionId : null;
if (\PHP_VERSION_ID < 70000) {
$this->prefetchData = $data;
}
return $data;
}
/**
* {@inheritdoc}
*/
public function write($sessionId, $data)
{
if (\PHP_VERSION_ID < 70000 && $this->prefetchData) {
$readData = $this->prefetchData;
$this->prefetchData = null;
if ($readData === $data) {
return $this->updateTimestamp($sessionId, $data);
}
}
if (null === $this->igbinaryEmptyData) {
// see https://github.com/igbinary/igbinary/issues/146
$this->igbinaryEmptyData = \function_exists('igbinary_serialize') ? igbinary_serialize([]) : '';
}
if ('' === $data || $this->igbinaryEmptyData === $data) {
return $this->destroy($sessionId);
}
$this->newSessionId = null;
return $this->doWrite($sessionId, $data);
}
/**
* {@inheritdoc}
*/
public function destroy($sessionId)
{
if (\PHP_VERSION_ID < 70000) {
$this->prefetchData = null;
}
if (!headers_sent() && filter_var(ini_get('session.use_cookies'), FILTER_VALIDATE_BOOLEAN)) {
if (!$this->sessionName) {
throw new \LogicException(sprintf('Session name cannot be empty, did you forget to call "parent::open()" in "%s"?.', \get_class($this)));
}
$sessionCookie = sprintf(' %s=', urlencode($this->sessionName));
$sessionCookieWithId = sprintf('%s%s;', $sessionCookie, urlencode($sessionId));
$sessionCookieFound = false;
$otherCookies = [];
foreach (headers_list() as $h) {
if (0 !== stripos($h, 'Set-Cookie:')) {
continue;
}
if (11 === strpos($h, $sessionCookie, 11)) {
$sessionCookieFound = true;
if (11 !== strpos($h, $sessionCookieWithId, 11)) {
$otherCookies[] = $h;
}
} else {
$otherCookies[] = $h;
}
}
if ($sessionCookieFound) {
header_remove('Set-Cookie');
foreach ($otherCookies as $h) {
header($h, false);
}
} else {
setcookie($this->sessionName, '', 0, ini_get('session.cookie_path'), ini_get('session.cookie_domain'), filter_var(ini_get('session.cookie_secure'), FILTER_VALIDATE_BOOLEAN), filter_var(ini_get('session.cookie_httponly'), FILTER_VALIDATE_BOOLEAN));
}
}
return $this->newSessionId === $sessionId || $this->doDestroy($sessionId);
}
}

View File

@@ -11,8 +11,12 @@
namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
@trigger_error(sprintf('The class %s is deprecated since Symfony 3.4 and will be removed in 4.0. Use Symfony\Component\HttpFoundation\Session\Storage\Handler\MemcachedSessionHandler instead.', MemcacheSessionHandler::class), E_USER_DEPRECATED);
/**
* @author Drak <drak@zikula.org>
*
* @deprecated since version 3.4, to be removed in 4.0. Use Symfony\Component\HttpFoundation\Session\Storage\Handler\MemcachedSessionHandler instead.
*/
class MemcacheSessionHandler implements \SessionHandlerInterface
{
@@ -40,9 +44,9 @@ class MemcacheSessionHandler implements \SessionHandlerInterface
*
* @throws \InvalidArgumentException When unsupported options are passed
*/
public function __construct(\Memcache $memcache, array $options = array())
public function __construct(\Memcache $memcache, array $options = [])
{
if ($diff = array_diff(array_keys($options), array('prefix', 'expiretime'))) {
if ($diff = array_diff(array_keys($options), ['prefix', 'expiretime'])) {
throw new \InvalidArgumentException(sprintf('The following options are not supported "%s"', implode(', ', $diff)));
}

View File

@@ -15,11 +15,11 @@ namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
* Memcached based session storage handler based on the Memcached class
* provided by the PHP memcached extension.
*
* @see http://php.net/memcached
* @see https://php.net/memcached
*
* @author Drak <drak@zikula.org>
*/
class MemcachedSessionHandler implements \SessionHandlerInterface
class MemcachedSessionHandler extends AbstractSessionHandler
{
private $memcached;
@@ -38,18 +38,15 @@ class MemcachedSessionHandler implements \SessionHandlerInterface
*
* List of available options:
* * prefix: The prefix to use for the memcached keys in order to avoid collision
* * expiretime: The time to live in seconds
*
* @param \Memcached $memcached A \Memcached instance
* @param array $options An associative array of Memcached options
* * expiretime: The time to live in seconds.
*
* @throws \InvalidArgumentException When unsupported options are passed
*/
public function __construct(\Memcached $memcached, array $options = array())
public function __construct(\Memcached $memcached, array $options = [])
{
$this->memcached = $memcached;
if ($diff = array_diff(array_keys($options), array('prefix', 'expiretime'))) {
if ($diff = array_diff(array_keys($options), ['prefix', 'expiretime'])) {
throw new \InvalidArgumentException(sprintf('The following options are not supported "%s"', implode(', ', $diff)));
}
@@ -58,33 +55,35 @@ class MemcachedSessionHandler implements \SessionHandlerInterface
}
/**
* {@inheritdoc}
*/
public function open($savePath, $sessionName)
{
return true;
}
/**
* {@inheritdoc}
* @return bool
*/
public function close()
{
return true;
return $this->memcached->quit();
}
/**
* {@inheritdoc}
*/
public function read($sessionId)
protected function doRead($sessionId)
{
return $this->memcached->get($this->prefix.$sessionId) ?: '';
}
/**
* @return bool
*/
public function updateTimestamp($sessionId, $data)
{
$this->memcached->touch($this->prefix.$sessionId, time() + $this->ttl);
return true;
}
/**
* {@inheritdoc}
*/
public function write($sessionId, $data)
protected function doWrite($sessionId, $data)
{
return $this->memcached->set($this->prefix.$sessionId, $data, time() + $this->ttl);
}
@@ -92,7 +91,7 @@ class MemcachedSessionHandler implements \SessionHandlerInterface
/**
* {@inheritdoc}
*/
public function destroy($sessionId)
protected function doDestroy($sessionId)
{
$result = $this->memcached->delete($this->prefix.$sessionId);
@@ -100,7 +99,7 @@ class MemcachedSessionHandler implements \SessionHandlerInterface
}
/**
* {@inheritdoc}
* @return bool
*/
public function gc($maxlifetime)
{

View File

@@ -12,9 +12,14 @@
namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
/**
* Session handler using the mongodb/mongodb package and MongoDB driver extension.
*
* @author Markus Bachmann <markus.bachmann@bachi.biz>
*
* @see https://packagist.org/packages/mongodb/mongodb
* @see https://php.net/mongodb
*/
class MongoDbSessionHandler implements \SessionHandlerInterface
class MongoDbSessionHandler extends AbstractSessionHandler
{
private $mongo;
@@ -37,7 +42,7 @@ class MongoDbSessionHandler implements \SessionHandlerInterface
* * id_field: The field name for storing the session id [default: _id]
* * data_field: The field name for storing the session data [default: data]
* * time_field: The field name for storing the timestamp [default: time]
* * expiry_field: The field name for storing the expiry-timestamp [default: expires_at]
* * expiry_field: The field name for storing the expiry-timestamp [default: expires_at].
*
* It is strongly recommended to put an index on the `expiry_field` for
* garbage-collection. Alternatively it's possible to automatically expire
@@ -51,19 +56,23 @@ class MongoDbSessionHandler implements \SessionHandlerInterface
* { "expireAfterSeconds": 0 }
* )
*
* More details on: http://docs.mongodb.org/manual/tutorial/expire-data/
* More details on: https://docs.mongodb.org/manual/tutorial/expire-data/
*
* If you use such an index, you can drop `gc_probability` to 0 since
* no garbage-collection is required.
*
* @param \Mongo|\MongoClient|\MongoDB\Client $mongo A MongoDB\Client, MongoClient or Mongo instance
* @param array $options An associative array of field options
* @param \MongoDB\Client $mongo A MongoDB\Client instance
* @param array $options An associative array of field options
*
* @throws \InvalidArgumentException When MongoClient or Mongo instance not provided
* @throws \InvalidArgumentException When "database" or "collection" not provided
*/
public function __construct($mongo, array $options)
{
if ($mongo instanceof \MongoClient || $mongo instanceof \Mongo) {
@trigger_error(sprintf('Using %s with the legacy mongo extension is deprecated as of 3.4 and will be removed in 4.0. Use it with the mongodb/mongodb package and ext-mongodb instead.', __CLASS__), E_USER_DEPRECATED);
}
if (!($mongo instanceof \MongoDB\Client || $mongo instanceof \MongoClient || $mongo instanceof \Mongo)) {
throw new \InvalidArgumentException('MongoClient or Mongo instance required');
}
@@ -74,20 +83,12 @@ class MongoDbSessionHandler implements \SessionHandlerInterface
$this->mongo = $mongo;
$this->options = array_merge(array(
$this->options = array_merge([
'id_field' => '_id',
'data_field' => 'data',
'time_field' => 'time',
'expiry_field' => 'expires_at',
), $options);
}
/**
* {@inheritdoc}
*/
public function open($savePath, $sessionName)
{
return true;
], $options);
}
/**
@@ -101,13 +102,13 @@ class MongoDbSessionHandler implements \SessionHandlerInterface
/**
* {@inheritdoc}
*/
public function destroy($sessionId)
protected function doDestroy($sessionId)
{
$methodName = $this->mongo instanceof \MongoDB\Client ? 'deleteOne' : 'remove';
$this->getCollection()->$methodName(array(
$this->getCollection()->$methodName([
$this->options['id_field'] => $sessionId,
));
]);
return true;
}
@@ -119,9 +120,9 @@ class MongoDbSessionHandler implements \SessionHandlerInterface
{
$methodName = $this->mongo instanceof \MongoDB\Client ? 'deleteMany' : 'remove';
$this->getCollection()->$methodName(array(
$this->options['expiry_field'] => array('$lt' => $this->createDateTime()),
));
$this->getCollection()->$methodName([
$this->options['expiry_field'] => ['$lt' => $this->createDateTime()],
]);
return true;
}
@@ -129,16 +130,16 @@ class MongoDbSessionHandler implements \SessionHandlerInterface
/**
* {@inheritdoc}
*/
public function write($sessionId, $data)
protected function doWrite($sessionId, $data)
{
$expiry = $this->createDateTime(time() + (int) ini_get('session.gc_maxlifetime'));
$fields = array(
$fields = [
$this->options['time_field'] => $this->createDateTime(),
$this->options['expiry_field'] => $expiry,
);
];
$options = array('upsert' => true);
$options = ['upsert' => true];
if ($this->mongo instanceof \MongoDB\Client) {
$fields[$this->options['data_field']] = new \MongoDB\BSON\Binary($data, \MongoDB\BSON\Binary::TYPE_OLD_BINARY);
@@ -150,8 +151,8 @@ class MongoDbSessionHandler implements \SessionHandlerInterface
$methodName = $this->mongo instanceof \MongoDB\Client ? 'updateOne' : 'update';
$this->getCollection()->$methodName(
array($this->options['id_field'] => $sessionId),
array('$set' => $fields),
[$this->options['id_field'] => $sessionId],
['$set' => $fields],
$options
);
@@ -161,12 +162,39 @@ class MongoDbSessionHandler implements \SessionHandlerInterface
/**
* {@inheritdoc}
*/
public function read($sessionId)
public function updateTimestamp($sessionId, $data)
{
$dbData = $this->getCollection()->findOne(array(
$expiry = $this->createDateTime(time() + (int) ini_get('session.gc_maxlifetime'));
if ($this->mongo instanceof \MongoDB\Client) {
$methodName = 'updateOne';
$options = [];
} else {
$methodName = 'update';
$options = ['multiple' => false];
}
$this->getCollection()->$methodName(
[$this->options['id_field'] => $sessionId],
['$set' => [
$this->options['time_field'] => $this->createDateTime(),
$this->options['expiry_field'] => $expiry,
]],
$options
);
return true;
}
/**
* {@inheritdoc}
*/
protected function doRead($sessionId)
{
$dbData = $this->getCollection()->findOne([
$this->options['id_field'] => $sessionId,
$this->options['expiry_field'] => array('$gte' => $this->createDateTime()),
));
$this->options['expiry_field'] => ['$gte' => $this->createDateTime()],
]);
if (null === $dbData) {
return '';

View File

@@ -23,9 +23,10 @@ class NativeFileSessionHandler extends NativeSessionHandler
* Default null will leave setting as defined by PHP.
* '/path', 'N;/path', or 'N;octal-mode;/path
*
* @see http://php.net/session.configuration.php#ini.session.save-path for further details.
* @see https://php.net/session.configuration#ini.session.save-path for further details.
*
* @throws \InvalidArgumentException On invalid $savePath
* @throws \RuntimeException When failing to create the save directory
*/
public function __construct($savePath = null)
{

View File

@@ -11,14 +11,14 @@
namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
// Adds SessionHandler functionality if available.
// @see http://php.net/sessionhandler
if (\PHP_VERSION_ID >= 50400) {
class NativeSessionHandler extends \SessionHandler
{
}
} else {
class NativeSessionHandler
/**
* @deprecated since version 3.4, to be removed in 4.0. Use \SessionHandler instead.
* @see https://php.net/sessionhandler
*/
class NativeSessionHandler extends \SessionHandler
{
public function __construct()
{
@trigger_error('The '.__NAMESPACE__.'\NativeSessionHandler class is deprecated since Symfony 3.4 and will be removed in 4.0. Use the \SessionHandler class instead.', E_USER_DEPRECATED);
}
}

View File

@@ -12,22 +12,12 @@
namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
/**
* NullSessionHandler.
*
* Can be used in unit testing or in a situations where persisted sessions are not desired.
*
* @author Drak <drak@zikula.org>
*/
class NullSessionHandler implements \SessionHandlerInterface
class NullSessionHandler extends AbstractSessionHandler
{
/**
* {@inheritdoc}
*/
public function open($savePath, $sessionName)
{
return true;
}
/**
* {@inheritdoc}
*/
@@ -39,7 +29,15 @@ class NullSessionHandler implements \SessionHandlerInterface
/**
* {@inheritdoc}
*/
public function read($sessionId)
public function validateId($sessionId)
{
return true;
}
/**
* {@inheritdoc}
*/
protected function doRead($sessionId)
{
return '';
}
@@ -47,7 +45,7 @@ class NullSessionHandler implements \SessionHandlerInterface
/**
* {@inheritdoc}
*/
public function write($sessionId, $data)
public function updateTimestamp($sessionId, $data)
{
return true;
}
@@ -55,7 +53,7 @@ class NullSessionHandler implements \SessionHandlerInterface
/**
* {@inheritdoc}
*/
public function destroy($sessionId)
protected function doWrite($sessionId, $data)
{
return true;
}
@@ -63,6 +61,14 @@ class NullSessionHandler implements \SessionHandlerInterface
/**
* {@inheritdoc}
*/
protected function doDestroy($sessionId)
{
return true;
}
/**
* @return bool
*/
public function gc($maxlifetime)
{
return true;

View File

@@ -32,13 +32,13 @@ namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
* Saving it in a character column could corrupt the data. You can use createTable()
* to initialize a correctly defined table.
*
* @see http://php.net/sessionhandlerinterface
* @see https://php.net/sessionhandlerinterface
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Michael Williams <michael.williams@funsational.com>
* @author Tobias Schultze <http://tobion.de>
*/
class PdoSessionHandler implements \SessionHandlerInterface
class PdoSessionHandler extends AbstractSessionHandler
{
/**
* No locking is done. This means sessions are prone to loss of data due to
@@ -118,7 +118,7 @@ class PdoSessionHandler implements \SessionHandlerInterface
/**
* @var array Connection options when lazy-connect
*/
private $connectionOptions = array();
private $connectionOptions = [];
/**
* @var int The strategy for locking, see constants
@@ -130,7 +130,7 @@ class PdoSessionHandler implements \SessionHandlerInterface
*
* @var \PDOStatement[] An array of statements to release advisory locks
*/
private $unlockStatements = array();
private $unlockStatements = [];
/**
* @var bool True when the current session exists but expired according to session.gc_maxlifetime
@@ -161,15 +161,15 @@ class PdoSessionHandler implements \SessionHandlerInterface
* * db_time_col: The column where to store the timestamp [default: sess_time]
* * db_username: The username when lazy-connect [default: '']
* * db_password: The password when lazy-connect [default: '']
* * db_connection_options: An array of driver-specific connection options [default: array()]
* * db_connection_options: An array of driver-specific connection options [default: []]
* * lock_mode: The strategy for locking, see constants [default: LOCK_TRANSACTIONAL]
*
* @param \PDO|string|null $pdoOrDsn A \PDO instance or DSN string or null
* @param \PDO|string|null $pdoOrDsn A \PDO instance or DSN string or URL string or null
* @param array $options An associative array of options
*
* @throws \InvalidArgumentException When PDO error mode is not PDO::ERRMODE_EXCEPTION
*/
public function __construct($pdoOrDsn = null, array $options = array())
public function __construct($pdoOrDsn = null, array $options = [])
{
if ($pdoOrDsn instanceof \PDO) {
if (\PDO::ERRMODE_EXCEPTION !== $pdoOrDsn->getAttribute(\PDO::ATTR_ERRMODE)) {
@@ -178,6 +178,8 @@ class PdoSessionHandler implements \SessionHandlerInterface
$this->pdo = $pdoOrDsn;
$this->driver = $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME);
} elseif (\is_string($pdoOrDsn) && false !== strpos($pdoOrDsn, '://')) {
$this->dsn = $this->buildDsnFromUrl($pdoOrDsn);
} else {
$this->dsn = $pdoOrDsn;
}
@@ -216,7 +218,7 @@ class PdoSessionHandler implements \SessionHandlerInterface
// - trailing space removal
// - case-insensitivity
// - language processing like é == e
$sql = "CREATE TABLE $this->table ($this->idCol VARBINARY(128) NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol MEDIUMINT NOT NULL, $this->timeCol INTEGER UNSIGNED NOT NULL) COLLATE utf8_bin, ENGINE = InnoDB";
$sql = "CREATE TABLE $this->table ($this->idCol VARBINARY(128) NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER UNSIGNED NOT NULL, $this->timeCol INTEGER UNSIGNED NOT NULL) COLLATE utf8_bin, ENGINE = InnoDB";
break;
case 'sqlite':
$sql = "CREATE TABLE $this->table ($this->idCol TEXT NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)";
@@ -260,11 +262,13 @@ class PdoSessionHandler implements \SessionHandlerInterface
*/
public function open($savePath, $sessionName)
{
$this->sessionExpired = false;
if (null === $this->pdo) {
$this->connect($this->dsn ?: $savePath);
}
return true;
return parent::open($savePath, $sessionName);
}
/**
@@ -273,7 +277,7 @@ class PdoSessionHandler implements \SessionHandlerInterface
public function read($sessionId)
{
try {
return $this->doRead($sessionId);
return parent::read($sessionId);
} catch (\PDOException $e) {
$this->rollback();
@@ -282,7 +286,7 @@ class PdoSessionHandler implements \SessionHandlerInterface
}
/**
* {@inheritdoc}
* @return bool
*/
public function gc($maxlifetime)
{
@@ -296,7 +300,7 @@ class PdoSessionHandler implements \SessionHandlerInterface
/**
* {@inheritdoc}
*/
public function destroy($sessionId)
protected function doDestroy($sessionId)
{
// delete the record associated with this id
$sql = "DELETE FROM $this->table WHERE $this->idCol = :id";
@@ -317,7 +321,7 @@ class PdoSessionHandler implements \SessionHandlerInterface
/**
* {@inheritdoc}
*/
public function write($sessionId, $data)
protected function doWrite($sessionId, $data)
{
$maxlifetime = (int) ini_get('session.gc_maxlifetime');
@@ -360,6 +364,30 @@ class PdoSessionHandler implements \SessionHandlerInterface
return true;
}
/**
* {@inheritdoc}
*/
public function updateTimestamp($sessionId, $data)
{
$maxlifetime = (int) ini_get('session.gc_maxlifetime');
try {
$updateStmt = $this->pdo->prepare(
"UPDATE $this->table SET $this->lifetimeCol = :lifetime, $this->timeCol = :time WHERE $this->idCol = :id"
);
$updateStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
$updateStmt->bindParam(':lifetime', $maxlifetime, \PDO::PARAM_INT);
$updateStmt->bindValue(':time', time(), \PDO::PARAM_INT);
$updateStmt->execute();
} catch (\PDOException $e) {
$this->rollback();
throw $e;
}
return true;
}
/**
* {@inheritdoc}
*/
@@ -405,6 +433,102 @@ class PdoSessionHandler implements \SessionHandlerInterface
$this->driver = $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME);
}
/**
* Builds a PDO DSN from a URL-like connection string.
*
* @param string $dsnOrUrl
*
* @return string
*
* @todo implement missing support for oci DSN (which look totally different from other PDO ones)
*/
private function buildDsnFromUrl($dsnOrUrl)
{
// (pdo_)?sqlite3?:///... => (pdo_)?sqlite3?://localhost/... or else the URL will be invalid
$url = preg_replace('#^((?:pdo_)?sqlite3?):///#', '$1://localhost/', $dsnOrUrl);
$params = parse_url($url);
if (false === $params) {
return $dsnOrUrl; // If the URL is not valid, let's assume it might be a DSN already.
}
$params = array_map('rawurldecode', $params);
// Override the default username and password. Values passed through options will still win over these in the constructor.
if (isset($params['user'])) {
$this->username = $params['user'];
}
if (isset($params['pass'])) {
$this->password = $params['pass'];
}
if (!isset($params['scheme'])) {
throw new \InvalidArgumentException('URLs without scheme are not supported to configure the PdoSessionHandler');
}
$driverAliasMap = [
'mssql' => 'sqlsrv',
'mysql2' => 'mysql', // Amazon RDS, for some weird reason
'postgres' => 'pgsql',
'postgresql' => 'pgsql',
'sqlite3' => 'sqlite',
];
$driver = isset($driverAliasMap[$params['scheme']]) ? $driverAliasMap[$params['scheme']] : $params['scheme'];
// Doctrine DBAL supports passing its internal pdo_* driver names directly too (allowing both dashes and underscores). This allows supporting the same here.
if (0 === strpos($driver, 'pdo_') || 0 === strpos($driver, 'pdo-')) {
$driver = substr($driver, 4);
}
switch ($driver) {
case 'mysql':
case 'pgsql':
$dsn = $driver.':';
if (isset($params['host']) && '' !== $params['host']) {
$dsn .= 'host='.$params['host'].';';
}
if (isset($params['port']) && '' !== $params['port']) {
$dsn .= 'port='.$params['port'].';';
}
if (isset($params['path'])) {
$dbName = substr($params['path'], 1); // Remove the leading slash
$dsn .= 'dbname='.$dbName.';';
}
return $dsn;
case 'sqlite':
return 'sqlite:'.substr($params['path'], 1);
case 'sqlsrv':
$dsn = 'sqlsrv:server=';
if (isset($params['host'])) {
$dsn .= $params['host'];
}
if (isset($params['port']) && '' !== $params['port']) {
$dsn .= ','.$params['port'];
}
if (isset($params['path'])) {
$dbName = substr($params['path'], 1); // Remove the leading slash
$dsn .= ';Database='.$dbName;
}
return $dsn;
default:
throw new \InvalidArgumentException(sprintf('The scheme "%s" is not supported by the PdoSessionHandler URL configuration. Pass a PDO DSN directly.', $params['scheme']));
}
}
/**
* Helper method to begin a transaction.
*
@@ -414,7 +538,7 @@ class PdoSessionHandler implements \SessionHandlerInterface
* PDO::rollback or PDO::inTransaction for SQLite.
*
* Also MySQLs default isolation, REPEATABLE READ, causes deadlock for different sessions
* due to http://www.mysqlperformanceblog.com/2013/12/12/one-more-innodb-gap-lock-to-avoid/ .
* due to https://percona.com/blog/2013/12/12/one-more-innodb-gap-lock-to-avoid/ .
* So we change it to READ COMMITTED.
*/
private function beginTransaction()
@@ -483,10 +607,8 @@ class PdoSessionHandler implements \SessionHandlerInterface
*
* @return string The session data
*/
private function doRead($sessionId)
protected function doRead($sessionId)
{
$this->sessionExpired = false;
if (self::LOCK_ADVISORY === $this->lockMode) {
$this->unlockStatements[] = $this->doAdvisoryLock($sessionId);
}
@@ -515,7 +637,9 @@ class PdoSessionHandler implements \SessionHandlerInterface
throw new \RuntimeException('Failed to read session: INSERT reported a duplicate id but next SELECT did not return any data.');
}
if (self::LOCK_TRANSACTIONAL === $this->lockMode && 'sqlite' !== $this->driver) {
if (!filter_var(ini_get('session.use_strict_mode'), FILTER_VALIDATE_BOOLEAN) && self::LOCK_TRANSACTIONAL === $this->lockMode && 'sqlite' !== $this->driver) {
// In strict mode, session fixation is not possible: new sessions always start with a unique
// random id, so that concurrency is not possible and this code path can be skipped.
// Exclusive-reading of non-existent rows does not block, so we need to do an insert to block
// until other connections to the session are committed.
try {
@@ -559,7 +683,7 @@ class PdoSessionHandler implements \SessionHandlerInterface
switch ($this->driver) {
case 'mysql':
// MySQL 5.7.5 and later enforces a maximum length on lock names of 64 characters. Previously, no limit was enforced.
$lockId = \substr($sessionId, 0, 64);
$lockId = substr($sessionId, 0, 64);
// should we handle the return value? 0 on timeout, null on error
// we use a timeout of 50 seconds which is also the default for innodb_lock_wait_timeout
$stmt = $this->pdo->prepare('SELECT GET_LOCK(:key, 50)');
@@ -740,7 +864,7 @@ class PdoSessionHandler implements \SessionHandlerInterface
break;
case 'sqlsrv' === $this->driver && version_compare($this->pdo->getAttribute(\PDO::ATTR_SERVER_VERSION), '10', '>='):
// MERGE is only available since SQL Server 2008 and must be terminated by semicolon
// It also requires HOLDLOCK according to http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx
// It also requires HOLDLOCK according to https://weblogs.sqlteam.com/dang/2009/01/31/upsert-race-condition-with-merge/
$mergeSql = "MERGE INTO $this->table WITH (HOLDLOCK) USING (SELECT 1 AS dummy) AS src ON ($this->idCol = ?) ".
"WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (?, ?, ?, ?) ".
"WHEN MATCHED THEN UPDATE SET $this->dataCol = ?, $this->lifetimeCol = ?, $this->timeCol = ?;";
@@ -753,7 +877,7 @@ class PdoSessionHandler implements \SessionHandlerInterface
"ON CONFLICT ($this->idCol) DO UPDATE SET ($this->dataCol, $this->lifetimeCol, $this->timeCol) = (EXCLUDED.$this->dataCol, EXCLUDED.$this->lifetimeCol, EXCLUDED.$this->timeCol)";
break;
default:
// MERGE is not supported with LOBs: http://www.oracle.com/technetwork/articles/fuecks-lobs-095315.html
// MERGE is not supported with LOBs: https://oracle.com/technetwork/articles/fuecks-lobs-095315.html
return null;
}

View File

@@ -0,0 +1,103 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
/**
* Adds basic `SessionUpdateTimestampHandlerInterface` behaviors to another `SessionHandlerInterface`.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class StrictSessionHandler extends AbstractSessionHandler
{
private $handler;
private $doDestroy;
public function __construct(\SessionHandlerInterface $handler)
{
if ($handler instanceof \SessionUpdateTimestampHandlerInterface) {
throw new \LogicException(sprintf('"%s" is already an instance of "SessionUpdateTimestampHandlerInterface", you cannot wrap it with "%s".', \get_class($handler), self::class));
}
$this->handler = $handler;
}
/**
* {@inheritdoc}
*/
public function open($savePath, $sessionName)
{
parent::open($savePath, $sessionName);
return $this->handler->open($savePath, $sessionName);
}
/**
* {@inheritdoc}
*/
protected function doRead($sessionId)
{
return $this->handler->read($sessionId);
}
/**
* {@inheritdoc}
*/
public function updateTimestamp($sessionId, $data)
{
return $this->write($sessionId, $data);
}
/**
* {@inheritdoc}
*/
protected function doWrite($sessionId, $data)
{
return $this->handler->write($sessionId, $data);
}
/**
* {@inheritdoc}
*/
public function destroy($sessionId)
{
$this->doDestroy = true;
$destroyed = parent::destroy($sessionId);
return $this->doDestroy ? $this->doDestroy($sessionId) : $destroyed;
}
/**
* {@inheritdoc}
*/
protected function doDestroy($sessionId)
{
$this->doDestroy = false;
return $this->handler->destroy($sessionId);
}
/**
* {@inheritdoc}
*/
public function close()
{
return $this->handler->close();
}
/**
* @return bool
*/
public function gc($maxlifetime)
{
return $this->handler->gc($maxlifetime);
}
}

View File

@@ -15,6 +15,8 @@ namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
* Wraps another SessionHandlerInterface to only write the session when it has been modified.
*
* @author Adrien Brault <adrien.brault@gmail.com>
*
* @deprecated since version 3.4, to be removed in 4.0. Implement `SessionUpdateTimestampHandlerInterface` or extend `AbstractSessionHandler` instead.
*/
class WriteCheckSessionHandler implements \SessionHandlerInterface
{
@@ -27,6 +29,8 @@ class WriteCheckSessionHandler implements \SessionHandlerInterface
public function __construct(\SessionHandlerInterface $wrappedSessionHandler)
{
@trigger_error(sprintf('The %s class is deprecated since Symfony 3.4 and will be removed in 4.0. Implement `SessionUpdateTimestampHandlerInterface` or extend `AbstractSessionHandler` instead.', self::class), E_USER_DEPRECATED);
$this->wrappedSessionHandler = $wrappedSessionHandler;
}

View File

@@ -39,7 +39,7 @@ class MetadataBag implements SessionBagInterface
/**
* @var array
*/
protected $meta = array(self::CREATED => 0, self::UPDATED => 0, self::LIFETIME => 0);
protected $meta = [self::CREATED => 0, self::UPDATED => 0, self::LIFETIME => 0];
/**
* Unix timestamp.

View File

@@ -50,7 +50,7 @@ class MockArraySessionStorage implements SessionStorageInterface
/**
* @var array
*/
protected $data = array();
protected $data = [];
/**
* @var MetadataBag
@@ -60,7 +60,7 @@ class MockArraySessionStorage implements SessionStorageInterface
/**
* @var array|SessionBagInterface[]
*/
protected $bags = array();
protected $bags = [];
/**
* @param string $name Session name
@@ -170,7 +170,7 @@ class MockArraySessionStorage implements SessionStorageInterface
}
// clear out the session
$this->data = array();
$this->data = [];
// reconnect the bags to the session
$this->loadSession();
@@ -242,11 +242,11 @@ class MockArraySessionStorage implements SessionStorageInterface
protected function loadSession()
{
$bags = array_merge($this->bags, array($this->metadataBag));
$bags = array_merge($this->bags, [$this->metadataBag]);
foreach ($bags as $bag) {
$key = $bag->getStorageKey();
$this->data[$key] = isset($this->data[$key]) ? $this->data[$key] : array();
$this->data[$key] = isset($this->data[$key]) ? $this->data[$key] : [];
$bag->initialize($this->data[$key]);
}

View File

@@ -91,7 +91,26 @@ class MockFileSessionStorage extends MockArraySessionStorage
throw new \RuntimeException('Trying to save a session that was not started yet or was already closed');
}
file_put_contents($this->getFilePath(), serialize($this->data));
$data = $this->data;
foreach ($this->bags as $bag) {
if (empty($data[$key = $bag->getStorageKey()])) {
unset($data[$key]);
}
}
if ([$key = $this->metadataBag->getStorageKey()] === array_keys($data)) {
unset($data[$key]);
}
try {
if ($data) {
file_put_contents($this->getFilePath(), serialize($data));
} else {
$this->destroy();
}
} finally {
$this->data = $data;
}
// this is needed for Silex, where the session object is re-used across requests
// in functional tests. In Symfony, the container is rebooted, so we don't have
@@ -126,7 +145,7 @@ class MockFileSessionStorage extends MockArraySessionStorage
private function read()
{
$filePath = $this->getFilePath();
$this->data = is_readable($filePath) && is_file($filePath) ? unserialize(file_get_contents($filePath)) : array();
$this->data = is_readable($filePath) && is_file($filePath) ? unserialize(file_get_contents($filePath)) : [];
$this->loadSession();
}

View File

@@ -12,9 +12,8 @@
namespace Symfony\Component\HttpFoundation\Session\Storage;
use Symfony\Component\HttpFoundation\Session\SessionBagInterface;
use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeSessionHandler;
use Symfony\Component\HttpFoundation\Session\Storage\Handler\StrictSessionHandler;
use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy;
use Symfony\Component\HttpFoundation\Session\Storage\Proxy\NativeProxy;
use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy;
/**
@@ -25,11 +24,9 @@ use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy;
class NativeSessionStorage implements SessionStorageInterface
{
/**
* Array of SessionBagInterface.
*
* @var SessionBagInterface[]
*/
protected $bags;
protected $bags = [];
/**
* @var bool
@@ -42,7 +39,7 @@ class NativeSessionStorage implements SessionStorageInterface
protected $closed = false;
/**
* @var AbstractProxy
* @var AbstractProxy|\SessionHandlerInterface
*/
protected $saveHandler;
@@ -57,13 +54,14 @@ class NativeSessionStorage implements SessionStorageInterface
*
* List of options for $options array with their defaults.
*
* @see http://php.net/session.configuration for options
* @see https://php.net/session.configuration for options
* but we omit 'session.' from the beginning of the keys for convenience.
*
* ("auto_start", is not supported as it tells PHP to start a session before
* PHP starts to execute user-land code. Setting during runtime has no effect).
*
* cache_limiter, "" (use "0" to prevent headers from being sent entirely).
* cache_expire, "0"
* cookie_domain, ""
* cookie_httponly, ""
* cookie_lifetime, "0"
@@ -96,24 +94,25 @@ class NativeSessionStorage implements SessionStorageInterface
* trans_sid_hosts, $_SERVER['HTTP_HOST']
* trans_sid_tags, "a=href,area=href,frame=src,form="
*
* @param array $options Session configuration options
* @param AbstractProxy|NativeSessionHandler|\SessionHandlerInterface|null $handler
* @param MetadataBag $metaBag MetadataBag
* @param array $options Session configuration options
* @param \SessionHandlerInterface|null $handler
* @param MetadataBag $metaBag MetadataBag
*/
public function __construct(array $options = array(), $handler = null, MetadataBag $metaBag = null)
public function __construct(array $options = [], $handler = null, MetadataBag $metaBag = null)
{
$options += array(
// disable by default because it's managed by HeaderBag (if used)
'cache_limiter' => '',
'use_cookies' => 1,
);
if (\PHP_VERSION_ID >= 50400) {
session_register_shutdown();
} else {
register_shutdown_function('session_write_close');
if (!\extension_loaded('session')) {
throw new \LogicException('PHP extension "session" is required.');
}
$options += [
'cache_limiter' => '',
'cache_expire' => 0,
'use_cookies' => 1,
'lazy_write' => 1,
];
session_register_shutdown();
$this->setMetadataBag($metaBag);
$this->setOptions($options);
$this->setSaveHandler($handler);
@@ -122,7 +121,7 @@ class NativeSessionStorage implements SessionStorageInterface
/**
* Gets the save handler instance.
*
* @return AbstractProxy
* @return AbstractProxy|\SessionHandlerInterface
*/
public function getSaveHandler()
{
@@ -138,16 +137,11 @@ class NativeSessionStorage implements SessionStorageInterface
return true;
}
if (\PHP_VERSION_ID >= 50400 && \PHP_SESSION_ACTIVE === session_status()) {
if (\PHP_SESSION_ACTIVE === session_status()) {
throw new \RuntimeException('Failed to start the session: already started by PHP.');
}
if (\PHP_VERSION_ID < 50400 && !$this->closed && isset($_SESSION) && session_id()) {
// not 100% fool-proof, but is the most reliable way to determine if a session is active in PHP 5.3
throw new \RuntimeException('Failed to start the session: already started by PHP ($_SESSION is set).');
}
if (ini_get('session.use_cookies') && headers_sent($file, $line)) {
if (filter_var(ini_get('session.use_cookies'), FILTER_VALIDATE_BOOLEAN) && headers_sent($file, $line)) {
throw new \RuntimeException(sprintf('Failed to start the session because headers have already been sent by "%s" at line %d.', $file, $line));
}
@@ -157,10 +151,6 @@ class NativeSessionStorage implements SessionStorageInterface
}
$this->loadSession();
if (!$this->saveHandler->isWrapper() && !$this->saveHandler->isSessionHandlerInterface()) {
// This condition matches only PHP 5.3 with internal save handlers
$this->saveHandler->setActive(true);
}
return true;
}
@@ -203,12 +193,7 @@ class NativeSessionStorage implements SessionStorageInterface
public function regenerate($destroy = false, $lifetime = null)
{
// Cannot regenerate the session ID for non-active sessions.
if (\PHP_VERSION_ID >= 50400 && \PHP_SESSION_ACTIVE !== session_status()) {
return false;
}
// Check if session ID exists in PHP 5.3
if (\PHP_VERSION_ID < 50400 && '' === session_id()) {
if (\PHP_SESSION_ACTIVE !== session_status()) {
return false;
}
@@ -227,7 +212,7 @@ class NativeSessionStorage implements SessionStorageInterface
$isRegenerated = session_regenerate_id($destroy);
// The reference to $_SESSION in session bags is lost in PHP7 and we need to re-create it.
// @see https://bugs.php.net/bug.php?id=70013
// @see https://bugs.php.net/70013
$this->loadSession();
return $isRegenerated;
@@ -238,11 +223,32 @@ class NativeSessionStorage implements SessionStorageInterface
*/
public function save()
{
session_write_close();
$session = $_SESSION;
if (!$this->saveHandler->isWrapper() && !$this->saveHandler->isSessionHandlerInterface()) {
// This condition matches only PHP 5.3 with internal save handlers
$this->saveHandler->setActive(false);
foreach ($this->bags as $bag) {
if (empty($_SESSION[$key = $bag->getStorageKey()])) {
unset($_SESSION[$key]);
}
}
if ([$key = $this->metadataBag->getStorageKey()] === array_keys($_SESSION)) {
unset($_SESSION[$key]);
}
// Register error handler to add information about the current save handler
$previousHandler = set_error_handler(function ($type, $msg, $file, $line) use (&$previousHandler) {
if (E_WARNING === $type && 0 === strpos($msg, 'session_write_close():')) {
$handler = $this->saveHandler instanceof SessionHandlerProxy ? $this->saveHandler->getHandler() : $this->saveHandler;
$msg = sprintf('session_write_close(): Failed to write session data with "%s" handler', \get_class($handler));
}
return $previousHandler ? $previousHandler($type, $msg, $file, $line) : false;
});
try {
session_write_close();
} finally {
restore_error_handler();
$_SESSION = $session;
}
$this->closed = true;
@@ -260,7 +266,7 @@ class NativeSessionStorage implements SessionStorageInterface
}
// clear out the session
$_SESSION = array();
$_SESSION = [];
// reconnect the bags to the session
$this->loadSession();
@@ -329,17 +335,17 @@ class NativeSessionStorage implements SessionStorageInterface
* For convenience we omit 'session.' from the beginning of the keys.
* Explicitly ignores other ini keys.
*
* @param array $options Session ini directives array(key => value)
* @param array $options Session ini directives [key => value]
*
* @see http://php.net/session.configuration
* @see https://php.net/session.configuration
*/
public function setOptions(array $options)
{
if (headers_sent() || (\PHP_VERSION_ID >= 50400 && \PHP_SESSION_ACTIVE === session_status())) {
if (headers_sent() || \PHP_SESSION_ACTIVE === session_status()) {
return;
}
$validOptions = array_flip(array(
$validOptions = array_flip([
'cache_expire', 'cache_limiter', 'cookie_domain', 'cookie_httponly',
'cookie_lifetime', 'cookie_path', 'cookie_secure',
'entropy_file', 'entropy_length', 'gc_divisor',
@@ -350,7 +356,7 @@ class NativeSessionStorage implements SessionStorageInterface
'upload_progress.cleanup', 'upload_progress.prefix', 'upload_progress.name',
'upload_progress.freq', 'upload_progress.min_freq', 'url_rewriter.tags',
'sid_length', 'sid_bits_per_character', 'trans_sid_hosts', 'trans_sid_tags',
));
]);
foreach ($options as $key => $value) {
if (isset($validOptions[$key])) {
@@ -368,54 +374,41 @@ class NativeSessionStorage implements SessionStorageInterface
* ini_set('session.save_handler', 'files');
* ini_set('session.save_path', '/tmp');
*
* or pass in a NativeSessionHandler instance which configures session.save_handler in the
* or pass in a \SessionHandler instance which configures session.save_handler in the
* constructor, for a template see NativeFileSessionHandler or use handlers in
* composer package drak/native-session
*
* @see http://php.net/session-set-save-handler
* @see http://php.net/sessionhandlerinterface
* @see http://php.net/sessionhandler
* @see http://github.com/drak/NativeSession
* @see https://php.net/session-set-save-handler
* @see https://php.net/sessionhandlerinterface
* @see https://php.net/sessionhandler
* @see https://github.com/zikula/NativeSession
*
* @param AbstractProxy|NativeSessionHandler|\SessionHandlerInterface|null $saveHandler
* @param \SessionHandlerInterface|null $saveHandler
*
* @throws \InvalidArgumentException
*/
public function setSaveHandler($saveHandler = null)
{
if (!$saveHandler instanceof AbstractProxy &&
!$saveHandler instanceof NativeSessionHandler &&
!$saveHandler instanceof \SessionHandlerInterface &&
null !== $saveHandler) {
throw new \InvalidArgumentException('Must be instance of AbstractProxy or NativeSessionHandler; implement \SessionHandlerInterface; or be null.');
throw new \InvalidArgumentException('Must be instance of AbstractProxy; implement \SessionHandlerInterface; or be null.');
}
// Wrap $saveHandler in proxy and prevent double wrapping of proxy
if (!$saveHandler instanceof AbstractProxy && $saveHandler instanceof \SessionHandlerInterface) {
$saveHandler = new SessionHandlerProxy($saveHandler);
} elseif (!$saveHandler instanceof AbstractProxy) {
$saveHandler = \PHP_VERSION_ID >= 50400 ?
new SessionHandlerProxy(new \SessionHandler()) : new NativeProxy();
$saveHandler = new SessionHandlerProxy(new StrictSessionHandler(new \SessionHandler()));
}
$this->saveHandler = $saveHandler;
if (headers_sent() || (\PHP_VERSION_ID >= 50400 && \PHP_SESSION_ACTIVE === session_status())) {
if (headers_sent() || \PHP_SESSION_ACTIVE === session_status()) {
return;
}
if ($this->saveHandler instanceof \SessionHandlerInterface) {
if (\PHP_VERSION_ID >= 50400) {
session_set_save_handler($this->saveHandler, false);
} else {
session_set_save_handler(
array($this->saveHandler, 'open'),
array($this->saveHandler, 'close'),
array($this->saveHandler, 'read'),
array($this->saveHandler, 'write'),
array($this->saveHandler, 'destroy'),
array($this->saveHandler, 'gc')
);
}
if ($this->saveHandler instanceof SessionHandlerProxy) {
session_set_save_handler($this->saveHandler, false);
}
}
@@ -433,11 +426,11 @@ class NativeSessionStorage implements SessionStorageInterface
$session = &$_SESSION;
}
$bags = array_merge($this->bags, array($this->metadataBag));
$bags = array_merge($this->bags, [$this->metadataBag]);
foreach ($bags as $bag) {
$key = $bag->getStorageKey();
$session[$key] = isset($session[$key]) ? $session[$key] : array();
$session[$key] = isset($session[$key]) && \is_array($session[$key]) ? $session[$key] : [];
$bag->initialize($session[$key]);
}

View File

@@ -11,9 +11,6 @@
namespace Symfony\Component\HttpFoundation\Session\Storage;
use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeSessionHandler;
use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy;
/**
* Allows session to be started by PHP and managed by Symfony.
*
@@ -22,11 +19,15 @@ use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy;
class PhpBridgeSessionStorage extends NativeSessionStorage
{
/**
* @param AbstractProxy|NativeSessionHandler|\SessionHandlerInterface|null $handler
* @param MetadataBag $metaBag MetadataBag
* @param \SessionHandlerInterface|null $handler
* @param MetadataBag $metaBag MetadataBag
*/
public function __construct($handler = null, MetadataBag $metaBag = null)
{
if (!\extension_loaded('session')) {
throw new \LogicException('PHP extension "session" is required.');
}
$this->setMetadataBag($metaBag);
$this->setSaveHandler($handler);
}
@@ -41,10 +42,6 @@ class PhpBridgeSessionStorage extends NativeSessionStorage
}
$this->loadSession();
if (!$this->saveHandler->isWrapper() && !$this->saveHandler->isSessionHandlerInterface()) {
// This condition matches only PHP 5.3 + internal save handlers
$this->saveHandler->setActive(true);
}
return true;
}

View File

@@ -12,8 +12,6 @@
namespace Symfony\Component\HttpFoundation\Session\Storage\Proxy;
/**
* AbstractProxy.
*
* @author Drak <drak@zikula.org>
*/
abstract class AbstractProxy
@@ -25,11 +23,6 @@ abstract class AbstractProxy
*/
protected $wrapper = false;
/**
* @var bool
*/
protected $active = false;
/**
* @var string
*/
@@ -38,7 +31,7 @@ abstract class AbstractProxy
/**
* Gets the session.save_handler name.
*
* @return string
* @return string|null
*/
public function getSaveHandlerName()
{
@@ -72,32 +65,7 @@ abstract class AbstractProxy
*/
public function isActive()
{
if (\PHP_VERSION_ID >= 50400) {
return $this->active = \PHP_SESSION_ACTIVE === session_status();
}
return $this->active;
}
/**
* Sets the active flag.
*
* Has no effect under PHP 5.4+ as status is detected
* automatically in isActive()
*
* @internal
*
* @param bool $flag
*
* @throws \LogicException
*/
public function setActive($flag)
{
if (\PHP_VERSION_ID >= 50400) {
throw new \LogicException('This method is disabled in PHP 5.4.0+');
}
$this->active = (bool) $flag;
return \PHP_SESSION_ACTIVE === session_status();
}
/**

View File

@@ -11,9 +11,13 @@
namespace Symfony\Component\HttpFoundation\Session\Storage\Proxy;
@trigger_error('The '.__NAMESPACE__.'\NativeProxy class is deprecated since Symfony 3.4 and will be removed in 4.0. Use your session handler implementation directly.', E_USER_DEPRECATED);
/**
* This proxy is built-in session handlers in PHP 5.3.x.
*
* @deprecated since version 3.4, to be removed in 4.0. Use your session handler implementation directly.
*
* @author Drak <drak@zikula.org>
*/
class NativeProxy extends AbstractProxy

View File

@@ -14,13 +14,10 @@ namespace Symfony\Component\HttpFoundation\Session\Storage\Proxy;
/**
* @author Drak <drak@zikula.org>
*/
class SessionHandlerProxy extends AbstractProxy implements \SessionHandlerInterface
class SessionHandlerProxy extends AbstractProxy implements \SessionHandlerInterface, \SessionUpdateTimestampHandlerInterface
{
protected $handler;
/**
* @param \SessionHandlerInterface $handler
*/
public function __construct(\SessionHandlerInterface $handler)
{
$this->handler = $handler;
@@ -28,6 +25,14 @@ class SessionHandlerProxy extends AbstractProxy implements \SessionHandlerInterf
$this->saveHandlerName = $this->wrapper ? ini_get('session.save_handler') : 'user';
}
/**
* @return \SessionHandlerInterface
*/
public function getHandler()
{
return $this->handler;
}
// \SessionHandlerInterface
/**
@@ -35,13 +40,7 @@ class SessionHandlerProxy extends AbstractProxy implements \SessionHandlerInterf
*/
public function open($savePath, $sessionName)
{
$return = (bool) $this->handler->open($savePath, $sessionName);
if (true === $return) {
$this->active = true;
}
return $return;
return (bool) $this->handler->open($savePath, $sessionName);
}
/**
@@ -49,8 +48,6 @@ class SessionHandlerProxy extends AbstractProxy implements \SessionHandlerInterf
*/
public function close()
{
$this->active = false;
return (bool) $this->handler->close();
}
@@ -79,10 +76,26 @@ class SessionHandlerProxy extends AbstractProxy implements \SessionHandlerInterf
}
/**
* {@inheritdoc}
* @return bool
*/
public function gc($maxlifetime)
{
return (bool) $this->handler->gc($maxlifetime);
}
/**
* {@inheritdoc}
*/
public function validateId($sessionId)
{
return !$this->handler instanceof \SessionUpdateTimestampHandlerInterface || $this->handler->validateId($sessionId);
}
/**
* {@inheritdoc}
*/
public function updateTimestamp($sessionId, $data)
{
return $this->handler instanceof \SessionUpdateTimestampHandlerInterface ? $this->handler->updateTimestamp($sessionId, $data) : $this->write($sessionId, $data);
}
}

View File

@@ -77,7 +77,7 @@ interface SessionStorageInterface
* only delete the session data from persistent storage.
*
* Care: When regenerating the session ID no locking is involved in PHP's
* session design. See https://bugs.php.net/bug.php?id=61470 for a discussion.
* session design. See https://bugs.php.net/61470 for a discussion.
* So you must make sure the regenerated session is saved BEFORE sending the
* headers with the new ID. Symfony's HttpKernel offers a listener for this.
* See Symfony\Component\HttpKernel\EventListener\SaveSessionListener.

View File

@@ -35,7 +35,7 @@ class StreamedResponse extends Response
* @param int $status The response status code
* @param array $headers An array of response headers
*/
public function __construct($callback = null, $status = 200, $headers = array())
public function __construct(callable $callback = null, $status = 200, $headers = [])
{
parent::__construct(null, $status, $headers);
@@ -55,7 +55,7 @@ class StreamedResponse extends Response
*
* @return static
*/
public static function create($callback = null, $status = 200, $headers = array())
public static function create($callback = null, $status = 200, $headers = [])
{
return new static($callback, $status, $headers);
}
@@ -65,20 +65,21 @@ class StreamedResponse extends Response
*
* @param callable $callback A valid PHP callback
*
* @throws \LogicException
* @return $this
*/
public function setCallback($callback)
public function setCallback(callable $callback)
{
if (!\is_callable($callback)) {
throw new \LogicException('The Response callback must be a valid PHP callable.');
}
$this->callback = $callback;
return $this;
}
/**
* {@inheritdoc}
*
* This method only sends the headers once.
*
* @return $this
*/
public function sendHeaders()
{
@@ -95,6 +96,8 @@ class StreamedResponse extends Response
* {@inheritdoc}
*
* This method only sends the content once.
*
* @return $this
*/
public function sendContent()
{
@@ -117,6 +120,8 @@ class StreamedResponse extends Response
* {@inheritdoc}
*
* @throws \LogicException when the content is not null
*
* @return $this
*/
public function setContent($content)
{
@@ -125,12 +130,12 @@ class StreamedResponse extends Response
}
$this->streamed = true;
return $this;
}
/**
* {@inheritdoc}
*
* @return false
*/
public function getContent()
{

View File

@@ -16,13 +16,12 @@
}
],
"require": {
"php": ">=5.3.9",
"symfony/polyfill-php54": "~1.0",
"symfony/polyfill-php55": "~1.0",
"symfony/polyfill-mbstring": "~1.1"
"php": "^5.5.9|>=7.0.8",
"symfony/polyfill-mbstring": "~1.1",
"symfony/polyfill-php70": "~1.6"
},
"require-dev": {
"symfony/expression-language": "~2.4|~3.0.0"
"symfony/expression-language": "~2.8|~3.0|~4.0"
},
"autoload": {
"psr-4": { "Symfony\\Component\\HttpFoundation\\": "" },
@@ -33,7 +32,7 @@
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-master": "2.8-dev"
"dev-master": "3.4-dev"
}
}
}