為什麼在知乎編輯框里正確處理字元轉義如此困難?

眾所周知,將用戶在文本框里輸入的內容在網頁上展示出來,是需要對一些特殊字元做轉義處理的。為了正確並安全地顯示用戶輸入內容,需要轉義的字元有 7 個,它們分別是:and 符號、小於號、大於號、單引號、雙引號、斜杠以及段首白空格和段中多個連續白空格。

然而,知乎對於這些轉義字元的處理一直是錯誤的,提了很多次意見之後知乎團隊一直表示他們一直在努力,但由於人手有限,這個問題解決起來比較困難。以一個局外人的眼光,我一直覺得這個問題的解決僅僅只是幾個字元的查找替換而已,應該基本上不需要佔用多少時間。可既然這個問題一直得不到解決,我想知道的是,為什麼這個問題對於知乎來說如此之困難?到底是什麼原因導致的?


我們現在討論的這個問題和嵌入 code 一點關係都沒有。如果支持嵌入 code,無非就是可以讓部分內容用等寬字體顯示,或者再高級一點,能支持個關鍵字高亮,那些功能如果能有當然能好,但是現階段其實並不著急,而且正如你們所說的,「眾口難調」,所以這些高級功能,的確是不那麼迫切需要的。而我現在一直對知乎團隊窮追不捨的,僅僅是最最基本的功能:讓編輯器能夠原樣還原用戶的輸入。我們現在要討論的也不是什麼「更好的處理實現方法」,而是正確的處理實現方法。如果連正確都沒有保障的話,「更好」根本無從談起。

@馬驍 給出的解釋是要支持富文本,並且保證安全和性能,這個解釋我完全不認同。我們先看一下用戶輸入的大於小於號是如何到達知乎的吧:

1. 用戶在高級編輯器里輸入了一段文字。由於編輯器支持富文本,因此編輯器會在客戶端用 HTML 格式對所有輸入內容進行編碼。此時,我們輸入的所有特殊符號,譬如我們前面提到的大於小於號,在客戶端就已經正確地編碼成 entity 了,也就是說,此時的大於小於號已經分別是 > 和 < 這樣的形式了。

2. 用戶點擊了「提交」按鈕,此時,瀏覽器發起了一個 AJAX 請求,以 POST 的方式對知乎伺服器提交數據。此時,瀏覽器把已經正確進行過 entity 轉義的內容用 URL Encoded 格式進行編碼。此時的大於小於號在前往伺服器的途中是這樣的格式:%26gt%3B 和 %26lt%3B%。

3. 服務端收到客戶端發來的請求後,對數據進行 URL Decode 解碼,解碼完畢後大於小於號變回 > 和 < 這樣的形式。

那我們需要怎麼樣才能讓用戶原樣看到之前輸入的大於小於號呢?答案是,為了讓用戶能看到這兩個符號,我們必須以 entity 的方式輸出給用戶,也就是說,我們需要返回給用戶 > 和 < 這樣的形式才行。既然伺服器拿到的數據已經是這樣的形式了,因此,為了讓用戶能夠正常看到,我們對於這部分內容應該做的事情就是讓它保持原樣就好

從上面的步驟可以看出,從第 1 步到第 3 步,大於小於號都得到了正確的編碼,整個傳遞過程也是沒有問題的。而且,由於大於小於號已經被編碼了,因此用戶輸入的大於小於號是不會對伺服器造成任何傷害的。那麼,什麼樣的東西是有可能造成損害的呢?答案是,沒有被編碼過的真正的大於小於號。

我們已經說了,用戶輸入的大於小於號都會被編輯器自動轉義成 entity,那麼,為什麼會存在「真正的」大於小於號呢?那是因為,我們的高級編輯器是支持格式的,當用戶設置了格式,譬如說用戶選擇了加粗、傾斜的時候,高級編輯器就會插入一些 HTML 代碼來實現這些效果。另外,用戶輸入的回車,在高級編輯器里其實也是通過 HTML 標籤來實現的。這些 HTML 代碼在提交的時候會以「真正的大於小於號」的形式發送出去,這和用戶自己輸入的大於小於號是完全不一樣的。

