Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
89.13% |
82 / 92 |
|
80.00% |
4 / 5 |
CRAP | |
0.00% |
0 / 1 |
| Configuration | |
89.13% |
82 / 92 |
|
80.00% |
4 / 5 |
47.60 | |
0.00% |
0 / 1 |
| __construct | |
87.95% |
73 / 83 |
|
0.00% |
0 / 1 |
41.66 | |||
| get | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getDefaults | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getKey | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
| getSection | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
| 1 | <?php declare(strict_types=1); |
| 2 | /** |
| 3 | * PrivateBin |
| 4 | * |
| 5 | * a zero-knowledge paste bin |
| 6 | * |
| 7 | * @link https://github.com/PrivateBin/PrivateBin |
| 8 | * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net) |
| 9 | * @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License |
| 10 | */ |
| 11 | |
| 12 | namespace PrivateBin; |
| 13 | |
| 14 | use Exception; |
| 15 | |
| 16 | /** |
| 17 | * Configuration |
| 18 | * |
| 19 | * parses configuration file, ensures default values present |
| 20 | */ |
| 21 | class Configuration |
| 22 | { |
| 23 | /** |
| 24 | * parsed configuration |
| 25 | * |
| 26 | * @var array |
| 27 | */ |
| 28 | protected $_configuration; |
| 29 | |
| 30 | /** |
| 31 | * default configuration |
| 32 | * |
| 33 | * @var array |
| 34 | */ |
| 35 | private static $_defaults = array( |
| 36 | 'main' => array( |
| 37 | 'name' => 'PrivateBin', |
| 38 | 'basepath' => '', |
| 39 | 'discussion' => true, |
| 40 | 'opendiscussion' => false, |
| 41 | 'discussiondatedisplay' => true, |
| 42 | 'password' => true, |
| 43 | 'fileupload' => false, |
| 44 | 'burnafterreadingselected' => false, |
| 45 | 'defaultformatter' => 'plaintext', |
| 46 | 'syntaxhighlightingtheme' => '', |
| 47 | 'sizelimit' => 10485760, |
| 48 | 'templateselection' => false, |
| 49 | 'template' => 'bootstrap5', |
| 50 | 'availabletemplates' => array( |
| 51 | 'bootstrap5', |
| 52 | 'bootstrap', |
| 53 | 'bootstrap-page', |
| 54 | 'bootstrap-dark', |
| 55 | 'bootstrap-dark-page', |
| 56 | 'bootstrap-compact', |
| 57 | 'bootstrap-compact-page', |
| 58 | ), |
| 59 | 'info' => 'More information on the <a href=\'https://privatebin.info/\'>project page</a>.', |
| 60 | 'notice' => '', |
| 61 | 'languageselection' => false, |
| 62 | 'languagedefault' => '', |
| 63 | 'urlshortener' => '', |
| 64 | 'shortenbydefault' => false, |
| 65 | 'qrcode' => true, |
| 66 | 'email' => true, |
| 67 | 'icon' => 'jdenticon', |
| 68 | 'cspheader' => 'default-src \'none\'; base-uri \'self\'; form-action \'none\'; manifest-src \'self\'; connect-src * blob:; script-src \'self\' \'wasm-unsafe-eval\'; style-src \'self\'; font-src \'self\'; frame-ancestors \'none\'; frame-src blob:; img-src \'self\' data: blob:; media-src blob:; object-src blob:; sandbox allow-same-origin allow-scripts allow-forms allow-modals allow-downloads', |
| 69 | 'httpwarning' => true, |
| 70 | 'compression' => 'zlib', |
| 71 | ), |
| 72 | 'expire' => array( |
| 73 | 'default' => '1week', |
| 74 | ), |
| 75 | 'expire_options' => array( |
| 76 | '5min' => 300, |
| 77 | '10min' => 600, |
| 78 | '1hour' => 3600, |
| 79 | '1day' => 86400, |
| 80 | '1week' => 604800, |
| 81 | '1month' => 2592000, |
| 82 | '1year' => 31536000, |
| 83 | 'never' => 0, |
| 84 | ), |
| 85 | 'formatter_options' => array( |
| 86 | 'plaintext' => 'Plain Text', |
| 87 | 'syntaxhighlighting' => 'Source Code', |
| 88 | 'markdown' => 'Markdown', |
| 89 | ), |
| 90 | 'traffic' => array( |
| 91 | 'limit' => 10, |
| 92 | 'header' => '', |
| 93 | 'exempted' => '', |
| 94 | 'creators' => '', |
| 95 | ), |
| 96 | 'purge' => array( |
| 97 | 'limit' => 300, |
| 98 | 'batchsize' => 10, |
| 99 | ), |
| 100 | 'model' => array( |
| 101 | 'class' => 'Filesystem', |
| 102 | ), |
| 103 | 'model_options' => array( |
| 104 | 'dir' => 'data', |
| 105 | ), |
| 106 | 'yourls' => array( |
| 107 | 'signature' => '', |
| 108 | 'apiurl' => '', |
| 109 | ), |
| 110 | 'shlink' => array( |
| 111 | 'apikey' => '', |
| 112 | 'apiurl' => '', |
| 113 | ), |
| 114 | // update this array when adding/changing/removing js files |
| 115 | 'sri' => array( |
| 116 | 'js/base-x-5.0.1.js' => 'sha512-FmhlnjIxQyxkkxQmzf0l6IRGsGbgyCdgqPxypFsEtHMF1naRqaLLo6mcyN5rEaT16nKx1PeJ4g7+07D6gnk/Tg==', |
| 117 | 'js/bootstrap-3.4.1.js' => 'sha512-oBTprMeNEKCnqfuqKd6sbvFzmFQtlXS3e0C/RGFV0hD6QzhHV+ODfaQbAlmY6/q0ubbwlAM/nCJjkrgA3waLzg==', |
| 118 | 'js/bootstrap-5.3.8.js' => 'sha512-BkZvJ5rZ3zbDCod5seWHpRGg+PRd6ZgE8Nua/OMtcxqm8Wtg0PqwhUUXK5bqvl3oclMt5O+3zjRVX0L+L2j7fA==', |
| 119 | 'js/dark-mode-switch.js' => 'sha512-BhY7dNU14aDN5L+muoUmA66x0CkYUWkQT0nxhKBLP/o2d7jE025+dvWJa4OiYffBGEFgmhrD/Sp+QMkxGMTz2g==', |
| 120 | 'js/jquery-3.7.1.js' => 'sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g==', |
| 121 | 'js/kjua-0.10.0.js' => 'sha512-BYj4xggowR7QD150VLSTRlzH62YPfhpIM+b/1EUEr7RQpdWAGKulxWnOvjFx1FUlba4m6ihpNYuQab51H6XlYg==', |
| 122 | 'js/legacy.js' => 'sha512-rGXYUpIqbFoHAgBXZ0UlJBdNAIMOC9EQ67MG0X46D5uRB8LvwzgKirbSQRGdYfk8I2jsUcm+tvHXYboUnC6DUg==', |
| 123 | 'js/prettify.js' => 'sha512-puO0Ogy++IoA2Pb9IjSxV1n4+kQkKXYAEUtVzfZpQepyDPyXk8hokiYDS7ybMogYlyyEIwMLpZqVhCkARQWLMg==', |
| 124 | 'js/privatebin.js' => 'sha512-C9Mc6qgEHhaMKC+VzN7Hp8C77HVm8cD5N/AMlP6qkaYj/QLZ0HdtYfOMWrXNn9i83MbqkRD//DnM7bHHEixzIg==', |
| 125 | 'js/purify-3.3.0.js' => 'sha512-lsHD5zxs4lu/NDzaaibe27Vd2t7Cy9JQ3qDHUvDfb4oZvKoWDNEhwUY+4bT3R68cGgpgCYp8U1x2ifeVxqurdQ==', |
| 126 | 'js/showdown-2.1.0.js' => 'sha512-WYXZgkTR0u/Y9SVIA4nTTOih0kXMEd8RRV6MLFdL6YU8ymhR528NLlYQt1nlJQbYz4EW+ZsS0fx1awhiQJme1Q==', |
| 127 | 'js/zlib-1.3.1-1.js' => 'sha512-5bU9IIP4PgBrOKLZvGWJD4kgfQrkTz8Z3Iqeu058mbQzW3mCumOU6M3UVbVZU9rrVoVwaW4cZK8U8h5xjF88eQ==', |
| 128 | ), |
| 129 | ); |
| 130 | |
| 131 | /** |
| 132 | * parse configuration file and ensure default configuration values are present |
| 133 | * |
| 134 | * @throws Exception |
| 135 | */ |
| 136 | public function __construct() |
| 137 | { |
| 138 | $basePaths = array(); |
| 139 | $config = array(); |
| 140 | $configPath = getenv('CONFIG_PATH'); |
| 141 | if ($configPath !== false && !empty($configPath)) { |
| 142 | $basePaths[] = $configPath; |
| 143 | } |
| 144 | $basePaths[] = PATH . 'cfg'; |
| 145 | foreach ($basePaths as $basePath) { |
| 146 | $configFile = $basePath . DIRECTORY_SEPARATOR . 'conf.php'; |
| 147 | if (is_readable($configFile)) { |
| 148 | $config = parse_ini_file($configFile, true); |
| 149 | foreach (array('main', 'model', 'model_options') as $section) { |
| 150 | if (!array_key_exists($section, $config)) { |
| 151 | throw new Exception(I18n::_('PrivateBin requires configuration section [%s] to be present in configuration file.', $section), 2); |
| 152 | } |
| 153 | } |
| 154 | break; |
| 155 | } |
| 156 | } |
| 157 | |
| 158 | $opts = '_options'; |
| 159 | foreach (self::getDefaults() as $section => $values) { |
| 160 | // fill missing sections with default values |
| 161 | if (!array_key_exists($section, $config) || count($config[$section]) == 0) { |
| 162 | $this->_configuration[$section] = $values; |
| 163 | if (array_key_exists('dir', $this->_configuration[$section])) { |
| 164 | $this->_configuration[$section]['dir'] = PATH . $this->_configuration[$section]['dir']; |
| 165 | } |
| 166 | continue; |
| 167 | } |
| 168 | // provide different defaults for database model |
| 169 | elseif ( |
| 170 | $section == 'model_options' && |
| 171 | $this->_configuration['model']['class'] === 'Database' |
| 172 | ) { |
| 173 | $values = array( |
| 174 | 'dsn' => 'sqlite:' . PATH . 'data' . DIRECTORY_SEPARATOR . 'db.sq3', |
| 175 | 'tbl' => null, |
| 176 | 'usr' => null, |
| 177 | 'pwd' => null, |
| 178 | 'opt' => array(), |
| 179 | ); |
| 180 | } elseif ( |
| 181 | $section == 'model_options' && |
| 182 | $this->_configuration['model']['class'] === 'GoogleCloudStorage' |
| 183 | ) { |
| 184 | $values = array( |
| 185 | 'bucket' => getenv('PRIVATEBIN_GCS_BUCKET') ? getenv('PRIVATEBIN_GCS_BUCKET') : null, |
| 186 | 'prefix' => 'pastes', |
| 187 | 'uniformacl' => false, |
| 188 | ); |
| 189 | } elseif ( |
| 190 | $section == 'model_options' && |
| 191 | $this->_configuration['model']['class'] === 'S3Storage' |
| 192 | ) { |
| 193 | $values = array( |
| 194 | 'region' => null, |
| 195 | 'version' => null, |
| 196 | 'endpoint' => null, |
| 197 | 'accesskey' => null, |
| 198 | 'secretkey' => null, |
| 199 | 'use_path_style_endpoint' => null, |
| 200 | 'bucket' => null, |
| 201 | 'prefix' => '', |
| 202 | ); |
| 203 | } |
| 204 | |
| 205 | // "*_options" sections don't require all defaults to be set |
| 206 | if ( |
| 207 | $section !== 'model_options' && |
| 208 | ($from = strlen($section) - strlen($opts)) >= 0 && |
| 209 | strpos($section, $opts, $from) !== false |
| 210 | ) { |
| 211 | if (is_int(current($values))) { |
| 212 | $config[$section] = array_map('intval', $config[$section]); |
| 213 | } |
| 214 | $this->_configuration[$section] = $config[$section]; |
| 215 | } |
| 216 | // check for missing keys and set defaults if necessary |
| 217 | else { |
| 218 | // preserve configured SRI hashes |
| 219 | if ($section == 'sri' && array_key_exists($section, $config)) { |
| 220 | $this->_configuration[$section] = $config[$section]; |
| 221 | } |
| 222 | foreach ($values as $key => $val) { |
| 223 | if ($key == 'dir') { |
| 224 | $val = PATH . $val; |
| 225 | } |
| 226 | $result = $val; |
| 227 | if (array_key_exists($key, $config[$section])) { |
| 228 | if ($val === null) { |
| 229 | $result = $config[$section][$key]; |
| 230 | } elseif (is_bool($val)) { |
| 231 | $val = strtolower($config[$section][$key]); |
| 232 | if (in_array($val, array('true', 'yes', 'on'))) { |
| 233 | $result = true; |
| 234 | } elseif (in_array($val, array('false', 'no', 'off'))) { |
| 235 | $result = false; |
| 236 | } else { |
| 237 | $result = (bool) $config[$section][$key]; |
| 238 | } |
| 239 | } elseif (is_int($val)) { |
| 240 | $result = (int) $config[$section][$key]; |
| 241 | } elseif (is_string($val) && !empty($config[$section][$key])) { |
| 242 | $result = (string) $config[$section][$key]; |
| 243 | } elseif (is_array($val) && is_array($config[$section][$key])) { |
| 244 | $result = $config[$section][$key]; |
| 245 | } |
| 246 | } |
| 247 | $this->_configuration[$section][$key] = $result; |
| 248 | } |
| 249 | } |
| 250 | } |
| 251 | |
| 252 | // ensure a valid expire default key is set |
| 253 | if (!array_key_exists($this->_configuration['expire']['default'], $this->_configuration['expire_options'])) { |
| 254 | $this->_configuration['expire']['default'] = key($this->_configuration['expire_options']); |
| 255 | } |
| 256 | |
| 257 | // ensure the basepath ends in a slash, if one is set |
| 258 | if ( |
| 259 | !empty($this->_configuration['main']['basepath']) && |
| 260 | substr_compare($this->_configuration['main']['basepath'], '/', -1) !== 0 |
| 261 | ) { |
| 262 | $this->_configuration['main']['basepath'] .= '/'; |
| 263 | } |
| 264 | } |
| 265 | |
| 266 | /** |
| 267 | * get configuration as array |
| 268 | * |
| 269 | * @return array |
| 270 | */ |
| 271 | public function get() |
| 272 | { |
| 273 | return $this->_configuration; |
| 274 | } |
| 275 | |
| 276 | /** |
| 277 | * get default configuration as array |
| 278 | * |
| 279 | * @return array |
| 280 | */ |
| 281 | public static function getDefaults() |
| 282 | { |
| 283 | return self::$_defaults; |
| 284 | } |
| 285 | |
| 286 | /** |
| 287 | * get a key from the configuration, typically the main section or all keys |
| 288 | * |
| 289 | * @param string $key |
| 290 | * @param string $section defaults to main |
| 291 | * @throws Exception |
| 292 | * @return mixed |
| 293 | */ |
| 294 | public function getKey($key, $section = 'main') |
| 295 | { |
| 296 | $options = $this->getSection($section); |
| 297 | if (!array_key_exists($key, $options)) { |
| 298 | throw new Exception(I18n::_('Invalid data.') . " $section / $key", 4); |
| 299 | } |
| 300 | return $this->_configuration[$section][$key]; |
| 301 | } |
| 302 | |
| 303 | /** |
| 304 | * get a section from the configuration, must exist |
| 305 | * |
| 306 | * @param string $section |
| 307 | * @throws Exception |
| 308 | * @return mixed |
| 309 | */ |
| 310 | public function getSection($section) |
| 311 | { |
| 312 | if (!array_key_exists($section, $this->_configuration)) { |
| 313 | throw new Exception(I18n::_('%s requires configuration section [%s] to be present in configuration file.', I18n::_($this->getKey('name')), $section), 3); |
| 314 | } |
| 315 | return $this->_configuration[$section]; |
| 316 | } |
| 317 | } |