Skip to content

Commit 83d6c56

Browse files
authored
Wrong replication of resource names in embedded provider (#46067)
1 parent da4910a commit 83d6c56

File tree

6 files changed

+189
-10
lines changed

6 files changed

+189
-10
lines changed

src/FileProviders/Embedded/src/EmbeddedFileProvider.cs

+132-10
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System;
55
using System.Collections.Generic;
6+
using System.Globalization;
67
using System.IO;
78
using System.Linq;
89
using System.Reflection;
@@ -87,21 +88,23 @@ public IFileInfo GetFileInfo(string subpath)
8788
// Relative paths starting with a leading slash okay
8889
if (subpath.StartsWith("/", StringComparison.Ordinal))
8990
{
90-
builder.Append(subpath, 1, subpath.Length - 1);
91-
}
92-
else
93-
{
94-
builder.Append(subpath);
91+
subpath = subpath.Substring(1, subpath.Length - 1);
9592
}
9693

97-
for (var i = _baseNamespace.Length; i < builder.Length; i++)
94+
// Make valid everett id from directory name
95+
// The call to this method also replaces directory separator chars to dots
96+
var everettId = MakeValidEverettIdentifier(Path.GetDirectoryName(subpath));
97+
98+
// if directory name was empty, everett id is empty as well
99+
if (!string.IsNullOrEmpty(everettId))
98100
{
99-
if (builder[i] == '/' || builder[i] == '\\')
100-
{
101-
builder[i] = '.';
102-
}
101+
builder.Append(everettId);
102+
builder.Append('.');
103103
}
104104

105+
// Append file name of path
106+
builder.Append(Path.GetFileName(subpath));
107+
105108
var resourcePath = builder.ToString();
106109
if (HasInvalidPathChars(resourcePath))
107110
{
@@ -175,4 +178,123 @@ private static bool HasInvalidPathChars(string path)
175178
{
176179
return path.IndexOfAny(_invalidFileNameChars) != -1;
177180
}
181+
182+
#region Helper methods
183+
184+
/// <summary>
185+
/// Is the character a valid first Everett identifier character?
186+
/// </summary>
187+
private static bool IsValidEverettIdFirstChar(char c)
188+
{
189+
return
190+
char.IsLetter(c) ||
191+
CharUnicodeInfo.GetUnicodeCategory(c) == UnicodeCategory.ConnectorPunctuation;
192+
}
193+
194+
/// <summary>
195+
/// Is the character a valid Everett identifier character?
196+
/// </summary>
197+
private static bool IsValidEverettIdChar(char c)
198+
{
199+
var cat = CharUnicodeInfo.GetUnicodeCategory(c);
200+
201+
return
202+
char.IsLetterOrDigit(c) ||
203+
cat == UnicodeCategory.ConnectorPunctuation ||
204+
cat == UnicodeCategory.NonSpacingMark ||
205+
cat == UnicodeCategory.SpacingCombiningMark ||
206+
cat == UnicodeCategory.EnclosingMark;
207+
}
208+
209+
/// <summary>
210+
/// Make a folder subname into an Everett-compatible identifier
211+
/// </summary>
212+
private static void MakeValidEverettSubFolderIdentifier(StringBuilder builder, string subName)
213+
{
214+
if (string.IsNullOrEmpty(subName)) { return; }
215+
216+
// the first character has stronger restrictions than the rest
217+
if (IsValidEverettIdFirstChar(subName[0]))
218+
{
219+
builder.Append(subName[0]);
220+
}
221+
else
222+
{
223+
builder.Append('_');
224+
if (IsValidEverettIdChar(subName[0]))
225+
{
226+
// if it is a valid subsequent character, prepend an underscore to it
227+
builder.Append(subName[0]);
228+
}
229+
}
230+
231+
// process the rest of the subname
232+
for (var i = 1; i < subName.Length; i++)
233+
{
234+
if (!IsValidEverettIdChar(subName[i]))
235+
{
236+
builder.Append('_');
237+
}
238+
else
239+
{
240+
builder.Append(subName[i]);
241+
}
242+
}
243+
}
244+
245+
/// <summary>
246+
/// Make a folder name into an Everett-compatible identifier
247+
/// </summary>
248+
internal static void MakeValidEverettFolderIdentifier(StringBuilder builder, string name)
249+
{
250+
if (string.IsNullOrEmpty(name)) { return; }
251+
252+
// store the original length for use later
253+
var length = builder.Length;
254+
255+
// split folder name into subnames separated by '.', if any
256+
var subNames = name.Split('.');
257+
258+
// convert each subname separately
259+
MakeValidEverettSubFolderIdentifier(builder, subNames[0]);
260+
261+
for (var i = 1; i < subNames.Length; i++)
262+
{
263+
builder.Append('.');
264+
MakeValidEverettSubFolderIdentifier(builder, subNames[i]);
265+
}
266+
267+
// folder name cannot be a single underscore - add another underscore to it
268+
if ((builder.Length - length) == 1 && builder[length] == '_')
269+
{
270+
builder.Append('_');
271+
}
272+
}
273+
274+
/// <summary>
275+
/// This method is provided for compatibility with Everett which used to convert parts of resource names into
276+
/// valid identifiers
277+
/// </summary>
278+
private static string? MakeValidEverettIdentifier(string? name)
279+
{
280+
if (string.IsNullOrEmpty(name)) { return name; }
281+
282+
var everettId = new StringBuilder(name.Length);
283+
284+
// split the name into folder names
285+
var subNames = name.Split(new[] { '/', '\\' });
286+
287+
// convert every folder name
288+
MakeValidEverettFolderIdentifier(everettId, subNames[0]);
289+
290+
for (var i = 1; i < subNames.Length; i++)
291+
{
292+
everettId.Append('.');
293+
MakeValidEverettFolderIdentifier(everettId, subNames[i]);
294+
}
295+
296+
return everettId.ToString();
297+
}
298+
299+
#endregion
178300
}

src/FileProviders/Embedded/test/EmbeddedFileProviderTests.cs

+38
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,44 @@ public void GetFileInfo_LocatesFilesUnderSubDirectories(string path)
159159
Assert.Equal("File.txt", fileInfo.Name);
160160
}
161161

