Passed
Push — multiproject/db ( 16c0c4...9dd649 )
by Simon
15:28 queued 11:05
created

PageErrorLogViewer::view()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 42
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 25
c 2
b 0
f 0
dl 0
loc 42
ccs 0
cts 30
cp 0
rs 9.52
cc 3
nc 2
nop 0
crap 12
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
        $this->setHtmlTitle('Exception viewer');
23
24
        $user = User::getCurrent($this->getDatabase());
25
        $this->assign('canView', $this->barrierTest('view', $user));
26
        $this->assign('canRemove', $this->barrierTest('remove', $user));
27
28
        // Get the list of exception logs from the error log directory
29
        $errorLogDirectory = $this->getSiteConfiguration()->getErrorLog();
30
        $files = scandir($errorLogDirectory);
31
32
        // Exclude the files we know should be there
33
        $filteredFiles = array_filter($files, function($file) {
34
            return !in_array($file, ['.', '..', 'README.md']);
35
        });
36
37
        $exceptionDetails = array_map(function($item) use ($errorLogDirectory) {
38
            $filename = realpath($errorLogDirectory) . DIRECTORY_SEPARATOR . $item;
39
40
            return [
41
                'id'   => str_replace('.log', '', $item),
42
                'date' => date('Y-m-d H:i:s', filemtime($filename)),
43
                'data' => str_replace($this->getSiteConfiguration()->getFilePath(), '.',
44
                    unserialize(file_get_contents($filename))),
45
            ];
46
        }, $filteredFiles);
47
48
        $this->assign('exceptionEntries', $exceptionDetails);
49
        $this->setTemplate('errorlog/main.tpl');
50
    }
51
52
    protected function view()
53
    {
54
        $this->setHtmlTitle('Exception viewer');
55
56
        $requestedErrorId = WebRequest::getString('id');
57
        $safeFilename = $this->safetyCheck($requestedErrorId);
58
59
        if ($safeFilename === false) {
60
            $this->redirect('errorLog');
61
62
            return;
63
        }
64
65
        // note: at this point we've done sufficient sanity checks that we can be confident this value is safe to echo
66
        // back to the user.
67
        $this->assign('id', $requestedErrorId);
68
        $this->assign('date', date('Y-m-d H:i:s', filemtime($safeFilename)));
69
70
        $data = unserialize(file_get_contents($safeFilename));
71
        $this->assign('server', $data['server']);
72
        $this->assign('get', $data['get']);
73
        $this->assign('post', $data['post']);
74
75
        $this->assign('globalHandler', $data['globalHandler']);
76
77
        $exceptionList = [];
78
        $current = $data;
79
        do {
80
            $ex = [
81
                'exception' => $current['exception'],
82
                'message'   => str_replace($this->getSiteConfiguration()->getFilePath(), '.', $current['message']),
83
                'stack'     => str_replace($this->getSiteConfiguration()->getFilePath(), '.', $current['stack']),
84
            ];
85
            $exceptionList[] = $ex;
86
87
            $current = $current['previous'];
88
        }
89
        while ($current !== null);
90
91
        $this->assign('exceptionList', $exceptionList);
92
93
        $this->setTemplate('errorlog/details.tpl');
94
    }
95
96
    public function remove()
97
    {
98
        $safeFilename = $this->safetyCheck(WebRequest::getString('id'));
99
100
        if ($safeFilename === false) {
101
            $this->redirect('errorLog');
102
103
            return;
104
        }
105
106
        unlink($safeFilename);
107
108
        $this->redirect('errorLog');
109
110
        return;
111
    }
112
113
    /**
114
     * @param string|null $requestedErrorId
115
     *
116
     * @return bool|string
117
     */
118
    protected function safetyCheck(?string $requestedErrorId)
119
    {
120
        if ($requestedErrorId === null) {
121
            return false;
122
        }
123
124
        // security - only allow hex-encoded filenames, as this is what is generated.
125
        // This is prefixed with the configured directory. Path traversal is protected against due to . and / not being
126
        // part of the hex character set.
127
        if (!preg_match('/^[a-f0-9]{40}$/', $requestedErrorId)) {
128
            return false;
129
        }
130
131
        $errorLogDirectory = $this->getSiteConfiguration()->getErrorLog();
132
        $filename = realpath($errorLogDirectory) . DIRECTORY_SEPARATOR . $requestedErrorId . '.log';
133
134
        if (!file_exists($filename)) {
135
            return false;
136
        }
137
138
        return $filename;
139
    }
140
}