一段中文粘进三档,吐出三条串
这页六个函数体我逐字摘出来在纯 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 枚是上面两组相加。
图 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 解 |
|---|---|---|---|
| component | 5/5 | 5/5 | 5/5 |
| uri | 5/5 | 5/5 | 4/5 |
| php | 3/5 | 3/5 | 5/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 编码是怎么回事 没做过。