Merge branch '3.3' into 3.4

* 3.3:
  [appveyor] set memory_limit=-1
  [Router] Skip anonymous classes when loading annotated routes
  Fixed Request::__toString ignoring cookies
  Make sure we only build once and have one time the prefix when importing routes
  [Security] Fix fatal error on non string username
This commit is contained in:
Nicolas Grekas 2018-01-16 19:03:57 +01:00
commit 86d01b550f
12 changed files with 149 additions and 29 deletions

View File

@ -27,6 +27,7 @@ install:
- 7z x php_memcache-3.0.8-5.5-nts-vc11-x86.zip -y >nul - 7z x php_memcache-3.0.8-5.5-nts-vc11-x86.zip -y >nul
- cd .. - cd ..
- copy /Y php.ini-development php.ini-min - copy /Y php.ini-development php.ini-min
- echo memory_limit=-1 >> php.ini-min
- echo serialize_precision=14 >> php.ini-min - echo serialize_precision=14 >> php.ini-min
- echo max_execution_time=1200 >> php.ini-min - echo max_execution_time=1200 >> php.ini-min
- echo date.timezone="America/Los_Angeles" >> php.ini-min - echo date.timezone="America/Los_Angeles" >> php.ini-min

View File

@ -531,9 +531,21 @@ class Request
return trigger_error($e, E_USER_ERROR); return trigger_error($e, E_USER_ERROR);
} }
$cookieHeader = '';
$cookies = array();
foreach ($this->cookies as $k => $v) {
$cookies[] = $k.'='.$v;
}
if (!empty($cookies)) {
$cookieHeader = 'Cookie: '.implode('; ', $cookies)."\r\n";
}
return return
sprintf('%s %s %s', $this->getMethod(), $this->getRequestUri(), $this->server->get('SERVER_PROTOCOL'))."\r\n". sprintf('%s %s %s', $this->getMethod(), $this->getRequestUri(), $this->server->get('SERVER_PROTOCOL'))."\r\n".
$this->headers."\r\n". $this->headers.
$cookieHeader."\r\n".
$content; $content;
} }

View File

@ -1523,8 +1523,18 @@ class RequestTest extends TestCase
$request = new Request(); $request = new Request();
$request->headers->set('Accept-language', 'zh, en-us; q=0.8, en; q=0.6'); $request->headers->set('Accept-language', 'zh, en-us; q=0.8, en; q=0.6');
$request->cookies->set('Foo', 'Bar');
$this->assertContains('Accept-Language: zh, en-us; q=0.8, en; q=0.6', $request->__toString()); $asString = (string) $request;
$this->assertContains('Accept-Language: zh, en-us; q=0.8, en; q=0.6', $asString);
$this->assertContains('Cookie: Foo=Bar', $asString);
$request->cookies->set('Another', 'Cookie');
$asString = (string) $request;
$this->assertContains('Cookie: Foo=Bar; Another=Cookie', $asString);
} }
public function testIsMethod() public function testIsMethod()

View File

@ -112,22 +112,22 @@ class AnnotationFileLoader extends FileLoader
} }
if (T_CLASS === $token[0]) { if (T_CLASS === $token[0]) {
// Skip usage of ::class constant // Skip usage of ::class constant and anonymous classes
$isClassConstant = false; $skipClassToken = false;
for ($j = $i - 1; $j > 0; --$j) { for ($j = $i - 1; $j > 0; --$j) {
if (!isset($tokens[$j][1])) { if (!isset($tokens[$j][1])) {
break; break;
} }
if (T_DOUBLE_COLON === $tokens[$j][0]) { if (T_DOUBLE_COLON === $tokens[$j][0] || T_NEW === $tokens[$j][0]) {
$isClassConstant = true; $skipClassToken = true;
break; break;
} elseif (!in_array($tokens[$j][0], array(T_WHITESPACE, T_DOC_COMMENT, T_COMMENT))) { } elseif (!in_array($tokens[$j][0], array(T_WHITESPACE, T_DOC_COMMENT, T_COMMENT))) {
break; break;
} }
} }
if (!$isClassConstant) { if (!$skipClassToken) {
$class = true; $class = true;
} }
} }

View File

@ -76,11 +76,11 @@ class RouteCollectionBuilder
foreach ($collection->getResources() as $resource) { foreach ($collection->getResources() as $resource) {
$builder->addResource($resource); $builder->addResource($resource);
} }
// mount into this builder
$this->mount($prefix, $builder);
} }
// mount into this builder
$this->mount($prefix, $builder);
return $builder; return $builder;
} }

View File

@ -0,0 +1,24 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Routing\Tests\Fixtures\OtherAnnotatedClasses;
trait AnonymousClassInTrait
{
public function test()
{
return new class() {
public function foo()
{
}
};
}
}

View File

