Move into nested docroot

This commit is contained in:
Rob Davies 2017-02-13 15:31:17 +00:00
parent 83a0d3a149
commit c8b70abde9
13405 changed files with 0 additions and 0 deletions

View file

@ -0,0 +1,3 @@
vendor/
composer.lock
phpunit.xml

View file

@ -0,0 +1,172 @@
<?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;
/**
* Represents an Accept-* header.
*
* An accept header is compound with a list of items,
* sorted by descending quality.
*
* @author Jean-François Simon <contact@jfsimon.fr>
*/
class AcceptHeader
{
/**
* @var AcceptHeaderItem[]
*/
private $items = array();
/**
* @var bool
*/
private $sorted = true;
/**
* Constructor.
*
* @param AcceptHeaderItem[] $items
*/
public function __construct(array $items)
{
foreach ($items as $item) {
$this->add($item);
}
}
/**
* Builds an AcceptHeader instance from a string.
*
* @param string $headerValue
*
* @return self
*/
public static function fromString($headerValue)
{
$index = 0;
return new self(array_map(function ($itemValue) use (&$index) {
$item = AcceptHeaderItem::fromString($itemValue);
$item->setIndex($index++);
return $item;
}, preg_split('/\s*(?:,*("[^"]+"),*|,*(\'[^\']+\'),*|,+)\s*/', $headerValue, 0, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE)));
}
/**
* Returns header value's string representation.
*
* @return string
*/
public function __toString()
{
return implode(',', $this->items);
}
/**
* Tests if header has given value.
*
* @param string $value
*
* @return bool
*/
public function has($value)
{
return isset($this->items[$value]);
}
/**
* Returns given value's item, if exists.
*
* @param string $value
*
* @return AcceptHeaderItem|null
*/
public function get($value)
{
return isset($this->items[$value]) ? $this->items[$value] : null;
}
/**
* Adds an item.
*
* @param AcceptHeaderItem $item
*
* @return $this
*/
public function add(AcceptHeaderItem $item)
{
$this->items[$item->getValue()] = $item;
$this->sorted = false;
return $this;
}
/**
* Returns all items.
*
* @return AcceptHeaderItem[]
*/
public function all()
{
$this->sort();
return $this->items;
}
/**
* Filters items on their value using given regex.
*
* @param string $pattern
*
* @return self
*/
public function filter($pattern)
{
return new self(array_filter($this->items, function (AcceptHeaderItem $item) use ($pattern) {
return preg_match($pattern, $item->getValue());
}));
}
/**
* Returns first item.
*
* @return AcceptHeaderItem|null
*/
public function first()
{
$this->sort();
return !empty($this->items) ? reset($this->items) : null;
}
/**
* Sorts items by descending quality.
*/
private function sort()
{
if (!$this->sorted) {
uasort($this->items, function ($a, $b) {
$qA = $a->getQuality();
$qB = $b->getQuality();
if ($qA === $qB) {
return $a->getIndex() > $b->getIndex() ? 1 : -1;
}
return $qA > $qB ? -1 : 1;
});
$this->sorted = true;
}
}
}

View file

@ -0,0 +1,226 @@
<?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;
/**
* Represents an Accept-* header item.
*
* @author Jean-François Simon <contact@jfsimon.fr>
*/
class AcceptHeaderItem
{
/**
* @var string
*/
private $value;
/**
* @var float
*/
private $quality = 1.0;
/**
* @var int
*/
private $index = 0;
/**
* @var array
*/
private $attributes = array();
/**
* Constructor.
*
* @param string $value
* @param array $attributes
*/
public function __construct($value, array $attributes = array())
{
$this->value = $value;
foreach ($attributes as $name => $value) {
$this->setAttribute($name, $value);
}
}
/**
* Builds an AcceptHeaderInstance instance from a string.
*
* @param string $itemValue
*
* @return self
*/
public static function fromString($itemValue)
{
$bits = preg_split('/\s*(?:;*("[^"]+");*|;*(\'[^\']+\');*|;+)\s*/', $itemValue, 0, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
$value = array_shift($bits);
$attributes = array();
$lastNullAttribute = null;
foreach ($bits as $bit) {
if (($start = substr($bit, 0, 1)) === ($end = substr($bit, -1)) && ($start === '"' || $start === '\'')) {
$attributes[$lastNullAttribute] = substr($bit, 1, -1);
} elseif ('=' === $end) {
$lastNullAttribute = $bit = substr($bit, 0, -1);
$attributes[$bit] = null;
} else {
$parts = explode('=', $bit);
$attributes[$parts[0]] = isset($parts[1]) && strlen($parts[1]) > 0 ? $parts[1] : '';
}
}
return new self(($start = substr($value, 0, 1)) === ($end = substr($value, -1)) && ($start === '"' || $start === '\'') ? substr($value, 1, -1) : $value, $attributes);
}
/**
* Returns header value's string representation.
*
* @return string
*/
public function __toString()
{
$string = $this->value.($this->quality < 1 ? ';q='.$this->quality : '');
if (count($this->attributes) > 0) {
$string .= ';'.implode(';', array_map(function ($name, $value) {
return sprintf(preg_match('/[,;=]/', $value) ? '%s="%s"' : '%s=%s', $name, $value);
}, array_keys($this->attributes), $this->attributes));
}
return $string;
}
/**
* Set the item value.
*
* @param string $value
*
* @return $this
*/
public function setValue($value)
{
$this->value = $value;
return $this;
}
/**
* Returns the item value.
*
* @return string
*/
public function getValue()
{
return $this->value;
}
/**
* Set the item quality.
*
* @param float $quality
*
* @return $this
*/
public function setQuality($quality)
{
$this->quality = $quality;
return $this;
}
/**
* Returns the item quality.
*
* @return float
*/
public function getQuality()
{
return $this->quality;
}
/**
* Set the item index.
*
* @param int $index
*
* @return $this
*/
public function setIndex($index)
{
$this->index = $index;
return $this;
}
/**
* Returns the item index.
*
* @return int
*/
public function getIndex()
{
return $this->index;
}
/**
* Tests if an attribute exists.
*
* @param string $name
*
* @return bool
*/
public function hasAttribute($name)
{
return isset($this->attributes[$name]);
}
/**
* Returns an attribute by its name.
*
* @param string $name
* @param mixed $default
*
* @return mixed
*/
public function getAttribute($name, $default = null)
{
return isset($this->attributes[$name]) ? $this->attributes[$name] : $default;
}
/**
* Returns all attributes.
*
* @return array
*/
public function getAttributes()
{
return $this->attributes;
}
/**
* Set an attribute.
*
* @param string $name
* @param string $value
*
* @return $this
*/
public function setAttribute($name, $value)
{
if ('q' === $name) {
$this->quality = (float) $value;
} else {
$this->attributes[$name] = (string) $value;
}
return $this;
}
}

View file

@ -0,0 +1,43 @@
<?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;
/**
* Request represents an HTTP request from an Apache server.
*
* @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 (false === strpos($this->server->get('REQUEST_URI'), $baseUrl)) {
// assume mod_rewrite
return rtrim(dirname($baseUrl), '/\\');
}
return $baseUrl;
}
}

View file

@ -0,0 +1,359 @@
<?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\File\File;
use Symfony\Component\HttpFoundation\File\Exception\FileException;
/**
* BinaryFileResponse represents an HTTP response delivering a file.
*
* @author Niklas Fiekas <niklas.fiekas@tu-clausthal.de>
* @author stealth35 <stealth35-php@live.fr>
* @author Igor Wiedler <igor@wiedler.ch>
* @author Jordan Alliot <jordan.alliot@gmail.com>
* @author Sergey Linnik <linniksa@gmail.com>
*/
class BinaryFileResponse extends Response
{
protected static $trustXSendfileTypeHeader = false;
/**
* @var File
*/
protected $file;
protected $offset;
protected $maxlen;
protected $deleteFileAfterSend = false;
/**
* Constructor.
*
* @param \SplFileInfo|string $file The file to stream
* @param int $status The response status code
* @param array $headers An array of response headers
* @param bool $public Files are public by default
* @param null|string $contentDisposition The type of Content-Disposition to set automatically with the filename
* @param bool $autoEtag Whether the ETag header should be automatically set
* @param bool $autoLastModified Whether the Last-Modified header should be automatically set
*/
public function __construct($file, $status = 200, $headers = array(), $public = true, $contentDisposition = null, $autoEtag = false, $autoLastModified = true)
{
parent::__construct(null, $status, $headers);
$this->setFile($file, $contentDisposition, $autoEtag, $autoLastModified);
if ($public) {
$this->setPublic();
}
}
/**
* @param \SplFileInfo|string $file The file to stream
* @param int $status The response status code
* @param array $headers An array of response headers
* @param bool $public Files are public by default
* @param null|string $contentDisposition The type of Content-Disposition to set automatically with the filename
* @param bool $autoEtag Whether the ETag header should be automatically set
* @param bool $autoLastModified Whether the Last-Modified header should be automatically set
*
* @return static
*/
public static function create($file = null, $status = 200, $headers = array(), $public = true, $contentDisposition = null, $autoEtag = false, $autoLastModified = true)
{
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
*
* @return $this
*
* @throws FileException
*/
public function setFile($file, $contentDisposition = null, $autoEtag = false, $autoLastModified = true)
{
if (!$file instanceof File) {
if ($file instanceof \SplFileInfo) {
$file = new File($file->getPathname());
} else {
$file = new File((string) $file);
}
}
if (!$file->isReadable()) {
throw new FileException('File must be readable.');
}
$this->file = $file;
if ($autoEtag) {
$this->setAutoEtag();
}
if ($autoLastModified) {
$this->setAutoLastModified();
}
if ($contentDisposition) {
$this->setContentDisposition($contentDisposition);
}
return $this;
}
/**
* Gets the file.
*
* @return File The file to stream
*/
public function getFile()
{
return $this->file;
}
/**
* Automatically sets the Last-Modified header according the file modification date.
*/
public function setAutoLastModified()
{
$this->setLastModified(\DateTime::createFromFormat('U', $this->file->getMTime()));
return $this;
}
/**
* Automatically sets the ETag header according to the checksum of the file.
*/
public function setAutoEtag()
{
$this->setEtag(sha1_file($this->file->getPathname()));
return $this;
}
/**
* Sets the Content-Disposition header with the given filename.
*
* @param string $disposition ResponseHeaderBag::DISPOSITION_INLINE or ResponseHeaderBag::DISPOSITION_ATTACHMENT
* @param string $filename Optionally use this filename instead of the real name of the file
* @param string $filenameFallback A fallback filename, containing only ASCII characters. Defaults to an automatically encoded filename
*
* @return $this
*/
public function setContentDisposition($disposition, $filename = '', $filenameFallback = '')
{
if ($filename === '') {
$filename = $this->file->getFilename();
}
if ('' === $filenameFallback && (!preg_match('/^[\x20-\x7e]*$/', $filename) || false !== strpos($filename, '%'))) {
$encoding = mb_detect_encoding($filename, null, true);
for ($i = 0; $i < mb_strlen($filename, $encoding); ++$i) {
$char = mb_substr($filename, $i, 1, $encoding);
if ('%' === $char || ord($char) < 32 || ord($char) > 126) {
$filenameFallback .= '_';
} else {
$filenameFallback .= $char;
}
}
}
$dispositionHeader = $this->headers->makeDisposition($disposition, $filename, $filenameFallback);
$this->headers->set('Content-Disposition', $dispositionHeader);
return $this;
}
/**
* {@inheritdoc}
*/
public function prepare(Request $request)
{
$this->headers->set('Content-Length', $this->file->getSize());
if (!$this->headers->has('Accept-Ranges')) {
// Only accept ranges on safe HTTP methods
$this->headers->set('Accept-Ranges', $request->isMethodSafe(false) ? 'bytes' : 'none');
}
if (!$this->headers->has('Content-Type')) {
$this->headers->set('Content-Type', $this->file->getMimeType() ?: 'application/octet-stream');
}
if ('HTTP/1.0' !== $request->server->get('SERVER_PROTOCOL')) {
$this->setProtocolVersion('1.1');
}
$this->ensureIEOverSSLCompatibility($request);
$this->offset = 0;
$this->maxlen = -1;
if (self::$trustXSendfileTypeHeader && $request->headers->has('X-Sendfile-Type')) {
// Use X-Sendfile, do not send any content.
$type = $request->headers->get('X-Sendfile-Type');
$path = $this->file->getRealPath();
// Fall back to scheme://path for stream wrapped locations.
if (false === $path) {
$path = $this->file->getPathname();
}
if (strtolower($type) === 'x-accel-redirect') {
// Do X-Accel-Mapping substitutions.
// @link http://wiki.nginx.org/X-accel#X-Accel-Redirect
foreach (explode(',', $request->headers->get('X-Accel-Mapping', '')) as $mapping) {
$mapping = explode('=', $mapping, 2);
if (2 === count($mapping)) {
$pathPrefix = trim($mapping[0]);
$location = trim($mapping[1]);
if (substr($path, 0, strlen($pathPrefix)) === $pathPrefix) {
$path = $location.substr($path, strlen($pathPrefix));
break;
}
}
}
}
$this->headers->set($type, $path);
$this->maxlen = 0;
} elseif ($request->headers->has('Range')) {
// Process the range headers.
if (!$request->headers->has('If-Range') || $this->hasValidIfRangeHeader($request->headers->get('If-Range'))) {
$range = $request->headers->get('Range');
$fileSize = $this->file->getSize();
list($start, $end) = explode('-', substr($range, 6), 2) + array(0);
$end = ('' === $end) ? $fileSize - 1 : (int) $end;
if ('' === $start) {
$start = $fileSize - $end;
$end = $fileSize - 1;
} else {
$start = (int) $start;
}
if ($start <= $end) {
if ($start < 0 || $end > $fileSize - 1) {
$this->setStatusCode(416);
$this->headers->set('Content-Range', sprintf('bytes */%s', $fileSize));
} elseif ($start !== 0 || $end !== $fileSize - 1) {
$this->maxlen = $end < $fileSize ? $end - $start + 1 : -1;
$this->offset = $start;
$this->setStatusCode(206);
$this->headers->set('Content-Range', sprintf('bytes %s-%s/%s', $start, $end, $fileSize));
$this->headers->set('Content-Length', $end - $start + 1);
}
}
}
}
return $this;
}
private function hasValidIfRangeHeader($header)
{
if ($this->getEtag() === $header) {
return true;
}
if (null === $lastModified = $this->getLastModified()) {
return false;
}
return $lastModified->format('D, d M Y H:i:s').' GMT' === $header;
}
/**
* Sends the file.
*
* {@inheritdoc}
*/
public function sendContent()
{
if (!$this->isSuccessful()) {
return parent::sendContent();
}
if (0 === $this->maxlen) {
return $this;
}
$out = fopen('php://output', 'wb');
$file = fopen($this->file->getPathname(), 'rb');
stream_copy_to_stream($file, $out, $this->maxlen, $this->offset);
fclose($out);
fclose($file);
if ($this->deleteFileAfterSend) {
unlink($this->file->getPathname());
}
return $this;
}
/**
* {@inheritdoc}
*
* @throws \LogicException when the content is not null
*/
public function setContent($content)
{
if (null !== $content) {
throw new \LogicException('The content cannot be set on a BinaryFileResponse instance.');
}
}
/**
* {@inheritdoc}
*
* @return false
*/
public function getContent()
{
return false;
}
/**
* Trust X-Sendfile-Type header.
*/
public static function trustXSendfileTypeHeader()
{
self::$trustXSendfileTypeHeader = true;
}
/**
* If this is set to true, the file will be unlinked after the request is send
* Note: If the X-Sendfile header is used, the deleteFileAfterSend setting will not be used.
*
* @param bool $shouldDelete
*
* @return $this
*/
public function deleteFileAfterSend($shouldDelete)
{
$this->deleteFileAfterSend = $shouldDelete;
return $this;
}
}

View file

@ -0,0 +1,128 @@
CHANGELOG
=========
2.8.0
-----
* Finding deep items in `ParameterBag::get()` is deprecated since version 2.8 and
will be removed in 3.0.
2.6.0
-----
* PdoSessionHandler changes
- implemented different session locking strategies to prevent loss of data by concurrent access to the same session
- [BC BREAK] save session data in a binary column without base64_encode
- [BC BREAK] added lifetime column to the session table which allows to have different lifetimes for each session
- implemented lazy connections that are only opened when a session is used by either passing a dsn string
explicitly or falling back to session.save_path ini setting
- added a createTable method that initializes a correctly defined table depending on the database vendor
2.5.0
-----
* added `JsonResponse::setEncodingOptions()` & `JsonResponse::getEncodingOptions()` for easier manipulation
of the options used while encoding data to JSON format.
2.4.0
-----
* added RequestStack
* added Request::getEncodings()
* added accessors methods to session handlers
2.3.0
-----
* added support for ranges of IPs in trusted proxies
* `UploadedFile::isValid` now returns false if the file was not uploaded via HTTP (in a non-test mode)
* Improved error-handling of `\Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler`
to ensure the supplied PDO handler throws Exceptions on error (as the class expects). Added related test cases
to verify that Exceptions are properly thrown when the PDO queries fail.
2.2.0
-----
* fixed the Request::create() precedence (URI information always take precedence now)
* added Request::getTrustedProxies()
* deprecated Request::isProxyTrusted()
* [BC BREAK] JsonResponse does not turn a top level empty array to an object anymore, use an ArrayObject to enforce objects
* added a IpUtils class to check if an IP belongs to a CIDR
* added Request::getRealMethod() to get the "real" HTTP method (getMethod() returns the "intended" HTTP method)
* disabled _method request parameter support by default (call Request::enableHttpMethodParameterOverride() to
enable it, and Request::getHttpMethodParameterOverride() to check if it is supported)
* Request::splitHttpAcceptHeader() method is deprecated and will be removed in 2.3
* Deprecated Flashbag::count() and \Countable interface, will be removed in 2.3
2.1.0
-----
* added Request::getSchemeAndHttpHost() and Request::getUserInfo()
* added a fluent interface to the Response class
* added Request::isProxyTrusted()
* added JsonResponse
* added a getTargetUrl method to RedirectResponse
* added support for streamed responses
* made Response::prepare() method the place to enforce HTTP specification
* [BC BREAK] moved management of the locale from the Session class to the Request class
* added a generic access to the PHP built-in filter mechanism: ParameterBag::filter()
* made FileBinaryMimeTypeGuesser command configurable
* added Request::getUser() and Request::getPassword()
* added support for the PATCH method in Request
* removed the ContentTypeMimeTypeGuesser class as it is deprecated and never used on PHP 5.3
* added ResponseHeaderBag::makeDisposition() (implements RFC 6266)
* made mimetype to extension conversion configurable
* [BC BREAK] Moved all session related classes and interfaces into own namespace, as
`Symfony\Component\HttpFoundation\Session` and renamed classes accordingly.
Session handlers are located in the subnamespace `Symfony\Component\HttpFoundation\Session\Handler`.
* SessionHandlers must implement `\SessionHandlerInterface` or extend from the
`Symfony\Component\HttpFoundation\Storage\Handler\NativeSessionHandler` base class.
* Added internal storage driver proxy mechanism for forward compatibility with
PHP 5.4 `\SessionHandler` class.
* Added session handlers for custom Memcache, Memcached and Null session save handlers.
* [BC BREAK] Removed `NativeSessionStorage` and replaced with `NativeFileSessionHandler`.
* [BC BREAK] `SessionStorageInterface` methods removed: `write()`, `read()` and
`remove()`. Added `getBag()`, `registerBag()`. The `NativeSessionStorage` class
is a mediator for the session storage internals including the session handlers
which do the real work of participating in the internal PHP session workflow.
* [BC BREAK] Introduced mock implementations of `SessionStorage` to enable unit
and functional testing without starting real PHP sessions. Removed
`ArraySessionStorage`, and replaced with `MockArraySessionStorage` for unit
tests; removed `FilesystemSessionStorage`, and replaced with`MockFileSessionStorage`
for functional tests. These do not interact with global session ini
configuration values, session functions or `$_SESSION` superglobal. This means
they can be configured directly allowing multiple instances to work without
conflicting in the same PHP process.
* [BC BREAK] Removed the `close()` method from the `Session` class, as this is
now redundant.
* Deprecated the following methods from the Session class: `setFlash()`, `setFlashes()`
`getFlash()`, `hasFlash()`, and `removeFlash()`. Use `getFlashBag()` instead
which returns a `FlashBagInterface`.
* `Session->clear()` now only clears session attributes as before it cleared
flash messages and attributes. `Session->getFlashBag()->all()` clears flashes now.
* Session data is now managed by `SessionBagInterface` to better encapsulate
session data.
* Refactored session attribute and flash messages system to their own
`SessionBagInterface` implementations.
* Added `FlashBag`. Flashes expire when retrieved by `get()` or `all()`. This
implementation is ESI compatible.
* Added `AutoExpireFlashBag` (default) to replicate Symfony 2.0.x auto expire
behaviour of messages auto expiring after one page page load. Messages must
be retrieved by `get()` or `all()`.
* Added `Symfony\Component\HttpFoundation\Attribute\AttributeBag` to replicate
attributes storage behaviour from 2.0.x (default).
* Added `Symfony\Component\HttpFoundation\Attribute\NamespacedAttributeBag` for
namespace session attributes.
* Flash API can stores messages in an array so there may be multiple messages
per flash type. The old `Session` class API remains without BC break as it
will allow single messages as before.
* Added basic session meta-data to the session to record session create time,
last updated time, and the lifetime of the session cookie that was provided
to the client.
* Request::getClientIp() method doesn't take a parameter anymore but bases
itself on the trustProxy parameter.
* Added isMethod() to Request object.
* [BC BREAK] The methods `getPathInfo()`, `getBaseUrl()` and `getBasePath()` of
a `Request` now all return a raw value (vs a urldecoded value before). Any call
to one of these methods must be checked and wrapped in a `rawurldecode()` if
needed.

View file

@ -0,0 +1,190 @@
<?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;
/**
* Represents a cookie.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class Cookie
{
protected $name;
protected $value;
protected $domain;
protected $expire;
protected $path;
protected $secure;
protected $httpOnly;
/**
* Constructor.
*
* @param string $name The name of the cookie
* @param string $value The value of the cookie
* @param int|string|\DateTime|\DateTimeInterface $expire The time the cookie expires
* @param string $path The path on the server in which the cookie will be available on
* @param string $domain The domain that the cookie is available to
* @param bool $secure Whether the cookie should only be transmitted over a secure HTTPS connection from the client
* @param bool $httpOnly Whether the cookie will be made accessible only through the HTTP protocol
*
* @throws \InvalidArgumentException
*/
public function __construct($name, $value = null, $expire = 0, $path = '/', $domain = null, $secure = false, $httpOnly = true)
{
// from PHP source code
if (preg_match("/[=,; \t\r\n\013\014]/", $name)) {
throw new \InvalidArgumentException(sprintf('The cookie name "%s" contains invalid characters.', $name));
}
if (empty($name)) {
throw new \InvalidArgumentException('The cookie name cannot be empty.');
}
// convert expiration time to a Unix timestamp
if ($expire instanceof \DateTime || $expire instanceof \DateTimeInterface) {
$expire = $expire->format('U');
} elseif (!is_numeric($expire)) {
$expire = strtotime($expire);
if (false === $expire) {
throw new \InvalidArgumentException('The cookie expiration time is not valid.');
}
}
$this->name = $name;
$this->value = $value;
$this->domain = $domain;
$this->expire = 0 < $expire ? (int) $expire : 0;
$this->path = empty($path) ? '/' : $path;
$this->secure = (bool) $secure;
$this->httpOnly = (bool) $httpOnly;
}
/**
* Returns the cookie as a string.
*
* @return string The cookie
*/
public function __toString()
{
$str = urlencode($this->getName()).'=';
if ('' === (string) $this->getValue()) {
$str .= 'deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001);
} else {
$str .= urlencode($this->getValue());
if (0 !== $this->getExpiresTime()) {
$str .= '; expires='.gmdate('D, d-M-Y H:i:s T', $this->getExpiresTime());
}
}
if ($this->path) {
$str .= '; path='.$this->path;
}
if ($this->getDomain()) {
$str .= '; domain='.$this->getDomain();
}
if (true === $this->isSecure()) {
$str .= '; secure';
}
if (true === $this->isHttpOnly()) {
$str .= '; httponly';
}
return $str;
}
/**
* Gets the name of the cookie.
*
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Gets the value of the cookie.
*
* @return string
*/
public function getValue()
{
return $this->value;
}
/**
* Gets the domain that the cookie is available to.
*
* @return string
*/
public function getDomain()
{
return $this->domain;
}
/**
* Gets the time the cookie expires.
*
* @return int
*/
public function getExpiresTime()
{
return $this->expire;
}
/**
* Gets the path on the server in which the cookie will be available on.
*
* @return string
*/
public function getPath()
{
return $this->path;
}
/**
* Checks whether the cookie should only be transmitted over a secure HTTPS connection from the client.
*
* @return bool
*/
public function isSecure()
{
return $this->secure;
}
/**
* Checks whether the cookie will be made accessible only through the HTTP protocol.
*
* @return bool
*/
public function isHttpOnly()
{
return $this->httpOnly;
}
/**
* Whether this cookie is about to be cleared.
*
* @return bool
*/
public function isCleared()
{
return $this->expire < time();
}
}

View file

@ -0,0 +1,23 @@
<?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;
/**
* The HTTP request contains headers with conflicting information.
*
* This exception should trigger an HTTP 400 response in your application code.
*
* @author Magnus Nordlander <magnus@fervo.se>
*/
class ConflictingHeadersException extends \RuntimeException
{
}

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;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
/**
* ExpressionRequestMatcher uses an expression to match a Request.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ExpressionRequestMatcher extends RequestMatcher
{
private $language;
private $expression;
public function setExpression(ExpressionLanguage $language, $expression)
{
$this->language = $language;
$this->expression = $expression;
}
public function matches(Request $request)
{
if (!$this->language) {
throw new \LogicException('Unable to match the request as the expression language is not available.');
}
return $this->language->evaluate($this->expression, array(
'request' => $request,
'method' => $request->getMethod(),
'path' => rawurldecode($request->getPathInfo()),
'host' => $request->getHost(),
'ip' => $request->getClientIp(),
'attributes' => $request->attributes->all(),
)) && parent::matches($request);
}
}

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\File\Exception;
/**
* Thrown when the access on a file was denied.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class AccessDeniedException extends FileException
{
/**
* Constructor.
*
* @param string $path The path to the accessed file
*/
public function __construct($path)
{
parent::__construct(sprintf('The file %s could not be accessed', $path));
}
}

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\File\Exception;
/**
* Thrown when an error occurred in the component File.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class FileException extends \RuntimeException
{
}

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\File\Exception;
/**
* Thrown when a file was not found.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class FileNotFoundException extends FileException
{
/**
* Constructor.
*
* @param string $path The path to the file that was not found
*/
public function __construct($path)
{
parent::__construct(sprintf('The file "%s" does not exist', $path));
}
}

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\File\Exception;
class UnexpectedTypeException extends FileException
{
public function __construct($value, $expectedType)
{
parent::__construct(sprintf('Expected argument of type %s, %s given', $expectedType, is_object($value) ? get_class($value) : gettype($value)));
}
}

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\File\Exception;
/**
* Thrown when an error occurred during file upload.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class UploadException extends FileException
{
}

View file

@ -0,0 +1,136 @@
<?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;
use Symfony\Component\HttpFoundation\File\Exception\FileException;
use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException;
use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesser;
use Symfony\Component\HttpFoundation\File\MimeType\ExtensionGuesser;
/**
* A file in the file system.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class File extends \SplFileInfo
{
/**
* Constructs a new file from the given path.
*
* @param string $path The path to the file
* @param bool $checkPath Whether to check the path or not
*
* @throws FileNotFoundException If the given path is not a file
*/
public function __construct($path, $checkPath = true)
{
if ($checkPath && !is_file($path)) {
throw new FileNotFoundException($path);
}
parent::__construct($path);
}
/**
* Returns the extension based on the mime type.
*
* If the mime type is unknown, returns null.
*
* 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
*
* @see ExtensionGuesser
* @see getMimeType()
*/
public function guessExtension()
{
$type = $this->getMimeType();
$guesser = ExtensionGuesser::getInstance();
return $guesser->guess($type);
}
/**
* Returns the mime type of the file.
*
* The mime type is guessed using a MimeTypeGuesser instance, which uses finfo(),
* mime_content_type() and the system binary "file" (in this order), depending on
* which of those are available.
*
* @return string|null The guessed mime type (e.g. "application/pdf")
*
* @see MimeTypeGuesser
*/
public function getMimeType()
{
$guesser = MimeTypeGuesser::getInstance();
return $guesser->guess($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
*
* @throws FileException if the target file could not be created
*/
public function move($directory, $name = null)
{
$target = $this->getTargetFile($directory, $name);
if (!@rename($this->getPathname(), $target)) {
$error = error_get_last();
throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $target, strip_tags($error['message'])));
}
@chmod($target, 0666 & ~umask());
return $target;
}
protected function getTargetFile($directory, $name = null)
{
if (!is_dir($directory)) {
if (false === @mkdir($directory, 0777, true) && !is_dir($directory)) {
throw new FileException(sprintf('Unable to create the "%s" directory', $directory));
}
} elseif (!is_writable($directory)) {
throw new FileException(sprintf('Unable to write in the "%s" directory', $directory));
}
$target = rtrim($directory, '/\\').DIRECTORY_SEPARATOR.(null === $name ? $this->getBasename() : $this->getName($name));
return new self($target, false);
}
/**
* Returns locale independent base name of the given path.
*
* @param string $name The new file name
*
* @return string containing
*/
protected function getName($name)
{
$originalName = str_replace('\\', '/', $name);
$pos = strrpos($originalName, '/');
$originalName = false === $pos ? $originalName : substr($originalName, $pos + 1);
return $originalName;
}
}

