update 1.0.8.0

Commits for version update
This commit is contained in:
Manish Verma
2016-10-17 12:02:27 +05:30
parent dec927987b
commit 76e85db070
9674 changed files with 495757 additions and 58922 deletions

View File

@@ -29,13 +29,13 @@ class Cookie
/**
* 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
* @param string $name The name of the cookie
* @param string $value The value of the cookie
* @param int|string|\DateTimeInterface $expire The time the cookie expires
* @param string $path The path on the server in which the cookie will be available on
* @param string $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
*/

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

@@ -68,7 +68,7 @@ class File extends \SplFileInfo
* 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 (i.e. "application/pdf")
* @return string|null The guessed mime type (e.g. "application/pdf")
*
* @see MimeTypeGuesser
*/

View File

@@ -661,6 +661,7 @@ class MimeTypeExtensionGuesser implements ExtensionGuesserInterface
'image/gif' => 'gif',
'image/ief' => 'ief',
'image/jpeg' => 'jpeg',
'image/pjpeg' => 'jpeg',
'image/ktx' => 'ktx',
'image/png' => 'png',
'image/prs.btif' => 'btif',

View File

@@ -194,7 +194,7 @@ class UploadedFile extends File
/**
* Returns whether the file was uploaded successfully.
*
* @return bool True if the file has been uploaded with HTTP and no error occurred.
* @return bool True if the file has been uploaded with HTTP and no error occurred
*/
public function isValid()
{

View File

@@ -57,7 +57,7 @@ class IpUtils
* @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.
* @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)
{

View File

@@ -190,10 +190,10 @@ class ParameterBag implements \IteratorAggregate, \Countable
/**
* Filter key.
*
* @param string $key Key.
* @param mixed $default Default = null.
* @param int $filter FILTER_* constant.
* @param mixed $options Filter options.
* @param string $key Key
* @param mixed $default Default = null
* @param int $filter FILTER_* constant
* @param mixed $options Filter options
*
* @see http://php.net/manual/en/function.filter-var.php
*

View File

@@ -66,7 +66,7 @@ class RedirectResponse extends Response
*
* @param string $url The URL to redirect to
*
* @return RedirectResponse The current response.
* @return RedirectResponse The current response
*
* @throws \InvalidArgumentException
*/

View File

@@ -11,6 +11,7 @@
namespace Symfony\Component\HttpFoundation;
use Symfony\Component\HttpFoundation\Exception\ConflictingHeadersException;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
/**
@@ -553,7 +554,7 @@ class Request
/**
* Gets the list of trusted proxies.
*
* @return array An array of trusted proxies.
* @return array An array of trusted proxies
*/
public static function getTrustedProxies()
{
@@ -579,7 +580,7 @@ class Request
/**
* Gets the list of trusted host patterns.
*
* @return array An array of trusted host patterns.
* @return array An array of trusted host patterns
*/
public static function getTrustedHosts()
{
@@ -798,41 +799,34 @@ class Request
return array($ip);
}
if (self::$trustedHeaders[self::HEADER_FORWARDED] && $this->headers->has(self::$trustedHeaders[self::HEADER_FORWARDED])) {
$hasTrustedForwardedHeader = self::$trustedHeaders[self::HEADER_FORWARDED] && $this->headers->has(self::$trustedHeaders[self::HEADER_FORWARDED]);
$hasTrustedClientIpHeader = self::$trustedHeaders[self::HEADER_CLIENT_IP] && $this->headers->has(self::$trustedHeaders[self::HEADER_CLIENT_IP]);
if ($hasTrustedForwardedHeader) {
$forwardedHeader = $this->headers->get(self::$trustedHeaders[self::HEADER_FORWARDED]);
preg_match_all('{(for)=("?\[?)([a-z0-9\.:_\-/]*)}', $forwardedHeader, $matches);
$clientIps = $matches[3];
} elseif (self::$trustedHeaders[self::HEADER_CLIENT_IP] && $this->headers->has(self::$trustedHeaders[self::HEADER_CLIENT_IP])) {
$clientIps = array_map('trim', explode(',', $this->headers->get(self::$trustedHeaders[self::HEADER_CLIENT_IP])));
$forwardedClientIps = $matches[3];
$forwardedClientIps = $this->normalizeAndFilterClientIps($forwardedClientIps, $ip);
$clientIps = $forwardedClientIps;
}
$clientIps[] = $ip; // Complete the IP chain with the IP the request actually came from
$firstTrustedIp = null;
if ($hasTrustedClientIpHeader) {
$xForwardedForClientIps = array_map('trim', explode(',', $this->headers->get(self::$trustedHeaders[self::HEADER_CLIENT_IP])));
foreach ($clientIps as $key => $clientIp) {
// Remove port (unfortunately, it does happen)
if (preg_match('{((?:\d+\.){3}\d+)\:\d+}', $clientIp, $match)) {
$clientIps[$key] = $clientIp = $match[1];
}
if (!filter_var($clientIp, FILTER_VALIDATE_IP)) {
unset($clientIps[$key]);
continue;
}
if (IpUtils::checkIp($clientIp, self::$trustedProxies)) {
unset($clientIps[$key]);
// Fallback to this when the client IP falls into the range of trusted proxies
if (null === $firstTrustedIp) {
$firstTrustedIp = $clientIp;
}
}
$xForwardedForClientIps = $this->normalizeAndFilterClientIps($xForwardedForClientIps, $ip);
$clientIps = $xForwardedForClientIps;
}
// Now the IP chain contains only untrusted proxies and the client IP
return $clientIps ? array_reverse($clientIps) : array($firstTrustedIp);
if ($hasTrustedForwardedHeader && $hasTrustedClientIpHeader && $forwardedClientIps !== $xForwardedForClientIps) {
throw new ConflictingHeadersException('The request has both a trusted Forwarded header and a trusted Client IP header, conflicting with each other with regards to the originating IP addresses of the request. This is the result of a misconfiguration. You should either configure your proxy only to send one of these headers, or configure Symfony to distrust one of them.');
}
if (!$hasTrustedForwardedHeader && !$hasTrustedClientIpHeader) {
return $this->normalizeAndFilterClientIps(array(), $ip);
}
return $clientIps;
}
/**
@@ -1389,7 +1383,7 @@ class Request
/**
* Sets the request format.
*
* @param string $format The request format.
* @param string $format The request format
*/
public function setRequestFormat($format)
{
@@ -1453,7 +1447,7 @@ class Request
/**
* Checks if the request method is of specified type.
*
* @param string $method Uppercase request method (GET, POST etc).
* @param string $method Uppercase request method (GET, POST etc)
*
* @return bool
*/
@@ -1469,7 +1463,7 @@ class Request
*/
public function isMethodSafe()
{
return in_array($this->getMethod(), array('GET', 'HEAD'));
return in_array($this->getMethod(), array('GET', 'HEAD', 'OPTIONS', 'TRACE'));
}
/**
@@ -1477,7 +1471,7 @@ class Request
*
* @param bool $asResource If true, a resource will be returned
*
* @return string|resource The request body content or a resource to read the body stream.
* @return string|resource The request body content or a resource to read the body stream
*
* @throws \LogicException
*/
@@ -1923,4 +1917,35 @@ class Request
{
return self::$trustedProxies && IpUtils::checkIp($this->server->get('REMOTE_ADDR'), self::$trustedProxies);
}
private function normalizeAndFilterClientIps(array $clientIps, $ip)
{
$clientIps[] = $ip; // Complete the IP chain with the IP the request actually came from
$firstTrustedIp = null;
foreach ($clientIps as $key => $clientIp) {
// Remove port (unfortunately, it does happen)
if (preg_match('{((?:\d+\.){3}\d+)\:\d+}', $clientIp, $match)) {
$clientIps[$key] = $clientIp = $match[1];
}
if (!filter_var($clientIp, FILTER_VALIDATE_IP)) {
unset($clientIps[$key]);
continue;
}
if (IpUtils::checkIp($clientIp, self::$trustedProxies)) {
unset($clientIps[$key]);
// Fallback to this when the client IP falls into the range of trusted proxies
if (null === $firstTrustedIp) {
$firstTrustedIp = $clientIp;
}
}
}
// Now the IP chain contains only untrusted proxies and the client IP
return $clientIps ? array_reverse($clientIps) : array($firstTrustedIp);
}
}

View File

@@ -258,7 +258,7 @@ class Response
*
* @param Request $request A Request instance
*
* @return Response The current response.
* @return Response The current response
*/
public function prepare(Request $request)
{

View File

@@ -230,7 +230,7 @@ class ResponseHeaderBag extends HeaderBag
* 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.
* @return string A string suitable for use as a Content-Disposition field-value
*
* @throws \InvalidArgumentException
*

View File

@@ -29,8 +29,8 @@ class NamespacedAttributeBag extends AttributeBag
/**
* Constructor.
*
* @param string $storageKey Session storage key.
* @param string $namespaceCharacter Namespace character to use in keys.
* @param string $storageKey Session storage key
* @param string $namespaceCharacter Namespace character to use in keys
*/
public function __construct($storageKey = '_sf2_attributes', $namespaceCharacter = '/')
{

View File

@@ -37,7 +37,7 @@ class AutoExpireFlashBag implements FlashBagInterface
/**
* Constructor.
*
* @param string $storageKey The key used to store flashes in the session.
* @param string $storageKey The key used to store flashes in the session
*/
public function __construct($storageKey = '_sf2_flashes')
{

View File

@@ -37,7 +37,7 @@ class FlashBag implements FlashBagInterface
/**
* Constructor.
*
* @param string $storageKey The key used to store flashes in the session.
* @param string $storageKey The key used to store flashes in the session
*/
public function __construct($storageKey = '_sf2_flashes')
{

View File

@@ -39,8 +39,8 @@ interface FlashBagInterface extends SessionBagInterface
/**
* Gets flash messages for a given type.
*
* @param string $type Message category type.
* @param array $default Default value if $type does not exist.
* @param string $type Message category type
* @param array $default Default value if $type does not exist
*
* @return array
*/
@@ -57,7 +57,7 @@ interface FlashBagInterface extends SessionBagInterface
* Gets and clears flash from the stack.
*
* @param string $type
* @param array $default Default value if $type does not exist.
* @param array $default Default value if $type does not exist
*
* @return array
*/

View File

@@ -46,7 +46,7 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable
/**
* Constructor.
*
* @param SessionStorageInterface $storage A SessionStorageInterface instance.
* @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)
*/

View File

@@ -42,7 +42,7 @@ interface SessionBagInterface
/**
* Clears out data from bag.
*
* @return mixed Whatever data was contained.
* @return mixed Whatever data was contained
*/
public function clear();
}

View File

@@ -23,7 +23,7 @@ interface SessionInterface
/**
* Starts the session storage.
*
* @return bool True if session started.
* @return bool True if session started
*
* @throws \RuntimeException If session fails to start.
*/
@@ -32,7 +32,7 @@ interface SessionInterface
/**
* Returns the session ID.
*
* @return string The session ID.
* @return string The session ID
*/
public function getId();
@@ -46,7 +46,7 @@ interface SessionInterface
/**
* Returns the session name.
*
* @return mixed The session name.
* @return mixed The session name
*/
public function getName();
@@ -68,7 +68,7 @@ interface SessionInterface
* to expire with browser session. Time is in seconds, and is
* not a Unix timestamp.
*
* @return bool True if session invalidated, false if error.
* @return bool True if session invalidated, false if error
*/
public function invalidate($lifetime = null);
@@ -76,13 +76,13 @@ interface SessionInterface
* 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 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.
* @return bool True if session migrated, false if error
*/
public function migrate($destroy = false, $lifetime = null);
@@ -108,7 +108,7 @@ interface SessionInterface
* Returns an attribute.
*
* @param string $name The attribute name
* @param mixed $default The default value if not found.
* @param mixed $default The default value if not found
*
* @return mixed
*/

View File

@@ -19,7 +19,7 @@ namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
class MemcacheSessionHandler implements \SessionHandlerInterface
{
/**
* @var \Memcache Memcache driver.
* @var \Memcache Memcache driver
*/
private $memcache;
@@ -29,7 +29,7 @@ class MemcacheSessionHandler implements \SessionHandlerInterface
private $ttl;
/**
* @var string Key prefix for shared environments.
* @var string Key prefix for shared environments
*/
private $prefix;

View File

@@ -24,7 +24,7 @@ namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
class MemcachedSessionHandler implements \SessionHandlerInterface
{
/**
* @var \Memcached Memcached driver.
* @var \Memcached Memcached driver
*/
private $memcached;
@@ -34,7 +34,7 @@ class MemcachedSessionHandler implements \SessionHandlerInterface
private $ttl;
/**
* @var string Key prefix for shared environments.
* @var string Key prefix for shared environments
*/
private $prefix;

View File

@@ -19,7 +19,7 @@ namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
class MongoDbSessionHandler implements \SessionHandlerInterface
{
/**
* @var \Mongo
* @var \Mongo|\MongoClient|\MongoDB\Client
*/
private $mongo;
@@ -61,15 +61,15 @@ class MongoDbSessionHandler implements \SessionHandlerInterface
* If you use such an index, you can drop `gc_probability` to 0 since
* no garbage-collection is required.
*
* @param \Mongo|\MongoClient $mongo A MongoClient or Mongo instance
* @param array $options An associative array of field options
* @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 \MongoClient || $mongo instanceof \Mongo)) {
if (!($mongo instanceof \MongoDB\Client || $mongo instanceof \MongoClient || $mongo instanceof \Mongo)) {
throw new \InvalidArgumentException('MongoClient or Mongo instance required');
}
@@ -108,7 +108,9 @@ class MongoDbSessionHandler implements \SessionHandlerInterface
*/
public function destroy($sessionId)
{
$this->getCollection()->remove(array(
$methodName = $this->mongo instanceof \MongoDB\Client ? 'deleteOne' : 'remove';
$this->getCollection()->$methodName(array(
$this->options['id_field'] => $sessionId,
));
@@ -120,8 +122,10 @@ class MongoDbSessionHandler implements \SessionHandlerInterface
*/
public function gc($maxlifetime)
{
$this->getCollection()->remove(array(
$this->options['expiry_field'] => array('$lt' => new \MongoDate()),
$methodName = $this->mongo instanceof \MongoDB\Client ? 'deleteOne' : 'remove';
$this->getCollection()->$methodName(array(
$this->options['expiry_field'] => array('$lt' => $this->createDateTime()),
));
return true;
@@ -132,18 +136,28 @@ class MongoDbSessionHandler implements \SessionHandlerInterface
*/
public function write($sessionId, $data)
{
$expiry = new \MongoDate(time() + (int) ini_get('session.gc_maxlifetime'));
$expiry = $this->createDateTime(time() + (int) ini_get('session.gc_maxlifetime'));
$fields = array(
$this->options['data_field'] => new \MongoBinData($data, \MongoBinData::BYTE_ARRAY),
$this->options['time_field'] => new \MongoDate(),
$this->options['time_field'] => $this->createDateTime(),
$this->options['expiry_field'] => $expiry,
);
$this->getCollection()->update(
$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),
array('upsert' => true, 'multiple' => false)
$options
);
return true;
@@ -156,10 +170,18 @@ class MongoDbSessionHandler implements \SessionHandlerInterface
{
$dbData = $this->getCollection()->findOne(array(
$this->options['id_field'] => $sessionId,
$this->options['expiry_field'] => array('$gte' => new \MongoDate()),
$this->options['expiry_field'] => array('$gte' => $this->createDateTime()),
));
return null === $dbData ? '' : $dbData[$this->options['data_field']]->bin;
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;
}
/**
@@ -179,10 +201,30 @@ class MongoDbSessionHandler implements \SessionHandlerInterface
/**
* Return a Mongo instance.
*
* @return \Mongo
* @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.
*/
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

@@ -23,7 +23,7 @@ class NativeFileSessionHandler extends NativeSessionHandler
/**
* Constructor.
*
* @param string $savePath Path of directory to save session files.
* @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
*

View File

@@ -325,14 +325,8 @@ class PdoSessionHandler implements \SessionHandlerInterface
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', $data, \PDO::PARAM_LOB);
$mergeStmt->bindParam(':lifetime', $maxlifetime, \PDO::PARAM_INT);
$mergeStmt->bindValue(':time', time(), \PDO::PARAM_INT);
$mergeStmt = $this->getMergeStatement($sessionId, $data, $maxlifetime);
if (null !== $mergeStmt) {
$mergeStmt->execute();
return true;
@@ -347,7 +341,7 @@ class PdoSessionHandler implements \SessionHandlerInterface
$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
// 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
@@ -510,54 +504,51 @@ class PdoSessionHandler implements \SessionHandlerInterface
$selectSql = $this->getSelectSql();
$selectStmt = $this->pdo->prepare($selectSql);
$selectStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
$selectStmt->execute();
$sessionRows = $selectStmt->fetchAll(\PDO::FETCH_NUM);
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. SELECT
// FOR UPDATE is necessary to avoid deadlock of connection that starts reading
// before we write (transform intention to real lock).
$selectStmt->execute();
$sessionRows = $selectStmt->fetchAll(\PDO::FETCH_NUM);
if ($sessionRows) {
return is_resource($sessionRows[0][0]) ? stream_get_contents($sessionRows[0][0]) : $sessionRows[0][0];
}
if ($sessionRows) {
if ($sessionRows[0][1] + $sessionRows[0][2] < time()) {
$this->sessionExpired = true;
return '';
}
throw $e;
return is_resource($sessionRows[0][0]) ? stream_get_contents($sessionRows[0][0]) : $sessionRows[0][0];
}
}
return '';
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);
}
/**
@@ -653,29 +644,64 @@ class PdoSessionHandler implements \SessionHandlerInterface
}
/**
* Returns a merge/upsert (i.e. insert or update) SQL query when supported by the database for writing session data.
* Returns a merge/upsert (i.e. insert or update) statement when supported by the database for writing session data.
*
* @return string|null The SQL string or null when not supported
* @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 getMergeSql()
private function getMergeStatement($sessionId, $data, $maxlifetime)
{
switch ($this->driver) {
case 'mysql':
return "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time) ".
$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)";
case 'oci':
break;
case 'oci' === $this->driver:
// 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->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time) ".
"WHEN MATCHED THEN UPDATE SET $this->dataCol = :data, $this->lifetimeCol = :lifetime, $this->timeCol = :time";
$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
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->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time) ".
"WHEN MATCHED THEN UPDATE SET $this->dataCol = :data, $this->lifetimeCol = :lifetime, $this->timeCol = :time;";
case 'sqlite':
return "INSERT OR REPLACE INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time)";
$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;
}
}

View File

@@ -56,7 +56,7 @@ class MetadataBag implements SessionBagInterface
/**
* Constructor.
*
* @param string $storageKey The key used to store bag in the session.
* @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)

View File

@@ -66,7 +66,7 @@ class MockArraySessionStorage implements SessionStorageInterface
* Constructor.
*
* @param string $name Session name
* @param MetadataBag $metaBag MetadataBag instance.
* @param MetadataBag $metaBag MetadataBag instance
*/
public function __construct($name = 'MOCKSESSID', MetadataBag $metaBag = null)
{

View File

@@ -32,9 +32,9 @@ class MockFileSessionStorage extends MockArraySessionStorage
/**
* Constructor.
*
* @param string $savePath Path of directory to save session files.
* @param string $name Session name.
* @param MetadataBag $metaBag MetadataBag instance.
* @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)
{

View File

@@ -91,9 +91,9 @@ class NativeSessionStorage implements SessionStorageInterface
* upload_progress.min-freq, "1"
* url_rewriter.tags, "a=href,area=href,frame=src,form=,fieldset="
*
* @param array $options Session configuration options.
* @param array $options Session configuration options
* @param AbstractProxy|NativeSessionHandler|\SessionHandlerInterface|null $handler
* @param MetadataBag $metaBag MetadataBag.
* @param MetadataBag $metaBag MetadataBag
*/
public function __construct(array $options = array(), $handler = null, MetadataBag $metaBag = null)
{
@@ -299,7 +299,7 @@ class NativeSessionStorage implements SessionStorageInterface
* For convenience we omit 'session.' from the beginning of the keys.
* Explicitly ignores other ini keys.
*
* @param array $options Session ini directives array(key => value).
* @param array $options Session ini directives array(key => value)
*
* @see http://php.net/session.configuration
*/

View File

@@ -32,7 +32,7 @@ class NativeProxy extends AbstractProxy
/**
* Returns true if this handler wraps an internal PHP session save handler using \SessionHandler.
*
* @return bool False.
* @return bool False
*/
public function isWrapper()
{

View File

@@ -24,7 +24,7 @@ interface SessionStorageInterface
/**
* Starts the session.
*
* @return bool True if started.
* @return bool True if started
*
* @throws \RuntimeException If something goes wrong starting the session.
*/
@@ -33,14 +33,14 @@ interface SessionStorageInterface
/**
* Checks if the session is started.
*
* @return bool True if started, false otherwise.
* @return bool True if started, false otherwise
*/
public function isStarted();
/**
* Returns the session ID.
*
* @return string The session ID or empty.
* @return string The session ID or empty
*/
public function getId();
@@ -54,7 +54,7 @@ interface SessionStorageInterface
/**
* Returns the session name.
*
* @return mixed The session name.
* @return mixed The session name
*/
public function getName();

View File

@@ -923,6 +923,74 @@ class RequestTest extends \PHPUnit_Framework_TestCase
);
}
/**
* @expectedException \Symfony\Component\HttpFoundation\Exception\ConflictingHeadersException
* @dataProvider testGetClientIpsWithConflictingHeadersProvider
*/
public function testGetClientIpsWithConflictingHeaders($httpForwarded, $httpXForwardedFor)
{
$request = new Request();
$server = array(
'REMOTE_ADDR' => '88.88.88.88',
'HTTP_FORWARDED' => $httpForwarded,
'HTTP_X_FORWARDED_FOR' => $httpXForwardedFor,
);
Request::setTrustedProxies(array('88.88.88.88'));
$request->initialize(array(), array(), array(), array(), array(), $server);
$request->getClientIps();
}
public function testGetClientIpsWithConflictingHeadersProvider()
{
// $httpForwarded $httpXForwardedFor
return array(
array('for=87.65.43.21', '192.0.2.60'),
array('for=87.65.43.21, for=192.0.2.60', '192.0.2.60'),
array('for=192.0.2.60', '192.0.2.60,87.65.43.21'),
array('for="::face", for=192.0.2.60', '192.0.2.60,192.0.2.43'),
array('for=87.65.43.21, for=192.0.2.60', '192.0.2.60,87.65.43.21'),
);
}
/**
* @dataProvider testGetClientIpsWithAgreeingHeadersProvider
*/
public function testGetClientIpsWithAgreeingHeaders($httpForwarded, $httpXForwardedFor)
{
$request = new Request();
$server = array(
'REMOTE_ADDR' => '88.88.88.88',
'HTTP_FORWARDED' => $httpForwarded,
'HTTP_X_FORWARDED_FOR' => $httpXForwardedFor,
);
Request::setTrustedProxies(array('88.88.88.88'));
$request->initialize(array(), array(), array(), array(), array(), $server);
$request->getClientIps();
Request::setTrustedProxies(array());
}
public function testGetClientIpsWithAgreeingHeadersProvider()
{
// $httpForwarded $httpXForwardedFor
return array(
array('for="192.0.2.60"', '192.0.2.60'),
array('for=192.0.2.60, for=87.65.43.21', '192.0.2.60,87.65.43.21'),
array('for="[::face]", for=192.0.2.60', '::face,192.0.2.60'),
array('for="192.0.2.60:80"', '192.0.2.60'),
array('for=192.0.2.60;proto=http;by=203.0.113.43', '192.0.2.60'),
array('for="[2001:db8:cafe::17]:4711"', '2001:db8:cafe::17'),
);
}
public function testGetContentWorksTwiceInDefaultMode()
{
$req = new Request();
@@ -1866,6 +1934,32 @@ class RequestTest extends \PHPUnit_Framework_TestCase
array(str_repeat(':', 101)),
);
}
/**
* @dataProvider methodSafeProvider
*/
public function testMethodSafe($method, $safe)
{
$request = new Request();
$request->setMethod($method);
$this->assertEquals($safe, $request->isMethodSafe());
}
public function methodSafeProvider()
{
return array(
array('HEAD', true),
array('GET', true),
array('POST', false),
array('PUT', false),
array('PATCH', false),
array('DELETE', false),
array('PURGE', false),
array('OPTIONS', true),
array('TRACE', true),
array('CONNECT', false),
);
}
}
class RequestContentProxy extends Request

View File

@@ -6,7 +6,7 @@
* (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.
* file this was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler;
@@ -15,7 +15,6 @@ use Symfony\Component\HttpFoundation\Session\Storage\Handler\MongoDbSessionHandl
/**
* @author Markus Bachmann <markus.bachmann@bachi.biz>
* @requires extension mongo
* @group time-sensitive
*/
class MongoDbSessionHandlerTest extends \PHPUnit_Framework_TestCase
@@ -31,7 +30,15 @@ class MongoDbSessionHandlerTest extends \PHPUnit_Framework_TestCase
{
parent::setUp();
$mongoClass = version_compare(phpversion('mongo'), '1.3.0', '<') ? 'Mongo' : 'MongoClient';
if (!extension_loaded('mongo') && !extension_loaded('mongodb')) {
$this->markTestSkipped('The Mongo or MongoDB extension is required.');
}
if (phpversion('mongodb')) {
$mongoClass = 'MongoDB\Client';
} else {
$mongoClass = version_compare(phpversion('mongo'), '1.3.0', '<') ? 'Mongo' : 'MongoClient';
}
$this->mongo = $this->getMockBuilder($mongoClass)
->disableOriginalConstructor()
@@ -96,14 +103,28 @@ class MongoDbSessionHandlerTest extends \PHPUnit_Framework_TestCase
$this->assertArrayHasKey($this->options['expiry_field'], $criteria);
$this->assertArrayHasKey('$gte', $criteria[$this->options['expiry_field']]);
$this->assertInstanceOf('MongoDate', $criteria[$this->options['expiry_field']]['$gte']);
$this->assertGreaterThanOrEqual($criteria[$this->options['expiry_field']]['$gte']->sec, $testTimeout);
return array(
if (phpversion('mongodb')) {
$this->assertInstanceOf('MongoDB\BSON\UTCDateTime', $criteria[$this->options['expiry_field']]['$gte']);
$this->assertGreaterThanOrEqual(round(intval((string) $criteria[$this->options['expiry_field']]['$gte']) / 1000), $testTimeout);
} else {
$this->assertInstanceOf('MongoDate', $criteria[$this->options['expiry_field']]['$gte']);
$this->assertGreaterThanOrEqual($criteria[$this->options['expiry_field']]['$gte']->sec, $testTimeout);
}
$fields = array(
$this->options['id_field'] => 'foo',
$this->options['data_field'] => new \MongoBinData('bar', \MongoBinData::BYTE_ARRAY),
$this->options['id_field'] => new \MongoDate(),
);
if (phpversion('mongodb')) {
$fields[$this->options['data_field']] = new \MongoDB\BSON\Binary('bar', \MongoDB\BSON\Binary::TYPE_OLD_BINARY);
$fields[$this->options['id_field']] = new \MongoDB\BSON\UTCDateTime(time() * 1000);
} else {
$fields[$this->options['data_field']] = new \MongoBinData('bar', \MongoBinData::BYTE_ARRAY);
$fields[$this->options['id_field']] = new \MongoDate();
}
return $fields;
}));
$this->assertEquals('bar', $this->storage->read('foo'));
@@ -120,11 +141,18 @@ class MongoDbSessionHandlerTest extends \PHPUnit_Framework_TestCase
$data = array();
$methodName = phpversion('mongodb') ? 'updateOne' : 'update';
$collection->expects($this->once())
->method('update')
->method($methodName)
->will($this->returnCallback(function ($criteria, $updateData, $options) use (&$data) {
$this->assertEquals(array($this->options['id_field'] => 'foo'), $criteria);
$this->assertEquals(array('upsert' => true, 'multiple' => false), $options);
if (phpversion('mongodb')) {
$this->assertEquals(array('upsert' => true), $options);
} else {
$this->assertEquals(array('upsert' => true, 'multiple' => false), $options);
}
$data = $updateData['$set'];
}));
@@ -132,10 +160,17 @@ class MongoDbSessionHandlerTest extends \PHPUnit_Framework_TestCase
$expectedExpiry = time() + (int) ini_get('session.gc_maxlifetime');
$this->assertTrue($this->storage->write('foo', 'bar'));
$this->assertEquals('bar', $data[$this->options['data_field']]->bin);
$this->assertInstanceOf('MongoDate', $data[$this->options['time_field']]);
$this->assertInstanceOf('MongoDate', $data[$this->options['expiry_field']]);
$this->assertGreaterThanOrEqual($expectedExpiry, $data[$this->options['expiry_field']]->sec);
if (phpversion('mongodb')) {
$this->assertEquals('bar', $data[$this->options['data_field']]->getData());
$this->assertInstanceOf('MongoDB\BSON\UTCDateTime', $data[$this->options['time_field']]);
$this->assertInstanceOf('MongoDB\BSON\UTCDateTime', $data[$this->options['expiry_field']]);
$this->assertGreaterThanOrEqual($expectedExpiry, round(intval((string) $data[$this->options['expiry_field']]) / 1000));
} else {
$this->assertEquals('bar', $data[$this->options['data_field']]->bin);
$this->assertInstanceOf('MongoDate', $data[$this->options['time_field']]);
$this->assertInstanceOf('MongoDate', $data[$this->options['expiry_field']]);
$this->assertGreaterThanOrEqual($expectedExpiry, $data[$this->options['expiry_field']]->sec);
}
}
public function testWriteWhenUsingExpiresField()
@@ -160,20 +195,33 @@ class MongoDbSessionHandlerTest extends \PHPUnit_Framework_TestCase
$data = array();
$methodName = phpversion('mongodb') ? 'updateOne' : 'update';
$collection->expects($this->once())
->method('update')
->method($methodName)
->will($this->returnCallback(function ($criteria, $updateData, $options) use (&$data) {
$this->assertEquals(array($this->options['id_field'] => 'foo'), $criteria);
$this->assertEquals(array('upsert' => true, 'multiple' => false), $options);
if (phpversion('mongodb')) {
$this->assertEquals(array('upsert' => true), $options);
} else {
$this->assertEquals(array('upsert' => true, 'multiple' => false), $options);
}
$data = $updateData['$set'];
}));
$this->assertTrue($this->storage->write('foo', 'bar'));
$this->assertEquals('bar', $data[$this->options['data_field']]->bin);
$this->assertInstanceOf('MongoDate', $data[$this->options['time_field']]);
$this->assertInstanceOf('MongoDate', $data[$this->options['expiry_field']]);
if (phpversion('mongodb')) {
$this->assertEquals('bar', $data[$this->options['data_field']]->getData());
$this->assertInstanceOf('MongoDB\BSON\UTCDateTime', $data[$this->options['time_field']]);
$this->assertInstanceOf('MongoDB\BSON\UTCDateTime', $data[$this->options['expiry_field']]);
} else {
$this->assertEquals('bar', $data[$this->options['data_field']]->bin);
$this->assertInstanceOf('MongoDate', $data[$this->options['time_field']]);
$this->assertInstanceOf('MongoDate', $data[$this->options['expiry_field']]);
}
}
public function testReplaceSessionData()
@@ -187,8 +235,10 @@ class MongoDbSessionHandlerTest extends \PHPUnit_Framework_TestCase
$data = array();
$methodName = phpversion('mongodb') ? 'updateOne' : 'update';
$collection->expects($this->exactly(2))
->method('update')
->method($methodName)
->will($this->returnCallback(function ($criteria, $updateData, $options) use (&$data) {
$data = $updateData;
}));
@@ -196,7 +246,11 @@ class MongoDbSessionHandlerTest extends \PHPUnit_Framework_TestCase
$this->storage->write('foo', 'bar');
$this->storage->write('foo', 'foobar');
$this->assertEquals('foobar', $data['$set'][$this->options['data_field']]->bin);
if (phpversion('mongodb')) {
$this->assertEquals('foobar', $data['$set'][$this->options['data_field']]->getData());
} else {
$this->assertEquals('foobar', $data['$set'][$this->options['data_field']]->bin);
}
}
public function testDestroy()
@@ -208,8 +262,10 @@ class MongoDbSessionHandlerTest extends \PHPUnit_Framework_TestCase
->with($this->options['database'], $this->options['collection'])
->will($this->returnValue($collection));
$methodName = phpversion('mongodb') ? 'deleteOne' : 'remove';
$collection->expects($this->once())
->method('remove')
->method($methodName)
->with(array($this->options['id_field'] => 'foo'));
$this->assertTrue($this->storage->destroy('foo'));
@@ -224,11 +280,18 @@ class MongoDbSessionHandlerTest extends \PHPUnit_Framework_TestCase
->with($this->options['database'], $this->options['collection'])
->will($this->returnValue($collection));
$methodName = phpversion('mongodb') ? 'deleteOne' : 'remove';
$collection->expects($this->once())
->method('remove')
->method($methodName)
->will($this->returnCallback(function ($criteria) {
$this->assertInstanceOf('MongoDate', $criteria[$this->options['expiry_field']]['$lt']);
$this->assertGreaterThanOrEqual(time() - 1, $criteria[$this->options['expiry_field']]['$lt']->sec);
if (phpversion('mongodb')) {
$this->assertInstanceOf('MongoDB\BSON\UTCDateTime', $criteria[$this->options['expiry_field']]['$lt']);
$this->assertGreaterThanOrEqual(time() - 1, round(intval((string) $criteria[$this->options['expiry_field']]['$lt']) / 1000));
} else {
$this->assertInstanceOf('MongoDate', $criteria[$this->options['expiry_field']]['$lt']);
$this->assertGreaterThanOrEqual(time() - 1, $criteria[$this->options['expiry_field']]['$lt']->sec);
}
}));
$this->assertTrue($this->storage->gc(1));
@@ -239,14 +302,23 @@ class MongoDbSessionHandlerTest extends \PHPUnit_Framework_TestCase
$method = new \ReflectionMethod($this->storage, 'getMongo');
$method->setAccessible(true);
$mongoClass = (version_compare(phpversion('mongo'), '1.3.0', '<')) ? '\Mongo' : '\MongoClient';
if (phpversion('mongodb')) {
$mongoClass = 'MongoDB\Client';
} else {
$mongoClass = version_compare(phpversion('mongo'), '1.3.0', '<') ? 'Mongo' : 'MongoClient';
}
$this->assertInstanceOf($mongoClass, $method->invoke($this->storage));
}
private function createMongoCollectionMock()
{
$collection = $this->getMockBuilder('MongoCollection')
$collectionClass = 'MongoCollection';
if (phpversion('mongodb')) {
$collectionClass = 'MongoDB\Collection';
}
$collection = $this->getMockBuilder($collectionClass)
->disableOriginalConstructor()
->getMock();

View File

@@ -362,4 +362,8 @@ class MockPdo extends \PDO
public function beginTransaction()
{
}
public function rollBack()
{
}
}