Самый быстрый способ избежать HTML-тегов как HTML-объектов?

99

Я пишу расширение Chrome , который включает в себя делает много следующей работу: дезинфицирующее строку , которые могут содержать теги HTML, путь преобразования <, >и &в &lt;, &gt;и &amp;, соответственно.

(Другими словами, то же самое, что и PHP htmlspecialchars(str, ENT_NOQUOTES)- я не думаю, что есть реальная необходимость преобразовывать символы двойных кавычек.)

Это самая быстрая функция, которую я нашел до сих пор:

function safe_tags(str) {
    return str.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;') ;
}

Но все еще есть большая задержка, когда мне нужно пропустить несколько тысяч струн за один раз.

Кто-нибудь может улучшить это? Это в основном для строк от 10 до 150 символов, если это имеет значение.

(Одна из моих идей заключалась в том, чтобы не беспокоиться о кодировании знака «больше» - будет ли это реальная опасность?)

каллум
источник
2
Зачем? В большинстве случаев, когда вы хотите это сделать, вы хотите вставить данные в DOM, и в этом случае вам следует забыть об экранировании и просто создать из него textNode.
Quentin
1
@ Дэвид Дорвард: возможно, он хотел очистить данные POST, а сервер не передает данные правильно.
Ли Райан
4
@Lie - если да, то решение - «Ради Пита, исправьте сервер, так как у вас большая дыра XSS»
Квентин
2
@ Дэвид Дорвард: возможно, дело в том, что он не контролирует сервер. Недавно я попал в такую ​​ситуацию, когда я писал сценарий greasemonkey для обхода пары вещей, которые мне не нравятся на веб-сайте моего университета; Мне пришлось выполнить POST на сервере, который у меня не контролируется, и дезинфицировать данные POST с помощью javascript (поскольку необработанные данные поступают из текстового поля с расширенным форматом, и поэтому есть куча тегов html, которые не выполняют двустороннюю передачу на сервере) . Веб-администратор игнорировал мой запрос на исправление веб-сайта, поэтому у меня не было другого выбора.
Ли Райан
1
У меня есть вариант использования, когда мне нужно отобразить сообщение об ошибке в div. Сообщение об ошибке может содержать HTML и символы новой строки. Я хочу избежать HTML и заменить символы новой строки на <br>. Затем поместите результат в div для отображения.
mozey

Ответы:

84

Вы можете попробовать передать функцию обратного вызова для выполнения замены:

var tagsToReplace = {
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;'
};

function replaceTag(tag) {
    return tagsToReplace[tag] || tag;
}

function safe_tags_replace(str) {
    return str.replace(/[&<>]/g, replaceTag);
}

Вот тест производительности: http://jsperf.com/encode-html-entities для сравнения с replaceповторным вызовом функции и использованием метода DOM, предложенного Дмитрием.

Кажется, твой путь быстрее ...

Но зачем тебе это нужно?

