Я разрабатываю систему «plug-n-play», в которой отдельные компоненты могут быть зарегистрированы и связаны с загруженным файлом с помощью графического интерфейса приложения.
Но чтобы быть действительно «подключи и работай», приложение должно распознавать компонент, и, поскольку каждый компонент является классом, я мог бы добиться этого с помощью интерфейсов.
Но как я могу проверить содержимое загруженного файла в поисках определенного интерфейса?
Моей первой мыслью было использовать Tokenizer, но это оказалось мне сложнее, чем я ожидал. Файл простого тестового компонента, подобный следующему:
<?php
class ValidComponent implements Serializable {
public serialize() {}
public unserialize( $serialized ) {}
}
После того, как передано token_get_all () привело к:
Array
(
[0] => Array
(
[0] => T_OPEN_TAG
[1] => <?php
[2] => 1
)
[1] => Array
(
[0] => T_WHITESPACE
[1] =>
[2] => 2
)
[2] => Array
(
[0] => T_CLASS
[1] => class
[2] => 3
)
[3] => Array
(
[0] => T_WHITESPACE
[1] =>
[2] => 3
)
[4] => Array
(
[0] => T_STRING
[1] => ValidComponent
[2] => 3
)
[5] => Array
(
[0] => T_WHITESPACE
[1] =>
[2] => 3
)
[6] => Array
(
[0] => T_IMPLEMENTS
[1] => implements
[2] => 3
)
[7] => Array
(
[0] => T_WHITESPACE
[1] =>
[2] => 3
)
[8] => Array
(
[0] => T_STRING
[1] => Serializable
[2] => 3
)
[9] => Array
(
[0] => T_WHITESPACE
[1] =>
[2] => 3
)
[10] => U
[11] => Array
(
[0] => T_WHITESPACE
[1] =>[2] => 3
)
[12] => Array
(
[0] => T_PUBLIC
[1] => public
[2] => 5
)
[13] => Array
(
[0] => T_WHITESPACE
[1] =>
[2] => 5
)
[14] => Array
(
[0] => T_STRING
[1] => serialize
[2] => 5
)
[15] => U
[16] => U
[17] => Array
(
[0] => T_WHITESPACE
[1] =>
[2] => 5
)
[18] => U
[19] => U
[20] => Array
(
[0] => T_WHITESPACE
[1] =>
[2] => 5
)
[21] => Array
(
[0] => T_PUBLIC
[1] => public
[2] => 6
)
[22] => Array
(
[0] => T_WHITESPACE
[1] =>
[2] => 6
)
[23] => Array
(
[0] => T_STRING
[1] => unserialize
[2] => 6
)
[24] => U
[25] => Array
(
[0] => T_WHITESPACE
[1] =>
[2] => 6
)
[26] => Array
(
[0] => T_VARIABLE
[1] => $serialized
[2] => 6
)
[27] => Array
(
[0] => T_WHITESPACE
[1] =>
[2] => 6
)
[28] => U
[29] => Array
(
[0] => T_WHITESPACE
[1] =>
[2] => 6
)
[30] => U
[31] => U
[32] => Array
(
[0] => T_WHITESPACE
[1] =>
[2] => 6
)
[33] => U
)
Мало того, что это не очень эффективно, потому что реальные компоненты могут быть намного больше и приводить к огромным массивам, но я не думаю, что это очень надежно.
Я, конечно, мог бы использовать эту структуру и искать ее рекурсивно, ища имя какого-то определенного интерфейса, но это, безусловно, дало бы мне ложное срабатывание, если бы это имя интерфейса появлялось в любом месте кода (комментарии, обычные строки …).
Я хотел бы избежать сравнения текста или регулярных выражений, если это возможно, но я не знаю, возможно ли создать изолированную песочницу для оценки загруженного файла, чтобы использовать Reflection.
Итак, вы хотите создать «систему», в которую пользователи могут загружать файлы PHP, которые, в свою очередь, будут использоваться указанной системой?
Если вы полностью не доверяете пользователям или не используете их в контексте, где система доверяет загрузчику на 100%, как в среде разработки, это крайне небезопасно…
Это, как говорится, лучший и, вероятно, единственный путь СЛАБОЙ СЕЙН проанализировать файл php, не запуская его с токенизатор.
Например, если вы хотите знать, содержит ли этот файл класс, реализующий предопределенный интерфейс:
$source = file_get_contents('file.php');
$tokens = token_get_all($source);
function startsWithOpenTag($tokens)
{
return ($tokens[0][0] === T_OPEN_TAG);
}
function searchForInterface($tokens, $interfaceName)
{
$i = 0;
foreach ($tokens as $tk) {
if (isset($tk[1]) && strtolower($tk[1]) === 'implements') {
for ($ii = $i; $ii < count($tokens); ++$ii) {
if ($tokens[$ii] === '{') {
break;
} else {
if (isset($tokens[$ii][1]) && $tokens[$ii][2] === $interfaceName) {
return true;
}
}
}
}
++$i;
}
return false;
}
var_dump(startsWithOpenTag($tokens));
var_dump(searchForInterface($tokens, 'Serializable'));
достаточно. Однако это не означает, что в файле нет ошибок синтаксического анализа (или логических ошибок). На самом деле, если только вы не создадите полный PHP Parser (что-то вроде INSANE), единственный способ убедиться, что файл действителен — это запустить его.
Лучший способ достичь желаемого — это создать песочницу PHP. Вы можете сделать это, запустив другой процесс / поток PHP.
Runkit это расширение, которое предоставляет средства для изменения констант, пользовательских функций и пользовательских классов. Он также предоставляет настраиваемые суперглобальные переменные и встраиваемые суб-интерпретаторы через песочницу.
Runkit_Sandbox Класс создает новый поток со своей областью действия и программным стеком. Используя набор параметров, передаваемых конструктору, эта среда может быть ограничена подмножеством того, что может делать основной интерпретатор, и предоставлять более безопасную среду для выполнения предоставленного пользователем кода.
Вы можете создать своего рода «песочницу», открыв другой процесс PHP с proc_open
или же exec
например, он имеет логику песочницы и отвечает за анализ и тестирование загруженного файла.
В этом примере мы создаем 3 файла:
Посмотри на Консоль Symfony а также Symfony Config компоненты, которые могут помочь достичь этого.
$sandBoxWrapperPath = realpath('sandbox.php');
$uploadedFile = realpath('file.php');
$className = "\ValidComponent";
$command = "php \"$sandBoxWrapperPath\" -f \"$uploadedFile\" -c \"$className\"";
$descriptorspec = array(
1 => array("pipe", "w"), // STDOUT
2 => array("pipe", "w") // STDERR
);
$phpSandBox = proc_open($command, $descriptorspec, $pipes);if (is_resource($phpSandBox)) {
$stdOut = stream_get_contents($pipes[1]);
fclose($pipes[1]);
$stdErr = stream_get_contents($pipes[2]);
fclose($pipes[2]);
$exitCode = proc_close($phpSandBox);echo "STDOUT: " . $stdOut . PHP_EOL . PHP_EOL;
echo "STDERR: " . $stdErr . PHP_EOL . PHP_EOL;
}
$shortopts = "";
$shortopts .= "f:"; // Uploaded File
$shortopts .= "c:"; // Name of the class, with namespace
$opts = getopt($shortopts);
if (!isset($opts['f'])) {
exit('File parameter is required');
}
// Instead, you can use tokenizer to pre parse the file.
// For instance, you can find class name this way
if (!isset($opts['c'])) {
exit('Class parameter is required');
}
$file = $opts['f'];
$className = $opts['c'];
require $file;
$refClass = new ReflectionClass($className);
//Do stuff with reflection
На github есть пара песочниц PHP:
Проекты не кажутся очень активными, хотя …
Других решений пока нет …