如何过滤除特定白名单之外的所有HTML标记?

时间:2008-11-20 22:00:38

标签: c# html vb.net regex

这适用于.NET。 IgnoreCase已设置且未设置MultiLine。

通常我在正则表达式上都很不错,也许我的咖啡因含量很低......

允许用户输入HTML编码的实体(< lt;,< amp;等),并使用以下HTML标记:

u, i, b, h3, h4, br, a, img

自我关闭< br />和< img />允许,有或没有额外的空间,但不是必需的。

我想:

  1. 除去上面列出的所有HTML标签之外的所有开始和结束标签。
  2. 从其余标签移除属性,除了锚点可以有href。
  3. 到目前为止,我的搜索模式(替换为空字符串):

    <(?!i|b|h3|h4|a|img|/i|/b|/h3|/h4|/a|/img)[^>]+>
    

    这个似乎除了我想要的开始和结束标签之外的所有标签,但有三个问题:

    1. 必须包含每个允许标记的结束标记版本是丑陋的。
    2. 这些属性存活下来。这可以在一次更换吗?
    3. 标记开头,允许的标记名称单击。例如,“&lt; abbrev&gt;”和“&lt; iframe&gt;”。
    4. 以下建议的模式不会删除没有属性的标记。

      </?(?!i|b|h3|h4|a|img)\b[^>]*>
      

      如下所述,“&gt;”在属性值中是合法的,但可以肯定地说我不会支持它。此外,不会担心CDATA块等。只是一点HTML。

      到目前为止,漏洞的答案是最好的,谢谢!这是他的模式(希望PRE能更好地适合我):

      static string SanitizeHtml(string html)
      {
          string acceptable = "script|link|title";
          string stringPattern = @"</?(?(?=" + acceptable + @")notag|[a-zA-Z0-9]+)(?:\s[a-zA-Z0-9\-]+=?(?:([""']?).*?\1?)?)*\s*/?>";
          return Regex.Replace(html, stringPattern, "sausage");
      }
      

      我认为仍然可以对这个答案进行一些小的调整:

      1. 我认为可以通过在“可接受的”变量中添加“! - ”并对表达式的末尾进行小的更改来修改以捕获简单的HTML注释(那些本身不包含标记的注释)允许可选的尾随“\ s - ”。

      2. 我认为如果属性之间有多个空格字符会破坏(例如:带有换行符的格式严格的HTML和属性之间的标签)。

      3. 编辑2009-07-23:以下是我使用的最终解决方案(在VB.NET中):

         Dim AcceptableTags As String = "i|b|u|sup|sub|ol|ul|li|br|h2|h3|h4|h5|span|div|p|a|img|blockquote"
         Dim WhiteListPattern As String = "</?(?(?=" & AcceptableTags & _
              ")notag|[a-zA-Z0-9]+)(?:\s[a-zA-Z0-9\-]+=?(?:([""']?).*?\1?)?)*\s*/?>"
         html = Regex.Replace(html, WhiteListPattern, "", RegExOptions.Compiled)
        

        需要注意的是,A标签的HREF属性仍然被擦除,这是不理想的。

8 个答案:

答案 0 :(得分:30)

这是我为此任务编写的函数:

static string SanitizeHtml(string html)
{
    string acceptable = "script|link|title";
    string stringPattern = @"</?(?(?=" + acceptable + @")notag|[a-zA-Z0-9]+)(?:\s[a-zA-Z0-9\-]+=?(?:(["",']?).*?\1?)?)*\s*/?>";
    return Regex.Replace(html, stringPattern, "sausage");
}

编辑:出于某种原因,我将上一个答案的更正作为一个单独的答案发布,所以我在这里整理它们。

我会解释一下正则表达式,因为它有点长。

第一部分匹配一个开括号和0或1斜杠(如果它是一个关闭标记)。

接下来,您会看到一个if-then结构,并且具有前瞻性。 (?(?= SomeTag)然后| else)我正在检查字符串的下一部分是否是可接受的标签之一。您可以看到我将正则表达式字符串与可接受的变量连接起来,该变量是由垂直条分隔的可接受的标记名称,以便任何术语都匹配。如果它是一个匹配,你可以看到我输入“notag”这个词,因为没有标签可以匹配,如果可以接受,我想不管它。否则我转到else部分,在那里我匹配任何标签名称[a-z,A-Z,0-9] +

