Skip to content
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

Escape single quotes when launching a script by path #1957

Merged
merged 1 commit into from
Nov 18, 2022
Merged
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
@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Management.Automation;
Expand Down Expand Up @@ -112,7 +112,7 @@ internal async Task LaunchScriptAsync(string scriptToLaunch)
{
// For a saved file we just execute its path (after escaping it).
command = PSCommandHelpers.BuildDotSourceCommandWithArguments(
string.Concat("'", scriptToLaunch, "'"), _debugStateService?.Arguments);
PSCommandHelpers.EscapeScriptFilePath(scriptToLaunch), _debugStateService?.Arguments);
}
else // It's a URI to an untitled script, or a raw script.
{
Expand Down
2 changes: 2 additions & 0 deletions src/PowerShellEditorServices/Utility/PSCommandExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ private static StringBuilder AddCommandText(this StringBuilder sb, Command comma
return sb;
}

public static string EscapeScriptFilePath(string f) => string.Concat("'", f.Replace("'", "''"), "'");

public static PSCommand BuildDotSourceCommandWithArguments(string command, IEnumerable<string> arguments)
{
string args = string.Join(" ", arguments ?? Array.Empty<string>());
Expand Down
99 changes: 53 additions & 46 deletions test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public class DebugServiceTests : IDisposable
private readonly BlockingCollection<DebuggerStoppedEventArgs> debuggerStoppedQueue = new();
private readonly WorkspaceService workspace;
private readonly ScriptFile debugScriptFile;
private readonly ScriptFile oddPathScriptFile;
private readonly ScriptFile variableScriptFile;
private readonly TestReadLine testReadLine = new();

Expand Down Expand Up @@ -68,9 +69,10 @@ public DebugServiceTests()

debugService.DebuggerStopped += OnDebuggerStopped;

// Load the test debug files
// Load the test debug files.
workspace = new WorkspaceService(NullLoggerFactory.Instance);
debugScriptFile = GetDebugScript("DebugTest.ps1");
oddPathScriptFile = GetDebugScript("Debug' W&ith $Params [Test].ps1");
variableScriptFile = GetDebugScript("VariableTest.ps1");
}

Expand Down Expand Up @@ -105,16 +107,16 @@ private VariableDetailsBase[] GetVariables(string scopeName)
return debugService.GetVariables(scope.Id);
}

private Task ExecutePowerShellCommand(string command, params string[] args)
private Task ExecuteScriptFileAsync(string scriptFilePath, params string[] args)
{
return psesHost.ExecutePSCommandAsync(
PSCommandHelpers.BuildDotSourceCommandWithArguments(string.Concat('"', command, '"'), args),
PSCommandHelpers.BuildDotSourceCommandWithArguments(PSCommandHelpers.EscapeScriptFilePath(scriptFilePath), args),
CancellationToken.None);
}

private Task ExecuteDebugFile() => ExecutePowerShellCommand(debugScriptFile.FilePath);
private Task ExecuteDebugFileAsync() => ExecuteScriptFileAsync(debugScriptFile.FilePath);

private Task ExecuteVariableScriptFile() => ExecutePowerShellCommand(variableScriptFile.FilePath);
private Task ExecuteVariableScriptFileAsync() => ExecuteScriptFileAsync(variableScriptFile.FilePath);

