Обзор
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