View file

@ -0,0 +1,96 @@
<?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;
/**
* 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.
*/
class ExtensionGuesser implements ExtensionGuesserInterface
{
/**
* The singleton instance.
*
* @var ExtensionGuesser
*/
private static $instance = null;
/**
* All registered ExtensionGuesserInterface instances.
*
* @var array
*/
protected $guessers = array();
/**
* 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.
*
* @param ExtensionGuesserInterface $guesser
*/
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;
}
}
}
}

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\File\MimeType;
/**
* Guesses the file extension corresponding to a given mime type.
*/
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

@ -0,0 +1,87 @@
<?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\FileNotFoundException;
use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException;
/**
* Guesses the mime type with the binary "file" (only available on *nix).
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class FileBinaryMimeTypeGuesser implements MimeTypeGuesserInterface
{
private $cmd;
/**
* Constructor.
*
* 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($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()
{
return '\\' !== DIRECTORY_SEPARATOR && function_exists('passthru') && function_exists('escapeshellarg');
}
/**
* {@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;
}
ob_start();
// need to use --mime instead of -i. see #6641
passthru(sprintf($this->cmd, escapeshellarg($path)), $return);
if ($return > 0) {
ob_end_clean();
return;
}
$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;
}
return $match[1];
}
}

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\File\MimeType;
use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException;
use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException;
/**
* Guesses the mime type using the PECL extension FileInfo.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class FileinfoMimeTypeGuesser implements MimeTypeGuesserInterface
{
private $magicFile;
/**
* Constructor.
*
* @param string $magicFile A magic file to use with the finfo instance
*
* @see http://www.php.net/manual/en/function.finfo-open.php
*/
public function __construct($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;
}
if (!$finfo = new \finfo(FILEINFO_MIME_TYPE, $this->magicFile)) {
return;
}
return $finfo->file($path);
}
}

View file

@ -0,0 +1,808 @@
<?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;
/**
* Provides a best-guess mapping of mime type to file extension.
*/
class MimeTypeExtensionGuesser implements ExtensionGuesserInterface
{
/**
* A map of mime types and their default extensions.
*
* This list has been placed under the public domain by the Apache HTTPD project.
* This list has been updated from upstream on 2013-04-23.
*
* @see http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types
*
* @var array
*/
protected $defaultExtensions = array(
'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-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' => 'mp4a',
'audio/mpeg' => 'mpga',
'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-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',
'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.dvb.subtitle' => 'sub',
'image/vnd.djvu' => 'djvu',
'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.scurl' => 'scurl',
'text/vnd.curl.mcurl' => 'mcurl',
'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/x-asm' => 's',
'text/x-c' => 'c',
'text/x-fortran' => 'f',
'text/x-pascal' => 'p',
'text/x-java-source' => 'java',
'text/x-opml' => 'opml',
'text/x-nfo' => 'nfo',
'text/x-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)
{
return isset($this->defaultExtensions[$mimeType]) ? $this->defaultExtensions[$mimeType] : null;
}
}

View file

@ -0,0 +1,144 @@
<?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\FileNotFoundException;
use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException;
/**
* 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 = array();
/**
* 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()
{
if (FileBinaryMimeTypeGuesser::isSupported()) {
$this->register(new FileBinaryMimeTypeGuesser());
}
if (FileinfoMimeTypeGuesser::isSupported()) {
$this->register(new FileinfoMimeTypeGuesser());
}
}
/**
* Registers a new mime type guesser.
*
* When guessing, this guesser is preferred over previously registered ones.
*
* @param MimeTypeGuesserInterface $guesser
*/
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);
}
if (!$this->guessers) {
$msg = 'Unable to guess the mime type as no guessers are available';
if (!FileinfoMimeTypeGuesser::isSupported()) {
$msg .= ' (Did you enable the php_fileinfo extension?)';
}
throw new \LogicException($msg);
}
foreach ($this->guessers as $guesser) {
if (null !== $mimeType = $guesser->guess($path)) {
return $mimeType;
}
}
}
}

View file

@ -0,0 +1,35 @@
<?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\FileNotFoundException;
use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException;
/**
* Guesses the mime type of a file.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
interface MimeTypeGuesserInterface
{
/**
* Guesses the mime type of the file with the given path.
*
* @param string $path The path to the file
*
* @return string 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

@ -0,0 +1,293 @@
<?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;
use Symfony\Component\HttpFoundation\File\Exception\FileException;
use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException;
use Symfony\Component\HttpFoundation\File\MimeType\ExtensionGuesser;
/**
* A file uploaded through a form.
*
* @author Bernhard Schussek <bschussek@gmail.com>
* @author Florian Eckerstorfer <florian@eckerstorfer.org>
* @author Fabien Potencier <fabien@symfony.com>
*/
class UploadedFile extends File
{
/**
* Whether the test mode is activated.
*
* Local files are used in test mode hence the code should not enforce HTTP uploads.
*
* @var bool
*/
private $test = false;
/**
* The original name of the uploaded file.
*
* @var string
*/
private $originalName;
/**
* The mime type provided by the uploader.
*
* @var string
*/
private $mimeType;
/**
* The file size provided by the uploader.
*
* @var int|null
*/
private $size;
/**
* The UPLOAD_ERR_XXX constant provided by the uploader.
*
* @var int
*/
private $error;
/**
* Accepts the information of the uploaded file as provided by the PHP global $_FILES.
*
* The file object is only created when the uploaded file is valid (i.e. when the
* isValid() method returns true). Otherwise the only methods that could be called
* on an UploadedFile instance are:
*
* * getClientOriginalName,
* * getClientMimeType,
* * isValid,
* * getError.
*
* Calling any other method on an non-valid instance will cause an unpredictable result.
*
* @param string $path The full temporary path to the file
* @param string $originalName The original file name
* @param string|null $mimeType The type of the file as provided by PHP; null defaults to application/octet-stream
* @param int|null $size The file size
* @param int|null $error The error constant of the upload (one of PHP's UPLOAD_ERR_XXX constants); null defaults to UPLOAD_ERR_OK
* @param bool $test Whether the test mode is active
*
* @throws FileException If file_uploads is disabled
* @throws FileNotFoundException If the file does not exist
*/
public function __construct($path, $originalName, $mimeType = null, $size = null, $error = null, $test = false)
{
$this->originalName = $this->getName($originalName);
$this->mimeType = $mimeType ?: 'application/octet-stream';
$this->size = $size;
$this->error = $error ?: UPLOAD_ERR_OK;
$this->test = (bool) $test;
parent::__construct($path, UPLOAD_ERR_OK === $this->error);
}
/**
* Returns the original file name.
*
* 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|null The original name
*/
public function getClientOriginalName()
{
return $this->originalName;
}
/**
* Returns the original file extension.
*
* 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
*/
public function getClientOriginalExtension()
{
return pathinfo($this->originalName, PATHINFO_EXTENSION);
}
/**
* Returns the file mime type.
*
* The client mime type is extracted from the request from which the file
* was uploaded, so it should not be considered as a safe value.
*
* For a trusted mime type, use getMimeType() instead (which guesses the mime
* type based on the file content).
*
* @return string|null The mime type
*
* @see getMimeType()
*/
public function getClientMimeType()
{
return $this->mimeType;
}
/**
* Returns the extension based on the client mime type.
*
* If the mime type is unknown, returns null.
*
* This method uses the mime type as guessed by getClientMimeType()
* to guess the file extension. As such, the extension returned
* by this method cannot be trusted.
*
* 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
*
* @see guessExtension()
* @see getClientMimeType()
*/
public function guessClientExtension()
{
$type = $this->getClientMimeType();
$guesser = ExtensionGuesser::getInstance();
return $guesser->guess($type);
}
/**
* 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.
*
* @return int|null The file size
*/
public function getClientSize()
{
return $this->size;
}
/**
* 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
*/
public function getError()
{
return $this->error;
}
/**
* Returns whether the file was uploaded successfully.
*
* @return bool True if the file has been uploaded with HTTP and no error occurred
*/
public function isValid()
{
$isOk = $this->error === UPLOAD_ERR_OK;
return $this->test ? $isOk : $isOk && is_uploaded_file($this->getPathname());
}
/**
* 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
*
* @throws FileException if, for any reason, the file could not have been moved
*/
public function move($directory, $name = null)
{
if ($this->isValid()) {
if ($this->test) {
return parent::move($directory, $name);
}
$target = $this->getTargetFile($directory, $name);
if (!@move_uploaded_file($this->getPathname(), $target)) {
$error = error_get_last();
throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $target, strip_tags($error['message'])));
}
@chmod($target, 0666 & ~umask());
return $target;
}
throw new FileException($this->getErrorMessage());
}
/**
* Returns the maximum size of an uploaded file as configured in php.ini.
*
* @return int The maximum size of an uploaded file in bytes
*/
public static function getMaxFilesize()
{
$iniMax = strtolower(ini_get('upload_max_filesize'));
if ('' === $iniMax) {
return PHP_INT_MAX;
}
$max = ltrim($iniMax, '+');
if (0 === strpos($max, '0x')) {
$max = intval($max, 16);
} elseif (0 === strpos($max, '0')) {
$max = intval($max, 8);
} else {
$max = (int) $max;
}
switch (substr($iniMax, -1)) {
case 't': $max *= 1024;
case 'g': $max *= 1024;
case 'm': $max *= 1024;
case 'k': $max *= 1024;
}
return $max;
}
/**
* Returns an informative upload error message.
*
* @return string The error message regarding the specified error code
*/
public function getErrorMessage()
{
static $errors = array(
UPLOAD_ERR_INI_SIZE => 'The file "%s" exceeds your upload_max_filesize ini directive (limit is %d KiB).',
UPLOAD_ERR_FORM_SIZE => 'The file "%s" exceeds the upload limit defined in your form.',
UPLOAD_ERR_PARTIAL => 'The file "%s" was only partially uploaded.',
UPLOAD_ERR_NO_FILE => 'No file was uploaded.',
UPLOAD_ERR_CANT_WRITE => 'The file "%s" could not be written on disk.',
UPLOAD_ERR_NO_TMP_DIR => 'File could not be uploaded: missing temporary directory.',
UPLOAD_ERR_EXTENSION => 'File upload was stopped by a PHP extension.',
);
$errorCode = $this->error;
$maxFilesize = $errorCode === UPLOAD_ERR_INI_SIZE ? self::getMaxFilesize() / 1024 : 0;
$message = isset($errors[$errorCode]) ? $errors[$errorCode] : 'The file "%s" was not uploaded due to an unknown error.';
return sprintf($message, $this->getClientOriginalName(), $maxFilesize);
}
}

View file

@ -0,0 +1,145 @@
<?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\File\UploadedFile;
/**
* FileBag is a container for uploaded files.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Bulat Shakirzyanov <mallluhuct@gmail.com>
*/
class FileBag extends ParameterBag
{
private static $fileKeys = array('error', 'name', 'size', 'tmp_name', 'type');
/**
* Constructor.
*
* @param array $parameters An array of HTTP files
*/
public function __construct(array $parameters = array())
{
$this->replace($parameters);
}
/**
* {@inheritdoc}
*/
public function replace(array $files = array())
{
$this->parameters = array();
$this->add($files);
}
/**
* {@inheritdoc}
*/
public function set($key, $value)
{
if (!is_array($value) && !$value instanceof UploadedFile) {
throw new \InvalidArgumentException('An uploaded file must be an array or an instance of UploadedFile.');
}
parent::set($key, $this->convertFileInformation($value));
}
/**
* {@inheritdoc}
*/
public function add(array $files = array())
{
foreach ($files as $key => $file) {
$this->set($key, $file);
}
}
/**
* Converts uploaded files to UploadedFile instances.
*
* @param array|UploadedFile $file A (multi-dimensional) array of uploaded file information
*
* @return array A (multi-dimensional) array of UploadedFile instances
*/
protected function convertFileInformation($file)
{
if ($file instanceof UploadedFile) {
return $file;
}
$file = $this->fixPhpFilesArray($file);
if (is_array($file)) {
$keys = array_keys($file);
sort($keys);
if ($keys == self::$fileKeys) {
if (UPLOAD_ERR_NO_FILE == $file['error']) {
$file = null;
} else {
$file = new UploadedFile($file['tmp_name'], $file['name'], $file['type'], $file['size'], $file['error']);
}
} else {
$file = array_map(array($this, 'convertFileInformation'), $file);
}
}
return $file;
}
/**
* Fixes a malformed PHP $_FILES array.
*
* PHP has a bug that the format of the $_FILES array differs, depending on
* whether the uploaded file fields had normal field names or array-like
* field names ("normal" vs. "parent[child]").
*
* This method fixes the array to look like the "normal" $_FILES array.
*
* It's safe to pass an already converted array, in which case this method
* just returns the original array unmodified.
*
* @param array $data
*
* @return array
*/
protected function fixPhpFilesArray($data)
{
if (!is_array($data)) {
return $data;
}
$keys = array_keys($data);
sort($keys);
if (self::$fileKeys != $keys || !isset($data['name']) || !is_array($data['name'])) {
return $data;
}
$files = $data;
foreach (self::$fileKeys as $k) {
unset($files[$k]);
}
foreach ($data['name'] as $key => $name) {
$files[$key] = $this->fixPhpFilesArray(array(
'error' => $data['error'][$key],
'name' => $name,
'type' => $data['type'][$key],
'tmp_name' => $data['tmp_name'][$key],
'size' => $data['size'][$key],
));
}
return $files;
}
}

View file

