
本文详解如何用 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 默认以空白符(空格、制表符、换行)或 \0 为终止边界,而查询字符串中不含这些字符,& 对它完全“不可见”。
例如原代码:
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 个 \0)。最终安全写法如下:
#include <stdio.h>
#include <stdlib.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 || *data == '\0') {
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 %d\n", ret);
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;
}? 关键要点总结:
- ❌ 避免裸用 %s 解析无空白分隔的 URL 参数;
- ✅ 优先使用 %[^&](或 %[^&=] 等)配合明确分隔符;
- ✅ 始终指定最大宽度(如 %49[^&]),杜绝缓冲区溢出风险;
- ✅ 检查 sscanf 返回值,确保所有预期字段成功解析(返回值应等于格式项数量);
- ⚠️ 注意:sscanf 不解码 URL 编码(如 + → 空格、%20 → 空格),生产环境需额外实现 url_decode() 函数。
此方案兼顾安全性、可读性与 CGI 场景的实用性,是解析简单查询字符串的经典实践。









