這篇算是一個隨手紀錄,不要輕易用 <br/>
替換換行 ↵
,先想想有沒有其他的方式。
起因
前陣子在處理前人的 Legacy code,有個情境是使用者在後台頁面輸入文章,前端把其 show 在前台頁面上。
當然後端會在需顯示的前台頁面 API 將其(字串)發過來,這個字串不包含 HTML 標籤,可能包含換行符號,為了處理這個狀況,前人寫了個共用 function 來將換行符號替換成 HTML <br/>
標籤,例如:
const setTextLineFeed = text => text.replace(/\n/g,'<br/>');
將後端傳來包含換行符號的字串,先丟進這個 function 處理過,好像很合理。
現在處理過的字串多了 HTML 標籤 <br>
,前端開發是用 React,為了解決字串多了 <br>
標籤並達成換行,原本顯示文字的地方就用了 dangerouslySetInnerHTML
這樣寫有什麼問題?
其實 <br/>
本身沒什麼問題,有問題是上面這個思路。
replace <br/>
後端 API 發來的 response 參數可能是這樣
text: "早安↵您好"
預期網頁上看到
早安
您好
而不是
早安↵您好
// or
早安 您好
text 值本來不包含 HTML Tag,為了要處理 ↵
,才用上了自己寫的 setTextLineFeed 去 replace,只不過 replace 後的 text 變成
"早安<br/>您好"
顯示方面就必須這樣寫
<p
dangerouslySetInnerHTML={{ __html: setTextLineFeed(text) }}
/>
dangerouslySetInnerHTML
看看官方文件說明
dangerouslySetInnerHTML 是 React 用來替代 DOM 的 innerHTML。普遍來說,從程式碼中注入 HTML 是個冒險的行為,你會很輕易地讓使用者暴露在 cross-site scripting (XSS) 攻擊風險之下。所以在 React 裡你還是可以直接注入 HTML,但是你必須使用 dangerouslySetInnerHTML,然後傳入一個有
__html
為 key 的 object
只要用到 innerHTML
就有 XSS 風險,尤其不能信任使用者自行輸入的內容。但是現實世界中,即便用了 React 開發網站,可能還是有需要用到 innerHTML
的情境(例如後端發來包含 HTML 的字串),說穿了就是 React 要提醒開發者注意 XSS 攻擊故意將包好的 innerHTML
命名這麼長和使用方式改的很迂迴。
該怎麼改?
總之釐清一下,現在要處理這兩點問題:
- 文字換行
- 預防潛在的 xss 風險
使用 CSS 處理換行
使用 CSS white-space
、overflow-wrap
、word-break
可以很好的處理換行
overflow-wrap
https://developer.mozilla.org/zh-TW/docs/Web/CSS/overflow-wrapword-break
https://developer.mozilla.org/en-US/docs/Web/CSS/word-breakwhite-space
https://developer.mozilla.org/zh-TW/docs/Web/CSS/white-space
CSS 具體用法就不解釋,詳情看文件。
所以 setTextLineFeed
這個 function 用不到了,可以拿掉 replace <br>
這個動作。
原本這樣寫
<p
dangerouslySetInnerHTML={{ __html: setTextLineFeed(text) }}
/>
改成
<p>{text}</p>
p {
white-space: pre-wrap;
}
就好了!
使用 replace 替換危險字元
如果不使用 CSS 解,照舊使用 dangerouslySetInnerHTML
,那我們需要先濾掉潛在的危險字元才 replace 換行成 <br>
const replaceXSSText = str => {
return str.replace(/[<">'&/]/g, dangerText => {
return {
'<': '<',
'&': '&',
'"': '"',
'>': '>',
"'": ''',
'/': '/',
}[dangerText];
});
};
不過要注意,若 dangerouslySetInnerHTML 對象本來本來就包含 HTML Tag (不然幹嘛用 dangerouslySetInnerHTML),那用上面這個 function 去處理就完全沒意義了,會將 <
、&
、"
、>
、'
、/
顯示出來。
後記
其實這篇技術含量不高,但和周遭一些前端聊到,發現好幾位第一時間都是想 replace,也許是我同溫層的問題(汗)
我覺得挺可怕的是:對很多寫 JS 習慣的前端工程師來說,遇到類似的問題都是習慣性地用 JS 去處理這件事情,撇開開發上的資安敏感度,CSS 能解決的事就應當用 CSS 才對。
參考
https://zh-hant.reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
https://developer.mozilla.org/zh-TW/docs/Web/CSS/overflow-wrap
https://developer.mozilla.org/en-US/docs/Web/CSS/word-break
https://developer.mozilla.org/zh-TW/docs/Web/CSS/white-space