From 8d4bdbdb841a6b7fd243483a3952c68090abf8f1 Mon Sep 17 00:00:00 2001 From: "N. Taylor Mullen" Date: Tue, 28 Oct 2014 12:52:56 -0700 Subject: [PATCH] Allow invalid HTML to be in Razor pages. - Modified the TagHelperParseTreeRewriter to not remove invalid HTML snippets. - Added tests to validate invalid HTML is allowed. #212 --- .../TagHelpers/TagHelperParseTreeRewriter.cs | 11 +- .../Tokenizer/Symbols/HtmlSymbolType.cs | 3 + .../Tokenizer/Symbols/SymbolExtensions.cs | 2 +- .../TagHelperParseTreeRewriterTest.cs | 123 ++++++++++++++++++ 4 files changed, 136 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.AspNet.Razor/Parser/TagHelpers/TagHelperParseTreeRewriter.cs b/src/Microsoft.AspNet.Razor/Parser/TagHelpers/TagHelperParseTreeRewriter.cs index 4a08a901d2..7611f97cad 100644 --- a/src/Microsoft.AspNet.Razor/Parser/TagHelpers/TagHelperParseTreeRewriter.cs +++ b/src/Microsoft.AspNet.Razor/Parser/TagHelpers/TagHelperParseTreeRewriter.cs @@ -56,8 +56,10 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal // Get tag name of the current block (doesn't matter if it's an end or start tag) var tagName = GetTagName(childBlock); + // Could not determine tag name, it can't be a TagHelper, continue on and track the element. if (tagName == null) { + _currentBlock.Children.Add(child); continue; } @@ -224,9 +226,14 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal } var childSpan = (Span)child; - var textSymbol = childSpan.Symbols.FirstHtmlSymbolAs(HtmlSymbolType.Text); + var textSymbol = childSpan.Symbols.FirstHtmlSymbolAs(HtmlSymbolType.WhiteSpace | HtmlSymbolType.Text); - return textSymbol != null ? textSymbol.Content : null; + if (textSymbol == null) + { + return null; + } + + return textSymbol.Type == HtmlSymbolType.WhiteSpace ? null : textSymbol.Content; } private static bool IsSelfClosing(Block beginTagBlock) diff --git a/src/Microsoft.AspNet.Razor/Tokenizer/Symbols/HtmlSymbolType.cs b/src/Microsoft.AspNet.Razor/Tokenizer/Symbols/HtmlSymbolType.cs index 7fa85e3c33..c880493338 100644 --- a/src/Microsoft.AspNet.Razor/Tokenizer/Symbols/HtmlSymbolType.cs +++ b/src/Microsoft.AspNet.Razor/Tokenizer/Symbols/HtmlSymbolType.cs @@ -1,8 +1,11 @@ // Copyright (c) Microsoft Open Technologies, Inc. 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.AspNet.Razor.Tokenizer.Symbols { + [Flags] public enum HtmlSymbolType { Unknown, diff --git a/src/Microsoft.AspNet.Razor/Tokenizer/Symbols/SymbolExtensions.cs b/src/Microsoft.AspNet.Razor/Tokenizer/Symbols/SymbolExtensions.cs index c72862656d..13523fe4de 100644 --- a/src/Microsoft.AspNet.Razor/Tokenizer/Symbols/SymbolExtensions.cs +++ b/src/Microsoft.AspNet.Razor/Tokenizer/Symbols/SymbolExtensions.cs @@ -49,7 +49,7 @@ namespace Microsoft.AspNet.Razor.Tokenizer.Symbols /// The first of type . public static HtmlSymbol FirstHtmlSymbolAs(this IEnumerable symbols, HtmlSymbolType type) { - return symbols.OfType().FirstOrDefault(sym => sym.Type == type); + return symbols.OfType().FirstOrDefault(sym => (type & sym.Type) == sym.Type); } } } diff --git a/test/Microsoft.AspNet.Razor.Test/TagHelpers/TagHelperParseTreeRewriterTest.cs b/test/Microsoft.AspNet.Razor.Test/TagHelpers/TagHelperParseTreeRewriterTest.cs index 4896a5936d..51b2dd10c2 100644 --- a/test/Microsoft.AspNet.Razor.Test/TagHelpers/TagHelperParseTreeRewriterTest.cs +++ b/test/Microsoft.AspNet.Razor.Test/TagHelpers/TagHelperParseTreeRewriterTest.cs @@ -92,6 +92,7 @@ namespace Microsoft.AspNet.Razor.Test.TagHelpers }; } } + [Theory] [MemberData(nameof(IncompleteHelperBlockData))] public void TagHelperParseTreeRewriter_CreatesErrorForIncompleteTagHelper( @@ -102,6 +103,128 @@ namespace Microsoft.AspNet.Razor.Test.TagHelpers RunParseTreeRewriterTest(documentContent, expectedOutput, new[] { expectedError }, "strong", "p"); } + public static TheoryData InvalidHtmlBlockData + { + get + { + var factory = CreateDefaultSpanFactory(); + var blockFactory = new BlockFactory(factory); + var dateTimeNow = new ExpressionBlock( + factory.CodeTransition(), + factory.Code("DateTime.Now") + .AsImplicitExpression(CSharpCodeParser.DefaultKeywords) + .Accepts(AcceptedCharacters.NonWhiteSpace)); + + return new TheoryData + { + { + "<<

