文件扩展名不可信,应通过文件头(magic bytes)识别真实类型;c++可手写检测常见格式,或调用libmagic库实现更可靠的mime推断,但需注意bom、容器格式及内存管理等细节。

为什么不能只看文件扩展名
文件扩展名完全可被任意修改,report.pdf 可能是 Excel 二进制流,image.jpg 可能是 ZIP 压缩包。操作系统和浏览器都依赖文件头(magic bytes)做真实类型识别——比如 PNG 固定以 \x89\x50\x4E\x47\x0D\x0A\x1A\x0A 开头,PDF 以 %PDF- 开头。C++ 标准库不提供 MIME 推断功能,必须手动比对或调用外部库。
手写 magic bytes 检查的最小可行方案
适用于常见格式(PNG/JPEG/GIF/PDF/TXT),无需第三方依赖,但需注意字节序、偏移和边界条件:
- 打开文件为
std::ifstream并设为std::ios::binary模式 - 读取前 16 字节(多数 magic 在前 4–12 字节内,PDF 需跳过可能的 BOM)
- 用
std::memcmp或字节逐位比对,避免字符串函数误判 \0 - JPEG 的 magic 是
\xFF\xD8\xFF,但某些变体在第 3 字节后有额外标记,建议至少检查前 3 字节 - GIF 必须匹配
"GIF87a"或"GIF89a",不能只写"GIF"
std::string detect_mime(const std::string& path) {
std::ifstream f(path, std::ios::binary);
if (!f.is_open()) return "application/octet-stream";
std::array<uint8_t> buf{};
f.read(reinterpret_cast<char>(buf.data()), buf.size());
size_t n = f.gcount();
<pre class="brush:php;toolbar:false;">if (n >= 3 && buf[0] == 0xFF && buf[1] == 0xD8 && buf[2] == 0xFF) return "image/jpeg";
if (n >= 8 && std::memcmp(buf.data(), "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A", 8) == 0) return "image/png";
if (n >= 6 && (std::memcmp(buf.data(), "GIF87a", 6) == 0 || std::memcmp(buf.data(), "GIF89a", 6) == 0)) return "image/gif";
if (n >= 5 && std::memcmp(buf.data(), "%PDF-", 5) == 0) return "application/pdf";
if (n >= 4 && std::memcmp(buf.data(), "\x7F\x45\x4C\x46", 4) == 0) return "application/x-executable"; // ELF
return "application/octet-stream";
}
用 libmagic(file 命令底层)更可靠但需链接
libmagic 是 Unix 系统 file 命令的 C 库,覆盖超 1000 种格式,支持压缩包内嵌类型、文本编码检测等。C++ 调用它需注意:
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
- Linux/macOS 默认已装,Windows 需手动编译或用 vcpkg:
vcpkg install libmagic - 初始化时必须调用
magic_open(MAGIC_MIME_TYPE),不是MAGIC_NONE—— 否则返回描述文本而非 MIME - 务必用
magic_load(magic, nullptr)加载默认数据库,否则返回空 - 检测完要
magic_close(magic),否则 fd 泄漏 - 返回值是堆上字符串,需
free()(不是delete)
#include <magic.h>
std::string detect_mime_libmagic(const std::string& path) {
magic_t magic = magic_open(MAGIC_MIME_TYPE);
if (!magic || magic_load(magic, nullptr) != 0) {
if (magic) magic_close(magic);
return "application/octet-stream";
}
const char* mime = magic_file(magic, path.c_str());
std::string result = mime ? mime : "application/octet-stream";
free(const_cast<void>(static_cast<const void>(mime))); // 注意:magic_file 返回 malloc 分配的内存
magic_close(magic);
return result;
}
</const></void></magic.h>
容易忽略的坑:文本文件、UTF-8 BOM 和容器格式
纯文本(如 JSON、XML、CSV)没有统一 magic,libmagic 会返回 text/plain,但实际需进一步检测编码或内容结构。而 ZIP、RAR、7z 这类容器格式的 magic 仅标识“这是 ZIP”,无法知道内部是 APK 还是 DOCX —— 它们本质都是 ZIP,MIME 应为 application/zip,而非 application/vnd.openxmlformats-officedocument.wordprocessingml.document。
另外,UTF-8 BOM(\xEF\xBB\xBF)出现在文件头时,会干扰 JPEG/PNG 等二进制格式判断。若业务场景需兼容带 BOM 的文本文件,应在读取后跳过 BOM 再比对;但对二进制文件,BOM 本身就是非法字节,直接按 application/octet-stream 处理更安全。
真实项目里,别只信一个 magic 表;PDF 可能伪装成 ZIP(开头是 PK\x03\x04 但后面含 %PDF-),这种得结合多级 signature 或专用解析器验证。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










