rfc3986合规url编码仅对非unreserved字符(a-z、a-z、0-9、-、.、_、~)逐utf-8字节编码为%xx大写格式,保留/、?、:等sub-delimiter,空格编码为%20而非+。

什么是RFC3986合规的URL编码
URL编码不是简单地把空格换成%20,也不是用std::hex随便转一转就行。RFC3986明确规定:只对非“unreserved”字符(即A-Z、a-z、0-9、-、.、_、~)进行百分号编码,且必须使用大写十六进制(如%E2%80%94),不能小写或混用。常见错误是直接套用curl_easy_escape(它默认不遵循RFC3986)、或漏掉对/、?、:等“sub-delimiter”字符的保留判断。
手动实现时如何判断哪些字符要编码
核心是查RFC3986的unreserved字符集,其余ASCII控制字符、空格、以及所有非ASCII字节(UTF-8多字节序列)都必须逐字节编码。注意:不能按Unicode码点编码,必须按UTF-8字节流处理——比如中文“你好”是\xE4\xBD\xA0\xE5\xA5\xBD,得拆成6个字节分别编码,而不是当成两个U+4F60 U+597D去转。
- 保留字符列表:
A-Z、a-z、0-9、-、.、_、~ -
/、?、#、[、]等在路径/查询中可能有特殊含义,但RFC3986明确要求它们属于“sub-delimiter”,**不应被编码**(除非你刻意破坏URL结构) - 空格必须编码为
%20,不是+(那是application/x-www-form-urlencoded的规则)
用C++11及以上标准写一个安全的encode函数
避免依赖Boost或第三方库,纯标准库实现。关键点:输入是std::string(假设为UTF-8),输出也是std::string;每个非保留字节转为%XX格式,十六进制字母大写。
std::string url_encode(const std::string& s) {
std::string result;
result.reserve(s.size() * 3);
for (unsigned char c : s) {
if ((c >= 'A' && c = 'a' && c = '0' && c > 4];
result += "0123456789ABCDEF"[c & 0x0F];
}
}
return result;
}
这个实现不处理surrogate pair或BOM,因为UTF-8下不存在这些问题;但它对任意字节流都有效——哪怕输入不是合法UTF-8,也照常编码(这反而是安全的,比如处理二进制payload)。
容易被忽略的边界情况
很多人测试只用英文和空格,结果上线后遇到中文、emoji或用户粘贴的特殊符号就出错。
- 输入含
\0:C++std::string允许null字节,但URL里不允许出现%00(虽然语法上合法,但多数服务端会截断)。如有此需求,需提前过滤或报错 - 输入是Latin-1或GBK:RFC3986要求输入是URI字符序列,实际中应确保传入的是UTF-8。如果原始字符串是其他编码,先转UTF-8再编码,否则会出现双编码(如GBK的
你好直接编码得到乱码%C4%E3%BA%C3) - 性能敏感场景:频繁调用时,
reserve()和避免+=多次realloc很关键;若需更高性能,可改用std::vector<char></char>预分配+data()返回
真正麻烦的从来不是写几行编码逻辑,而是确认输入编码、理清哪些字符该放行、以及和服务端约定好是否允许非ASCII路径——这些比%转义本身更容易引发线上问题。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











