upgraded dependencies

This commit is contained in:
RafficMohammed
2023-01-08 01:59:16 +05:30
parent 51056e3aad
commit f9ae387337
6895 changed files with 133617 additions and 178680 deletions

View File

@@ -47,15 +47,13 @@ class AcceptHeader
/**
* Builds an AcceptHeader instance from a string.
*
* @param string $headerValue
*
* @return self
*/
public static function fromString($headerValue)
public static function fromString(?string $headerValue)
{
$index = 0;
$parts = HeaderUtils::split((string) $headerValue, ',;=');
$parts = HeaderUtils::split($headerValue ?? '', ',;=');
return new self(array_map(function ($subParts) use (&$index) {
$part = array_shift($subParts);
@@ -81,11 +79,9 @@ class AcceptHeader
/**
* Tests if header has given value.
*
* @param string $value
*
* @return bool
*/
public function has($value)
public function has(string $value)
{
return isset($this->items[$value]);
}
@@ -93,11 +89,9 @@ class AcceptHeader
/**
* Returns given value's item, if exists.
*
* @param string $value
*
* @return AcceptHeaderItem|null
*/
public function get($value)
public function get(string $value)
{
return $this->items[$value] ?? $this->items[explode('/', $value)[0].'/*'] ?? $this->items['*/*'] ?? $this->items['*'] ?? null;
}
@@ -130,11 +124,9 @@ class AcceptHeader
/**
* Filters items on their value using given regex.
*
* @param string $pattern
*
* @return self
*/
public function filter($pattern)
public function filter(string $pattern)
{
return new self(array_filter($this->items, function (AcceptHeaderItem $item) use ($pattern) {
return preg_match($pattern, $item->getValue());

View File

@@ -34,13 +34,11 @@ class AcceptHeaderItem
/**
* Builds an AcceptHeaderInstance instance from a string.
*
* @param string $itemValue
*
* @return self
*/
public static function fromString($itemValue)
public static function fromString(?string $itemValue)
{
$parts = HeaderUtils::split($itemValue, ';=');
$parts = HeaderUtils::split($itemValue ?? '', ';=');
$part = array_shift($parts);
$attributes = HeaderUtils::combine($parts);
@@ -66,11 +64,9 @@ class AcceptHeaderItem
/**
* Set the item value.
*
* @param string $value
*
* @return $this
*/
public function setValue($value)
public function setValue(string $value)
{
$this->value = $value;
@@ -90,11 +86,9 @@ class AcceptHeaderItem
/**
* Set the item quality.
*
* @param float $quality
*
* @return $this
*/
public function setQuality($quality)
public function setQuality(float $quality)
{
$this->quality = $quality;
@@ -114,11 +108,9 @@ class AcceptHeaderItem
/**
* Set the item index.
*
* @param int $index
*
* @return $this
*/
public function setIndex($index)
public function setIndex(int $index)
{
$this->index = $index;
@@ -138,11 +130,9 @@ class AcceptHeaderItem
/**
* Tests if an attribute exists.
*
* @param string $name
*
* @return bool
*/
public function hasAttribute($name)
public function hasAttribute(string $name)
{
return isset($this->attributes[$name]);
}
@@ -150,12 +140,11 @@ class AcceptHeaderItem
/**
* Returns an attribute by its name.
*
* @param string $name
* @param mixed $default
* @param mixed $default
*
* @return mixed
*/
public function getAttribute($name, $default = null)
public function getAttribute(string $name, $default = null)
{
return $this->attributes[$name] ?? $default;
}
@@ -173,17 +162,14 @@ class AcceptHeaderItem
/**
* Set an attribute.
*
* @param string $name
* @param string $value
*
* @return $this
*/
public function setAttribute($name, $value)
public function setAttribute(string $name, string $value)
{
if ('q' === $name) {
$this->quality = (float) $value;
} else {
$this->attributes[$name] = (string) $value;
$this->attributes[$name] = $value;
}
return $this;

View File

@@ -1,47 +0,0 @@
<?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;
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', ApacheRequest::class, Request::class), \E_USER_DEPRECATED);
/**
* Request represents an HTTP request from an Apache server.
*
* @deprecated since Symfony 4.4. Use the Request class instead.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ApacheRequest extends Request
{
/**
* {@inheritdoc}
*/
protected function prepareRequestUri()
{
return $this->server->get('REQUEST_URI');
}
/**
* {@inheritdoc}
*/
protected function prepareBaseUrl()
{
$baseUrl = $this->server->get('SCRIPT_NAME');
if (!str_contains($this->server->get('REQUEST_URI'), $baseUrl)) {
// assume mod_rewrite
return rtrim(\dirname($baseUrl), '/\\');
}
return $baseUrl;
}
}

View File

@@ -66,25 +66,26 @@ class BinaryFileResponse extends Response
* @param bool $autoLastModified Whether the Last-Modified header should be automatically set
*
* @return static
*
* @deprecated since Symfony 5.2, use __construct() instead.
*/
public static function create($file = null, $status = 200, $headers = [], $public = true, $contentDisposition = null, $autoEtag = false, $autoLastModified = true)
public static function create($file = null, int $status = 200, array $headers = [], bool $public = true, string $contentDisposition = null, bool $autoEtag = false, bool $autoLastModified = true)
{
trigger_deprecation('symfony/http-foundation', '5.2', 'The "%s()" method is deprecated, use "new %s()" instead.', __METHOD__, static::class);
return new static($file, $status, $headers, $public, $contentDisposition, $autoEtag, $autoLastModified);
}
/**
* Sets the file to stream.
*
* @param \SplFileInfo|string $file The file to stream
* @param string $contentDisposition
* @param bool $autoEtag
* @param bool $autoLastModified
* @param \SplFileInfo|string $file The file to stream
*
* @return $this
*
* @throws FileException
*/
public function setFile($file, $contentDisposition = null, $autoEtag = false, $autoLastModified = true)
public function setFile($file, string $contentDisposition = null, bool $autoEtag = false, bool $autoLastModified = true)
{
if (!$file instanceof File) {
if ($file instanceof \SplFileInfo) {
@@ -118,7 +119,7 @@ class BinaryFileResponse extends Response
/**
* Gets the file.
*
* @return File The file to stream
* @return File
*/
public function getFile()
{
@@ -143,6 +144,8 @@ class BinaryFileResponse extends Response
/**
* Automatically sets the Last-Modified header according the file modification date.
*
* @return $this
*/
public function setAutoLastModified()
{
@@ -153,6 +156,8 @@ class BinaryFileResponse extends Response
/**
* Automatically sets the ETag header according to the checksum of the file.
*
* @return $this
*/
public function setAutoEtag()
{
@@ -170,7 +175,7 @@ class BinaryFileResponse extends Response
*
* @return $this
*/
public function setContentDisposition($disposition, $filename = '', $filenameFallback = '')
public function setContentDisposition(string $disposition, string $filename = '', string $filenameFallback = '')
{
if ('' === $filename) {
$filename = $this->file->getFilename();
@@ -312,8 +317,6 @@ class BinaryFileResponse extends Response
}
/**
* Sends the file.
*
* {@inheritdoc}
*/
public function sendContent()
@@ -351,7 +354,7 @@ class BinaryFileResponse extends Response
fclose($out);
fclose($file);
} finally {
if ($this->deleteFileAfterSend && file_exists($this->file->getPathname())) {
if ($this->deleteFileAfterSend && is_file($this->file->getPathname())) {
unlink($this->file->getPathname());
}
}
@@ -364,7 +367,7 @@ class BinaryFileResponse extends Response
*
* @throws \LogicException when the content is not null
*/
public function setContent($content)
public function setContent(?string $content)
{
if (null !== $content) {
throw new \LogicException('The content cannot be set on a BinaryFileResponse instance.');
@@ -393,11 +396,9 @@ class BinaryFileResponse extends Response
* 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
*
* @return $this
*/
public function deleteFileAfterSend($shouldDelete = true)
public function deleteFileAfterSend(bool $shouldDelete = true)
{
$this->deleteFileAfterSend = $shouldDelete;

View File

@@ -1,6 +1,65 @@
CHANGELOG
=========
5.4
---
* Deprecate passing `null` as `$requestIp` to `IpUtils::__checkIp()`, `IpUtils::__checkIp4()` or `IpUtils::__checkIp6()`, pass an empty string instead.
* Add the `litespeed_finish_request` method to work with Litespeed
* Deprecate `upload_progress.*` and `url_rewriter.tags` session options
* Allow setting session options via DSN
5.3
---
* Add the `SessionFactory`, `NativeSessionStorageFactory`, `PhpBridgeSessionStorageFactory` and `MockFileSessionStorageFactory` classes
* Calling `Request::getSession()` when there is no available session throws a `SessionNotFoundException`
* Add the `RequestStack::getSession` method
* Deprecate the `NamespacedAttributeBag` class
* Add `ResponseFormatSame` PHPUnit constraint
* Deprecate the `RequestStack::getMasterRequest()` method and add `getMainRequest()` as replacement
5.2.0
-----
* added support for `X-Forwarded-Prefix` header
* added `HeaderUtils::parseQuery()`: it does the same as `parse_str()` but preserves dots in variable names
* added `File::getContent()`
* added ability to use comma separated ip addresses for `RequestMatcher::matchIps()`
* added `Request::toArray()` to parse a JSON request body to an array
* added `RateLimiter\RequestRateLimiterInterface` and `RateLimiter\AbstractRequestRateLimiter`
* deprecated not passing a `Closure` together with `FILTER_CALLBACK` to `ParameterBag::filter()`; wrap your filter in a closure instead.
* Deprecated the `Request::HEADER_X_FORWARDED_ALL` constant, use either `HEADER_X_FORWARDED_FOR | HEADER_X_FORWARDED_HOST | HEADER_X_FORWARDED_PORT | HEADER_X_FORWARDED_PROTO` or `HEADER_X_FORWARDED_AWS_ELB` or `HEADER_X_FORWARDED_TRAEFIK` constants instead.
* Deprecated `BinaryFileResponse::create()`, use `__construct()` instead
5.1.0
-----
* added `Cookie::withValue`, `Cookie::withDomain`, `Cookie::withExpires`,
`Cookie::withPath`, `Cookie::withSecure`, `Cookie::withHttpOnly`,
`Cookie::withRaw`, `Cookie::withSameSite`
* Deprecate `Response::create()`, `JsonResponse::create()`,
`RedirectResponse::create()`, and `StreamedResponse::create()` methods (use
`__construct()` instead)
* added `Request::preferSafeContent()` and `Response::setContentSafe()` to handle "safe" HTTP preference
according to [RFC 8674](https://tools.ietf.org/html/rfc8674)
* made the Mime component an optional dependency
* added `MarshallingSessionHandler`, `IdentityMarshaller`
* made `Session` accept a callback to report when the session is being used
* Add support for all core cache control directives
* Added `Symfony\Component\HttpFoundation\InputBag`
* Deprecated retrieving non-string values using `InputBag::get()`, use `InputBag::all()` if you need access to the collection of values
5.0.0
-----
* made `Cookie` auto-secure and lax by default
* removed classes in the `MimeType` namespace, use the Symfony Mime component instead
* removed method `UploadedFile::getClientSize()` and the related constructor argument
* made `Request::getSession()` throw if the session has not been set before
* removed `Response::HTTP_RESERVED_FOR_WEBDAV_ADVANCED_COLLECTIONS_EXPIRED_PROPOSAL`
* passing a null url when instantiating a `RedirectResponse` is not allowed
4.4.0
-----

View File

@@ -34,19 +34,16 @@ class Cookie
private $sameSite;
private $secureDefault = false;
private static $reservedCharsList = "=,; \t\r\n\v\f";
private const RESERVED_CHARS_LIST = "=,; \t\r\n\v\f";
private const RESERVED_CHARS_FROM = ['=', ',', ';', ' ', "\t", "\r", "\n", "\v", "\f"];
private const RESERVED_CHARS_TO = ['%3D', '%2C', '%3B', '%20', '%09', '%0D', '%0A', '%0B', '%0C'];
/**
* Creates cookie from raw header string.
*
* @param string $cookie
* @param bool $decode
*
* @return static
*/
public static function fromString($cookie, $decode = false)
public static function fromString(string $cookie, bool $decode = false)
{
$data = [
'expires' => 0,
@@ -65,8 +62,9 @@ class Cookie
$value = isset($part[1]) ? ($decode ? urldecode($part[1]) : $part[1]) : null;
$data = HeaderUtils::combine($parts) + $data;
$data['expires'] = self::expiresTimestamp($data['expires']);
if (isset($data['max-age'])) {
if (isset($data['max-age']) && ($data['max-age'] > 0 || $data['expires'] > time())) {
$data['expires'] = time() + (int) $data['max-age'];
}
@@ -91,14 +89,10 @@ class Cookie
*
* @throws \InvalidArgumentException
*/
public function __construct(string $name, string $value = null, $expire = 0, ?string $path = '/', string $domain = null, ?bool $secure = false, bool $httpOnly = true, bool $raw = false, string $sameSite = null)
public function __construct(string $name, string $value = null, $expire = 0, ?string $path = '/', string $domain = null, bool $secure = null, bool $httpOnly = true, bool $raw = false, ?string $sameSite = 'lax')
{
if (9 > \func_num_args()) {
@trigger_error(sprintf('The default value of the "$secure" and "$samesite" arguments of "%s"\'s constructor will respectively change from "false" to "null" and from "null" to "lax" in Symfony 5.0, you should define their values explicitly or use "Cookie::create()" instead.', __METHOD__), \E_USER_DEPRECATED);
}
// from PHP source code
if ($raw && false !== strpbrk($name, self::$reservedCharsList)) {
if ($raw && false !== strpbrk($name, self::RESERVED_CHARS_LIST)) {
throw new \InvalidArgumentException(sprintf('The cookie name "%s" contains invalid characters.', $name));
}
@@ -106,6 +100,65 @@ class Cookie
throw new \InvalidArgumentException('The cookie name cannot be empty.');
}
$this->name = $name;
$this->value = $value;
$this->domain = $domain;
$this->expire = self::expiresTimestamp($expire);
$this->path = empty($path) ? '/' : $path;
$this->secure = $secure;
$this->httpOnly = $httpOnly;
$this->raw = $raw;
$this->sameSite = $this->withSameSite($sameSite)->sameSite;
}
/**
* Creates a cookie copy with a new value.
*
* @return static
*/
public function withValue(?string $value): self
{
$cookie = clone $this;
$cookie->value = $value;
return $cookie;
}
/**
* Creates a cookie copy with a new domain that the cookie is available to.
*
* @return static
*/
public function withDomain(?string $domain): self
{
$cookie = clone $this;
$cookie->domain = $domain;
return $cookie;
}
/**
* Creates a cookie copy with a new time the cookie expires.
*
* @param int|string|\DateTimeInterface $expire
*
* @return static
*/
public function withExpires($expire = 0): self
{
$cookie = clone $this;
$cookie->expire = self::expiresTimestamp($expire);
return $cookie;
}
/**
* Converts expires formats to a unix timestamp.
*
* @param int|string|\DateTimeInterface $expire
*/
private static function expiresTimestamp($expire = 0): int
{
// convert expiration time to a Unix timestamp
if ($expire instanceof \DateTimeInterface) {
$expire = $expire->format('U');
@@ -117,15 +170,72 @@ class Cookie
}
}
$this->name = $name;
$this->value = $value;
$this->domain = $domain;
$this->expire = 0 < $expire ? (int) $expire : 0;
$this->path = empty($path) ? '/' : $path;
$this->secure = $secure;
$this->httpOnly = $httpOnly;
$this->raw = $raw;
return 0 < $expire ? (int) $expire : 0;
}
/**
* Creates a cookie copy with a new path on the server in which the cookie will be available on.
*
* @return static
*/
public function withPath(string $path): self
{
$cookie = clone $this;
$cookie->path = '' === $path ? '/' : $path;
return $cookie;
}
/**
* Creates a cookie copy that only be transmitted over a secure HTTPS connection from the client.
*
* @return static
*/
public function withSecure(bool $secure = true): self
{
$cookie = clone $this;
$cookie->secure = $secure;
return $cookie;
}
/**
* Creates a cookie copy that be accessible only through the HTTP protocol.
*
* @return static
*/
public function withHttpOnly(bool $httpOnly = true): self
{
$cookie = clone $this;
$cookie->httpOnly = $httpOnly;
return $cookie;
}
/**
* Creates a cookie copy that uses no url encoding.
*
* @return static
*/
public function withRaw(bool $raw = true): self
{
if ($raw && false !== strpbrk($this->name, self::RESERVED_CHARS_LIST)) {
throw new \InvalidArgumentException(sprintf('The cookie name "%s" contains invalid characters.', $this->name));
}
$cookie = clone $this;
$cookie->raw = $raw;
return $cookie;
}
/**
* Creates a cookie copy with SameSite attribute.
*
* @return static
*/
public function withSameSite(?string $sameSite): self
{
if ('' === $sameSite) {
$sameSite = null;
} elseif (null !== $sameSite) {
@@ -136,13 +246,16 @@ class Cookie
throw new \InvalidArgumentException('The "sameSite" parameter value is not valid.');
}
$this->sameSite = $sameSite;
$cookie = clone $this;
$cookie->sameSite = $sameSite;
return $cookie;
}
/**
* Returns the cookie as a string.
*
* @return string The cookie
* @return string
*/
public function __toString()
{

View File

@@ -0,0 +1,19 @@
<?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 sends a malformed request.
*/
class BadRequestException 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;
/**
* Thrown by Request::toArray() when the content cannot be JSON-decoded.
*
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
*/
final class JsonException extends \UnexpectedValueException implements RequestExceptionInterface
{
}

View File

@@ -0,0 +1,27 @@
<?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 session does not exist. This happens in the following cases:
* - the session is not enabled
* - attempt to read a session outside a request context (ie. cli script).
*
* @author Jérémy Derussé <jeremy@derusse.com>
*/
class SessionNotFoundException extends \LogicException implements RequestExceptionInterface
{
public function __construct(string $message = 'There is currently no session available.', int $code = 0, \Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
}
}

View File

@@ -15,6 +15,6 @@ class UnexpectedTypeException extends FileException
{
public function __construct($value, string $expectedType)
{
parent::__construct(sprintf('Expected argument of type %s, %s given', $expectedType, \is_object($value) ? \get_class($value) : \gettype($value)));
parent::__construct(sprintf('Expected argument of type %s, %s given', $expectedType, get_debug_type($value)));
}
}

View File

@@ -47,13 +47,17 @@ class File extends \SplFileInfo
* This method uses the mime type as guessed by getMimeType()
* to guess the file extension.
*
* @return string|null The guessed extension or null if it cannot be guessed
* @return string|null
*
* @see MimeTypes
* @see getMimeType()
*/
public function guessExtension()
{
if (!class_exists(MimeTypes::class)) {
throw new \LogicException('You cannot guess the extension as the Mime component is not installed. Try running "composer require symfony/mime".');
}
return MimeTypes::getDefault()->getExtensions($this->getMimeType())[0] ?? null;
}
@@ -64,32 +68,36 @@ class File extends \SplFileInfo
* which uses finfo_file() then the "file" system binary,
* depending on which of those are available.
*
* @return string|null The guessed mime type (e.g. "application/pdf")
* @return string|null
*
* @see MimeTypes
*/
public function getMimeType()
{
if (!class_exists(MimeTypes::class)) {
throw new \LogicException('You cannot guess the mime type as the Mime component is not installed. Try running "composer require symfony/mime".');
}
return MimeTypes::getDefault()->guessMimeType($this->getPathname());
}
/**
* Moves the file to a new location.
*
* @param string $directory The destination folder
* @param string $name The new file name
*
* @return self A File object representing the new file
* @return self
*
* @throws FileException if the target file could not be created
*/
public function move($directory, $name = null)
public function move(string $directory, string $name = null)
{
$target = $this->getTargetFile($directory, $name);
set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; });
$renamed = rename($this->getPathname(), $target);
restore_error_handler();
try {
$renamed = rename($this->getPathname(), $target);
} finally {
restore_error_handler();
}
if (!$renamed) {
throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s).', $this->getPathname(), $target, strip_tags($error)));
}
@@ -99,10 +107,21 @@ class File extends \SplFileInfo
return $target;
}
public function getContent(): string
{
$content = file_get_contents($this->getPathname());
if (false === $content) {
throw new FileException(sprintf('Could not get the content of the file "%s".', $this->getPathname()));
}
return $content;
}
/**
* @return self
*/
protected function getTargetFile($directory, $name = null)
protected function getTargetFile(string $directory, string $name = null)
{
if (!is_dir($directory)) {
if (false === @mkdir($directory, 0777, true) && !is_dir($directory)) {
@@ -120,11 +139,9 @@ class File extends \SplFileInfo
/**
* Returns locale independent base name of the given path.
*
* @param string $name The new file name
*
* @return string
*/
protected function getName($name)
protected function getName(string $name)
{
$originalName = str_replace('\\', '/', $name);
$pos = strrpos($originalName, '/');

View File

@@ -1,102 +0,0 @@
<?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\MimeType;
use Symfony\Component\Mime\MimeTypes;
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" instead.', ExtensionGuesser::class, MimeTypes::class), \E_USER_DEPRECATED);
/**
* A singleton mime type to file extension guesser.
*
* A default guesser is provided.
* You can register custom guessers by calling the register()
* method on the singleton instance:
*
* $guesser = ExtensionGuesser::getInstance();
* $guesser->register(new MyCustomExtensionGuesser());
*
* The last registered guesser is preferred over previously registered ones.
*
* @deprecated since Symfony 4.3, use {@link MimeTypes} instead
*/
class ExtensionGuesser implements ExtensionGuesserInterface
{
/**
* The singleton instance.
*
* @var ExtensionGuesser
*/
private static $instance = null;
/**
* All registered ExtensionGuesserInterface instances.
*
* @var array
*/
protected $guessers = [];
/**
* Returns the singleton instance.
*
* @return self
*/
public static function getInstance()
{
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Registers all natively provided extension guessers.
*/
private function __construct()
{
$this->register(new MimeTypeExtensionGuesser());
}
/**
* Registers a new extension guesser.
*
* When guessing, this guesser is preferred over previously registered ones.
*/
public function register(ExtensionGuesserInterface $guesser)
{
array_unshift($this->guessers, $guesser);
}
/**
* Tries to guess the extension.
*
* The mime type is passed to each registered mime type guesser in reverse order
* of their registration (last registered is queried first). Once a guesser
* returns a value that is not NULL, this method terminates and returns the
* value.
*
* @param string $mimeType The mime type
*
* @return string The guessed extension or NULL, if none could be guessed
*/
public function guess($mimeType)
{
foreach ($this->guessers as $guesser) {
if (null !== $extension = $guesser->guess($mimeType)) {
return $extension;
}
}
return null;
}
}

View File

@@ -1,31 +0,0 @@
<?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\MimeType;
use Symfony\Component\Mime\MimeTypesInterface;
/**
* Guesses the file extension corresponding to a given mime type.
*
* @deprecated since Symfony 4.3, use {@link MimeTypesInterface} instead
*/
interface ExtensionGuesserInterface
{
/**
* Makes a best guess for a file extension, given a mime type.
*
* @param string $mimeType The mime type
*
* @return string The guessed extension or NULL, if none could be guessed
*/
public function guess($mimeType);
}

View File

@@ -1,104 +0,0 @@
<?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\MimeType;
use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException;
use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException;
use Symfony\Component\Mime\FileBinaryMimeTypeGuesser as NewFileBinaryMimeTypeGuesser;
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" instead.', FileBinaryMimeTypeGuesser::class, NewFileBinaryMimeTypeGuesser::class), \E_USER_DEPRECATED);
/**
* Guesses the mime type with the binary "file" (only available on *nix).
*
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @deprecated since Symfony 4.3, use {@link NewFileBinaryMimeTypeGuesser} instead
*/
class FileBinaryMimeTypeGuesser implements MimeTypeGuesserInterface
{
private $cmd;
/**
* The $cmd pattern must contain a "%s" string that will be replaced
* with the file name to guess.
*
* The command output must start with the mime type of the file.
*
* @param string $cmd The command to run to get the mime type of a file
*/
public function __construct(string $cmd = 'file -b --mime -- %s 2>/dev/null')
{
$this->cmd = $cmd;
}
/**
* Returns whether this guesser is supported on the current OS.
*
* @return bool
*/
public static function isSupported()
{
static $supported = null;
if (null !== $supported) {
return $supported;
}
if ('\\' === \DIRECTORY_SEPARATOR || !\function_exists('passthru') || !\function_exists('escapeshellarg')) {
return $supported = false;
}
ob_start();
passthru('command -v file', $exitStatus);
$binPath = trim(ob_get_clean());
return $supported = 0 === $exitStatus && '' !== $binPath;
}
/**
* {@inheritdoc}
*/
public function guess($path)
{
if (!is_file($path)) {
throw new FileNotFoundException($path);
}
if (!is_readable($path)) {
throw new AccessDeniedException($path);
}
if (!self::isSupported()) {
return null;
}
ob_start();
// need to use --mime instead of -i. see #6641
passthru(sprintf($this->cmd, escapeshellarg((str_starts_with($path, '-') ? './' : '').$path)), $return);
if ($return > 0) {
ob_end_clean();
return null;
}
$type = trim(ob_get_clean());
if (!preg_match('#^([a-z0-9\-]+/[a-z0-9\-\+\.]+)#i', $type, $match)) {
// it's not a type, but an error message
return null;
}
return $match[1];
}
}

View File

@@ -1,80 +0,0 @@
<?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\MimeType;
use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException;
use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException;
use Symfony\Component\Mime\FileinfoMimeTypeGuesser as NewFileinfoMimeTypeGuesser;
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" instead.', FileinfoMimeTypeGuesser::class, NewFileinfoMimeTypeGuesser::class), \E_USER_DEPRECATED);
/**
* Guesses the mime type using the PECL extension FileInfo.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @deprecated since Symfony 4.3, use {@link NewFileinfoMimeTypeGuesser} instead
*/
class FileinfoMimeTypeGuesser implements MimeTypeGuesserInterface
{
private $magicFile;
/**
* @param string $magicFile A magic file to use with the finfo instance
*
* @see https://php.net/finfo-open
*/
public function __construct(string $magicFile = null)
{
$this->magicFile = $magicFile;
}
/**
* Returns whether this guesser is supported on the current OS/PHP setup.
*
* @return bool
*/
public static function isSupported()
{
return \function_exists('finfo_open');
}
/**
* {@inheritdoc}
*/
public function guess($path)
{
if (!is_file($path)) {
throw new FileNotFoundException($path);
}
if (!is_readable($path)) {
throw new AccessDeniedException($path);
}
if (!self::isSupported()) {
return null;
}
if (!$finfo = new \finfo(\FILEINFO_MIME_TYPE, $this->magicFile)) {
return null;
}
$mimeType = $finfo->file($path);
if ($mimeType && 0 === (\strlen($mimeType) % 2)) {
$mimeStart = substr($mimeType, 0, \strlen($mimeType) >> 1);
$mimeType = $mimeStart.$mimeStart === $mimeType ? $mimeStart : $mimeType;
}
return $mimeType;
}
}

View File

@@ -1,826 +0,0 @@
<?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\MimeType;
use Symfony\Component\Mime\MimeTypes;
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" instead.', MimeTypeExtensionGuesser::class, MimeTypes::class), \E_USER_DEPRECATED);
/**
* Provides a best-guess mapping of mime type to file extension.
*
* @deprecated since Symfony 4.3, use {@link MimeTypes} instead
*/
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 2019-01-14.
*
* @see https://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types
*/
protected $defaultExtensions = [
'application/andrew-inset' => 'ez',
'application/applixware' => 'aw',
'application/atom+xml' => 'atom',
'application/atomcat+xml' => 'atomcat',
'application/atomsvc+xml' => 'atomsvc',
'application/ccxml+xml' => 'ccxml',
'application/cdmi-capability' => 'cdmia',
'application/cdmi-container' => 'cdmic',
'application/cdmi-domain' => 'cdmid',
'application/cdmi-object' => 'cdmio',
'application/cdmi-queue' => 'cdmiq',
'application/cu-seeme' => 'cu',
'application/davmount+xml' => 'davmount',
'application/docbook+xml' => 'dbk',
'application/dssc+der' => 'dssc',
'application/dssc+xml' => 'xdssc',
'application/ecmascript' => 'ecma',
'application/emma+xml' => 'emma',
'application/epub+zip' => 'epub',
'application/exi' => 'exi',
'application/font-tdpfr' => 'pfr',
'application/gml+xml' => 'gml',
'application/gpx+xml' => 'gpx',
'application/gxf' => 'gxf',
'application/hyperstudio' => 'stk',
'application/inkml+xml' => 'ink',
'application/ipfix' => 'ipfix',
'application/java-archive' => 'jar',
'application/java-serialized-object' => 'ser',
'application/java-vm' => 'class',
'application/javascript' => 'js',
'application/json' => 'json',
'application/jsonml+json' => 'jsonml',
'application/lost+xml' => 'lostxml',
'application/mac-binhex40' => 'hqx',
'application/mac-compactpro' => 'cpt',
'application/mads+xml' => 'mads',
'application/marc' => 'mrc',
'application/marcxml+xml' => 'mrcx',
'application/mathematica' => 'ma',
'application/mathml+xml' => 'mathml',
'application/mbox' => 'mbox',
'application/mediaservercontrol+xml' => 'mscml',
'application/metalink+xml' => 'metalink',
'application/metalink4+xml' => 'meta4',
'application/mets+xml' => 'mets',
'application/mods+xml' => 'mods',
'application/mp21' => 'm21',
'application/mp4' => 'mp4s',
'application/msword' => 'doc',
'application/mxf' => 'mxf',
'application/octet-stream' => 'bin',
'application/oda' => 'oda',
'application/oebps-package+xml' => 'opf',
'application/ogg' => 'ogx',
'application/omdoc+xml' => 'omdoc',
'application/onenote' => 'onetoc',
'application/oxps' => 'oxps',
'application/patch-ops-error+xml' => 'xer',
'application/pdf' => 'pdf',
'application/pgp-encrypted' => 'pgp',
'application/pgp-signature' => 'asc',
'application/pics-rules' => 'prf',
'application/pkcs10' => 'p10',
'application/pkcs7-mime' => 'p7m',
'application/pkcs7-signature' => 'p7s',
'application/pkcs8' => 'p8',
'application/pkix-attr-cert' => 'ac',
'application/pkix-cert' => 'cer',
'application/pkix-crl' => 'crl',
'application/pkix-pkipath' => 'pkipath',
'application/pkixcmp' => 'pki',
'application/pls+xml' => 'pls',
'application/postscript' => 'ai',
'application/prs.cww' => 'cww',
'application/pskc+xml' => 'pskcxml',
'application/rdf+xml' => 'rdf',
'application/reginfo+xml' => 'rif',
'application/relax-ng-compact-syntax' => 'rnc',
'application/resource-lists+xml' => 'rl',
'application/resource-lists-diff+xml' => 'rld',
'application/rls-services+xml' => 'rs',
'application/rpki-ghostbusters' => 'gbr',
'application/rpki-manifest' => 'mft',
'application/rpki-roa' => 'roa',
'application/rsd+xml' => 'rsd',
'application/rss+xml' => 'rss',
'application/rtf' => 'rtf',
'application/sbml+xml' => 'sbml',
'application/scvp-cv-request' => 'scq',
'application/scvp-cv-response' => 'scs',
'application/scvp-vp-request' => 'spq',
'application/scvp-vp-response' => 'spp',
'application/sdp' => 'sdp',
'application/set-payment-initiation' => 'setpay',
'application/set-registration-initiation' => 'setreg',
'application/shf+xml' => 'shf',
'application/smil+xml' => 'smi',
'application/sparql-query' => 'rq',
'application/sparql-results+xml' => 'srx',
'application/srgs' => 'gram',
'application/srgs+xml' => 'grxml',
'application/sru+xml' => 'sru',
'application/ssdl+xml' => 'ssdl',
'application/ssml+xml' => 'ssml',
'application/tei+xml' => 'tei',
'application/thraud+xml' => 'tfi',
'application/timestamped-data' => 'tsd',
'application/vnd.3gpp.pic-bw-large' => 'plb',
'application/vnd.3gpp.pic-bw-small' => 'psb',
'application/vnd.3gpp.pic-bw-var' => 'pvb',
'application/vnd.3gpp2.tcap' => 'tcap',
'application/vnd.3m.post-it-notes' => 'pwn',
'application/vnd.accpac.simply.aso' => 'aso',
'application/vnd.accpac.simply.imp' => 'imp',
'application/vnd.acucobol' => 'acu',
'application/vnd.acucorp' => 'atc',
'application/vnd.adobe.air-application-installer-package+zip' => 'air',
'application/vnd.adobe.formscentral.fcdt' => 'fcdt',
'application/vnd.adobe.fxp' => 'fxp',
'application/vnd.adobe.xdp+xml' => 'xdp',
'application/vnd.adobe.xfdf' => 'xfdf',
'application/vnd.ahead.space' => 'ahead',
'application/vnd.airzip.filesecure.azf' => 'azf',
'application/vnd.airzip.filesecure.azs' => 'azs',
'application/vnd.amazon.ebook' => 'azw',
'application/vnd.americandynamics.acc' => 'acc',
'application/vnd.amiga.ami' => 'ami',
'application/vnd.android.package-archive' => 'apk',
'application/vnd.anser-web-certificate-issue-initiation' => 'cii',
'application/vnd.anser-web-funds-transfer-initiation' => 'fti',
'application/vnd.antix.game-component' => 'atx',
'application/vnd.apple.installer+xml' => 'mpkg',
'application/vnd.apple.mpegurl' => 'm3u8',
'application/vnd.aristanetworks.swi' => 'swi',
'application/vnd.astraea-software.iota' => 'iota',
'application/vnd.audiograph' => 'aep',
'application/vnd.blueice.multipass' => 'mpm',
'application/vnd.bmi' => 'bmi',
'application/vnd.businessobjects' => 'rep',
'application/vnd.chemdraw+xml' => 'cdxml',
'application/vnd.chipnuts.karaoke-mmd' => 'mmd',
'application/vnd.cinderella' => 'cdy',
'application/vnd.claymore' => 'cla',
'application/vnd.cloanto.rp9' => 'rp9',
'application/vnd.clonk.c4group' => 'c4g',
'application/vnd.cluetrust.cartomobile-config' => 'c11amc',
'application/vnd.cluetrust.cartomobile-config-pkg' => 'c11amz',
'application/vnd.commonspace' => 'csp',
'application/vnd.contact.cmsg' => 'cdbcmsg',
'application/vnd.cosmocaller' => 'cmc',
'application/vnd.crick.clicker' => 'clkx',
'application/vnd.crick.clicker.keyboard' => 'clkk',
'application/vnd.crick.clicker.palette' => 'clkp',
'application/vnd.crick.clicker.template' => 'clkt',
'application/vnd.crick.clicker.wordbank' => 'clkw',
'application/vnd.criticaltools.wbs+xml' => 'wbs',
'application/vnd.ctc-posml' => 'pml',
'application/vnd.cups-ppd' => 'ppd',
'application/vnd.curl.car' => 'car',
'application/vnd.curl.pcurl' => 'pcurl',
'application/vnd.dart' => 'dart',
'application/vnd.data-vision.rdz' => 'rdz',
'application/vnd.dece.data' => 'uvf',
'application/vnd.dece.ttml+xml' => 'uvt',
'application/vnd.dece.unspecified' => 'uvx',
'application/vnd.dece.zip' => 'uvz',
'application/vnd.denovo.fcselayout-link' => 'fe_launch',
'application/vnd.dna' => 'dna',
'application/vnd.dolby.mlp' => 'mlp',
'application/vnd.dpgraph' => 'dpg',
'application/vnd.dreamfactory' => 'dfac',
'application/vnd.ds-keypoint' => 'kpxx',
'application/vnd.dvb.ait' => 'ait',
'application/vnd.dvb.service' => 'svc',
'application/vnd.dynageo' => 'geo',
'application/vnd.ecowin.chart' => 'mag',
'application/vnd.enliven' => 'nml',
'application/vnd.epson.esf' => 'esf',
'application/vnd.epson.msf' => 'msf',
'application/vnd.epson.quickanime' => 'qam',
'application/vnd.epson.salt' => 'slt',
'application/vnd.epson.ssf' => 'ssf',
'application/vnd.eszigno3+xml' => 'es3',
'application/vnd.ezpix-album' => 'ez2',
'application/vnd.ezpix-package' => 'ez3',
'application/vnd.fdf' => 'fdf',
'application/vnd.fdsn.mseed' => 'mseed',
'application/vnd.fdsn.seed' => 'seed',
'application/vnd.flographit' => 'gph',
'application/vnd.fluxtime.clip' => 'ftc',
'application/vnd.framemaker' => 'fm',
'application/vnd.frogans.fnc' => 'fnc',
'application/vnd.frogans.ltf' => 'ltf',
'application/vnd.fsc.weblaunch' => 'fsc',
'application/vnd.fujitsu.oasys' => 'oas',
'application/vnd.fujitsu.oasys2' => 'oa2',
'application/vnd.fujitsu.oasys3' => 'oa3',
'application/vnd.fujitsu.oasysgp' => 'fg5',
'application/vnd.fujitsu.oasysprs' => 'bh2',
'application/vnd.fujixerox.ddd' => 'ddd',
'application/vnd.fujixerox.docuworks' => 'xdw',
'application/vnd.fujixerox.docuworks.binder' => 'xbd',
'application/vnd.fuzzysheet' => 'fzs',
'application/vnd.genomatix.tuxedo' => 'txd',
'application/vnd.geogebra.file' => 'ggb',
'application/vnd.geogebra.tool' => 'ggt',
'application/vnd.geometry-explorer' => 'gex',
'application/vnd.geonext' => 'gxt',
'application/vnd.geoplan' => 'g2w',
'application/vnd.geospace' => 'g3w',
'application/vnd.gmx' => 'gmx',
'application/vnd.google-earth.kml+xml' => 'kml',
'application/vnd.google-earth.kmz' => 'kmz',
'application/vnd.grafeq' => 'gqf',
'application/vnd.groove-account' => 'gac',
'application/vnd.groove-help' => 'ghf',
'application/vnd.groove-identity-message' => 'gim',
'application/vnd.groove-injector' => 'grv',
'application/vnd.groove-tool-message' => 'gtm',
'application/vnd.groove-tool-template' => 'tpl',
'application/vnd.groove-vcard' => 'vcg',
'application/vnd.hal+xml' => 'hal',
'application/vnd.handheld-entertainment+xml' => 'zmm',
'application/vnd.hbci' => 'hbci',
'application/vnd.hhe.lesson-player' => 'les',
'application/vnd.hp-hpgl' => 'hpgl',
'application/vnd.hp-hpid' => 'hpid',
'application/vnd.hp-hps' => 'hps',
'application/vnd.hp-jlyt' => 'jlt',
'application/vnd.hp-pcl' => 'pcl',
'application/vnd.hp-pclxl' => 'pclxl',
'application/vnd.hydrostatix.sof-data' => 'sfd-hdstx',
'application/vnd.ibm.minipay' => 'mpy',
'application/vnd.ibm.modcap' => 'afp',
'application/vnd.ibm.rights-management' => 'irm',
'application/vnd.ibm.secure-container' => 'sc',
'application/vnd.iccprofile' => 'icc',
'application/vnd.igloader' => 'igl',
'application/vnd.immervision-ivp' => 'ivp',
'application/vnd.immervision-ivu' => 'ivu',
'application/vnd.insors.igm' => 'igm',
'application/vnd.intercon.formnet' => 'xpw',
'application/vnd.intergeo' => 'i2g',
'application/vnd.intu.qbo' => 'qbo',
'application/vnd.intu.qfx' => 'qfx',
'application/vnd.ipunplugged.rcprofile' => 'rcprofile',
'application/vnd.irepository.package+xml' => 'irp',
'application/vnd.is-xpr' => 'xpr',
'application/vnd.isac.fcs' => 'fcs',
'application/vnd.jam' => 'jam',
'application/vnd.jcp.javame.midlet-rms' => 'rms',
'application/vnd.jisp' => 'jisp',
'application/vnd.joost.joda-archive' => 'joda',
'application/vnd.kahootz' => 'ktz',
'application/vnd.kde.karbon' => 'karbon',
'application/vnd.kde.kchart' => 'chrt',
'application/vnd.kde.kformula' => 'kfo',
'application/vnd.kde.kivio' => 'flw',
'application/vnd.kde.kontour' => 'kon',
'application/vnd.kde.kpresenter' => 'kpr',
'application/vnd.kde.kspread' => 'ksp',
'application/vnd.kde.kword' => 'kwd',
'application/vnd.kenameaapp' => 'htke',
'application/vnd.kidspiration' => 'kia',
'application/vnd.kinar' => 'kne',
'application/vnd.koan' => 'skp',
'application/vnd.kodak-descriptor' => 'sse',
'application/vnd.las.las+xml' => 'lasxml',
'application/vnd.llamagraphics.life-balance.desktop' => 'lbd',
'application/vnd.llamagraphics.life-balance.exchange+xml' => 'lbe',
'application/vnd.lotus-1-2-3' => '123',
'application/vnd.lotus-approach' => 'apr',
'application/vnd.lotus-freelance' => 'pre',
'application/vnd.lotus-notes' => 'nsf',
'application/vnd.lotus-organizer' => 'org',
'application/vnd.lotus-screencam' => 'scm',
'application/vnd.lotus-wordpro' => 'lwp',
'application/vnd.macports.portpkg' => 'portpkg',
'application/vnd.mcd' => 'mcd',
'application/vnd.medcalcdata' => 'mc1',
'application/vnd.mediastation.cdkey' => 'cdkey',
'application/vnd.mfer' => 'mwf',
'application/vnd.mfmp' => 'mfm',
'application/vnd.micrografx.flo' => 'flo',
'application/vnd.micrografx.igx' => 'igx',
'application/vnd.mif' => 'mif',
'application/vnd.mobius.daf' => 'daf',
'application/vnd.mobius.dis' => 'dis',
'application/vnd.mobius.mbk' => 'mbk',
'application/vnd.mobius.mqy' => 'mqy',
'application/vnd.mobius.msl' => 'msl',
'application/vnd.mobius.plc' => 'plc',
'application/vnd.mobius.txf' => 'txf',
'application/vnd.mophun.application' => 'mpn',
'application/vnd.mophun.certificate' => 'mpc',
'application/vnd.mozilla.xul+xml' => 'xul',
'application/vnd.ms-artgalry' => 'cil',
'application/vnd.ms-cab-compressed' => 'cab',
'application/vnd.ms-excel' => 'xls',
'application/vnd.ms-excel.addin.macroenabled.12' => 'xlam',
'application/vnd.ms-excel.sheet.binary.macroenabled.12' => 'xlsb',
'application/vnd.ms-excel.sheet.macroenabled.12' => 'xlsm',
'application/vnd.ms-excel.template.macroenabled.12' => 'xltm',
'application/vnd.ms-fontobject' => 'eot',
'application/vnd.ms-htmlhelp' => 'chm',
'application/vnd.ms-ims' => 'ims',
'application/vnd.ms-lrm' => 'lrm',
'application/vnd.ms-officetheme' => 'thmx',
'application/vnd.ms-pki.seccat' => 'cat',
'application/vnd.ms-pki.stl' => 'stl',
'application/vnd.ms-powerpoint' => 'ppt',
'application/vnd.ms-powerpoint.addin.macroenabled.12' => 'ppam',
'application/vnd.ms-powerpoint.presentation.macroenabled.12' => 'pptm',
'application/vnd.ms-powerpoint.slide.macroenabled.12' => 'sldm',
'application/vnd.ms-powerpoint.slideshow.macroenabled.12' => 'ppsm',
'application/vnd.ms-powerpoint.template.macroenabled.12' => 'potm',
'application/vnd.ms-project' => 'mpp',
'application/vnd.ms-word.document.macroenabled.12' => 'docm',
'application/vnd.ms-word.template.macroenabled.12' => 'dotm',
'application/vnd.ms-works' => 'wps',
'application/vnd.ms-wpl' => 'wpl',
'application/vnd.ms-xpsdocument' => 'xps',
'application/vnd.mseq' => 'mseq',
'application/vnd.musician' => 'mus',
'application/vnd.muvee.style' => 'msty',
'application/vnd.mynfc' => 'taglet',
'application/vnd.neurolanguage.nlu' => 'nlu',
'application/vnd.nitf' => 'ntf',
'application/vnd.noblenet-directory' => 'nnd',
'application/vnd.noblenet-sealer' => 'nns',
'application/vnd.noblenet-web' => 'nnw',
'application/vnd.nokia.n-gage.data' => 'ngdat',
'application/vnd.nokia.n-gage.symbian.install' => 'n-gage',
'application/vnd.nokia.radio-preset' => 'rpst',
'application/vnd.nokia.radio-presets' => 'rpss',
'application/vnd.novadigm.edm' => 'edm',
'application/vnd.novadigm.edx' => 'edx',
'application/vnd.novadigm.ext' => 'ext',
'application/vnd.oasis.opendocument.chart' => 'odc',
'application/vnd.oasis.opendocument.chart-template' => 'otc',
'application/vnd.oasis.opendocument.database' => 'odb',
'application/vnd.oasis.opendocument.formula' => 'odf',
'application/vnd.oasis.opendocument.formula-template' => 'odft',
'application/vnd.oasis.opendocument.graphics' => 'odg',
'application/vnd.oasis.opendocument.graphics-template' => 'otg',
'application/vnd.oasis.opendocument.image' => 'odi',
'application/vnd.oasis.opendocument.image-template' => 'oti',
'application/vnd.oasis.opendocument.presentation' => 'odp',
'application/vnd.oasis.opendocument.presentation-template' => 'otp',
'application/vnd.oasis.opendocument.spreadsheet' => 'ods',
'application/vnd.oasis.opendocument.spreadsheet-template' => 'ots',
'application/vnd.oasis.opendocument.text' => 'odt',
'application/vnd.oasis.opendocument.text-master' => 'odm',
'application/vnd.oasis.opendocument.text-template' => 'ott',
'application/vnd.oasis.opendocument.text-web' => 'oth',
'application/vnd.olpc-sugar' => 'xo',
'application/vnd.oma.dd2+xml' => 'dd2',
'application/vnd.openofficeorg.extension' => 'oxt',
'application/vnd.openxmlformats-officedocument.presentationml.presentation' => 'pptx',
'application/vnd.openxmlformats-officedocument.presentationml.slide' => 'sldx',
'application/vnd.openxmlformats-officedocument.presentationml.slideshow' => 'ppsx',
'application/vnd.openxmlformats-officedocument.presentationml.template' => 'potx',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'xlsx',
'application/vnd.openxmlformats-officedocument.spreadsheetml.template' => 'xltx',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'docx',
'application/vnd.openxmlformats-officedocument.wordprocessingml.template' => 'dotx',
'application/vnd.osgeo.mapguide.package' => 'mgp',
'application/vnd.osgi.dp' => 'dp',
'application/vnd.osgi.subsystem' => 'esa',
'application/vnd.palm' => 'pdb',
'application/vnd.pawaafile' => 'paw',
'application/vnd.pg.format' => 'str',
'application/vnd.pg.osasli' => 'ei6',
'application/vnd.picsel' => 'efif',
'application/vnd.pmi.widget' => 'wg',
'application/vnd.pocketlearn' => 'plf',
'application/vnd.powerbuilder6' => 'pbd',
'application/vnd.previewsystems.box' => 'box',
'application/vnd.proteus.magazine' => 'mgz',
'application/vnd.publishare-delta-tree' => 'qps',
'application/vnd.pvi.ptid1' => 'ptid',
'application/vnd.quark.quarkxpress' => 'qxd',
'application/vnd.realvnc.bed' => 'bed',
'application/vnd.recordare.musicxml' => 'mxl',
'application/vnd.recordare.musicxml+xml' => 'musicxml',
'application/vnd.rig.cryptonote' => 'cryptonote',
'application/vnd.rim.cod' => 'cod',
'application/vnd.rn-realmedia' => 'rm',
'application/vnd.rn-realmedia-vbr' => 'rmvb',
'application/vnd.route66.link66+xml' => 'link66',
'application/vnd.sailingtracker.track' => 'st',
'application/vnd.seemail' => 'see',
'application/vnd.sema' => 'sema',
'application/vnd.semd' => 'semd',
'application/vnd.semf' => 'semf',
'application/vnd.shana.informed.formdata' => 'ifm',
'application/vnd.shana.informed.formtemplate' => 'itp',
'application/vnd.shana.informed.interchange' => 'iif',
'application/vnd.shana.informed.package' => 'ipk',
'application/vnd.simtech-mindmapper' => 'twd',
'application/vnd.smaf' => 'mmf',
'application/vnd.smart.teacher' => 'teacher',
'application/vnd.solent.sdkm+xml' => 'sdkm',
'application/vnd.spotfire.dxp' => 'dxp',
'application/vnd.spotfire.sfs' => 'sfs',
'application/vnd.stardivision.calc' => 'sdc',
'application/vnd.stardivision.draw' => 'sda',
'application/vnd.stardivision.impress' => 'sdd',
'application/vnd.stardivision.math' => 'smf',
'application/vnd.stardivision.writer' => 'sdw',
'application/vnd.stardivision.writer-global' => 'sgl',
'application/vnd.stepmania.package' => 'smzip',
'application/vnd.stepmania.stepchart' => 'sm',
'application/vnd.sun.xml.calc' => 'sxc',
'application/vnd.sun.xml.calc.template' => 'stc',
'application/vnd.sun.xml.draw' => 'sxd',
'application/vnd.sun.xml.draw.template' => 'std',
'application/vnd.sun.xml.impress' => 'sxi',
'application/vnd.sun.xml.impress.template' => 'sti',
'application/vnd.sun.xml.math' => 'sxm',
'application/vnd.sun.xml.writer' => 'sxw',
'application/vnd.sun.xml.writer.global' => 'sxg',
'application/vnd.sun.xml.writer.template' => 'stw',
'application/vnd.sus-calendar' => 'sus',
'application/vnd.svd' => 'svd',
'application/vnd.symbian.install' => 'sis',
'application/vnd.syncml+xml' => 'xsm',
'application/vnd.syncml.dm+wbxml' => 'bdm',
'application/vnd.syncml.dm+xml' => 'xdm',
'application/vnd.tao.intent-module-archive' => 'tao',
'application/vnd.tcpdump.pcap' => 'pcap',
'application/vnd.tmobile-livetv' => 'tmo',
'application/vnd.trid.tpt' => 'tpt',
'application/vnd.triscape.mxs' => 'mxs',
'application/vnd.trueapp' => 'tra',
'application/vnd.ufdl' => 'ufd',
'application/vnd.uiq.theme' => 'utz',
'application/vnd.umajin' => 'umj',
'application/vnd.unity' => 'unityweb',
'application/vnd.uoml+xml' => 'uoml',
'application/vnd.vcx' => 'vcx',
'application/vnd.visio' => 'vsd',
'application/vnd.visionary' => 'vis',
'application/vnd.vsf' => 'vsf',
'application/vnd.wap.wbxml' => 'wbxml',
'application/vnd.wap.wmlc' => 'wmlc',
'application/vnd.wap.wmlscriptc' => 'wmlsc',
'application/vnd.webturbo' => 'wtb',
'application/vnd.wolfram.player' => 'nbp',
'application/vnd.wordperfect' => 'wpd',
'application/vnd.wqd' => 'wqd',
'application/vnd.wt.stf' => 'stf',
'application/vnd.xara' => 'xar',
'application/vnd.xfdl' => 'xfdl',
'application/vnd.yamaha.hv-dic' => 'hvd',
'application/vnd.yamaha.hv-script' => 'hvs',
'application/vnd.yamaha.hv-voice' => 'hvp',
'application/vnd.yamaha.openscoreformat' => 'osf',
'application/vnd.yamaha.openscoreformat.osfpvg+xml' => 'osfpvg',
'application/vnd.yamaha.smaf-audio' => 'saf',
'application/vnd.yamaha.smaf-phrase' => 'spf',
'application/vnd.yellowriver-custom-menu' => 'cmp',
'application/vnd.zul' => 'zir',
'application/vnd.zzazz.deck+xml' => 'zaz',
'application/voicexml+xml' => 'vxml',
'application/widget' => 'wgt',
'application/winhlp' => 'hlp',
'application/wsdl+xml' => 'wsdl',
'application/wspolicy+xml' => 'wspolicy',
'application/x-7z-compressed' => '7z',
'application/x-abiword' => 'abw',
'application/x-ace-compressed' => 'ace',
'application/x-apple-diskimage' => 'dmg',
'application/x-authorware-bin' => 'aab',
'application/x-authorware-map' => 'aam',
'application/x-authorware-seg' => 'aas',
'application/x-bcpio' => 'bcpio',
'application/x-bittorrent' => 'torrent',
'application/x-blorb' => 'blb',
'application/x-bzip' => 'bz',
'application/x-bzip2' => 'bz2',
'application/x-cbr' => 'cbr',
'application/x-cdlink' => 'vcd',
'application/x-cfs-compressed' => 'cfs',
'application/x-chat' => 'chat',
'application/x-chess-pgn' => 'pgn',
'application/x-conference' => 'nsc',
'application/x-cpio' => 'cpio',
'application/x-csh' => 'csh',
'application/x-debian-package' => 'deb',
'application/x-dgc-compressed' => 'dgc',
'application/x-director' => 'dir',
'application/x-doom' => 'wad',
'application/x-dtbncx+xml' => 'ncx',
'application/x-dtbook+xml' => 'dtb',
'application/x-dtbresource+xml' => 'res',
'application/x-dvi' => 'dvi',
'application/x-envoy' => 'evy',
'application/x-eva' => 'eva',
'application/x-font-bdf' => 'bdf',
'application/x-font-ghostscript' => 'gsf',
'application/x-font-linux-psf' => 'psf',
'application/x-font-otf' => 'otf',
'application/x-font-pcf' => 'pcf',
'application/x-font-snf' => 'snf',
'application/x-font-ttf' => 'ttf',
'application/x-font-type1' => 'pfa',
'application/x-font-woff' => 'woff',
'application/x-freearc' => 'arc',
'application/x-futuresplash' => 'spl',
'application/x-gca-compressed' => 'gca',
'application/x-glulx' => 'ulx',
'application/x-gnumeric' => 'gnumeric',
'application/x-gramps-xml' => 'gramps',
'application/x-gtar' => 'gtar',
'application/x-hdf' => 'hdf',
'application/x-install-instructions' => 'install',
'application/x-iso9660-image' => 'iso',
'application/x-java-jnlp-file' => 'jnlp',
'application/x-latex' => 'latex',
'application/x-lzh-compressed' => 'lzh',
'application/x-mie' => 'mie',
'application/x-mobipocket-ebook' => 'prc',
'application/x-ms-application' => 'application',
'application/x-ms-shortcut' => 'lnk',
'application/x-ms-wmd' => 'wmd',
'application/x-ms-wmz' => 'wmz',
'application/x-ms-xbap' => 'xbap',
'application/x-msaccess' => 'mdb',
'application/x-msbinder' => 'obd',
'application/x-mscardfile' => 'crd',
'application/x-msclip' => 'clp',
'application/x-msdownload' => 'exe',
'application/x-msmediaview' => 'mvb',
'application/x-msmetafile' => 'wmf',
'application/x-msmoney' => 'mny',
'application/x-mspublisher' => 'pub',
'application/x-msschedule' => 'scd',
'application/x-msterminal' => 'trm',
'application/x-mswrite' => 'wri',
'application/x-netcdf' => 'nc',
'application/x-nzb' => 'nzb',
'application/x-pkcs12' => 'p12',
'application/x-pkcs7-certificates' => 'p7b',
'application/x-pkcs7-certreqresp' => 'p7r',
'application/x-rar-compressed' => 'rar',
'application/x-rar' => 'rar',
'application/x-research-info-systems' => 'ris',
'application/x-sh' => 'sh',
'application/x-shar' => 'shar',
'application/x-shockwave-flash' => 'swf',
'application/x-silverlight-app' => 'xap',
'application/x-sql' => 'sql',
'application/x-stuffit' => 'sit',
'application/x-stuffitx' => 'sitx',
'application/x-subrip' => 'srt',
'application/x-sv4cpio' => 'sv4cpio',
'application/x-sv4crc' => 'sv4crc',
'application/x-t3vm-image' => 't3',
'application/x-tads' => 'gam',
'application/x-tar' => 'tar',
'application/x-tcl' => 'tcl',
'application/x-tex' => 'tex',
'application/x-tex-tfm' => 'tfm',
'application/x-texinfo' => 'texinfo',
'application/x-tgif' => 'obj',
'application/x-ustar' => 'ustar',
'application/x-wais-source' => 'src',
'application/x-x509-ca-cert' => 'der',
'application/x-xfig' => 'fig',
'application/x-xliff+xml' => 'xlf',
'application/x-xpinstall' => 'xpi',
'application/x-xz' => 'xz',
'application/x-zip-compressed' => 'zip',
'application/x-zmachine' => 'z1',
'application/xaml+xml' => 'xaml',
'application/xcap-diff+xml' => 'xdf',
'application/xenc+xml' => 'xenc',
'application/xhtml+xml' => 'xhtml',
'application/xml' => 'xml',
'application/xml-dtd' => 'dtd',
'application/xop+xml' => 'xop',
'application/xproc+xml' => 'xpl',
'application/xslt+xml' => 'xslt',
'application/xspf+xml' => 'xspf',
'application/xv+xml' => 'mxml',
'application/yang' => 'yang',
'application/yin+xml' => 'yin',
'application/zip' => 'zip',
'audio/adpcm' => 'adp',
'audio/basic' => 'au',
'audio/midi' => 'mid',
'audio/mp4' => 'm4a',
'audio/mpeg' => 'mp3',
'audio/ogg' => 'oga',
'audio/s3m' => 's3m',
'audio/silk' => 'sil',
'audio/vnd.dece.audio' => 'uva',
'audio/vnd.digital-winds' => 'eol',
'audio/vnd.dra' => 'dra',
'audio/vnd.dts' => 'dts',
'audio/vnd.dts.hd' => 'dtshd',
'audio/vnd.lucent.voice' => 'lvp',
'audio/vnd.ms-playready.media.pya' => 'pya',
'audio/vnd.nuera.ecelp4800' => 'ecelp4800',
'audio/vnd.nuera.ecelp7470' => 'ecelp7470',
'audio/vnd.nuera.ecelp9600' => 'ecelp9600',
'audio/vnd.rip' => 'rip',
'audio/webm' => 'weba',
'audio/x-aac' => 'aac',
'audio/x-aiff' => 'aif',
'audio/x-caf' => 'caf',
'audio/x-flac' => 'flac',
'audio/x-hx-aac-adts' => 'aac',
'audio/x-matroska' => 'mka',
'audio/x-mpegurl' => 'm3u',
'audio/x-ms-wax' => 'wax',
'audio/x-ms-wma' => 'wma',
'audio/x-pn-realaudio' => 'ram',
'audio/x-pn-realaudio-plugin' => 'rmp',
'audio/x-wav' => 'wav',
'audio/xm' => 'xm',
'chemical/x-cdx' => 'cdx',
'chemical/x-cif' => 'cif',
'chemical/x-cmdf' => 'cmdf',
'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',
'image/g3fax' => 'g3',
'image/gif' => 'gif',
'image/ief' => 'ief',
'image/jpeg' => 'jpeg',
'image/pjpeg' => 'jpeg',
'image/ktx' => 'ktx',
'image/png' => 'png',
'image/prs.btif' => 'btif',
'image/sgi' => 'sgi',
'image/svg+xml' => 'svg',
'image/tiff' => 'tiff',
'image/vnd.adobe.photoshop' => 'psd',
'image/vnd.dece.graphic' => 'uvi',
'image/vnd.djvu' => 'djvu',
'image/vnd.dvb.subtitle' => 'sub',
'image/vnd.dwg' => 'dwg',
'image/vnd.dxf' => 'dxf',
'image/vnd.fastbidsheet' => 'fbs',
'image/vnd.fpx' => 'fpx',
'image/vnd.fst' => 'fst',
'image/vnd.fujixerox.edmics-mmr' => 'mmr',
'image/vnd.fujixerox.edmics-rlc' => 'rlc',
'image/vnd.ms-modi' => 'mdi',
'image/vnd.ms-photo' => 'wdp',
'image/vnd.net-fpx' => 'npx',
'image/vnd.wap.wbmp' => 'wbmp',
'image/vnd.xiff' => 'xif',
'image/webp' => 'webp',
'image/x-3ds' => '3ds',
'image/x-cmu-raster' => 'ras',
'image/x-cmx' => 'cmx',
'image/x-freehand' => 'fh',
'image/x-icon' => 'ico',
'image/x-mrsid-image' => 'sid',
'image/x-pcx' => 'pcx',
'image/x-pict' => 'pic',
'image/x-portable-anymap' => 'pnm',
'image/x-portable-bitmap' => 'pbm',
'image/x-portable-graymap' => 'pgm',
'image/x-portable-pixmap' => 'ppm',
'image/x-rgb' => 'rgb',
'image/x-tga' => 'tga',
'image/x-xbitmap' => 'xbm',
'image/x-xpixmap' => 'xpm',
'image/x-xwindowdump' => 'xwd',
'message/rfc822' => 'eml',
'model/iges' => 'igs',
'model/mesh' => 'msh',
'model/vnd.collada+xml' => 'dae',
'model/vnd.dwf' => 'dwf',
'model/vnd.gdl' => 'gdl',
'model/vnd.gtw' => 'gtw',
'model/vnd.mts' => 'mts',
'model/vnd.vtu' => 'vtu',
'model/vrml' => 'wrl',
'model/x3d+binary' => 'x3db',
'model/x3d+vrml' => 'x3dv',
'model/x3d+xml' => 'x3d',
'text/cache-manifest' => 'appcache',
'text/calendar' => 'ics',
'text/css' => 'css',
'text/csv' => 'csv',
'text/html' => 'html',
'text/n3' => 'n3',
'text/plain' => 'txt',
'text/prs.lines.tag' => 'dsc',
'text/richtext' => 'rtx',
'text/rtf' => 'rtf',
'text/sgml' => 'sgml',
'text/tab-separated-values' => 'tsv',
'text/troff' => 't',
'text/turtle' => 'ttl',
'text/uri-list' => 'uri',
'text/vcard' => 'vcard',
'text/vnd.curl' => 'curl',
'text/vnd.curl.dcurl' => 'dcurl',
'text/vnd.curl.mcurl' => 'mcurl',
'text/vnd.curl.scurl' => 'scurl',
'text/vnd.dvb.subtitle' => 'sub',
'text/vnd.fly' => 'fly',
'text/vnd.fmi.flexstor' => 'flx',
'text/vnd.graphviz' => 'gv',
'text/vnd.in3d.3dml' => '3dml',
'text/vnd.in3d.spot' => 'spot',
'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-java-source' => 'java',
'text/x-nfo' => 'nfo',
'text/x-opml' => 'opml',
'text/x-pascal' => 'p',
'text/x-setext' => 'etx',
'text/x-sfv' => 'sfv',
'text/x-uuencode' => 'uu',
'text/x-vcalendar' => 'vcs',
'text/x-vcard' => 'vcf',
'video/3gpp' => '3gp',
'video/3gpp2' => '3g2',
'video/h261' => 'h261',
'video/h263' => 'h263',
'video/h264' => 'h264',
'video/jpeg' => 'jpgv',
'video/jpm' => 'jpm',
'video/mj2' => 'mj2',
'video/mp4' => 'mp4',
'video/mpeg' => 'mpeg',
'video/ogg' => 'ogv',
'video/quicktime' => 'qt',
'video/vnd.dece.hd' => 'uvh',
'video/vnd.dece.mobile' => 'uvm',
'video/vnd.dece.pd' => 'uvp',
'video/vnd.dece.sd' => 'uvs',
'video/vnd.dece.video' => 'uvv',
'video/vnd.dvb.file' => 'dvb',
'video/vnd.fvt' => 'fvt',
'video/vnd.mpegurl' => 'mxu',
'video/vnd.ms-playready.media.pyv' => 'pyv',
'video/vnd.uvvu.mp4' => 'uvu',
'video/vnd.vivo' => 'viv',
'video/webm' => 'webm',
'video/x-f4v' => 'f4v',
'video/x-fli' => 'fli',
'video/x-flv' => 'flv',
'video/x-m4v' => 'm4v',
'video/x-matroska' => 'mkv',
'video/x-mng' => 'mng',
'video/x-ms-asf' => 'asf',
'video/x-ms-vob' => 'vob',
'video/x-ms-wm' => 'wm',
'video/x-ms-wmv' => 'wmv',
'video/x-ms-wmx' => 'wmx',
'video/x-ms-wvx' => 'wvx',
'video/x-msvideo' => 'avi',
'video/x-sgi-movie' => 'movie',
'video/x-smv' => 'smv',
'x-conference/x-cooltalk' => 'ice',
];
/**
* {@inheritdoc}
*/
public function guess($mimeType)
{
if (isset($this->defaultExtensions[$mimeType])) {
return $this->defaultExtensions[$mimeType];
}
$lcMimeType = strtolower($mimeType);
return $this->defaultExtensions[$lcMimeType] ?? null;
}
}

View File

@@ -1,138 +0,0 @@
<?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\MimeType;
use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException;
use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException;
use Symfony\Component\Mime\MimeTypes;
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" instead.', MimeTypeGuesser::class, MimeTypes::class), \E_USER_DEPRECATED);
/**
* A singleton mime type guesser.
*
* By default, all mime type guessers provided by the framework are installed
* (if available on the current OS/PHP setup).
*
* You can register custom guessers by calling the register() method on the
* singleton instance. Custom guessers are always called before any default ones.
*
* $guesser = MimeTypeGuesser::getInstance();
* $guesser->register(new MyCustomMimeTypeGuesser());
*
* If you want to change the order of the default guessers, just re-register your
* preferred one as a custom one. The last registered guesser is preferred over
* previously registered ones.
*
* Re-registering a built-in guesser also allows you to configure it:
*
* $guesser = MimeTypeGuesser::getInstance();
* $guesser->register(new FileinfoMimeTypeGuesser('/path/to/magic/file'));
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class MimeTypeGuesser implements MimeTypeGuesserInterface
{
/**
* The singleton instance.
*
* @var MimeTypeGuesser
*/
private static $instance = null;
/**
* All registered MimeTypeGuesserInterface instances.
*
* @var array
*/
protected $guessers = [];
/**
* Returns the singleton instance.
*
* @return self
*/
public static function getInstance()
{
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Resets the singleton instance.
*/
public static function reset()
{
self::$instance = null;
}
/**
* Registers all natively provided mime type guessers.
*/
private function __construct()
{
$this->register(new FileBinaryMimeTypeGuesser());
$this->register(new FileinfoMimeTypeGuesser());
}
/**
* Registers a new mime type guesser.
*
* When guessing, this guesser is preferred over previously registered ones.
*/
public function register(MimeTypeGuesserInterface $guesser)
{
array_unshift($this->guessers, $guesser);
}
/**
* Tries to guess the mime type of the given file.
*
* The file is passed to each registered mime type guesser in reverse order
* of their registration (last registered is queried first). Once a guesser
* returns a value that is not NULL, this method terminates and returns the
* value.
*
* @param string $path The path to the file
*
* @return string The mime type or NULL, if none could be guessed
*
* @throws \LogicException
* @throws FileNotFoundException
* @throws AccessDeniedException
*/
public function guess($path)
{
if (!is_file($path)) {
throw new FileNotFoundException($path);
}
if (!is_readable($path)) {
throw new AccessDeniedException($path);
}
foreach ($this->guessers as $guesser) {
if (null !== $mimeType = $guesser->guess($path)) {
return $mimeType;
}
}
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

@@ -1,38 +0,0 @@
<?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\MimeType;
use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException;
use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException;
use Symfony\Component\Mime\MimeTypesInterface;
/**
* Guesses the mime type of a file.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @deprecated since Symfony 4.3, use {@link MimeTypesInterface} instead
*/
interface MimeTypeGuesserInterface
{
/**
* Guesses the mime type of the file with the given path.
*
* @param string $path The path to the file
*
* @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
*/
public function guess($path);
}

View File

@@ -60,17 +60,10 @@ class UploadedFile extends File
* @throws FileException If file_uploads is disabled
* @throws FileNotFoundException If the file does not exist
*/
public function __construct(string $path, string $originalName, string $mimeType = null, int $error = null, $test = false)
public function __construct(string $path, string $originalName, string $mimeType = null, int $error = null, bool $test = false)
{
$this->originalName = $this->getName($originalName);
$this->mimeType = $mimeType ?: 'application/octet-stream';
if (4 < \func_num_args() ? !\is_bool($test) : null !== $error && @filesize($path) === $error) {
@trigger_error(sprintf('Passing a size as 4th argument to the constructor of "%s" is deprecated since Symfony 4.1.', __CLASS__), \E_USER_DEPRECATED);
$error = $test;
$test = 5 < \func_num_args() ? func_get_arg(5) : false;
}
$this->error = $error ?: \UPLOAD_ERR_OK;
$this->test = $test;
@@ -83,7 +76,7 @@ class UploadedFile extends File
* It is extracted from the request from which the file has been uploaded.
* Then it should not be considered as a safe value.
*
* @return string The original name
* @return string
*/
public function getClientOriginalName()
{
@@ -96,7 +89,7 @@ class UploadedFile extends File
* It is extracted from the original file name that was uploaded.
* Then it should not be considered as a safe value.
*
* @return string The extension
* @return string
*/
public function getClientOriginalExtension()
{
@@ -112,7 +105,7 @@ class UploadedFile extends File
* For a trusted mime type, use getMimeType() instead (which guesses the mime
* type based on the file content).
*
* @return string The mime type
* @return string
*
* @see getMimeType()
*/
@@ -133,40 +126,27 @@ class UploadedFile extends File
* For a trusted extension, use guessExtension() instead (which guesses
* the extension based on the guessed mime type for the file).
*
* @return string|null The guessed extension or null if it cannot be guessed
* @return string|null
*
* @see guessExtension()
* @see getClientMimeType()
*/
public function guessClientExtension()
{
if (!class_exists(MimeTypes::class)) {
throw new \LogicException('You cannot guess the extension as the Mime component is not installed. Try running "composer require symfony/mime".');
}
return MimeTypes::getDefault()->getExtensions($this->getClientMimeType())[0] ?? null;
}
/**
* Returns the file size.
*
* It is extracted from the request from which the file has been uploaded.
* Then it should not be considered as a safe value.
*
* @deprecated since Symfony 4.1, use getSize() instead.
*
* @return int|null The file sizes
*/
public function getClientSize()
{
@trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.1. Use getSize() instead.', __METHOD__), \E_USER_DEPRECATED);
return $this->getSize();
}
/**
* Returns the upload error.
*
* If the upload was successful, the constant UPLOAD_ERR_OK is returned.
* Otherwise one of the other UPLOAD_ERR_XXX constants is returned.
*
* @return int The upload error
* @return int
*/
public function getError()
{
@@ -174,9 +154,9 @@ class UploadedFile extends File
}
/**
* Returns whether the file was uploaded successfully.
* Returns whether the file has been uploaded with HTTP and no error occurred.
*
* @return bool True if the file has been uploaded with HTTP and no error occurred
* @return bool
*/
public function isValid()
{
@@ -188,14 +168,11 @@ class UploadedFile extends File
/**
* Moves the file to a new location.
*
* @param string $directory The destination folder
* @param string $name The new file name
*
* @return File A File object representing the new file
* @return File
*
* @throws FileException if, for any reason, the file could not have been moved
*/
public function move($directory, $name = null)
public function move(string $directory, string $name = null)
{
if ($this->isValid()) {
if ($this->test) {
@@ -205,8 +182,11 @@ class UploadedFile extends File
$target = $this->getTargetFile($directory, $name);
set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; });
$moved = move_uploaded_file($this->getPathname(), $target);
restore_error_handler();
try {
$moved = move_uploaded_file($this->getPathname(), $target);
} finally {
restore_error_handler();
}
if (!$moved) {
throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s).', $this->getPathname(), $target, strip_tags($error)));
}
@@ -273,11 +253,11 @@ class UploadedFile extends File
switch (substr($size, -1)) {
case 't': $max *= 1024;
// no break
// no break
case 'g': $max *= 1024;
// no break
// no break
case 'm': $max *= 1024;
// no break
// no break
case 'k': $max *= 1024;
}
@@ -287,7 +267,7 @@ class UploadedFile extends File
/**
* Returns an informative upload error message.
*
* @return string The error message regarding the specified error code
* @return string
*/
public function getErrorMessage()
{

View File

@@ -43,7 +43,7 @@ class FileBag extends ParameterBag
/**
* {@inheritdoc}
*/
public function set($key, $value)
public function set(string $key, $value)
{
if (!\is_array($value) && !$value instanceof UploadedFile) {
throw new \InvalidArgumentException('An uploaded file must be an array or an instance of UploadedFile.');
@@ -67,7 +67,7 @@ class FileBag extends ParameterBag
*
* @param array|UploadedFile $file A (multi-dimensional) array of uploaded file information
*
* @return UploadedFile[]|UploadedFile|null A (multi-dimensional) array of UploadedFile instances
* @return UploadedFile[]|UploadedFile|null
*/
protected function convertFileInformation($file)
{
@@ -107,11 +107,9 @@ 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)
protected function fixPhpFilesArray(array $data)
{
// Remove extra key added by PHP 8.1.
unset($data['full_path']);

View File

@@ -15,12 +15,17 @@ namespace Symfony\Component\HttpFoundation;
* HeaderBag is a container for HTTP headers.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @implements \IteratorAggregate<string, list<string|null>>
*/
class HeaderBag implements \IteratorAggregate, \Countable
{
protected const UPPER = '_ABCDEFGHIJKLMNOPQRSTUVWXYZ';
protected const LOWER = '-abcdefghijklmnopqrstuvwxyz';
/**
* @var array<string, list<string|null>>
*/
protected $headers = [];
protected $cacheControl = [];
@@ -34,7 +39,7 @@ class HeaderBag implements \IteratorAggregate, \Countable
/**
* Returns the headers as a string.
*
* @return string The headers
* @return string
*/
public function __toString()
{
@@ -60,11 +65,11 @@ class HeaderBag implements \IteratorAggregate, \Countable
*
* @param string|null $key The name of the headers to return or null to get them all
*
* @return array An array of headers
* @return array<string, array<int, string|null>>|array<int, string|null>
*/
public function all(/* string $key = null */)
public function all(string $key = null)
{
if (1 <= \func_num_args() && null !== $key = func_get_arg(0)) {
if (null !== $key) {
return $this->headers[strtr($key, self::UPPER, self::LOWER)] ?? [];
}
@@ -74,7 +79,7 @@ class HeaderBag implements \IteratorAggregate, \Countable
/**
* Returns the parameter keys.
*
* @return array An array of parameter keys
* @return string[]
*/
public function keys()
{
@@ -101,23 +106,13 @@ class HeaderBag implements \IteratorAggregate, \Countable
}
/**
* Returns a header value by name.
* Returns the first header by name or the default one.
*
* @param string $key The header name
* @param string|null $default The default value
*
* @return string|null The first header value or default value
* @return string|null
*/
public function get($key, $default = null)
public function get(string $key, string $default = null)
{
$headers = $this->all((string) $key);
if (2 < \func_num_args()) {
@trigger_error(sprintf('Passing a third argument to "%s()" is deprecated since Symfony 4.4, use method "all()" instead', __METHOD__), \E_USER_DEPRECATED);
if (!func_get_arg(2)) {
return $headers;
}
}
$headers = $this->all($key);
if (!$headers) {
return $default;
@@ -133,11 +128,10 @@ class HeaderBag implements \IteratorAggregate, \Countable
/**
* Sets a header by name.
*
* @param string $key The key
* @param string|string[]|null $values The value or an array of values
* @param bool $replace Whether to replace the actual value or not (true by default)
*/
public function set($key, $values, $replace = true)
public function set(string $key, $values, bool $replace = true)
{
$key = strtr($key, self::UPPER, self::LOWER);
@@ -165,11 +159,9 @@ class HeaderBag implements \IteratorAggregate, \Countable
/**
* Returns true if the HTTP header is defined.
*
* @param string $key The HTTP header
*
* @return bool true if the parameter exists, false otherwise
* @return bool
*/
public function has($key)
public function has(string $key)
{
return \array_key_exists(strtr($key, self::UPPER, self::LOWER), $this->all());
}
@@ -177,22 +169,17 @@ class HeaderBag implements \IteratorAggregate, \Countable
/**
* Returns true if the given HTTP header contains the given value.
*
* @param string $key The HTTP header name
* @param string $value The HTTP value
*
* @return bool true if the value is contained in the header, false otherwise
* @return bool
*/
public function contains($key, $value)
public function contains(string $key, string $value)
{
return \in_array($value, $this->all((string) $key));
return \in_array($value, $this->all($key));
}
/**
* Removes a header.
*
* @param string $key The HTTP header name
*/
public function remove($key)
public function remove(string $key)
{
$key = strtr($key, self::UPPER, self::LOWER);
@@ -206,13 +193,11 @@ class HeaderBag implements \IteratorAggregate, \Countable
/**
* Returns the HTTP header value converted to a date.
*
* @param string $key The parameter key
*
* @return \DateTimeInterface|null The parsed DateTime or the default value if the header does not exist
* @return \DateTimeInterface|null
*
* @throws \RuntimeException When the HTTP header is not parseable
*/
public function getDate($key, \DateTime $default = null)
public function getDate(string $key, \DateTime $default = null)
{
if (null === $value = $this->get($key)) {
return $default;
@@ -228,10 +213,9 @@ class HeaderBag implements \IteratorAggregate, \Countable
/**
* Adds a custom Cache-Control directive.
*
* @param string $key The Cache-Control directive name
* @param bool|string $value The Cache-Control directive value
*/
public function addCacheControlDirective($key, $value = true)
public function addCacheControlDirective(string $key, $value = true)
{
$this->cacheControl[$key] = $value;
@@ -241,11 +225,9 @@ class HeaderBag implements \IteratorAggregate, \Countable
/**
* Returns true if the Cache-Control directive is defined.
*
* @param string $key The Cache-Control directive
*
* @return bool true if the directive exists, false otherwise
* @return bool
*/
public function hasCacheControlDirective($key)
public function hasCacheControlDirective(string $key)
{
return \array_key_exists($key, $this->cacheControl);
}
@@ -253,21 +235,17 @@ class HeaderBag implements \IteratorAggregate, \Countable
/**
* Returns a Cache-Control directive value by name.
*
* @param string $key The directive name
*
* @return bool|string|null The directive value if defined, null otherwise
* @return bool|string|null
*/
public function getCacheControlDirective($key)
public function getCacheControlDirective(string $key)
{
return $this->cacheControl[$key] ?? null;
}
/**
* Removes a Cache-Control directive.
*
* @param string $key The Cache-Control directive
*/
public function removeCacheControlDirective($key)
public function removeCacheControlDirective(string $key)
{
unset($this->cacheControl[$key]);
@@ -277,7 +255,7 @@ class HeaderBag implements \IteratorAggregate, \Countable
/**
* Returns an iterator for headers.
*
* @return \ArrayIterator An \ArrayIterator instance
* @return \ArrayIterator<string, list<string|null>>
*/
#[\ReturnTypeWillChange]
public function getIterator()
@@ -288,7 +266,7 @@ class HeaderBag implements \IteratorAggregate, \Countable
/**
* Returns the number of headers.
*
* @return int The number of headers
* @return int
*/
#[\ReturnTypeWillChange]
public function count()
@@ -306,11 +284,9 @@ class HeaderBag implements \IteratorAggregate, \Countable
/**
* Parses a Cache-Control HTTP header.
*
* @param string $header The value of the Cache-Control HTTP header
*
* @return array An array representing the attribute values
* @return array
*/
protected function parseCacheControl($header)
protected function parseCacheControl(string $header)
{
$parts = HeaderUtils::split($header, ',=');

View File

@@ -154,8 +154,6 @@ class HeaderUtils
* is semantically equivalent to $filename. If the filename is already ASCII,
* it can be omitted, or just copied from $filename
*
* @return string A string suitable for use as a Content-Disposition field-value
*
* @throws \InvalidArgumentException
*
* @see RFC 6266
@@ -193,6 +191,64 @@ class HeaderUtils
return $disposition.'; '.self::toString($params, ';');
}
/**
* Like parse_str(), but preserves dots in variable names.
*/
public static function parseQuery(string $query, bool $ignoreBrackets = false, string $separator = '&'): array
{
$q = [];
foreach (explode($separator, $query) as $v) {
if (false !== $i = strpos($v, "\0")) {
$v = substr($v, 0, $i);
}
if (false === $i = strpos($v, '=')) {
$k = urldecode($v);
$v = '';
} else {
$k = urldecode(substr($v, 0, $i));
$v = substr($v, $i);
}
if (false !== $i = strpos($k, "\0")) {
$k = substr($k, 0, $i);
}
$k = ltrim($k, ' ');
if ($ignoreBrackets) {
$q[$k][] = urldecode(substr($v, 1));
continue;
}
if (false === $i = strpos($k, '[')) {
$q[] = bin2hex($k).$v;
} else {
$q[] = bin2hex(substr($k, 0, $i)).rawurlencode(substr($k, $i)).$v;
}
}
if ($ignoreBrackets) {
return $q;
}
parse_str(implode('&', $q), $q);
$query = [];
foreach ($q as $k => $v) {
if (false !== $i = strpos($k, '_')) {
$query[substr_replace($k, hex2bin(substr($k, 0, $i)).'[', 0, 1 + $i)] = $v;
} else {
$query[hex2bin($k)] = $v;
}
}
return $query;
}
private static function groupParts(array $matches, string $separators, bool $first = true): array
{
$separator = $separators[0];

View File

@@ -0,0 +1,113 @@
<?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;
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
/**
* InputBag is a container for user input values such as $_GET, $_POST, $_REQUEST, and $_COOKIE.
*
* @author Saif Eddin Gmati <azjezz@protonmail.com>
*/
final class InputBag extends ParameterBag
{
/**
* Returns a scalar input value by name.
*
* @param string|int|float|bool|null $default The default value if the input key does not exist
*
* @return string|int|float|bool|null
*/
public function get(string $key, $default = null)
{
if (null !== $default && !\is_scalar($default) && !(\is_object($default) && method_exists($default, '__toString'))) {
trigger_deprecation('symfony/http-foundation', '5.1', 'Passing a non-scalar value as 2nd argument to "%s()" is deprecated, pass a scalar or null instead.', __METHOD__);
}
$value = parent::get($key, $this);
if (null !== $value && $this !== $value && !\is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) {
trigger_deprecation('symfony/http-foundation', '5.1', 'Retrieving a non-scalar value from "%s()" is deprecated, and will throw a "%s" exception in Symfony 6.0, use "%s::all($key)" instead.', __METHOD__, BadRequestException::class, __CLASS__);
}
return $this === $value ? $default : $value;
}
/**
* {@inheritdoc}
*/
public function all(string $key = null): array
{
return parent::all($key);
}
/**
* Replaces the current input values by a new set.
*/
public function replace(array $inputs = [])
{
$this->parameters = [];
$this->add($inputs);
}
/**
* Adds input values.
*/
public function add(array $inputs = [])
{
foreach ($inputs as $input => $value) {
$this->set($input, $value);
}
}
/**
* Sets an input by name.
*
* @param string|int|float|bool|array|null $value
*/
public function set(string $key, $value)
{
if (null !== $value && !\is_scalar($value) && !\is_array($value) && !method_exists($value, '__toString')) {
trigger_deprecation('symfony/http-foundation', '5.1', 'Passing "%s" as a 2nd Argument to "%s()" is deprecated, pass a scalar, array, or null instead.', get_debug_type($value), __METHOD__);
}
$this->parameters[$key] = $value;
}
/**
* {@inheritdoc}
*/
public function filter(string $key, $default = null, int $filter = \FILTER_DEFAULT, $options = [])
{
$value = $this->has($key) ? $this->all()[$key] : $default;
// Always turn $options into an array - this allows filter_var option shortcuts.
if (!\is_array($options) && $options) {
$options = ['flags' => $options];
}
if (\is_array($value) && !(($options['flags'] ?? 0) & (\FILTER_REQUIRE_ARRAY | \FILTER_FORCE_ARRAY))) {
trigger_deprecation('symfony/http-foundation', '5.1', 'Filtering an array value with "%s()" without passing the FILTER_REQUIRE_ARRAY or FILTER_FORCE_ARRAY flag is deprecated', __METHOD__);
if (!isset($options['flags'])) {
$options['flags'] = \FILTER_REQUIRE_ARRAY;
}
}
if ((\FILTER_CALLBACK & $filter) && !(($options['options'] ?? null) instanceof \Closure)) {
trigger_deprecation('symfony/http-foundation', '5.2', 'Not passing a Closure together with FILTER_CALLBACK to "%s()" is deprecated. Wrap your filter in a closure instead.', __METHOD__);
// throw new \InvalidArgumentException(sprintf('A Closure must be passed to "%s()" when FILTER_CALLBACK is used, "%s" given.', __METHOD__, get_debug_type($options['options'] ?? null)));
}
return filter_var($value, $filter, $options);
}
}

View File

@@ -30,14 +30,15 @@ class IpUtils
/**
* Checks if an IPv4 or IPv6 address is contained in the list of given IPs or subnets.
*
* @param string $requestIp IP to check
* @param string|array $ips List of IPs or subnets (can be a string if only a single one)
* @param string|array $ips List of IPs or subnets (can be a string if only a single one)
*
* @return bool Whether the IP is valid
* @return bool
*/
public static function checkIp($requestIp, $ips)
public static function checkIp(?string $requestIp, $ips)
{
if (null === $requestIp) {
trigger_deprecation('symfony/http-foundation', '5.4', 'Passing null as $requestIp to "%s()" is deprecated, pass an empty string instead.', __METHOD__);
return false;
}
@@ -60,13 +61,18 @@ class IpUtils
* Compares two IPv4 addresses.
* In case a subnet is given, it checks if it contains the request IP.
*
* @param string $requestIp IPv4 address to check
* @param string $ip IPv4 address or subnet in CIDR notation
* @param string $ip IPv4 address or subnet in CIDR notation
*
* @return bool Whether the request IP matches the IP, or whether the request IP is within the CIDR subnet
*/
public static function checkIp4($requestIp, $ip)
public static function checkIp4(?string $requestIp, string $ip)
{
if (null === $requestIp) {
trigger_deprecation('symfony/http-foundation', '5.4', 'Passing null as $requestIp to "%s()" is deprecated, pass an empty string instead.', __METHOD__);
return false;
}
$cacheKey = $requestIp.'-'.$ip;
if (isset(self::$checkedIps[$cacheKey])) {
return self::$checkedIps[$cacheKey];
@@ -106,15 +112,20 @@ class IpUtils
*
* @see https://github.com/dsp/v6tools
*
* @param string $requestIp IPv6 address to check
* @param string $ip IPv6 address or subnet in CIDR notation
* @param string $ip IPv6 address or subnet in CIDR notation
*
* @return bool Whether the IP is valid
* @return bool
*
* @throws \RuntimeException When IPV6 support is not enabled
*/
public static function checkIp6($requestIp, $ip)
public static function checkIp6(?string $requestIp, string $ip)
{
if (null === $requestIp) {
trigger_deprecation('symfony/http-foundation', '5.4', 'Passing null as $requestIp to "%s()" is deprecated, pass an empty string instead.', __METHOD__);
return false;
}
$cacheKey = $requestIp.'-'.$ip;
if (isset(self::$checkedIps[$cacheKey])) {
return self::$checkedIps[$cacheKey];
@@ -125,10 +136,6 @@ class IpUtils
}
// Check to see if we were given a IP4 $requestIp or $ip by mistake
if (str_contains($requestIp, '.') || str_contains($ip, '.')) {
return self::$checkedIps[$cacheKey] = false;
}
if (!filter_var($requestIp, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) {
return self::$checkedIps[$cacheKey] = false;
}
@@ -136,6 +143,10 @@ class IpUtils
if (str_contains($ip, '/')) {
[$address, $netmask] = explode('/', $ip, 2);
if (!filter_var($address, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) {
return self::$checkedIps[$cacheKey] = false;
}
if ('0' === $netmask) {
return (bool) unpack('n*', @inet_pton($address));
}
@@ -144,6 +155,10 @@ class IpUtils
return self::$checkedIps[$cacheKey] = false;
}
} else {
if (!filter_var($ip, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) {
return self::$checkedIps[$cacheKey] = false;
}
$address = $ip;
$netmask = 128;
}

View File

@@ -67,9 +67,13 @@ class JsonResponse extends Response
* @param array $headers An array of response headers
*
* @return static
*
* @deprecated since Symfony 5.1, use __construct() instead.
*/
public static function create($data = null, $status = 200, $headers = [])
public static function create($data = null, int $status = 200, array $headers = [])
{
trigger_deprecation('symfony/http-foundation', '5.1', 'The "%s()" method is deprecated, use "new %s()" instead.', __METHOD__, static::class);
return new static($data, $status, $headers);
}
@@ -87,7 +91,7 @@ class JsonResponse extends Response
*
* @return static
*/
public static function fromJsonString($data, $status = 200, $headers = [])
public static function fromJsonString(string $data, int $status = 200, array $headers = [])
{
return new static($data, $status, $headers, true);
}
@@ -101,7 +105,7 @@ class JsonResponse extends Response
*
* @throws \InvalidArgumentException When the callback name is not valid
*/
public function setCallback($callback = null)
public function setCallback(string $callback = null)
{
if (null !== $callback) {
// partially taken from https://geekality.net/2011/08/03/valid-javascript-identifier/
@@ -130,11 +134,9 @@ class JsonResponse extends Response
/**
* Sets a raw string containing a JSON document to be sent.
*
* @param string $json
*
* @return $this
*/
public function setJson($json)
public function setJson(string $json)
{
$this->data = $json;
@@ -185,13 +187,11 @@ class JsonResponse extends Response
/**
* Sets options used while encoding data to JSON.
*
* @param int $encodingOptions
*
* @return $this
*/
public function setEncodingOptions($encodingOptions)
public function setEncodingOptions(int $encodingOptions)
{
$this->encodingOptions = (int) $encodingOptions;
$this->encodingOptions = $encodingOptions;
return $this->setData(json_decode($this->data));
}

View File

@@ -11,10 +11,14 @@
namespace Symfony\Component\HttpFoundation;
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
/**
* ParameterBag is a container for key/value pairs.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @implements \IteratorAggregate<string, mixed>
*/
class ParameterBag implements \IteratorAggregate, \Countable
{
@@ -31,17 +35,29 @@ class ParameterBag implements \IteratorAggregate, \Countable
/**
* Returns the parameters.
*
* @return array An array of parameters
* @param string|null $key The name of the parameter to return or null to get them all
*
* @return array
*/
public function all()
public function all(/* string $key = null */)
{
return $this->parameters;
$key = \func_num_args() > 0 ? func_get_arg(0) : null;
if (null === $key) {
return $this->parameters;
}
if (!\is_array($value = $this->parameters[$key] ?? [])) {
throw new BadRequestException(sprintf('Unexpected value for parameter "%s": expecting "array", got "%s".', $key, get_debug_type($value)));
}
return $value;
}
/**
* Returns the parameter keys.
*
* @return array An array of parameter keys
* @return array
*/
public function keys()
{
@@ -67,12 +83,11 @@ class ParameterBag implements \IteratorAggregate, \Countable
/**
* Returns a parameter by name.
*
* @param string $key The key
* @param mixed $default The default value if the parameter key does not exist
* @param mixed $default The default value if the parameter key does not exist
*
* @return mixed
*/
public function get($key, $default = null)
public function get(string $key, $default = null)
{
return \array_key_exists($key, $this->parameters) ? $this->parameters[$key] : $default;
}
@@ -80,10 +95,9 @@ class ParameterBag implements \IteratorAggregate, \Countable
/**
* Sets a parameter by name.
*
* @param string $key The key
* @param mixed $value The value
* @param mixed $value The value
*/
public function set($key, $value)
public function set(string $key, $value)
{
$this->parameters[$key] = $value;
}
@@ -91,21 +105,17 @@ class ParameterBag implements \IteratorAggregate, \Countable
/**
* Returns true if the parameter is defined.
*
* @param string $key The key
*
* @return bool true if the parameter exists, false otherwise
* @return bool
*/
public function has($key)
public function has(string $key)
{
return \array_key_exists($key, $this->parameters);
}
/**
* Removes a parameter.
*
* @param string $key The key
*/
public function remove($key)
public function remove(string $key)
{
unset($this->parameters[$key]);
}
@@ -113,12 +123,9 @@ class ParameterBag implements \IteratorAggregate, \Countable
/**
* Returns the alphabetic characters of the parameter value.
*
* @param string $key The parameter key
* @param string $default The default value if the parameter key does not exist
*
* @return string The filtered value
* @return string
*/
public function getAlpha($key, $default = '')
public function getAlpha(string $key, string $default = '')
{
return preg_replace('/[^[:alpha:]]/', '', $this->get($key, $default));
}
@@ -126,12 +133,9 @@ class ParameterBag implements \IteratorAggregate, \Countable
/**
* Returns the alphabetic characters and digits of the parameter value.
*
* @param string $key The parameter key
* @param string $default The default value if the parameter key does not exist
*
* @return string The filtered value
* @return string
*/
public function getAlnum($key, $default = '')
public function getAlnum(string $key, string $default = '')
{
return preg_replace('/[^[:alnum:]]/', '', $this->get($key, $default));
}
@@ -139,12 +143,9 @@ class ParameterBag implements \IteratorAggregate, \Countable
/**
* Returns the digits of the parameter value.
*
* @param string $key The parameter key
* @param string $default The default value if the parameter key does not exist
*
* @return string The filtered value
* @return string
*/
public function getDigits($key, $default = '')
public function getDigits(string $key, string $default = '')
{
// we need to remove - and + because they're allowed in the filter
return str_replace(['-', '+'], '', $this->filter($key, $default, \FILTER_SANITIZE_NUMBER_INT));
@@ -153,12 +154,9 @@ class ParameterBag implements \IteratorAggregate, \Countable
/**
* Returns the parameter value converted to integer.
*
* @param string $key The parameter key
* @param int $default The default value if the parameter key does not exist
*
* @return int The filtered value
* @return int
*/
public function getInt($key, $default = 0)
public function getInt(string $key, int $default = 0)
{
return (int) $this->get($key, $default);
}
@@ -166,12 +164,9 @@ class ParameterBag implements \IteratorAggregate, \Countable
/**
* Returns the parameter value converted to boolean.
*
* @param string $key The parameter key
* @param bool $default The default value if the parameter key does not exist
*
* @return bool The filtered value
* @return bool
*/
public function getBoolean($key, $default = false)
public function getBoolean(string $key, bool $default = false)
{
return $this->filter($key, $default, \FILTER_VALIDATE_BOOLEAN);
}
@@ -179,16 +174,15 @@ class ParameterBag implements \IteratorAggregate, \Countable
/**
* Filter key.
*
* @param string $key Key
* @param mixed $default Default = null
* @param int $filter FILTER_* constant
* @param mixed $options Filter options
* @param mixed $default Default = null
* @param int $filter FILTER_* constant
* @param mixed $options Filter options
*
* @see https://php.net/filter-var
*
* @return mixed
*/
public function filter($key, $default = null, $filter = \FILTER_DEFAULT, $options = [])
public function filter(string $key, $default = null, int $filter = \FILTER_DEFAULT, $options = [])
{
$value = $this->get($key, $default);
@@ -202,13 +196,18 @@ class ParameterBag implements \IteratorAggregate, \Countable
$options['flags'] = \FILTER_REQUIRE_ARRAY;
}
if ((\FILTER_CALLBACK & $filter) && !(($options['options'] ?? null) instanceof \Closure)) {
trigger_deprecation('symfony/http-foundation', '5.2', 'Not passing a Closure together with FILTER_CALLBACK to "%s()" is deprecated. Wrap your filter in a closure instead.', __METHOD__);
// throw new \InvalidArgumentException(sprintf('A Closure must be passed to "%s()" when FILTER_CALLBACK is used, "%s" given.', __METHOD__, get_debug_type($options['options'] ?? null)));
}
return filter_var($value, $filter, $options);
}
/**
* Returns an iterator for parameters.
*
* @return \ArrayIterator An \ArrayIterator instance
* @return \ArrayIterator<string, mixed>
*/
#[\ReturnTypeWillChange]
public function getIterator()
@@ -219,7 +218,7 @@ class ParameterBag implements \IteratorAggregate, \Countable
/**
* Returns the number of parameters.
*
* @return int The number of parameters
* @return int
*/
#[\ReturnTypeWillChange]
public function count()

View File

@@ -4,6 +4,16 @@ HttpFoundation Component
The HttpFoundation component defines an object-oriented layer for the HTTP
specification.
Sponsor
-------
The HttpFoundation component for Symfony 5.4/6.0 is [backed][1] by [Laravel][2].
Laravel is a PHP web development framework that is passionate about maximum developer
happiness. Laravel is built using a variety of bespoke and Symfony based components.
Help Symfony by [sponsoring][3] its development!
Resources
---------
@@ -12,3 +22,7 @@ Resources
* [Report issues](https://github.com/symfony/symfony/issues) and
[send Pull Requests](https://github.com/symfony/symfony/pulls)
in the [main Symfony repository](https://github.com/symfony/symfony)
[1]: https://symfony.com/backers
[2]: https://laravel.com/
[3]: https://symfony.com/sponsor

View File

@@ -0,0 +1,71 @@
<?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\RateLimiter;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\RateLimiter\LimiterInterface;
use Symfony\Component\RateLimiter\Policy\NoLimiter;
use Symfony\Component\RateLimiter\RateLimit;
/**
* An implementation of RequestRateLimiterInterface that
* fits most use-cases.
*
* @author Wouter de Jong <wouter@wouterj.nl>
*/
abstract class AbstractRequestRateLimiter implements RequestRateLimiterInterface
{
public function consume(Request $request): RateLimit
{
$limiters = $this->getLimiters($request);
if (0 === \count($limiters)) {
$limiters = [new NoLimiter()];
}
$minimalRateLimit = null;
foreach ($limiters as $limiter) {
$rateLimit = $limiter->consume(1);
$minimalRateLimit = $minimalRateLimit ? self::getMinimalRateLimit($minimalRateLimit, $rateLimit) : $rateLimit;
}
return $minimalRateLimit;
}
public function reset(Request $request): void
{
foreach ($this->getLimiters($request) as $limiter) {
$limiter->reset();
}
}
/**
* @return LimiterInterface[] a set of limiters using keys extracted from the request
*/
abstract protected function getLimiters(Request $request): array;
private static function getMinimalRateLimit(RateLimit $first, RateLimit $second): RateLimit
{
if ($first->isAccepted() !== $second->isAccepted()) {
return $first->isAccepted() ? $second : $first;
}
$firstRemainingTokens = $first->getRemainingTokens();
$secondRemainingTokens = $second->getRemainingTokens();
if ($firstRemainingTokens === $secondRemainingTokens) {
return $first->getRetryAfter() < $second->getRetryAfter() ? $second : $first;
}
return $firstRemainingTokens > $secondRemainingTokens ? $second : $first;
}
}

View File

@@ -0,0 +1,30 @@
<?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\RateLimiter;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\RateLimiter\RateLimit;
/**
* A special type of limiter that deals with requests.
*
* This allows to limit on different types of information
* from the requests.
*
* @author Wouter de Jong <wouter@wouterj.nl>
*/
interface RequestRateLimiterInterface
{
public function consume(Request $request): RateLimit;
public function reset(Request $request): void;
}

View File

@@ -32,13 +32,8 @@ class RedirectResponse extends Response
*
* @see https://tools.ietf.org/html/rfc2616#section-10.3
*/
public function __construct(?string $url, int $status = 302, array $headers = [])
public function __construct(string $url, int $status = 302, array $headers = [])
{
if (null === $url) {
@trigger_error(sprintf('Passing a null url when instantiating a "%s" is deprecated since Symfony 4.4.', __CLASS__), \E_USER_DEPRECATED);
$url = '';
}
parent::__construct('', $status, $headers);
$this->setTargetUrl($url);
@@ -55,21 +50,23 @@ class RedirectResponse extends Response
/**
* Factory method for chainability.
*
* @param string $url The url to redirect to
* @param int $status The response status code
* @param array $headers An array of response headers
* @param string $url The URL to redirect to
*
* @return static
*
* @deprecated since Symfony 5.1, use __construct() instead.
*/
public static function create($url = '', $status = 302, $headers = [])
public static function create($url = '', int $status = 302, array $headers = [])
{
trigger_deprecation('symfony/http-foundation', '5.1', 'The "%s()" method is deprecated, use "new %s()" instead.', __METHOD__, static::class);
return new static($url, $status, $headers);
}
/**
* Returns the target URL.
*
* @return string target URL
* @return string
*/
public function getTargetUrl()
{
@@ -79,15 +76,13 @@ class RedirectResponse extends Response
/**
* Sets the redirect target of this response.
*
* @param string $url The URL to redirect to
*
* @return $this
*
* @throws \InvalidArgumentException
*/
public function setTargetUrl($url)
public function setTargetUrl(string $url)
{
if ('' === ($url ?? '')) {
if ('' === $url) {
throw new \InvalidArgumentException('Cannot redirect to an empty URL.');
}

View File

@@ -12,6 +12,8 @@
namespace Symfony\Component\HttpFoundation;
use Symfony\Component\HttpFoundation\Exception\ConflictingHeadersException;
use Symfony\Component\HttpFoundation\Exception\JsonException;
use Symfony\Component\HttpFoundation\Exception\SessionNotFoundException;
use Symfony\Component\HttpFoundation\Exception\SuspiciousOperationException;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
@@ -20,6 +22,7 @@ class_exists(AcceptHeader::class);
class_exists(FileBag::class);
class_exists(HeaderBag::class);
class_exists(HeaderUtils::class);
class_exists(InputBag::class);
class_exists(ParameterBag::class);
class_exists(ServerBag::class);
@@ -38,13 +41,17 @@ class_exists(ServerBag::class);
*/
class Request
{
public const HEADER_FORWARDED = 0b00001; // When using RFC 7239
public const HEADER_X_FORWARDED_FOR = 0b00010;
public const HEADER_X_FORWARDED_HOST = 0b00100;
public const HEADER_X_FORWARDED_PROTO = 0b01000;
public const HEADER_X_FORWARDED_PORT = 0b10000;
public const HEADER_X_FORWARDED_ALL = 0b11110; // All "X-Forwarded-*" headers
public const HEADER_X_FORWARDED_AWS_ELB = 0b11010; // AWS ELB doesn't send X-Forwarded-Host
public const HEADER_FORWARDED = 0b000001; // When using RFC 7239
public const HEADER_X_FORWARDED_FOR = 0b000010;
public const HEADER_X_FORWARDED_HOST = 0b000100;
public const HEADER_X_FORWARDED_PROTO = 0b001000;
public const HEADER_X_FORWARDED_PORT = 0b010000;
public const HEADER_X_FORWARDED_PREFIX = 0b100000;
/** @deprecated since Symfony 5.2, use either "HEADER_X_FORWARDED_FOR | HEADER_X_FORWARDED_HOST | HEADER_X_FORWARDED_PORT | HEADER_X_FORWARDED_PROTO" or "HEADER_X_FORWARDED_AWS_ELB" or "HEADER_X_FORWARDED_TRAEFIK" constants instead. */
public const HEADER_X_FORWARDED_ALL = 0b1011110; // All "X-Forwarded-*" headers sent by "usual" reverse proxy
public const HEADER_X_FORWARDED_AWS_ELB = 0b0011010; // AWS ELB doesn't send X-Forwarded-Host
public const HEADER_X_FORWARDED_TRAEFIK = 0b0111110; // All "X-Forwarded-*" headers sent by Traefik reverse proxy
public const METHOD_HEAD = 'HEAD';
public const METHOD_GET = 'GET';
@@ -84,14 +91,14 @@ class Request
/**
* Request body parameters ($_POST).
*
* @var ParameterBag
* @var InputBag
*/
public $request;
/**
* Query string parameters ($_GET).
*
* @var ParameterBag
* @var InputBag
*/
public $query;
@@ -112,7 +119,7 @@ class Request
/**
* Cookies ($_COOKIE).
*
* @var ParameterBag
* @var InputBag
*/
public $cookies;
@@ -179,7 +186,7 @@ class Request
protected $format;
/**
* @var SessionInterface|callable
* @var SessionInterface|callable(): SessionInterface
*/
protected $session;
@@ -207,6 +214,11 @@ class Request
private $isHostValid = true;
private $isForwardedValid = true;
/**
* @var bool|null
*/
private $isSafeContentPreferred;
private static $trustedHeaderSet = -1;
private const FORWARDED_PARAMS = [
@@ -231,6 +243,7 @@ class Request
self::HEADER_X_FORWARDED_HOST => 'X_FORWARDED_HOST',
self::HEADER_X_FORWARDED_PROTO => 'X_FORWARDED_PROTO',
self::HEADER_X_FORWARDED_PORT => 'X_FORWARDED_PORT',
self::HEADER_X_FORWARDED_PREFIX => 'X_FORWARDED_PREFIX',
];
/**
@@ -262,10 +275,10 @@ class Request
*/
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);
$this->request = new InputBag($request);
$this->query = new InputBag($query);
$this->attributes = new ParameterBag($attributes);
$this->cookies = new ParameterBag($cookies);
$this->cookies = new InputBag($cookies);
$this->files = new FileBag($files);
$this->server = new ServerBag($server);
$this->headers = new HeaderBag($this->server->getHeaders());
@@ -296,7 +309,7 @@ class Request
&& \in_array(strtoupper($request->server->get('REQUEST_METHOD', 'GET')), ['PUT', 'DELETE', 'PATCH'])
) {
parse_str($request->getContent(), $data);
$request->request = new ParameterBag($data);
$request->request = new InputBag($data);
}
return $request;
@@ -318,7 +331,7 @@ class Request
*
* @return static
*/
public static function create($uri, $method = 'GET', $parameters = [], $cookies = [], $files = [], $server = [], $content = null)
public static function create(string $uri, string $method = 'GET', array $parameters = [], array $cookies = [], array $files = [], array $server = [], $content = null)
{
$server = array_replace([
'SERVER_NAME' => 'localhost',
@@ -417,10 +430,8 @@ class Request
* This is mainly useful when you need to override the Request class
* to keep BC with an existing system. It should not be used for any
* other purpose.
*
* @param callable|null $callable A PHP callable
*/
public static function setFactory($callable)
public static function setFactory(?callable $callable)
{
self::$requestFactory = $callable;
}
@@ -441,16 +452,16 @@ class Request
{
$dup = clone $this;
if (null !== $query) {
$dup->query = new ParameterBag($query);
$dup->query = new InputBag($query);
}
if (null !== $request) {
$dup->request = new ParameterBag($request);
$dup->request = new InputBag($request);
}
if (null !== $attributes) {
$dup->attributes = new ParameterBag($attributes);
}
if (null !== $cookies) {
$dup->cookies = new ParameterBag($cookies);
$dup->cookies = new InputBag($cookies);
}
if (null !== $files) {
$dup->files = new FileBag($files);
@@ -501,7 +512,7 @@ class Request
/**
* Returns the request as a string.
*
* @return string The request
* @return string
*/
public function __toString()
{
@@ -511,10 +522,10 @@ class Request
$cookies = [];
foreach ($this->cookies as $k => $v) {
$cookies[] = $k.'='.$v;
$cookies[] = \is_array($v) ? http_build_query([$k => $v], '', '; ', \PHP_QUERY_RFC3986) : "$k=$v";
}
if (!empty($cookies)) {
if ($cookies) {
$cookieHeader = 'Cookie: '.implode('; ', $cookies)."\r\n";
}
@@ -573,6 +584,9 @@ class Request
*/
public static function setTrustedProxies(array $proxies, int $trustedHeaderSet)
{
if (self::HEADER_X_FORWARDED_ALL === $trustedHeaderSet) {
trigger_deprecation('symfony/http-foundation', '5.2', 'The "HEADER_X_FORWARDED_ALL" constant is deprecated, use either "HEADER_X_FORWARDED_FOR | HEADER_X_FORWARDED_HOST | HEADER_X_FORWARDED_PORT | HEADER_X_FORWARDED_PROTO" or "HEADER_X_FORWARDED_AWS_ELB" or "HEADER_X_FORWARDED_TRAEFIK" constants instead.');
}
self::$trustedProxies = array_reduce($proxies, function ($proxies, $proxy) {
if ('REMOTE_ADDR' !== $proxy) {
$proxies[] = $proxy;
@@ -588,7 +602,7 @@ class Request
/**
* Gets the list of trusted proxies.
*
* @return array An array of trusted proxies
* @return array
*/
public static function getTrustedProxies()
{
@@ -624,7 +638,7 @@ class Request
/**
* Gets the list of trusted host patterns.
*
* @return array An array of trusted host patterns
* @return array
*/
public static function getTrustedHosts()
{
@@ -637,17 +651,15 @@ class Request
* It builds a normalized query string, where keys/value pairs are alphabetized,
* have consistent escaping and unneeded delimiters are removed.
*
* @param string $qs Query string
*
* @return string A normalized query string for the Request
* @return string
*/
public static function normalizeQueryString($qs)
public static function normalizeQueryString(?string $qs)
{
if ('' === ($qs ?? '')) {
return '';
}
parse_str($qs, $qs);
$qs = HeaderUtils::parseQuery($qs);
ksort($qs);
return http_build_query($qs, '', '&', \PHP_QUERY_RFC3986);
@@ -672,7 +684,7 @@ class Request
/**
* Checks whether support for the _method request parameter is enabled.
*
* @return bool True when the _method request parameter is enabled, false otherwise
* @return bool
*/
public static function getHttpMethodParameterOverride()
{
@@ -688,23 +700,24 @@ class Request
*
* Order of precedence: PATH (routing placeholders or custom attributes), GET, POST
*
* @param string $key The key
* @param mixed $default The default value if the parameter key does not exist
* @param mixed $default The default value if the parameter key does not exist
*
* @return mixed
*
* @internal since Symfony 5.4, use explicit input sources instead
*/
public function get($key, $default = null)
public function get(string $key, $default = null)
{
if ($this !== $result = $this->attributes->get($key, $this)) {
return $result;
}
if ($this !== $result = $this->query->get($key, $this)) {
return $result;
if ($this->query->has($key)) {
return $this->query->all()[$key];
}
if ($this !== $result = $this->request->get($key, $this)) {
return $result;
if ($this->request->has($key)) {
return $this->request->all()[$key];
}
return $default;
@@ -713,7 +726,7 @@ class Request
/**
* Gets the Session.
*
* @return SessionInterface The session
* @return SessionInterface
*/
public function getSession()
{
@@ -723,8 +736,7 @@ class Request
}
if (null === $session) {
@trigger_error(sprintf('Calling "%s()" when no session has been set is deprecated since Symfony 4.1 and will throw an exception in 5.0. Use "hasSession()" instead.', __METHOD__), \E_USER_DEPRECATED);
// throw new \BadMethodCallException('Session has not been set.');
throw new SessionNotFoundException('Session has not been set.');
}
return $session;
@@ -749,11 +761,15 @@ class Request
* like whether the session is started or not. It is just a way to check if this Request
* is associated with a Session instance.
*
* @return bool true when the Request contains a Session object, false otherwise
* @param bool $skipIfUninitialized When true, ignores factories injected by `setSessionFactory`
*
* @return bool
*/
public function hasSession()
public function hasSession(/* bool $skipIfUninitialized = false */)
{
return null !== $this->session;
$skipIfUninitialized = \func_num_args() > 0 ? func_get_arg(0) : false;
return null !== $this->session && (!$skipIfUninitialized || $this->session instanceof SessionInterface);
}
public function setSession(SessionInterface $session)
@@ -763,6 +779,8 @@ class Request
/**
* @internal
*
* @param callable(): SessionInterface $factory
*/
public function setSessionFactory(callable $factory)
{
@@ -778,7 +796,7 @@ class Request
*
* Use this method carefully; you should use getClientIp() instead.
*
* @return array The client IP addresses
* @return array
*
* @see getClientIp()
*/
@@ -806,7 +824,7 @@ class Request
* ("Client-Ip" for instance), configure it via the $trustedHeaderSet
* argument of the Request::setTrustedProxies() method instead.
*
* @return string|null The client IP address
* @return string|null
*
* @see getClientIps()
* @see https://wikipedia.org/wiki/X-Forwarded-For
@@ -883,6 +901,24 @@ class Request
* @return string The raw URL (i.e. not urldecoded)
*/
public function getBaseUrl()
{
$trustedPrefix = '';
// the proxy prefix must be prepended to any prefix being needed at the webserver level
if ($this->isFromTrustedProxy() && $trustedPrefixValues = $this->getTrustedValues(self::HEADER_X_FORWARDED_PREFIX)) {
$trustedPrefix = rtrim($trustedPrefixValues[0], '/');
}
return $trustedPrefix.$this->getBaseUrlReal();
}
/**
* Returns the real base URL received by the webserver from which this request is executed.
* The URL does not include trusted reverse proxy prefix.
*
* @return string The raw URL (i.e. not urldecoded)
*/
private function getBaseUrlReal(): string
{
if (null === $this->baseUrl) {
$this->baseUrl = $this->prepareBaseUrl();
@@ -909,7 +945,7 @@ class Request
*
* The "X-Forwarded-Port" header must contain the client port.
*
* @return int|string can be a string if fetched from the server bag
* @return int|string|null Can be a string if fetched from the server bag
*/
public function getPort()
{
@@ -1010,7 +1046,7 @@ class Request
* If the URL was called with basic authentication, the user
* and the password are not added to the generated string.
*
* @return string The scheme and HTTP host
* @return string
*/
public function getSchemeAndHttpHost()
{
@@ -1020,7 +1056,7 @@ class Request
/**
* Generates a normalized URI (URL) for the Request.
*
* @return string A normalized URI (URL) for the Request
* @return string
*
* @see getQueryString()
*/
@@ -1038,9 +1074,9 @@ class Request
*
* @param string $path A path to use instead of the current one
*
* @return string The normalized URI for the path
* @return string
*/
public function getUriForPath($path)
public function getUriForPath(string $path)
{
return $this->getSchemeAndHttpHost().$this->getBaseUrl().$path;
}
@@ -1060,11 +1096,9 @@ class Request
* - "/a/b/c/other" -> "other"
* - "/a/x/y" -> "../../x/y"
*
* @param string $path The target path
*
* @return string The relative target path
* @return string
*/
public function getRelativeUriForPath($path)
public function getRelativeUriForPath(string $path)
{
// be sure that we are dealing with an absolute path
if (!isset($path[0]) || '/' !== $path[0]) {
@@ -1106,7 +1140,7 @@ class Request
* It builds a normalized query string, where keys/value pairs are alphabetized
* and have consistent escaping.
*
* @return string|null A normalized query string for the Request
* @return string|null
*/
public function getQueryString()
{
@@ -1202,10 +1236,8 @@ class Request
/**
* Sets the request method.
*
* @param string $method
*/
public function setMethod($method)
public function setMethod(string $method)
{
$this->method = null;
$this->server->set('REQUEST_METHOD', $method);
@@ -1222,7 +1254,7 @@ class Request
*
* The method is always an uppercased string.
*
* @return string The request method
* @return string
*
* @see getRealMethod()
*/
@@ -1264,7 +1296,7 @@ class Request
/**
* Gets the "real" request method.
*
* @return string The request method
* @return string
*
* @see getMethod()
*/
@@ -1276,11 +1308,9 @@ class Request
/**
* Gets the mime type associated with the format.
*
* @param string $format The format
*
* @return string|null The associated mime type (null if not found)
* @return string|null
*/
public function getMimeType($format)
public function getMimeType(string $format)
{
if (null === static::$formats) {
static::initializeFormats();
@@ -1292,11 +1322,9 @@ class Request
/**
* Gets the mime types associated with the format.
*
* @param string $format The format
*
* @return array The associated mime types
* @return array
*/
public static function getMimeTypes($format)
public static function getMimeTypes(string $format)
{
if (null === static::$formats) {
static::initializeFormats();
@@ -1308,14 +1336,12 @@ class Request
/**
* Gets the format associated with the mime type.
*
* @param string $mimeType The associated mime type
*
* @return string|null The format (null if not found)
* @return string|null
*/
public function getFormat($mimeType)
public function getFormat(?string $mimeType)
{
$canonicalMimeType = null;
if (false !== $pos = strpos($mimeType, ';')) {
if ($mimeType && false !== $pos = strpos($mimeType, ';')) {
$canonicalMimeType = trim(substr($mimeType, 0, $pos));
}
@@ -1338,10 +1364,9 @@ class Request
/**
* Associates a format with mime types.
*
* @param string $format The format
* @param string|array $mimeTypes The associated mime types (the preferred one must be the first as it will be used as the content type)
*/
public function setFormat($format, $mimeTypes)
public function setFormat(?string $format, $mimeTypes)
{
if (null === static::$formats) {
static::initializeFormats();
@@ -1361,11 +1386,9 @@ class Request
*
* @see getPreferredFormat
*
* @param string|null $default The default format
*
* @return string|null The request format
* @return string|null
*/
public function getRequestFormat($default = 'html')
public function getRequestFormat(?string $default = 'html')
{
if (null === $this->format) {
$this->format = $this->attributes->get('_format');
@@ -1376,10 +1399,8 @@ class Request
/**
* Sets the request format.
*
* @param string $format The request format
*/
public function setRequestFormat($format)
public function setRequestFormat(?string $format)
{
$this->format = $format;
}
@@ -1387,7 +1408,7 @@ class Request
/**
* Gets the format associated with the request.
*
* @return string|null The format (null if no content type is present)
* @return string|null
*/
public function getContentType()
{
@@ -1396,10 +1417,8 @@ class Request
/**
* Sets the default locale.
*
* @param string $locale
*/
public function setDefaultLocale($locale)
public function setDefaultLocale(string $locale)
{
$this->defaultLocale = $locale;
@@ -1420,10 +1439,8 @@ class Request
/**
* Sets the locale.
*
* @param string $locale
*/
public function setLocale($locale)
public function setLocale(string $locale)
{
$this->setPhpDefaultLocale($this->locale = $locale);
}
@@ -1445,7 +1462,7 @@ class Request
*
* @return bool
*/
public function isMethod($method)
public function isMethod(string $method)
{
return $this->getMethod() === strtoupper($method);
}
@@ -1459,10 +1476,6 @@ class Request
*/
public function isMethodSafe()
{
if (\func_num_args() > 0) {
@trigger_error(sprintf('Passing arguments to "%s()" has been deprecated since Symfony 4.4; use "%s::isMethodCacheable()" to check if the method is cacheable instead.', __METHOD__, __CLASS__), \E_USER_DEPRECATED);
}
return \in_array($this->getMethod(), ['GET', 'HEAD', 'OPTIONS', 'TRACE']);
}
@@ -1481,7 +1494,7 @@ class Request
*
* @see https://tools.ietf.org/html/rfc7231#section-4.2.3
*
* @return bool True for GET and HEAD, false otherwise
* @return bool
*/
public function isMethodCacheable()
{
@@ -1517,9 +1530,9 @@ class Request
*
* @param bool $asResource If true, a resource will be returned
*
* @return string|resource The request body content or a resource to read the body stream
* @return string|resource
*/
public function getContent($asResource = false)
public function getContent(bool $asResource = false)
{
$currentContentIsResource = \is_resource($this->content);
@@ -1557,10 +1570,40 @@ class Request
return $this->content;
}
/**
* Gets the request body decoded as array, typically from a JSON payload.
*
* @throws JsonException When the body cannot be decoded to an array
*
* @return array
*/
public function toArray()
{
if ('' === $content = $this->getContent()) {
throw new JsonException('Request body is empty.');
}
try {
$content = json_decode($content, true, 512, \JSON_BIGINT_AS_STRING | (\PHP_VERSION_ID >= 70300 ? \JSON_THROW_ON_ERROR : 0));
} catch (\JsonException $e) {
throw new JsonException('Could not decode request body.', $e->getCode(), $e);
}
if (\PHP_VERSION_ID < 70300 && \JSON_ERROR_NONE !== json_last_error()) {
throw new JsonException('Could not decode request body: '.json_last_error_msg(), json_last_error());
}
if (!\is_array($content)) {
throw new JsonException(sprintf('JSON content was expected to decode to an array, "%s" returned.', get_debug_type($content)));
}
return $content;
}
/**
* Gets the Etags.
*
* @return array The entity tags
* @return array
*/
public function getETags()
{
@@ -1577,7 +1620,7 @@ class Request
/**
* Gets the preferred format for the response by inspecting, in the following order:
* * the request format set using setRequestFormat
* * the request format set using setRequestFormat;
* * the values of the Accept HTTP header.
*
* Note that if you use this method, you should send the "Vary: Accept" header
@@ -1603,7 +1646,7 @@ class Request
*
* @param string[] $locales An array of ordered available locales
*
* @return string|null The preferred locale
* @return string|null
*/
public function getPreferredLanguage(array $locales = null)
{
@@ -1634,9 +1677,9 @@ class Request
}
/**
* Gets a list of languages acceptable by the client browser.
* Gets a list of languages acceptable by the client browser ordered in the user browser preferences.
*
* @return array Languages ordered in the user browser preferences
* @return array
*/
public function getLanguages()
{
@@ -1675,9 +1718,9 @@ class Request
}
/**
* Gets a list of charsets acceptable by the client browser.
* Gets a list of charsets acceptable by the client browser in preferable order.
*
* @return array List of charsets in preferable order
* @return array
*/
public function getCharsets()
{
@@ -1689,9 +1732,9 @@ class Request
}
/**
* Gets a list of encodings acceptable by the client browser.
* Gets a list of encodings acceptable by the client browser in preferable order.
*
* @return array List of encodings in preferable order
* @return array
*/
public function getEncodings()
{
@@ -1703,9 +1746,9 @@ class Request
}
/**
* Gets a list of content types acceptable by the client browser.
* Gets a list of content types acceptable by the client browser in preferable order.
*
* @return array List of content types in preferable order
* @return array
*/
public function getAcceptableContentTypes()
{
@@ -1724,13 +1767,32 @@ class Request
*
* @see https://wikipedia.org/wiki/List_of_Ajax_frameworks#JavaScript
*
* @return bool true if the request is an XMLHttpRequest, false otherwise
* @return bool
*/
public function isXmlHttpRequest()
{
return 'XMLHttpRequest' == $this->headers->get('X-Requested-With');
}
/**
* Checks whether the client browser prefers safe content or not according to RFC8674.
*
* @see https://tools.ietf.org/html/rfc8674
*/
public function preferSafeContent(): bool
{
if (null !== $this->isSafeContentPreferred) {
return $this->isSafeContentPreferred;
}
if (!$this->isSecure()) {
// see https://tools.ietf.org/html/rfc8674#section-3
return $this->isSafeContentPreferred = false;
}
return $this->isSafeContentPreferred = AcceptHeader::fromString($this->headers->get('Prefer'))->has('safe');
}
/*
* The following methods are derived from code of the Zend Framework (1.10dev - 2010-01-24)
*
@@ -1856,7 +1918,7 @@ class Request
/**
* Prepares the base path.
*
* @return string base path
* @return string
*/
protected function prepareBasePath()
{
@@ -1882,7 +1944,7 @@ class Request
/**
* Prepares the path info.
*
* @return string path info
* @return string
*/
protected function preparePathInfo()
{
@@ -1898,7 +1960,7 @@ class Request
$requestUri = '/'.$requestUri;
}
if (null === ($baseUrl = $this->getBaseUrl())) {
if (null === ($baseUrl = $this->getBaseUrlReal())) {
return $requestUri;
}
@@ -1927,7 +1989,7 @@ class Request
'rdf' => ['application/rdf+xml'],
'atom' => ['application/atom+xml'],
'rss' => ['application/rss+xml'],
'form' => ['application/x-www-form-urlencoded'],
'form' => ['application/x-www-form-urlencoded', 'multipart/form-data'],
];
}
@@ -1984,7 +2046,7 @@ class Request
* 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
* @return bool
*/
public function isFromTrustedProxy()
{
@@ -2002,7 +2064,7 @@ class Request
}
}
if ((self::$trustedHeaderSet & self::HEADER_FORWARDED) && $this->headers->has(self::TRUSTED_HEADERS[self::HEADER_FORWARDED])) {
if ((self::$trustedHeaderSet & self::HEADER_FORWARDED) && (isset(self::FORWARDED_PARAMS[$type])) && $this->headers->has(self::TRUSTED_HEADERS[self::HEADER_FORWARDED])) {
$forwarded = $this->headers->get(self::TRUSTED_HEADERS[self::HEADER_FORWARDED]);
$parts = HeaderUtils::split($forwarded, ',;=');
$forwardedValues = [];

View File

@@ -84,10 +84,8 @@ class RequestMatcher implements RequestMatcherInterface
/**
* Adds a check for the URL host name.
*
* @param string|null $regexp A Regexp
*/
public function matchHost($regexp)
public function matchHost(?string $regexp)
{
$this->host = $regexp;
}
@@ -104,10 +102,8 @@ class RequestMatcher implements RequestMatcherInterface
/**
* Adds a check for the URL path info.
*
* @param string|null $regexp A Regexp
*/
public function matchPath($regexp)
public function matchPath(?string $regexp)
{
$this->path = $regexp;
}
@@ -117,7 +113,7 @@ class RequestMatcher implements RequestMatcherInterface
*
* @param string $ip A specific IP address or a range specified using IP/netmask like 192.168.1.0/24
*/
public function matchIp($ip)
public function matchIp(string $ip)
{
$this->matchIps($ip);
}
@@ -129,7 +125,11 @@ class RequestMatcher implements RequestMatcherInterface
*/
public function matchIps($ips)
{
$this->ips = null !== $ips ? (array) $ips : [];
$ips = null !== $ips ? (array) $ips : [];
$this->ips = array_reduce($ips, static function (array $ips, string $ip) {
return array_merge($ips, preg_split('/\s*,\s*/', $ip));
}, []);
}
/**
@@ -144,11 +144,8 @@ class RequestMatcher implements RequestMatcherInterface
/**
* Adds a check for request attribute.
*
* @param string $key The request attribute name
* @param string $regexp A Regexp
*/
public function matchAttribute($key, $regexp)
public function matchAttribute(string $key, string $regexp)
{
$this->attributes[$key] = $regexp;
}
@@ -188,7 +185,7 @@ class RequestMatcher implements RequestMatcherInterface
return false;
}
if (IpUtils::checkIp($request->getClientIp(), $this->ips)) {
if (IpUtils::checkIp($request->getClientIp() ?? '', $this->ips)) {
return true;
}

View File

@@ -21,7 +21,7 @@ interface RequestMatcherInterface
/**
* Decides whether the rule(s) implemented by the strategy matches the supplied request.
*
* @return bool true if the request matches, false otherwise
* @return bool
*/
public function matches(Request $request);
}

View File

@@ -11,6 +11,9 @@
namespace Symfony\Component\HttpFoundation;
use Symfony\Component\HttpFoundation\Exception\SessionNotFoundException;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
/**
* Request stack that controls the lifecycle of requests.
*
@@ -62,15 +65,13 @@ class RequestStack
}
/**
* Gets the master Request.
* Gets the main request.
*
* Be warned that making your code aware of the master request
* Be warned that making your code aware of the main request
* might make it un-compatible with other features of your framework
* like ESI support.
*
* @return Request|null
*/
public function getMasterRequest()
public function getMainRequest(): ?Request
{
if (!$this->requests) {
return null;
@@ -79,6 +80,20 @@ class RequestStack
return $this->requests[0];
}
/**
* Gets the master request.
*
* @return Request|null
*
* @deprecated since symfony/http-foundation 5.3, use getMainRequest() instead
*/
public function getMasterRequest()
{
trigger_deprecation('symfony/http-foundation', '5.3', '"%s()" is deprecated, use "getMainRequest()" instead.', __METHOD__);
return $this->getMainRequest();
}
/**
* Returns the parent request of the current.
*
@@ -86,7 +101,7 @@ class RequestStack
* might make it un-compatible with other features of your framework
* like ESI support.
*
* If current Request is the master request, it returns null.
* If current Request is the main request, it returns null.
*
* @return Request|null
*/
@@ -96,4 +111,18 @@ class RequestStack
return $this->requests[$pos] ?? null;
}
/**
* Gets the current session.
*
* @throws SessionNotFoundException
*/
public function getSession(): SessionInterface
{
if ((null !== $request = end($this->requests) ?: null) && $request->hasSession()) {
return $request->getSession();
}
throw new SessionNotFoundException();
}
}

View File

@@ -67,11 +67,6 @@ class Response
public const HTTP_UNPROCESSABLE_ENTITY = 422; // RFC4918
public const HTTP_LOCKED = 423; // RFC4918
public const HTTP_FAILED_DEPENDENCY = 424; // RFC4918
/**
* @deprecated
*/
public const HTTP_RESERVED_FOR_WEBDAV_ADVANCED_COLLECTIONS_EXPIRED_PROPOSAL = 425; // RFC2817
public const HTTP_TOO_EARLY = 425; // RFC-ietf-httpbis-replay-04
public const HTTP_UPGRADE_REQUIRED = 426; // RFC2817
public const HTTP_PRECONDITION_REQUIRED = 428; // RFC6585
@@ -90,6 +85,24 @@ class Response
public const HTTP_NOT_EXTENDED = 510; // RFC2774
public const HTTP_NETWORK_AUTHENTICATION_REQUIRED = 511; // RFC6585
/**
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control
*/
private const HTTP_RESPONSE_CACHE_CONTROL_DIRECTIVES = [
'must_revalidate' => false,
'no_cache' => false,
'no_store' => false,
'no_transform' => false,
'public' => false,
'private' => false,
'proxy_revalidate' => false,
'max_age' => true,
's_maxage' => true,
'immutable' => false,
'last_modified' => true,
'etag' => true,
];
/**
* @var ResponseHeaderBag
*/
@@ -125,7 +138,7 @@ class Response
*
* The list of codes is complete according to the
* {@link https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml Hypertext Transfer Protocol (HTTP) Status Code Registry}
* (last updated 2018-09-21).
* (last updated 2021-10-01).
*
* Unless otherwise noted, the status code is defined in RFC2616.
*
@@ -167,14 +180,14 @@ class Response
410 => 'Gone',
411 => 'Length Required',
412 => 'Precondition Failed',
413 => 'Payload Too Large',
413 => 'Content Too Large', // RFC-ietf-httpbis-semantics
414 => 'URI Too Long',
415 => 'Unsupported Media Type',
416 => 'Range Not Satisfiable',
417 => 'Expectation Failed',
418 => 'I\'m a teapot', // RFC2324
421 => 'Misdirected Request', // RFC7540
422 => 'Unprocessable Entity', // RFC4918
422 => 'Unprocessable Content', // RFC-ietf-httpbis-semantics
423 => 'Locked', // RFC4918
424 => 'Failed Dependency', // RFC4918
425 => 'Too Early', // RFC-ietf-httpbis-replay-04
@@ -199,7 +212,7 @@ class Response
/**
* @throws \InvalidArgumentException When the HTTP status code is not valid
*/
public function __construct($content = '', int $status = 200, array $headers = [])
public function __construct(?string $content = '', int $status = 200, array $headers = [])
{
$this->headers = new ResponseHeaderBag($headers);
$this->setContent($content);
@@ -215,14 +228,14 @@ class Response
* return Response::create($body, 200)
* ->setSharedMaxAge(300);
*
* @param mixed $content The response content, see setContent()
* @param int $status The response status code
* @param array $headers An array of response headers
*
* @return static
*
* @deprecated since Symfony 5.1, use __construct() instead.
*/
public static function create($content = '', $status = 200, $headers = [])
public static function create(?string $content = '', int $status = 200, array $headers = [])
{
trigger_deprecation('symfony/http-foundation', '5.1', 'The "%s()" method is deprecated, use "new %s()" instead.', __METHOD__, static::class);
return new static($content, $status, $headers);
}
@@ -233,7 +246,7 @@ class Response
* one that will be sent to the client only if the prepare() method
* has been called before.
*
* @return string The Response as an HTTP string
* @return string
*
* @see prepare()
*/
@@ -382,6 +395,8 @@ class Response
if (\function_exists('fastcgi_finish_request')) {
fastcgi_finish_request();
} elseif (\function_exists('litespeed_finish_request')) {
litespeed_finish_request();
} elseif (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) {
static::closeOutputBuffers(0, true);
flush();
@@ -393,21 +408,11 @@ class Response
/**
* Sets the response content.
*
* Valid types are strings, numbers, null, and objects that implement a __toString() method.
*
* @param mixed $content Content that can be cast to string
*
* @return $this
*
* @throws \UnexpectedValueException
*/
public function setContent($content)
public function setContent(?string $content)
{
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)));
}
$this->content = (string) $content;
$this->content = $content ?? '';
return $this;
}
@@ -429,7 +434,7 @@ class Response
*
* @final
*/
public function setProtocolVersion(string $version)
public function setProtocolVersion(string $version): object
{
$this->version = $version;
@@ -458,7 +463,7 @@ class Response
*
* @final
*/
public function setStatusCode(int $code, $text = null)
public function setStatusCode(int $code, string $text = null): object
{
$this->statusCode = $code;
if ($this->isInvalid()) {
@@ -499,7 +504,7 @@ class Response
*
* @final
*/
public function setCharset(string $charset)
public function setCharset(string $charset): object
{
$this->charset = $charset;
@@ -580,7 +585,7 @@ class Response
*
* @final
*/
public function setPrivate()
public function setPrivate(): object
{
$this->headers->removeCacheControlDirective('public');
$this->headers->addCacheControlDirective('private');
@@ -597,7 +602,7 @@ class Response
*
* @final
*/
public function setPublic()
public function setPublic(): object
{
$this->headers->addCacheControlDirective('public');
$this->headers->removeCacheControlDirective('private');
@@ -612,7 +617,7 @@ class Response
*
* @final
*/
public function setImmutable(bool $immutable = true)
public function setImmutable(bool $immutable = true): object
{
if ($immutable) {
$this->headers->addCacheControlDirective('immutable');
@@ -667,7 +672,7 @@ class Response
*
* @final
*/
public function setDate(\DateTimeInterface $date)
public function setDate(\DateTimeInterface $date): object
{
if ($date instanceof \DateTime) {
$date = \DateTimeImmutable::createFromMutable($date);
@@ -732,7 +737,7 @@ class Response
*
* @final
*/
public function setExpires(\DateTimeInterface $date = null)
public function setExpires(\DateTimeInterface $date = null): object
{
if (null === $date) {
$this->headers->remove('Expires');
@@ -785,7 +790,7 @@ class Response
*
* @final
*/
public function setMaxAge(int $value)
public function setMaxAge(int $value): object
{
$this->headers->addCacheControlDirective('max-age', $value);
@@ -801,7 +806,7 @@ class Response
*
* @final
*/
public function setSharedMaxAge(int $value)
public function setSharedMaxAge(int $value): object
{
$this->setPublic();
$this->headers->addCacheControlDirective('s-maxage', $value);
@@ -835,7 +840,7 @@ class Response
*
* @final
*/
public function setTtl(int $seconds)
public function setTtl(int $seconds): object
{
$this->setSharedMaxAge($this->getAge() + $seconds);
@@ -851,7 +856,7 @@ class Response
*
* @final
*/
public function setClientTtl(int $seconds)
public function setClientTtl(int $seconds): object
{
$this->setMaxAge($this->getAge() + $seconds);
@@ -879,7 +884,7 @@ class Response
*
* @final
*/
public function setLastModified(\DateTimeInterface $date = null)
public function setLastModified(\DateTimeInterface $date = null): object
{
if (null === $date) {
$this->headers->remove('Last-Modified');
@@ -917,7 +922,7 @@ class Response
*
* @final
*/
public function setEtag(string $etag = null, bool $weak = false)
public function setEtag(string $etag = null, bool $weak = false): object
{
if (null === $etag) {
$this->headers->remove('Etag');
@@ -935,7 +940,7 @@ class Response
/**
* Sets the response's cache headers (validation and/or expiration).
*
* Available options are: etag, last_modified, max_age, s_maxage, private, public and immutable.
* Available options are: must_revalidate, no_cache, no_store, no_transform, public, private, proxy_revalidate, max_age, s_maxage, immutable, last_modified and etag.
*
* @return $this
*
@@ -943,9 +948,9 @@ class Response
*
* @final
*/
public function setCache(array $options)
public function setCache(array $options): object
{
if ($diff = array_diff(array_keys($options), ['etag', 'last_modified', 'max_age', 's_maxage', 'private', 'public', 'immutable'])) {
if ($diff = array_diff(array_keys($options), array_keys(self::HTTP_RESPONSE_CACHE_CONTROL_DIRECTIVES))) {
throw new \InvalidArgumentException(sprintf('Response does not support the following options: "%s".', implode('", "', $diff)));
}
@@ -965,6 +970,16 @@ class Response
$this->setSharedMaxAge($options['s_maxage']);
}
foreach (self::HTTP_RESPONSE_CACHE_CONTROL_DIRECTIVES as $directive => $hasValue) {
if (!$hasValue && isset($options[$directive])) {
if ($options[$directive]) {
$this->headers->addCacheControlDirective(str_replace('_', '-', $directive));
} else {
$this->headers->removeCacheControlDirective(str_replace('_', '-', $directive));
}
}
}
if (isset($options['public'])) {
if ($options['public']) {
$this->setPublic();
@@ -981,10 +996,6 @@ class Response
}
}
if (isset($options['immutable'])) {
$this->setImmutable((bool) $options['immutable']);
}
return $this;
}
@@ -1000,7 +1011,7 @@ class Response
*
* @final
*/
public function setNotModified()
public function setNotModified(): object
{
$this->setStatusCode(304);
$this->setContent(null);
@@ -1036,10 +1047,10 @@ class Response
$ret = [];
foreach ($vary as $item) {
$ret = array_merge($ret, preg_split('/[\s,]+/', $item));
$ret[] = preg_split('/[\s,]+/', $item);
}
return $ret;
return array_merge([], ...$ret);
}
/**
@@ -1052,7 +1063,7 @@ class Response
*
* @final
*/
public function setVary($headers, bool $replace = true)
public function setVary($headers, bool $replace = true): object
{
$this->headers->set('Vary', $headers, $replace);
@@ -1066,8 +1077,6 @@ class Response
* If the Response is not modified, it sets the status code to 304 and
* removes the actual content by calling the setNotModified() method.
*
* @return bool true if the Response validators match the Request, false otherwise
*
* @final
*/
public function isNotModified(Request $request): bool
@@ -1243,6 +1252,22 @@ class Response
}
}
/**
* Marks a response as safe according to RFC8674.
*
* @see https://tools.ietf.org/html/rfc8674
*/
public function setContentSafe(bool $safe = true): void
{
if ($safe) {
$this->headers->set('Preference-Applied', 'safe');
} elseif ('safe' === $this->headers->get('Preference-Applied')) {
$this->headers->remove('Preference-Applied');
}
$this->setVary('Prefer', false);
}
/**
* Checks if we need to remove Cache-Control for SSL encrypted downloads when using IE < 9.
*

View File

@@ -45,7 +45,7 @@ class ResponseHeaderBag extends HeaderBag
/**
* Returns the headers, with original capitalizations.
*
* @return array An array of headers
* @return array
*/
public function allPreserveCase()
{
@@ -87,14 +87,12 @@ class ResponseHeaderBag extends HeaderBag
/**
* {@inheritdoc}
*
* @param string|null $key The name of the headers to return or null to get them all
*/
public function all(/* string $key = null */)
public function all(string $key = null)
{
$headers = parent::all();
if (1 <= \func_num_args() && null !== $key = func_get_arg(0)) {
if (null !== $key) {
$key = strtr($key, self::UPPER, self::LOWER);
return 'set-cookie' !== $key ? $headers[$key] ?? [] : array_map('strval', $this->getCookies());
@@ -110,7 +108,7 @@ class ResponseHeaderBag extends HeaderBag
/**
* {@inheritdoc}
*/
public function set($key, $values, $replace = true)
public function set(string $key, $values, bool $replace = true)
{
$uniqueKey = strtr($key, self::UPPER, self::LOWER);
@@ -141,7 +139,7 @@ class ResponseHeaderBag extends HeaderBag
/**
* {@inheritdoc}
*/
public function remove($key)
public function remove(string $key)
{
$uniqueKey = strtr($key, self::UPPER, self::LOWER);
unset($this->headerNames[$uniqueKey]);
@@ -166,7 +164,7 @@ class ResponseHeaderBag extends HeaderBag
/**
* {@inheritdoc}
*/
public function hasCacheControlDirective($key)
public function hasCacheControlDirective(string $key)
{
return \array_key_exists($key, $this->computedCacheControl);
}
@@ -174,7 +172,7 @@ class ResponseHeaderBag extends HeaderBag
/**
* {@inheritdoc}
*/
public function getCacheControlDirective($key)
public function getCacheControlDirective(string $key)
{
return $this->computedCacheControl[$key] ?? null;
}
@@ -187,12 +185,8 @@ class ResponseHeaderBag extends HeaderBag
/**
* Removes a cookie from the array, but does not unset it in the browser.
*
* @param string $name
* @param string $path
* @param string $domain
*/
public function removeCookie($name, $path = '/', $domain = null)
public function removeCookie(string $name, ?string $path = '/', string $domain = null)
{
if (null === $path) {
$path = '/';
@@ -216,13 +210,11 @@ class ResponseHeaderBag extends HeaderBag
/**
* Returns an array with all cookies.
*
* @param string $format
*
* @return Cookie[]
*
* @throws \InvalidArgumentException When the $format is invalid
*/
public function getCookies($format = self::COOKIES_FLAT)
public function getCookies(string $format = self::COOKIES_FLAT)
{
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])));
@@ -246,27 +238,18 @@ class ResponseHeaderBag extends HeaderBag
/**
* Clears a cookie in the browser.
*
* @param string $name
* @param string $path
* @param string $domain
* @param bool $secure
* @param bool $httpOnly
* @param string $sameSite
*/
public function clearCookie($name, $path = '/', $domain = null, $secure = false, $httpOnly = true/* , $sameSite = null */)
public function clearCookie(string $name, ?string $path = '/', string $domain = null, bool $secure = false, bool $httpOnly = true, string $sameSite = null)
{
$sameSite = \func_num_args() > 5 ? func_get_arg(5) : null;
$this->setCookie(new Cookie($name, null, 1, $path, $domain, $secure, $httpOnly, false, $sameSite));
}
/**
* @see HeaderUtils::makeDisposition()
*/
public function makeDisposition($disposition, $filename, $filenameFallback = '')
public function makeDisposition(string $disposition, string $filename, string $filenameFallback = '')
{
return HeaderUtils::makeDisposition((string) $disposition, (string) $filename, (string) $filenameFallback);
return HeaderUtils::makeDisposition($disposition, $filename, $filenameFallback);
}
/**
@@ -303,8 +286,6 @@ class ResponseHeaderBag extends HeaderBag
private function initDate(): void
{
$now = \DateTime::createFromFormat('U', time());
$now->setTimezone(new \DateTimeZone('UTC'));
$this->set('Date', $now->format('D, d M Y H:i:s').' GMT');
$this->set('Date', gmdate('D, d M Y H:i:s').' GMT');
}
}

View File

@@ -13,6 +13,8 @@ namespace Symfony\Component\HttpFoundation\Session\Attribute;
/**
* This class relates to session attribute storage.
*
* @implements \IteratorAggregate<string, mixed>
*/
class AttributeBag implements AttributeBagInterface, \IteratorAggregate, \Countable
{
@@ -37,7 +39,7 @@ class AttributeBag implements AttributeBagInterface, \IteratorAggregate, \Counta
return $this->name;
}
public function setName($name)
public function setName(string $name)
{
$this->name = $name;
}
@@ -61,7 +63,7 @@ class AttributeBag implements AttributeBagInterface, \IteratorAggregate, \Counta
/**
* {@inheritdoc}
*/
public function has($name)
public function has(string $name)
{
return \array_key_exists($name, $this->attributes);
}
@@ -69,7 +71,7 @@ class AttributeBag implements AttributeBagInterface, \IteratorAggregate, \Counta
/**
* {@inheritdoc}
*/
public function get($name, $default = null)
public function get(string $name, $default = null)
{
return \array_key_exists($name, $this->attributes) ? $this->attributes[$name] : $default;
}
@@ -77,7 +79,7 @@ class AttributeBag implements AttributeBagInterface, \IteratorAggregate, \Counta
/**
* {@inheritdoc}
*/
public function set($name, $value)
public function set(string $name, $value)
{
$this->attributes[$name] = $value;
}
@@ -104,7 +106,7 @@ class AttributeBag implements AttributeBagInterface, \IteratorAggregate, \Counta
/**
* {@inheritdoc}
*/
public function remove($name)
public function remove(string $name)
{
$retval = null;
if (\array_key_exists($name, $this->attributes)) {
@@ -129,7 +131,7 @@ class AttributeBag implements AttributeBagInterface, \IteratorAggregate, \Counta
/**
* Returns an iterator for attributes.
*
* @return \ArrayIterator An \ArrayIterator instance
* @return \ArrayIterator<string, mixed>
*/
#[\ReturnTypeWillChange]
public function getIterator()
@@ -140,7 +142,7 @@ class AttributeBag implements AttributeBagInterface, \IteratorAggregate, \Counta
/**
* Returns the number of attributes.
*
* @return int The number of attributes
* @return int
*/
#[\ReturnTypeWillChange]
public function count()

View File

@@ -23,34 +23,30 @@ interface AttributeBagInterface extends SessionBagInterface
/**
* Checks if an attribute is defined.
*
* @param string $name The attribute name
*
* @return bool true if the attribute is defined, false otherwise
* @return bool
*/
public function has($name);
public function has(string $name);
/**
* Returns an attribute.
*
* @param string $name The attribute name
* @param mixed $default The default value if not found
* @param mixed $default The default value if not found
*
* @return mixed
*/
public function get($name, $default = null);
public function get(string $name, $default = null);
/**
* Sets an attribute.
*
* @param string $name
* @param mixed $value
* @param mixed $value
*/
public function set($name, $value);
public function set(string $name, $value);
/**
* Returns attributes.
*
* @return array
* @return array<string, mixed>
*/
public function all();
@@ -59,9 +55,7 @@ interface AttributeBagInterface extends SessionBagInterface
/**
* Removes an attribute.
*
* @param string $name
*
* @return mixed The removed value or null when it does not exist
*/
public function remove($name);
public function remove(string $name);
}

View File

@@ -11,11 +11,15 @@
namespace Symfony\Component\HttpFoundation\Session\Attribute;
trigger_deprecation('symfony/http-foundation', '5.3', 'The "%s" class is deprecated.', NamespacedAttributeBag::class);
/**
* This class provides structured storage of session attributes using
* a name spacing character in the key.
*
* @author Drak <drak@zikula.org>
*
* @deprecated since Symfony 5.3
*/
class NamespacedAttributeBag extends AttributeBag
{
@@ -34,7 +38,7 @@ class NamespacedAttributeBag extends AttributeBag
/**
* {@inheritdoc}
*/
public function has($name)
public function has(string $name)
{
// reference mismatch: if fixed, re-introduced in array_key_exists; keep as it is
$attributes = $this->resolveAttributePath($name);
@@ -50,7 +54,7 @@ class NamespacedAttributeBag extends AttributeBag
/**
* {@inheritdoc}
*/
public function get($name, $default = null)
public function get(string $name, $default = null)
{
// reference mismatch: if fixed, re-introduced in array_key_exists; keep as it is
$attributes = $this->resolveAttributePath($name);
@@ -66,7 +70,7 @@ class NamespacedAttributeBag extends AttributeBag
/**
* {@inheritdoc}
*/
public function set($name, $value)
public function set(string $name, $value)
{
$attributes = &$this->resolveAttributePath($name, true);
$name = $this->resolveKey($name);
@@ -76,7 +80,7 @@ class NamespacedAttributeBag extends AttributeBag
/**
* {@inheritdoc}
*/
public function remove($name)
public function remove(string $name)
{
$retval = null;
$attributes = &$this->resolveAttributePath($name);
@@ -99,7 +103,7 @@ class NamespacedAttributeBag extends AttributeBag
*
* @return array|null
*/
protected function &resolveAttributePath($name, $writeContext = false)
protected function &resolveAttributePath(string $name, bool $writeContext = false)
{
$array = &$this->attributes;
$name = (str_starts_with($name, $this->namespaceCharacter)) ? substr($name, 1) : $name;
@@ -144,11 +148,9 @@ class NamespacedAttributeBag extends AttributeBag
*
* This is the last part in a dot separated string.
*
* @param string $name
*
* @return string
*/
protected function resolveKey($name)
protected function resolveKey(string $name)
{
if (false !== $pos = strrpos($name, $this->namespaceCharacter)) {
$name = substr($name, $pos + 1);

View File

@@ -38,7 +38,7 @@ class AutoExpireFlashBag implements FlashBagInterface
return $this->name;
}
public function setName($name)
public function setName(string $name)
{
$this->name = $name;
}
@@ -60,7 +60,7 @@ class AutoExpireFlashBag implements FlashBagInterface
/**
* {@inheritdoc}
*/
public function add($type, $message)
public function add(string $type, $message)
{
$this->flashes['new'][$type][] = $message;
}
@@ -68,7 +68,7 @@ class AutoExpireFlashBag implements FlashBagInterface
/**
* {@inheritdoc}
*/
public function peek($type, array $default = [])
public function peek(string $type, array $default = [])
{
return $this->has($type) ? $this->flashes['display'][$type] : $default;
}
@@ -84,7 +84,7 @@ class AutoExpireFlashBag implements FlashBagInterface
/**
* {@inheritdoc}
*/
public function get($type, array $default = [])
public function get(string $type, array $default = [])
{
$return = $default;
@@ -122,7 +122,7 @@ class AutoExpireFlashBag implements FlashBagInterface
/**
* {@inheritdoc}
*/
public function set($type, $messages)
public function set(string $type, $messages)
{
$this->flashes['new'][$type] = (array) $messages;
}
@@ -130,7 +130,7 @@ class AutoExpireFlashBag implements FlashBagInterface
/**
* {@inheritdoc}
*/
public function has($type)
public function has(string $type)
{
return \array_key_exists($type, $this->flashes['display']) && $this->flashes['display'][$type];
}

View File

@@ -38,7 +38,7 @@ class FlashBag implements FlashBagInterface
return $this->name;
}
public function setName($name)
public function setName(string $name)
{
$this->name = $name;
}
@@ -54,7 +54,7 @@ class FlashBag implements FlashBagInterface
/**
* {@inheritdoc}
*/
public function add($type, $message)
public function add(string $type, $message)
{
$this->flashes[$type][] = $message;
}
@@ -62,7 +62,7 @@ class FlashBag implements FlashBagInterface
/**
* {@inheritdoc}
*/
public function peek($type, array $default = [])
public function peek(string $type, array $default = [])
{
return $this->has($type) ? $this->flashes[$type] : $default;
}
@@ -78,7 +78,7 @@ class FlashBag implements FlashBagInterface
/**
* {@inheritdoc}
*/
public function get($type, array $default = [])
public function get(string $type, array $default = [])
{
if (!$this->has($type)) {
return $default;
@@ -105,7 +105,7 @@ class FlashBag implements FlashBagInterface
/**
* {@inheritdoc}
*/
public function set($type, $messages)
public function set(string $type, $messages)
{
$this->flashes[$type] = (array) $messages;
}
@@ -121,7 +121,7 @@ class FlashBag implements FlashBagInterface
/**
* {@inheritdoc}
*/
public function has($type)
public function has(string $type)
{
return \array_key_exists($type, $this->flashes) && $this->flashes[$type];
}

View File

@@ -23,18 +23,16 @@ interface FlashBagInterface extends SessionBagInterface
/**
* Adds a flash message for the given type.
*
* @param string $type
* @param mixed $message
* @param mixed $message
*/
public function add($type, $message);
public function add(string $type, $message);
/**
* Registers one or more messages for a given type.
*
* @param string $type
* @param string|array $messages
*/
public function set($type, $messages);
public function set(string $type, $messages);
/**
* Gets flash messages for a given type.
@@ -44,7 +42,7 @@ interface FlashBagInterface extends SessionBagInterface
*
* @return array
*/
public function peek($type, array $default = []);
public function peek(string $type, array $default = []);
/**
* Gets all flash messages.
@@ -56,12 +54,11 @@ interface FlashBagInterface extends SessionBagInterface
/**
* Gets and clears flash from the stack.
*
* @param string $type
* @param array $default Default value if $type does not exist
* @param array $default Default value if $type does not exist
*
* @return array
*/
public function get($type, array $default = []);
public function get(string $type, array $default = []);
/**
* Gets and clears flashes from the stack.
@@ -78,11 +75,9 @@ interface FlashBagInterface extends SessionBagInterface
/**
* Has flash messages for a given type?
*
* @param string $type
*
* @return bool
*/
public function has($type);
public function has(string $type);
/**
* Returns a list of all defined types.

View File

@@ -26,6 +26,8 @@ class_exists(SessionBagProxy::class);
/**
* @author Fabien Potencier <fabien@symfony.com>
* @author Drak <drak@zikula.org>
*
* @implements \IteratorAggregate<string, mixed>
*/
class Session implements SessionInterface, \IteratorAggregate, \Countable
{
@@ -35,10 +37,12 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable
private $attributeName;
private $data = [];
private $usageIndex = 0;
private $usageReporter;
public function __construct(SessionStorageInterface $storage = null, AttributeBagInterface $attributes = null, FlashBagInterface $flashes = null)
public function __construct(SessionStorageInterface $storage = null, AttributeBagInterface $attributes = null, FlashBagInterface $flashes = null, callable $usageReporter = null)
{
$this->storage = $storage ?? new NativeSessionStorage();
$this->usageReporter = $usageReporter;
$attributes = $attributes ?? new AttributeBag();
$this->attributeName = $attributes->getName();
@@ -60,7 +64,7 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable
/**
* {@inheritdoc}
*/
public function has($name)
public function has(string $name)
{
return $this->getAttributeBag()->has($name);
}
@@ -68,7 +72,7 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable
/**
* {@inheritdoc}
*/
public function get($name, $default = null)
public function get(string $name, $default = null)
{
return $this->getAttributeBag()->get($name, $default);
}
@@ -76,7 +80,7 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable
/**
* {@inheritdoc}
*/
public function set($name, $value)
public function set(string $name, $value)
{
$this->getAttributeBag()->set($name, $value);
}
@@ -100,7 +104,7 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable
/**
* {@inheritdoc}
*/
public function remove($name)
public function remove(string $name)
{
return $this->getAttributeBag()->remove($name);
}
@@ -124,7 +128,7 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable
/**
* Returns an iterator for attributes.
*
* @return \ArrayIterator An \ArrayIterator instance
* @return \ArrayIterator<string, mixed>
*/
#[\ReturnTypeWillChange]
public function getIterator()
@@ -155,6 +159,9 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable
{
if ($this->isStarted()) {
++$this->usageIndex;
if ($this->usageReporter && 0 <= $this->usageIndex) {
($this->usageReporter)();
}
}
foreach ($this->data as &$data) {
if (!empty($data)) {
@@ -168,7 +175,7 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable
/**
* {@inheritdoc}
*/
public function invalidate($lifetime = null)
public function invalidate(int $lifetime = null)
{
$this->storage->clear();
@@ -178,7 +185,7 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable
/**
* {@inheritdoc}
*/
public function migrate($destroy = false, $lifetime = null)
public function migrate(bool $destroy = false, int $lifetime = null)
{
return $this->storage->regenerate($destroy, $lifetime);
}
@@ -202,7 +209,7 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable
/**
* {@inheritdoc}
*/
public function setId($id)
public function setId(string $id)
{
if ($this->storage->getId() !== $id) {
$this->storage->setId($id);
@@ -220,7 +227,7 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable
/**
* {@inheritdoc}
*/
public function setName($name)
public function setName(string $name)
{
$this->storage->setName($name);
}
@@ -231,6 +238,9 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable
public function getMetadataBag()
{
++$this->usageIndex;
if ($this->usageReporter && 0 <= $this->usageIndex) {
($this->usageReporter)();
}
return $this->storage->getMetadataBag();
}
@@ -240,13 +250,13 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable
*/
public function registerBag(SessionBagInterface $bag)
{
$this->storage->registerBag(new SessionBagProxy($bag, $this->data, $this->usageIndex));
$this->storage->registerBag(new SessionBagProxy($bag, $this->data, $this->usageIndex, $this->usageReporter));
}
/**
* {@inheritdoc}
*/
public function getBag($name)
public function getBag(string $name)
{
$bag = $this->storage->getBag($name);

View File

@@ -21,17 +21,22 @@ final class SessionBagProxy implements SessionBagInterface
private $bag;
private $data;
private $usageIndex;
private $usageReporter;
public function __construct(SessionBagInterface $bag, array &$data, ?int &$usageIndex)
public function __construct(SessionBagInterface $bag, array &$data, ?int &$usageIndex, ?callable $usageReporter)
{
$this->bag = $bag;
$this->data = &$data;
$this->usageIndex = &$usageIndex;
$this->usageReporter = $usageReporter;
}
public function getBag(): SessionBagInterface
{
++$this->usageIndex;
if ($this->usageReporter && 0 <= $this->usageIndex) {
($this->usageReporter)();
}
return $this->bag;
}
@@ -42,6 +47,9 @@ final class SessionBagProxy implements SessionBagInterface
return true;
}
++$this->usageIndex;
if ($this->usageReporter && 0 <= $this->usageIndex) {
($this->usageReporter)();
}
return empty($this->data[$this->bag->getStorageKey()]);
}
@@ -60,6 +68,10 @@ final class SessionBagProxy implements SessionBagInterface
public function initialize(array &$array): void
{
++$this->usageIndex;
if ($this->usageReporter && 0 <= $this->usageIndex) {
($this->usageReporter)();
}
$this->data[$this->bag->getStorageKey()] = &$array;
$this->bag->initialize($array);

View File

@@ -0,0 +1,40 @@
<?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;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Session\Storage\SessionStorageFactoryInterface;
// Help opcache.preload discover always-needed symbols
class_exists(Session::class);
/**
* @author Jérémy Derussé <jeremy@derusse.com>
*/
class SessionFactory implements SessionFactoryInterface
{
private $requestStack;
private $storageFactory;
private $usageReporter;
public function __construct(RequestStack $requestStack, SessionStorageFactoryInterface $storageFactory, callable $usageReporter = null)
{
$this->requestStack = $requestStack;
$this->storageFactory = $storageFactory;
$this->usageReporter = $usageReporter;
}
public function createSession(): SessionInterface
{
return new Session($this->storageFactory->createStorage($this->requestStack->getMainRequest()), null, null, $this->usageReporter);
}
}

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\Session;
/**
* @author Kevin Bond <kevinbond@gmail.com>
*/
interface SessionFactoryInterface
{
public function createSession(): SessionInterface;
}

View File

@@ -38,10 +38,8 @@ interface SessionInterface
/**
* Sets the session ID.
*
* @param string $id
*/
public function setId($id);
public function setId(string $id);
/**
* Returns the session name.
@@ -52,10 +50,8 @@ interface SessionInterface
/**
* Sets the session name.
*
* @param string $name
*/
public function setName($name);
public function setName(string $name);
/**
* Invalidates the current session.
@@ -70,7 +66,7 @@ interface SessionInterface
*
* @return bool
*/
public function invalidate($lifetime = null);
public function invalidate(int $lifetime = null);
/**
* Migrates the current session to a new session id while maintaining all
@@ -84,7 +80,7 @@ interface SessionInterface
*
* @return bool
*/
public function migrate($destroy = false, $lifetime = null);
public function migrate(bool $destroy = false, int $lifetime = null);
/**
* Force the session to be saved and closed.
@@ -98,29 +94,25 @@ interface SessionInterface
/**
* Checks if an attribute is defined.
*
* @param string $name The attribute name
*
* @return bool
*/
public function has($name);
public function has(string $name);
/**
* Returns an attribute.
*
* @param string $name The attribute name
* @param mixed $default The default value if not found
* @param mixed $default The default value if not found
*
* @return mixed
*/
public function get($name, $default = null);
public function get(string $name, $default = null);
/**
* Sets an attribute.
*
* @param string $name
* @param mixed $value
* @param mixed $value
*/
public function set($name, $value);
public function set(string $name, $value);
/**
* Returns attributes.
@@ -137,11 +129,9 @@ interface SessionInterface
/**
* Removes an attribute.
*
* @param string $name
*
* @return mixed The removed value or null when it does not exist
*/
public function remove($name);
public function remove(string $name);
/**
* Clears all attributes.
@@ -163,11 +153,9 @@ interface SessionInterface
/**
* Gets a bag instance by name.
*
* @param string $name
*
* @return SessionBagInterface
*/
public function getBag($name);
public function getBag(string $name);
/**
* Gets session meta.

View File

@@ -43,26 +43,19 @@ abstract class AbstractSessionHandler implements \SessionHandlerInterface, \Sess
}
/**
* @param string $sessionId
*
* @return string
*/
abstract protected function doRead($sessionId);
abstract protected function doRead(string $sessionId);
/**
* @param string $sessionId
* @param string $data
*
* @return bool
*/
abstract protected function doWrite($sessionId, $data);
abstract protected function doWrite(string $sessionId, string $data);
/**
* @param string $sessionId
*
* @return bool
*/
abstract protected function doDestroy($sessionId);
abstract protected function doDestroy(string $sessionId);
/**
* @return bool

View File

@@ -0,0 +1,42 @@
<?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;
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
/**
* @author Ahmed TAILOULOUTE <ahmed.tailouloute@gmail.com>
*/
class IdentityMarshaller implements MarshallerInterface
{
/**
* {@inheritdoc}
*/
public function marshall(array $values, ?array &$failed): array
{
foreach ($values as $key => $value) {
if (!\is_string($value)) {
throw new \LogicException(sprintf('%s accepts only string as data.', __METHOD__));
}
}
return $values;
}
/**
* {@inheritdoc}
*/
public function unmarshall(string $value): string
{
return $value;
}
}

View File

@@ -0,0 +1,108 @@
<?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;
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
/**
* @author Ahmed TAILOULOUTE <ahmed.tailouloute@gmail.com>
*/
class MarshallingSessionHandler implements \SessionHandlerInterface, \SessionUpdateTimestampHandlerInterface
{
private $handler;
private $marshaller;
public function __construct(AbstractSessionHandler $handler, MarshallerInterface $marshaller)
{
$this->handler = $handler;
$this->marshaller = $marshaller;
}
/**
* @return bool
*/
#[\ReturnTypeWillChange]
public function open($savePath, $name)
{
return $this->handler->open($savePath, $name);
}
/**
* @return bool
*/
#[\ReturnTypeWillChange]
public function close()
{
return $this->handler->close();
}
/**
* @return bool
*/
#[\ReturnTypeWillChange]
public function destroy($sessionId)
{
return $this->handler->destroy($sessionId);
}
/**
* @return int|false
*/
#[\ReturnTypeWillChange]
public function gc($maxlifetime)
{
return $this->handler->gc($maxlifetime);
}
/**
* @return string
*/
#[\ReturnTypeWillChange]
public function read($sessionId)
{
return $this->marshaller->unmarshall($this->handler->read($sessionId));
}
/**
* @return bool
*/
#[\ReturnTypeWillChange]
public function write($sessionId, $data)
{
$failed = [];
$marshalledData = $this->marshaller->marshall(['data' => $data], $failed);
if (isset($failed['data'])) {
return false;
}
return $this->handler->write($sessionId, $marshalledData['data']);
}
/**
* @return bool
*/
#[\ReturnTypeWillChange]
public function validateId($sessionId)
{
return $this->handler->validateId($sessionId);
}
/**
* @return bool
*/
#[\ReturnTypeWillChange]
public function updateTimestamp($sessionId, $data)
{
return $this->handler->updateTimestamp($sessionId, $data);
}
}

View File

@@ -38,7 +38,7 @@ class MemcachedSessionHandler extends AbstractSessionHandler
*
* 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.
* * ttl: The time to live in seconds.
*
* @throws \InvalidArgumentException When unsupported options are passed
*/
@@ -46,11 +46,11 @@ class MemcachedSessionHandler extends AbstractSessionHandler
{
$this->memcached = $memcached;
if ($diff = array_diff(array_keys($options), ['prefix', 'expiretime'])) {
if ($diff = array_diff(array_keys($options), ['prefix', 'expiretime', 'ttl'])) {
throw new \InvalidArgumentException(sprintf('The following options are not supported "%s".', implode(', ', $diff)));
}
$this->ttl = isset($options['expiretime']) ? (int) $options['expiretime'] : 86400;
$this->ttl = $options['expiretime'] ?? $options['ttl'] ?? null;
$this->prefix = $options['prefix'] ?? 'sf2s';
}
@@ -66,7 +66,7 @@ class MemcachedSessionHandler extends AbstractSessionHandler
/**
* {@inheritdoc}
*/
protected function doRead($sessionId)
protected function doRead(string $sessionId)
{
return $this->memcached->get($this->prefix.$sessionId) ?: '';
}
@@ -77,7 +77,7 @@ class MemcachedSessionHandler extends AbstractSessionHandler
#[\ReturnTypeWillChange]
public function updateTimestamp($sessionId, $data)
{
$this->memcached->touch($this->prefix.$sessionId, time() + $this->ttl);
$this->memcached->touch($this->prefix.$sessionId, $this->getCompatibleTtl());
return true;
}
@@ -85,15 +85,28 @@ class MemcachedSessionHandler extends AbstractSessionHandler
/**
* {@inheritdoc}
*/
protected function doWrite($sessionId, $data)
protected function doWrite(string $sessionId, string $data)
{
return $this->memcached->set($this->prefix.$sessionId, $data, time() + $this->ttl);
return $this->memcached->set($this->prefix.$sessionId, $data, $this->getCompatibleTtl());
}
private function getCompatibleTtl(): int
{
$ttl = (int) ($this->ttl ?? \ini_get('session.gc_maxlifetime'));
// If the relative TTL that is used exceeds 30 days, memcached will treat the value as Unix time.
// We have to convert it to an absolute Unix time at this point, to make sure the TTL is correct.
if ($ttl > 60 * 60 * 24 * 30) {
$ttl += time();
}
return $ttl;
}
/**
* {@inheritdoc}
*/
protected function doDestroy($sessionId)
protected function doDestroy(string $sessionId)
{
$result = $this->memcached->delete($this->prefix.$sessionId);

View File

@@ -22,7 +22,14 @@ namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
*/
class MigratingSessionHandler implements \SessionHandlerInterface, \SessionUpdateTimestampHandlerInterface
{
/**
* @var \SessionHandlerInterface&\SessionUpdateTimestampHandlerInterface
*/
private $currentHandler;
/**
* @var \SessionHandlerInterface&\SessionUpdateTimestampHandlerInterface
*/
private $writeOnlyHandler;
public function __construct(\SessionHandlerInterface $currentHandler, \SessionHandlerInterface $writeOnlyHandler)

View File

@@ -11,6 +11,11 @@
namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
use MongoDB\BSON\Binary;
use MongoDB\BSON\UTCDateTime;
use MongoDB\Client;
use MongoDB\Collection;
/**
* Session handler using the mongodb/mongodb package and MongoDB driver extension.
*
@@ -24,7 +29,7 @@ class MongoDbSessionHandler extends AbstractSessionHandler
private $mongo;
/**
* @var \MongoDB\Collection
* @var Collection
*/
private $collection;
@@ -63,7 +68,7 @@ class MongoDbSessionHandler extends AbstractSessionHandler
*
* @throws \InvalidArgumentException When "database" or "collection" not provided
*/
public function __construct(\MongoDB\Client $mongo, array $options)
public function __construct(Client $mongo, array $options)
{
if (!isset($options['database']) || !isset($options['collection'])) {
throw new \InvalidArgumentException('You must provide the "database" and "collection" option for MongoDBSessionHandler.');
@@ -91,7 +96,7 @@ class MongoDbSessionHandler extends AbstractSessionHandler
/**
* {@inheritdoc}
*/
protected function doDestroy($sessionId)
protected function doDestroy(string $sessionId)
{
$this->getCollection()->deleteOne([
$this->options['id_field'] => $sessionId,
@@ -107,21 +112,21 @@ class MongoDbSessionHandler extends AbstractSessionHandler
public function gc($maxlifetime)
{
return $this->getCollection()->deleteMany([
$this->options['expiry_field'] => ['$lt' => new \MongoDB\BSON\UTCDateTime()],
$this->options['expiry_field'] => ['$lt' => new UTCDateTime()],
])->getDeletedCount();
}
/**
* {@inheritdoc}
*/
protected function doWrite($sessionId, $data)
protected function doWrite(string $sessionId, string $data)
{
$expiry = new \MongoDB\BSON\UTCDateTime((time() + (int) \ini_get('session.gc_maxlifetime')) * 1000);
$expiry = new UTCDateTime((time() + (int) \ini_get('session.gc_maxlifetime')) * 1000);
$fields = [
$this->options['time_field'] => new \MongoDB\BSON\UTCDateTime(),
$this->options['time_field'] => new UTCDateTime(),
$this->options['expiry_field'] => $expiry,
$this->options['data_field'] => new \MongoDB\BSON\Binary($data, \MongoDB\BSON\Binary::TYPE_OLD_BINARY),
$this->options['data_field'] => new Binary($data, Binary::TYPE_OLD_BINARY),
];
$this->getCollection()->updateOne(
@@ -139,12 +144,12 @@ class MongoDbSessionHandler extends AbstractSessionHandler
#[\ReturnTypeWillChange]
public function updateTimestamp($sessionId, $data)
{
$expiry = new \MongoDB\BSON\UTCDateTime((time() + (int) \ini_get('session.gc_maxlifetime')) * 1000);
$expiry = new UTCDateTime((time() + (int) \ini_get('session.gc_maxlifetime')) * 1000);
$this->getCollection()->updateOne(
[$this->options['id_field'] => $sessionId],
['$set' => [
$this->options['time_field'] => new \MongoDB\BSON\UTCDateTime(),
$this->options['time_field'] => new UTCDateTime(),
$this->options['expiry_field'] => $expiry,
]]
);
@@ -155,11 +160,11 @@ class MongoDbSessionHandler extends AbstractSessionHandler
/**
* {@inheritdoc}
*/
protected function doRead($sessionId)
protected function doRead(string $sessionId)
{
$dbData = $this->getCollection()->findOne([
$this->options['id_field'] => $sessionId,
$this->options['expiry_field'] => ['$gte' => new \MongoDB\BSON\UTCDateTime()],
$this->options['expiry_field'] => ['$gte' => new UTCDateTime()],
]);
if (null === $dbData) {
@@ -169,7 +174,7 @@ class MongoDbSessionHandler extends AbstractSessionHandler
return $dbData[$this->options['data_field']]->getData();
}
private function getCollection(): \MongoDB\Collection
private function getCollection(): Collection
{
if (null === $this->collection) {
$this->collection = $this->mongo->selectCollection($this->options['database'], $this->options['collection']);
@@ -179,7 +184,7 @@ class MongoDbSessionHandler extends AbstractSessionHandler
}
/**
* @return \MongoDB\Client
* @return Client
*/
protected function getMongo()
{

View File

@@ -39,7 +39,7 @@ class NullSessionHandler extends AbstractSessionHandler
/**
* {@inheritdoc}
*/
protected function doRead($sessionId)
protected function doRead(string $sessionId)
{
return '';
}
@@ -56,7 +56,7 @@ class NullSessionHandler extends AbstractSessionHandler
/**
* {@inheritdoc}
*/
protected function doWrite($sessionId, $data)
protected function doWrite(string $sessionId, string $data)
{
return true;
}
@@ -64,7 +64,7 @@ class NullSessionHandler extends AbstractSessionHandler
/**
* {@inheritdoc}
*/
protected function doDestroy($sessionId)
protected function doDestroy(string $sessionId)
{
return true;
}

View File

@@ -73,57 +73,67 @@ class PdoSessionHandler extends AbstractSessionHandler
private $pdo;
/**
* @var string|false|null DSN string or null for session.save_path or false when lazy connection disabled
* DSN string or null for session.save_path or false when lazy connection disabled.
*
* @var string|false|null
*/
private $dsn = false;
/**
* @var string Database driver
* @var string|null
*/
private $driver;
/**
* @var string Table name
* @var string
*/
private $table = 'sessions';
/**
* @var string Column for session id
* @var string
*/
private $idCol = 'sess_id';
/**
* @var string Column for session data
* @var string
*/
private $dataCol = 'sess_data';
/**
* @var string Column for lifetime
* @var string
*/
private $lifetimeCol = 'sess_lifetime';
/**
* @var string Column for timestamp
* @var string
*/
private $timeCol = 'sess_time';
/**
* @var string Username when lazy-connect
* Username when lazy-connect.
*
* @var string
*/
private $username = '';
/**
* @var string Password when lazy-connect
* Password when lazy-connect.
*
* @var string
*/
private $password = '';
/**
* @var array Connection options when lazy-connect
* Connection options when lazy-connect.
*
* @var array
*/
private $connectionOptions = [];
/**
* @var int The strategy for locking, see constants
* The strategy for locking, see constants.
*
* @var int
*/
private $lockMode = self::LOCK_TRANSACTIONAL;
@@ -135,17 +145,23 @@ class PdoSessionHandler extends AbstractSessionHandler
private $unlockStatements = [];
/**
* @var bool True when the current session exists but expired according to session.gc_maxlifetime
* True when the current session exists but expired according to session.gc_maxlifetime.
*
* @var bool
*/
private $sessionExpired = false;
/**
* @var bool Whether a transaction is active
* Whether a transaction is active.
*
* @var bool
*/
private $inTransaction = false;
/**
* @var bool Whether gc() has been called
* Whether gc() has been called.
*
* @var bool
*/
private $gcCalled = false;
@@ -252,7 +268,7 @@ class PdoSessionHandler extends AbstractSessionHandler
*
* Can be used to distinguish between a new session and one that expired due to inactivity.
*
* @return bool Whether current session expired
* @return bool
*/
public function isSessionExpired()
{
@@ -305,7 +321,7 @@ class PdoSessionHandler extends AbstractSessionHandler
/**
* {@inheritdoc}
*/
protected function doDestroy($sessionId)
protected function doDestroy(string $sessionId)
{
// delete the record associated with this id
$sql = "DELETE FROM $this->table WHERE $this->idCol = :id";
@@ -326,7 +342,7 @@ class PdoSessionHandler extends AbstractSessionHandler
/**
* {@inheritdoc}
*/
protected function doWrite($sessionId, $data)
protected function doWrite(string $sessionId, string $data)
{
$maxlifetime = (int) \ini_get('session.gc_maxlifetime');
@@ -430,6 +446,7 @@ class PdoSessionHandler extends AbstractSessionHandler
if (false !== $this->dsn) {
$this->pdo = null; // only close lazy-connection
$this->driver = null;
}
return true;
@@ -491,10 +508,32 @@ class PdoSessionHandler extends AbstractSessionHandler
$driver = substr($driver, 4);
}
$dsn = null;
switch ($driver) {
case 'mysql':
$dsn = 'mysql:';
if ('' !== ($params['query'] ?? '')) {
$queryParams = [];
parse_str($params['query'], $queryParams);
if ('' !== ($queryParams['charset'] ?? '')) {
$dsn .= 'charset='.$queryParams['charset'].';';
}
if ('' !== ($queryParams['unix_socket'] ?? '')) {
$dsn .= 'unix_socket='.$queryParams['unix_socket'].';';
if (isset($params['path'])) {
$dbName = substr($params['path'], 1); // Remove the leading slash
$dsn .= 'dbname='.$dbName.';';
}
return $dsn;
}
}
// If "unix_socket" is not in the query, we continue with the same process as pgsql
// no break
case 'pgsql':
$dsn = $driver.':';
$dsn ?? $dsn = 'pgsql:';
if (isset($params['host']) && '' !== $params['host']) {
$dsn .= 'host='.$params['host'].';';
@@ -611,11 +650,9 @@ class PdoSessionHandler extends AbstractSessionHandler
* We need to make sure we do not return session data that is already considered garbage according
* to the session.gc_maxlifetime setting because gc() is called after read() and only sometimes.
*
* @param string $sessionId Session ID
*
* @return string The session data
* @return string
*/
protected function doRead($sessionId)
protected function doRead(string $sessionId)
{
if (self::LOCK_ADVISORY === $this->lockMode) {
$this->unlockStatements[] = $this->doAdvisoryLock($sessionId);

View File

@@ -54,7 +54,7 @@ class RedisSessionHandler extends AbstractSessionHandler
!$redis instanceof RedisProxy &&
!$redis instanceof RedisClusterProxy
) {
throw new \InvalidArgumentException(sprintf('"%s()" expects parameter 1 to be Redis, RedisArray, RedisCluster or Predis\ClientInterface, "%s" given.', __METHOD__, \is_object($redis) ? \get_class($redis) : \gettype($redis)));
throw new \InvalidArgumentException(sprintf('"%s()" expects parameter 1 to be Redis, RedisArray, RedisCluster or Predis\ClientInterface, "%s" given.', __METHOD__, get_debug_type($redis)));
}
if ($diff = array_diff(array_keys($options), ['prefix', 'ttl'])) {
@@ -69,7 +69,7 @@ class RedisSessionHandler extends AbstractSessionHandler
/**
* {@inheritdoc}
*/
protected function doRead($sessionId): string
protected function doRead(string $sessionId): string
{
return $this->redis->get($this->prefix.$sessionId) ?: '';
}
@@ -77,7 +77,7 @@ class RedisSessionHandler extends AbstractSessionHandler
/**
* {@inheritdoc}
*/
protected function doWrite($sessionId, $data): bool
protected function doWrite(string $sessionId, string $data): bool
{
$result = $this->redis->setEx($this->prefix.$sessionId, (int) ($this->ttl ?? \ini_get('session.gc_maxlifetime')), $data);
@@ -87,9 +87,21 @@ class RedisSessionHandler extends AbstractSessionHandler
/**
* {@inheritdoc}
*/
protected function doDestroy($sessionId): bool
protected function doDestroy(string $sessionId): bool
{
$this->redis->del($this->prefix.$sessionId);
static $unlink = true;
if ($unlink) {
try {
$unlink = false !== $this->redis->unlink($this->prefix.$sessionId);
} catch (\Throwable $e) {
$unlink = false;
}
}
if (!$unlink) {
$this->redis->del($this->prefix.$sessionId);
}
return true;
}

View File

@@ -27,7 +27,11 @@ class SessionHandlerFactory
public static function createHandler($connection): AbstractSessionHandler
{
if (!\is_string($connection) && !\is_object($connection)) {
throw new \TypeError(sprintf('Argument 1 passed to "%s()" must be a string or a connection object, "%s" given.', __METHOD__, \gettype($connection)));
throw new \TypeError(sprintf('Argument 1 passed to "%s()" must be a string or a connection object, "%s" given.', __METHOD__, get_debug_type($connection)));
}
if ($options = \is_string($connection) ? parse_url($connection) : false) {
parse_str($options['query'] ?? '', $options);
}
switch (true) {
@@ -46,7 +50,7 @@ class SessionHandlerFactory
return new PdoSessionHandler($connection);
case !\is_string($connection):
throw new \InvalidArgumentException(sprintf('Unsupported Connection: "%s".', \get_class($connection)));
throw new \InvalidArgumentException(sprintf('Unsupported Connection: "%s".', get_debug_type($connection)));
case str_starts_with($connection, 'file://'):
$savePath = substr($connection, 7);
@@ -61,7 +65,7 @@ class SessionHandlerFactory
$handlerClass = str_starts_with($connection, 'memcached:') ? MemcachedSessionHandler::class : RedisSessionHandler::class;
$connection = AbstractAdapter::createConnection($connection, ['lazy' => true]);
return new $handlerClass($connection);
return new $handlerClass($connection, array_intersect_key($options ?: [], ['prefix' => 1, 'ttl' => 1]));
case str_starts_with($connection, 'pdo_oci://'):
if (!class_exists(DriverManager::class)) {
@@ -79,7 +83,7 @@ class SessionHandlerFactory
case str_starts_with($connection, 'sqlsrv://'):
case str_starts_with($connection, 'sqlite://'):
case str_starts_with($connection, 'sqlite3://'):
return new PdoSessionHandler($connection);
return new PdoSessionHandler($connection, $options ?: []);
}
throw new \InvalidArgumentException(sprintf('Unsupported Connection: "%s".', $connection));

View File

@@ -24,7 +24,7 @@ class StrictSessionHandler extends AbstractSessionHandler
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));
throw new \LogicException(sprintf('"%s" is already an instance of "SessionUpdateTimestampHandlerInterface", you cannot wrap it with "%s".', get_debug_type($handler), self::class));
}
$this->handler = $handler;
@@ -54,7 +54,7 @@ class StrictSessionHandler extends AbstractSessionHandler
/**
* {@inheritdoc}
*/
protected function doRead($sessionId)
protected function doRead(string $sessionId)
{
return $this->handler->read($sessionId);
}
@@ -71,7 +71,7 @@ class StrictSessionHandler extends AbstractSessionHandler
/**
* {@inheritdoc}
*/
protected function doWrite($sessionId, $data)
protected function doWrite(string $sessionId, string $data)
{
return $this->handler->write($sessionId, $data);
}
@@ -91,7 +91,7 @@ class StrictSessionHandler extends AbstractSessionHandler
/**
* {@inheritdoc}
*/
protected function doDestroy($sessionId)
protected function doDestroy(string $sessionId)
{
$this->doDestroy = false;

View File

@@ -100,7 +100,7 @@ class MetadataBag implements SessionBagInterface
* to expire with browser session. Time is in seconds, and is
* not a Unix timestamp.
*/
public function stampNew($lifetime = null)
public function stampNew(int $lifetime = null)
{
$this->stampCreated($lifetime);
}
@@ -139,6 +139,7 @@ class MetadataBag implements SessionBagInterface
public function clear()
{
// nothing to do
return null;
}
/**
@@ -151,10 +152,8 @@ class MetadataBag implements SessionBagInterface
/**
* Sets name.
*
* @param string $name
*/
public function setName($name)
public function setName(string $name)
{
$this->name = $name;
}

View File

@@ -94,7 +94,7 @@ class MockArraySessionStorage implements SessionStorageInterface
/**
* {@inheritdoc}
*/
public function regenerate($destroy = false, $lifetime = null)
public function regenerate(bool $destroy = false, int $lifetime = null)
{
if (!$this->started) {
$this->start();
@@ -117,7 +117,7 @@ class MockArraySessionStorage implements SessionStorageInterface
/**
* {@inheritdoc}
*/
public function setId($id)
public function setId(string $id)
{
if ($this->started) {
throw new \LogicException('Cannot set session ID after the session has started.');
@@ -137,7 +137,7 @@ class MockArraySessionStorage implements SessionStorageInterface
/**
* {@inheritdoc}
*/
public function setName($name)
public function setName(string $name)
{
$this->name = $name;
}
@@ -183,7 +183,7 @@ class MockArraySessionStorage implements SessionStorageInterface
/**
* {@inheritdoc}
*/
public function getBag($name)
public function getBag(string $name)
{
if (!isset($this->bags[$name])) {
throw new \InvalidArgumentException(sprintf('The SessionBagInterface "%s" is not registered.', $name));

View File

@@ -28,8 +28,7 @@ class MockFileSessionStorage extends MockArraySessionStorage
private $savePath;
/**
* @param string $savePath Path of directory to save session files
* @param string $name Session name
* @param string|null $savePath Path of directory to save session files
*/
public function __construct(string $savePath = null, string $name = 'MOCKSESSID', MetadataBag $metaBag = null)
{
@@ -69,7 +68,7 @@ class MockFileSessionStorage extends MockArraySessionStorage
/**
* {@inheritdoc}
*/
public function regenerate($destroy = false, $lifetime = null)
public function regenerate(bool $destroy = false, int $lifetime = null)
{
if (!$this->started) {
$this->start();

View File

@@ -0,0 +1,42 @@
<?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;
use Symfony\Component\HttpFoundation\Request;
// Help opcache.preload discover always-needed symbols
class_exists(MockFileSessionStorage::class);
/**
* @author Jérémy Derussé <jeremy@derusse.com>
*/
class MockFileSessionStorageFactory implements SessionStorageFactoryInterface
{
private $savePath;
private $name;
private $metaBag;
/**
* @see MockFileSessionStorage constructor.
*/
public function __construct(string $savePath = null, string $name = 'MOCKSESSID', MetadataBag $metaBag = null)
{
$this->savePath = $savePath;
$this->name = $name;
$this->metaBag = $metaBag;
}
public function createStorage(?Request $request): SessionStorageInterface
{
return new MockFileSessionStorage($this->savePath, $this->name, $this->metaBag);
}
}

View File

@@ -90,13 +90,6 @@ class NativeSessionStorage implements SessionStorageInterface
* use_cookies, "1"
* use_only_cookies, "1"
* use_trans_sid, "0"
* upload_progress.enabled, "1"
* upload_progress.cleanup, "1"
* upload_progress.prefix, "upload_progress_"
* upload_progress.name, "PHP_SESSION_UPLOAD_PROGRESS"
* upload_progress.freq, "1%"
* upload_progress.min-freq, "1"
* url_rewriter.tags, "a=href,area=href,frame=src,form=,fieldset="
* sid_length, "32"
* sid_bits_per_character, "5"
* trans_sid_hosts, $_SERVER['HTTP_HOST']
@@ -216,7 +209,7 @@ class NativeSessionStorage implements SessionStorageInterface
/**
* {@inheritdoc}
*/
public function setId($id)
public function setId(string $id)
{
$this->saveHandler->setId($id);
}
@@ -232,7 +225,7 @@ class NativeSessionStorage implements SessionStorageInterface
/**
* {@inheritdoc}
*/
public function setName($name)
public function setName(string $name)
{
$this->saveHandler->setName($name);
}
@@ -240,7 +233,7 @@ class NativeSessionStorage implements SessionStorageInterface
/**
* {@inheritdoc}
*/
public function regenerate($destroy = false, $lifetime = null)
public function regenerate(bool $destroy = false, int $lifetime = null)
{
// Cannot regenerate the session ID for non-active sessions.
if (\PHP_SESSION_ACTIVE !== session_status()) {
@@ -347,7 +340,7 @@ class NativeSessionStorage implements SessionStorageInterface
/**
* {@inheritdoc}
*/
public function getBag($name)
public function getBag(string $name)
{
if (!isset($this->bags[$name])) {
throw new \InvalidArgumentException(sprintf('The SessionBagInterface "%s" is not registered.', $name));
@@ -419,6 +412,13 @@ class NativeSessionStorage implements SessionStorageInterface
foreach ($options as $key => $value) {
if (isset($validOptions[$key])) {
if (str_starts_with($key, 'upload_progress.')) {
trigger_deprecation('symfony/http-foundation', '5.4', 'Support for the "%s" session option is deprecated. The settings prefixed with "session.upload_progress." can not be changed at runtime.', $key);
continue;
}
if ('url_rewriter.tags' === $key) {
trigger_deprecation('symfony/http-foundation', '5.4', 'Support for the "%s" session option is deprecated. Use "trans_sid_tags" instead.', $key);
}
if ('cookie_samesite' === $key && \PHP_VERSION_ID < 70300) {
// PHP < 7.3 does not support same_site cookies. We will emulate it in
// the start() method instead.

View File

@@ -0,0 +1,49 @@
<?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;
use Symfony\Component\HttpFoundation\Request;
// Help opcache.preload discover always-needed symbols
class_exists(NativeSessionStorage::class);
/**
* @author Jérémy Derussé <jeremy@derusse.com>
*/
class NativeSessionStorageFactory implements SessionStorageFactoryInterface
{
private $options;
private $handler;
private $metaBag;
private $secure;
/**
* @see NativeSessionStorage constructor.
*/
public function __construct(array $options = [], $handler = null, MetadataBag $metaBag = null, bool $secure = false)
{
$this->options = $options;
$this->handler = $handler;
$this->metaBag = $metaBag;
$this->secure = $secure;
}
public function createStorage(?Request $request): SessionStorageInterface
{
$storage = new NativeSessionStorage($this->options, $this->handler, $this->metaBag);
if ($this->secure && $request && $request->isSecure()) {
$storage->setOptions(['cookie_secure' => true]);
}
return $storage;
}
}

View File

@@ -0,0 +1,47 @@
<?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;
use Symfony\Component\HttpFoundation\Request;
// Help opcache.preload discover always-needed symbols
class_exists(PhpBridgeSessionStorage::class);
/**
* @author Jérémy Derussé <jeremy@derusse.com>
*/
class PhpBridgeSessionStorageFactory implements SessionStorageFactoryInterface
{
private $handler;
private $metaBag;
private $secure;
/**
* @see PhpBridgeSessionStorage constructor.
*/
public function __construct($handler = null, MetadataBag $metaBag = null, bool $secure = false)
{
$this->handler = $handler;
$this->metaBag = $metaBag;
$this->secure = $secure;
}
public function createStorage(?Request $request): SessionStorageInterface
{
$storage = new PhpBridgeSessionStorage($this->handler, $this->metaBag);
if ($this->secure && $request && $request->isSecure()) {
$storage->setOptions(['cookie_secure' => true]);
}
return $storage;
}
}

View File

@@ -81,11 +81,9 @@ abstract class AbstractProxy
/**
* Sets the session ID.
*
* @param string $id
*
* @throws \LogicException
*/
public function setId($id)
public function setId(string $id)
{
if ($this->isActive()) {
throw new \LogicException('Cannot change the ID of an active session.');
@@ -107,11 +105,9 @@ abstract class AbstractProxy
/**
* Sets the session name.
*
* @param string $name
*
* @throws \LogicException
*/
public function setName($name)
public function setName(string $name)
{
if ($this->isActive()) {
throw new \LogicException('Cannot change the name of an active session.');

View File

@@ -0,0 +1,38 @@
<?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;
use Symfony\Component\HttpFoundation\Request;
/**
* @author Jérémy Derussé <jeremy@derusse.com>
*
* @internal to be removed in Symfony 6
*/
final class ServiceSessionFactory implements SessionStorageFactoryInterface
{
private $storage;
public function __construct(SessionStorageInterface $storage)
{
$this->storage = $storage;
}
public function createStorage(?Request $request): SessionStorageInterface
{
if ($this->storage instanceof NativeSessionStorage && $request && $request->isSecure()) {
$this->storage->setOptions(['cookie_secure' => true]);
}
return $this->storage;
}
}

View File

@@ -0,0 +1,25 @@
<?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;
use Symfony\Component\HttpFoundation\Request;
/**
* @author Jérémy Derussé <jeremy@derusse.com>
*/
interface SessionStorageFactoryInterface
{
/**
* Creates a new instance of SessionStorageInterface.
*/
public function createStorage(?Request $request): SessionStorageInterface;
}

View File

@@ -24,7 +24,7 @@ interface SessionStorageInterface
/**
* Starts the session.
*
* @return bool True if started
* @return bool
*
* @throws \RuntimeException if something goes wrong starting the session
*/
@@ -33,37 +33,33 @@ interface SessionStorageInterface
/**
* Checks if the session is started.
*
* @return bool True if started, false otherwise
* @return bool
*/
public function isStarted();
/**
* Returns the session ID.
*
* @return string The session ID or empty
* @return string
*/
public function getId();
/**
* Sets the session ID.
*
* @param string $id
*/
public function setId($id);
public function setId(string $id);
/**
* Returns the session name.
*
* @return mixed The session name
* @return string
*/
public function getName();
/**
* Sets the session name.
*
* @param string $name
*/
public function setName($name);
public function setName(string $name);
/**
* Regenerates id that represents this storage.
@@ -90,11 +86,11 @@ interface SessionStorageInterface
* to expire with browser session. Time is in seconds, and is
* not a Unix timestamp.
*
* @return bool True if session regenerated, false if error
* @return bool
*
* @throws \RuntimeException If an error occurs while regenerating this storage
*/
public function regenerate($destroy = false, $lifetime = null);
public function regenerate(bool $destroy = false, int $lifetime = null);
/**
* Force the session to be saved and closed.
@@ -117,13 +113,11 @@ interface SessionStorageInterface
/**
* Gets a SessionBagInterface by name.
*
* @param string $name
*
* @return SessionBagInterface
*
* @throws \InvalidArgumentException If the bag does not exist
*/
public function getBag($name);
public function getBag(string $name);
/**
* Registers a SessionBagInterface for use.

View File

@@ -30,11 +30,6 @@ class StreamedResponse extends Response
protected $streamed;
private $headersSent;
/**
* @param callable|null $callback A valid PHP callback or null to set it later
* @param int $status The response status code
* @param array $headers An array of response headers
*/
public function __construct(callable $callback = null, int $status = 200, array $headers = [])
{
parent::__construct(null, $status, $headers);
@@ -50,13 +45,15 @@ class StreamedResponse extends Response
* Factory method for chainability.
*
* @param callable|null $callback A valid PHP callback or null to set it later
* @param int $status The response status code
* @param array $headers An array of response headers
*
* @return static
*
* @deprecated since Symfony 5.1, use __construct() instead.
*/
public static function create($callback = null, $status = 200, $headers = [])
public static function create($callback = null, int $status = 200, array $headers = [])
{
trigger_deprecation('symfony/http-foundation', '5.1', 'The "%s()" method is deprecated, use "new %s()" instead.', __METHOD__, static::class);
return new static($callback, $status, $headers);
}
@@ -121,7 +118,7 @@ class StreamedResponse extends Response
*
* @return $this
*/
public function setContent($content)
public function setContent(?string $content)
{
if (null !== $content) {
throw new \LogicException('The content cannot be set on a StreamedResponse instance.');

View File

@@ -0,0 +1,71 @@
<?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\Test\Constraint;
use PHPUnit\Framework\Constraint\Constraint;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* Asserts that the response is in the given format.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
final class ResponseFormatSame extends Constraint
{
private $request;
private $format;
public function __construct(Request $request, ?string $format)
{
$this->request = $request;
$this->format = $format;
}
/**
* {@inheritdoc}
*/
public function toString(): string
{
return 'format is '.($this->format ?? 'null');
}
/**
* @param Response $response
*
* {@inheritdoc}
*/
protected function matches($response): bool
{
return $this->format === $this->request->getFormat($response->headers->get('Content-Type'));
}
/**
* @param Response $response
*
* {@inheritdoc}
*/
protected function failureDescription($response): string
{
return 'the Response '.$this->toString();
}
/**
* @param Response $response
*
* {@inheritdoc}
*/
protected function additionalFailureDescription($response): string
{
return (string) $response;
}
}

View File

@@ -0,0 +1,56 @@
<?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\Test\Constraint;
use PHPUnit\Framework\Constraint\Constraint;
use Symfony\Component\HttpFoundation\Response;
final class ResponseIsUnprocessable extends Constraint
{
/**
* {@inheritdoc}
*/
public function toString(): string
{
return 'is unprocessable';
}
/**
* @param Response $other
*
* {@inheritdoc}
*/
protected function matches($other): bool
{
return Response::HTTP_UNPROCESSABLE_ENTITY === $other->getStatusCode();
}
/**
* @param Response $other
*
* {@inheritdoc}
*/
protected function failureDescription($other): string
{
return 'the Response '.$this->toString();
}
/**
* @param Response $other
*
* {@inheritdoc}
*/
protected function additionalFailureDescription($other): string
{
return (string) $other;
}
}

View File

@@ -35,7 +35,7 @@ final class UrlHelper
return $path;
}
if (null === $request = $this->requestStack->getMasterRequest()) {
if (null === $request = $this->requestStack->getMainRequest()) {
return $this->getAbsoluteUrlFromContext($path);
}
@@ -64,7 +64,7 @@ final class UrlHelper
return $path;
}
if (null === $request = $this->requestStack->getMasterRequest()) {
if (null === $request = $this->requestStack->getMainRequest()) {
return $path;
}

View File

@@ -16,14 +16,22 @@
}
],
"require": {
"php": ">=7.1.3",
"symfony/mime": "^4.3|^5.0",
"php": ">=7.2.5",
"symfony/deprecation-contracts": "^2.1|^3",
"symfony/polyfill-mbstring": "~1.1",
"symfony/polyfill-php80": "^1.16"
},
"require-dev": {
"predis/predis": "~1.0",
"symfony/expression-language": "^3.4|^4.0|^5.0"
"symfony/cache": "^4.4|^5.0|^6.0",
"symfony/dependency-injection": "^5.4|^6.0",
"symfony/http-kernel": "^5.4.12|^6.0.12|^6.1.4",
"symfony/mime": "^4.4|^5.0|^6.0",
"symfony/expression-language": "^4.4|^5.0|^6.0",
"symfony/rate-limiter": "^5.2|^6.0"
},
"suggest" : {
"symfony/mime": "To use the file extension guesser"
},
"autoload": {
"psr-4": { "Symfony\\Component\\HttpFoundation\\": "" },