Passed
Push — master ( 3f508b...939a62 )
by El
05:26
created

PrivateBin::_create()   D

Complexity

Conditions 17
Paths 202

Size

Total Lines 99
Code Lines 60

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 57
CRAP Score 17

Importance

Changes 0
Metric Value
dl 0
loc 99
ccs 57
cts 57
cp 1
rs 4.5026
c 0
b 0
f 0
cc 17
eloc 60
nc 202
nop 0
crap 17

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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.1.1
11
 */
12
13
namespace PrivateBin;
14
15
use Exception;
16
use PrivateBin\Persistence\ServerSalt;
0 ignored issues
show
Bug introduced by El RIDO
The type PrivateBin\Persistence\ServerSalt was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
17
use PrivateBin\Persistence\TrafficLimiter;
0 ignored issues
show
Bug introduced by El RIDO
The type PrivateBin\Persistence\TrafficLimiter was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
18
19
/**
20
 * PrivateBin
21
 *
22
 * Controller, puts it all together.
23
 */
24
class PrivateBin
25
{
26
    /**
27
     * version
28
     *
29
     * @const string
30
     */
31
    const VERSION = '1.1.1';
32
33
    /**
34
     * minimal required PHP version
35
     *
36
     * @const string
37
     */
38
    const MIN_PHP_VERSION = '5.4.0';
39
40
    /**
41
     * show the same error message if the paste expired or does not exist
42
     *
43
     * @const string
44
     */
45
    const GENERIC_ERROR = 'Paste does not exist, has expired or has been deleted.';
46
47
    /**
48
     * configuration
49
     *
50
     * @access private
51
     * @var    Configuration
52
     */
53
    private $_conf;
54
55
    /**
56
     * data
57
     *
58
     * @access private
59
     * @var    string
60
     */
61
    private $_data = '';
62
63
    /**
64
     * does the paste expire
65
     *
66
     * @access private
67
     * @var    bool
68
     */
69
    private $_doesExpire = false;
70
71
    /**
72
     * error message
73
     *
74
     * @access private
75
     * @var    string
76
     */
77
    private $_error = '';
78
79
    /**
80
     * status message
81
     *
82
     * @access private
83
     * @var    string
84
     */
85
    private $_status = '';
86
87
    /**
88
     * JSON message
89
     *
90
     * @access private
91
     * @var    string
92
     */
93
    private $_json = '';
94
95
    /**
96
     * Factory of instance models
97
     *
98
     * @access private
99
     * @var    model
100
     */
101
    private $_model;
102
103
    /**
104
     * request
105
     *
106
     * @access private
107
     * @var    request
108
     */
109
    private $_request;
110
111
    /**
112
     * URL base
113
     *
114
     * @access private
115
     * @var    string
116
     */
117
    private $_urlBase;
118
119
    /**
120
     * constructor
121
     *
122
     * initializes and runs PrivateBin
123
     *
124
     * @access public
125
     * @throws Exception
126
     */
127 98
    public function __construct()
128
    {
129 98
        if (version_compare(PHP_VERSION, self::MIN_PHP_VERSION) < 0) {
130
            throw new Exception(I18n::_('%s requires php %s or above to work. Sorry.', I18n::_('PrivateBin'), self::MIN_PHP_VERSION), 1);
131
        }
132 98
        if (strlen(PATH) < 0 && substr(PATH, -1) !== DIRECTORY_SEPARATOR) {
133
            throw new Exception(I18n::_('%s requires the PATH to end in a "%s". Please update the PATH in your index.php.', I18n::_('PrivateBin'), DIRECTORY_SEPARATOR), 5);
134
        }
135
136
        // load config from ini file, initialize required classes
137 98
        $this->_init();
138
139 96
        switch ($this->_request->getOperation()) {
140 96
            case 'create':
141 44
                $this->_create();
142 44
                break;
143 52
            case 'delete':
144 18
                $this->_delete(
145 18
                    $this->_request->getParam('pasteid'),
146 18
                    $this->_request->getParam('deletetoken')
147
                );
148 18
                break;
149 34
            case 'read':
150 21
                $this->_read($this->_request->getParam('pasteid'));
151 21
                break;
152 13
            case 'jsonld':
153 5
                $this->_jsonld($this->_request->getParam('jsonld'));
154 5
                return;
155
        }
156
157
        // output JSON or HTML
158 91
        if ($this->_request->isJsonApiCall()) {
159 55
            header('Content-type: ' . Request::MIME_JSON);
160 55
            header('Access-Control-Allow-Origin: *');
161 55
            header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE');
162 55
            header('Access-Control-Allow-Headers: X-Requested-With, Content-Type');
163 55
            echo $this->_json;
164
        } else {
165 36
            $this->_view();
166
        }
167 91
    }
168
169
    /**
170
     * initialize privatebin
171
     *
172
     * @access private
173
     */
174 98
    private function _init()
175
    {
176 98
        $this->_conf    = new Configuration;
177 96
        $this->_model   = new Model($this->_conf);
178 96
        $this->_request = new Request;
179 96
        $this->_urlBase = $this->_request->getRequestUri();
180 96
        ServerSalt::setPath($this->_conf->getKey('dir', 'traffic'));
181
182
        // set default language
183 96
        $lang = $this->_conf->getKey('languagedefault');
184 96
        I18n::setLanguageFallback($lang);
185
        // force default language, if language selection is disabled and a default is set
186 96
        if (!$this->_conf->getKey('languageselection') && strlen($lang) == 2) {
187 2
            $_COOKIE['lang'] = $lang;
188 2
            setcookie('lang', $lang);
189
        }
190 96
    }
191
192
    /**
193
     * Store new paste or comment
194
     *
195
     * POST contains one or both:
196
     * data = json encoded SJCL encrypted text (containing keys: iv,v,iter,ks,ts,mode,adata,cipher,salt,ct)
197
     * attachment = json encoded SJCL encrypted text (containing keys: iv,v,iter,ks,ts,mode,adata,cipher,salt,ct)
198
     *
199
     * All optional data will go to meta information:
200
     * expire (optional) = expiration delay (never,5min,10min,1hour,1day,1week,1month,1year,burn) (default:never)
201
     * formatter (optional) = format to display the paste as (plaintext,syntaxhighlighting,markdown) (default:syntaxhighlighting)
202
     * burnafterreading (optional) = if this paste may only viewed once ? (0/1) (default:0)
203
     * opendiscusssion (optional) = is the discussion allowed on this paste ? (0/1) (default:0)
204
     * attachmentname = json encoded SJCL encrypted text (containing keys: iv,v,iter,ks,ts,mode,adata,cipher,salt,ct)
205
     * nickname (optional) = in discussion, encoded SJCL encrypted text nickname of author of comment (containing keys: iv,v,iter,ks,ts,mode,adata,cipher,salt,ct)
206
     * parentid (optional) = in discussion, which comment this comment replies to.
207
     * pasteid (optional) = in discussion, which paste this comment belongs to.
208
     *
209
     * @access private
210
     * @return string
211
     */
212 44
    private function _create()
213
    {
214
        // Ensure last paste from visitors IP address was more than configured amount of seconds ago.
215 44
        TrafficLimiter::setConfiguration($this->_conf);
216 44
        if (!TrafficLimiter::canPass()) {
217 2
            return $this->_return_message(
0 ignored issues
show
Bug introduced by Sobak
Are you sure the usage of $this->_return_message(1...y('limit', 'traffic'))) targeting PrivateBin\PrivateBin::_return_message() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
218 2
            1, I18n::_(
219 2
                'Please wait %d seconds between each post.',
220 2
                $this->_conf->getKey('limit', 'traffic')
221
            )
222
        );
223
        }
224
225 44
        $data           = $this->_request->getParam('data');
226 44
        $attachment     = $this->_request->getParam('attachment');
227 44
        $attachmentname = $this->_request->getParam('attachmentname');
228
229
        // Ensure content is not too big.
230 44
        $sizelimit = $this->_conf->getKey('sizelimit');
231
        if (
232 44
            strlen($data) + strlen($attachment) + strlen($attachmentname) > $sizelimit
233
        ) {
234 2
            return $this->_return_message(
0 ignored issues
show
Bug introduced by Sobak
Are you sure the usage of $this->_return_message(1...dableSize($sizelimit))) targeting PrivateBin\PrivateBin::_return_message() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
235 2
            1,
236 2
            I18n::_(
237 2
                'Paste is limited to %s of encrypted data.',
238 2
                Filter::formatHumanReadableSize($sizelimit)
239
            )
240
        );
241
        }
