diff --git a/src/Symfony/Component/DependencyInjection/Container.php b/src/Symfony/Component/DependencyInjection/Container.php index 13341c9cec..8a2c1b3a1e 100644 --- a/src/Symfony/Component/DependencyInjection/Container.php +++ b/src/Symfony/Component/DependencyInjection/Container.php @@ -338,8 +338,10 @@ class Container implements IntrospectableContainerInterface unset($this->scopedServices[$name]); foreach ($this->scopeChildren[$name] as $child) { - $services[$child] = $this->scopedServices[$child]; - unset($this->scopedServices[$child]); + if (isset($this->scopedServices[$child])) { + $services[$child] = $this->scopedServices[$child]; + unset($this->scopedServices[$child]); + } } // update global map diff --git a/src/Symfony/Component/DependencyInjection/Dumper/GraphvizDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/GraphvizDumper.php index 00b212a1b4..4499e52310 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/GraphvizDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/GraphvizDumper.php @@ -15,6 +15,8 @@ use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Parameter; use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; /** * GraphvizDumper dumps a service container as a graphviz file. @@ -159,7 +161,7 @@ class GraphvizDumper extends Dumper { $nodes = array(); - $container = clone $this->container; + $container = $this->cloneContainer(); foreach ($container->getDefinitions() as $id => $definition) { $nodes[$id] = array('class' => str_replace('\\', '\\\\', $this->container->getParameterBag()->resolveValue($definition->getClass())), 'attributes' => array_merge($this->options['node.definition'], array('style' => ContainerInterface::SCOPE_PROTOTYPE !== $definition->getScope() ? 'filled' : 'dotted'))); @@ -175,13 +177,32 @@ class GraphvizDumper extends Dumper } if (!$container->hasDefinition($id)) { - $nodes[$id] = array('class' => str_replace('\\', '\\\\', get_class($service)), 'attributes' => $this->options['node.instance']); + $class = ('service_container' === $id) ? get_class($this->container) : get_class($service); + $nodes[$id] = array('class' => str_replace('\\', '\\\\', $class), 'attributes' => $this->options['node.instance']); } } return $nodes; } + private function cloneContainer() + { + $parameterBag = new ParameterBag($this->container->getParameterBag()->all()); + + $container = new ContainerBuilder($parameterBag); + $container->setDefinitions($this->container->getDefinitions()); + $container->setAliases($this->container->getAliases()); + $container->setResources($this->container->getResources()); + foreach ($this->container->getScopes() as $scope) { + $container->addScope($scope); + } + foreach ($this->container->getExtensions() as $extension) { + $container->registerExtension($extension); + } + + return $container; + } + /** * Returns the start dot. * diff --git a/src/Symfony/Component/DependencyInjection/Tests/ContainerTest.php b/src/Symfony/Component/DependencyInjection/Tests/ContainerTest.php index 95b3953991..23c13c5094 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/ContainerTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/ContainerTest.php @@ -261,6 +261,38 @@ class ContainerTest extends \PHPUnit_Framework_TestCase $this->assertFalse($container->has('a')); } + public function testEnterScopeRecursivelyWithInactiveChildScopes() + { + $container = new Container(); + $container->addScope(new Scope('foo')); + $container->addScope(new Scope('bar', 'foo')); + + $this->assertFalse($container->isScopeActive('foo')); + + $container->enterScope('foo'); + + $this->assertTrue($container->isScopeActive('foo')); + $this->assertFalse($container->isScopeActive('bar')); + $this->assertFalse($container->has('a')); + + $a = new \stdClass(); + $container->set('a', $a, 'foo'); + + $services = $this->getField($container, 'scopedServices'); + $this->assertTrue(isset($services['foo']['a'])); + $this->assertSame($a, $services['foo']['a']); + + $this->assertTrue($container->has('a')); + $container->enterScope('foo'); + + $services = $this->getField($container, 'scopedServices'); + $this->assertFalse(isset($services['a'])); + + $this->assertTrue($container->isScopeActive('foo')); + $this->assertFalse($container->isScopeActive('bar')); + $this->assertFalse($container->has('a')); + } + public function testLeaveScopeNotActive() { $container = new Container(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Dumper/GraphvizDumperTest.php b/src/Symfony/Component/DependencyInjection/Tests/Dumper/GraphvizDumperTest.php index 245c9775b1..0dc1ce8de1 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/GraphvizDumperTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/GraphvizDumperTest.php @@ -48,4 +48,18 @@ class GraphvizDumperTest extends \PHPUnit_Framework_TestCase 'node.missing' => array('fillcolor' => 'red', 'style' => 'empty'), )), str_replace('%path%', __DIR__, file_get_contents(self::$fixturesPath.'/graphviz/services10-1.dot')), '->dump() dumps services'); } + + public function testDumpWithFrozenContainer() + { + $container = include self::$fixturesPath.'/containers/container13.php'; + $dumper = new GraphvizDumper($container); + $this->assertEquals(str_replace('%path%', __DIR__, file_get_contents(self::$fixturesPath.'/graphviz/services13.dot')), $dumper->dump(), '->dump() dumps services'); + } + + public function testDumpWithFrozenCustomClassContainer() + { + $container = include self::$fixturesPath.'/containers/container14.php'; + $dumper = new GraphvizDumper($container); + $this->assertEquals(str_replace('%path%', __DIR__, file_get_contents(self::$fixturesPath.'/graphviz/services14.dot')), $dumper->dump(), '->dump() dumps services'); + } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container13.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container13.php new file mode 100644 index 0000000000..17b32cf512 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container13.php @@ -0,0 +1,13 @@ + + register('foo', 'FooClass')-> + addArgument(new Reference('bar')) +; +$container->compile(); + +return $container; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container14.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container14.php new file mode 100644 index 0000000000..593be9c399 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container14.php @@ -0,0 +1,11 @@ +listeners[$eventName] as $priority => $listeners) { - if (false !== ($key = array_search($listener, $listeners))) { + if (false !== ($key = array_search($listener, $listeners, true))) { unset($this->listeners[$eventName][$priority][$key], $this->sorted[$eventName]); } } diff --git a/src/Symfony/Component/EventDispatcher/Tests/EventDispatcherTest.php b/src/Symfony/Component/EventDispatcher/Tests/EventDispatcherTest.php index f38c695f8d..ad7e448454 100644 --- a/src/Symfony/Component/EventDispatcher/Tests/EventDispatcherTest.php +++ b/src/Symfony/Component/EventDispatcher/Tests/EventDispatcherTest.php @@ -244,6 +244,29 @@ class EventDispatcherTest extends \PHPUnit_Framework_TestCase $this->dispatcher->dispatch('test'); $this->assertSame($this->dispatcher, $dispatcher); } + + /** + * @see https://bugs.php.net/bug.php?id=62976 + * + * This bug affects: + * - The PHP 5.3 branch for versions < 5.3.18 + * - The PHP 5.4 branch for versions < 5.4.8 + * - The PHP 5.5 branch is not affected + */ + public function testWorkaroundForPhpBug62976() + { + $dispatcher = new EventDispatcher(); + $dispatcher->addListener('bug.62976', new CallableClass()); + $dispatcher->removeListener('bug.62976', function() {}); + $this->assertTrue($dispatcher->hasListeners('bug.62976')); + } +} + +class CallableClass +{ + public function __invoke() + { + } } class TestEventListener diff --git a/src/Symfony/Component/HttpFoundation/File/MimeType/FileBinaryMimeTypeGuesser.php b/src/Symfony/Component/HttpFoundation/File/MimeType/FileBinaryMimeTypeGuesser.php index 958e84cbae..f23ddd2f48 100644 --- a/src/Symfony/Component/HttpFoundation/File/MimeType/FileBinaryMimeTypeGuesser.php +++ b/src/Symfony/Component/HttpFoundation/File/MimeType/FileBinaryMimeTypeGuesser.php @@ -77,7 +77,7 @@ class FileBinaryMimeTypeGuesser implements MimeTypeGuesserInterface $type = trim(ob_get_clean()); - if (!preg_match('#^([a-z0-9\-]+/[a-z0-9\-]+)#i', $type, $match)) { + if (!preg_match('#^([a-z0-9\-]+/[a-z0-9\-\.]+)#i', $type, $match)) { // it's not a type, but an error message return null; } diff --git a/src/Symfony/Component/HttpFoundation/Request.php b/src/Symfony/Component/HttpFoundation/Request.php index 25c35e897d..6f34fdeef3 100644 --- a/src/Symfony/Component/HttpFoundation/Request.php +++ b/src/Symfony/Component/HttpFoundation/Request.php @@ -1468,12 +1468,16 @@ class Request if ($this->headers->has('X_ORIGINAL_URL') && false !== stripos(PHP_OS, 'WIN')) { // IIS with Microsoft Rewrite Module $requestUri = $this->headers->get('X_ORIGINAL_URL'); + $this->headers->remove('X_ORIGINAL_URL'); } elseif ($this->headers->has('X_REWRITE_URL') && false !== stripos(PHP_OS, 'WIN')) { // IIS with ISAPI_Rewrite $requestUri = $this->headers->get('X_REWRITE_URL'); + $this->headers->remove('X_REWRITE_URL'); } elseif ($this->server->get('IIS_WasUrlRewritten') == '1' && $this->server->get('UNENCODED_URL') != '') { // IIS7 with URL Rewrite: make sure we get the unencoded url (double slash problem) $requestUri = $this->server->get('UNENCODED_URL'); + $this->server->remove('UNENCODED_URL'); + $this->server->remove('IIS_WasUrlRewritten'); } elseif ($this->server->has('REQUEST_URI')) { $requestUri = $this->server->get('REQUEST_URI'); // HTTP proxy reqs setup request uri with scheme and host [and port] + the url path, only use url path @@ -1487,8 +1491,12 @@ class Request if ('' != $this->server->get('QUERY_STRING')) { $requestUri .= '?'.$this->server->get('QUERY_STRING'); } + $this->server->remove('ORIG_PATH_INFO'); } + // normalize the request URI to ease creating sub-requests from this request + $this->server->set('REQUEST_URI', $requestUri); + return $requestUri; } diff --git a/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php b/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php index 4b5b0c1574..3d21168fef 100644 --- a/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php +++ b/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php @@ -418,6 +418,18 @@ class HttpCache implements HttpKernelInterface, TerminableInterface $subRequest->headers->remove('if_modified_since'); $subRequest->headers->remove('if_none_match'); + // modify the X-Forwarded-For header if needed + $forwardedFor = $subRequest->headers->get('X-Forwarded-For'); + if ($forwardedFor) { + $subRequest->headers->set('X-Forwarded-For', $forwardedFor.', '.$subRequest->server->get('REMOTE_ADDR')); + } else { + $subRequest->headers->set('X-Forwarded-For', $subRequest->server->get('REMOTE_ADDR')); + } + + // fix the client IP address by setting it to 127.0.0.1 as HttpCache + // is always called from the same process as the backend. + $subRequest->server->set('REMOTE_ADDR', '127.0.0.1'); + $response = $this->forward($subRequest, $catch); if ($this->isPrivateRequest($request) && !$response->headers->hasCacheControlDirective('public')) { diff --git a/src/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTest.php b/src/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTest.php index 5060a696c2..89203a8ae2 100644 --- a/src/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTest.php @@ -1034,4 +1034,36 @@ class HttpCacheTest extends HttpCacheTestCase $this->assertEquals('Hello World!', $this->response->getContent()); $this->assertEquals(12, $this->response->headers->get('Content-Length')); } + + public function testClientIpIsAlwaysLocalhostForForwardedRequests() + { + $this->setNextResponse(); + $this->request('GET', '/', array('REMOTE_ADDR' => '10.0.0.1')); + + $this->assertEquals('127.0.0.1', $this->kernel->getBackendRequest()->server->get('REMOTE_ADDR')); + } + + /** + * @dataProvider getXForwardedForData + */ + public function testXForwarderForHeaderForForwardedRequests($xForwardedFor, $expected) + { + $this->setNextResponse(); + $server = array('REMOTE_ADDR' => '10.0.0.1'); + if (false !== $xForwardedFor) { + $server['HTTP_X_FORWARDED_FOR'] = $xForwardedFor; + } + $this->request('GET', '/', $server); + + $this->assertEquals($expected, $this->kernel->getBackendRequest()->headers->get('X-Forwarded-For')); + } + + public function getXForwardedForData() + { + return array( + array(false, '10.0.0.1'), + array('10.0.0.2', '10.0.0.2, 10.0.0.1'), + array('10.0.0.2, 10.0.0.3', '10.0.0.2, 10.0.0.3, 10.0.0.1'), + ); + } } diff --git a/src/Symfony/Component/HttpKernel/Tests/HttpCache/TestHttpKernel.php b/src/Symfony/Component/HttpKernel/Tests/HttpCache/TestHttpKernel.php index c732c673f3..cf23d7bf8a 100644 --- a/src/Symfony/Component/HttpKernel/Tests/HttpCache/TestHttpKernel.php +++ b/src/Symfony/Component/HttpKernel/Tests/HttpCache/TestHttpKernel.php @@ -26,6 +26,7 @@ class TestHttpKernel extends HttpKernel implements ControllerResolverInterface protected $called; protected $customizer; protected $catch; + protected $backendRequest; public function __construct($body, $status, $headers, \Closure $customizer = null) { @@ -39,9 +40,15 @@ class TestHttpKernel extends HttpKernel implements ControllerResolverInterface parent::__construct(new EventDispatcher(), $this); } + public function getBackendRequest() + { + return $this->backendRequest; + } + public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = false) { $this->catch = $catch; + $this->backendRequest = $request; return parent::handle($request, $type, $catch); } diff --git a/src/Symfony/Component/HttpKernel/Tests/HttpCache/TestMultipleHttpKernel.php b/src/Symfony/Component/HttpKernel/Tests/HttpCache/TestMultipleHttpKernel.php index eeb9bea0dc..6dd3d9e499 100644 --- a/src/Symfony/Component/HttpKernel/Tests/HttpCache/TestMultipleHttpKernel.php +++ b/src/Symfony/Component/HttpKernel/Tests/HttpCache/TestMultipleHttpKernel.php @@ -25,6 +25,7 @@ class TestMultipleHttpKernel extends HttpKernel implements ControllerResolverInt protected $headers; protected $catch; protected $call; + protected $backendRequest; public function __construct($responses) { @@ -42,8 +43,15 @@ class TestMultipleHttpKernel extends HttpKernel implements ControllerResolverInt parent::__construct(new EventDispatcher(), $this); } + public function getBackendRequest() + { + return $this->backendRequest; + } + public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = false) { + $this->backendRequest = $request; + return parent::handle($request, $type, $catch); } diff --git a/src/Symfony/Component/Security/Http/Firewall/ContextListener.php b/src/Symfony/Component/Security/Http/Firewall/ContextListener.php index 6c06ca80f6..8c718768fe 100644 --- a/src/Symfony/Component/Security/Http/Firewall/ContextListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/ContextListener.php @@ -117,14 +117,16 @@ class ContextListener implements ListenerInterface } $request = $event->getRequest(); - $session = $request->hasPreviousSession() ? $request->getSession() : null; + $session = $request->getSession(); if (null === $session) { return; } if ((null === $token = $this->context->getToken()) || ($token instanceof AnonymousToken)) { - $session->remove('_security_'.$this->contextKey); + if ($request->hasPreviousSession()) { + $session->remove('_security_'.$this->contextKey); + } } else { $session->set('_security_'.$this->contextKey, serialize($token)); } diff --git a/src/Symfony/Component/Security/Tests/Http/Firewall/ContextListenerTest.php b/src/Symfony/Component/Security/Tests/Http/Firewall/ContextListenerTest.php index 2a8a28e942..ffe6195ff4 100644 --- a/src/Symfony/Component/Security/Tests/Http/Firewall/ContextListenerTest.php +++ b/src/Symfony/Component/Security/Tests/Http/Firewall/ContextListenerTest.php @@ -99,6 +99,25 @@ class ContextListenerTest extends \PHPUnit_Framework_TestCase $listener = new ContextListener($this->securityContext, array(), 'session'); $listener->onKernelResponse($event); + $this->assertTrue($session->isStarted()); + } + + public function testOnKernelResponseWithoutSessionNorToken() + { + $request = new Request(); + $session = new Session(new MockArraySessionStorage()); + $request->setSession($session); + + $event = new FilterResponseEvent( + $this->getMock('Symfony\Component\HttpKernel\HttpKernelInterface'), + $request, + HttpKernelInterface::MASTER_REQUEST, + new Response() + ); + + $listener = new ContextListener($this->securityContext, array(), 'session'); + $listener->onKernelResponse($event); + $this->assertFalse($session->isStarted()); }