Commit d617be8c authored by alexpott's avatar alexpott

Issue #2331079 by znerol, dawehner: Use RouteMatch in access-checks and remove...

Issue #2331079 by znerol, dawehner: Use RouteMatch in access-checks and remove RequestHelper::duplicate().
parent 3295e62a
......@@ -673,11 +673,11 @@ services:
csrf_token:
class: Drupal\Core\Access\CsrfTokenGenerator
arguments: ['@private_key', '@session_manager.metadata_bag']
access_arguments_resolver:
class: Drupal\Core\Access\AccessArgumentsResolver
access_arguments_resolver_factory:
class: Drupal\Core\Access\AccessArgumentsResolverFactory
access_manager:
class: Drupal\Core\Access\AccessManager
arguments: ['@router.route_provider', '@url_generator', '@paramconverter_manager', '@access_arguments_resolver', '@request_stack', '@current_user']
arguments: ['@router.route_provider', '@paramconverter_manager', '@access_arguments_resolver_factory', '@current_user']
calls:
- [setContainer, ['@service_container']]
access_route_subscriber:
......@@ -704,13 +704,13 @@ services:
- { name: access_check, applies_to: _access_theme }
access_check.custom:
class: Drupal\Core\Access\CustomAccessCheck
arguments: ['@controller_resolver', '@access_arguments_resolver']
arguments: ['@controller_resolver', '@access_arguments_resolver_factory']
tags:
- { name: access_check, applies_to: _custom_access }
access_check.csrf:
class: Drupal\Core\Access\CsrfAccessCheck
tags:
- { name: access_check, applies_to: _csrf_token }
- { name: access_check, applies_to: _csrf_token, needs_incoming_request: TRUE }
arguments: ['@csrf_token']
maintenance_mode:
class: Drupal\Core\Site\MaintenanceMode
......
......@@ -2,27 +2,61 @@
/**
* @file
* Contains \Drupal\Core\Access\AccessArgumentsResolver.
* Contains \Drupal\Component\Utility\ArgumentsResolver.
*/
namespace Drupal\Core\Access;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\Routing\Route;
use Symfony\Component\HttpFoundation\Request;
namespace Drupal\Component\Utility;
/**
* Resolves the arguments to pass to an access check callable.
* Resolves the arguments to pass to a callable.
*/
class AccessArgumentsResolver implements AccessArgumentsResolverInterface {
class ArgumentsResolver implements ArgumentsResolverInterface {
/**
* An associative array of parameter names to scalar candidate values.
*
* @var array
*/
protected $scalars;
/**
* An associative array of parameter names to object candidate values.
*
* @var array
*/
protected $objects;
/**
* An array object candidates tried on every parameter regardless of name.
*
* @var array
*/
protected $wildcards;
/**
* Constructs a new ArgumentsResolver.
*
* @param array $scalars
* An associative array of parameter names to scalar candidate values.
* @param object[] $objects
* An associative array of parameter names to object candidate values.
* @param object[] $wildcards
* An array object candidates tried on every parameter regardless of its
* name.
*/
public function __construct(array $scalars, array $objects, array $wildcards) {
$this->scalars = $scalars;
$this->objects = $objects;
$this->wildcards = $wildcards;
}
/**
* {@inheritdoc}
*/
public function getArguments(callable $callable, Route $route, Request $request, AccountInterface $account) {
public function getArguments(callable $callable) {
$arguments = array();
foreach ($this->getReflector($callable)->getParameters() as $parameter) {
$arguments[] = $this->getArgument($parameter, $route, $request, $account);
$arguments[] = $this->getArgument($parameter);
}
return $arguments;
}
......@@ -32,12 +66,6 @@ public function getArguments(callable $callable, Route $route, Request $request,
*
* @param \ReflectionParameter $parameter
* The parameter of a callable to get the value for.
* @param \Symfony\Component\Routing\Route $route
* The access checked route.
* @param \Symfony\Component\HttpFoundation\Request $request
* The current request.
* @param \Drupal\Core\Session\AccountInterface $account
* The current user.
*
* @return mixed
* The value of the requested parameter value.
......@@ -45,39 +73,30 @@ public function getArguments(callable $callable, Route $route, Request $request,
* @throws \RuntimeException
* Thrown when there is a missing parameter.
*/
protected function getArgument(\ReflectionParameter $parameter, Route $route, Request $request, AccountInterface $account) {
$upcasted_route_arguments = $request->attributes->all();
$raw_route_arguments = isset($upcasted_route_arguments['_raw_variables']) ? $upcasted_route_arguments['_raw_variables']->all() : $upcasted_route_arguments;
protected function getArgument(\ReflectionParameter $parameter) {
$parameter_type_hint = $parameter->getClass();
$parameter_name = $parameter->getName();
// @todo Remove this once AccessManagerInterface::checkNamedRoute() is fixed
// to not leak _raw_variables from the request being duplicated.
// @see https://drupal.org/node/2265939
$raw_route_arguments += $upcasted_route_arguments;
// If the route argument exists and is NULL, return it, regardless of
// If the argument exists and is NULL, return it, regardless of
// parameter type hint.
if (!isset($upcasted_route_arguments[$parameter_name]) && array_key_exists($parameter_name, $upcasted_route_arguments)) {
if (!isset($this->objects[$parameter_name]) && array_key_exists($parameter_name, $this->objects)) {
return NULL;
}
if ($parameter_type_hint) {
// If the argument exists and complies with the type hint, return it.
if (isset($upcasted_route_arguments[$parameter_name]) && is_object($upcasted_route_arguments[$parameter_name]) && $parameter_type_hint->isInstance($upcasted_route_arguments[$parameter_name])) {
return $upcasted_route_arguments[$parameter_name];
if (isset($this->objects[$parameter_name]) && is_object($this->objects[$parameter_name]) && $parameter_type_hint->isInstance($this->objects[$parameter_name])) {
return $this->objects[$parameter_name];
}
// Otherwise, resolve $request, $route, and $account by type matching
// only. This way, the callable may rename them in case the route
// defines other parameters with these names.
foreach (array($request, $route, $account) as $special_argument) {
if ($parameter_type_hint->isInstance($special_argument)) {
return $special_argument;
// Otherwise, resolve wildcard arguments by type matching.
foreach ($this->wildcards as $wildcard) {
if ($parameter_type_hint->isInstance($wildcard)) {
return $wildcard;
}
}
}
elseif (isset($raw_route_arguments[$parameter_name])) {
return $raw_route_arguments[$parameter_name];
elseif (isset($this->scalars[$parameter_name])) {
return $this->scalars[$parameter_name];
}
// If the callable provides a default value, use it.
......@@ -125,7 +144,7 @@ protected function handleUnresolvedArgument(\ReflectionParameter $parameter) {
else {
$function_name = $function->getName();
}
throw new \RuntimeException(sprintf('Access callable "%s" requires a value for the "$%s" argument.', $function_name, $parameter->getName()));
throw new \RuntimeException(sprintf('Callable "%s" requires a value for the "$%s" argument.', $function_name, $parameter->getName()));
}
}
<?php
/**
* @file
* Contains \Drupal\Component\Utility\ArgumentsResolverInterface.
*/
namespace Drupal\Component\Utility;
/**
* Resolves the arguments to pass to a callable.
*/
interface ArgumentsResolverInterface {
/**
* Returns arguments suitable for passing to the given callable.
*
* @return array
* An array of arguments to pass to the callable.
*
* @throws \RuntimeException
* When a value for an argument given cannot be resolved.
*/
public function getArguments(callable $callable);
}
<?php
/**
* @file
* Contains \Drupal\Core\Access\AccessArgumentsResolverFactory.
*/
namespace Drupal\Core\Access;
use Drupal\Component\Utility\ArgumentsResolver;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* Resolves the arguments to pass to an access check callable.
*/
class AccessArgumentsResolverFactory implements AccessArgumentsResolverFactoryInterface {
/**
* {@inheritdoc}
*/
public function getArgumentsResolver(RouteMatchInterface $route_match, AccountInterface $account, Request $request = NULL) {
$route = $route_match->getRouteObject();
// Defaults for the parameters defined on the route object need to be added
// to the raw arguments.
$raw_route_arguments = $route_match->getRawParameters()->all() + $route->getDefaults();
$upcasted_route_arguments = $route_match->getParameters()->all();
// Parameters which are not defined on the route object, but still are
// essential for access checking are passed as wildcards to the argument
// resolver. An access-check method with a parameter of type Route,
// RouteMatchInterface, AccountInterface or Request will receive those
// arguments regardless of the parameter name.
$wildcard_arguments = [$route, $route_match, $account];
if (isset($request)) {
$wildcard_arguments[] = $request;
}
return new ArgumentsResolver($raw_route_arguments, $upcasted_route_arguments, $wildcard_arguments);
}
}
<?php
/**
* @file
* Contains \Drupal\Core\Access\AccessArgumentsResolverFactoryInterface.
*/
namespace Drupal\Core\Access;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* Constructs the arguments resolver instance to use when running access checks.
*/
interface AccessArgumentsResolverFactoryInterface {
/**
* Returns the arguments resolver to use when running access checks.
*
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* The route match object to be checked.
* @param \Drupal\Core\Session\AccountInterface $account
* The account being checked.
* @param \Symfony\Component\HttpFoundation\Request $request
* Optional, the request object.
*
* @return \Drupal\Component\Utility\ArgumentsResolverInterface
* The parametrized arguments resolver instance.
*/
public function getArgumentsResolver(RouteMatchInterface $route_match, AccountInterface $account, Request $request = NULL);
}
<?php
/**
* @file
* Contains \Drupal\Core\Access\AccessArgumentsResolverInterface.
*/
namespace Drupal\Core\Access;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\Routing\Route;
use Symfony\Component\HttpFoundation\Request;
/**
* Resolves the arguments to pass to an access check callable.
*/
interface AccessArgumentsResolverInterface {
/**
* Returns the arguments to pass to the access check callable.
*
* @param callable $callable
* A PHP callable.
* @param \Symfony\Component\Routing\Route $route
* The route to check access to.
* @param \Symfony\Component\HttpFoundation\Request $request
* The request object.
* @param \Drupal\Core\Session\AccountInterface $account
* The currently logged in account.
*
* @return array
* An array of arguments to pass to the callable.
*
* @throws \RuntimeException
* When a value for an argument given is not provided.
*/
public function getArguments(callable $callable, Route $route, Request $request, AccountInterface $account);
}
......@@ -7,10 +7,10 @@
namespace Drupal\Core\Access;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\RouteCollection;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Routing\RouteMatchInterface;
/**
* Provides an interface for attaching and running access check services.
......@@ -44,9 +44,6 @@ interface AccessManagerInterface {
* @param \Drupal\Core\Session\AccountInterface $account
* (optional) Run access checks for this account. Defaults to the current
* user.
* @param \Symfony\Component\HttpFoundation\Request $route_request
* Optional incoming request object. If not provided, one will be built
* using the route information and the current request from the container.
* @param bool $return_as_object
* (optional) Defaults to FALSE.
*
......@@ -57,7 +54,27 @@ interface AccessManagerInterface {
* returned, i.e. TRUE means access is explicitly allowed, FALSE means
* access is either explicitly forbidden or "no opinion".
*/
public function checkNamedRoute($route_name, array $parameters = array(), AccountInterface $account = NULL, Request $route_request = NULL, $return_as_object = FALSE);
public function checkNamedRoute($route_name, array $parameters = array(), AccountInterface $account = NULL, $return_as_object = FALSE);
/**
* Execute access checks against the incoming request.
*
* @param Request $request
* The incoming request.
* @param \Drupal\Core\Session\AccountInterface $account
* (optional) Run access checks for this account. Defaults to the current
* user.
* @param bool $return_as_object
* (optional) Defaults to FALSE.
*
* @return bool|\Drupal\Core\Access\AccessResultInterface
* The access result. Returns a boolean if $return_as_object is FALSE (this
* is the default) and otherwise an AccessResultInterface object.
* When a boolean is returned, the result of AccessInterface::isAllowed() is
* returned, i.e. TRUE means access is explicitly allowed, FALSE means
* access is either explicitly forbidden or "no opinion".
*/
public function checkRequest(Request $request, AccountInterface $account = NULL, $return_as_object = FALSE);
/**
* For each route, saves a list of applicable access checks to the route.
......@@ -77,21 +94,24 @@ public function setChecks(RouteCollection $routes);
* @param array $applies_checks
* (optional) An array of route requirement keys the checker service applies
* to.
* @param bool $needs_incoming_request
* (optional) True if access-check method only acts on an incoming request.
*/
public function addCheckService($service_id, $service_method, array $applies_checks = array());
public function addCheckService($service_id, $service_method, array $applies_checks = array(), $needs_incoming_request = FALSE);
/**
* Checks a route against applicable access check services.
*
* Determines whether the route is accessible or not.
*
* @param \Symfony\Component\Routing\Route $route
* The route to check access to.
* @param \Symfony\Component\HttpFoundation\Request $request
* The incoming request object.
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* The route match.
* @param \Drupal\Core\Session\AccountInterface $account
* (optional) Run access checks for this account. Defaults to the current
* user.
* @param \Symfony\Component\HttpFoundation\Request $request
* Optional, a request. Only supply this parameter when checking the
* incoming request, do not specify when checking routes on output.
* @param bool $return_as_object
* (optional) Defaults to FALSE.
*
......@@ -102,6 +122,6 @@ public function addCheckService($service_id, $service_method, array $applies_che
* returned, i.e. TRUE means access is explicitly allowed, FALSE means
* access is either explicitly forbidden or "no opinion".
*/
public function check(Route $route, Request $request, AccountInterface $account = NULL, $return_as_object = FALSE);
public function check(RouteMatchInterface $route_match, AccountInterface $account = NULL, Request $request = NULL, $return_as_object = FALSE);
}
......@@ -33,7 +33,7 @@ class CsrfAccessCheck implements RoutingAccessInterface {
* @param \Drupal\Core\Access\CsrfTokenGenerator $csrf_token
* The CSRF token generator.
*/
function __construct(CsrfTokenGenerator $csrf_token) {
public function __construct(CsrfTokenGenerator $csrf_token) {
$this->csrfToken = $csrf_token;
}
......@@ -51,27 +51,15 @@ function __construct(CsrfTokenGenerator $csrf_token) {
public function access(Route $route, Request $request) {
// Not cacheable because the CSRF token is highly dynamic.
$access = AccessResult::create()->setCacheable(FALSE);
// If this is the controller request, check CSRF access as normal.
if ($request->attributes->get('_controller_request')) {
// @todo Remove dependency on the internal _system_path attribute:
// https://www.drupal.org/node/2293501.
if ($this->csrfToken->validate($request->query->get('token'), $request->attributes->get('_system_path'))) {
$access->allow();
}
else {
$access->forbid();
}
return $access;
// @todo Remove dependency on the internal _system_path attribute:
// https://www.drupal.org/node/2293501.
if ($this->csrfToken->validate($request->query->get('token'), $request->attributes->get('_system_path'))) {
$access->allow();
}
// Otherwise, this could be another requested access check that we don't
// want to check CSRF tokens on.
$conjunction = $route->getOption('_access_mode') ?: AccessManagerInterface::ACCESS_MODE_ANY;
// Allow if all access checks are needed. This sets DENY if not all access
// checks are needed, because another access checker should explicitly grant
// access for the route.
return $access->allowIf($conjunction == AccessManagerInterface::ACCESS_MODE_ALL);
else {
$access->forbid();
}
return $access;
}
}
......@@ -9,8 +9,8 @@
use Drupal\Core\Controller\ControllerResolverInterface;
use Drupal\Core\Routing\Access\AccessInterface as RoutingAccessInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Route;
/**
......@@ -35,39 +35,39 @@ class CustomAccessCheck implements RoutingAccessInterface {
/**
* The arguments resolver.
*
* @var \Drupal\Core\Access\AccessArgumentsResolverInterface
* @var \Drupal\Core\Access\AccessArgumentsResolverFactoryInterface
*/
protected $argumentsResolver;
protected $argumentsResolverFactory;
/**
* Constructs a CustomAccessCheck instance.
*
* @param \Drupal\Core\Controller\ControllerResolverInterface $controller_resolver
* The controller resolver.
* @param \Drupal\Core\Access\AccessArgumentsResolverInterface $arguments_resolver
* The arguments resolver.
* @param \Drupal\Core\Access\AccessArgumentsResolverFactoryInterface $arguments_resolver_factory
* The arguments resolver factory.
*/
public function __construct(ControllerResolverInterface $controller_resolver, AccessArgumentsResolverInterface $arguments_resolver) {
public function __construct(ControllerResolverInterface $controller_resolver, AccessArgumentsResolverFactoryInterface $arguments_resolver_factory) {
$this->controllerResolver = $controller_resolver;
$this->argumentsResolver = $arguments_resolver;
$this->argumentsResolverFactory = $arguments_resolver_factory;
}
/**
* Checks access for the account and route using the custom access checker.
*
* @param \Symfony\Component\Routing\Route $route
* The route to check against.
* @param \Symfony\Component\HttpFoundation\Request $request
* The request object.
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* The route match object to be checked.
* @param \Drupal\Core\Session\AccountInterface $account
* The currently logged in account.
* The account being checked.
*
* @return \Drupal\Core\Access\AccessResultInterface
* The access result.
*/
public function access(Route $route, Request $request, AccountInterface $account) {
public function access(Route $route, RouteMatchInterface $route_match, AccountInterface $account) {
$callable = $this->controllerResolver->getControllerFromDefinition($route->getRequirement('_custom_access'));
$arguments = $this->argumentsResolver->getArguments($callable, $route, $request, $account);
$arguments_resolver = $this->argumentsResolverFactory->getArgumentsResolver($route_match, $account);
$arguments = $arguments_resolver->getArguments($callable);
return call_user_func_array($callable, $arguments);
}
......
......@@ -28,6 +28,7 @@ public function process(ContainerBuilder $container) {
foreach ($container->findTaggedServiceIds('access_check') as $id => $attributes) {
$applies = array();
$method = 'access';
$needs_incoming_request = FALSE;
foreach ($attributes as $attribute) {
if (isset($attribute['applies_to'])) {
$applies[] = $attribute['applies_to'];
......@@ -35,8 +36,11 @@ public function process(ContainerBuilder $container) {
if (isset($attribute['method'])) {
$method = $attribute['method'];
}
if (!empty($attribute['needs_incoming_request'])) {
$needs_incoming_request = TRUE;
}
}
$access_manager->addMethodCall('addCheckService', array($id, $method, $applies));
$access_manager->addMethodCall('addCheckService', array($id, $method, $applies, $needs_incoming_request));
}
}
}
......@@ -9,9 +9,9 @@
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Routing\Access\AccessInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\Routing\Route;
use Symfony\Component\HttpFoundation\Request;
/**
* Provides a generic access checker for entities.
......@@ -33,21 +33,22 @@ class EntityAccessCheck implements AccessInterface {
*
* @param \Symfony\Component\Routing\Route $route
* The route to check against.
* @param \Symfony\Component\HttpFoundation\Request $request
* The request object.
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* The parametrized route
* @param \Drupal\Core\Session\AccountInterface $account
* The currently logged in account.
*
* @return \Drupal\Core\Access\AccessResultInterface
* The access result.
*/
public function access(Route $route, Request $request, AccountInterface $account) {
public function access(Route $route, RouteMatchInterface $route_match, AccountInterface $account) {
// Split the entity type and the operation.
$requirement = $route->getRequirement('_entity_access');
list($entity_type, $operation) = explode('.', $requirement);
// If there is valid entity of the given entity type, check its access.
if ($request->attributes->has($entity_type)) {
$entity = $request->attributes->get($entity_type);
$parameters = $route_match->getParameters();
if ($parameters->has($entity_type)) {
$entity = $parameters->get($entity_type);
if ($entity instanceof EntityInterface) {
return $entity->access($operation, $account, TRUE);
}
......
......@@ -9,8 +9,8 @@
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Routing\Access\AccessInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Route;
/**
......@@ -47,22 +47,22 @@ public function __construct(EntityManagerInterface $entity_manager) {
*
* @param \Symfony\Component\Routing\Route $route
* The route to check against.
* @param \Symfony\Component\HttpFoundation\Request $request
* The request object.
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* The parametrized route.
* @param \Drupal\Core\Session\AccountInterface $account
* The currently logged in account.
*
* @return \Drupal\Core\Access\AccessResultInterface
* The access result.
*/
public function access(Route $route, Request $request, AccountInterface $account) {
public function access(Route $route, RouteMatchInterface $route_match, AccountInterface $account) {
list($entity_type, $bundle) = explode(':', $route->getRequirement($this->requirementsKey) . ':');
// The bundle argument can contain request argument placeholders like
// {name}, loop over the raw variables and attempt to replace them in the
// bundle name. If a placeholder does not exist, it won't get replaced.
if ($bundle && strpos($bundle, '{') !== FALSE) {
foreach ($request->get('_raw_variables')->all() as $name => $value) {
foreach ($route_match->getRawParameters()->all() as $name => $value) {
$bundle = str_replace('{' . $name . '}', $value, $bundle);
}
// If we were unable to replace all placeholders, deny access.
......
......@@ -10,7 +10,6 @@
use Drupal\Core\Access\AccessManagerInterface;
use Drupal\Core\Session\AccountInterface;
use Symfony\Cmf\Component\Routing\ChainRouter;
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\Routing\RequestContext;
......@@ -102,21 +101,8 @@ public function matchRequest(Request $request) {
* The request to access check.
*/
protected function checkAccess(Request $request) {
// The controller is being handled by the HTTP kernel, so add an attribute
// to tell us this is the controller request.
$request->attributes->set('_controller_request', TRUE);
$e = FALSE;
try {
if (!$this->accessManager->check($request->attributes->get(RouteObjectInterface::ROUTE_OBJECT), $request, $this->account)) {
$e = new AccessDeniedHttpException();
}
}
catch (\Exception $e) {
}
// @todo Once PHP 5.5 is a requirement refactor this using finally.
$request->attributes->remove('_controller_request');
if ($e) {
throw $e;
if (!$this->accessManager->checkRequest($request, $this->account)) {
throw new AccessDeniedHttpException();
}
}
......
......@@ -14,120 +14,6 @@
*/
class RequestHelper {
/**
* Duplicates a request for another path.
*
* This method does basically the same as Request::create() but keeping all
* the previous variables to speed it up.
*
* @param \Symfony\Component\HttpFoundation\Request $original_request
* The original request object to clone.
* @param string $uri
* The URI.
* @param string $method
* The HTTP method.
* @param array $parameters
* The query (GET) or request (POST) parameters.
* @param array $query
* The GET parameters.
* @param array $post
* The POST parameters.
* @param array $attributes
* The request attributes (parameters parsed from the PATH_INFO, ...).
* @param array $cookies
* The COOKIE parameters.
* @param array $files
* The FILES parameters.
* @param array $server
* The SERVER parameters.
*
* @return \Symfony\Component\HttpFoundation\Request
* The cloned request instance.
*
* @see \Symfony\Component\HttpFoundation\Request::create()
* @see \Symfony\Component\HttpFoundation\Request::duplicate()
*/
public static function duplicate(Request $original_request, $uri, $method = 'GET', $parameters = array(), array $query = NULL, array $post = NULL, array $attributes = NULL, array $cookies = NULL, array $files = NULL, array $server = NULL) {
$request = $original_request->duplicate($query, $post, $attributes, $cookies, $files, $server);
$server = array();
$server['PATH_INFO'] = '';
$server['REQUEST_METHOD'] = strtoupper($method);
$components = parse_url($uri);
if (isset($components['host'])) {
$server['SERVER_NAME'] = $components['host'];
$server['HTTP_HOST'] = $components['host'];