
本文详解如何用 sscanf 正确解析形如 Name=ss&Port=8081&ID=0&Config=testconfig 的 CGI 查询字符串,解决 %s 贪婪匹配导致字段越界的问题,并提供带缓冲区保护的安全格式化方案。
本文详解如何用 `sscanf` 正确解析形如 `name=ss&port=8081&id=0&config=testconfig` 的 cgi 查询字符串,解决 `%s` 贪婪匹配导致字段越界的问题,并提供带缓冲区保护的安全格式化方案。
在 C/C++ 编写的 CGI 程序中,常通过 getenv(“QUERY_STRING”) 获取 URL 查询参数(如 Name=ss&Port=8081&ID=0&Config=testconfig),再借助 sscanf 提取各字段。但若直接使用 %s 格式符(如 “Name=%s&Port=%d…”),会导致首个字符串字段贪婪捕获直到字符串末尾——因为 %s 默认以空白符(空格、制表符、换行)或 为终止边界,而查询字符串中不含这些字符,& 对它完全“不可见”。
例如原代码:
sscanf(data, "Name=%s&Port=%d&ID=%d&Config=%s", &name, &port, &id, &config);
当输入为 Name=ss&Port=8081&ID=0&Config=testconfig 时,%s 会将 ss&Port=8081&ID=0&Config=testconfig 全部读入 name,后续字段解析失败。
✅ 正确做法是:显式指定扫描终止符。使用 [^&] 字符集格式符——%[^&] 表示“匹配任意非 & 字符”,一旦遇到 & 即停止读取,完美契合查询字符串的分隔逻辑。
同时,必须限定缓冲区长度以防溢出(name[50] 最多存 49 字符 + 1 个 )。最终安全写法如下:
#include <stdio.h> #include <stdlib.h> int main(void) { printf("Content-Type: text/plain;charset=us-asciinn"); printf("Hello worldnn"); char* data = getenv("QUERY_STRING"); if (!data || *data == ' ') { printf("Error: No query string received.n"); return 1; } char name[50]; int port, id; char config[50]; // 关键改进:用 %49[^&] 替代 %s,明确以 '&' 为截断点;%49s 同样加宽度限制 int ret = sscanf(data, "Name=%49[^&]&Port=%d&ID=%d&Config=%49s", name, &port, &id, config); if (ret != 4) { printf("Parse error: expected 4 fields, got %dn", ret); return 1; } printf("Name: %sn", name); // 输出 "ss" printf("Port: %dn", port); // 输出 "8081" printf("ID: %dn", id); // 输出 "0" printf("Config: %sn", config); // 输出 "testconfig" return 0; }
? 关键要点总结:
- ❌ 避免裸用 %s 解析无空白分隔的 URL 参数;
- ✅ 优先使用 %[^&](或 %[^&=] 等)配合明确分隔符;
- ✅ 始终指定最大宽度(如 %49[^&]),杜绝缓冲区溢出风险;
- ✅ 检查 sscanf 返回值,确保所有预期字段成功解析(返回值应等于格式项数量);
- ⚠️ 注意:sscanf 不解码 URL 编码(如 + → 空格、%20 → 空格),生产环境需额外实现 url_decode() 函数。
此方案兼顾安全性、可读性与 CGI 场景的实用性,是解析简单查询字符串的经典实践。