From 95bc7404fccbc5b4a7de37d74b7f3aeeae344fb1 Mon Sep 17 00:00:00 2001 From: Taylor Southwick Date: Thu, 31 Aug 2023 19:43:27 -0700 Subject: [PATCH] Add HtmlTextWriter and required support This is a port of HtmlTextWriter with the following changes: - `HtmlTextWriter.[Enter|Exit]Style(...)` methods (4 total) are not supported. The `Style` class brings in a lot of additional stuff we probably don't want to support - HashTable usage has been switched to Dictionary<,> - Manual array management has been switched to lists - Helper structs have been made into readonly records to simplify definition and usage - Annotated for nullability --- .../Generated/ExcludedAttributes.txt | 1 + .../Generated/Header.txt | 3 + .../Generated/Ref.Standard.cs | 329 ++++ .../Generated/TypeForwards.Framework.cs | 8 + .../HttpResponseWrapper.cs | 1 - .../Internal/HttpValueCollection.cs | 1 - .../UI/CssTextWriter.cs | 475 ++++++ .../UI/HtmlTextWriter.cs | 1317 +++++++++++++++++ .../UI/HtmlTextWriterAttribute.cs | 115 ++ .../UI/HtmlTextWriterStyle.cs | 93 ++ .../UI/HtmlTextWriterTag.cs | 202 +++ .../UI/RenderStyle.cs | 9 + .../Utilities/HttpEncoderUtility.cs | 39 + 13 files changed, 2591 insertions(+), 2 deletions(-) create mode 100644 src/Microsoft.AspNetCore.SystemWebAdapters/UI/CssTextWriter.cs create mode 100644 src/Microsoft.AspNetCore.SystemWebAdapters/UI/HtmlTextWriter.cs create mode 100644 src/Microsoft.AspNetCore.SystemWebAdapters/UI/HtmlTextWriterAttribute.cs create mode 100644 src/Microsoft.AspNetCore.SystemWebAdapters/UI/HtmlTextWriterStyle.cs create mode 100644 src/Microsoft.AspNetCore.SystemWebAdapters/UI/HtmlTextWriterTag.cs create mode 100644 src/Microsoft.AspNetCore.SystemWebAdapters/UI/RenderStyle.cs create mode 100644 src/Microsoft.AspNetCore.SystemWebAdapters/Utilities/HttpEncoderUtility.cs diff --git a/src/Microsoft.AspNetCore.SystemWebAdapters/Generated/ExcludedAttributes.txt b/src/Microsoft.AspNetCore.SystemWebAdapters/Generated/ExcludedAttributes.txt index 4437f6a62..e3e5357ee 100644 --- a/src/Microsoft.AspNetCore.SystemWebAdapters/Generated/ExcludedAttributes.txt +++ b/src/Microsoft.AspNetCore.SystemWebAdapters/Generated/ExcludedAttributes.txt @@ -1 +1,2 @@ T:System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute +T:System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute diff --git a/src/Microsoft.AspNetCore.SystemWebAdapters/Generated/Header.txt b/src/Microsoft.AspNetCore.SystemWebAdapters/Generated/Header.txt index d6fbbe3b4..37f23012d 100644 --- a/src/Microsoft.AspNetCore.SystemWebAdapters/Generated/Header.txt +++ b/src/Microsoft.AspNetCore.SystemWebAdapters/Generated/Header.txt @@ -17,4 +17,7 @@ #pragma warning disable CS0809 // Obsolete member overrides non-obsolete member #pragma warning disable CA1063 // Implement IDisposable Correctly #pragma warning disable CA1816 // Dispose methods should call SuppressFinalize +#pragma warning disable CA1721 // Property names should not match get methods +#pragma warning disable CA1711 // Identifiers should not have incorrect suffix +#pragma warning disable CA1720 // Identifier contains type name diff --git a/src/Microsoft.AspNetCore.SystemWebAdapters/Generated/Ref.Standard.cs b/src/Microsoft.AspNetCore.SystemWebAdapters/Generated/Ref.Standard.cs index a21259934..ebe757798 100644 --- a/src/Microsoft.AspNetCore.SystemWebAdapters/Generated/Ref.Standard.cs +++ b/src/Microsoft.AspNetCore.SystemWebAdapters/Generated/Ref.Standard.cs @@ -17,6 +17,9 @@ #pragma warning disable CS0809 // Obsolete member overrides non-obsolete member #pragma warning disable CA1063 // Implement IDisposable Correctly #pragma warning disable CA1816 // Dispose methods should call SuppressFinalize +#pragma warning disable CA1721 // Property names should not match get methods +#pragma warning disable CA1711 // Identifiers should not have incorrect suffix +#pragma warning disable CA1720 // Identifier contains type name namespace System.Web { @@ -823,3 +826,329 @@ public enum SessionStateMode StateServer = 2, } } +namespace System.Web.UI +{ + public partial class HtmlTextWriter : System.IO.TextWriter + { + public const string DefaultTabString = "\t"; + public const char DoubleQuoteChar = '"'; + public const string EndTagLeftChars = ""; + public const char SemicolonChar = ';'; + public const char SingleQuoteChar = '\''; + public const char SlashChar = '/'; + public const char SpaceChar = ' '; + public const char StyleEqualsChar = ':'; + public const char TagLeftChar = '<'; + public const char TagRightChar = '>'; + public HtmlTextWriter(System.IO.TextWriter writer) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + public HtmlTextWriter(System.IO.TextWriter writer, string tabString) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + public override System.Text.Encoding Encoding { get { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} } + public int Indent { get { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} set { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} } + public System.IO.TextWriter InnerWriter { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} } + public override string NewLine { get { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} set { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} } + protected System.Web.UI.HtmlTextWriterTag TagKey { get { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} set { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} } + protected string TagName { get { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} set { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} } + public virtual void AddAttribute(string name, string value) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + public virtual void AddAttribute(string name, string value, bool fEndode) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + protected virtual void AddAttribute(string name, string value, System.Web.UI.HtmlTextWriterAttribute key) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + public virtual void AddAttribute(System.Web.UI.HtmlTextWriterAttribute key, string value) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + public virtual void AddAttribute(System.Web.UI.HtmlTextWriterAttribute key, string value, bool fEncode) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + public virtual void AddStyleAttribute(string name, string value) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + protected virtual void AddStyleAttribute(string name, string value, System.Web.UI.HtmlTextWriterStyle key) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + public virtual void AddStyleAttribute(System.Web.UI.HtmlTextWriterStyle key, string value) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + public virtual void BeginRender() { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + public override void Close() { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + protected string EncodeAttributeValue(string value, bool fEncode) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + protected virtual string EncodeAttributeValue(System.Web.UI.HtmlTextWriterAttribute attrKey, string value) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + protected string EncodeUrl(string url) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + public virtual void EndRender() { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + protected virtual void FilterAttributes() { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + public override void Flush() { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + protected System.Web.UI.HtmlTextWriterAttribute GetAttributeKey(string attrName) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + protected string GetAttributeName(System.Web.UI.HtmlTextWriterAttribute attrKey) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + protected System.Web.UI.HtmlTextWriterStyle GetStyleKey(string styleName) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + protected string GetStyleName(System.Web.UI.HtmlTextWriterStyle styleKey) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + protected virtual System.Web.UI.HtmlTextWriterTag GetTagKey(string tagName) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + protected virtual string GetTagName(System.Web.UI.HtmlTextWriterTag tagKey) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + protected bool IsAttributeDefined(System.Web.UI.HtmlTextWriterAttribute key) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + protected bool IsAttributeDefined(System.Web.UI.HtmlTextWriterAttribute key, out string value) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + protected bool IsStyleAttributeDefined(System.Web.UI.HtmlTextWriterStyle key) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + protected bool IsStyleAttributeDefined(System.Web.UI.HtmlTextWriterStyle key, out string value) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + public virtual bool IsValidFormAttribute(string attribute) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + protected virtual bool OnAttributeRender(string name, string value, System.Web.UI.HtmlTextWriterAttribute key) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + protected virtual bool OnStyleAttributeRender(string name, string value, System.Web.UI.HtmlTextWriterStyle key) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + protected virtual bool OnTagRender(string name, System.Web.UI.HtmlTextWriterTag key) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + protected virtual void OutputTabs() { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + protected string PopEndTag() { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + protected void PushEndTag(string endTag) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + protected static void RegisterAttribute(string name, System.Web.UI.HtmlTextWriterAttribute key) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + protected static void RegisterStyle(string name, System.Web.UI.HtmlTextWriterStyle key) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + protected static void RegisterTag(string name, System.Web.UI.HtmlTextWriterTag key) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + protected virtual string RenderAfterContent() { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + protected virtual string RenderAfterTag() { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + protected virtual string RenderBeforeContent() { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + protected virtual string RenderBeforeTag() { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + public virtual void RenderBeginTag(string tagName) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + public virtual void RenderBeginTag(System.Web.UI.HtmlTextWriterTag tagKey) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + public virtual void RenderEndTag() { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + public override void Write(bool value) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + public override void Write(char value) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + public override void Write(char[] buffer) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + public override void Write(char[] buffer, int index, int count) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + public override void Write(double value) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + public override void Write(int value) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + public override void Write(long value) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + public override void Write(object value) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + public override void Write(float value) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + public override void Write(string value) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + public override void Write(string format, object arg0) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + public override void Write(string format, object arg0, object arg1) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + public override void Write(string format, params object[] arg) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + public virtual void WriteAttribute(string name, string value) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + public virtual void WriteAttribute(string name, string value, bool fEncode) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + public virtual void WriteBeginTag(string tagName) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + public virtual void WriteBreak() { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + public virtual void WriteEncodedText(string text) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + public virtual void WriteEncodedUrl(string url) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + public virtual void WriteEncodedUrlParameter(string urlText) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + public virtual void WriteEndTag(string tagName) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + public virtual void WriteFullBeginTag(string tagName) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + public override void WriteLine() { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + public override void WriteLine(bool value) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + public override void WriteLine(char value) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + public override void WriteLine(char[] buffer) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + public override void WriteLine(char[] buffer, int index, int count) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + public override void WriteLine(double value) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + public override void WriteLine(int value) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + public override void WriteLine(long value) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + public override void WriteLine(object value) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + public override void WriteLine(float value) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + public override void WriteLine(string value) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + public override void WriteLine(string format, object arg0) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + public override void WriteLine(string format, object arg0, object arg1) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + public override void WriteLine(string format, params object[] arg) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + public override void WriteLine(uint value) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + public void WriteLineNoTabs(string s) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + public virtual void WriteStyleAttribute(string name, string value) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + public virtual void WriteStyleAttribute(string name, string value, bool fEncode) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + protected void WriteUrlEncodedString(string text, bool argument) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} + } + public enum HtmlTextWriterAttribute + { + Abbr = 40, + Accesskey = 0, + Align = 1, + Alt = 2, + AutoComplete = 41, + Axis = 42, + Background = 3, + Bgcolor = 4, + Border = 5, + Bordercolor = 6, + Cellpadding = 7, + Cellspacing = 8, + Checked = 9, + Class = 10, + Cols = 11, + Colspan = 12, + Content = 43, + Coords = 44, + DesignerRegion = 45, + Dir = 46, + Disabled = 13, + For = 14, + Headers = 47, + Height = 15, + Href = 16, + Id = 17, + Longdesc = 48, + Maxlength = 18, + Multiple = 19, + Name = 20, + Nowrap = 21, + Onchange = 22, + Onclick = 23, + ReadOnly = 24, + Rel = 49, + Rows = 25, + Rowspan = 26, + Rules = 27, + Scope = 50, + Selected = 28, + Shape = 51, + Size = 29, + Src = 30, + Style = 31, + Tabindex = 32, + Target = 33, + Title = 34, + Type = 35, + Usemap = 52, + Valign = 36, + Value = 37, + VCardName = 53, + Width = 38, + Wrap = 39, + } + public enum HtmlTextWriterStyle + { + BackgroundColor = 0, + BackgroundImage = 1, + BorderCollapse = 2, + BorderColor = 3, + BorderStyle = 4, + BorderWidth = 5, + Color = 6, + Cursor = 16, + Direction = 17, + Display = 18, + Filter = 19, + FontFamily = 7, + FontSize = 8, + FontStyle = 9, + FontVariant = 20, + FontWeight = 10, + Height = 11, + Left = 21, + ListStyleImage = 14, + ListStyleType = 15, + Margin = 22, + MarginBottom = 23, + MarginLeft = 24, + MarginRight = 25, + MarginTop = 26, + Overflow = 27, + OverflowX = 28, + OverflowY = 29, + Padding = 30, + PaddingBottom = 31, + PaddingLeft = 32, + PaddingRight = 33, + PaddingTop = 34, + Position = 35, + TextAlign = 36, + TextDecoration = 12, + TextOverflow = 38, + Top = 39, + VerticalAlign = 37, + Visibility = 40, + WhiteSpace = 41, + Width = 13, + ZIndex = 42, + } + public enum HtmlTextWriterTag + { + A = 1, + Acronym = 2, + Address = 3, + Area = 4, + B = 5, + Base = 6, + Basefont = 7, + Bdo = 8, + Bgsound = 9, + Big = 10, + Blockquote = 11, + Body = 12, + Br = 13, + Button = 14, + Caption = 15, + Center = 16, + Cite = 17, + Code = 18, + Col = 19, + Colgroup = 20, + Dd = 21, + Del = 22, + Dfn = 23, + Dir = 24, + Div = 25, + Dl = 26, + Dt = 27, + Em = 28, + Embed = 29, + Fieldset = 30, + Font = 31, + Form = 32, + Frame = 33, + Frameset = 34, + H1 = 35, + H2 = 36, + H3 = 37, + H4 = 38, + H5 = 39, + H6 = 40, + Head = 41, + Hr = 42, + Html = 43, + I = 44, + Iframe = 45, + Img = 46, + Input = 47, + Ins = 48, + Isindex = 49, + Kbd = 50, + Label = 51, + Legend = 52, + Li = 53, + Link = 54, + Map = 55, + Marquee = 56, + Menu = 57, + Meta = 58, + Nobr = 59, + Noframes = 60, + Noscript = 61, + Object = 62, + Ol = 63, + Option = 64, + P = 65, + Param = 66, + Pre = 67, + Q = 68, + Rt = 69, + Ruby = 70, + S = 71, + Samp = 72, + Script = 73, + Select = 74, + Small = 75, + Span = 76, + Strike = 77, + Strong = 78, + Style = 79, + Sub = 80, + Sup = 81, + Table = 82, + Tbody = 83, + Td = 84, + Textarea = 85, + Tfoot = 86, + Th = 87, + Thead = 88, + Title = 89, + Tr = 90, + Tt = 91, + U = 92, + Ul = 93, + Unknown = 0, + Var = 94, + Wbr = 95, + Xml = 96, + } +} +namespace System.Web.UI.WebControls +{ + public enum HorizontalAlign + { + Center = 2, + Justify = 4, + Left = 1, + NotSet = 0, + Right = 3, + } +} diff --git a/src/Microsoft.AspNetCore.SystemWebAdapters/Generated/TypeForwards.Framework.cs b/src/Microsoft.AspNetCore.SystemWebAdapters/Generated/TypeForwards.Framework.cs index 9c2eadb34..e03cfe92c 100644 --- a/src/Microsoft.AspNetCore.SystemWebAdapters/Generated/TypeForwards.Framework.cs +++ b/src/Microsoft.AspNetCore.SystemWebAdapters/Generated/TypeForwards.Framework.cs @@ -17,6 +17,9 @@ #pragma warning disable CS0809 // Obsolete member overrides non-obsolete member #pragma warning disable CA1063 // Implement IDisposable Correctly #pragma warning disable CA1816 // Dispose methods should call SuppressFinalize +#pragma warning disable CA1721 // Property names should not match get methods +#pragma warning disable CA1711 // Identifiers should not have incorrect suffix +#pragma warning disable CA1720 // Identifier contains type name [assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Web.HttpApplication))] [assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Web.HttpApplicationState))] @@ -69,3 +72,8 @@ [assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Web.Hosting.HostingEnvironment))] [assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Web.SessionState.HttpSessionState))] [assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Web.SessionState.SessionStateMode))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Web.UI.HtmlTextWriter))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Web.UI.HtmlTextWriterAttribute))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Web.UI.HtmlTextWriterStyle))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Web.UI.HtmlTextWriterTag))] +[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Web.UI.WebControls.HorizontalAlign))] diff --git a/src/Microsoft.AspNetCore.SystemWebAdapters/HttpResponseWrapper.cs b/src/Microsoft.AspNetCore.SystemWebAdapters/HttpResponseWrapper.cs index 22c0c46f1..4ceafb9d0 100644 --- a/src/Microsoft.AspNetCore.SystemWebAdapters/HttpResponseWrapper.cs +++ b/src/Microsoft.AspNetCore.SystemWebAdapters/HttpResponseWrapper.cs @@ -4,7 +4,6 @@ using System.Collections.Specialized; using System.IO; using System.Text; -using Microsoft.AspNetCore.Http; namespace System.Web { diff --git a/src/Microsoft.AspNetCore.SystemWebAdapters/Internal/HttpValueCollection.cs b/src/Microsoft.AspNetCore.SystemWebAdapters/Internal/HttpValueCollection.cs index c611ae34c..582dbbe91 100644 --- a/src/Microsoft.AspNetCore.SystemWebAdapters/Internal/HttpValueCollection.cs +++ b/src/Microsoft.AspNetCore.SystemWebAdapters/Internal/HttpValueCollection.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Collections; using System.Collections.Specialized; using System.Text; using System.Web; diff --git a/src/Microsoft.AspNetCore.SystemWebAdapters/UI/CssTextWriter.cs b/src/Microsoft.AspNetCore.SystemWebAdapters/UI/CssTextWriter.cs new file mode 100644 index 000000000..6a3b203c7 --- /dev/null +++ b/src/Microsoft.AspNetCore.SystemWebAdapters/UI/CssTextWriter.cs @@ -0,0 +1,475 @@ +// MIT License. + +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Text; + +namespace System.Web.UI; + +/// +/// Derived TextWriter that provides CSS rendering API. +/// +internal sealed class CssTextWriter : TextWriter +{ + private readonly TextWriter _writer; + + private static readonly Dictionary attrKeyLookupTable = new(StringComparer.OrdinalIgnoreCase); + private static readonly List attrNameLookupArray = new(); + + [Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1810:Initialize reference type static fields inline", Justification = "")] + static CssTextWriter() + { + // register known style attributes, HtmlTextWriterStyle.Length + RegisterAttribute("background-color", HtmlTextWriterStyle.BackgroundColor); + RegisterAttribute("background-image", HtmlTextWriterStyle.BackgroundImage, true, true); + RegisterAttribute("border-collapse", HtmlTextWriterStyle.BorderCollapse); + RegisterAttribute("border-color", HtmlTextWriterStyle.BorderColor); + RegisterAttribute("border-style", HtmlTextWriterStyle.BorderStyle); + RegisterAttribute("border-width", HtmlTextWriterStyle.BorderWidth); + RegisterAttribute("color", HtmlTextWriterStyle.Color); + RegisterAttribute("cursor", HtmlTextWriterStyle.Cursor); + RegisterAttribute("direction", HtmlTextWriterStyle.Direction); + RegisterAttribute("display", HtmlTextWriterStyle.Display); + RegisterAttribute("filter", HtmlTextWriterStyle.Filter); + RegisterAttribute("font-family", HtmlTextWriterStyle.FontFamily, true); + RegisterAttribute("font-size", HtmlTextWriterStyle.FontSize); + RegisterAttribute("font-style", HtmlTextWriterStyle.FontStyle); + RegisterAttribute("font-variant", HtmlTextWriterStyle.FontVariant); + RegisterAttribute("font-weight", HtmlTextWriterStyle.FontWeight); + RegisterAttribute("height", HtmlTextWriterStyle.Height); + RegisterAttribute("left", HtmlTextWriterStyle.Left); + RegisterAttribute("list-style-image", HtmlTextWriterStyle.ListStyleImage, true, true); + RegisterAttribute("list-style-type", HtmlTextWriterStyle.ListStyleType); + RegisterAttribute("margin", HtmlTextWriterStyle.Margin); + RegisterAttribute("margin-bottom", HtmlTextWriterStyle.MarginBottom); + RegisterAttribute("margin-left", HtmlTextWriterStyle.MarginLeft); + RegisterAttribute("margin-right", HtmlTextWriterStyle.MarginRight); + RegisterAttribute("margin-top", HtmlTextWriterStyle.MarginTop); + RegisterAttribute("overflow-x", HtmlTextWriterStyle.OverflowX); + RegisterAttribute("overflow-y", HtmlTextWriterStyle.OverflowY); + RegisterAttribute("overflow", HtmlTextWriterStyle.Overflow); + RegisterAttribute("padding", HtmlTextWriterStyle.Padding); + RegisterAttribute("padding-bottom", HtmlTextWriterStyle.PaddingBottom); + RegisterAttribute("padding-left", HtmlTextWriterStyle.PaddingLeft); + RegisterAttribute("padding-right", HtmlTextWriterStyle.PaddingRight); + RegisterAttribute("padding-top", HtmlTextWriterStyle.PaddingTop); + RegisterAttribute("position", HtmlTextWriterStyle.Position); + RegisterAttribute("text-align", HtmlTextWriterStyle.TextAlign); + RegisterAttribute("text-decoration", HtmlTextWriterStyle.TextDecoration); + RegisterAttribute("text-overflow", HtmlTextWriterStyle.TextOverflow); + RegisterAttribute("top", HtmlTextWriterStyle.Top); + RegisterAttribute("vertical-align", HtmlTextWriterStyle.VerticalAlign); + RegisterAttribute("visibility", HtmlTextWriterStyle.Visibility); + RegisterAttribute("width", HtmlTextWriterStyle.Width); + RegisterAttribute("white-space", HtmlTextWriterStyle.WhiteSpace); + RegisterAttribute("z-index", HtmlTextWriterStyle.ZIndex); + } + + /// + /// Initializes an instance of a CssTextWriter with its underlying TextWriter. + /// + public CssTextWriter(TextWriter writer) + { + _writer = writer; + } + + /// + public override Encoding Encoding => _writer.Encoding; + + /// + [AllowNull] + public override string NewLine + { + get + { + return _writer.NewLine; + } + set + { + _writer.NewLine = value; + } + } + + /// + public override void Close() + { + _writer.Close(); + } + + /// + public override void Flush() + { + _writer.Flush(); + } + + /// + /// Returns the HtmlTextWriterStyle value for known style attributes. + /// + public static HtmlTextWriterStyle GetStyleKey(string styleName) + { + if (!string.IsNullOrEmpty(styleName)) + { + if (attrKeyLookupTable.TryGetValue(styleName, out var key)) + { + return key; + } + } + + return (HtmlTextWriterStyle)(-1); + } + + /// + /// Returns the name of the attribute corresponding to the specified HtmlTextWriterStyle value. + /// + public static string GetStyleName(HtmlTextWriterStyle styleKey) + { + return (int)styleKey >= 0 && (int)styleKey < attrNameLookupArray.Count ? attrNameLookupArray[(int)styleKey].Name : string.Empty; + } + + /// + /// Does the specified style key require attribute value encoding if the value is being + /// rendered in a style attribute. + /// + public static bool IsStyleEncoded(HtmlTextWriterStyle styleKey) + { + return (int)styleKey >= 0 && (int)styleKey < attrNameLookupArray.Count ? attrNameLookupArray[(int)styleKey].Encode : true; + } + + /// + /// + /// Registers the specified style attribute to create a mapping between a string representation + /// and the corresponding HtmlTextWriterStyle value. + /// + internal static void RegisterAttribute(string name, HtmlTextWriterStyle key) + { + RegisterAttribute(name, key, false, false); + } + + /// + /// + /// Registers the specified style attribute to create a mapping between a string representation + /// and the corresponding HtmlTextWriterStyle value. + /// + internal static void RegisterAttribute(string name, HtmlTextWriterStyle key, bool encode) + { + RegisterAttribute(name, key, encode, false); + } + + /// + /// + /// Registers the specified style attribute to create a mapping between a string representation + /// and the corresponding HtmlTextWriterStyle value. + /// In addition, the mapping can include additional information about the attribute type + /// such as whether it is a URL. + /// + internal static void RegisterAttribute(string name, HtmlTextWriterStyle key, bool encode, bool isUrl) + { + attrKeyLookupTable[name] = key; + + if ((int)key < attrNameLookupArray.Count) + { + attrNameLookupArray[(int)key] = new AttributeInformation(name, encode, isUrl); + } + } + + /// + public override void Write(string? s) + { + _writer.Write(s); + } + + /// + public override void Write(bool value) + { + _writer.Write(value); + } + + /// + public override void Write(char value) + { + _writer.Write(value); + } + + /// + public override void Write(char[]? buffer) + { + _writer.Write(buffer); + } + + /// + public override void Write(char[] buffer, int index, int count) + { + _writer.Write(buffer, index, count); + } + + /// + public override void Write(double value) + { + _writer.Write(value); + } + + /// + public override void Write(float value) + { + _writer.Write(value); + } + + /// + public override void Write(int value) + { + _writer.Write(value); + } + + /// + public override void Write(long value) + { + _writer.Write(value); + } + + /// + public override void Write(object? value) + { + _writer.Write(value); + } + + /// + public override void Write(string format, object? arg0) + { + _writer.Write(format, arg0); + } + + /// + public override void Write(string format, object? arg0, object? arg1) + { + _writer.Write(format, arg0, arg1); + } + + /// + public override void Write(string format, params object?[] arg) + { + _writer.Write(format, arg); + } + + /// + /// Render out the specified style attribute and value. + /// + public void WriteAttribute(string name, string value) + { + WriteAttribute(_writer, GetStyleKey(name), name, value); + } + + /// + /// Render out the specified style attribute and value. + /// + public void WriteAttribute(HtmlTextWriterStyle key, string value) + { + WriteAttribute(_writer, key, GetStyleName(key), value); + } + + /// + /// Render the specified style attribute into the specified TextWriter. + /// This method contains all the logic for rendering a CSS name/value pair. + /// + private static void WriteAttribute(TextWriter writer, HtmlTextWriterStyle key, string name, string value) + { + writer.Write(name); + writer.Write(':'); + + bool isUrl = false; + if (key != (HtmlTextWriterStyle)(-1)) + { + isUrl = attrNameLookupArray[(int)key].IsUrl; + } + + if (isUrl == false) + { + writer.Write(value); + } + else + { + WriteUrlAttribute(writer, value); + } + + writer.Write(';'); + } + + /// + /// Render the specified style attributes. This is used by HtmlTextWriter to render out all + /// its collected style attributes. + /// + internal static void WriteAttributes(TextWriter writer, IEnumerable styles) + { + foreach (var style in styles) + { + WriteAttribute(writer, style.Key, style.Name, style.Value); + } + } + + /// + /// Start rendering a new CSS rule with the given selector. + /// + public void WriteBeginCssRule(string selector) + { + _writer.Write(selector); + _writer.Write(" { "); + } + + /// + /// End the current CSS rule that is being rendered. + /// + public void WriteEndCssRule() + { + _writer.WriteLine(" }"); + } + + /// + public override void WriteLine(string? s) + { + _writer.WriteLine(s); + } + + /// + public override void WriteLine() + { + _writer.WriteLine(); + } + + /// + public override void WriteLine(bool value) + { + _writer.WriteLine(value); + } + + /// + public override void WriteLine(char value) + { + _writer.WriteLine(value); + } + + /// + public override void WriteLine(char[]? buffer) + { + _writer.WriteLine(buffer); + } + + /// + public override void WriteLine(char[] buffer, int index, int count) + { + _writer.WriteLine(buffer, index, count); + } + + /// + public override void WriteLine(double value) + { + _writer.WriteLine(value); + } + + /// + public override void WriteLine(float value) + { + _writer.WriteLine(value); + } + + /// + public override void WriteLine(int value) + { + _writer.WriteLine(value); + } + + /// + public override void WriteLine(long value) + { + _writer.WriteLine(value); + } + + /// + public override void WriteLine(object? value) + { + _writer.WriteLine(value); + } + + /// + public override void WriteLine(string format, object? arg0) + { + _writer.WriteLine(format, arg0); + } + + /// + public override void WriteLine(string format, object? arg0, object? arg1) + { + _writer.WriteLine(format, arg0, arg1); + } + + /// + public override void WriteLine(string format, params object?[] arg) + { + _writer.WriteLine(format, arg); + } + + /// + public override void WriteLine(uint value) + { + _writer.WriteLine(value); + } + + /// + /// Writes out the specified URL value with the appropriate encoding + /// and url() syntax. + /// internal for unit testing. + /// + internal static void WriteUrlAttribute(TextWriter writer, string url) + { + string urlValue = url; + + char[] quotes = new char[] { '\'', '"' }; + char? surroundingQuote = null; + + if (url.StartsWith("url(", StringComparison.OrdinalIgnoreCase)) + { + int urlIndex = 4; + int urlLength = url.Length - 4; + if (url.EndsWith(')')) + { + urlLength--; + } + + // extract out the actual URL value + urlValue = url.Substring(urlIndex, urlLength).Trim(); + } + + // The CSS specification http://www.w3.org/TR/CSS2/syndata.html#uri says the ' and " characters are + // optional for specifying the url values. + // And we do not want to pass them to UrlPathEncode if they are present. + foreach (char quote in quotes) + { + if (urlValue.StartsWith(quote) && urlValue.EndsWith(quote)) + { + urlValue = urlValue.Trim(quote); + surroundingQuote = quote; + break; + } + } + + // write out the "url(" prefix + writer.Write("url("); + if (surroundingQuote != null) + { + writer.Write(surroundingQuote); + } + + writer.Write(HttpUtility.UrlPathEncode(urlValue)); + + if (surroundingQuote != null) + { + writer.Write(surroundingQuote); + } + // write out the end of the "url()" syntax + writer.Write(")"); + } + + /// + /// Holds information about each registered style attribute. + /// + private readonly record struct AttributeInformation(string Name, bool Encode, bool IsUrl); +} diff --git a/src/Microsoft.AspNetCore.SystemWebAdapters/UI/HtmlTextWriter.cs b/src/Microsoft.AspNetCore.SystemWebAdapters/UI/HtmlTextWriter.cs new file mode 100644 index 000000000..bedc4337a --- /dev/null +++ b/src/Microsoft.AspNetCore.SystemWebAdapters/UI/HtmlTextWriter.cs @@ -0,0 +1,1317 @@ +// MIT License. + +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.IO; +using System.Text; +using Microsoft.AspNetCore.SystemWebAdapters.Utilities; + +namespace System.Web.UI; + +[SuppressMessage("Design", "CA1054:URI-like parameters should not be strings", Justification = Constants.ApiFromAspNet)] +[SuppressMessage("Naming", "CA1721:Property names should not match get methods", Justification = Constants.ApiFromAspNet)] +[SuppressMessage("Design", "CA1055:URI-like return values should not be strings", Justification = Constants.ApiFromAspNet)] +public class HtmlTextWriter : TextWriter +{ + public const char TagLeftChar = '<'; + public const char TagRightChar = '>'; + public const string SelfClosingChars = " /"; + public const string SelfClosingTagEnd = " />"; + public const string EndTagLeftChars = " _tagKeyLookupTable = new((int)HtmlTextWriterTag.Xml + 1, StringComparer.OrdinalIgnoreCase); + private static readonly TagInformation[] _tagNameLookupArray = new TagInformation[(int)HtmlTextWriterTag.Xml + 1]; + private static readonly Dictionary _attrKeyLookupTable = new((int)HtmlTextWriterAttribute.VCardName + 1); + private static readonly AttributeInformation[] _attrNameLookupArray = new AttributeInformation[(int)HtmlTextWriterAttribute.VCardName + 1]; + + private int _indentLevel; + private bool _tabsPending; + private readonly bool _isDescendant; + private int _tagIndex; + private HtmlTextWriterTag _tagKey; + private string? _tagName; + + private readonly Stack _endTags = new(); + private readonly List _styleList = new(); + private readonly List _attrList = new(); + private readonly string _tabString; + + static HtmlTextWriter() + { + RegisterTag(string.Empty, HtmlTextWriterTag.Unknown, TagType.Other); + RegisterTag("a", HtmlTextWriterTag.A, TagType.Inline); + RegisterTag("acronym", HtmlTextWriterTag.Acronym, TagType.Inline); + RegisterTag("address", HtmlTextWriterTag.Address, TagType.Other); + RegisterTag("area", HtmlTextWriterTag.Area, TagType.NonClosing); + RegisterTag("b", HtmlTextWriterTag.B, TagType.Inline); + RegisterTag("base", HtmlTextWriterTag.Base, TagType.NonClosing); + RegisterTag("basefont", HtmlTextWriterTag.Basefont, TagType.NonClosing); + RegisterTag("bdo", HtmlTextWriterTag.Bdo, TagType.Inline); + RegisterTag("bgsound", HtmlTextWriterTag.Bgsound, TagType.NonClosing); + RegisterTag("big", HtmlTextWriterTag.Big, TagType.Inline); + RegisterTag("blockquote", HtmlTextWriterTag.Blockquote, TagType.Other); + RegisterTag("body", HtmlTextWriterTag.Body, TagType.Other); + RegisterTag("br", HtmlTextWriterTag.Br, TagType.NonClosing); + RegisterTag("button", HtmlTextWriterTag.Button, TagType.Inline); + RegisterTag("caption", HtmlTextWriterTag.Caption, TagType.Other); + RegisterTag("center", HtmlTextWriterTag.Center, TagType.Other); + RegisterTag("cite", HtmlTextWriterTag.Cite, TagType.Inline); + RegisterTag("code", HtmlTextWriterTag.Code, TagType.Inline); + RegisterTag("col", HtmlTextWriterTag.Col, TagType.NonClosing); + RegisterTag("colgroup", HtmlTextWriterTag.Colgroup, TagType.Other); + RegisterTag("del", HtmlTextWriterTag.Del, TagType.Inline); + RegisterTag("dd", HtmlTextWriterTag.Dd, TagType.Inline); + RegisterTag("dfn", HtmlTextWriterTag.Dfn, TagType.Inline); + RegisterTag("dir", HtmlTextWriterTag.Dir, TagType.Other); + RegisterTag("div", HtmlTextWriterTag.Div, TagType.Other); + RegisterTag("dl", HtmlTextWriterTag.Dl, TagType.Other); + RegisterTag("dt", HtmlTextWriterTag.Dt, TagType.Inline); + RegisterTag("em", HtmlTextWriterTag.Em, TagType.Inline); + RegisterTag("embed", HtmlTextWriterTag.Embed, TagType.NonClosing); + RegisterTag("fieldset", HtmlTextWriterTag.Fieldset, TagType.Other); + RegisterTag("font", HtmlTextWriterTag.Font, TagType.Inline); + RegisterTag("form", HtmlTextWriterTag.Form, TagType.Other); + RegisterTag("frame", HtmlTextWriterTag.Frame, TagType.NonClosing); + RegisterTag("frameset", HtmlTextWriterTag.Frameset, TagType.Other); + RegisterTag("h1", HtmlTextWriterTag.H1, TagType.Other); + RegisterTag("h2", HtmlTextWriterTag.H2, TagType.Other); + RegisterTag("h3", HtmlTextWriterTag.H3, TagType.Other); + RegisterTag("h4", HtmlTextWriterTag.H4, TagType.Other); + RegisterTag("h5", HtmlTextWriterTag.H5, TagType.Other); + RegisterTag("h6", HtmlTextWriterTag.H6, TagType.Other); + RegisterTag("head", HtmlTextWriterTag.Head, TagType.Other); + RegisterTag("hr", HtmlTextWriterTag.Hr, TagType.NonClosing); + RegisterTag("html", HtmlTextWriterTag.Html, TagType.Other); + RegisterTag("i", HtmlTextWriterTag.I, TagType.Inline); + RegisterTag("iframe", HtmlTextWriterTag.Iframe, TagType.Other); + RegisterTag("img", HtmlTextWriterTag.Img, TagType.NonClosing); + RegisterTag("input", HtmlTextWriterTag.Input, TagType.NonClosing); + RegisterTag("ins", HtmlTextWriterTag.Ins, TagType.Inline); + RegisterTag("isindex", HtmlTextWriterTag.Isindex, TagType.NonClosing); + RegisterTag("kbd", HtmlTextWriterTag.Kbd, TagType.Inline); + RegisterTag("label", HtmlTextWriterTag.Label, TagType.Inline); + RegisterTag("legend", HtmlTextWriterTag.Legend, TagType.Other); + RegisterTag("li", HtmlTextWriterTag.Li, TagType.Inline); + RegisterTag("link", HtmlTextWriterTag.Link, TagType.NonClosing); + RegisterTag("map", HtmlTextWriterTag.Map, TagType.Other); + RegisterTag("marquee", HtmlTextWriterTag.Marquee, TagType.Other); + RegisterTag("menu", HtmlTextWriterTag.Menu, TagType.Other); + RegisterTag("meta", HtmlTextWriterTag.Meta, TagType.NonClosing); + RegisterTag("nobr", HtmlTextWriterTag.Nobr, TagType.Inline); + RegisterTag("noframes", HtmlTextWriterTag.Noframes, TagType.Other); + RegisterTag("noscript", HtmlTextWriterTag.Noscript, TagType.Other); + RegisterTag("object", HtmlTextWriterTag.Object, TagType.Other); + RegisterTag("ol", HtmlTextWriterTag.Ol, TagType.Other); + RegisterTag("option", HtmlTextWriterTag.Option, TagType.Other); + RegisterTag("p", HtmlTextWriterTag.P, TagType.Inline); + RegisterTag("param", HtmlTextWriterTag.Param, TagType.Other); + RegisterTag("pre", HtmlTextWriterTag.Pre, TagType.Other); + RegisterTag("ruby", HtmlTextWriterTag.Ruby, TagType.Other); + RegisterTag("rt", HtmlTextWriterTag.Rt, TagType.Other); + RegisterTag("q", HtmlTextWriterTag.Q, TagType.Inline); + RegisterTag("s", HtmlTextWriterTag.S, TagType.Inline); + RegisterTag("samp", HtmlTextWriterTag.Samp, TagType.Inline); + RegisterTag("script", HtmlTextWriterTag.Script, TagType.Other); + RegisterTag("select", HtmlTextWriterTag.Select, TagType.Other); + RegisterTag("small", HtmlTextWriterTag.Small, TagType.Other); + RegisterTag("span", HtmlTextWriterTag.Span, TagType.Inline); + RegisterTag("strike", HtmlTextWriterTag.Strike, TagType.Inline); + RegisterTag("strong", HtmlTextWriterTag.Strong, TagType.Inline); + RegisterTag("style", HtmlTextWriterTag.Style, TagType.Other); + RegisterTag("sub", HtmlTextWriterTag.Sub, TagType.Inline); + RegisterTag("sup", HtmlTextWriterTag.Sup, TagType.Inline); + RegisterTag("table", HtmlTextWriterTag.Table, TagType.Other); + RegisterTag("tbody", HtmlTextWriterTag.Tbody, TagType.Other); + RegisterTag("td", HtmlTextWriterTag.Td, TagType.Inline); + RegisterTag("textarea", HtmlTextWriterTag.Textarea, TagType.Inline); + RegisterTag("tfoot", HtmlTextWriterTag.Tfoot, TagType.Other); + RegisterTag("th", HtmlTextWriterTag.Th, TagType.Inline); + RegisterTag("thead", HtmlTextWriterTag.Thead, TagType.Other); + RegisterTag("title", HtmlTextWriterTag.Title, TagType.Other); + RegisterTag("tr", HtmlTextWriterTag.Tr, TagType.Other); + RegisterTag("tt", HtmlTextWriterTag.Tt, TagType.Inline); + RegisterTag("u", HtmlTextWriterTag.U, TagType.Inline); + RegisterTag("ul", HtmlTextWriterTag.Ul, TagType.Other); + RegisterTag("var", HtmlTextWriterTag.Var, TagType.Inline); + RegisterTag("wbr", HtmlTextWriterTag.Wbr, TagType.NonClosing); + RegisterTag("xml", HtmlTextWriterTag.Xml, TagType.Other); + + RegisterAttribute("abbr", HtmlTextWriterAttribute.Abbr, true); + RegisterAttribute("accesskey", HtmlTextWriterAttribute.Accesskey, true); + RegisterAttribute("align", HtmlTextWriterAttribute.Align, false); + RegisterAttribute("alt", HtmlTextWriterAttribute.Alt, true); + RegisterAttribute("autocomplete", HtmlTextWriterAttribute.AutoComplete, false); + RegisterAttribute("axis", HtmlTextWriterAttribute.Axis, true); + RegisterAttribute("background", HtmlTextWriterAttribute.Background, true, true); + RegisterAttribute("bgcolor", HtmlTextWriterAttribute.Bgcolor, false); + RegisterAttribute("border", HtmlTextWriterAttribute.Border, false); + RegisterAttribute("bordercolor", HtmlTextWriterAttribute.Bordercolor, false); + RegisterAttribute("cellpadding", HtmlTextWriterAttribute.Cellpadding, false); + RegisterAttribute("cellspacing", HtmlTextWriterAttribute.Cellspacing, false); + RegisterAttribute("checked", HtmlTextWriterAttribute.Checked, false); + RegisterAttribute("class", HtmlTextWriterAttribute.Class, true); + RegisterAttribute("cols", HtmlTextWriterAttribute.Cols, false); + RegisterAttribute("colspan", HtmlTextWriterAttribute.Colspan, false); + RegisterAttribute("content", HtmlTextWriterAttribute.Content, true); + RegisterAttribute("coords", HtmlTextWriterAttribute.Coords, false); + RegisterAttribute("dir", HtmlTextWriterAttribute.Dir, false); + RegisterAttribute("disabled", HtmlTextWriterAttribute.Disabled, false); + RegisterAttribute("for", HtmlTextWriterAttribute.For, false); + RegisterAttribute("headers", HtmlTextWriterAttribute.Headers, true); + RegisterAttribute("height", HtmlTextWriterAttribute.Height, false); + RegisterAttribute("href", HtmlTextWriterAttribute.Href, true, true); + RegisterAttribute("id", HtmlTextWriterAttribute.Id, false); + RegisterAttribute("longdesc", HtmlTextWriterAttribute.Longdesc, true, true); + RegisterAttribute("maxlength", HtmlTextWriterAttribute.Maxlength, false); + RegisterAttribute("multiple", HtmlTextWriterAttribute.Multiple, false); + RegisterAttribute("name", HtmlTextWriterAttribute.Name, false); + RegisterAttribute("nowrap", HtmlTextWriterAttribute.Nowrap, false); + RegisterAttribute("onclick", HtmlTextWriterAttribute.Onclick, true); + RegisterAttribute("onchange", HtmlTextWriterAttribute.Onchange, true); + RegisterAttribute("readonly", HtmlTextWriterAttribute.ReadOnly, false); + RegisterAttribute("rel", HtmlTextWriterAttribute.Rel, false); + RegisterAttribute("rows", HtmlTextWriterAttribute.Rows, false); + RegisterAttribute("rowspan", HtmlTextWriterAttribute.Rowspan, false); + RegisterAttribute("rules", HtmlTextWriterAttribute.Rules, false); + RegisterAttribute("scope", HtmlTextWriterAttribute.Scope, false); + RegisterAttribute("selected", HtmlTextWriterAttribute.Selected, false); + RegisterAttribute("shape", HtmlTextWriterAttribute.Shape, false); + RegisterAttribute("size", HtmlTextWriterAttribute.Size, false); + RegisterAttribute("src", HtmlTextWriterAttribute.Src, true, true); + RegisterAttribute("style", HtmlTextWriterAttribute.Style, false); + RegisterAttribute("tabindex", HtmlTextWriterAttribute.Tabindex, false); + RegisterAttribute("target", HtmlTextWriterAttribute.Target, false); + RegisterAttribute("title", HtmlTextWriterAttribute.Title, true); + RegisterAttribute("type", HtmlTextWriterAttribute.Type, false); + RegisterAttribute("usemap", HtmlTextWriterAttribute.Usemap, false); + RegisterAttribute("valign", HtmlTextWriterAttribute.Valign, false); + RegisterAttribute("value", HtmlTextWriterAttribute.Value, true); + RegisterAttribute("vcard_name", HtmlTextWriterAttribute.VCardName, false); + RegisterAttribute("width", HtmlTextWriterAttribute.Width, false); + RegisterAttribute("wrap", HtmlTextWriterAttribute.Wrap, false); + RegisterAttribute(DesignerRegionAttributeName, HtmlTextWriterAttribute.DesignerRegion, false); + } + + public virtual bool IsValidFormAttribute(string attribute) + { + return true; + } + + public override Encoding Encoding => InnerWriter.Encoding; + + [AllowNull] + public override string NewLine + { + get + { + return InnerWriter.NewLine; + } + + set + { + InnerWriter.NewLine = value; + } + } + + public int Indent + { + get + { + return _indentLevel; + } + set + { + Debug.Assert(value >= 0, "Bogus Indent... probably caused by mismatched Indent++ and Indent--"); + if (value < 0) + { + value = 0; + } + _indentLevel = value; + } + } + + //Gets or sets the TextWriter to use. + public TextWriter InnerWriter { get; set; } + + public virtual void BeginRender() + { + } + + //Closes the document being written to. + public override void Close() + { + InnerWriter.Close(); + } + + public virtual void EndRender() + { + } + + public override void Flush() + { + InnerWriter.Flush(); + } + + protected virtual void OutputTabs() + { + if (_tabsPending) + { + for (var i = 0; i < _indentLevel; i++) + { + InnerWriter.Write(_tabString); + } + _tabsPending = false; + } + } + + public override void Write(string? value) + { + if (_tabsPending) + { + OutputTabs(); + } + InnerWriter.Write(value); + } + + public override void Write(bool value) + { + if (_tabsPending) + { + OutputTabs(); + } + InnerWriter.Write(value); + } + + public override void Write(char value) + { + if (_tabsPending) + { + OutputTabs(); + } + InnerWriter.Write(value); + } + + public override void Write(char[]? buffer) + { + if (_tabsPending) + { + OutputTabs(); + } + InnerWriter.Write(buffer); + } + + public override void Write(char[] buffer, int index, int count) + { + if (_tabsPending) + { + OutputTabs(); + } + InnerWriter.Write(buffer, index, count); + } + + public override void Write(double value) + { + if (_tabsPending) + { + OutputTabs(); + } + InnerWriter.Write(value); + } + + public override void Write(float value) + { + if (_tabsPending) + { + OutputTabs(); + } + InnerWriter.Write(value); + } + + public override void Write(int value) + { + if (_tabsPending) + { + OutputTabs(); + } + InnerWriter.Write(value); + } + + public override void Write(long value) + { + if (_tabsPending) + { + OutputTabs(); + } + InnerWriter.Write(value); + } + + public override void Write(object? value) + { + if (_tabsPending) + { + OutputTabs(); + } + InnerWriter.Write(value); + } + + public override void Write(string format, object? arg0) + { + if (_tabsPending) + { + OutputTabs(); + } + InnerWriter.Write(format, arg0); + } + + public override void Write(string format, object? arg0, object? arg1) + { + if (_tabsPending) + { + OutputTabs(); + } + InnerWriter.Write(format, arg0, arg1); + } + + public override void Write(string format, params object?[] arg) + { + if (_tabsPending) + { + OutputTabs(); + } + InnerWriter.Write(format, arg); + } + + public void WriteLineNoTabs(string s) + { + InnerWriter.WriteLine(s); + _tabsPending = true; + } + + public override void WriteLine(string? value) + { + if (_tabsPending) + { + OutputTabs(); + } + InnerWriter.WriteLine(value); + _tabsPending = true; + } + + public override void WriteLine() + { + InnerWriter.WriteLine(); + _tabsPending = true; + } + + public override void WriteLine(bool value) + { + if (_tabsPending) + { + OutputTabs(); + } + InnerWriter.WriteLine(value); + _tabsPending = true; + } + + public override void WriteLine(char value) + { + if (_tabsPending) + { + OutputTabs(); + } + InnerWriter.WriteLine(value); + _tabsPending = true; + } + + public override void WriteLine(char[]? buffer) + { + if (_tabsPending) + { + OutputTabs(); + } + InnerWriter.WriteLine(buffer); + _tabsPending = true; + } + + public override void WriteLine(char[] buffer, int index, int count) + { + if (_tabsPending) + { + OutputTabs(); + } + InnerWriter.WriteLine(buffer, index, count); + _tabsPending = true; + } + + public override void WriteLine(double value) + { + if (_tabsPending) + { + OutputTabs(); + } + InnerWriter.WriteLine(value); + _tabsPending = true; + } + + public override void WriteLine(float value) + { + if (_tabsPending) + { + OutputTabs(); + } + InnerWriter.WriteLine(value); + _tabsPending = true; + } + + public override void WriteLine(int value) + { + if (_tabsPending) + { + OutputTabs(); + } + InnerWriter.WriteLine(value); + _tabsPending = true; + } + + public override void WriteLine(long value) + { + if (_tabsPending) + { + OutputTabs(); + } + InnerWriter.WriteLine(value); + _tabsPending = true; + } + + public override void WriteLine(object? value) + { + if (_tabsPending) + { + OutputTabs(); + } + InnerWriter.WriteLine(value); + _tabsPending = true; + } + + public override void WriteLine(string format, object? arg0) + { + if (_tabsPending) + { + OutputTabs(); + } + InnerWriter.WriteLine(format, arg0); + _tabsPending = true; + } + + public override void WriteLine(string format, object? arg0, object? arg1) + { + if (_tabsPending) + { + OutputTabs(); + } + InnerWriter.WriteLine(format, arg0, arg1); + _tabsPending = true; + } + + public override void WriteLine(string format, params object?[] arg) + { + if (_tabsPending) + { + OutputTabs(); + } + InnerWriter.WriteLine(format, arg); + _tabsPending = true; + } + + public override void WriteLine(uint value) + { + if (_tabsPending) + { + OutputTabs(); + } + InnerWriter.WriteLine(value); + _tabsPending = true; + } + + protected static void RegisterTag(string name, HtmlTextWriterTag key) + { + ArgumentNullException.ThrowIfNull(name); + + RegisterTag(name, key, TagType.Other); + } + + [SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase", Justification = "Tags should be lower case")] + private static void RegisterTag(string name, HtmlTextWriterTag key, TagType type) + { + _tagKeyLookupTable[name] = key; + + // Pre-resolve the end tag + string? endTag = null; + if (type != TagType.NonClosing && key != HtmlTextWriterTag.Unknown) + { + endTag = EndTagLeftChars + name.ToLowerInvariant() + TagRightChar.ToString(CultureInfo.InvariantCulture); + } + + if ((int)key < _tagNameLookupArray.Length) + { + _tagNameLookupArray[(int)key] = new TagInformation(name, type, endTag); + } + } + + protected static void RegisterAttribute(string name, HtmlTextWriterAttribute key) + { + RegisterAttribute(name, key, false); + } + + private static void RegisterAttribute(string name, HtmlTextWriterAttribute key, bool encode) + { + RegisterAttribute(name, key, encode, false); + } + + private static void RegisterAttribute(string name, HtmlTextWriterAttribute key, bool encode, bool isUrl) + { + _attrKeyLookupTable[name] = key; + + if ((int)key < _attrNameLookupArray.Length) + { + _attrNameLookupArray[(int)key] = new AttributeInformation(name, encode, isUrl); + } + } + + protected static void RegisterStyle(string name, HtmlTextWriterStyle key) + { + CssTextWriter.RegisterAttribute(name, key); + } + + public HtmlTextWriter(TextWriter writer) : this(writer, DefaultTabString) + { + } + + public HtmlTextWriter(TextWriter writer, string tabString) + : base(CultureInfo.InvariantCulture) + { + InnerWriter = writer; + + _tabString = tabString; + _indentLevel = 0; + _tabsPending = false; + _isDescendant = GetType() != typeof(HtmlTextWriter); + } + + protected HtmlTextWriterTag TagKey + { + get + { + return _tagKey; + } + set + { + _tagIndex = (int)value; + if (_tagIndex < 0 || _tagIndex >= _tagNameLookupArray.Length) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + _tagKey = value; + + // If explicitly setting to unknown, keep the old tag name. This allows a string tag + // to be set without clobbering it if setting TagKey to itself. + if (value != HtmlTextWriterTag.Unknown) + { + _tagName = _tagNameLookupArray[_tagIndex].Name; + } + } + } + + protected string? TagName + { + get + { + return _tagName; + } + set + { + _tagName = value; + _tagKey = GetTagKey(_tagName); + _tagIndex = (int)_tagKey; + Debug.Assert(_tagIndex >= 0 && _tagIndex < _tagNameLookupArray.Length); + } + } + + public virtual void AddAttribute(string name, string value) + { + var attributeKey = GetAttributeKey(name); + value = EncodeAttributeValue(attributeKey, value); + + AddAttribute(name, value, attributeKey); + } + + //do not fix this spelling error + //believe it or not, it is a backwards breaking change for languages that + //support late binding with named parameters VB.Net + public virtual void AddAttribute(string name, string value, bool fEndode) + { + value = EncodeAttributeValue(value, fEndode); + AddAttribute(name, value, GetAttributeKey(name)); + } + + public virtual void AddAttribute(HtmlTextWriterAttribute key, string value) + { + var attributeIndex = (int)key; + if (attributeIndex >= 0 && attributeIndex < _attrNameLookupArray.Length) + { + var info = _attrNameLookupArray[attributeIndex]; + AddAttribute(info.Name, value, key, info.Encode, info.IsUrl); + } + } + + public virtual void AddAttribute(HtmlTextWriterAttribute key, string value, bool fEncode) + { + var attributeIndex = (int)key; + if (attributeIndex >= 0 && attributeIndex < _attrNameLookupArray.Length) + { + var info = _attrNameLookupArray[attributeIndex]; + AddAttribute(info.Name, value, key, fEncode, info.IsUrl); + } + } + + protected virtual void AddAttribute(string name, string value, HtmlTextWriterAttribute key) + { + AddAttribute(name, value, key, false, false); + } + + private void AddAttribute(string name, string value, HtmlTextWriterAttribute key, bool encode, bool isUrl) + { + _attrList.Add(new RenderAttribute(name, value, key, encode, isUrl)); + } + + public virtual void AddStyleAttribute(string name, string value) + { + AddStyleAttribute(name, value, CssTextWriter.GetStyleKey(name)); + } + + public virtual void AddStyleAttribute(HtmlTextWriterStyle key, string value) + { + AddStyleAttribute(CssTextWriter.GetStyleName(key), value, key); + } + + protected virtual void AddStyleAttribute(string name, string value, HtmlTextWriterStyle key) + { + var style = new RenderStyle(name, value, key); + + if (CssTextWriter.IsStyleEncoded(key)) + { + // note that only css attributes in an inline style value need to be attribute encoded + // since CssTextWriter is used to render both embedded stylesheets and style attributes + // the attribute encoding is done here. + style = style with { Value = HttpUtility.HtmlAttributeEncode(value) }; + } + + _styleList.Add(style); + } + + [SuppressMessage("Performance", "CA1822:Mark members as static", Justification = Constants.ApiFromAspNet)] + [return: NotNullIfNotNull(nameof(value))] + protected string? EncodeAttributeValue(string value, bool fEncode) + { + if (value == null) + { + return null; + } + + return !fEncode ? value : HttpUtility.HtmlAttributeEncode(value); + } + + [return: NotNullIfNotNull(nameof(value))] + protected virtual string? EncodeAttributeValue(HtmlTextWriterAttribute attrKey, string value) + { + var encode = true; + + if (0 <= (int)attrKey && (int)attrKey < _attrNameLookupArray.Length) + { + encode = _attrNameLookupArray[(int)attrKey].Encode; + } + + return EncodeAttributeValue(value, encode); + } + + // This does minimal URL encoding by converting spaces in the url to "%20". + [SuppressMessage("Performance", "CA1822:Mark members as static", Justification = Constants.ApiFromAspNet)] + protected string EncodeUrl(string url) + { + ArgumentNullException.ThrowIfNull(url); + + // VSWhidbey 454348: escaped spaces in UNC share paths don't work in IE, so + // we're not going to encode if it's a share. + return !IsUncSharePath(url) ? HttpUtility.UrlPathEncode(url) : url; + + static bool IsUncSharePath(string path) + { + // e.g \\server\share\foo or //server/share/foo + return path.Length > 2 && IsDirectorySeparatorChar(path[0]) && IsDirectorySeparatorChar(path[1]); + } + + static bool IsDirectorySeparatorChar(char ch) + { + return (ch == '\\' || ch == '/'); + } + } + + [SuppressMessage("Performance", "CA1822:Mark members as static", Justification = Constants.ApiFromAspNet)] + protected HtmlTextWriterAttribute GetAttributeKey(string attrName) + { + if (!string.IsNullOrEmpty(attrName)) + { + if (_attrKeyLookupTable.TryGetValue(attrName, out var key)) + { + return key; + } + } + + return (HtmlTextWriterAttribute)(-1); + } + + [SuppressMessage("Performance", "CA1822:Mark members as static", Justification = Constants.ApiFromAspNet)] + protected string GetAttributeName(HtmlTextWriterAttribute attrKey) + { + return (int)attrKey >= 0 && (int)attrKey < _attrNameLookupArray.Length ? _attrNameLookupArray[(int)attrKey].Name : string.Empty; + } + + [SuppressMessage("Performance", "CA1822:Mark members as static", Justification = Constants.ApiFromAspNet)] + protected HtmlTextWriterStyle GetStyleKey(string styleName) + { + return CssTextWriter.GetStyleKey(styleName); + } + + [SuppressMessage("Performance", "CA1822:Mark members as static", Justification = Constants.ApiFromAspNet)] + protected string GetStyleName(HtmlTextWriterStyle styleKey) + { + return CssTextWriter.GetStyleName(styleKey); + } + + protected virtual HtmlTextWriterTag GetTagKey(string? tagName) + { + if (!string.IsNullOrEmpty(tagName)) + { + if (_tagKeyLookupTable.TryGetValue(tagName, out var key)) + { + return key; + } + } + + return HtmlTextWriterTag.Unknown; + } + + protected virtual string GetTagName(HtmlTextWriterTag tagKey) + { + var tagIndex = (int)tagKey; + return tagIndex >= 0 && tagIndex < _tagNameLookupArray.Length ? _tagNameLookupArray[tagIndex].Name : string.Empty; + } + + protected bool IsAttributeDefined(HtmlTextWriterAttribute key) + { + foreach (var attr in _attrList) + { + if (attr.Key == key) + { + return true; + } + } + return false; + } + + protected bool IsAttributeDefined(HtmlTextWriterAttribute key, [MaybeNullWhen(false)] out string value) + { + value = null; + foreach (var attr in _attrList) + { + if (attr.Key == key) + { + value = attr.Value; + return true; + } + } + return false; + } + + protected bool IsStyleAttributeDefined(HtmlTextWriterStyle key) + { + foreach (var style in _styleList) + { + if (style.Key == key) + { + return true; + } + } + return false; + } + + protected bool IsStyleAttributeDefined(HtmlTextWriterStyle key, [MaybeNullWhen(false)] out string value) + { + value = null; + foreach (var style in _styleList) + { + if (style.Key == key) + { + value = style.Value; + return true; + } + } + return false; + } + + protected virtual bool OnAttributeRender(string name, string? value, HtmlTextWriterAttribute key) + { + return true; + } + + protected virtual bool OnStyleAttributeRender(string name, string? value, HtmlTextWriterStyle key) + { + return true; + } + + protected virtual bool OnTagRender(string? name, HtmlTextWriterTag key) + { + return true; + } + + protected string? PopEndTag() + { + if (_endTags.Count == 0) + { + throw new InvalidOperationException("A PopEndTag was called without a corresponding PushEndTag."); + } + + var endTag = _endTags.Pop(); + + TagKey = endTag.Tag; + + return endTag.Text; + } + + protected void PushEndTag(string? endTag) + { + _endTags.Push(new TagStackEntry(_tagKey, endTag)); + } + + protected virtual void FilterAttributes() + { + _styleList.RemoveAll(style => !OnStyleAttributeRender(style.Name, style.Value, style.Key)); + _attrList.RemoveAll(attr => !OnAttributeRender(attr.Name, attr.Value, attr.Key)); + } + + public virtual void RenderBeginTag(string tagName) + { + TagName = tagName; + RenderBeginTag(_tagKey); + } + + public virtual void RenderBeginTag(HtmlTextWriterTag tagKey) + { + TagKey = tagKey; + var renderTag = true; + + if (_isDescendant) + { + renderTag = OnTagRender(_tagName, _tagKey); + + // Inherited renderers will be expecting to be able to filter any of the attributes at this point + FilterAttributes(); + + // write text before begin tag + var textBeforeTag = RenderBeforeTag(); + if (textBeforeTag != null) + { + if (_tabsPending) + { + OutputTabs(); + } + InnerWriter.Write(textBeforeTag); + } + } + + // gather information about this tag. + var tagInfo = _tagNameLookupArray[_tagIndex]; + var tagType = tagInfo.TagType; + var renderEndTag = renderTag && (tagType != TagType.NonClosing); + var endTag = renderEndTag ? tagInfo.ClosingTag : null; + + // write the begin tag + if (renderTag) + { + if (_tabsPending) + { + OutputTabs(); + } + InnerWriter.Write(TagLeftChar); + InnerWriter.Write(_tagName); + + string? styleValue = null; + + foreach (var attr in _attrList) + { + if (attr.Key == HtmlTextWriterAttribute.Style) + { + // append style attribute in with other styles + styleValue = attr.Value; + } + else + { + InnerWriter.Write(SpaceChar); + InnerWriter.Write(attr.Name); + if (attr.Value != null) + { + InnerWriter.Write(EqualsDoubleQuoteString); + + var attrValue = attr.Value; + if (attr.IsUrl) + { + if (attr.Key != HtmlTextWriterAttribute.Href || !attrValue.StartsWith("javascript:", StringComparison.Ordinal)) + { + attrValue = EncodeUrl(attrValue); + } + } + if (attr.Encode) + { + WriteHtmlAttributeEncode(attrValue); + } + else + { + InnerWriter.Write(attrValue); + } + InnerWriter.Write(DoubleQuoteChar); + } + } + } + + if (_styleList.Count > 0 || styleValue != null) + { + InnerWriter.Write(SpaceChar); + InnerWriter.Write("style"); + InnerWriter.Write(EqualsDoubleQuoteString); + + CssTextWriter.WriteAttributes(InnerWriter, _styleList); + if (styleValue != null) + { + InnerWriter.Write(styleValue); + } + InnerWriter.Write(DoubleQuoteChar); + } + + if (tagType == TagType.NonClosing) + { + InnerWriter.Write(SelfClosingTagEnd); + } + else + { + InnerWriter.Write(TagRightChar); + } + } + + var textBeforeContent = RenderBeforeContent(); + if (textBeforeContent != null) + { + if (_tabsPending) + { + OutputTabs(); + } + InnerWriter.Write(textBeforeContent); + } + + // write text before the content + if (renderEndTag) + { + if (tagType != TagType.Inline) + { + WriteLine(); + Indent++; + } + + // Manually build end tags for unknown tag types. + if (endTag == null) + { + endTag = EndTagLeftChars + _tagName + TagRightChar.ToString(CultureInfo.InvariantCulture); + } + } + + if (_isDescendant) + { + // append text after the tag + var textAfterTag = RenderAfterTag(); + if (textAfterTag != null) + { + endTag = (endTag == null) ? textAfterTag : textAfterTag + endTag; + } + + // build end content and push it on stack to write in RenderEndTag + // prepend text after the content + var textAfterContent = RenderAfterContent(); + if (textAfterContent != null) + { + endTag = (endTag == null) ? textAfterContent : textAfterContent + endTag; + } + } + + // push end tag onto stack + PushEndTag(endTag); + + // flush attribute and style lists for next tag + _attrList.Clear(); + _styleList.Clear(); + } + + public virtual void RenderEndTag() + { + var endTag = PopEndTag(); + + if (endTag != null) + { + if (_tagNameLookupArray[_tagIndex].TagType == TagType.Inline) + { + // Never inject crlfs at end of inline tags. + // + Write(endTag); + } + else + { + // unindent if not an inline tag + WriteLine(); + Indent--; + Write(endTag); + } + } + } + + protected virtual string? RenderBeforeTag() + { + return null; + } + + protected virtual string? RenderBeforeContent() + { + return null; + } + + protected virtual string? RenderAfterContent() + { + return null; + } + + protected virtual string? RenderAfterTag() + { + return null; + } + + public virtual void WriteAttribute(string name, string value) + { + WriteAttribute(name, value, false /*encode*/); + } + + public virtual void WriteAttribute(string name, string value, bool fEncode) + { + InnerWriter.Write(SpaceChar); + InnerWriter.Write(name); + if (value != null) + { + InnerWriter.Write(EqualsDoubleQuoteString); + if (fEncode) + { + WriteHtmlAttributeEncode(value); + } + else + { + InnerWriter.Write(value); + } + InnerWriter.Write(DoubleQuoteChar); + } + } + + public virtual void WriteBeginTag(string tagName) + { + if (_tabsPending) + { + OutputTabs(); + } + InnerWriter.Write(TagLeftChar); + InnerWriter.Write(tagName); + } + + public virtual void WriteBreak() + { + // Space between br and / is for improved html compatibility. See XHTML 1.0 specification, section C.2. + Write("
"); + } + + public virtual void WriteFullBeginTag(string tagName) + { + if (_tabsPending) + { + OutputTabs(); + } + InnerWriter.Write(TagLeftChar); + InnerWriter.Write(tagName); + InnerWriter.Write(TagRightChar); + } + + public virtual void WriteEndTag(string tagName) + { + if (_tabsPending) + { + OutputTabs(); + } + InnerWriter.Write(TagLeftChar); + InnerWriter.Write(SlashChar); + InnerWriter.Write(tagName); + InnerWriter.Write(TagRightChar); + } + + public virtual void WriteStyleAttribute(string name, string value) + { + WriteStyleAttribute(name, value, false /*encode*/); + } + + public virtual void WriteStyleAttribute(string name, string value, bool fEncode) + { + InnerWriter.Write(name); + InnerWriter.Write(StyleEqualsChar); + if (fEncode) + { + WriteHtmlAttributeEncode(value); + } + else + { + InnerWriter.Write(value); + } + InnerWriter.Write(SemicolonChar); + } + + public virtual void WriteEncodedUrl(string url) + { + ArgumentNullException.ThrowIfNull(url); + + var i = url.IndexOf('?', StringComparison.OrdinalIgnoreCase); + if (i != -1) + { + WriteUrlEncodedString(url.Substring(0, i), false); + Write(url.AsSpan(i)); + } + else + { + WriteUrlEncodedString(url, false); + } + } + + public virtual void WriteEncodedUrlParameter(string urlText) + { + WriteUrlEncodedString(urlText, true); + } + + public virtual void WriteEncodedText(string text) + { + ArgumentNullException.ThrowIfNull(text); + + const char NBSP = '\u00A0'; + + // When inner text is retrieved for a text control,   is + // decoded to 0x00A0 (code point for nbsp in Unicode). + // HtmlEncode doesn't encode 0x00A0 to  , we need to do it + // manually here. + var length = text.Length; + var pos = 0; + while (pos < length) + { + var nbsp = text.IndexOf(NBSP, pos); + if (nbsp < 0) + { + HttpUtility.HtmlEncode(pos == 0 ? text : text.Substring(pos, length - pos), this); + pos = length; + } + else + { + if (nbsp > pos) + { + HttpUtility.HtmlEncode(text.Substring(pos, nbsp - pos), this); + } + Write(" "); + pos = nbsp + 1; + } + } + } + + protected void WriteUrlEncodedString(string text, bool argument) + { + ArgumentNullException.ThrowIfNull(text); + + var length = text.Length; + for (var i = 0; i < length; i++) + { + var ch = text[i]; + if (HttpEncoderUtility.IsUrlSafeChar(ch)) + { + Write(ch); + } + else if (!argument && + (ch == '/' || + ch == ':' || + ch == '#' || + ch == ',' + ) + ) + { + Write(ch); + } + else if (ch == ' ' && argument) + { + Write('+'); + } + // for chars that their code number is less than 128 and have + // not been handled above + else if ((ch & 0xff80) == 0) + { + Write('%'); + Write(HttpEncoderUtility.IntToHex((ch >> 4) & 0xf)); + Write(HttpEncoderUtility.IntToHex((ch) & 0xf)); + } + else + { + // VSWhidbey 448625: For DBCS characters, use UTF8 encoding + // which can be handled by IIS5 and above. + Write(HttpUtility.UrlEncode(char.ToString(ch), Encoding.UTF8)); + } + } + } + + internal void WriteHtmlAttributeEncode(string s) + { + HttpUtility.HtmlAttributeEncode(s, InnerWriter); + } + + private readonly record struct TagStackEntry(HtmlTextWriterTag Tag, string? Text); + + private readonly record struct RenderAttribute(string Name, string Value, HtmlTextWriterAttribute Key, bool Encode, bool IsUrl); + + private readonly record struct AttributeInformation(string Name, bool Encode, bool IsUrl); + + private readonly record struct TagInformation(string Name, TagType TagType, string? ClosingTag); + + private enum TagType + { + Inline, + NonClosing, + Other, + } +} diff --git a/src/Microsoft.AspNetCore.SystemWebAdapters/UI/HtmlTextWriterAttribute.cs b/src/Microsoft.AspNetCore.SystemWebAdapters/UI/HtmlTextWriterAttribute.cs new file mode 100644 index 000000000..0561d348e --- /dev/null +++ b/src/Microsoft.AspNetCore.SystemWebAdapters/UI/HtmlTextWriterAttribute.cs @@ -0,0 +1,115 @@ +// MIT License. + +namespace System.Web.UI; + +[Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1711:Identifiers should not have incorrect suffix", Justification = Constants.ApiFromAspNet)] +public enum HtmlTextWriterAttribute +{ + Accesskey, + + Align, + + Alt, + + Background, + + Bgcolor, + + Border, + + Bordercolor, + + Cellpadding, + + Cellspacing, + + Checked, + + Class, + + Cols, + + Colspan, + + Disabled, + + For, + + Height, + + Href, + + Id, + + Maxlength, + + Multiple, + + Name, + + Nowrap, + + Onchange, + + Onclick, + + ReadOnly, + + Rows, + + Rowspan, + + Rules, + + Selected, + + Size, + + Src, + + Style, + + Tabindex, + + Target, + + Title, + + Type, + + Valign, + + Value, + + Width, + + Wrap, + + Abbr, + + AutoComplete, + + Axis, + + Content, + + Coords, + + DesignerRegion, + + Dir, + + Headers, + + Longdesc, + + Rel, + + Scope, + + Shape, + + Usemap, + + VCardName, +} diff --git a/src/Microsoft.AspNetCore.SystemWebAdapters/UI/HtmlTextWriterStyle.cs b/src/Microsoft.AspNetCore.SystemWebAdapters/UI/HtmlTextWriterStyle.cs new file mode 100644 index 000000000..75d1b86b6 --- /dev/null +++ b/src/Microsoft.AspNetCore.SystemWebAdapters/UI/HtmlTextWriterStyle.cs @@ -0,0 +1,93 @@ +// MIT License. + +namespace System.Web.UI; + +public enum HtmlTextWriterStyle +{ + + BackgroundColor, + + BackgroundImage, + + BorderCollapse, + + BorderColor, + + BorderStyle, + + BorderWidth, + + Color, + + FontFamily, + + FontSize, + + FontStyle, + + FontWeight, + + Height, + + TextDecoration, + + Width, + + ListStyleImage, + + ListStyleType, + + Cursor, + + Direction, + + Display, + + Filter, + + FontVariant, + + Left, + + Margin, + + MarginBottom, + + MarginLeft, + + MarginRight, + + MarginTop, + + Overflow, + + OverflowX, + + OverflowY, + + Padding, + + PaddingBottom, + + PaddingLeft, + + PaddingRight, + + PaddingTop, + + Position, + + TextAlign, + + VerticalAlign, + + TextOverflow, + + Top, + + Visibility, + + WhiteSpace, + + ZIndex +} diff --git a/src/Microsoft.AspNetCore.SystemWebAdapters/UI/HtmlTextWriterTag.cs b/src/Microsoft.AspNetCore.SystemWebAdapters/UI/HtmlTextWriterTag.cs new file mode 100644 index 000000000..f4b7b28f4 --- /dev/null +++ b/src/Microsoft.AspNetCore.SystemWebAdapters/UI/HtmlTextWriterTag.cs @@ -0,0 +1,202 @@ +// MIT License. + +namespace System.Web.UI; + +[Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1720:Identifier contains type name", Justification = Constants.ApiFromAspNet)] +public enum HtmlTextWriterTag +{ + + Unknown, + + A, + + Acronym, + + Address, + + Area, + + B, + + Base, + + Basefont, + + Bdo, + + Bgsound, + + Big, + + Blockquote, + + Body, + + Br, + + Button, + + Caption, + + Center, + + Cite, + + Code, + + Col, + + Colgroup, + + Dd, + + Del, + + Dfn, + + Dir, + + Div, + + Dl, + + Dt, + + Em, + + Embed, + + Fieldset, + + Font, + + Form, + + Frame, + + Frameset, + + H1, + + H2, + + H3, + + H4, + + H5, + + H6, + + Head, + + Hr, + + Html, + + I, + + Iframe, + + Img, + + Input, + + Ins, + + Isindex, + + Kbd, + + Label, + + Legend, + + Li, + + Link, + + Map, + + Marquee, + + Menu, + + Meta, + + Nobr, + + Noframes, + + Noscript, + + Object, + + Ol, + + Option, + + P, + + Param, + + Pre, + + Q, + + Rt, + + Ruby, + + S, + + Samp, + + Script, + + Select, + + Small, + + Span, + + Strike, + + Strong, + + Style, + + Sub, + + Sup, + + Table, + + Tbody, + + Td, + + Textarea, + + Tfoot, + + Th, + + Thead, + + Title, + + Tr, + + Tt, + + U, + + Ul, + + Var, + + Wbr, + + Xml, +} diff --git a/src/Microsoft.AspNetCore.SystemWebAdapters/UI/RenderStyle.cs b/src/Microsoft.AspNetCore.SystemWebAdapters/UI/RenderStyle.cs new file mode 100644 index 000000000..c32dc4d9f --- /dev/null +++ b/src/Microsoft.AspNetCore.SystemWebAdapters/UI/RenderStyle.cs @@ -0,0 +1,9 @@ +// MIT License. + +namespace System.Web.UI; + +/// +/// Holds information about each style attribute that needs to be rendered. +/// This is used by the tag rendering API of HtmlTextWriter. +/// +internal readonly record struct RenderStyle(string Name, string Value, HtmlTextWriterStyle Key); diff --git a/src/Microsoft.AspNetCore.SystemWebAdapters/Utilities/HttpEncoderUtility.cs b/src/Microsoft.AspNetCore.SystemWebAdapters/Utilities/HttpEncoderUtility.cs new file mode 100644 index 000000000..baae932dd --- /dev/null +++ b/src/Microsoft.AspNetCore.SystemWebAdapters/Utilities/HttpEncoderUtility.cs @@ -0,0 +1,39 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; + +namespace Microsoft.AspNetCore.SystemWebAdapters.Utilities; + +internal static class HttpEncoderUtility +{ + public static char IntToHex(int n) + { + Debug.Assert(n < 0x10); + + return n <= 9 ? (char)(n + '0') : (char)(n - 10 + 'a'); + } + + // Set of safe chars, from RFC 1738.4 minus '+' + public static bool IsUrlSafeChar(char ch) + { + if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9')) + { + return true; + } + + switch (ch) + { + case '-': + case '_': + case '.': + case '!': + case '*': + case '(': + case ')': + return true; + } + + return false; + } +}