HTML-кодування втрачається, коли атрибут читається з поля введення


745

Я використовую JavaScript, щоб витягнути значення із прихованого поля та відобразити його в текстовому полі. Значення в прихованому полі кодується.

Наприклад,

<input id='hiddenId' type='hidden' value='chalk &amp; cheese' />

втягується

<input type='text' value='chalk &amp; cheese' />

через деякий jQuery, щоб отримати значення з прихованого поля (саме в цей момент я втрачаю кодування):

$('#hiddenId').attr('value')

Проблема полягає в тому, що коли я читаю chalk &amp; cheeseз прихованого поля, JavaScript, здається, втрачає кодування. Я не хочу, щоб цінність була chalk & cheese. Я хочу, щоб буквальне amp;було збережене.

Чи існує бібліотека JavaScript або метод jQuery, який кодує HTML-рядок?


Чи можете ви показати Javascript, який ви використовуєте?
Сінан Тайфур

1
додали, як я отримую цінність із прихованого поля
AJM

5
НЕ використовуйте метод innerHTML (метод jQuery .html () використовує innerHTML), так як у деяких браузерах (я лише перевіряв Chrome) це не вийде з лапок, тож якщо ви мали би ввести своє значення у значення атрибута , у вас з'явиться вразливість XSS.
Джеймс Ропер

21
в якому контексті chalkі cheeseколи-небудь використовувати разом 0_o
d -_- b

2
@d -_- b при порівнянні двох елементів. приклад. вони настільки ж різні, як крейда та сир;)
Анураг

Відповіді:


1067

EDIT: Ця відповідь була розміщена давно, і htmlDecodeфункція ввела вразливість XSS. Він був змінений, змінюючи тимчасовий елемент з а divна textareaзменшення шансу XSS. Але сьогодні я б закликав вас використовувати API DOMParser, як це запропоновано в іншій відповіді .


Я використовую ці функції:

function htmlEncode(value){
  // Create a in-memory element, set its inner text (which is automatically encoded)
  // Then grab the encoded contents back out. The element never exists on the DOM.
  return $('<textarea/>').text(value).html();
}

function htmlDecode(value){
  return $('<textarea/>').html(value).text();
}

В основному елемент textarea створюється в пам'яті, але він ніколи не додається до документа.

У htmlEncodeфункції я встановлюю innerTextелемент і отримую закодований innerHTML; на htmlDecodeфункції я встановлюю innerHTMLзначення елемента іinnerText витягується.

Перевірте приклад запуску тут .


95
Це працює для більшості сценаріїв, але ця реалізація htmlDecode усуне зайвий пробіл. Так для деяких значень "input", input! = HtmlDecode (htmlEncode (input)). Це було проблемою для нас у деяких сценаріях. Наприклад, якщо input = "<p> \ t Привіт \ n Там </p>", кодування / декодування турецького коду дасть "<p> Привіт там </p>". У більшості випадків це нормально, але іноді це не так. :)
pettys

7
Дякую за рішення! Я вирішив усунення зайвого пробілу, замінивши нові рядки на зразок %% NL %% у текстовому значенні, потім викликав .html (), щоб отримати кодоване значення HTML, а потім замінив %% NL %% на <br /> ' s ... Не є кулезахисним, але спрацювало, і мої користувачі, мабуть, не набирали %% NL %%.
benno

1
Найцікавіше, що CSS має white-spaceвластивість, яка говорить про те, як слід обробляти пробіли у вмісті HTML. Презентація предмета передбачає, що "це попередньо відформатовано, пробіли та розриви ліній повинні бути збережені". Це порушує поділ стилю та вмісту, тому що якщо ви намагаєтеся переформатувати HTML на "гарненький" або ви перейдете його через цикл кодування / декодування, як це, то пробіли / перерви скорочуються, а в кодері немає спосіб дізнатися, чи було це нормально, оскільки він не знає white-space:pre-*;індикатор у зовнішньому файлі CSS!
Трайнко

