Passed
Push — master ( 90e83d...4a3542 )
by El
03:14
created

Controller::_view()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 54
Code Lines 42

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 43
CRAP Score 4

Importance

Changes 0
Metric Value
eloc 42
dl 0
loc 54
ccs 43
cts 43
cp 1
rs 9.248
c 0
b 0
f 0
cc 4
nc 6
nop 0
crap 4

How to fix   Long Method   

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.2
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
 * Controller
21
 *
22
 * Puts it all together.
23
 */
24
class Controller
25
{
26
    /**
27
     * version
28
     *
29
     * @const string
30
     */
31
    const VERSION = '1.2';
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
     * error message
57
     *
58
     * @access private
59
     * @var    string
60
     */
61
    private $_error = '';
62
63
    /**
64
     * status message
65
     *
66
     * @access private
67
     * @var    string
68
     */
69
    private $_status = '';
70
71
    /**
72
     * JSON message
73
     *
74
     * @access private
75
     * @var    string
76
     */
77
    private $_json = '';
78
79
    /**
80
     * Factory of instance models
81
     *
82
     * @access private
83
     * @var    model
84
     */
85
    private $_model;
86
87
    /**
88
     * request
89
     *
90
     * @access private
91
     * @var    request
92
     */
93
    private $_request;
94
95
    /**
96
     * URL base
97
     *
98
     * @access private
99
     * @var    string
100
     */
101
    private $_urlBase;
102
103
    /**
104
     * constructor
105
     *
106
     * initializes and runs PrivateBin
107
     *
108
     * @access public
109
     * @throws Exception
110
     */
111 90
    public function __construct()
112
    {
113 90
        if (version_compare(PHP_VERSION, self::MIN_PHP_VERSION) < 0) {
114
            throw new Exception(I18n::_('%s requires php %s or above to work. Sorry.', I18n::_('PrivateBin'), self::MIN_PHP_VERSION), 1);
115
        }
116 90
        if (strlen(PATH) < 0 && substr(PATH, -1) !== DIRECTORY_SEPARATOR) {
117
            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);
118
        }
119
120
        // load config from ini file, initialize required classes
121 90
        $this->_init();
122
123 88
        switch ($this->_request->getOperation()) {
124 88
            case 'create':
125 44
                $this->_create();
126 44
                break;
127 44
            case 'delete':
128 18
                $this->_delete(
129 18
                    $this->_request->getParam('pasteid'),
130 18
                    $this->_request->getParam('deletetoken')
131
                );
132 18
                break;
133 26
            case 'read':
134 13
                $this->_read($this->_request->getParam('pasteid'));
135 13
                break;
136 13
            case 'jsonld':
137 5
                $this->_jsonld($this->_request->getParam('jsonld'));
138 5
                return;
139
        }
140
141
        // output JSON or HTML
142 83
        if ($this->_request->isJsonApiCall()) {
143 63
            header('Content-type: ' . Request::MIME_JSON);
144 63
            header('Access-Control-Allow-Origin: *');
145 63
            header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE');
146 63
            header('Access-Control-Allow-Headers: X-Requested-With, Content-Type');
147 63
            echo $this->_json;
148
        } else {
149 20
            $this->_view();
150
        }
151 83
    }
152
153
    /**
154
     * initialize PrivateBin
155
     *
156
     * @access private
157
     */
158 90
    private function _init()
159
    {
160 90
        $this->_conf    = new Configuration;
161 88
        $this->_model   = new Model($this->_conf);
162 88
        $this->_request = new Request;
163 88
        $this->_urlBase = $this->_request->getRequestUri();
164 88
        ServerSalt::setPath($this->_conf->getKey('dir', 'traffic'));
165
166
        // set default language
167 88
        $lang = $this->_conf->getKey('languagedefault');
168 88
        I18n::setLanguageFallback($lang);
169
        // force default language, if language selection is disabled and a default is set
170 88
        if (!$this->_conf->getKey('languageselection') && strlen($lang) == 2) {
171 2
            $_COOKIE['lang'] = $lang;
172 2
            setcookie('lang', $lang);
173
        }
174 88
    }
