2016-01-22 22:03:40 +00:00
|
|
|
|
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
|
|
|
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
|
|
|
|
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Text;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
|
|
|
|
namespace Microsoft.DotNet.Cli.Utils
|
|
|
|
|
{
|
|
|
|
|
public static class ArgumentEscaper
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Undo the processing which took place to create string[] args in Main,
|
|
|
|
|
/// so that the next process will receive the same string[] args
|
|
|
|
|
///
|
|
|
|
|
/// See here for more info:
|
|
|
|
|
/// http://blogs.msdn.com/b/twistylittlepassagesallalike/archive/2011/04/23/everyone-quotes-arguments-the-wrong-way.aspx
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="args"></param>
|
|
|
|
|
/// <returns></returns>
|
2016-01-22 23:37:37 +00:00
|
|
|
|
public static string EscapeAndConcatenateArgArrayForProcessStart(IEnumerable<string> args)
|
2016-01-22 22:03:40 +00:00
|
|
|
|
{
|
2016-01-22 11:59:04 +00:00
|
|
|
|
return string.Join(" ", EscapeArgArray(args));
|
2016-01-22 22:03:40 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Undo the processing which took place to create string[] args in Main,
|
|
|
|
|
/// so that the next process will receive the same string[] args
|
|
|
|
|
///
|
|
|
|
|
/// See here for more info:
|
|
|
|
|
/// http://blogs.msdn.com/b/twistylittlepassagesallalike/archive/2011/04/23/everyone-quotes-arguments-the-wrong-way.aspx
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="args"></param>
|
|
|
|
|
/// <returns></returns>
|
2016-01-23 00:31:35 +00:00
|
|
|
|
public static string EscapeAndConcatenateArgArrayForCmdProcessStart(IEnumerable<string> args)
|
2016-01-22 22:03:40 +00:00
|
|
|
|
{
|
2016-01-22 11:59:04 +00:00
|
|
|
|
return string.Join(" ", EscapeArgArrayForCmd(args));
|
2016-01-22 22:03:40 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Undo the processing which took place to create string[] args in Main,
|
|
|
|
|
/// so that the next process will receive the same string[] args
|
|
|
|
|
///
|
|
|
|
|
/// See here for more info:
|
|
|
|
|
/// http://blogs.msdn.com/b/twistylittlepassagesallalike/archive/2011/04/23/everyone-quotes-arguments-the-wrong-way.aspx
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="args"></param>
|
|
|
|
|
/// <returns></returns>
|
2016-01-22 21:55:13 +00:00
|
|
|
|
private static IEnumerable<string> EscapeArgArray(IEnumerable<string> args)
|
2016-01-22 22:03:40 +00:00
|
|
|
|
{
|
|
|
|
|
var escapedArgs = new List<string>();
|
|
|
|
|
|
|
|
|
|
foreach (var arg in args)
|
|
|
|
|
{
|
|
|
|
|
escapedArgs.Add(EscapeArg(arg));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return escapedArgs;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// This prefixes every character with the '^' character to force cmd to
|
|
|
|
|
/// interpret the argument string literally. An alternative option would
|
|
|
|
|
/// be to do this only for cmd metacharacters.
|
|
|
|
|
///
|
|
|
|
|
/// See here for more info:
|
|
|
|
|
/// http://blogs.msdn.com/b/twistylittlepassagesallalike/archive/2011/04/23/everyone-quotes-arguments-the-wrong-way.aspx
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="args"></param>
|
|
|
|
|
/// <returns></returns>
|
2016-01-22 21:55:13 +00:00
|
|
|
|
private static IEnumerable<string> EscapeArgArrayForCmd(IEnumerable<string> arguments)
|
2016-01-22 22:03:40 +00:00
|
|
|
|
{
|
|
|
|
|
var escapedArgs = new List<string>();
|
|
|
|
|
|
|
|
|
|
foreach (var arg in arguments)
|
|
|
|
|
{
|
|
|
|
|
escapedArgs.Add(EscapeArgForCmd(arg));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return escapedArgs;
|
|
|
|
|
}
|
|
|
|
|
|
2016-01-22 11:59:04 +00:00
|
|
|
|
private static string EscapeArg(string arg)
|
2016-01-22 22:03:40 +00:00
|
|
|
|
{
|
|
|
|
|
var sb = new StringBuilder();
|
|
|
|
|
|
2016-01-22 11:59:04 +00:00
|
|
|
|
var quoted = ShouldSurroundWithQuotes(arg);
|
|
|
|
|
if (quoted) sb.Append("\"");
|
2016-01-22 22:03:40 +00:00
|
|
|
|
|
2016-01-22 11:59:04 +00:00
|
|
|
|
for (int i = 0; i < arg.Length; ++i)
|
2016-01-22 22:03:40 +00:00
|
|
|
|
{
|
2016-01-22 11:59:04 +00:00
|
|
|
|
var backslashCount = 0;
|
|
|
|
|
|
|
|
|
|
// Consume All Backslashes
|
|
|
|
|
while (i < arg.Length && arg[i] == '\\')
|
2016-01-22 22:03:40 +00:00
|
|
|
|
{
|
2016-01-22 11:59:04 +00:00
|
|
|
|
backslashCount++;
|
|
|
|
|
i++;
|
2016-01-22 22:03:40 +00:00
|
|
|
|
}
|
2016-01-22 11:59:04 +00:00
|
|
|
|
|
|
|
|
|
// Escape any backslashes at the end of the arg
|
|
|
|
|
// This ensures the outside quote is interpreted as
|
|
|
|
|
// an argument delimiter
|
|
|
|
|
if (i == arg.Length)
|
|
|
|
|
{
|
|
|
|
|
sb.Append('\\', 2 * backslashCount);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Escape any preceding backslashes and the quote
|
|
|
|
|
else if (arg[i] == '"')
|
2016-01-22 22:03:40 +00:00
|
|
|
|
{
|
2016-01-22 11:59:04 +00:00
|
|
|
|
sb.Append('\\', (2 * backslashCount) + 1);
|
|
|
|
|
sb.Append('"');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Output any consumed backslashes and the character
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
sb.Append('\\', backslashCount);
|
|
|
|
|
sb.Append(arg[i]);
|
2016-01-22 22:03:40 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-01-22 11:59:04 +00:00
|
|
|
|
if (quoted) sb.Append("\"");
|
2016-01-22 22:03:40 +00:00
|
|
|
|
|
|
|
|
|
return sb.ToString();
|
|
|
|
|
}
|
|
|
|
|
|
2016-01-22 11:59:04 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Prepare as single argument to
|
|
|
|
|
/// roundtrip properly through cmd.
|
|
|
|
|
///
|
|
|
|
|
/// This prefixes every character with the '^' character to force cmd to
|
|
|
|
|
/// interpret the argument string literally. An alternative option would
|
|
|
|
|
/// be to do this only for cmd metacharacters.
|
|
|
|
|
///
|
|
|
|
|
/// See here for more info:
|
|
|
|
|
/// http://blogs.msdn.com/b/twistylittlepassagesallalike/archive/2011/04/23/everyone-quotes-arguments-the-wrong-way.aspx
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="args"></param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
private static string EscapeArgForCmd(string argument)
|
2016-01-22 22:03:40 +00:00
|
|
|
|
{
|
2016-01-22 11:59:04 +00:00
|
|
|
|
var sb = new StringBuilder();
|
|
|
|
|
|
|
|
|
|
var quoted = ShouldSurroundWithQuotes(argument);
|
|
|
|
|
|
|
|
|
|
if (quoted) sb.Append("^\"");
|
|
|
|
|
|
|
|
|
|
foreach (var character in argument)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
if (character == '"')
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
sb.Append('^');
|
|
|
|
|
sb.Append('"');
|
|
|
|
|
sb.Append('^');
|
|
|
|
|
sb.Append(character);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
sb.Append("^");
|
|
|
|
|
sb.Append(character);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (quoted) sb.Append("^\"");
|
|
|
|
|
|
|
|
|
|
return sb.ToString();
|
2016-01-22 22:03:40 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Prepare as single argument to
|
|
|
|
|
/// roundtrip properly through cmd.
|
|
|
|
|
///
|
|
|
|
|
/// This prefixes every character with the '^' character to force cmd to
|
|
|
|
|
/// interpret the argument string literally. An alternative option would
|
|
|
|
|
/// be to do this only for cmd metacharacters.
|
|
|
|
|
///
|
|
|
|
|
/// See here for more info:
|
|
|
|
|
/// http://blogs.msdn.com/b/twistylittlepassagesallalike/archive/2011/04/23/everyone-quotes-arguments-the-wrong-way.aspx
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="args"></param>
|
|
|
|
|
/// <returns></returns>
|
2016-01-22 11:59:04 +00:00
|
|
|
|
internal static bool ShouldSurroundWithQuotes(string argument)
|
2016-01-22 22:03:40 +00:00
|
|
|
|
{
|
2016-01-22 11:59:04 +00:00
|
|
|
|
// Don't quote already quoted strings
|
|
|
|
|
if (argument.StartsWith("\"", StringComparison.Ordinal) &&
|
|
|
|
|
argument.EndsWith("\"", StringComparison.Ordinal))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2016-01-22 22:03:40 +00:00
|
|
|
|
|
2016-01-22 11:59:04 +00:00
|
|
|
|
// Only quote if whitespace exists in the string
|
|
|
|
|
if (argument.Contains(" ") || argument.Contains("\t") || argument.Contains("\n"))
|
2016-01-22 22:03:40 +00:00
|
|
|
|
{
|
2016-01-22 11:59:04 +00:00
|
|
|
|
return true;
|
2016-01-22 22:03:40 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-01-22 11:59:04 +00:00
|
|
|
|
return true;
|
2016-01-22 22:03:40 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|