如何在不使用魔数的情况下判断一个文件是SVG格式?

14
一个 SVG 文件基本上是一个 XML 文件,所以我可以使用字符串 <?xml(或十六进制表示:'3c 3f 78 6d 6c')作为魔数,但如果有额外的空格,这可能会破坏此检查,因此有一些反对理由不这样做。
我需要/期望检查的其他图像都是二进制文件并且具有魔数。如何快速检查文件是否为 SVG 格式而不使用扩展名,最终使用 Python?

读取文件开头的二进制内容如何?如果找不到魔数,就将其读为文本并尝试匹配已知的文本模式。 - dmg
@DJV 听起来很合理。我也不知道它怎么可能不会出问题。 - Eduard Florinescu
3个回答

16

XML不必以<?xml前缀开头,因此测试该前缀并不是一种好的检测技术——更不用说这会将每个XML都识别为SVG。一个不错的检测方法,而且非常容易实现,是使用真正的XML解析器来测试文件是否包含svg顶级元素,并且格式正确。

import xml.etree.cElementTree as et

def is_svg(filename):
    tag = None
    with open(filename, "r") as f:
        try:
            for event, el in et.iterparse(f, ('start',)):
                tag = el.tag
                break
        except et.ParseError:
            pass
    return tag == '{http://www.w3.org/2000/svg}svg'

使用 cElementTree 可确保通过expat的使用检测效率高; timeit 显示SVG文件被检测为此类文件需要大约200μs,而非SVG文件只需35μs。 iterparse API使解析器能够避免创建整个元素树(模块名称不论)并仅读取文档的初始部分,而与总文件大小无关。

2
通过阅读问题,二进制魔数和XML混合触发了红色警报。这个答案明确指出解析二进制格式需要一种方法,而读取(基于文本的)XML则需要完全不同的方法。 - heltonbiker
2
@heltonbiker 没错。魔数确实有一个优点:原始性能。这就是为什么答案包括一个代码示例,演示了所提出的方法的高效实现。 - user4815162342
另外,如果我理解正确,二进制文件本质上是无结构的,例如纯文本文件。在纯文本中,我们应该包含shebangs、doctype等等,而二进制需要那些简洁、神秘的幻数。我相信,在某种意义上,这个幻数就像是最小可能大小、低级别、存储数据到文件的“旧方式”,而XML和JSON等更现代化、易读性更强、臃肿多余地存储数据到文件的方式。因此,这两种方法在不止一个方面有所不同。 - heltonbiker
文档中得知:"自版本3.3起,该模块将在可用时使用快速实现。xml.etree.cElementTree模块已被弃用。" - djvg
我喜欢这个,但要注意:XML漏洞页面提到了对billion laughs和类似攻击的漏洞。测试验证了et.iterparse()确实会崩溃。文档建议使用defusedxml - djvg
对于那些想了解期望的 tag 值的语法的人:带有 svg namespace 的 xml 标记看起来像 <svg xmlns="http://www.w3.org/2000/svg">,而 xml 模块会将其扩展为 {<namespace uri>}<tag name>,如文档所述,因此变成 '{http://www.w3.org/2000/svg}svg' - djvg

2

您可以尝试将文件的开头读取为二进制格式——如果找不到任何幻数,就将其读取为文本文件,并匹配任何您想要的文本模式。或者反过来。


1
这段内容来自于Unix命令file的手册(此处):

魔法测试用于检查具有特定固定格式数据的文件。 其中最典型的例子是二进制可执行文件...这些文件在文件开头附近的特定位置存储了一个“魔数”,告诉UNIX操作系统该文件是二进制可执行文件,以及其中的几种类型。“魔法”的概念已经扩展到数据文件上。 任何具有不变标识符的文件通常可以用这种方式描述。...

(我强调)

下面是file命令用于识别svg文件的一个“魔法”示例(详见源码):

...
0       string        \<?xml\ version=
>14     regex         ['"\ \t]*[0-9.]+['"\ \t]*
>>19    search/4096   \<svg         SVG Scalable Vector Graphics image
...
0       string        \<svg         SVG Scalable Vector Graphics image
...

根据man magic所述,每行都遵循以下格式<offset> <type> <test> <message>
如果我理解正确,上面的代码查找文字"<?xml version="。如果找到,它会查找版本号,如正则表达式所描述的。如果找到,它会搜索接下来的4096个字节,直到找到文字"<svg"。如果任何一项失败,它会在文件开头查找文字"<svg",以此类推。
Python中也可以实现类似的功能。
请注意还有python-magic,它提供了一个接口到libmagic,就像unix的file命令一样。

一个 XML 文件可能以 BOM(字节顺序标记)开头。由于这段代码似乎从第零个字节开始读取 <?xml,如果 XML 文件包含 BOM,则会失败。https://en.wikipedia.org/wiki/Byte_order_mark - mortb
@mortb 上面的代码只是 Linux file 命令源代码中众多模式之一的代表。源代码链接 - djvg
我理解。我认为你使用现成的解决方案很棒。我只是提出这个意见,以防某人由于SVG文件开头有BOM而遇到边界情况错误。 - mortb

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接