%PDF- %PDF-
Direktori : /var/www/projetos/stb.ind.br/wp-content/plugins/webp-express/lib/classes/ |
Current File : /var/www/projetos/stb.ind.br/wp-content/plugins/webp-express/lib/classes/PathHelper.php |
<?php namespace WebPExpress; class PathHelper { public static function isDocRootAvailable() { //return false; if (!isset($_SERVER['DOCUMENT_ROOT'])) { return false; } if ($_SERVER['DOCUMENT_ROOT'] == '') { return false; } return true; } /** * Test if a path exists as is resolvable (will be unless it is outside open_basedir) * * @param string $absPath The path to test (must be absolute. symlinks allowed) * @return boolean The result */ public static function pathExistsAndIsResolvable($absPath) { if (!@realpath($absPath)) { return false; } return true; } /** * Test if document root is available, exists and symlinks are resolvable (resolved path is within open basedir) * * @return boolean The result */ public static function isDocRootAvailableAndResolvable() { return ( self::isDocRootAvailable() && self::pathExistsAndIsResolvable($_SERVER['DOCUMENT_ROOT']) ); } public static function fixAbsPathToUseUnresolvedDocRoot($absPath) { if (self::isDocRootAvailableAndResolvable()) { if (strpos($absPath, realpath($_SERVER['DOCUMENT_ROOT'])) === 0) { return $_SERVER['DOCUMENT_ROOT'] . substr($absPath, strlen(realpath($_SERVER['DOCUMENT_ROOT']))); } } return $absPath; } /** * Find out if path is below - or equal to a path. * * "/var/www" below/equal to "/var"? : Yes * "/var/www" below/equal to "/var/www"? : Yes * "/var/www2" below/equal to "/var/www"? : No */ /* public static function isPathBelowOrEqualToPath($path1, $path2) { return (strpos($path1 . '/', $path2 . '/') === 0); //$rel = self::getRelDir($path2, $path1); //return (substr($rel, 0, 3) != '../'); }*/ /** * Calculate relative path from document root to a given absolute path (must exist and be resolvable) - if possible AND * if it can be done without directory traversal. * * The function is designed with the usual folders in mind (index, uploads, wp-content, plugins), which all presumably * exists and are within open_basedir. * * @param string $dir An absolute path (may contain symlinks). The path must exist and be resolvable. * @throws \Exception If it is not possible to get such path (ie if doc-root is unavailable or the dir is outside doc-root) * @return string Relative path to document root or empty string if document root is unavailable */ public static function getRelPathFromDocRootToDirNoDirectoryTraversalAllowed($dir) { if (!self::isDocRootAvailable()) { throw new \Exception('Cannot calculate relative path from document root to dir, as document root is not available'); } // First try unresolved. // This will even work when ie wp-content is symlinked to somewhere outside document root, while the symlink itself is within document root) $relPath = self::getRelDir($_SERVER['DOCUMENT_ROOT'], $dir); if (strpos($relPath, '../') !== 0) { // Check if relPath starts with "../" (if it does, we cannot use it) return $relPath; } if (self::isDocRootAvailableAndResolvable()) { if (self::pathExistsAndIsResolvable($dir)) { // Try with both resolved $relPath = self::getRelDir(realpath($_SERVER['DOCUMENT_ROOT']), realpath($dir)); if (strpos($relPath, '../') !== 0) { return $relPath; } } // Try with just document root resolved $relPath = self::getRelDir(realpath($_SERVER['DOCUMENT_ROOT']), $dir); if (strpos($relPath, '../') !== 0) { return $relPath; } } if (self::pathExistsAndIsResolvable($dir)) { // Try with dir resolved $relPath = self::getRelDir($_SERVER['DOCUMENT_ROOT'], realpath($dir)); if (strpos($relPath, '../') !== 0) { return $relPath; } } // Problem: // - dir is already resolved (ie: /disk/the-content) // - document root is ie. /var/www/website/wordpress // - the unresolved symlink is ie. /var/www/website/wordpress/wp-content // - we do not know what the unresolved symlink is // The result should be "wp-content". But how do we get to that result? // I guess we must check out all folders below document root to see if anyone resolves to dir // we could start out trying usual suspects such as "wp-content" and "wp-content/uploads" //foreach (glob($dir . DIRECTORY_SEPARATOR . $filePattern) as $filename) /* $iter = new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator($_SERVER['DOCUMENT_ROOT'], \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::SELF_FIRST, \RecursiveIteratorIterator::CATCH_GET_CHILD // Ignore "Permission denied" ); foreach ($iter as $path => $dirObj) { if ($dirObj->isDir()) { if (realpath($path) == $dir) { //return $path; $relPath = self::getRelDir(realpath($_SERVER['DOCUMENT_ROOT']), $path); if (strpos($relPath, '../') !== 0) { return $relPath; } } } } */ // Ok, the above works - but when subfolders to the symlink is referenced. Ie referencing uploads when wp-content is symlinked // - dir is already resolved (ie: /disk/the-content/uploads) // - document root is ie. /var/www/website/wordpress // - the unresolved symlink is ie. /var/www/website/wordpress/wp-content/uploads // - we do not know what the unresolved symlink is // The result should be "wp-content/uploads". But how do we get to that result? // What if we collect all symlinks below document root in a assoc array? // ['/disk/the-content' => 'wp-content'] // Input is: '/disk/the-content/uploads' // 1. We check the symlinks and substitute. We get: 'wp-content/uploads'. // 2. We test if realpath($_SERVER['DOCUMENT_ROOT'] . '/' . 'wp-content/uploads') equals input. // It seems I have a solution! // - I shall continue work soon! - for a 0.15.1 release (test instance #26) // PS: cache the result of the symlinks in docroot collector. throw new \Exception( 'Cannot get relative path from document root to dir without resolving to directory traversal. ' . 'It seems the dir is not below document root' ); /* if (!self::pathExistsAndIsResolvable($dir)) { throw new \Exception('Cannot calculate relative path from document root to dir. The path given is not resolvable (realpath fails)'); } // Check if relPath starts with "../" if (strpos($relPath, '../') === 0) { // Unresolved failed. Try with document root resolved $relPath = self::getRelDir(realpath($_SERVER['DOCUMENT_ROOT']), $dir); if (strpos($relPath, '../') === 0) { // Try with both resolved $relPath = self::getRelDir($dir, $dir); throw new \Exception('Cannot calculate relative path from document root to dir. The path given is not within document root'); } } } return $relPath; } else { // We cannot get the resolved doc-root. // This might be ok as long as the (resolved) path we are examining begins with the configured doc-root. $relPath = self::getRelDir($_SERVER['DOCUMENT_ROOT'], $dir); // Check if relPath starts with "../" (it may not) if (strpos($relPath, '../') === 0) { // Well, that did not work. We can try the resolved path instead. if (!self::pathExistsAndIsResolvable($dir)) { throw new \Exception('Cannot calculate relative path from document root to dir. The path given is not resolvable (realpath fails)'); } $relPath = self::getRelDir($_SERVER['DOCUMENT_ROOT'], realpath($dir)); if (strpos($relPath, '../') === 0) { // That failed too. // Either it is in fact outside document root or it is because of a special setup. throw new \Exception( 'Cannot calculate relative path from document root to dir. Either the path given is not within the configured document root or ' . 'it is because of a special setup. The document root is outside open_basedir. If it is also symlinked, but the other Wordpress paths ' . 'are not using that same symlink, it will not be possible to calculate the relative path.' ); } } return $relPath; }*/ } public static function canCalculateRelPathFromDocRootToDir($dir) { try { $relPath = self::getRelPathFromDocRootToDirNoDirectoryTraversalAllowed($dir); } catch (\Exception $e) { return false; } return true; } /** * Find closest existing folder with symlinks expandend, using realpath. * * Note that if the input or the closest existing folder is outside open_basedir, no folder will * be found and an empty string will be returned. * * @return string closest existing path or empty string if none found (due to open_basedir restriction) */ public static function findClosestExistingFolderSymLinksExpanded($input) { // The strategy is to first try the supplied directory. If it fails, try the parent, etc. $dir = $input; // We count the levels up to avoid infinite loop - as good practice. It ought not to get that far $levelsUp = 0; while ($levelsUp < 100) { // We suppress warning because we are aware that we might get a // open_basedir restriction warning. $realPathResult = @realpath($dir); if ($realPathResult !== false) { return $realPathResult; } // Stop at root. This will happen if the original path is outside basedir. if (($dir == '/') || (strlen($dir) < 4)) { return ''; } // Peal off one directory $dir = @dirname($dir); $levelsUp++; } return ''; } /** * Look if filepath is within a dir path (both by string matching and by using realpath, see notes). * * Note that the naive string match does not resolve '..'. You might want to call ::canonicalize first. * Note that the realpath match requires: 1. that the dir exist and is within open_basedir * 2. that the closest existing folder within filepath is within open_basedir * * @param string $filePath Path to file. It may be non-existing. * @param string $dirPath Path to dir. It must exist and be within open_basedir in order for the realpath match to execute. */ public static function isFilePathWithinDirPath($filePath, $dirPath) { // See if $filePath begins with $dirPath + '/'. if (strpos($filePath, $dirPath . '/') === 0) { return true; } if (strpos(self::canonicalize($filePath), self::canonicalize($dirPath) . '/') === 0) { return true; } // Also try with symlinks expanded. // As symlinks can only be retrieved with realpath and realpath fails with non-existing paths, // we settle with checking if closest existing folder in the filepath is within the dir. // If that is the case, then surely, the complete filepath is also within the dir. // Note however that it might be that the closest existing folder is not within the dir, while the // file would be (if it existed) // For WebP Express, we are pretty sure that the dirs we are checking against (uploads folder, // wp-content, plugins folder) exists. So getting the closest existing folder should be sufficient. // but could it be that these are outside open_basedir on some setups? Perhaps on a few systems. if (self::pathExistsAndIsResolvable($dirPath)) { $closestExistingDirOfFile = PathHelper::findClosestExistingFolderSymLinksExpanded($filePath); if (strpos($closestExistingDirOfFile, realpath($dirPath) . '/') === 0) { return true; } } return false; } /** * Look if path is within a dir path. Also tries expanding symlinks * * @param string $path Path to examine. It may be non-existing. * @param string $dirPath Path to dir. It must exist in order for symlinks to be expanded. */ public static function isPathWithinExistingDirPath($path, $dirPath) { if ($path == $dirPath) { return true; } // See if $filePath begins with $dirPath + '/'. if (strpos($path, $dirPath . '/') === 0) { return true; } // Also try with symlinks expanded (see comments in ::isFilePathWithinDirPath()) $closestExistingDir = PathHelper::findClosestExistingFolderSymLinksExpanded($path); if (strpos($closestExistingDir . '/', $dirPath . '/') === 0) { return true; } return false; } public static function frontslasher($str) { // TODO: replace backslash with frontslash return $str; } /** * Replace double slash with single slash. ie '/var//www/' => '/var/www/' * This allows you to lazely concatenate paths with '/' and then call this method to clean up afterwards. * Also removes triple slash etc. */ public static function fixDoubleSlash($str) { return preg_replace('/\/\/+/', '/', $str); } /** * Remove trailing slash, if any */ public static function untrailSlash($str) { return rtrim($str, '/'); //return preg_replace('/\/$/', '', $str); } // Canonicalize a path by resolving '../' and './' // Got it from a comment here: http://php.net/manual/en/function.realpath.php // But fixed it (it could not handle './../') public static function canonicalize($path) { $parts = explode('/', $path); // Remove parts containing just '.' (and the empty holes afterwards) $parts = array_values(array_filter($parts, function($var) { return ($var != '.'); })); // Remove parts containing '..' and the preceding $keys = array_keys($parts, '..'); foreach($keys as $keypos => $key) { array_splice($parts, $key - ($keypos * 2 + 1), 2); } return implode('/', $parts); } public static function dirname($path) { return self::canonicalize($path . '/..'); } /** * Get base name of a path (the last component of a path - ie the filename). * * This function operates natively on the string and is not locale aware. * It only works with "/" path separators. * * @return string the last component of a path */ public static function basename($path) { $parts = explode('/', $path); return array_pop($parts); } /** * Returns absolute path from a relative path and root * The result is canonicalized (dots and double-dots are resolved) * * @param $path Absolute path or relative path * @param $root What the path is relative to, if its relative */ public static function relPathToAbsPath($path, $root) { return self::canonicalize(self::fixDoubleSlash($root . '/' . $path)); } /** * isAbsPath * If path starts with '/', it is considered an absolute path (no Windows support) * * @param $path Path to inspect */ public static function isAbsPath($path) { return (substr($path, 0, 1) == '/'); } /** * Returns absolute path from a path which can either be absolute or relative to second argument. * If path starts with '/', it is considered an absolute path. * The result is canonicalized (dots and double-dots are resolved) * * @param $path Absolute path or relative path * @param $root What the path is relative to, if its relative */ public static function pathToAbsPath($path, $root) { if (self::isAbsPath($path)) { // path is already absolute return $path; } else { return self::relPathToAbsPath($path, $root); } } /** * Get relative path between two absolute paths * Examples: * from '/var/www' to 'var/ddd'. Result: '../ddd' * from '/var/www' to 'var/www/images'. Result: 'images' * from '/var/www' to 'var/www'. Result: '.' */ public static function getRelDir($fromPath, $toPath) { $fromDirParts = explode('/', str_replace('\\', '/', self::canonicalize(self::untrailSlash($fromPath)))); $toDirParts = explode('/', str_replace('\\', '/', self::canonicalize(self::untrailSlash($toPath)))); $i = 0; while (($i < count($fromDirParts)) && ($i < count($toDirParts)) && ($fromDirParts[$i] == $toDirParts[$i])) { $i++; } $rel = ""; for ($j = $i; $j < count($fromDirParts); $j++) { $rel .= "../"; } for ($j = $i; $j < count($toDirParts); $j++) { $rel .= $toDirParts[$j]; if ($j < count($toDirParts)-1) { $rel .= '/'; } } if ($rel == '') { $rel = '.'; } return $rel; } }