Абсолютный путь без разрешения символической ссылки / Сохранить пользователя в домашнем каталоге
Есть ли способ в PHP, как определить абсолютный путь с учетом относительного пути без фактического разрешения символических ссылок? Что-то вроде realpath
функция, но без разрешения символической ссылки.
Или в качестве альтернативы, есть ли какой-нибудь простой способ проверить, не случайно ли пользователь (который использует мой PHP-скрипт для просмотра файлов) случайно вышел из домашнего каталога виртуального хоста Apache? (или запретить ему использовать противное и.. в пути)
Спасибо!
4 ответа
Я не знаю собственного решения PHP для этого, но одна хорошая реализация абсолютного пути находится здесь: http://www.php.net/manual/en/function.realpath.php
Вот мой подход, имитирующий оригинал realpath()
которые также:
- Добавляет / удаляет буквы дисков Windows как в
c:
, - Удаляет завершающие косые черты.
- Добавляет текущий рабочий каталог к относительным путям.
- Опционально проверяет существование файла. В случае несуществующего файла A) ничего не делать, B) вернуть значение FALSE или C) выдать ошибку.
- По желанию следует за символическими ссылками.
NB. В этом подходе используются регулярные выражения, которые, как известно, требуют значительных ресурсов процессора, поэтому мне нравится метод, использующий массивы из http://www.php.net/manual/en/function.realpath.php.
// Set constants for when a file does not exist.
// 0: Check file existence, set FALSE when file not exists.
define('FILE_EXISTENCE_CHECK', 0);
// 1: Require file existence, throw error when file not exists.
define('FILE_EXISTENCE_CHECK_REQUIRE_FILE', 1);
// 2: Do not check file existence.
define('FILE_EXISTENCE_CHECK_SKIP', 2);
// Windows flag.
define('IS_WINDOWS', preg_match('#WIN(DOWS|\d+|_?NT)#i', PHP_OS));
// Directory separator shortcuts.
define('DIR_SEP', DIRECTORY_SEPARATOR);
define('PREG_DIR_SEP', preg_quote(DIR_SEP));
/**
* The original realpath() follows symbolic links which makes it harder to check
* their paths.
*
* Options
* file_existence_check:
* - FILE_EXISTENCE_CHECK_REQUIRE_FILE: Script will break if the checked
* file does not exist (default).
* - FILE_EXISTENCE_CHECK: If a file does not exist, a FALSE value will be
* returned.
* - FILE_EXISTENCE_CHECK_SKIP: File existence will not be checked at all.
*
* follow_link: Resolve a symbolic link or not (default: FALSE).
*/
function _realpath($path = NULL, $options = array()) {
// Merge default options with user options.
$options = array_merge(array(
'file_existence_check' => FILE_EXISTENCE_CHECK_REQUIRE_FILE,
'follow_link' => FALSE,
), $options);
// Use current working directory if path has not been defined.
$path = $path ? $path : getcwd();
// Replace slashes with OS specific slashes.
$path = preg_replace('#[\\\/]#', DIR_SEP, $path);
// Handle `./`. Another great approach using arrays can be found at:
// @link p://php.net/manual/en/function.realpath.php#84012
$path = preg_replace('#' . PREG_DIR_SEP . '(\.?' . PREG_DIR_SEP . ')+#', DIR_SEP, $path);
// Handle `../`.
while (preg_match('#^(.*?)' . PREG_DIR_SEP . '[^' . PREG_DIR_SEP . ']+' . PREG_DIR_SEP . '\.\.($|' . PREG_DIR_SEP . '.*)#', $path, $m)) {
$path = $m[1] . $m[2];
}
// Remove trailing slash.
$path = rtrim($path, DIR_SEP);
// If we are on Windows.
if (IS_WINDOWS) {
// If path starts with a lowercase drive letter.
if (preg_match('#^([a-z]:)(.*)#', $path, $m)) {
$path = strtoupper($m[1]) . $m[2];
}
// If path starts with a slash instead of a drive letter.
elseif ($path[0] === DIR_SEP) {
// Add current working directory's drive letter, ie. "D:".
$path = substr(getcwd(), 0, 2) . $path;
}
}
else {
// Remove drive letter.
if (preg_match('#^[A-Z]:(' . PREG_DIR_SEP . '.*)#i', $path, $m)) {
$path = $m[1];
}
}
// If path is relative.
if (!preg_match('#^([A-Z]:)?' . PREG_DIR_SEP . '#', $path)) {
// Add current working directory to path.
$path = getcwd() . DIR_SEP . $path;
}
// If file existence has to be checked and file does not exist.
if ($options['file_existence_check'] !== DSC_FILE_EXISTENCE_CHECK_SKIP && !file_exists($path)) {
// Return FALSE value.
if ($options['file_existence_check'] === DSC_FILE_EXISTENCE_CHECK) {
return FALSE;
}
// Or throw error.
else {
dsc_print_error('File does not exist: ' . $path);
}
}
// Follow sybmolic links, but only if the file exists.
if (!empty($options['follow_link']) && file_exists($path)) {
$path = readlink($path);
}
return $path;
}
Вы можете использовать параметр open_basedir ini php.ini или в объявлении виртуального хоста (как директива php_admin_value).
@Karolis предоставил принятый ответ, но это не совсем то, что я прочитал в вопросе. Например, следующий скрипт:
<?php
include('Karolis-answer') ;
chdir('/home/username') ;
printf("%s\n", get_absolute_path('some/folder/below') ;
при выполнении выдаст:
some/folder/below
... который не является абсолютным путем. В свою защиту Каролис сослался на это в своем ответе, сказав: «...не содержать (обратной) косой черты в позиции 0».
Правильный ответ будет:
/home/username/some/folder/below
Следующее небольшое изменение ответа @Karolis будет:
function absolutePath(string $path, $startingFolder = null) : string {
// Credit to Karolis at https://stackoverflow.com/questions/6519010/absolute-path-without-symlink-resolution-keep-user-in-home-directory
if (is_null($startingFolder))
$startingFolder = getcwd() ;
$path = str_replace( [ '/', '\\' ], DIRECTORY_SEPARATOR, $path) ;
if (substr($path, 0, 1) != DIRECTORY_SEPARATOR)
$path = $startingFolder . DIRECTORY_SEPARATOR . $path ;
$parts = array_filter(explode(DIRECTORY_SEPARATOR, $path), 'strlen') ;
$absolutes = [] ;
foreach ($parts as $part) {
if ( $part == '.' )
continue ;
if ( $part == '..')
array_pop($absolutes) ;
else
$absolutes[] = $part ;
}
return DIRECTORY_SEPARATOR . implode(DIRECTORY_SEPARATOR, $absolutes) ;
}
Это по-прежнему не разрешает символические ссылки, но разрешает «относительную» часть переданного значения.