@ -0,0 +1,324 @@
<?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;
/**
* HeaderBag is a container for HTTP headers.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class HeaderBag implements \IteratorAggregate, \Countable
{
protected $headers = array();
protected $cacheControl = array();
/**
* Constructor.
*
* @param array $headers An array of HTTP headers
*/
public function __construct(array $headers = array())
{
foreach ($headers as $key => $values) {
$this->set($key, $values);
}
}
/**
* Returns the headers as a string.
*
* @return string The headers
*/
public function __toString()
{
if (!$this->headers) {
return '';
}
$max = max(array_map('strlen', array_keys($this->headers))) + 1;
$content = '';
ksort($this->headers);
foreach ($this->headers as $name => $values) {
$name = implode('-', array_map('ucfirst', explode('-', $name)));
foreach ($values as $value) {
$content .= sprintf("%-{$max}s %s\r\n", $name.':', $value);
}
}
return $content;
}
/**
* Returns the headers.
*
* @return array An array of headers
*/
public function all()
{
return $this->headers;
}
/**
* Returns the parameter keys.
*
* @return array An array of parameter keys
*/
public function keys()
{
return array_keys($this->headers);
}
/**
* Replaces the current HTTP headers by a new set.
*
* @param array $headers An array of HTTP headers
*/
public function replace(array $headers = array())
{
$this->headers = array();
$this->add($headers);
}
/**
* Adds new headers the current HTTP headers set.
*
* @param array $headers An array of HTTP headers
*/
public function add(array $headers)
{
foreach ($headers as $key => $values) {
$this->set($key, $values);
}
}
/**
* Returns a header value by name.
*
* @param string $key The header name
* @param mixed $default The default value
* @param bool $first Whether to return the first value or all header values
*
* @return string|array The first header value if $first is true, an array of values otherwise
*/
public function get($key, $default = null, $first = true)
{
$key = str_replace('_', '-', strtolower($key));
if (!array_key_exists($key, $this->headers)) {
if (null === $default) {
return $first ? null : array();
}
return $first ? $default : array($default);
}
if ($first) {
return count($this->headers[$key]) ? $this->headers[$key][0] : $default;
}
return $this->headers[$key];
}
/**
* Sets a header by name.
*
* @param string $key The key
* @param string|array $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)
{
$key = str_replace('_', '-', strtolower($key));
$values = array_values((array) $values);
if (true === $replace || !isset($this->headers[$key])) {
$this->headers[$key] = $values;
} else {
$this->headers[$key] = array_merge($this->headers[$key], $values);
}
if ('cache-control' === $key) {
$this->cacheControl = $this->parseCacheControl($values[0]);
}
}
/**
* Returns true if the HTTP header is defined.
*
* @param string $key The HTTP header
*
* @return bool true if the parameter exists, false otherwise
*/
public function has($key)
{
return array_key_exists(str_replace('_', '-', strtolower($key)), $this->headers);
}
/**
* 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
*/
public function contains($key, $value)
{
return in_array($value, $this->get($key, null, false));
}
/**
* Removes a header.
*
* @param string $key The HTTP header name
*/
public function remove($key)
{
$key = str_replace('_', '-', strtolower($key));
unset($this->headers[$key]);
if ('cache-control' === $key) {
$this->cacheControl = array();
}
}
/**
* Returns the HTTP header value converted to a date.
*
* @param string $key The parameter key
* @param \DateTime $default The default value
*
* @return null|\DateTime The parsed DateTime or the default value if the header does not exist
*
* @throws \RuntimeException When the HTTP header is not parseable
*/
public function getDate($key, \DateTime $default = null)
{
if (null === $value = $this->get($key)) {
return $default;
}
if (false === $date = \DateTime::createFromFormat(DATE_RFC2822, $value)) {
throw new \RuntimeException(sprintf('The %s HTTP header is not parseable (%s).', $key, $value));
}
return $date;
}
/**
* Adds a custom Cache-Control directive.
*
* @param string $key The Cache-Control directive name
* @param mixed $value The Cache-Control directive value
*/
public function addCacheControlDirective($key, $value = true)
{
$this->cacheControl[$key] = $value;
$this->set('Cache-Control', $this->getCacheControlHeader());
}
/**
* 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
*/
public function hasCacheControlDirective($key)
{
return array_key_exists($key, $this->cacheControl);
}
/**
* Returns a Cache-Control directive value by name.
*
* @param string $key The directive name
*
* @return mixed|null The directive value if defined, null otherwise
*/
public function getCacheControlDirective($key)
{
return array_key_exists($key, $this->cacheControl) ? $this->cacheControl[$key] : null;
}
/**
* Removes a Cache-Control directive.
*
* @param string $key The Cache-Control directive
*/
public function removeCacheControlDirective($key)
{
unset($this->cacheControl[$key]);
$this->set('Cache-Control', $this->getCacheControlHeader());
}
/**
* Returns an iterator for headers.
*
* @return \ArrayIterator An \ArrayIterator instance
*/
public function getIterator()
{
return new \ArrayIterator($this->headers);
}
/**
* Returns the number of headers.
*
* @return int The number of headers
*/
public function count()
{
return count($this->headers);
}
protected function getCacheControlHeader()
{
$parts = array();
ksort($this->cacheControl);
foreach ($this->cacheControl as $key => $value) {
if (true === $value) {
$parts[] = $key;
} else {
if (preg_match('#[^a-zA-Z0-9._-]#', $value)) {
$value = '"'.$value.'"';
}
$parts[] = "$key=$value";
}
}
return implode(', ', $parts);
}
/**
* 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
*/
protected function parseCacheControl($header)
{
$cacheControl = array();
preg_match_all('#([a-zA-Z][a-zA-Z_-]*)\s*(?:=(?:"([^"]*)"|([^ \t",;]*)))?#', $header, $matches, PREG_SET_ORDER);
foreach ($matches as $match) {
$cacheControl[strtolower($match[1])] = isset($match[3]) ? $match[3] : (isset($match[2]) ? $match[2] : true);
}
return $cacheControl;
}
}

View file

@ -0,0 +1,136 @@
<?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;
/**
* Http utility functions.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class IpUtils
{
/**
* This class should not be instantiated.
*/
private function __construct()
{
}
/**
* 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)
*
* @return bool Whether the IP is valid
*/
public static function checkIp($requestIp, $ips)
{
if (!is_array($ips)) {
$ips = array($ips);
}
$method = substr_count($requestIp, ':') > 1 ? 'checkIp6' : 'checkIp4';
foreach ($ips as $ip) {
if (self::$method($requestIp, $ip)) {
return true;
}
}
return false;
}
/**
* 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
*
* @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)
{
if (!filter_var($requestIp, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
return false;
}
if (false !== strpos($ip, '/')) {
list($address, $netmask) = explode('/', $ip, 2);
if ($netmask === '0') {
return filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4);
}
if ($netmask < 0 || $netmask > 32) {
return false;
}
} else {
$address = $ip;
$netmask = 32;
}
return 0 === substr_compare(sprintf('%032b', ip2long($requestIp)), sprintf('%032b', ip2long($address)), 0, $netmask);
}
/**
* Compares two IPv6 addresses.
* In case a subnet is given, it checks if it contains the request IP.
*
* @author David Soria Parra <dsp at php dot net>
*
* @see https://github.com/dsp/v6tools
*
* @param string $requestIp IPv6 address to check
* @param string $ip IPv6 address or subnet in CIDR notation
*
* @return bool Whether the IP is valid
*
* @throws \RuntimeException When IPV6 support is not enabled
*/
public static function checkIp6($requestIp, $ip)
{
if (!((extension_loaded('sockets') && defined('AF_INET6')) || @inet_pton('::1'))) {
throw new \RuntimeException('Unable to check Ipv6. Check that PHP was not compiled with option "disable-ipv6".');
}
if (false !== strpos($ip, '/')) {
list($address, $netmask) = explode('/', $ip, 2);
if ($netmask < 1 || $netmask > 128) {
return false;
}
} else {
$address = $ip;
$netmask = 128;
}
$bytesAddr = unpack('n*', @inet_pton($address));
$bytesTest = unpack('n*', @inet_pton($requestIp));
if (!$bytesAddr || !$bytesTest) {
return false;
}
for ($i = 1, $ceil = ceil($netmask / 16); $i <= $ceil; ++$i) {
$left = $netmask - 16 * ($i - 1);
$left = ($left <= 16) ? $left : 16;
$mask = ~(0xffff >> $left) & 0xffff;
if (($bytesAddr[$i] & $mask) != ($bytesTest[$i] & $mask)) {
return false;
}
}
return true;
}
}

View file

@ -0,0 +1,218 @@
<?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;
/**
* Response represents an HTTP response in JSON format.
*
* Note that this class does not force the returned JSON content to be an
* object. It is however recommended that you do return an object as it
* protects yourself against XSSI and JSON-JavaScript Hijacking.
*
* @see https://www.owasp.org/index.php/OWASP_AJAX_Security_Guidelines#Always_return_JSON_with_an_Object_on_the_outside
*
* @author Igor Wiedler <igor@wiedler.ch>
*/
class JsonResponse extends Response
{
protected $data;
protected $callback;
// Encode <, >, ', &, and " characters in the JSON, making it also safe to be embedded into HTML.
// 15 === JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT
protected $encodingOptions = 15;
/**
* Constructor.
*
* @param mixed $data The response data
* @param int $status The response status code
* @param array $headers An array of response headers
*/
public function __construct($data = null, $status = 200, $headers = array())
{
parent::__construct('', $status, $headers);
if (null === $data) {
$data = new \ArrayObject();
}
$this->setData($data);
}
/**
* Factory method for chainability.
*
* Example:
*
* return JsonResponse::create($data, 200)
* ->setSharedMaxAge(300);
*
* @param mixed $data The json response data
* @param int $status The response status code
* @param array $headers An array of response headers
*
* @return static
*/
public static function create($data = null, $status = 200, $headers = array())
{
return new static($data, $status, $headers);
}
/**
* Sets the JSONP callback.
*
* @param string|null $callback The JSONP callback or null to use none
*
* @return $this
*
* @throws \InvalidArgumentException When the callback name is not valid
*/
public function setCallback($callback = null)
{
if (null !== $callback) {
// partially token from http://www.geekality.net/2011/08/03/valid-javascript-identifier/
// partially token from https://github.com/willdurand/JsonpCallbackValidator
// JsonpCallbackValidator is released under the MIT License. See https://github.com/willdurand/JsonpCallbackValidator/blob/v1.1.0/LICENSE for details.
// (c) William Durand <william.durand1@gmail.com>
$pattern = '/^[$_\p{L}][$_\p{L}\p{Mn}\p{Mc}\p{Nd}\p{Pc}\x{200C}\x{200D}]*(?:\[(?:"(?:\\\.|[^"\\\])*"|\'(?:\\\.|[^\'\\\])*\'|\d+)\])*?$/u';
$reserved = array(
'break', 'do', 'instanceof', 'typeof', 'case', 'else', 'new', 'var', 'catch', 'finally', 'return', 'void', 'continue', 'for', 'switch', 'while',
'debugger', 'function', 'this', 'with', 'default', 'if', 'throw', 'delete', 'in', 'try', 'class', 'enum', 'extends', 'super', 'const', 'export',
'import', 'implements', 'let', 'private', 'public', 'yield', 'interface', 'package', 'protected', 'static', 'null', 'true', 'false',
);
$parts = explode('.', $callback);
foreach ($parts as $part) {
if (!preg_match($pattern, $part) || in_array($part, $reserved, true)) {
throw new \InvalidArgumentException('The callback name is not valid.');
}
}
}
$this->callback = $callback;
return $this->update();
}
/**
* Sets the data to be sent as JSON.
*
* @param mixed $data
*
* @return $this
*
* @throws \InvalidArgumentException
*/
public function setData($data = array())
{
if (defined('HHVM_VERSION')) {
// HHVM does not trigger any warnings and let exceptions
// thrown from a JsonSerializable object pass through.
// If only PHP did the same...
$data = json_encode($data, $this->encodingOptions);
} else {
try {
if (PHP_VERSION_ID < 50400) {
// PHP 5.3 triggers annoying warnings for some
// types that can't be serialized as JSON (INF, resources, etc.)
// but doesn't provide the JsonSerializable interface.
set_error_handler(function () { return false; });
$data = @json_encode($data, $this->encodingOptions);
} else {
// PHP 5.4 and up wrap exceptions thrown by JsonSerializable
// objects in a new exception that needs to be removed.
// Fortunately, PHP 5.5 and up do not trigger any warning anymore.
if (PHP_VERSION_ID < 50500) {
// Clear json_last_error()
json_encode(null);
$errorHandler = set_error_handler('var_dump');
restore_error_handler();
set_error_handler(function () use ($errorHandler) {
if (JSON_ERROR_NONE === json_last_error()) {
return $errorHandler && false !== call_user_func_array($errorHandler, func_get_args());
}
});
}
$data = json_encode($data, $this->encodingOptions);
}
if (PHP_VERSION_ID < 50500) {
restore_error_handler();
}
} catch (\Exception $e) {
if (PHP_VERSION_ID < 50500) {
restore_error_handler();
}
if (PHP_VERSION_ID >= 50400 && 'Exception' === get_class($e) && 0 === strpos($e->getMessage(), 'Failed calling ')) {
throw $e->getPrevious() ?: $e;
}
throw $e;
}
}
if (JSON_ERROR_NONE !== json_last_error()) {
throw new \InvalidArgumentException(json_last_error_msg());
}
$this->data = $data;
return $this->update();
}
/**
* Returns options used while encoding data to JSON.
*
* @return int
*/
public function getEncodingOptions()
{
return $this->encodingOptions;
}
/**
* Sets options used while encoding data to JSON.
*
* @param int $encodingOptions
*
* @return $this
*/
public function setEncodingOptions($encodingOptions)
{
$this->encodingOptions = (int) $encodingOptions;
return $this->setData(json_decode($this->data));
}
/**
* Updates the content and headers according to the JSON data and callback.
*
* @return $this
*/
protected function update()
{
if (null !== $this->callback) {
// Not using application/javascript for compatibility reasons with older browsers.
$this->headers->set('Content-Type', 'text/javascript');
return $this->setContent(sprintf('/**/%s(%s);', $this->callback, $this->data));
}
// Only set the header when there is none or when it equals 'text/javascript' (from a previous update with callback)
// in order to not overwrite a custom definition.
if (!$this->headers->has('Content-Type') || 'text/javascript' === $this->headers->get('Content-Type')) {
$this->headers->set('Content-Type', 'application/json');
}
return $this->setContent($this->data);
}
}

View file

@ -0,0 +1,19 @@
Copyright (c) 2004-2017 Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View file

@ -0,0 +1,312 @@
<?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;
/**
* ParameterBag is a container for key/value pairs.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ParameterBag implements \IteratorAggregate, \Countable
{
/**
* Parameter storage.
*
* @var array
*/
protected $parameters;
/**
* Constructor.
*
* @param array $parameters An array of parameters
*/
public function __construct(array $parameters = array())
{
$this->parameters = $parameters;
}
/**
* Returns the parameters.
*
* @return array An array of parameters
*/
public function all()
{
return $this->parameters;
}
/**
* Returns the parameter keys.
*
* @return array An array of parameter keys
*/
public function keys()
{
return array_keys($this->parameters);
}
/**
* Replaces the current parameters by a new set.
*
* @param array $parameters An array of parameters
*/
public function replace(array $parameters = array())
{
$this->parameters = $parameters;
}
/**
* Adds parameters.
*
* @param array $parameters An array of parameters
*/
public function add(array $parameters = array())
{
$this->parameters = array_replace($this->parameters, $parameters);
}
/**
* Returns a parameter by name.
*
* Note: Finding deep items is deprecated since version 2.8, to be removed in 3.0.
*
* @param string $key The key
* @param mixed $default The default value if the parameter key does not exist
* @param bool $deep If true, a path like foo[bar] will find deeper items
*
* @return mixed
*
* @throws \InvalidArgumentException
*/
public function get($key, $default = null, $deep = false)
{
if ($deep) {
@trigger_error('Using paths to find deeper items in '.__METHOD__.' is deprecated since version 2.8 and will be removed in 3.0. Filter the returned value in your own code instead.', E_USER_DEPRECATED);
}
if (!$deep || false === $pos = strpos($key, '[')) {
return array_key_exists($key, $this->parameters) ? $this->parameters[$key] : $default;
}
$root = substr($key, 0, $pos);
if (!array_key_exists($root, $this->parameters)) {
return $default;
}
$value = $this->parameters[$root];
$currentKey = null;
for ($i = $pos, $c = strlen($key); $i < $c; ++$i) {
$char = $key[$i];
if ('[' === $char) {
if (null !== $currentKey) {
throw new \InvalidArgumentException(sprintf('Malformed path. Unexpected "[" at position %d.', $i));
}
$currentKey = '';
} elseif (']' === $char) {
if (null === $currentKey) {
throw new \InvalidArgumentException(sprintf('Malformed path. Unexpected "]" at position %d.', $i));
}
if (!is_array($value) || !array_key_exists($currentKey, $value)) {
return $default;
}
$value = $value[$currentKey];
$currentKey = null;
} else {
if (null === $currentKey) {
throw new \InvalidArgumentException(sprintf('Malformed path. Unexpected "%s" at position %d.', $char, $i));
}
$currentKey .= $char;
}
}
if (null !== $currentKey) {
throw new \InvalidArgumentException(sprintf('Malformed path. Path must end with "]".'));
}
return $value;
}
/**
* Sets a parameter by name.
*
* @param string $key The key
* @param mixed $value The value
*/
public function set($key, $value)
{
$this->parameters[$key] = $value;
}
/**
* Returns true if the parameter is defined.
*
* @param string $key The key
*
* @return bool true if the parameter exists, false otherwise
*/
public function has($key)
{
return array_key_exists($key, $this->parameters);
}
/**
* Removes a parameter.
*
* @param string $key The key
*/
public function remove($key)
{
unset($this->parameters[$key]);
}
/**
* 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
* @param bool $deep If true, a path like foo[bar] will find deeper items
*
* @return string The filtered value
*/
public function getAlpha($key, $default = '', $deep = false)
{
return preg_replace('/[^[:alpha:]]/', '', $this->get($key, $default, $deep));
}
/**
* 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
* @param bool $deep If true, a path like foo[bar] will find deeper items
*
* @return string The filtered value
*/
public function getAlnum($key, $default = '', $deep = false)
{
return preg_replace('/[^[:alnum:]]/', '', $this->get($key, $default, $deep));
}
/**
* 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
* @param bool $deep If true, a path like foo[bar] will find deeper items
*
* @return string The filtered value
*/
public function getDigits($key, $default = '', $deep = false)
{
// we need to remove - and + because they're allowed in the filter
return str_replace(array('-', '+'), '', $this->filter($key, $default, FILTER_SANITIZE_NUMBER_INT, array(), $deep));
}
/**
* 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
* @param bool $deep If true, a path like foo[bar] will find deeper items
*
* @return int The filtered value
*/
public function getInt($key, $default = 0, $deep = false)
{
return (int) $this->get($key, $default, $deep);
}
/**
* Returns the parameter value converted to boolean.
*
* @param string $key The parameter key
* @param mixed $default The default value if the parameter key does not exist
* @param bool $deep If true, a path like foo[bar] will find deeper items
*
* @return bool The filtered value
*/
public function getBoolean($key, $default = false, $deep = false)
{
return $this->filter($key, $default, FILTER_VALIDATE_BOOLEAN, array(), $deep);
}
/**
* Filter key.
*
* @param string $key Key
* @param mixed $default Default = null
* @param int $filter FILTER_* constant
* @param mixed $options Filter options
* @param bool $deep Default = false
*
* @see http://php.net/manual/en/function.filter-var.php
*
* @return mixed
*/
public function filter($key, $default = null, $filter = FILTER_DEFAULT, $options = array(), $deep = false)
{
static $filters = null;
if (null === $filters) {
foreach (filter_list() as $tmp) {
$filters[filter_id($tmp)] = 1;
}
}
if (is_bool($filter) || !isset($filters[$filter]) || is_array($deep)) {
@trigger_error('Passing the $deep boolean as 3rd argument to the '.__METHOD__.' method is deprecated since version 2.8 and will be removed in 3.0. Remove it altogether as the $deep argument will be removed in 3.0.', E_USER_DEPRECATED);
$tmp = $deep;
$deep = $filter;
$filter = $options;
$options = $tmp;
}
$value = $this->get($key, $default, $deep);
// Always turn $options into an array - this allows filter_var option shortcuts.
if (!is_array($options) && $options) {
$options = array('flags' => $options);
}
// Add a convenience check for arrays.
if (is_array($value) && !isset($options['flags'])) {
$options['flags'] = FILTER_REQUIRE_ARRAY;
}
return filter_var($value, $filter, $options);
}
/**
* Returns an iterator for parameters.
*
* @return \ArrayIterator An \ArrayIterator instance
*/
public function getIterator()
{
return new \ArrayIterator($this->parameters);
}
/**
* Returns the number of parameters.
*
* @return int The number of parameters
*/
public function count()
{
return count($this->parameters);
}
}

View file

