Passed
Push — search ( 8b187a...5eeca1 )
by Simon
09:13 queued 05:22
created

PageErrorLogViewer   A

Complexity

Total Complexity 10

Size/Duplication

Total Lines 122
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 10
eloc 59
c 2
b 0
f 0
dl 0
loc 122
ccs 0
cts 78
cp 0
rs 10

4 Methods

Rating   Name   Duplication   Size   Complexity  
A view() 0 40 3
A main() 0 30 1
A safetyCheck() 0 21 4
A remove() 0 15 2
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
        $exceptionList = [];
76
        $current = $data;
77
        do {
78
            $ex = [
79
                'exception' => $current['exception'],
80
                'message'   => str_replace($this->getSiteConfiguration()->getFilePath(), '.', $current['message']),
81
                'stack'     => str_replace($this->getSiteConfiguration()->getFilePath(), '.', $current['stack']),
82
            ];
83
            $exceptionList[] = $ex;
84
85
            $current = $current['previous'];
86
        }
87
        while ($current !== null);
88
89
        $this->assign('exceptionList', $exceptionList);
90
91
        $this->setTemplate('errorlog/details.tpl');
92
    }
93
94
    public function remove()
95
    {
96
        $safeFilename = $this->safetyCheck(WebRequest::getString('id'));
97
98
        if ($safeFilename === false) {
99
            $this->redirect('errorLog');
100
101
            return;
102
        }
103
104
        unlink($safeFilename);
105
106
        $this->redirect('errorLog');
107
108
        return;
109
    }
110
111
    /**
112
     * @param string|null $requestedErrorId
113
     *
114
     * @return bool|string
115
     */
116
    protected function safetyCheck(?string $requestedErrorId)
117
    {
118
        if ($requestedErrorId === null) {
119
            return false;
120
        }
121
122
        // security - only allow hex-encoded filenames, as this is what is generated.
123
        // This is prefixed with the configured directory. Path traversal is protected against due to . and / not being
124
        // part of the hex character set.
125
        if (!preg_match('/^[a-f0-9]{40}$/', $requestedErrorId)) {
126
            return false;
127
        }
128
129
        $errorLogDirectory = $this->getSiteConfiguration()->getErrorLog();
130
        $filename = realpath($errorLogDirectory) . DIRECTORY_SEPARATOR . $requestedErrorId . '.log';
131
132
        if (!file_exists($filename)) {
133
            return false;
134
        }
135
136
        return $filename;
137
    }
138
}