mirror of
https://github.com/nextcloud/server.git
synced 2026-04-21 14:23:17 -04:00
feat(DI): Abort querying if infinite loop is detected
Signed-off-by: provokateurin <kate@provokateurin.de>
This commit is contained in:
parent
6911a33d50
commit
3dbf848ee9
3 changed files with 53 additions and 27 deletions
|
|
@ -310,21 +310,29 @@ class DIContainer extends SimpleContainer implements IAppContainer {
|
|||
return false;
|
||||
}
|
||||
|
||||
public function query(string $name, bool $autoload = true) {
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @param list<class-string> $chain
|
||||
*/
|
||||
public function query(string $name, bool $autoload = true, array $chain = []) {
|
||||
if ($name === 'AppName' || $name === 'appName') {
|
||||
return $this->appName;
|
||||
}
|
||||
|
||||
$isServerClass = str_starts_with($name, 'OCP\\') || str_starts_with($name, 'OC\\');
|
||||
if ($isServerClass && !$this->has($name)) {
|
||||
return $this->getServer()->query($name, $autoload);
|
||||
/** @var ServerContainer $server */
|
||||
$server = $this->getServer();
|
||||
return $server->query($name, $autoload, $chain);
|
||||
}
|
||||
|
||||
try {
|
||||
return $this->queryNoFallback($name);
|
||||
return $this->queryNoFallback($name, $chain);
|
||||
} catch (QueryException $firstException) {
|
||||
try {
|
||||
return $this->getServer()->query($name, $autoload);
|
||||
/** @var ServerContainer $server */
|
||||
$server = $this->getServer();
|
||||
return $server->query($name, $autoload, $chain);
|
||||
} catch (QueryException $secondException) {
|
||||
if ($firstException->getCode() === 1) {
|
||||
throw $secondException;
|
||||
|
|
@ -339,23 +347,23 @@ class DIContainer extends SimpleContainer implements IAppContainer {
|
|||
* @return mixed
|
||||
* @throws QueryException if the query could not be resolved
|
||||
*/
|
||||
public function queryNoFallback($name) {
|
||||
public function queryNoFallback($name, array $chain) {
|
||||
$name = $this->sanitizeName($name);
|
||||
|
||||
if ($this->offsetExists($name)) {
|
||||
return parent::query($name);
|
||||
return parent::query($name, chain: $chain);
|
||||
} elseif ($this->appName === 'settings' && str_starts_with($name, 'OC\\Settings\\')) {
|
||||
return parent::query($name);
|
||||
return parent::query($name, chain: $chain);
|
||||
} elseif ($this->appName === 'core' && str_starts_with($name, 'OC\\Core\\')) {
|
||||
return parent::query($name);
|
||||
return parent::query($name, chain: $chain);
|
||||
} elseif (str_starts_with($name, \OC\AppFramework\App::buildAppNamespace($this->appName) . '\\')) {
|
||||
return parent::query($name);
|
||||
return parent::query($name, chain: $chain);
|
||||
} elseif (
|
||||
str_starts_with($name, 'OC\\AppFramework\\Services\\')
|
||||
|| str_starts_with($name, 'OC\\AppFramework\\Middleware\\')
|
||||
) {
|
||||
/* AppFramework services are scoped to the application */
|
||||
return parent::query($name);
|
||||
return parent::query($name, chain: $chain);
|
||||
}
|
||||
|
||||
throw new QueryException('Could not resolve ' . $name . '!'
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ use ReflectionClass;
|
|||
use ReflectionException;
|
||||
use ReflectionNamedType;
|
||||
use ReflectionParameter;
|
||||
use RuntimeException;
|
||||
use function class_exists;
|
||||
|
||||
/**
|
||||
|
|
@ -52,10 +53,11 @@ class SimpleContainer implements ArrayAccess, ContainerInterface, IContainer {
|
|||
|
||||
/**
|
||||
* @param ReflectionClass $class the class to instantiate
|
||||
* @param list<class-string> $chain
|
||||
* @return object the created class
|
||||
* @suppress PhanUndeclaredClassInstanceof
|
||||
*/
|
||||
private function buildClass(ReflectionClass $class): object {
|
||||
private function buildClass(ReflectionClass $class, array $chain): object {
|
||||
$constructor = $class->getConstructor();
|
||||
if ($constructor === null) {
|
||||
/* No constructor, return a instance directly */
|
||||
|
|
@ -64,17 +66,20 @@ class SimpleContainer implements ArrayAccess, ContainerInterface, IContainer {
|
|||
if (PHP_VERSION_ID >= 80400 && self::$useLazyObjects && !$class->isInternal()) {
|
||||
/* For PHP>=8.4, use a lazy ghost to delay constructor and dependency resolving */
|
||||
/** @psalm-suppress UndefinedMethod */
|
||||
return $class->newLazyGhost(function (object $object) use ($constructor): void {
|
||||
return $class->newLazyGhost(function (object $object) use ($constructor, $chain): void {
|
||||
/** @psalm-suppress DirectConstructorCall For lazy ghosts we have to call the constructor directly */
|
||||
$object->__construct(...$this->buildClassConstructorParameters($constructor));
|
||||
$object->__construct(...$this->buildClassConstructorParameters($constructor, $chain));
|
||||
});
|
||||
} else {
|
||||
return $class->newInstanceArgs($this->buildClassConstructorParameters($constructor));
|
||||
return $class->newInstanceArgs($this->buildClassConstructorParameters($constructor, $chain));
|
||||
}
|
||||
}
|
||||
|
||||
private function buildClassConstructorParameters(\ReflectionMethod $constructor): array {
|
||||
return array_map(function (ReflectionParameter $parameter) {
|
||||
/**
|
||||
* @param list<class-string> $chain
|
||||
*/
|
||||
private function buildClassConstructorParameters(\ReflectionMethod $constructor, array $chain): array {
|
||||
return array_map(function (ReflectionParameter $parameter) use ($chain) {
|
||||
$parameterType = $parameter->getType();
|
||||
|
||||
$resolveName = $parameter->getName();
|
||||
|
|
@ -87,7 +92,7 @@ class SimpleContainer implements ArrayAccess, ContainerInterface, IContainer {
|
|||
try {
|
||||
$builtIn = $parameterType !== null && ($parameterType instanceof ReflectionNamedType)
|
||||
&& $parameterType->isBuiltin();
|
||||
return $this->query($resolveName, !$builtIn);
|
||||
return $this->query($resolveName, !$builtIn, $chain);
|
||||
} catch (ContainerExceptionInterface $e) {
|
||||
// Service not found, use the default value when available
|
||||
if ($parameter->isDefaultValueAvailable()) {
|
||||
|
|
@ -97,7 +102,7 @@ class SimpleContainer implements ArrayAccess, ContainerInterface, IContainer {
|
|||
if ($parameterType !== null && ($parameterType instanceof ReflectionNamedType) && !$parameterType->isBuiltin()) {
|
||||
$resolveName = $parameter->getName();
|
||||
try {
|
||||
return $this->query($resolveName);
|
||||
return $this->query($resolveName, chain: $chain);
|
||||
} catch (ContainerExceptionInterface $e2) {
|
||||
// Pass null if typed and nullable
|
||||
if ($parameter->allowsNull() && ($parameterType instanceof ReflectionNamedType)) {
|
||||
|
|
@ -114,12 +119,16 @@ class SimpleContainer implements ArrayAccess, ContainerInterface, IContainer {
|
|||
}, $constructor->getParameters());
|
||||
}
|
||||
|
||||
public function resolve($name) {
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @param list<class-string> $chain
|
||||
*/
|
||||
public function resolve($name, array $chain = []) {
|
||||
$baseMsg = 'Could not resolve ' . $name . '!';
|
||||
try {
|
||||
$class = new ReflectionClass($name);
|
||||
if ($class->isInstantiable()) {
|
||||
return $this->buildClass($class);
|
||||
return $this->buildClass($class, $chain);
|
||||
} else {
|
||||
throw new QueryException($baseMsg
|
||||
. ' Class can not be instantiated');
|
||||
|
|
@ -130,14 +139,22 @@ class SimpleContainer implements ArrayAccess, ContainerInterface, IContainer {
|
|||
}
|
||||
}
|
||||
|
||||
public function query(string $name, bool $autoload = true) {
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @param list<class-string> $chain
|
||||
*/
|
||||
public function query(string $name, bool $autoload = true, array $chain = []) {
|
||||
$name = $this->sanitizeName($name);
|
||||
if (isset($this->container[$name])) {
|
||||
return $this->container[$name];
|
||||
}
|
||||
|
||||
if ($autoload) {
|
||||
$object = $this->resolve($name);
|
||||
if (in_array($name, $chain, true)) {
|
||||
throw new RuntimeException('Tried to query ' . $name . ', but it is already in the chain: ' . implode(', ', $chain));
|
||||
}
|
||||
|
||||
$object = $this->resolve($name, array_merge($chain, [$name]));
|
||||
$this->registerService($name, function () use ($object) {
|
||||
return $object;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -111,6 +111,7 @@ class ServerContainer extends SimpleContainer {
|
|||
/**
|
||||
* @template T
|
||||
* @param class-string<T>|string $name
|
||||
* @param list<class-string> $chain
|
||||
* @return T|mixed
|
||||
* @psalm-template S as class-string<T>|string
|
||||
* @psalm-param S $name
|
||||
|
|
@ -118,13 +119,13 @@ class ServerContainer extends SimpleContainer {
|
|||
* @throws QueryException
|
||||
* @deprecated 20.0.0 use \Psr\Container\ContainerInterface::get
|
||||
*/
|
||||
public function query(string $name, bool $autoload = true) {
|
||||
public function query(string $name, bool $autoload = true, array $chain = []) {
|
||||
$name = $this->sanitizeName($name);
|
||||
|
||||
if (str_starts_with($name, 'OCA\\')) {
|
||||
// Skip server container query for app namespace classes
|
||||
try {
|
||||
return parent::query($name, false);
|
||||
return parent::query($name, false, $chain);
|
||||
} catch (QueryException $e) {
|
||||
// Continue with general autoloading then
|
||||
}
|
||||
|
|
@ -132,7 +133,7 @@ class ServerContainer extends SimpleContainer {
|
|||
// the apps container first.
|
||||
if (($appContainer = $this->getAppContainerForService($name)) !== null) {
|
||||
try {
|
||||
return $appContainer->queryNoFallback($name);
|
||||
return $appContainer->queryNoFallback($name, $chain);
|
||||
} catch (QueryException $e) {
|
||||
// Didn't find the service or the respective app container
|
||||
// In this case the service won't be part of the core container,
|
||||
|
|
@ -144,14 +145,14 @@ class ServerContainer extends SimpleContainer {
|
|||
$segments = explode('\\', $name);
|
||||
try {
|
||||
$appContainer = $this->getAppContainer(strtolower($segments[1]), $segments[1]);
|
||||
return $appContainer->queryNoFallback($name);
|
||||
return $appContainer->queryNoFallback($name, $chain);
|
||||
} catch (QueryException $e) {
|
||||
// Didn't find the service or the respective app container,
|
||||
// ignore it and fall back to the core container.
|
||||
}
|
||||
}
|
||||
|
||||
return parent::query($name, $autoload);
|
||||
return parent::query($name, $autoload, $chain);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Reference in a new issue