php strip_tags正则表达式用于将单个< 替换为HTML实体

cwxwcias  于 2023-09-29  发布在  PHP
关注(0)|答案(1)|浏览(103)

我使用strip_tags来确保在保存字符串之前删除每个HTML标记。现在我得到了一个问题,没有任何结束标签的单个<也被删除了。现在的想法是用匹配的HTML实体&#60;替换每一个<,我得到了一个正则表达式,但它只替换了第一个find,有什么想法吗?
这是我现在得到的正则表达式:preg_replace("/<([^>]*(<|$))/", "&lt;$1", $string);
我想要这个

<p> Hello < 30 </p> < < < <!-- Test --> &#60;> > > >

成为preg_replace(REGEX, REPLACE, $string)的第一个:

<p> Hello &#60; 30 </p> &#60; &#60; &#60; <!-- Test --> &#60;> > > >

然后在strip_tags($string)之后:

Hello &#60; 30  &#60; &#60; &#60;  &#60;> > > >

你知道我该怎么做吗?
也许你知道一个更好的方法。

dfuffjeb

dfuffjeb1#

你的问题很有趣,所以我花时间尝试解决它。我认为唯一的方法是分几步来做:
1.第一步是删除HTML注解。
1.下一步是尝试将所有HTML标记与正则表达式匹配,以便将它们重写为另一种形式,将<>字符替换为其他字符,例如[[]]
1.之后,您可以将<替换为&lt;,将>替换为&gt;
1.我们将[[tag attr="value"]][[/tag]]替换为原始的HTML标记<tag attr="value"></tag>
1.我们现在可以使用strip_tags()或更安全、更灵活的库(如HTMLPurifier)来剥离我们想要的HTML标记。

PHP代码

抱歉,但颜色突出显示似乎是错误的,因为我使用Nowdoc字符串来方便编辑:

<?php

define('LINE_LENGTH', 60);

