From e5fc193adf88e585db136a2ee556799d1f1354fe Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 19 Sep 2018 10:48:40 -0700 Subject: [PATCH 1/4] Fix issue where # in path causes the path to resolve incorrectly --- .../Workspace/Workspace.cs | 5 ++--- .../Session/WorkspaceTests.cs | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/PowerShellEditorServices/Workspace/Workspace.cs b/src/PowerShellEditorServices/Workspace/Workspace.cs index 7b192cfed..135751140 100644 --- a/src/PowerShellEditorServices/Workspace/Workspace.cs +++ b/src/PowerShellEditorServices/Workspace/Workspace.cs @@ -355,15 +355,14 @@ private void RecursivelyFindReferences( } } - private string ResolveFilePath(string filePath) + internal string ResolveFilePath(string filePath) { if (!IsPathInMemory(filePath)) { if (filePath.StartsWith(@"file://")) { // Client sent the path in URI format, extract the local path - Uri fileUri = new Uri(Uri.UnescapeDataString(filePath)); - filePath = fileUri.LocalPath; + filePath = new Uri(filePath).LocalPath; } // Clients could specify paths with escaped space, [ and ] characters which .NET APIs diff --git a/test/PowerShellEditorServices.Test/Session/WorkspaceTests.cs b/test/PowerShellEditorServices.Test/Session/WorkspaceTests.cs index d40e59511..30b1b9383 100644 --- a/test/PowerShellEditorServices.Test/Session/WorkspaceTests.cs +++ b/test/PowerShellEditorServices.Test/Session/WorkspaceTests.cs @@ -70,5 +70,23 @@ public void CanDetermineIsPathInMemory() $"Testing path {testCase.Path}"); } } + + [Theory()] + [InlineData("file:///C:/banana/", @"C:\banana\")] + [InlineData("file:///C:/banana/ex.ps1", @"C:\banana\ex.ps1")] + [InlineData("file:///E:/Path/to/awful%23path", @"E:\Path\to\awful#path")] + [InlineData("file:///path/with/no/drive", @"C:\path\with\no\drive")] + [InlineData("file:///path/wi[th]/squ[are/brackets/", @"C:\path\wi[th]\squ[are\brackets\")] + [InlineData("file:///Carrots/A%5Ere/Good/", @"C:\Carrots\A^re\Good\")] + [InlineData("file:///Users/barnaby/%E8%84%9A%E6%9C%AC/Reduce-Directory", @"C:\Users\barnaby\脚本\Reduce-Directory")] + [InlineData("file:///C:/Program%20Files%20(x86)/PowerShell/6/pwsh.exe", @"C:\Program Files (x86)\PowerShell\6\pwsh.exe")] + public void CorrectlyResolvesPaths(string givenPath, string expectedPath) + { + Workspace workspace = new Workspace(PowerShellVersion, Logging.NullLogger); + + string resolvedPath = workspace.ResolveFilePath(givenPath); + + Assert.Equal(expectedPath, resolvedPath); + } } } From 6da6524aec54992d1935b047e2bb5d12ebf2e5a1 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 19 Sep 2018 15:22:24 -0700 Subject: [PATCH 2/4] Fix tests, add workaround for escaped drive colon --- .../Workspace/Workspace.cs | 35 +++++++++++++++++++ .../Session/WorkspaceTests.cs | 9 ++--- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/src/PowerShellEditorServices/Workspace/Workspace.cs b/src/PowerShellEditorServices/Workspace/Workspace.cs index 135751140..2fcdff4a9 100644 --- a/src/PowerShellEditorServices/Workspace/Workspace.cs +++ b/src/PowerShellEditorServices/Workspace/Workspace.cs @@ -361,6 +361,7 @@ internal string ResolveFilePath(string filePath) { if (filePath.StartsWith(@"file://")) { + filePath = Workspace.UnescapeDriveColon(filePath); // Client sent the path in URI format, extract the local path filePath = new Uri(filePath).LocalPath; } @@ -485,6 +486,40 @@ private string ResolveRelativeScriptPath(string baseFilePath, string relativePat return combinedPath; } + /// + /// Takes a file-scheme URI with an escaped colon after the drive letter and unescapes only the colon. + /// VSCode sends escaped colons after drive letters, but System.Uri expects unescaped. + /// + /// The fully-escaped file-scheme URI string. + /// A file-scheme URI string with the drive colon unescaped. + private static string UnescapeDriveColon(string fileUri) + { +#if CoreCLR + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return fileUri; + } +#endif + // Check here that we have something like "file:///C%3A/" as a prefix (caller must check the file:// part) + if (!(fileUri[7] == '/' + && char.IsLetter(fileUri[8]) + && fileUri[9] == '%' + && fileUri[10] == '3' + && fileUri[11] == 'A' + && fileUri[12] == '/')) + { + return fileUri; + } + + var sb = new StringBuilder(fileUri.Length - 2); // We lost "%3A" and gained ":", so length - 2 + sb.Append("file:///"); + sb.Append(fileUri[8]); // The drive letter + sb.Append(':'); + sb.Append(fileUri.Substring(12)); // The rest of the URI after the colon + + return sb.ToString(); + } + #endregion } } diff --git a/test/PowerShellEditorServices.Test/Session/WorkspaceTests.cs b/test/PowerShellEditorServices.Test/Session/WorkspaceTests.cs index 30b1b9383..b93d796d0 100644 --- a/test/PowerShellEditorServices.Test/Session/WorkspaceTests.cs +++ b/test/PowerShellEditorServices.Test/Session/WorkspaceTests.cs @@ -72,14 +72,15 @@ public void CanDetermineIsPathInMemory() } [Theory()] - [InlineData("file:///C:/banana/", @"C:\banana\")] - [InlineData("file:///C:/banana/ex.ps1", @"C:\banana\ex.ps1")] - [InlineData("file:///E:/Path/to/awful%23path", @"E:\Path\to\awful#path")] + [InlineData("file:///C%3A/banana/", @"C:\banana\")] + [InlineData("file:///C%3A/banana/ex.ps1", @"C:\banana\ex.ps1")] + [InlineData("file:///E%3A/Path/to/awful%23path", @"E:\Path\to\awful#path")] [InlineData("file:///path/with/no/drive", @"C:\path\with\no\drive")] [InlineData("file:///path/wi[th]/squ[are/brackets/", @"C:\path\wi[th]\squ[are\brackets\")] [InlineData("file:///Carrots/A%5Ere/Good/", @"C:\Carrots\A^re\Good\")] [InlineData("file:///Users/barnaby/%E8%84%9A%E6%9C%AC/Reduce-Directory", @"C:\Users\barnaby\脚本\Reduce-Directory")] - [InlineData("file:///C:/Program%20Files%20(x86)/PowerShell/6/pwsh.exe", @"C:\Program Files (x86)\PowerShell\6\pwsh.exe")] + [InlineData("file:///C%3A/Program%20Files%20%28x86%29/PowerShell/6/pwsh.exe", @"C:\Program Files (x86)\PowerShell\6\pwsh.exe")] + [InlineData("file:///home/maxim/test%20folder/%D0%9F%D0%B0%D0%BF%D0%BA%D0%B0/helloworld.ps1", @"C:\home\maxim\test folder\Папка\helloworld.ps1")] public void CorrectlyResolvesPaths(string givenPath, string expectedPath) { Workspace workspace = new Workspace(PowerShellVersion, Logging.NullLogger); From 4ca8b75afa8cdea937378867f3afef17aee80699 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 19 Sep 2018 15:31:28 -0700 Subject: [PATCH 3/4] Add RuntimeInformation for CoreCLR --- src/PowerShellEditorServices/Workspace/Workspace.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/PowerShellEditorServices/Workspace/Workspace.cs b/src/PowerShellEditorServices/Workspace/Workspace.cs index 2fcdff4a9..0f1bac4c1 100644 --- a/src/PowerShellEditorServices/Workspace/Workspace.cs +++ b/src/PowerShellEditorServices/Workspace/Workspace.cs @@ -11,6 +11,10 @@ using System.Security; using System.Text; +#if CoreCLR +using System.Runtime.InteropServices; +#endif + namespace Microsoft.PowerShell.EditorServices { /// From 3b87e4d1a94c6010801ea7875315d804bbd97751 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 19 Sep 2018 15:53:49 -0700 Subject: [PATCH 4/4] Change multiline if expression style --- src/PowerShellEditorServices/Workspace/Workspace.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/PowerShellEditorServices/Workspace/Workspace.cs b/src/PowerShellEditorServices/Workspace/Workspace.cs index 0f1bac4c1..edbe23d96 100644 --- a/src/PowerShellEditorServices/Workspace/Workspace.cs +++ b/src/PowerShellEditorServices/Workspace/Workspace.cs @@ -505,12 +505,12 @@ private static string UnescapeDriveColon(string fileUri) } #endif // Check here that we have something like "file:///C%3A/" as a prefix (caller must check the file:// part) - if (!(fileUri[7] == '/' - && char.IsLetter(fileUri[8]) - && fileUri[9] == '%' - && fileUri[10] == '3' - && fileUri[11] == 'A' - && fileUri[12] == '/')) + if (!(fileUri[7] == '/' && + char.IsLetter(fileUri[8]) && + fileUri[9] == '%' && + fileUri[10] == '3' && + fileUri[11] == 'A' && + fileUri[12] == '/')) { return fileUri; }