>>

", + new MarkupBlock( + blockFactory.MarkupTagBlock("<"), + blockFactory.MarkupTagBlock("<"), + new MarkupTagHelperBlock("p", + factory.Markup(">>"))) + }, + { + "<

", + new MarkupBlock( + blockFactory.MarkupTagBlock("<"), + new MarkupTagHelperBlock("p")) + }, + { + "< p />", + new MarkupBlock( + blockFactory.MarkupTagBlock("< p />")) + }, + { + "", + new MarkupBlock( + blockFactory.MarkupTagBlock("", + new MarkupBlock( + new MarkupTagBlock( + factory.Markup("<"), + new MarkupBlock( + new AttributeBlockCodeGenerator( + name: "class", + prefix: new LocationTagged(" class=\"", 1, 0, 1), + suffix: new LocationTagged("\"", 12, 0, 12)), + factory.Markup(" class=\"").With(SpanCodeGenerator.Null), + factory.Markup("foo").With(new LiteralAttributeCodeGenerator( + prefix: new LocationTagged(string.Empty, 9, 0, 9), + value: new LocationTagged("foo", 9, 0, 9))), + factory.Markup("\"").With(SpanCodeGenerator.Null)), + factory.Markup(" ")), + new MarkupTagHelperBlock("p")) + }, + { + "/>

>", + new MarkupBlock( + blockFactory.MarkupTagBlock("")), + factory.Markup(">")) + }, + { + "/>

>", + new MarkupBlock( + blockFactory.MarkupTagBlock(""), + blockFactory.MarkupTagBlock("")), + factory.Markup(">")) + }, + { + "@DateTime.Now/>

>", + new MarkupBlock( + blockFactory.MarkupTagBlock(""), + blockFactory.MarkupTagBlock("")), + factory.Markup(">")) + }, + { + "

@DateTime.Now / >

", + new MarkupBlock( + blockFactory.MarkupTagBlock(""), + new MarkupTagHelperBlock("p", + dateTimeNow, + factory.Markup(" / >"), + blockFactory.MarkupTagBlock("")), + blockFactory.MarkupTagBlock("")) + }, + { + "

< @DateTime.Now >

", + new MarkupBlock( + new MarkupTagHelperBlock("p", + new MarkupTagBlock( + factory.Markup("< "), + dateTimeNow, + factory.Markup(" >")), + blockFactory.MarkupTagBlock(""))) + } + }; + } + } + + [Theory] + [MemberData(nameof(InvalidHtmlBlockData))] + public void TagHelperParseTreeRewriter_AllowsInvalidHtml(string documentContent, MarkupBlock expectedOutput) + { + RunParseTreeRewriterTest(documentContent, expectedOutput, "p"); + } + public static IEnumerable TextTagsBlockData { get