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 = array();
13: public $_latest_token = '';
14: public $messages = array();
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 = array(
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: // render form as plain html
45: /**
46: * @param string $salt
47: * @param int $timeout
48: * @param string $area
49: *
50: * @return string
51: */
52: public function getTicketHtml($salt = '', $timeout = 1800, $area = '')
53: {
54: return '<input type="hidden" name="XOOPS_G_TICKET" value="' . $this->issue($salt, $timeout, $area) . '" />';
55: }
56:
57: // returns an object of XoopsFormHidden including theh ticket
58: /**
59: * @param string $salt
60: * @param int $timeout
61: * @param string $area
62: *
63: * @return XoopsFormHidden
64: */
65: public function getTicketXoopsForm($salt = '', $timeout = 1800, $area = '')
66: {
67: return new XoopsFormHidden('XOOPS_G_TICKET', $this->issue($salt, $timeout, $area));
68: }
69:
70: // add a ticket as Hidden Element into XoopsForm
71: /**
72: * @param $form
73: * @param string $salt
74: * @param int $timeout
75: * @param string $area
76: */
77: public function addTicketXoopsFormElement(&$form, $salt = '', $timeout = 1800, $area = '')
78: {
79: $form->addElement(new XoopsFormHidden('XOOPS_G_TICKET', $this->issue($salt, $timeout, $area)));
80: }
81:
82: // returns an array for xoops_confirm() ;
83: /**
84: * @param string $salt
85: * @param int $timeout
86: * @param string $area
87: *
88: * @return array
89: */
90: public function getTicketArray($salt = '', $timeout = 1800, $area = '')
91: {
92: return array('XOOPS_G_TICKET' => $this->issue($salt, $timeout, $area));
93: }
94:
95: // return GET parameter string.
96: /**
97: * @param string $salt
98: * @param bool $noamp
99: * @param int $timeout
100: * @param string $area
101: *
102: * @return string
103: */
104: public function getTicketParamString($salt = '', $noamp = false, $timeout = 1800, $area = '')
105: {
106: return ($noamp ? '' : '&amp;') . 'XOOPS_G_TICKET=' . $this->issue($salt, $timeout, $area);
107: }
108:
109: // issue a ticket
110: /**
111: * @param string $salt
112: * @param int $timeout
113: * @param string $area
114: *
115: * @return string
116: */
117: public function issue($salt = '', $timeout = 1800, $area = '')
118: {
119: global $xoopsModule;
120:
121: if ('' === $salt) {
122: $salt = '$2y$07$' . str_replace('+', '.', base64_encode(random_bytes(16)));
123: }
124:
125: // create a token
126: list($usec, $sec) = explode(' ', microtime());
127: $appendix_salt = empty($_SERVER['PATH']) ? XOOPS_DB_NAME : $_SERVER['PATH'];
128: $token = crypt($salt . $usec . $appendix_salt . $sec, $salt);
129: $this->_latest_token = $token;
130:
131: if (empty($_SESSION['XOOPS_G_STUBS'])) {
132: $_SESSION['XOOPS_G_STUBS'] = array();
133: }
134:
135: // limit max stubs 10
136: if (count($_SESSION['XOOPS_G_STUBS']) > 10) {
137: $_SESSION['XOOPS_G_STUBS'] = array_slice($_SESSION['XOOPS_G_STUBS'], -10);
138: }
139:
140: // record referer if browser send it
141: $referer = empty($_SERVER['HTTP_REFERER']) ? '' : $_SERVER['REQUEST_URI'];
142:
143: // area as module's dirname
144: if (!$area && isset($xoopsModule) && is_object($xoopsModule)) {
145: $area = $xoopsModule->getVar('dirname');
146: }
147:
148: // store stub
149: $_SESSION['XOOPS_G_STUBS'][] = array(
150: 'expire' => time() + $timeout,
151: 'referer' => $referer,
152: 'area' => $area,
153: 'token' => $token);
154:
155: // paid md5ed token as a ticket
156: return md5($token . XOOPS_DB_PREFIX);
157: }
158:
159: // check a ticket
160: /**
161: * @param bool $post
162: * @param string $area
163: * @param bool $allow_repost
164: *
165: * @return bool
166: */
167: public function check($post = true, $area = '', $allow_repost = true)
168: {
169: global $xoopsModule;
170:
171: $this->_errors = array();
172:
173: // CHECK: stubs are not stored in session
174: if (!isset($_SESSION['XOOPS_G_STUBS']) || !is_array($_SESSION['XOOPS_G_STUBS'])) {
175: $this->_errors[] = $this->messages['err_nostubs'];
176: $_SESSION['XOOPS_G_STUBS'] = array();
177: }
178:
179: // get key&val of the ticket from a user's query
180: $ticket = '';
181: if ($post) {
182: if (isset($_POST['XOOPS_G_TICKET'])) {
183: $ticket = $_POST['XOOPS_G_TICKET'];
184: }
185: } else {
186: if (isset($_GET['XOOPS_G_TICKET'])) {
187: $ticket = $_GET['XOOPS_G_TICKET'];
188: }
189: }
190:
191: // CHECK: no tickets found
192: if (empty($ticket)) {
193: $this->_errors[] = $this->messages['err_noticket'];
194: }
195:
196: // gargage collection & find a right stub
197: $stubs_tmp = $_SESSION['XOOPS_G_STUBS'];
198: $_SESSION['XOOPS_G_STUBS'] = array();
199: foreach ($stubs_tmp as $stub) {
200: // default lifetime 30min
201: if ($stub['expire'] >= time()) {
202: if (md5($stub['token'] . XOOPS_DB_PREFIX) === $ticket) {
203: $found_stub = $stub;
204: } else {
205: // store the other valid stubs into session
206: $_SESSION['XOOPS_G_STUBS'][] = $stub;
207: }
208: } else {
209: if (md5($stub['token'] . XOOPS_DB_PREFIX) === $ticket) {
210: // not CSRF but Time-Out
211: $timeout_flag = true;
212: }
213: }
214: }
215:
216: // CHECK: the right stub found or not
217: if (empty($found_stub)) {
218: if (empty($timeout_flag)) {
219: $this->_errors[] = $this->messages['err_nopair'];
220: } else {
221: $this->_errors[] = $this->messages['err_timeout'];
222: }
223: } else {
224:
225: // set area if necessary
226: // area as module's dirname
227: if (!$area && isset($xoopsModule) && is_object($xoopsModule)) {
228: $area = $xoopsModule->getVar('dirname');
229: }
230:
231: // check area or referer
232: if (isset($found_stub['area']) && $found_stub['area'] == $area) {
233: $area_check = true;
234: }
235:
236: if (!empty($found_stub['referer']) && isset($_SERVER['HTTP_REFERER']) && false !== strpos($_SERVER['HTTP_REFERER'], $found_stub['referer'])) {
237: $referer_check = true;
238: }
239:
240:
241: if (empty($area_check) && empty($referer_check)) { // loose
242: $this->_errors[] = $this->messages['err_areaorref'];
243: }
244: }
245:
246: if (!empty($this->_errors)) {
247: if ($allow_repost) {
248: // repost form
249: $this->draw_repost_form($area);
250: exit;
251: } else {
252: // failed
253: $this->clear();
254:
255: return false;
256: }
257: } else {
258: // all green
259: return true;
260: }
261: }
262:
263: // draw form for repost
264: /**
265: * @param string $area
266: */
267: public function draw_repost_form($area = '')
268: {
269: // Notify which file is broken
270: if (headers_sent()) {
271: restore_error_handler();
272: set_error_handler(array(&$this, 'errorHandler4FindOutput'));
273: header('Dummy: for warning');
274: restore_error_handler();
275: exit;
276: }
277:
278: error_reporting(0);
279: while (ob_get_level()) {
280: ob_end_clean();
281: }
282:
283: $table = '<table>';
284: $form = '<form action="?' . htmlspecialchars(isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : '', ENT_QUOTES) . '" method="post">';
285:
286: foreach ($_POST as $key => $val) {
287: if ($key === 'XOOPS_G_TICKET') {
288: continue;
289: }
290: if (@get_magic_quotes_gpc()) {
291: $key = stripslashes($key);
292: }
293: if (is_array($val)) {
294: list($tmp_table, $tmp_form) = $this->extract_post_recursive(htmlspecialchars($key, ENT_QUOTES), $val);
295: $table .= $tmp_table;
296: $form .= $tmp_form;
297: } else {
298: if (@get_magic_quotes_gpc()) {
299: $val = stripslashes($val);
300: }
301: $table .= '<tr><th>' . htmlspecialchars($key, ENT_QUOTES) . '</th><td>' . htmlspecialchars($val, ENT_QUOTES) . '</td></tr>' . "\n";
302: $form .= '<input type="hidden" name="' . htmlspecialchars($key, ENT_QUOTES) . '" value="' . htmlspecialchars($val, ENT_QUOTES) . '" />' . "\n";
303: }
304: }
305: $table .= '</table>';
306: $form .= $this->getTicketHtml(__LINE__, 300, $area) . '<input type="submit" value="' . $this->messages['btn_repost'] . '" /></form>';
307:
308: 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>';
309: }
310:
311: /**
312: * @param $key_name
313: * @param $tmp_array
314: *
315: * @return array
316: */
317: public function extract_post_recursive($key_name, $tmp_array)
318: {
319: $table = '';
320: $form = '';
321: foreach ($tmp_array as $key => $val) {
322: if (@get_magic_quotes_gpc()) {
323: $key = stripslashes($key);
324: }
325: if (is_array($val)) {
326: list($tmp_table, $tmp_form) = $this->extract_post_recursive($key_name . '[' . htmlspecialchars($key, ENT_QUOTES) . ']', $val);
327: $table .= $tmp_table;
328: $form .= $tmp_form;
329: } else {
330: if (@get_magic_quotes_gpc()) {
331: $val = stripslashes($val);
332: }
333: $table .= '<tr><th>' . $key_name . '[' . htmlspecialchars($key, ENT_QUOTES) . ']</th><td>' . htmlspecialchars($val, ENT_QUOTES) . '</td></tr>' . "\n";
334: $form .= '<input type="hidden" name="' . $key_name . '[' . htmlspecialchars($key, ENT_QUOTES) . ']" value="' . htmlspecialchars($val, ENT_QUOTES) . '" />' . "\n";
335: }
336: }
337:
338: return array($table, $form);
339: }
340:
341: // clear all stubs
342: public function clear()
343: {
344: $_SESSION['XOOPS_G_STUBS'] = array();
345: }
346:
347: // Ticket Using
348: /**
349: * @return bool
350: */
351: public function using()
352: {
353: if (!empty($_SESSION['XOOPS_G_STUBS'])) {
354: return true;
355: } else {
356: return false;
357: }
358: }
359:
360: // return errors
361: /**
362: * @param bool $ashtml
363: *
364: * @return array|string
365: */
366: public function getErrors($ashtml = true)
367: {
368: if ($ashtml) {
369: $ret = '';
370: foreach ($this->_errors as $msg) {
371: $ret .= "$msg<br>\n";
372: }
373: } else {
374: $ret = $this->_errors;
375: }
376:
377: return $ret;
378: }
379:
380: /**
381: * @param $errNo
382: * @param $errStr
383: * @param $errFile
384: * @param $errLine
385: * @return null
386: */
387: public function errorHandler4FindOutput($errNo, $errStr, $errFile, $errLine)
388: {
389: if (preg_match('#' . preg_quote(XOOPS_ROOT_PATH, '#') . '([^:]+)\:(\d+)?#', $errStr, $regs)) {
390: echo 'Irregular output! check the file ' . htmlspecialchars($regs[1], ENT_QUOTES) . ' line ' . htmlspecialchars($regs[2], ENT_QUOTES);
391: } else {
392: echo 'Irregular output! check language files etc.';
393: }
394:
395: return null;
396: }
397: // end of class
398: }
399:
400: // create a instance in global scope
401: $GLOBALS['xoopsGTicket'] = new XoopsGTicket();
402: }
403:
404: if (!function_exists('admin_refcheck')) {
405:
406: //Admin Referer Check By Marijuana(Rev.011)
407: /**
408: * @param string $chkref
409: *
410: * @return bool
411: */
412: function admin_refcheck($chkref = '')
413: {
414: if (empty($_SERVER['HTTP_REFERER'])) {
415: return true;
416: } else {
417: $ref = $_SERVER['HTTP_REFERER'];
418: }
419: $cr = XOOPS_URL;
420: if ($chkref != '') {
421: $cr .= $chkref;
422: }
423: return !(strpos($ref, $cr) !== 0);
424: }
425: }
426: