Skip to content

Commit

Permalink
remove old copy of NeatHtml from mojoPortal.Web.Framework
Browse files Browse the repository at this point in the history
  • Loading branch information
JosephMDavis committed May 24, 2024
1 parent d1709da commit d4e1138
Show file tree
Hide file tree
Showing 11 changed files with 424 additions and 2,098 deletions.
1 change: 0 additions & 1 deletion Brettle.Web.NeatHtml/Brettle.Web.NeatHtml.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@
<Compile Include="Filter.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="UntrustedContent.cs" />
<Compile Include="XssFilter.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="NeatHtml\NeatHtml.js" />
Expand Down
645 changes: 329 additions & 316 deletions Brettle.Web.NeatHtml/Filter.cs

Large diffs are not rendered by default.

143 changes: 73 additions & 70 deletions Brettle.Web.NeatHtml/UntrustedContent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,37 +19,33 @@
*/

using System;
using System.Web;
using System.Web.UI;
using System.Web.UI.Design;
using System.ComponentModel;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
using System.IO;
using System.Security.Permissions;
using System.Drawing.Design;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.UI;

namespace Brettle.Web.NeatHtml
{
/// <summary>
/// Renders it's content using NeatHtml.js to fight XSS and other attacks.
/// </summary>
/// <remarks>
/// Tables that are not at the top-level of the content may cause the content to not display properly
/// for users without javascript.
/// </remarks>
//[AspNetHostingPermissionAttribute (SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
[AspNetHostingPermissionAttribute(SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Minimal)]
[AspNetHostingPermissionAttribute (SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
/// <summary>
/// Renders it's content using NeatHtml.js to fight XSS and other attacks.
/// </summary>
/// <remarks>
/// Tables that are not at the top-level of the content may cause the content to not display properly
/// for users without javascript.
/// </remarks>
//[AspNetHostingPermissionAttribute (SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
[AspNetHostingPermissionAttribute(SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Minimal)]
[AspNetHostingPermissionAttribute(SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
[ParseChildren(false)]
[PersistChildren(true)]
//#if NET_2_0
// [Designer(typeof(System.Web.UI.Design.ContainerControlDesigner))]
//#else
// [Designer(typeof(System.Web.UI.Design.ReadWriteControlDesigner))]
//#endif
public class UntrustedContent : System.Web.UI.Control
//#if NET_2_0
// [Designer(typeof(System.Web.UI.Design.ContainerControlDesigner))]
//#else
// [Designer(typeof(System.Web.UI.Design.ReadWriteControlDesigner))]
//#endif
public class UntrustedContent : Control
{
/// <summary>
/// The name of the filter in client-side script.
Expand All @@ -60,11 +56,10 @@ public class UntrustedContent : System.Web.UI.Control
[DefaultValue("NeatHtml.DefaultFilter")]
public string ClientSideFilterName
{
get { return (ViewState["ClientSideFilterName"] != null
? (string)ViewState["ClientSideFilterName"] : "NeatHtml.DefaultFilter"); }
set { ViewState["ClientSideFilterName"] = value; }
get => ViewState["ClientSideFilterName"] != null ? (string)ViewState["ClientSideFilterName"] : "NeatHtml.DefaultFilter";
set => ViewState["ClientSideFilterName"] = value;
}

/// <summary>
/// Enables/disables support for displaying TABLE elements to users that have JavaScript disabled.
/// </summary>
Expand All @@ -76,10 +71,10 @@ public string ClientSideFilterName
[DefaultValue(false)]
public bool SupportNoScriptTables
{
get { return (ViewState["SupportNoScriptTables"] != null && (bool)ViewState["SupportNoScriptTables"]); }
set { ViewState["SupportNoScriptTables"] = value; }
get => ViewState["SupportNoScriptTables"] != null && (bool)ViewState["SupportNoScriptTables"];
set => ViewState["SupportNoScriptTables"] = value;
}

/// <summary>
/// The maximum number of "<" characters (including in tags), "&" characters (including entities),
/// attributes, and style properties, combined, that are allowed in the untrusted content.
Expand All @@ -93,16 +88,16 @@ public bool SupportNoScriptTables
[DefaultValue(10000)]
public int MaxComplexity
{
get { return (ViewState["MaxComplexity"] != null ? (int)ViewState["MaxComplexity"] : 10000); }
set { ViewState["MaxComplexity"] = value; }
get => ViewState["MaxComplexity"] != null ? (int)ViewState["MaxComplexity"] : 10000;
set => ViewState["MaxComplexity"] = value;
}

private bool IsDesignTime = (HttpContext.Current == null);
private readonly bool IsDesignTime = HttpContext.Current == null;

// This is used to ensure that the browser gets the latest NeatHtml.js each time this assembly is
// reloaded. Strictly speaking the browser only needs to get the latest when NeatHtml.js changes,
// but computing a hash on that file everytime this assembly is loaded strikes me as overkill.
private static Guid CacheBustingGuid = System.Guid.NewGuid();
private static Guid CacheBustingGuid = Guid.NewGuid();

/// <summary>
/// URL of NeatHtml.js.</summary>
Expand All @@ -112,57 +107,67 @@ public int MaxComplexity
//[Editor(typeof(UrlEditor), typeof(UITypeEditor)), Bindable(true), DefaultValue("~/NeatHtml/NeatHtml.js")]
public string ClientScriptUrl
{
get { return (string)ViewState["ClientScriptUrl"]; }
set { ViewState["ClientScriptUrl"] = value; }
get => (string)ViewState["ClientScriptUrl"];
set => ViewState["ClientScriptUrl"] = value;
}

/// <summary>
/// The regular expression pattern that a trusted image URL must match.
/// </summary>
[DefaultValue("^$")]
public string TrustedImageUrlPattern
{
get { return (string)ViewState["TrustedImageUrlPattern"]; }
set {
get => (string)ViewState["TrustedImageUrlPattern"];
set
{
ViewState["TrustedImageUrlPattern"] = value;
_TrustedImageUrlRegex = new Regex(value);
}
}

private Regex _TrustedImageUrlRegex = null;
private Regex TrustedImageUrlRegex
{
get {
get
{
if (_TrustedImageUrlRegex == null && TrustedImageUrlPattern != null)
{
_TrustedImageUrlRegex = new Regex(TrustedImageUrlPattern);
}

return _TrustedImageUrlRegex;
}
}

/// <summary>
/// The regular expression pattern that a link URL must match in order for it to void being marked with
/// rel="nofollow"
/// </summary>
[DefaultValue("^$")]
public string SpamFreeLinkUrlPattern
{
get { return (string)ViewState["SpamFreeLinkUrlPattern"]; }
set {
get => (string)ViewState["SpamFreeLinkUrlPattern"];
set
{
ViewState["SpamFreeLinkUrlPattern"] = value;
_SpamFreeLinkUrlRegex = new Regex(value);
}
}

private Regex _SpamFreeLinkUrlRegex = null;
private Regex SpamFreeLinkUrlRegex
{
get {
get
{
if (_SpamFreeLinkUrlRegex == null && SpamFreeLinkUrlPattern != null)
{
_SpamFreeLinkUrlRegex = new Regex(SpamFreeLinkUrlPattern);
}

return _SpamFreeLinkUrlRegex;
}
}

internal static string ApplyAppPathModifier(string url)
{
string appPath = HttpContext.Current.Request.ApplicationPath;
Expand All @@ -172,37 +177,34 @@ internal static string ApplyAppPathModifier(string url)
}
string requestUrl = HttpContext.Current.Request.RawUrl;
string result = HttpContext.Current.Response.ApplyAppPathModifier(url);

// Workaround Mono XSP bug where ApplyAppPathModifier() doesn't add the session id
if (requestUrl.StartsWith(appPath + "/(") && !result.StartsWith(appPath + "/("))
if (requestUrl.StartsWith($"{appPath}/(") && !result.StartsWith($"{appPath}/("))
{
if (url.StartsWith("/") && url.StartsWith(appPath))
{
url = "~" + url.Remove(0, appPath.Length);
url = $"~{url.Remove(0, appPath.Length)}";
}
if (url.StartsWith("~/"))
{
string[] compsOfPathWithinApp = requestUrl.Substring(appPath.Length).Split('/');
url = appPath + "/" + compsOfPathWithinApp[1] + "/" + url.Substring(2);
url = $"{appPath}/{compsOfPathWithinApp[1]}/{url.Substring(2)}";
}
result = url;
}
return result;
}

protected override void OnPreRender (EventArgs e)


protected override void OnPreRender(EventArgs e)
{
if (!IsDesignTime)
{
if (ClientScriptUrl == null)
{
ClientScriptUrl = "~/NeatHtml/NeatHtml.js";
}
ClientScriptUrl ??= "~/NeatHtml/NeatHtml.js";

if (!Page.ClientScript.IsClientScriptBlockRegistered("NeatHtmlJs"))
{
Page.ClientScript.RegisterClientScriptBlock(typeof(Page),"NeatHtmlJs", @"
<script data-loader='NeatHtmlUntrustedContentControl' src='" + ApplyAppPathModifier(ClientScriptUrl) + @"?guid="
+ CacheBustingGuid + @"'></script>");
Page.ClientScript.RegisterClientScriptBlock(typeof(Page), "NeatHtmlJs", $"<script data-loader=\"NeatHtmlUntrustedContentControl\" src=\"{ApplyAppPathModifier(ClientScriptUrl)}?guid={CacheBustingGuid}\"></script>");
}
}
base.OnPreRender(e);
Expand All @@ -212,21 +214,22 @@ protected override void OnPreRender (EventArgs e)
protected override void Render(HtmlTextWriter writer)
{
// Render the content of this control to a string
StringWriter sw = new StringWriter();
System.Reflection.ConstructorInfo constructor
= writer.GetType().GetConstructor(new Type[] { sw.GetType() });
HtmlTextWriter htw = constructor.Invoke(new object[] {sw}) as HtmlTextWriter;
var sw = new StringWriter();
System.Reflection.ConstructorInfo constructor = writer.GetType().GetConstructor([sw.GetType()]);
HtmlTextWriter htw = constructor.Invoke([sw]) as HtmlTextWriter;
base.RenderChildren(htw);
htw.Close();

// Filter the string and write the result
Filter f = new Filter();
f.ClientSideFilterName = ClientSideFilterName;
f.SupportNoScriptTables = SupportNoScriptTables;
f.MaxComplexity = MaxComplexity;
f.TrustedImageUrlRegex = TrustedImageUrlRegex;
f.SpamFreeLinkUrlRegex = SpamFreeLinkUrlRegex;
writer.Write(f.FilterUntrusted(sw.ToString()));
var filter = new Filter
{
ClientSideFilterName = ClientSideFilterName,
SupportNoScriptTables = SupportNoScriptTables,
MaxComplexity = MaxComplexity,
TrustedImageUrlRegex = TrustedImageUrlRegex,
SpamFreeLinkUrlRegex = SpamFreeLinkUrlRegex
};
writer.Write(filter.FilterUntrusted(sw.ToString()));
}
}
}
42 changes: 0 additions & 42 deletions Brettle.Web.NeatHtml/XssFilter.cs

This file was deleted.

10 changes: 5 additions & 5 deletions Web/Admin/ContentValidation.aspx.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,16 +55,16 @@ protected void Page_Load(object sender, EventArgs e)

private void ShowInvalidContent(string tableName, string contentFieldName, string linkFormat, params string[] fieldNames)
{
string schemaFolder = HttpContext.Current.Server.MapPath(WebUtils.GetApplicationRoot() + "/NeatHtml/schema" );
string schemaFile = Path.Combine(schemaFolder, "NeatHtml.xsd");
//string schemaFolder = HttpContext.Current.Server.MapPath(WebUtils.GetApplicationRoot() + "/NeatHtml/schema" );
//string schemaFile = Path.Combine(schemaFolder, "NeatHtml.xsd");

//XssFilter filter = XssFilter.GetForSchema(schemaFile);
Filter filter = Filter.DefaultFilter;

DataTable dataTable = DatabaseHelper.GetTable(
this.txtConnectionString.Text,
txtConnectionString.Text,
tableName,
String.Empty);
string.Empty);
tableResults.BorderWidth = Unit.Pixel(1);
tableResults.BorderColor = Color.Black;
tableResults.BorderStyle = BorderStyle.Solid;
Expand Down Expand Up @@ -97,7 +97,7 @@ private void ShowInvalidContent(string tableName, string contentFieldName, strin
contentCell.Controls.Add(new LiteralControl(String.Format(@"<span style=""color: #ff0000;"">{0}</span>:<br>{1}",
HttpUtility.HtmlEncode(ex.Message), HttpUtility.HtmlEncode(htmlString))));
tableRow.Cells.Add(contentCell);
this.tableResults.Rows.Add(tableRow);
tableResults.Rows.Add(tableRow);
}
}
}
Expand Down
Loading

0 comments on commit d4e1138

Please sign in to comment.