【问题标题】:Detect excel .xlsx file mimetype via PHP通过 PHP 检测 excel .xlsx 文件 mimetype
【发布时间】:2011-09-01 17:23:04
【问题描述】:

我无法通过 PHP 检测 xlsx Excel 文件的 mimetype,因为它是 zip 存档。

文件工具

file file.xlsx
file.xlsx: Zip archive data, at least v2.0 to extract

PECL 文件信息

$finfo = finfo_open(FILEINFO_MIME_TYPE);
finfo_file($finfo, "file.xlsx");
application/zip

如何验证?解压并查看结构?但如果是弧形炸弹呢?

【问题讨论】:

  • 你必须解压文件。 PHP 不会为您查看文件内部。如果它是一个弧形炸弹,你只需要处理它。
  • 这是一个半副本:stackoverflow.com/questions/6595183/…
  • 好的,谢谢。我希望女士使用更多信息。 nautilus中的文件信息说它是“Microsoft Excel工作表(application/vnd.openxmlformats-officedocument.spreadsheetml.sheet)”,但它使用扩展列表..

标签: php excel mime-types xlsx fileinfo


【解决方案1】:

概述

PHP 使用 libmagic。当 Magic 检测到 MIME 类型为“application/zip”而不是“application/vnd.openxmlformats-officedocument.spreadsheetml.sheet”时,这是因为添加到 ZIP 存档的文件需要按特定顺序排列。

当将文件上传到强制匹配文件扩展名和 MIME 类型的服务时,这会导致问题。例如,基于 Mediawiki 的 wiki(使用 PHP 编写)阻止上传某些 XLSX 文件,因为它们被检测为 ZIP 文件。

您需要做的是通过重新排序写入 ZIP 存档的文件来修复您的 XLSX,以便 Magic 可以正确检测 MIME 类型。

分析文件

对于本示例,我们将分析使用 Openpyxl 和 Excel 创建的 XLSX 文件。

解压后可以查看文件列表:

$ unzip -l Openpyxl.xlsx
Archive:  Openpyxl.xlsx
  Length      Date    Time    Name
---------  ---------- -----   ----
      177  2019-12-21 04:34   docProps/app.xml
      452  2019-12-21 04:34   docProps/core.xml
    10140  2019-12-21 04:34   xl/theme/theme1.xml
    22445  2019-12-21 04:34   xl/worksheets/sheet1.xml
      586  2019-12-21 04:34   xl/tables/table1.xml
      238  2019-12-21 04:34   xl/worksheets/_rels/sheet1.xml.rels
      951  2019-12-21 04:34   xl/styles.xml
      534  2019-12-21 04:34   _rels/.rels
      552  2019-12-21 04:34   xl/workbook.xml
      507  2019-12-21 04:34   xl/_rels/workbook.xml.rels
     1112  2019-12-21 04:34   [Content_Types].xml
---------                     -------
    37694                     11 files

$ unzip -l Excel.xlsx
Archive:  Excel.xlsx
  Length      Date    Time    Name
---------  ---------- -----   ----
     1476  1980-01-01 00:00   [Content_Types].xml
      732  1980-01-01 00:00   _rels/.rels
      831  1980-01-01 00:00   xl/_rels/workbook.xml.rels
     1159  1980-01-01 00:00   xl/workbook.xml
      239  1980-01-01 00:00   xl/sharedStrings.xml
      293  1980-01-01 00:00   xl/worksheets/_rels/sheet1.xml.rels
     6796  1980-01-01 00:00   xl/theme/theme1.xml
     1540  1980-01-01 00:00   xl/styles.xml
     1119  1980-01-01 00:00   xl/worksheets/sheet1.xml
    39574  1980-01-01 00:00   docProps/thumbnail.wmf
      785  1980-01-01 00:00   docProps/app.xml
      169  1980-01-01 00:00   xl/calcChain.xml
      513  1980-01-01 00:00   xl/tables/table1.xml
      601  1980-01-01 00:00   docProps/core.xml
---------                     -------
    55827                     14 files

注意文件顺序不同。

