├── CHANGELOG.md ├── LICENSE ├── README.md ├── composer.json └── src └── Args.php /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [Unreleased] 2 | 3 | ## [1.1.1] - 2019-11-07 4 | * Fixed: platform inconsistencies when escaping unexpected `false` or `null` values. 5 | 6 | ## [1.1.0] - 2018-08-14 7 | * Added: `escapeCommand` method, taking an array of arguments that includes the executable. 8 | * Added: optional `module` argument to improve edge-case escaping for the executable. 9 | * Fixed: replaced `escapeshellarg` usage to avoid locale problems. 10 | * Fixed: continual updates to use Chocolatey with appveyor. 11 | 12 | ## [1.0.0] - 2016-08-04 13 | * Initial release 14 | 15 | [Unreleased]: https://github.com/johnstevenson/winbox-args/compare/v1.1.1...HEAD 16 | [1.1.1]: https://github.com/johnstevenson/winbox-args/compare/v1.1.0...v1.1.1 17 | [1.1.0]: https://github.com/johnstevenson/winbox-args/compare/v1.0.0...v1.1.0 18 | [1.0.0]: https://github.com/johnstevenson/winbox-args/compare/a6a5783f708a...v1.0.0 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 John Stevenson 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Winbox-Args 2 | =========== 3 | 4 | [![Build Status](https://travis-ci.org/johnstevenson/winbox-args.svg?branch=master)](https://travis-ci.org/johnstevenson/winbox-args) 5 | [![Build status](https://ci.appveyor.com/api/projects/status/p4k75qqcyioj0mfl?svg=true)](https://ci.appveyor.com/project/johnstevenson/winbox-args) 6 | 7 | A PHP function to escape command-line arguments, which replaces `escapeshellarg` with more robust methods for both Windows and non-Windows platforms. Install from [Packagist][packagist] and use it like this: 8 | 9 | ```php 10 | $escaped = Winbox\Args::escape($argument); 11 | ``` 12 | 13 | Alternatively, you can just [copy the code][function] into your own project (but please keep the license attribution and documentation link). 14 | 15 | ### What it does on the Windows platform 16 | The following transformations are made: 17 | 18 | * Double-quotes are escaped with a backslash, with any preceeding backslashes doubled up. 19 | * The argument is only enclosed in double-quotes if it contains whitespace or is empty. 20 | * Trailing backslashes are doubled up if the argument is enclosed in double-quotes. 21 | 22 | See [How Windows parses the command-line](https://github.com/johnstevenson/winbox-args/wiki/How-Windows-parses-the-command-line) if you would like to know why. 23 | 24 | By default, _cmd.exe_ meta characters are also escaped: 25 | 26 | * by caret-escaping the transformed argument (if it contains internal double-quotes or `%...%` syntax). 27 | * or by enclosing the argument in double-quotes. 28 | 29 | There are some limitations: 30 | 31 | 1. If _cmd_ is started with _DelayedExpansion_ enabled, `!...!` syntax could expand environment variables. 32 | 2. If the program name requires caret-escaping and contains whitespace, _cmd_ will not recognize it. 33 | 3. If an argument contain a newline `\n` character, this will not be escaped. 34 | 35 | See [How cmd.exe parses a command](https://github.com/johnstevenson/winbox-args/wiki/How-cmd.exe-parses-a-command) and [Implementing a solution](https://github.com/johnstevenson/winbox-args/wiki/Implementing-a-solution) for more information. 36 | 37 | ### What it does on non-Windows platforms 38 | The argument is enclosed is single-quotes, with internal single-quotes escaped. 39 | 40 | ### Is that it? 41 | Yup. An entire repo for a tiny function. However, it needs quite a lot of explanation because: 42 | 43 | * the command-line parsing rules in Windows are not immediately obvious. 44 | * PHP generally uses _cmd.exe_ to execute programs and this applies a different set of rules. 45 | * there is no simple solution. 46 | 47 | Full details explaining the different parsing rules, potential pitfalls and limitations can be found in the [Wiki][wiki]. 48 | 49 | ## License 50 | Winbox-Args is licensed under the MIT License - see the LICENSE file for details. 51 | 52 | [function]: https://github.com/johnstevenson/winbox-args/blob/master/src/Args.php#L15 53 | [wiki]:https://github.com/johnstevenson/winbox-args/wiki/Home 54 | [packagist]: https://packagist.org/packages/winbox/args 55 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "winbox/args", 3 | "description": "Windows command-line formatter", 4 | "keywords": ["windows", "escape", "command"], 5 | "homepage": "http://github.com/johnstevenson/winbox-args", 6 | "type": "library", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "John Stevenson", 11 | "email": "john-stevenson@blueyonder.co.uk" 12 | } 13 | ], 14 | "require": { 15 | "php": "^5.3.2 || ^7.0 || ^8.0" 16 | }, 17 | "require-dev": { 18 | "phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 8" 19 | }, 20 | "autoload": { 21 | "psr-4": { 22 | "Winbox\\": "src" 23 | } 24 | }, 25 | "autoload-dev": { 26 | "psr-4": { 27 | "Winbox\\Tests\\": "tests" 28 | } 29 | }, 30 | "scripts": { 31 | "test": "phpunit" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Args.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Winbox; 12 | 13 | class Args 14 | { 15 | /** 16 | * Escapes a string to be used as a shell argument 17 | * 18 | * Provides a more robust method on Windows than escapeshellarg. When $meta 19 | * is true cmd.exe meta-characters will also be escaped. If $module is true, 20 | * the argument will be treated as the name of the module (executable) to 21 | * be invoked, with an additional check for edge-case characters that cannot 22 | * be reliably escaped for cmd.exe. This has no effect if $meta is false. 23 | * 24 | * Feel free to copy this function, but please keep the following notice: 25 | * MIT Licensed (c) John Stevenson 26 | * See https://github.com/johnstevenson/winbox-args for more information. 27 | * 28 | * @param string $arg The argument to be escaped 29 | * @param bool $meta Additionally escape cmd.exe meta characters 30 | * @param bool $module The argument is the module to invoke 31 | * 32 | * @return string The escaped argument 33 | */ 34 | public static function escape($arg, $meta = true, $module = false) 35 | { 36 | if (!defined('PHP_WINDOWS_VERSION_BUILD')) { 37 | // Escape single-quotes and enclose in single-quotes 38 | return "'".str_replace("'", "'\\''", $arg)."'"; 39 | } 40 | 41 | // Check for whitespace or an empty value 42 | $quote = strpbrk($arg, " \t") !== false || (string) $arg === ''; 43 | 44 | // Escape double-quotes and double-up preceding backslashes 45 | $arg = preg_replace('/(\\\\*)"/', '$1$1\\"', $arg, -1, $dquotes); 46 | 47 | if ($meta) { 48 | // Check for expansion %..% sequences 49 | $meta = $dquotes || preg_match('/%[^%]+%/', $arg); 50 | 51 | if (!$meta) { 52 | // Check for characters that can be escaped in double-quotes 53 | $quote = $quote || strpbrk($arg, '^&|<>()') !== false; 54 | 55 | } elseif ($module && !$dquotes && $quote) { 56 | // Caret-escaping a module name with whitespace will split the 57 | // argument, so just quote it and hope there is no expansion 58 | $meta = false; 59 | } 60 | } 61 | 62 | if ($quote) { 63 | // Double-up trailing backslashes and enclose in double-quotes 64 | $arg = '"'.preg_replace('/(\\\\*)$/', '$1$1', $arg).'"'; 65 | } 66 | 67 | if ($meta) { 68 | // Caret-escape all meta characters 69 | $arg = preg_replace('/(["^&|<>()%])/', '^$1', $arg); 70 | } 71 | 72 | return $arg; 73 | } 74 | 75 | /** 76 | * Escapes an array of arguments that make up a shell command 77 | * 78 | * The first argument must be the module (executable) to be invoked. 79 | * 80 | * @param array $args A list of arguments, with the module name first 81 | * @param bool $meta Additionally escape cmd.exe meta characters 82 | * 83 | * @return string The escaped command line 84 | */ 85 | public static function escapeCommand(array $args, $meta = true) 86 | { 87 | $cmd = self::escape(array_shift($args), $meta, true); 88 | foreach ($args as $arg) { 89 | $cmd .= ' '.self::escape($arg, $meta); 90 | } 91 | 92 | return $cmd; 93 | } 94 | } 95 | --------------------------------------------------------------------------------