雜記 › 技術筆記 › 保留集縮成 66 個字元:這一頁三檔編碼在 128 個碼位上的逐字元實測與 9 格編解碼矩陣

保留集縮成 66 個字元:這一頁三檔編碼在 128 個碼位上的逐字元實測與 9 格編解碼矩陣

這一頁三檔編碼在 128 枚碼位上的保留集實測是 71、82、66,六次 replace 把五枚符號收成百分號形式、空格換成加號;九格編解碼矩陣 45 條實測自還原 40,翻車全在加號上。

一段中文貼進三檔,吐出三條串

這頁六個函數體我逐字摘出來在純 Node 裡原樣跑(NODE=v24.19.0,非瀏覽器實測)。「你好 world」原始 10 碼元,component 與 uri 檔編成同一條 26 碼元的 %E4%BD%A0%E5%A5%BD%20world,php 檔 25 碼元——差別只在空格:1 碼元的 + 替掉 3 碼元的 %20。「PHP 模式為什麼短幾個字元」得把 128 個碼位逐個餵一遍才說得清。

128 個碼位上,三檔各自「不編碼」哪些字元

對 ASCII 0–127 全部 128 個碼位逐字元餵進三檔(探針 B 組),輸出與輸入相同就算該檔保留。component 檔保留 71 個、轉義 57 個;uri 檔保留 82 個、轉義 46 個;php 檔保留 66 個、轉義 62 個。三檔都轉義 46 枚;三檔都保留 66 枚,就是標題那個數——字母數字外只剩 - _ . ~。

  • uri 比 component 多保留 11 枚:# $ & + , / : ; = ? @。
  • component 比 php 多保留 5 枚:! ' ( ) *(碼位 33、39、40、41、42),php 檔收成 %21 %27 %28 %29 %2A。
  • 十二字元 ;/?:@&=+$,#[] 裡十枚只在 uri 檔保留,[ ] 兩枚三檔全轉義成 %5B %5D。

差集對得上帳:66 = 71 − 5、82 = 71 + 11,uri 比 php 多留的 16 枚是上面兩組相加。

實心底色=保留,空心=轉義 component 保留 71 轉義 57 uri 保留 82 轉義 46 php 保留 66 轉義 62 php 少 5 枚,uri 多 11 枚

圖 1:三檔保留集在 128 枚碼位上的寬度對照

phpUrlencode 六次替換收走了什麼

函數體是 encodeURIComponent 之後串六次 replace。探針 C 組輸入 !']()()%20a~_-., +(16 碼元)先經 encodeURIComponent 吐成 24 碼元,之後五步各命中 1 次、每步 +2,串長走 24→26→28→30→32→34;末步 %20→+ 命中 1 次把 3 碼元換回 1 碼元、34→32。兩條鏈上 ~ 與 - _ . 全程未動,五枚符號的五步恰好把保留集從 71 收成 66。

把 %20→+ 挪到最前作對照(H 組):128 枚單字元逐一探測,兩序分歧 0/128,整串也逐字相等——本頁可測樣本上觀察不到換序差異,只按實測口徑寫。

輸入裡本來就寫著 %21 時會被自己改寫,這是本頁唯一真實的二次編碼路徑:phpUrlencode("%21") 得 %2521、phpUrlencode("%20") 得 %2520(手寫 %20 不會被第 6 步吃成 +,只有本次編出的真空格才變 +)、phpUrlencode("+") 得 %2B。"a b!" 回餵三連得 pass1 = a+b%21、pass2 = a%2Bb%2521、pass3 = a%252Bb%252521,phpUrldecode 每跑一次剝一層。URL 編解碼 這六個函數是無狀態純函數,同一輸入連調 1000 次輸出一致。

三檔編碼 × 三檔解碼:九格矩陣

探針 D 組三種編碼檔各配三種解碼檔,五樣本「你好 world」「a b」「a+b」「x=1&y=2」「50%」每格 5 條,9 格共 45 條實測:自還原 40、不自還原 5、異常 0。

