本章导读

二进制数据要进文本通道(HTTP 头、JSON 字段、URL 参数),必须先"翻译"成纯文本字节——codec 模块提供三种标准翻译:Base64(3 字节编 4 字符,体积膨胀 33%,数据头、JWT、邮件附件)、Hex(1 字节编 2 字符,膨胀 100%,调试转储、密钥指纹、短二进制值)、Percent(只转义非法字符,膨胀最小,URL 与表单)。三件套共享一套工程约定:容量规则可推导(不猜缓冲大小)、失败不写半个结果(原子性)、变体显式声明(URL-safe、大小写、自定义字母表都是参数不是猜)。这些约定与第 26 章"严格解析"同宗——文本世界的每个入口出口都精确到字节。

28.1引入

三个真实需求。邮件附件是二进制,SMTP 只收文本——Base64 把 3 字节变成 4 个可打印字符,体积涨三分之一,换来邮件通道的通行证。调试时要在日志里打出一小段密钥材料——Hex 让每个字节变成两个十六进制字符,人类可读、可复制、可对比。把用户输入放进 URL 查询参数——reports/July 2026 里的 / 和空格都是 URL 的保留字符或非法字符,Percent 编码把它们转成 %2F、%20,其余字符原样通过——三种编码对应"整体转换、可读转换、最小转换"三种诉求。

选型的第一问永远是通道允许多大:膨胀 33% 的 Base64、100% 的 Hex、接近 0% 的 Percent——答案经常由协议直接给定(Content-Transfer-Encoding: base64、URL 规范的 percent-encoding),你只需要正确使用而不是自由选择。

28.2概念

三件套的形态对比

1Base643 字节 → 4 字符(33% 膨胀);数据头 / JWT / 附件的标准
2Hex1 字节 → 2 字符(100% 膨胀);转储 / 指纹 / 短值,人类可读
3Percent仅转义目标集合(≈0% 膨胀);URL 与表单的最小改写

容量规则:输出大小永远可笔算

Base64 输出 = 4 × ceil(n / 3)(加填充);Hex 输出 = 2n;Percent 输出上限 = 3n(每字节都可能被转义)。先算容量再给缓冲是这一章的基本功——Encode 族写调用方缓冲、容量不足整体失败且不写半个结果(原子性:输出缓冲不会留下半截可被误用的内容),EncodeNew 族直接返回拥有式缓冲(xrtFree 释放)让库替你分配。两条路线的选择与第 25 章"调用方缓冲 vs 拥有式"完全同构。

变体显式:标准与 URL-safe

Base64 标准字母表里有 + 和 /——它们在 URL 里是保留字符,所以有 URL-safe 变体(换成 - 和 _),通过 xbase64config 的字母表参数启用(Alphabet 字段传 NULL 用默认标准表);解码按同一配置严格校验,字母表不匹配就是失败。Hex 有大小写两种输出形态(解码两种都吃)。Percent 的转义集合按场景声明(URL 路径段与查询参数的保留字不同)。原则同第 26 章:变体是显式参数,不猜默认。

解码的严格性

解码同样严格:非法字符(字母表外)、长度不对齐(Base64 非 4 倍数)、截断的转义序列(Percent 的孤立 %)都整体失败并留错误——垃圾进不去、半截出不来。这与 charset 章(第 27 章)的 STRICT 策略、number 章(第 26 章)的整段消耗是同一个设计哲学:文本边界是攻击面与事故面的交界,模糊处理在这里从来不是宽容是隐患。

28.3示例

完整程序:Base64 标准与拥有式两姿势

来自仓库范例 examples/codec/base64/main.c:

examples/codec/base64/main.c源码 ↗
















 */

#include <stdio.h>
#include <string.h>

#include <xrt.h>



int main(void)
{
	/* sizeof 含结尾零,编码长度用 sizeof-1 显式排除。 */
	static const char Message[] = "xrt codec";
	char Encoded[32];         /* 调用方缓冲:12 字符 + 零足够 */
	bytes pDecoded;           /* 拥有式:解码产物 */
	size_t iEncodedSize;
	size_t iDecodedSize;

	



	 */
	if ( !xrtBase64Encode(
		Message, sizeof(Message) - 1u, Encoded, sizeof(Encoded),
		&iEncodedSize, NULL
	) ) {
		return 1;
	}

	



	 */
	pDecoded = xrtBase64DecodeNew(
		Encoded, iEncodedSize, &iDecodedSize, NULL
	);
	if ( (pDecoded == NULL) || (iDecodedSize != sizeof(Message) - 1u) ||
		(memcmp(pDecoded, Message, iDecodedSize) != 0) ) {
		xrtFree(pDecoded);
		return 2;
	}
	printf("%s -> %s\n", Message, Encoded);
	xrtFree(pDecoded);
	return 0;
}
$ gcc -O1 -DXRT_MODULE_ALL -I single impl.c examples/codec/base64/main.c -lws2_32 -liphlpapixrt codec -> eHJ0IGNvZGVj

