杂记 › 技术笔记 › 保留集缩成 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 取色器只收六位数:三位短写摘出能算、页面永远走不到,全空间恒等率 10.5813% 2026-11-08 改了盐值不点按钮,四个框一动不动:MD5 页的三段拼接与多行输入实测 2026-11-08 60 位时间戳在这一页只剩 28 个位:UUID 生成器 v1 分支的移位与掩码逐毫秒实测 2026-11-08