@ -0,0 +1,14 @@
HttpFoundation Component
========================
The HttpFoundation component defines an object-oriented layer for the HTTP
specification.
Resources
---------
* [Documentation](https://symfony.com/doc/current/components/http_foundation/index.html)
* [Contributing](https://symfony.com/doc/current/contributing/index.html)
* [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)

View file

@ -0,0 +1,99 @@
<?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;
/**
* RedirectResponse represents an HTTP response doing a redirect.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class RedirectResponse extends Response
{
protected $targetUrl;
/**
* Creates a redirect response so that it conforms to the rules defined for a redirect status code.
*
* @param string $url The URL to redirect to. The URL should be a full URL, with schema etc.,
* but practically every browser redirects on paths only as well
* @param int $status The status code (302 by default)
* @param array $headers The headers (Location is always set to the given URL)
*
* @throws \InvalidArgumentException
*
* @see http://tools.ietf.org/html/rfc2616#section-10.3
*/
public function __construct($url, $status = 302, $headers = array())
{
parent::__construct('', $status, $headers);
$this->setTargetUrl($url);
if (!$this->isRedirect()) {
throw new \InvalidArgumentException(sprintf('The HTTP status code is not a redirect ("%s" given).', $status));
}
}
/**
* {@inheritdoc}
*/
public static function create($url = '', $status = 302, $headers = array())
{
return new static($url, $status, $headers);
}
/**
* Returns the target URL.
*
* @return string target URL
*/
public function getTargetUrl()
{
return $this->targetUrl;
}
/**
* Sets the redirect target of this response.
*
* @param string $url The URL to redirect to
*
* @return $this
*
* @throws \InvalidArgumentException
*/
public function setTargetUrl($url)
{
if (empty($url)) {
throw new \InvalidArgumentException('Cannot redirect to an empty URL.');
}
$this->targetUrl = $url;
$this->setContent(
sprintf('<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta http-equiv="refresh" content="1;url=%1$s" />
<title>Redirecting to %1$s</title>
</head>
<body>
Redirecting to <a href="%1$s">%1$s</a>.
</body>
</html>', htmlspecialchars($url, ENT_QUOTES, 'UTF-8')));
$this->headers->set('Location', $url);
return $this;
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,178 @@
<?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;
/**
* RequestMatcher compares a pre-defined set of checks against a Request instance.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class RequestMatcher implements RequestMatcherInterface
{
/**
* @var string|null
*/
private $path;
/**
* @var string|null
*/
private $host;
/**
* @var string[]
*/
private $methods = array();
/**
* @var string[]
*/
private $ips = array();
/**
* @var array
*/
private $attributes = array();
/**
* @var string[]
*/
private $schemes = array();
/**
* @param string|null $path
* @param string|null $host
* @param string|string[]|null $methods
* @param string|string[]|null $ips
* @param array $attributes
* @param string|string[]|null $schemes
*/
public function __construct($path = null, $host = null, $methods = null, $ips = null, array $attributes = array(), $schemes = null)
{
$this->matchPath($path);
$this->matchHost($host);
$this->matchMethod($methods);
$this->matchIps($ips);
$this->matchScheme($schemes);
foreach ($attributes as $k => $v) {
$this->matchAttribute($k, $v);
}
}
/**
* Adds a check for the HTTP scheme.
*
* @param string|string[]|null $scheme An HTTP scheme or an array of HTTP schemes
*/
public function matchScheme($scheme)
{
$this->schemes = null !== $scheme ? array_map('strtolower', (array) $scheme) : array();
}
/**
* Adds a check for the URL host name.
*
* @param string|null $regexp A Regexp
*/
public function matchHost($regexp)
{
$this->host = $regexp;
}
/**
* Adds a check for the URL path info.
*
* @param string|null $regexp A Regexp
*/
public function matchPath($regexp)
{
$this->path = $regexp;
}
/**
* Adds a check for the client IP.
*
* @param string $ip A specific IP address or a range specified using IP/netmask like 192.168.1.0/24
*/
public function matchIp($ip)
{
$this->matchIps($ip);
}
/**
* Adds a check for the client IP.
*
* @param string|string[]|null $ips A specific IP address or a range specified using IP/netmask like 192.168.1.0/24
*/
public function matchIps($ips)
{
$this->ips = null !== $ips ? (array) $ips : array();
}
/**
* Adds a check for the HTTP method.
*
* @param string|string[]|null $method An HTTP method or an array of HTTP methods
*/
public function matchMethod($method)
{
$this->methods = null !== $method ? array_map('strtoupper', (array) $method) : array();
}
/**
* Adds a check for request attribute.
*
* @param string $key The request attribute name
* @param string $regexp A Regexp
*/
public function matchAttribute($key, $regexp)
{
$this->attributes[$key] = $regexp;
}
/**
* {@inheritdoc}
*/
public function matches(Request $request)
{
if ($this->schemes && !in_array($request->getScheme(), $this->schemes, true)) {
return false;
}
if ($this->methods && !in_array($request->getMethod(), $this->methods, true)) {
return false;
}
foreach ($this->attributes as $key => $pattern) {
if (!preg_match('{'.$pattern.'}', $request->attributes->get($key))) {
return false;
}
}
if (null !== $this->path && !preg_match('{'.$this->path.'}', rawurldecode($request->getPathInfo()))) {
return false;
}
if (null !== $this->host && !preg_match('{'.$this->host.'}i', $request->getHost())) {
return false;
}
if (IpUtils::checkIp($request->getClientIp(), $this->ips)) {
return true;
}
// Note to future implementors: add additional checks above the
// foreach above or else your check might not be run!
return count($this->ips) === 0;
}
}

View file

@ -0,0 +1,29 @@
<?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;
/**
* RequestMatcherInterface is an interface for strategies to match a Request.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface RequestMatcherInterface
{
/**
* Decides whether the rule(s) implemented by the strategy matches the supplied request.
*
* @param Request $request The request to check for a match
*
* @return bool true if the request matches, false otherwise
*/
public function matches(Request $request);
}

View file

@ -0,0 +1,103 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation;
/**
* Request stack that controls the lifecycle of requests.
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
class RequestStack
{
/**
* @var Request[]
*/
private $requests = array();
/**
* Pushes a Request on the stack.
*
* This method should generally not be called directly as the stack
* management should be taken care of by the application itself.
*/
public function push(Request $request)
{
$this->requests[] = $request;
}
/**
* Pops the current request from the stack.
*
* This operation lets the current request go out of scope.
*
* This method should generally not be called directly as the stack
* management should be taken care of by the application itself.
*
* @return Request|null
*/
public function pop()
{
if (!$this->requests) {
return;
}
return array_pop($this->requests);
}
/**
* @return Request|null
*/
public function getCurrentRequest()
{
return end($this->requests) ?: null;
}
/**
* Gets the master Request.
*
* Be warned that making your code aware of the master request
* might make it un-compatible with other features of your framework
* like ESI support.
*
* @return Request|null
*/
public function getMasterRequest()
{
if (!$this->requests) {
return;
}
return $this->requests[0];
}
/**
* Returns the parent request of the current.
*
* Be warned that making your code aware of the parent request
* might make it un-compatible with other features of your framework
* like ESI support.
*
* If current Request is the master request, it returns null.
*
* @return Request|null
*/
public function getParentRequest()
{
$pos = count($this->requests) - 2;
if (!isset($this->requests[$pos])) {
return;
}
return $this->requests[$pos];
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,304 @@
<?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;
/**
* ResponseHeaderBag is a container for Response HTTP headers.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ResponseHeaderBag extends HeaderBag
{
const COOKIES_FLAT = 'flat';
const COOKIES_ARRAY = 'array';
const DISPOSITION_ATTACHMENT = 'attachment';
const DISPOSITION_INLINE = 'inline';
/**
* @var array
*/
protected $computedCacheControl = array();
/**
* @var array
*/
protected $cookies = array();
/**
* @var array
*/
protected $headerNames = array();
/**
* Constructor.
*
* @param array $headers An array of HTTP headers
*/
public function __construct(array $headers = array())
{
parent::__construct($headers);
if (!isset($this->headers['cache-control'])) {
$this->set('Cache-Control', '');
}
}
/**
* {@inheritdoc}
*/
public function __toString()
{
$cookies = '';
foreach ($this->getCookies() as $cookie) {
$cookies .= 'Set-Cookie: '.$cookie."\r\n";
}
ksort($this->headerNames);
return parent::__toString().$cookies;
}
/**
* Returns the headers, with original capitalizations.
*
* @return array An array of headers
*/
public function allPreserveCase()
{
return array_combine($this->headerNames, $this->headers);
}
/**
* {@inheritdoc}
*/
public function replace(array $headers = array())
{
$this->headerNames = array();
parent::replace($headers);
if (!isset($this->headers['cache-control'])) {
$this->set('Cache-Control', '');
}
}
/**
* {@inheritdoc}
*/
public function set($key, $values, $replace = true)
{
parent::set($key, $values, $replace);
$uniqueKey = str_replace('_', '-', strtolower($key));
$this->headerNames[$uniqueKey] = $key;
// ensure the cache-control header has sensible defaults
if (in_array($uniqueKey, array('cache-control', 'etag', 'last-modified', 'expires'))) {
$computed = $this->computeCacheControlValue();
$this->headers['cache-control'] = array($computed);
$this->headerNames['cache-control'] = 'Cache-Control';
$this->computedCacheControl = $this->parseCacheControl($computed);
}
}
/**
* {@inheritdoc}
*/
public function remove($key)
{
parent::remove($key);
$uniqueKey = str_replace('_', '-', strtolower($key));
unset($this->headerNames[$uniqueKey]);
if ('cache-control' === $uniqueKey) {
$this->computedCacheControl = array();
}
}
/**
* {@inheritdoc}
*/
public function hasCacheControlDirective($key)
{
return array_key_exists($key, $this->computedCacheControl);
}
/**
* {@inheritdoc}
*/
public function getCacheControlDirective($key)
{
return array_key_exists($key, $this->computedCacheControl) ? $this->computedCacheControl[$key] : null;
}
/**
* Sets a cookie.
*
* @param Cookie $cookie
*/
public function setCookie(Cookie $cookie)
{
$this->cookies[$cookie->getDomain()][$cookie->getPath()][$cookie->getName()] = $cookie;
}
/**
* 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)
{
if (null === $path) {
$path = '/';
}
unset($this->cookies[$domain][$path][$name]);
if (empty($this->cookies[$domain][$path])) {
unset($this->cookies[$domain][$path]);
if (empty($this->cookies[$domain])) {
unset($this->cookies[$domain]);
}
}
}
/**
* Returns an array with all cookies.
*
* @param string $format
*
* @return array
*
* @throws \InvalidArgumentException When the $format is invalid
*/
public function getCookies($format = self::COOKIES_FLAT)
{
if (!in_array($format, array(self::COOKIES_FLAT, self::COOKIES_ARRAY))) {
throw new \InvalidArgumentException(sprintf('Format "%s" invalid (%s).', $format, implode(', ', array(self::COOKIES_FLAT, self::COOKIES_ARRAY))));
}
if (self::COOKIES_ARRAY === $format) {
return $this->cookies;
}
$flattenedCookies = array();
foreach ($this->cookies as $path) {
foreach ($path as $cookies) {
foreach ($cookies as $cookie) {
$flattenedCookies[] = $cookie;
}
}
}
return $flattenedCookies;
}
/**
* Clears a cookie in the browser.
*
* @param string $name
* @param string $path
* @param string $domain
* @param bool $secure
* @param bool $httpOnly
*/
public function clearCookie($name, $path = '/', $domain = null, $secure = false, $httpOnly = true)
{
$this->setCookie(new Cookie($name, null, 1, $path, $domain, $secure, $httpOnly));
}
/**
* Generates a HTTP Content-Disposition field-value.
*
* @param string $disposition One of "inline" or "attachment"
* @param string $filename A unicode string
* @param string $filenameFallback A string containing only ASCII characters that
* 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
*/
public function makeDisposition($disposition, $filename, $filenameFallback = '')
{
if (!in_array($disposition, array(self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE))) {
throw new \InvalidArgumentException(sprintf('The disposition must be either "%s" or "%s".', self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE));
}
if ('' == $filenameFallback) {
$filenameFallback = $filename;
}
// filenameFallback is not ASCII.
if (!preg_match('/^[\x20-\x7e]*$/', $filenameFallback)) {
throw new \InvalidArgumentException('The filename fallback must only contain ASCII characters.');
}
// percent characters aren't safe in fallback.
if (false !== strpos($filenameFallback, '%')) {
throw new \InvalidArgumentException('The filename fallback cannot contain the "%" character.');
}
// path separators aren't allowed in either.
if (false !== strpos($filename, '/') || false !== strpos($filename, '\\') || false !== strpos($filenameFallback, '/') || false !== strpos($filenameFallback, '\\')) {
throw new \InvalidArgumentException('The filename and the fallback cannot contain the "/" and "\\" characters.');
}
$output = sprintf('%s; filename="%s"', $disposition, str_replace('"', '\\"', $filenameFallback));
if ($filename !== $filenameFallback) {
$output .= sprintf("; filename*=utf-8''%s", rawurlencode($filename));
}
return $output;
}
/**
* Returns the calculated value of the cache-control header.
*
* This considers several other headers and calculates or modifies the
* cache-control header to a sensible, conservative value.
*
* @return string
*/
protected function computeCacheControlValue()
{
if (!$this->cacheControl && !$this->has('ETag') && !$this->has('Last-Modified') && !$this->has('Expires')) {
return 'no-cache';
}
if (!$this->cacheControl) {
// conservative by default
return 'private, must-revalidate';
}
$header = $this->getCacheControlHeader();
if (isset($this->cacheControl['public']) || isset($this->cacheControl['private'])) {
return $header;
}
// public if s-maxage is defined, private otherwise
if (!isset($this->cacheControl['s-maxage'])) {
return $header.', private';
}
return $header;
}
}

View file

@ -0,0 +1,102 @@
<?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;
/**
* ServerBag is a container for HTTP headers from the $_SERVER variable.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Bulat Shakirzyanov <mallluhuct@gmail.com>
* @author Robert Kiss <kepten@gmail.com>
*/
class ServerBag extends ParameterBag
{
/**
* Gets the HTTP headers.
*
* @return array
*/
public function getHeaders()
{
$headers = array();
$contentHeaders = array('CONTENT_LENGTH' => true, 'CONTENT_MD5' => true, 'CONTENT_TYPE' => true);
foreach ($this->parameters as $key => $value) {
if (0 === strpos($key, 'HTTP_')) {
$headers[substr($key, 5)] = $value;
}
// CONTENT_* are not prefixed with HTTP_
elseif (isset($contentHeaders[$key])) {
$headers[$key] = $value;
}
}
if (isset($this->parameters['PHP_AUTH_USER'])) {
$headers['PHP_AUTH_USER'] = $this->parameters['PHP_AUTH_USER'];
$headers['PHP_AUTH_PW'] = isset($this->parameters['PHP_AUTH_PW']) ? $this->parameters['PHP_AUTH_PW'] : '';
} else {
/*
* php-cgi under Apache does not pass HTTP Basic user/pass to PHP by default
* For this workaround to work, add these lines to your .htaccess file:
* RewriteCond %{HTTP:Authorization} ^(.+)$
* RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
*
* A sample .htaccess file:
* RewriteEngine On
* RewriteCond %{HTTP:Authorization} ^(.+)$
* RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
* RewriteCond %{REQUEST_FILENAME} !-f
* RewriteRule ^(.*)$ app.php [QSA,L]
*/
$authorizationHeader = null;
if (isset($this->parameters['HTTP_AUTHORIZATION'])) {
$authorizationHeader = $this->parameters['HTTP_AUTHORIZATION'];
} elseif (isset($this->parameters['REDIRECT_HTTP_AUTHORIZATION'])) {
$authorizationHeader = $this->parameters['REDIRECT_HTTP_AUTHORIZATION'];
}
if (null !== $authorizationHeader) {
if (0 === stripos($authorizationHeader, 'basic ')) {
// Decode AUTHORIZATION header into PHP_AUTH_USER and PHP_AUTH_PW when authorization header is basic
$exploded = explode(':', base64_decode(substr($authorizationHeader, 6)), 2);
if (count($exploded) == 2) {
list($headers['PHP_AUTH_USER'], $headers['PHP_AUTH_PW']) = $exploded;
}
} elseif (empty($this->parameters['PHP_AUTH_DIGEST']) && (0 === stripos($authorizationHeader, 'digest '))) {
// In some circumstances PHP_AUTH_DIGEST needs to be set
$headers['PHP_AUTH_DIGEST'] = $authorizationHeader;
$this->parameters['PHP_AUTH_DIGEST'] = $authorizationHeader;
} elseif (0 === stripos($authorizationHeader, 'bearer ')) {
/*
* XXX: Since there is no PHP_AUTH_BEARER in PHP predefined variables,
* I'll just set $headers['AUTHORIZATION'] here.
* http://php.net/manual/en/reserved.variables.server.php
*/
$headers['AUTHORIZATION'] = $authorizationHeader;
}
}
}
if (isset($headers['AUTHORIZATION'])) {
return $headers;
}
// PHP_AUTH_USER/PHP_AUTH_PW
if (isset($headers['PHP_AUTH_USER'])) {
$headers['AUTHORIZATION'] = 'Basic '.base64_encode($headers['PHP_AUTH_USER'].':'.$headers['PHP_AUTH_PW']);
} elseif (isset($headers['PHP_AUTH_DIGEST'])) {
$headers['AUTHORIZATION'] = $headers['PHP_AUTH_DIGEST'];
}
return $headers;
}
}

View file

@ -0,0 +1,157 @@
<?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\Attribute;
/**
* This class relates to session attribute storage.
*/
class AttributeBag implements AttributeBagInterface, \IteratorAggregate, \Countable
{
private $name = 'attributes';
/**
* @var string
*/
private $storageKey;
/**
* @var array
*/
protected $attributes = array();
/**
* Constructor.
*
* @param string $storageKey The key used to store attributes in the session
*/
public function __construct($storageKey = '_sf2_attributes')
{
$this->storageKey = $storageKey;
}
/**
* {@inheritdoc}
*/
public function getName()
{
return $this->name;
}
public function setName($name)
{
$this->name = $name;
}
/**
* {@inheritdoc}
*/
public function initialize(array &$attributes)
{
$this->attributes = &$attributes;
}
/**
* {@inheritdoc}
*/
public function getStorageKey()
{
return $this->storageKey;
}
/**
* {@inheritdoc}
*/
public function has($name)
{
return array_key_exists($name, $this->attributes);
}
/**
* {@inheritdoc}
*/
public function get($name, $default = null)
{
return array_key_exists($name, $this->attributes) ? $this->attributes[$name] : $default;
}
/**
* {@inheritdoc}
*/
public function set($name, $value)
{
$this->attributes[$name] = $value;
}
/**
* {@inheritdoc}
*/
public function all()
{
return $this->attributes;
}
/**
* {@inheritdoc}
*/
public function replace(array $attributes)
{
$this->attributes = array();
foreach ($attributes as $key => $value) {
$this->set($key, $value);
}
}
/**
* {@inheritdoc}
*/
public function remove($name)
{
$retval = null;
if (array_key_exists($name, $this->attributes)) {
$retval = $this->attributes[$name];
unset($this->attributes[$name]);
}
return $retval;
}
/**
* {@inheritdoc}
*/
public function clear()
{
$return = $this->attributes;
$this->attributes = array();
return $return;
}
/**
* Returns an iterator for attributes.
*
* @return \ArrayIterator An \ArrayIterator instance
*/
public function getIterator()
{
return new \ArrayIterator($this->attributes);
}
/**
* Returns the number of attributes.
*
* @return int The number of attributes
*/
public function count()
{
return count($this->attributes);
}
}

View file

@ -0,0 +1,72 @@
<?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\Attribute;
use Symfony\Component\HttpFoundation\Session\SessionBagInterface;
/**
* Attributes store.
*
* @author Drak <drak@zikula.org>
*/
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
*/
public function has($name);
/**
* Returns an attribute.
*
* @param string $name The attribute name
* @param mixed $default The default value if not found
*
* @return mixed
*/
public function get($name, $default = null);
/**
* Sets an attribute.
*
* @param string $name
* @param mixed $value
*/
public function set($name, $value);
/**
* Returns attributes.
*
* @return array Attributes
*/
public function all();
/**
* Sets attributes.
*
* @param array $attributes Attributes
*/
public function replace(array $attributes);
/**
* Removes an attribute.
*
* @param string $name
*
* @return mixed The removed value or null when it does not exist
*/
public function remove($name);
}

View file

@ -0,0 +1,160 @@
<?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\Attribute;
/**
* This class provides structured storage of session attributes using
* a name spacing character in the key.
*
* @author Drak <drak@zikula.org>
*/
class NamespacedAttributeBag extends AttributeBag
{
/**
* Namespace character.
*
* @var string
*/
private $namespaceCharacter;
/**
* Constructor.
*
* @param string $storageKey Session storage key
* @param string $namespaceCharacter Namespace character to use in keys
*/
public function __construct($storageKey = '_sf2_attributes', $namespaceCharacter = '/')
{
$this->namespaceCharacter = $namespaceCharacter;
parent::__construct($storageKey);
}
/**
* {@inheritdoc}
*/
public function has($name)
{
// reference mismatch: if fixed, re-introduced in array_key_exists; keep as it is
$attributes = $this->resolveAttributePath($name);
$name = $this->resolveKey($name);
if (null === $attributes) {
return false;
}
return array_key_exists($name, $attributes);
}
/**
* {@inheritdoc}
*/
public function get($name, $default = null)
{
// reference mismatch: if fixed, re-introduced in array_key_exists; keep as it is
$attributes = $this->resolveAttributePath($name);
$name = $this->resolveKey($name);
if (null === $attributes) {
return $default;
}
return array_key_exists($name, $attributes) ? $attributes[$name] : $default;
}
/**
* {@inheritdoc}
*/
public function set($name, $value)
{
$attributes = &$this->resolveAttributePath($name, true);
$name = $this->resolveKey($name);
$attributes[$name] = $value;
}
/**
* {@inheritdoc}
*/
public function remove($name)
{
$retval = null;
$attributes = &$this->resolveAttributePath($name);
$name = $this->resolveKey($name);
if (null !== $attributes && array_key_exists($name, $attributes)) {
$retval = $attributes[$name];
unset($attributes[$name]);
}
return $retval;
}
/**
* Resolves a path in attributes property and returns it as a reference.
*
* This method allows structured namespacing of session attributes.
*
* @param string $name Key name
* @param bool $writeContext Write context, default false
*
* @return array
*/
protected function &resolveAttributePath($name, $writeContext = false)
{
$array = &$this->attributes;
$name = (strpos($name, $this->namespaceCharacter) === 0) ? substr($name, 1) : $name;
// Check if there is anything to do, else return
if (!$name) {
return $array;
}
$parts = explode($this->namespaceCharacter, $name);
if (count($parts) < 2) {
if (!$writeContext) {
return $array;
}
$array[$parts[0]] = array();
return $array;
}
unset($parts[count($parts) - 1]);
foreach ($parts as $part) {
if (null !== $array && !array_key_exists($part, $array)) {
$array[$part] = $writeContext ? array() : null;
}
$array = &$array[$part];
}
return $array;
}
/**
* Resolves the key from the name.
*
* This is the last part in a dot separated string.
*
* @param string $name
*
* @return string
*/
protected function resolveKey($name)
{
if (false !== $pos = strrpos($name, $this->namespaceCharacter)) {
$name = substr($name, $pos + 1);
}
return $name;
}
}

View file

@ -0,0 +1,175 @@
<?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\Flash;
/**
* AutoExpireFlashBag flash message container.
*
* @author Drak <drak@zikula.org>
*/
class AutoExpireFlashBag implements FlashBagInterface
{
private $name = 'flashes';
/**
* Flash messages.
*
* @var array
*/
private $flashes = array('display' => array(), 'new' => array());
/**
* The storage key for flashes in the session.
*
* @var string
*/
private $storageKey;
/**
* Constructor.
*
* @param string $storageKey The key used to store flashes in the session
*/
public function __construct($storageKey = '_sf2_flashes')
{
$this->storageKey = $storageKey;
}
/**
* {@inheritdoc}
*/
public function getName()
{
return $this->name;
}
public function setName($name)
{
$this->name = $name;
}
/**
* {@inheritdoc}
*/
public function initialize(array &$flashes)
{
$this->flashes = &$flashes;
// The logic: messages from the last request will be stored in new, so we move them to previous
// This request we will show what is in 'display'. What is placed into 'new' this time round will
// be moved to display next time round.
$this->flashes['display'] = array_key_exists('new', $this->flashes) ? $this->flashes['new'] : array();
$this->flashes['new'] = array();
}
/**
* {@inheritdoc}
*/
public function add($type, $message)
{
$this->flashes['new'][$type][] = $message;
}
/**
* {@inheritdoc}
*/
public function peek($type, array $default = array())
{
return $this->has($type) ? $this->flashes['display'][$type] : $default;
}
/**
* {@inheritdoc}
*/
public function peekAll()
{
return array_key_exists('display', $this->flashes) ? (array) $this->flashes['display'] : array();
}
/**
* {@inheritdoc}
*/
public function get($type, array $default = array())
{
$return = $default;
if (!$this->has($type)) {
return $return;
}
if (isset($this->flashes['display'][$type])) {
$return = $this->flashes['display'][$type];
unset($this->flashes['display'][$type]);
}
return $return;
}
/**
* {@inheritdoc}
*/
public function all()
{
$return = $this->flashes['display'];
$this->flashes = array('new' => array(), 'display' => array());
return $return;
}
/**
* {@inheritdoc}
*/
public function setAll(array $messages)
{
$this->flashes['new'] = $messages;
}
/**
* {@inheritdoc}
*/
public function set($type, $messages)
{
$this->flashes['new'][$type] = (array) $messages;
}
/**
* {@inheritdoc}
*/
public function has($type)
{
return array_key_exists($type, $this->flashes['display']) && $this->flashes['display'][$type];
}
/**
* {@inheritdoc}
*/
public function keys()
{
return array_keys($this->flashes['display']);
}
/**
* {@inheritdoc}
*/
public function getStorageKey()
{
return $this->storageKey;
}
/**
* {@inheritdoc}
*/
public function clear()
{
return $this->all();
}
}

View file

@ -0,0 +1,182 @@
<?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\Flash;
/**
* FlashBag flash message container.
*
* \IteratorAggregate implementation is deprecated and will be removed in 3.0.
*
* @author Drak <drak@zikula.org>
*/
class FlashBag implements FlashBagInterface, \IteratorAggregate
{
private $name = 'flashes';
/**
* Flash messages.
*
* @var array
*/
private $flashes = array();
/**
* The storage key for flashes in the session.
*
* @var string
*/
private $storageKey;
/**
* Constructor.
*
* @param string $storageKey The key used to store flashes in the session
*/
public function __construct($storageKey = '_sf2_flashes')
{
$this->storageKey = $storageKey;
}
/**
* {@inheritdoc}
*/
public function getName()
{
return $this->name;
}
public function setName($name)
{
$this->name = $name;
}
/**
* {@inheritdoc}
*/
public function initialize(array &$flashes)
{
$this->flashes = &$flashes;
}
/**
* {@inheritdoc}
*/
public function add($type, $message)
{
$this->flashes[$type][] = $message;
}
/**
* {@inheritdoc}
*/
public function peek($type, array $default = array())
{
return $this->has($type) ? $this->flashes[$type] : $default;
}
/**
* {@inheritdoc}
*/
public function peekAll()
{
return $this->flashes;
}
/**
* {@inheritdoc}
*/
public function get($type, array $default = array())
{
if (!$this->has($type)) {
return $default;
}
$return = $this->flashes[$type];
unset($this->flashes[$type]);
return $return;
}
/**
* {@inheritdoc}
*/
public function all()
{
$return = $this->peekAll();
$this->flashes = array();
return $return;
}
/**
* {@inheritdoc}
*/
public function set($type, $messages)
{
$this->flashes[$type] = (array) $messages;
}
/**
* {@inheritdoc}
*/
public function setAll(array $messages)
{
$this->flashes = $messages;
}
/**
* {@inheritdoc}
*/
public function has($type)
{
return array_key_exists($type, $this->flashes) && $this->flashes[$type];
}
/**
* {@inheritdoc}
*/
public function keys()
{
return array_keys($this->flashes);
}
/**
* {@inheritdoc}
*/
public function getStorageKey()
{
return $this->storageKey;
}
/**
* {@inheritdoc}
*/
public function clear()
{
return $this->all();
}
/**
* Returns an iterator for flashes.
*
* @deprecated since version 2.4, to be removed in 3.0.
*
* @return \ArrayIterator An \ArrayIterator instance
*/
public function getIterator()
{
@trigger_error('The '.__METHOD__.' method is deprecated since version 2.4 and will be removed in 3.0.', E_USER_DEPRECATED);
return new \ArrayIterator($this->all());
}
}

View file

@ -0,0 +1,95 @@
<?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\Flash;
use Symfony\Component\HttpFoundation\Session\SessionBagInterface;
/**
* FlashBagInterface.
*
* @author Drak <drak@zikula.org>
*/
interface FlashBagInterface extends SessionBagInterface
{
/**
* Adds a flash message for type.
*
* @param string $type
* @param string $message
*/
public function add($type, $message);
/**
* Registers a message for a given type.
*
* @param string $type
* @param string|array $message
*/
public function set($type, $message);
/**
* Gets flash messages for a given type.
*
* @param string $type Message category type
* @param array $default Default value if $type does not exist
*
* @return array
*/
public function peek($type, array $default = array());
/**
* Gets all flash messages.
*
* @return array
*/
public function peekAll();
/**
* Gets and clears flash from the stack.
*
* @param string $type
* @param array $default Default value if $type does not exist
*
* @return array
*/
public function get($type, array $default = array());
/**
* Gets and clears flashes from the stack.
*
* @return array
*/
public function all();
/**
* Sets all flash messages.
*
* @param array $messages
*/
public function setAll(array $messages);
/**
* Has flash messages for a given type?
*
* @param string $type
*
* @return bool
*/
public function has($type);
/**
* Returns a list of all defined types.
*
* @return array
*/
public function keys();
}

View file

@ -0,0 +1,249 @@
<?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\Session\Storage\SessionStorageInterface;
use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag;
use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBagInterface;
use Symfony\Component\HttpFoundation\Session\Flash\FlashBag;
use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface;
use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
/**
* Session.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Drak <drak@zikula.org>
*/
class Session implements SessionInterface, \IteratorAggregate, \Countable
{
/**
* Storage driver.
*
* @var SessionStorageInterface
*/
protected $storage;
/**
* @var string
*/
private $flashName;
/**
* @var string
*/
private $attributeName;
/**
* Constructor.
*
* @param SessionStorageInterface $storage A SessionStorageInterface instance
* @param AttributeBagInterface $attributes An AttributeBagInterface instance, (defaults null for default AttributeBag)
* @param FlashBagInterface $flashes A FlashBagInterface instance (defaults null for default FlashBag)
*/
public function __construct(SessionStorageInterface $storage = null, AttributeBagInterface $attributes = null, FlashBagInterface $flashes = null)
{
$this->storage = $storage ?: new NativeSessionStorage();
$attributes = $attributes ?: new AttributeBag();
$this->attributeName = $attributes->getName();
$this->registerBag($attributes);
$flashes = $flashes ?: new FlashBag();
$this->flashName = $flashes->getName();
$this->registerBag($flashes);
}
/**
* {@inheritdoc}
*/
public function start()
{
return $this->storage->start();
}
/**
* {@inheritdoc}
*/
public function has($name)
{
return $this->storage->getBag($this->attributeName)->has($name);
}
/**
* {@inheritdoc}
*/
public function get($name, $default = null)
{
return $this->storage->getBag($this->attributeName)->get($name, $default);
}
/**
* {@inheritdoc}
*/
public function set($name, $value)
{
$this->storage->getBag($this->attributeName)->set($name, $value);
}
/**
* {@inheritdoc}
*/
public function all()
{
return $this->storage->getBag($this->attributeName)->all();
}
/**
* {@inheritdoc}
*/
public function replace(array $attributes)
{
$this->storage->getBag($this->attributeName)->replace($attributes);
}
/**
* {@inheritdoc}
*/
public function remove($name)
{
return $this->storage->getBag($this->attributeName)->remove($name);
}
/**
* {@inheritdoc}
*/
public function clear()
{
$this->storage->getBag($this->attributeName)->clear();
}
/**
* {@inheritdoc}
*/
public function isStarted()
{
return $this->storage->isStarted();
}
/**
* Returns an iterator for attributes.
*
* @return \ArrayIterator An \ArrayIterator instance
*/
public function getIterator()
{
return new \ArrayIterator($this->storage->getBag($this->attributeName)->all());
}
/**
* Returns the number of attributes.
*
* @return int The number of attributes
*/
public function count()
{
return count($this->storage->getBag($this->attributeName)->all());
}
/**
* {@inheritdoc}
*/
public function invalidate($lifetime = null)
{
$this->storage->clear();
return $this->migrate(true, $lifetime);
}
/**
* {@inheritdoc}
*/
public function migrate($destroy = false, $lifetime = null)
{
return $this->storage->regenerate($destroy, $lifetime);
}
/**
* {@inheritdoc}
*/
public function save()
{
$this->storage->save();
}
/**
* {@inheritdoc}
*/
public function getId()
{
return $this->storage->getId();
}
/**
* {@inheritdoc}
*/
public function setId($id)
{
$this->storage->setId($id);
}
/**
* {@inheritdoc}
*/
public function getName()
{
return $this->storage->getName();
}
/**
* {@inheritdoc}
*/
public function setName($name)
{
$this->storage->setName($name);
}
/**
* {@inheritdoc}
*/
public function getMetadataBag()
{
return $this->storage->getMetadataBag();
}
/**
* {@inheritdoc}
*/
public function registerBag(SessionBagInterface $bag)
{
$this->storage->registerBag($bag);
}
/**
* {@inheritdoc}
*/
public function getBag($name)
{
return $this->storage->getBag($name);
}
/**
* Gets the flashbag interface.
*
* @return FlashBagInterface
*/
public function getFlashBag()
{
return $this->getBag($this->flashName);
}
}

View file

@ -0,0 +1,48 @@
<?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;
/**
* Session Bag store.
*
* @author Drak <drak@zikula.org>
*/
interface SessionBagInterface
{
/**
* Gets this bag's name.
*
* @return string
*/
public function getName();
/**
* Initializes the Bag.
*
* @param array $array
*/
public function initialize(array &$array);
/**
* Gets the storage key for this bag.
*
* @return string
*/
public function getStorageKey();
/**
* Clears out data from bag.
*
* @return mixed Whatever data was contained
*/
public function clear();
}

View file

@ -0,0 +1,182 @@
<?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\Session\Storage\MetadataBag;
/**
* Interface for the session.
*
* @author Drak <drak@zikula.org>
*/
interface SessionInterface
{
/**
* Starts the session storage.
*
* @return bool True if session started
*
* @throws \RuntimeException If session fails to start.
*/
public function start();
/**
* Returns the session ID.
*
* @return string The session ID
*/
public function getId();
/**
* Sets the session ID.
*
* @param string $id
*/
public function setId($id);
/**
* Returns the session name.
*
* @return mixed The session name
*/
public function getName();
/**
* Sets the session name.
*
* @param string $name
*/
public function setName($name);
/**
* Invalidates the current session.
*
* Clears all session attributes and flashes and regenerates the
* session and deletes the old session from persistence.
*
* @param int $lifetime Sets the cookie lifetime for the session cookie. A null value
* will leave the system settings unchanged, 0 sets the cookie
* to expire with browser session. Time is in seconds, and is
* not a Unix timestamp.
*
* @return bool True if session invalidated, false if error
*/
public function invalidate($lifetime = null);
/**
* Migrates the current session to a new session id while maintaining all
* session attributes.
*
* @param bool $destroy Whether to delete the old session or leave it to garbage collection
* @param int $lifetime Sets the cookie lifetime for the session cookie. A null value
* will leave the system settings unchanged, 0 sets the cookie
* to expire with browser session. Time is in seconds, and is
* not a Unix timestamp.
*
* @return bool True if session migrated, false if error
*/
public function migrate($destroy = false, $lifetime = null);
/**
* Force the session to be saved and closed.
*
* This method is generally not required for real sessions as
* the session will be automatically saved at the end of
* code execution.
*/
public function save();
/**
* Checks if an attribute is defined.
*
* @param string $name The attribute name
*
* @return bool true if the attribute is defined, false otherwise
*/
public function has($name);
/**
* Returns an attribute.
*
* @param string $name The attribute name
* @param mixed $default The default value if not found
*
* @return mixed
*/
public function get($name, $default = null);
/**
* Sets an attribute.
*
* @param string $name
* @param mixed $value
*/
public function set($name, $value);
/**
* Returns attributes.
*
* @return array Attributes
*/
public function all();
/**
* Sets attributes.
*
* @param array $attributes Attributes
*/
public function replace(array $attributes);
/**
* Removes an attribute.
*
* @param string $name
*
* @return mixed The removed value or null when it does not exist
*/
public function remove($name);
/**
* Clears all attributes.
*/
public function clear();
/**
* Checks if the session was started.
*
* @return bool
*/
public function isStarted();
/**
* Registers a SessionBagInterface with the session.
*
* @param SessionBagInterface $bag
*/
public function registerBag(SessionBagInterface $bag);
/**
* Gets a bag instance by name.
*
* @param string $name
*
* @return SessionBagInterface
*/
public function getBag($name);
/**
* Gets session meta.
*
* @return MetadataBag
*/
public function getMetadataBag();
}

View file

@ -0,0 +1,271 @@
<?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;
@trigger_error('The '.__NAMESPACE__.'\LegacyPdoSessionHandler class is deprecated since version 2.6 and will be removed in 3.0. Use the Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler class instead.', E_USER_DEPRECATED);
/**
* Session handler using a PDO connection to read and write data.
*
* Session data is a binary string that can contain non-printable characters like the null byte.
* For this reason this handler base64 encodes the data to be able to save it in a character column.
*
* This version of the PdoSessionHandler does NOT implement locking. So concurrent requests to the
* same session can result in data loss due to race conditions.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Michael Williams <michael.williams@funsational.com>
* @author Tobias Schultze <http://tobion.de>
*
* @deprecated since version 2.6, to be removed in 3.0. Use
* {@link PdoSessionHandler} instead.
*/
class LegacyPdoSessionHandler implements \SessionHandlerInterface
{
/**
* @var \PDO PDO instance
*/
private $pdo;
/**
* @var string Table name
*/
private $table;
/**
* @var string Column for session id
*/
private $idCol;
/**
* @var string Column for session data
*/
private $dataCol;
/**
* @var string Column for timestamp
*/
private $timeCol;
/**
* Constructor.
*
* List of available options:
* * db_table: The name of the table [required]
* * db_id_col: The column where to store the session id [default: sess_id]
* * db_data_col: The column where to store the session data [default: sess_data]
* * db_time_col: The column where to store the timestamp [default: sess_time]
*
* @param \PDO $pdo A \PDO instance
* @param array $dbOptions An associative array of DB options
*
* @throws \InvalidArgumentException When "db_table" option is not provided
*/
public function __construct(\PDO $pdo, array $dbOptions = array())
{
if (!array_key_exists('db_table', $dbOptions)) {
throw new \InvalidArgumentException('You must provide the "db_table" option for a PdoSessionStorage.');
}
if (\PDO::ERRMODE_EXCEPTION !== $pdo->getAttribute(\PDO::ATTR_ERRMODE)) {
throw new \InvalidArgumentException(sprintf('"%s" requires PDO error mode attribute be set to throw Exceptions (i.e. $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION))', __CLASS__));
}
$this->pdo = $pdo;
$dbOptions = array_merge(array(
'db_id_col' => 'sess_id',
'db_data_col' => 'sess_data',
'db_time_col' => 'sess_time',
), $dbOptions);
$this->table = $dbOptions['db_table'];
$this->idCol = $dbOptions['db_id_col'];
$this->dataCol = $dbOptions['db_data_col'];
$this->timeCol = $dbOptions['db_time_col'];
}
/**
* {@inheritdoc}
*/
public function open($savePath, $sessionName)
{
return true;
}
/**
* {@inheritdoc}
*/
public function close()
{
return true;
}
/**
* {@inheritdoc}
*/
public function destroy($sessionId)
{
// delete the record associated with this id
$sql = "DELETE FROM $this->table WHERE $this->idCol = :id";
try {
$stmt = $this->pdo->prepare($sql);
$stmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
$stmt->execute();
} catch (\PDOException $e) {
throw new \RuntimeException(sprintf('PDOException was thrown when trying to delete a session: %s', $e->getMessage()), 0, $e);
}
return true;
}
/**
* {@inheritdoc}
*/
public function gc($maxlifetime)
{
// delete the session records that have expired
$sql = "DELETE FROM $this->table WHERE $this->timeCol < :time";
try {
$stmt = $this->pdo->prepare($sql);
$stmt->bindValue(':time', time() - $maxlifetime, \PDO::PARAM_INT);
$stmt->execute();
} catch (\PDOException $e) {
throw new \RuntimeException(sprintf('PDOException was thrown when trying to delete expired sessions: %s', $e->getMessage()), 0, $e);
}
return true;
}
/**
* {@inheritdoc}
*/
public function read($sessionId)
{
$sql = "SELECT $this->dataCol FROM $this->table WHERE $this->idCol = :id";
try {
$stmt = $this->pdo->prepare($sql);
$stmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
$stmt->execute();
// We use fetchAll instead of fetchColumn to make sure the DB cursor gets closed
$sessionRows = $stmt->fetchAll(\PDO::FETCH_NUM);
if ($sessionRows) {
return base64_decode($sessionRows[0][0]);
}
return '';
} catch (\PDOException $e) {
throw new \RuntimeException(sprintf('PDOException was thrown when trying to read the session data: %s', $e->getMessage()), 0, $e);
}
}
/**
* {@inheritdoc}
*/
public function write($sessionId, $data)
{
$encoded = base64_encode($data);
try {
// We use a single MERGE SQL query when supported by the database.
$mergeSql = $this->getMergeSql();
if (null !== $mergeSql) {
$mergeStmt = $this->pdo->prepare($mergeSql);
$mergeStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
$mergeStmt->bindParam(':data', $encoded, \PDO::PARAM_STR);
$mergeStmt->bindValue(':time', time(), \PDO::PARAM_INT);
$mergeStmt->execute();
return true;
}
$updateStmt = $this->pdo->prepare(
"UPDATE $this->table SET $this->dataCol = :data, $this->timeCol = :time WHERE $this->idCol = :id"
);
$updateStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
$updateStmt->bindParam(':data', $encoded, \PDO::PARAM_STR);
$updateStmt->bindValue(':time', time(), \PDO::PARAM_INT);
$updateStmt->execute();
// When MERGE is not supported, like in Postgres, we have to use this approach that can result in
// duplicate key errors when the same session is written simultaneously. We can just catch such an
// error and re-execute the update. This is similar to a serializable transaction with retry logic
// on serialization failures but without the overhead and without possible false positives due to
// longer gap locking.
if (!$updateStmt->rowCount()) {
try {
$insertStmt = $this->pdo->prepare(
"INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->timeCol) VALUES (:id, :data, :time)"
);
$insertStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
$insertStmt->bindParam(':data', $encoded, \PDO::PARAM_STR);
$insertStmt->bindValue(':time', time(), \PDO::PARAM_INT);
$insertStmt->execute();
} catch (\PDOException $e) {
// Handle integrity violation SQLSTATE 23000 (or a subclass like 23505 in Postgres) for duplicate keys
if (0 === strpos($e->getCode(), '23')) {
$updateStmt->execute();
} else {
throw $e;
}
}
}
} catch (\PDOException $e) {
throw new \RuntimeException(sprintf('PDOException was thrown when trying to write the session data: %s', $e->getMessage()), 0, $e);
}
return true;
}
/**
* Returns a merge/upsert (i.e. insert or update) SQL query when supported by the database.
*
* @return string|null The SQL string or null when not supported
*/
private function getMergeSql()
{
$driver = $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME);
switch ($driver) {
case 'mysql':
return "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->timeCol) VALUES (:id, :data, :time) ".
"ON DUPLICATE KEY UPDATE $this->dataCol = VALUES($this->dataCol), $this->timeCol = VALUES($this->timeCol)";
case 'oci':
// DUAL is Oracle specific dummy table
return "MERGE INTO $this->table USING DUAL ON ($this->idCol = :id) ".
"WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->timeCol) VALUES (:id, :data, :time) ".
"WHEN MATCHED THEN UPDATE SET $this->dataCol = :data, $this->timeCol = :time";
case 'sqlsrv' === $driver && version_compare($this->pdo->getAttribute(\PDO::ATTR_SERVER_VERSION), '10', '>='):
// MERGE is only available since SQL Server 2008 and must be terminated by semicolon
// It also requires HOLDLOCK according to http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx
return "MERGE INTO $this->table WITH (HOLDLOCK) USING (SELECT 1 AS dummy) AS src ON ($this->idCol = :id) ".
"WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->timeCol) VALUES (:id, :data, :time) ".
"WHEN MATCHED THEN UPDATE SET $this->dataCol = :data, $this->timeCol = :time;";
case 'sqlite':
return "INSERT OR REPLACE INTO $this->table ($this->idCol, $this->dataCol, $this->timeCol) VALUES (:id, :data, :time)";
}
}
/**
* Return a PDO instance.
*
* @return \PDO
*/
protected function getConnection()
{
return $this->pdo;
}
}

View file

@ -0,0 +1,119 @@
<?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;
/**
* MemcacheSessionHandler.
*
* @author Drak <drak@zikula.org>
*/
class MemcacheSessionHandler implements \SessionHandlerInterface
{
/**
* @var \Memcache Memcache driver
*/
private $memcache;
/**
* @var int Time to live in seconds
*/
private $ttl;
/**
* @var string Key prefix for shared environments
*/
private $prefix;
/**
* Constructor.
*
* List of available options:
* * prefix: The prefix to use for the memcache keys in order to avoid collision
* * expiretime: The time to live in seconds
*
* @param \Memcache $memcache A \Memcache instance
* @param array $options An associative array of Memcache options
*
* @throws \InvalidArgumentException When unsupported options are passed
*/
public function __construct(\Memcache $memcache, array $options = array())
{
if ($diff = array_diff(array_keys($options), array('prefix', 'expiretime'))) {
throw new \InvalidArgumentException(sprintf(
'The following options are not supported "%s"', implode(', ', $diff)
));
}
$this->memcache = $memcache;
$this->ttl = isset($options['expiretime']) ? (int) $options['expiretime'] : 86400;
$this->prefix = isset($options['prefix']) ? $options['prefix'] : 'sf2s';
}
/**
* {@inheritdoc}
*/
public function open($savePath, $sessionName)
{
return true;
}
/**
* {@inheritdoc}
*/
public function close()
{
return true;
}
/**
* {@inheritdoc}
*/
public function read($sessionId)
{
return $this->memcache->get($this->prefix.$sessionId) ?: '';
}
/**
* {@inheritdoc}
*/
public function write($sessionId, $data)
{
return $this->memcache->set($this->prefix.$sessionId, $data, 0, time() + $this->ttl);
}
/**
* {@inheritdoc}
*/
public function destroy($sessionId)
{
return $this->memcache->delete($this->prefix.$sessionId);
}
/**
* {@inheritdoc}
*/
public function gc($maxlifetime)
{
// not required here because memcache will auto expire the records anyhow.
return true;
}
/**
* Return a Memcache instance.
*
* @return \Memcache
*/
protected function getMemcache()
{
return $this->memcache;
}
}

View file

@ -0,0 +1,125 @@
<?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;
/**
* MemcachedSessionHandler.
*
* Memcached based session storage handler based on the Memcached class
* provided by the PHP memcached extension.
*
* @see http://php.net/memcached
*
* @author Drak <drak@zikula.org>
*/
class MemcachedSessionHandler implements \SessionHandlerInterface
{
/**
* @var \Memcached Memcached driver
*/
private $memcached;
/**
* @var int Time to live in seconds
*/
private $ttl;
/**
* @var string Key prefix for shared environments
*/
private $prefix;
/**
* Constructor.
*
* List of available options:
* * prefix: The prefix to use for the memcached keys in order to avoid collision
* * expiretime: The time to live in seconds
*
* @param \Memcached $memcached A \Memcached instance
* @param array $options An associative array of Memcached options
*
* @throws \InvalidArgumentException When unsupported options are passed
*/
public function __construct(\Memcached $memcached, array $options = array())
{
$this->memcached = $memcached;
if ($diff = array_diff(array_keys($options), array('prefix', 'expiretime'))) {
throw new \InvalidArgumentException(sprintf(
'The following options are not supported "%s"', implode(', ', $diff)
));
}
$this->ttl = isset($options['expiretime']) ? (int) $options['expiretime'] : 86400;
$this->prefix = isset($options['prefix']) ? $options['prefix'] : 'sf2s';
}
/**
* {@inheritdoc}
*/
public function open($savePath, $sessionName)
{
return true;
}
/**
* {@inheritdoc}
*/
public function close()
{
return true;
}
/**
* {@inheritdoc}
*/
public function read($sessionId)
{
return $this->memcached->get($this->prefix.$sessionId) ?: '';
}
/**
* {@inheritdoc}
*/
public function write($sessionId, $data)
{
return $this->memcached->set($this->prefix.$sessionId, $data, time() + $this->ttl);
}
/**
* {@inheritdoc}
*/
public function destroy($sessionId)
{
return $this->memcached->delete($this->prefix.$sessionId);
}
/**
* {@inheritdoc}
*/
public function gc($maxlifetime)
{
// not required here because memcached will auto expire the records anyhow.
return true;
}
/**
* Return a Memcached instance.
*
* @return \Memcached
*/
protected function getMemcached()
{
return $this->memcached;
}
}

View file

@ -0,0 +1,232 @@
<?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;
/**
* MongoDB session handler.
*
* @author Markus Bachmann <markus.bachmann@bachi.biz>
*/
class MongoDbSessionHandler implements \SessionHandlerInterface
{
/**
* @var \Mongo|\MongoClient|\MongoDB\Client
*/
private $mongo;
/**
* @var \MongoCollection
*/
private $collection;
/**
* @var array
*/
private $options;
/**
* Constructor.
*
* List of available options:
* * database: The name of the database [required]
* * collection: The name of the collection [required]
* * id_field: The field name for storing the session id [default: _id]
* * data_field: The field name for storing the session data [default: data]
* * time_field: The field name for storing the timestamp [default: time]
* * expiry_field: The field name for storing the expiry-timestamp [default: expires_at]
*
* It is strongly recommended to put an index on the `expiry_field` for
* garbage-collection. Alternatively it's possible to automatically expire
* the sessions in the database as described below:
*
* A TTL collections can be used on MongoDB 2.2+ to cleanup expired sessions
* automatically. Such an index can for example look like this:
*
* db.<session-collection>.ensureIndex(
* { "<expiry-field>": 1 },
* { "expireAfterSeconds": 0 }
* )
*
* More details on: http://docs.mongodb.org/manual/tutorial/expire-data/
*
* If you use such an index, you can drop `gc_probability` to 0 since
* no garbage-collection is required.
*
* @param \Mongo|\MongoClient|\MongoDB\Client $mongo A MongoDB\Client, MongoClient or Mongo instance
* @param array $options An associative array of field options
*
* @throws \InvalidArgumentException When MongoClient or Mongo instance not provided
* @throws \InvalidArgumentException When "database" or "collection" not provided
*/
public function __construct($mongo, array $options)
{
if (!($mongo instanceof \MongoDB\Client || $mongo instanceof \MongoClient || $mongo instanceof \Mongo)) {
throw new \InvalidArgumentException('MongoClient or Mongo instance required');
}
if (!isset($options['database']) || !isset($options['collection'])) {
throw new \InvalidArgumentException('You must provide the "database" and "collection" option for MongoDBSessionHandler');
}
$this->mongo = $mongo;
$this->options = array_merge(array(
'id_field' => '_id',
'data_field' => 'data',
'time_field' => 'time',
'expiry_field' => 'expires_at',
), $options);
}
/**
* {@inheritdoc}
*/
public function open($savePath, $sessionName)
{
return true;
}
/**
* {@inheritdoc}
*/
public function close()
{
return true;
}
/**
* {@inheritdoc}
*/
public function destroy($sessionId)
{
$methodName = $this->mongo instanceof \MongoDB\Client ? 'deleteOne' : 'remove';
$this->getCollection()->$methodName(array(
$this->options['id_field'] => $sessionId,
));
return true;
}
/**
* {@inheritdoc}
*/
public function gc($maxlifetime)
{
$methodName = $this->mongo instanceof \MongoDB\Client ? 'deleteOne' : 'remove';
$this->getCollection()->$methodName(array(
$this->options['expiry_field'] => array('$lt' => $this->createDateTime()),
));
return true;
}
/**
* {@inheritdoc}
*/
public function write($sessionId, $data)
{
$expiry = $this->createDateTime(time() + (int) ini_get('session.gc_maxlifetime'));
$fields = array(
$this->options['time_field'] => $this->createDateTime(),
$this->options['expiry_field'] => $expiry,
);
$options = array('upsert' => true);
if ($this->mongo instanceof \MongoDB\Client) {
$fields[$this->options['data_field']] = new \MongoDB\BSON\Binary($data, \MongoDB\BSON\Binary::TYPE_OLD_BINARY);
} else {
$fields[$this->options['data_field']] = new \MongoBinData($data, \MongoBinData::BYTE_ARRAY);
$options['multiple'] = false;
}
$methodName = $this->mongo instanceof \MongoDB\Client ? 'updateOne' : 'update';
$this->getCollection()->$methodName(
array($this->options['id_field'] => $sessionId),
array('$set' => $fields),
$options
);
return true;
}
/**
* {@inheritdoc}
*/
public function read($sessionId)
{
$dbData = $this->getCollection()->findOne(array(
$this->options['id_field'] => $sessionId,
$this->options['expiry_field'] => array('$gte' => $this->createDateTime()),
));
if (null === $dbData) {
return '';
}
if ($dbData[$this->options['data_field']] instanceof \MongoDB\BSON\Binary) {
return $dbData[$this->options['data_field']]->getData();
}
return $dbData[$this->options['data_field']]->bin;
}
/**
* Return a "MongoCollection" instance.
*
* @return \MongoCollection
*/
private function getCollection()
{
if (null === $this->collection) {
$this->collection = $this->mongo->selectCollection($this->options['database'], $this->options['collection']);
}
return $this->collection;
}
/**
* Return a Mongo instance.
*
* @return \Mongo|\MongoClient|\MongoDB\Client
*/
protected function getMongo()
{
return $this->mongo;
}
/**
* Create a date object using the class appropriate for the current mongo connection.
*
* Return an instance of a MongoDate or \MongoDB\BSON\UTCDateTime
*
* @param int $seconds An integer representing UTC seconds since Jan 1 1970. Defaults to now.
*
* @return \MongoDate|\MongoDB\BSON\UTCDateTime
*/
private function createDateTime($seconds = null)
{
if (null === $seconds) {
$seconds = time();
}
if ($this->mongo instanceof \MongoDB\Client) {
return new \MongoDB\BSON\UTCDateTime($seconds * 1000);
}
return new \MongoDate($seconds);
}
}

View file

@ -0,0 +1,58 @@
<?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;
/**
* NativeFileSessionHandler.
*
* Native session handler using PHP's built in file storage.
*
* @author Drak <drak@zikula.org>
*/
class NativeFileSessionHandler extends NativeSessionHandler
{
/**
* Constructor.
*
* @param string $savePath Path of directory to save session files
* Default null will leave setting as defined by PHP.
* '/path', 'N;/path', or 'N;octal-mode;/path
*
* @see http://php.net/session.configuration.php#ini.session.save-path for further details.
*
* @throws \InvalidArgumentException On invalid $savePath
*/
public function __construct($savePath = null)
{
if (null === $savePath) {
$savePath = ini_get('session.save_path');
}
$baseDir = $savePath;
if ($count = substr_count($savePath, ';')) {
if ($count > 2) {
throw new \InvalidArgumentException(sprintf('Invalid argument $savePath \'%s\'', $savePath));
}
// characters after last ';' are the path
$baseDir = ltrim(strrchr($savePath, ';'), ';');
}
if ($baseDir && !is_dir($baseDir) && !@mkdir($baseDir, 0777, true) && !is_dir($baseDir)) {
throw new \RuntimeException(sprintf('Session Storage was not able to create directory "%s"', $baseDir));
}
ini_set('session.save_path', $savePath);
ini_set('session.save_handler', 'files');
}
}

View file

@ -0,0 +1,24 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
// Adds SessionHandler functionality if available.
// @see http://php.net/sessionhandler
if (PHP_VERSION_ID >= 50400) {
class NativeSessionHandler extends \SessionHandler
{
}
} else {
class NativeSessionHandler
{
}
}

View file

@ -0,0 +1,70 @@
<?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;
/**
* NullSessionHandler.
*
* Can be used in unit testing or in a situations where persisted sessions are not desired.
*
* @author Drak <drak@zikula.org>
*/
class NullSessionHandler implements \SessionHandlerInterface
{
/**
* {@inheritdoc}
*/
public function open($savePath, $sessionName)
{
return true;
}
/**
* {@inheritdoc}
*/
public function close()
{
return true;
}
/**
* {@inheritdoc}
*/
public function read($sessionId)
{
return '';
}
/**
* {@inheritdoc}
*/
public function write($sessionId, $data)
{
return true;
}
/**
* {@inheritdoc}
*/
public function destroy($sessionId)
{
return true;
}
/**
* {@inheritdoc}
*/
public function gc($maxlifetime)
{
return true;
}
}

View file

@ -0,0 +1,721 @@
<?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;
/**
* Session handler using a PDO connection to read and write data.
*
* It works with MySQL, PostgreSQL, Oracle, SQL Server and SQLite and implements
* different locking strategies to handle concurrent access to the same session.
* Locking is necessary to prevent loss of data due to race conditions and to keep
* the session data consistent between read() and write(). With locking, requests
* for the same session will wait until the other one finished writing. For this
* reason it's best practice to close a session as early as possible to improve
* concurrency. PHPs internal files session handler also implements locking.
*
* Attention: Since SQLite does not support row level locks but locks the whole database,
* it means only one session can be accessed at a time. Even different sessions would wait
* for another to finish. So saving session in SQLite should only be considered for
* development or prototypes.
*
* Session data is a binary string that can contain non-printable characters like the null byte.
* For this reason it must be saved in a binary column in the database like BLOB in MySQL.
* Saving it in a character column could corrupt the data. You can use createTable()
* to initialize a correctly defined table.
*
* @see http://php.net/sessionhandlerinterface
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Michael Williams <michael.williams@funsational.com>
* @author Tobias Schultze <http://tobion.de>
*/
class PdoSessionHandler implements \SessionHandlerInterface
{
/**
* No locking is done. This means sessions are prone to loss of data due to
* race conditions of concurrent requests to the same session. The last session
* write will win in this case. It might be useful when you implement your own
* logic to deal with this like an optimistic approach.
*/
const LOCK_NONE = 0;
/**
* Creates an application-level lock on a session. The disadvantage is that the
* lock is not enforced by the database and thus other, unaware parts of the
* application could still concurrently modify the session. The advantage is it
* does not require a transaction.
* This mode is not available for SQLite and not yet implemented for oci and sqlsrv.
*/
const LOCK_ADVISORY = 1;
/**
* Issues a real row lock. Since it uses a transaction between opening and
* closing a session, you have to be careful when you use same database connection
* that you also use for your application logic. This mode is the default because
* it's the only reliable solution across DBMSs.
*/
const LOCK_TRANSACTIONAL = 2;
/**
* @var \PDO|null PDO instance or null when not connected yet
*/
private $pdo;
/**
* @var string|null|false DSN string or null for session.save_path or false when lazy connection disabled
*/
private $dsn = false;
/**
* @var string Database driver
*/
private $driver;
/**
* @var string Table name
*/
private $table = 'sessions';
/**
* @var string Column for session id
*/
private $idCol = 'sess_id';
/**
* @var string Column for session data
*/
private $dataCol = 'sess_data';
/**
* @var string Column for lifetime
*/
private $lifetimeCol = 'sess_lifetime';
/**
* @var string Column for timestamp
*/
private $timeCol = 'sess_time';
/**
* @var string Username when lazy-connect
*/
private $username = '';
/**
* @var string Password when lazy-connect
*/
private $password = '';
/**
* @var array Connection options when lazy-connect
*/
private $connectionOptions = array();
/**
* @var int The strategy for locking, see constants
*/
private $lockMode = self::LOCK_TRANSACTIONAL;
/**
* It's an array to support multiple reads before closing which is manual, non-standard usage.
*
* @var \PDOStatement[] An array of statements to release advisory locks
*/
private $unlockStatements = array();
/**
* @var bool True when the current session exists but expired according to session.gc_maxlifetime
*/
private $sessionExpired = false;
/**
* @var bool Whether a transaction is active
*/
private $inTransaction = false;
/**
* @var bool Whether gc() has been called
*/
private $gcCalled = false;
/**
* Constructor.
*
* You can either pass an existing database connection as PDO instance or
* pass a DSN string that will be used to lazy-connect to the database
* when the session is actually used. Furthermore it's possible to pass null
* which will then use the session.save_path ini setting as PDO DSN parameter.
*
* List of available options:
* * db_table: The name of the table [default: sessions]
* * db_id_col: The column where to store the session id [default: sess_id]
* * db_data_col: The column where to store the session data [default: sess_data]
* * db_lifetime_col: The column where to store the lifetime [default: sess_lifetime]
* * db_time_col: The column where to store the timestamp [default: sess_time]
* * db_username: The username when lazy-connect [default: '']
* * db_password: The password when lazy-connect [default: '']
* * db_connection_options: An array of driver-specific connection options [default: array()]
* * lock_mode: The strategy for locking, see constants [default: LOCK_TRANSACTIONAL]
*
* @param \PDO|string|null $pdoOrDsn A \PDO instance or DSN string or null
* @param array $options An associative array of options
*
* @throws \InvalidArgumentException When PDO error mode is not PDO::ERRMODE_EXCEPTION
*/
public function __construct($pdoOrDsn = null, array $options = array())
{
if ($pdoOrDsn instanceof \PDO) {
if (\PDO::ERRMODE_EXCEPTION !== $pdoOrDsn->getAttribute(\PDO::ATTR_ERRMODE)) {
throw new \InvalidArgumentException(sprintf('"%s" requires PDO error mode attribute be set to throw Exceptions (i.e. $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION))', __CLASS__));
}
$this->pdo = $pdoOrDsn;
$this->driver = $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME);
} else {
$this->dsn = $pdoOrDsn;
}
$this->table = isset($options['db_table']) ? $options['db_table'] : $this->table;
$this->idCol = isset($options['db_id_col']) ? $options['db_id_col'] : $this->idCol;
$this->dataCol = isset($options['db_data_col']) ? $options['db_data_col'] : $this->dataCol;
$this->lifetimeCol = isset($options['db_lifetime_col']) ? $options['db_lifetime_col'] : $this->lifetimeCol;
$this->timeCol = isset($options['db_time_col']) ? $options['db_time_col'] : $this->timeCol;
$this->username = isset($options['db_username']) ? $options['db_username'] : $this->username;
$this->password = isset($options['db_password']) ? $options['db_password'] : $this->password;
$this->connectionOptions = isset($options['db_connection_options']) ? $options['db_connection_options'] : $this->connectionOptions;
$this->lockMode = isset($options['lock_mode']) ? $options['lock_mode'] : $this->lockMode;
}
/**
* Creates the table to store sessions which can be called once for setup.
*
* Session ID is saved in a column of maximum length 128 because that is enough even
* for a 512 bit configured session.hash_function like Whirlpool. Session data is
* saved in a BLOB. One could also use a shorter inlined varbinary column
* if one was sure the data fits into it.
*
* @throws \PDOException When the table already exists
* @throws \DomainException When an unsupported PDO driver is used
*/
public function createTable()
{
// connect if we are not yet
$this->getConnection();
switch ($this->driver) {
case 'mysql':
// We use varbinary for the ID column because it prevents unwanted conversions:
// - character set conversions between server and client
// - trailing space removal
// - case-insensitivity
// - language processing like é == e
$sql = "CREATE TABLE $this->table ($this->idCol VARBINARY(128) NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol MEDIUMINT NOT NULL, $this->timeCol INTEGER UNSIGNED NOT NULL) COLLATE utf8_bin, ENGINE = InnoDB";
break;
case 'sqlite':
$sql = "CREATE TABLE $this->table ($this->idCol TEXT NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)";
break;
case 'pgsql':
$sql = "CREATE TABLE $this->table ($this->idCol VARCHAR(128) NOT NULL PRIMARY KEY, $this->dataCol BYTEA NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)";
break;
case 'oci':
$sql = "CREATE TABLE $this->table ($this->idCol VARCHAR2(128) NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)";
break;
case 'sqlsrv':
$sql = "CREATE TABLE $this->table ($this->idCol VARCHAR(128) NOT NULL PRIMARY KEY, $this->dataCol VARBINARY(MAX) NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)";
break;
default:
throw new \DomainException(sprintf('Creating the session table is currently not implemented for PDO driver "%s".', $this->driver));
}
try {
$this->pdo->exec($sql);
} catch (\PDOException $e) {
$this->rollback();
throw $e;
}
}
/**
* Returns true when the current session exists but expired according to session.gc_maxlifetime.
*
* Can be used to distinguish between a new session and one that expired due to inactivity.
*
* @return bool Whether current session expired
*/
public function isSessionExpired()
{
return $this->sessionExpired;
}
/**
* {@inheritdoc}
*/
public function open($savePath, $sessionName)
{
if (null === $this->pdo) {
$this->connect($this->dsn ?: $savePath);
}
return true;
}
/**
* {@inheritdoc}
*/
public function read($sessionId)
{
try {
return $this->doRead($sessionId);
} catch (\PDOException $e) {
$this->rollback();
throw $e;
}
}
/**
* {@inheritdoc}
*/
public function gc($maxlifetime)
{
// We delay gc() to close() so that it is executed outside the transactional and blocking read-write process.
// This way, pruning expired sessions does not block them from being started while the current session is used.
$this->gcCalled = true;
return true;
}
/**
* {@inheritdoc}
*/
public function destroy($sessionId)
{
// delete the record associated with this id
$sql = "DELETE FROM $this->table WHERE $this->idCol = :id";
try {
$stmt = $this->pdo->prepare($sql);
$stmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
$stmt->execute();
} catch (\PDOException $e) {
$this->rollback();
throw $e;
}
return true;
}
/**
* {@inheritdoc}
*/
public function write($sessionId, $data)
{
$maxlifetime = (int) ini_get('session.gc_maxlifetime');
try {
// We use a single MERGE SQL query when supported by the database.
$mergeStmt = $this->getMergeStatement($sessionId, $data, $maxlifetime);
if (null !== $mergeStmt) {
$mergeStmt->execute();
return true;
}
$updateStmt = $this->pdo->prepare(
"UPDATE $this->table SET $this->dataCol = :data, $this->lifetimeCol = :lifetime, $this->timeCol = :time WHERE $this->idCol = :id"
);
$updateStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
$updateStmt->bindParam(':data', $data, \PDO::PARAM_LOB);
$updateStmt->bindParam(':lifetime', $maxlifetime, \PDO::PARAM_INT);
$updateStmt->bindValue(':time', time(), \PDO::PARAM_INT);
$updateStmt->execute();
// When MERGE is not supported, like in Postgres < 9.5, we have to use this approach that can result in
// duplicate key errors when the same session is written simultaneously (given the LOCK_NONE behavior).
// We can just catch such an error and re-execute the update. This is similar to a serializable
// transaction with retry logic on serialization failures but without the overhead and without possible
// false positives due to longer gap locking.
if (!$updateStmt->rowCount()) {
try {
$insertStmt = $this->pdo->prepare(
"INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time)"
);
$insertStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
$insertStmt->bindParam(':data', $data, \PDO::PARAM_LOB);
$insertStmt->bindParam(':lifetime', $maxlifetime, \PDO::PARAM_INT);
$insertStmt->bindValue(':time', time(), \PDO::PARAM_INT);
$insertStmt->execute();
} catch (\PDOException $e) {
// Handle integrity violation SQLSTATE 23000 (or a subclass like 23505 in Postgres) for duplicate keys
if (0 === strpos($e->getCode(), '23')) {
$updateStmt->execute();
} else {
throw $e;
}
}
}
} catch (\PDOException $e) {
$this->rollback();
throw $e;
}
return true;
}
/**
* {@inheritdoc}
*/
public function close()
{
$this->commit();
while ($unlockStmt = array_shift($this->unlockStatements)) {
$unlockStmt->execute();
}
if ($this->gcCalled) {
$this->gcCalled = false;
// delete the session records that have expired
$sql = "DELETE FROM $this->table WHERE $this->lifetimeCol + $this->timeCol < :time";
$stmt = $this->pdo->prepare($sql);
$stmt->bindValue(':time', time(), \PDO::PARAM_INT);
$stmt->execute();
}
if (false !== $this->dsn) {
$this->pdo = null; // only close lazy-connection
}
return true;
}
/**
* Lazy-connects to the database.
*
* @param string $dsn DSN string
*/
private function connect($dsn)
{
$this->pdo = new \PDO($dsn, $this->username, $this->password, $this->connectionOptions);
$this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
$this->driver = $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME);
}
/**
* Helper method to begin a transaction.
*
* Since SQLite does not support row level locks, we have to acquire a reserved lock
* on the database immediately. Because of https://bugs.php.net/42766 we have to create
* such a transaction manually which also means we cannot use PDO::commit or
* PDO::rollback or PDO::inTransaction for SQLite.
*
* Also MySQLs default isolation, REPEATABLE READ, causes deadlock for different sessions
* due to http://www.mysqlperformanceblog.com/2013/12/12/one-more-innodb-gap-lock-to-avoid/ .
* So we change it to READ COMMITTED.
*/
private function beginTransaction()
{
if (!$this->inTransaction) {
if ('sqlite' === $this->driver) {
$this->pdo->exec('BEGIN IMMEDIATE TRANSACTION');
} else {
if ('mysql' === $this->driver) {
$this->pdo->exec('SET TRANSACTION ISOLATION LEVEL READ COMMITTED');
}
$this->pdo->beginTransaction();
}
$this->inTransaction = true;
}
}
/**
* Helper method to commit a transaction.
*/
private function commit()
{
if ($this->inTransaction) {
try {
// commit read-write transaction which also releases the lock
if ('sqlite' === $this->driver) {
$this->pdo->exec('COMMIT');
} else {
$this->pdo->commit();
}
$this->inTransaction = false;
} catch (\PDOException $e) {
$this->rollback();
throw $e;
}
}
}
/**
* Helper method to rollback a transaction.
*/
private function rollback()
{
// We only need to rollback if we are in a transaction. Otherwise the resulting
// error would hide the real problem why rollback was called. We might not be
// in a transaction when not using the transactional locking behavior or when
// two callbacks (e.g. destroy and write) are invoked that both fail.
if ($this->inTransaction) {
if ('sqlite' === $this->driver) {
$this->pdo->exec('ROLLBACK');
} else {
$this->pdo->rollBack();
}
$this->inTransaction = false;
}
}
/**
* Reads the session data in respect to the different locking strategies.
*
* 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
*/
private function doRead($sessionId)
{
$this->sessionExpired = false;
if (self::LOCK_ADVISORY === $this->lockMode) {
$this->unlockStatements[] = $this->doAdvisoryLock($sessionId);
}
$selectSql = $this->getSelectSql();
$selectStmt = $this->pdo->prepare($selectSql);
$selectStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
do {
$selectStmt->execute();
$sessionRows = $selectStmt->fetchAll(\PDO::FETCH_NUM);
if ($sessionRows) {
if ($sessionRows[0][1] + $sessionRows[0][2] < time()) {
$this->sessionExpired = true;
return '';
}
return is_resource($sessionRows[0][0]) ? stream_get_contents($sessionRows[0][0]) : $sessionRows[0][0];
}
if (self::LOCK_TRANSACTIONAL === $this->lockMode && 'sqlite' !== $this->driver) {
// Exclusive-reading of non-existent rows does not block, so we need to do an insert to block
// until other connections to the session are committed.
try {
$insertStmt = $this->pdo->prepare(
"INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time)"
);
$insertStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
$insertStmt->bindValue(':data', '', \PDO::PARAM_LOB);
$insertStmt->bindValue(':lifetime', 0, \PDO::PARAM_INT);
$insertStmt->bindValue(':time', time(), \PDO::PARAM_INT);
$insertStmt->execute();
} catch (\PDOException $e) {
// Catch duplicate key error because other connection created the session already.
// It would only not be the case when the other connection destroyed the session.
if (0 === strpos($e->getCode(), '23')) {
// Retrieve finished session data written by concurrent connection by restarting the loop.
// We have to start a new transaction as a failed query will mark the current transaction as
// aborted in PostgreSQL and disallow further queries within it.
$this->rollback();
$this->beginTransaction();
continue;
}
throw $e;
}
}
return '';
} while (true);
}
/**
* Executes an application-level lock on the database.
*
* @param string $sessionId Session ID
*
* @return \PDOStatement The statement that needs to be executed later to release the lock
*
* @throws \DomainException When an unsupported PDO driver is used
*
* @todo implement missing advisory locks
* - for oci using DBMS_LOCK.REQUEST
* - for sqlsrv using sp_getapplock with LockOwner = Session
*/
private function doAdvisoryLock($sessionId)
{
switch ($this->driver) {
case 'mysql':
// should we handle the return value? 0 on timeout, null on error
// we use a timeout of 50 seconds which is also the default for innodb_lock_wait_timeout
$stmt = $this->pdo->prepare('SELECT GET_LOCK(:key, 50)');
$stmt->bindValue(':key', $sessionId, \PDO::PARAM_STR);
$stmt->execute();
$releaseStmt = $this->pdo->prepare('DO RELEASE_LOCK(:key)');
$releaseStmt->bindValue(':key', $sessionId, \PDO::PARAM_STR);
return $releaseStmt;
case 'pgsql':
// Obtaining an exclusive session level advisory lock requires an integer key.
// So we convert the HEX representation of the session id to an integer.
// Since integers are signed, we have to skip one hex char to fit in the range.
if (4 === PHP_INT_SIZE) {
$sessionInt1 = hexdec(substr($sessionId, 0, 7));
$sessionInt2 = hexdec(substr($sessionId, 7, 7));
$stmt = $this->pdo->prepare('SELECT pg_advisory_lock(:key1, :key2)');
$stmt->bindValue(':key1', $sessionInt1, \PDO::PARAM_INT);
$stmt->bindValue(':key2', $sessionInt2, \PDO::PARAM_INT);
$stmt->execute();
$releaseStmt = $this->pdo->prepare('SELECT pg_advisory_unlock(:key1, :key2)');
$releaseStmt->bindValue(':key1', $sessionInt1, \PDO::PARAM_INT);
$releaseStmt->bindValue(':key2', $sessionInt2, \PDO::PARAM_INT);
} else {
$sessionBigInt = hexdec(substr($sessionId, 0, 15));
$stmt = $this->pdo->prepare('SELECT pg_advisory_lock(:key)');
$stmt->bindValue(':key', $sessionBigInt, \PDO::PARAM_INT);
$stmt->execute();
$releaseStmt = $this->pdo->prepare('SELECT pg_advisory_unlock(:key)');
$releaseStmt->bindValue(':key', $sessionBigInt, \PDO::PARAM_INT);
}
return $releaseStmt;
case 'sqlite':
throw new \DomainException('SQLite does not support advisory locks.');
default:
throw new \DomainException(sprintf('Advisory locks are currently not implemented for PDO driver "%s".', $this->driver));
}
}
/**
* Return a locking or nonlocking SQL query to read session information.
*
* @return string The SQL string
*
* @throws \DomainException When an unsupported PDO driver is used
*/
private function getSelectSql()
{
if (self::LOCK_TRANSACTIONAL === $this->lockMode) {
$this->beginTransaction();
switch ($this->driver) {
case 'mysql':
case 'oci':
case 'pgsql':
return "SELECT $this->dataCol, $this->lifetimeCol, $this->timeCol FROM $this->table WHERE $this->idCol = :id FOR UPDATE";
case 'sqlsrv':
return "SELECT $this->dataCol, $this->lifetimeCol, $this->timeCol FROM $this->table WITH (UPDLOCK, ROWLOCK) WHERE $this->idCol = :id";
case 'sqlite':
// we already locked when starting transaction
break;
default:
throw new \DomainException(sprintf('Transactional locks are currently not implemented for PDO driver "%s".', $this->driver));
}
}
return "SELECT $this->dataCol, $this->lifetimeCol, $this->timeCol FROM $this->table WHERE $this->idCol = :id";
}
/**
* Returns a merge/upsert (i.e. insert or update) statement when supported by the database for writing session data.
*
* @param string $sessionId Session ID
* @param string $data Encoded session data
* @param int $maxlifetime session.gc_maxlifetime
*
* @return \PDOStatement|null The merge statement or null when not supported
*/
private function getMergeStatement($sessionId, $data, $maxlifetime)
{
$mergeSql = null;
switch (true) {
case 'mysql' === $this->driver:
$mergeSql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time) ".
"ON DUPLICATE KEY UPDATE $this->dataCol = VALUES($this->dataCol), $this->lifetimeCol = VALUES($this->lifetimeCol), $this->timeCol = VALUES($this->timeCol)";
break;
case 'oci' === $this->driver:
// DUAL is Oracle specific dummy table
$mergeSql = "MERGE INTO $this->table USING DUAL ON ($this->idCol = ?) ".
"WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (?, ?, ?, ?) ".
"WHEN MATCHED THEN UPDATE SET $this->dataCol = ?, $this->lifetimeCol = ?, $this->timeCol = ?";
break;
case 'sqlsrv' === $this->driver && version_compare($this->pdo->getAttribute(\PDO::ATTR_SERVER_VERSION), '10', '>='):
// MERGE is only available since SQL Server 2008 and must be terminated by semicolon
// It also requires HOLDLOCK according to http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx
$mergeSql = "MERGE INTO $this->table WITH (HOLDLOCK) USING (SELECT 1 AS dummy) AS src ON ($this->idCol = ?) ".
"WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (?, ?, ?, ?) ".
"WHEN MATCHED THEN UPDATE SET $this->dataCol = ?, $this->lifetimeCol = ?, $this->timeCol = ?;";
break;
case 'sqlite' === $this->driver:
$mergeSql = "INSERT OR REPLACE INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time)";
break;
case 'pgsql' === $this->driver && version_compare($this->pdo->getAttribute(\PDO::ATTR_SERVER_VERSION), '9.5', '>='):
$mergeSql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time) ".
"ON CONFLICT ($this->idCol) DO UPDATE SET ($this->dataCol, $this->lifetimeCol, $this->timeCol) = (EXCLUDED.$this->dataCol, EXCLUDED.$this->lifetimeCol, EXCLUDED.$this->timeCol)";
break;
}
if (null !== $mergeSql) {
$mergeStmt = $this->pdo->prepare($mergeSql);
if ('sqlsrv' === $this->driver || 'oci' === $this->driver) {
$mergeStmt->bindParam(1, $sessionId, \PDO::PARAM_STR);
$mergeStmt->bindParam(2, $sessionId, \PDO::PARAM_STR);
$mergeStmt->bindParam(3, $data, \PDO::PARAM_LOB);
$mergeStmt->bindParam(4, $maxlifetime, \PDO::PARAM_INT);
$mergeStmt->bindValue(5, time(), \PDO::PARAM_INT);
$mergeStmt->bindParam(6, $data, \PDO::PARAM_LOB);
$mergeStmt->bindParam(7, $maxlifetime, \PDO::PARAM_INT);
$mergeStmt->bindValue(8, time(), \PDO::PARAM_INT);
} else {
$mergeStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
$mergeStmt->bindParam(':data', $data, \PDO::PARAM_LOB);
$mergeStmt->bindParam(':lifetime', $maxlifetime, \PDO::PARAM_INT);
$mergeStmt->bindValue(':time', time(), \PDO::PARAM_INT);
}
return $mergeStmt;
}
}
/**
* Return a PDO instance.
*
* @return \PDO
*/
protected function getConnection()
{
if (null === $this->pdo) {
$this->connect($this->dsn ?: ini_get('session.save_path'));
}
return $this->pdo;
}
}

