удалить тег скрипта из содержимого HTML

Я использую очиститель HTML (http://htmlpurifier.org/)

Я просто хочу удалить только теги <script>. Я не хочу удалять встроенное форматирование или что-то еще.

Как я могу этого добиться?

Еще одна вещь, есть ли другой способ удалить теги скрипта из HTML


person I-M-JM    schedule 20.08.2011    source источник
comment
Имейте в виду, что теги скриптов - не единственные уязвимые части HTML.   -  person Karolis    schedule 20.08.2011
comment
Да, я знаю и о других уязвимых частях, но мне просто нужно удалить теги скрипта.   -  person I-M-JM    schedule 20.08.2011
comment
Прочтите это. Это поможет тебе   -  person Jose Adrian    schedule 20.08.2011
comment
@ Хосе, черт возьми, нет. прочтите это stackoverflow.com/questions/1732348/ нет регулярного выражения для синтаксического анализа HTML   -  person Madara's Ghost    schedule 20.08.2011
comment
Этот вопрос уже задавали много раз, например. здесь или здесь, но остерегайтесь это.   -  person dma_k    schedule 20.08.2011
comment
@Rikudo Ну ... если ему нужно использовать регулярное выражение для удаления тегов html ... должна быть причина. Спасибо за ссылку!   -  person Jose Adrian    schedule 20.08.2011
comment
@Jose причина в том, что вы не знакомы с другими лучшими инструментами. По той же причине люди до сих пор используют mysql_* функции в php.   -  person Madara's Ghost    schedule 20.08.2011
comment
@Rikudo Sennin - или вообще PHP. :)   -  person Malvolio    schedule 20.08.2011
comment
@Malvolio неххх, это уж слишком далеко: P   -  person Madara's Ghost    schedule 20.08.2011
comment
@Rikudo Использование регулярного выражения для синтаксического анализа HTML имеет свои преимущества и недостатки. Его полезность зависит от конкретной ситуации. Не будь таким фанатиком. Мир намного сложнее, и одно и то же правило нельзя использовать для всех целей. Да, во многих случаях регулярное выражение - не лучший инструмент для синтаксического анализа HTML, но это ничего не значит.   -  person Karolis    schedule 20.08.2011
comment
Однако очевидно, что в большинстве случаев использование регулярных выражений очень неэффективно и небезопасно. Очень проблематично использовать парсер, который не понимает язык, на котором он разбирается. Вот почему существуют специальные парсеры HTML и XML.   -  person Madara's Ghost    schedule 20.08.2011
comment
@Rikudo Вы пытаетесь использовать одно правило для всего :) Потом вы увидите, что не все так просто.   -  person Karolis    schedule 20.08.2011
comment
Что касается спора о синтаксическом анализаторе html и регулярном выражении - вам, вероятно, понадобится и то, и другое; имейте в виду, что синтаксический анализатор html не распознает условные комментарии, что означает, что IE с радостью отобразит в них теги сценария. Общая проблема с элегантным решением этой проблемы заключается в том, что браузерам все равно ...   -  person jgivoni    schedule 18.01.2013


Ответы (13)


Поскольку этот вопрос помечен тегами regex, я собираюсь ответить бедным решением в этой ситуации:

$html = preg_replace('#<script(.*?)>(.*?)</script>#is', '', $html);

Однако регулярные выражения не предназначены для синтаксического анализа HTML / XML, даже если вы напишете идеальное выражение, оно в конечном итоге сломается, оно того не стоит, хотя в некоторых случаях полезно быстро исправить некоторую разметку и как это бывает с быстрыми исправлениями, забудьте о безопасности. Используйте регулярное выражение только для контента / разметки, которым вы доверяете.

Помните, что все, что вводит пользователь, следует считать небезопасным.

Лучшим решением здесь было бы использовать DOMDocument, который предназначен для этого. Вот фрагмент, демонстрирующий, насколько легко, чисто (по сравнению с регулярным выражением), (почти) надежно и (почти) безопасно делать то же самое:

<?php

$html = <<<HTML
...
HTML;

$dom = new DOMDocument();

$dom->loadHTML($html);

$script = $dom->getElementsByTagName('script');

