From fcad4c5c57feb3f2c284f5dbc2b41272063b8ea5 Mon Sep 17 00:00:00 2001 From: Ajay Bhargav Baaskaran Date: Tue, 4 Aug 2015 15:52:23 -0700 Subject: [PATCH] [Fixes #2841] Support comma separated globbed include and exclude pattern in Script and Link tag helpers --- .../Internal/GlobbingUrlBuilder.cs | 18 +++-- .../LinkTagHelper.cs | 17 ----- .../ScriptTagHelper.cs | 17 ----- ...Site.HtmlGeneration_Home.Link.Encoded.html | 3 + ...ationWebSite.HtmlGeneration_Home.Link.html | 3 + ...te.HtmlGeneration_Home.Script.Encoded.html | 3 + ...ionWebSite.HtmlGeneration_Home.Script.html | 3 + .../Internal/GlobbingUrlBuilderTest.cs | 76 ++++++++++++++++--- .../Views/HtmlGeneration_Home/Link.cshtml | 7 +- .../Views/HtmlGeneration_Home/Script.cshtml | 3 + 10 files changed, 100 insertions(+), 50 deletions(-) diff --git a/src/Microsoft.AspNet.Mvc.TagHelpers/Internal/GlobbingUrlBuilder.cs b/src/Microsoft.AspNet.Mvc.TagHelpers/Internal/GlobbingUrlBuilder.cs index 7e84e5f2ad..f98e3c631f 100644 --- a/src/Microsoft.AspNet.Mvc.TagHelpers/Internal/GlobbingUrlBuilder.cs +++ b/src/Microsoft.AspNet.Mvc.TagHelpers/Internal/GlobbingUrlBuilder.cs @@ -20,6 +20,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal { 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 readonly FileProviderGlobbingDirectory _baseGlobbingDirectory; @@ -125,11 +129,11 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal { var matcher = MatcherBuilder != null ? MatcherBuilder() : new Matcher(); - matcher.AddIncludePatterns(includePatterns.Select(pattern => TrimLeadingSlash(pattern))); + matcher.AddIncludePatterns(includePatterns.Select(pattern => TrimLeadingTildeSlash(pattern))); if (excludePatterns != null) { - matcher.AddExcludePatterns(excludePatterns.Select(pattern => TrimLeadingSlash(pattern))); + matcher.AddExcludePatterns(excludePatterns.Select(pattern => TrimLeadingTildeSlash(pattern))); } 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)) { // Trim the leading slash as the matcher runs from the provided root only anyway diff --git a/src/Microsoft.AspNet.Mvc.TagHelpers/LinkTagHelper.cs b/src/Microsoft.AspNet.Mvc.TagHelpers/LinkTagHelper.cs index 345905123c..70dcd3f3fb 100644 --- a/src/Microsoft.AspNet.Mvc.TagHelpers/LinkTagHelper.cs +++ b/src/Microsoft.AspNet.Mvc.TagHelpers/LinkTagHelper.cs @@ -253,15 +253,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers 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); if (string.IsNullOrEmpty(Href)) { @@ -277,14 +268,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers { FallbackHref = resolvedUrl; } - if (TryResolveUrl(FallbackHrefInclude, encodeWebRoot: false, resolvedUrl: out resolvedUrl)) - { - FallbackHrefInclude = resolvedUrl; - } - if (TryResolveUrl(FallbackHrefExclude, encodeWebRoot: false, resolvedUrl: out resolvedUrl)) - { - FallbackHrefExclude = resolvedUrl; - } BuildFallbackBlock(builder); } diff --git a/src/Microsoft.AspNet.Mvc.TagHelpers/ScriptTagHelper.cs b/src/Microsoft.AspNet.Mvc.TagHelpers/ScriptTagHelper.cs index 19729ad61f..94ceb57985 100644 --- a/src/Microsoft.AspNet.Mvc.TagHelpers/ScriptTagHelper.cs +++ b/src/Microsoft.AspNet.Mvc.TagHelpers/ScriptTagHelper.cs @@ -220,15 +220,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers 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); if (string.IsNullOrEmpty(Src)) { @@ -244,14 +235,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers { 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); } diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Link.Encoded.html b/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Link.Encoded.html index 49e37c2fe9..0e7bc812e8 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Link.Encoded.html +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Link.Encoded.html @@ -10,6 +10,9 @@ + + + diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Link.html b/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Link.html index 9e419b629d..e42cbc29f4 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Link.html +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Link.html @@ -10,6 +10,9 @@ + + + diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Script.Encoded.html b/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Script.Encoded.html index 071a745a64..686f5cd516 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Script.Encoded.html +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Script.Encoded.html @@ -75,6 +75,9 @@ // Globbed script tag missing include but with static src + + + diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Script.html b/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Script.html index 83e376e90e..bc5d840b07 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Script.html +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Script.html @@ -75,6 +75,9 @@ // Globbed script tag missing include but with static src + + + diff --git a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/Internal/GlobbingUrlBuilderTest.cs b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/Internal/GlobbingUrlBuilderTest.cs index 76abc652f5..8aefed116b 100644 --- a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/Internal/GlobbingUrlBuilderTest.cs +++ b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/Internal/GlobbingUrlBuilderTest.cs @@ -86,7 +86,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal { /* staticUrl */ "/site.css", /* dirStructure */ new FileNode(null, new [] { - new FileNode("A", new [] { new FileNode("c.css"), new FileNode("d.css") @@ -299,10 +298,68 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal Mock.Get(cache).VerifyAll(); } + public static TheoryData CommaSeparatedPatternData + { + get + { + // Include pattern, expected output + return new TheoryData + { + { + "~/*.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] + [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("\\")] - public void TrimsLeadingSlashFromPatterns(string leadingSlash) + [InlineData(" \\")] + [InlineData("~/")] + [InlineData(" ~/")] + public void TrimsLeadingTildeAndSlashFromPatterns(string prefix) { // Arrange var fileProvider = MakeFileProvider(MakeDirectoryContents("site.css", "blank.css")); @@ -317,8 +374,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal // Act var urlList = globbingUrlBuilder.BuildUrlList( staticUrl: null, - includePattern: $"{leadingSlash}**/*.css", - excludePattern: $"{leadingSlash}**/*.min.css"); + includePattern: $"{prefix}**/*.css", + excludePattern: $"{prefix}**/*.min.css"); // Assert Assert.Collection(includePatterns, pattern => Assert.Equal("**/*.css", pattern)); @@ -326,12 +383,13 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal } [Theory] + [InlineData("~/")] [InlineData("/")] [InlineData("\\")] - public void TrimsOnlySingleLeadingSlashFromPatterns(string leadingSlash) + public void TrimsOnlySingleLeadingSlashOrTildeSlashFromPatterns(string prefix) { // Arrange - var leadingSlashes = $"{leadingSlash}{leadingSlash}"; + var leadingSlashes = $"{prefix}{prefix}"; var fileProvider = MakeFileProvider(MakeDirectoryContents("site.css", "blank.css")); IMemoryCache cache = null; var requestPathBase = PathString.Empty; @@ -348,8 +406,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal excludePattern: $"{leadingSlashes}**/*.min.css"); // Assert - Assert.Collection(includePatterns, pattern => Assert.Equal($"{leadingSlash}**/*.css", pattern)); - Assert.Collection(excludePatterns, pattern => Assert.Equal($"{leadingSlash}**/*.min.css", pattern)); + Assert.Collection(includePatterns, pattern => Assert.Equal($"{prefix}**/*.css", pattern)); + Assert.Collection(excludePatterns, pattern => Assert.Equal($"{prefix}**/*.min.css", pattern)); } public class FileNode diff --git a/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Link.cshtml b/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Link.cshtml index e7afe5e044..150027ba5a 100644 --- a/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Link.cshtml +++ b/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Link.cshtml @@ -12,8 +12,11 @@ + + + - + @@ -51,7 +54,7 @@ asp-fallback-test-value="hidden" /> - + + +