坑爹的URL編碼-PHP正確處理URL中的加號(+)

問題背景

接收客戶端傳入參數,base64解碼失敗,經過排查發現原因是參數上傳前字符串中有+,但是PHP接收後,發現+變成了空格,導致base64解碼失敗。

測試驗證

訪問一個測試的接口 /internal/test

curl 'http://127.0.0.1/internal/test?a=abc+def'

驗證1:
簡單輸出$_GET

public function test() {
   var_dump($_GET);
}

結果:

array(1) {
  ["a"]=>
  string(7) "abc def"
}

結論:可以看到直接接收GET參數,+變成了空格

+變成空格的原因

經過一頓查資料,首先我們要知道URL編碼是什麼

URL編碼

一個例子

我們常見的一個URL,例如CSDN搜索功能的一個URL(https://so.csdn.net/so/search/s.do?q=PHP基於字典樹算法實現搜索聯想功能&t=&u=),當你從瀏覽器中複製出來的時候,這個URL長下面這樣子 https://so.csdn.net/so/search/s.do?q=PHP%E5%9F%BA%E4%BA%8E%E5%AD%97%E5%85%B8%E6%A0%91%E7%AE%97%E6%B3%95%E5%AE%9E%E7%8E%B0%E6%90%9C%E7%B4%A2%E8%81%94%E6%83%B3%E5%8A%9F%E8%83%BD&t=&u=

這就是URL被編碼了,這裏編碼是將中文轉換成了%開頭的兩個十六進制數。

爲什麼URL會被編碼呢?

URL中的參數部分是由一個個key=value的參數對組成,而如果&=/?等在URL具有一定功能的特殊字符出現在key或者value中時,就會導致語義出現不一致的情況,例如參數q的值是a&b,那麼當出現q=a&b&f=s這樣一個參數對時,是表示q的值是a&b,還是q的值是a,而b的值爲空呢?

因此需要對URL進行編碼,這樣被編碼過的字符就不再會有歧義,上面例子中的q=a&b&f=s會被編碼成q=a%26b&f=s,你看這樣是不是就不會混亂了。

如何對URL進行編碼呢?

URL如何進行編碼由RFC標準進行規定,

  1. 在RFC-1738對URL進行說明的各項標準中,提出了要對URL中不安全的字符進行編碼,編碼方式即使用%和緊跟的兩個十六進制數字表示,注意在該標準中空格被編碼成+
  2. 在升級版RFC-2396對URI進行說明的各項標準中,再次提到了對參數進行編碼,注意在在該標準中空格被編碼成%20
  3. 在再次升級版的RFC-3986標準中,對Url的編解碼問題做出了更加詳細的建議,指出了哪些字符需要被編碼纔不會引起Url語義的轉變,以及對爲什麼這些字符需要編碼做出了相應的解釋。

回頭來研究下一開始的問題

通過以上的資料,我們可以看出來+被變成了空格的原因,正是按照RFC-1738標準進行的反編碼,也就是.PHP接收$_GET參數遵循的是的是RFC-1738標準。
所以直接讀取$_GET時,+就反過來被解碼成了空格

怎麼解決這個問題

那我們怎麼讓PHP不按照RFC-1738標準進行解碼,而是按照升級版的RFC-3986標準進行解碼呢?

最簡單的辦法當然是讓+以正確的方式進行編碼,也就是在客戶端請求接口時,按照RFC-3986標準進行對URL進行編碼。此時+被編碼成%2b,當PHP接收參數時,將%2b解碼成+,大功告成。

驗證結果

對URL進行正確的編碼

 curl 'http://127.0.0.1/internal/test?a=abc%2bdef' 

此時可以看到接口輸出

array(1) {
  ["a"]=>
  string(7) "abc+def"
}

PHP語言裏還有別的坑嗎?

除了接收$_GET參數外,PHP中還有對URL參數處理的兩個常用的函數urlencode和urldecode。
注意這兩個函數也是遵循RFC-1738進行編碼和解碼,從官網的說明可以看到

This differs from the » RFC 3986 encoding (see rawurlencode()) in that for historical reasons, spaces are encoded as plus (+) signs.

做個試驗

先對字符串abc def進行編碼

 $str = 'abc def';
 echo urlencode($str);

輸出結果

abc+def

然後對字符串a=abc+def進行解碼

 $str = 'a=abc+def';
 echo urldecode($str);

輸出結果

a=abc def

可以看出確實空格被編碼成了+,而+則被解碼成空格

怎麼解決呢?

那PHP裏面有沒有使用RFC-3986標準進行編碼的呢,有的,是rawurlencode和rawurldecode,PHP官方是這麼寫的

rawurlencode — URL-encode according to RFC 3986

再來做個試驗
先對字符串abc def進行編碼

 $str = 'abc def';
 echo rawurlencode($str);

輸出結果

abc%20def

可以看到空格被編碼成%20,然後對字符串a=abc+def進行解碼

 $str = 'a=abc+def';
 echo rawurldecode($str);

輸出結果

a=abc+def

可以看到+解碼後還是+,沒有變成空格

結論

所以最符合標準,並且比較容易實現的方案就是讓客戶端or前端在請求服務端接口時遵循RFC-3986標準進行正確的URL編碼

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章