private void AssertDebuggerPaused()
{
Expand Down Expand Up @@ -201,27 +203,22 @@ await debugService.SetCommandBreakpointsAsync(
[MemberData(nameof(DebuggerAcceptsScriptArgsTestData))]
public async Task DebuggerAcceptsScriptArgs(string[] args)
{
// The path is intentionally odd (some escaped chars but not all) because we are testing
// the internal path escaping mechanism - it should escape certain chars ([, ] and space) but
// it should not escape already escaped chars.
ScriptFile debugWithParamsFile = GetDebugScript("Debug W&ith Params [Test].ps1");

BreakpointDetails[] breakpoints = await debugService.SetLineBreakpointsAsync(
debugWithParamsFile,
new[] { BreakpointDetails.Create(debugWithParamsFile.FilePath, 3) }).ConfigureAwait(true);
oddPathScriptFile,
new[] { BreakpointDetails.Create(oddPathScriptFile.FilePath, 3) }).ConfigureAwait(true);

Assert.Single(breakpoints);
Assert.Collection(breakpoints, (breakpoint) =>
{
// TODO: The drive letter becomes lower cased on Windows for some reason.
Assert.Equal(debugWithParamsFile.FilePath, breakpoint.Source, ignoreCase: true);
Assert.Equal(oddPathScriptFile.FilePath, breakpoint.Source, ignoreCase: true);
Assert.Equal(3, breakpoint.LineNumber);
Assert.True(breakpoint.Verified);
});

Task _ = ExecutePowerShellCommand(debugWithParamsFile.FilePath, args);
Task _ = ExecuteScriptFileAsync(oddPathScriptFile.FilePath, args);

AssertDebuggerStopped(debugWithParamsFile.FilePath, 3);
AssertDebuggerStopped(oddPathScriptFile.FilePath, 3);

VariableDetailsBase[] variables = GetVariables(VariableContainerDetails.LocalScopeName);

Expand Down Expand Up @@ -273,8 +270,7 @@ public async Task DebuggerSetsAndClearsFunctionBreakpoints()
breakpoints = await debugService.SetCommandBreakpointsAsync(
new[] { CommandBreakpointDetails.Create("Get-Host") }).ConfigureAwait(true);

Assert.Single(breakpoints);
Assert.Equal("Get-Host", breakpoints[0].Name);
Assert.Equal("Get-Host", Assert.Single(breakpoints).Name);

breakpoints = await debugService.SetCommandBreakpointsAsync(
Array.Empty<CommandBreakpointDetails>()).ConfigureAwait(true);
Expand All @@ -288,7 +284,7 @@ public async Task DebuggerStopsOnFunctionBreakpoints()
CommandBreakpointDetails[] breakpoints = await debugService.SetCommandBreakpointsAsync(
new[] { CommandBreakpointDetails.Create("Write-Host") }).ConfigureAwait(true);

Task _ = ExecuteDebugFile();
Task _ = ExecuteDebugFileAsync();
AssertDebuggerStopped(debugScriptFile.FilePath, 6);

VariableDetailsBase[] variables = GetVariables(VariableContainerDetails.LocalScopeName);
Expand Down Expand Up @@ -355,7 +351,7 @@ await debugService.SetLineBreakpointsAsync(
BreakpointDetails.Create(debugScriptFile.FilePath, 7)
}).ConfigureAwait(true);

Task _ = ExecuteDebugFile();
Task _ = ExecuteDebugFileAsync();
AssertDebuggerStopped(debugScriptFile.FilePath, 5);
debugService.Continue();
AssertDebuggerStopped(debugScriptFile.FilePath, 7);
Expand All @@ -373,7 +369,7 @@ await debugService.SetLineBreakpointsAsync(
BreakpointDetails.Create(debugScriptFile.FilePath, 7, null, $"$i -eq {breakpointValue1} -or $i -eq {breakpointValue2}"),
}).ConfigureAwait(true);

Task _ = ExecuteDebugFile();
Task _ = ExecuteDebugFileAsync();
AssertDebuggerStopped(debugScriptFile.FilePath, 7);

VariableDetailsBase[] variables = GetVariables(VariableContainerDetails.LocalScopeName);
Expand Down Expand Up @@ -409,7 +405,7 @@ await debugService.SetLineBreakpointsAsync(
BreakpointDetails.Create(debugScriptFile.FilePath, 6, null, null, $"{hitCount}"),
}).ConfigureAwait(true);

Task _ = ExecuteDebugFile();
Task _ = ExecuteDebugFileAsync();
AssertDebuggerStopped(debugScriptFile.FilePath, 6);

VariableDetailsBase[] variables = GetVariables(VariableContainerDetails.LocalScopeName);
Expand All @@ -430,7 +426,7 @@ await debugService.SetLineBreakpointsAsync(
debugScriptFile,
new[] { BreakpointDetails.Create(debugScriptFile.FilePath, 6, null, "$i % 2 -eq 0", $"{hitCount}") }).ConfigureAwait(true);

Task _ = ExecuteDebugFile();
Task _ = ExecuteDebugFileAsync();
AssertDebuggerStopped(debugScriptFile.FilePath, 6);

