url编码必须转义0x00–0x20、0x7f–0xff及空格、/、?、#、&、=、+、%、$、@、!、~、*、\、(、)、[、]等字符;'-'、'_'、'.'属未保留字符无需编码;utf-8需按字节处理,' '编码为"%20"而非"+",'%'自身须编码为"%25";c++中推荐std::format("%02x")或ostringstream配合setw(2)/setfill('0'),并强制转unsigned char避免符号扩展;解码时须严格校验%后两位十六进制,非法序列原样保留,不自动将+转空格,除非明确启用application/x-www-form-urlencoded模式。

URL编码时哪些字符必须转义?
不是所有非字母数字字符都要变成%XX,只有保留字符(如/、?、&)和不安全字符(如空格、中文、+、#)才需要处理。标准做法是:只对0x00–0x20、0x7F–0xFF以及' '、'/'、'?'、'#'、'&'、'='、'+'、'%'、'$'、'@'、'!'、'~'、'*'、'\'、'('、')'、'['、']'等显式列出的字符做百分号编码。
常见错误是把'-'、'_'、'.'、'*'也编码了——其实它们在RFC 3986中属于“未保留字符”,可不转义;但注意'*'在部分老服务里会被误解析,保险起见可选编码。
- UTF-8字符串要先按字节处理,不能按
wchar_t或std::u8string单个code point切分 -
' '必须转成"%20",不是'+'(那是application/x-www-form-urlencoded的规则) - 编码后的
'%'本身必须变成"%25",否则会破坏转义结构
用std::ostringstream还是std::format拼接十六进制?
C++20起std::format最干净,但若项目还在C++17或更低,std::ostringstream比手写sprintf安全,且避免std::to_string无法补零的问题。
别用std::hex直接输出到std::ostringstream而不设std::setw(2)和std::setfill('0')——会导致0xA变成"a"而非"0a",且小写字母不符合多数服务端预期(虽然RFC允许大小写,但Nginx、Python urllib.parse默认小写,Go默认大写,建议统一用小写)。
立即学习“C++免费学习笔记(深入)”;
- 示例片段(C++17):
oss << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(static_cast<unsigned char>(c)); - C++20推荐:
std::format("%02x", static_cast<unsigned int>(static_cast<unsigned char>(c))) - 注意强制转成
unsigned char再转int,否则char为有符号时,0xFF会变成-1,std::hex输出"ffffffff"
URL解码时如何安全处理%XX格式?
解码不是简单正则替换。必须校验%后是否紧跟两个十六进制字符,且不能跨字节截断——比如"%ff%xx"中的%xx非法,应原样保留;"%g1"也非法;"%2"不完整,也要跳过。
更隐蔽的坑是:输入含'%'但后面没跟合法hex,此时不能丢弃这个'%',否则会丢失原始信息(例如用户故意传"hello%world",解码后应为"hello%world",不是"helloworld")。
- 逐字节扫描,遇到
'%'就检查后续两字节是否都为0-9或a-f或A-F - 用
std::isxdigit判断,但注意它接受int且对EOF敏感,传入前需确保不是char(-1) - 十六进制转换建议用
std::stoi(s, nullptr, 16)或查表法(更快),避免手写(c>='a'?c-'a'+10:c-'0')时没处理大小写 - 解码结果字节流直接存入
std::string,不要尝试转成std::u8string——解码后仍是UTF-8字节序列,语义由上层协议约定
要不要支持+替代空格?
严格来说,URL编码(percent-encoding)不包含'+'→空格规则;那是HTML表单编码(application/x-www-form-urlencoded)的特例。如果你对接的是浏览器GET参数或旧API,大概率会混用这两种规则。
所以实际实现里,解码函数最好加一个bool allow_plus_for_space = false参数,默认关掉。开启时,只在非路径段(如查询参数值)中把'+'当空格处理——路径里的+永远不该被替换(https://a.com/b+c中b+c是路径名,不是空格)。
- 检测是否在查询参数值中,需依赖外部上下文;纯解码函数无法自动判断,所以别默认启用
- 编码函数一律不生成
'+',只输出"%20" - 如果调用方明确说“这是form-data”,再套一层预处理:把
' '先替换成'+',再做标准URL编码
%就吃掉后续字符,或者大小写混用导致签名不一致。










