Giải mã & amp; quay lại và trong JavaScript


229

Tôi có chuỗi như

var str = 'One & two & three';

được kết xuất thành HTML bởi máy chủ web. Tôi cần phải chuyển đổi các chuỗi đó thành

'One & two & three'

Hiện tại, đó là những gì tôi đang làm (với sự trợ giúp của jQuery):

$(document.createElement('div')).html('{{ driver.person.name }}').text()

Tuy nhiên tôi có một cảm giác đáng lo ngại rằng tôi đang làm sai. Tôi đã thử

unescape("&")

nhưng nó dường như không hoạt động, cũng không làm decodeURI / decodeURIComponent.

Có cách nào khác, bản địa và thanh lịch hơn để làm như vậy không?


Hàm khổng lồ có trong bài viết này dường như hoạt động tốt: blog.msdn.com/b/aoakley/archive/2003/11/12/49645.aspx Tôi không nghĩ đó là giải pháp thông minh nhất nhưng hiệu quả.
Matias

1
Vì các chuỗi chứa các thực thể HTML là một cái gì đó khác với các chuỗi được mã hóaescape d hoặc URI , các hàm đó sẽ không hoạt động.
Marcel Korpel

1
@Matias lưu ý rằng các thực thể có tên mới đã được thêm vào HTML (ví dụ: thông qua thông số HTML 5) vì chức năng đó đã được tác giả vào năm 2003 - ví dụ, nó không nhận ra 𝕫. Đây là một vấn đề với một thông số kỹ thuật đang phát triển; như vậy, bạn nên chọn một công cụ thực sự được duy trì để giải quyết nó.
Mark Amery

1
@MarkAmery vâng, tôi hoàn toàn đồng ý! Đó là một kinh nghiệm tốt đẹp để trở lại câu hỏi này sau một vài năm, cảm ơn!
Matias

Câu trả lời:


104

Một tùy chọn hiện đại hơn để diễn giải HTML (văn bản và các cách khác) từ JavaScript là hỗ trợ HTML trong DOMParserAPI ( xem tại đây trong MDN ). Điều này cho phép bạn sử dụng trình phân tích cú pháp HTML gốc của trình duyệt để chuyển đổi một chuỗi thành tài liệu HTML. Nó đã được hỗ trợ trong các phiên bản mới của tất cả các trình duyệt chính kể từ cuối năm 2014.

Nếu chúng ta chỉ muốn giải mã một số nội dung văn bản, chúng ta có thể đặt nó làm nội dung duy nhất trong thân tài liệu, phân tích tài liệu và rút ra nội dung đó .body.textContent.

var encodedStr = 'hello & world';

var parser = new DOMParser;
var dom = parser.parseFromString(
    '<!doctype html><body>' + encodedStr,
    'text/html');
var decodedString = dom.body.textContent;

console.log(decodedString);

Chúng ta có thể thấy trong bản phác thảo đặc tả choDOMParser rằng JavaScript không được kích hoạt cho tài liệu được phân tích cú pháp, vì vậy chúng ta có thể thực hiện chuyển đổi văn bản này mà không cần lo ngại về bảo mật.

Các parseFromString(str, type)phương pháp phải chạy các bước này, tùy thuộc vào loại :

  • "text/html"

    Parse str với một HTML parser, và trả lại mới được tạoDocument .

    Cờ script phải được đặt thành "vô hiệu hóa".

    GHI CHÚ

    scriptcác phần tử được đánh dấu không thể thực hiện được và nội dung noscriptđược phân tích cú pháp dưới dạng đánh dấu.

Nó nằm ngoài phạm vi của câu hỏi này, nhưng xin lưu ý rằng nếu bạn tự lấy các nút DOM được phân tích cú pháp (không chỉ nội dung văn bản của chúng) và chuyển chúng sang DOM tài liệu trực tiếp, có thể kịch bản của chúng sẽ được kích hoạt lại và có thể được quan tâm về an ninh. Tôi chưa nghiên cứu về nó, vì vậy hãy thận trọng.


5
bất kỳ thay thế cho NodeJs?
coderInrRain

284

Bạn có cần giải mã tất cả các thực thể HTML được mã hóa hay chỉ &amp;chính nó?

Nếu bạn chỉ cần xử lý &amp;thì bạn có thể làm điều này:

var decoded = encoded.replace(/&amp;/g, '&');

Nếu bạn cần giải mã tất cả các thực thể HTML thì bạn có thể làm điều đó mà không cần jQuery:

