使用 sscanf 安全解析 URL 编码查询字符串的正确方法

2次阅读

使用 sscanf 安全解析 URL 编码查询字符串的正确方法

本文详解如何用 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 是贪婪匹配——它不识别 & 为分隔符,而是持续读取直到遇到空白字符或字符串结尾 。因此 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-asciinn");     printf("Hello worldnn");      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 %dn", result);         return 1;     }      printf("Name: %sn", name);     // 输出: ss     printf("Port: %dn", port);     // 输出: 8081     printf("ID: %dn", id);         // 输出: 0     printf("Config: %sn", config); // 输出: testconfig      return 0; }

⚠️ 重要注意事项:

  • 永远指定宽度:%49[^&] 中的 49 确保最多写入 49 字节,为末尾 留出空间;忽略宽度会导致溢出(name[50] 实际仅容 49 字符 + 1 终止符)。
  • 避免 &name 错误:name 是数组名,自动衰减为指针,sscanf 第二参数应传 name(而非 &name),否则类型不匹配可能引发未定义行为。
  • 增强健壮性:添加 sscanf 返回值检查(应等于成功匹配的字段数),并验证 QUERY_STRING 是否为空。
  • 进阶建议:生产环境推荐使用 std::string + std::Regex 或专用 URL 解析库(如 cpr、cpp-httplib),避免手动解析编码、空格、+ 及 %XX 转义等问题。

通过精准控制扫描边界与严格缓冲区约束,即可让 sscanf 成为 CGI 参数解析中简洁可靠的工具。

text=ZqhQzanResources