[security] Self-XSS in setup (host parameter), see PMASA-2011-19
[phpmyadmin.git] / libraries / config / ConfigFile.class.php
blob178a184774a12dd2a11fa1191504057f14501581
1 <?php
2 /* vim: set expandtab sw=4 ts=4 sts=4: */
3 /**
4 * Config file management
6 * @package phpMyAdmin
7 */
9 /**
10 * Config file management class.
11 * Stores its data in $_SESSION
13 * @package phpMyAdmin
15 class ConfigFile
17 /**
18 * Stores default PMA config from config.default.php
19 * @var array
21 private $cfg;
23 /**
24 * Stores original PMA_Config object, not modified by user preferences
25 * @var PMA_Config
27 private $orgCfgObject;
29 /**
30 * Stores allowed values for non-standard fields
31 * @var array
33 private $cfgDb;
35 /**
36 * Keys which will be always written to config file
37 * @var array
39 private $persistKeys = array();
41 /**
42 * Changes keys while updating config in {@link updateWithGlobalConfig()} or reading
43 * by {@link getConfig()} or {@link getConfigArray()}
44 * @var array
46 private $cfgUpdateReadMapping = array();
48 /**
49 * Key filter for {@link set()}
50 * @var array|null
52 private $setFilter;
54 /**
55 * Instance id (key in $_SESSION array, separate for each server - ConfigFile{server id})
56 * @var string
58 private $id;
60 /**
61 * Result for {@link _flattenArray()}
62 * @var array
64 private $_flattenArrayResult;
66 /**
67 * ConfigFile instance
68 * @var ConfigFile
70 private static $_instance;
72 /**
73 * Private constructor, use {@link getInstance()}
75 * @uses PMA_array_write()
77 private function __construct()
79 // load default config values
80 $cfg = &$this->cfg;
81 require './libraries/config.default.php';
82 $cfg['fontsize'] = '82%';
84 // create PMA_Config to read config.inc.php values
85 $this->orgCfgObject = new PMA_Config(CONFIG_FILE);
87 // load additional config information
88 $cfg_db = &$this->cfgDb;
89 require './libraries/config.values.php';
91 // apply default values overrides
92 if (count($cfg_db['_overrides'])) {
93 foreach ($cfg_db['_overrides'] as $path => $value) {
94 PMA_array_write($path, $cfg, $value);
98 $this->id = 'ConfigFile' . $GLOBALS['server'];
99 if (!isset($_SESSION[$this->id])) {
100 $_SESSION[$this->id] = array();
105 * Returns class instance
107 * @return ConfigFile
109 public static function getInstance()
111 if (is_null(self::$_instance)) {
112 self::$_instance = new ConfigFile();
114 return self::$_instance;
118 * Returns PMA_Config without user preferences applied
120 * @return PMA_Config
122 public function getOrgConfigObj()
124 return $this->orgCfgObject;
128 * Sets names of config options which will be placed in config file even if they are set
129 * to their default values (use only full paths)
131 * @param array $keys
133 public function setPersistKeys($keys)
135 // checking key presence is much faster than searching so move values to keys
136 $this->persistKeys = array_flip($keys);
140 * Returns flipped array set by {@link setPersistKeys()}
142 * @return array
144 public function getPersistKeysMap()
146 return $this->persistKeys;
150 * By default ConfigFile allows setting of all configuration keys, use this method
151 * to set up a filter on {@link set()} method
153 * @param array|null $keys array of allowed keys or null to remove filter
155 public function setAllowedKeys($keys)
157 if ($keys === null) {
158 $this->setFilter = null;
159 return;
161 // checking key presence is much faster than searching so move values to keys
162 $this->setFilter = array_flip($keys);
166 * Sets path mapping for updating config in {@link updateWithGlobalConfig()} or reading
167 * by {@link getConfig()} or {@link getConfigArray()}
168 * @var array
170 public function setCfgUpdateReadMapping(array $mapping)
172 $this->cfgUpdateReadMapping = $mapping;
176 * Resets configuration data
178 public function resetConfigData()
180 $_SESSION[$this->id] = array();
184 * Sets configuration data (overrides old data)
186 * @param array $cfg
188 public function setConfigData(array $cfg)
190 $_SESSION[$this->id] = $cfg;
194 * Sets config value
196 * @uses PMA_array_read()
197 * @uses PMA_array_remove()
198 * @uses PMA_array_write()
199 * @param string $path
200 * @param mixed $value
201 * @param string $canonical_path
203 public function set($path, $value, $canonical_path = null)
205 if ($canonical_path === null) {
206 $canonical_path = $this->getCanonicalPath($path);
208 // apply key whitelist
209 if ($this->setFilter !== null && !isset($this->setFilter[$canonical_path])) {
210 return;
212 // remove if the path isn't protected and it's empty or has a default value
213 if (!isset($this->persistKeys[$canonical_path])) {
214 $default_value = $this->getDefault($canonical_path);
215 // we need oryginal config values not overwritten by user preferences
216 // to allow for overwriting options set in config.inc.php with default values
217 $instance_default_value = PMA_array_read($canonical_path, $this->orgCfgObject->settings);
218 if (($value === $default_value && (defined('PMA_SETUP') || $instance_default_value === $default_value))
219 || (empty($value) && empty($default_value) && (defined('PMA_SETUP') || empty($current_global)))) {
220 PMA_array_remove($path, $_SESSION[$this->id]);
221 return;
224 PMA_array_write($path, $_SESSION[$this->id], $value);
228 * Flattens multidimensional array, changes indices to paths (eg. 'key/subkey').
229 * Used as array_walk() callback.
231 * @param mixed $value
232 * @param mixed $key
233 * @param mixed $prefix
235 private function _flattenArray($value, $key, $prefix)
237 // no recursion for numeric arrays
238 if (is_array($value) && !isset($value[0])) {
239 $prefix .= $key . '/';
240 array_walk($value, array($this, '_flattenArray'), $prefix);
241 } else {
242 $this->_flattenArrayResult[$prefix . $key] = $value;
247 * Returns default config in a flattened array
249 * @return array
251 public function getFlatDefaultConfig()
253 $this->_flattenArrayResult = array();
254 array_walk($this->cfg, array($this, '_flattenArray'), '');
255 $flat_cfg = $this->_flattenArrayResult;
256 $this->_flattenArrayResult = null;
257 return $flat_cfg;
261 * Updates config with values read from given array
262 * (config will contain differences to defaults from config.defaults.php).
264 * @param array $cfg
266 public function updateWithGlobalConfig(array $cfg)
268 // load config array and flatten it
269 $this->_flattenArrayResult = array();
270 array_walk($cfg, array($this, '_flattenArray'), '');
271 $flat_cfg = $this->_flattenArrayResult;
272 $this->_flattenArrayResult = null;
274 // save values
275 // map for translating a few user preferences paths, should be complemented
276 // by code reading from generated config to perform inverse mapping
277 foreach ($flat_cfg as $path => $value) {
278 if (isset($this->cfgUpdateReadMapping[$path])) {
279 $path = $this->cfgUpdateReadMapping[$path];
281 $this->set($path, $value, $path);
286 * Returns config value or $default if it's not set
288 * @uses PMA_array_read()
289 * @param string $path
290 * @param mixed $default
291 * @return mixed
293 public function get($path, $default = null)
295 return PMA_array_read($path, $_SESSION[$this->id], $default);
299 * Returns default config value or $default it it's not set ie. it doesn't
300 * exist in config.default.php ($cfg) and config.values.php
301 * ($_cfg_db['_overrides'])
303 * @uses PMA_array_read()
304 * @param string $canonical_path
305 * @param mixed $default
306 * @return mixed
308 public function getDefault($canonical_path, $default = null)
310 return PMA_array_read($canonical_path, $this->cfg, $default);
314 * Returns config value, if it's not set uses the default one; returns
315 * $default if the path isn't set and doesn't contain a default value
317 * @uses PMA_array_read()
318 * @param string $path
319 * @param mixed $default
320 * @return mixed
322 public function getValue($path, $default = null)
324 $v = PMA_array_read($path, $_SESSION[$this->id], null);
325 if ($v !== null) {
326 return $v;
328 $path = $this->getCanonicalPath($path);
329 return $this->getDefault($path, $default);
333 * Returns canonical path
335 * @param string $path
336 * @return string
338 public function getCanonicalPath($path) {
339 return preg_replace('#^Servers/([\d]+)/#', 'Servers/1/', $path);
343 * Returns config database entry for $path ($cfg_db in config_info.php)
345 * @uses PMA_array_read()
346 * @param string $path
347 * @param mixed $default
348 * @return mixed
350 public function getDbEntry($path, $default = null)
352 return PMA_array_read($path, $this->cfgDb, $default);
356 * Returns server count
358 * @return int
360 public function getServerCount()
362 return isset($_SESSION[$this->id]['Servers'])
363 ? count($_SESSION[$this->id]['Servers'])
364 : 0;
368 * Returns server list
370 * @return array|null
372 public function getServers()
374 return isset($_SESSION[$this->id]['Servers'])
375 ? $_SESSION[$this->id]['Servers']
376 : null;
380 * Returns DSN of given server
382 * @param integer $server
383 * @return string
385 function getServerDSN($server)
387 if (!isset($_SESSION[$this->id]['Servers'][$server])) {
388 return '';
391 $path = 'Servers/' . $server;
392 $dsn = $this->getValue("$path/extension") . '://';
393 if ($this->getValue("$path/auth_type") == 'config') {
394 $dsn .= $this->getValue("$path/user");
395 if (!$this->getValue("$path/nopassword")) {
396 $dsn .= ':***';
398 $dsn .= '@';
400 if ($this->getValue("$path/connect_type") == 'tcp') {
401 $dsn .= $this->getValue("$path/host");
402 $port = $this->getValue("$path/port");
403 if ($port) {
404 $dsn .= ':' . $port;
406 } else {
407 $dsn .= $this->getValue("$path/socket");
409 return $dsn;
413 * Returns server name
415 * @param int $id
416 * @return string
418 public function getServerName($id)
420 if (!isset($_SESSION[$this->id]['Servers'][$id])) {
421 return '';
423 $verbose = $this->get("Servers/$id/verbose");
424 if (!empty($verbose)) {
425 return htmlspecialchars($verbose);
427 $host = $this->get("Servers/$id/host");
428 return empty($host) ? 'localhost' : htmlspecialchars($host);
432 * Removes server
434 * @param int $server
436 public function removeServer($server)
438 if (!isset($_SESSION[$this->id]['Servers'][$server])) {
439 return;
441 $last_server = $this->getServerCount();
443 for ($i = $server; $i < $last_server; $i++) {
444 $_SESSION[$this->id]['Servers'][$i] = $_SESSION[$this->id]['Servers'][$i+1];
446 unset($_SESSION[$this->id]['Servers'][$last_server]);
448 if (isset($_SESSION[$this->id]['ServerDefault'])
449 && $_SESSION[$this->id]['ServerDefault'] >= 0) {
450 unset($_SESSION[$this->id]['ServerDefault']);
455 * Returns config file path, relative to phpMyAdmin's root path
457 * @return string
459 public function getFilePath()
461 // Load paths
462 if (!defined('SETUP_CONFIG_FILE')) {
463 require_once './libraries/vendor_config.php';
466 return SETUP_CONFIG_FILE;
470 * Returns configuration array (full, multidimensional format)
472 * @return array
474 public function getConfig()
476 $c = $_SESSION[$this->id];
477 foreach ($this->cfgUpdateReadMapping as $map_to => $map_from) {
478 PMA_array_write($map_to, $c, PMA_array_read($map_from, $c));
479 PMA_array_remove($map_from, $c);
481 return $c;
485 * Returns configuration array (flat format)
487 * @return array
489 public function getConfigArray()
491 $this->_flattenArrayResult = array();
492 array_walk($_SESSION[$this->id], array($this, '_flattenArray'), '');
493 $c = $this->_flattenArrayResult;
494 $this->_flattenArrayResult = null;
496 $persistKeys = array_diff(array_keys($this->persistKeys), array_keys($c));
497 foreach ($persistKeys as $k) {
498 $c[$k] = $this->getDefault($k);
501 foreach ($this->cfgUpdateReadMapping as $map_to => $map_from) {
502 if (!isset($c[$map_from])) {
503 continue;
505 $c[$map_to] = $c[$map_from];
506 unset($c[$map_from]);
508 return $c;