var elem = document.createElement('textarea');
elem.innerHTML = encoded;
var decoded = elem.value;

Vui lòng lưu ý các nhận xét của Mark bên dưới, nêu bật các lỗ hổng bảo mật trong phiên bản trước của câu trả lời này và khuyên bạn nên sử dụng textareathay vì divgiảm thiểu các lỗ hổng XSS tiềm năng. Các lỗ hổng này tồn tại cho dù bạn sử dụng jQuery hay JavaScript đơn giản.


16
Coi chừng! Điều này có khả năng không an toàn. Nếu encoded='<img src="bla" onerror="alert(1)">'sau đó đoạn trích ở trên sẽ hiển thị một cảnh báo. Điều này có nghĩa là nếu văn bản được mã hóa của bạn đến từ đầu vào của người dùng, việc giải mã nó bằng đoạn mã này có thể gây ra lỗ hổng XSS.
Đánh dấu Amery

@MarkAmery Tôi không phải là chuyên gia bảo mật, nhưng có vẻ như nếu bạn ngay lập tức cài đặt div nullsau khi nhận được văn bản, cảnh báo trong img không được kích hoạt - jsfiddle.net/Mottie/gaBeb/128
Mottie

4
@Mottie lưu ý rằng trình duyệt nào phù hợp với bạn, nhưng trình duyệt alert(1)vẫn kích hoạt cho tôi trên Chrome trên OS X. Nếu bạn muốn có một biến thể an toàn của bản hack này, hãy thử sử dụng atextarea .
Đánh dấu Amery

+1 cho thay thế regex đơn giản thay thế cho chỉ một loại thực thể html. Sử dụng điều này nếu bạn đang mong đợi dữ liệu html được nội suy từ, giả sử, một ứng dụng bình python sang một mẫu.
OzzyTheGiant

Làm thế nào để làm điều này trên máy chủ Node?
Mohammad Kermani

44

Matthias Bynens có một thư viện cho việc này: https://github.com/mathiasbynens/he

Thí dụ:

console.log(
    he.decode("J&#246;rg &amp J&#xFC;rgen rocked to &amp; fro ")
);
// Logs "Jörg & Jürgen rocked to & fro"

Tôi đề nghị ủng hộ nó hơn các bản hack liên quan đến việc thiết lập nội dung HTML của một phần tử và sau đó đọc lại nội dung văn bản của nó. Những cách tiếp cận như vậy có thể hoạt động, nhưng rất nguy hiểm và mang lại cơ hội XSS nếu được sử dụng trên đầu vào của người dùng không đáng tin cậy.

Nếu bạn thực sự không thể tải trong thư viện, bạn có thể sử dụng textareahack được mô tả trong câu trả lời này cho một câu hỏi gần như trùng lặp, không giống như các cách tiếp cận tương tự khác đã được đề xuất, không có lỗ hổng bảo mật mà tôi biết:

function decodeEntities(encodedString) {
    var textArea = document.createElement('textarea');
    textArea.innerHTML = encodedString;
    return textArea.value;
}

console.log(decodeEntities('1 &amp; 2')); // '1 & 2'

Nhưng hãy lưu ý các vấn đề bảo mật, ảnh hưởng đến các cách tiếp cận tương tự với vấn đề này, mà tôi liệt kê trong câu trả lời được liên kết! Cách tiếp cận này là một hack và những thay đổi trong tương lai đối với nội dung cho phép của một textarea(hoặc lỗi trong các trình duyệt cụ thể) có thể dẫn đến mã dựa trên việc nó đột nhiên có lỗ XSS một ngày nào đó.


Thư viện của Matthias Bynens hehoàn toàn tuyệt vời! Cảm ơn bạn rất nhiều vì sự giới thiệu!
Pedro A

23
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
    };
})();

Đây là từ mã nguồn ExtJS.


4
-1; điều này không thể xử lý phần lớn các thực thể được đặt tên. Ví dụ, htmlEnDecode.htmlDecode('&euro;')nên trả lại '€', nhưng thay vào đó trả về '&euro;'.
Mark Amery


15

Bạn có thể sử dụng chức năng Lodash unescape / esc https://lodash.com/docs/4.17.5#unescape

import unescape from 'lodash/unescape';

const str = unescape('fred, barney, &amp; pebbles');

str sẽ trở thành 'fred, barney, & pebbles'


