知識 · 文字、資料與資安
URL 編碼是什麼?
簡答
URL 編碼正式名稱為百分比編碼(percent-encoding),會把 URL 中不允許或具特殊意義的字元,以每個位元組 % 加兩位十六進位數字的形式取代。空格變成 %20,& 變成 %26,非 ASCII 文字則先轉成 UTF-8,所以「é」會變成 %C3%A9。字串「a b&c」編碼後是 a%20b%26c。
為什麼 URL 需要編碼
RFC 3986 把 URL 限制在一小組 ASCII 字元內,其中有些字元還有特定任務:? 開始查詢字串,& 分隔參數,= 分開名稱與值,# 開始片段,/ 分隔路徑段落。如果某個值含有這些字元,URL 就會產生歧義。
以搜尋「salt & pepper」為例。直接寫成 ?q=salt & pepper,伺服器會看到參數 q 的值是 salt ,外加另一個名為 pepper 的空參數。編碼成 ?q=salt%20%26%20pepper 後,才會以單一值送達。
哪些字元需要編碼
| 類別 | 字元 | 規則 |
|---|---|---|
| 非保留字元 | A–Z a–z 0–9 - . _ ~ | 永遠不需編碼 |
| 保留字元(gen-delims) | : / ? # [ ] @ | 出現在值裡時要編碼 |
| 保留字元(sub-delims) | ! $ & ' ( ) * + , ; = | 出現在值裡時要編碼 |
| 其他所有字元 | 空格、"、<、>、%、非 ASCII | 一律編碼 |
編碼是以位元組為單位,而不是字元。非 ASCII 文字會先轉成 UTF-8,每個位元組各自變成一組 %XX:
| 輸入 | UTF-8 位元組 | 編碼結果 |
|---|---|---|
| 空格 | 20 | %20 |
| & | 26 | %26 |
| % | 25 | %25 |
| é | C3 A9 | %C3%A9 |
| 日本 | E6 97 A5 E6 9C AC | %E6%97%A5%E6%9C%AC |
十六進位數字不分大小寫,%c3%a9 和 %C3%A9 意思相同,但 RFC 3986 建議統一用大寫。
%20 還是 +:空格的兩套規則
依 RFC 3986 定義的百分比編碼,空格一律寫成 %20。HTML 表單送出時使用另一種相關格式 application/x-www-form-urlencoded,其中空格變成 +,真正的加號則變成 %2B。
- 在
/files/my report.pdf這類路徑中請用%20。路徑中的+就是字面上的加號。 - 在查詢字串中,解析表單編碼的伺服器會把
+當成空格。%20兩種慣例都看得懂,不確定時用它比較保險。 - 如果值裡可能有真正的
+,例如+84這種電話號碼,就必須以%2B送出,否則會變成空格。
JavaScript 的 encodeURIComponent 與 encodeURI
| 函式 | 不編碼的字元 | 用途 |
|---|---|---|
encodeURIComponent() | A–Z a–z 0–9 - _ . ! ~ * ' ( ) | 單一值:一個查詢參數或一個路徑段落 |
encodeURI() | 上述字元加上 ; , / ? : @ & = + $ # | 已經有完整結構的整個 URL |
URLSearchParams | 表單編碼規則(空格變成 +) | 建立或讀取查詢字串 |
encodeURIComponent("a b&c") 會回傳 a%20b%26c,而 encodeURI("a b&c") 回傳 a%20b&c,因為它假設 & 是分隔符號。常見的錯誤是對參數值呼叫 encodeURI,讓 &、= 和 # 原樣通過,把值切斷了。
new URLSearchParams({ q: "a b&c" }).toString() 會得到 q=a+b%26c。在標準伺服器上,兩種形式解碼後都是同一個值。
常見錯誤
- 重複編碼:對已編碼的文字再編碼一次,
%20會變成%2520,因為%本身被編碼成%25。每個值只在放進 URL 的那一刻編碼一次。 - 用元件函式編碼整個 URL:
https://會變成https%3A%2F%2F,連結就失效了。 - 路由前先解碼:伺服器若在比對路由前就把
%2F解成/,可能被誘導去讀取另一個路徑。 - 預設使用舊字元集:有些舊系統把「é」編碼成
%E9(Latin-1)。現代瀏覽器和 WHATWG URL Standard 都使用 UTF-8。
常見問題
為什麼 URL 中的空格是 %20?
URL 中不允許空格,所以用 % 加上它的十六進位位元組值取代。空格的 ASCII 碼十進位是 32,十六進位就是 20。
URL 中的 %2F 是什麼意思?
%2F 是編碼後的斜線,讓 / 能出現在值裡,而不會被當成路徑分隔符號。
URL 編碼和 Base64 一樣嗎?
不一樣。URL 編碼只取代不安全的字元,其餘仍可閱讀。Base64 則把所有位元組轉成 64 個字元的字母表,而且標準版的 + 和 / 本身也需要 URL 編碼,除非使用 URL 安全版本。
帶重音的字母或中文字需要編碼嗎?
實際送出的請求中需要。瀏覽器會在網址列以可讀形式顯示,但送出的是 UTF-8 百分比編碼形式,例如「日」會送成 %E6%97%A5。