Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
90.65% |
97 / 107 |
|
80.00% |
4 / 5 |
CRAP | |
0.00% |
0 / 1 |
Configuration | |
90.65% |
97 / 107 |
|
80.00% |
4 / 5 |
46.65 | |
0.00% |
0 / 1 |
__construct | |
89.80% |
88 / 98 |
|
0.00% |
0 / 1 |
40.62 | |||
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 | private $_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 | 'template' => 'bootstrap', |
49 | 'info' => 'More information on the <a href=\'https://privatebin.info/\'>project page</a>.', |
50 | 'notice' => '', |
51 | 'languageselection' => false, |
52 | 'languagedefault' => '', |
53 | 'urlshortener' => '', |
54 | 'qrcode' => true, |
55 | 'email' => true, |
56 | 'icon' => 'identicon', |
57 | '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\'; img-src \'self\' data: blob:; media-src blob:; object-src blob:; sandbox allow-same-origin allow-scripts allow-forms allow-popups allow-modals allow-downloads', |
58 | 'zerobincompatibility' => false, |
59 | 'httpwarning' => true, |
60 | 'compression' => 'zlib', |
61 | ), |
62 | 'expire' => array( |
63 | 'default' => '1week', |
64 | ), |
65 | 'expire_options' => array( |
66 | '5min' => 300, |
67 | '10min' => 600, |
68 | '1hour' => 3600, |
69 | '1day' => 86400, |
70 | '1week' => 604800, |
71 | '1month' => 2592000, |
72 | '1year' => 31536000, |
73 | 'never' => 0, |
74 | ), |
75 | 'formatter_options' => array( |
76 | 'plaintext' => 'Plain Text', |
77 | 'syntaxhighlighting' => 'Source Code', |
78 | 'markdown' => 'Markdown', |
79 | ), |
80 | 'traffic' => array( |
81 | 'limit' => 10, |
82 | 'header' => '', |
83 | 'exempted' => '', |
84 | 'creators' => '', |
85 | ), |
86 | 'purge' => array( |
87 | 'limit' => 300, |
88 | 'batchsize' => 10, |
89 | ), |
90 | 'model' => array( |
91 | 'class' => 'Filesystem', |
92 | ), |
93 | 'model_options' => array( |
94 | 'dir' => 'data', |
95 | ), |
96 | 'yourls' => array( |
97 | 'signature' => '', |
98 | 'apiurl' => '', |
99 | ), |
100 | // update this array when adding/changing/removing js files |
101 | 'sri' => array( |
102 | 'js/base-x-4.0.0.js' => 'sha512-nNPg5IGCwwrveZ8cA/yMGr5HiRS5Ps2H+s0J/mKTPjCPWUgFGGw7M5nqdnPD3VsRwCVysUh3Y8OWjeSKGkEQJQ==', |
103 | 'js/base64-1.7.js' => 'sha512-JdwsSP3GyHR+jaCkns9CL9NTt4JUJqm/BsODGmYhBcj5EAPKcHYh+OiMfyHbcDLECe17TL0hjXADFkusAqiYgA==', |
104 | 'js/bootstrap-3.4.1.js' => 'sha512-oBTprMeNEKCnqfuqKd6sbvFzmFQtlXS3e0C/RGFV0hD6QzhHV+ODfaQbAlmY6/q0ubbwlAM/nCJjkrgA3waLzg==', |
105 | 'js/bootstrap-5.3.3.js' => 'sha512-in2rcOpLTdJ7/pw5qjF4LWHFRtgoBDxXCy49H4YGOcVdGiPaQucGIbOqxt1JvmpvOpq3J/C7VTa0FlioakB2gQ==', |
106 | 'js/dark-mode-switch.js' => 'sha512-BhY7dNU14aDN5L+muoUmA66x0CkYUWkQT0nxhKBLP/o2d7jE025+dvWJa4OiYffBGEFgmhrD/Sp+QMkxGMTz2g==', |
107 | 'js/jquery-3.7.1.js' => 'sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g==', |
108 | 'js/kjua-0.9.0.js' => 'sha512-CVn7af+vTMBd9RjoS4QM5fpLFEOtBCoB0zPtaqIDC7sF4F8qgUSRFQQpIyEDGsr6yrjbuOLzdf20tkHHmpaqwQ==', |
109 | 'js/legacy.js' => 'sha512-UxW/TOZKon83n6dk/09GsYKIyeO5LeBHokxyIq+r7KFS5KMBeIB/EM7NrkVYIezwZBaovnyNtY2d9tKFicRlXg==', |
110 | 'js/prettify.js' => 'sha512-puO0Ogy++IoA2Pb9IjSxV1n4+kQkKXYAEUtVzfZpQepyDPyXk8hokiYDS7ybMogYlyyEIwMLpZqVhCkARQWLMg==', |
111 | 'js/privatebin.js' => 'sha512-POa+8KNXFFwJFsqp7r9APmR5Rc1w2l363y+OScSzLCySrHN7UhOOgt1VH/o8mVddFvvUozj3FZVmdkTxRlrS5g==', |
112 | 'js/purify-3.2.4.js' => 'sha512-Mu9BqoHURMeycg6AgqTpokUv9guq88pajfaFqz53fx1OxohyROkydXPLEIbdKCQ7EdDs9hgcrYeZ9zTiPQQ4CA==', |
113 | 'js/rawinflate-0.3.js' => 'sha512-g8uelGgJW9A/Z1tB6Izxab++oj5kdD7B4qC7DHwZkB6DGMXKyzx7v5mvap2HXueI2IIn08YlRYM56jwWdm2ucQ==', |
114 | 'js/showdown-2.1.0.js' => 'sha512-WYXZgkTR0u/Y9SVIA4nTTOih0kXMEd8RRV6MLFdL6YU8ymhR528NLlYQt1nlJQbYz4EW+ZsS0fx1awhiQJme1Q==', |
115 | 'js/zlib-1.3.1.js' => 'sha512-5bU9IIP4PgBrOKLZvGWJD4kgfQrkTz8Z3Iqeu058mbQzW3mCumOU6M3UVbVZU9rrVoVwaW4cZK8U8h5xjF88eQ==', |
116 | ), |
117 | ); |
118 | |
119 | /** |
120 | * parse configuration file and ensure default configuration values are present |
121 | * |
122 | * @throws Exception |
123 | */ |
124 | public function __construct() |
125 | { |
126 | $basePaths = array(); |
127 | $config = array(); |
128 | $configPath = getenv('CONFIG_PATH'); |
129 | if ($configPath !== false && !empty($configPath)) { |
130 | $basePaths[] = $configPath; |
131 | } |
132 | $basePaths[] = PATH . 'cfg'; |
133 | foreach ($basePaths as $basePath) { |
134 | $configFile = $basePath . DIRECTORY_SEPARATOR . 'conf.php'; |
135 | if (is_readable($configFile)) { |
136 | $config = parse_ini_file($configFile, true); |
137 | foreach (array('main', 'model', 'model_options') as $section) { |
138 | if (!array_key_exists($section, $config)) { |
139 | throw new Exception(I18n::_('PrivateBin requires configuration section [%s] to be present in configuration file.', $section), 2); |
140 | } |
141 | } |
142 | break; |
143 | } |
144 | } |
145 | |
146 | $opts = '_options'; |
147 | foreach (self::getDefaults() as $section => $values) { |
148 | // fill missing sections with default values |
149 | if (!array_key_exists($section, $config) || count($config[$section]) == 0) { |
150 | $this->_configuration[$section] = $values; |
151 | if (array_key_exists('dir', $this->_configuration[$section])) { |
152 | $this->_configuration[$section]['dir'] = PATH . $this->_configuration[$section]['dir']; |
153 | } |
154 | continue; |
155 | } |
156 | // provide different defaults for database model |
157 | elseif ( |
158 | $section == 'model_options' && in_array( |
159 | $this->_configuration['model']['class'], |
160 | array('Database', 'privatebin_db', 'zerobin_db') |
161 | ) |
162 | ) { |
163 | $values = array( |
164 | 'dsn' => 'sqlite:' . PATH . 'data' . DIRECTORY_SEPARATOR . 'db.sq3', |
165 | 'tbl' => null, |
166 | 'usr' => null, |
167 | 'pwd' => null, |
168 | 'opt' => array(), |
169 | ); |
170 | } elseif ( |
171 | $section == 'model_options' && in_array( |
172 | $this->_configuration['model']['class'], |
173 | array('GoogleCloudStorage') |
174 | ) |
175 | ) { |
176 | $values = array( |
177 | 'bucket' => getenv('PRIVATEBIN_GCS_BUCKET') ? getenv('PRIVATEBIN_GCS_BUCKET') : null, |
178 | 'prefix' => 'pastes', |
179 | 'uniformacl' => false, |
180 | ); |
181 | } elseif ( |
182 | $section == 'model_options' && in_array( |
183 | $this->_configuration['model']['class'], |
184 | array('S3Storage') |
185 | ) |
186 | ) { |
187 | $values = array( |
188 | 'region' => null, |
189 | 'version' => null, |
190 | 'endpoint' => null, |
191 | 'accesskey' => null, |
192 | 'secretkey' => null, |
193 | 'use_path_style_endpoint' => null, |
194 | 'bucket' => null, |
195 | 'prefix' => '', |
196 | ); |
197 | } |
198 | |
199 | // "*_options" sections don't require all defaults to be set |
200 | if ( |
201 | $section !== 'model_options' && |
202 | ($from = strlen($section) - strlen($opts)) >= 0 && |
203 | strpos($section, $opts, $from) !== false |
204 | ) { |
205 | if (is_int(current($values))) { |
206 | $config[$section] = array_map('intval', $config[$section]); |
207 | } |
208 | $this->_configuration[$section] = $config[$section]; |
209 | } |
210 | // check for missing keys and set defaults if necessary |
211 | else { |
212 | // preserve configured SRI hashes |
213 | if ($section == 'sri' && array_key_exists($section, $config)) { |
214 | $this->_configuration[$section] = $config[$section]; |
215 | } |
216 | foreach ($values as $key => $val) { |
217 | if ($key == 'dir') { |
218 | $val = PATH . $val; |
219 | } |
220 | $result = $val; |
221 | if (array_key_exists($key, $config[$section])) { |
222 | if ($val === null) { |
223 | $result = $config[$section][$key]; |
224 | } elseif (is_bool($val)) { |
225 | $val = strtolower($config[$section][$key]); |
226 | if (in_array($val, array('true', 'yes', 'on'))) { |
227 | $result = true; |
228 | } elseif (in_array($val, array('false', 'no', 'off'))) { |
229 | $result = false; |
230 | } else { |
231 | $result = (bool) $config[$section][$key]; |
232 | } |
233 | } elseif (is_int($val)) { |
234 | $result = (int) $config[$section][$key]; |
235 | } elseif (is_string($val) && !empty($config[$section][$key])) { |
236 | $result = (string) $config[$section][$key]; |
237 | } elseif (is_array($val) && is_array($config[$section][$key])) { |
238 | $result = $config[$section][$key]; |
239 | } |
240 | } |
241 | $this->_configuration[$section][$key] = $result; |
242 | } |
243 | } |
244 | } |
245 | |
246 | // support for old config file format, before the fork was renamed and PSR-4 introduced |
247 | $this->_configuration['model']['class'] = str_replace( |
248 | 'zerobin_', 'privatebin_', |
249 | $this->_configuration['model']['class'] |
250 | ); |
251 | |
252 | $this->_configuration['model']['class'] = str_replace( |
253 | array('privatebin_data', 'privatebin_db'), |
254 | array('Filesystem', 'Database'), |
255 | $this->_configuration['model']['class'] |
256 | ); |
257 | |
258 | // ensure a valid expire default key is set |
259 | if (!array_key_exists($this->_configuration['expire']['default'], $this->_configuration['expire_options'])) { |
260 | $this->_configuration['expire']['default'] = key($this->_configuration['expire_options']); |
261 | } |
262 | |
263 | // ensure the basepath ends in a slash, if one is set |
264 | if ( |
265 | !empty($this->_configuration['main']['basepath']) && |
266 | substr_compare($this->_configuration['main']['basepath'], '/', -1) !== 0 |
267 | ) { |
268 | $this->_configuration['main']['basepath'] .= '/'; |
269 | } |
270 | } |
271 | |
272 | /** |
273 | * get configuration as array |
274 | * |
275 | * @return array |
276 | */ |
277 | public function get() |
278 | { |
279 | return $this->_configuration; |
280 | } |
281 | |
282 | /** |
283 | * get default configuration as array |
284 | * |
285 | * @return array |
286 | */ |
287 | public static function getDefaults() |
288 | { |
289 | return self::$_defaults; |
290 | } |
291 | |
292 | /** |
293 | * get a key from the configuration, typically the main section or all keys |
294 | * |
295 | * @param string $key |
296 | * @param string $section defaults to main |
297 | * @throws Exception |
298 | * @return mixed |
299 | */ |
300 | public function getKey($key, $section = 'main') |
301 | { |
302 | $options = $this->getSection($section); |
303 | if (!array_key_exists($key, $options)) { |
304 | throw new Exception(I18n::_('Invalid data.') . " $section / $key", 4); |
305 | } |
306 | return $this->_configuration[$section][$key]; |
307 | } |
308 | |
309 | /** |
310 | * get a section from the configuration, must exist |
311 | * |
312 | * @param string $section |
313 | * @throws Exception |
314 | * @return mixed |
315 | */ |
316 | public function getSection($section) |
317 | { |
318 | if (!array_key_exists($section, $this->_configuration)) { |
319 | throw new Exception(I18n::_('%s requires configuration section [%s] to be present in configuration file.', I18n::_($this->getKey('name')), $section), 3); |
320 | } |
321 | return $this->_configuration[$section]; |
322 | } |
323 | } |