刚才发生了什么。 ① 输入 9 字节,容量规则 4 × ceil(9/3) = 12 字符——缓冲按公式给足,Encode 一步写完。② sizeof-1 排除结尾零——第 12 章哈希的老规矩:编码的输入是字节串,零结尾只是 C 字符串的意外。③ 解码用 DecodeNew 走拥有式路线——往返验证编码正确。④ "原子性"值得亲手验证:把缓冲故意给小,Encode 返回失败且输出区原样——没有半截 base64 留在那里等人误用。

完整程序:Percent 编解码往返

来自 examples/codec/percent/main.c:

examples/codec/percent/main.c源码 ↗
















 */

#include <stdio.h>

#include <xrt.h>



int main(void)
{
	/* 含路径分隔符与空格的原始 segment——两者都必须被转义。 */
	static const char Segment[] = "reports/July 2026";
	char Encoded[64];        /* 最坏情况 3n:17 字节 → 51 字符,64 够用 */
	bytes pDecoded;
	size_t iEncodedSize;
	size_t iDecodedSize;

	


	 */
	if ( !xrtPercentEncode(
		Segment, sizeof(Segment) - 1u, XRT_STR_LITERAL(""),
		Encoded, sizeof(Encoded), &iEncodedSize
	) ) {
		return 1;
	}

	/* 分配型解码:%XX 还原为字节;非法转义(如 %G0)会失败返回 NULL。 */
	pDecoded = xrtPercentDecodeNew(
		(xstrview){ Encoded, iEncodedSize }, &iDecodedSize
	);
	if ( pDecoded == NULL ) {
		return 2;
	}

	/* 解码产物是二进制缓冲:打印时用显式长度,不依赖结尾零。 */
	printf("encoded: %s\ndecoded: %.*s\n",
		Encoded, (int)iDecodedSize, (const char*)pDecoded);
	xrtFree(pDecoded);
	return 0;
}
$ gcc -O1 -DXRT_MODULE_ALL -I single impl.c examples/codec/percent/main.c -lws2_32 -liphlpapiencoded: reports%2FJuly%202026decoded: reports/July 2026

刚才发生了什么。 ① 编码只动了两个字符:/ → %2F、空格 → %20,字母原样——最小改写的形态一目了然。② 解码往返无损——Percent 的解码与编码严格互逆,孤立 % 或非法十六进制在解码侧整体失败。③ URL 场景的落点:把它接在第 25 章的查询串解析器后面,就是完整的 URL 工具链——解析(string)→ 编码(codec)→ 传输。

解码侧的陷阱地形

解码比编码多三类陷阱,都值得点名。字母表漂移:发送方 URL-safe、接收方按标准表解——-/_ 不在标准表里,整体失败算走运;更阴的是双方都"宽容"地各自处理,数据悄悄错位。填充歧义:Base64 的 = 填充有协议明确该不该带(JWT 的段不带),实现宽容省略——严格模式拒绝非对齐输入正是为了把这类歧义挡在门外。嵌套编码探测:看到 %2520 要意识到这是双重 Percent 编码的化石——解码器修不了上游的流程错误,它只能严格地失败。三类的共同解法都是同一条:两端同一份配置、严格模式、失败当作流程信号而不是数据噪声。

与第 27 章转码的分工

容易混淆的一对:charset 的 xrtTranscode(第 27 章)与本章的编解码都是"字节形态变换",分界线在哪?转码改变的是同一字符的不同字节表示(UTF-8 的"你"与 UTF-16 的"你"是同一个字符),语义不变、可无损往返;编码改变的是字节的呈现形态(二进制字节流变成可打印字符),语义是"等价搬运"而非"同一字符"。判断口诀:输入输出是不是同一种东西的两种写法——是则转码,否则编码。两者串联也常见:二进制 → Base64 → UTF-16 交给 Windows API,两步各自独立正确。

28.4契约

  • 容量可推导:Base64 4⌈n/3⌉、Hex 2n、Percent 上限 3n;先算后给。
  • 原子性:调用方缓冲不足整体失败,不写半个结果。
  • 双路线:Encode/Decode 走调用方缓冲,EncodeNew/DecodeNew 拥有式产物(xrtFree)。
  • 变体显式:URL-safe、大小写、转义集合都是参数;解码按声明的字母表严格校验。
  • 解码严格:非法字符、长度不对齐、截断转义整体失败。

