PHP функция для удаления определенных HTML-тегов и атрибутов

В течение долгого времени я пользуюсь функцией PHP HTML “очистки” пользовательского ввода, чтобы гарантировать, что никакой вредоносный код не будет размещен нарочно или случайно. Но при этом я разрешаю использовать определенные HTML-теги и атрибуты:

shutterstock_377372347

Большая часть функций для работы с HTML-кодом используют регулярные выражения, а это сплошная головная боль. В течение длительного времени созданная мной функция работала достаточно хорошо, пока мне не понадобилось разрешить внутри некоторых тегов data-атрибуты HTML5.

Они выглядят следующим образом:

data-conversation=»none»

После “data” идет имя переменной для соответствующего типа данных. И здесь старая версия моей функции не смогла справиться с этой переменной частью имени, и мне приходилось добавлять каждый тип данных атрибута в используемый мной список.

В итоге я разработал улучшенную функцию, которая использует PHP библиотеку DOMDocument, чтобы создать объектную модель документа и удалить всё кроме тегов и атрибутов, которые необходимо сохранить.

Новый подход позволяет использовать регулярное выражение только для выбранных типов данных — не надо искать эти атрибуты в HTML-коде. Задача поиска тегов и атрибутов осуществляется с помощью XPath методов PHP DOMDocument.

Новая функция удаления HTML тегов в PHP принимает строку для “очистки” и два массива в качестве параметров; массивы — список разрешенных тегов и список допустимых атрибутов. Если атрибуты “href” или “src” разрешены, функция проверяет: если значение атрибута – это код JavaScript, то изменяет его на “#”:

<?php
function stripTagsAttributes($html, $allowedTags = array(), $allowedAttributes = array('(?:a^)')) {
    if (!empty($html)) {
        $theTags = count($allowedTags) ? '<' . implode('><', $allowedTags) . '>' : '';
        $theAttributes = '%' . implode('|', $allowedAttributes) . '%i';
        $dom = @DOMDocument::loadHTML(
            mb_convert_encoding(
                strip_tags(
                    $html,
                    $theTags
                ),
                'HTML-ENTITIES',
                'UTF-8'
            )
        );
        $xpath = new DOMXPath($dom);
        $tags = $xpath->query('//*');
        foreach ($tags as $tag) {
            $attrs = array();
            for ($i = 0; $i < $tag->attributes->length; $i++) {
                $attrs[] = $tag->attributes->item($i)->name;
            }
            foreach ($attrs as $attribute) {
                if (!preg_match($theAttributes, $attribute)) {
                    $tag->removeAttribute($attribute);
                } elseif (preg_match('%^(?:href|src)$%i', $attribute) and preg_match('%^javascript:%i', $tag->getAttribute($attribute))) {
                    $tag->setAttribute($attribute, '#');
                }
            }
        }
        return (
            trim(
                strip_tags(
                    html_entity_decode(
                        $dom->saveHTML()
                    ),
                    $theTags
                )
            )
        );
    }
}
?>

Перед тем, как с помощью PHP убрать HTML теги, расскажу о нескольких проблемах, связанных с использованием DOMDocument:

  1. Если вы парсите фрагмент кода вместо всей страницы, и в нем нет тега кодировки символов, то DOMDocument будет считать, что текст закодирован в ISO-8859 вместо UTF-8. Поэтому, прежде чем загрузить фрагмент кода, эта функция использует mb_convert_encoding, чтобы преобразовать любые символы Unicode в объекты HTML. Затем использует html_entity_decode, чтобы преобразовать объекты назад в символы, когда парсинг завершится;
  2. Когда вы парсите HTML- фрагмент, DOMDocument всегда добавляет DOCTYPE, <HTML> и <Body> теги к фрагменту, и вы не можете отключить эту «опцию«. Поэтому после парсинга моя функция использует strip_tags во второй раз, чтобы удалить лишние и ненужные теги.

Два массива тегов и атрибутов похожи на комментарии. Заметьте, что последний элемент в массиве $commentAttributes — простое регулярное выражение, которое соответствует любому типу данных атрибута:

<?php
$commentTags = array(
    'b',
    'i',
    'a',
    'strong',
    'em',
    'blockquote',
    'div',
    'p',
    'br',
    'strike',
    'del',
    'sup',
    'sub',
    'code',
    'pre',
    'span',
    'img',
    'button',
);
$commentAttributes = array(
    'href',
    'rel',
    'target',
    'src',
    'width',
    'height',
    'class',
    'data-S*'
);
?>

Кстати, при удалении HTML тегов из PHP соблюдаются дополнительные меры санитизации (где добавлены комментарии). Например, изображения могут быть встроены только в комментарии, если они загружены и размещены на сайте, иначе они превращаются в ссылки на внешние изображения. Это необходимо для предотвращения распространения изображений, содержащих вредоносный код, порно и т. д.

Перевод статьи “Tech Note: A PHP Function to Strip Specific HTML Tags and Attributes” был подготовлен дружной командой проекта Сайтостроение от А до Я.