175
176
    /**
177
     * Store new paste or comment
178
     *
179
     * POST contains one or both:
180
     * data = json encoded SJCL encrypted text (containing keys: iv,v,iter,ks,ts,mode,adata,cipher,salt,ct)
181
     * attachment = json encoded SJCL encrypted text (containing keys: iv,v,iter,ks,ts,mode,adata,cipher,salt,ct)
182
     *
183
     * All optional data will go to meta information:
184
     * expire (optional) = expiration delay (never,5min,10min,1hour,1day,1week,1month,1year,burn) (default:never)
185
     * formatter (optional) = format to display the paste as (plaintext,syntaxhighlighting,markdown) (default:syntaxhighlighting)
186
     * burnafterreading (optional) = if this paste may only viewed once ? (0/1) (default:0)
187
     * opendiscusssion (optional) = is the discussion allowed on this paste ? (0/1) (default:0)
188
     * attachmentname = json encoded SJCL encrypted text (containing keys: iv,v,iter,ks,ts,mode,adata,cipher,salt,ct)
189
     * 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)
190
     * parentid (optional) = in discussion, which comment this comment replies to.
191
     * pasteid (optional) = in discussion, which paste this comment belongs to.
192
     *
193
     * @access private
194
     * @return string
195
     */
196 44
    private function _create()
197
    {
198
        // Ensure last paste from visitors IP address was more than configured amount of seconds ago.
199 44
        TrafficLimiter::setConfiguration($this->_conf);
200 44
        if (!TrafficLimiter::canPass()) {
201 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\Controller::_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...
202 2
            1, I18n::_(
203 2
                'Please wait %d seconds between each post.',
204 2
                $this->_conf->getKey('limit', 'traffic')
205
            )
206
        );
207
        }
208
209 44
        $data           = $this->_request->getParam('data');
210 44
        $attachment     = $this->_request->getParam('attachment');
211 44
        $attachmentname = $this->_request->getParam('attachmentname');
212
213
        // Ensure content is not too big.
214 44
        $sizelimit = $this->_conf->getKey('sizelimit');
215
        if (
216 44
            strlen($data) + strlen($attachment) + strlen($attachmentname) > $sizelimit
217
        ) {
218 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\Controller::_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...
219 2
            1,
220 2
            I18n::_(
221 2
                'Paste is limited to %s of encrypted data.',
222 2
                Filter::formatHumanReadableSize($sizelimit)
223
            )
224
        );
225
        }
226
227
        // Ensure attachment did not get lost due to webserver limits or Suhosin
228 42
        if (strlen($attachmentname) > 0 && strlen($attachment) == 0) {
229 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\Controller::_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...
230
        }
231
232
        // The user posts a comment.
233 40
        $pasteid  = $this->_request->getParam('pasteid');
234 40
        $parentid = $this->_request->getParam('parentid');
235 40
        if (!empty($pasteid) && !empty($parentid)) {
236 12
            $paste = $this->_model->getPaste($pasteid);
237 12
            if ($paste->exists()) {
238
                try {
239 10
                    $comment = $paste->getComment($parentid);
240
241 8
                    $nickname = $this->_request->getParam('nickname');
242 8
                    if (!empty($nickname)) {
243 8
                        $comment->setNickname($nickname);
244
                    }
245
246 6
                    $comment->setData($data);
247 6
                    $comment->store();
248 8
                } catch (Exception $e) {
249 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\Controller::_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...
250
                }
251 2
                $this->_return_message(0, $comment->getId());
252
            } else {
253 4
                $this->_return_message(1, 'Invalid data.');
254
            }
255
        }
256
        // The user posts a standard paste.
257
        else {
258 28
            $this->_model->purge();
259 28
            $paste = $this->_model->getPaste();
260
            try {
261 28
                $paste->setData($data);
262
263 28
                if (!empty($attachment)) {
264 2
                    $paste->setAttachment($attachment);
265 2
                    if (!empty($attachmentname)) {
266 2
                        $paste->setAttachmentName($attachmentname);
267
                    }
268
                }
269
270 28
                $expire = $this->_request->getParam('expire');
271 28
                if (!empty($expire)) {
272 6
                    $paste->setExpiration($expire);
273
                }
274
275 28
                $burnafterreading = $this->_request->getParam('burnafterreading');
276 28
                if (!empty($burnafterreading)) {
277 2
                    $paste->setBurnafterreading($burnafterreading);
278
                }
279
280 26
                $opendiscussion = $this->_request->getParam('opendiscussion');
281 26
                if (!empty($opendiscussion)) {
282 4
                    $paste->setOpendiscussion($opendiscussion);
283
                }
284
285 24
                $formatter = $this->_request->getParam('formatter');
286 24
                if (!empty($formatter)) {
287 2
                    $paste->setFormatter($formatter);
288
                }
289
290 24
                $paste->store();
291 6
            } catch (Exception $e) {
292 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\Controller::_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...
293
            }
294 22
            $this->_return_message(0, $paste->getId(), array('deletetoken' => $paste->getDeleteToken()));
295
        }
