zipfile标头语言编码位在Python2和Python3之间的设置不同

时间:2018-11-12 00:29:46

标签: python python-2.7 zipfile python-3.7

我希望此代码在与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,vs ActivePython 2.7.14.2717,Windows 10。 在Linux上: Python 3.6.6和Python 2.7.11 如果很重要,我将按照我的示例运行此操作,不进行哈希爆炸,直接调用解释器:

pythonX test.py

2 个答案:

答案 0 :(得分:1)

编辑:以下代码对我适用于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可能是不正确的情况,尽管我不太清楚。

下面的原始答案

我无法重现此错误,但您是对的,如果文件名是Unicode并且编码为ASCII失败,那么标志位的第11位将被设置:

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对象后,我试图通过设置标志来强制启用此位,但是在_open_to_write()中将其重置为0x00。

如果我添加:

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

(将文件名设置为u"schön"之后)并在Python 2.7或3.6下运行,我得到了标头中设置的位(当然zip目录中的文件名改回了{{1}) }。

答案 1 :(得分:0)

我暂时正在使用类似的东西:

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()似乎将来不会改变。