Практический JS: проблемы innerHTML

Разработка веб-сайтов
Автор оригинала: Julien Lecomte
Примечание: ниже перевод статьи Julien Lecomte «The Problem With innerHTML», в которой автор рассматривает проблемы при использовании метода innerHTML в современных браузерах и предлагает ряд советов, как ее можно избежать. Мои комментарии далее курсивом

Свойство innerHTML крайне популярно среди веб-разработчиков в силу своей простоты и удобства, поскольку оно совершено элементарно позволяет заменить HTML-содержание у конкретного тега. Можно также воспользоваться DOM Level 2 API (removeChild, createElement, appendChild), но использование innerHTML гораздо более простой и эффективный способ для модификации DOM-дерева. Однако, есть ряд проблем при использовании innerHTML, которых следует избегать:

  • Неправильная обработка свойства innerHTML может привести к атакам, связанным со script-инъекциями (XSS) в Internet Explorer, когда HTML-строка содержит вызов <script>, помеченного как отложенный: <script defer>...</script>
  • Выставление свойства innerHTML уничтожит все текущие вложенные HTML-элементы со всеми обработчиками событий, что потенциально может вызвать утечки памяти в некоторых браузерах.


Есть и еще несколько более мелких недостатков, которые тоже стоит упомянуть:

  • Нельзя получить ссылку на только что созданные элементы, вам приходится добавлять код для получения ссылки на них вручную (используя DOM API).
  • Вы не можете выставить innerHTML для всех HTML-элементов во всех браузерах (к примеру, Internet Explorer не позволяет выставить innerHTML для строки таблицы (tr)).




Лично меня больше волнуют вопросы безопасности и использовании памяти, связанные с использованием свойства innerHTML. Однако, описанная проблема далеко не нова, и уже некоторые светлые головы уделили ей внимание и предложили методы, для ее решения.

Douglas Crockford написал функцию purge, которая удаляет некоторые циклические ссылки (circular references), вызванные добавлением обработчиков событий к HTML-элементам, позволяя сборщику мусора (garbage collector) полностью освободить всю память, с ними связанную.

Удаление тега <script> из HTML-строки не так просто, как кажется на первый взгляд. Регулярное выражение, используемое для этой цели, должно быть достаточно сложным, хотя я сам не знаю, охватывает ли оно все возможные случаи. Ниже вариант, который я лично используя в работе:

/<script[^>]*>[\S\s]*?<\/script[^>]*>/ig


Сейчас давайте попробуем совместить обе эти техники в одной функции setInnerHTML (UPD: спасибо всем, кто добавил свои комментарии: я поправил все ошибки/дыры, на которые вы указали. А также решил включить функцию setInnerHTML в YAHOO.util.Dom)

YAHOO.util.Dom.setInnerHTML = function (el, html) {
	el = YAHOO.util.Dom.get(el);
	if (!el || typeof html !== 'string') {
		return null;
	}

	// удаляем циклические ссылки
	(function (o) {

		var a = o.attributes, i, l, n, c;
		if (a) {
			l = a.length;
			for (i = 0; i < l; i += 1) {
				n = a[i].name;
				if (typeof o[n] === 'function') {
					o[n] = null;
				}
			}
		}

		a = o.childNodes;

		if (a) {
			l = a.length;
			for (i = 0; i < l; i += 1) {
				c = o.childNodes[i];

				// Удаляем дочерние узлы 
				arguments.callee(c);

				// Удаляем все обработчики событий,
				// добавленные к элементу через YUI addListener
				YAHOO.util.Event.purgeElement(c);
			}
		}

	})(el);

	// Удаляем все скрипты из HTML-строки и выставляем свойство innerHTML
	el.innerHTML = html.replace(/<script[^>]*>[\S\s]*?<\/script[^>]*>/ig, "");

	// Возвращаем ссылку на первый дочерний узел
	return el.firstChild;
};


Вуаля! Сообщите мне, пожалуйста, если я упустил что-то в реализации данной функции или нужно усовершенствовать регулярное выражение.

UPD: Существует огромное количество путей для вставки вредоносного кода на веб-страницу. Функция setInnerHTML всего лишь нормализирует поведение тега <script> при исполнении по всех браузерах класса А (A-grade browsers). Если вы собираетесь вставить HTML-код, которому нельзя доверять, очистите сначала его на стороне сервера. Для этого существует огромное количество библиотек.

Спасибо всем, кто ознакомился с заметкой. Буду рад ваших замечаниям на тему того как можно улучшить приведенный пример.
Теги:javascriptinnerHTMLXSSЬехопасностьDOMYUIIEme
Хабы: Разработка веб-сайтов
+31
36,4k 63
Комментарии 50

Лучшие публикации за сутки