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