Correct HTML and JavaScript encoding of `<link>` and `<script>` attribute values

- #4083
- `<link>` tag helper did not HTML encode `href` values in fallback elements
- `<script>` tag helper did not correctly encode any attribute value in fallback elements
 - e.g. double quotes in literal strings would slip through
- only needed to change one existing unit test (!!); so added a bunch

nit: use `Process()`, not `ProcessAsync()` in `<script>` tag helper tests
This commit is contained in:
Doug Bunting 2016-03-11 20:07:32 -08:00
parent f1fa1bd8f4
commit ffe2d2609a
8 changed files with 390 additions and 83 deletions

View File

@ -4,14 +4,12 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Razor.TagHelpers;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Mvc.TagHelpers.Internal;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.Caching.Memory;
@ -362,15 +360,21 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
firstAdded = true;
}
// fallbackHrefs come from bound attributes and globbing. Must always be non-null.
// fallbackHrefs come from bound attributes (a C# context) and globbing. Must always be non-null.
Debug.Assert(fallbackHrefs[i] != null);
var valueToWrite = fallbackHrefs[i];
if (AppendVersion == true)
{
valueToWrite = _fileVersionProvider.AddFileVersionToPath(fallbackHrefs[i]);
}
builder.AppendHtml(JavaScriptEncoder.Encode(valueToWrite));
// Must HTML-encode the href attribute value to ensure the written <link/> element is valid. Must also
// JavaScript-encode that value to ensure the doc.write() statement is valid.
valueToWrite = HtmlEncoder.Encode(valueToWrite);
valueToWrite = JavaScriptEncoder.Encode(valueToWrite);
builder.AppendHtml(valueToWrite);
builder.AppendHtml("\"");
}
builder.AppendHtml("]);");

View File

