source: branches/1.1dev/lib/App.inc.php

Last change on this file was 782, checked in by anonymous, 15 months ago

Backporting a few things from codebase 2.x

File size: 35.4 KB
Line 
1<?php
2/**
3 * App.inc.php
4 * Code by Strangecode :: www.strangecode.com :: This document contains copyrighted information
5 */
6
7/******************************************************************************
8 * CONFIG
9 ******************************************************************************
10
11 This library has some functions that require globally defined values.
12 These are defined here.
13 */
14
15//  Message Types
16/** @constant MSG_NOTICE
17    An informational message: Welcome to asdf, Logout successful, etc. */
18define('MSG_NOTICE', 0);
19
20/** @constant MSG_SUCCESS
21    A success message: Message sent, You are logged-in, etc. */
22define('MSG_SUCCESS', 1);
23
24/** @constant MSG_WARNING
25    A warning message: Access denied, Email address invalid, Article not found, etc. */
26define('MSG_WARNING', 2);
27
28/** @constant MSG_ERR
29    Unrecoverable failure: Message could not be sent, File not found, etc. */
30define('MSG_ERR', 4); // PHP user error style.
31define('MSG_ERROR', 4);
32
33
34
35/******************************************************************************
36 * FUNCTIONS
37 ******************************************************************************
38
39/**
40 * Add a message to the string globalmessage, which is printed in the header.
41 * Just a simple way to print messages to the user.
42 *
43 * @access public
44 *
45 * @param string $message The text description of the message.
46 * @param int    $type    The type of message: MSG_NOTICE,
47 *                        MSG_SUCCESS, MSG_WARNING, or MSG_ERR.
48 * @param string $file    __FILE__.
49 * @param string $line    __LINE__.
50 */
51function raiseMsg($message, $type=MSG_NOTICE, $file=null, $line=null)
52{
53    $_SESSION['_messages'][] = array(
54        'type'    => $type,
55        'message' => $message,
56        'file'    => $file,
57        'line'    => $line
58    );
59}
60
61/**
62 * Logs a message to a user defined log file. Additional actions to take for
63 * different types of message types can be specified (ERROR, NOTICE, etc).
64 *
65 * @access public
66 *
67 * @param string $message   The text description of the message.
68 * @param int    $priority  The type of message priority (in descending order):
69 *                          LOG_EMERG     system is unusable
70 *                          LOG_ALERT     action must be taken immediately
71 *                          LOG_CRIT      critical conditions
72 *                          LOG_ERR       error conditions
73 *                          LOG_WARNING   warning conditions
74 *                          LOG_NOTICE    normal, but significant, condition
75 *                          LOG_INFO      informational message
76 *                          LOG_DEBUG     debug-level message
77 * @param string $file      The file where the log event occurs.
78 * @param string $line      The line of the file where the log event occurs.
79 */
80function logMsg($message, $priority=LOG_INFO, $file=null, $line=null)
81{
82    global $CFG;
83    static $previous_events = array();
84
85    // If priority is not specified, assume the worst.
86    if (!priorityToString($priority)) {
87        logMsg(sprintf('Log priority %s not defined. (Message: %s)', $priority, $message), LOG_EMERG, $file, $line);
88        $priority = LOG_EMERG;
89    }
90
91    // If log file is not specified, create one in the codebase root.
92    if (!is_dir($CFG->log_directory) || !is_writable($CFG->log_directory)) {
93        // We must use trigger_error rather than calling logMsg, which might lead to an infinite loop.
94        trigger_error(sprintf('Codebase error: log directory not found or writable: %s', $CFG->log_directory), E_USER_NOTICE);
95        $CFG->log_directory = '/tmp';
96        $CFG->log_filename = sprintf('%s_%s.log', getenv('USER'), getenv('HTTP_HOST'));
97    }
98
99    // In case __FILE__ and __LINE__ are not provided, note that fact.
100    $file = '' == $file ? 'unknown-file' : $file;
101    $line = '' == $line ? 'unknown-line' : $line;
102
103    // Strip HTML tags except any with more than 7 characters because that's probably not a HTML tag, e.g. <email@address.com>.
104    preg_match_all('/(<[^>\s]{7,})[^>]*>/', $message, $strip_tags_allow);
105    $message = strip_tags(preg_replace('/\s+/', ' ', $message), (!empty($strip_tags_allow[1]) ? join('> ', $strip_tags_allow[1]) . '>' : null));
106
107    // Serialize multi-line messages.
108    $message = preg_replace('/\s+/m', ' ', trim($message));
109
110    // Store this event under a unique key, counting each time it occurs so that it only gets reported a limited number of times.
111    $msg_id = md5($message . $priority . $file . $line);
112    if ($CFG->log_ignore_repeated_events && isset($previous_events[$msg_id])) {
113        $previous_events[$msg_id]++;
114        if ($previous_events[$msg_id] == 2) {
115            logMsg(sprintf('%s (Event repeated %s or more times)', $message, $previous_events[$msg_id]), $priority, $file, $line);
116        }
117        return false;
118    } else {
119        $previous_events[$msg_id] = 1;
120    }
121
122    // Make sure to log in the system's locale.
123    $locale = setlocale(LC_TIME, 0);
124    setlocale(LC_TIME, 'C');
125
126    // Data to be stored for a log event.
127    $event = array(
128        'date'      => date('Y-m-d H:i:s'),
129        'remote ip' => getRemoteAddr(),
130        'pid'       => getmypid(),
131        'type'      => priorityToString($priority),
132        'file:line' => "$file : $line",
133        'url'       => (isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : ''),
134        'message'   => $message
135    );
136    // Here's a shortened version of event data.
137    $event_short = $event;
138    $event_short['url'] = truncate($event_short['url'], 120);
139
140    // Restore original locale.
141    setlocale(LC_TIME, $locale);
142
143    // FILE ACTION
144    if ($CFG->log_file_priority && $priority <= $CFG->log_file_priority) {
145        $event_str = '[' . join('] [', $event_short) . ']';
146        error_log($event_str . "\n", 3, $CFG->log_directory . '/' . $CFG->log_filename);
147    }
148
149    // EMAIL ACTION
150    if ($CFG->log_email_priority && $priority <= $CFG->log_email_priority) {
151        if (empty($CFG->log_to_email)) {
152            $CFG->log_to_email = 'bug@strangecode.com';
153        }
154        $hostname = (isset($_SERVER['HTTP_HOST']) && '' != $_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : php_uname('n');
155        $subject = sprintf('[%s %s] %s', $hostname, $event['type'], mb_substr($event['message'], 0, 64));
156        $email_msg = sprintf("A log event of type '%s' occurred on %s\n\n", $event['type'], $hostname);
157        $headers = "From: codebase@strangecode.com\r\n";
158        foreach ($event as $k=>$v) {
159            $email_msg .= sprintf("%-11s%s\n", $k, $v);
160        }
161        mb_send_mail($CFG->log_to_email, $subject, $email_msg, $headers, '-f codebase@strangecode.com');
162    }
163
164    // SMS ACTION
165    if ($CFG->log_sms_priority && $priority <= $CFG->log_sms_priority) {
166        if (empty($CFG->log_to_email)) {
167            $CFG->log_to_sms = 'bug@strangecode.com';
168        }
169        $hostname = (isset($_SERVER['HTTP_HOST']) && '' != $_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : php_uname('n');
170        $subject = sprintf('[%s %s]', $hostname, $priority);
171        $sms_msg = sprintf('%s [%s:%s]', mb_substr($event_short['message'], 0, 64), basename($file), $line);
172        $headers = 'From: ' . $CFG->site_email;
173        mb_send_mail($CFG->log_to_sms, $subject, $sms_msg, $headers);
174    }
175
176    // SCREEN ACTION
177    if ($CFG->log_screen_priority && $priority <= $CFG->log_screen_priority) {
178        echo "[{$event['date']}] [{$event['type']}] [{$event['file:line']}] [{$event['message']}]\n";
179    }
180}
181
182/**
183 * Returns the string representation of a LOG_* integer constant.
184 *
185 * @param int  $priority  The LOG_* integer constant.
186 *
187 * @return                The string representation of $priority.
188 */
189function priorityToString ($priority) {
190    $priorities = array(
191        LOG_EMERG   => 'emergency',
192        LOG_ALERT   => 'alert',
193        LOG_CRIT    => 'critical',
194        LOG_ERR     => 'error',
195        LOG_WARNING => 'warning',
196        LOG_NOTICE  => 'notice',
197        LOG_INFO    => 'info',
198        LOG_DEBUG   => 'debug'
199    );
200    if (isset($priorities[$priority])) {
201        return $priorities[$priority];
202    } else {
203        return false;
204    }
205}
206
207/**
208 * Set the URL to return to when dieBoomerangURL() is called.
209 *
210 * @param string  $url  A fully validated URL.
211 * @param bool  $id     An identification tag for this url.
212 * FIXME: url garbage collection?
213 */
214function setBoomerangURL($url=null, $id=null)
215{
216    // A redirection will never happen immediately after setting the boomerangURL.
217    // Set the time so ensure this doesn't happen. See validBoomerangURL for more.
218
219    if (isset($url) && is_string($url)) {
220        // Delete any boomerang request keys in the query string (along with any trailing delimiters after the deletion).
221        $url = preg_replace(array('/([&?])boomerang=[^&?]+[&?]?/', '/[&?]$/'), array('$1', ''), $url);
222
223        if (isset($_SESSION['_boomerang']['url']) && is_array($_SESSION['_boomerang']['url']) && !empty($_SESSION['_boomerang']['url'])) {
224            // If the URL currently exists in the boomerang array, delete.
225            while ($existing_key = array_search($url, $_SESSION['_boomerang']['url'])) {
226                unset($_SESSION['_boomerang']['url'][$existing_key]);
227            }
228        }
229
230        if (isset($id)) {
231            $_SESSION['_boomerang']['url'][$id] = $url;
232        } else {
233            $_SESSION['_boomerang']['url'][] = $url;
234        }
235        logMsg(sprintf('setBoomerangURL added URL %s to session %s=%s', $url, session_name(), session_id()), LOG_DEBUG, __FILE__, __LINE__);
236        return true;
237    } else {
238        return false;
239    }
240}
241
242/**
243 * Return the URL set for the specified $id.
244 *
245 * @param string  $id     An identification tag for this url.
246 */
247function getBoomerangURL($id=null)
248{
249    if (isset($id)) {
250        if (isset($_SESSION['_boomerang']['url'][$id])) {
251            return $_SESSION['_boomerang']['url'][$id];
252        } else {
253            return '';
254        }
255    } else if (is_array($_SESSION['_boomerang']['url'])) {
256        return end($_SESSION['_boomerang']['url']);
257    } else {
258        return false;
259    }
260}
261
262/**
263 * Delete the URL set for the specified $id.
264 *
265 * @param string  $id     An identification tag for this url.
266 */
267function deleteBoomerangURL($id=null)
268{
269    if (isset($id) && isset($_SESSION['_boomerang']['url'][$id])) {
270        unset($_SESSION['_boomerang']['url'][$id]);
271    } else if (is_array($_SESSION['_boomerang']['url'])) {
272        array_pop($_SESSION['_boomerang']['url']);
273    }
274}
275
276/**
277 * Check if a valid boomerang URL value has been set.
278 * if it is not the current url, and has not been accessed within n seconds.
279 *
280 * @return bool  True if it is set and not the current URL.
281 */
282function validBoomerangURL($id=null, $use_nonspecific_boomerang=false)
283{
284    global $CFG;
285
286    if (!isset($_SESSION['_boomerang']['url'])) {
287        logMsg(sprintf('validBoomerangURL no URL set in session %s=%s', session_name(), session_id()), LOG_DEBUG, __FILE__, __LINE__);
288        return false;
289    }
290
291    // Time is the timestamp of a boomerangURL redirection, or setting of a boomerangURL.
292    // a boomerang redirection will always occur at least several seconds after the last boomerang redirect
293    // or a boomerang being set.
294    $boomerang_time = isset($_SESSION['_boomerang']['time']) ? $_SESSION['_boomerang']['time'] : 0;
295
296    if (isset($id) && isset($_SESSION['_boomerang']['url'][$id])) {
297        $url = $_SESSION['_boomerang']['url'][$id];
298    } else if (!isset($id) || $use_nonspecific_boomerang) {
299        // Use non specific boomerang if available.
300        $url = end($_SESSION['_boomerang']['url']);
301    } else {
302        // If URL is not specified, use the $CFG->redirect_home config value.
303        $url = $CFG->redirect_home;
304    }
305
306    logMsg(sprintf('validBoomerangURL testing url: %s', $url), LOG_DEBUG, __FILE__, __LINE__);
307    if (empty($url)) {
308        return false;
309    }
310    if ($url == absoluteMe() || $url == getenv('REQUEST_URI')) {
311        // The URL we are directing to is not the current page.
312        logMsg(sprintf('Boomerang URL not valid, same as absoluteMe or REQUEST_URI: %s', $url), LOG_DEBUG, __FILE__, __LINE__);
313        return false;
314    }
315    if ($boomerang_time >= (time() - 2)) {
316        // Last boomerang direction was more than 2 seconds ago.
317        logMsg(sprintf('Boomerang URL not valid, boomerang_time too short: %s', time() - $boomerang_time), LOG_DEBUG, __FILE__, __LINE__);
318        return false;
319    }
320
321    return true;
322}
323
324/*
325* Redirects a user by calling App::dieURL(). It will use:
326* 1. the stored boomerang URL, it it exists
327* 2. a specified $default_url, it it exists
328* 3. the referring URL, it it exists.
329* 4. redirect_home_url configuration variable.
330*
331* @access   public
332* @param    string  $id             Identifier for this script.
333* @param    mixed   $carry_args     Additional arguments to carry in the URL automatically (see App::oHREF()).
334* @param    string  $default_url    A default URL if there is not a valid specified boomerang URL.
335* @return   bool                    False if the session is not running. No return otherwise.
336* @author   Quinn Comendant <quinn@strangecode.com>
337* @since    31 Mar 2006 19:17:00
338*/
339function dieBoomerangURL($id=null, $carry_args=null, $default_url=null)
340{
341    // Get URL from stored boomerang. Allow non specific URL if ID not valid.
342    if (validBoomerangURL($id, true)) {
343        if (isset($id) && isset($_SESSION['_boomerang']['url'][$id])) {
344            $url = $_SESSION['_boomerang']['url'][$id];
345            logMsg(sprintf('dieBoomerangURL(%s) found: %s', $id, $url), LOG_DEBUG, __FILE__, __LINE__);
346        } else {
347            $url = end($_SESSION['_boomerang']['url']);
348            logMsg(sprintf('dieBoomerangURL(%s) using: %s', $id, $url), LOG_DEBUG, __FILE__, __LINE__);
349        }
350    } else if (isset($default_url)) {
351        $url = $default_url;
352    } else if (!refererIsMe() && '' != getenv('HTTP_REFERER')) {
353        // Ensure that the redirecting page is not also the referrer.
354        $url = getenv('HTTP_REFERER');
355        logMsg(sprintf('dieBoomerangURL(%s) using referrer: %s', $id, $url), LOG_DEBUG, __FILE__, __LINE__);
356    } else {
357        $url = '';
358        logMsg(sprintf('dieBoomerangURL(%s) using empty: %s', $id, $url), LOG_DEBUG, __FILE__, __LINE__);
359    }
360
361    logMsg(sprintf('dieBoomerangURL found URL: %s', $url), LOG_DEBUG, __FILE__, __LINE__);
362
363    // Delete stored boomerang.
364    deleteBoomerangURL($id);
365
366    // A redirection will never happen immediately twice.
367    // Set the time so ensure this doesn't happen.
368    $_SESSION['_boomerang']['time'] = time();
369    dieURL($url, $carry_args);
370}
371
372/**
373 * Uses an http header to redirect the client to the given $url. If sessions are not used
374 * and the session is not already defined in the given $url, the SID is appended as a URI query.
375 * As with all header generating functions, make sure this is called before any other output.
376 *
377 * @param   string  $url                    The URL the client will be redirected to.
378 * @param   mixed   $carry_args             Additional url arguments to carry in the query,
379 *                                          or FALSE to prevent carrying queries. Can be any of the following formats:
380 *                                          -array('key1', key2', key3')  <-- to save these keys if in the form data.
381 *                                          -array('key1'=>'value', key2'='value')  <-- to set keys to default values if not present in form data.
382 *                                          -false  <-- To not carry any queries. If URL already has queries those will be retained.
383 * @param   bool    $always_include_sid     Force session id to be added to Location header.
384 */
385function dieURL($url, $carry_args=null, $always_include_sid=false)
386{
387    global $CFG;
388
389    if ('' == $url) {
390        // If URL is not specified, use the redirect_home.
391        $url = $CFG->redirect_home;
392    }
393
394    if (preg_match('!^/!', $url)) {
395        // If relative URL is given, prepend correct local hostname.
396        $hostname = ('on' == getenv('HTTPS')) ? 'https://' . getenv('HTTP_HOST') : 'http://' . getenv('HTTP_HOST');
397        $url = $hostname . $url;
398    }
399
400    $url = url($url, $carry_args, $always_include_sid);
401
402    header(sprintf('Location: %s', $url));
403    logMsg(sprintf('dieURL dying to URL: %s', $url), LOG_DEBUG, __FILE__, __LINE__);
404    die;
405}
406
407/**
408 * Prints a hidden form element with the PHPSESSID when cookies are not used, as well
409 * as hidden form elements for GET_VARS that might be in use.
410 *
411 * @global string $carry_queries     An array of keys to define which values to
412 *                                   carry through from the POST or GET.
413 *                                   $carry_queries = array('qry'); for example
414 *
415 * @param  mixed  $carry_args        Additional url arguments to carry in the query,
416 *                                   or FALSE to prevent carrying queries. Can be any of the following formats:
417 *                                   -array('key1', key2', key3')  <-- to save these keys if in the form data.
418 *                                   -array('key1'=>'value', key2'='value')  <-- to set keys to default values if not present in form data.
419 *                                   -false  <-- To not carry any queries. If URL already has queries those will be retained.
420 */
421function printHiddenSession($carry_args=null, $include_csrf_token=false)
422{
423    static $_using_trans_sid;
424    global $carry_queries;
425
426    // Save the trans_sid setting.
427    if (!isset($_using_trans_sid)) {
428        $_using_trans_sid = ini_get('session.use_trans_sid');
429    }
430
431    // Initialize the carried queries.
432    if (!isset($carry_queries['_carry_queries_init'])) {
433        if (!is_array($carry_queries)) {
434            $carry_queries = array($carry_queries);
435        }
436        $tmp = $carry_queries;
437        $carry_queries = array();
438        foreach ($tmp as $key) {
439            if (!empty($key) && getFormData($key, false)) {
440                $carry_queries[$key] = getFormData($key);
441            }
442        }
443        $carry_queries['_carry_queries_init'] = true;
444    }
445
446    // Get any additional query names to add to the $carry_queries array
447    // that are found as function arguments.
448    // If FALSE is a function argument, DO NOT carry the queries.
449    $do_carry_queries = true;
450    $one_time_carry_queries = array();
451    if (!is_null($carry_args)) {
452        if (is_array($carry_args) && !empty($carry_args)) {
453            foreach ($carry_args as $key=>$arg) {
454                // Get query from appropriate source.
455                if (false === $arg) {
456                    $do_carry_queries = false;
457                } else if (false !== getFormData($arg, false)) {
458                    $one_time_carry_queries[$arg] = getFormData($arg); // Set arg to form data if available.
459                } else if (!is_numeric($key) && '' != $arg) {
460                    $one_time_carry_queries[$key] = getFormData($key, $arg); // Set to arg to default if specified (overwritten by form data).
461                }
462            }
463        } else if (false !== getFormData($carry_args, false)) {
464            $one_time_carry_queries[$carry_args] = getFormData($carry_args);
465        } else if (false === $carry_args) {
466            $do_carry_queries = false;
467        }
468    }
469
470    // For each existing POST value, we create a hidden input to carry it through a form.
471    if ($do_carry_queries) {
472        // Join the perm and temp carry_queries and filter out the _carry_queries_init element for the final query args.
473        $query_args = array_diff_assoc(array_merge($carry_queries, $one_time_carry_queries), array('_carry_queries_init' => true));
474        foreach ($query_args as $key=>$val) {
475            echo '<input type="hidden" name="' . $key . '" value="' . $val . '" />';
476        }
477    }
478
479    // Include the SID if cookies are disabled.
480    if (!isset($_COOKIE[session_name()]) && !$_using_trans_sid) {
481        echo '<input type="hidden" name="' . session_name() . '" value="' . session_id() . '" />';
482    }
483
484    // Include the csrf_token in the form.
485    // This token can be validated upon form submission with $app->verifyCSRFToken() or $app->requireValidCSRFToken()
486    if ($include_csrf_token) {
487        printf('<input type="hidden" name="csrf_token" value="%s" />', getCSRFToken());
488    }
489}
490
491/**
492 * Outputs a fully qualified URL with a query of all the used (ie: not empty)
493 * keys and values, including optional queries. This allows simple printing of
494 * links without needing to know which queries to add to it. If cookies are not
495 * used, the session id will be propogated in the URL.
496 *
497 * @global string $carry_queries       An array of keys to define which values to
498 *                                     carry through from the POST or GET.
499 *                                     $carry_queries = array('qry'); for example.
500 *
501 * @param  string $url                 The initial url
502 * @param  mixed  $carry_args          Additional url arguments to carry in the query,
503 *                                     or FALSE to prevent carrying queries. Can be any of the following formats:
504 *                                     -array('key1', key2', key3')  <-- to save these keys if in the form data.
505 *                                     -array('key1'=>'value', key2'='value')  <-- to set keys to default values if not present in form data.
506 *                                     -false  <-- To not carry any queries. If URL already has queries those will be retained.
507 *
508 * @param  mixed  $always_include_sid  Always add the session id, even if using_trans_sid = true. This is required when
509 *                                     URL starts with http, since PHP using_trans_sid doesn't do those and also for
510 *                                     header('Location...') redirections.
511 *
512 * @return string url with attached queries and, if not using cookies, the session id
513 */
514function url($url='', $carry_args=null, $always_include_sid=false)
515{
516    static $_using_trans_sid;
517    global $carry_queries;
518    global $CFG;
519
520    // Save the trans_sid setting.
521    if (!isset($_using_trans_sid)) {
522        $_using_trans_sid = ini_get('session.use_trans_sid');
523    }
524
525    // Initialize the carried queries.
526    if (!isset($carry_queries['_carry_queries_init'])) {
527        if (!is_array($carry_queries)) {
528            $carry_queries = array($carry_queries);
529        }
530        $tmp = $carry_queries;
531        $carry_queries = array();
532        foreach ($tmp as $key) {
533            if (!empty($key) && getFormData($key, false)) {
534                $carry_queries[$key] = getFormData($key);
535            }
536        }
537        $carry_queries['_carry_queries_init'] = true;
538    }
539
540    // Get any additional query arguments to add to the $carry_queries array.
541    // If FALSE is a function argument, DO NOT carry the queries.
542    $do_carry_queries = true;
543    $one_time_carry_queries = array();
544    if (!is_null($carry_args)) {
545        if (is_array($carry_args) && !empty($carry_args)) {
546            foreach ($carry_args as $key=>$arg) {
547                // Get query from appropriate source.
548                if (false === $arg) {
549                    $do_carry_queries = false;
550                } else if (false !== getFormData($arg, false)) {
551                    $one_time_carry_queries[$arg] = getFormData($arg); // Set arg to form data if available.
552                } else if (!is_numeric($key) && '' != $arg) {
553                    $one_time_carry_queries[$key] = getFormData($key, $arg); // Set to arg to default if specified (overwritten by form data).
554                }
555            }
556        } else if (false !== getFormData($carry_args, false)) {
557            $one_time_carry_queries[$carry_args] = getFormData($carry_args);
558        } else if (false === $carry_args) {
559            $do_carry_queries = false;
560        }
561    }
562
563    // Get the first delimiter that is needed in the url.
564    $delim = preg_match('/\?/', $url) ? ini_get('arg_separator.output') : '?';
565
566    $q = '';
567    if ($do_carry_queries) {
568        // Join the perm and temp carry_queries and filter out the _carry_queries_init element for the final query args.
569        $query_args = array_diff_assoc(urlEncodeArray(array_merge($carry_queries, $one_time_carry_queries)), array('_carry_queries_init' => true));
570        foreach ($query_args as $key=>$val) {
571            // Check value is set and value does not already exist in the url.
572            if (!preg_match('/[?&]' . preg_quote($key) . '=/', $url)) {
573                $q .= $delim . $key . '=' . $val;
574                $delim = ini_get('arg_separator.output');
575            }
576        }
577    }
578
579    // Include the necessary SID if the following is true:
580    // - no cookie in http request OR cookies disabled in config
581    // - sessions are enabled
582    // - the link stays on our site
583    // - transparent SID propagation with session.use_trans_sid is not being used OR url begins with protocol (using_trans_sid has no effect here)
584    // OR
585    // - we must include the SID because we say so (it's used in a context where cookies will not be effective, ie. moving from http to https)
586    // AND
587    // - the SID is not already in the query.
588    if (
589        (
590            (
591                (
592                    !isset($_COOKIE[session_name()])
593                    || !$CFG->session_use_cookies
594                )
595                && $CFG->session_use_trans_sid
596                && $CFG->enable_session
597                && isMyDomain($url)
598                &&
599                (
600                    !$_using_trans_sid
601                    || preg_match('!^(http|https)://!i', $url)
602                )
603            )
604            || $always_include_sid
605        )
606        && !preg_match('/[?&]' . preg_quote(session_name()) . '=/', $url)
607    ) {
608        $url .= $q . $delim . session_name() . '=' . session_id();
609//         logMsg(sprintf('oHREF appending session id to URL: %s', $url), LOG_DEBUG, __FILE__, __LINE__);
610    } else {
611        $url .= $q;
612    }
613
614    return $url;
615}
616
617/**
618 * Returns a URL processed with App::url and htmlentities for printing in html.
619 *
620 * @access  public
621 * @param   string  $url    Input URL to parse.
622 * @return  string          URL with App::url() and htmlentities() applied.
623 * @author  Quinn Comendant <quinn@strangecode.com>
624 * @since   09 Dec 2005 17:58:45
625 */
626function oHREF($url, $carry_args=null, $always_include_sid=false)
627{
628    $url = url($url, $carry_args, $always_include_sid);
629    // Replace any & not followed by an html or unicode entity with it's &amp; equivalent.
630    $url = preg_replace('/&(?![\w\d#]{1,10};)/', '&amp;', $url);
631    return $url;
632}
633
634/*
635* Generate a csrf_token if it doesn't exist or is expired, save it to the session and return its value.
636* Otherwise just return the current token.
637* Details on the synchronizer token pattern:
638* https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet#General_Recommendation:_Synchronizer_Token_Pattern
639*
640* @access   public
641* @param    bool    $force_new_token    Generate a new token, replacing any existing token in the session (used by $app->resetCSRFToken())
642* @return   string The new or current csrf_token
643* @author   Quinn Comendant <quinn@strangecode.com>
644* @version  1.0
645* @since    15 Nov 2014 17:57:17
646*/
647function getCSRFToken($force_new_token=false)
648{
649    if ($force_new_token || !isset($_SESSION['csrf_token']) || (removeSignature($_SESSION['csrf_token']) + 86400 < time())) {
650        // No token, or token is expired; generate one and return it.
651        return $_SESSION['csrf_token'] = addSignature(time(), null, 64);
652    }
653    // Current token is not expired; return it.
654    return $_SESSION['csrf_token'];
655}
656
657/*
658* Generate a new token, replacing any existing token in the session. Call this function after $app->requireValidCSRFToken() for a new token to be required for each request.
659*
660* @access   public
661* @return   void
662* @author   Quinn Comendant <quinn@strangecode.com>
663* @since    14 Oct 2021 17:35:19
664*/
665function resetCSRFToken()
666{
667    getCSRFToken(true);
668}
669
670/*
671* Compares the given csrf_token with the current or previous one saved in the session.
672*
673* @access   public
674* @param    string  $user_submitted_csrf_token The user-submitted token to compare with the session token.
675* @return   bool    True if the tokens match, false otherwise.
676* @author   Quinn Comendant <quinn@strangecode.com>
677* @version  1.0
678* @since    15 Nov 2014 18:06:55
679*/
680function verifyCSRFToken($user_submitted_csrf_token)
681{
682
683    if ('' == trim($user_submitted_csrf_token)) {
684        logMsg(sprintf('Empty string failed CSRF verification.', null), LOG_NOTICE, __FILE__, __LINE__);
685        return false;
686    }
687    if (!verifySignature($user_submitted_csrf_token, null, 64)) {
688        logMsg(sprintf('Input failed CSRF verification (invalid signature in %s).', $user_submitted_csrf_token), LOG_WARNING, __FILE__, __LINE__);
689        return false;
690    }
691    $csrf_token = getCSRFToken();
692    if ($user_submitted_csrf_token != $csrf_token) {
693        logMsg(sprintf('Input failed CSRF verification (%s not in %s).', $user_submitted_csrf_token, $csrf_token), LOG_WARNING, __FILE__, __LINE__);
694        return false;
695    }
696    logMsg(sprintf('Verified CSRF token %s', $user_submitted_csrf_token), LOG_DEBUG, __FILE__, __LINE__);
697    return true;
698}
699
700/*
701* Bounce user if they submit a token that doesn't match the one saved in the session.
702* Because this function calls dieURL() it must be called before any other HTTP header output.
703*
704* @access   public
705* @param    string  $message    Optional message to display to the user (otherwise default message will display). Set to an empty string to display no message.
706* @param    int    $type    The type of message: MSG_NOTICE,
707*                           MSG_SUCCESS, MSG_WARNING, or MSG_ERR.
708* @param    string $file    __FILE__.
709* @param    string $line    __LINE__.
710* @return   void
711* @author   Quinn Comendant <quinn@strangecode.com>
712* @version  1.0
713* @since    15 Nov 2014 18:10:17
714*/
715function requireValidCSRFToken($message=null, $type=MSG_NOTICE, $file=null, $line=null)
716{
717    if (!verifyCSRFToken(getFormData('csrf_token'))) {
718        $message = isset($message) ? $message : _("Sorry, the form token expired. Please try again.");
719        raiseMsg($message, $type, $file, $line);
720        dieBoomerangURL();
721    }
722}
723
724/**
725 * This function has changed to do nothing. SSL redirection should happen at the server layer, doing so here may result in a redirect loop.
726 */
727function sslOn()
728{
729    logMsg(sprintf('sslOn was called and ignored.', null), LOG_DEBUG, __FILE__, __LINE__);
730}
731
732/**
733 * This function has changed to do nothing. There is no reason to prefer a non-SSL connection, and doing so may result in a redirect loop.
734 */
735function sslOff()
736{
737    logMsg(sprintf('sslOff was called and ignored.', null), LOG_DEBUG, __FILE__, __LINE__);
738}
739
740/**
741 * If the given $url is on the same web site, return true. This can be used to
742 * prevent from sending sensitive info in a get query (like the SID) to another
743 * domain. $method can be "ip" or "domain". The domain method might be preferred
744 * if your domain spans mutiple IP's (load sharing servers)
745 *
746 * @param  string $url    the URI to test.
747 * @param  mixed $method  the method to use. Either 'domain' or 'ip'.
748 *
749 * @return bool    true if given $url is this domain or has no domain (is a
750 *                 relative url), false if it's another
751 */
752function isMyDomain($url)
753{
754    if (!preg_match('|\w{1,}\.\w{2,5}/|', $url)) {
755        // If we can't find a domain we assume the URL is relative.
756        return true;
757    } else {
758        return preg_match('/' . preg_quote(getenv('HTTP_HOST')) . '/', $url);
759    }
760}
761
762/**
763 * Loads a list of tables in the current database into an array, and returns
764 * true if the requested table is found. Use this function to enable/disable
765 * funtionality based upon the current available db tables.
766 *
767 * @param  string $table    The name of the table to search.
768 *
769 * @return bool    true if given $table exists.
770 */
771function dbTableExists($table)
772{
773    static $existing_tables;
774
775    // Save the trans_sid setting.
776    if (!isset($existing_tables)) {
777        // Get DB tables.
778        $existing_tables = array();
779        $qid = dbQuery("SHOW TABLES");
780        while (list($row) = mysql_fetch_row($qid)) {
781            $existing_tables[] = $row;
782        }
783    }
784
785    // Test if requested table is in database.
786    return in_array($table, $existing_tables);
787}
788
789/**
790 * Takes a URL and returns it without the query or anchor portion
791 *
792 * @param  string $url   any kind of URI
793 *
794 * @return string        the URI with ? or # and everything after removed
795 */
796function stripQuery($url)
797{
798    return preg_replace('![?#].*!', '', $url);
799}
800
801/**
802 * Returns the remote IP address, taking into consideration proxy servers.
803 *
804 * @param  bool $dolookup   If true we resolve to IP to a host name,
805 *                          if false we don't.
806 *
807 * @return string    IP address if $dolookup is false or no arg
808 *                   Hostname if $dolookup is true
809 */
810function getRemoteAddr($dolookup=false)
811{
812    $ip = getenv('HTTP_CLIENT_IP');
813    if (empty($ip) || $ip == 'unknown' || $ip == 'localhost' || $ip == '127.0.0.1') {
814        $ip = getenv('HTTP_X_FORWARDED_FOR');
815        if (empty($ip) || $ip == 'unknown' || $ip == 'localhost' || $ip == '127.0.0.1') {
816            $ip = getenv('REMOTE_ADDR');
817        }
818    }
819    return $dolookup && '' != $ip ? gethostbyaddr($ip) : $ip;
820}
821
822/**
823 * Tests whether a given iP address can be found in an array of IP address networks.
824 * Elements of networks array can be single IP addresses or an IP address range in CIDR notation
825 * See: http://en.wikipedia.org/wiki/Classless_inter-domain_routing
826 *
827 * @access  public
828 *
829 * @param   string  IP address to search for.
830 * @param   array   Array of networks to search within.
831 *
832 * @return  mixed   Returns the network that matched on success, false on failure.
833 */
834function ipInRange($my_ip, $ip_pool)
835{
836    if (!is_array($ip_pool)) {
837        $ip_pool = array($ip_pool);
838    }
839
840    $my_ip_binary = sprintf('%032b', ip2long($my_ip));
841    foreach ($ip_pool as $ip) {
842        if (preg_match('![\d\.]{7,15}/\d{1,2}!', $ip)) {
843            // IP is in CIDR notation.
844            list($cidr_ip, $cidr_bitmask) = split('/', $ip);
845            $cidr_ip_binary = sprintf('%032b', ip2long($cidr_ip));
846            if (substr($my_ip_binary, 0, $cidr_bitmask) === substr($cidr_ip_binary, 0, $cidr_bitmask)) {
847               // IP address is within the specified IP range.
848               return $ip;
849            }
850        } else {
851            if ($my_ip === $ip) {
852               // IP address exactly matches.
853               return $ip;
854            }
855        }
856    }
857
858    return false;
859}
860
861/**
862 * Returns a fully qualified URL to the current script, including the query.
863 *
864 * @return string    a full url to the current script
865 */
866function absoluteMe()
867{
868    $safe_http_host = preg_replace('/[^a-z\d.:-]/', '', getenv('HTTP_HOST'));
869    return sprintf('%s://%s%s', (getenv('HTTPS') ? 'https' : 'http'), $safe_http_host, getenv('REQUEST_URI'));
870}
871
872/**
873 * Compares the current url with the referring url.
874 *
875 * @param  string  $compary_query  Include the query string in the comparison.
876 *
877 * @return bool    true if the current script (or specified valid_referer)
878 *                 is the referrer. false otherwise.
879 */
880function refererIsMe($exclude_query=false)
881{
882    $current_url = absoluteMe();
883    $referrer_url = getenv('HTTP_REFERER');
884
885    // If one of the hostnames is an IP address, compare only the path of both.
886    if (preg_match('/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/', parse_url($current_url, PHP_URL_HOST)) || preg_match('/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/', parse_url($referrer_url, PHP_URL_HOST))) {
887        $current_url = preg_replace('@^https?://[^/]+@u', '', $current_url);
888        $referrer_url = preg_replace('@^https?://[^/]+@u', '', $referrer_url);
889    }
890
891    if ($exclude_query) {
892        return (stripQuery($current_url) == stripQuery($referrer_url));
893    } else {
894        logMsg(sprintf('refererIsMe comparison: %s == %s', $current_url, $referrer_url), LOG_DEBUG, __FILE__, __LINE__);
895        return ($current_url == $referrer_url);
896    }
897}
Note: See TracBrowser for help on using the repository browser.