diff --git a/README.md b/README.md index f07f64d466..de0feaacd0 100644 --- a/README.md +++ b/README.md @@ -33,8 +33,8 @@ work for you: Installation ------------ -The best way to install Symfony is to download the Symfony Standard Edition -available at . +The best way to install Symfony is to use the [official Symfony Installer][7]. +It allows you to start a new project based on the version you want. Documentation ------------- @@ -64,3 +64,4 @@ Information on how to run the Symfony test suite can be found in the [4]: https://symfony.com/doc/current/contributing/code/patches.html#check-list [5]: https://symfony.com/doc/current/contributing/code/patches.html#make-a-pull-request [6]: https://symfony.com/doc/master/contributing/code/tests.html +[7]: https://symfony.com/doc/current/book/installation.html#installing-the-symfony-installer diff --git a/src/Symfony/Bridge/Twig/Extension/CodeExtension.php b/src/Symfony/Bridge/Twig/Extension/CodeExtension.php index df87a19284..e4955db0d0 100644 --- a/src/Symfony/Bridge/Twig/Extension/CodeExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/CodeExtension.php @@ -49,7 +49,7 @@ class CodeExtension extends \Twig_Extension new \Twig_SimpleFilter('file_excerpt', array($this, 'fileExcerpt'), array('is_safe' => array('html'))), new \Twig_SimpleFilter('format_file', array($this, 'formatFile'), array('is_safe' => array('html'))), new \Twig_SimpleFilter('format_file_from_text', array($this, 'formatFileFromText'), array('is_safe' => array('html'))), - new \Twig_SimpleFilter('file_link', array($this, 'getFileLink'), array('is_safe' => array('html'))), + new \Twig_SimpleFilter('file_link', array($this, 'getFileLink')), ); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Controller/TemplateController.php b/src/Symfony/Bundle/FrameworkBundle/Controller/TemplateController.php index bed6824611..16d15f9023 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Controller/TemplateController.php +++ b/src/Symfony/Bundle/FrameworkBundle/Controller/TemplateController.php @@ -47,7 +47,7 @@ class TemplateController extends ContainerAware if ($private) { $response->setPrivate(); } elseif ($private === false || (null === $private && ($maxAge || $sharedAge))) { - $response->setPublic($private); + $response->setPublic(); } return $response; diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/public/css/body.css b/src/Symfony/Bundle/FrameworkBundle/Resources/public/css/body.css index fa4d076917..6b81755056 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/public/css/body.css +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/public/css/body.css @@ -52,6 +52,7 @@ build: 56 background-color: #FFFFFF; border: 1px solid #dfdfdf; padding: 40px 50px; + word-break: break-all; } .sf-reset h2 { font-size: 16px; diff --git a/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/CodeHelper.php b/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/CodeHelper.php index 0d7dc15771..b02feb54b1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/CodeHelper.php +++ b/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/CodeHelper.php @@ -154,24 +154,25 @@ class CodeHelper extends Helper */ public function formatFile($file, $line, $text = null) { + if (PHP_VERSION_ID >= 50400) { + $flags = ENT_QUOTES | ENT_SUBSTITUTE; + } else { + $flags = ENT_QUOTES; + } + if (null === $text) { $file = trim($file); $fileStr = $file; if (0 === strpos($fileStr, $this->rootDir)) { $fileStr = str_replace($this->rootDir, '', str_replace('\\', '/', $fileStr)); - $fileStr = sprintf('kernel.root_dir/%s', $this->rootDir, $fileStr); + $fileStr = htmlspecialchars($fileStr, $flags, $this->charset); + $fileStr = sprintf('kernel.root_dir/%s', htmlspecialchars($this->rootDir, $flags, $this->charset), $fileStr); } - $text = "$fileStr at line $line"; + $text = sprintf('%s at line %d', $fileStr, $line); } if (false !== $link = $this->getFileLink($file, $line)) { - if (PHP_VERSION_ID >= 50400) { - $flags = ENT_QUOTES | ENT_SUBSTITUTE; - } else { - $flags = ENT_QUOTES; - } - return sprintf('%s', htmlspecialchars($link, $flags, $this->charset), $text); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/RouterDebugCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/RouterDebugCommandTest.php new file mode 100644 index 0000000000..bf5e183caa --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/RouterDebugCommandTest.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\Command; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Tester\CommandTester; +use Symfony\Bundle\FrameworkBundle\Command\RouterDebugCommand; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +class RouterDebugCommandTest extends \PHPUnit_Framework_TestCase +{ + public function testDebugAllRoutes() + { + $tester = $this->createCommandTester(); + $ret = $tester->execute(array('name' => null)); + + $this->assertEquals(0, $ret, 'Returns 0 in case of success'); + $this->assertContains('[router] Current routes', $tester->getDisplay()); + } + + public function testDebugSingleRoute() + { + $tester = $this->createCommandTester(); + $ret = $tester->execute(array('name' => 'foo')); + + $this->assertEquals(0, $ret, 'Returns 0 in case of success'); + $this->assertContains('[router] Route "foo"', $tester->getDisplay()); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testDebugInvalidRoute() + { + $this->createCommandTester()->execute(array('name' => 'test')); + } + + /** + * @return CommandTester + */ + private function createCommandTester() + { + $application = new Application(); + + $command = new RouterDebugCommand(); + $command->setContainer($this->getContainer()); + $application->add($command); + + return new CommandTester($application->find('router:debug')); + } + + private function getContainer() + { + $routeCollection = new RouteCollection(); + $routeCollection->add('foo', new Route('foo')); + $router = $this->getMock('Symfony\Component\Routing\RouterInterface'); + $router + ->expects($this->atLeastOnce()) + ->method('getRouteCollection') + ->will($this->returnValue($routeCollection)) + ; + + $container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface'); + $container + ->expects($this->once()) + ->method('has') + ->with('router') + ->will($this->returnValue(true)) + ; + $container + ->expects($this->atLeastOnce()) + ->method('get') + ->with('router') + ->will($this->returnValue($router)) + ; + + return $container; + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/RouterMatchCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/RouterMatchCommandTest.php new file mode 100644 index 0000000000..918ba02f6d --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/RouterMatchCommandTest.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\Command; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Tester\CommandTester; +use Symfony\Bundle\FrameworkBundle\Command\RouterMatchCommand; +use Symfony\Bundle\FrameworkBundle\Command\RouterDebugCommand; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\RequestContext; + +class RouterMatchCommandTest extends \PHPUnit_Framework_TestCase +{ + public function testWithMatchPath() + { + $tester = $this->createCommandTester(); + $ret = $tester->execute(array('path_info' => '/foo', 'foo')); + + $this->assertEquals(0, $ret, 'Returns 0 in case of success'); + $this->assertContains('[router] Route "foo"', $tester->getDisplay()); + } + + public function testWithNotMatchPath() + { + $tester = $this->createCommandTester(); + $ret = $tester->execute(array('path_info' => '/test', 'foo')); + + $this->assertEquals(1, $ret, 'Returns 1 in case of failure'); + $this->assertContains('None of the routes match the path "/test"', $tester->getDisplay()); + } + + /** + * @return CommandTester + */ + private function createCommandTester() + { + $application = new Application(); + + $command = new RouterMatchCommand(); + $command->setContainer($this->getContainer()); + $application->add($command); + + $command = new RouterDebugCommand(); + $command->setContainer($this->getContainer()); + $application->add($command); + + return new CommandTester($application->find('router:match')); + } + + private function getContainer() + { + $routeCollection = new RouteCollection(); + $routeCollection->add('foo', new Route('foo')); + $requestContext = new RequestContext(); + $router = $this->getMock('Symfony\Component\Routing\RouterInterface'); + $router + ->expects($this->any()) + ->method('getRouteCollection') + ->will($this->returnValue($routeCollection)) + ; + $router + ->expects($this->any()) + ->method('getContext') + ->will($this->returnValue($requestContext)) + ; + + $container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface'); + $container + ->expects($this->once()) + ->method('has') + ->with('router') + ->will($this->returnValue(true)) + ; + $container + ->expects($this->atLeastOnce()) + ->method('get') + ->with('router') + ->will($this->returnValue($router)) + ; + + return $container; + } +} diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php index 93a9a37728..ca69487c23 100644 --- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php +++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php @@ -12,6 +12,7 @@ namespace Symfony\Bundle\TwigBundle\DependencyInjection; use Symfony\Component\Config\FileLocator; +use Symfony\Component\Config\Resource\DirectoryResource; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; @@ -66,22 +67,26 @@ class TwigExtension extends Extension } else { $twigFilesystemLoaderDefinition->addMethodCall('addPath', array($path, $namespace)); } + $container->addResource(new DirectoryResource($path)); } // register bundles as Twig namespaces foreach ($container->getParameter('kernel.bundles') as $bundle => $class) { if (is_dir($dir = $container->getParameter('kernel.root_dir').'/Resources/'.$bundle.'/views')) { $this->addTwigPath($twigFilesystemLoaderDefinition, $dir, $bundle); + $container->addResource(new DirectoryResource($dir)); } $reflection = new \ReflectionClass($class); if (is_dir($dir = dirname($reflection->getFilename()).'/Resources/views')) { $this->addTwigPath($twigFilesystemLoaderDefinition, $dir, $bundle); + $container->addResource(new DirectoryResource($dir)); } } if (is_dir($dir = $container->getParameter('kernel.root_dir').'/Resources/views')) { $twigFilesystemLoaderDefinition->addMethodCall('addPath', array($dir)); + $container->addResource(new DirectoryResource($dir)); } if (!empty($config['globals'])) { diff --git a/src/Symfony/Component/Console/Formatter/OutputFormatter.php b/src/Symfony/Component/Console/Formatter/OutputFormatter.php index 90b1970f4b..a053ac6ba0 100644 --- a/src/Symfony/Component/Console/Formatter/OutputFormatter.php +++ b/src/Symfony/Component/Console/Formatter/OutputFormatter.php @@ -142,6 +142,7 @@ class OutputFormatter implements OutputFormatterInterface */ public function format($message) { + $message = (string) $message; $offset = 0; $output = ''; $tagRegex = '[a-z][a-z0-9_=;-]*'; diff --git a/src/Symfony/Component/Console/Tests/Command/CommandTest.php b/src/Symfony/Component/Console/Tests/Command/CommandTest.php index c35617d19a..85d51e6fe1 100644 --- a/src/Symfony/Component/Console/Tests/Command/CommandTest.php +++ b/src/Symfony/Component/Console/Tests/Command/CommandTest.php @@ -167,15 +167,6 @@ class CommandTest extends \PHPUnit_Framework_TestCase $this->assertEquals($formatterHelper->getName(), $command->getHelper('formatter')->getName(), '->getHelper() returns the correct helper'); } - public function testGet() - { - $application = new Application(); - $command = new \TestCommand(); - $command->setApplication($application); - $formatterHelper = new FormatterHelper(); - $this->assertEquals($formatterHelper->getName(), $command->getHelper('formatter')->getName(), '->__get() returns the correct helper'); - } - public function testMergeApplicationDefinition() { $application1 = new Application(); diff --git a/src/Symfony/Component/Console/Tests/Formatter/OutputFormatterTest.php b/src/Symfony/Component/Console/Tests/Formatter/OutputFormatterTest.php index deb64a75a5..510a4e7414 100644 --- a/src/Symfony/Component/Console/Tests/Formatter/OutputFormatterTest.php +++ b/src/Symfony/Component/Console/Tests/Formatter/OutputFormatterTest.php @@ -166,6 +166,14 @@ class OutputFormatterTest extends \PHPUnit_Framework_TestCase $this->assertEquals("\033[37;41msome error\033[39;49m".$long, $formatter->format('some error'.$long)); } + public function testFormatToStringObject() + { + $formatter = new OutputFormatter(false); + $this->assertEquals( + 'some info', $formatter->format(new TableCell()) + ); + } + public function testNotDecoratedFormatter() { $formatter = new OutputFormatter(false); @@ -255,3 +263,11 @@ EOF )); } } + +class TableCell +{ + public function __toString() + { + return 'some info'; + } +} diff --git a/src/Symfony/Component/DomCrawler/FormFieldRegistry.php b/src/Symfony/Component/DomCrawler/FormFieldRegistry.php index 6a38e86691..edb2788910 100644 --- a/src/Symfony/Component/DomCrawler/FormFieldRegistry.php +++ b/src/Symfony/Component/DomCrawler/FormFieldRegistry.php @@ -124,13 +124,15 @@ class FormFieldRegistry public function set($name, $value) { $target = &$this->get($name); - if (!is_array($value) || $target instanceof Field\ChoiceFormField) { + if ((!is_array($value) && $target instanceof Field\FormField) || $target instanceof Field\ChoiceFormField) { $target->setValue($value); - } else { + } elseif (is_array($value)) { $fields = self::create($name, $value); foreach ($fields->all() as $k => $v) { $this->set($k, $v); } + } else { + throw new \InvalidArgumentException(sprintf('Cannot set value on a compound field "%s".', $name)); } } diff --git a/src/Symfony/Component/DomCrawler/Tests/FormTest.php b/src/Symfony/Component/DomCrawler/Tests/FormTest.php index 03080bce0f..e2c1904738 100644 --- a/src/Symfony/Component/DomCrawler/Tests/FormTest.php +++ b/src/Symfony/Component/DomCrawler/Tests/FormTest.php @@ -807,6 +807,31 @@ class FormTest extends \PHPUnit_Framework_TestCase )); } + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Cannot set value on a compound field "foo[bar]". + */ + public function testFormRegistrySetValueOnCompoundField() + { + $registry = new FormFieldRegistry(); + $registry->add($this->getFormFieldMock('foo[bar][baz]')); + + $registry->set('foo[bar]', 'fbb'); + } + + /** + * @expectedException InvalidArgumentException + * @expectedExceptionMessage Unreachable field "0" + */ + public function testFormRegistrySetArrayOnNotCompoundField() + { + + $registry = new FormFieldRegistry(); + $registry->add($this->getFormFieldMock('bar')); + + $registry->set('bar', array('baz')); + } + public function testDifferentFieldTypesWithSameName() { $dom = new \DOMDocument(); diff --git a/src/Symfony/Component/Form/FormBuilder.php b/src/Symfony/Component/Form/FormBuilder.php index 8b5b919413..81c9ad5f6d 100644 --- a/src/Symfony/Component/Form/FormBuilder.php +++ b/src/Symfony/Component/Form/FormBuilder.php @@ -62,7 +62,7 @@ class FormBuilder extends FormConfigBuilder implements \IteratorAggregate, FormB throw new BadMethodCallException('FormBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); } - if ($child instanceof self) { + if ($child instanceof FormBuilderInterface) { $this->children[$child->getName()] = $child; // In case an unresolved child with the same name exists @@ -72,7 +72,7 @@ class FormBuilder extends FormConfigBuilder implements \IteratorAggregate, FormB } if (!is_string($child) && !is_int($child)) { - throw new UnexpectedTypeException($child, 'string, integer or Symfony\Component\Form\FormBuilder'); + throw new UnexpectedTypeException($child, 'string, integer or Symfony\Component\Form\FormBuilderInterface'); } if (null !== $type && !is_string($type) && !$type instanceof FormTypeInterface) { diff --git a/src/Symfony/Component/Form/Tests/FormBuilderTest.php b/src/Symfony/Component/Form/Tests/FormBuilderTest.php index da691838c3..8c7b96587d 100644 --- a/src/Symfony/Component/Form/Tests/FormBuilderTest.php +++ b/src/Symfony/Component/Form/Tests/FormBuilderTest.php @@ -11,7 +11,9 @@ namespace Symfony\Component\Form\Tests; +use Symfony\Component\Form\ButtonBuilder; use Symfony\Component\Form\FormBuilder; +use Symfony\Component\Form\SubmitButtonBuilder; class FormBuilderTest extends \PHPUnit_Framework_TestCase { @@ -154,6 +156,12 @@ class FormBuilderTest extends \PHPUnit_Framework_TestCase $this->builder->create('foo'); } + public function testAddButton() + { + $this->builder->add(new ButtonBuilder('reset')); + $this->builder->add(new SubmitButtonBuilder('submit')); + } + public function testGetUnknown() { $this->setExpectedException('Symfony\Component\Form\Exception\InvalidArgumentException', 'The child with the name "foo" does not exist.'); diff --git a/src/Symfony/Component/HttpFoundation/IpUtils.php b/src/Symfony/Component/HttpFoundation/IpUtils.php index 68e9421d94..fb906b6812 100644 --- a/src/Symfony/Component/HttpFoundation/IpUtils.php +++ b/src/Symfony/Component/HttpFoundation/IpUtils.php @@ -62,6 +62,10 @@ class IpUtils public static function checkIp4($requestIp, $ip) { if (false !== strpos($ip, '/')) { + if ('0.0.0.0/0' === $ip) { + return true; + } + list($address, $netmask) = explode('/', $ip, 2); if ($netmask < 1 || $netmask > 32) { diff --git a/src/Symfony/Component/HttpFoundation/Request.php b/src/Symfony/Component/HttpFoundation/Request.php index 1c730ef059..b5aec97570 100644 --- a/src/Symfony/Component/HttpFoundation/Request.php +++ b/src/Symfony/Component/HttpFoundation/Request.php @@ -1760,7 +1760,7 @@ class Request return $prefix; } - if ($baseUrl && false !== $prefix = $this->getUrlencodedPrefix($requestUri, dirname($baseUrl).'/')) { + if ($baseUrl && false !== $prefix = $this->getUrlencodedPrefix($requestUri, rtrim(dirname($baseUrl), '/').'/')) { // directory portion of $baseUrl matches return rtrim($prefix, '/'); } diff --git a/src/Symfony/Component/HttpFoundation/ServerBag.php b/src/Symfony/Component/HttpFoundation/ServerBag.php index 6a4f2c2b16..fa1cb2fc9f 100644 --- a/src/Symfony/Component/HttpFoundation/ServerBag.php +++ b/src/Symfony/Component/HttpFoundation/ServerBag.php @@ -75,6 +75,13 @@ class ServerBag extends ParameterBag // In some circumstances PHP_AUTH_DIGEST needs to be set $headers['PHP_AUTH_DIGEST'] = $authorizationHeader; $this->parameters['PHP_AUTH_DIGEST'] = $authorizationHeader; + } elseif (0 === stripos($authorizationHeader, 'bearer ')) { + /* + * XXX: Since there is no PHP_AUTH_BEARER in PHP predefined variables, + * I'll just set $headers['AUTHORIZATION'] here. + * http://php.net/manual/en/reserved.variables.server.php + */ + $headers['AUTHORIZATION'] = $authorizationHeader; } } } diff --git a/src/Symfony/Component/HttpFoundation/Tests/IpUtilsTest.php b/src/Symfony/Component/HttpFoundation/Tests/IpUtilsTest.php index 726ba6a347..0002478246 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/IpUtilsTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/IpUtilsTest.php @@ -34,6 +34,9 @@ class IpUtilsTest extends \PHPUnit_Framework_TestCase array(true, '192.168.1.1', array('1.2.3.4/1', '192.168.1.0/24')), array(true, '192.168.1.1', array('192.168.1.0/24', '1.2.3.4/1')), array(false, '192.168.1.1', array('1.2.3.4/1', '4.3.2.1/1')), + array(true, '1.2.3.4', '0.0.0.0/0'), + array(false, '1.2.3.4', '256.256.256/0'), + array(false, '1.2.3.4', '192.168.1.0/0'), ); } diff --git a/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php b/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php index e650b2909c..883fe802a4 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php @@ -1345,6 +1345,26 @@ class RequestTest extends \PHPUnit_Framework_TestCase public function getBaseUrlData() { return array( + array( + '/fruit/strawberry/1234index.php/blah', + array( + 'SCRIPT_FILENAME' => 'E:/Sites/cc-new/public_html/fruit/index.php', + 'SCRIPT_NAME' => '/fruit/index.php', + 'PHP_SELF' => '/fruit/index.php', + ), + '/fruit', + '/strawberry/1234index.php/blah', + ), + array( + '/fruit/strawberry/1234index.php/blah', + array( + 'SCRIPT_FILENAME' => 'E:/Sites/cc-new/public_html/index.php', + 'SCRIPT_NAME' => '/index.php', + 'PHP_SELF' => '/index.php', + ), + '', + '/fruit/strawberry/1234index.php/blah', + ), array( '/foo%20bar/', array( diff --git a/src/Symfony/Component/HttpFoundation/Tests/ServerBagTest.php b/src/Symfony/Component/HttpFoundation/Tests/ServerBagTest.php index 7bc8f02c30..20773c4d7a 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/ServerBagTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/ServerBagTest.php @@ -141,4 +141,14 @@ class ServerBagTest extends \PHPUnit_Framework_TestCase 'AUTHORIZATION' => $headerContent, ), $bag->getHeaders()); } + + public function testOAuthBearerAuthWithRedirect() + { + $headerContent = 'Bearer L-yLEOr9zhmUYRkzN1jwwxwQ-PBNiKDc8dgfB4hTfvo'; + $bag = new ServerBag(array('REDIRECT_HTTP_AUTHORIZATION' => $headerContent)); + + $this->assertEquals(array( + 'AUTHORIZATION' => $headerContent, + ), $bag->getHeaders()); + } } diff --git a/src/Symfony/Component/HttpKernel/HttpCache/Store.php b/src/Symfony/Component/HttpKernel/HttpCache/Store.php index 75ba3cf6fd..044d14e0c5 100644 --- a/src/Symfony/Component/HttpKernel/HttpCache/Store.php +++ b/src/Symfony/Component/HttpKernel/HttpCache/Store.php @@ -127,7 +127,7 @@ class Store implements StoreInterface // find a cached entry that matches the request. $match = null; foreach ($entries as $entry) { - if ($this->requestsMatch(isset($entry[1]['vary'][0]) ? $entry[1]['vary'][0] : '', $request->headers->all(), $entry[0])) { + if ($this->requestsMatch(isset($entry[1]['vary'][0]) ? implode(', ', $entry[1]['vary']) : '', $request->headers->all(), $entry[0])) { $match = $entry; break; diff --git a/src/Symfony/Component/HttpKernel/Tests/HttpCache/StoreTest.php b/src/Symfony/Component/HttpKernel/Tests/HttpCache/StoreTest.php index cf4e4b5985..4198ce4031 100644 --- a/src/Symfony/Component/HttpKernel/Tests/HttpCache/StoreTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/HttpCache/StoreTest.php @@ -168,6 +168,16 @@ class StoreTest extends \PHPUnit_Framework_TestCase $this->assertNull($this->store->lookup($req2)); } + public function testDoesNotReturnEntriesThatSlightlyVaryWithLookup() + { + $req1 = Request::create('/test', 'get', array(), array(), array(), array('HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar')); + $req2 = Request::create('/test', 'get', array(), array(), array(), array('HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bam')); + $res = new Response('test', 200, array('Vary' => array('Foo', 'Bar'))); + $this->store->write($req1, $res); + + $this->assertNull($this->store->lookup($req2)); + } + public function testStoresMultipleResponsesForEachVaryCombination() { $req1 = Request::create('/test', 'get', array(), array(), array(), array('HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar')); diff --git a/src/Symfony/Component/Intl/DateFormatter/IntlDateFormatter.php b/src/Symfony/Component/Intl/DateFormatter/IntlDateFormatter.php index 7636be9a34..9b08d1a378 100644 --- a/src/Symfony/Component/Intl/DateFormatter/IntlDateFormatter.php +++ b/src/Symfony/Component/Intl/DateFormatter/IntlDateFormatter.php @@ -119,7 +119,7 @@ class IntlDateFormatter /** * @var bool */ - private $unitializedTimeZoneId = false; + private $uninitializedTimeZoneId = false; /** * @var string @@ -371,7 +371,7 @@ class IntlDateFormatter */ public function getTimeZoneId() { - if (!$this->unitializedTimeZoneId) { + if (!$this->uninitializedTimeZoneId) { return $this->timeZoneId; } @@ -551,7 +551,7 @@ class IntlDateFormatter $timeZoneId = getenv('TZ') ?: 'UTC'; } - $this->unitializedTimeZoneId = true; + $this->uninitializedTimeZoneId = true; } // Backup original passed time zone diff --git a/src/Symfony/Component/Security/Http/Firewall/ExceptionListener.php b/src/Symfony/Component/Security/Http/Firewall/ExceptionListener.php index e52f927952..e388f0a74d 100644 --- a/src/Symfony/Component/Security/Http/Firewall/ExceptionListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/ExceptionListener.php @@ -201,7 +201,7 @@ class ExceptionListener protected function setTargetPath(Request $request) { // session isn't required when using HTTP basic authentication mechanism for example - if ($request->hasSession() && $request->isMethodSafe()) { + if ($request->hasSession() && $request->isMethodSafe() && !$request->isXmlHttpRequest()) { $request->getSession()->set('_security.'.$this->providerKey.'.target_path', $request->getUri()); } } diff --git a/src/Symfony/Component/Security/Http/RememberMe/AbstractRememberMeServices.php b/src/Symfony/Component/Security/Http/RememberMe/AbstractRememberMeServices.php index b14e36da4f..16f7831e7c 100644 --- a/src/Symfony/Component/Security/Http/RememberMe/AbstractRememberMeServices.php +++ b/src/Symfony/Component/Security/Http/RememberMe/AbstractRememberMeServices.php @@ -268,9 +268,17 @@ abstract class AbstractRememberMeServices implements RememberMeServicesInterface * @param array $cookieParts * * @return string + * + * @throws \InvalidArgumentException When $cookieParts contain the cookie delimiter. Extending class should either remove or escape it. */ protected function encodeCookie(array $cookieParts) { + foreach ($cookieParts as $cookiePart) { + if (false !== strpos($cookiePart, self::COOKIE_DELIMITER)) { + throw new \InvalidArgumentException(sprintf('$cookieParts should not contain the cookie delimiter "%s"', self::COOKIE_DELIMITER)); + } + } + return base64_encode(implode(self::COOKIE_DELIMITER, $cookieParts)); } diff --git a/src/Symfony/Component/Security/Http/RememberMe/TokenBasedRememberMeServices.php b/src/Symfony/Component/Security/Http/RememberMe/TokenBasedRememberMeServices.php index 3fe39ac1d0..65bac0a815 100644 --- a/src/Symfony/Component/Security/Http/RememberMe/TokenBasedRememberMeServices.php +++ b/src/Symfony/Component/Security/Http/RememberMe/TokenBasedRememberMeServices.php @@ -95,12 +95,12 @@ class TokenBasedRememberMeServices extends AbstractRememberMeServices * @param int $expires The Unix timestamp when the cookie expires * @param string $password The encoded password * - * @throws \RuntimeException if username contains invalid chars - * * @return string */ protected function generateCookieValue($class, $username, $expires, $password) { + // $username is encoded because it might contain COOKIE_DELIMITER, + // we assume other values don't return $this->encodeCookie(array( $class, base64_encode($username), @@ -117,8 +117,6 @@ class TokenBasedRememberMeServices extends AbstractRememberMeServices * @param int $expires The Unix timestamp when the cookie expires * @param string $password The encoded password * - * @throws \RuntimeException when the private key is empty - * * @return string */ protected function generateCookieHash($class, $username, $expires, $password) diff --git a/src/Symfony/Component/Security/Http/Tests/RememberMe/AbstractRememberMeServicesTest.php b/src/Symfony/Component/Security/Http/Tests/RememberMe/AbstractRememberMeServicesTest.php index c3d926095f..2225b6c553 100644 --- a/src/Symfony/Component/Security/Http/Tests/RememberMe/AbstractRememberMeServicesTest.php +++ b/src/Symfony/Component/Security/Http/Tests/RememberMe/AbstractRememberMeServicesTest.php @@ -14,6 +14,7 @@ namespace Symfony\Component\Security\Http\Tests\RememberMe; use Symfony\Component\Security\Http\RememberMe\RememberMeServicesInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Security\Http\RememberMe\AbstractRememberMeServices; class AbstractRememberMeServicesTest extends \PHPUnit_Framework_TestCase { @@ -236,6 +237,30 @@ class AbstractRememberMeServicesTest extends \PHPUnit_Framework_TestCase ); } + public function testEncodeCookieAndDecodeCookieAreInvertible() + { + $cookieParts = array('aa', 'bb', 'cc'); + $service = $this->getService(); + + $encoded = $this->callProtected($service, 'encodeCookie', array($cookieParts)); + $this->assertInternalType('string', $encoded); + + $decoded = $this->callProtected($service, 'decodeCookie', array($encoded)); + $this->assertSame($cookieParts, $decoded); + } + + /** + * @expectedException InvalidArgumentException + * @expectedExceptionMessage cookie delimiter + */ + public function testThereShouldBeNoCookieDelimiterInCookieParts() + { + $cookieParts = array('aa', 'b'.AbstractRememberMeServices::COOKIE_DELIMITER.'b', 'cc'); + $service = $this->getService(); + + $this->callProtected($service, 'encodeCookie', array($cookieParts)); + } + protected function getService($userProvider = null, $options = array(), $logger = null) { if (null === $userProvider) { @@ -258,4 +283,13 @@ class AbstractRememberMeServicesTest extends \PHPUnit_Framework_TestCase return $provider; } + + private function callProtected($object, $method, array $args) + { + $reflection = new \ReflectionClass(get_class($object)); + $reflectionMethod = $reflection->getMethod($method); + $reflectionMethod->setAccessible(true); + + return $reflectionMethod->invokeArgs($object, $args); + } } diff --git a/src/Symfony/Component/Security/Http/Tests/RememberMe/TokenBasedRememberMeServicesTest.php b/src/Symfony/Component/Security/Http/Tests/RememberMe/TokenBasedRememberMeServicesTest.php index 9801bc85f4..8383cec574 100644 --- a/src/Symfony/Component/Security/Http/Tests/RememberMe/TokenBasedRememberMeServicesTest.php +++ b/src/Symfony/Component/Security/Http/Tests/RememberMe/TokenBasedRememberMeServicesTest.php @@ -105,7 +105,12 @@ class TokenBasedRememberMeServicesTest extends \PHPUnit_Framework_TestCase $this->assertTrue($request->attributes->get(RememberMeServicesInterface::COOKIE_ATTR_NAME)->isCleared()); } - public function testAutoLogin() + /** + * @dataProvider provideUsernamesForAutoLogin + * + * @param string $username + */ + public function testAutoLogin($username) { $user = $this->getMock('Symfony\Component\Security\Core\User\UserInterface'); $user @@ -123,13 +128,13 @@ class TokenBasedRememberMeServicesTest extends \PHPUnit_Framework_TestCase $userProvider ->expects($this->once()) ->method('loadUserByUsername') - ->with($this->equalTo('foouser')) + ->with($this->equalTo($username)) ->will($this->returnValue($user)) ; $service = $this->getService($userProvider, array('name' => 'foo', 'always_remember_me' => true, 'lifetime' => 3600)); $request = new Request(); - $request->cookies->set('foo', $this->getCookie('fooclass', 'foouser', time() + 3600, 'foopass')); + $request->cookies->set('foo', $this->getCookie('fooclass', $username, time() + 3600, 'foopass')); $returnedToken = $service->autoLogin($request); @@ -138,6 +143,14 @@ class TokenBasedRememberMeServicesTest extends \PHPUnit_Framework_TestCase $this->assertEquals('fookey', $returnedToken->getKey()); } + public function provideUsernamesForAutoLogin() + { + return array( + array('foouser', 'Simple username'), + array('foo'.TokenBasedRememberMeServices::COOKIE_DELIMITER.'user', 'Username might contain the delimiter'), + ); + } + public function testLogout() { $service = $this->getService(null, array('name' => 'foo', 'path' => null, 'domain' => null)); diff --git a/src/Symfony/Component/Security/Resources/translations/security.fr.xlf b/src/Symfony/Component/Security/Resources/translations/security.fr.xlf index f3965d3fb3..5a77c6e9ff 100644 --- a/src/Symfony/Component/Security/Resources/translations/security.fr.xlf +++ b/src/Symfony/Component/Security/Resources/translations/security.fr.xlf @@ -8,7 +8,7 @@ Authentication credentials could not be found. - Les droits d'authentification n'ont pas pu être trouvés. + Les identifiants d'authentification n'ont pas pu être trouvés. Authentication request could not be processed due to a system problem. @@ -16,7 +16,7 @@ Invalid credentials. - Droits invalides. + Identifiants invalides. Cookie has already been used by someone else. @@ -24,7 +24,7 @@ Not privileged to request the resource. - Pas de privilèges pour accéder à la ressource. + Privilèges insuffisants pour accéder à la ressource. Invalid CSRF token. @@ -40,7 +40,7 @@ No session available, it either timed out or cookies are not enabled. - Pas de session disponible, celle-ci a expiré ou les cookies ne sont pas activés. + Aucune session disponible, celle-ci a expiré ou les cookies ne sont pas activés. No token could be found. @@ -48,7 +48,7 @@ Username could not be found. - Le nom d'utilisateur ne peut pas être trouvé. + Le nom d'utilisateur n'a pas pu être trouvé. Account has expired. @@ -56,7 +56,7 @@ Credentials have expired. - Les droits ont expirés. + Les identifiants ont expiré. Account is disabled. diff --git a/src/Symfony/Component/Serializer/README.md b/src/Symfony/Component/Serializer/README.md index 4d8ab012e9..156a288426 100644 --- a/src/Symfony/Component/Serializer/README.md +++ b/src/Symfony/Component/Serializer/README.md @@ -1,7 +1,7 @@ Serializer Component ==================== -With the Serializer component its possible to handle serializing data structures, +With the Serializer component it's possible to handle serializing data structures, including object graphs, into array structures or other formats like XML and JSON. It can also handle deserializing XML and JSON back to object graphs.