Skip to content

Fix: Add special EscapePowershellArgument handling - MOVED to 1611 #1609

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -163,16 +163,9 @@ private static PSCommand BuildPSCommandFromArguments(string command, IReadOnlyLi

foreach (string arg in arguments)
{
sb.Append(' ');

if (StringEscaping.PowerShellArgumentNeedsEscaping(arg))
{
sb.Append(StringEscaping.SingleQuoteAndEscape(arg));
}
else
{
sb.Append(arg);
}
sb
.Append(' ')
.Append(StringEscaping.EscapePowershellArgument(arg));
}

return new PSCommand().AddScript(sb.ToString());
Expand Down
29 changes: 26 additions & 3 deletions src/PowerShellEditorServices/Utility/StringEscaping.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,24 @@ internal static class StringEscaping
{
public static StringBuilder SingleQuoteAndEscape(string s)
{
var dequotedString = s.TrimStart('\'').TrimEnd('\'');
var psEscapedInnerQuotes = dequotedString.Replace("'", "`'");
return new StringBuilder(s.Length)
.Append("'")
.Append(s.Replace("'", "''"))
.Append("'");
.Append('\'')
.Append(psEscapedInnerQuotes)
.Append('\'');
}

public static bool PowerShellArgumentNeedsEscaping(string argument)
{
//Already quoted arguments dont require escaping unless there is a quote inside as well
if (argument.StartsWith("'") && argument.EndsWith("'"))
{
var dequotedString = argument.TrimStart('\'').TrimEnd('\'');
// need to escape if there is a single quote between single quotes
return dequotedString.Contains("'");
}

foreach (char c in argument)
{
switch (c)
Expand All @@ -33,5 +43,18 @@ public static bool PowerShellArgumentNeedsEscaping(string argument)

return false;
}

public static string EscapePowershellArgument(string argument)
{
if (PowerShellArgumentNeedsEscaping(argument))
{
return SingleQuoteAndEscape(argument).ToString();
}
else
{
return argument;
}
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,13 @@ public void CorrectlyWildcardEscapesPaths_Spaces(string unescapedPath, string es
[InlineData("C:\\look\\an*\\here.ps1", "'C:\\look\\an*\\here.ps1'")]
[InlineData("/Users/me/Documents/?here.ps1", "'/Users/me/Documents/?here.ps1'")]
[InlineData("/Brackets [and s]paces/path.ps1", "'/Brackets [and s]paces/path.ps1'")]
[InlineData("/file path/that isn't/normal/", "'/file path/that isn''t/normal/'")]
[InlineData("/file path/that isn't/normal/", "'/file path/that isn`'t/normal/'")]
[InlineData("/CJK.chars/脚本/hello.ps1", "'/CJK.chars/脚本/hello.ps1'")]
[InlineData("/CJK chars/脚本/[hello].ps1", "'/CJK chars/脚本/[hello].ps1'")]
[InlineData("C:\\Animal s\\утка\\quack.ps1", "'C:\\Animal s\\утка\\quack.ps1'")]
[InlineData("C:\\&nimals\\утка\\qu*ck?.ps1", "'C:\\&nimals\\утка\\qu*ck?.ps1'")]
[InlineData("../../Quote'InPathTest.ps1", "'../../Quote`'InPathTest.ps1'")]

public void CorrectlyQuoteEscapesPaths(string unquotedPath, string expectedQuotedPath)
{
string extensionQuotedPath = StringEscaping.SingleQuoteAndEscape(unquotedPath).ToString();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using Xunit;
using Microsoft.PowerShell.EditorServices.Utility;
using System.Management.Automation;
using System.Linq;

namespace Microsoft.PowerShell.EditorServices.Test.Session
{
public class ArgumentEscapingTests
{
[Trait("Category", "ArgumentEscaping")]
[Theory]
[InlineData("/path/to/file", "/path/to/file")]
[InlineData("'/path/to/file'", "'/path/to/file'")]
[InlineData("not|allowed|pipeline", "'not|allowed|pipeline'")]
[InlineData("doublequote\"inmiddle", "'doublequote\"inmiddle'")]
[InlineData("am&persand", "'am&persand'")]
[InlineData("semicolon;", "'semicolon;'")]
[InlineData(":colon", "':colon'")]
[InlineData(" has space s", "' has space s'")]
[InlineData("[brackets]areOK", "[brackets]areOK")]
[InlineData("$(expressionsAreOK)", "$(expressionsAreOK)")]
[InlineData("{scriptBlocksAreOK}", "{scriptBlocksAreOK}")]
[InlineData("'quote ' in middle of argument'", "'quote `' in middle of argument'")]

public void CorrectlyEscapesPowerShellArguments(string Arg, string expectedArg)
{
string quotedArg = StringEscaping.EscapePowershellArgument(Arg);
Assert.Equal(expectedArg, quotedArg);
}

[Trait("Category", "ArgumentEscaping")]
[Theory]
[InlineData("/path/to/file", "/path/to/file")]
[InlineData("'/path/to/file'", "/path/to/file")]
[InlineData("not|allowed|pipeline", "not|allowed|pipeline")]
[InlineData("doublequote\"inmiddle", "doublequote\"inmiddle")]
[InlineData("am&persand", "am&persand")]
[InlineData("semicolon;", "semicolon;")]
[InlineData(":colon", ":colon")]
[InlineData(" has space s", " has space s")]
[InlineData("[brackets]areOK", "[brackets]areOK")]
[InlineData("$(echo 'expressionsAreOK')", "expressionsAreOK")]
// [InlineData("{scriptBlocksAreOK}", "{scriptBlocksAreOK}")]
public void CanEvaluateArgumentsSafely(string Arg, string expectedOutput)
{
var escapedArg = StringEscaping.EscapePowershellArgument(Arg);
var psCommand = new PSCommand().AddScript($"& Write-Output {escapedArg}");
using var pwsh = System.Management.Automation.PowerShell.Create();
pwsh.Commands = psCommand;
var scriptOutput = pwsh.Invoke<string>().First();
Assert.Equal(expectedOutput, scriptOutput);
}
}
}