Issues (847)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

inc/Parsing/Handler/Table.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace dokuwiki\Parsing\Handler;
4
5
class Table extends AbstractRewriter
6
{
7
8
    protected $tableCalls = array();
9
    protected $maxCols = 0;
10
    protected $maxRows = 1;
11
    protected $currentCols = 0;
12
    protected $firstCell = false;
13
    protected $lastCellType = 'tablecell';
14
    protected $inTableHead = true;
15
    protected $currentRow = array('tableheader' => 0, 'tablecell' => 0);
16
    protected $countTableHeadRows = 0;
17
18
    /** @inheritdoc */
19
    public function finalise()
20
    {
21
        $last_call = end($this->calls);
22
        $this->writeCall(array('table_end',array(), $last_call[2]));
23
24
        $this->process();
25
        $this->callWriter->finalise();
26
        unset($this->callWriter);
27
    }
28
29
    /** @inheritdoc */
30
    public function process()
31
    {
32
        foreach ($this->calls as $call) {
33
            switch ($call[0]) {
34
                case 'table_start':
35
                    $this->tableStart($call);
36
                    break;
37
                case 'table_row':
38
                    $this->tableRowClose($call);
39
                    $this->tableRowOpen(array('tablerow_open',$call[1],$call[2]));
40
                    break;
41
                case 'tableheader':
42
                case 'tablecell':
43
                    $this->tableCell($call);
44
                    break;
45
                case 'table_end':
46
                    $this->tableRowClose($call);
47
                    $this->tableEnd($call);
48
                    break;
49
                default:
50
                    $this->tableDefault($call);
51
                    break;
52
            }
53
        }
54
        $this->callWriter->writeCalls($this->tableCalls);
55
56
        return $this->callWriter;
57
    }
58
59
    protected function tableStart($call)
60
    {
61
        $this->tableCalls[] = array('table_open',$call[1],$call[2]);
62
        $this->tableCalls[] = array('tablerow_open',array(),$call[2]);
63
        $this->firstCell = true;
64
    }
65
66
    protected function tableEnd($call)
67
    {
68
        $this->tableCalls[] = array('table_close',$call[1],$call[2]);
69
        $this->finalizeTable();
70
    }
71
72
    protected function tableRowOpen($call)
73
    {
74
        $this->tableCalls[] = $call;
75
        $this->currentCols = 0;
76
        $this->firstCell = true;
77
        $this->lastCellType = 'tablecell';
78
        $this->maxRows++;
79
        if ($this->inTableHead) {
80
            $this->currentRow = array('tablecell' => 0, 'tableheader' => 0);
81
        }
82
    }
83
84
    protected function tableRowClose($call)
85
    {
86
        if ($this->inTableHead && ($this->inTableHead = $this->isTableHeadRow())) {
87
            $this->countTableHeadRows++;
88
        }
89
        // Strip off final cell opening and anything after it
90
        while ($discard = array_pop($this->tableCalls)) {
91
            if ($discard[0] == 'tablecell_open' || $discard[0] == 'tableheader_open') {
92
                break;
93
            }
94
            if (!empty($this->currentRow[$discard[0]])) {
95
                $this->currentRow[$discard[0]]--;
96
            }
97
        }
98
        $this->tableCalls[] = array('tablerow_close', array(), $call[2]);
99
100
        if ($this->currentCols > $this->maxCols) {
101
            $this->maxCols = $this->currentCols;
102
        }
103
    }
104
105
    protected function isTableHeadRow()
106
    {
107
        $td = $this->currentRow['tablecell'];
108
        $th = $this->currentRow['tableheader'];
109
110
        if (!$th || $td > 2) return false;
111
        if (2*$td > $th) return false;
112
113
        return true;
114
    }
115
116
    protected function tableCell($call)
117
    {
118
        if ($this->inTableHead) {
119
            $this->currentRow[$call[0]]++;
120
        }
121
        if (!$this->firstCell) {
122
            // Increase the span
123
            $lastCall = end($this->tableCalls);
124
125
            // A cell call which follows an open cell means an empty cell so span
126
            if ($lastCall[0] == 'tablecell_open' || $lastCall[0] == 'tableheader_open') {
127
                $this->tableCalls[] = array('colspan',array(),$call[2]);
128
            }
129
130
            $this->tableCalls[] = array($this->lastCellType.'_close',array(),$call[2]);
131
            $this->tableCalls[] = array($call[0].'_open',array(1,null,1),$call[2]);
132
            $this->lastCellType = $call[0];
133
        } else {
134
            $this->tableCalls[] = array($call[0].'_open',array(1,null,1),$call[2]);
135
            $this->lastCellType = $call[0];
136
            $this->firstCell = false;
137
        }
138
139
        $this->currentCols++;
140
    }
141
142
    protected function tableDefault($call)
143
    {
144
        $this->tableCalls[] = $call;
145
    }
146
147
    protected function finalizeTable()
148
    {
149
150
        // Add the max cols and rows to the table opening
151
        if ($this->tableCalls[0][0] == 'table_open') {
152
            // Adjust to num cols not num col delimeters
153
            $this->tableCalls[0][1][] = $this->maxCols - 1;
154
            $this->tableCalls[0][1][] = $this->maxRows;
155
            $this->tableCalls[0][1][] = array_shift($this->tableCalls[0][1]);
156
        } else {
157
            trigger_error('First element in table call list is not table_open');
158
        }
159
160
        $lastRow = 0;
161
        $lastCell = 0;
162
        $cellKey = array();
163
        $toDelete = array();
164
165
        // if still in tableheader, then there can be no table header
166
        // as all rows can't be within <THEAD>
167
        if ($this->inTableHead) {
168
            $this->inTableHead = false;
169
            $this->countTableHeadRows = 0;
170
        }
171
172
        // Look for the colspan elements and increment the colspan on the
173
        // previous non-empty opening cell. Once done, delete all the cells
174
        // that contain colspans
175
        for ($key = 0; $key < count($this->tableCalls); ++$key) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
176
            $call = $this->tableCalls[$key];
177
178
            switch ($call[0]) {
179
                case 'table_open':
180
                    if ($this->countTableHeadRows) {
181
                        array_splice($this->tableCalls, $key+1, 0, array(
182
                                                          array('tablethead_open', array(), $call[2])));
183
                    }
184
                    break;
185
186
                case 'tablerow_open':
187
                    $lastRow++;
188
                    $lastCell = 0;
189
                    break;
190
191
                case 'tablecell_open':
192
                case 'tableheader_open':
193
                    $lastCell++;
194
                    $cellKey[$lastRow][$lastCell] = $key;
195
                    break;
196
197
                case 'table_align':
198
                    $prev = in_array($this->tableCalls[$key-1][0], array('tablecell_open', 'tableheader_open'));
199
                    $next = in_array($this->tableCalls[$key+1][0], array('tablecell_close', 'tableheader_close'));
200
                    // If the cell is empty, align left
201
                    if ($prev && $next) {
202
                        $this->tableCalls[$key-1][1][1] = 'left';
203
204
                        // If the previous element was a cell open, align right
205
                    } elseif ($prev) {
206
                        $this->tableCalls[$key-1][1][1] = 'right';
207
208
                        // If the next element is the close of an element, align either center or left
209
                    } elseif ($next) {
210
                        if ($this->tableCalls[$cellKey[$lastRow][$lastCell]][1][1] == 'right') {
211
                            $this->tableCalls[$cellKey[$lastRow][$lastCell]][1][1] = 'center';
212
                        } else {
213
                            $this->tableCalls[$cellKey[$lastRow][$lastCell]][1][1] = 'left';
214
                        }
215
                    }
216
217
                    // Now convert the whitespace back to cdata
218
                    $this->tableCalls[$key][0] = 'cdata';
219
                    break;
220
221
                case 'colspan':
222
                    $this->tableCalls[$key-1][1][0] = false;
223
224
                    for ($i = $key-2; $i >= $cellKey[$lastRow][1]; $i--) {
225
                        if ($this->tableCalls[$i][0] == 'tablecell_open' ||
226
                            $this->tableCalls[$i][0] == 'tableheader_open'
227
                        ) {
228
                            if (false !== $this->tableCalls[$i][1][0]) {
229
                                $this->tableCalls[$i][1][0]++;
230
                                break;
231
                            }
232
                        }
233
                    }
234
235
                    $toDelete[] = $key-1;
236
                    $toDelete[] = $key;
237
                    $toDelete[] = $key+1;
238
                    break;
239
240
                case 'rowspan':
241
                    if ($this->tableCalls[$key-1][0] == 'cdata') {
242
                        // ignore rowspan if previous call was cdata (text mixed with :::)
243
                        // we don't have to check next call as that wont match regex
244
                        $this->tableCalls[$key][0] = 'cdata';
245
                    } else {
246
                        $spanning_cell = null;
247
248
                        // can't cross thead/tbody boundary
249
                        if (!$this->countTableHeadRows || ($lastRow-1 != $this->countTableHeadRows)) {
250
                            for ($i = $lastRow-1; $i > 0; $i--) {
251
                                if ($this->tableCalls[$cellKey[$i][$lastCell]][0] == 'tablecell_open' ||
252
                                    $this->tableCalls[$cellKey[$i][$lastCell]][0] == 'tableheader_open'
253
                                ) {
254
                                    if ($this->tableCalls[$cellKey[$i][$lastCell]][1][2] >= $lastRow - $i) {
255
                                        $spanning_cell = $i;
256
                                        break;
257
                                    }
258
                                }
259
                            }
260
                        }
261
                        if (is_null($spanning_cell)) {
262
                            // No spanning cell found, so convert this cell to
263
                            // an empty one to avoid broken tables
264
                            $this->tableCalls[$key][0] = 'cdata';
265
                            $this->tableCalls[$key][1][0] = '';
266
                            break;
267
                        }
268
                        $this->tableCalls[$cellKey[$spanning_cell][$lastCell]][1][2]++;
269
270
                        $this->tableCalls[$key-1][1][2] = false;
271
272
                        $toDelete[] = $key-1;
273
                        $toDelete[] = $key;
274
                        $toDelete[] = $key+1;
275
                    }
276
                    break;
277
278
                case 'tablerow_close':
279
                    // Fix broken tables by adding missing cells
280
                    $moreCalls = array();
281
                    while (++$lastCell < $this->maxCols) {
282
                        $moreCalls[] = array('tablecell_open', array(1, null, 1), $call[2]);
283
                        $moreCalls[] = array('cdata', array(''), $call[2]);
284
                        $moreCalls[] = array('tablecell_close', array(), $call[2]);
285
                    }
286
                    $moreCallsLength = count($moreCalls);
287
                    if ($moreCallsLength) {
288
                        array_splice($this->tableCalls, $key, 0, $moreCalls);
289
                        $key += $moreCallsLength;
290
                    }
291
292
                    if ($this->countTableHeadRows == $lastRow) {
293
                        array_splice($this->tableCalls, $key+1, 0, array(
294
                            array('tablethead_close', array(), $call[2])));
295
                    }
296
                    break;
297
            }
298
        }
299
300
        // condense cdata
301
        $cnt = count($this->tableCalls);
302
        for ($key = 0; $key < $cnt; $key++) {
303
            if ($this->tableCalls[$key][0] == 'cdata') {
304
                $ckey = $key;
305
                $key++;
306
                while ($this->tableCalls[$key][0] == 'cdata') {
307
                    $this->tableCalls[$ckey][1][0] .= $this->tableCalls[$key][1][0];
308
                    $toDelete[] = $key;
309
                    $key++;
310
                }
311
                continue;
312
            }
313
        }
314
315
        foreach ($toDelete as $delete) {
316
            unset($this->tableCalls[$delete]);
317
        }
318
        $this->tableCalls = array_values($this->tableCalls);
319
    }
320
}
321