Prevent newlines from being encoded in the output (dotnet/aspnetcore-tooling#374)

* Prevent newlines from being encoded in the output
\n\nCommit migrated from 5f74fae0d2
This commit is contained in:
Ajay Bhargav Baaskaran 2019-03-29 13:04:15 -07:00 committed by GitHub
parent 8a8e144653
commit 8bb16c28aa
5 changed files with 115 additions and 9 deletions

View File

@ -16,9 +16,9 @@ namespace __GeneratedComponent
{
builder.OpenElement(0, "div");
builder.AddAttribute(1, "class", this.ToString());
builder.AddContent(2, "\r\n Hello world\r\n ");
builder.AddMarkupContent(2, "\r\n Hello world\r\n ");
builder.AddContent(3, string.Format("{0}", "Hello"));
builder.AddContent(4, "\r\n");
builder.AddMarkupContent(4, "\r\n");
builder.CloseElement();
}
#pragma warning restore 1998

View File

@ -0,0 +1,71 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Linq;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
namespace Microsoft.AspNetCore.Razor.Language.Components
{
internal class ComponentMarkupEncodingPass : ComponentIntermediateNodePassBase, IRazorOptimizationPass
{
// Runs after ComponentMarkupBlockPass
public override int Order => 10010;
protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
{
if (!IsComponentDocument(documentNode))
{
return;
}
if (documentNode.Options.DesignTime)
{
// Nothing to do during design time.
return;
}
var rewriter = new Rewriter();
rewriter.Visit(documentNode);
}
private class Rewriter : IntermediateNodeWalker
{
// Markup content in components are rendered in one of the following two ways,
// AddContent - we encode it when used with prerendering and inserted into the DOM in a safe way (low perf impact)
// AddMarkupContent - renders the content directly as markup (high perf impact)
// Because of this, we want to use AddContent as much as possible.
//
// We want to use AddMarkupContent to avoid aggresive encoding during prerendering.
// Specifically, when one of the following characters are in the content,
// 1. New lines (\r, \n), tabs(\t) - so they get rendered as actual new lines, tabs instead of 

// 2. Ampersands (&) - so that HTML entities are rendered correctly without getting encoded
// 3. Any character outside the ASCII range
private static readonly char[] EncodedCharacters = new[] { '\r', '\n', '\t', '&' };
public override void VisitHtml(HtmlContentIntermediateNode node)
{
for (var i = 0; i < node.Children.Count; i++)
{
var child = node.Children[i];
if (!(child is IntermediateToken token) || !token.IsHtml)
{
// We only care about Html tokens.
continue;
}
for (var j = 0; j < token.Content.Length; j++)
{
var ch = token.Content[j];
// ASCII range is 0 - 127
if (ch > 127 || EncodedCharacters.Contains(ch))
{
node.SetEncoded();
return;
}
}
}
}
}
}
}

View File

@ -98,7 +98,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
// Since we're not in the middle of writing an element, this must evaluate as some
// text to display
context.CodeWriter
.WriteStartMethodInvocation($"{_scopeStack.BuilderVarName}.{nameof(ComponentsApi.RenderTreeBuilder.AddContent)}")
.WriteStartMethodInvocation($"{_scopeStack.BuilderVarName}.{ComponentsApi.RenderTreeBuilder.AddContent}")
.Write((_sourceSequence++).ToString())
.WriteParameterSeparator();
@ -158,7 +158,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
}
context.CodeWriter
.WriteStartMethodInvocation($"{_scopeStack.BuilderVarName}.{nameof(ComponentsApi.RenderTreeBuilder.AddMarkupContent)}")
.WriteStartMethodInvocation($"{_scopeStack.BuilderVarName}.{ComponentsApi.RenderTreeBuilder.AddMarkupContent}")
.Write((_sourceSequence++).ToString())
.WriteParameterSeparator()
.WriteStringLiteral(node.Content)
@ -178,7 +178,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
}
context.CodeWriter
.WriteStartMethodInvocation($"{_scopeStack.BuilderVarName}.{nameof(ComponentsApi.RenderTreeBuilder.OpenElement)}")
.WriteStartMethodInvocation($"{_scopeStack.BuilderVarName}.{ComponentsApi.RenderTreeBuilder.OpenElement}")
.Write((_sourceSequence++).ToString())
.WriteParameterSeparator()
.WriteStringLiteral(node.TagName)
@ -255,8 +255,15 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
// Text node
var content = GetHtmlContent(node);
var renderApi = ComponentsApi.RenderTreeBuilder.AddContent;
if (node.IsEncoded())
{
// This content is already encoded.
renderApi = ComponentsApi.RenderTreeBuilder.AddMarkupContent;
}
context.CodeWriter
.WriteStartMethodInvocation($"{_scopeStack.BuilderVarName}.{nameof(ComponentsApi.RenderTreeBuilder.AddContent)}")
.WriteStartMethodInvocation($"{_scopeStack.BuilderVarName}.{renderApi}")
.Write((_sourceSequence++).ToString())
.WriteParameterSeparator()
.WriteStringLiteral(content)
@ -644,8 +651,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
var codeWriter = context.CodeWriter;
var methodName = node.IsComponentCapture
? nameof(ComponentsApi.RenderTreeBuilder.AddComponentReferenceCapture)
: nameof(ComponentsApi.RenderTreeBuilder.AddElementReferenceCapture);
? ComponentsApi.RenderTreeBuilder.AddComponentReferenceCapture
: ComponentsApi.RenderTreeBuilder.AddElementReferenceCapture;
codeWriter
.WriteStartMethodInvocation($"{_scopeStack.BuilderVarName}.{methodName}")
.Write((_sourceSequence++).ToString())
@ -693,7 +700,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
protected override void BeginWriteAttribute(CodeWriter codeWriter, string key)
{
codeWriter
.WriteStartMethodInvocation($"{_scopeStack.BuilderVarName}.{nameof(ComponentsApi.RenderTreeBuilder.AddAttribute)}")
.WriteStartMethodInvocation($"{_scopeStack.BuilderVarName}.{ComponentsApi.RenderTreeBuilder.AddAttribute}")
.Write((_sourceSequence++).ToString())
.WriteParameterSeparator()
.WriteStringLiteral(key)

View File

@ -0,0 +1,27 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.AspNetCore.Razor.Language.Intermediate
{
internal static class HtmlContentIntermediateNodeExtensions
{
private static readonly string HasEncodedContent = "HasEncodedContent";
public static bool IsEncoded(this HtmlContentIntermediateNode node)
{
return ReferenceEquals(node.Annotations[HasEncodedContent], HasEncodedContent);
}
public static void SetEncoded(this HtmlContentIntermediateNode node)
{
if (node == null)
{
throw new ArgumentNullException(nameof(node));
}
node.Annotations[HasEncodedContent] = HasEncodedContent;
}
}
}

View File

@ -232,6 +232,7 @@ namespace Microsoft.AspNetCore.Razor.Language
builder.Features.Add(new ComponentGenericTypePass());
builder.Features.Add(new ComponentChildContentDiagnosticPass());
builder.Features.Add(new ComponentMarkupBlockPass());
builder.Features.Add(new ComponentMarkupEncodingPass());
}
private static void LoadExtensions(RazorProjectEngineBuilder builder, IReadOnlyList<RazorExtension> extensions)