162+
public static TheoryData GetFileInfo_LocatesFilesUnderSubDirectories_IfDirectoriesContainsInvalidEverettCharData
163+
{
164+
get
165+
{
166+
var theoryData = new TheoryData<string>
167+
{
168+
"sub/sub-dir/File3.txt"
169+
};
170+
171+
if (TestPlatformHelper.IsWindows)
172+
{
173+
theoryData.Add("sub\\sub-dir\\File3.txt");
174+
}
175+
176+
return theoryData;
177+
}
178+
}
179+
180+
[Theory]
181+
[MemberData(nameof(GetFileInfo_LocatesFilesUnderSubDirectories_IfDirectoriesContainsInvalidEverettCharData))]
182+
public void GetFileInfo_LocatesFilesUnderSubDirectories_IfDirectoriesContainsInvalidEverettChar(string path)
183+
{
184+
// Arrange
185+
var provider = new EmbeddedFileProvider(GetType().Assembly);
186+
187+
// Act
188+
var fileInfo = provider.GetFileInfo(path);
189+
190+
// Assert
191+
Assert.NotNull(fileInfo);
192+
Assert.True(fileInfo.Exists);
193+
Assert.NotEqual(default(DateTimeOffset), fileInfo.LastModified);
194+
Assert.True(fileInfo.Length > 0);
195+
Assert.False(fileInfo.IsDirectory);
196+
Assert.Null(fileInfo.PhysicalPath);
197+
Assert.Equal("File3.txt", fileInfo.Name);
198+
}
199+
162200
[Theory]
163201
[InlineData("")]
164202
[InlineData("/")]

src/FileProviders/Embedded/test/Microsoft.Extensions.FileProviders.Embedded.Tests.csproj

+4
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88
<EmbeddedResource Include="File.txt;sub\**\*;Resources\**\*" />
99
</ItemGroup>
1010

11+
<ItemGroup>
12+
<None Remove="sub\sub-dir\File3.txt" />
13+
</ItemGroup>
14+
1115
<ItemGroup>
1216
<Reference Include="Microsoft.Extensions.FileProviders.Abstractions" />
1317
<Reference Include="Microsoft.Extensions.FileProviders.Embedded" />
Binary file not shown.

src/FileProviders/FileProviders.slnf

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"solution": {
3+
"path": "..\\..\\AspNetCore.sln",
4+
"projects": [
5+
"src\\FileProviders\\Embedded\\src\\Microsoft.Extensions.FileProviders.Embedded.csproj",
6+
"src\\FileProviders\\Embedded\\test\\Microsoft.Extensions.FileProviders.Embedded.Tests.csproj",
7+
"src\\FileProviders\\Manifest.MSBuildTask\\src\\Microsoft.Extensions.FileProviders.Embedded.Manifest.Task.csproj",
8+
"src\\FileProviders\\Manifest.MSBuildTask\\test\\Microsoft.Extensions.FileProviders.Embedded.Manifest.Task.Tests.csproj",
9+
"src\\Testing\\src\\Microsoft.AspNetCore.Testing.csproj"
10+
]
11+
}
12+
}

src/FileProviders/startvs.cmd

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
@ECHO OFF
2+
3+
%~dp0..\..\startvs.cmd %~dp0FileProviders.slnf

0 commit comments

Comments
 (0)