Phương pháp nhanh nhất để thoát thẻ HTML dưới dạng thực thể HTML?


98

Tôi đang viết một phần mở rộng của Chrome mà liên quan đến việc làm một rất nhiều của công việc sau: vệ sinh chuỗi có thể chứa các thẻ HTML, bằng cách chuyển đổi <, >&để &lt;, &gt;&amp;, tương ứng.

(Nói cách khác, giống như PHP htmlspecialchars(str, ENT_NOQUOTES)- tôi không nghĩ rằng thực sự cần thiết phải chuyển đổi các ký tự dấu ngoặc kép.)

Đây là chức năng nhanh nhất mà tôi tìm thấy cho đến nay:

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

Nhưng vẫn có độ trễ lớn khi tôi phải chạy vài nghìn chuỗi trong một lần.

Bất cứ ai có thể cải thiện điều này? Nó chủ yếu dành cho các chuỗi từ 10 đến 150 ký tự, nếu điều đó tạo ra sự khác biệt.

(Một ý tưởng của tôi là không bận tâm đến việc mã hóa dấu lớn hơn - liệu có bất kỳ mối nguy hiểm thực sự nào với điều đó không?)


2
Tại sao? Trong hầu hết các trường hợp bạn muốn làm điều này, bạn muốn chèn dữ liệu vào DOM, trong trường hợp này, bạn nên quên việc thoát nó và chỉ tạo một mã textNode từ nó.
Quentin

1
@David Dorward: có lẽ anh ấy muốn làm sạch dữ liệu POST và máy chủ không chuyển dữ liệu một cách chính xác.
Lie Ryan

4
@Lie - nếu như vậy, sau đó giải pháp là "Vì lợi ích của Pete, sửa chữa các máy chủ như bạn có một lỗ XSS lớn"
Quentin

2
@David Dorward: có thể trường hợp là anh ta không có quyền kiểm soát máy chủ. Gần đây, tôi đã rơi vào tình huống như vậy khi tôi đang viết một đoạn mã Greasemonkey để giải quyết một số điều tôi không thích trong trang web của trường đại học của tôi; Tôi đã phải thực hiện POST trên một máy chủ mà tôi không có quyền kiểm soát và làm sạch dữ liệu POST bằng javascript (vì dữ liệu thô đến từ một hộp văn bản đa dạng thức và đống thẻ html không hoạt động vòng trên máy chủ cũng vậy) . Quản trị viên web đã phớt lờ yêu cầu của tôi để họ sửa trang web, vì vậy tôi không còn lựa chọn nào khác.
Lie Ryan

1
Tôi có một trường hợp sử dụng mà tôi cần hiển thị thông báo lỗi trong một div. Thông báo lỗi có thể chứa HTML và dòng mới. Tôi muốn thoát HTML và thay thế các dòng mới bằng <br>. Sau đó đưa kết quả vào một div để hiển thị.
mozey

Câu trả lời:


83

Bạn có thể thử chuyển một hàm gọi lại để thực hiện thay thế:

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

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

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

Đây là bài kiểm tra hiệu suất: http://jsperf.com/encode-html-entities để so sánh với việc gọi replacehàm nhiều lần và sử dụng phương thức DOM do Dmitrij đề xuất.

Cách của bạn dường như nhanh hơn ...

Tuy nhiên, tại sao bạn cần nó?


2
Không cần thiết phải trốn thoát >.

6
Trên thực tế, nếu bạn đặt giá trị thoát trong thuộc tính của phần tử html, bạn cần phải thoát khỏi biểu tượng>. Nếu không, nó sẽ phá vỡ thẻ cho phần tử html đó.
Zlatin Zlatev