View file

@ -0,0 +1,91 @@
<?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;
/**
* Wraps another SessionHandlerInterface to only write the session when it has been modified.
*
* @author Adrien Brault <adrien.brault@gmail.com>
*/
class WriteCheckSessionHandler implements \SessionHandlerInterface
{
/**
* @var \SessionHandlerInterface
*/
private $wrappedSessionHandler;
/**
* @var array sessionId => session
*/
private $readSessions;
public function __construct(\SessionHandlerInterface $wrappedSessionHandler)
{
$this->wrappedSessionHandler = $wrappedSessionHandler;
}
/**
* {@inheritdoc}
*/
public function close()
{
return $this->wrappedSessionHandler->close();
}
/**
* {@inheritdoc}
*/
public function destroy($sessionId)
{
return $this->wrappedSessionHandler->destroy($sessionId);
}
/**
* {@inheritdoc}
*/
public function gc($maxlifetime)
{
return $this->wrappedSessionHandler->gc($maxlifetime);
}
/**
* {@inheritdoc}
*/
public function open($savePath, $sessionName)
{
return $this->wrappedSessionHandler->open($savePath, $sessionName);
}
/**
* {@inheritdoc}
*/
public function read($sessionId)
{
$session = $this->wrappedSessionHandler->read($sessionId);
$this->readSessions[$sessionId] = $session;
return $session;
}
/**
* {@inheritdoc}
*/
public function write($sessionId, $data)
{
if (isset($this->readSessions[$sessionId]) && $data === $this->readSessions[$sessionId]) {
return true;
}
return $this->wrappedSessionHandler->write($sessionId, $data);
}
}

