วิธีที่เร็วที่สุดในการหลีกเลี่ยงแท็ก HTML เป็นเอนทิตี HTML?


100

ผมเขียนส่วนขยายของ Chrome ที่เกี่ยวข้องกับการทำมากของงานดังต่อไปนี้: ฆ่าเชื้อสตริงที่อาจมีแท็ก HTML โดยการแปลง<, >และ&เพื่อ&lt;, &gt;และ&amp;ตามลำดับ

(กล่าวอีกนัยหนึ่งก็เหมือนกับของ PHP htmlspecialchars(str, ENT_NOQUOTES)- ฉันไม่คิดว่าจะต้องมีการแปลงอักขระแบบ double-quote)

นี่เป็นฟังก์ชั่นที่เร็วที่สุดที่ฉันเคยพบ:

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

แต่ยังคงมีความล่าช้าอย่างมากเมื่อฉันต้องวิ่งสองสามพันสายผ่านมันในครั้งเดียว

ใครสามารถปรับปรุงเรื่องนี้ได้บ้าง? ส่วนใหญ่เป็นสตริงระหว่าง 10 ถึง 150 อักขระหากสิ่งนั้นสร้างความแตกต่าง

(ความคิดหนึ่งที่ฉันมีคือไม่ต้องกังวลกับการเข้ารหัสเครื่องหมายที่ใหญ่กว่า - จะมีอันตรายจริงหรือไม่)


2
ทำไม? ในกรณีส่วนใหญ่ที่คุณต้องการทำเช่นนี้คุณต้องการแทรกข้อมูลลงใน DOM ซึ่งในกรณีนี้คุณควรลืมเกี่ยวกับการหลีกเลี่ยงและสร้าง textNode จากข้อมูลนั้น
Quentin

1
@David Dorward: บางทีเขาอาจต้องการล้างข้อมูล POST และเซิร์ฟเวอร์ไม่ได้รับข้อมูลอย่างถูกต้อง
Lie Ryan

4
@ ลี - ถ้าเป็นเช่นนั้นวิธีแก้ปัญหาคือ "เพื่อประโยชน์ของพีทแก้ไขเซิร์ฟเวอร์เนื่องจากคุณมีรู XSS ขนาดใหญ่"
Quentin

2
@David Dorward: เป็นไปได้ว่ากรณีนี้คือเขาไม่สามารถควบคุมเซิร์ฟเวอร์ได้ เมื่อเร็ว ๆ นี้ฉันเคยตกอยู่ในสถานการณ์เช่นนี้ซึ่งฉันกำลังเขียนสคริปต์ greasemonkey เพื่อแก้ไขปัญหาสองสามอย่างที่ฉันไม่ชอบในเว็บไซต์ของมหาวิทยาลัย ฉันต้องทำการ POST บนเซิร์ฟเวอร์ที่ฉันไม่สามารถควบคุมและทำความสะอาดข้อมูล POST โดยใช้จาวาสคริปต์ (เนื่องจากข้อมูลดิบมาจากกล่องข้อความที่หลากหลายและมีแท็ก html มากมายซึ่งไม่ได้ทำแบบไปกลับบนเซิร์ฟเวอร์) . ผู้ดูแลเว็บไม่สนใจคำขอของฉันให้แก้ไขเว็บไซต์ดังนั้นฉันจึงไม่มีทางเลือกอื่น
Lie Ryan

1
ฉันมีกรณีการใช้งานที่ฉันต้องการแสดงข้อความแสดงข้อผิดพลาดใน div ข้อความแสดงข้อผิดพลาดอาจมี HTML และบรรทัดใหม่ ฉันต้องการหลีกเลี่ยง HTML และแทนที่บรรทัดใหม่ด้วย <br> จากนั้นใส่ผลลัพธ์ลงใน div เพื่อแสดง
mozey

คำตอบ:


85

คุณสามารถลองส่งผ่านฟังก์ชันโทรกลับเพื่อทำการแทนที่:

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 ที่ Dmitrij เสนอ

หนทางของคุณน่าจะเร็วกว่า ...

ทำไมคุณถึงต้องการมัน?


2
>ไม่มีความจำเป็นที่จะหลบหนีคือ

6
จริงๆแล้วถ้าคุณใส่ค่า Escape ในแอตทริบิวต์ขององค์ประกอบ html คุณจะต้องออกจากสัญลักษณ์> มิฉะนั้นจะทำลายแท็กสำหรับองค์ประกอบ html นั้น
Zlatin Zlatev

