首页 > 解决方案 > zipfile 标头语言编码位在 Python2 和 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_bits0x800对于 Python 2,0x00对于 Python 3 设置为 。 那是 BIT11:语言编码。BIT11 似乎得到了定if filename.encode("ascii")投。

我试图通过在创建 ZipInfo 对象后设置标志来强制启用此位,但它被重置回0x00in _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 以防万一,我完全按照我的示例运行它,不hashbang,直接调用解释器:

pythonX test.py

标签: pythonpython-2.7zipfilepython-3.7

解决方案


编辑:这是适用于我的 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)。


推荐阅读