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 | 'templateselection' => false, |
49 | 'template' => 'bootstrap', |
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 | '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 | 'qrcode' => true, |
66 | 'email' => true, |
67 | 'icon' => 'identicon', |
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-popups allow-modals allow-downloads', |
69 | 'zerobincompatibility' => false, |
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 | // update this array when adding/changing/removing js files |
112 | 'sri' => array( |
113 | 'js/base-x-4.0.0.js' => 'sha512-nNPg5IGCwwrveZ8cA/yMGr5HiRS5Ps2H+s0J/mKTPjCPWUgFGGw7M5nqdnPD3VsRwCVysUh3Y8OWjeSKGkEQJQ==', |
114 | 'js/base64-1.7.js' => 'sha512-JdwsSP3GyHR+jaCkns9CL9NTt4JUJqm/BsODGmYhBcj5EAPKcHYh+OiMfyHbcDLECe17TL0hjXADFkusAqiYgA==', |
115 | 'js/bootstrap-3.4.1.js' => 'sha512-oBTprMeNEKCnqfuqKd6sbvFzmFQtlXS3e0C/RGFV0hD6QzhHV+ODfaQbAlmY6/q0ubbwlAM/nCJjkrgA3waLzg==', |
116 | 'js/bootstrap-5.3.3.js' => 'sha512-in2rcOpLTdJ7/pw5qjF4LWHFRtgoBDxXCy49H4YGOcVdGiPaQucGIbOqxt1JvmpvOpq3J/C7VTa0FlioakB2gQ==', |
117 | 'js/dark-mode-switch.js' => 'sha512-BhY7dNU14aDN5L+muoUmA66x0CkYUWkQT0nxhKBLP/o2d7jE025+dvWJa4OiYffBGEFgmhrD/Sp+QMkxGMTz2g==', |
118 | 'js/jquery-3.7.1.js' => 'sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g==', |
119 | 'js/kjua-0.9.0.js' => 'sha512-CVn7af+vTMBd9RjoS4QM5fpLFEOtBCoB0zPtaqIDC7sF4F8qgUSRFQQpIyEDGsr6yrjbuOLzdf20tkHHmpaqwQ==', |
120 | 'js/legacy.js' => 'sha512-UxW/TOZKon83n6dk/09GsYKIyeO5LeBHokxyIq+r7KFS5KMBeIB/EM7NrkVYIezwZBaovnyNtY2d9tKFicRlXg==', |
121 | 'js/prettify.js' => 'sha512-puO0Ogy++IoA2Pb9IjSxV1n4+kQkKXYAEUtVzfZpQepyDPyXk8hokiYDS7ybMogYlyyEIwMLpZqVhCkARQWLMg==', |
122 | 'js/privatebin.js' => 'sha512-zvJ6Feu2NvROB236BBxbP+8eYbUTJ5GCfhOJVL/RI6pJQpR3AS4ps0d1cVDqgUFW8wY0tiwE7JTE13gPWO3lHA==', |
123 | 'js/purify-3.2.6.js' => 'sha512-zqwL4OoBLFx89QPewkz4Lz5CSA2ktU+f31fuECkF0iK3Id5qd3Zpq5dMby8KwHjIEpsUgOqwF58cnmcaNem0EA==', |
124 | 'js/rawinflate-0.3.js' => 'sha512-g8uelGgJW9A/Z1tB6Izxab++oj5kdD7B4qC7DHwZkB6DGMXKyzx7v5mvap2HXueI2IIn08YlRYM56jwWdm2ucQ==', |
125 | 'js/showdown-2.1.0.js' => 'sha512-WYXZgkTR0u/Y9SVIA4nTTOih0kXMEd8RRV6MLFdL6YU8ymhR528NLlYQt1nlJQbYz4EW+ZsS0fx1awhiQJme1Q==', |
126 | 'js/zlib-1.3.1-1.js' => 'sha512-5bU9IIP4PgBrOKLZvGWJD4kgfQrkTz8Z3Iqeu058mbQzW3mCumOU6M3UVbVZU9rrVoVwaW4cZK8U8h5xjF88eQ==', |
127 | ), |
128 | ); |
129 | |
130 | /** |
131 | * parse configuration file and ensure default configuration values are present |
132 | * |
133 | * @throws Exception |
134 | */ |
135 | public function __construct() |
136 | { |
137 | $basePaths = array(); |
138 | $config = array(); |
139 | $configPath = getenv('CONFIG_PATH'); |
140 | if ($configPath !== false && !empty($configPath)) { |
141 | $basePaths[] = $configPath; |
142 | } |
143 | $basePaths[] = PATH . 'cfg'; |
144 | foreach ($basePaths as $basePath) { |
145 | $configFile = $basePath . DIRECTORY_SEPARATOR . 'conf.php'; |
146 | if (is_readable($configFile)) { |
147 | $config = parse_ini_file($configFile, true); |
148 | foreach (array('main', 'model', 'model_options') as $section) { |
149 | if (!array_key_exists($section, $config)) { |
150 | throw new Exception(I18n::_('PrivateBin requires configuration section [%s] to be present in configuration file.', $section), 2); |
151 | } |
152 | } |
153 | break; |
154 | } |
155 | } |
156 | |
157 | $opts = '_options'; |
158 | foreach (self::getDefaults() as $section => $values) { |
159 | // fill missing sections with default values |
160 | if (!array_key_exists($section, $config) || count($config[$section]) == 0) { |
161 | $this->_configuration[$section] = $values; |
162 | if (array_key_exists('dir', $this->_configuration[$section])) { |
163 | $this->_configuration[$section]['dir'] = PATH . $this->_configuration[$section]['dir']; |
164 | } |
165 | continue; |
166 | } |
167 | // provide different defaults for database model |
168 | elseif ( |
169 | $section == 'model_options' && in_array( |
170 | $this->_configuration['model']['class'], |
171 | array('Database', 'privatebin_db', 'zerobin_db') |
172 | ) |
173 | ) { |
174 | $values = array( |
175 | 'dsn' => 'sqlite:' . PATH . 'data' . DIRECTORY_SEPARATOR . 'db.sq3', |
176 | 'tbl' => null, |
177 | 'usr' => null, |
178 | 'pwd' => null, |
179 | 'opt' => array(), |
180 | ); |
181 | } elseif ( |
182 | $section == 'model_options' && in_array( |
183 | $this->_configuration['model']['class'], |
184 | array('GoogleCloudStorage') |
185 | ) |
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' && in_array( |
194 | $this->_configuration['model']['class'], |
195 | array('S3Storage') |
196 | ) |
197 | ) { |
198 | $values = array( |
199 | 'region' => null, |
200 | 'version' => null, |
201 | 'endpoint' => null, |
202 | 'accesskey' => null, |
203 | 'secretkey' => null, |
204 | 'use_path_style_endpoint' => null, |
205 | 'bucket' => null, |
206 | 'prefix' => '', |
207 | ); |
208 | } |
209 | |
210 | // "*_options" sections don't require all defaults to be set |
211 | if ( |
212 | $section !== 'model_options' && |
213 | ($from = strlen($section) - strlen($opts)) >= 0 && |
214 | strpos($section, $opts, $from) !== false |
215 | ) { |
216 | if (is_int(current($values))) { |
217 | $config[$section] = array_map('intval', $config[$section]); |
218 | } |
219 | $this->_configuration[$section] = $config[$section]; |
220 | } |
221 | // check for missing keys and set defaults if necessary |
222 | else { |
223 | // preserve configured SRI hashes |
224 | if ($section == 'sri' && array_key_exists($section, $config)) { |
225 | $this->_configuration[$section] = $config[$section]; |
226 | } |
227 | foreach ($values as $key => $val) { |
228 | if ($key == 'dir') { |
229 | $val = PATH . $val; |
230 | } |
231 | $result = $val; |
232 | if (array_key_exists($key, $config[$section])) { |
233 | if ($val === null) { |
234 | $result = $config[$section][$key]; |
235 | } elseif (is_bool($val)) { |
236 | $val = strtolower($config[$section][$key]); |
237 | if (in_array($val, array('true', 'yes', 'on'))) { |
238 | $result = true; |
239 | } elseif (in_array($val, array('false', 'no', 'off'))) { |
240 | $result = false; |
241 | } else { |
242 | $result = (bool) $config[$section][$key]; |
243 | } |
244 | } elseif (is_int($val)) { |
245 | $result = (int) $config[$section][$key]; |
246 | } elseif (is_string($val) && !empty($config[$section][$key])) { |
247 | $result = (string) $config[$section][$key]; |
248 | } elseif (is_array($val) && is_array($config[$section][$key])) { |
249 | $result = $config[$section][$key]; |
250 | } |
251 | } |
252 | $this->_configuration[$section][$key] = $result; |
253 | } |
254 | } |
255 | } |
256 | |
257 | // support for old config file format, before the fork was renamed and PSR-4 introduced |
258 | $this->_configuration['model']['class'] = str_replace( |
259 | 'zerobin_', 'privatebin_', |
260 | $this->_configuration['model']['class'] |
261 | ); |
262 | |
263 | $this->_configuration['model']['class'] = str_replace( |
264 | array('privatebin_data', 'privatebin_db'), |
265 | array('Filesystem', 'Database'), |
266 | $this->_configuration['model']['class'] |
267 | ); |
268 | |
269 | // ensure a valid expire default key is set |
270 | if (!array_key_exists($this->_configuration['expire']['default'], $this->_configuration['expire_options'])) { |
271 | $this->_configuration['expire']['default'] = key($this->_configuration['expire_options']); |
272 | } |
273 | |
274 | // ensure the basepath ends in a slash, if one is set |
275 | if ( |
276 | !empty($this->_configuration['main']['basepath']) && |
277 | substr_compare($this->_configuration['main']['basepath'], '/', -1) !== 0 |
278 | ) { |
279 | $this->_configuration['main']['basepath'] .= '/'; |
280 | } |
281 | } |
282 | |
283 | /** |
284 | * get configuration as array |
285 | * |
286 | * @return array |
287 | */ |
288 | public function get() |
289 | { |
290 | return $this->_configuration; |
291 | } |
292 | |
293 | /** |
294 | * get default configuration as array |
295 | * |
296 | * @return array |
297 | */ |
298 | public static function getDefaults() |
299 | { |
300 | return self::$_defaults; |
301 | } |
302 | |
303 | /** |
304 | * get a key from the configuration, typically the main section or all keys |
305 | * |
306 | * @param string $key |
307 | * @param string $section defaults to main |
308 | * @throws Exception |
309 | * @return mixed |
310 | */ |
311 | public function getKey($key, $section = 'main') |
312 | { |
313 | $options = $this->getSection($section); |
314 | if (!array_key_exists($key, $options)) { |
315 | throw new Exception(I18n::_('Invalid data.') . " $section / $key", 4); |
316 | } |
317 | return $this->_configuration[$section][$key]; |
318 | } |
319 | |
320 | /** |
321 | * get a section from the configuration, must exist |
322 | * |
323 | * @param string $section |
324 | * @throws Exception |
325 | * @return mixed |
326 | */ |
327 | public function getSection($section) |
328 | { |
329 | if (!array_key_exists($section, $this->_configuration)) { |
330 | throw new Exception(I18n::_('%s requires configuration section [%s] to be present in configuration file.', I18n::_($this->getKey('name')), $section), 3); |
331 | } |
332 | return $this->_configuration[$section]; |
333 | } |
334 | } |