Skip to content

Commit 834ae3d

Browse files
authored
Fix issue where # in path causes the path to resolve incorrectly (#750)
* Fix issue where # in path causes the path to resolve incorrectly * Fix tests, add workaround for escaped drive colon * Add RuntimeInformation for CoreCLR * Change multiline if expression style
1 parent 11f4e9b commit 834ae3d

File tree

2 files changed

+60
-3
lines changed

2 files changed

+60
-3
lines changed

src/PowerShellEditorServices/Workspace/Workspace.cs

+41-3
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111
using System.Security;
1212
using System.Text;
1313

14+
#if CoreCLR
15+
using System.Runtime.InteropServices;
16+
#endif
17+
1418
namespace Microsoft.PowerShell.EditorServices
1519
{
1620
/// <summary>
@@ -355,15 +359,15 @@ private void RecursivelyFindReferences(
355359
}
356360
}
357361

358-
private string ResolveFilePath(string filePath)
362+
internal string ResolveFilePath(string filePath)
359363
{
360364
if (!IsPathInMemory(filePath))
361365
{
362366
if (filePath.StartsWith(@"file://"))
363367
{
368+
filePath = Workspace.UnescapeDriveColon(filePath);
364369
// Client sent the path in URI format, extract the local path
365-
Uri fileUri = new Uri(Uri.UnescapeDataString(filePath));
366-
filePath = fileUri.LocalPath;
370+
filePath = new Uri(filePath).LocalPath;
367371
}
368372

369373
// Clients could specify paths with escaped space, [ and ] characters which .NET APIs
@@ -486,6 +490,40 @@ private string ResolveRelativeScriptPath(string baseFilePath, string relativePat
486490
return combinedPath;
487491
}
488492

493+
/// <summary>
494+
/// Takes a file-scheme URI with an escaped colon after the drive letter and unescapes only the colon.
495+
/// VSCode sends escaped colons after drive letters, but System.Uri expects unescaped.
496+
/// </summary>
497+
/// <param name="fileUri">The fully-escaped file-scheme URI string.</param>
498+
/// <returns>A file-scheme URI string with the drive colon unescaped.</returns>
499+
private static string UnescapeDriveColon(string fileUri)
500+
{
501+
#if CoreCLR
502+
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
503+
{
504+
return fileUri;
505+
}
506+
#endif
507+
// Check here that we have something like "file:///C%3A/" as a prefix (caller must check the file:// part)
508+
if (!(fileUri[7] == '/' &&
509+
char.IsLetter(fileUri[8]) &&
510+
fileUri[9] == '%' &&
511+
fileUri[10] == '3' &&
512+
fileUri[11] == 'A' &&
513+
fileUri[12] == '/'))
514+
{
515+
return fileUri;
516+
}
517+
518+
var sb = new StringBuilder(fileUri.Length - 2); // We lost "%3A" and gained ":", so length - 2
519+
sb.Append("file:///");
520+
sb.Append(fileUri[8]); // The drive letter
521+
sb.Append(':');
522+
sb.Append(fileUri.Substring(12)); // The rest of the URI after the colon
523+
524+
return sb.ToString();
525+
}
526+
489527
#endregion
490528
}
491529
}

test/PowerShellEditorServices.Test/Session/WorkspaceTests.cs

+19
Original file line numberDiff line numberDiff line change
@@ -70,5 +70,24 @@ public void CanDetermineIsPathInMemory()
7070
$"Testing path {testCase.Path}");
7171
}
7272
}
73+
74+
[Theory()]
75+
[InlineData("file:///C%3A/banana/", @"C:\banana\")]
76+
[InlineData("file:///C%3A/banana/ex.ps1", @"C:\banana\ex.ps1")]
77+
[InlineData("file:///E%3A/Path/to/awful%23path", @"E:\Path\to\awful#path")]
78+
[InlineData("file:///path/with/no/drive", @"C:\path\with\no\drive")]
79+
[InlineData("file:///path/wi[th]/squ[are/brackets/", @"C:\path\wi[th]\squ[are\brackets\")]
80+
[InlineData("file:///Carrots/A%5Ere/Good/", @"C:\Carrots\A^re\Good\")]
81+
[InlineData("file:///Users/barnaby/%E8%84%9A%E6%9C%AC/Reduce-Directory", @"C:\Users\barnaby\脚本\Reduce-Directory")]
82+
[InlineData("file:///C%3A/Program%20Files%20%28x86%29/PowerShell/6/pwsh.exe", @"C:\Program Files (x86)\PowerShell\6\pwsh.exe")]
83+
[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")]
84+
public void CorrectlyResolvesPaths(string givenPath, string expectedPath)
85+
{
86+
Workspace workspace = new Workspace(PowerShellVersion, Logging.NullLogger);
87+
88+
string resolvedPath = workspace.ResolveFilePath(givenPath);
89+
90+
Assert.Equal(expectedPath, resolvedPath);
91+
}
7392
}
7493
}

0 commit comments

Comments
 (0)