HtmlSpecialChars เทียบเท่าใน Javascript หรือไม่


167

เห็นได้ชัดว่ามันยากกว่าที่ฉันคิด และมันก็ง่ายมาก ...

มีฟังก์ชั่นที่เทียบเท่ากับ htmlspecialchars ของ PHP ที่สร้างขึ้นใน Javascript หรือไม่? ฉันรู้ว่ามันค่อนข้างง่ายที่จะติดตั้งด้วยตัวเอง แต่การใช้ฟังก์ชั่นในตัวถ้ามีก็เป็นเรื่องที่ดี

สำหรับผู้ที่ไม่คุ้นเคยกับ PHP, htmlspecialchars แปลสิ่งที่ชอบ<htmltag/>ลงไป&lt;htmltag/&gt;

ฉันรู้ว่าescape()และencodeURI()ไม่ทำงานด้วยวิธีนี้


php มีเครื่องมือที่ดีจริง ๆ , var_dump, print_r, htmlspecialchars ฯลฯ น่าเสียดายที่ฉันสงสัยว่าไม่เหมือนกันกับ js การแจ้งเตือน js แย่มาก วิธีที่รวดเร็วในการดูว่ามีบางสตริงที่ไม่คาดคิด (และมองไม่เห็นในกล่องการแจ้งเตือน) กำลังจะแจ้งเตือนความยาวสตริงแทนที่จะเป็นสตริง
Melsi


ดูstackoverflow.com/a/12034334/8804293มันมีคำตอบที่ดี
Elijah Mock

คำตอบ:


330

มีปัญหาเกี่ยวกับรหัสโซลูชันของคุณ - มันจะหลบหนีการเกิดขึ้นครั้งแรกของอักขระพิเศษแต่ละตัวเท่านั้น ตัวอย่างเช่น:

escapeHtml('Kip\'s <b>evil</b> "test" code\'s here');
Actual:   Kip&#039;s &lt;b&gt;evil</b> &quot;test" code's here
Expected: Kip&#039;s &lt;b&gt;evil&lt;/b&gt; &quot;test&quot; code&#039;s here

นี่คือรหัสที่ทำงานอย่างถูกต้อง:

