6 * @copyright (c) 2008 phpBB Group
7 * @license http://opensource.org/licenses/gpl-license.php GNU Public License
14 if (!defined('IN_PHPBB'))
20 * Class responsible for URL handling, URL building, redirects, meta refreshs and session id handling.
21 * Basically everything url/sid-related.
25 class phpbb_url
extends phpbb_plugin_support
28 * @var array required phpBB objects
30 public $phpbb_required = array('user', 'config');
33 * @var array Optional phpBB objects
35 public $phpbb_optional = array('template');
37 public function __construct() {}
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
46 public function is_absolute($path)
48 return ($path[0] == '/' ||
(DIRECTORY_SEPARATOR
== '\\' && preg_match('#^[a-z]:/#i', $path))) ?
true : false;
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
60 private function own_realpath($path)
62 // Switch to use UNIX slashes
63 $path = str_replace(DIRECTORY_SEPARATOR
, '/', $path);
66 // Determine what sort of path we have
67 if ($this->is_absolute($path))
73 // Absolute path, *NIX style
78 // Absolute path, Windows style
79 // Remove the drive letter and colon
80 $path_prefix = $path[0] . ':';
81 $path = substr($path, 2);
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;
93 if (preg_match('#^[a-z]:#i', $path))
95 $path_prefix = $path[0] . ':';
96 $path = substr($path, 2);
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;
113 // We have no way of getting the absolute path, just run on using relative ones.
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++
)
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!
143 unset($bits[$i - 1]);
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
158 // Prepend the path prefix
159 array_unshift($bits, $path_prefix);
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")))
173 if ($symlink_resolve && is_link("$resolved/$bit") && ($link = readlink("$resolved/$bit")))
175 // Resolved a symlink.
176 $resolved = $link . (($i == $max) ?
'' : '/');
182 // Something doesn't exist here!
183 // This is correct realpath() behaviour but sadly open_basedir and safe_mode make this problematic
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)))
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!
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
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);
254 * All urls are run through this... either after {@link append_sid() append_sid} or directly
256 * @param string $url URL to process
260 public function get($url)
266 * Append session id to url.
270 * append_sid(PHPBB_ROOT_PATH . 'viewtopic.' . PHP_EXT . '?t=1&f=2'); // VALID
271 * append_sid(PHPBB_ROOT_PATH . 'viewtopic.' . PHP_EXT, 't=1&f=2'); // VALID
272 * append_sid('viewtopic', 't=1&f=2'); // short notation of the above example - VALID
273 * append_sid('viewtopic', 't=1&f=2', false); // Instead of & use &
274 * append_sid('viewtopic', array('t' => 1, 'f' => 2)); // Instead of parameter in string notation, use an array
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 & (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
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];
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
;
310 $params_is_array = is_array($params);
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) ?
'&' : '&');
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) ?
'&' : '&';
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)
363 return $this->get($url . (($append_url) ?
$url_delim . $append_url : '') . $anchor);
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)
376 foreach ($params as $key => $item)
385 $anchor = '#' . $item;
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
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'];
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);
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
466 public function redirect($url, $return = false, $disable_cd_check = false)
468 if (empty(phpbb
::$user->lang
))
470 phpbb
::$user->add_lang('common');
475 garbage_collection();
478 // Make sure no &'s are in, this will break the redirect
479 $url = str_replace('&', '&', $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;
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;
524 $url = $this->generate_board_url() . '/' . $url;
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
);
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'] . '">';
592 echo '<meta http-equiv="content-type" content="text/html; charset=utf-8" />';
593 echo '<meta http-equiv="refresh" content="0; url=' . str_replace('&', '&', $url) . '" />';
594 echo '<title>' . phpbb
::$user->lang
['REDIRECT'] . '</title>';
597 echo '<div style="text-align: center;">' . phpbb
::$user->lang('URL_REDIRECT', '<a href="' . str_replace('&', '&', $url) . '">', '</a>') . '</div>';
604 // Behave as per HTTP/1.1 spec for others
605 header('Location: ' . $url);
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
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('&', '&', $result_url);
630 // For XHTML compatibility we change back & to &
631 phpbb
::$template->assign_var('META', '<meta http-equiv="refresh" content="' . $time . ';url=' . $result_url . '" />');
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
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]+(&|&)?/', '\1', $url);
664 else if (strpos($url, '&sid=') !== false)
666 $url = preg_replace('/&sid=[a-z0-9]+(&)?/', '\1', $url);
668 else if (strpos($url, '&sid=') !== false)
670 $url = preg_replace('/&sid=[a-z0-9]+(&)?/', '\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
683 public function build_url($strip_vars = false)
686 $redirect = $this->append_sid(phpbb
::$user->page
['page'], false, false);
688 // Add delimiter if not there...
689 if (strpos($redirect, '?') === false)
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('&', '&', $redirect);