codeql/csharp/extractor/Semmle.Util/CommandBuilder.cs

197 строки
6.3 KiB
C#

using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Semmle.Util
{
/// <summary>
/// Utility to construct a build command.
/// </summary>
public class CommandBuilder
{
private enum EscapeMode { Process, Cmd };
private readonly StringBuilder arguments;
private bool firstCommand;
private string? executable;
private readonly EscapeMode escapingMode;
private readonly string? workingDirectory;
private readonly IDictionary<string, string>? environment;
private readonly bool silent;
/// <summary>
/// Initializes a new instance of the <see cref="T:Semmle.Autobuild.CommandBuilder"/> class.
/// </summary>
/// <param name="workingDirectory">The working directory (<code>null</code> for current directory).</param>
/// <param name="environment">Additional environment variables.</param>
/// <param name="silent">Whether this command should be run silently.</param>
public CommandBuilder(IBuildActions actions, string? workingDirectory = null, IDictionary<string, string>? environment = null, bool silent = false)
{
arguments = new StringBuilder();
if (actions.IsWindows())
{
executable = "cmd.exe";
arguments.Append("/C");
escapingMode = EscapeMode.Cmd;
}
else
{
escapingMode = EscapeMode.Process;
}
firstCommand = true;
this.workingDirectory = workingDirectory;
this.environment = environment;
this.silent = silent;
}
public CommandBuilder CallBatFile(string batFile, string? argumentsOpt = null)
{
NextCommand();
arguments.Append(" CALL");
QuoteArgument(batFile);
Argument(argumentsOpt);
return this;
}
private static readonly char[] specialChars = { ' ', '\t', '\n', '\v', '\"' };
private static readonly char[] cmdMetacharacter = { '(', ')', '%', '!', '^', '\"', '<', '>', '&', '|', ' ' };
/// <summary>
/// Appends the given argument to the command line.
/// </summary>
/// <param name="argument">The argument to append.</param>
/// <param name="force">Whether to always quote the argument.</param>
///
/// <remarks>
/// This implementation is copied from
/// https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/
/// </remarks>
private void ArgvQuote(string argument, bool force)
{
var cmd = escapingMode == EscapeMode.Cmd;
if (!force &&
!string.IsNullOrEmpty(argument) &&
argument.IndexOfAny(specialChars) == -1)
{
arguments.Append(argument);
}
else
{
if (cmd)
arguments.Append('^');
arguments.Append('\"');
for (var it = 0; ; ++it)
{
var numBackslashes = 0;
while (it != argument.Length && argument[it] == '\\')
{
++it;
++numBackslashes;
}
if (it == argument.Length)
{
arguments.Append('\\', numBackslashes * 2);
break;
}
if (argument[it] == '\"')
{
arguments.Append('\\', numBackslashes * 2 + 1);
if (cmd)
arguments.Append('^');
arguments.Append(arguments[it]);
}
else
{
arguments.Append('\\', numBackslashes);
if (cmd && cmdMetacharacter.Any(c => c == argument[it]))
arguments.Append('^');
arguments.Append(argument[it]);
}
}
if (cmd)
arguments.Append('^');
arguments.Append('\"');
}
}
public CommandBuilder QuoteArgument(string argumentsOpt)
{
if (argumentsOpt is not null)
{
NextArgument();
ArgvQuote(argumentsOpt, false);
}
return this;
}
private void NextArgument()
{
if (arguments.Length > 0)
arguments.Append(' ');
}
public CommandBuilder Argument(string? argumentsOpt)
{
if (argumentsOpt is not null)
{
NextArgument();
arguments.Append(argumentsOpt);
}
return this;
}
private void NextCommand()
{
if (firstCommand)
firstCommand = false;
else
arguments.Append(" &&");
}
public CommandBuilder RunCommand(string exe, string? argumentsOpt = null, bool quoteExe = true)
{
var (exe0, arg0) =
escapingMode == EscapeMode.Process && exe.EndsWith(".exe", System.StringComparison.Ordinal)
? ("mono", exe) // Linux
: (exe, null);
NextCommand();
if (executable is null)
{
executable = exe0;
}
else
{
if (quoteExe)
QuoteArgument(exe0);
else
Argument(exe0);
}
Argument(arg0);
Argument(argumentsOpt);
return this;
}
/// <summary>
/// Returns a build script that contains just this command.
/// </summary>
public BuildScript Script
{
get
{
if (executable is null)
throw new System.InvalidOperationException("executable is null");
return BuildScript.Create(executable, arguments.ToString(), silent, workingDirectory, environment);
}
}
}
}