View file

@ -0,0 +1,170 @@
<?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\Session\SessionBagInterface;
/**
* Metadata container.
*
* Adds metadata to the session.
*
* @author Drak <drak@zikula.org>
*/
class MetadataBag implements SessionBagInterface
{
const CREATED = 'c';
const UPDATED = 'u';
const LIFETIME = 'l';
/**
* @var string
*/
private $name = '__metadata';
/**
* @var string
*/
private $storageKey;
/**
* @var array
*/
protected $meta = array(self::CREATED => 0, self::UPDATED => 0, self::LIFETIME => 0);
/**
* Unix timestamp.
*
* @var int
*/
private $lastUsed;
/**
* @var int
*/
private $updateThreshold;
/**
* Constructor.
*
* @param string $storageKey The key used to store bag in the session
* @param int $updateThreshold The time to wait between two UPDATED updates
*/
public function __construct($storageKey = '_sf2_meta', $updateThreshold = 0)
{
$this->storageKey = $storageKey;
$this->updateThreshold = $updateThreshold;
}
/**
* {@inheritdoc}
*/
public function initialize(array &$array)
{
$this->meta = &$array;
if (isset($array[self::CREATED])) {
$this->lastUsed = $this->meta[self::UPDATED];
$timeStamp = time();
if ($timeStamp - $array[self::UPDATED] >= $this->updateThreshold) {
$this->meta[self::UPDATED] = $timeStamp;
}
} else {
$this->stampCreated();
}
}
/**
* Gets the lifetime that the session cookie was set with.
*
* @return int
*/
public function getLifetime()
{
return $this->meta[self::LIFETIME];
}
/**
* Stamps a new session's metadata.
*
* @param int $lifetime Sets the cookie lifetime for the session cookie. A null value
* will leave the system settings unchanged, 0 sets the cookie
* to expire with browser session. Time is in seconds, and is
* not a Unix timestamp.
*/
public function stampNew($lifetime = null)
{
$this->stampCreated($lifetime);
}
/**
* {@inheritdoc}
*/
public function getStorageKey()
{
return $this->storageKey;
}
/**
* Gets the created timestamp metadata.
*
* @return int Unix timestamp
*/
public function getCreated()
{
return $this->meta[self::CREATED];
}
/**
* Gets the last used metadata.
*
* @return int Unix timestamp
*/
public function getLastUsed()
{
return $this->lastUsed;
}
/**
* {@inheritdoc}
*/
public function clear()
{
// nothing to do
}
/**
* {@inheritdoc}
*/
public function getName()
{
return $this->name;
}
/**
* Sets name.
*
* @param string $name
*/
public function setName($name)
{
$this->name = $name;
}
private function stampCreated($lifetime = null)
{
$timeStamp = time();
$this->meta[self::CREATED] = $this->meta[self::UPDATED] = $this->lastUsed = $timeStamp;
$this->meta[self::LIFETIME] = (null === $lifetime) ? ini_get('session.cookie_lifetime') : $lifetime;
}
}

