Lines
83.67%
41 / 49
Methods
40.00%
2 / 5
Classes
0.00%
0 / 1
| Name | Lines | Methods | CRAP | ||||
|---|---|---|---|---|---|---|---|
| resolve | 50.00% | 4 / 8 | 0.00% | 0 / 1 | 8.12 | ||
| resolveDatabaseFile | 84.21% | 16 / 19 | 0.00% | 0 / 1 | 11.48 | ||
| loadConfigConstants | 88.88% | 8 / 9 | 0.00% | 0 / 1 | 4.02 | ||
| resolveAttachmentsDir | 100.00% | 2 / 2 | 100.00% | 1 / 1 | 2 | ||
| computeAttachmentsPath | 100.00% | 11 / 11 | 100.00% | 1 / 1 | 7 | ||
| 24 | class ConfigDirectoryResolver | |
| 25 | { | |
| 26 | /** | |
| 27 | * Defines PMF_CONFIG_DIR and PMF_LEGACY_CONFIG_DIR constants. | |
| 28 | */ | |
| 29 | public static function resolve(): void | |
| 30 | { | |
| 31 | if (defined('PMF_MULTI_INSTANCE_CONFIG_DIR')) { | |
| 32 | if (!defined('PMF_CONFIG_DIR')) { | |
| 33 | define('PMF_CONFIG_DIR', (string) constant('PMF_MULTI_INSTANCE_CONFIG_DIR')); | |
| 34 | } | |
| 35 | ||
| 36 | return; | |
| 37 | } | |
| 38 | ||
| 39 | if (!defined('PMF_CONFIG_DIR')) { | |
| 40 | define('PMF_CONFIG_DIR', (string) PMF_ROOT_DIR . '/content/core/config'); | |
| 41 | } | |
| 42 | ||
| 43 | // For backward compatibility, we also define PMF_LEGACY_CONFIG_DIR if not already defined, | |
| 44 | // but it should not be used by new code. | |
| 45 | // This can be removed if we drop support updates from phpMyFAQ 3.x versions that still use the old config | |
| 46 | // location. | |
| 47 | if (!defined('PMF_LEGACY_CONFIG_DIR')) { | |
| 48 | define('PMF_LEGACY_CONFIG_DIR', (string) PMF_ROOT_DIR . '/config'); | |
| 49 | } | |
| 50 | } | |
| 51 | ||
| 52 | /** | |
| 53 | * Detects the database.php config file. Redirects to /setup/ if missing, | |
| 54 | * and we're not yet in a setup/update context. | |
| 55 | * | |
| 56 | * @return string|null Path to the database.php file, or null if not found (setup redirect sent) | |
| 57 | */ | |
| 58 | public static function resolveDatabaseFile(): ?string | |
| 59 | { | |
| 60 | $requestUri = $_SERVER['REQUEST_URI'] ?? ''; | |
| 61 | $isSetupContext = | |
| 62 | PHP_SAPI === 'cli' | |
| 63 | || str_contains($requestUri, '/setup/') | |
| 64 | || str_contains($requestUri, '/api/setup/') | |
| 65 | || str_contains($requestUri, '/update') | |
| 66 | || str_contains($requestUri, '/update/'); | |
| 67 | ||
| 68 | $legacyConfigDir = defined('PMF_LEGACY_CONFIG_DIR') ? (string) PMF_LEGACY_CONFIG_DIR : null; | |
| 69 | ||
| 70 | $configExists = file_exists((string) PMF_CONFIG_DIR . '/database.php'); | |
| 71 | $legacyExists = $legacyConfigDir !== null && file_exists($legacyConfigDir . '/database.php'); | |
| 72 | ||
| 73 | if (!$configExists && !$legacyExists) { | |
| 74 | if (!$isSetupContext) { | |
| 75 | $response = new RedirectResponse('/setup/'); | |
| 76 | $response->send(); | |
| 77 | exit(); | |
| 78 | } | |
| 79 | ||
| 80 | return null; | |
| 81 | } | |
| 82 | ||
| 83 | if ($configExists) { | |
| 84 | return (string) PMF_CONFIG_DIR . '/database.php'; | |
| 85 | } | |
| 86 | ||
| 87 | return $legacyConfigDir . '/database.php'; | |
| 88 | } | |
| 89 | ||
| 90 | /** | |
| 91 | * Loads the config-specific constants.php file. | |
| 92 | */ | |
| 93 | public static function loadConfigConstants(): void | |
| 94 | { | |
| 95 | $constantsFile = (string) PMF_CONFIG_DIR . '/constants.php'; | |
| 96 | if (file_exists($constantsFile)) { | |
| 97 | require_once $constantsFile; | |
| 98 | return; | |
| 99 | } | |
| 100 | ||
| 101 | if (!defined('PMF_LEGACY_CONFIG_DIR')) { | |
| 102 | return; | |
| 103 | } | |
| 104 | ||
| 105 | $legacyConstantsFile = (string) PMF_LEGACY_CONFIG_DIR . '/constants.php'; | |
| 106 | if (file_exists($legacyConstantsFile)) { | |
| 107 | require_once $legacyConstantsFile; | |
| 108 | } | |
| 109 | } | |
| 110 | ||
| 111 | /** | |
| 112 | * Resolves the attachments directory with path-traversal protection and defines PMF_ATTACHMENTS_DIR. | |
| 113 | */ | |
| 114 | public static function resolveAttachmentsDir(string $confAttachmentsPath, string $rootDir): void | |
| 115 | { | |
| 116 | if (!defined('PMF_ATTACHMENTS_DIR')) { | |
| 117 | define('PMF_ATTACHMENTS_DIR', self::computeAttachmentsPath($confAttachmentsPath, $rootDir)); | |
| 118 | } | |
| 119 | } | |
| 120 | ||
| 121 | /** | |
| 122 | * Computes the resolved attachments path without side effects (no constant definition). | |
| 123 | * | |
| 124 | * @return string|false The resolved path, or false if path traversal was detected | |
| 125 | */ | |
| 126 | public static function computeAttachmentsPath(string $confAttachmentsPath, string $rootDir): string|false | |
| 127 | { | |
| 128 | $confAttachmentsPath = trim($confAttachmentsPath); | |
| 129 | if ($confAttachmentsPath === '') { | |
| 130 | return $rootDir . DIRECTORY_SEPARATOR; | |
| 131 | } | |
| 132 | ||
| 133 | if ($confAttachmentsPath[0] === '/' || preg_match('%^[a-z]:[\\\\/]%i', $confAttachmentsPath)) { | |
| 134 | return $confAttachmentsPath; | |
| 135 | } | |
| 136 | ||
| 137 | $pathSegments = preg_split('%[\\\\/]%', $confAttachmentsPath); | |
| 138 | $pathSegments = $pathSegments === false ? [] : $pathSegments; | |
| 139 | foreach ($pathSegments as $pathSegment) { | |
| 140 | if ($pathSegment === '..') { | |
| 141 | return false; | |
| 142 | } | |
| 143 | } | |
| 144 | ||
| 145 | return $rootDir . DIRECTORY_SEPARATOR . $confAttachmentsPath; | |
| 146 | } | |
| 147 | } |