Completed
Push — errorviewer ( deca07 )
by Simon
04:24
created

PageErrorLogViewer::safetyCheck()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 21
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 9
c 1
b 0
f 0
dl 0
loc 21
ccs 0
cts 14
cp 0
rs 9.9666
cc 4
nc 4
nop 1
crap 20
1
<?php
2
/******************************************************************************
3
 * Wikipedia Account Creation Assistance tool                                 *
4
 *                                                                            *
5
 * All code in this file is released into the public domain by the ACC        *
6
 * Development Team. Please see team.json for a list of contributors.         *
7
 ******************************************************************************/
8
9
namespace Waca\Pages;
10
11
use Waca\DataObjects\User;
12
use Waca\Tasks\InternalPageBase;
13
use Waca\WebRequest;
14
15
class PageErrorLogViewer extends InternalPageBase
16
{
17
    /**
18
     * @inheritDoc
19
     */
20
    protected function main()
21
    {
22
        $user = User::getCurrent($this->getDatabase());
23
        $this->assign('canView', $this->barrierTest('view', $user));
24
        $this->assign('canRemove', $this->barrierTest('remove', $user));
25
26
        // Get the list of exception logs from the error log directory
27
        $errorLogDirectory = $this->getSiteConfiguration()->getErrorLog();
28
        $files = scandir($errorLogDirectory);
29
30
        // Exclude the files we know should be there
31
        $filteredFiles = array_filter($files, function($file) {
0 ignored issues
show
Bug introduced by
It seems like $files can also be of type false; however, parameter $input of array_filter() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

31
        $filteredFiles = array_filter(/** @scrutinizer ignore-type */ $files, function($file) {
Loading history...
32
            return !in_array($file, ['.', '..', 'README.md']);
33
        });
34
35
        $exceptionDetails = array_map(function($item) use ($errorLogDirectory) {
36
            $filename = realpath($errorLogDirectory) . DIRECTORY_SEPARATOR . $item;
37
38
            return [
39
                'id'   => str_replace('.log', '', $item),
40
                'date' => date('Y-m-d H:i:s', filemtime($filename)),
41
                'data' => str_replace($this->getSiteConfiguration()->getFilePath(), '.',
42
                    unserialize(file_get_contents($filename))),
43
            ];
44
        }, $filteredFiles);
45
46
        $this->assign('exceptionEntries', $exceptionDetails);
47
        $this->setTemplate('errorlog/main.tpl');
48
    }
49
50
    protected function view()
51
    {
52
        $requestedErrorId = WebRequest::getString('id');
53
        $safeFilename = $this->safetyCheck($requestedErrorId);
54
55
        if ($safeFilename === false) {
56
            $this->redirect('errorLog');
57
58
            return;
59
        }
60
61
        // note: at this point we've done sufficient sanity checks that we can be confident this value is safe to echo
62
        // back to the user.
63
        $this->assign('id', $requestedErrorId);
64
        $this->assign('date', date('Y-m-d H:i:s', filemtime($safeFilename)));
65
66
        $data = unserialize(file_get_contents($safeFilename));
67
        $this->assign('server', $data['server']);
68
        $this->assign('get', $data['get']);
69
        $this->assign('post', $data['post']);
70
71
        $exceptionList = [];
72
        $current = $data;
73
        do {
74
            $ex = [
75
                'exception' => $current['exception'],
76
                'message'   => str_replace($this->getSiteConfiguration()->getFilePath(), '.', $current['message']),
77
                'stack'     => str_replace($this->getSiteConfiguration()->getFilePath(), '.', $current['stack']),
78
            ];
79
            $exceptionList[] = $ex;
80
81
            $current = $current['previous'];
82
        }
83
        while ($current !== null);
84
85
        $this->assign('exceptionList', $exceptionList);
86
87
        $this->setTemplate('errorlog/details.tpl');
88
    }
89
90
    public function remove()
91
    {
92
        $safeFilename = $this->safetyCheck(WebRequest::getString('id'));
93
94
        if ($safeFilename === false) {
95
            $this->redirect('errorLog');
96
97
            return;
98
        }
99
100
        unlink($safeFilename);
101
102
        $this->redirect('errorLog');
103
104
        return;
105
    }
106
107
    /**
108
     * @param string|null $requestedErrorId
109
     *
110
     * @return bool|string
111
     */
112
    protected function safetyCheck(?string $requestedErrorId)
113
    {
114
        if ($requestedErrorId === null) {
115
            return false;
116
        }
117
118
        // security - only allow hex-encoded filenames, as this is what is generated.
119
        // This is prefixed with the configured directory. Path traversal is protected against due to . and / not being
120
        // part of the hex character set.
121
        if (!preg_match('/^[a-f0-9]{40}$/', $requestedErrorId)) {
122
            return false;
123
        }
124
125
        $errorLogDirectory = $this->getSiteConfiguration()->getErrorLog();
126
        $filename = realpath($errorLogDirectory) . DIRECTORY_SEPARATOR . $requestedErrorId . '.log';
127
128
        if (!file_exists($filename)) {
129
            return false;
130
        }
131
132
        return $filename;
133
    }
134
}