296 26
    }
297
298
    /**
299
     * Delete an existing paste
300
     *
301
     * @access private
302
     * @param  string $dataid
303
     * @param  string $deletetoken
304
     */
305 18
    private function _delete($dataid, $deletetoken)
306
    {
307
        try {
308 18
            $paste = $this->_model->getPaste($dataid);
309 16
            if ($paste->exists()) {
310
                // accessing this property ensures that the paste would be
311
                // deleted if it has already expired
312 14
                $burnafterreading = $paste->isBurnafterreading();
313
                if (
314 12
                    ($burnafterreading && $deletetoken == 'burnafterreading') || // either we burn-after it has been read //@TODO: not needed anymore now?
315 12
                    Filter::slowEquals($deletetoken, $paste->getDeleteToken()) // or we manually delete it with this secret token
316
                ) {
317
                    // Paste exists and deletion token (if required) is valid: Delete the paste.
318 8
                    $paste->delete();
319 8
                    $this->_status = 'Paste was properly deleted.';
320
                } else {
321 4
                    if (!$burnafterreading && $deletetoken == 'burnafterreading') {
322 2
                        $this->_error = 'Paste is not of burn-after-reading type.';
323
                    } else {
324 12
                        $this->_error = 'Wrong deletion token. Paste was not deleted.';
325
                    }
326
                }
327
            } else {
328 14
                $this->_error = self::GENERIC_ERROR;
329
            }
330 4
        } catch (Exception $e) {
331 4
            $this->_error = $e->getMessage();
332
        }
333 18
        if ($this->_request->isJsonApiCall()) {
334 6
            if (strlen($this->_error)) {
335 2
                $this->_return_message(1, $this->_error);
336
            } else {
337 4
                $this->_return_message(0, $dataid);
338
            }
339
        }
340 18
    }
341
342
    /**
343
     * Read an existing paste or comment, only allowed via a JSON API call
344
     *
345
     * @access private
346
     * @param  string $dataid
347
     */
348 13
    private function _read($dataid)
349
    {
350 13
        if (!$this->_request->isJsonApiCall()) {
351
            return;
352
        }
353
354
        try {
355 13
            $paste = $this->_model->getPaste($dataid);
356 11
            if ($paste->exists()) {
357 9
                $data = $paste->get();
358 7
                if (property_exists($data->meta, 'salt')) {
359 7
                    unset($data->meta->salt);
360
                }
361 7
                $this->_return_message(0, $dataid, (array) $data);
362
            } else {
363 9
                $this->_return_message(1, self::GENERIC_ERROR);
364
            }
365 4
        } catch (Exception $e) {
366 4
            $this->_return_message(1, $e->getMessage());
367
        }
368 13
    }
369
370
    /**
371
     * Display frontend.
372
     *
373
     * @access private
374
     */
375 20
    private function _view()