242
243
        // Ensure attachment did not get lost due to webserver limits or Suhosin
244 42
        if (strlen($attachmentname) > 0 && strlen($attachment) == 0) {
245 2
            return $this->_return_message(1, 'Attachment missing in data received by server. Please check your webserver or suhosin configuration for maximum POST parameter limitations.');
0 ignored issues
show
Bug introduced by El RIDO
Are you sure the usage of $this->_return_message(1...arameter limitations.') targeting PrivateBin\PrivateBin::_return_message() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
246
        }
247
248
        // The user posts a comment.
249 40
        $pasteid  = $this->_request->getParam('pasteid');
250 40
        $parentid = $this->_request->getParam('parentid');
251 40
        if (!empty($pasteid) && !empty($parentid)) {
252 12
            $paste = $this->_model->getPaste($pasteid);
253 12
            if ($paste->exists()) {
254
                try {
255 10
                    $comment = $paste->getComment($parentid);
256
257 8
                    $nickname = $this->_request->getParam('nickname');
258 8
                    if (!empty($nickname)) {
259 8
                        $comment->setNickname($nickname);
260
                    }
261
262 6
                    $comment->setData($data);
263 6
                    $comment->store();
264 8
                } catch (Exception $e) {
265 8
                    return $this->_return_message(1, $e->getMessage());
0 ignored issues
show
Bug introduced by El RIDO
Are you sure the usage of $this->_return_message(1, $e->getMessage()) targeting PrivateBin\PrivateBin::_return_message() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
266
                }
267 2
                $this->_return_message(0, $comment->getId());
268
            } else {
269 4
                $this->_return_message(1, 'Invalid data.');
270
            }
271
        }
272
        // The user posts a standard paste.
273
        else {
274 28
            $this->_model->purge();
275 28
            $paste = $this->_model->getPaste();
276
            try {
277 28
                $paste->setData($data);
278
279 28
                if (!empty($attachment)) {
280 2
                    $paste->setAttachment($attachment);
281 2
                    if (!empty($attachmentname)) {
282 2
                        $paste->setAttachmentName($attachmentname);
283
                    }
284
                }
285
286 28
                $expire = $this->_request->getParam('expire');
287 28
                if (!empty($expire)) {
288 6
                    $paste->setExpiration($expire);
289
                }
290
291 28
                $burnafterreading = $this->_request->getParam('burnafterreading');
292 28
                if (!empty($burnafterreading)) {
293 2
                    $paste->setBurnafterreading($burnafterreading);
294
                }
295
296 26
                $opendiscussion = $this->_request->getParam('opendiscussion');
297 26
                if (!empty($opendiscussion)) {
298 4
                    $paste->setOpendiscussion($opendiscussion);
299
                }
300
301 24
                $formatter = $this->_request->getParam('formatter');
302 24
                if (!empty($formatter)) {
303 2
                    $paste->setFormatter($formatter);
304
                }
305
306 24
                $paste->store();
307 6
            } catch (Exception $e) {
308 6
                return $this->_return_message(1, $e->getMessage());
0 ignored issues
show
Bug introduced by El RIDO
Are you sure the usage of $this->_return_message(1, $e->getMessage()) targeting PrivateBin\PrivateBin::_return_message() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
309
            }
310 22
            $this->_return_message(0, $paste->getId(), array('deletetoken' => $paste->getDeleteToken()));
311
        }
312 26
    }
