From 61f501abc922c5e1d540939450480ade97d22b58 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 1 Nov 2020 12:25:02 -0800 Subject: [PATCH] Fix file parser failing if multiple configuration values are present on same line; closes #2604 --- app/Services/Eggs/EggConfigurationService.php | 134 +++++++++--------- 1 file changed, 68 insertions(+), 66 deletions(-) diff --git a/app/Services/Eggs/EggConfigurationService.php b/app/Services/Eggs/EggConfigurationService.php index 6f4eae689..e7d01df2e 100644 --- a/app/Services/Eggs/EggConfigurationService.php +++ b/app/Services/Eggs/EggConfigurationService.php @@ -5,18 +5,10 @@ namespace Pterodactyl\Services\Eggs; use Illuminate\Support\Arr; use Illuminate\Support\Str; use Pterodactyl\Models\Server; -use Pterodactyl\Contracts\Repository\EggRepositoryInterface; use Pterodactyl\Services\Servers\ServerConfigurationStructureService; class EggConfigurationService { - private const NOT_MATCHED = '__no_match'; - - /** - * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface - */ - private $repository; - /** * @var \Pterodactyl\Services\Servers\ServerConfigurationStructureService */ @@ -25,14 +17,10 @@ class EggConfigurationService /** * EggConfigurationService constructor. * - * @param \Pterodactyl\Contracts\Repository\EggRepositoryInterface $repository * @param \Pterodactyl\Services\Servers\ServerConfigurationStructureService $configurationStructureService */ - public function __construct( - EggRepositoryInterface $repository, - ServerConfigurationStructureService $configurationStructureService - ) { - $this->repository = $repository; + public function __construct(ServerConfigurationStructureService $configurationStructureService) + { $this->configurationStructureService = $configurationStructureService; } @@ -41,8 +29,6 @@ class EggConfigurationService * * @param \Pterodactyl\Models\Server $server * @return array - * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ public function handle(Server $server): array { @@ -111,8 +97,6 @@ class EggConfigurationService * @param \Pterodactyl\Models\Server $server * @param object $configs * @return array - * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ protected function replacePlaceholders(Server $server, object $configs) { @@ -130,6 +114,7 @@ class EggConfigurationService foreach ($configs as $file => $data) { $append = ['file' => $file, 'replace' => []]; + /** @noinspection PhpParameterByRefIsNotUsedAsReferenceInspection */ // I like to think I understand PHP pretty well, but if you don't pass $value // by reference here, you'll end up with a resursive array loop if the config // file has two replacements that reference the same item in the configuration @@ -167,71 +152,93 @@ class EggConfigurationService } /** + * Replaces the legacy modifies from eggs with their new counterpart. The legacy Daemon would + * set SERVER_MEMORY, SERVER_IP, and SERVER_PORT with their respective values on the Daemon + * side. Ensure that anything referencing those properly replaces them with the matching config + * value. + * + * @param string $key * @param string $value - * @param array $structure - * @return string|null + * @return string */ - protected function matchAndReplaceKeys(string $value, array $structure): ?string + protected function replaceLegacyModifiers(string $key, string $value): string { - preg_match('/{{(?.*)}}/', $value, $matches); - - if (! $key = $matches['key'] ?? null) { - return self::NOT_MATCHED; - } - - // Matched something in {{server.X}} format, now replace that with the actual - // value from the server properties. - // - // The Daemon supports server.X, env.X, and config.X placeholders. - if (! Str::startsWith($key, ['server.', 'env.', 'config.'])) { - return self::NOT_MATCHED; - } - - // The legacy Daemon would set SERVER_MEMORY, SERVER_IP, and SERVER_PORT with their - // respective values on the Daemon side. Ensure that anything referencing those properly - // replaces them with the matching config value. switch ($key) { case 'config.docker.interface': - $key = 'config.docker.network.interface'; + $replace = 'config.docker.network.interface'; break; case 'server.build.env.SERVER_MEMORY': case 'env.SERVER_MEMORY': - $key = 'server.build.memory'; + $replace = 'server.build.memory'; break; case 'server.build.env.SERVER_IP': case 'env.SERVER_IP': - $key = 'server.build.default.ip'; + $replace = 'server.build.default.ip'; break; case 'server.build.env.SERVER_PORT': case 'env.SERVER_PORT': - $key = 'server.build.default.port'; + $replace = 'server.build.default.port'; break; + // By default we don't need to change anything, only if we ended up matching a specific legacy item. + default: + $replace = $key; } - // We don't want to do anything with config keys since the Daemon will need to handle - // that. For example, the Spigot egg uses "config.docker.interface" to identify the Docker - // interface to proxy through, but the Panel would be unaware of that. - if (Str::startsWith($key, 'config.')) { - return preg_replace('/{{(.*)}}/', "{{{$key}}}", $value); - } + return str_replace("{{{$key}}}", "{{{$replace}}}", $value); + } - // Replace anything starting with "server." with the value out of the server configuration - // array that used to be created for the old daemon. - if (Str::startsWith($key, 'server.')) { + /** + * @param mixed $value + * @param array $structure + * @return mixed|null + */ + protected function matchAndReplaceKeys($value, array $structure) + { + preg_match_all('/{{(?[\w.-]*)}}/', $value, $matches); + + foreach ($matches['key'] as $key) { + // Matched something in {{server.X}} format, now replace that with the actual + // value from the server properties. + // + // The Daemon supports server.X, env.X, and config.X placeholders. + if (! Str::startsWith($key, ['server.', 'env.', 'config.'])) { + continue; + } + + // Don't do a replacement on anything that is not a string, we don't want to unintentionally + // modify the resulting output. + if (! is_string($value)) { + continue; + } + + $value = $this->replaceLegacyModifiers($key, $value); + + // We don't want to do anything with config keys since the Daemon will need to handle + // that. For example, the Spigot egg uses "config.docker.interface" to identify the Docker + // interface to proxy through, but the Panel would be unaware of that. + if (Str::startsWith($key, 'config.')) { + continue; + } + + // Replace anything starting with "server." with the value out of the server configuration + // array that used to be created for the old daemon. + if (Str::startsWith($key, 'server.')) { + $plucked = Arr::get($structure, preg_replace('/^server\./', '', $key), ''); + + $value = str_replace("{{{$key}}}", $plucked, $value); + continue; + } + + // Finally, replace anything starting with env. with the expected environment + // variable from the server configuration. $plucked = Arr::get( - $structure, preg_replace('/^server\./', '', $key), '' + $structure, preg_replace('/^env\./', 'build.env.', $key), '' ); - return preg_replace('/{{(.*)}}/', $plucked, $value); + $value = str_replace("{{{$key}}}", $plucked, $value); } - // Finally, replace anything starting with env. with the expected environment - // variable from the server configuration. - $plucked = Arr::get( - $structure, preg_replace('/^env\./', 'build.env.', $key), '' - ); - - return preg_replace('/{{(.*)}}/', $plucked, $value); + return $value; } /** @@ -255,12 +262,7 @@ class EggConfigurationService continue; } - $response = $this->matchAndReplaceKeys($value, $structure); - if ($response === self::NOT_MATCHED) { - continue; - } - - $value = $response; + $value = $this->matchAndReplaceKeys($value, $structure); } } }