376
    {
377
        // set headers to disable caching
378 20
        $time = gmdate('D, d M Y H:i:s \G\M\T');
379 20
        header('Cache-Control: no-store, no-cache, no-transform, must-revalidate');
380 20
        header('Pragma: no-cache');
381 20
        header('Expires: ' . $time);
382 20
        header('Last-Modified: ' . $time);
383 20
        header('Vary: Accept');
384 20
        header('Content-Security-Policy: ' . $this->_conf->getKey('cspheader'));
385 20
        header('X-Xss-Protection: 1; mode=block');
386 20
        header('X-Frame-Options: DENY');
387 20
        header('X-Content-Type-Options: nosniff');
388
389
        // label all the expiration options
390 20
        $expire = array();
391 20
        foreach ($this->_conf->getSection('expire_options') as $time => $seconds) {
392 20
            $expire[$time] = ($seconds == 0) ? I18n::_(ucfirst($time)) : Filter::formatHumanReadableTime($time);
393
        }
394
395
        // translate all the formatter options
396 20
        $formatters = array_map('PrivateBin\\I18n::_', $this->_conf->getSection('formatter_options'));
397
398
        // set language cookie if that functionality was enabled
399 20
        $languageselection = '';
400 20
        if ($this->_conf->getKey('languageselection')) {
401 2
            $languageselection = I18n::getLanguage();
402 2
            setcookie('lang', $languageselection);
403
        }
404
405 20
        $page = new View;
406 20
        $page->assign('NAME', $this->_conf->getKey('name'));
407 20
        $page->assign('ERROR', I18n::_($this->_error));
408 20
        $page->assign('STATUS', I18n::_($this->_status));
409 20
        $page->assign('VERSION', self::VERSION);
410 20
        $page->assign('DISCUSSION', $this->_conf->getKey('discussion'));
411 20
        $page->assign('OPENDISCUSSION', $this->_conf->getKey('opendiscussion'));
412 20
        $page->assign('MARKDOWN', array_key_exists('markdown', $formatters));
413 20
        $page->assign('SYNTAXHIGHLIGHTING', array_key_exists('syntaxhighlighting', $formatters));
414 20
        $page->assign('SYNTAXHIGHLIGHTINGTHEME', $this->_conf->getKey('syntaxhighlightingtheme'));
415 20
        $page->assign('FORMATTER', $formatters);
416 20
        $page->assign('FORMATTERDEFAULT', $this->_conf->getKey('defaultformatter'));
417 20
        $page->assign('NOTICE', I18n::_($this->_conf->getKey('notice')));
418 20
        $page->assign('BURNAFTERREADINGSELECTED', $this->_conf->getKey('burnafterreadingselected'));
419 20
        $page->assign('PASSWORD', $this->_conf->getKey('password'));
420 20
        $page->assign('FILEUPLOAD', $this->_conf->getKey('fileupload'));
421 20
        $page->assign('ZEROBINCOMPATIBILITY', $this->_conf->getKey('zerobincompatibility'));
422 20
        $page->assign('LANGUAGESELECTION', $languageselection);
423 20
        $page->assign('LANGUAGES', I18n::getLanguageLabels(I18n::getAvailableLanguages()));
424 20
        $page->assign('EXPIRE', $expire);
425 20
        $page->assign('EXPIREDEFAULT', $this->_conf->getKey('default', 'expire'));
426 20
        $page->assign('URLSHORTENER', $this->_conf->getKey('urlshortener'));
427 20
        $page->assign('QRCODE', $this->_conf->getKey('qrcode'));
428 20
        $page->draw($this->_conf->getKey('template'));
429 20
    }
430
431
    /**
432
     * outputs requested JSON-LD context
433
     *
434
     * @access private
435
     * @param string $type
436
     */
437 5
    private function _jsonld($type)
438
    {
439
        if (
440 5
            $type !== 'paste' && $type !== 'comment' &&
441 5
            $type !== 'pastemeta' && $type !== 'commentmeta'
442
        ) {
443 1
            $type = '';
444
        }
445 5
        $content = '{}';
446 5
        $file    = PUBLIC_PATH . DIRECTORY_SEPARATOR . 'js' . DIRECTORY_SEPARATOR . $type . '.jsonld';
447 5
        if (is_readable($file)) {
448 4
            $content = str_replace(
449 4
                '?jsonld=',
450 4
                $this->_urlBase . '?jsonld=',
451 4
                file_get_contents($file)
452
            );
453
        }
454
455 5
        header('Content-type: application/ld+json');
456 5
        header('Access-Control-Allow-Origin: *');
457 5
        header('Access-Control-Allow-Methods: GET');
458 5
        echo $content;
459 5
    }
460
461
    /**
462
     * prepares JSON encoded status message
463
     *
464
     * @access private
465
     * @param  int $status
466
     * @param  string $message
467
     * @param  array $other
468
     */
469 63
    private function _return_message($status, $message, $other = array())
470
    {
471 63
        $result = array('status' => $status);
472 63
        if ($status) {
473 30
            $result['message'] = I18n::_($message);
474
        } else {
475 35
            $result['id']  = $message;
476 35
            $result['url'] = $this->_urlBase . '?' . $message;
477
        }
478 63
        $result += $other;
479 63
        $this->_json = json_encode($result);
480 63
    }
481
}
482