* @author Aldo "xoen" Giambelluca * @author Bart Visscher * @author Brice Maron * @author Christoph Wurst * @author Daniel Kesselberg * @author Frank Karlitschek * @author Jakob Sack * @author Jan-Christoph Borchardt * @author Joas Schilling * @author John Molakvoæ (skjnldsv) * @author Lukas Reschke * @author Michael Gapczynski * @author Morris Jobke * @author Philipp Schaffrath * @author Robin Appelman * @author Robin McCorkell * @author Roeland Jago Douma * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see * */ namespace OC; /** * This class is responsible for reading and writing config.php, the very basic * configuration file of Nextcloud. */ class Config { public const ENV_PREFIX = 'NC_'; /** @var array Associative array ($key => $value) */ protected $cache = []; /** @var array */ protected $envCache = []; /** @var string */ protected $configDir; /** @var string */ protected $configFilePath; /** @var string */ protected $configFileName; /** * @param string $configDir Path to the config dir, needs to end with '/' * @param string $fileName (Optional) Name of the config file. Defaults to config.php */ public function __construct($configDir, $fileName = 'config.php') { $this->configDir = $configDir; $this->configFilePath = $this->configDir.$fileName; $this->configFileName = $fileName; $this->readData(); } /** * Lists all available config keys * * Please note that it does not return the values. * * @return array an array of key names */ public function getKeys() { return array_keys($this->cache); } /** * Returns a config value * * gets its value from an `NC_` prefixed environment variable * if it doesn't exist from config.php * if this doesn't exist either, it will return the given `$default` * * @param string $key key * @param mixed $default = null default value * @return mixed the value or $default */ public function getValue($key, $default = null) { $envKey = self::ENV_PREFIX . $key; if (isset($this->envCache[$envKey])) { return $this->envCache[$envKey]; } if (isset($this->cache[$key])) { return $this->cache[$key]; } return $default; } /** * Sets and deletes values and writes the config.php * * @param array $configs Associative array with `key => value` pairs * If value is null, the config key will be deleted */ public function setValues(array $configs) { $needsUpdate = false; foreach ($configs as $key => $value) { if ($value !== null) { $needsUpdate |= $this->set($key, $value); } else { $needsUpdate |= $this->delete($key); } } if ($needsUpdate) { // Write changes $this->writeData(); } } /** * Sets the value and writes it to config.php if required * * @param string $key key * @param mixed $value value */ public function setValue($key, $value) { if ($this->set($key, $value)) { // Write changes $this->writeData(); } } /** * This function sets the value * * @param string $key key * @param mixed $value value * @return bool True if the file needs to be updated, false otherwise */ protected function set($key, $value) { if (!isset($this->cache[$key]) || $this->cache[$key] !== $value) { // Add change $this->cache[$key] = $value; return true; } return false; } /** * Removes a key from the config and removes it from config.php if required * @param string $key */ public function deleteKey($key) { if ($this->delete($key)) { // Write changes $this->writeData(); } } /** * This function removes a key from the config * * @param string $key * @return bool True if the file needs to be updated, false otherwise */ protected function delete($key) { if (isset($this->cache[$key])) { // Delete key from cache unset($this->cache[$key]); return true; } return false; } /** * Loads the config file * * Reads the config file and saves it to the cache * * @throws \Exception If no lock could be acquired or the config file has not been found */ private function readData() { // Default config should always get loaded $configFiles = [$this->configFilePath]; // Add all files in the config dir ending with the same file name $extra = glob($this->configDir.'*.'.$this->configFileName); if (is_array($extra)) { natsort($extra); $configFiles = array_merge($configFiles, $extra); } // Include file and merge config foreach ($configFiles as $file) { $fileExistsAndIsReadable = file_exists($file) && is_readable($file); $filePointer = $fileExistsAndIsReadable ? fopen($file, 'r') : false; if ($file === $this->configFilePath && $filePointer === false) { // Opening the main config might not be possible, e.g. if the wrong // permissions are set (likely on a new installation) continue; } // Try to acquire a file lock if (!flock($filePointer, LOCK_SH)) { throw new \Exception(sprintf('Could not acquire a shared lock on the config file %s', $file)); } unset($CONFIG); include $file; if (isset($CONFIG) && is_array($CONFIG)) { $this->cache = array_merge($this->cache, $CONFIG); } // Close the file pointer and release the lock flock($filePointer, LOCK_UN); fclose($filePointer); } $this->envCache = getenv(); } /** * Writes the config file * * Saves the config to the config file. * * @throws HintException If the config file cannot be written to * @throws \Exception If no file lock can be acquired */ private function writeData() { // Create a php file ... $content = "cache, true); $content .= ";\n"; touch($this->configFilePath); $filePointer = fopen($this->configFilePath, 'r+'); // Prevent others not to read the config chmod($this->configFilePath, 0640); // File does not exist, this can happen when doing a fresh install if (!is_resource($filePointer)) { throw new HintException( "Can't write into config directory!", 'This can usually be fixed by giving the webserver write access to the config directory.'); } // Try to acquire a file lock if (!flock($filePointer, LOCK_EX)) { throw new \Exception(sprintf('Could not acquire an exclusive lock on the config file %s', $this->configFilePath)); } // Write the config and release the lock ftruncate($filePointer, 0); fwrite($filePointer, $content); fflush($filePointer); flock($filePointer, LOCK_UN); fclose($filePointer); if (function_exists('opcache_invalidate')) { @opcache_invalidate($this->configFilePath, true); } } }