1
ในข้อความปกติอักขระหนีจะหายาก จะดีกว่าที่จะโทรมาแทนที่เมื่อจำเป็นเท่านั้นหากคุณสนใจเกี่ยวกับความเร็วสูงสุด:if (/[<>&"]/.test(str) { ... }
Vitaly

3
@callum: ไม่ฉันไม่สนใจที่จะแจกแจงกรณีที่ฉันคิดว่า "อาจมีบางอย่างผิดพลาด" (ไม่น้อยเพราะเป็นกรณีที่ไม่คาดคิด / ลืมไปซึ่งจะทำร้ายคุณและเมื่อคุณคาดหวังอย่างน้อยที่สุด) ฉันสนใจในการเข้ารหัสตามมาตรฐาน (ดังนั้นกรณีที่ไม่คาดคิด / ลืมไม่สามารถทำร้ายคุณตามคำจำกัดความได้ ) ฉันไม่สามารถเน้นว่าสิ่งนี้สำคัญขนาดไหน >เป็นอักขระพิเศษใน HTML ดังนั้นจงหลีกเลี่ยง ง่ายๆแค่นั้นเอง :)
Lightness Races ใน Orbit

4
@LightnessRacesinOrbit มันเกี่ยวข้องเพราะคำถามคือวิธีที่เร็วที่สุดที่เป็นไปได้คืออะไร หากเป็นไปได้ที่จะข้ามการ>เปลี่ยนทดแทนนั่นจะทำให้เร็วขึ้น
callum

105

วิธีหนึ่งที่คุณสามารถทำได้มีดังนี้

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 เพื่อหลีกเลี่ยงข้อความตามตัวอักษร ดีมากฉันคิดว่าเคล็ดลับเล็ก ๆ น้อย ๆ นี้กำลังจะหาบ้านอีกหลัง
Ajax

3
@jazkat ฉันไม่ได้ใช้ฟังก์ชันนั้น ตัวแปร Escape ที่ฉันใช้ฉันกำหนดตัวเองในตัวอย่าง
Web_Designer

2
แต่สิ่งนี้จะสูญเสียพื้นที่สีขาวหรือไม่
Andrew

32

วิธีการของ Martijn เป็นฟังก์ชันต้นแบบ:

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 โดยทั่วไป นั่นคือString.escapeHtmlถูกต้อง แต่String.escapeทำให้เกิดคำถามว่า "หนีอะไร?"
Lawrence Dol

3
ใช่ความคิดที่ดี ฉันย้ายออกจากการขยายต้นแบบในวันนี้เพื่อหลีกเลี่ยงความขัดแย้ง
Aram Kocharyan

1
หากเบราว์เซอร์ของคุณรองรับ Symbol คุณสามารถใช้สิ่งนั้นแทนเพื่อหลีกเลี่ยงไม่ให้เนมสเปซคีย์สตริงเป็นมลพิษ var escape = สัญลักษณ์ใหม่ ("escape"); String.prototype [escape] = function () {... }; "text" [หนี] ();
Ajax

บวกหนึ่งสำหรับตัวอย่าง
Timo

13

วิธีที่เร็วที่สุดคือ:

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

วิธีการนี้จะเป็นเรื่องเกี่ยวกับสองครั้งเร็วกว่าวิธีการขึ้นอยู่กับ 'แทนที่' ดูhttp://jsperf.com/htmlencoderegex/35

ที่มา: https://stackoverflow.com/a/17546215/698168


12

วิธีแก้ปัญหาที่เร็วขึ้น / สั้นลงคือ:

escaped = new Option(html).innerHTML

สิ่งนี้เกี่ยวข้องกับร่องรอยแปลก ๆ ของ JavaScript โดยที่องค์ประกอบ Option ยังคงเป็นตัวสร้างที่ทำแบบนี้โดยอัตโนมัติ

เครดิตhttps://github.com/jasonmoo/t.js/blob/master/t.js


1
ซับเดียวเรียบร้อย แต่เป็นวิธีที่ช้าที่สุดหลังจาก regex นอกจากนี้ข้อความที่นี่อาจมีการเว้นวรรคตามข้อมูลจำเพาะ
ShortFuse

โปรดทราบว่าลิงก์ "วิธีการที่ช้าที่สุด" ของ @ ShortFuse ทำให้ระบบของฉันใช้ RAM หมด (พร้อม ~ 6GB ฟรี) และดูเหมือนว่า firefox จะหยุดการจัดสรรก่อนที่หน่วยความจำจะหมดดังนั้นแทนที่จะฆ่ากระบวนการที่ละเมิด linux จะนั่งที่นั่นและปล่อยให้คุณทำ ปิดเครื่องยาก
Luc

11

รหัส AngularJS แหล่งที่มานอกจากนี้ยังมีภายในรุ่นของเชิงมุม-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
ว้าว regex ที่ไม่ใช่ตัวอักษรและตัวเลขนั้นเข้มข้นมาก ฉันไม่คิดว่า | ในนิพจน์นั้นจำเป็น
Ajax

9

สคริปต์ All-in-one:

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


ฉันไม่ได้ลงคะแนน แต่การแทนที่ regex style ทั้งหมดจะล้มเหลวในการเข้ารหัส Unicode ... ดังนั้นใครก็ตามที่ใช้ภาษาต่างประเทศจะต้องผิดหวัง เคล็ดลับ <textarea> ที่กล่าวถึงข้างต้นนั้นเจ๋งมากและจัดการทุกอย่างได้อย่างรวดเร็วและปลอดภัย
Ajax

regex ทำงานได้ดีสำหรับฉันด้วยอักขระ Unicode ที่ไม่ใช่ละติน ฉันจะไม่คาดหวังสิ่งอื่นใด คุณคิดว่ามันจะไม่ได้ผลได้อย่างไร? คุณกำลังคิดถึง codepages แบบไบต์เดียวที่ต้องใช้เอนทิตี HTML หรือไม่? นั่นคือสิ่งที่ฟังก์ชันที่ 3 และ 4 มีไว้สำหรับและไม่ใช่ฟังก์ชันที่ 1 และ 2 อย่างชัดเจน ฉันชอบความแตกต่าง
ygoe

@LonelyPixel ฉันไม่คิดว่าเขาจะเห็นความคิดเห็นของคุณถ้าคุณไม่พูดถึงเขา ("สามารถแจ้งผู้ใช้เพิ่มเติมได้เพียงคนเดียวเจ้าของโพสต์จะได้รับการแจ้งเตือนเสมอ")
baptx

ฉันไม่ทราบว่ามีการแจ้งเตือนที่ตรงเป้าหมายเลย @Ajax โปรดดูความคิดเห็นของฉันด้านบน
ygoe

@LonelyPixel ฉันเห็นแล้ว ด้วยเหตุผลบางอย่างฉันไม่คิดว่าจะมีการแทนที่สไตล์ textarea ในคำตอบนี้ ฉันกำลังคิดถึงค่า Unicode ขนาดใหญ่ของ codepoint สองเท่าเช่นภาษาจีนกลาง ฉันหมายความว่ามันจะเป็นไปได้ที่จะทำให้ regex ฉลาดพอ แต่เมื่อคุณดูทางลัดที่ผู้ขายเบราว์เซอร์สามารถทำได้ฉันจะรู้สึกดีที่การพนันว่า textarea จะเร็วกว่ามาก (กว่า regex ที่มีความสามารถอย่างสมบูรณ์) มีคนโพสต์เกณฑ์มาตรฐานสำหรับคำตอบนี้หรือไม่? ฉันสาบานว่าฉันเคยเห็นมาแล้ว
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

ฉันไม่แน่ใจว่าทั้งหมดเกี่ยวกับความเร็ว แต่ถ้าคุณกำลังมองหาเรียบง่ายฉันจะแนะนำให้ใช้ lodash / ขีดหลบหนีฟังก์ชั่น


0

วิธี Martijn เป็นฟังก์ชั่นเดียวกับการจัดการ"เครื่องหมาย ( ใช้ใน 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);
}

