diff -r 01d91caa58b6 Lib/distutils/spawn.py --- a/Lib/distutils/spawn.py Tue Aug 18 13:27:18 2015 -0400 +++ b/Lib/distutils/spawn.py Tue Aug 18 13:09:34 2015 -0500 @@ -8,6 +8,7 @@ import sys import os +import re from distutils.errors import DistutilsPlatformError, DistutilsExecError from distutils.debug import DEBUG @@ -43,17 +44,32 @@ def _nt_quote_args(args): """Quote command-line arguments for DOS/Windows conventions. - Just wraps every argument which contains blanks in double quotes, and - returns a new argument list. + Wraps every argument which contains special characters in double quotes, + removes trailing backslashes from paths, properly escapes double quotes, + and returns a new argument list. """ - # XXX this doesn't seem very robust to me -- but if the Windows guys - # say it'll work, I guess I'll have to accept it. (What if an arg - # contains quotes? What other magic characters, other than spaces, - # have to be escaped? Is there an escaping mechanism other than - # quoting?) + + def has_balanced_quotes(arg): + return arg[0] == arg[-1] == '"' or arg[0] == arg[-1] == '\'' + + def is_unquoted_with_special_char(arg): + return (re.search(r'[ &\|\(\)<>^"]', arg) is not None and + has_balanced_quotes(arg) is False) + for i, arg in enumerate(args): - if ' ' in arg: - args[i] = '"%s"' % arg + # Quote anything with special characters and strip trailing backslash + if is_unquoted_with_special_char(arg): + args[i] = '"%s"' % arg.rstrip('\\') + # Check for trailing backslash in quoted arg + elif has_balanced_quotes(arg) and arg[-2] == '\\': + args[i] = arg[:-2] + arg[-1] + # Check for trailing backslash on unquoted paths without spaces + else: + args[i] = arg.rstrip('\\') + # Replace double quote in already quoted arg with "" + arg = args[i] + if arg[0] == '"': + args[i] = '"' + arg[1:-1].replace('"', '""') + '"' return args def _spawn_nt(cmd, search_path=1, verbose=0, dry_run=0): diff -r 01d91caa58b6 Lib/distutils/tests/test_spawn.py --- a/Lib/distutils/tests/test_spawn.py Tue Aug 18 13:27:18 2015 -0400 +++ b/Lib/distutils/tests/test_spawn.py Tue Aug 18 13:09:34 2015 -0500 @@ -18,7 +18,21 @@ for (args, wanted) in ((['with space', 'nospace'], ['"with space"', 'nospace']), (['nochange', 'nospace'], - ['nochange', 'nospace'])): + ['nochange', 'nospace']), + (['space\\b slash\\', 'space\\no bslash'], + ['"space\\b slash"', '"space\\no bslash"']), + (['nospace\\bslash\\', 'nospace\\nobslash'], + ['nospace\\bslash', 'nospace\\nobslash']), + (['"space quoted"', '"nospacequoted"'], + ['"space quoted"', '"nospacequoted"']), + (['"quote\\space bs\\"', '"quote\\bslash\\"'], + ['"quote\\space bs"', '"quote\\bslash"']), + (['/LIBPATH:"C:\\quotes space"'], + ['"/LIBPATH:""C:\\quotes space"""']), + (['/DMODULE_VERSION="1.0.5"'], + ['"/DMODULE_VERSION=""1.0.5"""']), + (['"mismatchedquotes\d'], + ['"""mismatchedquotes\d"'])): res = _nt_quote_args(args) self.assertEqual(res, wanted)