1
có lẽ tốt hơn để thực hiện "nhập _unescape từ 'lodash / unescape';" do đó, nó không xung đột với chức năng javascript không dùng cùng tên: unescape
Rick Penabella

14

Trong trường hợp bạn đang tìm kiếm nó, như tôi - trong khi đó có một phương thức JQuery đẹp và an toàn.

https://api.jquery.com/jquery.parsehtml/

Bạn có thể f.ex. gõ cái này trong bảng điều khiển của bạn:

var x = "test &amp;";
> undefined
$.parseHTML(x)[0].textContent
> "test &"

Vì vậy, $ .parseHTML (x) trả về một mảng và nếu bạn có đánh dấu HTML trong văn bản của mình, mảng.length sẽ lớn hơn 1.


Làm việc hoàn hảo cho tôi, đây chính xác là những gì tôi đang tìm kiếm, cảm ơn bạn.
Jonathan Nielsen

1
Nếu xcó một giá trị <script>alert('hello');</script>ở trên sẽ sụp đổ. Trong jQuery hiện tại, nó thực sự sẽ không cố chạy tập lệnh, nhưng [0]sẽ mang lại kết quả undefinedvì vậy lệnh gọi textContentsẽ thất bại và tập lệnh của bạn sẽ dừng ở đó. $('<div />').html(x).text();có vẻ an toàn hơn - thông qua gist.github.com/jmblog/3222899
Andrew Hodgkinson

@AndrewHodgkinson yeah, nhưng câu hỏi là "Giải mã & trở lại & trong JavaScript" - vì vậy trước tiên bạn sẽ kiểm tra nội dung của x hoặc đảm bảo bạn chỉ sử dụng nó trong các trường hợp chính xác.
cslotty

Tôi không thực sự thấy làm thế nào sau đó. Các mã trên hoạt động trong mọi trường hợp. Và chính xác là bạn sẽ "đảm bảo" giá trị của x cần sửa như thế nào? Và nếu ví dụ về tập lệnh ở trên cảnh báo '& amp;' Vì vậy, nó thực sự cần phải điều chỉnh? Chúng tôi không biết chuỗi của OP đến từ đâu, vì vậy đầu vào độc hại phải được xem xét.
Andrew Hodgkinson

@AndrewHodgkinson Tôi thích sự cân nhắc của bạn, nhưng đó không phải là câu hỏi ở đây. Hãy trả lời câu hỏi đó, mặc dù. Tôi đoán bạn có thể xóa các thẻ script, f.ex.
cslotty

8

jQuery sẽ mã hóa và giải mã cho bạn. Tuy nhiên, bạn cần sử dụng thẻ textarea, không phải div.

var str1 = 'One & two & three';
var str2 = "One &amp; two &amp; three";
  
$(document).ready(function() {
   $("#encoded").text(htmlEncode(str1)); 
   $("#decoded").text(htmlDecode(str2));
});

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