คุณสามารถดูสิ่งที่กำลังแทนที่ได้โดยการตรวจสอบข้อมูลจำเพาะทั้งสำหรับโหนดข้อความและสำหรับค่าแอตทริบิวต์ค่าแอตทริบิวต์เอกสารฉบับเต็มมีประเภทโหนดมากกว่า แต่แนวคิดเหมือนกัน

สำหรับประสิทธิภาพจะเร็วที่สุดเมื่อไม่ได้แคช เมื่อคุณอนุญาตให้แคชแล้วโทรinnerHTMLใช้ HTMLElement ด้วยโหนดข้อความลูกจะเร็วที่สุด 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
สิ่งเหล่านี้ทำบางสิ่งที่ไม่เกี่ยวข้องโดยสิ้นเชิง
callum

1
บางทีคำว่า "สมบูรณ์" ในทางที่ผิดที่ใหญ่ที่สุดที่ฉันเคยได้ยินมา ตัวอย่างเช่นในส่วนที่เกี่ยวข้องกับคำถามหัวข้อหลักสามารถใช้เพื่อถอดรหัสสตริง html ได้ (เห็นได้ชัดว่ามีเหตุผลในการจัดเก็บบางอย่าง) โดยไม่คำนึงถึงแท็ก html จากนั้นเข้ารหัสกลับเป็น html อีกครั้งได้อย่างง่ายดายเมื่อจำเป็น
suncat100
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.