Don't like ads? PRO users don't see any ads ;-)
Guest

User input sanitization

By: MrMistreater on May 8th, 2012  |  syntax: C#  |  size: 8.16 KB  |  hits: 38  |  expires: Never
download  |  raw  |  embed  |  report abuse  |  print
Text below is selected. Please press Ctrl+C to copy to your clipboard. (⌘+C on Mac)
  1. // Uses Tidy.Net
  2.  
  3. using System;
  4. using System.Collections.Generic;
  5. using System.IO;
  6. using System.Text;
  7. using System.Text.RegularExpressions;
  8. using Microsoft.Security.Application;
  9. using TidyNet;
  10.  
  11. namespace MyApp
  12. {
  13.     public class HtmlUtility
  14.     {
  15.         private static readonly Regex HtmlTagExpression = new Regex(@"
  16.            (?'tag_start'</?)
  17.            (?'tag'\w+)((\s+
  18.            (?'attribute'(\w+)(\s*=\s*(?:"".*?""|'.*?'|[^'"">\s]+)))?)+\s*|\s*)
  19.            (?'tag_end'/?>)",
  20.             RegexOptions.Singleline | RegexOptions.IgnorePatternWhitespace | RegexOptions.IgnoreCase | RegexOptions.Compiled);
  21.  
  22.         private static readonly Regex HtmlAttributeExpression = new Regex(@"
  23.            (?'attribute'\w+)
  24.            (\s*=\s*)
  25.            (""(?'value'.*?)""|'(?'value'.*?)')",
  26.             RegexOptions.Singleline | RegexOptions.IgnorePatternWhitespace | RegexOptions.IgnoreCase | RegexOptions.Compiled);
  27.        
  28.         private static readonly Dictionary<string, List<string>> ValidHtmlTags = new Dictionary<string, List<string>>
  29.         {
  30.             {"p", new List<string>          {"style", "class", "align"}},
  31.             {"div", new List<string>        {"style", "class", "align"}},
  32.             {"span", new List<string>       {"style", "class"}},
  33.             {"br", new List<string>         {"style", "class"}},
  34.             {"hr", new List<string>         {"style", "class"}},
  35.             {"label", new List<string>      {"style", "class"}},
  36.            
  37.             {"h1", new List<string>         {"style", "class"}},
  38.             {"h2", new List<string>         {"style", "class"}},
  39.             {"h3", new List<string>         {"style", "class"}},
  40.             {"h4", new List<string>         {"style", "class"}},
  41.             {"h5", new List<string>         {"style", "class"}},
  42.             {"h6", new List<string>         {"style", "class"}},
  43.            
  44.             {"font", new List<string>       {"style", "class", "color", "face", "size"}},
  45.             {"strong", new List<string>     {"style", "class"}},
  46.             {"b", new List<string>          {"style", "class"}},
  47.             {"em", new List<string>         {"style", "class"}},
  48.             {"i", new List<string>          {"style", "class"}},
  49.             {"u", new List<string>          {"style", "class"}},
  50.             {"strike", new List<string>     {"style", "class"}},
  51.             {"ol", new List<string>         {"style", "class"}},
  52.             {"ul", new List<string>         {"style", "class"}},
  53.             {"li", new List<string>         {"style", "class"}},
  54.             {"blockquote", new List<string> {"style", "class"}},
  55.             {"code", new List<string>       {"style", "class"}},
  56.            
  57.             {"a", new List<string>          {"style", "class", "href", "title", "target"}},
  58.             {"img", new List<string>        {"style", "class", "src", "height", "width", "alt", "title", "hspace", "vspace", "border"}},
  59.  
  60.             {"table", new List<string>      {"style", "class"}},
  61.             {"thead", new List<string>      {"style", "class"}},
  62.             {"tbody", new List<string>      {"style", "class"}},
  63.             {"tfoot", new List<string>      {"style", "class"}},
  64.             {"th", new List<string>         {"style", "class", "scope"}},
  65.             {"tr", new List<string>         {"style", "class"}},
  66.             {"td", new List<string>         {"style", "class", "colspan"}},
  67.  
  68.             {"q", new List<string>          {"style", "class", "cite"}},
  69.             {"cite", new List<string>       {"style", "class"}},
  70.             {"abbr", new List<string>       {"style", "class"}},
  71.             {"acronym", new List<string>    {"style", "class"}},
  72.             {"del", new List<string>        {"style", "class"}},
  73.             {"ins", new List<string>        {"style", "class"}}
  74.         };
  75.        
  76.         /// <summary>
  77.         /// Removes the invalid HTML tags.
  78.         /// </summary>
  79.         /// <param name="input">The text.</param>
  80.         /// <returns></returns>
  81.         public static string RemoveInvalidHtmlTags(string input)
  82.         {
  83.             var html = TidyHtml(input);
  84.  
  85.             if (string.IsNullOrEmpty(html))
  86.                 return AntiXss.HtmlEncode(input);
  87.  
  88.             return HtmlTagExpression.Replace(html, new MatchEvaluator(match =>
  89.             {              
  90.                 var builder = new StringBuilder(match.Length);
  91.  
  92.                 var tagStart = match.Groups["tag_start"];
  93.                 var tagEnd = match.Groups["tag_end"];
  94.                 var tag = match.Groups["tag"].Value;
  95.                 var attributes = match.Groups["attribute"];
  96.  
  97.                 if (false == ValidHtmlTags.ContainsKey(tag))
  98.                 {
  99.                     builder.Append(tagStart.Success ? tagStart.Value : "<");
  100.                     builder.Append(tag);
  101.                     builder.Append(tagEnd.Success ? tagEnd.Value : ">");
  102.  
  103.                     return AntiXss.HtmlEncode(builder.ToString());
  104.                 }
  105.  
  106.                 builder.Append(tagStart.Success ? tagStart.Value : "<");
  107.                 builder.Append(tag);
  108.  
  109.                 foreach (Capture attribute in attributes.Captures)
  110.                 {
  111.                     builder.Append(MatchHtmlAttribute(tag, attribute));
  112.                 }
  113.  
  114.                 // add nofollow to all hyperlinks
  115.                 if (tagStart.Success && tagStart.Value == "<" && tag.Equals("a", StringComparison.OrdinalIgnoreCase))
  116.                     builder.Append(" rel=\"nofollow\"");
  117.  
  118.                 builder.Append(tagEnd.Success ? tagEnd.Value : ">");
  119.  
  120.                 return builder.ToString();
  121.             }));
  122.         }
  123.  
  124.         private static string MatchHtmlAttribute(string tag, Capture capture)
  125.         {
  126.             var output = string.Empty;
  127.             var match = HtmlAttributeExpression.Match(capture.Value);
  128.  
  129.             var attribute = match.Groups["attribute"].Value;
  130.             var value = match.Groups["value"].Value;
  131.            
  132.             if (ValidHtmlTags[tag].Contains(attribute))
  133.             {
  134.                 switch (attribute)
  135.                 {
  136.                     case "src":
  137.                     case "href":
  138.                         if (Regex.IsMatch(value, @"https?://[^""]+"))
  139.                             output = string.Format(" {0}=\"{1}\"", attribute, AntiXss.UrlEncode(value));
  140.                         break;
  141.                     default:
  142.                         output = string.Format(" {0}=\"{1}\"", attribute, value);
  143.                         break;
  144.                 }
  145.             }
  146.  
  147.             return output;
  148.         }
  149.  
  150.         private static string TidyHtml(string text)
  151.         {
  152.             var doc = new Tidy();
  153.             var messages = new TidyMessageCollection();
  154.             var input = new MemoryStream();
  155.             var output = new MemoryStream();
  156.  
  157.             var array = Encoding.UTF8.GetBytes(text);
  158.             input.Write(array, 0, array.Length);
  159.             input.Position = 0;
  160.  
  161.             doc.Options.DocType = DocType.Strict;
  162.             doc.Options.Xhtml = true;
  163.             doc.Options.CharEncoding = CharEncoding.UTF8;
  164.             doc.Options.LogicalEmphasis = true;
  165.  
  166.             doc.Options.MakeClean = false;
  167.             doc.Options.SmartIndent = false;
  168.             doc.Options.IndentContent = false;
  169.             doc.Options.TidyMark = false;
  170.  
  171.             doc.Options.DropFontTags = false;
  172.             doc.Options.QuoteAmpersand = true;
  173.             doc.Options.DropEmptyParas = true;
  174.  
  175.             doc.Options.CharEncoding = CharEncoding.UTF8;
  176.             doc.Parse(input, output, messages);
  177.  
  178.             return RemoveTidyAdditions(Encoding.UTF8.GetString(output.ToArray()));
  179.         }
  180.  
  181.         private static string RemoveTidyAdditions(string text)
  182.         {
  183.             if (string.IsNullOrEmpty(text))
  184.                 return string.Empty;
  185.  
  186.             var start = text.IndexOf("<body>");
  187.             var end = text.IndexOf("</body>");
  188.  
  189.             if (start != -1 && end > start && end < text.Length)
  190.                 text = text.Substring(start + 6, end - (start + 6));
  191.             else
  192.                 return string.Empty;
  193.  
  194.             return Regex.Replace(text, "[\r\n\t]*", string.Empty);
  195.         }
  196.     }
  197. }