
本文详解如何用 sscanf 正确解析 CGI 查询字符串(如 Name=ss&Port=8081),解决 %s 贪婪匹配导致越界读取的问题,推荐使用 %[^&] 限定符并强制指定缓冲区宽度,兼顾安全性与准确性。
本文详解如何用 sscanf 正确解析 cgi 查询字符串(如 `name=ss&port=8081`),解决 `%s` 贪婪匹配导致越界读取的问题,推荐使用 `%[^&]` 限定符并强制指定缓冲区宽度,兼顾安全性与准确性。
在 Web CGI 开发中,常需通过 getenv("QUERY_STRING") 获取表单提交的 URL 查询字符串(例如 "Name=ss&Port=8081&ID=0&Config=testconfig"),再用 sscanf() 提取各字段。但若直接使用 %s 格式符(如 "Name=%s&Port=%d..."),会遭遇典型陷阱:%s 是贪婪匹配——它不识别 & 为分隔符,而是持续读取直到遇到空白字符或字符串结尾 \0。因此 name 缓冲区会意外捕获 "ss&Port=8081&ID=0&Config=testconfig" 整段内容,完全破坏解析逻辑。
根本原因在于:%s 仅以空白符(空格、制表符、换行等)为终止条件,而查询字符串中字段间由 & 分隔,且无空白符。此时应改用 扫描集格式符 %[...]:
- %[^&] 表示“匹配任意非 & 字符”,即明确以 & 为截止边界;
- 配合宽度限制(如 %49[^&])可防止缓冲区溢出,这是 C 语言字符串解析的强制安全实践。
修正后的代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void) {
printf("Content-Type: text/plain;charset=us-ascii\n\n");
printf("Hello world\n\n");
char* data = getenv("QUERY_STRING");
if (!data || strlen(data) == 0) {
printf("Error: No query string received.\n");
return 1;
}
char name[50];
int port, id;
char config[50];
// ✅ 关键修复:使用 %[^&] 限定边界 + 显式宽度防护
int result = sscanf(data, "Name=%49[^&]&Port=%d&ID=%d&Config=%49s",
name, &port, &id, config);
if (result != 4) {
printf("Parse error: expected 4 fields, got %d\n", result);
return 1;
}
printf("Name: %s\n", name); // 输出: ss
printf("Port: %d\n", port); // 输出: 8081
printf("ID: %d\n", id); // 输出: 0
printf("Config: %s\n", config); // 输出: testconfig
return 0;
}⚠️ 重要注意事项:
- 永远指定宽度:%49[^&] 中的 49 确保最多写入 49 字节,为末尾 \0 留出空间;忽略宽度会导致栈溢出(name[50] 实际仅容 49 字符 + 1 终止符)。
- 避免 &name 错误:name 是数组名,自动衰减为指针,sscanf 第二参数应传 name(而非 &name),否则类型不匹配可能引发未定义行为。
- 增强健壮性:添加 sscanf 返回值检查(应等于成功匹配的字段数),并验证 QUERY_STRING 是否为空。
- 进阶建议:生产环境推荐使用 std::string + std::regex 或专用 URL 解析库(如 cpr、cpp-httplib),避免手动解析编码、空格、+ 及 %XX 转义等问题。
通过精准控制扫描边界与严格缓冲区约束,即可让 sscanf 成为 CGI 参数解析中简洁可靠的工具。










