Completed
Push — master ( e5c989...351351 )
by
unknown
27:02
created
lib/private/Config.php 1 patch
Indentation   +314 added lines, -314 removed lines patch added patch discarded remove patch
@@ -14,318 +14,318 @@
 block discarded – undo
14 14
  * configuration file of Nextcloud.
15 15
  */
16 16
 class Config {
17
-	public const ENV_PREFIX = 'NC_';
18
-
19
-	/** @var array Associative array ($key => $value) */
20
-	protected $cache = [];
21
-	/** @var array */
22
-	protected $envCache = [];
23
-	/** @var string */
24
-	protected $configDir;
25
-	/** @var string */
26
-	protected $configFilePath;
27
-	/** @var string */
28
-	protected $configFileName;
29
-	/** @var bool */
30
-	protected $isReadOnly;
31
-
32
-	/**
33
-	 * @param string $configDir Path to the config dir, needs to end with '/'
34
-	 * @param string $fileName (Optional) Name of the config file. Defaults to config.php
35
-	 */
36
-	public function __construct($configDir, $fileName = 'config.php') {
37
-		$this->configDir = $configDir;
38
-		$this->configFilePath = $this->configDir . $fileName;
39
-		$this->configFileName = $fileName;
40
-		$this->readData();
41
-		$this->isReadOnly = $this->getValue('config_is_read_only', false);
42
-	}
43
-
44
-	/**
45
-	 * Lists all available config keys
46
-	 *
47
-	 * Please note that it does not return the values.
48
-	 *
49
-	 * @return array an array of key names
50
-	 */
51
-	public function getKeys() {
52
-		return array_merge(array_keys($this->cache), array_keys($this->envCache));
53
-	}
54
-
55
-	/**
56
-	 * Returns a config value
57
-	 *
58
-	 * gets its value from an `NC_` prefixed environment variable
59
-	 * if it doesn't exist from config.php
60
-	 * if this doesn't exist either, it will return the given `$default`
61
-	 *
62
-	 * @param string $key key
63
-	 * @param mixed $default = null default value
64
-	 * @return mixed the value or $default
65
-	 */
66
-	public function getValue($key, $default = null) {
67
-		if (isset($this->envCache[$key])) {
68
-			return self::trustSystemConfig($this->envCache[$key]);
69
-		}
70
-
71
-		if (isset($this->cache[$key])) {
72
-			return self::trustSystemConfig($this->cache[$key]);
73
-		}
74
-
75
-		return $default;
76
-	}
77
-
78
-	/**
79
-	 * Since system config is admin controlled, we can tell psalm to ignore any taint
80
-	 *
81
-	 * @psalm-taint-escape callable
82
-	 * @psalm-taint-escape cookie
83
-	 * @psalm-taint-escape file
84
-	 * @psalm-taint-escape has_quotes
85
-	 * @psalm-taint-escape header
86
-	 * @psalm-taint-escape html
87
-	 * @psalm-taint-escape include
88
-	 * @psalm-taint-escape ldap
89
-	 * @psalm-taint-escape shell
90
-	 * @psalm-taint-escape sql
91
-	 * @psalm-taint-escape unserialize
92
-	 * @psalm-pure
93
-	 */
94
-	public static function trustSystemConfig(mixed $value): mixed {
95
-		return $value;
96
-	}
97
-
98
-	/**
99
-	 * Sets and deletes values and writes the config.php
100
-	 *
101
-	 * @param array $configs Associative array with `key => value` pairs
102
-	 *                       If value is null, the config key will be deleted
103
-	 * @throws HintException
104
-	 */
105
-	public function setValues(array $configs) {
106
-		$needsUpdate = false;
107
-		foreach ($configs as $key => $value) {
108
-			if ($value !== null) {
109
-				$needsUpdate |= $this->set($key, $value);
110
-			} else {
111
-				$needsUpdate |= $this->delete($key);
112
-			}
113
-		}
114
-
115
-		if ($needsUpdate) {
116
-			// Write changes
117
-			$this->writeData();
118
-		}
119
-	}
120
-
121
-	/**
122
-	 * Sets the value and writes it to config.php if required
123
-	 *
124
-	 * @param string $key key
125
-	 * @param mixed $value value
126
-	 * @throws HintException
127
-	 */
128
-	public function setValue($key, $value) {
129
-		if ($this->set($key, $value)) {
130
-			// Write changes
131
-			$this->writeData();
132
-		}
133
-	}
134
-
135
-	/**
136
-	 * This function sets the value
137
-	 *
138
-	 * @param string $key key
139
-	 * @param mixed $value value
140
-	 * @return bool True if the file needs to be updated, false otherwise
141
-	 * @throws HintException
142
-	 */
143
-	protected function set($key, $value) {
144
-		if (!isset($this->cache[$key]) || $this->cache[$key] !== $value) {
145
-			// Add change
146
-			$this->cache[$key] = $value;
147
-			return true;
148
-		}
149
-
150
-		return false;
151
-	}
152
-
153
-	/**
154
-	 * Removes a key from the config and removes it from config.php if required
155
-	 *
156
-	 * @param string $key
157
-	 * @throws HintException
158
-	 */
159
-	public function deleteKey($key) {
160
-		if ($this->delete($key)) {
161
-			// Write changes
162
-			$this->writeData();
163
-		}
164
-	}
165
-
166
-	/**
167
-	 * This function removes a key from the config
168
-	 *
169
-	 * @param string $key
170
-	 * @return bool True if the file needs to be updated, false otherwise
171
-	 * @throws HintException
172
-	 */
173
-	protected function delete($key) {
174
-		if (isset($this->cache[$key])) {
175
-			// Delete key from cache
176
-			unset($this->cache[$key]);
177
-			return true;
178
-		}
179
-		return false;
180
-	}
181
-
182
-	/**
183
-	 * Loads the config file
184
-	 *
185
-	 * Reads the config file and saves it to the cache
186
-	 *
187
-	 * @throws \Exception If no lock could be acquired or the config file has not been found
188
-	 */
189
-	private function readData() {
190
-		// Default config should always get loaded
191
-		$configFiles = [$this->configFilePath];
192
-
193
-		// Add all files in the config dir ending with the same file name
194
-		$extra = glob($this->configDir . '*.' . $this->configFileName);
195
-		if (is_array($extra)) {
196
-			natsort($extra);
197
-			$configFiles = array_merge($configFiles, $extra);
198
-		}
199
-
200
-		// Include file and merge config
201
-		foreach ($configFiles as $file) {
202
-			unset($CONFIG);
203
-
204
-			// Invalidate opcache (only if the timestamp changed)
205
-			if (function_exists('opcache_invalidate')) {
206
-				@opcache_invalidate($file, false);
207
-			}
208
-
209
-			// suppressor doesn't work here at boot time since it'll go via our onError custom error handler
210
-			$filePointer = file_exists($file) ? @fopen($file, 'r') : false;
211
-			if ($filePointer === false) {
212
-				// e.g. wrong permissions are set
213
-				if ($file === $this->configFilePath) {
214
-					// opening the main config file might not be possible
215
-					// (likely on a new installation)
216
-					continue;
217
-				}
218
-
219
-				http_response_code(500);
220
-				die(sprintf('FATAL: Could not open the config file %s', $file));
221
-			}
222
-
223
-			// Try to acquire a file lock
224
-			if (!flock($filePointer, LOCK_SH)) {
225
-				throw new \Exception(sprintf('Could not acquire a shared lock on the config file %s', $file));
226
-			}
227
-
228
-			try {
229
-				include $file;
230
-			} finally {
231
-				// Close the file pointer and release the lock
232
-				flock($filePointer, LOCK_UN);
233
-				fclose($filePointer);
234
-			}
235
-
236
-			if (!defined('PHPUNIT_RUN') && headers_sent()) {
237
-				// syntax issues in the config file like leading spaces causing PHP to send output
238
-				$errorMessage = sprintf('Config file has leading content, please remove everything before "<?php" in %s', basename($file));
239
-				if (!defined('OC_CONSOLE')) {
240
-					print(\OCP\Util::sanitizeHTML($errorMessage));
241
-				}
242
-				throw new \Exception($errorMessage);
243
-			}
244
-			if (isset($CONFIG) && is_array($CONFIG)) {
245
-				$this->cache = array_replace_recursive($this->cache, $CONFIG);
246
-			}
247
-		}
248
-
249
-		// grab any "NC_" environment variables
250
-		$envRaw = getenv();
251
-		// only save environment variables prefixed with "NC_" in the cache
252
-		$envPrefixLen = strlen(self::ENV_PREFIX);
253
-		foreach ($envRaw as $rawEnvKey => $rawEnvValue) {
254
-			if (str_starts_with($rawEnvKey, self::ENV_PREFIX)) {
255
-				$realKey = substr($rawEnvKey, $envPrefixLen);
256
-				$this->envCache[$realKey] = $rawEnvValue;
257
-			}
258
-		}
259
-	}
260
-
261
-	/**
262
-	 * Writes the config file
263
-	 *
264
-	 * Saves the config to the config file.
265
-	 *
266
-	 * @throws HintException If the config file cannot be written to
267
-	 * @throws \Exception If no file lock can be acquired
268
-	 */
269
-	private function writeData(): void {
270
-		$this->checkReadOnly();
271
-
272
-		if (!is_file(\OC::$configDir . '/CAN_INSTALL') && !isset($this->cache['version'])) {
273
-			throw new HintException(sprintf('Configuration was not read or initialized correctly, not overwriting %s', $this->configFilePath));
274
-		}
275
-
276
-		// Create a php file ...
277
-		$content = "<?php\n";
278
-		$content .= '$CONFIG = ';
279
-		$content .= var_export(self::trustSystemConfig($this->cache), true);
280
-		$content .= ";\n";
281
-
282
-		touch($this->configFilePath);
283
-		$filePointer = fopen($this->configFilePath, 'r+');
284
-
285
-		// Prevent others not to read the config
286
-		chmod($this->configFilePath, 0640);
287
-
288
-		// File does not exist, this can happen when doing a fresh install
289
-		if (!is_resource($filePointer)) {
290
-			throw new HintException(
291
-				"Can't write into config directory!",
292
-				'This can usually be fixed by giving the webserver write access to the config directory.');
293
-		}
294
-
295
-		// Never write file back if disk space should be too low
296
-		if (function_exists('disk_free_space')) {
297
-			$df = disk_free_space($this->configDir);
298
-			$size = strlen($content) + 10240;
299
-			if ($df !== false && $df < (float)$size) {
300
-				throw new \Exception($this->configDir . ' does not have enough space for writing the config file! Not writing it back!');
301
-			}
302
-		}
303
-
304
-		// Try to acquire a file lock
305
-		if (!flock($filePointer, LOCK_EX)) {
306
-			throw new \Exception(sprintf('Could not acquire an exclusive lock on the config file %s', $this->configFilePath));
307
-		}
308
-
309
-		// Write the config and release the lock
310
-		ftruncate($filePointer, 0);
311
-		fwrite($filePointer, $content);
312
-		fflush($filePointer);
313
-		flock($filePointer, LOCK_UN);
314
-		fclose($filePointer);
315
-
316
-		if (function_exists('opcache_invalidate')) {
317
-			@opcache_invalidate($this->configFilePath, true);
318
-		}
319
-	}
320
-
321
-	/**
322
-	 * @throws HintException
323
-	 */
324
-	private function checkReadOnly(): void {
325
-		if ($this->isReadOnly) {
326
-			throw new HintException(
327
-				'Config is set to be read-only via option "config_is_read_only".',
328
-				'Unset "config_is_read_only" to allow changes to the config file.');
329
-		}
330
-	}
17
+    public const ENV_PREFIX = 'NC_';
18
+
19
+    /** @var array Associative array ($key => $value) */
20
+    protected $cache = [];
21
+    /** @var array */
22
+    protected $envCache = [];
23
+    /** @var string */
24
+    protected $configDir;
25
+    /** @var string */
26
+    protected $configFilePath;
27
+    /** @var string */
28
+    protected $configFileName;
29
+    /** @var bool */
30
+    protected $isReadOnly;
31
+
32
+    /**
33
+     * @param string $configDir Path to the config dir, needs to end with '/'
34
+     * @param string $fileName (Optional) Name of the config file. Defaults to config.php
35
+     */
36
+    public function __construct($configDir, $fileName = 'config.php') {
37
+        $this->configDir = $configDir;
38
+        $this->configFilePath = $this->configDir . $fileName;
39
+        $this->configFileName = $fileName;
40
+        $this->readData();
41
+        $this->isReadOnly = $this->getValue('config_is_read_only', false);
42
+    }
43
+
44
+    /**
45
+     * Lists all available config keys
46
+     *
47
+     * Please note that it does not return the values.
48
+     *
49
+     * @return array an array of key names
50
+     */
51
+    public function getKeys() {
52
+        return array_merge(array_keys($this->cache), array_keys($this->envCache));
53
+    }
54
+
55
+    /**
56
+     * Returns a config value
57
+     *
58
+     * gets its value from an `NC_` prefixed environment variable
59
+     * if it doesn't exist from config.php
60
+     * if this doesn't exist either, it will return the given `$default`
61
+     *
62
+     * @param string $key key
63
+     * @param mixed $default = null default value
64
+     * @return mixed the value or $default
65
+     */
66
+    public function getValue($key, $default = null) {
67
+        if (isset($this->envCache[$key])) {
68
+            return self::trustSystemConfig($this->envCache[$key]);
69
+        }
70
+
71
+        if (isset($this->cache[$key])) {
72
+            return self::trustSystemConfig($this->cache[$key]);
73
+        }
74
+
75
+        return $default;
76
+    }
77
+
78
+    /**
79
+     * Since system config is admin controlled, we can tell psalm to ignore any taint
80
+     *
81
+     * @psalm-taint-escape callable
82
+     * @psalm-taint-escape cookie
83
+     * @psalm-taint-escape file
84
+     * @psalm-taint-escape has_quotes
85
+     * @psalm-taint-escape header
86
+     * @psalm-taint-escape html
87
+     * @psalm-taint-escape include
88
+     * @psalm-taint-escape ldap
89
+     * @psalm-taint-escape shell
90
+     * @psalm-taint-escape sql
91
+     * @psalm-taint-escape unserialize
92
+     * @psalm-pure
93
+     */
94
+    public static function trustSystemConfig(mixed $value): mixed {
95
+        return $value;
96
+    }
97
+
98
+    /**
99
+     * Sets and deletes values and writes the config.php
100
+     *
101
+     * @param array $configs Associative array with `key => value` pairs
102
+     *                       If value is null, the config key will be deleted
103
+     * @throws HintException
104
+     */
105
+    public function setValues(array $configs) {
106
+        $needsUpdate = false;
107
+        foreach ($configs as $key => $value) {
108
+            if ($value !== null) {
109
+                $needsUpdate |= $this->set($key, $value);
110
+            } else {
111
+                $needsUpdate |= $this->delete($key);
112
+            }
113
+        }
114
+
115
+        if ($needsUpdate) {
116
+            // Write changes
117
+            $this->writeData();
118
+        }
119
+    }
120
+
121
+    /**
122
+     * Sets the value and writes it to config.php if required
123
+     *
124
+     * @param string $key key
125
+     * @param mixed $value value
126
+     * @throws HintException
127
+     */
128
+    public function setValue($key, $value) {
129
+        if ($this->set($key, $value)) {
130
+            // Write changes
131
+            $this->writeData();
132
+        }
133
+    }
134
+
135
+    /**
136
+     * This function sets the value
137
+     *
138
+     * @param string $key key
139
+     * @param mixed $value value
140
+     * @return bool True if the file needs to be updated, false otherwise
141
+     * @throws HintException
142
+     */
143
+    protected function set($key, $value) {
144
+        if (!isset($this->cache[$key]) || $this->cache[$key] !== $value) {
145
+            // Add change
146
+            $this->cache[$key] = $value;
147
+            return true;
148
+        }
149
+
150
+        return false;
151
+    }
152
+
153
+    /**
154
+     * Removes a key from the config and removes it from config.php if required
155
+     *
156
+     * @param string $key
157
+     * @throws HintException
158
+     */
159
+    public function deleteKey($key) {
160
+        if ($this->delete($key)) {
161
+            // Write changes
162
+            $this->writeData();
163
+        }
164
+    }
165
+
166
+    /**
167
+     * This function removes a key from the config
168
+     *
169
+     * @param string $key
170
+     * @return bool True if the file needs to be updated, false otherwise
171
+     * @throws HintException
172
+     */
173
+    protected function delete($key) {
174
+        if (isset($this->cache[$key])) {
175
+            // Delete key from cache
176
+            unset($this->cache[$key]);
177
+            return true;
178
+        }
179
+        return false;
180
+    }
181
+
182
+    /**
183
+     * Loads the config file
184
+     *
185
+     * Reads the config file and saves it to the cache
186
+     *
187
+     * @throws \Exception If no lock could be acquired or the config file has not been found
188
+     */
189
+    private function readData() {
190
+        // Default config should always get loaded
191
+        $configFiles = [$this->configFilePath];
192
+
193
+        // Add all files in the config dir ending with the same file name
194
+        $extra = glob($this->configDir . '*.' . $this->configFileName);
195
+        if (is_array($extra)) {
196
+            natsort($extra);
197
+            $configFiles = array_merge($configFiles, $extra);
198
+        }
199
+
200
+        // Include file and merge config
201
+        foreach ($configFiles as $file) {
202
+            unset($CONFIG);
203
+
204
+            // Invalidate opcache (only if the timestamp changed)
205
+            if (function_exists('opcache_invalidate')) {
206
+                @opcache_invalidate($file, false);
207
+            }
208
+
209
+            // suppressor doesn't work here at boot time since it'll go via our onError custom error handler
210
+            $filePointer = file_exists($file) ? @fopen($file, 'r') : false;
211
+            if ($filePointer === false) {
212
+                // e.g. wrong permissions are set
213
+                if ($file === $this->configFilePath) {
214
+                    // opening the main config file might not be possible
215
+                    // (likely on a new installation)
216
+                    continue;
217
+                }
218
+
219
+                http_response_code(500);
220
+                die(sprintf('FATAL: Could not open the config file %s', $file));
221
+            }
222
+
223
+            // Try to acquire a file lock
224
+            if (!flock($filePointer, LOCK_SH)) {
225
+                throw new \Exception(sprintf('Could not acquire a shared lock on the config file %s', $file));
226
+            }
227
+
228
+            try {
229
+                include $file;
230
+            } finally {
231
+                // Close the file pointer and release the lock
232
+                flock($filePointer, LOCK_UN);
233
+                fclose($filePointer);
234
+            }
235
+
236
+            if (!defined('PHPUNIT_RUN') && headers_sent()) {
237
+                // syntax issues in the config file like leading spaces causing PHP to send output
238
+                $errorMessage = sprintf('Config file has leading content, please remove everything before "<?php" in %s', basename($file));
239
+                if (!defined('OC_CONSOLE')) {
240
+                    print(\OCP\Util::sanitizeHTML($errorMessage));
241
+                }
242
+                throw new \Exception($errorMessage);
243
+            }
244
+            if (isset($CONFIG) && is_array($CONFIG)) {
245
+                $this->cache = array_replace_recursive($this->cache, $CONFIG);
246
+            }
247
+        }
248
+
249
+        // grab any "NC_" environment variables
250
+        $envRaw = getenv();
251
+        // only save environment variables prefixed with "NC_" in the cache
252
+        $envPrefixLen = strlen(self::ENV_PREFIX);
253
+        foreach ($envRaw as $rawEnvKey => $rawEnvValue) {
254
+            if (str_starts_with($rawEnvKey, self::ENV_PREFIX)) {
255
+                $realKey = substr($rawEnvKey, $envPrefixLen);
256
+                $this->envCache[$realKey] = $rawEnvValue;
257
+            }
258
+        }
259
+    }
260
+
261
+    /**
262
+     * Writes the config file
263
+     *
264
+     * Saves the config to the config file.
265
+     *
266
+     * @throws HintException If the config file cannot be written to
267
+     * @throws \Exception If no file lock can be acquired
268
+     */
269
+    private function writeData(): void {
270
+        $this->checkReadOnly();
271
+
272
+        if (!is_file(\OC::$configDir . '/CAN_INSTALL') && !isset($this->cache['version'])) {
273
+            throw new HintException(sprintf('Configuration was not read or initialized correctly, not overwriting %s', $this->configFilePath));
274
+        }
275
+
276
+        // Create a php file ...
277
+        $content = "<?php\n";
278
+        $content .= '$CONFIG = ';
279
+        $content .= var_export(self::trustSystemConfig($this->cache), true);
280
+        $content .= ";\n";
281
+
282
+        touch($this->configFilePath);
283
+        $filePointer = fopen($this->configFilePath, 'r+');
284
+
285
+        // Prevent others not to read the config
286
+        chmod($this->configFilePath, 0640);
287
+
288
+        // File does not exist, this can happen when doing a fresh install
289
+        if (!is_resource($filePointer)) {
290
+            throw new HintException(
291
+                "Can't write into config directory!",
292
+                'This can usually be fixed by giving the webserver write access to the config directory.');
293
+        }
294
+
295
+        // Never write file back if disk space should be too low
296
+        if (function_exists('disk_free_space')) {
297
+            $df = disk_free_space($this->configDir);
298
+            $size = strlen($content) + 10240;
299
+            if ($df !== false && $df < (float)$size) {
300
+                throw new \Exception($this->configDir . ' does not have enough space for writing the config file! Not writing it back!');
301
+            }
302
+        }
303
+
304
+        // Try to acquire a file lock
305
+        if (!flock($filePointer, LOCK_EX)) {
306
+            throw new \Exception(sprintf('Could not acquire an exclusive lock on the config file %s', $this->configFilePath));
307
+        }
308
+
309
+        // Write the config and release the lock
310
+        ftruncate($filePointer, 0);
311
+        fwrite($filePointer, $content);
312
+        fflush($filePointer);
313
+        flock($filePointer, LOCK_UN);
314
+        fclose($filePointer);
315
+
316
+        if (function_exists('opcache_invalidate')) {
317
+            @opcache_invalidate($this->configFilePath, true);
318
+        }
319
+    }
320
+
321
+    /**
322
+     * @throws HintException
323
+     */
324
+    private function checkReadOnly(): void {
325
+        if ($this->isReadOnly) {
326
+            throw new HintException(
327
+                'Config is set to be read-only via option "config_is_read_only".',
328
+                'Unset "config_is_read_only" to allow changes to the config file.');
329
+        }
330
+    }
331 331
 }
Please login to merge, or discard this patch.