Merge branch '2.1' into 2.2

* 2.1:
  added support for the X-Forwarded-For header (closes #6982, closes #7000)
  fixed the IP address in HttpCache when calling the backend
  [EventDispatcher] Added assertion.
  [EventDispathcer] Fix removeListener
  [DependencyInjection] Add clone for resources which were introduced in 2.1
  [DependencyInjection] Allow frozen containers to be dumped to graphviz
  Fix 'undefined index' error, when entering scope recursively
  [Security] fixed session creation on login (closes #7011)
  Add dot character `.` to legal mime subtype regular expression
  [HttpFoundation] fixed the creation of sub-requests under some circumstancies (closes #6923, closes #6936)
This commit is contained in:
Fabien Potencier 2013-02-11 12:26:43 +01:00
commit 743612bbbd
18 changed files with 227 additions and 8 deletions

View File

@ -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

View File

@ -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.
*

View File

@ -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();

View File

@ -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');
}
}

View File

@ -0,0 +1,13 @@
<?php
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
$container = new ContainerBuilder();
$container->
register('foo', 'FooClass')->
addArgument(new Reference('bar'))
;
$container->compile();
return $container;

View File

@ -0,0 +1,11 @@
<?php
namespace Container14;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class ProjectServiceContainer extends ContainerBuilder
{
}
return new ProjectServiceContainer();

View File

@ -0,0 +1,8 @@
digraph sc {
ratio="compress"
node [fontsize="11" fontname="Arial" shape="record"];
edge [fontsize="9" fontname="Arial" color="grey" arrowhead="open" arrowsize="0.5"];
node_foo [label="foo\nFooClass\n", shape=record, fillcolor="#eeeeee", style="filled"];
node_service_container [label="service_container\nSymfony\\Component\\DependencyInjection\\ContainerBuilder\n", shape=record, fillcolor="#9999ff", style="filled"];
}

View File

@ -0,0 +1,7 @@
digraph sc {
ratio="compress"
node [fontsize="11" fontname="Arial" shape="record"];
edge [fontsize="9" fontname="Arial" color="grey" arrowhead="open" arrowsize="0.5"];
node_service_container [label="service_container\nContainer14\\ProjectServiceContainer\n", shape=record, fillcolor="#9999ff", style="filled"];
}

View File

@ -106,7 +106,7 @@ class EventDispatcher implements EventDispatcherInterface
}
foreach ($this->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]);
}
}

View File

@ -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

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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')) {

View File

@ -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'),
);
}
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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));
}

View File

@ -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());
}