編碼檔component 解uri 解php 解
component5/55/55/5
uri5/55/54/5
php3/53/55/5

翻車全在 + 上。「你好 world」php 檔編成 %E4%BD%A0%E5%A5%BD+world,component 或 uri 檔解出「你好+world」——decodeURIComponent 對裸 + 原樣返回,這是矩陣裡 4 條不還原的成因;uri|php 格唯一翻車是 a+b 被 php 解碼的前置換成空格變「a b」。「50%」九格全還原。

編時不 trim、解時先 trim

urlEncode 讀原值不剪、urlDecode 先 trim,這處不對稱按「首尾有無空白」兩類給數(E 組 8 輸入 × 3 檔共 24 行)。「編碼再解碼」方向 24/24 自還原;「直接解碼再回編」方向首尾帶空白類 0/15 還原、無空白類 9/9 還原;24 行裡 trim 改變解碼結果的 15/24。JS 的 trim 連 Tab 與全形空格 U+3000 一起剪,「 a b 」php 檔編成 ++a+b++,Tab 則三檔都是 %09。

失敗時這一頁只發一句固定文案

解碼側 5 串非法 % 序列(%E4%B8、%ZZ、%、50%、%2)× 3 檔共 15 行全拋 URIError、e.message 都是 URI malformed,但 catch 發的是固定整句「解碼失敗,內容不是有效的URL編碼」、不含 e.message。編碼側 URIError 只能由孤立代理項觸發:\uD800、x\uD800y、\uDF00 × 3 檔共 9 行全拋,toast 拼成「編碼失敗:URI malformed」;正規代理對(\uD83D 起頭那對)三檔都編成 %F0%9F%98%80,攔下的是半截代理。兩處 catch 都不寫輸出框,瀏覽器呈現按未實測記。

對著 intro 逐條判

config/tools.php 第 61 行起是 url-encode 條目、intro 在第 70 行(1639 碼元 / 2129 位元組),逐條判定 13 條:[一致] 8、[衝突] 2、[部分一致] 3。兩條衝突一是「encodeURI 不編碼空格」——實測 encodeURI(' ') = %20;二是「即時雙向轉換」——模板沒有 oninput,轉換只由三枚 onclick 觸發。「相容 PHP urlencode 格式」按紅線只判到本頁六次 replace 的格式特徵吻合為止。參數拆解歸 URL 解析 頁,本篇不外推。

FAQ

Q:同一句中文,PHP 模式為什麼比別人少幾個字元?

少的是空格:php 檔空格編成 1 碼元的 +,另兩檔編成 3 碼元的 %20,「你好 world」26 對 25 就是這個差;其餘差在 ! ' ( ) * 那 5 枚,component 留著、php 收走。

Q:php 編完用 encodeURIComponent 解,+ 怎麼變回空格?

變不回來。decodeURIComponent 對裸 + 原樣返回,實測 php|component、php|uri 兩格各 3/5,「你好+world」解出來還帶 +。要回原值得配 php 檔解碼(先 +→%20 再解),矩陣對角三格都是 5/5。

Q:輸入裡已經寫著 %21,會被這一頁再編一次嗎?

會——phpUrlencode("%21") 得 %2521、% 被頂成 %25,這是本頁唯一真實的二次編碼路徑;手寫 %20 反而停在 %2520 不變 +。這組字元級實測舊文 URL 編碼是怎麼回事 沒做過。

📎 相關文章

解碼是四步連做:1194 條語料在 unicode-convert 頁上只有 798 條能原樣回來 2026-11-09 介面上八個框的先後和程式碼執行順序是反的:文字清理頁硬編碼八步順序、逐對換序真值表與統計碼元口徑實測 2026-11-09 取色器的解析門只收六位十六進位:三位短寫摘出能算、頁面永遠走不到 2026-11-08 改了鹽值不點按鈕,四個框一動不動:MD5 頁的三段拼接與多行輸入實測 2026-11-08 60 位時間戳在這一頁只剩 28 個位置:UUID 產生器 v1 分支的移位與掩碼逐毫秒實測 2026-11-08