From c7d0a30a151617f53fcb59c9aa0d64571977b407 Mon Sep 17 00:00:00 2001 From: mjansen Date: Fri, 27 Feb 2026 21:21:29 +0100 Subject: [PATCH] [FIX] FileDelivery: Add ext. data directory support for `X-Accel` --- .../ResponseBuilder/XAccelResponseBuilder.php | 12 +++ components/ILIAS/FileDelivery/src/Init.php | 6 +- .../src/Setup/DeliveryMethodObjective.php | 76 ++++++++++++++----- 3 files changed, 76 insertions(+), 18 deletions(-) diff --git a/components/ILIAS/FileDelivery/src/Delivery/ResponseBuilder/XAccelResponseBuilder.php b/components/ILIAS/FileDelivery/src/Delivery/ResponseBuilder/XAccelResponseBuilder.php index 12085ef57563..ccb812366fec 100755 --- a/components/ILIAS/FileDelivery/src/Delivery/ResponseBuilder/XAccelResponseBuilder.php +++ b/components/ILIAS/FileDelivery/src/Delivery/ResponseBuilder/XAccelResponseBuilder.php @@ -31,8 +31,14 @@ class XAccelResponseBuilder implements ResponseBuilder { private const DATA = 'data'; private const SECURED_DATA = 'secured-data'; + private const SECURED_EXT_DATA = 'secured-ext-data'; private const X_ACCEL_REDIRECT_HEADER = 'X-Accel-Redirect'; + public function __construct(private string $external_data_dir) + { + $this->external_data_dir = rtrim($this->external_data_dir, '/') . '/'; + } + public function getName(): string { return 'x-accel'; @@ -51,6 +57,12 @@ public function buildForStream( . '/', $path_to_file ); + } elseif (str_starts_with((string) $path_to_file, $this->external_data_dir)) { + $path_to_file = str_replace( + $this->external_data_dir, + '/' . self::SECURED_EXT_DATA . '/', + $path_to_file + ); } return $response->withHeader( diff --git a/components/ILIAS/FileDelivery/src/Init.php b/components/ILIAS/FileDelivery/src/Init.php index 3c7f01a7ed4d..a0fc5c4d316c 100755 --- a/components/ILIAS/FileDelivery/src/Init.php +++ b/components/ILIAS/FileDelivery/src/Init.php @@ -44,9 +44,13 @@ public static function init(Container $c): void switch ($settings[DeliveryMethodObjective::SETTINGS] ?? null) { case DeliveryMethodObjective::XACCEL: - return new XAccelResponseBuilder(); + return new XAccelResponseBuilder( + $settings[DeliveryMethodObjective::SETTINGS_EXTERNAL_DATA_DIR] + ); + case DeliveryMethodObjective::XSENDFILE: return new XSendFileResponseBuilder(); + case DeliveryMethodObjective::PHP: default: return new PHPResponseBuilder(); diff --git a/components/ILIAS/FileDelivery/src/Setup/DeliveryMethodObjective.php b/components/ILIAS/FileDelivery/src/Setup/DeliveryMethodObjective.php index 815638f84015..5e630922f2c7 100755 --- a/components/ILIAS/FileDelivery/src/Setup/DeliveryMethodObjective.php +++ b/components/ILIAS/FileDelivery/src/Setup/DeliveryMethodObjective.php @@ -23,7 +23,8 @@ use ILIAS\Setup\Artifact; use ILIAS\Setup\Artifact\ArrayArtifact; use ILIAS\Setup\Environment; -use ILIAS\Setup\Artifact\BuildArtifactObjective; +use ILIAS\Setup\CLI\IOWrapper; +use ILIAS\Setup\UnachievableException; /** * @author Fabian Schmid @@ -31,54 +32,95 @@ class DeliveryMethodObjective extends BuildStaticConfigStoredObjective { public const SETTINGS = 'delivery_method'; + public const SETTINGS_EXTERNAL_DATA_DIR = 'ext_data_dir'; public const XSENDFILE = 'xsendfile'; public const XACCEL = 'xaccel'; public const PHP = 'php'; + private ?string $ext_data_dir = null; + private ?IOWrapper $io = null; + public function getArtifactName(): string { - return "delivery_method"; + return 'delivery_method'; + } + + public function getPreconditions(Environment $environment): array + { + return array_merge( + parent::getPreconditions($environment), + [ + new \ilIniFilesPopulatedObjective() + ] + ); + } + + public function achieve(Environment $environment): Environment + { + /** @var \ilIniFile $ini */ + $ini = $environment->getResource(Environment::RESOURCE_ILIAS_INI); + if ($ini instanceof \ilIniFile && $ini->variableExists('clients', 'datadir')) { + $this->ext_data_dir = $ini->readVariable('clients', 'datadir'); + } else { + throw new UnachievableException( + 'Could not determine external data directory from ILIAS ini file' + ); + } + + $io = $environment->getResource(Environment::RESOURCE_ADMIN_INTERACTION); + if ($io instanceof IOWrapper) { + $this->io = $io; + } + + return parent::achieve($environment); } public function build(): Artifact { - // check if mod_xsendfile is loaded + $delivery_method = self::PHP; + + if (file_exists(self::PATH())) { + $settings = (@include self::PATH()) ?? []; + $delivery_method = $settings[self::SETTINGS] ?? self::PHP; + } + if ($this->isModXSendFileLoaded()) { - return new ArrayArtifact([ - self::SETTINGS => self::XSENDFILE - ]); + $delivery_method = self::XSENDFILE; } - return new ArrayArtifact([ - self::SETTINGS => self::PHP - ]); + return new ArrayArtifact(array_filter([ + self::SETTINGS => $delivery_method, + self::SETTINGS_EXTERNAL_DATA_DIR => $this->ext_data_dir + ])); } private function isModXSendFileLoaded(): bool { - if (function_exists('apache_get_modules') && in_array('mod_xsendfile', apache_get_modules(), true)) { + if (\function_exists('apache_get_modules') && \in_array('mod_xsendfile', apache_get_modules(), true)) { return true; } try { - $command_exists = shell_exec("which apache2ctl"); - if ($command_exists === null || empty($command_exists)) { + $command_exists = shell_exec('which apache2ctl'); + if (empty($command_exists)) { return false; } $loaded_modules = array_map( - static fn(string $module): string => explode(" ", trim($module))[0] ?? "", - explode("\n", shell_exec("apache2ctl -M 2>/dev/null") ?? '') + static fn(string $module): string => explode(' ', trim($module))[0] ?? '', + explode("\n", shell_exec('apache2ctl -M 2>/dev/null') ?? '') ); } catch (\Throwable $e) { + $this->io?->error($e->getMessage()); + $this->io?->error($e->getTraceAsString()); $loaded_modules = []; } - return in_array('xsendfile_module', $loaded_modules, true); + + return \in_array('xsendfile_module', $loaded_modules, true); } public function isApplicable(Environment $environment): bool { - return !file_exists(BuildArtifactObjective::PATH()); + return true; } - }