313
314
    /**
315
     * Delete an existing paste
316
     *
317
     * @access private
318
     * @param  string $dataid
319
     * @param  string $deletetoken
320
     */
321 18
    private function _delete($dataid, $deletetoken)
322
    {
323
        try {
324 18
            $paste = $this->_model->getPaste($dataid);
325 16
            if ($paste->exists()) {
326
                // accessing this property ensures that the paste would be
327
                // deleted if it has already expired
328 14
                $burnafterreading = $paste->isBurnafterreading();
329
                if (
330 12
                    ($burnafterreading && $deletetoken == 'burnafterreading') ||
331 12
                    Filter::slowEquals($deletetoken, $paste->getDeleteToken())
332
                ) {
333
                    // Paste exists and deletion token is valid: Delete the paste.
334 8
                    $paste->delete();
335 8
                    $this->_status = 'Paste was properly deleted.';
336
                } else {
337 4
                    if (!$burnafterreading && $deletetoken == 'burnafterreading') {
338 2
                        $this->_error = 'Paste is not of burn-after-reading type.';
339
                    } else {
340 12
                        $this->_error = 'Wrong deletion token. Paste was not deleted.';
341
                    }
342
                }
343
            } else {
344 14
                $this->_error = self::GENERIC_ERROR;
345
            }
346 4
        } catch (Exception $e) {
347 4
            $this->_error = $e->getMessage();
348
        }
349 18
        if ($this->_request->isJsonApiCall()) {
350 6
            if (strlen($this->_error)) {
351 2
                $this->_return_message(1, $this->_error);
352
            } else {
353 4
                $this->_return_message(0, $dataid);
354
            }
355
        }
356 18
    }
