問題背景
接收客戶端傳入參數,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標準進行規定,
- 在RFC-1738對URL進行說明的各項標準中,提出了要對URL中不安全的字符進行編碼,編碼方式即使用%和緊跟的兩個十六進制數字表示,注意在該標準中空格被編碼成+
- 在升級版RFC-2396對URI進行說明的各項標準中,再次提到了對參數進行編碼,注意在在該標準中空格被編碼成%20
- 在再次升級版的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編碼