VariableDetailsBase[] variables = GetVariables(VariableContainerDetails.LocalScopeName);
Expand Down Expand Up @@ -498,7 +494,7 @@ public async Task DebuggerBreaksWhenRequested()
{
IReadOnlyList<LineBreakpoint> confirmedBreakpoints = await GetConfirmedBreakpoints(debugScriptFile).ConfigureAwait(true);
Assert.Equal(0, confirmedBreakpoints.Count);
Task _ = ExecuteDebugFile();
Task _ = ExecuteDebugFileAsync();
// NOTE: This must be run on a separate thread so the async event handlers can fire.
await Task.Run(() => debugService.Break()).ConfigureAwait(true);
AssertDebuggerPaused();
Expand All @@ -507,7 +503,7 @@ public async Task DebuggerBreaksWhenRequested()
[Fact]
public async Task DebuggerRunsCommandsWhileStopped()
{
Task _ = ExecuteDebugFile();
Task _ = ExecuteDebugFileAsync();
// NOTE: This must be run on a separate thread so the async event handlers can fire.
await Task.Run(() => debugService.Break()).ConfigureAwait(true);
AssertDebuggerPaused();
Expand All @@ -529,7 +525,7 @@ await debugService.SetCommandBreakpointsAsync(
new[] { CommandBreakpointDetails.Create("Write-Host") }).ConfigureAwait(true);

ScriptFile testScript = GetDebugScript("PSDebugContextTest.ps1");
Task _ = ExecutePowerShellCommand(testScript.FilePath);
Task _ = ExecuteScriptFileAsync(testScript.FilePath);
AssertDebuggerStopped(testScript.FilePath, 11);

VariableDetails prompt = await debugService.EvaluateExpressionAsync("prompt", false).ConfigureAwait(true);
Expand Down Expand Up @@ -582,12 +578,10 @@ public async Task RecordsF5CommandInPowerShellHistory()
CancellationToken.None).ConfigureAwait(true);

// Check the PowerShell history
Assert.Single(historyResult);
Assert.Equal(". '" + debugScriptFile.FilePath + "'", historyResult[0]);
Assert.Equal(". '" + debugScriptFile.FilePath + "'", Assert.Single(historyResult));

// Check the stubbed PSReadLine history
Assert.Single(testReadLine.history);
Assert.Equal(". '" + debugScriptFile.FilePath + "'", testReadLine.history[0]);
Assert.Equal(". '" + debugScriptFile.FilePath + "'", Assert.Single(testReadLine.history));
}

[Fact]
Expand All @@ -606,12 +600,25 @@ public async Task RecordsF8CommandInHistory()
CancellationToken.None).ConfigureAwait(true);

// Check the PowerShell history
Assert.Single(historyResult);
Assert.Equal(script, historyResult[0]);
Assert.Equal(script, Assert.Single(historyResult));

// Check the stubbed PSReadLine history
Assert.Single(testReadLine.history);
Assert.Equal(script, testReadLine.history[0]);
Assert.Equal(script, Assert.Single(testReadLine.history));
}

[Fact]
public async Task OddFilePathsLaunchCorrectly()
{
ConfigurationDoneHandler configurationDoneHandler = new(
NullLoggerFactory.Instance, null, debugService, null, null, psesHost, workspace, null, psesHost);
await configurationDoneHandler.LaunchScriptAsync(oddPathScriptFile.FilePath).ConfigureAwait(true);

IReadOnlyList<string> historyResult = await psesHost.ExecutePSCommandAsync<string>(
new PSCommand().AddScript("(Get-History).CommandLine"),
CancellationToken.None).ConfigureAwait(true);

// Check the PowerShell history
Assert.Equal(". " + PSCommandHelpers.EscapeScriptFilePath(oddPathScriptFile.FilePath), Assert.Single(historyResult));
}

[Fact]
Expand All @@ -621,7 +628,7 @@ await debugService.SetLineBreakpointsAsync(
variableScriptFile,
new[] { BreakpointDetails.Create(variableScriptFile.FilePath, 8) }).ConfigureAwait(true);

Task _ = ExecuteVariableScriptFile();
Task _ = ExecuteVariableScriptFileAsync();
AssertDebuggerStopped(variableScriptFile.FilePath);

VariableDetailsBase[] variables = GetVariables(VariableContainerDetails.LocalScopeName);
Expand All @@ -639,7 +646,7 @@ await debugService.SetLineBreakpointsAsync(
variableScriptFile,
new[] { BreakpointDetails.Create(variableScriptFile.FilePath, 21) }).ConfigureAwait(true);

Task _ = ExecuteVariableScriptFile();
Task _ = ExecuteVariableScriptFileAsync();
AssertDebuggerStopped(variableScriptFile.FilePath);

VariableDetailsBase[] variables = GetVariables(VariableContainerDetails.LocalScopeName);
Expand Down Expand Up @@ -689,7 +696,7 @@ await debugService.SetLineBreakpointsAsync(
variableScriptFile,
new[] { BreakpointDetails.Create(variableScriptFile.FilePath, 14) }).ConfigureAwait(true);

Task _ = ExecuteVariableScriptFile();
Task _ = ExecuteVariableScriptFileAsync();
AssertDebuggerStopped(variableScriptFile.FilePath);

VariableScope[] scopes = debugService.GetVariableScopes(0);
Expand Down Expand Up @@ -743,7 +750,7 @@ await debugService.SetLineBreakpointsAsync(
new[] { BreakpointDetails.Create(variableScriptFile.FilePath, 14) }).ConfigureAwait(true);

// Execute the script and wait for the breakpoint to be hit
Task _ = ExecuteVariableScriptFile();
Task _ = ExecuteVariableScriptFileAsync();
AssertDebuggerStopped(variableScriptFile.FilePath);

VariableScope[] scopes = debugService.GetVariableScopes(0);
Expand Down Expand Up @@ -799,7 +806,7 @@ await debugService.SetLineBreakpointsAsync(
new[] { BreakpointDetails.Create(variableScriptFile.FilePath, 15) }).ConfigureAwait(true);

