Commit b9cf8dfb authored by Taylor Otwell's avatar Taylor Otwell

Upgrade to latest Symfony HttpFoundation tag. Closes #1865.

parent 92f602ed
<?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 AcceptHeader
*/
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 Boolean
*/
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 AcceptHeader
*/
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 AcceptHeader
*/
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;
}
}
}
<?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 AcceptHeaderItem
*/
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 AcceptHeaderItem
*/
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 AcceptHeaderItem
*/
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 AcceptHeaderItem
*/
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 Boolean
*/
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 AcceptHeaderItem
*/
public function setAttribute($name, $value)
{
if ('q' === $name) {
$this->quality = (float) $value;
} else {
$this->attributes[$name] = (string) $value;
}
return $this;
}
}
<?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;
protected $file;
protected $offset;
protected $maxlen;
/**
* Constructor.
*
* @param SplFileInfo|string $file The file to stream
* @param integer $status The response status code
* @param array $headers An array of response headers
* @param boolean $public Files are public by default
* @param null|string $contentDisposition The type of Content-Disposition to set automatically with the filename
* @param boolean $autoEtag Whether the ETag header should be automatically set
* @param boolean $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();
}
}
/**
* {@inheritdoc}
*/
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 Boolean $autoEtag
* @param Boolean $autoLastModified
*
* @return BinaryFileResponse
*
* @throws FileException
*/
public function setFile($file, $contentDisposition = null, $autoEtag = false, $autoLastModified = true)
{
$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 BinaryFileResponse
*/
public function setContentDisposition($disposition, $filename = '', $filenameFallback = '')
{
if ($filename === '') {
$filename = $this->file->getFilename();
}
$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());
$this->headers->set('Accept-Ranges', 'bytes');
$this->headers->set('Content-Transfer-Encoding', 'binary');
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->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();
if (strtolower($type) == 'x-accel-redirect') {
// Do X-Accel-Mapping substitutions.
foreach (explode(',', $request->headers->get('X-Accel-Mapping', '')) as $mapping) {
$mapping = explode('=', $mapping, 2);
if (2 == count($mapping)) {
$location = trim($mapping[0]);
$pathPrefix = 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->getEtag() == $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;
}
$start = max($start, 0);
$end = min($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));
}
}
return $this;
}
/**
* Sends the file.
*/
public function sendContent()
{
if (!$this->isSuccessful()) {
parent::sendContent();
return;
}
if (0 === $this->maxlen) {
return;
}
$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);
}
/**
* {@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;
}
}
CHANGELOG
=========
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
-----
......
......@@ -39,6 +39,8 @@ class Cookie
* @param Boolean $secure Whether the cookie should only be transmitted over a secure HTTPS connection from the client
* @param Boolean $httpOnly Whether the cookie will be made accessible only through the HTTP protocol
*
* @throws \InvalidArgumentException
*
* @api
*/
public function __construct($name, $value = null, $expire = 0, $path = '/', $domain = null, $secure = false, $httpOnly = true)
......
......@@ -45,7 +45,7 @@ class FileBinaryMimeTypeGuesser implements MimeTypeGuesserInterface
*/
public static function isSupported()
{
return !defined('PHP_WINDOWS_VERSION_BUILD');
return !defined('PHP_WINDOWS_VERSION_BUILD') && function_exists('passthru') && function_exists('escapeshellarg');
}
/**
......@@ -77,7 +77,7 @@ class FileBinaryMimeTypeGuesser implements MimeTypeGuesserInterface
$type = trim(ob_get_clean());
if (!preg_match('#^([a-z0-9\-]+/[a-z0-9\-]+)#i', $type, $match)) {
if (!preg_match('#^([a-z0-9\-]+/[a-z0-9\-\.]+)#i', $type, $match)) {
// it's not a type, but an error message
return null;
}
......
......@@ -11,7 +11,6 @@
namespace Symfony\Component\HttpFoundation\File\MimeType;
use Symfony\Component\HttpFoundation\File\Exception\FileException;
use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException;
use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException;
......@@ -99,7 +98,9 @@ class MimeTypeGuesser implements MimeTypeGuesserInterface
*
* @return string The mime type or NULL, if none could be guessed
*
* @throws FileException If the file does not exist
* @throws \LogicException
* @throws FileNotFoundException
* @throws AccessDeniedException
*/
public function guess($path)
{
......
......@@ -118,6 +118,19 @@ class UploadedFile extends File
return $this->originalName;
}
/**
* Returns the original file extension
*
* It is extracted from the original file name that was uploaded.
* Then is 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.
*
......
......@@ -149,7 +149,7 @@ class HeaderBag implements \IteratorAggregate, \Countable
*
* @param string $key The key
* @param string|array $values The value or an array of values
* @param Boolean $replace Whether to replace the actual value of not (true by default)
* @param Boolean $replace Whether to replace the actual value or not (true by default)
*
* @api
*/
......@@ -223,7 +223,7 @@ class HeaderBag implements \IteratorAggregate, \Countable
* @param string $key The parameter key
* @param \DateTime $default The default value
*
* @return null|\DateTime The filtered 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
*
......
<?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() {}
/**
* Validates an IPv4 or IPv6 address.
*
* @param string $requestIp
* @param string $ip
*
* @return boolean Whether the IP is valid
*/
public static function checkIp($requestIp, $ip)
{
if (false !== strpos($requestIp, ':')) {
return self::checkIp6($requestIp, $ip);
}
return self::checkIp4($requestIp, $ip);
}
/**
* Validates an IPv4 address.
*
* @param string $requestIp
* @param string $ip
*
* @return boolean Whether the IP is valid
*/
public static function checkIp4($requestIp, $ip)
{
if (false !== strpos($ip, '/')) {
list($address, $netmask) = explode('/', $ip, 2);
if ($netmask < 1 || $netmask > 32) {
return false;
}
} else {
$address = $ip;
$netmask = 32;
}
return 0 === substr_compare(sprintf('%032b', ip2long($requestIp)), sprintf('%032b', ip2long($address)), 0, $netmask);
}
/**
* Validates an IPv6 address.
*
* @author David Soria Parra <dsp at php dot net>
* @see https://github.com/dsp/v6tools
*
* @param string $requestIp
* @param string $ip
*
* @return boolean 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));
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;
}
}
......@@ -28,17 +28,20 @@ class JsonResponse extends Response
* @param integer $status The response status code
* @param array $headers An array of response headers
*/
public function __construct($data = array(), $status = 200, $headers = array())
public function __construct($data = null, $status = 200, $headers = array())
{
parent::__construct('', $status, $headers);
if (null === $data) {
$data = new \ArrayObject();
}
$this->setData($data);
}
/**
* {@inheritDoc}
*/
public static function create($data = array(), $status = 200, $headers = array())
public static function create($data = null, $status = 200, $headers = array())
{
return new static($data, $status, $headers);
}
......@@ -49,6 +52,8 @@ class JsonResponse extends Response
* @param string $callback
*
* @return JsonResponse
*
* @throws \InvalidArgumentException
*/
public function setCallback($callback = null)
{
......@@ -77,11 +82,6 @@ class JsonResponse extends Response
*/
public function setData($data = array())
{
// root should be JSON object, not array
if (is_array($data) && 0 === count($data)) {
$data = new \ArrayObject();
}
// Encode <, >, ', &, and " for RFC4627-compliant JSON, which may also be embedded into HTML.
$this->data = json_encode($data, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT);
......
Copyright (c) 2004-2012 Fabien Potencier
Copyright (c) 2004-2013 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
......
......@@ -96,6 +96,8 @@ class ParameterBag implements \IteratorAggregate, \Countable
*
* @return mixed
*
* @throws \InvalidArgumentException
*
* @api
*/
public function get($path, $default = null, $deep = false)
......
......@@ -31,7 +31,7 @@ the HTTP specification.
Loading
-------
If you are using PHP 5.3.x you must add the following to your autoloader:
If you are not using Composer but are using PHP 5.3.x, you must add the following to your autoloader:
// SessionHandlerInterface
if (!interface_exists('SessionHandlerInterface')) {
......@@ -43,4 +43,6 @@ Resources
You can run the unit tests with the following command:
phpunit
$ cd path/to/Symfony/Component/HttpFoundation/
$ composer.phar install --dev
$ phpunit
......@@ -29,6 +29,8 @@ class RedirectResponse extends Response
* @param integer $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
*
* @api
......@@ -72,6 +74,8 @@ class RedirectResponse extends Response
* @param string $url The URL to redirect to
*
* @return RedirectResponse The current response.
*
* @throws \InvalidArgumentException
*/
public function setTargetUrl($url)
{
......
......@@ -53,6 +53,8 @@ class Request
self::HEADER_CLIENT_PORT => 'X_FORWARDED_PORT',
);
protected static $httpMethodParameterOverride = false;
/**
* @var \Symfony\Component\HttpFoundation\ParameterBag
*
......@@ -251,6 +253,9 @@ class Request
/**
* Creates a Request based on a given URI and configuration.
*
* The information contained in the URI always take precedence
* over the other information (server and parameters).
*
* @param string $uri The URI
* @param string $method The HTTP method
* @param array $parameters The query (GET) or request (POST) parameters
......@@ -265,7 +270,7 @@ class Request
*/
public static function create($uri, $method = 'GET', $parameters = array(), $cookies = array(), $files = array(), $server = array(), $content = null)
{
$defaults = array(
$server = array_replace(array(
'SERVER_NAME' => 'localhost',
'SERVER_PORT' => 80,
'HTTP_HOST' => 'localhost',
......@@ -278,32 +283,38 @@ class Request
'SCRIPT_FILENAME' => '',
'SERVER_PROTOCOL' => 'HTTP/1.1',
'REQUEST_TIME' => time(),
);
), $server);
$server['PATH_INFO'] = '';
$server['REQUEST_METHOD'] = strtoupper($method);
$components = parse_url($uri);
if (isset($components['host'])) {
$defaults['SERVER_NAME'] = $components['host'];
$defaults['HTTP_HOST'] = $components['host'];
$server['SERVER_NAME'] = $components['host'];
$server['HTTP_HOST'] = $components['host'];
}
if (isset($components['scheme'])) {
if ('https' === $components['scheme']) {
$defaults['HTTPS'] = 'on';
$defaults['SERVER_PORT'] = 443;
$server['HTTPS'] = 'on';
$server['SERVER_PORT'] = 443;
} else {
unset($server['HTTPS']);
$server['SERVER_PORT'] = 80;
}
}
if (isset($components['port'])) {
$defaults['SERVER_PORT'] = $components['port'];
$defaults['HTTP_HOST'] = $defaults['HTTP_HOST'].':'.$components['port'];
$server['SERVER_PORT'] = $components['port'];
$server['HTTP_HOST'] = $server['HTTP_HOST'].':'.$components['port'];
}
if (isset($components['user'])) {
$defaults['PHP_AUTH_USER'] = $components['user'];
$server['PHP_AUTH_USER'] = $components['user'];
}
if (isset($components['pass'])) {
$defaults['PHP_AUTH_PW'] = $components['pass'];
$server['PHP_AUTH_PW'] = $components['pass'];
}
if (!isset($components['path'])) {
......@@ -314,7 +325,9 @@ class Request
case 'POST':
case 'PUT':
case 'DELETE':
$defaults['CONTENT_TYPE'] = 'application/x-www-form-urlencoded';
if (!isset($server['CONTENT_TYPE'])) {
$server['CONTENT_TYPE'] = 'application/x-www-form-urlencoded';
}
case 'PATCH':
$request = $parameters;
$query = array();
......@@ -331,14 +344,8 @@ class Request
}
$queryString = http_build_query($query, '', '&');
$uri = $components['path'].('' !== $queryString ? '?'.$queryString : '');
$server = array_replace($defaults, $server, array(
'REQUEST_METHOD' => strtoupper($method),
'PATH_INFO' => '',
'REQUEST_URI' => $uri,
'QUERY_STRING' => $queryString,
));
$server['REQUEST_URI'] = $components['path'].('' !== $queryString ? '?'.$queryString : '');
$server['QUERY_STRING'] = $queryString;
return new static($query, $request, array(), $cookies, $files, $server, $content);
}
......@@ -464,6 +471,8 @@ class Request
*/
public static function trustProxyData()
{
trigger_error('trustProxyData() is deprecated since version 2.0 and will be removed in 2.3. Use setTrustedProxies() instead.', E_USER_DEPRECATED);
self::$trustProxy = true;
}
......@@ -482,6 +491,16 @@ class Request
self::$trustProxy = $proxies ? true : false;
}
/**
* Gets the list of trusted proxies.
*
* @return array An array of trusted proxies.
*/
public static function getTrustedProxies()
{
return self::$trustedProxies;
}
/**
* Sets the name for trusted headers.
*
......@@ -496,6 +515,8 @@ class Request
*
* @param string $key The header key
* @param string $value The header name
*
* @throws \InvalidArgumentException
*/
public static function setTrustedHeaderName($key, $value)
{
......@@ -511,6 +532,8 @@ class Request
* false otherwise.
*
* @return boolean
*
* @deprecated Deprecated since version 2.2, to be removed in 2.3. Use getTrustedProxies instead.
*/
public static function isProxyTrusted()
{
......@@ -560,6 +583,29 @@ class Request
return implode('&', $parts);
}
/**
* Enables support for the _method request parameter to determine the intended HTTP method.
*
* Be warned that enabling this feature might lead to CSRF issues in your code.
* Check that you are using CSRF tokens when required.
*
* The HTTP method can only be overridden when the real HTTP method is POST.
*/
public static function enableHttpMethodParameterOverride()
{
self::$httpMethodParameterOverride = true;
}
/**
* Checks whether support for the _method request parameter is enabled.
*
* @return Boolean True when the _method request parameter is enabled, false otherwise
*/
public static function getHttpMethodParameterOverride()
{
return self::$httpMethodParameterOverride;
}
/**
* Gets a "parameter" value.
*
......@@ -657,8 +703,6 @@ class Request
*
* @see http://en.wikipedia.org/wiki/X-Forwarded-For
*
* @deprecated The proxy argument is deprecated since version 2.0 and will be removed in 2.3. Use setTrustedProxies instead.
*
* @api
*/
public function getClientIp()
......@@ -703,7 +747,7 @@ class Request
*
* * http://localhost/mysite returns an empty string
* * http://localhost/mysite/about returns '/about'
* * htpp://localhost/mysite/enco%20ded returns '/enco%20ded'
* * http://localhost/mysite/enco%20ded returns '/enco%20ded'
* * http://localhost/mysite/about?var=1 returns '/about'
*
* @return string The raw path (i.e. not urldecoded)
......@@ -897,8 +941,7 @@ class Request
*/
public function getUri()
{
$qs = $this->getQueryString();
if (null !== $qs) {
if (null !== $qs = $this->getQueryString()) {
$qs = '?'.$qs;
}
......@@ -1017,26 +1060,51 @@ class Request
}
/**
* Gets the request method.
* Gets the request "intended" method.
*
* If the X-HTTP-Method-Override header is set, and if the method is a POST,
* then it is used to determine the "real" intended HTTP method.
*
* The _method request parameter can also be used to determine the HTTP method,
* but only if enableHttpMethodParameterOverride() has been called.
*
* The method is always an uppercased string.
*
* @return string The request method
*
* @api
*
* @see getRealMethod
*/
public function getMethod()
{
if (null === $this->method) {
$this->method = strtoupper($this->server->get('REQUEST_METHOD', 'GET'));
if ('POST' === $this->method) {
$this->method = strtoupper($this->headers->get('X-HTTP-METHOD-OVERRIDE', $this->request->get('_method', $this->query->get('_method', 'POST'))));
if ($method = $this->headers->get('X-HTTP-METHOD-OVERRIDE')) {
$this->method = strtoupper($method);
} elseif (self::$httpMethodParameterOverride) {
$this->method = strtoupper($this->request->get('_method', $this->query->get('_method', 'POST')));
}
}
}
return $this->method;
}
/**
* Gets the "real" request method.
*
* @return string The request method
*
* @see getMethod
*/
public function getRealMethod()
{
return strtoupper($this->server->get('REQUEST_METHOD', 'GET'));
}
/**
* Gets the mime type associated with the format.
*
......@@ -1216,6 +1284,8 @@ class Request
* @param Boolean $asResource If true, a resource will be returned
*
* @return string|resource The request body content or a resource to read the body stream.
*
* @throws \LogicException
*/
public function getContent($asResource = false)
{
......@@ -1275,7 +1345,18 @@ class Request
return $locales[0];
}
$preferredLanguages = array_values(array_intersect($preferredLanguages, $locales));
$extendedPreferredLanguages = array();
foreach ($preferredLanguages as $language) {
$extendedPreferredLanguages[] = $language;
if (false !== $position = strpos($language, '_')) {
$superLanguage = substr($language, 0, $position);
if (!in_array($superLanguage, $preferredLanguages)) {
$extendedPreferredLanguages[] = $superLanguage;
}
}
}
$preferredLanguages = array_values(array_intersect($extendedPreferredLanguages, $locales));
return isset($preferredLanguages[0]) ? $preferredLanguages[0] : $locales[0];
}
......@@ -1293,9 +1374,9 @@ class Request
return $this->languages;
}
$languages = $this->splitHttpAcceptHeader($this->headers->get('Accept-Language'));
$languages = AcceptHeader::fromString($this->headers->get('Accept-Language'))->all();
$this->languages = array();
foreach ($languages as $lang => $q) {
foreach (array_keys($languages) as $lang) {
if (strstr($lang, '-')) {
$codes = explode('-', $lang);
if ($codes[0] == 'i') {
......@@ -1335,7 +1416,7 @@ class Request
return $this->charsets;
}
return $this->charsets = array_keys($this->splitHttpAcceptHeader($this->headers->get('Accept-Charset')));
return $this->charsets = array_keys(AcceptHeader::fromString($this->headers->get('Accept-Charset'))->all());
}
/**
......@@ -1351,14 +1432,15 @@ class Request
return $this->acceptableContentTypes;
}
return $this->acceptableContentTypes = array_keys($this->splitHttpAcceptHeader($this->headers->get('Accept')));
return $this->acceptableContentTypes = array_keys(AcceptHeader::fromString($this->headers->get('Accept'))->all());
}
/**
* Returns true if the request is a XMLHttpRequest.
*
* It works if your JavaScript library set an X-Requested-With HTTP header.
* It is known to work with Prototype, Mootools, jQuery.
* It is known to work with common JavaScript frameworks:
* @link http://en.wikipedia.org/wiki/List_of_Ajax_frameworks#JavaScript
*
* @return Boolean true if the request is an XMLHttpRequest, false otherwise
*
......@@ -1375,40 +1457,23 @@ class Request
* @param string $header Header to split
*
* @return array Array indexed by the values of the Accept-* header in preferred order
*
* @deprecated Deprecated since version 2.2, to be removed in 2.3.
*/
public function splitHttpAcceptHeader($header)
{
if (!$header) {
return array();
}
$values = array();
$groups = array();
foreach (array_filter(explode(',', $header)) as $value) {
// Cut off any q-value that might come after a semi-colon
if (preg_match('/;\s*(q=.*$)/', $value, $match)) {
$q = substr(trim($match[1]), 2);
$value = trim(substr($value, 0, -strlen($match[0])));
} else {
$q = 1;
}
trigger_error('splitHttpAcceptHeader() is deprecated since version 2.2 and will be removed in 2.3.', E_USER_DEPRECATED);
$groups[$q][] = $value;
}
krsort($groups);
foreach ($groups as $q => $items) {
$q = (float) $q;
if (0 < $q) {
foreach ($items as $value) {
$values[trim($value)] = $q;
}
$headers = array();
foreach (AcceptHeader::fromString($header)->all() as $item) {
$key = $item->getValue();
foreach ($item->getAttributes() as $name => $value) {
$key .= sprintf(';%s=%s', $name, $value);
}
$headers[$key] = $item->getQuality();
}
return $values;
return $headers;
}
/*
......@@ -1426,12 +1491,16 @@ class Request
if ($this->headers->has('X_ORIGINAL_URL') && false !== stripos(PHP_OS, 'WIN')) {
// IIS with Microsoft Rewrite Module
$requestUri = $this->headers->get('X_ORIGINAL_URL');
$this->headers->remove('X_ORIGINAL_URL');
} elseif ($this->headers->has('X_REWRITE_URL') && false !== stripos(PHP_OS, 'WIN')) {
// IIS with ISAPI_Rewrite
$requestUri = $this->headers->get('X_REWRITE_URL');
$this->headers->remove('X_REWRITE_URL');
} elseif ($this->server->get('IIS_WasUrlRewritten') == '1' && $this->server->get('UNENCODED_URL') != '') {
// IIS7 with URL Rewrite: make sure we get the unencoded url (double slash problem)
$requestUri = $this->server->get('UNENCODED_URL');
$this->server->remove('UNENCODED_URL');
$this->server->remove('IIS_WasUrlRewritten');
} elseif ($this->server->has('REQUEST_URI')) {
$requestUri = $this->server->get('REQUEST_URI');
// HTTP proxy reqs setup request uri with scheme and host [and port] + the url path, only use url path
......@@ -1445,8 +1514,12 @@ class Request
if ('' != $this->server->get('QUERY_STRING')) {
$requestUri .= '?'.$this->server->get('QUERY_STRING');
}
$this->server->remove('ORIG_PATH_INFO');
}
// normalize the request URI to ease creating sub-requests from this request
$this->server->set('REQUEST_URI', $requestUri);
return $requestUri;
}
......
......@@ -143,95 +143,10 @@ class RequestMatcher implements RequestMatcherInterface
return false;
}
if (null !== $this->ip && !$this->checkIp($request->getClientIp(), $this->ip)) {
if (null !== $this->ip && !IpUtils::checkIp($request->getClientIp(), $this->ip)) {
return false;
}
return true;
}
/**
* Validates an IP address.
*
* @param string $requestIp
* @param string $ip
*
* @return boolean True valid, false if not.
*/
protected function checkIp($requestIp, $ip)
{
// IPv6 address
if (false !== strpos($requestIp, ':')) {
return $this->checkIp6($requestIp, $ip);
} else {
return $this->checkIp4($requestIp, $ip);
}
}
/**
* Validates an IPv4 address.
*
* @param string $requestIp
* @param string $ip
*
* @return boolean True valid, false if not.
*/
protected function checkIp4($requestIp, $ip)
{
if (false !== strpos($ip, '/')) {
list($address, $netmask) = explode('/', $ip, 2);
if ($netmask < 1 || $netmask > 32) {
return false;
}
} else {
$address = $ip;
$netmask = 32;
}
return 0 === substr_compare(sprintf('%032b', ip2long($requestIp)), sprintf('%032b', ip2long($address)), 0, $netmask);
}
/**
* Validates an IPv6 address.
*
* @author David Soria Parra <dsp at php dot net>
* @see https://github.com/dsp/v6tools
*
* @param string $requestIp
* @param string $ip
*
* @return boolean True valid, false if not.
*/
protected 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));
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;
}
}
......@@ -131,6 +131,8 @@ class Response
* @param integer $status The response status code
* @param array $headers An array of response headers
*
* @throws \InvalidArgumentException When the HTTP status code is not valid
*
* @api
*/
public function __construct($content = '', $status = 200, $headers = array())
......@@ -231,7 +233,7 @@ class Response
$headers->remove('Content-Length');
}
if ('HEAD' === $request->getMethod()) {
if ($request->isMethod('HEAD')) {
// cf. RFC2616 14.13
$length = $headers->get('Content-Length');
$this->setContent(null);
......@@ -251,6 +253,16 @@ class Response
$this->headers->set('expires', -1);
}
/**
* Check if we need to remove Cache-Control for ssl encrypted downloads when using IE < 9
* @link http://support.microsoft.com/kb/323308
*/
if (false !== stripos($this->headers->get('Content-Disposition'), 'attachment') && preg_match('/MSIE (.*?);/i', $request->server->get('HTTP_USER_AGENT'), $match) == 1 && true === $request->isSecure()) {
if (intval(preg_replace("/(MSIE )(.*?);/", "$2", $match[0])) < 9) {
$this->headers->remove('Cache-Control');
}
}
return $this;
}
......@@ -270,7 +282,7 @@ class Response
header(sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText));
// headers
foreach ($this->headers->all() as $name => $values) {
foreach ($this->headers->allPreserveCase() as $name => $values) {
foreach ($values as $value) {
header($name.': '.$value, false);
}
......@@ -336,6 +348,8 @@ class Response
*
* @return Response
*
* @throws \UnexpectedValueException
*
* @api
*/
public function setContent($content)
......@@ -431,7 +445,7 @@ class Response
/**
* Retrieves the status code for the current web response.
*
* @return string Status code
* @return integer Status code
*
* @api
*/
......@@ -499,7 +513,7 @@ class Response
*
* Fresh responses may be served from cache without any interaction with the
* origin. A response is considered fresh when it includes a Cache-Control/max-age
* indicator or Expiration header and the calculated age is less than the freshness lifetime.
* indicator or Expires header and the calculated age is less than the freshness lifetime.
*
* @return Boolean true if the response is fresh, false otherwise
*
......@@ -612,8 +626,8 @@ class Response
*/
public function getAge()
{
if ($age = $this->headers->get('Age')) {
return $age;
if (null !== $age = $this->headers->get('Age')) {
return (int) $age;
}
return max(time() - $this->getDate()->format('U'), 0);
......@@ -638,21 +652,26 @@ class Response
/**
* Returns the value of the Expires header as a DateTime instance.
*
* @return \DateTime A DateTime instance
* @return \DateTime|null A DateTime instance or null if the header does not exist
*
* @api
*/
public function getExpires()
{
try {
return $this->headers->getDate('Expires');
} catch (\RuntimeException $e) {
// according to RFC 2616 invalid date formats (e.g. "0" and "-1") must be treated as in the past
return \DateTime::createFromFormat(DATE_RFC2822, 'Sat, 01 Jan 00 00:00:00 +0000');
}
}
/**
* Sets the Expires HTTP header with a DateTime instance.
*
* If passed a null value, it removes the header.
* Passing null as value will remove the header.
*
* @param \DateTime $date A \DateTime instance
* @param \DateTime|null $date A \DateTime instance or null to remove the header
*
* @return Response
*
......@@ -672,7 +691,7 @@ class Response
}
/**
* Sets the number of seconds after the time specified in the response's Date
* Returns the number of seconds after the time specified in the response's Date
* header when the the response should no longer be considered fresh.
*
* First, it checks for a s-maxage directive, then a max-age directive, and then it falls
......@@ -684,12 +703,12 @@ class Response
*/
public function getMaxAge()
{
if ($age = $this->headers->getCacheControlDirective('s-maxage')) {
return $age;
if ($this->headers->hasCacheControlDirective('s-maxage')) {
return (int) $this->headers->getCacheControlDirective('s-maxage');
}
if ($age = $this->headers->getCacheControlDirective('max-age')) {
return $age;
if ($this->headers->hasCacheControlDirective('max-age')) {
return (int) $this->headers->getCacheControlDirective('max-age');
}
if (null !== $this->getExpires()) {
......@@ -750,7 +769,7 @@ class Response
*/
public function getTtl()
{
if ($maxAge = $this->getMaxAge()) {
if (null !== $maxAge = $this->getMaxAge()) {
return $maxAge - $this->getAge();
}
......@@ -796,7 +815,9 @@ class Response
/**
* Returns the Last-Modified HTTP header as a DateTime instance.
*
* @return \DateTime A DateTime instance
* @return \DateTime|null A DateTime instance or null if the header does not exist
*
* @throws \RuntimeException When the HTTP header is not parseable
*
* @api
*/
......@@ -808,9 +829,9 @@ class Response
/**
* Sets the Last-Modified HTTP header with a DateTime instance.
*
* If passed a null value, it removes the header.
* Passing null as value will remove the header.
*
* @param \DateTime $date A \DateTime instance
* @param \DateTime|null $date A \DateTime instance or null to remove the header
*
* @return Response
*
......@@ -832,7 +853,7 @@ class Response
/**
* Returns the literal value of the ETag HTTP header.
*
* @return string The ETag HTTP header
* @return string|null The ETag HTTP header or null if it does not exist
*
* @api
*/
......@@ -844,7 +865,7 @@ class Response
/**
* Sets the ETag value.
*
* @param string $etag The ETag unique identifier
* @param string|null $etag The ETag unique identifier or null to remove the header
* @param Boolean $weak Whether you want a weak ETag or not
*
* @return Response
......@@ -875,6 +896,8 @@ class Response
*
* @return Response
*
* @throws \InvalidArgumentException
*
* @api
*/
public function setCache(array $options)
......@@ -952,7 +975,7 @@ class Response
*/
public function hasVary()
{
return (Boolean) $this->headers->get('Vary');
return null !== $this->headers->get('Vary');
}
/**
......@@ -1108,7 +1131,7 @@ class Response
}
/**
* Is the reponse forbidden?
* Is the response forbidden?
*
* @return Boolean
*
......
......@@ -36,6 +36,11 @@ class ResponseHeaderBag extends HeaderBag
*/
protected $cookies = array();
/**
* @var array
*/
protected $headerNames = array();
/**
* Constructor.
*
......@@ -48,7 +53,7 @@ class ResponseHeaderBag extends HeaderBag
parent::__construct($headers);
if (!isset($this->headers['cache-control'])) {
$this->set('cache-control', '');
$this->set('Cache-Control', '');
}
}
......@@ -62,9 +67,21 @@ class ResponseHeaderBag extends HeaderBag
$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}
*
......@@ -72,10 +89,12 @@ class ResponseHeaderBag extends HeaderBag
*/
public function replace(array $headers = array())
{
$this->headerNames = array();
parent::replace($headers);
if (!isset($this->headers['cache-control'])) {
$this->set('cache-control', '');
$this->set('Cache-Control', '');
}
}
......@@ -88,10 +107,14 @@ class ResponseHeaderBag extends HeaderBag
{
parent::set($key, $values, $replace);
$uniqueKey = strtr(strtolower($key), '_', '-');
$this->headerNames[$uniqueKey] = $key;
// ensure the cache-control header has sensible defaults
if (in_array(strtr(strtolower($key), '_', '-'), array('cache-control', 'etag', 'last-modified', 'expires'))) {
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);
}
}
......@@ -105,7 +128,10 @@ class ResponseHeaderBag extends HeaderBag
{
parent::remove($key);
if ('cache-control' === strtr(strtolower($key), '_', '-')) {
$uniqueKey = strtr(strtolower($key), '_', '-');
unset($this->headerNames[$uniqueKey]);
if ('cache-control' === $uniqueKey) {
$this->computedCacheControl = array();
}
}
......
......@@ -31,7 +31,7 @@ class AttributeBag implements AttributeBagInterface, \IteratorAggregate, \Counta
/**
* Constructor.
*
* @param string $storageKey The key used to store flashes in the session.
* @param string $storageKey The key used to store attributes in the session.
*/
public function __construct($storageKey = '_sf2_attributes')
{
......
......@@ -177,10 +177,17 @@ class FlashBag implements FlashBagInterface, \IteratorAggregate, \Countable
/**
* Returns the number of flashes.
*
* This method does not work.
*
* @deprecated in 2.2, removed in 2.3
* @see https://github.com/symfony/symfony/issues/6408
*
* @return int The number of flashes
*/
public function count()
{
trigger_error(sprintf('%s() is deprecated since 2.2 and will be removed in 2.3', __METHOD__), E_USER_DEPRECATED);
return count($this->flashes);
}
}
......@@ -259,6 +259,8 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable
*/
public function getFlashes()
{
trigger_error('getFlashes() is deprecated since version 2.1 and will be removed in 2.3. Use the FlashBag instead.', E_USER_DEPRECATED);
$all = $this->getBag($this->flashName)->all();
$return = array();
......@@ -282,6 +284,8 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable
*/
public function setFlashes($values)
{
trigger_error('setFlashes() is deprecated since version 2.1 and will be removed in 2.3. Use the FlashBag instead.', E_USER_DEPRECATED);
foreach ($values as $name => $value) {
$this->getBag($this->flashName)->set($name, $value);
}
......@@ -297,6 +301,8 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable
*/
public function getFlash($name, $default = null)
{
trigger_error('getFlash() is deprecated since version 2.1 and will be removed in 2.3. Use the FlashBag instead.', E_USER_DEPRECATED);
$return = $this->getBag($this->flashName)->get($name);
return empty($return) ? $default : reset($return);
......@@ -310,6 +316,8 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable
*/
public function setFlash($name, $value)
{
trigger_error('setFlash() is deprecated since version 2.1 and will be removed in 2.3. Use the FlashBag instead.', E_USER_DEPRECATED);
$this->getBag($this->flashName)->set($name, $value);
}
......@@ -322,6 +330,8 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable
*/
public function hasFlash($name)
{
trigger_error('hasFlash() is deprecated since version 2.1 and will be removed in 2.3. Use the FlashBag instead.', E_USER_DEPRECATED);
return $this->getBag($this->flashName)->has($name);
}
......@@ -332,6 +342,8 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable
*/
public function removeFlash($name)
{
trigger_error('removeFlash() is deprecated since version 2.1 and will be removed in 2.3. Use the FlashBag instead.', E_USER_DEPRECATED);
$this->getBag($this->flashName)->get($name);
}
......@@ -342,6 +354,8 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable
*/
public function clearFlashes()
{
trigger_error('clearFlashes() is deprecated since version 2.1 and will be removed in 2.3. Use the FlashBag instead.', E_USER_DEPRECATED);
return $this->getBag($this->flashName)->clear();
}
}
......@@ -36,6 +36,13 @@ class MongoDbSessionHandler implements \SessionHandlerInterface
/**
* 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]
*
* @param \Mongo|\MongoClient $mongo A MongoClient or Mongo instance
* @param array $options An associative array of field options
*
......@@ -55,9 +62,9 @@ class MongoDbSessionHandler implements \SessionHandlerInterface
$this->mongo = $mongo;
$this->options = array_merge(array(
'id_field' => 'sess_id',
'data_field' => 'sess_data',
'time_field' => 'sess_time',
'id_field' => '_id',
'data_field' => 'data',
'time_field' => 'time',
), $options);
}
......@@ -82,10 +89,9 @@ class MongoDbSessionHandler implements \SessionHandlerInterface
*/
public function destroy($sessionId)
{
$this->getCollection()->remove(
array($this->options['id_field'] => $sessionId),
array('justOne' => true)
);
$this->getCollection()->remove(array(
$this->options['id_field'] => $sessionId
));
return true;
}
......@@ -95,11 +101,21 @@ class MongoDbSessionHandler implements \SessionHandlerInterface
*/
public function gc($lifetime)
{
$time = new \MongoTimestamp(time() - $lifetime);
/* Note: MongoDB 2.2+ supports TTL collections, which may be used in
* place of this method by indexing the "time_field" field with an
* "expireAfterSeconds" option. Regardless of whether TTL collections
* are used, consider indexing this field to make the remove query more
* efficient.
*
* See: http://docs.mongodb.org/manual/tutorial/expire-data/
*/
$time = new \MongoDate(time() - $lifetime);
$this->getCollection()->remove(array(
$this->options['time_field'] => array('$lt' => $time),
));
return true;
}
/**
......@@ -107,16 +123,13 @@ class MongoDbSessionHandler implements \SessionHandlerInterface
*/
public function write($sessionId, $data)
{
$data = array(
$this->options['id_field'] => $sessionId,
$this->options['data_field'] => new \MongoBinData($data, \MongoBinData::BYTE_ARRAY),
$this->options['time_field'] => new \MongoTimestamp()
);
$this->getCollection()->update(
array($this->options['id_field'] => $sessionId),
array('$set' => $data),
array('upsert' => true)
array('$set' => array(
$this->options['data_field'] => new \MongoBinData($data, \MongoBinData::BYTE_ARRAY),
$this->options['time_field'] => new \MongoDate(),
)),
array('upsert' => true, 'multiple' => false)
);
return true;
......
......@@ -27,17 +27,17 @@ class NativeSessionStorage implements SessionStorageInterface
/**
* Array of SessionBagInterface
*
* @var array
* @var SessionBagInterface[]
*/
protected $bags;
/**
* @var boolean
* @var Boolean
*/
protected $started = false;
/**
* @var boolean
* @var Boolean
*/
protected $closed = false;
......@@ -332,9 +332,9 @@ class NativeSessionStorage implements SessionStorageInterface
* Registers save handler as a PHP session handler.
*
* To use internal PHP session save handlers, override this method using ini_set with
* session.save_handlers and session.save_path e.g.
* session.save_handler and session.save_path e.g.
*
* ini_set('session.save_handlers', 'files');
* ini_set('session.save_handler', 'files');
* ini_set('session.save_path', /tmp');
*
* @see http://php.net/session-set-save-handler
......
......@@ -99,6 +99,8 @@ abstract class AbstractProxy
* Sets the session ID.
*
* @param string $id
*
* @throws \LogicException
*/
public function setId($id)
{
......@@ -123,6 +125,8 @@ abstract class AbstractProxy
* Sets the session name.
*
* @param string $name
*
* @throws \LogicException
*/
public function setName($name)
{
......
......@@ -62,6 +62,8 @@ class StreamedResponse extends Response
* Sets the PHP callback associated with this Response.
*
* @param mixed $callback A valid PHP callback
*
* @throws \LogicException
*/
public function setCallback($callback)
{
......
......@@ -19,11 +19,14 @@
"php": ">=5.3.3"
},
"autoload": {
"psr-0": {
"Symfony\\Component\\HttpFoundation": "",
"SessionHandlerInterface": "Symfony/Component/HttpFoundation/Resources/stubs"
}
"psr-0": { "Symfony\\Component\\HttpFoundation\\": "" },
"classmap": [ "Symfony/Component/HttpFoundation/Resources/stubs" ]
},
"target-dir": "Symfony/Component/HttpFoundation",
"minimum-stability": "dev"
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-master": "2.2-dev"
}
}
}
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false"
bootstrap="vendor/autoload.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>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment