Skip to content

Commit

Permalink
SLVS-1436 Escape rfc 3986 reserved chars in file names.
Browse files Browse the repository at this point in the history
  • Loading branch information
gabriela-trutan-sonarsource committed Sep 3, 2024
1 parent c7b2486 commit e792185
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 2 deletions.
24 changes: 24 additions & 0 deletions src/SLCore.UnitTests/Common/Models/FileUriTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,19 @@ 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()
{
Expand Down Expand Up @@ -152,4 +165,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@");
}
}
29 changes: 27 additions & 2 deletions src/SLCore/Common/Models/FileUri.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@

using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using SonarLint.VisualStudio.SLCore.Protocol;

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

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 charsToEscape = Rfc3986ReservedCharsToEncoding.Where(stringToEscape.Contains).ToList();

return !charsToEscape.Any()
? stringToEscape
: charsToEscape.Aggregate(stringToEscape, (current, charToEscape) => current.Replace(charToEscape.ToString(), Uri.HexEscape(charToEscape)));
}

[ExcludeFromCodeCoverage]
public override bool Equals(object obj)
Expand Down

0 comments on commit e792185

Please sign in to comment.