Бит кодирования языка заголовка zipfile устанавливается по-разному между Python2 и Python3 - PullRequest
0 голосов
/ 12 ноября 2018

Мне бы хотелось, чтобы этот код работал одинаково при запуске с Python 2 или Python 3

from zipfile import ZipFile, ZipInfo

with ZipFile("out.zip", 'w') as zf:
    content = "content"
    info = ZipInfo()
    info.filename = "file.txt"
    info.flag_bits = 0x800
    info.file_size = len(content)
    zf.writestr(info, content)

Однако в Python 2 out.zip запускается:

50 4b 03 04 14 00 00 08

Под Python3 он запускается:

50 4b 03 04 14 00 00 00

Отличительной частью является flag_bits, для Python 2 установлено значение 0x800, для Python 3 0x00. Это BIT11: кодировка языка. BIT11, кажется, получает набор if filename.encode("ascii") бросков.

Я попытался включить этот бит, установив флаг после создания объекта ZipInfo, но он возвращается к 0x00 в _open_to_write().

Интересно, есть ли у кого-нибудь здесь хорошее решение? В идеале я хотел бы, чтобы оба выхода имели установленный флаг, потому что это отражает то, что делает утилита jar.

РЕДАКТИРОВАТЬ: Обновлен, чтобы добавить строку info.flag_bits = 0x800 просто, чтобы изложить, что я пытаюсь достичь. Я воспроизвел это на Windows: ActivePython 3.6.0.3600, против ActivePython 2.7.14.2717, Windows 10. И в Linux: Python 3.6.6 против Python 2.7.11 В случае, если это имеет значение, я запускаю это в точности как мой пример, без хэш-бенга, напрямую вызывая интерпретатор:

pythonX test.py

Ответы [ 2 ]

0 голосов
/ 12 ноября 2018

Я сейчас использую что-то вроде этого:

from zipfile import ZipFile, ZipInfo
import struct

orig_function = ZipInfo.FileHeader

def new_function(self, zip64=None):
    header = orig_function(self, zip64)
    fmt = "B"*len(header)
    blist = list(struct.unpack(fmt, header))
    blist[7] |= 0x8
    return struct.pack(fmt, *blist)

setattr(ZipInfo, "FileHeader", new_function)

with ZipFile("out.zip", 'w') as zf:
    content = "content"
    info = ZipInfo()
    info.filename = "file.txt"
    info.file_size = len(content)
    zf.writestr(info, content)

Надеюсь, он не сломается слишком скоро, FileHeader () выглядит как нечто, что не изменится в будущем.

0 голосов
/ 12 ноября 2018

Редактировать: Вот код, который работает для меня с Python 2.7, но не с 3.6 (немного загадкой, казалось, сработал ранее сегодня вечером):

$ cat zipf.py
from __future__ import print_function

from zipfile import ZipFile, ZipInfo

with ZipFile("out.zip", 'w') as zf:
    content = "content"
    info = ZipInfo()
    info.filename = "file.txt"
    info.flag_bits = 0x800
    # don't set info.file_size here: zf.writestr() does that
    zf.writestr(info, content)

with open('out.zip', 'rb') as stream:
    byteseq = stream.read(8)
    for i in byteseq:
        if isinstance(i, str): i = ord(i)
        print('{:02x}'.format(i), end=' ')
    print()

Запускать как:

$ python2.7 zipf.py
50 4b 03 04 14 00 00 08 

но:

$ python3.6 zipf.py
50 4b 03 04 14 00 00 00 

Конечно, можно заставить работать, убедившись, что файл открыт перед созданием записи info. Однако тогда вы должны избегать writestr, и это работает только с Python 3.6 (и выглядит довольно оскорбительно):

from __future__ import print_function

from zipfile import ZipFile, ZipInfo

with ZipFile("out.zip", 'w') as zf:
    info = ZipInfo()
    info.filename = "file.txt"
    content = "content"
    if not isinstance(content, bytes):
        content = content.encode('utf8')
    info.file_size = len(content)
    with zf.open(info, 'w') as stream:
        info.flag_bits = 0x800
        stream.write(content)

with open('out.zip', 'rb') as stream:
    byteseq = stream.read(8)
    for i in byteseq:
        if isinstance(i, str): i = ord(i)
        print('{:02x}'.format(i), end=' ')
    print()

Вероятно, дело в том, что 3,6 сброс всех info.flag_bits (через внутренний open, что он делает) просто неверен, хотя мне это не совсем понятно.

Оригинальный ответ ниже

Я не могу воспроизвести это, но вы правы, что бит 11 в битах флага установлен, если имя файла Unicode и кодировка ASCII завершается неудачно:

def _encodeFilenameFlags(self):
    if isinstance(self.filename, unicode):
        try:
            return self.filename.encode('ascii'), self.flag_bits
        except UnicodeEncodeError:
            return self.filename.encode('utf-8'), self.flag_bits | 0x800
    else:
        return self.filename, self.flag_bits

(источник Python 2.7 zipfile.py) или:

def _encodeFilenameFlags(self):
    try:
        return self.filename.encode('ascii'), self.flag_bits
    except UnicodeEncodeError:
        return self.filename.encode('utf-8'), self.flag_bits | 0x800

(источник Python 3.6 zipfile.py).

Чтобы установить бит, вам нужно имя файла, которое не может быть закодировано напрямую в ASCII, например ::

info.filename = u"sch\N{latin small letter o with diaeresis}n" # "file.txt"

(эта нотация работает как с Python 2.7, так и с 3.6).

Я попытался включить этот бит, установив флаг после создания объекта ZipInfo, но он возвращается к 0x00 в _open_to_write ().

Если я добавлю:

info.filename = "file.txt"
info.flag_bits |= 0x0800

(сразу после установки имени файла u"schön") и запуска его в Python 2.7 или 3.6, я получаю бит, установленный в заголовке (конечно, имя файла в zip-каталоге меняется на file.txt).

...