@ -67,6 +67,17 @@ class AnnotationFileLoaderTest extends AbstractAnnotationLoaderTest
$this->loader->load(__DIR__.'/../Fixtures/OtherAnnotatedClasses/VariadicClass.php'); $this->loader->load(__DIR__.'/../Fixtures/OtherAnnotatedClasses/VariadicClass.php');
} }
/**
* @requires PHP 7.0
*/
public function testLoadAnonymousClass()
{
$this->reader->expects($this->never())->method('getClassAnnotation');
$this->reader->expects($this->never())->method('getMethodAnnotations');
$this->loader->load(__DIR__.'/../Fixtures/OtherAnnotatedClasses/AnonymousClassInTrait.php');
}
public function testSupports() public function testSupports()
{ {
$fixture = __DIR__.'/../Fixtures/annotated.php'; $fixture = __DIR__.'/../Fixtures/annotated.php';

View File

@ -335,4 +335,30 @@ class RouteCollectionBuilderTest extends TestCase
// there are 2 routes (i.e. with non-conflicting names) // there are 2 routes (i.e. with non-conflicting names)
$this->assertCount(3, $collection->all()); $this->assertCount(3, $collection->all());
} }
public function testAddsThePrefixOnlyOnceWhenLoadingMultipleCollections()
{
$firstCollection = new RouteCollection();
$firstCollection->add('a', new Route('/a'));
$secondCollection = new RouteCollection();
$secondCollection->add('b', new Route('/b'));
$loader = $this->getMockBuilder('Symfony\Component\Config\Loader\LoaderInterface')->getMock();
$loader->expects($this->any())
->method('supports')
->will($this->returnValue(true));
$loader
->expects($this->any())
->method('load')
->will($this->returnValue(array($firstCollection, $secondCollection)));
$routeCollectionBuilder = new RouteCollectionBuilder($loader);
$routeCollectionBuilder->import('/directory/recurse/*', '/other/', 'glob');
$routes = $routeCollectionBuilder->build()->all();
$this->assertEquals(2, count($routes));
$this->assertEquals('/other/a', $routes['a']->getPath());
$this->assertEquals('/other/b', $routes['b']->getPath());
}
} }

View File

@ -45,8 +45,6 @@ class ContextListener implements ListenerInterface
private $trustResolver; private $trustResolver;
private $logoutOnUserChange = false; private $logoutOnUserChange = false;
private static $unserializeExceptionCode = 0x37313bc;
/** /**
* @param TokenStorageInterface $tokenStorage * @param TokenStorageInterface $tokenStorage
* @param iterable|UserProviderInterface[] $userProviders * @param iterable|UserProviderInterface[] $userProviders
@ -228,7 +226,7 @@ class ContextListener implements ListenerInterface
$prevUnserializeHandler = ini_set('unserialize_callback_func', __CLASS__.'::handleUnserializeCallback'); $prevUnserializeHandler = ini_set('unserialize_callback_func', __CLASS__.'::handleUnserializeCallback');
$prevErrorHandler = set_error_handler(function ($type, $msg, $file, $line, $context = array()) use (&$prevErrorHandler) { $prevErrorHandler = set_error_handler(function ($type, $msg, $file, $line, $context = array()) use (&$prevErrorHandler) {
if (__FILE__ === $file) { if (__FILE__ === $file) {
throw new \UnexpectedValueException($msg, self::$unserializeExceptionCode); throw new \UnexpectedValueException($msg, 0x37313bc);
} }
return $prevErrorHandler ? $prevErrorHandler($type, $msg, $file, $line, $context) : false; return $prevErrorHandler ? $prevErrorHandler($type, $msg, $file, $line, $context) : false;
@ -242,7 +240,7 @@ class ContextListener implements ListenerInterface
restore_error_handler(); restore_error_handler();
ini_set('unserialize_callback_func', $prevUnserializeHandler); ini_set('unserialize_callback_func', $prevUnserializeHandler);
if ($e) { if ($e) {
if (!$e instanceof \UnexpectedValueException || self::$unserializeExceptionCode !== $e->getCode()) { if (!$e instanceof \UnexpectedValueException || 0x37313bc !== $e->getCode()) {
throw $e; throw $e;
} }
if ($this->logger) { if ($this->logger) {
@ -258,6 +256,6 @@ class ContextListener implements ListenerInterface
*/ */
public static function handleUnserializeCallback($class) public static function handleUnserializeCallback($class)
{ {
throw new \UnexpectedValueException('Class not found: '.$class, self::$unserializeExceptionCode); throw new \UnexpectedValueException('Class not found: '.$class, 0x37313bc);
} }
} }

View File

