Update code_sniffer build.xml file to be executable on our system
[phpbb.git] / phpBB / includes / core / url.php
blobc182998b875fdbc08824f0fa6dfdae7475013791
1 <?php
2 /**
4 * @package core
5 * @version $Id$
6 * @copyright (c) 2008 phpBB Group
7 * @license http://opensource.org/licenses/gpl-license.php GNU Public License
9 */
11 /**
12 * @ignore
14 if (!defined('IN_PHPBB'))
16 exit();
19 /**
20 * Class responsible for URL handling, URL building, redirects, meta refreshs and session id handling.
21 * Basically everything url/sid-related.
23 * @package core
25 class phpbb_url extends phpbb_plugin_support
27 /**
28 * @var array required phpBB objects
30 public $phpbb_required = array('user', 'config');
32 /**
33 * @var array Optional phpBB objects
35 public $phpbb_optional = array('template');
37 public function __construct() {}
39 /**
40 * Checks if a path ($path) is absolute or relative
42 * @param string $path Path to check absoluteness of
43 * @return bool True if path is absolute
44 * @access public
46 public function is_absolute($path)
48 return ($path[0] == '/' || (DIRECTORY_SEPARATOR == '\\' && preg_match('#^[a-z]:/#i', $path))) ? true : false;
51 /**
52 * Mimic PHP realpath implementation
54 * @author Chris Smith <chris@project-minerva.org>
55 * @copyright 2006 Project Minerva Team
56 * @param string $path The path which we should attempt to resolve.
57 * @return mixed realpath
58 * @access private
60 private function own_realpath($path)
62 // Switch to use UNIX slashes
63 $path = str_replace(DIRECTORY_SEPARATOR, '/', $path);
64 $path_prefix = '';
66 // Determine what sort of path we have
67 if ($this->is_absolute($path))
69 $absolute = true;
71 if ($path[0] == '/')
73 // Absolute path, *NIX style
74 $path_prefix = '';
76 else
78 // Absolute path, Windows style
79 // Remove the drive letter and colon
80 $path_prefix = $path[0] . ':';
81 $path = substr($path, 2);
84 else
86 // Relative Path
87 // Prepend the current working directory
88 if (function_exists('getcwd'))
90 // This is the best method, hopefully it is enabled!
91 $path = str_replace(DIRECTORY_SEPARATOR, '/', getcwd()) . '/' . $path;
92 $absolute = true;
93 if (preg_match('#^[a-z]:#i', $path))
95 $path_prefix = $path[0] . ':';
96 $path = substr($path, 2);
98 else
100 $path_prefix = '';
103 else if (!empty($_SERVER['SCRIPT_FILENAME']))
105 // Warning: If chdir() has been used this will lie!
106 // Warning: This has some problems sometime (CLI can create them easily)
107 $path = str_replace(DIRECTORY_SEPARATOR, '/', dirname($_SERVER['SCRIPT_FILENAME'])) . '/' . $path;
108 $absolute = true;
109 $path_prefix = '';
111 else
113 // We have no way of getting the absolute path, just run on using relative ones.
114 $absolute = false;
115 $path_prefix = '.';
119 // Remove any repeated slashes
120 $path = preg_replace('#/{2,}#', '/', $path);
122 // Remove the slashes from the start and end of the path
123 $path = trim($path, '/');
125 // Break the string into little bits for us to nibble on
126 $bits = explode('/', $path);
128 // Remove any . in the path, renumber array for the loop below
129 $bits = array_values(array_diff($bits, array('.')));
131 // Lets get looping, run over and resolve any .. (up directory)
132 for ($i = 0, $max = sizeof($bits); $i < $max; $i++)
134 // @todo Optimise
135 if ($bits[$i] == '..' )
137 if (isset($bits[$i - 1]))
139 if ($bits[$i - 1] != '..')
141 // We found a .. and we are able to traverse upwards, lets do it!
142 unset($bits[$i]);
143 unset($bits[$i - 1]);
144 $i -= 2;
145 $max -= 2;
146 $bits = array_values($bits);
149 else if ($absolute) // ie. !isset($bits[$i - 1]) && $absolute
151 // We have an absolute path trying to descend above the root of the filesystem
152 // ... Error!
153 return false;
158 // Prepend the path prefix
159 array_unshift($bits, $path_prefix);
161 $resolved = '';
163 $max = sizeof($bits) - 1;
165 // Check if we are able to resolve symlinks, Windows cannot.
166 $symlink_resolve = (function_exists('readlink')) ? true : false;
168 foreach ($bits as $i => $bit)
170 if (@is_dir("$resolved/$bit") || ($i == $max && @is_file("$resolved/$bit")))
172 // Path Exists
173 if ($symlink_resolve && is_link("$resolved/$bit") && ($link = readlink("$resolved/$bit")))
175 // Resolved a symlink.
176 $resolved = $link . (($i == $max) ? '' : '/');
177 continue;
180 else
182 // Something doesn't exist here!
183 // This is correct realpath() behaviour but sadly open_basedir and safe_mode make this problematic
184 // return false;
186 $resolved .= $bit . (($i == $max) ? '' : '/');
189 // @todo If the file exists fine and open_basedir only has one path we should be able to prepend it
190 // because we must be inside that basedir, the question is where...
191 // @internal The slash in is_dir() gets around an open_basedir restriction
192 if (!@file_exists($resolved) || (!is_dir($resolved . '/') && !is_file($resolved)))
194 return false;
197 // Put the slashes back to the native operating systems slashes
198 $resolved = str_replace('/', DIRECTORY_SEPARATOR, $resolved);
200 // Check for DIRECTORY_SEPARATOR at the end (and remove it!)
201 if (substr($resolved, -1) == DIRECTORY_SEPARATOR)
203 return substr($resolved, 0, -1);
206 // We got here, in the end!
207 return $resolved;
211 * A wrapper for realpath
213 * @param string $path The path which we should attempt to resolve.
214 * @staticvar string $_phpbb_realpath_exist This is set to false if the PHP function realpath() is not accessible or returns incorrect results
216 * @return string Real path
217 * @access public
219 public function realpath($path)
221 static $_phpbb_realpath_exist;
223 if (!isset($_phpbb_realpath_exist))
225 $_phpbb_realpath_exist = (!function_exists('realpath')) ? false : true;
228 if (!$_phpbb_realpath_exist)
230 return $this->own_realpath($path);
233 $realpath = realpath($path);
235 // Strangely there are provider not disabling realpath but returning strange values. :o
236 // We at least try to cope with them.
237 if ($realpath === $path || $realpath === false)
239 $_phpbb_realpath_exist = false;
240 return $this->own_realpath($path);
243 // Check for DIRECTORY_SEPARATOR at the end (and remove it!)
244 if (substr($realpath, -1) == DIRECTORY_SEPARATOR)
246 $realpath = substr($realpath, 0, -1);
249 return $realpath;
253 * URL wrapper
254 * All urls are run through this... either after {@link append_sid() append_sid} or directly
256 * @param string $url URL to process
257 * @return string URL
258 * @access public
260 public function get($url)
262 return $url;
266 * Append session id to url.
268 * Examples:
269 * <code>
270 * append_sid(PHPBB_ROOT_PATH . 'viewtopic.' . PHP_EXT . '?t=1&amp;f=2'); // VALID
271 * append_sid(PHPBB_ROOT_PATH . 'viewtopic.' . PHP_EXT, 't=1&amp;f=2'); // VALID
272 * append_sid('viewtopic', 't=1&amp;f=2'); // short notation of the above example - VALID
273 * append_sid('viewtopic', 't=1&f=2', false); // Instead of &amp; use &
274 * append_sid('viewtopic', array('t' => 1, 'f' => 2)); // Instead of parameter in string notation, use an array
275 * </code>
277 * @param string $url The url the session id needs to be appended to (without parameter)
278 * @param string|array $params String or array of additional url parameter.
279 * @param bool $is_amp Is url using &amp; (true) or & (false)
280 * @param string $session_id Possibility to use a custom session id instead of the global one. This also forces the use of a session id.
282 * @plugin-support default, return
283 * @return string URL
284 * @access public
286 public function append_sid($url, $params = false, $is_amp = true, $session_id = false)
288 static $parsed_urls = array();
290 // The following code is used to make sure such calls like append_sid('viewtopic') (ommitting phpbb_root_path and php_ext) work as intended
291 if (isset($parsed_urls[$url]))
293 // Set an url like 'viewtopic' to PHPBB_ROOT_PATH . 'viewtopic.' . PHP_EXT
294 $url = $parsed_urls[$url];
296 else
298 // If we detect an url without root path and extension, and also not a relative or absolute path, we add it and put it to the parsed urls
299 if (strpos($url, '.' . PHP_EXT) === false && $url[0] != '.' && $url[0] != '/')
301 $parsed_urls[$url] = $url = PHPBB_ROOT_PATH . $url . '.' . PHP_EXT;
305 if (empty($params))
307 $params = false;
310 $params_is_array = is_array($params);
312 // Get anchor
313 $anchor = '';
314 if (strpos($url, '#') !== false)
316 list($url, $anchor) = explode('#', $url, 2);
317 $anchor = '#' . $anchor;
319 else if (!$params_is_array && strpos($params, '#') !== false)
321 list($params, $anchor) = explode('#', $params, 2);
322 $anchor = '#' . $anchor;
325 // Handle really simple cases quickly
326 if ($session_id === false && !phpbb::$user->need_sid && empty(phpbb::$user->extra_url) && !$params_is_array && !$anchor)
328 if ($params === false)
330 return $this->get($url);
333 $url_delim = (strpos($url, '?') === false) ? '?' : (($is_amp) ? '&amp;' : '&');
334 return $this->get($url . ($params !== false ? $url_delim . $params : ''));
337 // Assign sid if session id is not specified
338 if (phpbb::$user->need_sid && $session_id === false)
340 $session_id = phpbb::$user->session_id;
343 $amp_delim = ($is_amp) ? '&amp;' : '&';
344 $url_delim = (strpos($url, '?') === false) ? '?' : $amp_delim;
346 // Appending custom url parameter?
347 $append_url = (!empty(phpbb::$user->extra_url)) ? implode($amp_delim, phpbb::$user->extra_url) : '';
349 if ($this->method_inject(__FUNCTION__)) $this->call_inject(__FUNCTION__, array('default', &$url, &$params, &$session_id, &$append_url, &$anchor, &$amp_delim, &$url_delim));
351 if ($this->method_inject(__FUNCTION__, 'return'))
353 $url = $this->call_inject(__FUNCTION__, array('return', $url, $params, $session_id, $append_url, $anchor, $amp_delim, $url_delim));
354 return $this->get($url);
357 // Use the short variant if possible ;)
358 if ($params === false)
360 // Append session id
361 if (!$session_id)
363 return $this->get($url . (($append_url) ? $url_delim . $append_url : '') . $anchor);
365 else
367 return $this->get($url . (($append_url) ? $url_delim . $append_url . $amp_delim : $url_delim) . 'sid=' . $session_id . $anchor);
371 // Build string if parameters are specified as array
372 if ($params_is_array)
374 $output = array();
376 foreach ($params as $key => $item)
378 if ($item === NULL)
380 continue;
383 if ($key == '#')
385 $anchor = '#' . $item;
386 continue;
389 $output[] = $key . '=' . $item;
392 $params = implode($amp_delim, $output);
395 // Append session id and parameter
396 return $this->get($url . (($append_url) ? $url_delim . $append_url : '') . (($params) ? (($append_url) ? $amp_delim : $url_delim) . $params : '') . ((!$session_id) ? '' : $amp_delim . 'sid=' . $session_id) . $anchor);
400 * Generate board url (example: http://www.example.com/phpBB)
402 * @param bool $without_script_path If set to true the script path gets not appended (example: http://www.example.com instead of http://www.example.com/phpBB)
403 * @return string Board URL
404 * @access public
406 public function generate_board_url($without_script_path = false)
408 $server_name = phpbb::$user->system['host'];
409 $server_port = phpbb::$user->system['port'];
411 // Forcing server vars is the only way to specify/override the protocol
412 if (phpbb::$config['force_server_vars'] || !$server_name)
414 $server_protocol = (phpbb::$config['server_protocol']) ? phpbb::$config['server_protocol'] : ((phpbb::$config['cookie_secure']) ? 'https://' : 'http://');
415 $server_name = phpbb::$config['server_name'];
416 $server_port = (int) phpbb::$config['server_port'];
417 $script_path = phpbb::$config['script_path'];
419 $url = $server_protocol . $server_name;
420 $cookie_secure = phpbb::$config['cookie_secure'];
422 else
424 // Do not rely on cookie_secure, users seem to think that it means a secured cookie instead of an encrypted connection
425 $cookie_secure = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? 1 : 0;
426 $url = (($cookie_secure) ? 'https://' : 'http://') . $server_name;
428 $script_path = phpbb::$user->page['root_script_path'];
431 if ($server_port && (($cookie_secure && $server_port <> 443) || (!$cookie_secure && $server_port <> 80)))
433 // HTTP HOST can carry a port number (we fetch $user->system['host'], but for old versions this may be true)
434 if (strpos($server_name, ':') === false)
436 $url .= ':' . $server_port;
440 if (!$without_script_path)
442 $url .= $script_path;
445 // Strip / from the end
446 if (substr($url, -1, 1) == '/')
448 $url = substr($url, 0, -1);
451 return $url;
455 * Redirects the user to another page then exits the script nicely
456 * This function is intended for urls within the board. It's not meant to redirect to cross-domains.
458 * @param string $url The url to redirect to
459 * @param bool $return If true, do not redirect but return the sanitized URL.
460 * @param bool $disable_cd_check If true, redirect() will support redirects to an external domain.
461 * If false, the redirect points to the boards url if it does not match the current domain.
463 * @return mixed Sanitized URL if $return is true
464 * @access public
466 public function redirect($url, $return = false, $disable_cd_check = false)
468 if (empty(phpbb::$user->lang))
470 phpbb::$user->add_lang('common');
473 if (!$return)
475 garbage_collection();
478 // Make sure no &amp;'s are in, this will break the redirect
479 $url = str_replace('&amp;', '&', $url);
481 // Determine which type of redirect we need to handle...
482 $url_parts = parse_url($url);
484 if ($url_parts === false)
486 // Malformed url, redirect to current page...
487 $url = $this->generate_board_url() . '/' . phpbb::$user->page['page'];
489 else if (!empty($url_parts['scheme']) && !empty($url_parts['host']))
491 // Attention: only able to redirect within the same domain if $disable_cd_check is false (yourdomain.com -> www.yourdomain.com will not work)
492 if (!$disable_cd_check && $url_parts['host'] !== phpbb::$user->system['host'])
494 $url = $this->generate_board_url();
497 else if ($url[0] == '/')
499 // Absolute uri, prepend direct url...
500 $url = $this->generate_board_url(true) . $url;
502 else
504 // Relative uri
505 $pathinfo = pathinfo($url);
507 // Is the uri pointing to the current directory?
508 if ($pathinfo['dirname'] == '.')
510 $url = str_replace('./', '', $url);
512 // Strip / from the beginning
513 if ($url && substr($url, 0, 1) == '/')
515 $url = substr($url, 1);
518 if (phpbb::$user->page['page_dir'])
520 $url = $this->generate_board_url() . '/' . phpbb::$user->page['page_dir'] . '/' . $url;
522 else
524 $url = $this->generate_board_url() . '/' . $url;
527 else
529 // Used ./ before, but PHPBB_ROOT_PATH is working better with urls within another root path
530 $root_dirs = explode('/', str_replace('\\', '/', $this->realpath(PHPBB_ROOT_PATH)));
531 $page_dirs = explode('/', str_replace('\\', '/', $this->realpath($pathinfo['dirname'])));
532 $intersection = array_intersect_assoc($root_dirs, $page_dirs);
534 $root_dirs = array_diff_assoc($root_dirs, $intersection);
535 $page_dirs = array_diff_assoc($page_dirs, $intersection);
537 $dir = str_repeat('../', sizeof($root_dirs)) . implode('/', $page_dirs);
539 // Strip / from the end
540 if ($dir && substr($dir, -1, 1) == '/')
542 $dir = substr($dir, 0, -1);
545 // Strip / from the beginning
546 if ($dir && substr($dir, 0, 1) == '/')
548 $dir = substr($dir, 1);
551 $url = str_replace($pathinfo['dirname'] . '/', '', $url);
553 // Strip / from the beginning
554 if (substr($url, 0, 1) == '/')
556 $url = substr($url, 1);
559 $url = (!empty($dir) ? $dir . '/' : '') . $url;
560 $url = $this->generate_board_url() . '/' . $url;
564 // Make sure no linebreaks are there... to prevent http response splitting for PHP < 4.4.2
565 if (strpos(urldecode($url), "\n") !== false || strpos(urldecode($url), "\r") !== false || strpos($url, ';') !== false)
567 trigger_error('Tried to redirect to potentially insecure url.', E_USER_ERROR);
570 // Now, also check the protocol and for a valid url the last time...
571 $allowed_protocols = array('http', 'https', 'ftp', 'ftps');
572 $url_parts = parse_url($url);
574 if ($url_parts === false || empty($url_parts['scheme']) || !in_array($url_parts['scheme'], $allowed_protocols))
576 trigger_error('Tried to redirect to potentially insecure url.', E_USER_ERROR);
579 if ($return)
581 return $url;
584 // Redirect via an HTML form for PITA webservers
585 if (@preg_match('#Microsoft|WebSTAR|Xitami#', getenv('SERVER_SOFTWARE')))
587 header('Refresh: 0; URL=' . $url);
589 echo '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">';
590 echo '<html xmlns="http://www.w3.org/1999/xhtml" dir="' . phpbb::$user->lang['DIRECTION'] . '" lang="' . phpbb::$user->lang['USER_LANG'] . '" xml:lang="' . phpbb::$user->lang['USER_LANG'] . '">';
591 echo '<head>';
592 echo '<meta http-equiv="content-type" content="text/html; charset=utf-8" />';
593 echo '<meta http-equiv="refresh" content="0; url=' . str_replace('&', '&amp;', $url) . '" />';
594 echo '<title>' . phpbb::$user->lang['REDIRECT'] . '</title>';
595 echo '</head>';
596 echo '<body>';
597 echo '<div style="text-align: center;">' . phpbb::$user->lang('URL_REDIRECT', '<a href="' . str_replace('&', '&amp;', $url) . '">', '</a>') . '</div>';
598 echo '</body>';
599 echo '</html>';
601 exit;
604 // Behave as per HTTP/1.1 spec for others
605 header('Location: ' . $url);
606 exit;
610 * Meta refresh assignment
612 * If the template object is present, the META template variable holds the meta refresh, else a normal redirect is done.
614 * @param int $time The time in seconds when to redirect
615 * @param string $url The URL to redirect to
616 * @param bool $disable_cd_check If true, redirect() will support redirects to an external domain.
617 * If false, the redirect points to the boards url if it does not match the current domain.
619 * @return string Sanitized URL
620 * @plugin-support return
621 * @access public
623 public function meta_refresh($time, $url, $disable_cd_check = false)
625 if (phpbb::registered('template'))
627 $result_url = $this->redirect($url, true, $disable_cd_check);
628 $result_url = str_replace('&', '&amp;', $result_url);
630 // For XHTML compatibility we change back & to &amp;
631 phpbb::$template->assign_var('META', '<meta http-equiv="refresh" content="' . $time . ';url=' . $result_url . '" />');
633 else
635 $this->redirect($url, false, $disable_cd_check);
638 return ($this->method_inject(__FUNCTION__, 'return')) ? $this->call_inject(__FUNCTION__, array('return', $result_url, $time, $url, $disable_cd_check)) : $result_url;
642 * Re-Apply session id after page reloads
644 * @param string $url URL to re-apply session id to
645 * @return string URL with re-applied session id
646 * @access public
648 public function reapply_sid($url)
650 if ($url === 'index.' . PHP_EXT)
652 return $this->append_sid('index.' . PHP_EXT);
654 else if ($url === PHPBB_ROOT_PATH . 'index.' . PHP_EXT)
656 return $this->append_sid('index');
659 // Remove previously added sid
660 if (strpos($url, '?sid=') !== false)
662 $url = preg_replace('/(\?)sid=[a-z0-9]+(&amp;|&)?/', '\1', $url);
664 else if (strpos($url, '&sid=') !== false)
666 $url = preg_replace('/&sid=[a-z0-9]+(&)?/', '\1', $url);
668 else if (strpos($url, '&amp;sid=') !== false)
670 $url = preg_replace('/&amp;sid=[a-z0-9]+(&amp;)?/', '\1', $url);
673 return $this->append_sid($url);
677 * Returns url from the session/current page with an re-appended SID with optionally stripping vars from the url
679 * @param array|string $strip_vars An array containing variables to be stripped from the URL.
680 * @return string Current page URL with re-applied SID and optionally stripped parameter
681 * @access public
683 public function build_url($strip_vars = false)
685 // Append SID
686 $redirect = $this->append_sid(phpbb::$user->page['page'], false, false);
688 // Add delimiter if not there...
689 if (strpos($redirect, '?') === false)
691 $redirect .= '?';
694 // Strip vars...
695 if ($strip_vars !== false && strpos($redirect, '?') !== false)
697 if (!is_array($strip_vars))
699 $strip_vars = array($strip_vars);
702 $query = $_query = array();
704 $args = substr($redirect, strpos($redirect, '?') + 1);
705 $args = ($args) ? explode('&', $args) : array();
706 $redirect = substr($redirect, 0, strpos($redirect, '?'));
708 foreach ($args as $argument)
710 $arguments = explode('=', $argument);
711 $key = $arguments[0];
712 unset($arguments[0]);
714 $query[$key] = implode('=', $arguments);
717 // Strip the vars off
718 foreach ($strip_vars as $strip)
720 if (isset($query[$strip]))
722 unset($query[$strip]);
726 // Glue the remaining parts together... already urlencoded
727 foreach ($query as $key => $value)
729 $_query[] = $key . '=' . $value;
731 $query = implode('&', $_query);
733 $redirect .= ($query) ? '?' . $query : '';
736 return PHPBB_ROOT_PATH . str_replace('&', '&amp;', $redirect);