// A regular expression to find HTML tags.
define('REGEX_HTML_TAG', <<<'END_OF_REGEX'
~
<(?!!--)                # Opening of a tag, but not for HTML comments.
(?<tagcontent>          # Capture the text between the "<" and ">" chars.
  \s*/?\s*              # Optional spaces, optional slash (for closing tags).
  (?<tagname>[a-z]+\b)  # The tag name.
  (?<attributes>        # The tag attributes. Handle a possible ">" in one of them.
    (?:
      (?<quote>["']).*?\k<quote>
      |                 # Or
      [^>]              # Any char not beeing ">"
    )*
  )
  \s*/?\s*              # For self-closing tags such as <img .../>.
)
>                       # Closing of a tag.
~isx
END_OF_REGEX);

// A regular expression to find double-bracketed tags.
define('REGEX_BRACKETED_TAG', <<<'END_OF_REGEX'
~
\[\[            # Opening of a bracketed tag.
(?<tagcontent>  # Capture the text between the brackets.
  .*?
)
\]\]            # Closing of a bracketed tag.
~xs
END_OF_REGEX);

$html = <<<'END_OF_HTML'
<p> Hello < 30 </p> < < < <!-- Test --> &#60;> > > >
<p><span class="icon icon-print">print</SPAN></p>

<LABEL for="firstname">First name:</LABEL>
<input required type="text" id="firstname" name="firstname" /><!-- with self-closing slash -->
<label for="age">Age:</label>
<INPut required type="number" id="age" name="age"><!-- without self-closing slash -->

Shit should not happen with malformed HTML --> <p id="paragraph-58">Isn't closed
Or something not opened </div>
Be carefull with ">" in tag attribute values (seems to be allowed unescaped):
<input type="password" pattern="(?!.*[><]).{8,}" name="password">
<abbr data-symbol=">" title="Greater than">gt</abbr>

<nav class="floating-nav">
  <ul class="menu">
    <li><a href="/">Home</a></li>
    <li><a href="/contact">Contact</a></li>
  </ul>
</nav>

Test with spaces: <
textarea id="text"
         name="text"
         class="decorative"
>Some text< / textarea>
END_OF_HTML;

/**
 * Just to print a title or a step of the operations.
 *
 * @param string $text The text to print.
 * @param bool $is_a_step If set to false then no step counter will be printed
 *                        and incremented.
 * @return void
 */
function printTitle($text, $is_a_step = true) {
    static $counter = 1;
    if ($is_a_step) {
        print "\n\nSTEP $counter : $text\n";
        $counter++;
    } else {
        print "\n\n$text\n";
    }
    print str_repeat('=', LINE_LENGTH) . "\n\n";
}

printTitle('Input HTML:', false);
print $html;

printTitle('Strip out HTML comments');
$output = preg_replace('/<!--.*?-->/', '', $html);
print $output;

printTitle('replace all HTML tags by [[tag]]');
// preg_replace() doesn't support named groups but pre_replace_callback() does, so we'll use $1.
$output = preg_replace(REGEX_HTML_TAG, '[[$1]]', $output);
print $output;

printTitle('replace all < and > by &lt; and &gt;');
$output = htmlspecialchars($output, ENT_HTML5); // ENT_HTML5 will leave single and double quotes.
print $output;

printTitle('replace back [[tag]] by <tag>');
$output = preg_replace(REGEX_BRACKETED_TAG, '<$1>', $output);
print $output;

printTitle('Strip the HTML tags with strip_tags()');
$output = strip_tags($output);
print $output;

// It seems that the crapy strip_tags() doesn't always manage it's job!!!
// So let's see if we find some left HTML tags.
printTitle('Check what strip_tags() did');
if (preg_match_all(REGEX_HTML_TAG, $output, $matches, PREG_SET_ORDER)) {
    print "Oups! strip_tags() didn't clean up everything!\n";
    print "Found " . count($matches) . " occurrences:\n";
    foreach ($matches as $i => $match) {
        $indented_match = preg_replace('/\r?\n/', '$0  ', $match[0]);
        print '- match ' . ($i + 1) . " : $indented_match\n";
    }

    print "\n\nLet's try to do it ourselves, by replacing the matched tags by nothing.\n\n";
    $output = preg_replace(REGEX_HTML_TAG, '', $output);
    print $output;
}
else {
    print "Ok, no tag found.\n";
}

你可以在这里运行:https://onlinephp.io/c/005a3
对于正则表达式,我使用~而不是通常的/来分隔模式和标志。这只是因为我们可以在模式中使用斜杠而不用转义它。
我还使用了x标志作为extended表示法,这样我就可以在我的模式中添加一些注解,并将其写在几行中。
为了可读性和灵活性,我还使用了命名的捕获组,如(?<quote>),这样我们就没有索引,如果我们添加一些其他捕获组,索引可能会移动。使用\k<quote>而不是索引版本\4进行反向引用。
HTML5似乎相当宽容,因为似乎可以将> char放入属性值中,而无需将其替换为&gt;。我想这在过去是不允许的,它变成了“ok/accepted”,以帮助用户在<input>字段上写入可读的pattern属性。我添加了一个密码字段的示例,其中不允许使用<>字符。这是为了展示如何在正则表达式中处理它,通过接受带有单引号或双引号值的属性。

输出:

Input HTML:
============================================================

<p> Hello < 30 </p> < < < <!-- Test --> &#60;> > > >
<p><span class="icon icon-print">print</SPAN></p>

<LABEL for="firstname">First name:</LABEL>
<input required type="text" id="firstname" name="firstname" /><!-- with self-closing slash -->
<label for="age">Age:</label>
<INPut required type="number" id="age" name="age"><!-- without self-closing slash -->

Shit should not happen with malformed HTML --> <p id="paragraph-58">Isn't closed
Or something not opened </div>
Be carefull with ">" in tag attribute values (seems to be allowed unescaped):
<input type="password" pattern="(?!.*[><]).{8,}" name="password">
<abbr data-symbol=">" title="Greater than">gt</abbr>

<nav class="floating-nav">
  <ul class="menu">
    <li><a href="/">Home</a></li>
    <li><a href="/contact">Contact</a></li>
  </ul>
</nav>

Test with spaces: <
textarea id="text"
         name="text"
         class="decorative"
>Some text< / textarea>

STEP 1 : Strip out HTML comments
============================================================

<p> Hello < 30 </p> < < <  &#60;> > > >
<p><span class="icon icon-print">print</SPAN></p>

<LABEL for="firstname">First name:</LABEL>
<input required type="text" id="firstname" name="firstname" />
<label for="age">Age:</label>
<INPut required type="number" id="age" name="age">

Shit should not happen with malformed HTML --> <p id="paragraph-58">Isn't closed
Or something not opened </div>
Be carefull with ">" in tag attribute values (seems to be allowed unescaped):
<input type="password" pattern="(?!.*[><]).{8,}" name="password">
<abbr data-symbol=">" title="Greater than">gt</abbr>

<nav class="floating-nav">
  <ul class="menu">
    <li><a href="/">Home</a></li>
    <li><a href="/contact">Contact</a></li>
  </ul>
</nav>

Test with spaces: <
textarea id="text"
         name="text"
         class="decorative"
>Some text< / textarea>

STEP 2 : replace all HTML tags by [[tag]]
============================================================

[[p]] Hello < 30 [[/p]] < < <  &#60;> > > >
[[p]][[span class="icon icon-print"]]print[[/SPAN]][[/p]]

[[LABEL for="firstname"]]First name:[[/LABEL]]
[[input required type="text" id="firstname" name="firstname" /]]
[[label for="age"]]Age:[[/label]]
[[INPut required type="number" id="age" name="age"]]

Shit should not happen with malformed HTML --> [[p id="paragraph-58"]]Isn't closed
Or something not opened [[/div]]
Be carefull with ">" in tag attribute values (seems to be allowed unescaped):
[[input type="password" pattern="(?!.*[><]).{8,}" name="password"]]
[[abbr data-symbol=">" title="Greater than"]]gt[[/abbr]]

[[nav class="floating-nav"]]
  [[ul class="menu"]]
    [[li]][[a href="/"]]Home[[/a]][[/li]]
    [[li]][[a href="/contact"]]Contact[[/a]][[/li]]
  [[/ul]]
[[/nav]]

Test with spaces: [[
textarea id="text"
         name="text"
         class="decorative"
]]Some text[[ / textarea]]

STEP 3 : replace all < and > by &lt; and &gt;
============================================================

[[p]] Hello &lt; 30 [[/p]] &lt; &lt; &lt;  &amp;#60;&gt; &gt; &gt; &gt;
[[p]][[span class="icon icon-print"]]print[[/SPAN]][[/p]]

[[LABEL for="firstname"]]First name:[[/LABEL]]
[[input required type="text" id="firstname" name="firstname" /]]
[[label for="age"]]Age:[[/label]]
[[INPut required type="number" id="age" name="age"]]

Shit should not happen with malformed HTML --&gt; [[p id="paragraph-58"]]Isn't closed
Or something not opened [[/div]]
Be carefull with "&gt;" in tag attribute values (seems to be allowed unescaped):
[[input type="password" pattern="(?!.*[&gt;&lt;]).{8,}" name="password"]]
[[abbr data-symbol="&gt;" title="Greater than"]]gt[[/abbr]]

[[nav class="floating-nav"]]
  [[ul class="menu"]]
    [[li]][[a href="/"]]Home[[/a]][[/li]]
    [[li]][[a href="/contact"]]Contact[[/a]][[/li]]
  [[/ul]]
[[/nav]]

Test with spaces: [[
textarea id="text"
         name="text"
         class="decorative"
]]Some text[[ / textarea]]

STEP 4 : replace back [[tag]] by <tag>
============================================================

<p> Hello &lt; 30 </p> &lt; &lt; &lt;  &amp;#60;&gt; &gt; &gt; &gt;
<p><span class="icon icon-print">print</SPAN></p>

<LABEL for="firstname">First name:</LABEL>
<input required type="text" id="firstname" name="firstname" />
<label for="age">Age:</label>
<INPut required type="number" id="age" name="age">

Shit should not happen with malformed HTML --&gt; <p id="paragraph-58">Isn't closed
Or something not opened </div>
Be carefull with "&gt;" in tag attribute values (seems to be allowed unescaped):
<input type="password" pattern="(?!.*[&gt;&lt;]).{8,}" name="password">
<abbr data-symbol="&gt;" title="Greater than">gt</abbr>

<nav class="floating-nav">
  <ul class="menu">
    <li><a href="/">Home</a></li>
    <li><a href="/contact">Contact</a></li>
  </ul>
</nav>

Test with spaces: <
textarea id="text"
         name="text"
         class="decorative"
>Some text< / textarea>

STEP 5 : Strip the HTML tags with strip_tags()
============================================================

 Hello &lt; 30  &lt; &lt; &lt;  &amp;#60;&gt; &gt; &gt; &gt;
print

First name:

Age:

Shit should not happen with malformed HTML --&gt; Isn't closed
Or something not opened 
Be carefull with "&gt;" in tag attribute values (seems to be allowed unescaped):

gt

  
    Home
    Contact
  

Test with spaces: <
textarea id="text"
         name="text"
         class="decorative"
>Some text< / textarea>

STEP 6 : Check what strip_tags() did
============================================================

Oups! strip_tags() didn't clean up everything!
Found 2 occurrences:
- match 1 : <
  textarea id="text"
           name="text"
           class="decorative"
  >
- match 2 : < / textarea>

Let's try to do it ourselves, by replacing the matched tags by nothing.

 Hello &lt; 30  &lt; &lt; &lt;  &amp;#60;&gt; &gt; &gt; &gt;
print

First name:

Age:

Shit should not happen with malformed HTML --&gt; Isn't closed
Or something not opened 
Be carefull with "&gt;" in tag attribute values (seems to be allowed unescaped):

gt

  
    Home
    Contact
  

Test with spaces: Some text

正如您所看到的,strip_tags()没有处理标记名周围的空格,我发现这完全不安全!这就是为什么我建议使用HTMLPurifier或DOM解析器之类的库。

相关问题