@ -13,6 +13,7 @@ namespace Symfony\Component\Security\Http\Firewall;
use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException; use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException;
use Symfony\Component\Security\Csrf\CsrfToken; use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
@ -98,15 +99,17 @@ class SimpleFormAuthenticationListener extends AbstractAuthenticationListener
} }
} }
if ($this->options['post_only']) { $requestBag = $this->options['post_only'] ? $request->request : $request;
$username = trim(ParameterBagUtils::getParameterBagValue($request->request, $this->options['username_parameter'])); $username = ParameterBagUtils::getParameterBagValue($requestBag, $this->options['username_parameter']);
$password = ParameterBagUtils::getParameterBagValue($request->request, $this->options['password_parameter']); $password = ParameterBagUtils::getParameterBagValue($requestBag, $this->options['password_parameter']);
} else {
$username = trim(ParameterBagUtils::getRequestParameterValue($request, $this->options['username_parameter'])); if (!\is_string($username) || (\is_object($username) && !\method_exists($username, '__toString'))) {
$password = ParameterBagUtils::getRequestParameterValue($request, $this->options['password_parameter']); throw new BadRequestHttpException(sprintf('The key "%s" must be a string, "%s" given.', $this->options['username_parameter'], \gettype($username)));
} }
if (strlen($username) > Security::MAX_USERNAME_LENGTH) { $username = trim($username);
if (\strlen($username) > Security::MAX_USERNAME_LENGTH) {
throw new BadCredentialsException('Invalid username.'); throw new BadCredentialsException('Invalid username.');
} }

View File

@ -13,6 +13,7 @@ namespace Symfony\Component\Security\Http\Firewall;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Security\Csrf\CsrfToken; use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface; use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
@ -76,14 +77,16 @@ class UsernamePasswordFormAuthenticationListener extends AbstractAuthenticationL
} }
} }
if ($this->options['post_only']) { $requestBag = $this->options['post_only'] ? $request->request : $request;
$username = trim(ParameterBagUtils::getParameterBagValue($request->request, $this->options['username_parameter'])); $username = ParameterBagUtils::getParameterBagValue($requestBag, $this->options['username_parameter']);
$password = ParameterBagUtils::getParameterBagValue($request->request, $this->options['password_parameter']); $password = ParameterBagUtils::getParameterBagValue($requestBag, $this->options['password_parameter']);
} else {
$username = trim(ParameterBagUtils::getRequestParameterValue($request, $this->options['username_parameter'])); if (!\is_string($username) || (\is_object($username) && !\method_exists($username, '__toString'))) {
$password = ParameterBagUtils::getRequestParameterValue($request, $this->options['password_parameter']); throw new BadRequestHttpException(sprintf('The key "%s" must be a string, "%s" given.', $this->options['username_parameter'], \gettype($username)));
} }
$username = trim($username);
if (strlen($username) > Security::MAX_USERNAME_LENGTH) { if (strlen($username) > Security::MAX_USERNAME_LENGTH) {
throw new BadCredentialsException('Invalid username.'); throw new BadCredentialsException('Invalid username.');
} }

View File

@ -14,8 +14,15 @@ namespace Symfony\Component\Security\Tests\Http\Firewall;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Http\Firewall\UsernamePasswordFormAuthenticationListener; use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Http\Authentication\DefaultAuthenticationFailureHandler;
use Symfony\Component\Security\Http\Authentication\DefaultAuthenticationSuccessHandler;
use Symfony\Component\Security\Http\Firewall\UsernamePasswordFormAuthenticationListener;
use Symfony\Component\Security\Http\HttpUtils;
use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategy;
class UsernamePasswordFormAuthenticationListenerTest extends TestCase class UsernamePasswordFormAuthenticationListenerTest extends TestCase
{ {
@ -69,6 +76,31 @@ class UsernamePasswordFormAuthenticationListenerTest extends TestCase
$listener->handle($event); $listener->handle($event);
} }
/**
* @expectedException \Symfony\Component\HttpKernel\Exception\BadRequestHttpException
* @expectedExceptionMessage The key "_username" must be a string, "array" given.
*/
public function testHandleNonStringUsername()
{
$request = Request::create('/login_check', 'POST', array('_username' => array()));
$request->setSession($this->getMockBuilder('Symfony\Component\HttpFoundation\Session\SessionInterface')->getMock());
$listener = new UsernamePasswordFormAuthenticationListener(
new TokenStorage(),
$this->getMockBuilder('Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface')->getMock(),
new SessionAuthenticationStrategy(SessionAuthenticationStrategy::NONE),
$httpUtils = new HttpUtils(),
'foo',
new DefaultAuthenticationSuccessHandler($httpUtils),
new DefaultAuthenticationFailureHandler($this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(), $httpUtils),
array('require_previous_session' => false)
);
$event = new GetResponseEvent($this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(), $request, HttpKernelInterface::MASTER_REQUEST);
$listener->handle($event);
}
public function getUsernameForLength() public function getUsernameForLength()
{ {
return array( return array(