$remove = [];
foreach($script as $item)
{
  $remove[] = $item;
}

foreach ($remove as $item)
{
  $item->parentNode->removeChild($item); 
}

$html = $dom->saveHTML();

Я удалил HTML намеренно, потому что даже это может сработать.

person Dejan Marjanović    schedule 20.08.2011
comment
-1 для решения RegExp. См. это обсуждение. - person Alex; 20.08.2011
comment
Я видел это обсуждение давным-давно, вы должны его прочитать, а не просто увидеть. - person Dejan Marjanović; 20.08.2011
comment
Хотя я ценю ваш отчужденный ответ, я не одобряю ваш ответ. См. эту суть для созданного тега сценария, который обходит ваше регулярное выражение. Честно говоря, это, возможно, скорее недостаток вашего конкретного регулярного выражения, чем причина полностью отказаться от регулярного выражения. Но, мне все равно интересно. - person Alex; 08.12.2011
comment
Это конкретное регулярное выражение уязвимо для внедрения JavaScript. - person jmlnik; 31.03.2012
comment
@ParijatKalia - это глупая идея отображать удаленный HTML со скриптом или без него, какая разница? Если вы абсолютно уверены в содержании, я сомневаюсь, что вы столкнетесь с HTML, как вы написали. Кстати, я ответил регулярным выражением только потому, что вопросы были так помечены. - person Dejan Marjanović; 23.04.2013
comment
Если вы хотите использовать маршрут регулярного выражения, убедитесь, что вы запускаете prey_replace несколько раз, пока результат не перестанет меняться (улавливает пример ввода от @ParijatKalia). - person Mark; 22.08.2013
comment
Просто из интереса, зачем у вас два foreachloops? Почему не просто foreach($scripts as $script){$script->parentNode->removeChild($script);}? - person Arth; 16.12.2014
comment
@Arth, потому что вы не получите правильных результатов (итератор ведет себя не так, как ожидалось), см. этот комментарий. - person Dejan Marjanović; 17.12.2014
comment
@webarto Спасибо за ответ, особенно за реф! - person Arth; 17.12.2014
comment
почему #is для регулярного выражения? - person Arnold Roa; 18.12.2014
comment
Ради аргумента. Иногда необходимо использовать регулярное выражение для удаления тегов из содержимого. Конечно, мы все знаем, что это плохо, но иногда вам НЕОБХОДИМО использовать регулярное выражение. DOMDocument не будет работать, если это не HTML. Но допустим, вы импортируете контент из Drupal в WordPress ... DOMDocument не будет работать, поскольку это не настоящий HTML в контенте, а просто текст с разметкой в ​​нем. Это когда вам НЕОБХОДИМО использовать регулярное выражение, поскольку вы хотите сохранить большинство тегов, но удалить теги сценария, поскольку их в любом случае там не должно быть. Конечно, используйте DOMDocument, если можете, но сказать, что вам не следует использовать регулярное выражение для этого, просто невежественно. - person Jeremy; 09.02.2015
comment
Вы, ненавистники регулярных выражений, ведете себя так, будто DOMDocument безопаснее. Это не. - person jchook; 17.03.2016
comment
как заставить парсер DOMDocument не добавлять теги Doctype, HTML и BODY? - person Mike; 17.06.2016
comment
Спасибо за ответ, но я второй комментарий Майка выше. Если я работаю с фрагментом HTML, я не был бы признателен за добавление других вещей, таких как saveHTML, очевидно. - person DrLightman; 03.11.2016
comment
В решении с регулярным выражением, я думаю, вам следует избегать / в </script, иначе он будет рассматривать конец как модификаторы: ОШИБКА: Неизвестный модификатор 'c' - person Kyborek; 25.11.2016
comment
Чтобы избежать добавления тегов DOCTYPE, html и body, см. этот ответ. - person Wiktor Stribiżew; 30.10.2017
comment
Обратите внимание, что это нарушает синтаксический анализ DOMDocument при использовании loadHTML () из-за разметки HTML в строке Javascript: <div> <script> var str = '</div>this does NOT get removed'; </script> </div> - person Matthew Kolb; 28.09.2018
comment
saveHtml () добавит лишний ненужный html в строку, например: ‹! DOCTYPE html PUBLIC - // W3C // DTD HTML 4.0 Transitional // EN w3.org/TR/REC-html40/loose.dtd› ‹html› ‹body› ‹p› для получения дополнительной информации см. 3v4l.org/1TNHP - person relipse; 03.01.2020
comment
А как насчет <SCRIPT>alert(123)</SCRIPT> заглавных или смешанных тегов? - person Zsolt Janes; 09.04.2020
comment
Решение DOMDocument не работает для меня, оно помещает ‹p› внутри тега ‹h1›, таким образом испортив весь html. - person ; 17.06.2020

