Changeset View
Changeset View
Standalone View
Standalone View
src/xsprintf/PhutilCommandString.php
| <?php | <?php | ||||
| final class PhutilCommandString extends Phobject { | final class PhutilCommandString extends Phobject { | ||||
| private $argv; | private $argv; | ||||
| private $escapingMode = false; | private $escapingMode = false; | ||||
| const MODE_DEFAULT = 'default'; | const MODE_DEFAULT = 'default'; | ||||
| const MODE_WIN_PASSTHRU = 'winargv'; | |||||
| const MODE_POWERSHELL = 'powershell'; | const MODE_POWERSHELL = 'powershell'; | ||||
| public function __construct(array $argv) { | public function __construct(array $argv) { | ||||
| $this->argv = $argv; | $this->argv = $argv; | ||||
| $this->escapingMode = self::MODE_DEFAULT; | $this->escapingMode = self::MODE_DEFAULT; | ||||
| // This makes sure we throw immediately if there are errors in the | // This makes sure we throw immediately if there are errors in the | ||||
| Show All 26 Lines | return xsprintf( | ||||
| 'mode' => $this->escapingMode, | 'mode' => $this->escapingMode, | ||||
| ), | ), | ||||
| $this->argv); | $this->argv); | ||||
| } | } | ||||
| public static function escapeArgument($value, $mode) { | public static function escapeArgument($value, $mode) { | ||||
| switch ($mode) { | switch ($mode) { | ||||
| case self::MODE_DEFAULT: | case self::MODE_DEFAULT: | ||||
| return escapeshellarg($value); | return phutil_is_windows() ? self::escapeWindowsCMD($value) : escapeshellarg($value); | ||||
hach-que: There should be `MODE_WIN_CMD` to force Windows CMD escaping for e.g. when Drydock is executing… | |||||
BYKAuthorUnsubmitted Not Done Inline ActionsGood point. That said where should I change to make Drydock use that mode? Also in that case does MODE_POWERSHELL become obsolete? BYK: Good point. That said where should I change to make Drydock use that mode? Also in that case… | |||||
hach-queUnsubmitted Not Done Inline ActionsNo need to change Drydock. Adding a specific mode will just future proof this code. hach-que: No need to change Drydock. Adding a specific mode will just future proof this code. | |||||
hach-queUnsubmitted Not Done Inline ActionsDropping CMD now (apart from the other breakage) will mean we have to come back and add it later, because you can't remotely execute without cmd. hach-que: Dropping CMD now (apart from the other breakage) will mean we have to come back and add it… | |||||
BYKAuthorUnsubmitted Not Done Inline Actions
I'm not sure if I understand what is behind this. Can you elaborate? BYK: > because you can't remotely execute without cmd.
I'm not sure if I understand what is behind… | |||||
| case self::MODE_POWERSHELL: | case self::MODE_POWERSHELL: | ||||
| return self::escapePowershell($value); | return self::escapePowershell($value); | ||||
| case self::MODE_WIN_PASSTHRU: | |||||
| return self::escapeWindowsArgv($value); | |||||
| default: | default: | ||||
| throw new Exception(pht('Unknown escaping mode!')); | throw new Exception(pht('Unknown escaping mode!')); | ||||
| } | } | ||||
| } | } | ||||
| /** | |||||
| * Escapes a single argument to be glued together and passed into | |||||
| * CreateProcess on Windows through `proc_open`. | |||||
| * | |||||
| * Adapted from https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/ | |||||
| * | |||||
| * @param string The argument to be escaped | |||||
| * @result string Escaped argument that can be used in a CreateProcess call | |||||
| */ | |||||
| public static function escapeWindowsArgv($value) { | |||||
| // Don't quote unless we actually need to do so hopefully | |||||
| // avoid problems if programs won't parse quotes properly | |||||
| if ($value && !preg_match('/["[:space:]]/', $value)) { | |||||
| return $value; | |||||
| } | |||||
| $result = '"'; | |||||
| $len = strlen($value); | |||||
| for ($i = 0; $i < $len; $i++) { | |||||
| $numBackslashes = 0; | |||||
| while ($i < $len && $value[$i] == '\\') { | |||||
| $i++; | |||||
| $numBackslashes++; | |||||
| } | |||||
| if ($i == $len) { | |||||
| // Escape all backslashes, but let the terminating | |||||
| // double quotation mark we add below be interpreted | |||||
| // as a metacharacter. | |||||
| $result .= str_repeat('\\', $numBackslashes * 2); | |||||
| break; | |||||
| } elseif ($value[$i] == '"') { | |||||
| // Escape all backslashes and the following double quotation mark. | |||||
| $result .= str_repeat('\\', $numBackslashes * 2 + 1); | |||||
| $result .= $value[$i]; | |||||
| } else { | |||||
| // Backslashes aren't special here. | |||||
| $result .= str_repeat('\\', $numBackslashes); | |||||
| $result .= $value[$i]; | |||||
| } | |||||
| } | |||||
| $result .= '"'; | |||||
| return $result; | |||||
| } | |||||
| /** | |||||
| * Escapes all CMD metacharacters with a `^`. | |||||
| * | |||||
| * Adapted from https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/ | |||||
| * | |||||
| * @param string The argument to be escaped | |||||
| * @result string Escaped argument that can be used in CMD.exe | |||||
| */ | |||||
| private static function escapeWindowsCMD($value) { | |||||
| // Make sure this is CreateProcess-safe first | |||||
| $value = self::escapeWindowsArgv($value); | |||||
| // Now prefix all CMD meta characters with a `^` to escape them | |||||
| return preg_replace('/[()%!^"<>&|]/', '^$0', $value); | |||||
| } | |||||
| private static function escapePowershell($value) { | private static function escapePowershell($value) { | ||||
| // These escape sequences are from http://ss64.com/ps/syntax-esc.html | // These escape sequences are from http://ss64.com/ps/syntax-esc.html | ||||
| // Replace backticks first. | // Replace backticks first. | ||||
| $value = str_replace('`', '``', $value); | $value = str_replace('`', '``', $value); | ||||
| // Now replace other required notations. | // Now replace other required notations. | ||||
| $value = str_replace("\0", '`0', $value); | $value = str_replace("\0", '`0', $value); | ||||
| Show All 19 Lines | |||||
There should be MODE_WIN_CMD to force Windows CMD escaping for e.g. when Drydock is executing a command on a remote Windows host (in this scenario, phutil_is_windows returns false because the Phabricator host is Linux).