357
358
    /**
359
     * Read an existing paste or comment
360
     *
361
     * @access private
362
     * @param  string $dataid
363
     */
364 21
    private function _read($dataid)
365
    {
366
        try {
367 21
            $paste = $this->_model->getPaste($dataid);
368 19
            if ($paste->exists()) {
369 15
                $data              = $paste->get();
370 13
                $this->_doesExpire = property_exists($data, 'meta') && property_exists($data->meta, 'expire_date');
371 13
                if (property_exists($data->meta, 'salt')) {
372 13
                    unset($data->meta->salt);
373
                }
374 13
                $this->_data = json_encode($data);
375
            } else {
376 17
                $this->_error = self::GENERIC_ERROR;
377
            }
378 4
        } catch (Exception $e) {
379 4
            $this->_error = $e->getMessage();
380
        }
381
382 21
        if ($this->_request->isJsonApiCall()) {
383 5
            if (strlen($this->_error)) {
384 2
                $this->_return_message(1, $this->_error);
385
            } else {
386 3
                $this->_return_message(0, $dataid, json_decode($this->_data, true));
387
            }
388
        }
389 21
    }
390
391
    /**
392
     * Display PrivateBin frontend.
393
     *
394
     * @access private
395
     */
396 36
    private function _view()
397
    {
398
        // set headers to disable caching
399 36
        $time = gmdate('D, d M Y H:i:s \G\M\T');
400 36
        header('Cache-Control: no-store, no-cache, no-transform, must-revalidate');
401 36
        header('Pragma: no-cache');
402 36
        header('Expires: ' . $time);
403 36
        header('Last-Modified: ' . $time);
404 36
        header('Vary: Accept');
405 36
        header('Content-Security-Policy: ' . $this->_conf->getKey('cspheader'));
406 36
        header('X-Xss-Protection: 1; mode=block');
407 36
        header('X-Frame-Options: DENY');
408 36
        header('X-Content-Type-Options: nosniff');
409
410
        // label all the expiration options
411 36
        $expire = array();
412 36
        foreach ($this->_conf->getSection('expire_options') as $time => $seconds) {
413 36
            $expire[$time] = ($seconds == 0) ? I18n::_(ucfirst($time)) : Filter::formatHumanReadableTime($time);
414
        }
415
416
        // translate all the formatter options
417 36
        $formatters = array_map('PrivateBin\\I18n::_', $this->_conf->getSection('formatter_options'));
418
419
        // set language cookie if that functionality was enabled
420 36
        $languageselection = '';
421 36
        if ($this->_conf->getKey('languageselection')) {
422 2
            $languageselection = I18n::getLanguage();
423 2
            setcookie('lang', $languageselection);
424
        }
425
426 36
        $page = new View;
427 36
        $page->assign('NAME', $this->_conf->getKey('name'));
428 36
        $page->assign('CIPHERDATA', $this->_data);
429 36
        $page->assign('ERROR', I18n::_($this->_error));
430 36
        $page->assign('STATUS', I18n::_($this->_status));
431 36
        $page->assign('VERSION', self::VERSION);
432 36
        $page->assign('DISCUSSION', $this->_conf->getKey('discussion'));
433 36
        $page->assign('OPENDISCUSSION', $this->_conf->getKey('opendiscussion'));
434 36
        $page->assign('MARKDOWN', array_key_exists('markdown', $formatters));
435 36
        $page->assign('SYNTAXHIGHLIGHTING', array_key_exists('syntaxhighlighting', $formatters));
436 36
        $page->assign('SYNTAXHIGHLIGHTINGTHEME', $this->_conf->getKey('syntaxhighlightingtheme'));
437 36
        $page->assign('FORMATTER', $formatters);
438 36
        $page->assign('FORMATTERDEFAULT', $this->_conf->getKey('defaultformatter'));
439 36
        $page->assign('NOTICE', I18n::_($this->_conf->getKey('notice')));
440 36
        $page->assign('BURNAFTERREADINGSELECTED', $this->_conf->getKey('burnafterreadingselected'));
441 36
        $page->assign('PASSWORD', $this->_conf->getKey('password'));
442 36
        $page->assign('FILEUPLOAD', $this->_conf->getKey('fileupload'));
443 36
        $page->assign('ZEROBINCOMPATIBILITY', $this->_conf->getKey('zerobincompatibility'));
444 36
        $page->assign('LANGUAGESELECTION', $languageselection);
445 36
        $page->assign('LANGUAGES', I18n::getLanguageLabels(I18n::getAvailableLanguages()));
446 36
        $page->assign('EXPIRE', $expire);
447 36
        $page->assign('EXPIREDEFAULT', $this->_conf->getKey('default', 'expire'));
448 36
        $page->assign('EXPIRECLONE', !$this->_doesExpire || ($this->_doesExpire && $this->_conf->getKey('clone', 'expire')));
449 36
        $page->assign('URLSHORTENER', $this->_conf->getKey('urlshortener'));
450 36
        $page->assign('QRCODE', $this->_conf->getKey('qrcode'));
451 36
        $page->draw($this->_conf->getKey('template'));
452 36
    }