Мартейн
источник
2
Нет необходимости убегать >.
6
На самом деле, если вы поместите экранированное значение в атрибут элемента html, вам нужно экранировать символ>. В противном случае это сломало бы тег для этого элемента html.
Златин Златев
1
В обычном тексте экранированные символы встречаются редко. Замену лучше вызывать только при необходимости, если вам важна максимальная скорость:if (/[<>&"]/.test(str) { ... }
Виталий
3
@callum: Нет. Меня не интересует перечисление случаев, когда я думаю, что «что-то может пойти не так» (не в последнюю очередь потому, что это неожиданные / забытые случаи, которые причинят вам вред, и когда вы меньше всего этого ожидаете). Меня интересует кодирование в соответствии со стандартами (так что неожиданные / забытые случаи не могут повредить вам по определению ). Не могу не подчеркнуть, насколько это важно. >- это специальный символ в HTML, поэтому избегайте его. Просто как тот. :)
Lightness Races in Orbit
4
@LightnessRacesinOrbit Это актуально, потому что вопрос в том, какой из возможных методов самый быстрый. Если можно пропустить >замену, это ускорит процесс .
callum
104

Вот один из способов сделать это:

var escape = document.createElement('textarea');
function escapeHTML(html) {
    escape.textContent = html;
    return escape.innerHTML;
}

function unescapeHTML(html) {
    escape.innerHTML = html;
    return escape.textContent;
}

Вот демо.

Веб-дизайнер
источник
Переделал демку. Вот полноэкранная версия: jsfiddle.net/Daniel_Hug/qPUEX/show/light
Web_Designer
13
Не уверен, как / что / почему - но это гениально.
rob_james
4
Похоже, он использует существующий код элемента TextArea для экранирования буквального текста. Очень мило, я думаю, этот маленький трюк поможет найти другой дом.
Ajax
3
@jazkat Я не использую эту функцию. Используемую мной escape-переменную я определяю в этом примере.
Web_Designer 04
2
но теряет ли это пустое пространство и т. д.
Эндрю
31

Метод Мартейна как функция-прототип:

String.prototype.escape = function() {
    var tagsToReplace = {
        '&': '&amp;',
        '<': '&lt;',
        '>': '&gt;'
    };
    return this.replace(/[&<>]/g, function(tag) {
        return tagsToReplace[tag] || tag;
    });
};

var a = "<abc>";
var b = a.escape(); // "&lt;abc&gt;"
Арам Кочарян
источник
12
Добавьте к Stringэтому, что это должно быть escapeHtml, поскольку это не экранирование для String в целом. Это String.escapeHtmlправильно, но String.escapeвозникает вопрос: «Бежать ради чего?».
Лоуренс Дол
3
Да хорошая идея. В последнее время я отказался от расширения прототипа, чтобы избежать конфликтов.
Арам Кочарян
1
Если ваш браузер поддерживает Symbol, вы можете использовать ее вместо этого, чтобы избежать загрязнения пространства имен строковых ключей. var escape = новый символ («escape»); String.prototype [escape] = функция () {...}; «текст» [escape] ();
Ajax
12

Еще более быстрое / короткое решение:

escaped = new Option(html).innerHTML

Это связано с каким-то странным пережитком JavaScript, в котором элемент Option сохраняет конструктор, который выполняет такого рода экранирование автоматически.

Кредит на https://github.com/jasonmoo/t.js/blob/master/t.js

Тодд
источник
1
Аккуратный однострочный, но самый медленный метод после регулярного выражения. Кроме того, в тексте могут быть удалены пробелы в соответствии со спецификацией
ShortFuse
Обратите внимание, что ссылка на «самый медленный метод» @ ShortFuse приводит к тому, что в моей системе заканчивается ОЗУ (с ~ 6 ГБ свободного места), а firefox, похоже, перестает выделять память непосредственно перед тем, как закончится память, поэтому вместо того, чтобы убивать нарушающий процесс, linux будет сидеть там и позволять вам делать резкое отключение питания.
Люк
11

Исходный код AngularJS также имеет версию внутри angular-sanitize.js .

var SURROGATE_PAIR_REGEXP = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
    // Match everything outside of normal chars and " (quote character)
    NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g;
/**
 * Escapes all potentially dangerous characters, so that the
 * resulting string can be safely inserted into attribute or
 * element text.
 * @param value
 * @returns {string} escaped text
 */
function encodeEntities(value) {
  return value.
    replace(/&/g, '&amp;').
    replace(SURROGATE_PAIR_REGEXP, function(value) {
      var hi = value.charCodeAt(0);
      var low = value.charCodeAt(1);
      return '&#' + (((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000) + ';';
    }).
    replace(NON_ALPHANUMERIC_REGEXP, function(value) {
      return '&#' + value.charCodeAt(0) + ';';
    }).
    replace(/</g, '&lt;').
    replace(/>/g, '&gt;');
}
Кевин Хакансон
источник
1
Ух ты, это регулярное выражение без букв является интенсивным. Я не думаю, что | в выражении, тем не менее, необходимо.
Ajax
9

Универсальный сценарий:

// HTML entities Encode/Decode

function htmlspecialchars(str) {
    var map = {
        "&": "&amp;",
        "<": "&lt;",
        ">": "&gt;",
        "\"": "&quot;",
        "'": "&#39;" // ' -> &apos; for XML only
    };
    return str.replace(/[&<>"']/g, function(m) { return map[m]; });
}
function htmlspecialchars_decode(str) {
    var map = {
        "&amp;": "&",
        "&lt;": "<",
        "&gt;": ">",
        "&quot;": "\"",
        "&#39;": "'"
    };
    return str.replace(/(&amp;|&lt;|&gt;|&quot;|&#39;)/g, function(m) { return map[m]; });
}
function htmlentities(str) {
    var textarea = document.createElement("textarea");
    textarea.innerHTML = str;
    return textarea.innerHTML;
}
function htmlentities_decode(str) {
    var textarea = document.createElement("textarea");
    textarea.innerHTML = str;
    return textarea.value;
}

http://pastebin.com/JGCVs0Ts

baptx
источник
Я не голосовал против, но все замены стиля регулярных выражений не смогут кодировать Unicode ... Так что любой, кто использует иностранный язык, будет разочарован. Упомянутый выше трюк с <textarea> действительно крутой и позволяет быстро и надежно справиться со всем.
Ajax
Регулярное выражение отлично работает для меня с рядом нелатинских символов Unicode. Я бы и не ожидал другого. Как вы думаете, это не сработает? Вы думаете о однобайтовых кодовых страницах, для которых требуются объекты HTML? Вот для чего предназначены 3-я и 4-я функции, а не 1-я и 2-я функции. Мне нравится дифференциация.
ygoe
@LonelyPixel Я не думаю, что он увидит ваш комментарий, если вы не упомянете его («Только один дополнительный пользователь может быть уведомлен; владелец сообщения всегда будет уведомлен»)
baptx
Я вообще не знал, что существуют целевые уведомления. @Ajax, пожалуйста, посмотрите мой комментарий выше.
ygoe 01
@LonelyPixel Теперь я вижу. По какой-то причине я не думал, что в этом ответе есть замена стиля текстового поля. Я действительно думал о двойных кодовых точках больших значений Unicode, таких как мандарин. Я имею в виду, что можно было бы сделать регулярное выражение достаточно умным, но если вы посмотрите на ярлыки, которые могут использовать производители браузеров, я буду чувствовать себя довольно уверенно, сделав ставку на то, что текстовое поле будет намного быстрее (чем полностью компетентное регулярное выражение). Кто-то опубликовал тест на этот ответ? Я поклялся, что видел одну.
Ajax
2

function encode(r) {
  return r.replace(/[\x26\x0A\x3c\x3e\x22\x27]/g, function(r) {
	return "&#" + r.charCodeAt(0) + ";";
  });
}

test.value=encode('How to encode\nonly html tags &<>\'" nice & fast!');

/*
 \x26 is &ampersand (it has to be first),
 \x0A is newline,
 \x22 is ",
 \x27 is ',
 \x3c is <,
 \x3e is >
*/
<textarea id=test rows=11 cols=55>www.WHAK.com</textarea>

Дэйв Браун
источник
1

Я не совсем уверен в скорости, но если вы ищете простоты, я бы предложил использовать escape- функцию lodash / подчеркивания .

гильматический
источник
0

Метод Мартейн как одной функции с обработкой " знак ( с использованием в JavaScript ):

function escapeHTML(html) {
    var fn=function(tag) {
        var charsToReplace = {
            '&': '&amp;',
            '<': '&lt;',
            '>': '&gt;',
            '"': '&#34;'
        };
        return charsToReplace[tag] || tag;
    }
    return html.replace(/[&<>"]/g, fn);
}
Иман
источник
0

Добавлю XMLSerializerв кучу. Он обеспечивает самый быстрый результат без использования кэширования объектов (ни в сериализаторе, ни в узле Text).

function serializeTextNode(text) {
  return new XMLSerializer().serializeToString(document.createTextNode(text));
}

Дополнительным бонусом является то, что он поддерживает атрибуты, которые сериализуются иначе, чем текстовые узлы:

function serializeAttributeValue(value) {
  const attr = document.createAttribute('a');
  attr.value = value;
  return new XMLSerializer().serializeToString(attr);
}

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

Что касается производительности, то он самый быстрый без кеширования. Когда вы разрешаете кэширование, то вызов innerHTMLHTMLElement с дочерним узлом Text будет самым быстрым. Regex будет самым медленным (как доказывают другие комментарии). Конечно, XMLSerializer мог бы быть быстрее в других браузерах, но в моем (ограниченном) тестировании innerHTMLсамым быстрым был a .


Самая быстрая одиночная строка:

new XMLSerializer().serializeToString(document.createTextNode(text));

Самый быстрый с кешированием:

const cachedElementParent = document.createElement('div');
const cachedChildTextNode = document.createTextNode('');
cachedElementParent.appendChild(cachedChildTextNode);

function serializeTextNode(text) {
  cachedChildTextNode.nodeValue = text;
  return cachedElementParent.innerHTML;
}

https://jsperf.com/htmlentityencode/1

Короткий фитиль
источник
-3

Немного поздно к выставке, но что не так с использованием encodeURIComponent () и decodeURIComponent () ?

suncat100
источник
1
Те делают что-то совершенно не связанное с этим
callum
1
Возможно, самое большое злоупотребление словом «полностью», которое я когда-либо слышал. Например, что касается основного вопроса темы, его можно использовать для декодирования строки html (очевидно, по какой-то причине хранения), независимо от тегов html, а затем легко снова кодировать ее обратно в html, когда и если это необходимо.
suncat100 05