从示例到工程:编码的三种宿主

编码操作在工程里的三种挂法。协议出口:JWT 拼装、HTTP 头携带二进制——出口处编码、入口处解码,通道两侧各一次、绝不重复编码(双重编码是真实事故:Percent 编两次 %20 变 %2520)。日志转储:Hex 一次性、短小、给人看——配合第 26 章格式串做偏移标注。持久化字段:JSON/配置文件里放二进制(第 31 章会看到 JSON 的 Base64 支持)——存取各一次,字段语义在 schema 里声明。三宿主的共同纪律:编码是一次性边界动作,不是中间状态——"编了又编"与"半截解码"都说明流程设计有问题。

一张膨胀对照表

数据 100 字节Base64HexPercent(全转义最坏)
输出大小136(含填充 4⌈100/3⌉)200≤300
通道邮件/数据头/JSON转储/指纹URL/表单
可读性无好保留原文

表的关键提醒:Percent 的"最小膨胀"只在最坏情况之外成立——纯 ASCII 安全字符几乎零膨胀,二进制数据全转义时最坏 3 倍。给二进制数据选 Percent 是选型错误(它为文本里的少数特殊字符设计),转义集合的语义决定了它的适用面。

常见错误

坑 1:把 Base64 当加密

症状:安全审计发现"加密字段"只是 Base64;渗透测试中令牌被直接解码。

原因:Base64 是编码不是加密——人人可逆,没有任何保密性;名字里带"64"的迷惑性与"哈希不是加密"(第 12 章)同族。

错误写法
char arrToken[64];
xrtBase64Encode(SecretKey, sizeof(SecretKey), arrToken, sizeof(arrToken), NULL, NULL);
Send(arrToken);   /* "凭编码保密"——接收方一步解码就拿到密钥 */
正确写法
/* 机密数据的正确通道:卷八 crypto(加密)或令牌体系(第 11 章 SecureText) */
/* Base64 只解决"二进制进文本通道",不解决保密 */
坑 2:标准 Base64 直接进 URL

症状:带 + 或 / 的 Base64 值放进 URL 后,服务端解码偶发失败或数据错位——只在特定值上发生。

原因:+ 在查询串里被解释为空格、/ 是路径分隔符——标准字母表与 URL 字符集相撞。

错误写法
xrtBase64Encode(Data, Size, arrUrl, sizeof(arrUrl), NULL, NULL);   /* 默认字母表含 + / */
AppendQueryParam(Url, arrUrl);   /* + 变空格、/ 截路径——解码必错 */
正确写法
xrtBase64Encode(Data, Size, arrUrl, sizeof(arrUrl), NULL, &tUrlSafe);   /* URL-safe 字母表:- 与 _ */
AppendQueryParam(Url, arrUrl);   /* 全字符 URL 安全 */

编程练习

基础:容量公式实证

对 1、2、3、4、9、10 字节输入分别笔算 Base64 输出长度并编码验证(6 组数字先手算后对拍);再用故意不足的缓冲验证原子失败、输出区未被写。

进阶:Hex 转储器

实现 dump(视图, 前缀):按 Hex 输出字节流、每 16 字节一行、行首标偏移——调试器的十六进制视图就是它。提示:偏移用第 26 章的格式串(%06X 风格的十六进制)。

挑战:URL 构建器

实现 url_encode_query(基准URL, 键值对数组):键与值各自 Percent 编码、拼接查询串(第 25 章构建器)、输出完整 URL。验收标准:含中文键值、&/=/空格/+ 的十组输入编码后可被标准 URL 解析器正确还原;解码往返逐字节一致。

小结

知识点速查
三件套Base64(膨胀 33%)/ Hex(膨胀 100%)/ Percent(≈0%)按通道规则选
容量Base64 4⌈n/3⌉、Hex 2n、Percent ≤3n——先算后给
原子性缓冲不足整体失败,不写半个结果
双路线To 缓冲版 / New 拥有式版,与容器章同构
变体URL-safe(-/_)、Hex 大小写、Percent 转义集合——显式参数
红线编码不是加密;机密走卷八或 SecureText
三宿主协议出口一次编码 / 日志转储 / 持久化字段——编码是边界动作不是中间状态
与转码分界同一字符两种写法=转码;二进制变可打印=编码

📖 本章涉及的 API 参考手册:codec(26 个条目)

点击查看完整签名、参数约束、返回值与错误说明