From c533e6c1e3f3f45cea4a9e335dffd5ae7588c090 Mon Sep 17 00:00:00 2001 From: Nick Liu Date: Sat, 2 Jan 2021 02:13:11 +0100 Subject: [PATCH] #3867: Accurate relative path calculations in e107::set_paths() e107 historically conflated e_BASE with a URI path and a local file system path. e_BASE seems to have been redefined later to mean a relative path to the e107 root from the calling script, and e_HTTP was introduced to resolve URIs from the web browser. e_ROOT is the absolute path represented by e_BASE. Because of legacy usage of e_BASE depending on it being a relative path, e_BASE must remain as a relative path, but how the path was determined was incorrectly implemented. This commit fixes multiple issues with e107::set_paths(): * e_BASE is now a relative path calculated reliably by a helper function * If ./e107_handlers/e107_class.php is in a sensible place and ./class2.php can be found one directory up, this path will be used as the traversal target. * If ./e107_handlers/e107_class.php is at an exotic path, debug_backtrace() will be inspected to find "class2.php", and a relative path will be made to the dirname() of that class2.php * In CLI mode, chdir() is now called to set the working directory to the e107 root. This is to maintain relative path consistency. Previously, the absolute path would be stored in e_BASE, which may lead to inconsistent behavior. * e_HTTP is now resolved from $_SERVER['SCRIPT_NAME'] instead of $_SERVER['PHP_SELF'] because arbitrary strings and slashes can be added to the end of $_SERVER['PHP_SELF'] and could lead to e_HTTP storing URIs that descended too far. Fixes: #3867 --- e107_handlers/e107_class.php | 85 ++++++++++++++++++++++++++---------- 1 file changed, 62 insertions(+), 23 deletions(-) diff --git a/e107_handlers/e107_class.php b/e107_handlers/e107_class.php index 2d59dd1b2..f28fda8eb 100644 --- a/e107_handlers/e107_class.php +++ b/e107_handlers/e107_class.php @@ -4512,35 +4512,33 @@ class e107 $this->HTTP_SCHEME = 'https'; } - $path = ""; $i = 0; + $path = ""; - // FIXME - Again, what if someone moves handlers under the webroot? - if(!self::isCli()) + $needle = "/class2.php"; + if (file_exists(__DIR__."/..".$needle)) { - while (!file_exists("{$path}class2.php")) + $target_path = realpath(__DIR__."/..".$needle); + } + else + { + $debug_backtrace = array_reverse(debug_backtrace()); + foreach ($debug_backtrace as $stack_item) { - $path .= "../"; - $i++; + $target_path = isset($stack_item["file"]) ? $stack_item["file"] : ""; + if (substr_compare($target_path, $needle, -strlen($needle)) === 0) break; + break; } } - if (empty($_SERVER['PHP_SELF']) && !empty($_SERVER['SCRIPT_NAME'])) - { - $_SERVER['PHP_SELF'] = $_SERVER['SCRIPT_NAME']; - } + if (e107::isCli()) chdir(e_ROOT); // Maintain relative path consistency in CLI mode + $path = dirname(self::getRelativePath(getcwd(), $target_path))."/"; - $http_path = dirname($_SERVER['PHP_SELF']); + $http_path = dirname($_SERVER['SCRIPT_NAME']); $http_path = explode("/", $http_path); - $http_path = array_reverse($http_path); - $j = 0; - while ($j < $i) + for ($i = 0; $i < substr_count($path, "../"); $i ++) { - unset($http_path[$j]); - $j++; + array_pop($http_path); } - $http_path = array_reverse((array) $http_path); - - $this->server_path = implode("/", $http_path)."/"; $this->server_path = $this->fix_windows_paths($this->server_path); @@ -4556,10 +4554,7 @@ class e107 // Absolute file-path of directory containing class2.php // define("e_ROOT", realpath(dirname(__FILE__)."/../")."/"); - - - - $this->relative_base_path = (!self::isCli()) ? $path : e_ROOT; + $this->relative_base_path = $path; $_SERVER['HTTP_HOST'] = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : 'localhost'; $this->http_path = filter_var("http://{$_SERVER['HTTP_HOST']}{$this->server_path}", FILTER_SANITIZE_URL); $this->https_path = filter_var("https://{$_SERVER['HTTP_HOST']}{$this->server_path}", FILTER_SANITIZE_URL); @@ -4713,6 +4708,50 @@ class e107 return $fixed_path; } + /** + * Convert two absolute paths to a relative path between them + * @license https://creativecommons.org/licenses/by-sa/3.0/ CC BY-SA 3.0 + * @see https://stackoverflow.com/a/2638272 + * @param $from string Absolute path of traversal source + * @param $to string Absolute path of traversal destination + * @return string Relative path from the source to the destination + */ + private static function getRelativePath($from, $to) + { + $from = is_dir($from) ? rtrim($from, '\/') . '/' : $from; + $to = is_dir($to) ? rtrim($to, '\/') . '/' : $to; + $from = str_replace('\\', '/', $from); + $to = str_replace('\\', '/', $to); + + $from = explode('/', $from); + $to = explode('/', $to); + $relPath = $to; + + foreach ($from as $depth => $dir) + { + if ($dir === $to[$depth]) + { + array_shift($relPath); + } + else + { + $remaining = count($from) - $depth; + if ($remaining > 1) + { + $padLength = (count($relPath) + $remaining - 1) * -1; + $relPath = array_pad($relPath, $padLength, '..'); + break; + } + else + { + $relPath[0] = './' . $relPath[0]; + } + } + } + + return implode('/', $relPath); + } + /** * Define e_PAGE, e_SELF, e_ADMIN_AREA and USER_AREA; * The following files are assumed to use admin theme: