Обнаружение файла excel .xlsx mimetype через PHP

Я не могу определить mimetype для файла xlsx Excel через PHP, потому что это 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

Как это проверить? Распаковать и посмотреть структуру? А если это дуговая бомба?


person Shara    schedule 01.09.2011    source источник
comment
Вам придется распаковать файл. PHP не будет заглядывать внутрь файла за вас. если это дуговая бомба, вам просто придется иметь с ней дело.   -  person Marc B    schedule 01.09.2011
comment
вот полудубликат: stackoverflow.com/questions/6595183/   -  person Daniel A. White    schedule 01.09.2011
comment
Хорошо спасибо. Я надеялся, что госпожа использует дополнительную информацию. И информация о файле в nautilus говорит, что это рабочий лист Microsoft Excel (application/vnd.openxmlformats-officedocument.spreadsheetml.sheet), но он использует список расширений.   -  person Shara    schedule 01.09.2011


Ответы (3)


Я знаю, что это работает для 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 для каждого архива. Если это архивная бомба, то соотношение между этими двумя числами будет астрономическим. Вы можете просто установить ограничение в несколько мегабайт на максимальный размер распакованного файла, и если он превышает эту сумму, пропустите этот файл и верните пользователю сообщение об ошибке, иначе продолжите извлечение. Дополнительную информацию см. в руководстве.

person Mike    schedule 01.09.2011
comment
Спасибо, а разве арк не распаковывается во временную при открытии? - person Shara; 02.09.2011
comment
Нет. Он просто читает файл. Для извлечения вы должны использовать ZipArchive::extractTo (php.net/manual /en/function.ziparchive-extractto.php) - person Mike; 02.09.2011
comment
Нажмите на галочку слева от ответа, который вы хотите принять (под ним есть стрелки вверх и вниз и число между ними). - person Mike; 05.09.2011
comment
//Ой, извините, я не посмотрел, кто написал ответ со счетчиком - person Shara; 07.09.2011

Обзор

PHP использует libmagic. Когда Magic определяет тип MIME как «application/zip» вместо «application/vnd.openxmlformats-officedocument.spreadsheetml.sheet», это связано с тем, что файлы, добавляемые в ZIP-архив, должны быть в определенном порядке.

Это вызывает проблему при загрузке файлов в службы, которые обеспечивают соответствие расширения файла и типа MIME. Например, вики на основе Mediawiki (написанные с использованием PHP) блокируют загрузку определенных файлов XLSX, поскольку они определяются как файлы ZIP.

Что вам нужно сделать, так это исправить ваш XLSX, изменив порядок файлов, записанных в ZIP-архив, чтобы Magic мог правильно определить тип MIME.

Анализ файлов

В этом примере мы проанализируем файл XLSX, созданный с помощью Openpyxl и Excel.

Список файлов можно просмотреть с помощью распаковки:

$ 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

Обратите внимание, что порядок файлов другой.

Типы MIME можно просмотреть с помощью PHP:

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

или используя магию python:

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,

Выдергивая волосы в течение нескольких часов, я наконец понял, почему тип пантомимы неправильный. Оказывается, порядок, в котором XML-файлы добавляются в окончательный ZIP-файл (файл XLSX является ZIP-файлом с расширением xlsx), имеет значение для эвристики, используемой для определения типов.

В настоящее время файлы добавляются в таком порядке:

[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

Эвристика для определения правильного типа MIME для файлов XLSX предполагает наличие определенных файлов в начале архива XLSX. Поэтому порядок, в котором добавляются XML-файлы, имеет значение. В частности, сначала следует добавить «[Content_Types].xml», а затем файлы, расположенные в папке «xl» (не менее 1 файла).

Согласно Spout FileSystemHelper.php:

Чтобы правильно определить MIME-тип файла, файлы необходимо добавлять в zip-файл в определенном порядке. «[Content_Types].xml», то сначала следует заархивировать как минимум 2 файла, расположенных в папке «xl».

Решение состоит в том, чтобы добавить файлы «[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()
person XP1    schedule 21.12.2019
comment
Является ли наличие [Content_Types].xml первым членом архива требованием OpenXML, или это только недостаток libmagic и других библиотек обнаружения? - person pts; 27.03.2020

Вот оболочка, которая правильно идентифицирует документы Microsoft Office 2007. Это тривиально и просто в использовании, редактировании и добавлении дополнительных расширений файлов/MIME-типов.

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);
}
person EpokK    schedule 10.02.2014