繁体   English   中英

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

[英]zipfile header language encoding bit set differently between Python2 and Python3

我想在使用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:语言编码。 if filename.encode("ascii")抛出,BIT11似乎被设置。

我试图通过在创建ZipInfo对象后设置标志来强制启用此位,但它会在_open_to_write()重置为0x00

我想知道这里有没有人有一个好的解决方案。 理想情况下,我希望两个输出设置标志,因为这反映了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如果重要的话,我正在运行这个我的例子,没有hashbang,直接调用解释器:

pythonX test.py

编辑:这里的代码适用于我的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目录中的文件名更改回file.txt )。

我暂时使用这样的东西:

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM