function run($command_vs_args) { global $global; foreach ($global as $var) { global $$var; } $_SESSION['cmds_counter']++; ob_start(); # для однострочных запросов с несколькими командами разделёнными точкой с запятой и пробелом после неё # в идеале нужно пробежаться такой же функцией как это сделано чуть ниже, str_split, чтобы учесть влияние кавычек // if (strpos($command_vs_args, '; ') !== false) { // $commands = explode('; ', $command_vs_args); // foreach ($commands as $command) { // run($command); // } // } # бывш.: $argv = explode(' ', $command_vs_args); # нужно разбивать более грамотно, с учетом также одинарных, косых кавычек и символов экранирования \ # кроме того, косые кавычки нужно зарезервировать для исполнения команды, которая в них заключена и возврата ответа, чтобы some do `command arg1 arg2` соответствовало some do run('command arg1 arg2'); $signs = str_split($command_vs_args); $cmds = []; # двумерный массив комманд, [[команда, аргумент1, аргумент2...], [команда2, арг1, арг2...]] $cmdc = 0; # номер команды # command counter $argc = 0; # номер аргумента для этой команды, где 0 аргумент - имя команды # argument counter $quoted_str = 0; # флаг, определяемый является ли обрабатываемый символ частью выражения заключенного в кавычки или нет (заодно хранит символ самих кавычек для отеделния одинарных от двойных и косых) $prev_sign = null; $escape_character = '\\'; # экранирующий символ $quotemarks = '\'"`'; # варианты написания кавычек $argument_key_identifier = '-'; # символ определяющий то, что начинающийся с него аргумент является ключом аргумента следующего за этим ключем # « » # „ “, « », “ ”, ‘ ’ # ’ ' $arguments_separator = ' '; # символ разделяющий аргументы функции $executing_quotemarks = '`'; # символ подстановки результата выполненного выражения в место выражения $statements_separator = ';'; # символ разделяющий команды для выполнения (общепринят в линуксе символ |, а хотелось бы также обратные кавычки использовать для этого дела) foreach ($signs as $sign) { // $its_not_first_sign_of_argument_value = isset($cmds[$cmdc][$argc]); if (!$prev_sign || $prev_sign == $arguments_separator || $prev_sign == $statements_separator) { $its_first_sign_of_argument_value = true; } else { $its_first_sign_of_argument_value = false; } $its_not_first_sign_of_argument_value = !$its_first_sign_of_argument_value; # этот блок выполняется каждую итерацию пока не будет обнаружено выражение заключенное в кавычки if (!$quoted_str) { # если это первый символ аргумента а не где-то посередине if ($its_first_sign_of_argument_value /* && $prev_sign != $escape_character */) { # если открылись кавычки, причем кавычки не экранированы предварительно if ($prev_sign != $escape_character && strpbrk($sign, $quotemarks)) { $quoted_str = $sign; # do nothing, becouse all we need - switch $quoted_str to new sign # а если мы имеем дело с ключем аргумента, а не самим аргументом } elseif ($sign == $argument_key_identifier && $prev_sign != $escape_character /* && $prev_sign = $argument_key_identifier */) { # обрезаем оба символа дефиса, значение параметра после считывания дублируется как ключ ассоциативного массива, а следующий за ним параметр интерпретируется как значение элемента массива параметров с этим ключем $arg_is_its_key = true; # $cmds[$cmdc][$argc] = ''; // echo $sign . $quoted_str; } else { if ($sign != $arguments_separator) { $cmds[$cmdc][$argc] .= $sign; } } # a если это не первый символ аргумента } else { if ($sign == $statements_separator /* && $prev_sign != $escape_character */) { $cmdc++; $argc=0; } elseif ($sign == $arguments_separator /* && $prev_sign != $escape_character */) { if ($arg_is_its_key) { $arg_is_its_key = false; $arg_key = $cmds[$cmdc][$argc]; $cmds[$cmdc][$argc] = ''; // $arg_has_key = true; // echo '$arg_val = ' . $cmds[$cmdc][$argc]; # unset($cmds[$cmdc][$argc]); } elseif ($arg_key) { $cmds[$cmdc][ $arg_key ] = $cmds[$cmdc][$argc]; $arg_key = ''; $argc++; } else { $argc++; } } else { $cmds[$cmdc][$argc] .= $sign; } } # а этот блок выполняется при каждой итерации пока кавычки не закроются } else { # if $quoted_str (этот блок говорит как обрабатывать символы выражения заключенного в кавычки) # если символ кавычек соответсвует тому символу, с которого начиналась заключенная в кавычки строка, выходим из блока, при необходимости исполняем заключенную в косые кавычки строку, результат записываем в аргумент if ($sign == $quoted_str && $prev_sign != $escape_character) { # теперь $prev_sign - это надёжное решение, так как только НЕЧЕТНОЕ ЧИСЛО обратных слешей перед кавычкой не даёт закрыть кавычки, четное число считается экранированием слешем самого себя и позволяется использовать в конце строки. $quoted_str = 0; if ($sign == $executing_quotemarks) { # run($argv, $argc); # $command_name # выполняем выражение и подтавляем результат на его место $cmds[$cmdc][$argc] = run($cmds[$cmdc][$argc]); } # а если символ соответсвует, но предварительно экранирован, не выходим из блока, а сам символ экранирования заменяем на символ кавычек (то же самое что добавить символ кавычек к строке, но мы перед этим записали символ экранирования, его надо стереть) } elseif ($sign == $quoted_str && $prev_sign == $escape_character) { # определяет поведение для \" в "строке" $cmds[$cmdc][$argc] = preg_replace("!(.)$!", $sign, $cmds[$cmdc][$argc]); # а если это экранированный символ экранирования строки (\\), то дописываем его как есть, НО не записываем текущий символ предыдущим - то бишь текущий символ экранирования не будет экранировать следующий за ним символ теперь } elseif ($sign == $escape_character && $prev_sign == $escape_character) { # if (!$module['cmd']['halve_escape_character']) { $cmds[$cmdc][$argc] .= $sign; # эта строка определяет отличия между стандартной интерпретацией обратных слешей и моей. у меня все обратные слеши останутся в строке не сьеденными, обычно же их нужно двойное количество. в моём случае это не требуется нигде, кроме конца строки, там поведение не отличается от стандартного - два слеша превращаются в один } $sign = null; } else { # $cmds[$cmdc][$argc] .= $sign; } } $prev_sign = $sign; } foreach ($cmds as $cmdc => $argv) { $command_name = $argv[0]; // var_dump($cmds); # следует читать как "если запрошенное имя команды пусто", не путать с "если у команды нет аргументов" - первым аргументом всегда идёт имя вызываемой команды if (empty($argv)) continue; # если запрошенная команда начинается с ./ искать будем в текущей директории if (preg_match('!^\./!', $command_name)) { $commands_folder = ''; # chdir($_SESSION['cd']['user']); $command_directory = $_SESSION['cd']['user']; } else { $command_directory = $_SESSION['cd']['default']; $commands_folder = $GLOBALS['fold']['cmd']; } # если расширение файла исполняемого скрипта указано, не дописываем его вторично (полезно для вызова на исполнение некоего стороннего скриптика, например ./some.py или даже ./some.js, правила для их исполннения пока не написаны, однако позже мы это исправим) if (preg_match('!\\.(php|py|pl|c)$!', $command_name)) { $command_extension = ''; } else { $command_extension = $GLOBALS['site']['extensions']; } # Что очень важно, на локалке и на хостинге $command_directory сильно отличаются (на хостинге пусто, на локали адрес от корневой директории системы), так что нужно максимально нивелировать различия if ($command_directory) $command_directory .= '/'; $cmd_file = $command_directory . $commands_folder . $command_name . $command_extension; # если команда по прямому имени не найдена, пробуем прогрузить все известные синонимы всех команд и найти реальное имя команы по синонимуму, чтобы позже выполнить команду по её реальному имени $true_name = $command_name; if (!is_readable($cmd_file)) { $all_aliases = include_once $GLOBALS['path']['cmd']['aliases']; foreach ($all_aliases as $true_name => $command_aliases) { # удаляем из имени запрошенной команды все посторонние символы, которые могут интерпретироваться как часть регулярного выражения $command_name = preg_replace('/`[|\'\\\\]/', '', $command_name); # с одной стороны нужно и сохнаить возможность использования символов в псевдонимах, вроде "цитата дня" или исправляющие некорректный набор из-за раскладки # $command_name = preg_replace('![\^\/\|"\'\\\[\]\(\)\?\.\*\&\%\#\{\}\+\$]!', '', $command_name); # а вообще по хорошему следует оставить в наименованиях досутпными только цифро-буквенные знаки.. так и сделаю # if (!preg_match('!^[\w\d-_]+$!', $command_name)) { # $command_name = 'motherofgod'; # } # проверяет есть ли вхождение |АЛИАС| в строке if (preg_match('`\|'.$command_name.'\|`', '|' . $command_aliases . '|')) { $cmd_file = $commands_folder . $true_name . $command_extension; break; } } } # никаких else в этом месте, после первого выполняется изменение $cmd_file и неоходимо второй раз проверить на читабельность # запуск команды if (function_exists($true_name)) { try { customization('user'); # on # нужно передвать всем массивом $argv, для этого все функции нужно переписать на получения массива с неопределенным количеством переменных # argc - arguments counter $result .= $true_name($argv, $argc); customization('default'); # off $result .= history_add_cmd($command_vs_args); $result .= cmd_log($command_vs_args, $result); } catch(Exception $e) { $error->report($e->getMessage(), __LINE__, '`' . $true_name . '` Command Exception', __FILE__, $e->getCode()); } } else { # проверка существования и доступности для выполнения файла с инициализацией функции / описанием кода для выполнения if (is_readable($cmd_file)) { try { customization('user'); $result .= include $cmd_file; customization('default'); $result .= history_add_cmd($command_vs_args); $result .= cmd_log($command_vs_args, $result); } catch(Exception $e) { # элементарная многоязычеость ошибок: заменяем $e->getMessage() на выборку из БД по этой фразе в текущей языковой таблице # как вариант ещё можно сделать двойной запрос - сначала получать значение ID для фразы на разных языках, а потом по ID найти перевод на соответствующий язык. Ну, это требует и соотв. структуры БД global $db, $database; $query = sprintf('SELECT `%s` FROM `%s` WHERE noconflict_hash="adeptx.cmd.exception" AND unique_key="%s" OR ru="%s" limit 1', $db->escape($_SESSION['lang']), $database['prefix'] . $database['table']['phrase'], $db->escape($e->getCode()), $db->escape($e->getMessage()) ); $db->call($query); if ($res) { $res = $db->fetch_assoc($res); } $msg = $res[0][ $_SESSION['lang'] ]; if (!$msg) { $msg = $e->getMessage(); } $error->report($msg, __LINE__, '`' . (($true_name)?$true_name:$command_name) . '` Command Exception', __FILE__, $e->getCode()); } } else { $result .= "RU: Комманда \"$command_name\" не существует или её файл ($command_directory$cmd_file) переименован, перемещён, недоступен, скрыт настройками приватности или повреждён. Воспользуйтесь командой help чтобы узнать список доступных команд.\nEN: Command \"$command_name\" not useful for this site/profile, command-package not install, rename or moved. Use command \"help\" for access commands list."; // var_dump($cmds); // var_dump($argv); if (is_readable($GLOBALS['fold']['cmd'] . 'error.log' . $GLOBALS['site']['extentions'])) { $err_log = fopen($GLOBALS['fold']['cmd'] . 'error.log' . $GLOBALS['site']['extentions'], 'a'); fwrite($err_log, $command_vs_args . "\n"); fclose($err_log); } } } # no-no-no, ни за что! // if (substr($result, -1) != "\n") { // $result .= "\n"; // } } $result = '<div id="cmd-' . $_SESSION['cmds_counter'] . '-result-output">' . ob_get_contents() . $result . '</div>'; # только для почты, вот эту часть категорически надо переписать (не только из-за трафика, но мы ещё и при каждом запросе смотрим "а не почтой ли интересуются", а надо чтобы нам говорили "покажите почту", а до того не шевелиться) if ($_POST['cmd'] == 'select mail') { $result['#user-new-messages'] = $result; # так мы посылаем одни и те же данные дважды, нагружая траффик в двойной мере! нужно предпринять что-то, чтобы не дублировать данные } ob_end_clean(); return $result; }
function get($argv, $argc) { global $fold; $object = $argv[1]; // $epitet = $argv[2]; # $epitet = current, new, last etc $property = $argv[2]; if (isset($argv['s'])) { $property = $argv['s']; } if (isset($argv['session'])) { $property = $argv['session']; } switch ($object) { case 'my': case 'current': case 'user': switch ($property) { case 'version': return '0.732'; case 'mail': return run('select mail'); } if (!isset($_SESSION[$property])) { echo $property; throw new Exception('Значение свойства не установлено. Возможно также, что такого свойства вообще нет у объекта. Возможно даже, что такого объекта вообще не существует.', 5796); # id, nickname, email, msg, timezone, new_mail_count, mail } return $_SESSION[$property]; break; case 'bd': case 'db': case 'sql': case 'new': case 'mysql': switch ($property) { case 'dump': $return .= run('dump'); customization('default'); $dumps = glob($fold['sql'] . "*.sql", GLOB_MARK | GLOB_NOESCAPE); $dumps_count = count($dumps); if (!$dumps_count) { throw new Exception('Ошибка создания и открытия дампа, нет прав?', 5797); } $return .= file_get_contents($dumps[count($dumps) - 1]); customization('user'); $return .= $fileContents; return $return; break; } break; case 'last': case 'latest': switch ($property) { case 'dump': customization('default'); $dumps_list = glob($fold['sql'] . "*.sql", GLOB_MARK | GLOB_NOESCAPE); $last_dump = array_pop($dumps_list); if (!$last_dump) { throw new Exception('В папке хранения дампов не обнаружено ни одного дампа базы данных', 4978); } $return .= file_get_contents($last_dump); customization('user'); $return .= $fileContents; return $return; break; case 'version': return '0.732'; break; } break; case 'var': if (!isset($_SESSION['var'][ $property ])) { return '\\0'; } return $_SESSION['var'][ $property ]; default: switch ($property) { case 'code': $real_path = str_replace(['../', '..'], '', $fold['cmd'] . $object . $site['extensions']); if (is_readable($real_path)) { $file = htmlspecialchars(file_get_contents($real_path)); $return .= "Исходный код программы $object:\n $file"; } else { $return .= "RU: Программа \"$file\" не обнаружена."; } } break; } return $return; }
<? /** * Дамп базы данных * **/ set_time_limit(0); customization('default'); return dump($argv, $argc); customization('user'); # это необходимо, потому что если подряд выполняется несколько команд, необходимо вернуться к текущему состоянию function dump($argv, $argc) { global $fold, $database, $site; $fix = $argv[1]; $datestamp = date("Y-m-d_H-i-s"); $dumpFileName = /* $page['base']['href'] . */ $fold['sql'] . $database['name'] . "_$datestamp.sql"; $connection = mysql_connect($database['host'], $database['user'], $database['pass']); if (!$connection) { return "Святые одуванчики! У нас проблемы!"; } include_once $fold['classes'] . 'MySQLDump' . $site['extensions']; $dumper = new MySQLDump($database['name'], $dumpFileName, false, false); mysql_query("set names utf8"); $dumper->doDump(); if ($fix) {