Skip to content

Fix PowerShell path escaping #765

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

Merged
merged 10 commits into from
Oct 15, 2018

Conversation

rjmholt
Copy link
Contributor

@rjmholt rjmholt commented Oct 10, 2018

Fixes PowerShell/vscode-powershell#1400.
Fixes PowerShell/vscode-powershell#1526.

Replaces the strange regex logic we use with some glob-escaping string mangling.

Also single-quotes the place we dot-source a debug path.

Obsoletes some static public methods that have no business being public.

Adds a bunch of tests. Changes an existing one to check the ampersand case that we used to crash on.

@rjmholt
Copy link
Contributor Author

rjmholt commented Oct 10, 2018

I should also include a test for unusual characters, in aid of PowerShell/vscode-powershell#1392

Copy link
Collaborator

@SeeminglyScience SeeminglyScience left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! One major concern, the rest are nits or comments :)

@rjmholt
Copy link
Contributor Author

rjmholt commented Oct 10, 2018

Ok, I've added testing for international characters and for dot-sourcing files with unusual characters in them (and checked the latter fail if I don't quote). So I think this is ready now :)

Copy link
Member

@TylerLeonhardt TylerLeonhardt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Love the addition of all those tests


namespace Microsoft.PowerShell.EditorServices.Test.Session
{
public class PathEscapingTests
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+100 !

@@ -105,7 +105,7 @@ public async Task DebuggerAcceptsScriptArgs(string[] args)
// it should not escape already escaped chars.
ScriptFile debugWithParamsFile =
this.workspace.GetFile(
@"..\..\..\..\PowerShellEditorServices.Test.Shared\Debugging\Debug` With Params `[Test].ps1");
@"..\..\..\..\PowerShellEditorServices.Test.Shared\Debugging\Debug` W&ith Params `[Test].ps1");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What was the deal with the & char? It isn't a wildcard char. Ah, wait a tic. I see. On WinPS it's a reserved char and on PS Core it is used to create a (background) job.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it's a "token separator", so it will break a bareword string

@@ -796,7 +797,7 @@ public async Task ExecuteScriptWithArgs(string script, string arguments = null,
if (File.Exists(script) || File.Exists(scriptAbsPath))
{
// Dot-source the launched script path
script = ". " + EscapePath(script, escapeSpaces: true);
script = ". " + QuoteEscapeString(script);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this continue to work if someone has a debug launch config like this:

"script": "${workspaceFolder}/foo`[bar`].ps1"

I "think" if we single quote a path, we have to evaluate backticks as well and perhaps strip some out. For instance, with the above path the user tried to be helpful and escape the wildcard chars for us (even though unnecessary, this works today). If the backticks are left in, the path will fail with this change - I think. However take a file named foobar.ps1` and it requires the backtick as a literal char.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll give it a test. I don't think we can do both though. Even if it means a breaking change, personally my preference is to enable all paths to work rather than having some paths work two ways and others not work at all.

It feels like we should treat the config setting as a LiteralPath -- it's possible to call a file foo`[bar`].ps1 on the filesystem (I experimented with that while making the fix). In fact on Windows it's not possible to make *.ps1 or ?.ps1 but on *nix it is, and have `?.ps1 right next to them in the directory.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worth noting here too that Set-PSBreakpoint does not have a -LiteralPath parameter -- something which there is a desire to fix I think.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rkeithhill I just tested with two files in the same directory ([script].ps1 and `[script`].ps1) and in 1.9.0, backticks or not both hit the first file.

Unfortunately the same happens in 1.9.1, so looks like I still need to do some work in this PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I think I'm hitting a powershell issue here. I'll look into a workaround and open an issue there

Copy link
Contributor Author

@rjmholt rjmholt Oct 11, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've found something interesting. PowerShell behaves strangely with backtick escaping: PowerShell/PowerShell#7999.

It looks like we've always been receiving the message in the first place with the backtick escaped (or maybe the [ escaped) but not other places:

2018-10-11 11:31:44.987 [DIAGNOSTIC] C:\Users\roholt\Documents\Dev\PowerShellEditorServices\src\PowerShellEditorServices.Protocol\MessageProtocol\MessageReader.cs: In method 'ReadMessage', line 114:
    READ MESSAGE:
    
    {
      "command": "launch",
      "arguments": {
        "type": "PowerShell",
        "request": "launch",
        "name": "Launch Script",
        "script": "C:\\Users\\roholt\\Documents\\Dev\\sandbox/``[script``].ps1",
        "args": [],
        "cwd": "C:\\Users\\roholt\\Documents\\Dev\\sandbox",
        "createTemporaryIntegratedConsole": false,
        "internalConsoleOptions": "neverOpen",
        "__sessionId": "ecbc3316-e3f6-48ea-a3cf-dc6730758edc"
      },
      "type": "request",
      "seq": 2
    }

This miraculously worked because PowerShell escapes ``[ the same as it escapes `[, which I think is a bug.

So I'm currently trying to track down why the message we get doubles the backticks.

/// <param name="path">The path to process.</param>
/// <param name="escapeSpaces">Specify True to escape spaces in the path, otherwise False.</param>
/// <returns>The path with [ and ] escaped.</returns>
internal static string GlobEscapePath(string path, bool escapeSpaces = false)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor nit but does PowerShell really do globbing? Maybe this should be EscapeWildcardsInPath() or something like that?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah good point. Discussing with @JamesWTruher we were calling it "globbing" but the help topic is about_Wildcards. I will rename.

@rjmholt rjmholt changed the title Fix PowerShell path escaping WIP: Fix PowerShell path escaping Oct 11, 2018
@rjmholt
Copy link
Contributor Author

rjmholt commented Oct 11, 2018

Ok, I've finally worked out where the problem lay on our end. I'm going to do a bit more work to make this performant, but this is now working properly.

@rjmholt rjmholt changed the title WIP: Fix PowerShell path escaping Fix PowerShell path escaping Oct 11, 2018
@rjmholt
Copy link
Contributor Author

rjmholt commented Oct 11, 2018

Ok, thought I could jimmy some more StringBuilder usage in there, but I rely on string searching/iteration too much for it to be a good idea.

Gonna ask for a second review here (@tylerl0706, @SeeminglyScience, @rkeithhill), since I've changed some things now.

Copy link
Member

@TylerLeonhardt TylerLeonhardt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a small comment on the string concatenation

launchedScript = script + " " + arguments;
command.AddScript(launchedScript, false);
reportedScript += " " + arguments;
escapedScriptPath += " " + arguments;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you use a StringBuilder or string interpolation here instead?

Same for line 804, 801

@rjmholt rjmholt force-pushed the fix-path-escaping-debug branch from 73d7b6d to 7fab6d8 Compare October 12, 2018 16:27
@rjmholt
Copy link
Contributor Author

rjmholt commented Oct 12, 2018

So it turns out that PowerShell (back to v3 at least) has a System.Management.Automation.WildcardPattern.Escape() method.

But frankly it's broken:

> [WildcardPattern]::Escape('`[test`]')
``[test``]

And it's also subject to a PR or two currently. See PowerShell/PowerShell#7407.

@rjmholt rjmholt merged commit 78d96b8 into PowerShell:master Oct 15, 2018
@rjmholt rjmholt deleted the fix-path-escaping-debug branch December 12, 2018 05:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants