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?

AJM
источник
Можете ли вы показать Javascript, который вы используете?
Синан Тайфур
1
добавил, как я получаю значение из скрытого поля
AJM
5
НЕ используйте метод innerHTML (метод jQuery .html () использует innerHTML), поскольку в некоторых браузерах (я только тестировал Chrome) это не будет экранировать кавычки, поэтому, если вы поместите свое значение в значение атрибута , вы в конечном итоге с уязвимостью XSS.
Джеймс Ропер
21
в каком контексте chalkи cheeseкогда-либо использовались вместе 0_o
d -_- b
2
@d -_- b при сравнении двух предметов. пример. они такие же разные, как мел и сыр;)
Anurag

Ответы:

1067

РЕДАКТИРОВАТЬ: Этот ответ был опубликован давно, и htmlDecodeфункция представила уязвимость XSS. Он был изменен, изменив временный элемент с a 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 получаю.

Проверьте работающий пример здесь .

CMS
источник
95
Это работает для большинства сценариев, но эта реализация htmlDecode устранит любые дополнительные пробелы. Поэтому для некоторых значений «input» введите! = HtmlDecode (htmlEncode (input)). Это было проблемой для нас в некоторых сценариях. Например, если input = "<p> \ t Hi \ n There </ p>", кодирование / декодирование в обе стороны даст "<p> Hi There </ p>". В большинстве случаев это нормально, но иногда это не так. :)
Петтис
7
Спасибо за решение! Я решил устранить проблему лишних пробелов, заменив новые строки на %% NL %% в текстовом значении, затем вызвал .html (), чтобы получить значение в кодировке HTML, затем заменил %% NL %% на <br /> ' s ... Не пуленепробиваемый, но сработал, и мои пользователи вряд ли набрали %% NL %%.
Бенно
1
Что забавно, так это то, что у CSS есть white-spaceсвойство, которое подсказывает, как должны обрабатываться пробелы в контенте HTML. Наличие свойства подразумевает, что «это предварительно отформатировано, пробелы и разрывы строк должны быть сохранены». Это нарушает разделение стиля и содержимого, потому что если вы пытаетесь переформатировать HTML-код, чтобы он был «красивым», или вы совершаете обходной цикл через цикл кодирования / декодирования, как это, то количество пробелов / разрывов сокращается, и кодер не имеет способ узнать, нормально ли это сделать, потому что он не знает об white-space:pre-*;индикаторе во внешнем файле CSS!
Трийнко
2
Это решение может зависеть от того, написана ли страница в формате html или xhtml, поэтому я бы предпочел решение, не включающее DOM.
Фил Х
30
Хотя два года спустя на него ответили, ответ от @Anentropic ниже лучше во всех отношениях.
Чад
559

Уловка jQuery не кодирует кавычки, а в IE она лишит вас пробелов.

Основываясь на теге escape- шаблона в 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;');
Anentropic
источник
3
Вы также можете использовать &apos;вместо&#39;
Ферруччо
32
@Ferruccio ... и по причинам, почему бы не использовать & apos; см: stackoverflow.com/questions/2083754/... blogs.msdn.com/b/kirillosenkov/archive/2010/03/19/... fishbowl.pastiche.org/2003/07/01/the_curse_of_apos
Anentropic
5
Спасибо, я так и не понял, что &apos;это недопустимая сущность HTML.
Ферруччо
10
Без /g, .replace()заменит только первый матч.
ThinkingStiff
1
@ Tracker1 Я не согласен, если функция получает неправильный ввод, она должна выдать ошибку. Если в конкретном случае использования вы хотите обработать недопустимый ввод таким способом, то либо проверьте значение перед вызовом функции, либо оберните вызов функции в try / catch.
Anentropic
80

Вот не-jQuery-версия, которая значительно быстрее как jQuery- .html()версии, так и .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>
ThinkingStiff
источник
17
Возникает вопрос: почему это уже не глобальная функция в JS ?!
SEOF
2
.replace()версия без регулярных выражений, недавно предложенная @SEoF, оказывается намного быстрее: jsperf.com/htmlencoderegex/22
Anentropic
@Anentropic Это действительно быстро, но я не думаю, что это работает. Без /g, .replace()только делает первый матч.
ThinkingStiff
Интересно, что в 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();
} 
Boca
источник
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 '';
    }
}
leepowers
источник
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;
}
timodius
источник
6

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

Возможно, я здесь работаю строго по семантике, но я хотел, чтобы вы поняли цель кодирования. Слово «потерял» звучит так, как будто что-то не работает так, как должно.

JAAulde
источник
6

Быстрее без Jquery. Вы можете закодировать каждый символ в вашей строке:

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

Или просто нацеливайтесь на главных героев, о которых нужно беспокоиться (&, inebreaks, <,>, "и '), например:

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 . Так что, если вы используете / планируете использовать Prototype, он делает что-то вроде:

'<div class="article">This is an article</div>'.escapeHTML();
// -> "&lt;div class="article"&gt;This is an article&lt;/div&gt;"
Синан Тайфур
источник
9
Посмотрев на решение Prototype, это все, что он делает ... .replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); Достаточно просто.
Стив Уортам,
5
разве это не должно делать что-то с кавычками тоже? это не хорошо
Anentropic
@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" .

Netsi1964
источник
3

Основано на санитарной обработке angular ... (синтаксис модуля 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};
Tracker1
источник
Хотя мне действительно нравится этот ответ, и на самом деле я думаю, что это хороший подход, у меня есть сомнения, является ли побитовый оператор 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

Насколько я знаю, в javascript нет прямого метода HTML Encode / Decode.

Однако, что вы можете сделать, это использовать 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());
Chao
источник
2

У меня была похожая проблема, и я решил ее с помощью функции encodeURIComponentиз JavaScript ( документация )

Например, в вашем случае, если вы используете:

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

а также

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

вы получите chalk%20%26%20cheese. Даже места сохранены.

В моем случае мне пришлось закодировать одну обратную косую черту, и этот код работает отлично

encodeURIComponent('name/surname')

и я получил name%2Fsurname

Dmyan
источник
2

Вот немного, который эмулирует Server.HTMLEncodeфункцию из ASP Microsoft, написанную на чистом 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.

ReWrite
источник
1

Если вы хотите использовать jQuery. Я нашел это:

http://www.jquerysdk.com/api/jQuery.htmlspecialchars

(часть плагина jquery.string, предлагаемого jQuery SDK)

Я считаю, что проблема с прототипом заключается в том, что он расширяет базовые объекты в JavaScript и будет несовместим с любым jQuery, который вы, возможно, использовали. Конечно, если вы уже используете Prototype, а не jQuery, это не будет проблемой.

РЕДАКТИРОВАТЬ: Также есть это, который является портом строковых утилит Prototype для 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 '';
    }
  }
Sky Yip
источник
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() ), поэтому будет быстрее для больших строк.

Он не полагается на существование DOM API или других библиотек.

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;

Hashbrown
источник
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);

Израиль
источник