@ -3,8 +3,11 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.Razor.TagHelpers;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.Routing;
@ -39,6 +42,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
private const string AppendVersionAttributeName = "asp-append-version";
private static readonly Func<Mode, Mode, int> Compare = (a, b) => a - b;
private FileVersionProvider _fileVersionProvider;
private StringWriter _stringWriter;
private static readonly ModeAttributes<Mode>[] ModeDetails = new[] {
// Regular src with file version alone
@ -174,6 +178,20 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
// Internal for ease of use when testing.
protected internal GlobbingUrlBuilder GlobbingUrlBuilder { get; set; }
// Shared writer for determining the string content of a TagHelperAttribute's Value.
private StringWriter StringWriter
{
get
{
if (_stringWriter == null)
{
_stringWriter = new StringWriter();
}
return _stringWriter;
}
}
/// <inheritdoc />
public override void Process(TagHelperContext context, TagHelperOutput output)
{
@ -300,7 +318,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
if (!attribute.Name.Equals(SrcAttributeName, StringComparison.OrdinalIgnoreCase))
{
var encodedKey = JavaScriptEncoder.Encode(attribute.Name);
var attributeValue = attribute.Value.ToString();
var attributeValue = GetAttributeValue(attribute.Value);
var encodedValue = JavaScriptEncoder.Encode(attributeValue);
AppendAttribute(builder, encodedKey, encodedValue, escapeQuotes: true);
@ -324,6 +342,34 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
}
}
private string GetAttributeValue(object value)
{
string stringValue;
var htmlEncodedString = value as HtmlEncodedString;
if (htmlEncodedString != null)
{
// Value likely came from an HTML context in the .cshtml file but may still contain double quotes
// since attribute could have been enclosed in single quotes.
stringValue = htmlEncodedString.Value;
stringValue = stringValue.Replace("\"", "&quot;");
}
else
{
var writer = StringWriter;
RazorPage.WriteTo(writer, HtmlEncoder, value);
// Value is now correctly HTML-encoded but may still contain double quotes since attribute could
// have been enclosed in single quotes and portions that were HtmlEncodedStrings are not re-encoded.
var builder = writer.GetStringBuilder();
builder.Replace("\"", "&quot;");
stringValue = builder.ToString();
builder.Clear();
}
return stringValue;
}
private void AppendEncodedVersionedSrc(
string srcName,
string srcValue,
@ -337,6 +383,10 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
if (generateForDocumentWrite)
{
// srcValue comes from a C# context and globbing. Must HTML-encode it to ensure the
// written <script/> element is valid. Must also JavaScript-encode that value to ensure
// the document.write() statement is valid.
srcValue = HtmlEncoder.Encode(srcValue);
srcValue = JavaScriptEncoder.Encode(srcValue);
}

View File

@ -39,63 +39,63 @@
<!-- Fallback to static href -->
<link href="HtmlEncode[[/styles/site.min.css?a=b&c=d]]" rel="stylesheet" data-extra="test" title="&quot;the&quot; title" />
<meta name="x-stylesheet-fallback-test" content="" class="HtmlEncode[[hidden]]" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("JavaScriptEncode[[visibility]]","JavaScriptEncode[[hidden]]",["JavaScriptEncode[[/styles/site.css?a=b&c=d]]"]);</script>
<meta name="x-stylesheet-fallback-test" content="" class="HtmlEncode[[hidden]]" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("JavaScriptEncode[[visibility]]","JavaScriptEncode[[hidden]]",["JavaScriptEncode[[HtmlEncode[[/styles/site.css?a=b&c=d]]]]"]);</script>
<!-- Fallback from globbed href to static href -->
<meta name="x-stylesheet-fallback-test" content="" class="HtmlEncode[[hidden]]" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("JavaScriptEncode[[visibility]]","JavaScriptEncode[[hidden]]",["JavaScriptEncode[[/styles/site.css]]"]);</script>
<meta name="x-stylesheet-fallback-test" content="" class="HtmlEncode[[hidden]]" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("JavaScriptEncode[[visibility]]","JavaScriptEncode[[hidden]]",["JavaScriptEncode[[HtmlEncode[[/styles/site.css]]]]"]);</script>
<!-- Fallback from globbed href with exclude to static href -->
<meta name="x-stylesheet-fallback-test" content="" class="HtmlEncode[[hidden]]" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("JavaScriptEncode[[visibility]]","JavaScriptEncode[[hidden]]",["JavaScriptEncode[[/styles/site.css]]"]);</script>
<meta name="x-stylesheet-fallback-test" content="" class="HtmlEncode[[hidden]]" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("JavaScriptEncode[[visibility]]","JavaScriptEncode[[hidden]]",["JavaScriptEncode[[HtmlEncode[[/styles/site.css]]]]"]);</script>
<!-- Fallback from globbed and static href to static href -->
<link href="HtmlEncode[[styles/site.min.css]]" rel="stylesheet" data-extra="test" /><link href="HtmlEncode[[/styles/site.css]]" rel="stylesheet" data-extra="test" />
<meta name="x-stylesheet-fallback-test" content="" class="HtmlEncode[[hidden]]" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("JavaScriptEncode[[visibility]]","JavaScriptEncode[[hidden]]",["JavaScriptEncode[[/styles/site.css]]"]);</script>
<meta name="x-stylesheet-fallback-test" content="" class="HtmlEncode[[hidden]]" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("JavaScriptEncode[[visibility]]","JavaScriptEncode[[hidden]]",["JavaScriptEncode[[HtmlEncode[[/styles/site.css]]]]"]);</script>
<!-- Fallback from globbed and static href with exclude to static href -->
<link href="HtmlEncode[[styles/site.min.css]]" rel="stylesheet" data-extra="test" />
<meta name="x-stylesheet-fallback-test" content="" class="HtmlEncode[[hidden]]" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("JavaScriptEncode[[visibility]]","JavaScriptEncode[[hidden]]",["JavaScriptEncode[[/styles/site.css]]"]);</script>
<meta name="x-stylesheet-fallback-test" content="" class="HtmlEncode[[hidden]]" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("JavaScriptEncode[[visibility]]","JavaScriptEncode[[hidden]]",["JavaScriptEncode[[HtmlEncode[[/styles/site.css]]]]"]);</script>
<!-- Fallback to static href with no primary href -->
<link rel="stylesheet" data-extra="test">
<meta name="x-stylesheet-fallback-test" content="" class="HtmlEncode[[hidden]]" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("JavaScriptEncode[[visibility]]","JavaScriptEncode[[hidden]]",["JavaScriptEncode[[/styles/site.css]]"]);</script>
<meta name="x-stylesheet-fallback-test" content="" class="HtmlEncode[[hidden]]" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("JavaScriptEncode[[visibility]]","JavaScriptEncode[[hidden]]",["JavaScriptEncode[[HtmlEncode[[/styles/site.css]]]]"]);</script>
<!-- Fallback to globbed href -->
<link href="HtmlEncode[[/styles/site.min.css]]" rel="stylesheet" data-extra="test" />
<meta name="x-stylesheet-fallback-test" content="" class="HtmlEncode[[hidden]]" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("JavaScriptEncode[[visibility]]","JavaScriptEncode[[hidden]]",["JavaScriptEncode[[/styles/site.css]]"]);</script>
<meta name="x-stylesheet-fallback-test" content="" class="HtmlEncode[[hidden]]" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("JavaScriptEncode[[visibility]]","JavaScriptEncode[[hidden]]",["JavaScriptEncode[[HtmlEncode[[/styles/site.css]]]]"]);</script>
<!-- Fallback to static and globbed href -->
<link href="HtmlEncode[[/styles/site.min.css]]" rel="stylesheet" data-extra="test" />
<meta name="x-stylesheet-fallback-test" content="" class="HtmlEncode[[hidden]]" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("JavaScriptEncode[[visibility]]","JavaScriptEncode[[hidden]]",["JavaScriptEncode[[/styles/site.css]]","JavaScriptEncode[[/styles/sub/site2.css]]"]);</script>
<meta name="x-stylesheet-fallback-test" content="" class="HtmlEncode[[hidden]]" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("JavaScriptEncode[[visibility]]","JavaScriptEncode[[hidden]]",["JavaScriptEncode[[HtmlEncode[[/styles/site.css]]]]","JavaScriptEncode[[HtmlEncode[[/styles/sub/site2.css]]]]"]);</script>
<!-- Fallback to static and globbed href should dedupe -->
<link href="HtmlEncode[[/styles/site.min.css]]" rel="stylesheet" data-extra="test" />
<meta name="x-stylesheet-fallback-test" content="" class="HtmlEncode[[hidden]]" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("JavaScriptEncode[[visibility]]","JavaScriptEncode[[hidden]]",["JavaScriptEncode[[/styles/site.css]]"]);</script>
<meta name="x-stylesheet-fallback-test" content="" class="HtmlEncode[[hidden]]" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("JavaScriptEncode[[visibility]]","JavaScriptEncode[[hidden]]",["JavaScriptEncode[[HtmlEncode[[/styles/site.css]]]]"]);</script>
<!-- Fallback to static and globbed href with exclude -->
<link href="HtmlEncode[[/styles/site.min.css]]" rel="stylesheet" data-extra="test" />
<meta name="x-stylesheet-fallback-test" content="" class="HtmlEncode[[hidden]]" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("JavaScriptEncode[[visibility]]","JavaScriptEncode[[hidden]]",["JavaScriptEncode[[/styles/site.css]]","JavaScriptEncode[[/styles/sub/site2.css]]"]);</script>
<meta name="x-stylesheet-fallback-test" content="" class="HtmlEncode[[hidden]]" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("JavaScriptEncode[[visibility]]","JavaScriptEncode[[hidden]]",["JavaScriptEncode[[HtmlEncode[[/styles/site.css]]]]","JavaScriptEncode[[HtmlEncode[[/styles/sub/site2.css]]]]"]);</script>
<!-- Fallback from globbed href to glbobed href -->
<meta name="x-stylesheet-fallback-test" content="" class="HtmlEncode[[hidden]]" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("JavaScriptEncode[[visibility]]","JavaScriptEncode[[hidden]]",["JavaScriptEncode[[/styles/site.css]]"]);</script>
<meta name="x-stylesheet-fallback-test" content="" class="HtmlEncode[[hidden]]" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("JavaScriptEncode[[visibility]]","JavaScriptEncode[[hidden]]",["JavaScriptEncode[[HtmlEncode[[/styles/site.css]]]]"]);</script>
<!-- Fallback from globbed href with exclude to globbed href -->
<meta name="x-stylesheet-fallback-test" content="" class="HtmlEncode[[hidden]]" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("JavaScriptEncode[[visibility]]","JavaScriptEncode[[hidden]]",["JavaScriptEncode[[/styles/site.css]]"]);</script>
<meta name="x-stylesheet-fallback-test" content="" class="HtmlEncode[[hidden]]" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("JavaScriptEncode[[visibility]]","JavaScriptEncode[[hidden]]",["JavaScriptEncode[[HtmlEncode[[/styles/site.css]]]]"]);</script>
<!-- Fallback from globbed and static href to globbed href -->
<link href="HtmlEncode[[styles/site.min.css]]" rel="stylesheet" data-extra="test" /><link href="HtmlEncode[[/styles/site.css]]" rel="stylesheet" data-extra="test" />
<meta name="x-stylesheet-fallback-test" content="" class="HtmlEncode[[hidden]]" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("JavaScriptEncode[[visibility]]","JavaScriptEncode[[hidden]]",["JavaScriptEncode[[/styles/site.css]]"]);</script>
<meta name="x-stylesheet-fallback-test" content="" class="HtmlEncode[[hidden]]" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("JavaScriptEncode[[visibility]]","JavaScriptEncode[[hidden]]",["JavaScriptEncode[[HtmlEncode[[/styles/site.css]]]]"]);</script>
<!-- Fallback from globbed and static href with exclude to globbed href -->
<link href="HtmlEncode[[styles/site.min.css]]" rel="stylesheet" data-extra="test">
<meta name="x-stylesheet-fallback-test" content="" class="HtmlEncode[[hidden]]" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("JavaScriptEncode[[visibility]]","JavaScriptEncode[[hidden]]",["JavaScriptEncode[[/styles/site.css]]"]);</script>
<meta name="x-stylesheet-fallback-test" content="" class="HtmlEncode[[hidden]]" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("JavaScriptEncode[[visibility]]","JavaScriptEncode[[hidden]]",["JavaScriptEncode[[HtmlEncode[[/styles/site.css]]]]"]);</script>
<!-- Kitchen sink, all the attributes -->
<link href="HtmlEncode[[styles/site.min.css]]" rel="stylesheet" data-extra="test" />
<meta name="x-stylesheet-fallback-test" content="" class="HtmlEncode[[hidden]]" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("JavaScriptEncode[[visibility]]","JavaScriptEncode[[hidden]]",["JavaScriptEncode[[/styles/site.css]]","JavaScriptEncode[[/styles/sub/site2.css]]"]);</script>
<meta name="x-stylesheet-fallback-test" content="" class="HtmlEncode[[hidden]]" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("JavaScriptEncode[[visibility]]","JavaScriptEncode[[hidden]]",["JavaScriptEncode[[HtmlEncode[[/styles/site.css]]]]","JavaScriptEncode[[HtmlEncode[[/styles/sub/site2.css]]]]"]);</script>
<!-- Fallback to globbed href that doesn't exist -->
<link href="HtmlEncode[[/styles/site.min.css]]" rel="stylesheet" data-extra="test" />
@ -120,7 +120,7 @@
<!-- Fallback with file version -->
<link href="HtmlEncode[[/styles/site.min.css]]" rel="stylesheet" data-extra="test">
<meta name="x-stylesheet-fallback-test" content="" class="HtmlEncode[[hidden]]" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("JavaScriptEncode[[visibility]]","JavaScriptEncode[[hidden]]",["JavaScriptEncode[[/styles/site.css?v=XY7YsMemPf8AGU4SIX9ED9eOjK1LOQWu2dmCNmh-pQc]]"]);</script>
<meta name="x-stylesheet-fallback-test" content="" class="HtmlEncode[[hidden]]" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("JavaScriptEncode[[visibility]]","JavaScriptEncode[[hidden]]",["JavaScriptEncode[[HtmlEncode[[/styles/site.css?v=XY7YsMemPf8AGU4SIX9ED9eOjK1LOQWu2dmCNmh-pQc]]]]"]);</script>
<!-- Globbed link tag with existing file, static href and file version -->
<link href="HtmlEncode[[/styles/site.css?v=XY7YsMemPf8AGU4SIX9ED9eOjK1LOQWu2dmCNmh-pQc]]" rel="stylesheet" /><link href="HtmlEncode[[/styles/sub/site2.css?v=30cxPex0tA9xEatW7f1Qhnn8tVLAHgE6xwIZhESq0y0]]" rel="stylesheet" /><link href="HtmlEncode[[/styles/sub/site3.css?v=fSxxOr1Q4Dq2uPuzlju5UYGuK0SKABI-ghvaIGEsZDc]]" rel="stylesheet" /><link href="HtmlEncode[[/styles/sub/site3.min.css?v=s8JMmAZxBn0dzuhRtQ0wgOvNBK4XRJRWEC2wfzsVF9M]]" rel="stylesheet" />

View File

@ -39,7 +39,7 @@
<!-- Fallback to static href -->
<link href="/styles/site.min.css?a=b&amp;c=d" rel="stylesheet" data-extra="test" title="&quot;the&quot; title" />
<meta name="x-stylesheet-fallback-test" content="" class="hidden" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("visibility","hidden",["\/styles\/site.css?a=b\u0026c=d"]);</script>
<meta name="x-stylesheet-fallback-test" content="" class="hidden" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("visibility","hidden",["\/styles\/site.css?a=b\u0026amp;c=d"]);</script>
<!-- Fallback from globbed href to static href -->

View File

@ -1,4 +1,4 @@
<!doctype html>
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
@ -13,27 +13,27 @@
<script src="HtmlEncode[[/blank.js?a=b&c=d]]" data-foo="foo-data2" title="&lt;the title>">
// TagHelper script with comment in body, and extra properties.
</script>
<script>(false||document.write("<script src=\"JavaScriptEncode[[/styles/site.js?a=b&c=d]]\" JavaScriptEncode[[data-foo]]=\"JavaScriptEncode[[foo-data2]]\" JavaScriptEncode[[title]]=\"JavaScriptEncode[[&lt;the title>]]\"><\/script>"));</script>
<script>(false||document.write("<script src=\"JavaScriptEncode[[HtmlEncode[[/styles/site.js?a=b&c=d]]]]\" JavaScriptEncode[[data-foo]]=\"JavaScriptEncode[[foo-data2]]\" JavaScriptEncode[[title]]=\"JavaScriptEncode[[&lt;the title>]]\"><\/script>"));</script>
<script src="HtmlEncode[[/blank.js]]" title="&quot;the&quot; title">
// Fallback to globbed src
</script>
<script>(false||document.write("<script src=\"JavaScriptEncode[[/styles/site.js]]\" JavaScriptEncode[[title]]=\"JavaScriptEncode[["the" title]]\"><\/script>"));</script>
<script>(false||document.write("<script src=\"JavaScriptEncode[[HtmlEncode[[/styles/site.js]]]]\" JavaScriptEncode[[title]]=\"JavaScriptEncode[[&quot;the&quot; title]]\"><\/script>"));</script>
<script src="HtmlEncode[[/blank.js]]">
// Fallback to globbed src with exclude
</script>
<script>(false||document.write("<script src=\"JavaScriptEncode[[/styles/site.js]]\"><\/script><script src=\"JavaScriptEncode[[/styles/sub/site2.js]]\"><\/script>"));</script>
<script>(false||document.write("<script src=\"JavaScriptEncode[[HtmlEncode[[/styles/site.js]]]]\"><\/script><script src=\"JavaScriptEncode[[HtmlEncode[[/styles/sub/site2.js]]]]\"><\/script>"));</script>
<script src="HtmlEncode[[/blank.js]]">
// Fallback to globbed and static src
</script>
<script>(false||document.write("<script src=\"JavaScriptEncode[[/styles/site.js]]\"><\/script><script src=\"JavaScriptEncode[[/styles/sub/site2.js]]\"><\/script>"));</script>
<script>(false||document.write("<script src=\"JavaScriptEncode[[HtmlEncode[[/styles/site.js]]]]\"><\/script><script src=\"JavaScriptEncode[[HtmlEncode[[/styles/sub/site2.js]]]]\"><\/script>"));</script>
<script src="HtmlEncode[[/blank.js]]">
// Fallback to globbed and static src should de-dupe
</script>
<script>(false||document.write("<script src=\"JavaScriptEncode[[/styles/site.js]]\"><\/script>"));</script>
<script>(false||document.write("<script src=\"JavaScriptEncode[[HtmlEncode[[/styles/site.js]]]]\"><\/script>"));</script>
<script src="HtmlEncode[[/blank.js]]">
// Fallback to globbed src with missing include
@ -42,7 +42,7 @@
<script src="HtmlEncode[[/blank.js]]">
// Fallback to static and globbed src with missing include
</script>
<script>(false||document.write("<script src=\"JavaScriptEncode[[/styles/site.js]]\"><\/script>"));</script>
<script>(false||document.write("<script src=\"JavaScriptEncode[[HtmlEncode[[/styles/site.js]]]]\"><\/script>"));</script>
<script src="HtmlEncode[[/blank.js]]">
// Fallback to globbed src outside of webroot
@ -55,7 +55,7 @@
<script data-foo="foo-data3">
// Valid TagHelper (although no src is provided) script with comment in body, and extra properties.
</script>
<script>(false||document.write("<script JavaScriptEncode[[data-foo]]=\"JavaScriptEncode[[foo-data3]]\" src=\"JavaScriptEncode[[/styles/site.js]]\"><\/script>"));</script>
<script>(false||document.write("<script JavaScriptEncode[[data-foo]]=\"JavaScriptEncode[[foo-data3]]\" src=\"JavaScriptEncode[[HtmlEncode[[/styles/site.js]]]]\"><\/script>"));</script>
<script src="HtmlEncode[[/blank.js]]">
// Invalid TagHelper script with comment in body.
@ -98,12 +98,12 @@
<script src="HtmlEncode[[/blank.js]]">
// TagHelper script with comment in body, and file version.
</script>
<script>(false||document.write("<script src=\"JavaScriptEncode[[/styles/site.js?v=jx1PJjLX32-xgQQx2BxnckU9QH9DVKkm4-M5bSK869I]]\"><\/script>"));</script>
<script>(false||document.write("<script src=\"JavaScriptEncode[[HtmlEncode[[/styles/site.js?v=jx1PJjLX32-xgQQx2BxnckU9QH9DVKkm4-M5bSK869I]]]]\"><\/script>"));</script>
<script src="HtmlEncode[[/blank.js]]">
// Fallback to globbed src with file version.
</script>
<script>(false||document.write("<script src=\"JavaScriptEncode[[/styles/site.js?v=jx1PJjLX32-xgQQx2BxnckU9QH9DVKkm4-M5bSK869I]]\"><\/script>"));</script>
<script>(false||document.write("<script src=\"JavaScriptEncode[[HtmlEncode[[/styles/site.js?v=jx1PJjLX32-xgQQx2BxnckU9QH9DVKkm4-M5bSK869I]]]]\"><\/script>"));</script>
<script src="HtmlEncode[[/styles/site.js?v=jx1PJjLX32-xgQQx2BxnckU9QH9DVKkm4-M5bSK869I]]">
// Regular script with comment in body, and file version.

View File

@ -13,12 +13,12 @@
<script src="/blank.js?a=b&amp;c=d" data-foo="foo-data2" title="&lt;the title>">
// TagHelper script with comment in body, and extra properties.
</script>
<script>(false||document.write("<script src=\"\/styles\/site.js?a=b\u0026c=d\" data-foo=\"foo-data2\" title=\"\u0026lt;the title\u003E\"><\/script>"));</script>
<script>(false||document.write("<script src=\"\/styles\/site.js?a=b\u0026amp;c=d\" data-foo=\"foo-data2\" title=\"\u0026lt;the title\u003E\"><\/script>"));</script>
<script src="/blank.js" title="&quot;the&quot; title">
// Fallback to globbed src
</script>
<script>(false||document.write("<script src=\"\/styles\/site.js\" title=\"\u0022the\u0022 title\"><\/script>"));</script>
<script>(false||document.write("<script src=\"\/styles\/site.js\" title=\"\u0026quot;the\u0026quot; title\"><\/script>"));</script>
<script src="/blank.js">
// Fallback to globbed src with exclude

View File

@ -16,6 +16,7 @@ using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Mvc.TagHelpers.Internal;
using Microsoft.AspNetCore.Mvc.TestCommon;
using Microsoft.AspNetCore.Mvc.ViewEngines;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
@ -403,6 +404,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
// Assert
Assert.Null(output.TagName);
Assert.True(output.IsContentModified);
Assert.True(output.Content.IsEmpty);
Assert.True(output.PostElement.IsModified);
}
@ -595,6 +597,8 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
public void RendersLinkTagsForGlobbedHrefResults()
{
// Arrange
var expectedContent = "<link rel=\"stylesheet\" href=\"HtmlEncode[[/css/site.css]]\" />" +
"<link rel=\"stylesheet\" href=\"HtmlEncode[[/base.css]]\" />";
var context = MakeTagHelperContext(
attributes: new TagHelperAttributeList
{
@ -634,24 +638,41 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
// Assert
Assert.Equal("link", output.TagName);
Assert.Equal("/css/site.css", output.Attributes["href"].Value);
Assert.Equal("<link rel=\"stylesheet\" href=\"HtmlEncode[[/base.css]]\" />", output.PostElement.GetContent());
var content = HtmlContentUtilities.HtmlContentToString(output, new HtmlTestEncoder());
Assert.Equal(expectedContent, content);
}
[Fact]
public void RendersLinkTagsForGlobbedHrefResults_UsingProvidedEncoder()
public void RendersLinkTagsForGlobbedHrefResults_EncodesAsExpected()
{
// Arrange
var expectedContent =
"<link encoded=\"contains &quot;quotes&quot;\" href=\"HtmlEncode[[/css/site.css]]\" " +
"literal=\"HtmlEncode[[all HTML encoded]]\" " +
"mixed=\"HtmlEncode[[HTML encoded]] and contains &quot;quotes&quot;\" />" +
"<link encoded=\"contains &quot;quotes&quot;\" href=\"HtmlEncode[[/base.css]]\" " +
"literal=\"HtmlEncode[[all HTML encoded]]\" " +
"mixed=\"HtmlEncode[[HTML encoded]] and contains &quot;quotes&quot;\" />";
var mixed = new DefaultTagHelperContent();
mixed.Append("HTML encoded");
mixed.AppendHtml(" and contains \"quotes\"");
var context = MakeTagHelperContext(
attributes: new TagHelperAttributeList
{
new TagHelperAttribute("rel", "stylesheet"),
new TagHelperAttribute("href", "/css/site.css"),
new TagHelperAttribute("asp-href-include", "**/*.css")
{ "asp-href-include", "**/*.css" },
{ "encoded", new HtmlString("contains \"quotes\"") },
{ "href", "/css/site.css" },
{ "literal", "all HTML encoded" },
{ "mixed", mixed },
});
var output = MakeTagHelperOutput(
"link",
attributes: new TagHelperAttributeList
{
{ "encoded", new HtmlString("contains \"quotes\"") },
{ "literal", "all HTML encoded" },
{ "mixed", mixed },
});
var output = MakeTagHelperOutput("link", attributes: new TagHelperAttributeList
{
new TagHelperAttribute("rel", "stylesheet"),
});
var hostingEnvironment = MakeHostingEnvironment();
var viewContext = MakeViewContext();
var globbingUrlBuilder = new Mock<GlobbingUrlBuilder>(
@ -669,9 +690,9 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
MakeUrlHelperFactory())
{
GlobbingUrlBuilder = globbingUrlBuilder.Object,
ViewContext = viewContext,
Href = "/css/site.css",
HrefInclude = "**/*.css",
ViewContext = viewContext,
};
// Act
@ -680,12 +701,12 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
// Assert
Assert.Equal("link", output.TagName);
Assert.Equal("/css/site.css", output.Attributes["href"].Value);
Assert.Equal("<link rel=\"HtmlEncode[[stylesheet]]\" href=\"HtmlEncode[[/base.css]]\" />",
output.PostElement.GetContent());
var content = HtmlContentUtilities.HtmlContentToString(output, new HtmlTestEncoder());
Assert.Equal(expectedContent, content);
}
[Fact]
public void RendersLinkTags_AddsFileVersion()
public void RendersLinkTags_WithFileVersion()
{
// Arrange
var context = MakeTagHelperContext(
@ -723,7 +744,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
}
[Fact]
public void RendersLinkTags_AddsFileVersion_WithRequestPathBase()
public void RendersLinkTags_WithFileVersion_AndRequestPathBase()
{
// Arrange
var context = MakeTagHelperContext(
@ -761,7 +782,143 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
}
[Fact]
public void RendersLinkTags_GlobbedHref_AddsFileVersion()
public void RenderLinkTags_FallbackHref_WithFileVersion()
{
// Arrange
var expectedPostElement = Environment.NewLine +
"<meta name=\"x-stylesheet-fallback-test\" content=\"\" class=\"hidden\" />" +
"<script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName(\"SCRIPT\")," +
"g=f[f.length-1].previousElementSibling," +
"h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;" +
"if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel=\"stylesheet\" href=\"'+c[d]+'\"/>')}(" +
"\"JavaScriptEncode[[visibility]]\",\"JavaScriptEncode[[hidden]]\"," +
"[\"JavaScriptEncode[[HtmlEncode[[/fallback.css?v=f4OxZX_x_FO5LcGBSKHWXfwtSx-j1ncoSt3SABJtkGk]]]]\"]);" +
"</script>";
var context = MakeTagHelperContext(
attributes: new TagHelperAttributeList
{
{ "asp-append-version", "true" },
{ "asp-fallback-href-include", "**/fallback.css" },
{ "asp-fallback-test-class", "hidden" },
{ "asp-fallback-test-property", "visibility" },
{ "asp-fallback-test-value", "hidden" },
{ "href", "/css/site.css" },
});
var output = MakeTagHelperOutput("link", attributes: new TagHelperAttributeList());
var hostingEnvironment = MakeHostingEnvironment();
var viewContext = MakeViewContext();
var globbingUrlBuilder = new Mock<GlobbingUrlBuilder>(
new TestFileProvider(),
Mock.Of<IMemoryCache>(),
PathString.Empty);
globbingUrlBuilder.Setup(g => g.BuildUrlList(null, "**/fallback.css", null))
.Returns(new[] { "/fallback.css" });
var helper = new LinkTagHelper(
MakeHostingEnvironment(),
MakeCache(),
new HtmlTestEncoder(),
new JavaScriptTestEncoder(),
MakeUrlHelperFactory())
{
AppendVersion = true,
Href = "/css/site.css",
FallbackHrefInclude = "**/fallback.css",
FallbackTestClass = "hidden",
FallbackTestProperty = "visibility",
FallbackTestValue = "hidden",
GlobbingUrlBuilder = globbingUrlBuilder.Object,
ViewContext = viewContext,
};
// Act
helper.Process(context, output);
// Assert
Assert.Equal("link", output.TagName);
Assert.Equal("/css/site.css?v=f4OxZX_x_FO5LcGBSKHWXfwtSx-j1ncoSt3SABJtkGk", output.Attributes["href"].Value);
Assert.Equal(expectedPostElement, output.PostElement.GetContent());
}
[Fact]
public void RenderLinkTags_FallbackHref_WithFileVersion_EncodesAsExpected()
{
// Arrange
var expectedContent = "<link encoded=\"contains &quot;quotes&quot;\" " +
"href=\"HtmlEncode[[/css/site.css?v=f4OxZX_x_FO5LcGBSKHWXfwtSx-j1ncoSt3SABJtkGk]]\" " +
"literal=\"HtmlEncode[[all HTML encoded]]\" " +
"mixed=\"HtmlEncode[[HTML encoded]] and contains &quot;quotes&quot;\" />" +
Environment.NewLine +
"<meta name=\"x-stylesheet-fallback-test\" content=\"\" class=\"HtmlEncode[[hidden]]\" />" +
"<script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName(\"SCRIPT\")," +
"g=f[f.length-1].previousElementSibling," +
"h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;" +
"if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel=\"stylesheet\" href=\"'+c[d]+'\"/>')}(" +
"\"JavaScriptEncode[[visibility]]\",\"JavaScriptEncode[[hidden]]\"," +
"[\"JavaScriptEncode[[HtmlEncode[[/fallback.css?v=f4OxZX_x_FO5LcGBSKHWXfwtSx-j1ncoSt3SABJtkGk]]]]\"]);" +
"</script>";
var mixed = new DefaultTagHelperContent();
mixed.Append("HTML encoded");
mixed.AppendHtml(" and contains \"quotes\"");
var context = MakeTagHelperContext(
attributes: new TagHelperAttributeList
{
{ "asp-append-version", "true" },
{ "asp-fallback-href-include", "**/fallback.css" },
{ "asp-fallback-test-class", "hidden" },
{ "asp-fallback-test-property", "visibility" },
{ "asp-fallback-test-value", "hidden" },
{ "encoded", new HtmlString("contains \"quotes\"") },
{ "href", "/css/site.css" },
{ "literal", "all HTML encoded" },
{ "mixed", mixed },
});
var output = MakeTagHelperOutput(
"link",
attributes: new TagHelperAttributeList
{
{ "encoded", new HtmlString("contains \"quotes\"") },
{ "literal", "all HTML encoded" },
{ "mixed", mixed },
});
var hostingEnvironment = MakeHostingEnvironment();
var viewContext = MakeViewContext();
var globbingUrlBuilder = new Mock<GlobbingUrlBuilder>(
new TestFileProvider(),
Mock.Of<IMemoryCache>(),
PathString.Empty);
globbingUrlBuilder.Setup(g => g.BuildUrlList(null, "**/fallback.css", null))
.Returns(new[] { "/fallback.css" });
var helper = new LinkTagHelper(
MakeHostingEnvironment(),
MakeCache(),
new HtmlTestEncoder(),
new JavaScriptTestEncoder(),
MakeUrlHelperFactory())
{
AppendVersion = true,
FallbackHrefInclude = "**/fallback.css",
FallbackTestClass = "hidden",
FallbackTestProperty = "visibility",
FallbackTestValue = "hidden",
GlobbingUrlBuilder = globbingUrlBuilder.Object,
Href = "/css/site.css",
ViewContext = viewContext,
};
// Act
helper.Process(context, output);
// Assert
Assert.Equal("link", output.TagName);
Assert.Equal("/css/site.css?v=f4OxZX_x_FO5LcGBSKHWXfwtSx-j1ncoSt3SABJtkGk", output.Attributes["href"].Value);
var content = HtmlContentUtilities.HtmlContentToString(output, new HtmlTestEncoder());
Assert.Equal(expectedContent, content);
}
[Fact]
public void RendersLinkTags_GlobbedHref_WithFileVersion()
{
// Arrange
var context = MakeTagHelperContext(
@ -848,7 +1005,10 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
tagName,
attributes,
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(
new DefaultTagHelperContent()));
new DefaultTagHelperContent()))
{
TagMode = TagMode.SelfClosing,
};
}
private static IHostingEnvironment MakeHostingEnvironment()

View File

@ -16,6 +16,7 @@ using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Mvc.TagHelpers.Internal;
using Microsoft.AspNetCore.Mvc.TestCommon;
using Microsoft.AspNetCore.Mvc.ViewEngines;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
@ -95,7 +96,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
[Theory]
[MemberData(nameof(LinkTagHelperTest.MultiAttributeSameNameData), MemberType = typeof(LinkTagHelperTest))]
public async Task HandlesMultipleAttributesSameNameCorrectly(TagHelperAttributeList outputAttributes)
public void HandlesMultipleAttributesSameNameCorrectly(TagHelperAttributeList outputAttributes)
{
// Arrange
var allAttributes = new TagHelperAttributeList(
@ -134,7 +135,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
expectedAttributes.Add(new TagHelperAttribute("src", "/blank.js"));
// Act
await helper.ProcessAsync(tagHelperContext, output);
helper.Process(tagHelperContext, output);
// Assert
Assert.Equal(expectedAttributes, output.Attributes);
@ -265,7 +266,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
[Theory]
[MemberData(nameof(RunsWhenRequiredAttributesArePresent_Data))]
public async Task RunsWhenRequiredAttributesArePresent(
public void RunsWhenRequiredAttributesArePresent(
TagHelperAttributeList attributes,
Action<ScriptTagHelper> setProperties)
{
@ -294,7 +295,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
setProperties(helper);
// Act
await helper.ProcessAsync(context, output);
helper.Process(context, output);
// Assert
Assert.NotNull(output.TagName);
@ -362,7 +363,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
[Theory]
[MemberData(nameof(RunsWhenRequiredAttributesArePresent_NoSrc_Data))]
public async Task RunsWhenRequiredAttributesArePresent_NoSrc(
public void RunsWhenRequiredAttributesArePresent_NoSrc(
TagHelperAttributeList attributes,
Action<ScriptTagHelper> setProperties)
{
@ -391,11 +392,12 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
setProperties(helper);
// Act
await helper.ProcessAsync(context, output);
helper.Process(context, output);
// Assert
Assert.Null(output.TagName);
Assert.True(output.IsContentModified);
Assert.True(output.Content.IsEmpty);
Assert.True(output.PostElement.IsModified);
}
@ -498,7 +500,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
}
[Fact]
public async Task DoesNotRunWhenAllRequiredAttributesAreMissing()
public void DoesNotRunWhenAllRequiredAttributesAreMissing()
{
// Arrange
var tagHelperContext = MakeTagHelperContext();
@ -516,7 +518,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
};
// Act
await helper.ProcessAsync(tagHelperContext, output);
helper.Process(tagHelperContext, output);
// Assert
Assert.Equal("script", output.TagName);
@ -526,7 +528,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
}
[Fact]
public async Task PreservesOrderOfNonSrcAttributes()
public void PreservesOrderOfNonSrcAttributes()
{
// Arrange
var tagHelperContext = MakeTagHelperContext(
@ -564,7 +566,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
};
// Act
await helper.ProcessAsync(tagHelperContext, output);
helper.Process(tagHelperContext, output);
// Assert
Assert.Equal("data-extra", output.Attributes[0].Name);
@ -573,9 +575,11 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
}
[Fact]
public async Task RendersScriptTagsForGlobbedSrcResults()
public void RendersScriptTagsForGlobbedSrcResults()
{
// Arrange
var expectedContent = "<script src=\"HtmlEncode[[/js/site.js]]\"></script>" +
"<script src=\"HtmlEncode[[/common.js]]\"></script>";
var context = MakeTagHelperContext(
attributes: new TagHelperAttributeList
{
@ -606,25 +610,46 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
};
// Act
await helper.ProcessAsync(context, output);
helper.Process(context, output);
// Assert
Assert.Equal("script", output.TagName);
Assert.Equal("/js/site.js", output.Attributes["src"].Value);
Assert.Equal("<script src=\"HtmlEncode[[/common.js]]\"></script>", output.PostElement.GetContent());
var content = HtmlContentUtilities.HtmlContentToString(output, new HtmlTestEncoder());
Assert.Equal(expectedContent, content);
}
[Fact]
public async Task RendersScriptTagsForGlobbedSrcResults_UsesProvidedEncoder()
public void RendersScriptTagsForGlobbedSrcResults_EncodesAsExpected()
{
// Arrange
var expectedContent =
"<script encoded=\"contains &quot;quotes&quot;\" literal=\"HtmlEncode[[all HTML encoded]]\" " +
"mixed=\"HtmlEncode[[HTML encoded]] and contains &quot;quotes&quot;\" " +
"src=\"HtmlEncode[[/js/site.js]]\"></script>" +
"<script encoded=\"contains &quot;quotes&quot;\" literal=\"HtmlEncode[[all HTML encoded]]\" " +
"mixed=\"HtmlEncode[[HTML encoded]] and contains &quot;quotes&quot;\" " +
"src=\"HtmlEncode[[/common.js]]\"></script>";
var mixed = new DefaultTagHelperContent();
mixed.Append("HTML encoded");
mixed.AppendHtml(" and contains \"quotes\"");
var context = MakeTagHelperContext(
attributes: new TagHelperAttributeList
{
new TagHelperAttribute("src", "/js/site.js"),
new TagHelperAttribute("asp-src-include", "**/*.js")
{ "asp-src-include", "**/*.js" },
{ "encoded", new HtmlString("contains \"quotes\"") },
{ "literal", "all HTML encoded" },
{ "mixed", mixed },
{ "src", "/js/site.js" },
});
var output = MakeTagHelperOutput(
"script",
attributes: new TagHelperAttributeList
{
{ "encoded", new HtmlString("contains \"quotes\"") },
{ "literal", "all HTML encoded"},
{ "mixed", mixed},
});
var output = MakeTagHelperOutput("script", attributes: new TagHelperAttributeList());
var hostingEnvironment = MakeHostingEnvironment();
var viewContext = MakeViewContext();
var globbingUrlBuilder = new Mock<GlobbingUrlBuilder>(
@ -642,22 +667,23 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
MakeUrlHelperFactory())
{
GlobbingUrlBuilder = globbingUrlBuilder.Object,
ViewContext = viewContext,
Src = "/js/site.js",
SrcInclude = "**/*.js",
ViewContext = viewContext,
};
// Act
await helper.ProcessAsync(context, output);
helper.Process(context, output);
// Assert
Assert.Equal("script", output.TagName);
Assert.Equal("/js/site.js", output.Attributes["src"].Value);
Assert.Equal("<script src=\"HtmlEncode[[/common.js]]\"></script>", output.PostElement.GetContent());
var content = HtmlContentUtilities.HtmlContentToString(output, new HtmlTestEncoder());
Assert.Equal(expectedContent, content);
}
[Fact]
public async Task RenderScriptTags_WithFileVersion()
public void RenderScriptTags_WithFileVersion()
{
// Arrange
var context = MakeTagHelperContext(
@ -684,7 +710,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
};
// Act
await helper.ProcessAsync(context, output);
helper.Process(context, output);
// Assert
Assert.Equal("script", output.TagName);
@ -692,7 +718,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
}
[Fact]
public async Task RenderScriptTags_WithFileVersion_AndRequestPathBase()
public void RenderScriptTags_WithFileVersion_AndRequestPathBase()
{
// Arrange
var context = MakeTagHelperContext(
@ -718,7 +744,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
};
// Act
await helper.ProcessAsync(context, output);
helper.Process(context, output);
// Assert
Assert.Equal("script", output.TagName);
@ -726,7 +752,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
}
[Fact]
public async Task RenderScriptTags_FallbackSrc_WithFileVersion()
public void RenderScriptTags_FallbackSrc_WithFileVersion()
{
// Arrange
var context = MakeTagHelperContext(
@ -756,20 +782,87 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
};
// Act
await helper.ProcessAsync(context, output);
helper.Process(context, output);
// Assert
Assert.Equal("script", output.TagName);
Assert.Equal("/js/site.js?v=f4OxZX_x_FO5LcGBSKHWXfwtSx-j1ncoSt3SABJtkGk", output.Attributes["src"].Value);
Assert.Equal(Environment.NewLine + "<script>(isavailable()||document.write(\"<script " +
"src=\\\"JavaScriptEncode[[fallback.js?v=f4OxZX_x_FO5LcGBSKHWXfwtSx-j1ncoSt3SABJtkGk]]\\\">" +
"<\\/script>\"));</script>", output.PostElement.GetContent());
Assert.Equal(Environment.NewLine + "<script>(isavailable()||document.write(\"<script src=" +
"\\\"JavaScriptEncode[[HtmlEncode[[fallback.js?v=f4OxZX_x_FO5LcGBSKHWXfwtSx-j1ncoSt3SABJtkGk]]]]\\\"" +
"><\\/script>\"));</script>", output.PostElement.GetContent());
}
[Fact]
public async Task RenderScriptTags_GlobbedSrc_WithFileVersion()
public void RenderScriptTags_FallbackSrc_WithFileVersion_EncodesAsExpected()
{
// Arrange
var expectedContent =
"<script encoded=\"contains &quot;quotes&quot;\" literal=\"HtmlEncode[[all HTML encoded]]\" " +
"mixed=\"HtmlEncode[[HTML encoded]] and contains &quot;quotes&quot;\" " +
"src=\"HtmlEncode[[/js/site.js?v=f4OxZX_x_FO5LcGBSKHWXfwtSx-j1ncoSt3SABJtkGk]]\"></script>" +
Environment.NewLine +
"<script>(isavailable()||document.write(\"<script " +
"JavaScriptEncode[[encoded]]=\\\"JavaScriptEncode[[contains &quot;quotes&quot;]]\\\" " +
"JavaScriptEncode[[literal]]=\\\"JavaScriptEncode[[HtmlEncode[[all HTML encoded]]]]\\\" " +
"JavaScriptEncode[[mixed]]=\\\"JavaScriptEncode[[HtmlEncode[[HTML encoded]] and contains &quot;quotes&quot;]]\\\" " +
"src=\\\"JavaScriptEncode[[HtmlEncode[[fallback.js?v=f4OxZX_x_FO5LcGBSKHWXfwtSx-j1ncoSt3SABJtkGk]]]]\\\">" +
"<\\/script>\"));</script>";
var mixed = new DefaultTagHelperContent();
mixed.Append("HTML encoded");
mixed.AppendHtml(" and contains \"quotes\"");
var context = MakeTagHelperContext(
attributes: new TagHelperAttributeList
{
{ "asp-append-version", "true" },
{ "asp-fallback-src-include", "fallback.js" },
{ "asp-fallback-test", "isavailable()" },
{ "encoded", new HtmlString("contains \"quotes\"") },
{ "literal", "all HTML encoded" },
{ "mixed", mixed },
{ "src", "/js/site.js" },
});
var output = MakeTagHelperOutput(
"script",
attributes: new TagHelperAttributeList
{
{ "encoded", new HtmlString("contains \"quotes\"") },
{ "literal", "all HTML encoded" },
{ "mixed", mixed },
});
var hostingEnvironment = MakeHostingEnvironment();
var viewContext = MakeViewContext();
var helper = new ScriptTagHelper(
MakeHostingEnvironment(),
MakeCache(),
new HtmlTestEncoder(),
new JavaScriptTestEncoder(),
MakeUrlHelperFactory())
{
AppendVersion = true,
FallbackSrc = "fallback.js",
FallbackTestExpression = "isavailable()",
Src = "/js/site.js",
ViewContext = viewContext,
};
// Act
helper.Process(context, output);
// Assert
Assert.Equal("script", output.TagName);
Assert.Equal("/js/site.js?v=f4OxZX_x_FO5LcGBSKHWXfwtSx-j1ncoSt3SABJtkGk", output.Attributes["src"].Value);
var content = HtmlContentUtilities.HtmlContentToString(output, new HtmlTestEncoder());
Assert.Equal(expectedContent, content);
}
[Fact]
public void RenderScriptTags_GlobbedSrc_WithFileVersion()
{
// Arrange
var expectedContent = "<script " +
"src=\"HtmlEncode[[/js/site.js?v=f4OxZX_x_FO5LcGBSKHWXfwtSx-j1ncoSt3SABJtkGk]]\"></script>" +
"<script src=\"HtmlEncode[[/common.js?v=f4OxZX_x_FO5LcGBSKHWXfwtSx-j1ncoSt3SABJtkGk]]\"></script>";
var context = MakeTagHelperContext(
attributes: new TagHelperAttributeList
{
@ -802,13 +895,13 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
};
// Act
await helper.ProcessAsync(context, output);
helper.Process(context, output);
// Assert
Assert.Equal("script", output.TagName);
Assert.Equal("/js/site.js?v=f4OxZX_x_FO5LcGBSKHWXfwtSx-j1ncoSt3SABJtkGk", output.Attributes["src"].Value);
Assert.Equal("<script src=\"HtmlEncode[[/common.js?v=f4OxZX_x_FO5LcGBSKHWXfwtSx-j1ncoSt3SABJtkGk]]\">" +
"</script>", output.PostElement.GetContent());
var content = HtmlContentUtilities.HtmlContentToString(output, new HtmlTestEncoder());
Assert.Equal(expectedContent, content);
}
private TagHelperContext MakeTagHelperContext(