Используйте парсер DOMDocument PHP.

$doc = new DOMDocument();

// load the HTML string we want to strip
$doc->loadHTML($html);

// get all the script tags
$script_tags = $doc->getElementsByTagName('script');

$length = $script_tags->length;

// for each tag, remove it from the DOM
for ($i = 0; $i < $length; $i++) {
  $script_tags->item($i)->parentNode->removeChild($script_tags->item($i));
}

// get the HTML string back
$no_script_html_string = $doc->saveHTML();

Это помогло мне использовать следующий HTML-документ:

<!doctype html>
<html>
    <head>
        <meta charset="utf-8">
        <title>
            hey
        </title>
        <script>
            alert("hello");
        </script>
    </head>
    <body>
        hey
    </body>
</html>

Просто имейте в виду, что для парсера DOMDocument требуется PHP 5 или выше.

person Alex    schedule 20.08.2011
comment
+0 Мне надоело слышать об этом обсуждении регулярных выражений и HTML. В некоторых очень особых случаях должно быть нормально использовать регулярное выражение. В моем случае я получаю такую ​​ошибку: Warning: DOMDocument::loadHTML() [domdocument.loadhtml]: Tag myCustomTag invalid in Entity. Все перепробовал. Все, что я хочу сделать, это удалить теги сценария для одной крошечной части приложения (без тратить на это больше времени). Я собираюсь использовать preg_replace, и все. Я не хочу больше об этом слышать. :) - person Yes Barry; 07.12.2011
comment
Смотрите мой комментарий к выбранному лучшему ответу. Я бы предпочел, чтобы кодеры охватывали общие случаи, поскольку злоумышленники могут стать очень умными. Однако вы правы: например, при разработке внутреннего приложения можно считать нормальным игнорировать такие уязвимости и использовать регулярное выражение. - person Alex; 08.12.2011
comment
@Xeoncross Спасибо! Я попробую в следующий раз, когда у меня будет возможность поработать над этим. В данный момент я занят другим кодом и не хочу копаться в этом материале :). - person Yes Barry; 10.02.2012
comment
DOMDocument и SimpleXML можно использовать для загрузки файлов вне корня вашего документа. Используйте libxml_disable_entity_loader (true), чтобы отключить эту функцию libxml. php.net/manual/en/function.libxml- disable-entity-loader.php - person txyoji; 20.07.2012
comment
этот код выдаст 'Fatal error: Call to a member function removeChild() on null', если у вас будет пустой тег, например <script src="..."></script> - person SPi; 01.07.2015
comment
@Spi Интересно. Вы знаете, как исправить это в коде? - person Alex; 02.07.2015
comment
@SPi У меня все те же ошибки. У меня это сработало (я все же использовал ваш как основу, так что спасибо ...): // load HTML $dom = new DOMDocument; $dom->loadHTML($html_to_parse); // remove all scripts while (true) { $script = $dom->getElementsByTagName('script')->item(0); if ($script != NULL) { $script->parentNode->removeChild($script); } else { break; } } - person Paul; 07.09.2016
comment
Обратите внимание, что это нарушает синтаксический анализ DOMDocument при использовании loadHTML () из-за разметки HTML в строке Javascript: <div> <script> var str = '</div>this does NOT get removed'; </script> </div> - person Matthew Kolb; 28.09.2018
comment
Спасибо за обновление @MatthewKolb. Жаль, что это больше не работает (какую версию PHP вы используете?); ты знаешь, есть ли что-нибудь более подходящее? - person Alex; 30.09.2018
comment
@Alex Я использую php 5.6.35. Ваш пример по-прежнему отлично работает - до тех пор, пока JS не включает теги HTML. Я читал, что loadXML () лучше справится с подобным случаем, но, похоже, он вообще не может загрузить DOM, поскольку считает ввод недопустимым XML. Я не нашел лучшего решения, чем использовать REGEX для удаления скриптов перед загрузкой в ​​DOMDocument. - person Matthew Kolb; 01.10.2018

