Как я могу использовать escapeshellarg () в Windows, но «нацеленный на Linux» (и наоборот)? - PullRequest
3 голосов
/ 23 октября 2019

Если PHP работает в Windows, escapeshellarg () экранирует имена файлов (например) определенным образом и затем добавляет кавычки (DOUBLE) вокруг него.

Если PHP работает в Linux, escapeshellarg () использует экранирование на основе Linux, а затем добавляет к нему (SINGLE) кавычки.

В моей ситуации я создаю файл SHA256SUMS в Windows, но нацеленный на Linux. Так как я использую escapeshellarg () для экранированияимя файла, в итоге я получаю файл вроде:

cabcdccas12exdqdqadanacvdkjsc123ccfcfq3rdwcndwf2qefcf "cool filename with spaces.zip"

Однако инструменты Linux, вероятно, ожидают:

cabcdccas12exdqdqadanacvdkjsc123ccfcfq3rdwcndwf2qefcf 'cool filename with spaces.zip'

Глядя в руководство, кажется, нет никакого способа сделатьчто-то вроде: escapeshellarg ($ blabla, TARGET_OS_LINUX); для того, чтобы он использовал правила для Linux вместо ОС, выполняющей скрипт (Windows).

Я не могу просто str_relace кавычки, потому что это не будетпринять во внимание все правила, относящиеся к платформе.

Кроме того, да, мне нужны пробелы в имени файла (и любом другом кросс-платформенном символе).

К сожалению, я не нашел упоминанияwhatsoevо предпочтительном стиле цитаты из единственного источника информации, который у меня есть для этого: https://help.ubuntu.com/community/HowToSHA256SUM

Может быть, средства проверки безопасности SHA256, которые читают этот файл SHA256SUMS, понимают и могут анализировать оба вида?

1 Ответ

0 голосов
/ 23 октября 2019

Поведение 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 и вернуть пустую строку.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...