File: ArgumentEscaper.cs
Web Access
Project: src\src\Common\Microsoft.Arcade.Common\Microsoft.Arcade.Common.csproj (Microsoft.Arcade.Common)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace Microsoft.Arcade.Common
{
    public static class ArgumentEscaper
    {
        /// <summary>
        /// Escapes and quote arguments that need it (contain a space, a quote...).
        /// </summary>
        public static string EscapeAndConcatenateArgArrayForProcessStart(IEnumerable<string> args)
        {
            return string.Join(" ", args.Select(EscapeArg));
        }
 
        /// <summary>
        /// Escapes and quote arguments that need it.
        /// 
        /// 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>
        public static string EscapeAndConcatenateArgArrayForCmdProcessStart(IEnumerable<string> args)
        {
            return string.Join(" ", args.Select(EscapeArgForCmd));
        }
 
        private static string EscapeArg(string argument)
        {
            var sb = new StringBuilder();
 
            // Don't quote already quoted strings
            bool quoted = IsQuoted(argument);
            var shouldQuote = !quoted && ShouldSurroundWithQuotes(argument);
            if (shouldQuote || quoted)
            {
                sb.Append("\"");
            }
 
            for (int i = 0; i < argument.Length; ++i)
            {
                if (quoted && (i == 0 || i == argument.Length - 1))
                {
                    continue;
                }
 
                var backslashCount = 0;
 
                // Consume All Backslashes
                while (i < argument.Length && argument[i] == '\\')
                {
                    backslashCount++;
                    i++;
                }
 
                // Escape any backslashes at the end of the arg
                // This ensures the outside quote is interpreted as
                // an argument delimiter
                if (i == argument.Length)
                {
                    sb.Append('\\', 2 * backslashCount);
                }
 
                // Escape any preceding backslashes and the quote
                else if (argument[i] == '"')
                {
                    sb.Append('\\', (2 * backslashCount) + 1);
                    sb.Append('"');
                }
 
                // Output any consumed backslashes and the character
                else
                {
                    sb.Append('\\', backslashCount);
                    sb.Append(argument[i]);
                }
            }
 
            if (shouldQuote || quoted)
            {
                sb.Append("\"");
            }
 
            return sb.ToString();
        }
 
        private static string EscapeArgForCmd(string argument)
        {
            var sb = new StringBuilder();
 
            var quoted = IsQuoted(argument);
            var shouldQuote = !quoted && ShouldSurroundWithQuotes(argument);
 
            if (shouldQuote)
            {
                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 (shouldQuote)
            {
                sb.Append("^\"");
            }
 
            return sb.ToString();
        }
 
        private static bool ShouldSurroundWithQuotes(string argument)
        {
            // Only quote if whitespace exists in the string
            return argument.Contains(' ') || argument.Contains('\t') || argument.Contains('\n') || argument.Contains('"');
        }
 
 
        private static bool IsQuoted(string argument)
        {
            return argument.Length > 1 && (argument[0] == '\"' || argument[argument.Length - 1] == '\"');
        }
    }
}