接下来,我想匹配0个或更多属性,我假设它们的格式为attribute =“value”。所以现在我将这个部分分组表示一个属性,但我使用?:来防止这个组被捕获速度:(?:\ s [az,AZ,0-9, - ] + =?(?:( [“ ”,'])。?\ 1?))

这里我从标签和属性名称之间的空白字符开始,然后匹配属性名称:[a-z,A-Z,0-9, - ] +

接下来我匹配一个等号,然后引用。我对报价进行分组以便将其捕获,我可以稍后进行反向引用\ 1以匹配相同类型的报价。在这两个引号之间,您可以看到我使用句点来匹配任何内容,但是我使用了懒惰版本*?而不是贪婪的版本*,以便它只匹配将结束此值的下一个引用。

接下来我们在用括号关闭组之后放置*,以便它匹配多个attirbute /值组合(或者没有)。最后我们将一些空格与\ s匹配,并在xml样式自闭标记的标记中匹配0或1个结束斜杠。

你可以看到我正在用香肠替换这些标签,因为我很饿,但是你也可以用空字符串替换它们来清除它们。

答案 1 :(得分:11)

这是关于html标签过滤的一个很好的工作示例:

Sanitize HTML

答案 2 :(得分:2)

属性是使用正则表达式尝试使用HTML的主要问题。考虑潜在属性的绝对数量,以及大多数是可选的事实,以及它们可以按任何顺序出现的事实,以及“&gt;”的事实是引用属性值中的合法字符。当你开始尝试将所有这些考虑在内时,你需要处理它的正则表达式将很快变得无法管理。

我要做的是使用基于事件的HTML解析器,或者为您提供可以浏览的DOM树。

答案 3 :(得分:2)

我刚刚注意到当前的解决方案允许开头的标签中任何可接受的标签。因此,如果“b”是可接受的标签,则“闪烁”也是如此。没有什么大不了的,但是如果你对如何过滤HTML有严格要考虑的事情。你当然不希望允许“s”作为可接受的标签,因为它允许“脚本”。

答案 4 :(得分:1)

添加单词boundary \ b不起作用的原因是你没有把它放在前瞻中。因此,在&lt; b之后将尝试\ b。如果&lt;它将始终匹配。启动HTML标记。

把它放在这样的前瞻中:

<(?!/?(i|b|h3|h4|a|img)\b)[^>]+>

这也显示了如何在标签列表之前放置/,而不是每个标签。

答案 5 :(得分:0)

我认为我原本打算将值设为可选,但没有遵循,因为我可以看到我在等号后添加了?并对匹配的值部分进行了分组。让我们在该组之后添加?(标记为carot),以使其在匹配中也是可选的。我现在不在我的编译器上,但看看是否有效:

@"</?(?(?=" + acceptable + @")notag|[a-z,A-Z,0-9]+)(?:\s[a-z,A-Z,0-9,\-]+=?(?:(["",']?).*?\1?)?)*\s*/?>";
                                                                                             ^

答案 6 :(得分:0)

    /// <summary>
    /// Trims the ignoring spacified tags
    /// </summary>
    /// <param name="text">the text from which html is to be removed</param>
    /// <param name="isRemoveScript">specify if you want to remove scripts</param>
    /// <param name="ignorableTags">specify the tags that are to be ignored while stripping</param>
    /// <returns>Stripped Text</returns>
    public static string StripHtml(string text, bool isRemoveScript, params string[] ignorableTags)
    {
        if (!string.IsNullOrEmpty(text))
        {
            text = text.Replace("&lt;", "<");
            text = text.Replace("&gt;", ">");
            string ignorePattern = null;

            if (isRemoveScript)
            {
                text = Regex.Replace(text, "<script[^<]*</script>", string.Empty, RegexOptions.IgnoreCase);
            }
            if (!ignorableTags.Contains("style"))
            {
                text = Regex.Replace(text, "<style[^<]*</style>", string.Empty, RegexOptions.IgnoreCase);
            }
            foreach (string tag in ignorableTags)
            {
                //the character b spoils the regex so replace it with strong
                if (tag.Equals("b"))
                {
                    text = text.Replace("<b>", "<strong>");
                    text = text.Replace("</b>", "</strong>");
                    if (ignorableTags.Contains("strong"))
                    {
                        ignorePattern = string.Format("{0}(?!strong)(?!/strong)", ignorePattern);
                    }
                }
                else
                {
                    //Create ignore pattern fo the tags to ignore
                    ignorePattern = string.Format("{0}(?!{1})(?!/{1})", ignorePattern, tag);
                }

            }
            //finally add the ignore pattern into regex <[^<]*> which is used to match all html tags
            ignorePattern = string.Format(@"<{0}[^<]*>", ignorePattern);
            text = Regex.Replace(text, ignorePattern, "", RegexOptions.IgnoreCase);
        }

        return text;
    }

答案 7 :(得分:0)

HtmlRuleSanitizer构建在HTML Agility Pack之上,并具有用于清除标签的简单语法。

方法HtmlSanitizer.SimpleHtml5Sanitizer()会生成一个消毒器,其中包含我所需的一切,但这是一种更动态的方法:

public static string GetLimitedHtml(string value)
{
    var sanitizer = HtmlSanitizer.SimpleHtml5Sanitizer();
    var allowed = new string[] {"br", "h1", "h2", "h3", "h4", "h5", "h6", "small", "strike", "strong", "b"};
    foreach (var tag in allowed)
    {
        sanitizer.Tag(tag);
    }
    
    return sanitizer.Sanitize(value);
}