Найшвидший спосіб уникнути HTML-тегів як HTML-сутностей?


98

Я пишу розширення 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.
Квентін

1
@ Девід Дорвард: можливо, він хотів продезінфікувати дані POST, і сервер неправильно здійснює обхід даних.
Lie Ryan

4
@Lie - якщо так, то рішення: "Заради Піта, виправ сервер, оскільки у тебе велика діра XSS"
Квентін,

2
@ Девід Дорвард: можливо, справа в тому, що він не має контролю над сервером. Нещодавно я потрапив у таку ситуацію, коли писав сценарій greasemonkey, щоб обійти кілька речей, які мені не подобаються, на веб-сайті мого університету; Мені довелося зробити POST на сервері, який я не маю під контролем, і дезінфікувати дані POST за допомогою javascript (оскільки вихідні дані надходять із розширеного текстового поля, а також купи HTML-тегів, які не виконують зворотний шлях на сервері) . Адміністратор веб-сайту проігнорував мій запит на виправлення веб-сайту, тому у мене не було іншого вибору.
Lie Ryan

1
У мене є варіант використання, коли мені потрібно відобразити повідомлення про помилку в div. Повідомлення про помилку може містити HTML та нові рядки. Я хочу уникнути HTML-коду та замінити нові рядки на <br>. Потім помістіть результат у div для відображення.
mozey

Відповіді:


83

Ви можете спробувати передати функцію зворотного виклику, щоб виконати заміну:

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, тому уникайте його. Просто як це. :)
Легкі перегони на Орбіті

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 для екранування буквального тексту. Дуже приємно, я думаю, що ця маленька хитрість збирається знайти інший дім.
Аякс

3
@jazkat Я не використовую цю функцію. Вхідну змінну, яку я використовую, я визначаю у прикладі.
Web_Designer

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.escapeHtmlправильно, але String.escapeпіднімає питання "втекти заради чого?"
Лоуренс Дол

3
Так, гарна ідея. У наші дні я відмовився від розширення прототипу, щоб уникнути конфліктів.
Арам Кочарян

1
Якщо у вашому браузері є підтримка Symbol, замість цього ви можете використовувати це, щоб уникнути забруднення простору імен рядкового ключа. var escape = новий символ ("втеча"); String.prototype [escape] = function () {...}; "текст" [втеча] ();
Аякс

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
Ого, цей неальфановий регулярний вираз є інтенсивним. Я не думаю, що | у виразі потрібно.
Аякс


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


Я не голосував проти, але вся заміна стилю регулярного виразу не зможе кодувати Unicode ... Отже, будь-хто, хто користується іноземною мовою, буде розчарований. Згаданий вище трюк <textarea> дійсно класний і обробляє все швидко і надійно.
Аякс

Регулярний вираз добре працює для мене з низкою нелатинських символів Unicode. Я б не чекав нічого іншого. Як ви думаєте, що це не спрацювало? Ви думаєте про однобайтові кодові сторінки, для яких потрібні сутності HTML? Для цього призначена 3-а та 4-та функції, а явно не 1-а та друга. Мені подобається диференціація.
ygoe

@LonelyPixel Я не думаю, що він побачить ваш коментар, якщо ви його не згадаєте ("Можна повідомити лише одного додаткового користувача; власник допису буде завжди повідомлений")
baptx

Я взагалі не знав, що цільові сповіщення існують. @Ajax, будь ласка, див. Мій коментар вище.
ygoe

@LonelyPixel Я бачу зараз. Чомусь я не думав, що у цій відповіді було замінено стиль текстової області. Я справді думав про подвійні великі значення Unicode в кодовій точці, як мандарин. Я маю на увазі, що можна зробити регулярний вираз досить розумним, але коли ви подивитеся на ярлики, які можуть використовувати продавці браузерів, я відчував би себе досить добре, роблячи ставку, що textarea буде набагато швидшим (ніж цілком компетентний регулярний вираз). Хтось опублікував орієнтир на цю відповідь? Я поклявся, що бачив одного.
Аякс

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

Я не зовсім впевнений , про швидкість, але якщо ви шукаєте для простоти я запропонував би використовувати / підкреслення 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до купи. Він забезпечує найшвидший результат без використання кешування об'єктів (ні на серіалізаторі, ні на текстовому вузлі).

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 з дочірнім текстовим вузлом є найшвидшим. Regex був би найповільнішим (як підтверджують інші коментарі). Звичайно, XMLSerializer може бути швидшим в інших браузерах, але під час мого (обмеженого) тестування a innerHTMLє найшвидшим.


Найшвидший одиночний рядок:

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 () ?


1
Ті роблять щось абсолютно не пов’язане
Каллум,

1
Мабуть, найбільше зловживання словом "повністю", яке я коли-небудь чув. Наприклад, стосовно основного питання теми, він може бути використаний для декодування рядка html (очевидно, з певної причини сховища), незалежно від тегів html, а потім легко кодувати його назад у html знову, коли і якщо потрібно.
suncat100
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.