用 HTML br 替換 ↵ 換行前先想一想


Posted by SimonAllen on 2020-07-03

這篇算是一個隨手紀錄,不要輕易用 <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 命名這麼長和使用方式改的很迂迴。

該怎麼改?

總之釐清一下,現在要處理這兩點問題:

  1. 文字換行
  2. 預防潛在的 xss 風險

使用 CSS 處理換行

使用 CSS white-spaceoverflow-wrapword-break 可以很好的處理換行

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 {
      '<': '&lt;',
      '&': '&amp;',
      '"': '&quot;',
      '>': '&gt;',
      "'": '&#39;',
      '/': '&#x2F',
    }[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


#javascript #React.js #css







Related Posts

認識Kubernetes (學習隨筆)

認識Kubernetes (學習隨筆)

進階 React Component Patterns 筆記(上)

進階 React Component Patterns 筆記(上)

Python Table Manners - 測試 (二)

Python Table Manners - 測試 (二)


Comments