Замените реальный путь PHP()

Судя по всему, realpath сильно глючит. В PHP 5.3.1 это вызывает случайные сбои. В версии 5.3.0 и ниже realpath случайным образом дает сбой и возвращает false (конечно, для одной и той же строки), а также всегда терпит неудачу при realpath повторении одной и той же строки дважды/больше (и, конечно, срабатывает в первый раз).

Кроме того, в более ранних версиях PHP он настолько глючный, что совершенно непригоден для использования. Ну... это уже так, потому что это непоследовательно.

В любом случае, какие варианты у меня есть? Может, сам переписать? Это целесообразно?


person Christian    schedule 29.10.2010    source источник
comment
перейдите на bugs.php.net и посмотрите, не перечислены ли уже обнаруженные вами ошибки. . Если нет, отправьте отчет об ошибке, чтобы они были исправлены.   -  person Gordon    schedule 29.10.2010
comment
Однако они задокументированы, даже если они не были патчем, они не могут помочь более ранним (стабильным) версиям PHP... Мне нужно работать над чем-то, что действительно работает.   -  person Christian    schedule 29.10.2010
comment
не поделитесь ссылками на отчеты об ошибках?   -  person Pekka    schedule 29.10.2010
comment
Google: site:bugs.php.net realpath #39367 #14049 кажется интересным.   -  person Christian    schedule 29.10.2010


Ответы (6)


Благодаря коду Свена Ардуви (указанный Пеккой) и некоторым изменениям я создал (надеюсь) лучшую реализацию:

/**
 * This function is to replace PHP's extremely buggy realpath().
 * @param string The original path, can be relative etc.
 * @return string The resolved path, it might not exist.
 */
function truepath($path){
    // whether $path is unix or not
    $unipath=strlen($path)==0 || $path{0}!='/';
    // attempts to detect if path is relative in which case, add cwd
    if(strpos($path,':')===false && $unipath)
        $path=getcwd().DIRECTORY_SEPARATOR.$path;
    // resolve path parts (single dot, double dot and double delimiters)
    $path = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $path);
    $parts = array_filter(explode(DIRECTORY_SEPARATOR, $path), 'strlen');
    $absolutes = array();
    foreach ($parts as $part) {
        if ('.'  == $part) continue;
        if ('..' == $part) {
            array_pop($absolutes);
        } else {
            $absolutes[] = $part;
        }
    }
    $path=implode(DIRECTORY_SEPARATOR, $absolutes);
    // resolve any symlinks
    if(file_exists($path) && linkinfo($path)>0)$path=readlink($path);
    // put initial separator that could have been lost
    $path=!$unipath ? '/'.$path : $path;
    return $path;
}

Примечание: в отличие от realpath в PHP, эта функция не возвращает false при ошибке; он возвращает путь, который максимально близок к разрешению этих причуд.

Примечание 2: очевидно, что некоторые люди не умеют правильно читать. Truepath() не работает с сетевыми ресурсами, включая UNC и URL-адреса. Он работает только для локальной файловой системы.