View file

@ -0,0 +1,268 @@
<?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\Session\SessionBagInterface;
/**
* MockArraySessionStorage mocks the session for unit tests.
*
* No PHP session is actually started since a session can be initialized
* and shutdown only once per PHP execution cycle.
*
* When doing functional testing, you should use MockFileSessionStorage instead.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Bulat Shakirzyanov <mallluhuct@gmail.com>
* @author Drak <drak@zikula.org>
*/
class MockArraySessionStorage implements SessionStorageInterface
{
/**
* @var string
*/
protected $id = '';
/**
* @var string
*/
protected $name;
/**
* @var bool
*/
protected $started = false;
/**
* @var bool
*/
protected $closed = false;
/**
* @var array
*/
protected $data = array();
/**
* @var MetadataBag
*/
protected $metadataBag;
/**
* @var array
*/
protected $bags;
/**
* Constructor.
*
* @param string $name Session name
* @param MetadataBag $metaBag MetadataBag instance
*/
public function __construct($name = 'MOCKSESSID', MetadataBag $metaBag = null)
{
$this->name = $name;
$this->setMetadataBag($metaBag);
}
/**
* Sets the session data.
*
* @param array $array
*/
public function setSessionData(array $array)
{
$this->data = $array;
}
/**
* {@inheritdoc}
*/
public function start()
{
if ($this->started) {
return true;
}
if (empty($this->id)) {
$this->id = $this->generateId();
}
$this->loadSession();
return true;
}
/**
* {@inheritdoc}
*/
public function regenerate($destroy = false, $lifetime = null)
{
if (!$this->started) {
$this->start();
}
$this->metadataBag->stampNew($lifetime);
$this->id = $this->generateId();
return true;
}
/**
* {@inheritdoc}
*/
public function getId()
{
return $this->id;
}
/**
* {@inheritdoc}
*/
public function setId($id)
{
if ($this->started) {
throw new \LogicException('Cannot set session ID after the session has started.');
}
$this->id = $id;
}
/**
* {@inheritdoc}
*/
public function getName()
{
return $this->name;
}
/**
* {@inheritdoc}
*/
public function setName($name)
{
$this->name = $name;
}
/**
* {@inheritdoc}
*/
public function save()
{
if (!$this->started || $this->closed) {
throw new \RuntimeException('Trying to save a session that was not started yet or was already closed');
}
// nothing to do since we don't persist the session data
$this->closed = false;
$this->started = false;
}
/**
* {@inheritdoc}
*/
public function clear()
{
// clear out the bags
foreach ($this->bags as $bag) {
$bag->clear();
}
// clear out the session
$this->data = array();
// reconnect the bags to the session
$this->loadSession();
}
/**
* {@inheritdoc}
*/
public function registerBag(SessionBagInterface $bag)
{
$this->bags[$bag->getName()] = $bag;
}
/**
* {@inheritdoc}
*/
public function getBag($name)
{
if (!isset($this->bags[$name])) {
throw new \InvalidArgumentException(sprintf('The SessionBagInterface %s is not registered.', $name));
}
if (!$this->started) {
$this->start();
}
return $this->bags[$name];
}
/**
* {@inheritdoc}
*/
public function isStarted()
{
return $this->started;
}
/**
* Sets the MetadataBag.
*
* @param MetadataBag $bag
*/
public function setMetadataBag(MetadataBag $bag = null)
{
if (null === $bag) {
$bag = new MetadataBag();
}
$this->metadataBag = $bag;
}
/**
* Gets the MetadataBag.
*
* @return MetadataBag
*/
public function getMetadataBag()
{
return $this->metadataBag;
}
/**
* Generates a session ID.
*
* This doesn't need to be particularly cryptographically secure since this is just
* a mock.
*
* @return string
*/
protected function generateId()
{
return hash('sha256', uniqid('ss_mock_', true));
}
protected function loadSession()
{
$bags = array_merge($this->bags, array($this->metadataBag));
foreach ($bags as $bag) {
$key = $bag->getStorageKey();
$this->data[$key] = isset($this->data[$key]) ? $this->data[$key] : array();
$bag->initialize($this->data[$key]);
}
$this->started = true;
$this->closed = false;
}
}

View file

@ -0,0 +1,138 @@
<?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;
/**
* MockFileSessionStorage is used to mock sessions for
* functional testing when done in a single PHP process.
*
* No PHP session is actually started since a session can be initialized
* and shutdown only once per PHP execution cycle and this class does
* not pollute any session related globals, including session_*() functions
* or session.* PHP ini directives.
*
* @author Drak <drak@zikula.org>
*/
class MockFileSessionStorage extends MockArraySessionStorage
{
/**
* @var string
*/
private $savePath;
/**
* Constructor.
*
* @param string $savePath Path of directory to save session files
* @param string $name Session name
* @param MetadataBag $metaBag MetadataBag instance
*/
public function __construct($savePath = null, $name = 'MOCKSESSID', MetadataBag $metaBag = null)
{
if (null === $savePath) {
$savePath = sys_get_temp_dir();
}
if (!is_dir($savePath) && !@mkdir($savePath, 0777, true) && !is_dir($savePath)) {
throw new \RuntimeException(sprintf('Session Storage was not able to create directory "%s"', $savePath));
}
$this->savePath = $savePath;
parent::__construct($name, $metaBag);
}
/**
* {@inheritdoc}
*/
public function start()
{
if ($this->started) {
return true;
}
if (!$this->id) {
$this->id = $this->generateId();
}
$this->read();
$this->started = true;
return true;
}
/**
* {@inheritdoc}
*/
public function regenerate($destroy = false, $lifetime = null)
{
if (!$this->started) {
$this->start();
}
if ($destroy) {
$this->destroy();
}
return parent::regenerate($destroy, $lifetime);
}
/**
* {@inheritdoc}
*/
public function save()
{
if (!$this->started) {
throw new \RuntimeException('Trying to save a session that was not started yet or was already closed');
}
file_put_contents($this->getFilePath(), serialize($this->data));
// this is needed for Silex, where the session object is re-used across requests
// in functional tests. In Symfony, the container is rebooted, so we don't have
// this issue
$this->started = false;
}
/**
* Deletes a session from persistent storage.
* Deliberately leaves session data in memory intact.
*/
private function destroy()
{
if (is_file($this->getFilePath())) {
unlink($this->getFilePath());
}
}
/**
* Calculate path to file.
*
* @return string File path
*/
private function getFilePath()
{
return $this->savePath.'/'.$this->id.'.mocksess';
}
/**
* Reads session from storage and loads session.
*/
private function read()
{
$filePath = $this->getFilePath();
$this->data = is_readable($filePath) && is_file($filePath) ? unserialize(file_get_contents($filePath)) : array();
$this->loadSession();
}
}