453
454
    /**
455
     * outputs requested JSON-LD context
456
     *
457
     * @access private
458
     * @param string $type
459
     */
460 5
    private function _jsonld($type)
461
    {
462
        if (
463 5
            $type !== 'paste' && $type !== 'comment' &&
464 5
            $type !== 'pastemeta' && $type !== 'commentmeta'
465
        ) {
466 1
            $type = '';
467
        }
468 5
        $content = '{}';
469 5
        $file    = PUBLIC_PATH . DIRECTORY_SEPARATOR . 'js' . DIRECTORY_SEPARATOR . $type . '.jsonld';
470 5
        if (is_readable($file)) {
471 4
            $content = str_replace(
472 4
                '?jsonld=',
473 4
                $this->_urlBase . '?jsonld=',
474 4
                file_get_contents($file)
475
            );
476
        }
477
478 5
        header('Content-type: application/ld+json');
479 5
        header('Access-Control-Allow-Origin: *');
480 5
        header('Access-Control-Allow-Methods: GET');
481 5
        echo $content;
482 5
    }
483
484
    /**
485
     * prepares JSON encoded status message
486
     *
487
     * @access private
488
     * @param  int $status
489
     * @param  string $message
490
     * @param  array $other
491
     */
492 55
    private function _return_message($status, $message, $other = array())
493
    {
494 55
        $result = array('status' => $status);
495 55
        if ($status) {
496 26
            $result['message'] = I18n::_($message);
497
        } else {
498 31
            $result['id']  = $message;
499 31
            $result['url'] = $this->_urlBase . '?' . $message;
500
        }
501 55
        $result += $other;
502 55
        $this->_json = json_encode($result);
503 55
    }
504
}
505