person Christian    schedule 29.10.2010
comment
Выглядит неплохо. Я немного протестировал его в Windows, и он отлично работает даже с буквами дисков. - person Pekka; 29.10.2010
comment
Большое спасибо за тестирование! Знание того, что он очень стабилен, имеет огромное значение для моего проекта. - person Christian; 29.10.2010
comment
@Christian Я заметил, что если вы предоставляете протокол, возвращается только одна косая черта, когда ожидается две. пример: truepath("http://www.foobar.com/"); вернет http:/www.foobar.com - person Justin Bull; 16.08.2011
comment
@JustinBull - это потому, что он был разработан для работы с реальными файлами. - person Christian; 16.08.2011
comment
-1 Ага, конечно, меньше глючит. Просто взглянув, я вижу, что это неправильно для путей UNC, и, вероятно, это не правильно для многих других крайних случаев. - person NikiC; 16.09.2011
comment
@NikiC Эта функция используется на 5 разных серверах, каждый из которых realpath() вышел из строя по разным причинам. Что вы говорили о меньшем количестве ошибок? - person Christian; 16.09.2011
comment
ОПАСНО, НЕ ИСПОЛЬЗУЙТЕ. Кого волнуют пути UNC? С этой функцией есть серьезные проблемы, и я не говорю об ужасном форматировании кода. Это doesn't correctly resolve symbolic links, то есть major security problem! В возвращаемой части также может отсутствовать начальная косая черта, что является еще одной серьезной проблемой безопасности. Он также не соответствует POSIX во многих других отношениях. PHP realpath() не очень хорош, но это намного хуже! - person Martin Tournoij; 10.02.2012
comment
@Carpetsmoker - А? Что вы имеете в виду, что он не разрешает символические ссылки? Что, по вашему мнению, делает readlink()? Кроме того, я никогда не говорил и не хотел, чтобы он разрешал пути UNC. А что касается POSIX... а? - person Christian; 10.02.2012
comment
readlink() не работает, если любой промежуточный путь является ссылкой. Например, если test является символической ссылкой на /etc/ в /var/www/test/file/, она не будет разрешена. Это потому, что это работа не readlink(), а realpath(). -- realpath() — это стандартная функция, определенная в стандарте POSIX: pubs.opengroup. org/onlinepubs/9699919799/functions/ - person Martin Tournoij; 12.02.2012
comment
Это не правда. Он работает, по крайней мере, с PHP 5.1, который я тестировал. Кроме того, как я уже упоминал выше, вся эта функция используется уже довольно давно и без проблем. Если бы то, что вы сказали, было правдой, оно бы уже давно появилось. - person Christian; 12.02.2012
comment
Кроме того, почему вся эта суета о безопасности? Принятие путей из пользовательского ввода само по себе является проблемой безопасности, я не вижу реалистичности в этой панике. - person Christian; 12.02.2012
comment
К сожалению, когда я передаю этой функции путь unix /usr/texbin/, я получаю /usr\texbin, что неверно. Кстати, texbin — это символическая ссылка, созданная mklink на Win 7. - person McGafter; 04.07.2013
comment
@McGafter Это странно. Почему вы проверяете путь unix в системе dos/windows? Явно сломается... - person Christian; 05.07.2013
comment
Я использую код, который обычно работает на Mac, но я просто хочу запустить его копию в Windows без необходимости менять код туда и обратно. - person McGafter; 09.07.2013
comment
Я понимаю вашу озабоченность, но это невозможно напрямую; вам нужно вручную преобразовать путь на разных платформах. Например, предположив, что путь C:/myapp/test, а текущее приложение находится в C:/myapp, вы можете использовать это вместо пути: $dir = __DIR__ . DIRECTORY_SEPARATOR . 'test'; - person Christian; 09.07.2013
comment
В функции есть одна ошибка: если путь относительный и добавляется cwd, unipath снова должен стать ложным, иначе ведущий / будет отсутствовать в результате: // пытается определить, является ли путь относительным, и в этом случае добавить cwd, если (strpos($path,':')===false && $unipath) { $path=getcwd().DIRECTORY_SEPARATOR.$path; $unipath=ложь; } - person andig; 13.07.2013
comment
Я бы использовал is_link($path) вместо linkinfo($path), потому что, когда вы нажмете на фактическую ссылку linkinfo($path) › 0, но readlink() не работает. С is_link() это работает во всех случаях. - person Dom; 22.12.2016
comment
Извините, проигнорируйте мой последний комментарий, потому что это тоже не работает. Проблема в том, что я получаю «Предупреждение PHP: readlink (): недопустимый аргумент», когда путь не содержит символической ссылки в OSX. Например. Я запускаю truepath («Пользователи»), меня встречает вышеуказанная ошибка. Все еще ищу решение. - person Dom; 22.12.2016
comment
При использовании этой функции в PHP7.4 (и выше) используйте $path[0] вместо $path{0}, чтобы избежать ошибки: Синтаксис доступа к смещению массива и строки с фигурными скобками устарел. - person Bazardshoxer; 28.01.2020

вот модифицированный код, который также поддерживает пути UNC

static public function truepath($path)
{
    // whether $path is unix or not
    $unipath = strlen($path)==0 || $path{0}!='/';
    $unc = substr($path,0,2)=='\\\\'?true:false;
    // attempts to detect if path is relative in which case, add cwd
    if(strpos($path,':') === false && $unipath && !$unc){
        $path=getcwd().DIRECTORY_SEPARATOR.$path;
        if($path{0}=='/'){
            $unipath = false;
        }
    }

    // resolve path parts (single dot, double dot and double delimiters)
    $path = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $path);
    $parts = array_filter(explode(DIRECTORY_SEPARATOR, $path), 'strlen');
    $absolutes = array();
    foreach ($parts as $part) {
        if ('.'  == $part){
            continue;
        }
        if ('..' == $part) {
            array_pop($absolutes);
        } else {
            $absolutes[] = $part;
        }
    }
    $path = implode(DIRECTORY_SEPARATOR, $absolutes);
    // resolve any symlinks
    if( function_exists('readlink') && file_exists($path) && linkinfo($path)>0 ){
        $path = readlink($path);
    }
    // put initial separator that could have been lost
    $path = !$unipath ? '/'.$path : $path;
    $path = $unc ? '\\\\'.$path : $path;
    return $path;
}
person Pavel Perna    schedule 31.03.2015

Я знаю, что это старая тема, но она действительно полезна.

Я столкнулся со странной проблемой Phar::interceptFileFuncs, когда реализовал относительный путь в phpctags realpath() действительно содержит ошибки внутри phar.

