[Fixes #2841] Support comma separated globbed include and exclude pattern in Script and Link tag helpers
This commit is contained in:
parent
ed4896d1fd
commit
fcad4c5c57
|
|
@ -20,6 +20,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
|
||||||
{
|
{
|
||||||
private static readonly char[] PatternSeparator = new[] { ',' };
|
private static readonly char[] PatternSeparator = new[] { ',' };
|
||||||
|
|
||||||
|
// Valid whitespace characters defined by the HTML5 spec.
|
||||||
|
private static readonly char[] ValidAttributeWhitespaceChars =
|
||||||
|
new[] { '\t', '\n', '\u000C', '\r', ' ' };
|
||||||
|
|
||||||
private static readonly PathComparer DefaultPathComparer = new PathComparer();
|
private static readonly PathComparer DefaultPathComparer = new PathComparer();
|
||||||
|
|
||||||
private readonly FileProviderGlobbingDirectory _baseGlobbingDirectory;
|
private readonly FileProviderGlobbingDirectory _baseGlobbingDirectory;
|
||||||
|
|
@ -125,11 +129,11 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
|
||||||
{
|
{
|
||||||
var matcher = MatcherBuilder != null ? MatcherBuilder() : new Matcher();
|
var matcher = MatcherBuilder != null ? MatcherBuilder() : new Matcher();
|
||||||
|
|
||||||
matcher.AddIncludePatterns(includePatterns.Select(pattern => TrimLeadingSlash(pattern)));
|
matcher.AddIncludePatterns(includePatterns.Select(pattern => TrimLeadingTildeSlash(pattern)));
|
||||||
|
|
||||||
if (excludePatterns != null)
|
if (excludePatterns != null)
|
||||||
{
|
{
|
||||||
matcher.AddExcludePatterns(excludePatterns.Select(pattern => TrimLeadingSlash(pattern)));
|
matcher.AddExcludePatterns(excludePatterns.Select(pattern => TrimLeadingTildeSlash(pattern)));
|
||||||
}
|
}
|
||||||
|
|
||||||
var matches = matcher.Execute(_baseGlobbingDirectory);
|
var matches = matcher.Execute(_baseGlobbingDirectory);
|
||||||
|
|
@ -210,11 +214,15 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string TrimLeadingSlash(string value)
|
private static string TrimLeadingTildeSlash(string value)
|
||||||
{
|
{
|
||||||
var result = value;
|
var result = value.Trim(ValidAttributeWhitespaceChars);
|
||||||
|
|
||||||
if (result.StartsWith("/", StringComparison.Ordinal) ||
|
if (result.StartsWith("~/", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
result = result.Substring(2);
|
||||||
|
}
|
||||||
|
else if (result.StartsWith("/", StringComparison.Ordinal) ||
|
||||||
result.StartsWith("\\", StringComparison.Ordinal))
|
result.StartsWith("\\", StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
// Trim the leading slash as the matcher runs from the provided root only anyway
|
// Trim the leading slash as the matcher runs from the provided root only anyway
|
||||||
|
|
|
||||||
|
|
@ -253,15 +253,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||||
|
|
||||||
if (mode == Mode.GlobbedHref || mode == Mode.Fallback && !string.IsNullOrEmpty(HrefInclude))
|
if (mode == Mode.GlobbedHref || mode == Mode.Fallback && !string.IsNullOrEmpty(HrefInclude))
|
||||||
{
|
{
|
||||||
if (TryResolveUrl(HrefInclude, encodeWebRoot: false, resolvedUrl: out resolvedUrl))
|
|
||||||
{
|
|
||||||
HrefInclude = resolvedUrl;
|
|
||||||
}
|
|
||||||
if (TryResolveUrl(HrefExclude, encodeWebRoot: false, resolvedUrl: out resolvedUrl))
|
|
||||||
{
|
|
||||||
HrefExclude = resolvedUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
BuildGlobbedLinkTags(attributes, builder);
|
BuildGlobbedLinkTags(attributes, builder);
|
||||||
if (string.IsNullOrEmpty(Href))
|
if (string.IsNullOrEmpty(Href))
|
||||||
{
|
{
|
||||||
|
|
@ -277,14 +268,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||||
{
|
{
|
||||||
FallbackHref = resolvedUrl;
|
FallbackHref = resolvedUrl;
|
||||||
}
|
}
|
||||||
if (TryResolveUrl(FallbackHrefInclude, encodeWebRoot: false, resolvedUrl: out resolvedUrl))
|
|
||||||
{
|
|
||||||
FallbackHrefInclude = resolvedUrl;
|
|
||||||
}
|
|
||||||
if (TryResolveUrl(FallbackHrefExclude, encodeWebRoot: false, resolvedUrl: out resolvedUrl))
|
|
||||||
{
|
|
||||||
FallbackHrefExclude = resolvedUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
BuildFallbackBlock(builder);
|
BuildFallbackBlock(builder);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -220,15 +220,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||||
|
|
||||||
if (mode == Mode.GlobbedSrc || mode == Mode.Fallback && !string.IsNullOrEmpty(SrcInclude))
|
if (mode == Mode.GlobbedSrc || mode == Mode.Fallback && !string.IsNullOrEmpty(SrcInclude))
|
||||||
{
|
{
|
||||||
if (TryResolveUrl(SrcInclude, encodeWebRoot: false, resolvedUrl: out resolvedUrl))
|
|
||||||
{
|
|
||||||
SrcInclude = resolvedUrl;
|
|
||||||
}
|
|
||||||
if (TryResolveUrl(SrcExclude, encodeWebRoot: false, resolvedUrl: out resolvedUrl))
|
|
||||||
{
|
|
||||||
SrcExclude = resolvedUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
BuildGlobbedScriptTags(attributes, builder);
|
BuildGlobbedScriptTags(attributes, builder);
|
||||||
if (string.IsNullOrEmpty(Src))
|
if (string.IsNullOrEmpty(Src))
|
||||||
{
|
{
|
||||||
|
|
@ -244,14 +235,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||||
{
|
{
|
||||||
FallbackSrc = resolvedUrl;
|
FallbackSrc = resolvedUrl;
|
||||||
}
|
}
|
||||||
if (TryResolveUrl(FallbackSrcInclude, encodeWebRoot: false, resolvedUrl: out resolvedUrl))
|
|
||||||
{
|
|
||||||
FallbackSrcInclude = resolvedUrl;
|
|
||||||
}
|
|
||||||
if (TryResolveUrl(FallbackSrcExclude, encodeWebRoot: false, resolvedUrl: out resolvedUrl))
|
|
||||||
{
|
|
||||||
FallbackSrcExclude = resolvedUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
BuildFallbackBlock(attributes, builder);
|
BuildFallbackBlock(attributes, builder);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,9 @@
|
||||||
<!-- Globbed link tag with existing file -->
|
<!-- Globbed link tag with existing file -->
|
||||||
<link rel="stylesheet" title="<the title>" href="HtmlEncode[[/site.css]]" />
|
<link rel="stylesheet" title="<the title>" href="HtmlEncode[[/site.css]]" />
|
||||||
|
|
||||||
|
<!-- Globbed link tag with comma separated pattern -->
|
||||||
|
<link rel="stylesheet" title="<the title" href="HtmlEncode[[/site.css]]" /><link rel="stylesheet" title="<the title" href="HtmlEncode[[/sub/site2.css]]" /><link rel="stylesheet" title="<the title" href="HtmlEncode[[/sub/site3.css]]" /><link rel="stylesheet" title="<the title" href="HtmlEncode[[/sub/site3.min.css]]" />
|
||||||
|
|
||||||
<!-- Globbed link tag with existing file and exclude -->
|
<!-- Globbed link tag with existing file and exclude -->
|
||||||
<link rel="stylesheet" title=""the" title" href="HtmlEncode[[/site.css]]" /><link rel="stylesheet" title=""the" title" href="HtmlEncode[[/sub/site2.css]]" />
|
<link rel="stylesheet" title=""the" title" href="HtmlEncode[[/site.css]]" /><link rel="stylesheet" title=""the" title" href="HtmlEncode[[/sub/site2.css]]" />
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,9 @@
|
||||||
<!-- Globbed link tag with existing file -->
|
<!-- Globbed link tag with existing file -->
|
||||||
<link rel="stylesheet" title="<the title>" href="/site.css" />
|
<link rel="stylesheet" title="<the title>" href="/site.css" />
|
||||||
|
|
||||||
|
<!-- Globbed link tag with comma separated pattern -->
|
||||||
|
<link rel="stylesheet" title="<the title" href="/site.css" /><link rel="stylesheet" title="<the title" href="/sub/site2.css" /><link rel="stylesheet" title="<the title" href="/sub/site3.css" /><link rel="stylesheet" title="<the title" href="/sub/site3.min.css" />
|
||||||
|
|
||||||
<!-- Globbed link tag with existing file and exclude -->
|
<!-- Globbed link tag with existing file and exclude -->
|
||||||
<link rel="stylesheet" title=""the" title" href="/site.css" /><link rel="stylesheet" title=""the" title" href="/sub/site2.css" />
|
<link rel="stylesheet" title=""the" title" href="/site.css" /><link rel="stylesheet" title=""the" title" href="/sub/site2.css" />
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,9 @@
|
||||||
// Globbed script tag missing include but with static src
|
// Globbed script tag missing include but with static src
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<!-- Globbed script tag with comma separated include pattern -->
|
||||||
|
<script src="HtmlEncode[[/site.js]]"></script><script src="HtmlEncode[[/sub/site2.js]]"></script><script src="HtmlEncode[[/sub/site3.js]]"></script>
|
||||||
|
|
||||||
<!-- Globbed script tag with missing file -->
|
<!-- Globbed script tag with missing file -->
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,9 @@
|
||||||
// Globbed script tag missing include but with static src
|
// Globbed script tag missing include but with static src
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<!-- Globbed script tag with comma separated include pattern -->
|
||||||
|
<script src="/site.js"></script><script src="/sub/site2.js"></script><script src="/sub/site3.js"></script>
|
||||||
|
|
||||||
<!-- Globbed script tag with missing file -->
|
<!-- Globbed script tag with missing file -->
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
|
||||||
{
|
{
|
||||||
/* staticUrl */ "/site.css",
|
/* staticUrl */ "/site.css",
|
||||||
/* dirStructure */ new FileNode(null, new [] {
|
/* dirStructure */ new FileNode(null, new [] {
|
||||||
|
|
||||||
new FileNode("A", new [] {
|
new FileNode("A", new [] {
|
||||||
new FileNode("c.css"),
|
new FileNode("c.css"),
|
||||||
new FileNode("d.css")
|
new FileNode("d.css")
|
||||||
|
|
@ -299,10 +298,68 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
|
||||||
Mock.Get(cache).VerifyAll();
|
Mock.Get(cache).VerifyAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static TheoryData CommaSeparatedPatternData
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
// Include pattern, expected output
|
||||||
|
return new TheoryData<string, string[]>
|
||||||
|
{
|
||||||
|
{
|
||||||
|
"~/*.css, ~/*.txt",
|
||||||
|
new[] { "/site.css", "/site2.txt" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"*.css, /*.txt",
|
||||||
|
new[] { "/site.css", "/site2.txt" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"\\*.css,~/*.txt",
|
||||||
|
new[] { "/site.css", "/site2.txt" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~/*.js, *.txt",
|
||||||
|
new[] { "/blank.js", "/site.js", "/site2.txt" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
" ~/*.js,*.txt, /*.css",
|
||||||
|
new[] { "/blank.js", "/site.css", "/site.js", "/site2.txt" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~/blank.js, blank.js,/blank.js, \\blank.js",
|
||||||
|
new[] { "/blank.js" }
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
|
[MemberData(nameof(CommaSeparatedPatternData))]
|
||||||
|
public void HandlesCommaSeparatedPatterns(string includePattern, string[] expectedOutput)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var fileProvider = MakeFileProvider(MakeDirectoryContents("site.css", "blank.js", "site2.txt", "site.js"));
|
||||||
|
IMemoryCache cache = null;
|
||||||
|
var requestPathBase = PathString.Empty;
|
||||||
|
var globbingUrlBuilder = new GlobbingUrlBuilder(fileProvider, cache, requestPathBase);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var urlList = globbingUrlBuilder.BuildUrlList(
|
||||||
|
staticUrl: null,
|
||||||
|
includePattern: includePattern,
|
||||||
|
excludePattern: null);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expectedOutput, urlList, StringComparer.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("")]
|
||||||
[InlineData("/")]
|
[InlineData("/")]
|
||||||
[InlineData("\\")]
|
[InlineData(" \\")]
|
||||||
public void TrimsLeadingSlashFromPatterns(string leadingSlash)
|
[InlineData("~/")]
|
||||||
|
[InlineData(" ~/")]
|
||||||
|
public void TrimsLeadingTildeAndSlashFromPatterns(string prefix)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var fileProvider = MakeFileProvider(MakeDirectoryContents("site.css", "blank.css"));
|
var fileProvider = MakeFileProvider(MakeDirectoryContents("site.css", "blank.css"));
|
||||||
|
|
@ -317,8 +374,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
|
||||||
// Act
|
// Act
|
||||||
var urlList = globbingUrlBuilder.BuildUrlList(
|
var urlList = globbingUrlBuilder.BuildUrlList(
|
||||||
staticUrl: null,
|
staticUrl: null,
|
||||||
includePattern: $"{leadingSlash}**/*.css",
|
includePattern: $"{prefix}**/*.css",
|
||||||
excludePattern: $"{leadingSlash}**/*.min.css");
|
excludePattern: $"{prefix}**/*.min.css");
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Collection(includePatterns, pattern => Assert.Equal("**/*.css", pattern));
|
Assert.Collection(includePatterns, pattern => Assert.Equal("**/*.css", pattern));
|
||||||
|
|
@ -326,12 +383,13 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
|
[InlineData("~/")]
|
||||||
[InlineData("/")]
|
[InlineData("/")]
|
||||||
[InlineData("\\")]
|
[InlineData("\\")]
|
||||||
public void TrimsOnlySingleLeadingSlashFromPatterns(string leadingSlash)
|
public void TrimsOnlySingleLeadingSlashOrTildeSlashFromPatterns(string prefix)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var leadingSlashes = $"{leadingSlash}{leadingSlash}";
|
var leadingSlashes = $"{prefix}{prefix}";
|
||||||
var fileProvider = MakeFileProvider(MakeDirectoryContents("site.css", "blank.css"));
|
var fileProvider = MakeFileProvider(MakeDirectoryContents("site.css", "blank.css"));
|
||||||
IMemoryCache cache = null;
|
IMemoryCache cache = null;
|
||||||
var requestPathBase = PathString.Empty;
|
var requestPathBase = PathString.Empty;
|
||||||
|
|
@ -348,8 +406,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
|
||||||
excludePattern: $"{leadingSlashes}**/*.min.css");
|
excludePattern: $"{leadingSlashes}**/*.min.css");
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Collection(includePatterns, pattern => Assert.Equal($"{leadingSlash}**/*.css", pattern));
|
Assert.Collection(includePatterns, pattern => Assert.Equal($"{prefix}**/*.css", pattern));
|
||||||
Assert.Collection(excludePatterns, pattern => Assert.Equal($"{leadingSlash}**/*.min.css", pattern));
|
Assert.Collection(excludePatterns, pattern => Assert.Equal($"{prefix}**/*.min.css", pattern));
|
||||||
}
|
}
|
||||||
|
|
||||||
public class FileNode
|
public class FileNode
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,11 @@
|
||||||
<!-- Globbed link tag with existing file -->
|
<!-- Globbed link tag with existing file -->
|
||||||
<link asp-href-include="**/site.css" rel="stylesheet" title="<the title>" />
|
<link asp-href-include="**/site.css" rel="stylesheet" title="<the title>" />
|
||||||
|
|
||||||
|
<!-- Globbed link tag with comma separated pattern -->
|
||||||
|
<link asp-href-include="~/*.css, ~/sub/*.css" rel="stylesheet" title="<the title" />
|
||||||
|
|
||||||
<!-- Globbed link tag with existing file and exclude -->
|
<!-- Globbed link tag with existing file and exclude -->
|
||||||
<link asp-href-include="**/*.css" asp-href-exclude="**/site3*.css" rel="stylesheet" title='"the" title' />
|
<link asp-href-include=" **/*.css" asp-href-exclude="**/site3*.css" rel="stylesheet" title='"the" title' />
|
||||||
|
|
||||||
<!-- Globbed link tag missing include -->
|
<!-- Globbed link tag missing include -->
|
||||||
<link asp-href-exclude="**/site2.css" rel="stylesheet" />
|
<link asp-href-exclude="**/site2.css" rel="stylesheet" />
|
||||||
|
|
@ -51,7 +54,7 @@
|
||||||
asp-fallback-test-value="hidden" />
|
asp-fallback-test-value="hidden" />
|
||||||
|
|
||||||
<!-- Fallback from globbed href with exclude to static href -->
|
<!-- Fallback from globbed href with exclude to static href -->
|
||||||
<link asp-href-include="**/*.min.css" asp-href-exclude="**/site3.min.css" rel="stylesheet" data-extra="test"
|
<link asp-href-include="**/*.min.css" asp-href-exclude=" **/site3.min.css" rel="stylesheet" data-extra="test"
|
||||||
asp-fallback-href="~/site.css"
|
asp-fallback-href="~/site.css"
|
||||||
asp-fallback-test-class="hidden"
|
asp-fallback-test-class="hidden"
|
||||||
asp-fallback-test-property="visibility"
|
asp-fallback-test-property="visibility"
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,9 @@
|
||||||
// Globbed script tag missing include but with static src
|
// Globbed script tag missing include but with static src
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<!-- Globbed script tag with comma separated include pattern -->
|
||||||
|
<script asp-src-include="~/*.js, ~/sub/*.js"></script>
|
||||||
|
|
||||||
<!-- Globbed script tag with missing file -->
|
<!-- Globbed script tag with missing file -->
|
||||||
<script asp-src-include="**/notThere.js"></script>
|
<script asp-src-include="**/notThere.js"></script>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue