Проверить содержимое загруженных файлов

Я разрабатываю систему «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.

1

Решение

Итак, вы хотите создать «систему», в которую пользователи могут загружать файлы 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 это расширение, которое предоставляет средства для изменения констант, пользовательских функций и пользовательских классов. Он также предоставляет настраиваемые суперглобальные переменные и встраиваемые суб-интерпретаторы через песочницу.

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

С чистым PHP:

Вы можете создать своего рода «песочницу», открыв другой процесс PHP с proc_open или же execнапример, он имеет логику песочницы и отвечает за анализ и тестирование загруженного файла.

В этом примере мы создаем 3 файла:

  • main.php это ваш ApplicationGui. Он отвечает за получение файла, правильных параметров и параметров, а затем за запуск нового процесса PHP.
  • sandbox.php — ваш скрипт песочницы. Он будет включать / требовать файл, который вы хотите «протестировать», и использовать отражение для тестирования класса.
  • file.php — загруженный файл вы хотите проверить (я использовал ваш пример, но с действительным классом)

Посмотри на Консоль Symfony а также Symfony Config компоненты, которые могут помочь достичь этого.


main.php

$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;
}

sandbox.php

$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:

Проекты не кажутся очень активными, хотя …

1

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]