Спасибо, этот поток дал мне немного света, вот моя реализация, основанная на реализации Christian из этого потока и этого комментарии.

Надеюсь, это сработает для вас.

function relativePath($from, $to)
{
    $fromPath = absolutePath($from);
    $toPath = absolutePath($to);

    $fromPathParts = explode(DIRECTORY_SEPARATOR, rtrim($fromPath, DIRECTORY_SEPARATOR));
    $toPathParts = explode(DIRECTORY_SEPARATOR, rtrim($toPath, DIRECTORY_SEPARATOR));
    while(count($fromPathParts) && count($toPathParts) && ($fromPathParts[0] == $toPathParts[0]))
    {
        array_shift($fromPathParts);
        array_shift($toPathParts);
    }
    return str_pad("", count($fromPathParts)*3, '..'.DIRECTORY_SEPARATOR).implode(DIRECTORY_SEPARATOR, $toPathParts);
}

function absolutePath($path)
{
    $isEmptyPath    = (strlen($path) == 0);
    $isRelativePath = ($path{0} != '/');
    $isWindowsPath  = !(strpos($path, ':') === false);

    if (($isEmptyPath || $isRelativePath) && !$isWindowsPath)
        $path= getcwd().DIRECTORY_SEPARATOR.$path;

    // resolve path parts (single dot, double dot and double delimiters)
    $path = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $path);
    $pathParts = array_filter(explode(DIRECTORY_SEPARATOR, $path), 'strlen');
    $absolutePathParts = array();
    foreach ($pathParts as $part) {
        if ($part == '.')
            continue;

        if ($part == '..') {
            array_pop($absolutePathParts);
        } else {
            $absolutePathParts[] = $part;
        }
    }
    $path = implode(DIRECTORY_SEPARATOR, $absolutePathParts);

    // resolve any symlinks
    if (file_exists($path) && linkinfo($path)>0)
        $path = readlink($path);

    // put initial separator that could have been lost
    $path= (!$isWindowsPath ? '/'.$path : $path);

    return $path;
}
person Mark Wu    schedule 05.12.2013

Для тех пользователей Zend, ЭТОТ ответ может помочь вам, как и мне:

$path = APPLICATION_PATH . "/../directory";
$realpath = new Zend_Filter_RealPath(new Zend_Config(array('exists' => false)));
$realpath = $realpath->filter($path);
person axiom82    schedule 22.05.2014

Я никогда не слышал о таких серьезных проблемах с realpath() (я всегда думал, что он просто взаимодействует с некоторыми базовыми функциями ОС - были бы интересны некоторые ссылки), но Примечания пользователей к странице руководства имеют несколько альтернативных реализаций. Вот выглядит нормально.

Конечно, не гарантируется, что эти реализации позаботятся обо всех кроссплатформенных особенностях и проблемах, поэтому вам придется провести тщательное тестирование, чтобы увидеть, соответствует ли это вашим потребностям.

Насколько я вижу, ни один из них не возвращает канонизированный путь, они разрешают только относительные пути. Если вам это нужно, я не уверен, сможете ли вы обойти realpath() (за исключением, возможно, выполнения (зависимой от системы) консольной команды, которая дает вам полный путь.)

person Pekka    schedule 29.10.2010
comment
Разрешение символических ссылок было одной из основных причин использования realpath, к сожалению... - person Christian; 29.10.2010

В Windows 7 код работает нормально. В Linux есть проблема в том, что сгенерированный путь начинается (в моем случае) с home/xxx, тогда как он должен начинаться с /home/xxx... т.е. начальный /, указывающий на корневую папку, отсутствует. Проблема не столько в этой функции, сколько в том, что возвращает getcwd в Linux.

person Andrew Fry    schedule 06.09.2011
comment
Ах, я столкнулся с этой проблемой и исправил ее, но забыл обновить ответ. Я обновил его сейчас. Спасибо. Кстати, в следующий раз, пожалуйста, добавьте комментарий к моему ответу, а не создавайте новый ответ вообще. :) - person Christian; 06.09.2011
comment
Извините, не вижу, как прокомментировать ваш ответ. Однако эта исправленная версия теперь не работает ни в Windows, ни в Linux. Я обнаружил, что $unipath устанавливается для обеих ОС... и в случае, если я не понимаю логику утверждения '$unipath=strlen($path)==0 || $путь{0}!='/'; (почему любой из них означает использование Unix ???). Кроме того, наверняка в статье $path=!$unipath ? '/'.$путь : $путь; ! неверно и должно быть удалено ??? - person Andrew Fry; 08.09.2011
comment
Первая часть проверяет, является ли этот путь пустым и начинается с разделителя или нет. Имя неправильное, оно должно быть не unixpath или что-то в этом роде. Принимая это во внимание, окончательное утверждение верно; path = !nonunixpath ? '/'.path : path;. - person Christian; 08.09.2011