function escapeHtml(text) {
  return text
      .replace(/&/g, "&amp;")
      .replace(/</g, "&lt;")
      .replace(/>/g, "&gt;")
      .replace(/"/g, "&quot;")
      .replace(/'/g, "&#039;");
}

ปรับปรุง

โค้ดต่อไปนี้จะให้ผลลัพธ์ที่เหมือนกันกับด้านบน แต่มันจะทำงานได้ดีขึ้นโดยเฉพาะในกลุ่มข้อความขนาดใหญ่ (ขอบคุณjbo5112 )

function escapeHtml(text) {
  var map = {
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    '"': '&quot;',
    "'": '&#039;'
  };
  
  return text.replace(/[&<>"']/g, function(m) { return map[m]; });
}

5
สิ่งที่ดีเกี่ยวกับฟังก์ชั่นนี้ก็คือการทำงานใน Node.js ซึ่งไม่ได้มี Dom โดยเริ่มต้น
booyaa

6
มันเร็วกว่าที่จะใช้ฟังก์ชั่นการแทนที่และการทำแผนที่เดียวและการแทนที่แบบเดี่ยวจะดีกว่ามาก ( jsperf.com/escape-html-special-chars/11 )
jbo5112

1
@ jbo5112 จุดที่ดีฉันไม่ได้ตระหนักถึงการอนุญาตให้เรียกกลับ JS สำหรับการทดแทน รหัสนี้ง่ายต่อการเข้าใจและฉันสงสัยว่าการโกนสักสองสามมิลลิวินาทีจาก escapeHtml () จะสร้างความแตกต่างเว้นแต่คุณจะเรียกมันหลายร้อยครั้งติดต่อกันด้วยเหตุผลบางประการ
กี

นี้จะบิดเบือน URL ในข้อความซึ่งทำให้พวกเขาไม่สามารถใช้งานปลั๊กอินเช่นAutolinker.js มีวิธีใดบ้างในการเข้าถึงสิ่งนี้?
Radek Matěj

4
@ RadekMatějแม้ในกรณีนั้นมันใช้ได้อย่างสมบูรณ์ (ดีกว่าฉันจะเถียง) สำหรับ ampersands ทั้งสองจะถูกเข้ารหัสเมื่อใช้ในเอกสาร HTML ฉันจะยังคงคิดว่ามันเป็นบั๊กกับปลั๊กอิน
กีบ

31

นั่นคือการเข้ารหัส HTML ไม่มีฟังก์ชั่นจาวาสคริปต์ที่จะทำเช่นนั้นได้ แต่คุณสามารถ google และทำให้เสร็จได้อย่างสวยงาม

เช่นhttp://sanzon.wordpress.com/2008/05/01/neat-little-html-encoding-trick-in-javascript/

แก้ไข:
นี่คือสิ่งที่ฉันได้ทดสอบ:

var div = document.createElement('div');
  var text = document.createTextNode('<htmltag/>');
  div.appendChild(text);
  console.log(div.innerHTML);

เอาท์พุท: &lt;htmltag/&gt;


แย่มากฉันจะต้องใช้ฟังก์ชั่นที่กำหนดเองแล้ว
Bart van Heukelom

คุณสามารถลองวิธีการในลิงค์ที่ฉันได้รวมไว้ในโพสต์ของฉัน แนวคิดที่สวยเนี๊ยบ
okw

@okw: ตกลงก่อนอื่นคุณเชื่อมโยงกับสิ่งนี้: yuki-onna.co.uk/html/encode.htmlซึ่งทำสิ่งที่ถูกencodeURIComponentต้องและไม่ตรงกับที่ OP ถาม ดังนั้นคุณสามารถแก้ไขได้ไหม ฉันไม่สามารถยกเลิก -1 ของฉันได้
Crescent Fresh

ย่ะโค้ดของหน้านั้นดูสมเหตุสมผล แต่ฉันไม่ได้ทดสอบ ลิงค์ใหม่ใช้งานได้ แต่ฉันได้ตรวจสอบตัวเองแล้ว ฉันได้อัปเดตโพสต์ก่อนหน้านี้แล้ว
okw

@BeauCielBleu: ไม่โหนดเดียวที่สร้างขึ้นเป็นdivองค์ประกอบเดียวและโหนดข้อความ การสร้างโหนดข้อความด้วยข้อความ `<img src = bogus onerror = alert (1337)>` เพียงแค่สร้างโหนดข้อความไม่ใช่imgองค์ประกอบ
Tim Down

26

ควรอ่าน: http://bigdingus.com/2007/12/29/html-escaping-in-javascript/

escapeHTML: (function() {
 var MAP = {
   '&': '&amp;',
   '<': '&lt;',
   '>': '&gt;',
   '"': '&#34;',
   "'": '&#39;'
 };
  var repl = function(c) { return MAP[c]; };
  return function(s) {
    return s.replace(/[&<>'"]/g, repl);
  };
})()

หมายเหตุ : เรียกใช้เพียงครั้งเดียวเท่านั้น และอย่ารันบนสายอักขระที่เข้ารหัสแล้วเช่น&amp;จะกลายเป็น&amp;amp;


3
นี่ควรเป็นคำตอบที่ได้รับการยอมรับและโหวตสูงสุด ฉันไม่แน่ใจว่าทำไมไม่มีการลงคะแนน การเปรียบเทียบนี้เป็นวิธีที่เร็วที่สุดด้วยทั้งผลการค้นหาของ Google ขนาดยาว (326KB) และสตริงอินพุตแบบสั้นบน jsperf ( jsperf.com/escape-html-special-chars/11 ) กรุณาลงคะแนนนี้
jbo5112

คำตอบที่ได้รับคะแนนสูงสุดต่างกันอย่างไร? ทำไมฟังก์ชั่นเสริมเพิ่มเติม? คำอธิบายสามารถช่วยให้ผู้ใช้เข้าใจดีขึ้น
Kosem

19

ด้วย jQuery มันจะเป็นดังนี้:

var escapedValue = $('<div/>').text(value).html();

จากคำถามที่เกี่ยวข้อง หนีสตริง HTML ด้วย jQuery

ดังที่ได้กล่าวไว้ในเครื่องหมายคำพูดคู่ความคิดเห็นและเครื่องหมายคำพูดเดี่ยวจะคงอยู่ตามความเป็นจริงสำหรับการใช้งานนี้ นั่นหมายความว่าไม่ควรใช้วิธีแก้ปัญหานี้หากคุณต้องการสร้างแอททริบิวองค์ประกอบเป็นสตริงดิบ html


2
ความคิดใด ๆ หากมีค่าใช้จ่ายใด ๆ ในการนี้ - การเพิ่มวัตถุจำลองไปยัง DOM?
กีบ

และมีข้อดีอื่น ๆ อีกไหม (พูดว่าถ้าคุณมีอักขระ Unicode หรือบางอย่าง)?
คิป

4
สิ่งที่ฉันพบกับสิ่งนี้: เครื่องหมายอัญประกาศคู่และอัญประกาศเดี่ยวเป็นไปตามที่เป็น สิ่งนี้ทำให้เกิดปัญหาได้หากคุณต้องการใช้ในค่าคุณลักษณะ
กี

1
สำหรับข้อความขนาดเล็กสิ่งนี้ใช้เวลา 30x ตราบใดที่ใช้งานการแทนที่ทั้งหมด มันจะขยายขนาดได้ดีขึ้น ด้วยสิ่งที่ใหญ่โตพอ ๆ กับหน้าผลการค้นหาของ Google (326KB) มันจึงเร็วกว่าการแทนที่ 25-30% หรือทำสิ่งนี้ด้วยจาวาสคริปต์แบบตรง อย่างไรก็ตามพวกเขาทั้งหมดสูญเสียการแทนที่เดียวและฟังก์ชันการแมปอย่างสม่ำเสมอ
jbo5112

4
ผู้คนโหวตให้กับคำตอบนี้อย่างไร: คำตอบมี jquery: +1 - ไม่หนีราคาเดียวและสองคำ: ummmm .. (เกาหัว) .. +1 <!-- Caps rage begin --> คำตอบนี้ควรจะมีคะแนนเป็นลบเพราะไม่ได้มาใกล้กับคำถาม "HtmlSpecialChars ที่เทียบเท่า" <!-- Caps rage end -->มันจะไม่หนี-คำพูดของพระเยซูคริสต์และอื่นเทพ OMGคุณ jquery คน
Sharky

19

นี่คือฟังก์ชั่นเพื่อหลบหนี HTML:

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

และเพื่อถอดรหัส:

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

6

Underscore.js จัดเตรียมฟังก์ชันสำหรับสิ่งนี้:

_.escape(string)

หนีจากสตริงสำหรับการแทรกไปยัง HTML, การแทนที่ &, <,>, "และตัวอักษร '

http://underscorejs.org/#escape

มันไม่ใช่ฟังก์ชั่น Javascript ในตัว แต่ถ้าคุณใช้ Underscore อยู่แล้วมันเป็นทางเลือกที่ดีกว่าการเขียนฟังก์ชั่นของคุณเองถ้าสายของคุณที่จะแปลงไม่ใหญ่เกินไป


5

อีกสิ่งหนึ่งที่ต้องทำคือการละทิ้งการแม็พอักขระทั้งหมดและเพื่อแปลงอักขระที่ไม่ต้องการทั้งหมดให้เป็นการอ้างอิงอักขระตัวเลขที่เกี่ยวข้องเช่น:

function escapeHtml(raw) {
    return raw.replace(/[&<>"']/g, function onReplace(match) {
        return '&#' + match.charCodeAt(0) + ';';
    });
}

โปรดทราบว่า RegEx ที่ระบุจะจัดการกับอักขระเฉพาะที่ OP ต้องการหนี แต่ขึ้นอยู่กับบริบทที่จะใช้ HTML ที่ใช้ Escape นั้นอักขระเหล่านี้อาจไม่เพียงพอ บทความของ Ryan Grove มีมากกว่า HTML ที่หลบหนีมากกว่า &, <,> และ "เป็นการอ่านที่ดีในหัวข้อและขึ้นอยู่กับบริบทของคุณจำเป็นต้องใช้ RegEx ต่อไปนี้เพื่อหลีกเลี่ยงการฉีด XSS:

var regex = /[&<>"'` !@$%()=+{}[\]]/g

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

ตัวอย่าง:

var toto = "test<br>";
alert(toto.escapeHTML());

3

โอกาสที่คุณไม่ต้องการฟังก์ชั่นดังกล่าว เนื่องจากรหัสของคุณอยู่ในเบราว์เซอร์ * แล้วคุณสามารถเข้าถึง DOM โดยตรงแทนที่จะสร้างและเข้ารหัส HTML ซึ่งจะต้องถูกถอดรหัสย้อนกลับโดยเบราว์เซอร์ที่จะใช้งานจริง

ใช้innerTextคุณสมบัติเพื่อแทรกข้อความธรรมดาใน DOM อย่างปลอดภัยและเร็วกว่าการใช้ฟังก์ชั่นหลบหนีใด ๆ ที่นำเสนอ แม้ได้เร็วขึ้นกว่ากำหนดสตริง preencoded innerHTMLคงที่

ใช้classListเพื่อแก้ไขคลาสdatasetเพื่อตั้งค่าdata-คุณสมบัติและsetAttributeสำหรับผู้อื่น

ทั้งหมดนี้จะช่วยคุณหลบหนี แม่นยำยิ่งขึ้นไม่จำเป็นต้องมีการหลบหนีและไม่มีการเข้ารหัสใด ๆ ภายใต้ ** เนื่องจากคุณกำลังทำงานกับ HTML ซึ่งเป็นตัวแทนข้อความต้นฉบับของ DOM

// use existing element
var author = 'John "Superman" Doe <john@example.com>';
var el = document.getElementById('first');
el.dataset.author = author;
el.textContent = 'Author: '+author;

// or create a new element
var a = document.createElement('a');
a.classList.add('important');
a.href = '/search?q=term+"exact"&n=50';
a.textContent = 'Search for "exact" term';
document.body.appendChild(a);

// actual HTML code
console.log(el.outerHTML);
console.log(a.outerHTML);
.important { color: red; }
<div id="first"></div>

* คำตอบนี้ไม่ได้มีไว้สำหรับผู้ใช้ JavaScript ฝั่งเซิร์ฟเวอร์ (Node.js ฯลฯ )

** หากคุณไม่แปลงเป็น HTML จริงในภายหลัง เช่นโดยการเข้าถึงinnerHTML- นี่คือสิ่งที่เกิดขึ้นเมื่อคุณเรียกใช้$('<div/>').text(value).html();คำแนะนำในคำตอบอื่น ๆ ดังนั้นหากเป้าหมายสุดท้ายของคุณคือการแทรกข้อมูลลงในเอกสารด้วยวิธีนี้คุณจะทำงานสองครั้ง นอกจากนี้คุณจะเห็นได้ว่าใน HTML ที่ได้ผลลัพธ์ไม่ใช่ทุกอย่างที่ถูกเข้ารหัสมีเพียงค่าต่ำสุดที่จำเป็นเพื่อให้ถูกต้อง มันทำขึ้นอยู่กับบริบทนั่นคือสาเหตุที่วิธีการ jQuery นี้ไม่ได้เข้ารหัสคำพูดและดังนั้นจึงไม่ควรใช้เป็น escaper วัตถุประสงค์ทั่วไป การหลีกเลี่ยงการเสนอราคาเป็นสิ่งจำเป็นเมื่อคุณสร้าง HTML เป็นสตริงที่มีข้อมูลที่ไม่น่าเชื่อถือหรือมีเครื่องหมายคำพูดอยู่ที่ค่าแอตทริบิวต์ หากคุณใช้ DOM API คุณไม่ต้องกังวลกับการหลบหนีเลย


ขอบคุณสำหรับสิ่งนี้! ฉันใช้เวลานานในการมองหาวิธีแก้ปัญหาง่ายๆเช่นนี้ สิ่งสำคัญสิ่งหนึ่งที่ฉันค้นพบคือถ้าข้อความของคุณมีการขึ้นบรรทัดใหม่คุณจะต้องแทนที่ด้วยตัวแบ่งบรรทัด HTML (คล้ายel.textContent = str; el.innerHTML = el.innerHTML.replace(/\n/g, '<br>')) หรือตั้งค่าwhite-spaceคุณสมบัติCSS เป็นpreหรือpre-wrap
stellatedHexahedron

@stellatedHexahedron ขอบคุณที่ยกประเด็นนี้ขึ้น ฉันได้เปลี่ยนคำตอบของฉันที่จะแนะนำแทนinnerText textContentในขณะที่ช้าลงเล็กน้อยและมีความแตกต่างอื่น ๆเมื่ออ่านคุณสมบัติมันเป็นเรื่องที่เข้าใจได้ง่ายกว่าว่าจะทำการ<br>แทนที่โดยอัตโนมัติเมื่อกำหนดให้
ผู้ใช้

2

สำหรับผู้ใช้ Node.JS (หรือผู้ใช้ที่ใช้ Jade runtime ในเบราว์เซอร์) คุณสามารถใช้ฟังก์ชั่นหลบหนีของ Jade

require('jade').runtime.escape(...);

ไม่มีความรู้สึกในการเขียนด้วยตนเองถ้ามีคนอื่นกำลังดูแลมันอยู่ :)


1

ฉันอธิบายเพิ่มเติมเกี่ยวกับคำตอบของ okw

คุณสามารถใช้ฟังก์ชัน DOM ของเบราว์เซอร์ได้

var utils = {
    dummy: document.createElement('div'),
    escapeHTML: function(s) {
        this.dummy.textContent = s
        return this.dummy.innerHTML
    }
}

utils.escapeHTML('<escapeThis>&')

ผลตอบแทนนี้ &lt;escapeThis&gt;&amp;

มันใช้ฟังก์ชั่นมาตรฐานcreateElementเพื่อสร้างองค์ประกอบที่มองไม่เห็นจากนั้นใช้ฟังก์ชั่นtextContentเพื่อตั้งค่าสตริงใด ๆ ที่เป็นเนื้อหาของมันและจากนั้นinnerHTMLจะได้รับเนื้อหาในการเป็นตัวแทน HTML


0
function htmlspecialchars(str) {
 if (typeof(str) == "string") {
  str = str.replace(/&/g, "&amp;"); /* must do &amp; first */
  str = str.replace(/"/g, "&quot;");
  str = str.replace(/'/g, "&#039;");
  str = str.replace(/</g, "&lt;");
  str = str.replace(/>/g, "&gt;");
  }
 return str;
 }

0

หวังว่านี่จะชนะการแข่งขันเนื่องจากประสิทธิภาพและที่สำคัญที่สุดไม่ใช่ตรรกะที่ถูกล่ามโซ่โดยใช้. แทนที่ ('&', '&') แทนที่ ('<', '<') ...

var mapObj = {
   '&':"&amp;",
   '<':"&lt;",
   '>':"&gt;",
   '"':"&quot;",
   '\'':"&#039;"
};
var re = new RegExp(Object.keys(mapObj).join("|"),"gi");

function escapeHtml(str) 
{   
    return str.replace(re, function(matched)
    {
        return mapObj[matched.toLowerCase()];
    });
}

console.log('<script type="text/javascript">alert('Hello World');</script>');
console.log(escapeHtml('<script type="text/javascript">alert('Hello World');</script>'));

0

ย้อนกลับหนึ่ง:

function decodeHtml(text) {
    return text
        .replace(/&amp;/g, '&')
        .replace(/&lt;/ , '<')
        .replace(/&gt;/, '>')
        .replace(/&quot;/g,'"')
        .replace(/&#039;/g,"'");
}

คำถามไม่ได้ถามวิธีถอดรหัสเอนทิตี ตรงข้ามกับคำถามที่ถาม
เควนติ

สิ่งนี้จะแทนที่อินสแตนซ์แรกของ&lt;และ&gr;ในสตริงเท่านั้น
เควนติ

การดำเนินการนี้จะถอดรหัสอักขระห้าตัวที่ (นอกเอกสารที่ไม่ใช่ Unicode) เท่านั้นซึ่งจะต้องถูกหลบหนี แต่จะไม่ถอดรหัสอักขระที่อาจหนีออกมาได้
เควนติ

สิ่งนี้ไม่ได้พิจารณาถึงกฎว่าเมื่อใดที่เครื่องหมายอัฒภาคจะเป็นตัวเลือก
เควนติ

หาก HTML พูดว่า: To write a greater than sign in HTML type &amp;gt;จะแสดงอย่างไม่ถูกต้อง>แทน&gt;
Quentin

0

OWASP แนะนำว่า "[e] xcept สำหรับตัวอักษรและตัวเลขคุณควรหลีกเลี่ยงอักขระทั้งหมดที่มีค่า ASCII น้อยกว่า 256 ด้วย&#xHH;รูปแบบ (หรือเอนทิตีที่มีชื่อถ้ามี) เพื่อป้องกันการสลับจากแอตทริบิวต์ [an]"

ดังนั้นนี่คือฟังก์ชันที่ทำเช่นนั้นพร้อมตัวอย่างการใช้งาน:

function escapeHTML(unsafe) {
  return unsafe.replace(
    /[\u0000-\u002F]|[\u003A-\u0040]|[\u005B-\u00FF]/g,
    c => '&#' + ('000' + c.charCodeAt(0)).substr(-4, 4) + ';'
  )
}
document.querySelector('div').innerHTML =
  '<span class=' +
  escapeHTML('this should break it! " | / % * + , - / ; < = > ^') +
  '>' +
  escapeHTML('<script>alert("inspect the attributes")\u003C/script>') +
  '</span>'
<div></div>


-1
function htmlEscape(str){
    return str.replace(/[&<>'"]/g,x=>'&#'+x.charCodeAt(0)+';')
}

วิธีนี้ใช้รหัสตัวเลขของตัวละครตัวอย่างเช่น<ถูกแทนที่ด้วย&#60;จะถูกแทนที่ด้วย

แม้ว่าประสิทธิภาพของมันจะแย่กว่าเล็กน้อย โซลูชันที่ใช้แผนที่แต่ก็มีข้อดี:

  • ไม่ขึ้นอยู่กับไลบรารีหรือ DOM
  • ง่ายต่อการจดจำ (คุณไม่จำเป็นต้องจำตัวอักษร HTML 5 ตัว)
  • รหัสน้อย
  • เร็วพอสมควร (มันยังเร็วกว่าการแทนที่เชนที่ 5)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.