XoopsGTicket::using()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 4
dl 0
loc 6
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 0
1
<?php
2
3
// GIJOE's Ticket Class (based on Marijuana's Oreteki XOOPS)
4
// nobunobu's suggestions are applied
5
6
if (!class_exists('XoopsGTicket')) {
7
8
    /**
9
     * Class XoopsGTicket
10
     */
11
    class XoopsGTicket
12
    {
13
        public $_errors       = [];
14
        public $_latest_token = '';
15
        public $messages      = [];
16
17
        /**
18
         * XoopsGTicket constructor.
19
         */
20
        public function __construct()
21
        {
22
            global $xoopsConfig;
23
24
            // language file
25
            if (defined('XOOPS_ROOT_PATH') && !empty($xoopsConfig['language']) && false === strpos($xoopsConfig['language'], '/')) {
26
                if (file_exists(dirname(__DIR__) . '/language/' . $xoopsConfig['language'] . '/gticket_messages.phtml')) {
27
                    include dirname(__DIR__) . '/language/' . $xoopsConfig['language'] . '/gticket_messages.phtml';
28
                }
29
            }
30
31
            // default messages
32
            if (empty($this->messages)) {
33
                $this->messages = [
34
                    'err_general'       => 'GTicket Error',
35
                    'err_nostubs'       => 'No stubs found',
36
                    'err_noticket'      => 'No ticket found',
37
                    'err_nopair'        => 'No valid ticket-stub pair found',
38
                    'err_timeout'       => 'Time out',
39
                    'err_areaorref'     => 'Invalid area or referer',
40
                    '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?',
41
                    'btn_repost'        => 'repost',
42
                ];
43
            }
44
        }
45
46
        // render form as plain html
47
        /**
48
         * @param string $salt
49
         * @param int    $timeout
50
         * @param string $area
51
         *
52
         * @return string
53
         */
54
        public function getTicketHtml($salt = '', $timeout = 1800, $area = '')
55
        {
56
            return '<input type="hidden" name="XOOPS_G_TICKET" value="' . $this->issue($salt, $timeout, $area) . '" />';
57
        }
58
59
        // returns an object of XoopsFormHidden including theh ticket
60
        /**
61
         * @param string $salt
62
         * @param int    $timeout
63
         * @param string $area
64
         *
65
         * @return XoopsFormHidden
66
         */
67
        public function getTicketXoopsForm($salt = '', $timeout = 1800, $area = '')
68
        {
69
            return new XoopsFormHidden('XOOPS_G_TICKET', $this->issue($salt, $timeout, $area));
70
        }
71
72
        // add a ticket as Hidden Element into XoopsForm
73
        /**
74
         * @param        $form
75
         * @param string $salt
76
         * @param int    $timeout
77
         * @param string $area
78
         */
79
        public function addTicketXoopsFormElement($form, $salt = '', $timeout = 1800, $area = '')
80
        {
81
            $form->addElement(new XoopsFormHidden('XOOPS_G_TICKET', $this->issue($salt, $timeout, $area)));
82
        }
83
84
        // returns an array for xoops_confirm() ;
85
        /**
86
         * @param string $salt
87
         * @param int    $timeout
88
         * @param string $area
89
         *
90
         * @return array
91
         */
92
        public function getTicketArray($salt = '', $timeout = 1800, $area = '')
93
        {
94
            return ['XOOPS_G_TICKET' => $this->issue($salt, $timeout, $area)];
95
        }
96
97
        // return GET parameter string.
98
        /**
99
         * @param string $salt
100
         * @param bool   $noamp
101
         * @param int    $timeout
102
         * @param string $area
103
         *
104
         * @return string
105
         */
106
        public function getTicketParamString($salt = '', $noamp = false, $timeout = 1800, $area = '')
107
        {
108
            return ($noamp ? '' : '&amp;') . 'XOOPS_G_TICKET=' . $this->issue($salt, $timeout, $area);
109
        }
110
111
        // issue a ticket
112
        /**
113
         * @param string $salt
114
         * @param int    $timeout
115
         * @param string $area
116
         *
117
         * @return string
118
         */
119
        public function issue($salt = '', $timeout = 1800, $area = '')
120
        {
121
            global $xoopsModule;
122
123
            if ('' === $salt) {
124
                $salt = '$2y$07$' . str_replace('+', '.', base64_encode(random_bytes(16)));
125
            }
126
127
            // create a token
128
            [$usec, $sec] = explode(' ', microtime());
129
            $appendix_salt       = empty($_SERVER['PATH']) ? XOOPS_DB_NAME : $_SERVER['PATH'];
130
            $token               = crypt($salt . $usec . $appendix_salt . $sec, $salt);
131
            $this->_latest_token = $token;
132
133
            if (empty($_SESSION['XOOPS_G_STUBS'])) {
134
                $_SESSION['XOOPS_G_STUBS'] = [];
135
            }
136
137
            // limit max stubs 10
138
            if (count($_SESSION['XOOPS_G_STUBS']) > 10) {
139
                $_SESSION['XOOPS_G_STUBS'] = array_slice($_SESSION['XOOPS_G_STUBS'], -10);
140
            }
141
142
            // record referer if browser send it
143
            $referer = empty($_SERVER['HTTP_REFERER']) ? '' : $_SERVER['REQUEST_URI'];
144
145
            // area as module's dirname
146
            if (!$area && isset($xoopsModule) && is_object($xoopsModule)) {
147
                $area = $xoopsModule->getVar('dirname');
148
            }
149
150
            // store stub
151
            $_SESSION['XOOPS_G_STUBS'][] = [
152
                'expire'  => time() + $timeout,
153
                'referer' => $referer,
154
                'area'    => $area,
155
                'token'   => $token,
156
            ];
157
158
            // paid md5ed token as a ticket
159
            return md5($token . XOOPS_DB_PREFIX);
160
        }
161
162
        // check a ticket
163
        /**
164
         * @param bool   $post
165
         * @param string $area
166
         * @param bool   $allow_repost
167
         *
168
         * @return bool
169
         */
170
        public function check($post = true, $area = '', $allow_repost = true)
171
        {
172
            global $xoopsModule;
173
174
            $this->_errors = [];
175
176
            // CHECK: stubs are not stored in session
177
            if (!isset($_SESSION['XOOPS_G_STUBS']) || !is_array($_SESSION['XOOPS_G_STUBS'])) {
178
                $this->_errors[]           = $this->messages['err_nostubs'];
179
                $_SESSION['XOOPS_G_STUBS'] = [];
180
            }
181
182
            // get key&val of the ticket from a user's query
183
            $ticket = '';
184
            if ($post) {
185
                if (isset($_POST['XOOPS_G_TICKET'])) {
186
                    $ticket = $_POST['XOOPS_G_TICKET'];
187
                }
188
            } else {
189
                if (isset($_GET['XOOPS_G_TICKET'])) {
190
                    $ticket = $_GET['XOOPS_G_TICKET'];
191
                }
192
            }
193
194
            // CHECK: no tickets found
195
            if (empty($ticket)) {
196
                $this->_errors[] = $this->messages['err_noticket'];
197
            }
198
199
            // gargage collection & find a right stub
200
            $stubs_tmp                 = $_SESSION['XOOPS_G_STUBS'];
201
            $_SESSION['XOOPS_G_STUBS'] = [];
202
            foreach ($stubs_tmp as $stub) {
203
                // default lifetime 30min
204
                if ($stub['expire'] >= time()) {
205
                    if (md5($stub['token'] . XOOPS_DB_PREFIX) === $ticket) {
206
                        $found_stub = $stub;
207
                    } else {
208
                        // store the other valid stubs into session
209
                        $_SESSION['XOOPS_G_STUBS'][] = $stub;
210
                    }
211
                } else {
212
                    if (md5($stub['token'] . XOOPS_DB_PREFIX) === $ticket) {
213
                        // not CSRF but Time-Out
214
                        $timeout_flag = true;
215
                    }
216
                }
217
            }
218
219
            // CHECK: the right stub found or not
220
            if (empty($found_stub)) {
221
                if (empty($timeout_flag)) {
222
                    $this->_errors[] = $this->messages['err_nopair'];
223
                } else {
224
                    $this->_errors[] = $this->messages['err_timeout'];
225
                }
226
            } else {
227
228
                // set area if necessary
229
                // area as module's dirname
230
                if (!$area && isset($xoopsModule) && is_object($xoopsModule)) {
231
                    $area = $xoopsModule->getVar('dirname');
232
                }
233
234
                // check area or referer
235
                if (isset($found_stub['area']) && $found_stub['area'] == $area) {
236
                    $area_check = true;
237
                }
238
239
                if (!empty($found_stub['referer']) && isset($_SERVER['HTTP_REFERER']) && false !== strpos($_SERVER['HTTP_REFERER'], (string) $found_stub['referer'])) {
240
                    $referer_check = true;
241
                }
242
243
244
                if (empty($area_check) && empty($referer_check)) { // loose
245
                    $this->_errors[] = $this->messages['err_areaorref'];
246
                }
247
            }
248
249
            if (!empty($this->_errors)) {
250
                if ($allow_repost) {
251
                    // repost form
252
                    $this->draw_repost_form($area);
253
                    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...
254
                } else {
255
                    // failed
256
                    $this->clear();
257
258
                    return false;
259
                }
260
            } else {
261
                // all green
262
                return true;
263
            }
264
        }
265
266
        // draw form for repost
267
        /**
268
         * @param string $area
269
         */
270
        public function draw_repost_form($area = '')
271
        {
272
            // Notify which file is broken
273
            if (headers_sent()) {
274
                restore_error_handler();
275
                set_error_handler([&$this, 'errorHandler4FindOutput']);
276
                header('Dummy: for warning');
277
                restore_error_handler();
278
                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...
279
            }
280
281
            error_reporting(0);
282
            while (ob_get_level()) {
283
                ob_end_clean();
284
            }
285
286
            $table = '<table>';
287
            $form = '<form action="?' . htmlspecialchars($_SERVER['QUERY_STRING'] ?? '', ENT_QUOTES | ENT_HTML5) . '" method="post">';
288
289
            foreach ($_POST as $key => $val) {
290
                if ('XOOPS_G_TICKET' === $key) {
291
                    continue;
292
                }
293
294
                if (is_array($val)) {
295
                    [$tmp_table, $tmp_form] = $this->extract_post_recursive(htmlspecialchars($key, ENT_QUOTES | ENT_HTML5), $val);
296
                    $table .= $tmp_table;
297
                    $form .= $tmp_form;
298
                } else {
299
                    $table .= '<tr><th>' . htmlspecialchars($key, ENT_QUOTES | ENT_HTML5) . '</th><td>' . htmlspecialchars($val, ENT_QUOTES | ENT_HTML5) . '</td></tr>' . "\n";
300
                    $form .= '<input type="hidden" name="' . htmlspecialchars($key, ENT_QUOTES | ENT_HTML5) . '" value="' . htmlspecialchars($val, ENT_QUOTES | ENT_HTML5) . '" />' . "\n";
301
                }
302
            }
303
            $table .= '</table>';
304
            $form .= $this->getTicketHtml(__LINE__, 300, $area) . '<input type="submit" value="' . $this->messages['btn_repost'] . '" /></form>';
305
306
            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>';
307
        }
308
309
        /**
310
         * @param $key_name
311
         * @param $tmp_array
312
         *
313
         * @return array
314
         */
315
        public function extract_post_recursive($key_name, $tmp_array)
316
        {
317
            $table = '';
318
            $form  = '';
319
            foreach ($tmp_array as $key => $val) {
320
                if (is_array($val)) {
321
                    [$tmp_table, $tmp_form] = $this->extract_post_recursive($key_name . '[' . htmlspecialchars($key, ENT_QUOTES | ENT_HTML5) . ']', $val);
322
                    $table .= $tmp_table;
323
                    $form .= $tmp_form;
324
                } else {
325
                    $table .= '<tr><th>' . $key_name . '[' . htmlspecialchars($key, ENT_QUOTES | ENT_HTML5) . ']</th><td>' . htmlspecialchars($val, ENT_QUOTES | ENT_HTML5) . '</td></tr>' . "\n";
326
                    $form .= '<input type="hidden" name="' . $key_name . '[' . htmlspecialchars($key, ENT_QUOTES | ENT_HTML5) . ']" value="' . htmlspecialchars($val, ENT_QUOTES | ENT_HTML5) . '" />' . "\n";
327
                }
328
            }
329
330
            return [$table, $form];
331
        }
332
333
        // clear all stubs
334
        public function clear()
335
        {
336
            $_SESSION['XOOPS_G_STUBS'] = [];
337
        }
338
339
        // Ticket Using
340
        /**
341
         * @return bool
342
         */
343
        public function using()
344
        {
345
            if (!empty($_SESSION['XOOPS_G_STUBS'])) {
346
                return true;
347
            } else {
348
                return false;
349
            }
350
        }
351
352
        // return errors
353
        /**
354
         * @param bool $ashtml
355
         *
356
         * @return array|string
357
         */
358
        public function getErrors($ashtml = true)
359
        {
360
            if ($ashtml) {
361
                $ret = '';
362
                foreach ($this->_errors as $msg) {
363
                    $ret .= "$msg<br>\n";
364
                }
365
            } else {
366
                $ret = $this->_errors;
367
            }
368
369
            return $ret;
370
        }
371
372
        /**
373
         * @param $errNo
374
         * @param $errStr
375
         * @param $errFile
376
         * @param $errLine
377
         * @return null
378
         */
379
        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

379
        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

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