Простой способ манипулировать строкой.

$str = stripStr($str, '<script', '</script>');

function stripStr($str, $ini, $fin)
{
    while(($pos = mb_stripos($str, $ini)) !== false)
    {
        $aux = mb_substr($str, $pos + mb_strlen($ini));
        $str = mb_substr($str, 0, $pos).mb_substr($aux, mb_stripos($aux, $fin) + mb_strlen($fin));
    }

    return $str;
}
person José Carlos PHP    schedule 31.10.2018
comment
Для этого требуется MBString, верно? - person Someone_who_likes_SE; 21.07.2021
comment
@Someone_who_likes_SE Да, конечно. Вы можете использовать stripos и substr вместо mb_stripos и mb_substr, но я предпочитаю использовать функции MB, они более надежны. - person José Carlos PHP; 23.07.2021

Я бы использовал BeautifulSoup, если он доступен. Делает такие вещи очень легкими.

Не пытайтесь делать это с помощью регулярных выражений. В этом безумие.

person Malvolio    schedule 20.08.2011
comment
Почему бы не использовать регулярное выражение для этой простой операции? - person Dejan Marjanović; 20.08.2011
comment
@webarto См. это обсуждение - person Alex; 20.08.2011
comment
@Alex, я знаю это, но почему бы не использовать это здесь? - person Dejan Marjanović; 20.08.2011
comment
Из-за ответа, на который я ссылался. Это небезопасно или какая-либо гарантия. HTML / XML - гораздо лучшее решение. - person Alex; 20.08.2011