// Execute the script and wait for the breakpoint to be hit
Task _ = ExecuteVariableScriptFile();
Task _ = ExecuteVariableScriptFileAsync();
AssertDebuggerStopped(variableScriptFile.FilePath);

StackFrameDetails[] stackFrames = await debugService.GetStackFramesAsync().ConfigureAwait(true);
Expand All @@ -819,7 +826,7 @@ await debugService.SetLineBreakpointsAsync(
new[] { BreakpointDetails.Create(variableScriptFile.FilePath, 11) }).ConfigureAwait(true);

// Execute the script and wait for the breakpoint to be hit
Task _ = ExecuteVariableScriptFile();
Task _ = ExecuteVariableScriptFileAsync();
AssertDebuggerStopped(variableScriptFile.FilePath);

StackFrameDetails[] stackFrames = await debugService.GetStackFramesAsync().ConfigureAwait(true);
Expand Down Expand Up @@ -852,7 +859,7 @@ await debugService.SetLineBreakpointsAsync(
new[] { BreakpointDetails.Create(variableScriptFile.FilePath, 16) }).ConfigureAwait(true);

// Execute the script and wait for the breakpoint to be hit
Task _ = ExecuteVariableScriptFile();
Task _ = ExecuteVariableScriptFileAsync();
AssertDebuggerStopped(variableScriptFile.FilePath);

StackFrameDetails[] stackFrames = await debugService.GetStackFramesAsync().ConfigureAwait(true);
Expand All @@ -872,7 +879,7 @@ await debugService.SetLineBreakpointsAsync(
new[] { BreakpointDetails.Create(variableScriptFile.FilePath, 17) }).ConfigureAwait(true);

// Execute the script and wait for the breakpoint to be hit
Task _ = ExecuteVariableScriptFile();
Task _ = ExecuteVariableScriptFileAsync();
AssertDebuggerStopped(variableScriptFile.FilePath);

StackFrameDetails[] stackFrames = await debugService.GetStackFramesAsync().ConfigureAwait(true);
Expand All @@ -898,7 +905,7 @@ public async Task DebuggerEnumerableShowsRawView()
await debugService.SetCommandBreakpointsAsync(new[] { breakpoint }).ConfigureAwait(true);

// Execute the script and wait for the breakpoint to be hit
Task _ = ExecuteVariableScriptFile();
Task _ = ExecuteVariableScriptFileAsync();
AssertDebuggerStopped(commandBreakpointDetails: breakpoint);

VariableDetailsBase simpleArrayVar = Array.Find(
Expand Down Expand Up @@ -935,7 +942,7 @@ public async Task DebuggerDictionaryShowsRawView()
await debugService.SetCommandBreakpointsAsync(new[] { breakpoint }).ConfigureAwait(true);

// Execute the script and wait for the breakpoint to be hit
Task _ = ExecuteVariableScriptFile();
Task _ = ExecuteVariableScriptFileAsync();
AssertDebuggerStopped(commandBreakpointDetails: breakpoint);

VariableDetailsBase simpleDictionaryVar = Array.Find(
Expand Down Expand Up @@ -971,7 +978,7 @@ public async Task DebuggerDerivedDictionaryPropertyInRawView()
await debugService.SetCommandBreakpointsAsync(new[] { breakpoint }).ConfigureAwait(true);

// Execute the script and wait for the breakpoint to be hit
Task _ = ExecuteVariableScriptFile();
Task _ = ExecuteVariableScriptFileAsync();
AssertDebuggerStopped(commandBreakpointDetails: breakpoint);

VariableDetailsBase sortedDictionaryVar = Array.Find(
Expand Down Expand Up @@ -1000,7 +1007,7 @@ await debugService.SetLineBreakpointsAsync(
new[] { BreakpointDetails.Create(variableScriptFile.FilePath, 18) }).ConfigureAwait(true);

// Execute the script and wait for the breakpoint to be hit
Task _ = ExecuteVariableScriptFile();
Task _ = ExecuteVariableScriptFileAsync();
AssertDebuggerStopped(variableScriptFile.FilePath);

StackFrameDetails[] stackFrames = await debugService.GetStackFramesAsync().ConfigureAwait(true);
Expand Down Expand Up @@ -1029,7 +1036,7 @@ await debugService.SetLineBreakpointsAsync(
new[] { BreakpointDetails.Create(variableScriptFile.FilePath, 19) }).ConfigureAwait(true);

// Execute the script and wait for the breakpoint to be hit
Task _ = ExecuteVariableScriptFile();
Task _ = ExecuteVariableScriptFileAsync();
AssertDebuggerStopped(variableScriptFile.FilePath);

StackFrameDetails[] stackFrames = await debugService.GetStackFramesAsync().ConfigureAwait(true);
Expand Down