Поведение escapeshellarg()
жестко запрограммировано в зависимости от того, работает ли PHP в Windows или в любой другой операционной системе. Вы должны переопределить escapeshellarg()
для согласованного поведения.
Вот моя попытка переопределить escapeshellarg()
с помощью переключателя Windows / other-OS в PHP:
<?php namespace polyfill;
const TARGET_OS_WINDOWS = 1;
const TARGET_OS_UNIX = 2;
function escapeshellarg(string $input, int $os_mode = 0): string
{
if ($os_mode == 0)
{
$os_mode = TARGET_OS_UNIX;
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN')
$os_mode = TARGET_OS_WINDOWS;
}
$maxlen = 4096;
if ($os_mode === TARGET_OS_WINDOWS) $maxlen = 8192;
if (strlen($arg) > $maxlen - 2) return "";
if ($os_mode === TARGET_OS_WINDOWS)
{
$output =
str_replace(['"', '%', '!'],
[' ', ' ', ' '],
$input);
# https://bugs.php.net/bug.php?id=69646
if (substr($output, -1) === "\\")
{
$k = 0; $n = strlen($output) - 1;
for (; $n >= 0 && substr($output, $n, 1) === "\\"; $n--, $k++);
if ($k % 2) $output .= "\\";
}
$output = "\"$output\"";
}
else
{
$output = str_replace("'", "'\''", $input);
$output = "'$output'";
}
if (strlen($output) > $maxlen) return "";
return $output;
}
Он должен быть почти функционально эквивалентен нативному PHP escapeshellarg()
, за исключением того, что:
- он принимает второй аргумент, который устанавливает, хотите ли вы вывод в режиме Windows или не в режиме Windows,
- он не выдает ошибок из-за слишком длинного ввода, а
- имеет 4096 жестко запрограммированных в качестве максимальной длины аргумента на Unix-подобных платформах.
Чтобы использовать эту функцию замены:
# In Unix/Linux/macOS mode
\polyfill\escapeshellarg($blabla, \polyfill\TARGET_OS_UNIX);
# In Windows mode
\polyfill\escapeshellarg($blabla, \polyfill\TARGET_OS_WINDOWS);
# In auto-detect (running OS) mode
\polyfill\escapeshellarg($blabla);
Ссылка
Вот полная реализация C из PHP 7.3.10 (./ext/standard/exec.c
):
PHPAPI zend_string *php_escape_shell_arg(char *str)
{
size_t x, y = 0;
size_t l = strlen(str);
zend_string *cmd;
uint64_t estimate = (4 * (uint64_t)l) + 3;
/* max command line length - two single quotes - \0 byte length */
if (l > cmd_max_len - 2 - 1) {
php_error_docref(NULL, E_ERROR, "Argument exceeds the allowed length of %zu bytes", cmd_max_len);
return ZSTR_EMPTY_ALLOC();
}
cmd = zend_string_safe_alloc(4, l, 2, 0); /* worst case */
#ifdef PHP_WIN32
ZSTR_VAL(cmd)[y++] = '"';
#else
ZSTR_VAL(cmd)[y++] = '\'';
#endif
for (x = 0; x < l; x++) {
int mb_len = php_mblen(str + x, (l - x));
/* skip non-valid multibyte characters */
if (mb_len < 0) {
continue;
} else if (mb_len > 1) {
memcpy(ZSTR_VAL(cmd) + y, str + x, mb_len);
y += mb_len;
x += mb_len - 1;
continue;
}
switch (str[x]) {
#ifdef PHP_WIN32
case '"':
case '%':
case '!':
ZSTR_VAL(cmd)[y++] = ' ';
break;
#else
case '\'':
ZSTR_VAL(cmd)[y++] = '\'';
ZSTR_VAL(cmd)[y++] = '\\';
ZSTR_VAL(cmd)[y++] = '\'';
#endif
/* fall-through */
default:
ZSTR_VAL(cmd)[y++] = str[x];
}
}
#ifdef PHP_WIN32
if (y > 0 && '\\' == ZSTR_VAL(cmd)[y - 1]) {
int k = 0, n = y - 1;
for (; n >= 0 && '\\' == ZSTR_VAL(cmd)[n]; n--, k++);
if (k % 2) {
ZSTR_VAL(cmd)[y++] = '\\';
}
}
ZSTR_VAL(cmd)[y++] = '"';
#else
ZSTR_VAL(cmd)[y++] = '\'';
#endif
ZSTR_VAL(cmd)[y] = '\0';
if (y > cmd_max_len + 1) {
php_error_docref(NULL, E_ERROR, "Escaped argument exceeds the allowed length of %zu bytes", cmd_max_len);
zend_string_release_ex(cmd, 0);
return ZSTR_EMPTY_ALLOC();
}
if ((estimate - y) > 4096) {
/* realloc if the estimate was way overill
* Arbitrary cutoff point of 4096 */
cmd = zend_string_truncate(cmd, y, 0);
}
ZSTR_LEN(cmd) = y;
return cmd;
}
Логика довольно проста. Вот некоторые эквивалентные функциональные тестовые примеры в прозе:
- Применяется к входной строке,
- в режиме Windows,
- Добавить символ
"
. - Заменить все
"
, %
и !
символов на
. - Если конец состоит из нечетного числа
\
символов, добавьте один \
символк концу. ( Ошибка # 69646 ) - Добавление символа
"
.
- в режиме других платформ,
- Prepend a
'
символов. - Заменить все
'
символов на '\''
- Добавить
'
символов.
- В Windows, если длина вывода превышает 8192 символа, введите
E_ERROR
и верните пустую строку. - На других платформах, если длина вывода превышает 4096 символов (или любой другой переопределенный максимумвремя компиляции), выдать
E_ERROR
и вернуть пустую строку.