验证上载的文件是否为Python中的word文档

时间:2013-07-05 19:32:17

标签: python file-upload flask boto

在我的网络应用程序(Flask)中,我让用户上传word文档。

我检查文件的扩展名是.doc还是.docx。 但是,我将.jpg文件的扩展名更改为.docx,并且它也已经过了(正如我预期的那样)。

有没有办法验证上传的文件确实是word文档?我搜索并阅读了一些关于文件标题的内容,但找不到任何其他信息。

我使用boto将文件上传到aws,以防万一。 感谢。

7 个答案:

答案 0 :(得分:1)

Docx文件实际上是zip文件。此zip包含三个基本文件夹:worddocProps_rels。因此,使用zipfile来测试此文件中是否存在这些文件。

import zipfile

def isdir(z, name):
   return any(x.startswith("%s/" % name.rstrip("/")) for x in z.namelist())

def isValidDocx(filename):
  f = zipfile.ZipFile(filename, "r")
  return isdir(f, "word") and isdir(f, "docProps") and isdir(f, "_rels")

代码改编自Check if a directory exists in a zip file with Python

但是,包含这些文件夹的任何ZIP都将绕过验证。 我也不知道它是否适用于DOC或加密的DOCS。

答案 1 :(得分:1)

嗯,评论中链接的问题中的python-magic库看起来像是一个非常直接的解决方案。

尽管如此,我还是会提供更多手动选项。根据{{​​3}},DOC文件的签名为D0 CF 11 E0 A1 B1 1A E1(8个字节),而DOCX文件的签名为50 4B 03 04(4个字节)。两者都有0的偏移。可以安全地假设这些文件是小端的,因为它们来自微软(不过,可能Office文件在Mac上是Big Endian?我不确定)

您可以使用struct模块解压缩二进制数据,如下所示:

>>> with open("foo.doc", "rb") as h:
...    buf = h.read()
>>> byte = struct.unpack_from("<B", buf, 0)[0]
>>> print("{0:x}".format(byte))
d0

所以,这里我们从包含从文件读取的二进制数据的缓冲区中解压缩第一个小端(“&lt;”)字节(“B”),偏移量为0,我们发现“D0”, doc文件中的第一个字节。如果我们将偏移量设置为1,我们得到CF,即第二个字节。

让我们检查它是否确实是一个DOC文件:

def is_doc(file):
    with open(file, 'rb') as h:
        buf = h.read()
    fingerprint = []
    if len(buf) > 8:
        for i in range(8):
            byte = struct.unpack_from("<B", buf, i)[0]
            fingerprint.append("{0:x}".format(byte))
    if ' '.join(fingerprint).upper() == "D0 CF 11 E0 A1 B1 1A E1":        
        return True
    return False

>>> is_doc("foo.doc")
True

不幸的是我没有任何要测试的DOCX文件但过程应该是相同的,除了你只获得前4个字节并与其他指纹进行比较。

答案 2 :(得分:0)

如果安装外部库是一个选项,那么有一个Python的docx库。您可以尝试使用它:https://github.com/mikemaccana/python-docx

答案 3 :(得分:0)

我使用python-magic来验证文件类型是否是word文档。 但是我遇到了很多问题。如:不同的单词版本或不同的软件导致不同的类型。所以我放弃了python-magic

这是我的解决方案。

DOC_MAGIC_BYTES = [
    "D0 CF 11 E0 A1 B1 1A E1",
    "0D 44 4F 43",
    "CF 11 E0 A1 B1 1A E1 00",
    "DB A5 2D 00",
    "EC A5 C1 00"
]
DOCX_MAGIC_BYTES = [
    "50 4B 03 04"
]

def validate_is_word(content):
    magic_bytes = content[:8]
    fingerprint = []
    bytes_len = len(magic_bytes)
    if bytes_len >= 4:
        for i in xrange(bytes_len):
            byte = struct.unpack_from("<B", magic_bytes, i)[0]
            fingerprint.append("{:02x}".format(byte))
    if not fingerprint:
        return False
    if is_docx_file(fingerprint):
        return True
    if is_doc_file(fingerprint):
        return True
    return False


def is_doc_file(magic_bytes):
    four_bytes = " ".join(magic_bytes[:4]).upper()
    all_bytes = " ".join(magic_bytes).upper()
    return four_bytes in DOC_MAGIC_BYTES or all_bytes in DOC_MAGIC_BYTES


def is_docx_file(magic_bytes):
    type_ = " ".join(magic_bytes[:4]).upper()
    return type_ in DOCX_MAGIC_BYTES

你可以试试这个。

答案 4 :(得分:0)

您可以使用python-docx库

以下代码将引发值错误,该文件不是docx文件。

from docx import Document
try:
    Document("abc.docx")
except ValueError:
    print "Not a valid document type"

答案 5 :(得分:0)

我使用文件类型python lib检查并比较mime类型及其文档扩展名,因此我的用户不能仅仅通过更改其文件扩展名来愚弄我。

pip install filetype

然后

import filetype

kind = filetype.guess('path/to/file')
mime = kind.mime
ext = kind.extension

您可以检查他们的文档here

答案 6 :(得分:0)

python-magic 在检测 docxpptx 格式方面做得非常好。

这里有几个例子:

In [60]: magic.from_file("oz123.docx")
Out[60]: 'Microsoft Word 2007+'

In [61]: magic.from_file("oz123.docx", mime=True)
Out[61]: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'

In [62]: magic.from_file("presentation.pptx")
Out[62]: 'Microsoft PowerPoint 2007+'

In [63]: magic.from_file("presentation.pptx", mime=True)
Out[63]: 'application/vnd.openxmlformats-officedocument.presentationml.presentation'

由于 OP 询问文件上传,文件句柄不是很有用。幸运的是, magic 支持从缓冲区检测:

In [63]: fdox
Out[63]: <_io.BufferedReader name='/home/oz123/Documents/oz123.docx'>

In [64]: magic.from_buffer(fdox.read(2048))
Out[64]: 'Zip archive data, at least v2.0 to extract

天真地,我们读取的数量太小......读取更多字节可以解决问题:

In [65]: fdox.seek(0)
Out[65]: 0

In [66]: magic.from_buffer(fdox.read(4096))
Out[66]: 'Microsoft Word 2007+'

In [67]: fdox.seek(0)
Out[67]: 0

In [68]: magic.from_buffer(fdox.read(4096), mime=True)
Out[68]: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'