1
Trong văn bản bình thường các ký tự thoát là rất hiếm. Nó tốt hơn để gọi thay thế chỉ khi cần thiết, nếu bạn quan tâm đến tốc độ tối đa:if (/[<>&"]/.test(str) { ... }
Vitaly

3
@callum. Tôi quan tâm đến việc viết mã theo các tiêu chuẩn (vì vậy các trường hợp không mong muốn / bị quên không thể làm tổn thương bạn theo định nghĩa ). Tôi không thể nhấn mạnh điều này quan trọng như thế nào. >là một ký tự đặc biệt trong HTML, vì vậy hãy thoát khỏi nó. Đơn giản như vậy. :)
Các cuộc đua ánh sáng trong quỹ đạo vào

4
@LightnessRacesinOrbit Nó có liên quan vì câu hỏi là phương pháp nhanh nhất có thể là gì. Nếu có thể bỏ qua việc >thay thế, điều đó sẽ làm cho nó nhanh hơn.
callum,

104

Đây là một cách bạn có thể làm điều này:

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

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

Đây là một bản demo.


Thiết kế lại bản demo. Đây là phiên bản toàn màn hình: jsfiddle.net/Daniel_Hug/qPUEX/show/light
Web_Designer

13
Không chắc bằng cách nào / cái gì / tại sao - nhưng đây là một thiên tài.
rob_james

4
Có vẻ như nó đang tận dụng mã hiện có của phần tử TextArea để thoát văn bản theo nghĩa đen. Hay lắm, tôi nghĩ mẹo nhỏ này là bạn sẽ tìm được nhà khác.
Ajax

3
@jazkat Tôi không sử dụng chức năng đó. Biến thoát tôi sử dụng, tôi tự định nghĩa trong ví dụ.
Web_Designer

2
nhưng thực hiện điều này mất không gian trắng vv
Andrew

31

Phương thức của Martijn như một hàm nguyên mẫu:

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
Thêm vào Stringnhư thế này, nó phải là EscapeHtml vì nó không phải là một lối thoát cho một Chuỗi nói chung. Điều đó String.escapeHtmlđúng, nhưng lại String.escapeđặt ra câu hỏi “bỏ trốn để làm gì?”.
Lawrence Dol

3
Vâng, y hay. Những ngày này, tôi đã tránh xa việc mở rộng nguyên mẫu để tránh xung đột.
Aram Kocharyan

1
Nếu trình duyệt của bạn có hỗ trợ Symbol, bạn có thể sử dụng nó để tránh làm ô nhiễm không gian tên khóa chuỗi. var Escape = new Symbol ("Escape"); String.prototype [Escape] = function () {...}; "text" [Escape] ();
Ajax

12

Một giải pháp nhanh hơn / ngắn hơn là:

escaped = new Option(html).innerHTML

Điều này có liên quan đến một số vết tích kỳ lạ của JavaScript, theo đó phần tử Option giữ lại một hàm tạo để thực hiện loại thoát tự động này.

Ghi có vào https://github.com/jasonmoo/t.js/blob/master/t.js


1
Một lớp lót gọn gàng nhưng là phương pháp chậm nhất sau regex. Ngoài ra, văn bản ở đây có thể bị xóa khoảng trắng, theo thông số kỹ thuật
ShortFuse

Lưu ý rằng liên kết "phương pháp chậm nhất" của @ ShortFuse làm cho hệ thống của tôi hết RAM (với ~ 6GB trống) và firefox dường như ngừng phân bổ ngay trước khi hết bộ nhớ, vì vậy thay vì giết quá trình vi phạm, linux sẽ ngồi đó và để bạn làm. tắt nguồn khó.
Luc

11

Mã nguồn AngularJS cũng có một phiên bản bên trong angle-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
Chà, regex không alphanum đó rất dữ dội. Tôi không nghĩ rằng | trong biểu thức là cần thiết mặc dù.
Ajax


9

Tập lệnh tất cả trong một:

// 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


Tôi đã không phản đối, nhưng tất cả các thay thế kiểu regex sẽ không mã hóa được unicode ... Vì vậy, bất kỳ ai sử dụng ngoại ngữ sẽ thất vọng. Thủ thuật <textareosystem nói trên thực sự rất hay và xử lý mọi thứ một cách nhanh chóng và an toàn.
Ajax

Regex hoạt động tốt đối với tôi với một số ký tự Unicode không phải Latinh. Tôi sẽ không mong đợi bất cứ điều gì khác. Làm thế nào để bạn nghĩ rằng điều này sẽ không hoạt động? Bạn có đang nghĩ đến những đoạn mã một byte yêu cầu các thực thể HTML không? Đó là chức năng thứ 3 và thứ 4, và rõ ràng không phải là chức năng thứ nhất và thứ hai. Tôi thích sự khác biệt.
ygoe

@LonelyPixel Tôi không nghĩ anh ấy sẽ nhìn thấy bình luận của bạn nếu bạn không đề cập đến anh ấy ("Chỉ có thể thông báo cho một người dùng bổ sung; chủ sở hữu bài đăng sẽ luôn được thông báo")
baptx 29/02/16

Tôi hoàn toàn không biết thông báo được nhắm mục tiêu tồn tại. @Ajax vui lòng xem bình luận của tôi ở trên.
ygoe

@LonelyPixel Tôi thấy bây giờ. Vì lý do nào đó, tôi không nghĩ rằng có sự thay thế kiểu textarea trong câu trả lời này. Tôi thực sự đã nghĩ đến các giá trị unicode lớn có mã kép, như tiếng Quan Thoại. Ý tôi là, có thể tạo ra một regex đủ thông minh, nhưng khi bạn nhìn vào các phím tắt mà các nhà cung cấp trình duyệt có thể sử dụng, tôi sẽ cảm thấy khá tốt khi đặt cược rằng textarea sẽ nhanh hơn nhiều (so với một regex hoàn toàn có thẩm quyền). Ai đó đã đăng điểm chuẩn cho câu trả lời này? Tôi thề rằng tôi đã nhìn thấy một.
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

Tôi không hoàn toàn chắc chắn về tốc độ, nhưng nếu bạn đang tìm kiếm sự đơn giản, tôi khuyên bạn nên sử dụng hàm thoát lodash / underscore .


0

Phương thức của Martijn dưới dạng một hàm duy nhất có dấu " xử lý ( sử dụng trong javascript ):

function escapeHTML(html) {
    var fn=function(tag) {
        var charsToReplace = {
            '&': '&amp;',
            '<': '&lt;',
            '>': '&gt;',
            '"': '&#34;'
        };
        return charsToReplace[tag] || tag;
    }
    return html.replace(/[&<>"]/g, fn);
}

0

Tôi sẽ thêm XMLSerializervào đống. Nó cung cấp kết quả nhanh nhất mà không cần sử dụng bất kỳ bộ nhớ đệm đối tượng nào (không phải trên bộ tuần tự, cũng không phải trên nút Văn bản).

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

Phần thưởng bổ sung là nó hỗ trợ các thuộc tính được tuần tự hóa khác với các nút văn bản:

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

Bạn có thể thấy những gì nó thực sự thay thế bằng cách kiểm tra thông số kỹ thuật, cho cả các nút văn bảncác giá trị thuộc tính . Tài liệu đầy đủ có nhiều loại nút hơn, nhưng khái niệm thì giống nhau.

Đối với hiệu suất, nó nhanh nhất khi không được lưu trong bộ nhớ cache. Khi bạn cho phép lưu vào bộ nhớ đệm, thì việc gọi innerHTMLHTMLElement với nút Văn bản con là nhanh nhất. Regex sẽ chậm nhất (như đã được chứng minh bởi các nhận xét khác). Tất nhiên, XMLSerializer có thể nhanh hơn trên các trình duyệt khác, nhưng trong thử nghiệm (có giới hạn) của tôi, a innerHTMLlà nhanh nhất.


Dòng đơn nhanh nhất:

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

Nhanh nhất với bộ nhớ đệm:

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

Hơi trễ với chương trình, nhưng có gì sai khi sử dụng encodeURIComponent ()decodeURIComponent () ?


1
Những người đó làm điều gì đó hoàn toàn không liên quan
callum

1
Có lẽ sự lạm dụng lớn nhất của từ "hoàn toàn" mà tôi từng nghe. Ví dụ: liên quan đến câu hỏi chủ đề chính, nó có thể được sử dụng để giải mã một chuỗi html (rõ ràng là vì một số lý do lưu trữ), bất kể các thẻ html và sau đó dễ dàng mã hóa lại thành html khi và nếu được yêu cầu.
suncat100
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.