PDF文件的魔数是%PDF-,位于文件开头偏移0处,长度为5字节,是ISO 32000规范强制要求的标识符,读取前5字节比对即可可靠判断。

PDF文件的魔数(Magic Number)是什么
PDF 文件开头固定是 %PDF-,这是最可靠的识别依据。标准 PDF 规范(ISO 32000)要求文件前 4–5 字节必须为 %PDF-(含连字符),后面紧跟版本号,如 %PDF-1.7。这个字符串出现在文件开头(偏移 0 处),无需解析全文,读取前 10 字节就足够判断。
注意:不能只检查 %PDF 而漏掉连字符 —— 某些文本文件可能偶然包含该子串;也不能依赖后续版本号是否存在,因为极少数老旧或非标 PDF 可能省略版本(但 %PDF- 必须存在)。
用 C++ 读取文件头并比对魔数的正确写法
核心是打开文件、读取前若干字节、做字节级比较。关键点在于:用二进制模式打开,避免换行符转换干扰;用 std::ifstream::read 而非 >> 或 getline;缓冲区大小至少为 6 字节("%PDF-" 长度为 5,加 1 安全边界)。
- 用
std::ios::binary打开文件,否则 Windows 下可能把\r\n当作单个\n导致偏移错乱 - 用
file.read(buffer, 5)读 5 字节后检查gcount()是否等于 5,防止文件过短(比如空文件或只有 2 字节) - 直接用
memcmp或std::string_view(buffer, 5) == "%PDF-"比较,不要转成std::string再==,避免隐式 null 截断风险
bool isPdf(const std::string& path) {
std::ifstream file(path, std::ios::binary);
if (!file) return false;
char buf[6] = {0};
file.read(buf, 5);
if (file.gcount() != 5) return false;
return std::memcmp(buf, "%PDF-", 5) == 0;
}
为什么不能只靠扩展名或 MIME 类型
扩展名(如 .pdf)完全不可信 —— 用户可任意重命名;系统级 MIME 探测(如 Linux 的 file 命令)底层其实也是读魔数,但会额外检查后续结构(如 %%EOF、对象交叉引用表),属于过度验证。对于“是否 PDF”这一布尔判断,魔数已足够:PDF/A、PDF/X、加密 PDF、甚至被截断的 PDF(只要开头没丢),只要符合规范,都以 %PDF- 起始。
反例:某些所谓“PDF 生成器”输出的是 PostScript(以 %! 开头)或 HTML(以 /code> 或 <code> 开头),扩展名骗不了魔数校验。
容易忽略的边界情况和坑
实际部署时,以下几点常导致误判:
- 文件有 BOM(如 UTF-8 BOM
\xEF\xBB\xBF):PDF 规范禁止在开头插入 BOM,但某些编辑器保存时强行加了。此时真实 PDF 内容从第 4 字节开始,%PDF-出现在 offset 4 而非 0 —— 这类文件**不符合 PDF 标准**,不应视为合法 PDF - 文件是 PDF 的 ZIP 封装(如 .pdf.zip 重命名为 .pdf):魔数是
\x50\x4B\x03\x04(PK),不是%PDF-,应直接拒绝 - 内存映射或流式场景:若输入是
std::istream&(而非路径),需确保流位置在开头(调用seekg(0)),且流支持随机访问(std::stringstream支持,网络响应流通常不支持)
真正需要鲁棒性时,不是放宽魔数检查,而是先确认文件是否被篡改或封装 —— PDF 就是 PDF,不兼容的变体不该被“宽容识别”。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











