Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
90.29% covered (success)
90.29%
93 / 103
80.00% covered (success)
80.00%
4 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
Configuration
90.29% covered (success)
90.29%
93 / 103
80.00% covered (success)
80.00%
4 / 5
42.54
0.00% covered (danger)
0.00%
0 / 1
 __construct
89.36% covered (success)
89.36%
84 / 94
0.00% covered (danger)
0.00%
0 / 1
36.47
 get
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getDefaults
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getKey
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getSection
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
1<?php
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 * @version   1.7.2
11 */
12
13namespace PrivateBin;
14
15use Exception;
16use PDO;
17
18/**
19 * Configuration
20 *
21 * parses configuration file, ensures default values present
22 */
23class Configuration
24{
25    /**
26     * parsed configuration
27     *
28     * @var array
29     */
30    private $_configuration;
31
32    /**
33     * default configuration
34     *
35     * @var array
36     */
37    private static $_defaults = array(
38        'main' => array(
39            'name'                     => 'PrivateBin',
40            'basepath'                 => '',
41            'discussion'               => true,
42            'opendiscussion'           => false,
43            'discussiondatedisplay'    => true,
44            'password'                 => true,
45            'fileupload'               => false,
46            'burnafterreadingselected' => false,
47            'defaultformatter'         => 'plaintext',
48            'syntaxhighlightingtheme'  => '',
49            'sizelimit'                => 10485760,
50            'template'                 => 'bootstrap',
51            'info'                     => 'More information on the <a href=\'https://privatebin.info/\'>project page</a>.',
52            'notice'                   => '',
53            'languageselection'        => false,
54            'languagedefault'          => '',
55            'urlshortener'             => '',
56            'qrcode'                   => true,
57            'email'                    => true,
58            'icon'                     => 'identicon',
59            'cspheader'                => 'default-src \'none\'; base-uri \'self\'; form-action \'none\'; manifest-src \'self\'; connect-src * blob:; script-src \'self\' \'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',
60            'zerobincompatibility'     => false,
61            'httpwarning'              => true,
62            'compression'              => 'zlib',
63        ),
64        'expire' => array(
65            'default' => '1week',
66        ),
67        'expire_options' => array(
68            '5min'   => 300,
69            '10min'  => 600,
70            '1hour'  => 3600,
71            '1day'   => 86400,
72            '1week'  => 604800,
73            '1month' => 2592000,
74            '1year'  => 31536000,
75            'never'  => 0,
76        ),
77        'formatter_options' => array(
78            'plaintext'          => 'Plain Text',
79            'syntaxhighlighting' => 'Source Code',
80            'markdown'           => 'Markdown',
81        ),
82        'traffic' => array(
83            'limit'     => 10,
84            'header'    => '',
85            'exempted'  => '',
86            'creators'  => '',
87        ),
88        'purge' => array(
89            'limit'     => 300,
90            'batchsize' => 10,
91        ),
92        'model' => array(
93            'class' => 'Filesystem',
94        ),
95        'model_options' => array(
96            'dir' => 'data',
97        ),
98        'yourls' => array(
99            'signature' => '',
100            'apiurl'    => '',
101        ),
102    );
103
104    /**
105     * parse configuration file and ensure default configuration values are present
106     *
107     * @throws Exception
108     */
109    public function __construct()
110    {
111        $basePaths  = array();
112        $config     = array();
113        $configPath = getenv('CONFIG_PATH');
114        if ($configPath !== false && !empty($configPath)) {
115            $basePaths[] = $configPath;
116        }
117        $basePaths[] = PATH . 'cfg';
118        foreach ($basePaths as $basePath) {
119            $configFile = $basePath . DIRECTORY_SEPARATOR . 'conf.php';
120            if (is_readable($configFile)) {
121                $config = parse_ini_file($configFile, true);
122                foreach (array('main', 'model', 'model_options') as $section) {
123                    if (!array_key_exists($section, $config)) {
124                        throw new Exception(I18n::_('PrivateBin requires configuration section [%s] to be present in configuration file.', $section), 2);
125                    }
126                }
127                break;
128            }
129        }
130
131        $opts = '_options';
132        foreach (self::getDefaults() as $section => $values) {
133            // fill missing sections with default values
134            if (!array_key_exists($section, $config) || count($config[$section]) == 0) {
135                $this->_configuration[$section] = $values;
136                if (array_key_exists('dir', $this->_configuration[$section])) {
137                    $this->_configuration[$section]['dir'] = PATH . $this->_configuration[$section]['dir'];
138                }
139                continue;
140            }
141            // provide different defaults for database model
142            elseif (
143                $section == 'model_options' && in_array(
144                    $this->_configuration['model']['class'],
145                    array('Database', 'privatebin_db', 'zerobin_db')
146                )
147            ) {
148                $values = array(
149                    'dsn' => 'sqlite:' . PATH . 'data' . DIRECTORY_SEPARATOR . 'db.sq3',
150                    'tbl' => null,
151                    'usr' => null,
152                    'pwd' => null,
153                    'opt' => array(PDO::ATTR_PERSISTENT => true),
154                );
155            } elseif (
156                $section == 'model_options' && in_array(
157                    $this->_configuration['model']['class'],
158                    array('GoogleCloudStorage')
159                )
160            ) {
161                $values = array(
162                    'bucket'     => getenv('PRIVATEBIN_GCS_BUCKET') ? getenv('PRIVATEBIN_GCS_BUCKET') : null,
163                    'prefix'     => 'pastes',
164                    'uniformacl' => false,
165                );
166            } elseif (
167                $section == 'model_options' && in_array(
168                    $this->_configuration['model']['class'],
169                    array('S3Storage')
170                )
171            ) {
172                $values = array(
173                    'region'                  => null,
174                    'version'                 => null,
175                    'endpoint'                => null,
176                    'accesskey'               => null,
177                    'secretkey'               => null,
178                    'use_path_style_endpoint' => null,
179                    'bucket'                  => null,
180                    'prefix'                  => '',
181                );
182            }
183
184            // "*_options" sections don't require all defaults to be set
185            if (
186                $section !== 'model_options' &&
187                ($from = strlen($section) - strlen($opts)) >= 0 &&
188                strpos($section, $opts, $from) !== false
189            ) {
190                if (is_int(current($values))) {
191                    $config[$section] = array_map('intval', $config[$section]);
192                }
193                $this->_configuration[$section] = $config[$section];
194            }
195            // check for missing keys and set defaults if necessary
196            else {
197                foreach ($values as $key => $val) {
198                    if ($key == 'dir') {
199                        $val = PATH . $val;
200                    }
201                    $result = $val;
202                    if (array_key_exists($key, $config[$section])) {
203                        if ($val === null) {
204                            $result = $config[$section][$key];
205                        } elseif (is_bool($val)) {
206                            $val = strtolower($config[$section][$key]);
207                            if (in_array($val, array('true', 'yes', 'on'))) {
208                                $result = true;
209                            } elseif (in_array($val, array('false', 'no', 'off'))) {
210                                $result = false;
211                            } else {
212                                $result = (bool) $config[$section][$key];
213                            }
214                        } elseif (is_int($val)) {
215                            $result = (int) $config[$section][$key];
216                        } elseif (is_string($val) && !empty($config[$section][$key])) {
217                            $result = (string) $config[$section][$key];
218                        }
219                    }
220                    $this->_configuration[$section][$key] = $result;
221                }
222            }
223        }
224
225        // support for old config file format, before the fork was renamed and PSR-4 introduced
226        $this->_configuration['model']['class'] = str_replace(
227            'zerobin_', 'privatebin_',
228            $this->_configuration['model']['class']
229        );
230
231        $this->_configuration['model']['class'] = str_replace(
232            array('privatebin_data', 'privatebin_db'),
233            array('Filesystem', 'Database'),
234            $this->_configuration['model']['class']
235        );
236
237        // ensure a valid expire default key is set
238        if (!array_key_exists($this->_configuration['expire']['default'], $this->_configuration['expire_options'])) {
239            $this->_configuration['expire']['default'] = key($this->_configuration['expire_options']);
240        }
241
242        // ensure the basepath ends in a slash, if one is set
243        if (
244            !empty($this->_configuration['main']['basepath']) &&
245            substr_compare($this->_configuration['main']['basepath'], '/', -1) !== 0
246        ) {
247            $this->_configuration['main']['basepath'] .= '/';
248        }
249    }
250
251    /**
252     * get configuration as array
253     *
254     * @return array
255     */
256    public function get()
257    {
258        return $this->_configuration;
259    }
260
261    /**
262     * get default configuration as array
263     *
264     * @return array
265     */
266    public static function getDefaults()
267    {
268        return self::$_defaults;
269    }
270
271    /**
272     * get a key from the configuration, typically the main section or all keys
273     *
274     * @param string $key
275     * @param string $section defaults to main
276     * @throws Exception
277     * @return mixed
278     */
279    public function getKey($key, $section = 'main')
280    {
281        $options = $this->getSection($section);
282        if (!array_key_exists($key, $options)) {
283            throw new Exception(I18n::_('Invalid data.') . " $section / $key", 4);
284        }
285        return $this->_configuration[$section][$key];
286    }
287
288    /**
289     * get a section from the configuration, must exist
290     *
291     * @param string $section
292     * @throws Exception
293     * @return mixed
294     */
295    public function getSection($section)
296    {
297        if (!array_key_exists($section, $this->_configuration)) {
298            throw new Exception(I18n::_('%s requires configuration section [%s] to be present in configuration file.', I18n::_($this->getKey('name')), $section), 3);
299        }
300        return $this->_configuration[$section];
301    }
302}