Passed
Pull Request — master (#1467)
by Michael
05:13
created

XoopsGTicket::extract_post_recursive()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 16
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 11
c 0
b 0
f 0
nc 3
nop 2
dl 0
loc 16
rs 9.9
1
<?php
2
// GIJOE's Ticket Class (based on Marijuana's Oreteki XOOPS)
3
// nobunobu's suggestions are applied
4
5
if (!class_exists('XoopsGTicket')) {
6
7
    /**
8
     * Class XoopsGTicket
9
     */
10
    class XoopsGTicket
11
    {
12
        public $_errors       = [];
13
        public $_latest_token = '';
14
        public $messages      = [];
15
16
        /**
17
         * XoopsGTicket constructor.
18
         */
19
        public function __construct()
20
        {
21
            global $xoopsConfig;
22
23
            // language file
24
            if (defined('XOOPS_ROOT_PATH') && !empty($xoopsConfig['language']) && false === strpos($xoopsConfig['language'], '/')) {
25
                if (file_exists(dirname(__DIR__) . '/language/' . $xoopsConfig['language'] . '/gticket_messages.phtml')) {
26
                    include dirname(__DIR__) . '/language/' . $xoopsConfig['language'] . '/gticket_messages.phtml';
27
                }
28
            }
29
30
            // default messages
31
            if (empty($this->messages)) {
32
                $this->messages = [
33
                    'err_general'       => 'GTicket Error',
34
                    'err_nostubs'       => 'No stubs found',
35
                    'err_noticket'      => 'No ticket found',
36
                    'err_nopair'        => 'No valid ticket-stub pair found',
37
                    'err_timeout'       => 'Time out',
38
                    'err_areaorref'     => 'Invalid area or referer',
39
                    'fmt_prompt4repost' => 'error(s) found:<br><span style="background-color:red;font-weight:bold;color:white;">%s</span><br>Confirm it.<br>And do you want to post again?',
40
                    'btn_repost'        => 'repost'
41
                ];
42
            }
43
        }
44
45
        // render form as plain html
46
        /**
47
         * @param string $salt
48
         * @param int    $timeout
49
         * @param string $area
50
         *
51
         * @return string
52
         */
53
        public function getTicketHtml($salt = '', $timeout = 1800, $area = '')
54
        {
55
            return '<input type="hidden" name="XOOPS_G_TICKET" value="' . $this->issue($salt, $timeout, $area) . '" />';
56
        }
57
58
        // returns an object of XoopsFormHidden including theh ticket
59
        /**
60
         * @param string $salt
61
         * @param int    $timeout
62
         * @param string $area
63
         *
64
         * @return XoopsFormHidden
65
         */
66
        public function getTicketXoopsForm($salt = '', $timeout = 1800, $area = '')
67
        {
68
            return new XoopsFormHidden('XOOPS_G_TICKET', $this->issue($salt, $timeout, $area));
69
        }
70
71
        // add a ticket as Hidden Element into XoopsForm
72
        /**
73
         * @param        $form
74
         * @param string $salt
75
         * @param int    $timeout
76
         * @param string $area
77
         */
78
        public function addTicketXoopsFormElement(&$form, $salt = '', $timeout = 1800, $area = '')
79
        {
80
            $form->addElement(new XoopsFormHidden('XOOPS_G_TICKET', $this->issue($salt, $timeout, $area)));
81
        }
82
83
        // returns an array for xoops_confirm() ;
84
        /**
85
         * @param string $salt
86
         * @param int    $timeout
87
         * @param string $area
88
         *
89
         * @return array
90
         */
91
        public function getTicketArray($salt = '', $timeout = 1800, $area = '')
92
        {
93
            return ['XOOPS_G_TICKET' => $this->issue($salt, $timeout, $area)];
94
        }
95
96
        // return GET parameter string.
97
        /**
98
         * @param string $salt
99
         * @param bool   $noamp
100
         * @param int    $timeout
101
         * @param string $area
102
         *
103
         * @return string
104
         */
105
        public function getTicketParamString($salt = '', $noamp = false, $timeout = 1800, $area = '')
106
        {
107
            return ($noamp ? '' : '&amp;') . 'XOOPS_G_TICKET=' . $this->issue($salt, $timeout, $area);
108
        }
109
110
        // issue a ticket
111
        /**
112
         * @param string $salt
113
         * @param int    $timeout
114
         * @param string $area
115
         *
116
         * @return string
117
         */
118
        public function issue($salt = '', $timeout = 1800, $area = '')
119
        {
120
            global $xoopsModule;
121
122
            if ('' === $salt) {
123
				$salt = '$2y$07$' . str_replace('+', '.', base64_encode(random_bytes(16)));
124
            }
125
126
            // create a token
127
            list($usec, $sec) = explode(' ', microtime());
128
            $appendix_salt       = empty($_SERVER['PATH']) ? XOOPS_DB_NAME : $_SERVER['PATH'];
129
            $token               = crypt($salt . $usec . $appendix_salt . $sec, $salt);
130
            $this->_latest_token = $token;
131
132
            if (empty($_SESSION['XOOPS_G_STUBS'])) {
133
                $_SESSION['XOOPS_G_STUBS'] = [];
134
            }
135
136
            // limit max stubs 10
137
            if (count($_SESSION['XOOPS_G_STUBS']) > 10) {
138
                $_SESSION['XOOPS_G_STUBS'] = array_slice($_SESSION['XOOPS_G_STUBS'], -10);
139
            }
140
141
            // record referer if browser send it
142
            $referer = empty($_SERVER['HTTP_REFERER']) ? '' : $_SERVER['REQUEST_URI'];
143
144
            // area as module's dirname
145
            if (!$area && isset($xoopsModule) && is_object($xoopsModule)) {
146
                $area = $xoopsModule->getVar('dirname');
147
            }
148
149
            // store stub
150
            $_SESSION['XOOPS_G_STUBS'][] = [
151
                'expire'  => time() + $timeout,
152
                'referer' => $referer,
153
                'area'    => $area,
154
                'token'   => $token
155
            ];
156
157
            // paid md5ed token as a ticket
158
            return md5($token . XOOPS_DB_PREFIX);
159
        }
160
161
        // check a ticket
162
        /**
163
         * @param bool   $post
164
         * @param string $area
165
         * @param bool   $allow_repost
166
         *
167
         * @return bool
168
         */
169
        public function check($post = true, $area = '', $allow_repost = true)
170
        {
171
            global $xoopsModule;
172
173
            $this->_errors = [];
174
175
            // CHECK: stubs are not stored in session
176
            if (!isset($_SESSION['XOOPS_G_STUBS']) || !is_array($_SESSION['XOOPS_G_STUBS'])) {
177
                $this->_errors[]           = $this->messages['err_nostubs'];
178
                $_SESSION['XOOPS_G_STUBS'] = [];
179
            }
180
181
            // get key&val of the ticket from a user's query
182
            $ticket = '';
183
            if ($post) {
184
                if (isset($_POST['XOOPS_G_TICKET'])) {
185
                    $ticket = $_POST['XOOPS_G_TICKET'];
186
                }
187
            } else {
188
                if (isset($_GET['XOOPS_G_TICKET'])) {
189
                    $ticket = $_GET['XOOPS_G_TICKET'];
190
                }
191
            }
192
193
            // CHECK: no tickets found
194
            if (empty($ticket)) {
195
                $this->_errors[] = $this->messages['err_noticket'];
196
            }
197
198
            // gargage collection & find a right stub
199
            $stubs_tmp                 = $_SESSION['XOOPS_G_STUBS'];
200
            $_SESSION['XOOPS_G_STUBS'] = [];
201
            foreach ($stubs_tmp as $stub) {
202
                // default lifetime 30min
203
                if ($stub['expire'] >= time()) {
204
                    if (md5($stub['token'] . XOOPS_DB_PREFIX) === $ticket) {
205
                        $found_stub = $stub;
206
                    } else {
207
                        // store the other valid stubs into session
208
                        $_SESSION['XOOPS_G_STUBS'][] = $stub;
209
                    }
210
                } else {
211
                    if (md5($stub['token'] . XOOPS_DB_PREFIX) === $ticket) {
212
                        // not CSRF but Time-Out
213
                        $timeout_flag = true;
214
                    }
215
                }
216
            }
217
218
            // CHECK: the right stub found or not
219
            if (empty($found_stub)) {
220
                if (empty($timeout_flag)) {
221
                    $this->_errors[] = $this->messages['err_nopair'];
222
                } else {
223
                    $this->_errors[] = $this->messages['err_timeout'];
224
                }
225
            } else {
226
227
                // set area if necessary
228
                // area as module's dirname
229
                if (!$area && isset($xoopsModule) && is_object($xoopsModule)) {
230
                    $area = $xoopsModule->getVar('dirname');
231
                }
232
233
                // check area or referer
234
                if (isset($found_stub['area']) && $found_stub['area'] == $area) {
235
                    $area_check = true;
236
                }
237
238
                if (!empty($found_stub['referer']) && isset($_SERVER['HTTP_REFERER']) && false !== strpos($_SERVER['HTTP_REFERER'], $found_stub['referer'])) {
239
                    $referer_check = true;
240
                }
241
242
243
                if (empty($area_check) && empty($referer_check)) { // loose
244
                    $this->_errors[] = $this->messages['err_areaorref'];
245
                }
246
            }
247
248
            if (!empty($this->_errors)) {
249
                if ($allow_repost) {
250
                    // repost form
251
                    $this->draw_repost_form($area);
252
                    exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
253
                } else {
254
                    // failed
255
                    $this->clear();
256
257
                    return false;
258
                }
259
            } else {
260
                // all green
261
                return true;
262
            }
263
        }
264
265
        // draw form for repost
266
        /**
267
         * @param string $area
268
         */
269
        public function draw_repost_form($area = '')
270
        {
271
            // Notify which file is broken
272
            if (headers_sent()) {
273
                restore_error_handler();
274
                set_error_handler([&$this, 'errorHandler4FindOutput']);
275
                header('Dummy: for warning');
276
                restore_error_handler();
277
                exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
278
            }
279
280
            error_reporting(0);
281
            while (ob_get_level()) {
282
                ob_end_clean();
283
            }
284
285
            $table = '<table>';
286
            $form = '<form action="?' . htmlspecialchars(isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : '', ENT_QUOTES | ENT_HTML5) . '" method="post">';
287
288
            foreach ($_POST as $key => $val) {
289
                if ($key === 'XOOPS_G_TICKET') {
290
                    continue;
291
                }
292
293
                if (is_array($val)) {
294
                    list($tmp_table, $tmp_form) = $this->extract_post_recursive(htmlspecialchars($key, ENT_QUOTES | ENT_HTML5), $val);
295
                    $table .= $tmp_table;
296
                    $form .= $tmp_form;
297
                } else {
298
                    $table .= '<tr><th>' . htmlspecialchars($key, ENT_QUOTES | ENT_HTML5) . '</th><td>' . htmlspecialchars($val, ENT_QUOTES | ENT_HTML5) . '</td></tr>' . "\n";
299
                    $form .= '<input type="hidden" name="' . htmlspecialchars($key, ENT_QUOTES | ENT_HTML5) . '" value="' . htmlspecialchars($val, ENT_QUOTES | ENT_HTML5) . '" />' . "\n";
300
                }
301
            }
302
            $table .= '</table>';
303
            $form .= $this->getTicketHtml(__LINE__, 300, $area) . '<input type="submit" value="' . $this->messages['btn_repost'] . '" /></form>';
304
305
            echo '<html><head><title>' . $this->messages['err_general'] . '</title><style>table,td,th {border:solid black 1px; border-collapse:collapse;}</style></head><body>' . sprintf($this->messages['fmt_prompt4repost'], $this->getErrors()) . $table . $form . '</body></html>';
306
        }
307
308
        /**
309
         * @param $key_name
310
         * @param $tmp_array
311
         *
312
         * @return array
313
         */
314
        public function extract_post_recursive($key_name, $tmp_array)
315
        {
316
            $table = '';
317
            $form  = '';
318
            foreach ($tmp_array as $key => $val) {
319
                if (is_array($val)) {
320
                    list($tmp_table, $tmp_form) = $this->extract_post_recursive($key_name . '[' . htmlspecialchars($key, ENT_QUOTES | ENT_HTML5) . ']', $val);
321
                    $table .= $tmp_table;
322
                    $form .= $tmp_form;
323
                } else {
324
                    $table .= '<tr><th>' . $key_name . '[' . htmlspecialchars($key, ENT_QUOTES | ENT_HTML5) . ']</th><td>' . htmlspecialchars($val, ENT_QUOTES | ENT_HTML5) . '</td></tr>' . "\n";
325
                    $form .= '<input type="hidden" name="' . $key_name . '[' . htmlspecialchars($key, ENT_QUOTES | ENT_HTML5) . ']" value="' . htmlspecialchars($val, ENT_QUOTES | ENT_HTML5) . '" />' . "\n";
326
                }
327
            }
328
329
            return [$table, $form];
330
        }
331
332
        // clear all stubs
333
        public function clear()
334
        {
335
            $_SESSION['XOOPS_G_STUBS'] = [];
336
        }
337
338
        // Ticket Using
339
        /**
340
         * @return bool
341
         */
342
        public function using()
343
        {
344
            if (!empty($_SESSION['XOOPS_G_STUBS'])) {
345
                return true;
346
            } else {
347
                return false;
348
            }
349
        }
350
351
        // return errors
352
        /**
353
         * @param bool $ashtml
354
         *
355
         * @return array|string
356
         */
357
        public function getErrors($ashtml = true)
358
        {
359
            if ($ashtml) {
360
                $ret = '';
361
                foreach ($this->_errors as $msg) {
362
                    $ret .= "$msg<br>\n";
363
                }
364
            } else {
365
                $ret = $this->_errors;
366
            }
367
368
            return $ret;
369
        }
370
371
        /**
372
         * @param $errNo
373
         * @param $errStr
374
         * @param $errFile
375
         * @param $errLine
376
         * @return null
377
         */
378
        public function errorHandler4FindOutput($errNo, $errStr, $errFile, $errLine)
0 ignored issues
show
Unused Code introduced by
The parameter $errFile is not used and could be removed. ( Ignorable by Annotation )

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

378
        public function errorHandler4FindOutput($errNo, $errStr, /** @scrutinizer ignore-unused */ $errFile, $errLine)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $errLine is not used and could be removed. ( Ignorable by Annotation )

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

378
        public function errorHandler4FindOutput($errNo, $errStr, $errFile, /** @scrutinizer ignore-unused */ $errLine)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
379
        {
380
            if (preg_match('#' . preg_quote(XOOPS_ROOT_PATH, '#') . '([^:]+)\:(\d+)?#', $errStr, $regs)) {
381
                echo 'Irregular output! check the file ' . htmlspecialchars($regs[1], ENT_QUOTES | ENT_HTML5) . ' line ' . htmlspecialchars($regs[2], ENT_QUOTES | ENT_HTML5);
382
            } else {
383
                echo 'Irregular output! check language files etc.';
384
            }
385
386
            return null;
387
        }
388
        // end of class
389
    }
390
391
    // create a instance in global scope
392
    $GLOBALS['xoopsGTicket'] = new XoopsGTicket();
393
}
394
395
if (!function_exists('admin_refcheck')) {
396
397
    //Admin Referer Check By Marijuana(Rev.011)
398
    /**
399
     * @param string $chkref
400
     *
401
     * @return bool
402
     */
403
    function admin_refcheck($chkref = '')
404
    {
405
        if (empty($_SERVER['HTTP_REFERER'])) {
406
            return true;
407
        } else {
408
            $ref = $_SERVER['HTTP_REFERER'];
409
        }
410
        $cr = XOOPS_URL;
411
        if ($chkref != '') {
412
            $cr .= $chkref;
413
        }
414
        return !(strpos($ref, $cr) !== 0);
415
    }
416
}
417