可以使用 PHP 查看 MIME 类型:

<?php
echo mime_content_type('Openpyxl.xlsx') . "<br/>\n";
echo mime_content_type('Excel.xlsx');

或使用 python-magic:

pip install python-magic

在 Windows 上:

pip install python-magic-bin==0.4.14

‌代码:

import magic
mime = magic.Magic(mime=True)
print(mime.from_file("Openpyxl.xlsx"))
print(mime.from_file("Excel.xlsx"))

输出:

application/zip
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet

解决方案

@adrilo 已调查此问题并制定了解决方案。

嘿@garak,

拉了几个小时的头发后,我终于弄清楚了为什么 mime 类型是错误的。事实证明,将 XML 文件添加到最终 ZIP 文件(XLSX 文件是具有 xlsx 扩展名的 ZIP 文件)的顺序对于用于检测类型的启发式方法很重要。

目前,文件按以下顺序添加:

[Content_Types].xml
_rels/.rels
docProps/app.xml
docProps/core.xml
xl/_rels/workbook.xml.rels
xl/sharedStrings.xml
xl/styles.xml
xl/workbook.xml
xl/worksheets/sheet1.xml

问题来自插入“docProps”相关文件。似乎启发式方法是查看前几个字节并检查它是否找到Content_Types 和xl。通过在其间插入“docProps”文件,第一个 xl 出现必须发生在算法查看的第一个字节之外,因此得出结论它是一个简单的 zip 文件。

我会尽力解决这个问题

修复#149

启发式检测期望看到的 XLSX 文件的正确 mime 类型 XLSX 存档开头的某些文件。其中的顺序 因此添加了 XML 文件很重要。具体来说, 应首先添加“[Content_Types].xml”,然后是文件 位于“xl”文件夹中(至少 1 个文件)。

根据Spout'sFileSystemHelper.php:

为了正确检测文件的 mime 类型,文件需要 以特定顺序添加到 zip 文件中。 “[内容类型].xml” 则应首先压缩位于“xl”文件夹中的至少 2 个文件。

解决方案是依次添加文件“[Content_Types].xml”、“xl/workbook.xml”和“xl/styles.xml”,然后添加其余文件。

代码

此 Python 脚本将重写一个 XLSX 文件,该文件具有正确顺序的存档文件。

#!/usr/bin/env python

from io import BytesIO
from zipfile import ZipFile, ZIP_DEFLATED

XL_FOLDER_NAME = "xl"

CONTENT_TYPES_XML_FILE_NAME = "[Content_Types].xml"
WORKBOOK_XML_FILE_NAME = "workbook.xml"
STYLES_XML_FILE_NAME = "styles.xml"

FIRST_NAMES = [
    CONTENT_TYPES_XML_FILE_NAME,
    f"{XL_FOLDER_NAME}/{WORKBOOK_XML_FILE_NAME}",
    f"{XL_FOLDER_NAME}/{STYLES_XML_FILE_NAME}"
]


def fix_workbook_mime_type(file_path):
    buffer = BytesIO()

    with ZipFile(file_path) as zip_file:
        names = zip_file.namelist()
        print(names)

        remaining_names = [name for name in names if name not in FIRST_NAMES]
        ordered_names = FIRST_NAMES + remaining_names
        print(ordered_names)

        with ZipFile(buffer, "w", ZIP_DEFLATED, allowZip64=True) as buffer_zip_file:
            for name in ordered_names:
                try:
                    file = zip_file.open(name)
                    buffer_zip_file.writestr(file.name, file.read())
                except KeyError:
                    pass

    with open(file_path, "wb") as file:
        file.write(buffer.getvalue())


def main(*args):
    fix_workbook_mime_type("File.xlsx")


if __name__ == "__main__":
    main()

【讨论】:

  • 将[Content_Types].xml 作为第一个归档成员是 OpenXML 的要求,还是仅仅是 libmagic 和其他检测库的缺点?
【解决方案2】:

我知道这适用于 zip 文件,但我不太确定 xlsx 文件。值得一试:

列出 zip 存档中的文件:

$zip = new ZipArchive;
$res = $zip->open('test.zip');
if ($res === TRUE) {
    for ($i=0; $i<$zip->numFiles; $i++) {
        print_r($zip->statIndex($i));
    }
    $zip->close();
} else {
    echo 'failed, code:' . $res;
}

这将像这样打印所有文件:

Array
(
    [name] => file.png
    [index] => 2
    [crc] => -485783131
    [size] => 1486337
    [mtime] => 1311209860
    [comp_size] => 1484832
    [comp_method] => 8
)

正如您在此处看到的,它为每个存档提供了 size 和 comp_size。如果是档案炸弹,这两个数字的比例将是天文数字。您可以简单地限制您想要最大解压缩文件大小的兆字节,如果它超过该数量,则跳过该文件并将错误消息返回给用户,否则继续您的提取。请参阅the manual 了解更多信息。

【讨论】:

  • 谢谢,打开时不是将arch解压到temp中吗?
  • 没有。它只是读取文件。要提取,您将使用 ZipArchive::extractTo (php.net/manual/en/function.ziparchive-extractto.php)
  • 点击您希望接受的答案左侧的复选标记(下方有向上和向下箭头以及它们之间的数字)。
  • //噢,对不起,我没看谁用计数器写答案
【解决方案3】:

这是一个可以正确识别 Microsoft Office 2007 文档的包装器。使用、编辑和添加更多文件扩展名/mimetypes 简单直接。

function get_mimetype($filepath) {
    if(!preg_match('/\.[^\/\\\\]+$/',$filepath)) {
        return finfo_file(finfo_open(FILEINFO_MIME_TYPE), $filepath);
    }
    switch(strtolower(preg_replace('/^.*\./','',$filepath))) {
        // START MS Office 2007 Docs
        case 'docx':
            return 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
        case 'docm':
            return 'application/vnd.ms-word.document.macroEnabled.12';
        case 'dotx':
            return 'application/vnd.openxmlformats-officedocument.wordprocessingml.template';
        case 'dotm':
            return 'application/vnd.ms-word.template.macroEnabled.12';
        case 'xlsx':
            return 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
        case 'xlsm':
            return 'application/vnd.ms-excel.sheet.macroEnabled.12';
        case 'xltx':
            return 'application/vnd.openxmlformats-officedocument.spreadsheetml.template';
        case 'xltm':
            return 'application/vnd.ms-excel.template.macroEnabled.12';
        case 'xlsb':
            return 'application/vnd.ms-excel.sheet.binary.macroEnabled.12';
        case 'xlam':
            return 'application/vnd.ms-excel.addin.macroEnabled.12';
        case 'pptx':
            return 'application/vnd.openxmlformats-officedocument.presentationml.presentation';
        case 'pptm':
            return 'application/vnd.ms-powerpoint.presentation.macroEnabled.12';
        case 'ppsx':
            return 'application/vnd.openxmlformats-officedocument.presentationml.slideshow';
        case 'ppsm':
            return 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12';
        case 'potx':
            return 'application/vnd.openxmlformats-officedocument.presentationml.template';
        case 'potm':
            return 'application/vnd.ms-powerpoint.template.macroEnabled.12';
        case 'ppam':
            return 'application/vnd.ms-powerpoint.addin.macroEnabled.12';
        case 'sldx':
            return 'application/vnd.openxmlformats-officedocument.presentationml.slide';
        case 'sldm':
            return 'application/vnd.ms-powerpoint.slide.macroEnabled.12';
        case 'one':
            return 'application/msonenote';
        case 'onetoc2':
            return 'application/msonenote';
        case 'onetmp':
            return 'application/msonenote';
        case 'onepkg':
            return 'application/msonenote';
        case 'thmx':
            return 'application/vnd.ms-officetheme';
            //END MS Office 2007 Docs

    }
    return finfo_file(finfo_open(FILEINFO_MIME_TYPE), $filepath);
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-05-15
    • 1970-01-01
    • 2014-08-11
    • 1970-01-01
    • 1970-01-01
    • 2014-04-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多