Skip to content

Commit

Permalink
Showing 2 changed files with 91 additions and 2 deletions.
65 changes: 65 additions & 0 deletions src/SLCore.UnitTests/Common/Models/FileUriTests.cs
Original file line number Diff line number Diff line change
@@ -114,13 +114,67 @@ public void ToString_PercentEncodesBackticks()
new FileUri(@"C:\filewithbacktick`1").ToString().Should().Be("file:///C:/filewithbacktick%601");
}

[TestMethod]
[DataRow("[", "%5B")]
[DataRow("]", "%5D")]
[DataRow("#", "%2523")]
[DataRow("@", "%40")]
public void ToString_PercentEncodesReservedRfc3986Characters(string reservedChar, string expectedEncoding)
{
var actualString = @$"C:\filewithRfc3986ReservedChar{reservedChar}.cs";
var expectedString = @$"file:///C:/filewithRfc3986ReservedChar{expectedEncoding}.cs";

new FileUri(actualString).ToString().Should().Be(expectedString);
}

[TestMethod]
public void LocalPath_ReturnsCorrectPath()
{
var filePath = @"C:\file\path\with some spaces\and with some backticks`1`2`3";
new FileUri(filePath).LocalPath.Should().Be(filePath);
}

[TestMethod]
[DataRow("[", "%5B")]
[DataRow("]", "%5D")]
[DataRow("#", "%2523")]
[DataRow("@", "%40")]
[DataRow(" ", "%20")]
[DataRow("`", "%60")]
public void LocalPath_UnescapesEncodesCharacters(string reservedChar, string expectedEncoding)
{
var expectedFilePath = @$"C:\filewithRfc3986ReservedChar{reservedChar}.cs";
var encodedFilePath = @$"file:///C:/filewithRfc3986ReservedChar{expectedEncoding}.cs";

new FileUri(encodedFilePath).LocalPath.Should().Be(expectedFilePath);
}

[TestMethod]
[DataRow("[")]
[DataRow("]")]
[DataRow("@")]
[DataRow(" ")]
[DataRow("`")]
public void LocalPath_PathDoesNotHaveEncodedChars_ReturnsCorrectLocalPath(string reservedChar)
{
var notEncodedFilePath = @$"file:///C:/filewithRfc3986ReservedChar{reservedChar}.cs";
var expectedFilePath = @$"C:\filewithRfc3986ReservedChar{reservedChar}.cs";

new FileUri(notEncodedFilePath).LocalPath.Should().Be(expectedFilePath);
}

/// <summary>
/// The # character as the beginning of a fragment, so <see cref="Uri.LocalPath"/> will return the path without the fragment.
/// </summary>
[TestMethod]
public void LocalPath_PathWithHashCharacter_ReturnsLocalPathWithoutHash()
{
var notEncodedFilePath = @$"file:///C:/filewithRfc3986ReservedChar#.cs";
var expectedFilePath = @$"C:\filewithRfc3986ReservedChar";

new FileUri(notEncodedFilePath).LocalPath.Should().Be(expectedFilePath);
}

[TestMethod]
public void SerializeDeserializeToEqualObject()
{
@@ -152,4 +206,15 @@ public void Deserialize_ProducesCorrectUri()
fileUri.ToString().Should().Be("file:///C:/file%20with%20%204%20spaces%20and%20a%20back%60tick");
fileUri.LocalPath.Should().Be(@"C:\file with 4 spaces and a back`tick");
}

[TestMethod]
public void Deserialize_ReservedRfc3986Characters_ProducesCorrectUri()
{
var serialized = @"""file:///C:/file%5B%5Dand%2523and%40""";

var fileUri = JsonConvert.DeserializeObject<FileUri>(serialized);

fileUri.ToString().Should().Be("file:///C:/file%5B%5Dand%2523and%40");
fileUri.LocalPath.Should().Be(@"C:\file[]and#and@");
}
}
28 changes: 26 additions & 2 deletions src/SLCore/Common/Models/FileUri.cs
Original file line number Diff line number Diff line change
@@ -20,6 +20,7 @@

using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Text;
using SonarLint.VisualStudio.SLCore.Protocol;

namespace SonarLint.VisualStudio.SLCore.Common.Models;
@@ -28,15 +29,38 @@ namespace SonarLint.VisualStudio.SLCore.Common.Models;
public sealed class FileUri
{
private readonly Uri uri;
private static readonly char[] Rfc3986ReservedCharsToEncode = ['#', '[', ']', '@'];

public FileUri(string uriString)
{
uri = new Uri(uriString);
var unescapedUri = Uri.UnescapeDataString(uriString);
uri = new Uri(unescapedUri);
}

public string LocalPath => uri.LocalPath;

public override string ToString() => Uri.EscapeUriString(uri.ToString());
public override string ToString()
{
var escapedUri = Uri.EscapeUriString(uri.ToString());

return EscapeRfc3986ReservedCharacters(escapedUri);
}

/// <summary>
/// The backend (SlCore) uses java, in which the Uri follows the RFC 3986 protocol.
/// The <see cref="Uri.EscapeUriString"/> does not escape the reserved characters, that's why they are escaped here.
/// See https://learn.microsoft.com/en-us/dotnet/api/system.uri.escapeuristring?view=netframework-4.7.2
/// </summary>
/// <param name="stringToEscape"></param>
/// <returns></returns>
private static string EscapeRfc3986ReservedCharacters(string stringToEscape)
{
var stringBuilderToEscape = new StringBuilder(stringToEscape);

return Rfc3986ReservedCharsToEncode.Aggregate(stringBuilderToEscape,
(current, charToEscape) => current.Replace(charToEscape.ToString(), Uri.HexEscape(charToEscape)))
.ToString();
}

[ExcludeFromCodeCoverage]
public override bool Equals(object obj)

0 comments on commit 3779008

Please sign in to comment.