要把 URL 轉換成 Punycode,只需要對網址中的主機部分進行編碼——通訊協定、路徑、查詢字串和片段會保持不變,因為 DNS 只儲存網域的 ASCII 標籤。Punycode 轉換器完全在您的瀏覽器中運作,使用 RFC 3492 Bootstring 演算法,並接受像 bücher.example 或 münchen.de 這類純網域,產生其 ASCII 對應形式 xn--bcher-kva.example 或 xn--mnchen-3ya.de。由於完整的 URL(例如 https://münchen.de/path?q=test)仍然包含通訊協定、路徑和查詢字串,直接貼上會被拒絕;必須先把主機部分分離出來。一旦取得轉換後的 ASCII 名稱,在交給註冊商、TLS 憑證授權單位或 URL 解析器之前,必須將其重新接回原本的通訊協定與路徑。這種主機編碼與 URL 組裝之間的分離,是任何剛接觸 IDN 工作者最常見的絆腳石,也是為什麼「URL 轉 Punycode」實際上包含兩個步驟:先擷取,再編碼。

convert url to punycode
將 URL 轉換為 Punycode:先擷取網域

為什麼 URL 在 DNS 層需要 Punycode

網域名稱系統最初定義為只接受 A 到 Z 的 ASCII 字元、0 到 9 的數字以及連字號。這就是線路格式的完整字母集——沒有重音、沒有希臘字母、沒有漢字、沒有表情符號。當網際網路社群決定應讓人們能以自己的文字註冊網域名稱時,解決方法並不是擴充 DNS 本身;而是定義一種可逆的編碼方式,將 Unicode 碼點壓縮為 ASCII 字母與連字號。這種編碼就是 Punycode,在 RFC 3492 中定義。

應用層國際化網域名稱 (IDNA) 在 RFC 3492 之上加入了一小組規則,讓瀏覽器能夠處理像 münchen.de 這類主機,逐標籤分別編碼,並將 ASCII 形式 xn--mnchen-3ya.de 交給 DNS 解析器。解析器在儲存與查詢時完全使用該 ASCII 標籤,無須知道原始拼寫使用的語言。當回應返回時,瀏覽器會再次解碼該標籤,讓使用者在網址列中看到 münchen.de。整個來回過程中的每個步驟之所以能運作,是因為 RFC 3492 是精確的演算法,而不是語言翻譯——而這正是 Punycode 轉換器在本地端實作的內容。

URL vs. 網域:轉換器實際接受什麼

URL 是由通訊協定、授權單位(主機加上選擇性的埠)、路徑、選擇性的查詢字串以及選擇性的片段組成的結構化字串。Punycode 只處理主機,而且只處理該主機內的標籤層級。Punycode 轉換器的文件化範圍刻意比完整的 URL 解析器更窄:它不接受通訊協定、路徑、埠、查詢字串或片段,不會對任何內容進行百分號解碼,也不解析 DNS、測試註冊可用性或聯絡註冊管理機構。如果您把 https://münchen.de 貼到輸入框,它將無法運作。

這個工具預期接收像 münchen.de 或 xn--mnchen-3ya.de 這類網域。在進行任何編碼之前,會使用句點來分割網域為標籤,而在 CJK 文字中看起來與 ASCII 點號相同的 Unicode 句點(例如 U+3002)會被正規化為 ASCII 點號,以保持分割的可預測性。空標籤會被拒絕,超過 1,000 個碼點的輸入以及任何格式錯誤的 Bootstring 序列同樣會被拒絕。任何看起來像路徑、查詢字串或片段的內容,不會被解析為 URL——會被視為格式錯誤的標籤資料而拒絕處理。這就是為什麼百分號編碼的 URL 不是這裡正確的輸入;百分號編碼是另一套套用於路徑與查詢字串的編碼系統,如果您手上的是百分號編碼的字串,您可能需要的是 URL 解碼器。

將 URL 的主機部分轉換為 Punycode

這是當您的起點是真實 URL 而非純網域時的實務工作流程。

  1. 複製您想要編碼的完整 URL,例如 https://münchen.de/produkte/neu。
  2. 移除所有不屬於主機的部分。移除 https:// 通訊協定、移除路徑 /produkte/neu、移除任何查詢字串或片段。剩下的字串——münchen.de——就是轉換器能接受的內容。
  3. 開啟 Punycode 轉換器,選擇 Unicode-to-ASCII 作為此次轉換方向。只有在檢查 xn-- 名稱時才使用 ASCII-Punycode-to-Unicode。
  4. 將純主機貼到輸入欄位並執行轉換。輸出將會是 xn--mnchen-3ya.de,其中 xn-- 前綴只附加在含有非 ASCII 字元的標籤上,而 .de 標籤則保持為純 ASCII 字串。
  5. 複製結果,然後重新接回原本的通訊協定與路徑,讓最終 URL 成為 https://xn--mnchen-3ya.de/produkte/neu。將這個完整字串交給符合標準的 URL 函式庫、您註冊商的 IDN 欄位,或您憑證授權單位的 CN/SAN 項目。

同樣的五步驟流程適用於任何主機含有非 ASCII 字元的 URL,包括希臘文、西里爾文、希伯來文、阿拉伯文、CJK 以及輔助平面符號。每個標籤獨立處理,因此像 bücher.example.com 這類主機會產生 xn--bcher-kva.example.com——兩個不變的標籤(example、com)夾著一個編碼後的標籤(xn--bcher-kva)。在把結果交給任何預期接收完整 URL 的元件之前,請重新接回原本的通訊協定與路徑。

RFC 3492 Bootstring 演算法如何編碼標籤

在轉換器內部,每個標籤由 Bootstring 演算法處理,使用一組固定的常數:base 36、tmin 1、tmax 26、skew 38、damp 700、初始偏差 72,以及初始碼點 128。標籤中既有的 ASCII 字元會先原樣複製過去。接著將非 ASCII 碼點編碼為可變長度的增量值,每次處理一個值後,偏差參數會進行調整,以讓短標籤與長標籤都保持效率。實作方式是對完整的 Unicode 碼點逐一處理,而不是 UTF-16 的半段,這表示輔助字元能保持完整,而不會被拆成高位代理與低位代理而導致無法正確往返轉換。算術溢位檢查會同時保護增量乘法與權重累計,讓長字串不會無聲地發生環繞溢位。

輸入標籤方向輸出標籤說明
bücherUnicode → ASCIIxn--bcher-kva文件化範例,使用拉丁變音符號,單一標籤
mañanaUnicode → ASCIIxn--maana-pta西班牙文 ñ 加上波浪號,來自拉丁變音測試案例
exampleUnicode → ASCIIexample純 ASCII 標籤,僅進行小寫化,不加 xn-- 前綴
xn--maana-ptaASCII → Unicodemañana反向方向,xn-- 前綴在標籤邊界被消耗

不含任何非 ASCII 碼點的標籤永遠不會加上 xn-- 前綴——只會進行小寫化並保持不變,這就是為什麼 example.com 保持為 example.com,絕不會變成 xn--example-com。解碼模式只對以 xn-- 開頭的標籤執行 Bootstring 解碼;任何其他標籤會被視為純 ASCII 處理。這種狹窄的合約讓該工具接受的每個標籤都能保持可逆性,而任何偏差——開頭的通訊協定、路徑斜線、百分號編碼位元組——會被拒絕而不是猜測處理。對於宣告的原始 Punycode 慣例,輸出是確定性的,複製按鈕只會複製轉換後的網域,不包含通訊協定或尾端斜線。

在語法之外驗證結果

成功的轉換並不保證產生的網域會在所有地方被接受,也不保證它指向您認為的那個對象。生產環境的 URL 系統通常會在進行 RFC 3492 編碼之前,先套用 Unicode 正規化、上下文規則以及 UTS #46 對應,這表示瀏覽器顯示的 ASCII 形式可能與 Punycode 轉換器的結果略有不同。此工具只呈現原始標籤轉換;如果您的註冊商拒絕該輸出,請遵循該註冊管理機構的 IDN 政策,而不是手動編輯字元。標籤長度限制與註冊管理機構政策在轉換之後仍然適用——語法上編碼完成的標籤可能太長、被保留、遭到封鎖,或僅僅是無法使用。

安全性必須分開處理。Punycode 並不是語言翻譯器——將標籤解碼回 Unicode 只能告訴您拼法,但無法告訴您如何發音,或是哪個組織擁有該網域。不同書寫系統的字元可能看起來幾乎相同,這正是同形異義字攻擊的原理:аррӏе.com(使用西里爾字母)在匆忙的讀者眼中可能與 apple.com 難以分辨。請將轉換後的名稱送交符合標準的 URL 函式庫,查詢 WHOIS 紀錄,並在將結果視為可信之前,確認其書寫系統與擁有者。編碼步驟是可逆且精確的;可信度則是另一個問題,編碼步驟無法回答。請將安全性過濾與註冊管理機構驗證,與這個可逆的編碼步驟分開處理。

相關閱讀:如何在不寫程式碼的情況下用 Python 解碼 URL。

相關閱讀:PHP 轉換為 Punycode:快速的瀏覽器端做法。