View file

@ -0,0 +1,430 @@
<?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\Session\SessionBagInterface;
use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeSessionHandler;
use Symfony\Component\HttpFoundation\Session\Storage\Proxy\NativeProxy;
use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy;
use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy;
/**
* This provides a base class for session attribute storage.
*
* @author Drak <drak@zikula.org>
*/
class NativeSessionStorage implements SessionStorageInterface
{
/**
* Array of SessionBagInterface.
*
* @var SessionBagInterface[]
*/
protected $bags;
/**
* @var bool
*/
protected $started = false;
/**
* @var bool
*/
protected $closed = false;
/**
* @var AbstractProxy
*/
protected $saveHandler;
/**
* @var MetadataBag
*/
protected $metadataBag;
/**
* Constructor.
*
* Depending on how you want the storage driver to behave you probably
* want to override this constructor entirely.
*
* List of options for $options array with their defaults.
*
* @see http://php.net/session.configuration for options
* but we omit 'session.' from the beginning of the keys for convenience.
*
* ("auto_start", is not supported as it tells PHP to start a session before
* PHP starts to execute user-land code. Setting during runtime has no effect).
*
* cache_limiter, "" (use "0" to prevent headers from being sent entirely).
* cookie_domain, ""
* cookie_httponly, ""
* cookie_lifetime, "0"
* cookie_path, "/"
* cookie_secure, ""
* entropy_file, ""
* entropy_length, "0"
* gc_divisor, "100"
* gc_maxlifetime, "1440"
* gc_probability, "1"
* hash_bits_per_character, "4"
* hash_function, "0"
* name, "PHPSESSID"
* referer_check, ""
* serialize_handler, "php"
* 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="
*
* @param array $options Session configuration options
* @param AbstractProxy|NativeSessionHandler|\SessionHandlerInterface|null $handler
* @param MetadataBag $metaBag MetadataBag
*/
public function __construct(array $options = array(), $handler = null, MetadataBag $metaBag = null)
{
session_cache_limiter(''); // disable by default because it's managed by HeaderBag (if used)
ini_set('session.use_cookies', 1);
session_register_shutdown();
$this->setMetadataBag($metaBag);
$this->setOptions($options);
$this->setSaveHandler($handler);
}
/**
* Gets the save handler instance.
*
* @return AbstractProxy
*/
public function getSaveHandler()
{
return $this->saveHandler;
}
/**
* {@inheritdoc}
*/
public function start()
{
if ($this->started) {
return true;
}
if (PHP_VERSION_ID >= 50400 && \PHP_SESSION_ACTIVE === session_status()) {
throw new \RuntimeException('Failed to start the session: already started by PHP.');
}
if (PHP_VERSION_ID < 50400 && !$this->closed && isset($_SESSION) && session_id()) {
// not 100% fool-proof, but is the most reliable way to determine if a session is active in PHP 5.3
throw new \RuntimeException('Failed to start the session: already started by PHP ($_SESSION is set).');
}
if (ini_get('session.use_cookies') && headers_sent($file, $line)) {
throw new \RuntimeException(sprintf('Failed to start the session because headers have already been sent by "%s" at line %d.', $file, $line));
}
// ok to try and start the session
if (!session_start()) {
throw new \RuntimeException('Failed to start the session');
}
$this->loadSession();
if (!$this->saveHandler->isWrapper() && !$this->saveHandler->isSessionHandlerInterface()) {
// This condition matches only PHP 5.3 with internal save handlers
$this->saveHandler->setActive(true);
}
return true;
}
/**
* {@inheritdoc}
*/
public function getId()
{
return $this->saveHandler->getId();
}
/**
* {@inheritdoc}
*/
public function setId($id)
{
$this->saveHandler->setId($id);
}
/**
* {@inheritdoc}
*/
public function getName()
{
return $this->saveHandler->getName();
}
/**
* {@inheritdoc}
*/
public function setName($name)
{
$this->saveHandler->setName($name);
}
/**
* {@inheritdoc}
*/
public function regenerate($destroy = false, $lifetime = null)
{
// Cannot regenerate the session ID for non-active sessions.
if (PHP_VERSION_ID >= 50400 && \PHP_SESSION_ACTIVE !== session_status()) {
return false;
}
// Check if session ID exists in PHP 5.3
if (PHP_VERSION_ID < 50400 && '' === session_id()) {
return false;
}
if (null !== $lifetime) {
ini_set('session.cookie_lifetime', $lifetime);
}
if ($destroy) {
$this->metadataBag->stampNew();
}
$isRegenerated = session_regenerate_id($destroy);
// The reference to $_SESSION in session bags is lost in PHP7 and we need to re-create it.
// @see https://bugs.php.net/bug.php?id=70013
$this->loadSession();
return $isRegenerated;
}
/**
* {@inheritdoc}
*/
public function save()
{
session_write_close();
if (!$this->saveHandler->isWrapper() && !$this->saveHandler->isSessionHandlerInterface()) {
// This condition matches only PHP 5.3 with internal save handlers
$this->saveHandler->setActive(false);
}
$this->closed = true;
$this->started = false;
}
/**
* {@inheritdoc}
*/
public function clear()
{
// clear out the bags
foreach ($this->bags as $bag) {
$bag->clear();
}
// clear out the session
$_SESSION = array();
// reconnect the bags to the session
$this->loadSession();
}
/**
* {@inheritdoc}
*/
public function registerBag(SessionBagInterface $bag)
{
if ($this->started) {
throw new \LogicException('Cannot register a bag when the session is already started.');
}
$this->bags[$bag->getName()] = $bag;
}
/**
* {@inheritdoc}
*/
public function getBag($name)
{
if (!isset($this->bags[$name])) {
throw new \InvalidArgumentException(sprintf('The SessionBagInterface %s is not registered.', $name));
}
if ($this->saveHandler->isActive() && !$this->started) {
$this->loadSession();
} elseif (!$this->started) {
$this->start();
}
return $this->bags[$name];
}
/**
* Sets the MetadataBag.
*
* @param MetadataBag $metaBag
*/
public function setMetadataBag(MetadataBag $metaBag = null)
{
if (null === $metaBag) {
$metaBag = new MetadataBag();
}
$this->metadataBag = $metaBag;
}
/**
* Gets the MetadataBag.
*
* @return MetadataBag
*/
public function getMetadataBag()
{
return $this->metadataBag;
}
/**
* {@inheritdoc}
*/
public function isStarted()
{
return $this->started;
}
/**
* Sets session.* ini variables.
*
* For convenience we omit 'session.' from the beginning of the keys.
* Explicitly ignores other ini keys.
*
* @param array $options Session ini directives array(key => value)
*
* @see http://php.net/session.configuration
*/
public function setOptions(array $options)
{
$validOptions = array_flip(array(
'cache_limiter', 'cookie_domain', 'cookie_httponly',
'cookie_lifetime', 'cookie_path', 'cookie_secure',
'entropy_file', 'entropy_length', 'gc_divisor',
'gc_maxlifetime', 'gc_probability', 'hash_bits_per_character',
'hash_function', 'name', 'referer_check',
'serialize_handler', 'use_cookies',
'use_only_cookies', 'use_trans_sid', 'upload_progress.enabled',
'upload_progress.cleanup', 'upload_progress.prefix', 'upload_progress.name',
'upload_progress.freq', 'upload_progress.min-freq', 'url_rewriter.tags',
));
foreach ($options as $key => $value) {
if (isset($validOptions[$key])) {
ini_set('session.'.$key, $value);
}
}
}
/**
* Registers session save handler as a PHP session handler.
*
* To use internal PHP session save handlers, override this method using ini_set with
* session.save_handler and session.save_path e.g.
*
* ini_set('session.save_handler', 'files');
* ini_set('session.save_path', '/tmp');
*
* or pass in a NativeSessionHandler instance which configures session.save_handler in the
* constructor, for a template see NativeFileSessionHandler or use handlers in
* composer package drak/native-session
*
* @see http://php.net/session-set-save-handler
* @see http://php.net/sessionhandlerinterface
* @see http://php.net/sessionhandler
* @see http://github.com/drak/NativeSession
*
* @param AbstractProxy|NativeSessionHandler|\SessionHandlerInterface|null $saveHandler
*
* @throws \InvalidArgumentException
*/
public function setSaveHandler($saveHandler = null)
{
if (!$saveHandler instanceof AbstractProxy &&
!$saveHandler instanceof NativeSessionHandler &&
!$saveHandler instanceof \SessionHandlerInterface &&
null !== $saveHandler) {
throw new \InvalidArgumentException('Must be instance of AbstractProxy or NativeSessionHandler; implement \SessionHandlerInterface; or be null.');
}
// Wrap $saveHandler in proxy and prevent double wrapping of proxy
if (!$saveHandler instanceof AbstractProxy && $saveHandler instanceof \SessionHandlerInterface) {
$saveHandler = new SessionHandlerProxy($saveHandler);
} elseif (!$saveHandler instanceof AbstractProxy) {
$saveHandler = PHP_VERSION_ID >= 50400 ?
new SessionHandlerProxy(new \SessionHandler()) : new NativeProxy();
}
$this->saveHandler = $saveHandler;
if ($this->saveHandler instanceof \SessionHandlerInterface) {
if (PHP_VERSION_ID >= 50400) {
session_set_save_handler($this->saveHandler, false);
} else {
session_set_save_handler(
array($this->saveHandler, 'open'),
array($this->saveHandler, 'close'),
array($this->saveHandler, 'read'),
array($this->saveHandler, 'write'),
array($this->saveHandler, 'destroy'),
array($this->saveHandler, 'gc')
);
}
}
}
/**
* Load the session with attributes.
*
* After starting the session, PHP retrieves the session from whatever handlers
* are set to (either PHP's internal, or a custom save handler set with session_set_save_handler()).
* PHP takes the return value from the read() handler, unserializes it
* and populates $_SESSION with the result automatically.
*
* @param array|null $session
*/
protected function loadSession(array &$session = null)
{
if (null === $session) {
$session = &$_SESSION;
}
$bags = array_merge($this->bags, array($this->metadataBag));
foreach ($bags as $bag) {
$key = $bag->getStorageKey();
$session[$key] = isset($session[$key]) ? $session[$key] : array();
$bag->initialize($session[$key]);
}
$this->started = true;
$this->closed = false;
}
}

View file

@ -0,0 +1,68 @@
<?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\Session\Storage\Proxy\AbstractProxy;
use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeSessionHandler;
/**
* Allows session to be started by PHP and managed by Symfony.
*
* @author Drak <drak@zikula.org>
*/
class PhpBridgeSessionStorage extends NativeSessionStorage
{
/**
* Constructor.
*
* @param AbstractProxy|NativeSessionHandler|\SessionHandlerInterface|null $handler
* @param MetadataBag $metaBag MetadataBag
*/
public function __construct($handler = null, MetadataBag $metaBag = null)
{
$this->setMetadataBag($metaBag);
$this->setSaveHandler($handler);
}
/**
* {@inheritdoc}
*/
public function start()
{
if ($this->started) {
return true;
}
$this->loadSession();
if (!$this->saveHandler->isWrapper() && !$this->saveHandler->isSessionHandlerInterface()) {
// This condition matches only PHP 5.3 + internal save handlers
$this->saveHandler->setActive(true);
}
return true;
}
/**
* {@inheritdoc}
*/
public function clear()
{
// clear out the bags and nothing else that may be set
// since the purpose of this driver is to share a handler
foreach ($this->bags as $bag) {
$bag->clear();
}
// reconnect the bags to the session
$this->loadSession();
}
}

View file

@ -0,0 +1,154 @@
<?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\Proxy;
/**
* AbstractProxy.
*
* @author Drak <drak@zikula.org>
*/
abstract class AbstractProxy
{
/**
* Flag if handler wraps an internal PHP session handler (using \SessionHandler).
*
* @var bool
*/
protected $wrapper = false;
/**
* @var bool
*/
protected $active = false;
/**
* @var string
*/
protected $saveHandlerName;
/**
* Gets the session.save_handler name.
*
* @return string
*/
public function getSaveHandlerName()
{
return $this->saveHandlerName;
}
/**
* Is this proxy handler and instance of \SessionHandlerInterface.
*
* @return bool
*/
public function isSessionHandlerInterface()
{
return $this instanceof \SessionHandlerInterface;
}
/**
* Returns true if this handler wraps an internal PHP session save handler using \SessionHandler.
*
* @return bool
*/
public function isWrapper()
{
return $this->wrapper;
}
/**
* Has a session started?
*
* @return bool
*/
public function isActive()
{
if (PHP_VERSION_ID >= 50400) {
return $this->active = \PHP_SESSION_ACTIVE === session_status();
}
return $this->active;
}
/**
* Sets the active flag.
*
* Has no effect under PHP 5.4+ as status is detected
* automatically in isActive()
*
* @internal
*
* @param bool $flag
*
* @throws \LogicException
*/
public function setActive($flag)
{
if (PHP_VERSION_ID >= 50400) {
throw new \LogicException('This method is disabled in PHP 5.4.0+');
}
$this->active = (bool) $flag;
}
/**
* Gets the session ID.
*
* @return string
*/
public function getId()
{
return session_id();
}
/**
* Sets the session ID.
*
* @param string $id
*
* @throws \LogicException
*/
public function setId($id)
{
if ($this->isActive()) {
throw new \LogicException('Cannot change the ID of an active session');
}
session_id($id);
}
/**
* Gets the session name.
*
* @return string
*/
public function getName()
{
return session_name();
}
/**
* Sets the session name.
*
* @param string $name
*
* @throws \LogicException
*/
public function setName($name)
{
if ($this->isActive()) {
throw new \LogicException('Cannot change the name of an active session');
}
session_name($name);
}
}

View file

@ -0,0 +1,41 @@
<?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\Proxy;
/**
* NativeProxy.
*
* This proxy is built-in session handlers in PHP 5.3.x
*
* @author Drak <drak@zikula.org>
*/
class NativeProxy extends AbstractProxy
{
/**
* Constructor.
*/
public function __construct()
{
// this makes an educated guess as to what the handler is since it should already be set.
$this->saveHandlerName = ini_get('session.save_handler');
}
/**
* Returns true if this handler wraps an internal PHP session save handler using \SessionHandler.
*
* @return bool False
*/
public function isWrapper()
{
return false;
}
}

View file

@ -0,0 +1,95 @@
<?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\Proxy;
/**
* SessionHandler proxy.
*
* @author Drak <drak@zikula.org>
*/
class SessionHandlerProxy extends AbstractProxy implements \SessionHandlerInterface
{
/**
* @var \SessionHandlerInterface
*/
protected $handler;
/**
* Constructor.
*
* @param \SessionHandlerInterface $handler
*/
public function __construct(\SessionHandlerInterface $handler)
{
$this->handler = $handler;
$this->wrapper = ($handler instanceof \SessionHandler);
$this->saveHandlerName = $this->wrapper ? ini_get('session.save_handler') : 'user';
}
// \SessionHandlerInterface
/**
* {@inheritdoc}
*/
public function open($savePath, $sessionName)
{
$return = (bool) $this->handler->open($savePath, $sessionName);
if (true === $return) {
$this->active = true;
}
return $return;
}
/**
* {@inheritdoc}
*/
public function close()
{
$this->active = false;
return (bool) $this->handler->close();
}
/**
* {@inheritdoc}
*/
public function read($sessionId)
{
return (string) $this->handler->read($sessionId);
}
/**
* {@inheritdoc}
*/
public function write($sessionId, $data)
{
return (bool) $this->handler->write($sessionId, $data);
}
/**
* {@inheritdoc}
*/
public function destroy($sessionId)
{
return (bool) $this->handler->destroy($sessionId);
}
/**
* {@inheritdoc}
*/
public function gc($maxlifetime)
{
return (bool) $this->handler->gc($maxlifetime);
}
}

View file

@ -0,0 +1,139 @@
<?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\Session\SessionBagInterface;
/**
* StorageInterface.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Drak <drak@zikula.org>
*/
interface SessionStorageInterface
{
/**
* Starts the session.
*
* @return bool True if started
*
* @throws \RuntimeException If something goes wrong starting the session.
*/
public function start();
/**
* Checks if the session is started.
*
* @return bool True if started, false otherwise
*/
public function isStarted();
/**
* Returns the session ID.
*
* @return string The session ID or empty
*/
public function getId();
/**
* Sets the session ID.
*
* @param string $id
*/
public function setId($id);
/**
* Returns the session name.
*
* @return mixed The session name
*/
public function getName();
/**
* Sets the session name.
*
* @param string $name
*/
public function setName($name);
/**
* Regenerates id that represents this storage.
*
* This method must invoke session_regenerate_id($destroy) unless
* this interface is used for a storage object designed for unit
* or functional testing where a real PHP session would interfere
* with testing.
*
* Note regenerate+destroy should not clear the session data in memory
* only delete the session data from persistent storage.
*
* Care: When regenerating the session ID no locking is involved in PHP's
* session design. See https://bugs.php.net/bug.php?id=61470 for a discussion.
* So you must make sure the regenerated session is saved BEFORE sending the
* headers with the new ID. Symfony's HttpKernel offers a listener for this.
* See Symfony\Component\HttpKernel\EventListener\SaveSessionListener.
* Otherwise session data could get lost again for concurrent requests with the
* new ID. One result could be that you get logged out after just logging in.
*
* @param bool $destroy Destroy session when regenerating?
* @param int $lifetime Sets the cookie lifetime for the session cookie. A null value
* will leave the system settings unchanged, 0 sets the cookie
* to expire with browser session. Time is in seconds, and is
* not a Unix timestamp.
*
* @return bool True if session regenerated, false if error
*
* @throws \RuntimeException If an error occurs while regenerating this storage
*/
public function regenerate($destroy = false, $lifetime = null);
/**
* Force the session to be saved and closed.
*
* This method must invoke session_write_close() unless this interface is
* used for a storage object design for unit or functional testing where
* a real PHP session would interfere with testing, in which case
* it should actually persist the session data if required.
*
* @throws \RuntimeException If the session is saved without being started, or if the session
* is already closed.
*/
public function save();
/**
* Clear all session data in memory.
*/
public function clear();
/**
* Gets a SessionBagInterface by name.
*
* @param string $name
*
* @return SessionBagInterface
*
* @throws \InvalidArgumentException If the bag does not exist
*/
public function getBag($name);
/**
* Registers a SessionBagInterface for use.
*
* @param SessionBagInterface $bag
*/
public function registerBag(SessionBagInterface $bag);
/**
* @return MetadataBag
*/
public function getMetadataBag();
}

View file

@ -0,0 +1,137 @@
<?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;
/**
* StreamedResponse represents a streamed HTTP response.
*
* A StreamedResponse uses a callback for its content.
*
* The callback should use the standard PHP functions like echo
* to stream the response back to the client. The flush() method
* can also be used if needed.
*
* @see flush()
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class StreamedResponse extends Response
{
protected $callback;
protected $streamed;
private $headersSent;
/**
* Constructor.
*
* @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($callback = null, $status = 200, $headers = array())
{
parent::__construct(null, $status, $headers);
if (null !== $callback) {
$this->setCallback($callback);
}
$this->streamed = false;
$this->headersSent = false;
}
/**
* 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
*/
public static function create($callback = null, $status = 200, $headers = array())
{
return new static($callback, $status, $headers);
}
/**
* Sets the PHP callback associated with this Response.
*
* @param callable $callback A valid PHP callback
*
* @throws \LogicException
*/
public function setCallback($callback)
{
if (!is_callable($callback)) {
throw new \LogicException('The Response callback must be a valid PHP callable.');
}
$this->callback = $callback;
}
/**
* {@inheritdoc}
*
* This method only sends the headers once.
*/
public function sendHeaders()
{
if ($this->headersSent) {
return;
}
$this->headersSent = true;
parent::sendHeaders();
}
/**
* {@inheritdoc}
*
* This method only sends the content once.
*/
public function sendContent()
{
if ($this->streamed) {
return;
}
$this->streamed = true;
if (null === $this->callback) {
throw new \LogicException('The Response callback must not be null.');
}
call_user_func($this->callback);
}
/**
* {@inheritdoc}
*
* @throws \LogicException when the content is not null
*/
public function setContent($content)
{
if (null !== $content) {
throw new \LogicException('The content cannot be set on a StreamedResponse instance.');
}
}
/**
* {@inheritdoc}
*
* @return false
*/
public function getContent()
{
return false;
}
}

View file

@ -0,0 +1,39 @@
{
"name": "symfony/http-foundation",
"type": "library",
"description": "Symfony HttpFoundation Component",
"keywords": [],
"homepage": "https://symfony.com",
"license": "MIT",
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"require": {
"php": ">=5.3.9",
"symfony/polyfill-php54": "~1.0",
"symfony/polyfill-php55": "~1.0",
"symfony/polyfill-mbstring": "~1.1"
},
"require-dev": {
"symfony/expression-language": "~2.4|~3.0.0"
},
"autoload": {
"psr-4": { "Symfony\\Component\\HttpFoundation\\": "" },
"exclude-from-classmap": [
"/Tests/"
]
},
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-master": "2.8-dev"
}
}
}

View file

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd"
backupGlobals="false"
colors="true"
bootstrap="vendor/autoload.php"
>
<php>
<ini name="error_reporting" value="-1" />
</php>
<testsuites>
<testsuite name="Symfony HttpFoundation Component Test Suite">
<directory>./Tests/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory>./</directory>
<exclude>
<directory>./Resources</directory>
<directory>./Tests</directory>
<directory>./vendor</directory>
</exclude>
</whitelist>
</filter>
</phpunit>