為什麼這些「真正的大於小於號」是可能有害的呢?那是因為,通過這些「真正的大於小於號」,我們不僅僅可以對文字設置格式,我們還可以創建一些特殊的標籤,把一些惡意的腳本引入到答案裡面來,對其它瀏覽答案的人造成損害。雖然我們通過知乎內置的高級編輯器是無法創建這樣的惡意代碼的,可是,壞人顯然是可以繞過上面說的第 1 步、第 2 步,直接對伺服器提交專門構造出來的惡意數據給第 3 步的,不是么?

因此,知乎在收到第 3 步數據的時候,就必須對數據進行「消毒」處理了。消毒的過程稍微有點小複雜,不過原理其實很簡單:既然真正的大於小於號是罪魁禍首,那我們就需要對所有出現真正的大於小於號的地方嚴加防範,所有出現這些真正大於小於號的地方,我們都必須仔細地檢查一下,看看有沒有出現不是我們的編輯器構造出來的非法標籤,如果有的話,就把所有的非法標籤都消滅掉就可以了。

總而言之,為了保證大家的安全,我們所要做的事情就是,嚴加防範所有的「真正的大於小於號」,而對於用戶輸入的大於小於號,由於它們已經被編碼成 > 和 < 這樣的形式了,因此它們不屬於「真正的大於小於號」,因此我們是完全不需要考慮它們的。

可是,我一直不理解的一點就是,知乎在拿到正確編碼的數據之後,對它又進行了一次 entity 解碼,這下可不得了,已經正確編碼的 > 和 < 又重新變回了「真正的大於小於號」,和之前的別的真正的大於小於號全混在一起了。這下伺服器可如臨大敵了,為了保證大家的安全,只好再暴力干涉一次,乾脆把所有的小於號之後的內容全給刪了得了。於是,大家就發現,我怎麼打不出這個符號了:~~~ &> _ &< ~~~~

可是,為什麼呢?知乎為什麼要這麼做呢?為什麼要把已經正確編碼的大於小於號又做一次不必要的解碼呢?我不知道……你們知道么?我們還是把這個問題交給知乎團隊來回答吧,也許他們能告訴我們為什麼要這麼做。

以上說的是回答的編輯框里發生的情況。對於評論框,情況可就簡單太多了:由於評論框不支持格式,只支持純文本,因此評論是完全不存在「編碼過的大於小於號」和「真正的大於小於號」這兩個東西的區別的,事實上,評論裡面存在的大於小於號,全部都沒有經過編碼。那大家也許會問了,全都沒編碼,那不是很危險么?不,當然不是,全都沒編碼的話,那我們拿到數據之後,就對它們統一做一次全部編碼就好了,完全沒有前面說的「編碼過的大於小於」和「沒有編碼過的大於小於」混合在一起的這種困擾。編碼非常簡單,就是一個查找替換就行了,把所有的特殊符號都用 entity 來代替。全部編碼之後,文本裡面就不可能再出現任何「壞」的大於小於號了,因此我們沒有必要對它再做任何額外的消毒處理,比前面的情況還簡單。

那麼,為什麼知乎團隊不那麼做,卻要要把所有評論里的小於號之後的內容也全給刪了呢?為什麼呢?知乎為什麼要這麼做呢?我不知道……你們知道么?我們還是把這個問題交給知乎團隊來回答吧,也許他們能告訴我們為什麼要這麼做。


提交時伺服器端未對 html 特殊字元進行編碼

必須自行使用特殊編碼「 <」方能得到「 &< 」

---------------------補充---------------------

為什麼沒有呢?我猜測是——出於安全性的考慮(防止因Server端漏洞使得系統錯誤地執行了用戶可能提交的html代碼),知乎的客戶端腳本對錶單提交的特殊字元進行了鑒別和過濾。


&<&>


&alert("hacked!")&


宣城市廣德縣-初中物理教研組,。、『』 "" "ss","alert("haha");"&&&&&&"


&

alert("OK!");

&


&alert("hacked!")&


&<&>


&>------------------------&<


推薦閱讀:

TAG:知乎 | 知乎產品改進 | HTML | XSS |