2
Це рішення може залежати від того, написана сторінка як html або xhtml, тому я вважаю за краще рішення, яке не передбачає DOM.
Філ Х

30
Хоча на нього відповіли два роки пізніше, відповідь від @Anentropic нижче краще всіляко.
чад

559

Трюк jQuery не кодує лапки, і в IE він зніме ваш пробіл.

Спираючись на шаблон тегів для втечі в Django, який, напевно, вже широко використовується / перевіряється, я зробив цю функцію, яка робить те, що потрібно.

Це, мабуть, простіше (і, можливо, швидше), ніж будь-який із способів вирішення проблеми, що знімає пробіл, - і він кодує цитати, що важливо, якщо ви, наприклад, збираєтесь використовувати результат у значенні атрибута.

function htmlEscape(str) {
    return str
        .replace(/&/g, '&amp;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&#39;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;');
}

// I needed the opposite function today, so adding here too:
function htmlUnescape(str){
    return str
        .replace(/&quot;/g, '"')
        .replace(/&#39;/g, "'")
        .replace(/&lt;/g, '<')
        .replace(/&gt;/g, '>')
        .replace(/&amp;/g, '&');
}

Оновлення 2013-06-17:
У пошуках найшвидшого втечі я знайшов цю реалізацію replaceAllметоду:
http://dumpsite.com/forum/index.php?topic=4.msg29#msg29
(також тут посилається: Найшвидший метод заміни всіх екземплярів символу в рядку )
Деякі результати роботи тут:
http://jsperf.com/htmlencoderegex/25

Це дає ідентичний рядок результатів вбудованим replaceланцюжкам вище. Я був би дуже радий, якби хтось міг пояснити, чому це швидше !?

Оновлення 2015-03-04:
Я щойно помітив, що AngularJS використовує саме вказаний вище метод:
https://github.com/angular/angular.js/blob/v1.3.14/src/ngSanitize/sanitize.js#L435

Вони додають кілька уточнень - вони, мабуть, обробляють незрозумілу проблему Unicode , а також перетворюють усі не алфавітно-цифрові символи в сутності. У мене було враження, що останнє не було необхідним, доки для вашого документа вказано набір UTF8.

Зауважу, що (через 4 роки) Джанго все ще не робить жодної з цих речей, тому я не впевнений, наскільки вони важливі:
https://github.com/django/django/blob/1.8b1/django/utils /html.py#L44

Оновлення 2016-04-06:
Ви також можете уникнути нахилу вперед /. Це не потрібно для правильного кодування HTML, однак він рекомендує OWASP як запобіжний захід XSS. (дякую @JNF, що він запропонував це у коментарях)

        .replace(/\//g, '&#x2F;');

3
Ви також можете використовувати &apos;замість&#39;
Ferruccio


5
Дякую, я ніколи не розумів, що &apos;це неправдивий HTML-елемент.
Ферруччо

10
Без цього /g, .replace()замінить лише перший матч.
ДумаючиСтиф

1
@ Tracker1 Я не згоден, якщо функція отримує неправильний ввід, вона повинна видавати помилку. Якщо у конкретному випадку використання ви хочете опрацювати недійсний вхід таким чином, то або перевірте значення перед тим, як викликати функцію, або загорніть виклик функції в спробу / улов.
Анентропний

80

Ось не-jQuery версія, яка значно швидша, ніж .html()версія jQuery і .replace()версія. Це зберігає всі пробіли, але, як і версія jQuery, не обробляє котирування.

function htmlEncode( html ) {
    return document.createElement( 'a' ).appendChild( 
        document.createTextNode( html ) ).parentNode.innerHTML;
};

Швидкість: http://jsperf.com/htmlencoderegex/17

тест на швидкість

Демонстрація: jsFiddle

Вихід:

вихід

Сценарій:

function htmlEncode( html ) {
    return document.createElement( 'a' ).appendChild( 
        document.createTextNode( html ) ).parentNode.innerHTML;
};

function htmlDecode( html ) {
    var a = document.createElement( 'a' ); a.innerHTML = html;
    return a.textContent;
};

document.getElementById( 'text' ).value = htmlEncode( document.getElementById( 'hidden' ).value );

//sanity check
var html = '<div>   &amp; hello</div>';
document.getElementById( 'same' ).textContent = 
      'html === htmlDecode( htmlEncode( html ) ): ' 
    + ( html === htmlDecode( htmlEncode( html ) ) );

HTML:

<input id="hidden" type="hidden" value="chalk    &amp; cheese" />
<input id="text" value="" />
<div id="same"></div>

17
Це ставить питання: чому це вже не глобальна функція в JS ?!
SEOF

2
Ні-регулярний вираз .replace()версія недавно запропонований @SEoF виявляється масово швидше: jsperf.com/htmlencoderegex/22
Anentropic

@Anentropic Це дійсно швидко освітлюється, але я не думаю, що це працює. Без /g, .replace()тільки робить перший матч.
ДумаючиСтиф

Цікаво, що у Firefox ви можете зробити те, replace('a', 'b', 'g')що працює так само, як replace(/a/g, 'b')... швидкість теж однакова, хоча
Anentropic

1
я ні :) Я почав просто хотіти обробляти лапки, і я закінчився пошуком швидкості ...
Anentropic

32

Я знаю, що це стара, але я хотів опублікувати варіант прийнятої відповіді, який буде працювати в IE, не видаляючи рядки:

function multiLineHtmlEncode(value) {
    var lines = value.split(/\r\n|\r|\n/);
    for (var i = 0; i < lines.length; i++) {
        lines[i] = htmlEncode(lines[i]);
    }
    return lines.join('\r\n');
}

function htmlEncode(value) {
    return $('<div/>').text(value).html();
} 


12

Хороша відповідь. Зауважте, що якщо значення для кодування є undefinedабо nullз jQuery 1.4.2, ви можете отримати помилки, такі як:

jQuery("<div/>").text(value).html is not a function

АБО

Uncaught TypeError: Object has no method 'html'

Рішення полягає в зміні функції, щоб перевірити фактичне значення:

function htmlEncode(value){ 
    if (value) {
        return jQuery('<div/>').text(value).html(); 
    } else {
        return '';
    }
}

8
jQuery('<div/>').text(value || '').html()
roufamatic

3
@roufamatic - Приємний однолінійний. Але перевірка непорожнього, valueіз ifзаощадженнями, які мають створити DIV на льоту та захопити його значення. Це може бути набагато ефективнішим, якщо htmlEncodeйому дзвонять багато І якщо він, ймовірно, valueбуде порожнім.
leepowers

Привіт, це не робить β до & бета Ви знаєте, чому?
Діліп Райкумар

11

Для тих, хто вважає за краще звичайний JavaScript, ось метод, який я успішно використовував:

function escapeHTML (str)
{
    var div = document.createElement('div');
    var text = document.createTextNode(str);
    div.appendChild(text);
    return div.innerHTML;
}

6

FWIW, кодування не втрачається. Кодування використовується аналізатором розмітки (браузером) під час завантаження сторінки. Після того, як джерело зчитується і аналізується і браузер завантажує DOM у пам'ять, кодування розбирається на те, що воно представляє. Тож до того моменту, коли ваш JS виконаний, щоб прочитати що-небудь у пам'яті, знаки, які він отримує, - це те, що представляє кодування.

Я, можливо, тут суворо працює над семантикою, але я хотів, щоб ви зрозуміли мету кодування. Слово "загублене" робить звук так, ніби щось не працює так, як слід.


6

Швидше без Jquery. Ви можете кодувати кожен символ у рядку:

function encode(e){return e.replace(/[^]/g,function(e){return"&#"+e.charCodeAt(0)+";"})}

Або просто націліть на головних персонажів, які турбуються про (&, неполадки, <,>, "і"), наприклад:

function encode(r){
return r.replace(/[\x26\x0A\<>'"]/g,function(r){return"&#"+r.charCodeAt(0)+";"})
}

test.value=encode('Encode HTML entities!\n\n"Safe" escape <script id=\'\'> & useful in <pre> tags!');

testing.innerHTML=test.value;

/*************
* \x26 is &ampersand (it has to be first),
* \x0A is newline,
*************/
<textarea id=test rows="9" cols="55"></textarea>

<div id="testing">www.WHAK.com</div>


5

У прототипі є вбудований клас String . Тож якщо ви використовуєте / плануєте використовувати прототип, він робить щось на кшталт:

'<div class="article">This is an article</div>'.escapeHTML();
// -> "&lt;div class="article"&gt;This is an article&lt;/div&gt;"

9
Переглянувши рішення прототипу, це все, що робиться ... .replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); Досить просто.
Стів Вортем

5
чи не повинен це робити щось із лапок? це не добре
Анентропний

@Anentropic Я не бачу, чому потрібно було би щось робити з цитатами; оскільки цитати не потрібно уникати, якщо вони не знаходяться всередині значення атрибута.
Енді

ОК після деякого роздуму, я повертаю цей коментар назад - якщо ви створюєте фрагмент HTML, ви хотіли б кодувати кожну його частину, включаючи значення атрибутів, тому я погоджуюся з Anentropic і не думаю, що функція Prototypejs є достатньою для той випадок.
Енді

4

Ось просте рішення Javascript. Він розширює об'єкт String за допомогою методу "HTMLEncode", який можна використовувати на об'єкті без параметра або з параметром.

String.prototype.HTMLEncode = function(str) {
  var result = "";
  var str = (arguments.length===1) ? str : this;
  for(var i=0; i<str.length; i++) {
     var chrcode = str.charCodeAt(i);
     result+=(chrcode>128) ? "&#"+chrcode+";" : str.substr(i,1)
   }
   return result;
}
// TEST
console.log("stetaewteaw æø".HTMLEncode());
console.log("stetaewteaw æø".HTMLEncode("æåøåæå"))

Я створив суть "метод HTMLEncode для javascript" .


3

На основі санітарії кута ... (синтаксис модуля es6)

// ref: https://github.com/angular/angular.js/blob/v1.3.14/src/ngSanitize/sanitize.js
const SURROGATE_PAIR_REGEXP = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g;
const NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g;

const decodeElem = document.createElement('pre');


/**
 * Decodes html encoded text, so that the actual string may
 * be used.
 * @param value
 * @returns {string} decoded text
 */
export function decode(value) {
  if (!value) return '';
  decodeElem.innerHTML = value.replace(/</g, '&lt;');
  return decodeElem.textContent;
}


/**
 * Encodes all potentially dangerous characters, so that the
 * resulting string can be safely inserted into attribute or
 * element text.
 * @param value
 * @returns {string} encoded text
 */
export function encode(value) {
  if (value === null || value === undefined) return '';
  return String(value).
    replace(/&/g, '&amp;').
    replace(SURROGATE_PAIR_REGEXP, value => {
      var hi = value.charCodeAt(0);
      var low = value.charCodeAt(1);
      return '&#' + (((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000) + ';';
    }).
    replace(NON_ALPHANUMERIC_REGEXP, value => {
      return '&#' + value.charCodeAt(0) + ';';
    }).
    replace(/</g, '&lt;').
    replace(/>/g, '&gt;');
}

export default {encode,decode};

Хоча мені дуже подобається ця відповідь, і насправді я вважаю, що це хороший підхід, я маю сумніви, чи є побитним оператором під if (value === null | value === undefined) return '';час друку чи насправді функцією? Якщо так, то навіщо використовувати саме це, а не загальне ||? Дякую!!
Алехандро Валес

1
@AlejandroVales Я майже впевнений, що це була помилка ... виправлено.
Tracker1

1
Добре все ж майте на увазі, що | приведе до 0 або 1, тож насправді це спрацювало ^^
Алехандро Валес

ти не міг просто скористатися == null? undefinedце єдине, що має еквівалентність null, тому два потрійні рівні все одно не потрібні
Hashbrown

це зовсім не так. nullі 0вони обидва хибні, так, так що ви не можете просто робити !value, але вся справа в ==тому, щоб зробити деякі речі легшими. 0 == nullнеправдиво. undefined == nullправда. ви можете просто зробитиvalue == null
Hashbrown

3

Наскільки я знаю, немає жодного прямого методу кодування / декодування HTML в JavaScript.

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

Скажімо, з jQuery це має працювати:

var helper = $('chalk & cheese').hide().appendTo('body');
var htmled = helper.html();
helper.remove();

Або щось у цьому напрямку.


Мені здається, що цей потік дещо кумедний, враховуючи, що ця відповідь майже ідентична тому, що має понад 870 оновлень, і був розміщений трохи після цього.
Кен Егозі

2

Вам не доведеться уникати / кодувати значення, щоб перенести їх з одного поля введення в інше.

<form>
 <input id="button" type="button" value="Click me">
 <input type="hidden" id="hiddenId" name="hiddenId" value="I like cheese">
 <input type="text" id="output" name="output">
</form>
<script>
    $(document).ready(function(e) {
        $('#button').click(function(e) {
            $('#output').val($('#hiddenId').val());
        });
    });
</script>

JS не вставляє сирий HTML або щось інше; він просто повідомляє DOM встановити valueвластивість (або атрибут; не впевнений). У будь-якому випадку, DOM обробляє будь-які проблеми кодування для вас. Якщо ви не робите щось дивне на кшталт використання document.writeабо eval, HTML-кодування буде ефективно прозорим.

Якщо ви говорите про створення нового текстового поля для отримання результату ... це все одно так просто. Просто передайте статичну частину HTML в jQuery, а потім встановіть решта властивостей / атрибутів об’єкта, який він вам поверне.

$box = $('<input type="text" name="whatever">').val($('#hiddenId').val());

2

У мене була подібна проблема і вирішити її за допомогою функції encodeURIComponentз JavaScript ( документація) )

Наприклад, у вашому випадку, якщо ви використовуєте:

<input id='hiddenId' type='hidden' value='chalk & cheese' />

і

encodeURIComponent($('#hiddenId').attr('value'))

ви отримаєте chalk%20%26%20cheese. Навіть пробіли зберігаються.

У моєму випадку мені довелося кодувати одну зворотну косу рису, і цей код працює чудово

encodeURIComponent('name/surname')

і я отримав name%2Fsurname


2

Ось трохи, що емулює Server.HTMLEncodeфункцію від ASP Майкрософт, написаний у чистому JavaScript:

function htmlEncode(s) {
  var ntable = {
    "&": "amp",
    "<": "lt",
    ">": "gt",
    "\"": "quot"
  };
  s = s.replace(/[&<>"]/g, function(ch) {
    return "&" + ntable[ch] + ";";
  })
  s = s.replace(/[^ -\x7e]/g, function(ch) {
    return "&#" + ch.charCodeAt(0).toString() + ";";
  });
  return s;
}

Результат не кодує апострофи, але кодує інші спеціальні HTML та будь-які символи поза діапазоном 0x20-0x7e.



1

Якщо ви хочете використовувати jQuery. Я знайшов це:

http://www.jquerysdk.com/api/jQuery.htmlспеціалістів

(частина плагіна jquery.string, запропонованого jQuery SDK)

Я вважаю, що проблема з прототипом полягає в тому, що він розширює базові об'єкти в JavaScript і буде несумісний з будь-яким jQuery, який ви могли використовувати. Звичайно, якщо ви вже використовуєте прототип, а не jQuery, це не буде проблемою.

EDIT: Також є це, що є портом рядкових утиліт прототипу для jQuery:

http://stilldesigning.com/dotstring/


1
var htmlEnDeCode = (function() {
    var charToEntityRegex,
        entityToCharRegex,
        charToEntity,
        entityToChar;

    function resetCharacterEntities() {
        charToEntity = {};
        entityToChar = {};
        // add the default set
        addCharacterEntities({
            '&amp;'     :   '&',
            '&gt;'      :   '>',
            '&lt;'      :   '<',
            '&quot;'    :   '"',
            '&#39;'     :   "'"
        });
    }

    function addCharacterEntities(newEntities) {
        var charKeys = [],
            entityKeys = [],
            key, echar;
        for (key in newEntities) {
            echar = newEntities[key];
            entityToChar[key] = echar;
            charToEntity[echar] = key;
            charKeys.push(echar);
            entityKeys.push(key);
        }
        charToEntityRegex = new RegExp('(' + charKeys.join('|') + ')', 'g');
        entityToCharRegex = new RegExp('(' + entityKeys.join('|') + '|&#[0-9]{1,5};' + ')', 'g');
    }

    function htmlEncode(value){
        var htmlEncodeReplaceFn = function(match, capture) {
            return charToEntity[capture];
        };

        return (!value) ? value : String(value).replace(charToEntityRegex, htmlEncodeReplaceFn);
    }

    function htmlDecode(value) {
        var htmlDecodeReplaceFn = function(match, capture) {
            return (capture in entityToChar) ? entityToChar[capture] : String.fromCharCode(parseInt(capture.substr(2), 10));
        };

        return (!value) ? value : String(value).replace(entityToCharRegex, htmlDecodeReplaceFn);
    }

    resetCharacterEntities();

    return {
        htmlEncode: htmlEncode,
        htmlDecode: htmlDecode
    };
})();

Це з вихідного коду ExtJS.


1
<script>
String.prototype.htmlEncode = function () {
    return String(this)
        .replace(/&/g, '&amp;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&#39;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;');

}

var aString = '<script>alert("I hack your site")</script>';
console.log(aString.htmlEncode());
</script>

Виведе: &lt;script&gt;alert(&quot;I hack your site&quot;)&lt;/script&gt;

.htmlEncode () буде доступний для всіх рядків, щойно визначені.


1

HtmlEncodes задане значення

  var htmlEncodeContainer = $('<div />');
  function htmlEncode(value) {
    if (value) {
      return htmlEncodeContainer.text(value).html();
    } else {
      return '';
    }
  }


0

Вибір того, що escapeHTML()робиться в prototype.js

Додавання цього сценарію допоможе вам уникнутиHTML:

String.prototype.escapeHTML = function() { 
    return this.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')
}

тепер ви можете викликати метод escapeHTML на рядках у вашому сценарії, наприклад:

var escapedString = "<h1>this is HTML</h1>".escapeHTML();
// gives: "&lt;h1&gt;this is HTML&lt;/h1&gt;"

Сподіваємось, це допомагає всім, хто шукає просте рішення, не включаючи весь prototype.js


0

Використовуючи деякі інші відповіді тут, я створив версію, яка замінює всі відповідні символи за один прохід, незалежно від кількості чітко закодованих символів (лише один дзвінок на replace()), тому буде швидше для великих рядків.

Він не покладається на існування API DOM або на інші бібліотеки.

window.encodeHTML = (function() {
    function escapeRegex(s) {
        return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
    }
    var encodings = {
        '&'  : '&amp;',
        '"'  : '&quot;',
        '\'' : '&#39;',
        '<'  : '&lt;',
        '>'  : '&gt;',
        '\\' : '&#x2F;'
    };
    function encode(what) { return encodings[what]; };
    var specialChars = new RegExp('[' +
        escapeRegex(Object.keys(encodings).join('')) +
    ']', 'g');

    return function(text) { return text.replace(specialChars, encode); };
})();

Запустивши це колись, тепер можна дзвонити

encodeHTML('<>&"\'')

Отримати &lt;&gt;&amp;&quot;&#39;


0

function encodeHTML(str) {
    return document.createElement("a").appendChild( 
        document.createTextNode(str)).parentNode.innerHTML;
};

function decodeHTML(str) {
    var element = document.createElement("a"); 
    element.innerHTML = str;
    return element.textContent;
};
var str = "<"
var enc = encodeHTML(str);
var dec = decodeHTML(enc);
console.log("str: " + str, "\nenc: " + enc, "\ndec: " + dec);

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.