function htmlEncode(value) {
  return $('<textarea/>').text(value).html();
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>

<div id="encoded"></div>
<div id="decoded"></div>


2
-1 vì có một lỗ hổng bảo mật (đáng ngạc nhiên) ở đây đối với các phiên bản jQuery cũ, một số trong đó có thể vẫn có một cơ sở người dùng quan trọng - những phiên bản đó sẽ phát hiện và đánh giá rõ ràng các tập lệnh trong HTML được chuyển đến .html(). Do đó, ngay cả việc sử dụng textareakhông đủ để đảm bảo an ninh ở đây; Tôi đề nghị không sử dụng jQuery cho tác vụ này và viết mã tương đương với API DOM đơn giản . (Vâng, hành vi cũ đó của jQuery thật điên rồ và khủng khiếp.)
Mark Amery

Cảm ơn bạn đã chỉ ra rằng. Tuy nhiên, câu hỏi không bao gồm yêu cầu kiểm tra nội dung kịch bản. Câu hỏi đặc biệt hỏi về html được hiển thị bởi máy chủ web. Nội dung Html được lưu vào máy chủ web có thể phải được xác thực để tiêm script trước khi lưu.
Jason Williams

4

Đầu tiên tạo một <span id="decodeIt" style="display:none;"></span>nơi nào đó trong cơ thể

Tiếp theo, gán chuỗi được giải mã thành InternalHTML cho điều này:

document.getElementById("decodeIt").innerHTML=stringtodecode

Cuối cùng,

stringtodecode=document.getElementById("decodeIt").innerText

Đây là mã tổng thể:

var stringtodecode="<B>Hello</B> world<br>";
document.getElementById("decodeIt").innerHTML=stringtodecode;
stringtodecode=document.getElementById("decodeIt").innerText

1
-1; điều này là không an toàn để sử dụng trên đầu vào không đáng tin cậy. Ví dụ, xem xét những gì xảy ra nếu stringtodecodecó chứa một cái gì đó như <script>alert(1)</script>.
Đánh dấu Amery

2

một giải pháp javascript bắt những cái phổ biến:

var map = {amp: '&', lt: '<', gt: '>', quot: '"', '#039': "'"}
str = str.replace(/&([^;]+);/g, (m, c) => map[c])

đây là mặt trái của https://stackoverflow.com/a/4835406/2738039


Nếu bạn sử dụng map[c] || ''những cái không được nhận dạng sẽ không được hiển thị làundefined
Eldelshell

Bảo hiểm rất hạn chế; -1.
Đánh dấu Amery

2
+1, hơn nữa làunescapeHtml(str){ var map = {amp: '&', lt: '<', le: '≤', gt: '>', ge: '≥', quot: '"', '#039': "'"} return str.replace(/&([^;]+);/g, (m, c) => map[c]|| '') }
Trần Quốc Hoài mới 2015

Bảo hiểm bằng tay. Không được khuyến khích.
Sergio A.

2

Dành cho những người một dòng:

const htmlDecode = innerHTML => Object.assign(document.createElement('textarea'), {innerHTML}).value;

console.log(htmlDecode('Complicated - Dimitri Vegas &amp; Like Mike'));

2

Câu hỏi không chỉ định nguồn gốc của xnhưng có ý nghĩa để bảo vệ, nếu chúng ta có thể, chống lại đầu vào độc hại (hoặc chỉ bất ngờ, từ ứng dụng của chính chúng ta). Ví dụ, giả sử xcó một giá trị &amp; <script>alert('hello');</script>. Một cách an toàn và đơn giản để xử lý việc này trong jQuery là:

var x    = "&amp; <script>alert('hello');</script>";
var safe = $('<div />').html(x).text();

// => "& alert('hello');"

Tìm thấy qua https://gist.github.com/jmblog/3222899 . Tôi không thể thấy nhiều lý do để tránh sử dụng giải pháp này vì nó ít nhất là ngắn, nếu không ngắn hơn một số giải pháp thay thế cung cấp sự bảo vệ chống lại XSS.

(Ban đầu tôi đã đăng bài này dưới dạng một bình luận, nhưng tôi thêm nó dưới dạng một câu trả lời vì một bình luận tiếp theo trong cùng một chủ đề yêu cầu tôi làm như vậy).


1

Tôi đã thử mọi cách để loại bỏ & từ một mảng JSON. Không có ví dụ nào ở trên, nhưng https://stackoverflow.com/users/2030321/chris đã đưa ra một giải pháp tuyệt vời khiến tôi phải khắc phục vấn đề của mình.

var stringtodecode="<B>Hello</B> world<br>";
document.getElementById("decodeIt").innerHTML=stringtodecode;
stringtodecode=document.getElementById("decodeIt").innerText

Tôi đã không sử dụng, vì tôi không hiểu làm thế nào để chèn nó vào một cửa sổ phương thức đang kéo dữ liệu JSON vào một mảng, nhưng tôi đã thử điều này dựa trên ví dụ và nó đã hoạt động:

var modal = document.getElementById('demodal');
$('#ampersandcontent').text(replaceAll(data[0],"&amp;", "&"));

Tôi thích nó vì nó đơn giản, và nó hoạt động, nhưng không chắc tại sao nó không được sử dụng rộng rãi. Tìm kiếm hi & low để tìm một giải pháp đơn giản. Tôi tiếp tục tìm kiếm sự hiểu biết về cú pháp và nếu có bất kỳ rủi ro nào khi sử dụng nó. Chưa tìm thấy gì.


Đề xuất đầu tiên của bạn chỉ là một chút khó khăn, nhưng nó hoạt động tốt mà không cần nỗ lực nhiều. Mặt khác, thứ hai, chỉ sử dụng lực lượng vũ phu để giải mã các ký tự; điều này có nghĩa là có thể mất rất nhiều nỗ lực và thời gian để hoàn thành chức năng giải mã đầy đủ. Đó là lý do tại sao không ai sử dụng cách đó để giải quyết vấn đề của OP.
Sergio A.
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.