Я боролся с этим вопросом. Я обнаружил, что вам действительно нужна только одна функция. взорваться ('>', $ html); Единственный общий знаменатель для любого тега - ‹и>. После этого обычно используются кавычки ("). Вы можете так легко извлечь информацию, как только найдете общий знаменатель. Вот что я придумал:

$html = file_get_contents('http://some_page.html');

$h = explode('>', $html);

foreach($h as $k => $v){

    $v = trim($v);//clean it up a bit

    if(preg_match('/^(<script[.*]*)/ius', $v)){//my regex here might be questionable

        $counter = $k;//match opening tag and start counter for backtrace

        }elseif(preg_match('/([.*]*<\/script$)/ius', $v)){//but it gets the job done

            $script_length = $k - $counter;

            $counter = 0;

            for($i = $script_length; $i >= 0; $i--){
                $h[$k-$i] = '';//backtrace and clear everything in between
                }
            }           
        }
for($i = 0; $i <= count($h); $i++){
    if($h[$i] != ''){
    $ht[$i] = $h[$i];//clean out the blanks so when we implode it works right.
        }
    }
$html = implode('>', $ht);//all scripts stripped.


echo $html;

Я вижу, что это действительно работает только для тегов сценариев, потому что у вас никогда не будет вложенных тегов сценариев. Конечно, вы можете легко добавить дополнительный код, который выполняет ту же проверку и собирает вложенные теги.

Я называю это кодированием аккордеона. implode (); взорваться (); - это самый простой способ развить логику, если у вас есть общий знаменатель.

person ClandestineCoder    schedule 15.04.2013

Короче:

$html = preg_replace("/<script.*?\/script>/s", "", $html);

При выполнении регулярного выражения что-то может пойти не так, поэтому безопаснее сделать так:

$html = preg_replace("/<script.*?\/script>/s", "", $html) ? : $html;

Чтобы в случае «аварии» мы получили исходный $ html вместо пустой строки.

person Binh WPO    schedule 25.03.2015

  • это сочетание ClandestineCoder и Binh WPO.

проблема со стрелками тегов скрипта в том, что у них может быть более одного варианта

бывший. (‹= &lt; = &amp;lt;) & (> = &gt; = &amp;gt;)

поэтому вместо создания массива шаблонов с вариантом bazillion, имхо лучшим решением было бы

return preg_replace('/script.*?\/script/ius', '', $text)
       ? preg_replace('/script.*?\/script/ius', '', $text)
       : $text;

это удалит все, что выглядит как script.../script, независимо от кода / варианта стрелки, и вы можете проверить это здесь https://regex101.com/r/lK6vS8/1

person ctf0    schedule 31.07.2016

Пример изменения ответа ctf0. Это должно выполнить preg_replace только один раз, но также проверить наличие ошибок и заблокировать код символа для прямой косой черты.

$str = '<script> var a - 1; <&#47;script>'; 

$pattern = '/(script.*?(?:\/|&#47;|&#x0002F;)script)/ius';
$replace = preg_replace($pattern, '', $str); 
return ($replace !== null)? $replace : $str;  

Если вы используете php 7, вы можете использовать оператор объединения null, чтобы еще больше упростить его.

$pattern = '/(script.*?(?:\/|&#47;|&#x0002F;)script)/ius'; 
return (preg_replace($pattern, '', $str) ?? $str); 
person tech-e    schedule 22.03.2017
comment
У этого есть одно падение, если кто-то использует файлы из папки сценариев в html, например: ‹img src = / script / email / img.jpg› .. ‹img src = / script / email / img-0.jpg ›. Это создаст ловушку, которая удалит все, что между ними. - person tech-e; 24.03.2017

Это упрощенный вариант ответа Деяна Марьяновича:

function removeTags($html, $tag) {
    $dom = new DOMDocument();
    $dom->loadHTML($html);
    foreach (iterator_to_array($dom->getElementsByTagName($tag)) as $item) {
        $item->parentNode->removeChild($item);
    }
    return $dom->saveHTML();
}

Может использоваться для удаления любых тегов, включая <script>:

$scriptlessHtml = removeTags($html, 'script');
person mae    schedule 21.01.2018

function remove_script_tags($html){
    $dom = new DOMDocument();
    $dom->loadHTML($html);
    $script = $dom->getElementsByTagName('script');

    $remove = [];
    foreach($script as $item){
        $remove[] = $item;
    }

    foreach ($remove as $item){
        $item->parentNode->removeChild($item);
    }

    $html = $dom->saveHTML();
    $html = preg_replace('/<!DOCTYPE.*?<html>.*?<body><p>/ims', '', $html);
    $html = str_replace('</p></body></html>', '', $html);
    return $html;
}

Ответ Деяна был хорош, но saveHTML () добавляет ненужные теги doctype и body, это должно избавить от него. См. https://3v4l.org/82FNP.

person relipse    schedule 02.01.2020
comment
Нет, это loadHTML(...) функция добавляет это. См. LIBXML_HTML_NODEFDTD и LIBXML_HTML_NOIMPLIED здесь: php.net/manual/en/libxml.constants. php - person James Anderson Jr.; 08.05.2020

Попробуйте это полное и гибкое решение. Он работает отлично и частично основан на некоторых предыдущих ответах, но содержит дополнительные проверки и избавляет от дополнительного подразумеваемого HTML из функции loadHTML(...). Он разделен на две отдельные функции (одна с предыдущей зависимостью, поэтому не переупорядочивайте / не переставляйте), поэтому вы можете использовать ее с несколькими тегами HTML, которые вы хотите удалить одновременно (т. Е. Не только 'script' теги). Например, функция removeAllInstancesOfTag(...) принимает array имен тегов или, возможно, только одно как string. Итак, без лишних слов, вот код:


/* Remove all instances of a particular HTML tag (e.g. <script>...</script>) from a variable containing raw HTML data. [BEGIN] */

/* Usage Example: $scriptless_html = removeAllInstancesOfTag($html, 'script'); */

if (!function_exists('removeAllInstancesOfTag'))
    {
        function removeAllInstancesOfTag($html, $tag_nm)
            {
                if (!empty($html))
                    {
                        $html = mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'); /* For UTF-8 Compatibility. */
                        $doc = new DOMDocument();
                        $doc->loadHTML($html,LIBXML_HTML_NOIMPLIED|LIBXML_HTML_NODEFDTD|LIBXML_NOWARNING);

                        if (!empty($tag_nm))
                            {
                                if (is_array($tag_nm))
                                    {
                                        $tag_nms = $tag_nm;
                                        unset($tag_nm);

                                        foreach ($tag_nms as $tag_nm)
                                            {
                                                $rmvbl_itms = $doc->getElementsByTagName(strval($tag_nm));
                                                $rmvbl_itms_arr = [];

                                                foreach ($rmvbl_itms as $itm)
                                                    {
                                                        $rmvbl_itms_arr[] = $itm;
                                                    };

                                                foreach ($rmvbl_itms_arr as $itm)
                                                    {
                                                        $itm->parentNode->removeChild($itm);
                                                    };
                                            };
                                    }
                                else if (is_string($tag_nm))
                                    {
                                        $rmvbl_itms = $doc->getElementsByTagName($tag_nm);
                                        $rmvbl_itms_arr = [];

                                        foreach ($rmvbl_itms as $itm)
                                            {
                                                $rmvbl_itms_arr[] = $itm;
                                            };

                                        foreach ($rmvbl_itms_arr as $itm)
                                            {
                                                $itm->parentNode->removeChild($itm); 
                                            };
                                    };
                            };

                        return $doc->saveHTML();
                    }
                else
                    {
                        return '';
                    };
            };
    };

/* Remove all instances of a particular HTML tag (e.g. <script>...</script>) from a variable containing raw HTML data. [END] */

/* Remove all instances of dangerous and pesky <script> tags from a variable containing raw user-input HTML data. [BEGIN] */

/* Prerequisites: 'removeAllInstancesOfTag(...)' */

if (!function_exists('removeAllScriptTags'))
    {
        function removeAllScriptTags($html)
            {
                return removeAllInstancesOfTag($html, 'script');
            };
    };

/* Remove all instances of dangerous and pesky <script> tags from a variable containing raw user-input HTML data. [END] */


А вот пример использования test:


$html = 'This is a JavaScript retention test.<br><br><span id="chk_frst_scrpt">Congratulations! The first \'script\' tag was successfully removed!</span><br><br><span id="chk_secd_scrpt">Congratulations! The second \'script\' tag was successfully removed!</span><script>document.getElementById("chk_frst_scrpt").innerHTML = "Oops! The first \'script\' tag was NOT removed!";</script><script>document.getElementById("chk_secd_scrpt").innerHTML = "Oops! The second \'script\' tag was NOT removed!";</script>';
echo removeAllScriptTags($html);

Надеюсь, мой ответ кому-то действительно поможет. Наслаждаться!

person James Anderson Jr.    schedule 06.05.2020

person    schedule
comment
Я поддержал этот ответ, потому что, во-первых, он чистый и простой, а также напомнил мне, что iframe также могут вызвать у меня проблемы. - person soger; 06.12.2018
comment
Кроме того, я только что понял, что это добавляет теги doctype, html и body, что нормально для текущего вопроса, но не для меня, но мне нужно было изменить только одну строку (как говорится в верхнем комментарии на saveHTML php.net Страница 1_ - person soger; 06.12.2018

person    schedule
comment
Я не знаю, почему люди продолжают спорить о DOMDocument и каком-то регулярном выражении как о решении, а не о решении. Мне нравится ответ этого парня - просто использовать php str_replace (но я бы использовал str_ireplace из-за нечувствительности к регистру). Если у вас нет тонны вещей, которые вы хотите удалить, это кажется самым простым и эффективным решением. Я говорю своим пользователям, что не могут вставлять или вводить такие вещи. Если они это сделают, то не повезет - его уберут. - person McAuley; 27.10.2018
comment
Это решение сохраняет код javascript внутри строки html. Это шутка, а не хорошее решение! Тем не менее, вы можете пойти далеко и удалиться от ‹скрипта до ‹/script›. Что это могло быть хорошим решением. - person José Carlos PHP; 31.10.2018
comment
я замените ‹SCRIPT на‹! - и ‹/SCRIPT› на -! ›было бы лучше - person NeoTechni; 29.05.2021